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.
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
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:
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.
receiptsis a record of type name toreceipt<T>().receipt<T>()returns{ __receipt: true }— it is a pure type marker that carries the body type into the spec. - Derive a view.
viewreceives{ on, chain }.on(type)returns{ all(), last(), exists() }for receipts of that type;chain()returns the raw chain. - Return actions.
actionsreturns an array built fromaction,assistant,toolorhuman. Each carries arunfunction;whenandsideEffectsare both optional. See the TypeScript SDK and the action contract for the full contract. - Define the goal.
goalis a field on the spec, a({ view }) => booleanpredicate. 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:
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
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: