#01 problem#02 primitives #03 mapping#04 insight #05 QuiltAgent#06 playground #07 surfaces#08 roadmap

Quilt as Agent Substrate

The registry, the resolver, the substrate. Executable artifacts that any agent can compose — pinned, validated, templated, traced.

5 primitivespinned URIs JSON Schemaimmutable traces @quilt/mcp
View Schema ↓ Read SDK ↓ Open on GitHub →

01The problem: today's agents are stateless

Every LLM call today is a one-shot. You send a prompt, you get a response, the context evaporates. There is no memory of what was tried, no provenance for what was produced, no way to compose the output with anyone else's work, no way to roll back to a known-good state, and no way to share a result with the next agent without re-deriving it from scratch.

It is the worst of both worlds: as fragile as a shell pipeline, and as expensive as a kernel boot. We have all the machinery of distributed systems — content addressing, capability negotiation, deterministic replay — and none of it is in the agent stack.

Today

Stateless LLM calls

  • One-shot, no memory between calls
  • No composition between agents
  • No provenance ("who produced this?")
  • No rollback ("yesterday's run worked")
  • Re-deriving the same answer costs the same every time
  • Tools hand-coded per agent, not reusable
With the substrate

Quilt as agent substrate

  • Artifacts pinned by content hash — never drift
  • Manifests validated before any code runs
  • Run traces are immutable audit logs
  • {{template}} substitution built into URIs
  • Any agent can call any artifact — registry is shared
  • Rollback is one engine.serialize() away

The spec we're implementing is small — five primitives — but it is the same shape as the rest of the internet works (URIs, content addressing, signed manifests, immutable logs). We are not inventing a new model. We are catching up.

02The five primitives

The spec defines five operations. Every other feature — caches, retries, replay, lineage, capability routing — is a composition of these. They are deliberately minimal so that any agent (Claude, GPT, a custom LLM, a shell script) can implement them.

resolveArtifact(uri, ctx)

resolveArtifact(uri: string, ctx: Vars) => Promise<Artifact>

Given a canonical URI, return the pinned, content-addressed artifact. Templates like quilt://demo/dataset:{{run_id}} are filled from ctx before lookup. The result includes a SHA-256 of the bytes; downstream code can verify the result has not been tampered with.

// spec
const a = await resolveArtifact("quilt://demo/dataset:abc123", { run_id: "abc123" });
// → { uri, hash: "sha256:9f86d0...", bytes, kind }

// Quilt — every cell IS a content-addressed artifact
import { Engine } from '@quilt/core';
const engine = await Engine.load();
const cell   = engine.cell(`/demo/dataset:abc123`);  // pinned
await engine.flush();                            // resolve & cache
@quilt/corecontent-addressedidempotent

validateManifest(manifest)

validateManifest(m: Manifest) => { ok: boolean, errors: Error[] }

Check that a manifest is well-formed before any of its cells run. Two layers: structural (JSON Schema: required fields, types, enums) and semantic (preconditions: input URIs resolve, model names are known, conditions are satisfiable). If either fails, the run is rejected with a structured error — not a stack trace.

// spec
const r = validateManifest({ name: "classifier-dag", version: "1.0.0", cells: [...],
  preconditions: ["model_requirements.provider == 'openai'"] });
// → { ok: true }  or  { ok: false, errors: [{ path, msg }] }

// Quilt — ajv for schema, sensor + validate listeners for preconditions
import { validate } from '@quilt/sdk';
const r = validate(manifest);
if (!r.ok) throw new ManifestError(r.errors);
@quilt/sdkJSON Schema (ajv)precondition check

publishArtifact(path, metadata)

publishArtifact(path: string, meta: Metadata) => Promise<uri>

Take a file or directory, hash it, attach metadata, write it to the registry, return the canonical URI. The URI is content-addressed: quilt://<namespace>/<name>:<sha256-prefix>. Same bytes, same URI, always. The metadata is signed by the publisher's key — agents can verify provenance without trusting the registry.

// spec
const uri = await publishArtifact("./dist/classifier-dag.yaml",
  { name: "classifier-dag", version: "1.0.0", author: "team@quilt" });
// → "quilt://team/classifier-dag:a3f1b9c"  (sha256-prefix)

// Quilt — content-addressed storage, signed metadata
import { publish } from '@quilt/sdk';
const uri = await publish({ path: './dist/classifier-dag.yaml', registry: 'quilt://team' });
@quilt/sdkcontent-addressedsignedCAS-backed

publishRunTrace(trace)

publishRunTrace(trace: RunTrace) => Promise<trace_uri>

Append a run trace to the immutable audit log. A trace is the complete record: which artifact ran, which inputs it used, the order and result of every cell evaluation, wall time, tokens consumed, model versions, hashes of the manifest and output. Once appended, the trace cannot be edited — only new traces can be appended.

// spec
const uri = await publishRunTrace({
  manifest_uri: "quilt://team/classifier-dag:a3f1b9c",
  run_id: "01HXY...", started: "2026-01-15T14:22:01Z",
  steps: [{ cell: "load", ok: true, ms: 128 },
          { cell: "embed", ok: true, ms: 2410 }] });

// Quilt — engine.serialize() IS the trace (reactive history)
const trace = engine.serialize();   // { nodes, edges, evals, timing, hashes }
const uri   = await publish({ path: trace, registry: 'quilt://traces' });
@quilt/coreappend-onlyhash-chained

resolveTemplate(uri, ctx)

resolveTemplate(uri: string, ctx: Vars) => string // pure, no I/O

Substitute {{variables}} in a URI before resolution. This is the part that makes a registry composable rather than static: an artifact can declare quilt://team/classifier-dag:{{tag}} and the caller fills in { tag: 'a3f1b9c' } at run time. Pure function, no cache to invalidate — and the same shape works in cells (their derived_from can be templated too).

// spec
resolveTemplate("quilt://demo/dataset:{{run_id}}", { run_id: "01HXY" });
// → "quilt://demo/dataset:01HXY"

resolveTemplate("quilt://{{org}}/{{name}}:{{tag}}",
  { org: "team", name: "classifier", tag: "v1" });
// → "quilt://team/classifier:v1"

// Quilt — 4 lines, used by resolveArtifact() before lookup
export function resolveTemplate(uri, ctx) {
  return uri.replace(/\{\{(\w+)\}\}/g, (_, k) => {
    if (!(k in ctx)) throw new Error(`missing var: ${k}`);
    return ctx[k]; });
}
@quilt/sdkpure functionno I/O

03How Quilt already implements this

The mapping is not 1:1 — Quilt has its own vocabulary, and the right way to read this table is "the existing concept is the realization of the spec concept". In every case, the Quilt equivalent was either already the right primitive or was a small, principled generalization of one.

Spec conceptQuilt equivalentHow it works
Manifest (declarative primitive)Quilt sheet (YAML)A sheet is a list of named cells with kinds, formulas, and edges. The manifest schema is a strict subset of the sheet schema.
Container entrypointCell evaluate()No Docker needed. A cell is a function — call it, get a value. Process isolation is one engine, not one container.
Inputs / outputsderived_from + reactive propagationA cell declares which other cells it reads. The engine recomputes the closure when an input changes — no explicit "get input" call.
Pre / postconditionskind: 'sensor' + validate listenerA sensor cell checks a precondition; a validate listener runs a function on the value before it propagates. Both fire before any consumer sees the value.
Rollback@quilt/evolve revert stepThe engine holds a reactive history. Revert is engine.load(snapshot) — one call, deterministic, no manual checkpointing.
model_requirementskind: 'ai' + ai_kind + provider configAn ai cell declares its model, provider, and parameters. The engine routes the call to the right API key and tracks tokens.
Run traceengine.serialize() + reactive historyEvery cell evaluation is recorded. Serialize captures the full graph + history + hashes — the audit log is the engine state, not a separate write path.
Capability negotiation@quilt/ai routes across 4 providersQuilt already speaks OpenAI, Anthropic, DeepSeek, and z.ai. The router picks based on model + cost + availability — no agent has to encode this.
Agent substrate (the whole point)@quilt/mcp exposes Quilt to ANY agentMCP (Model Context Protocol) is the standard agent-to-tool bus. @quilt/mcp mounts the registry, validator, and runtime as MCP tools.

Why this mapping works

Quilt was designed as a reactive graph runtime. The spec was designed as a portable interface to agent runtimes. They were not designed in coordination — but they describe the same thing. The reactive graph IS the substrate; the spec is just the surface area that makes it accessible to agents that don't want to learn Quilt's internals.

04The insight: reactive > imperative

The most-quoted sentence in this design is also the most undersold. If you internalize it, the rest of the spec falls out for free.

Don't run containers, run cells.
Don't fetch inputs, watch them change.
Don't checkpoint state, serialize the engine.

Before: the imperative agent

# today — every agent re-implements this dance agent = LLM(prompt = f""" Step 1: docker run --rm team/classifier:v1 --input {input_url} Step 2: download output to /tmp/out.json Step 3: parse, validate, retry on fail Step 4: if schema mismatch → call another LLM to fix it """) agent.run() # opaque, untestable, no rollback agent.debug() # print the prompt; good luck agent.replay() # not a thing

After: the reactive agent

# with Quilt — the engine does the dance manifest = load("quilt://team/classifier:a3f1b9c") manifest.validate() # JSON Schema + preconditions engine = Engine(manifest) engine.tick(input_uri) # reactive propagation engine.serialize() # full trace, deterministic engine.load(snapshot) # rollback is one call # the LLM never had to know about Docker, parsing, or retry. # it just told the substrate WHAT, not HOW.

The shift is small in code, but enormous in what becomes possible:

The one-sentence version

An imperative agent is a program that calls functions. A reactive agent is a graph that the engine evaluates. The graph is the artifact, the evaluation is the run, the trace is the audit. Three things, one source of truth.

05The QuiltAgent class

The whole spec, condensed into four methods. An agent is anything that can plan (decompose a goal into artifacts), execute (run them in the substrate), replay (re-run with new inputs), and inspect (look at what actually happened). The class below is a working reference implementation — about 60 lines on top of @quilt/sdk and @quilt/core.

quilt-agent.ts @quilt/sdk + @quilt/core · ~60 LOC
// QuiltAgent — the 4-method interface every agent implements.
// The substrate does the rest.

import { Engine } from '@quilt/core';
import {
  resolveTemplate, resolveArtifact, validateManifest, publishRunTrace, publish,
} from '@quilt/sdk';

export class QuiltAgent {
  constructor(private llm: LLM, private reg = 'quilt://team') {}

  // ① plan — goal in, manifest out. LLM picks artifacts & wires them.
  async plan(goal: string) {
    const catalog = await this.listCatalog();
    const manifest = await this.llm.json({
      system: `You are a planner. Pick artifacts and return a Quilt manifest.`,
      user:   `Goal: ${goal}\nCatalog:\n${catalog}`,
    });
    return { manifest, uris: manifest.cells.map(c => c.ref) };
  }

  // ② execute — validate, resolve, run, trace. Four steps, all substrate.
  async execute(plan, inputs) {
    const v = validateManifest(plan.manifest);
    if (!v.ok) throw new Error(`manifest invalid: ${v.errors}`);
    await Promise.all(plan.uris.map(u => resolveArtifact(resolveTemplate(u, inputs))));
    const engine = await Engine.fromManifest(plan.manifest);
    await engine.flush();
    const trace_uri = await publishRunTrace(engine.serialize());
    return { trace_uri, outputs: engine.outputs() };
  }

  // ③ replay — same plan, different inputs. Reactive engine = free replay.
  async replay(plan, _prior, inputs) { return this.execute(plan, inputs); }

  // ④ inspect — what actually happened? Read the trace, walk the graph.
  async inspect(trace_uri: string) {
    const t = await resolveArtifact(trace_uri);
    return { manifest: t.manifest_uri, ms: t.duration_ms,
      cells: t.steps.map(s => ({ name: s.cell, ms: s.ms, ok: s.ok })),
      tokens: t.tokens, cost: t.tokens * 0.000002 };
  }
}

// Usage:
const agent  = new QuiltAgent(gpt4);
const plan   = await agent.plan("classify these 1000 reviews");
const result = await agent.execute(plan, { dataset: "s3://..." });
const report = await agent.inspect(result.trace_uri);

That is the entire agent surface area. plan → execute → replay → inspect. Everything else — caching, retries, parallelism, rollback, lineage — is provided by the substrate for free, because the engine already implements it.

Why 4 methods, not 40

The spec is deliberately tiny. A 40-method API would be a tax on every agent; a 4-method interface is one any LLM can hold in context. The substrate does the heavy lifting so the agent stays focused on what to do, not how to do it.

06Playground — try the primitives

All three examples run in the browser. No server, no API keys. Paste a manifest (JSON), click a button, see the substrate work.

Live primitives

① resolveTemplate
② validateManifest
③ publishRunTrace
click Resolve to see the template filled in…
click Validate to run JSON Schema + precondition checks…
click Publish to see the immutable audit record…

The point of these three boxes is not to be a full implementation — it is to show that the substrate's contract is small enough to demonstrate end-to-end in 50 lines of JS. When you read the SDK, you will find the same shape, just with the I/O filled in.

07Provenance-first UX

One backend, three surfaces. Every surface shows the same thing: where did this result come from? The TUI is for power users, the GUI is for the rest of us, the voice interface is for the visually impaired and the impatient. All three hit the same QuiltAgent instance — there is exactly one source of truth.

▣ TUI · terminal

quilt run classifier.yaml
$ quilt run classifier.yaml --input data.csv → resolving quilt://team/classifier:a3f1b9c → validating manifest ✓ 0 errors → executing 4 cells load 128 ms embed 2,410 ms 14.2k tokens classify 890 ms 2.1k tokens route 3 ms → trace quilt://traces/01HXY...:b3a9 → cost $0.043 ✓ done in 3.43s

◫ GUI · browser

quilt.studio / inspector
Run classifier-dag ● ok ──────────────────────────────────── manifest quilt://team/classifier:a3f1b9c started 14:22:01Z duration 3.43s tokens 16,300 ($0.043) ──────────────────────────────────── load 128 ms embed 2,410 ms classify 890 ms route 3 ms ──────────────────────────────────── trace quilt://traces/01HXY...:b3a9 [click to inspect graph]

◉ Voice · spoken

"Quilt, what just happened?"
"I ran classifier version a3f1... on your CSV, four cells, three point four seconds, sixteen thousand tokens, cost four cents. Two thumbs up — every cell passed. Trace is at traces / 01HXY ... b3a9. Want me to replay it with the new data?"

The single contract

All three surfaces serialize to the same RunResult. The TUI renders it as monospace, the GUI as a graph, the voice agent as natural language. None of them invent new state. That is the whole point of having a substrate — one source of truth, many ways to read it.

08Roadmap

Where we are, where we are going. The first three rows are shipped; the rest are in flight.

Manifest schema
JSON Schema for cells, edges, preconditions, model_requirements. The substrate's source of truth.
v0.1 — shipped
@quilt/sdk
The 5 primitives as a TypeScript package — resolveArtifact, validateManifest, publishArtifact, publishRunTrace, resolveTemplate.
v0.2 — shipped
quilt validate CLI
npx quilt validate manifest.yaml — schema check + precondition probe + URI reachability. Returns a structured report.
v0.3 — shipped
quilt-runner reference executor
A small executor that loads a manifest, resolves inputs, runs the engine, and publishes the trace. The "hello world" of agents on Quilt.
v0.4 — next
quilt-rag production RAG
A reference RAG sheet — embed, retrieve, rerank, answer — exposed as a single artifact. Pin it, version it, ship it.
v0.5 — in design
quilt-demo-classifier-dag
The end-to-end demo. 4 cells, 1.0.0 manifest, working trace, published artifact. The thing you copy to start your own.
v0.5 — in design

If you build on the substrate, your work goes here. The next row could be yours — the registry is open, the schema is open, the SDK is open.

Where to go from here

Three doors, depending on what brought you here.