Skip to main content
The SDK is how you write a receipt-native agent: you declare the receipt types your agent reads and writes, fold them into a view, and list the actions that are runnable against that view. The runtime does the rest — choosing which action runs next, claiming it so no second worker repeats it, and replaying the receipt chain to rebuild the view each pass.
The SDK is an in-repo module, not a published package. It lives at packages/receipt-app/src/sdk/index.ts inside the repository, the package that holds it is private, and it is not listed in that package’s exports — so there is nothing to install from a registry, and you write against it from a checkout. Authoring an agent covers the two directories a spec can live in and how to run one.

What the SDK exports

There are exactly eight exported values: Alongside them are fourteen exported types: ReceiptDeclaration, ReceiptBody, ModernAgentSpec, ActionCommitContext, ActionExecutionMode, ActionKind, ActionRunContext, ActionSideEffects, AgentAction, DurableActionContext, MergePolicy, MergeCandidate, MergeDecision and MergeScoreVector.
There is no goal export. goal is a field on the spec you pass to defineAgent, not a helper you import. Older internal notes list it among the SDK symbols; importing it will fail.
receipt<T>() returns { __receipt: true }. It is a pure type marker — it carries your receipt body type into the spec and does nothing at runtime. defineAgent(spec) and merge(policy) likewise return their argument unchanged; all three exist for type inference, not behaviour.
The barrel is not the whole SDK. A sixth module beside it holds loadDefinedAgentSpec, which is not re-exported — it is the seam the remote-action worker uses to reload your spec by id. You do not import it directly, but it is the reason remote actions care where your file lives.

The spec shape

The view function receives two helpers:
  • on(type) returns { all(), last(), exists() } for receipts of that type.
  • chain() returns the raw chain.

The action contract

The four kinds and how they are selected

Selection is deterministic. Every runnable action is sorted by kind priority — human first, then assistant, then action, then tool. From that ordering:
  1. If any runnable action is exclusive, the first such action in that ordering is selected on its own, with reason exclusive.
  2. Otherwise a concurrency cap is computed first, then applied: the cap is the minimum of the spec-level default and every runnable action’s maxConcurrency, floored at 1. The first n ordered actions are selected, where n is that cap. The reason is concurrency-cap when the cap is smaller than the number of runnable actions, and priority-order otherwise.
  3. If nothing is runnable, the reason is settled.
The spec-level default is maxConcurrency on the spec, or 8 when the spec does not set it. The spec’s maxIterations bounds how many selections one run may make and defaults to 200; reaching it fails the run with max iterations reached (200). The four constructors — action, assistant, tool and human — correspond to those four kinds. human requires responseWhen at the type level, and the controller enforces it again at run time with human action '<id>' requires responseWhen({ view }). When the predicate is false, the controller appends human.requested, records run.blocked with the reason waiting for a correlated response to human action '<id>', and returns. The run does not wait in the process: the next pass over the same pending selection re-evaluates responseWhen against the rebuilt view and proceeds as soon as a newly appended receipt makes it true.

The two execution modes

execution is local or remote. A remote action is dispatched through a durable function call rather than run in the calling process; see Remote actions below.

The three side-effect classes

sideEffects is the field that decides what the runtime is allowed to do to your action on a retry. Pick it deliberately.
Local work that only produces receipts. Safe to replay.
Local model or read work. It is at-least-once after lease ambiguity: the runtime may run it again. The buffered receipt output is still fenced by the durable claim and deduplicated by invocationId. An action.output.manifest receipt binds the whole result — its count and content hash — before any domain receipt is appended, so a repeated run cannot double-append, and a replay that produces different content is refused rather than spliced onto the prefix the previous owner committed.
A real mutation of something outside Receipt. It must set execution: "remote" — the controller checks this before every selection and throws action '<id>' declares external side effects and must use execution: 'remote' — and it must use invocationId as the downstream idempotency key, because remote delivery is at-least-once. Nothing else stops the same charge, message or write from happening twice.

Optional members

function
Gates whether the action is runnable against the current view.
function
An optimistic-validity fence. It is re-evaluated inside every retry, against both the view the action was selected at (selectedView) and the latest view (view). Returning false records an action.superseded receipt with the reason commit predicate rejected the current receipt-derived view, and the controller reselects from the new head — the action’s work is discarded rather than committed against stale state.
boolean
Forces a selection of exactly one action.
number
Lowers the per-selection cap. The effective cap is the minimum across every runnable action and the spec default, floored at 1 — so an action that lowers the cap can push itself out of the selection it just narrowed.
function
Required for human actions. Decides when the awaited human response has arrived.
string
Picks the worker group for a remote action. Falls back to the caller’s default group.

Determinism

Both identifiers an action runs under are digests, not random values, so a recovered run reproduces exactly the same ids:
The pinned policy versions are runtime-policy-v2 for control receipts — unless the spec sets its own runtimePolicyVersion — and scheduler-v2 for selection. Receipt content hashing sorts object keys before hashing, so a harmless difference in key order does not invalidate a recovered output manifest.

Control receipts

These are the receipts the agent loop writes on your behalf. They are the canonical set:
action.started may carry an inlineSelection. When a selection holds exactly one action that runs locally and is not a human action, the controller skips the separate action.selected receipt and durably selects and claims in that one receipt. Remote selections, multi-action selections and human actions still append action.selected first.

Remote actions

A remote action dispatches through a pinned, versioned durable function — receipt.agent.action.execute, version 2, pinned on both registration and the call options so a rolling deploy cannot route a v2 payload to a v1 worker. The call id is the invocationId, and the call timeout is 120000 ms. The worker does not trust the dispatch. It reloads the spec from packages/receipt-app/src/agents/<id>.agent.ts, re-derives the view from the selected head, and re-checks the spec id and version, that the action exists, the action’s kind, that execution === "remote", and when(...) before running anything. Each check has its own refusal:
  • agent spec '<id>' does not support remote execution
  • remote action '<id>' requires <agent>@<version>, but the worker loaded <agent>@<version>
  • remote action '<id>' selected head '<hash>' is unavailable
  • unknown action '<id>' for agent '<agent>'
  • remote action '<id>' no longer matches selected kind/execution provenance
  • remote action '<id>' is no longer runnable at its selected head
There are two spec loaders and they read different directories. The CLI loads <current working directory>/src/agents/<id>.agent.ts, and ./.receipt/bin/receipt changes to the repository root before it runs, so in practice that is <repo root>/src/agents/; the remote-action worker loads packages/receipt-app/src/agents/<id>.agent.ts. A spec scaffolded into the repository root is runnable from the CLI but is not visible to the remote-action worker unless it is moved into the package — and the reverse is equally true. Authoring an agent explains the trade-off.

Merge policy

shouldRecompute is optional, not required. rebracket is an alias for merge — the same function under a second name. A durable merge claims with merge.started, records the chosen candidate as merge.selected, binds any onMergeResult output with merge.output.manifest, and ends at merge.applied. A policy that is settled or returns no candidates ends at merge.skipped straight after merge.started, without a selection or a manifest; a failure ends at merge.failed. There is no merge.evidence.computed receipt and no merge.candidate.scored receipt.

A minimal agent

A working spec is about thirty lines: declare two receipt types, fold them into a view, expose one action that emits the terminal receipt, and let the goal predicate read it back. Authoring an agent carries that example in full, along with the two directories a spec can live in and the receipt run invocation that executes it inline from a checkout. Next step: scaffold and run your own agent.