Start here
September 17, 2026 · View on GitHub
capsule-emit records your agent's actions as verifiable capsules; seal() is the one call you make.
1. What it is
capsule-emit turns each consequential thing your AI agent does into a capsule — a hashed, signed, content-addressed record that anyone can verify offline, without trusting you.
2. The one call
You already log what your agent does. Move that log to the effect boundary — the moment an action takes effect — and call seal() instead of log(). Same habit you already have, one extra benefit: every call appends one entry to a local, append-only ledger and its digest is witnessed — together a witnessed, verifiable ledger of what your agent did, checkable by anyone. If you know git: seal() is git commit for your agent's actions.
from capsule_emit import seal
capsule = seal(
{"vendor": "Frobozz Supply", "total": "1240.19"}, # payload: any JSON-serializable value
action="write_order",
operator="acme-co", # the accountable tenant
developer="po-agent@v1", # the agent identity + version
verdict="executed", # executed | confirmed | denied | blocked
effect={"type": "write_order", "status": "dispatched"},
)
print(capsule.capsule_id, capsule.signature) # sealed, signed, witnessed by default
pip install capsule-emit
3. Or zero code changes
Don't want to write seal() at each call site? Add one adapter listener per framework and every tool call is sealed automatically:
| Your stack | One-liner |
|---|---|
| LangChain / LangGraph | agent.invoke(..., config={"callbacks": [LangChainCapsuleListener(operator="acme-co", developer="my-agent@v1")]}) — see adapters/langchain.md |
| CrewAI | wrap your tool object with CrewAICapsuleEmitter(...) — see adapters/crewai.md |
| MCP / any callable | decorate the tool with MCPCapsuleEmitter(...).tool("write_order") — see adapters/mcp.md |
| Others | Hermes, Google ADK, Dapr, Goose, agentgateway — see adapters/ |
All adapters are thin shells over one shared base and seal the same capsule you'd get from calling seal() yourself.
4. When to call it
At each consequential action — the moments where "did your agent really do that, and was it authorized?" has a real answer at stake. → what counts as consequential.
5. What's underneath: the Checkpointed Local Log (CLL)
You never touch this to use seal() — but it's what makes the ledger verifiable, and it's worth 30 seconds. Under every seal() is a Checkpointed Local Log (CLL): an append-only Merkle log on your own disk. seal() appends a leaf; on a cadence the library folds the whole history into one ~200-byte checkpoint and sends only that to a witness — an independent service that co-signs it so your history can't be quietly rewritten later. Your payloads never leave; only the checkpoint does.
What one seal() actually does — and doesn't:
- Now, at the call: canonicalizes and digests your payload (the raw value stays on your machine), self-signs the capsule with your producer key — a self-attested signature, you vouching for your own record — and appends it as a leaf to your local CLL.
cap.seqis that leaf's position; it's in the log immediately, before any checkpoint. - Later, on cadence (every ~100 records or ~15 min, or right away if you
push()): the whole log is committed into one ~200-byte checkpoint, sent to the witness, which stamps it — and your leaf inherits that stamp through its inclusion proof.
seal() doesn't fetch a witnessed receipt for the payload the moment you call it — it signs it and drops it in the MMR; the independent confirmation arrives with the next checkpoint. (The per-record signature is self-attested and immediate; the witness stamp is what comes later, via CLL.)
If you know git, you already know the shape:
| git | capsule-emit |
|---|---|
git commit | seal(payload) — record one action |
| your commit history | your CLL — the append-only log under every seal() |
| a signed tag | a checkpoint — one signed value over the whole history so far |
git push | witnessing — an independent party vouches your history existed |
Unpushed git history can be quietly rewritten; pushed history can't. Same here: an unwitnessed log is yours alone, a witnessed one is checkable by strangers.
6. Grow into depth, only when needed
You never rewrite anything to add these — they're the same seal() surface, reached for when you need them:
- Chain records into trails — link a confirmation to its parent (approved → executed → confirmed):
confirmation = seal(payload, confirms=parent_capsule_id) - Compose from slots — bind several members into one capsule that references them by slot and asserts nothing new:
Composition is nesting the slot verbsfrom capsule_emit import seal, who, can, did, audit capsule = seal(who(agent_id), can(mandate), did(action), audit(check))who/can/did/auditinsideseal()— there is no separatecompose()call. Each member can be a fresh payload or a receipt you already sealed along the way — an existing receipt is referenced (its digest is cited in its slot), never re-sealed:mandate = seal(payload_a) # sealed earlier in the run action = seal(payload_b) # sealed earlier in the run account = seal(can(mandate), did(action)) # composes them BY REFERENCE (digests, in slots) - Bring in something already signed — an artifact someone else (or another system) already signed goes in as-transmitted, never re-signed — its bytes committed exactly, its own digest still identifying it:
from capsule_emit import received effect = received(mandate_bytes, type="machine-mandate")received(...)is also legal nested insideseal()—seal(received(bytes, type=...))and the standalone call produce the identical capsule. - Force a checkpoint — push a signed checkpoint of your log to the witness now, instead of waiting for the cadence:
from capsule_emit import push push() - Witness / anchor knobs — witnessing to
witness.agentactioncapsule.org(POST /checkpoints) is on by default; turn it off with one flag (seal(payload, witness=False), orCAPSULE_WITNESS=offeverywhere). The legacy per-capsule anchor channel is off by default (anchor=Trueto opt back in).
7. Read & verify
View your ledger:
capsule-emit ledger view ./ledger.jsonl
Anyone can verify a capsule (or a whole ledger) independently, from the bytes alone — no keys, no network, no clock — with the separate spec package:
pip install agent-action-capsule
agent-action-capsule verify --store ./ledger.jsonl
The verifier is independent of capsule-emit on purpose: any tool can produce a capsule; any party can verify one.
capsule-emit verify --store ./ledger.jsonl runs that same check plus the producer signature agent-action-capsule verify deliberately leaves to the substrate layer (draft-mih-scitt-agent-action-capsule §6, Class 1 verification): each record is VALID only if its capsule_id recomputes from the carried content AND, when a signature/key_id envelope is present, that envelope verifies. A record with no envelope at all (e.g. a capsule_emit.surface.log() entry, which is never signed) still counts VALID by default, with a one-line summary — N record(s) carry no producer signature (producer_signature_unclaimed) — printed after the tally so the gap stays visible instead of silent. For a ledger whose producer always signs, pass --require-signature to make that same condition fail closed (INVALID, exit 1) instead of warning.
Coming from an older version?
emit()→seal(). The top-level producer verb was renamed.emit()remains importable for one release as a raising stub that points you atseal().compose()/carry()are retired as public verbs. There is nocompose()/carry()call. "Composition" is now nesting the slot verbswho/can/did/auditinsideseal(...); bringing in a foreign signed artifact isreceived(bytes, type=...)(nested inseal()or standalone).- Anchor is now legacy and off by default. The per-capsule anchor channel is an explicit, non-default opt-in (
anchor=True/CAPSULE_ANCHOR=legacy-on), kept for one release as a rollback path. - Checkpoint/witness is the default push. Every sealed ledger is folded into a per-ledger Merkle Mountain Range and, at the cadence, a signed checkpoint is registered with the witness — the separate, live
witness.agentactioncapsule.orgservice at itsPOST /checkpointsroute. This is the only default egress channel as of 0.5.0.