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.
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
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:
- If any runnable action is
exclusive, the first such action in that ordering is selected on its own, with reasonexclusive. - 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 isconcurrency-capwhen the cap is smaller than the number of runnable actions, andpriority-orderotherwise. - If nothing is runnable, the reason is
settled.
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.
receipt_only — replay-safe local work
receipt_only — replay-safe local work
Local work that only produces receipts. Safe to replay.
query — local reads and model calls
query — local reads and model calls
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.external — a real mutation
external — a real mutation
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: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 executionremote action '<id>' requires <agent>@<version>, but the worker loaded <agent>@<version>remote action '<id>' selected head '<hash>' is unavailableunknown action '<id>' for agent '<agent>'remote action '<id>' no longer matches selected kind/execution provenanceremote action '<id>' is no longer runnable at its selected head
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 oneaction 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.