Skip to content

Latest commit

 

History

History
248 lines (199 loc) · 10.7 KB

File metadata and controls

248 lines (199 loc) · 10.7 KB

Anatomy of a capsule

Six sections. One truth. Tamper-evident forever.

A capsule is one cryptographically sealed record of one action your agent took. Not a log line, not a chat bubble: a structured record that answers the six questions you would ask in any audit, then is hashed, signed, and linked to the action before it.

Every action your agent takes becomes one capsule. Every capsule tells the whole story of that action through six sections, carries its own cryptographic seal, and points back at the previous capsule, forming an unbroken chain.

            ┌──────────────────────────────────────────────┐
            │  CAPSULE  (one action)                         │
            │                                                │
   what ───▶│  1. TRIGGER     what initiated this action     │
  state ───▶│  2. CONTEXT     the state of the world         │
    why ───▶│  3. REASONING   why this decision was made     │
    who ───▶│  4. AUTHORITY   who or what approved it         │
   what ───▶│  5. EXECUTION   what actually happened          │
 result ───▶│  6. OUTCOME     the result and side effects     │
            │  ───────────────────────────────────────────  │
            │  SEAL           SHA3-256 hash + Ed25519 sig    │
            │  LINK           sequence + previous_hash        │
            └──────────────────────────────────────────────┘

The six sections

Each section is a plain JSON object. The keys below are the conventional ones; sections are intentionally permissive (a tool can add keys), and verification never depends on section contents, only on the canonical bytes and the seal.

1. Trigger: what initiated this action

The origin of the action: who or what asked for it, when, and what was asked.

"trigger": {
  "type": "user_request",          // user_request | scheduled | system | agent
  "source": "<session-id>",        // who/what triggered it
  "request": "Refactor the checkout flow to use the new pricing service"
}

2. Context: the state of the world

The environment the action ran in: which agent, which session, and provenance (working directory, git branch, model, timestamps).

"context": {
  "agent_id": "cursor",
  "session_id": "a3f1c2-checkout",
  "environment": { "cwd": "/Users/dev/app", "git_branch": "main", "model": "claude-opus-4-8" }
}

3. Reasoning: why this decision was made

The visible deliberation. analysis holds the agent's visible prose; reasoning holds hidden thinking text when a tool exposes it (often empty, since some tools redact it and keep only a proof-of-reasoning signature).

"reasoning": {
  "analysis": "The checkout flow calls the legacy pricing path; I'll route it through PricingService.quote().",
  "reasoning": "",
  "model": "claude-opus-4-8"
}

4. Authority: who or what approved it

The governance posture. Did the agent act on its own, under a policy, or with a human approval? This is how you see, after the fact, when an agent acted autonomously versus with a gate.

"authority": {
  "type": "autonomous",            // autonomous | policy | human_approved | escalated
  "policy_reference": "permission_mode=acceptEdits"
}

5. Execution: what actually happened

The concrete actions: the tool calls, their arguments, their results, timing, and resources used (token usage).

"execution": {
  "tool_calls": [
    { "tool": "edit_file_v2", "arguments": { "file_path": "src/checkout/flow.ts" },
      "result": "ok", "success": true, "duration_ms": 40, "error": null }
  ],
  "duration_ms": 40,
  "resources_used": { "input_tokens": 800, "output_tokens": 120 }
}

6. Outcome: the result and side effects

What came of it: status, a human-readable summary, side effects, and the full result payload.

"outcome": {
  "status": "success",             // pending | success | failure | partial | blocked
  "summary": "edit_file_v2: src/checkout/flow.ts",
  "side_effects": ["wrote src/checkout/flow.ts"],
  "result": "..."
}

The seal

After the six sections (plus the identity and link fields) are fixed, the capsule is sealed:

"hash":      "f379ba5ce838ad05...",   // SHA3-256 over the capsule's canonical bytes
"signature": "2a57caf40e90ca6a...",   // Ed25519 signature over the hash hex string
"signature_pq": "",                    // reserved for an optional post-quantum signature
"signed_at": "2026-05-31T00:00:00+00:00",
"signed_by": "153405388f063b60"        // public-key fingerprint

The seal fields are not part of what gets hashed (you cannot hash a field that holds its own hash). The exact bytes that are hashed, and the precise signature scheme, are pinned in wire-format.md.

The link

Two fields turn a pile of capsules into a tamper-evident chain:

"sequence": 1,                  // 0-based position in the chain
"previous_hash": "41f0ef48..."  // the hash of the capsule at sequence - 1 (null at genesis)

Change any byte of any capsule and its hash changes, which no longer matches the next capsule's previous_hash, which breaks the chain at exactly that point. That is the whole guarantee, and you can check it yourself: see verify-it-yourself.md.

The export bundle

agent-capsule export turns the on-disk chains into the static JSON the Capsule Explorer verifies. Beyond one file per chain (each carrying the exact canonical bytes per capsule), the bundle's index.json carries:

{
  "public_key": "0edc0349...",         // this machine's Ed25519 signing key
  "fingerprint": "0edc03491c799def",   // first 16 hex of public_key
  "keys": {                            // the keyring: fingerprint -> public key
    "0edc03491c799def": "0edc0349...", //   our own key
    "7879553cfff1a978": "7879553c..."  //   a registered known key (imported / rotated / peer)
  },
  "meta": { "length": 127, "head_hash": "91a8057c...", "all_hashes_ok": true },
  "chains": [
    { "id": "claude-code-<session>", "signed_by": ["7879553cfff1a978"],
      "started_at": "...", "ended_at": "...", "length": 2526, /* ... */ }
  ]
}

A capsule's signed_by is a 16-char fingerprint, not a full key, so verifying a signature needs the full public key. Our own key is always known; keys for chains we did not sign (an imported chain, a rotated key, a peer) are registered in ~/.agent-capsule/known_keys.json and bundled into keys. The Explorer resolves each capsule's key by signed_by and verifies every signer offline.

The meta-chain

meta.json is a second hashchain with one capsule per sealed conversation, recording that conversation's head hash and capsule count (in outcome.result). Because it is itself hash-linked, its single head commits to every conversation ever sealed, closing the two gaps a per-conversation chain cannot: deleting a whole conversation and truncating a conversation's tail. Verify it with agent-capsule verify-meta.

A full capsule

{
  "id": "a3f1c2e9-...-uuid",
  "type": "tool",                 // tool | chat | agent | system
  "domain": "cursor",             // the tool that produced it
  "parent_id": null,
  "sequence": 1,
  "previous_hash": "41f0ef48...",
  "spec_version": "1.0",

  "trigger":   { "type": "user_request", "source": "a3f1c2-checkout", "request": "Refactor the checkout flow..." },
  "context":   { "agent_id": "cursor", "session_id": "a3f1c2-checkout", "environment": { "cwd": "/Users/dev/app", "model": "claude-opus-4-8" } },
  "reasoning": { "analysis": "Route checkout through PricingService.quote()", "reasoning": "", "model": "claude-opus-4-8" },
  "authority": { "type": "autonomous", "policy_reference": "permission_mode=acceptEdits" },
  "execution": { "tool_calls": [ { "tool": "edit_file_v2", "arguments": { "file_path": "src/checkout/flow.ts" }, "result": "ok", "success": true, "duration_ms": 40, "error": null } ], "duration_ms": 40, "resources_used": { "input_tokens": 800, "output_tokens": 120 } },
  "outcome":   { "status": "success", "summary": "edit_file_v2: src/checkout/flow.ts", "side_effects": ["wrote src/checkout/flow.ts"], "result": "ok" },

  "hash": "f379ba5c...", "signature": "2a57caf4...", "signature_pq": "",
  "signed_at": "2026-05-31T00:00:00+00:00", "signed_by": "153405388f063b60"
}

Open any capsule in the Capsule Explorer to see these six sections rendered, with each section's seal re-verified live in your browser.

What the adapters fill in (the rich part)

The sections above are the skeleton. What makes a capsule worth reading is the content each adapter pulls from its tool's transcript. Capsules record what an action did, not just that it ran:

  • Real diffs. File edits carry a rendered unified diff with (+N/-M) line counts in the summary. (Claude Code structuredPatch, Codex apply_patch V4A envelope, Cursor/Cline edit payloads.)
  • Full tool results. Bash/shell capsules carry actual stdout and stderr; Read carries the file content and line count; Web search carries the query and the returned URLs. Cline joins its API history so even read/search/MCP tools carry their results.
  • The model's reasoning. Where a tool persists it (Codex reasoning items, Cursor/Cline thinking blocks), the deliberation lands in reasoning.reasoning. Claude Code redacts thinking text, so the capsule records the proof-of-reasoning signature and a thinking_redacted flag instead.
  • Subagent fan-out. A delegated subagent capsule carries a scorecard: [Explore: 2 edits, +14/-3, 9 tool calls], so the parent chain shows what its children did.
  • Cost and provenance. Token usage (including cache and reasoning tokens), per-turn model and context-window telemetry, checkpoint hashes, and, for Cursor, a per-conversation AI-authorship rollup from its code-tracking DB.

Heavy raw blobs (whole original files, base64 screenshots, entire subagent transcripts) are deliberately summarized or referenced rather than inlined, so capsules stay readable and the chain stays light. Per-tool specifics and their caveats are in tools/.

See also