Skip to main content
An agent you author here is a receipt-native loop. You declare the receipt types it reads and writes, fold them into a view, expose actions that are runnable against that view, and give it a goal predicate that says when it is done. Every decision the loop makes — which action it selected, what that action emitted, why the run stopped — lands in the same hash-chained stream you can trace, replay and fork afterwards. The three commands on this page (new, run, dev) belong to the in-repo developer CLI, not the released binary, and you run them from a checkout through the repository’s .receipt/bin/receipt wrapper.
receipt new is a template generator, not a working scaffold. It writes <repo root>/src/agents/<id>.agent.ts, and the first line of the file it writes imports "../sdk/index". That path does not exist: the repository has no root src/ directory, and the SDK lives at packages/receipt-app/src/sdk/index.ts. The generated file therefore does not import, and running it fails.Two locations produce a file that imports, and they are not interchangeable:
  1. Rewrite the generated import at <repo root>/src/agents/<id>.agent.ts so it points at the package SDK.
  2. Author the agent under packages/receipt-app/src/agents/ instead. From that directory the template’s own relative import "../sdk/index" resolves to packages/receipt-app/src/sdk/index.ts, and it is also the only location the remote-action loader will find.
Prefer the second when the agent has to reach the remote-action lane — but state the cost plainly. A spec that lives only under packages/receipt-app/src/agents/ is not the file receipt run loads through the wrapper. To run an agent inline with receipt run, the spec must sit at <repo root>/src/agents/<id>.agent.ts with its import rewritten — option 1.

Where an agent file can live

There are two loaders, and they read different paths. The wrapper .receipt/bin/receipt changes directory to the repo root before it runs the CLI, so through the wrapper the CLI loader resolves <repo root>/src/agents/<id>.agent.ts — the path receipt new writes to. The consequence is a real trade-off, not a preference. An agent left at the repo root is the file receipt run loads through the wrapper, but it is not deployable to the remote-action lane without being moved into the package. An agent under packages/receipt-app/src/agents/ is the only kind an action declaring execution: "remote" can ever execute, because the remote worker re-loads the spec from that directory by id — and it is not the file the wrapper’s receipt run resolves, so it cannot be run inline from that path.
receipt run resolves the agent relative to the process working directory. Nothing in the source documents a wrapper flag that repoints that loader at the package directory, so treat the two paths as two separate facts rather than assuming one command covers both.

receipt new and its templates

The default template is basic. An unrecognised --template value silently behaves like basic. The command needs write access to <repo root>/src/agents/, and on success prints created <path> relative to the repo root. The agent id must be kebab-case — it is matched against ^[a-z][a-z0-9-]*$. Anything else is refused with:
An existing file is never overwritten:
What each template declares:
The merge template does not type-check as generated. It falls back to the basic action body, so the file it writes emits a task.completed receipt that its own receipts block never declares.
The CLI’s own usage text files receipt new, receipt dev and receipt run under the heading Legacy agent-framework commands. They are the authoring surface described here; day-to-day background work goes through Factory objective ingress instead.

Write the agent

An agent spec is four things: the receipt types it reads and writes, a view folded from those receipts, the actions that are runnable against that view, and a goal predicate that says when the run is done.
  • Declare receipts. receipts is a record of type name to receipt<T>(). receipt<T>() returns { __receipt: true } — it is a pure type marker that carries the body type into the spec.
  • Derive a view. view receives { on, chain }. on(type) returns { all(), last(), exists() } for receipts of that type; chain() returns the raw chain.
  • Return actions. actions returns an array built from action, assistant, tool or human. Each carries a run function; when and sideEffects are both optional. See the TypeScript SDK and the action contract for the full contract.
  • Define the goal. goal is a field on the spec, a ({ view }) => boolean predicate. It is not an SDK import.

Run it

This section applies to a spec at <repo root>/src/agents/<agent-id>.agent.ts — option 1 in the warning above. A spec that lives only under packages/receipt-app/src/agents/ is not resolvable by receipt run through the wrapper, so the commands below will not load it.
--problem is required — --prompt is an alias for it — and omitting both throws --problem is required. The command loads the agent module and checks its default export looks like a defineAgent spec: an object with a string id, a string version, function view, actions and goal, and a truthy object receipts. Anything else is refused with:
What it seeds, and in what order. The run is seeded with a task.requested receipt if the spec declares that type; otherwise with prompt.received if the spec declares that; otherwise with nothing at all. The seed uses the deterministic event id seed:<runId>. Identifier defaults. The loop runs inline, with a Resonate-backed adapter for remote actions. It prints pretty JSON:
ok is status === "completed", and reason appears only when status is blocked. Any status other than completed sets the process exit code to 2. Prerequisites: a defineAgent spec at <repo root>/src/agents/<id>.agent.ts and Postgres. The command always builds a Resonate client for the remote-action lane, so an action declaring execution: "remote" also needs a reachable Resonate server and a worker-chat role to serve it.
The usage text advertises --max-iterations <n> and --workspace <path> for this command. The handler reads neither.
One inconsistency worth flagging rather than asserting: receipt run and receipt fork construct their Postgres stores with no data-directory argument, so they operate on the default schema, while stream resolution, chain reads and the queue helpers pass DATA_DIR. In the default single-tenant repo layout the two resolve to the same schema only when RECEIPT_POSTGRES_SCHEMA is set. Check this first if receipt trace cannot resolve a run you just executed.

Read the run back

Three commands read a chain. All of them need Postgres (ZERO_UPSTREAM_DB). How a target resolves. A value containing / is used as a stream name verbatim. Otherwise the store is searched for an exact stream name, then for any stream ending in /runs/<value>. Failure is:

Interpreting the control-receipt trail

The runtime records its own decisions as receipts alongside yours, drawn from the canonical control-receipt set listed in the SDK reference. Read a trace against that list and it tells you why a run stopped where it did. Which actions a pass picked, and the exclusive, concurrency-cap, priority-order or settled reason it recorded for that choice, follow the deterministic selection rules in the SDK reference. The selectionId and invocationId in the trail are SHA-256 digests of the run, spec and action they belong to rather than random values, so a recovered run reproduces exactly the same ids — the two formulas and the policy versions they hash are on the same page.

What receipt dev starts

It takes no flags and no arguments. It spawns scripts/start-resonate-dev.mjs from the repo root, which brings up the whole runtime role fan-out — worker-control ×1, worker-chat ×2, worker-codex, driver ×1 and api ×1 — plus a local Resonate server, unless RESONATE_START_SERVER=0 or RESONATE_URL points somewhere that is not loopback. The supervisor requires a Resonate CLI of at least 0.9.7 to start that server. The dev script sets RECEIPT_SERVER_WATCH=api, so the api role runs under bun --watch and the other roles do not. This is what actually executes remote actions: worker-chat is the role that registers the remote-agent-action function, so an action declaring execution: "remote" needs that role running. Exit code 0 resolves. Anything else rejects with:
Next step: set the variables the runtime reads.