Attestations
Attestations are graph-level audit nodes that summarize an AI agent session — cost, token usage, model breakdown, code change statistics, and which changes are covered. They are created automatically when a session ends and travel with the changes they cover on push.
What Is an Attestation?
When an agent session ends (the user closes the conversation, or the session is deleted), Atomic creates an attestation covering all the changes recorded during that session. The attestation aggregates data from the provenance entries embedded in each covered change:
Session: agent-ses_3781fc7a6ffet5c6r1ILy1BEbv
│
├── Turn 1: Change ABC123 (provenance: claude-sonnet-4-5, 3.2k tokens, $0.04)
├── Turn 2: Change DEF456 (provenance: claude-sonnet-4-5, 5.1k tokens, $0.06)
└── Turn 3: Change GHI789 (provenance: claude-sonnet-4-5, 4.1k tokens, $0.05)
│
▼
Attestation XMJZ3IPF
├── Agent: OpenCode (anthropic)
├── Session: agent-ses_3781fc...
├── Models: claude-sonnet-4-5 (12.4k tokens, $0.15)
├── Code: +116 lines, -8 lines
├── Wall time: 3m 42s
└── Changes covered: ABC123, DEF456, GHI789
Viewing Attestations
List All Attestations
$ atomic agent attest
XMJZ3IPF OpenCode · claude-sonnet-4-5 · 12.4k tokens · 3m 42s · 3 changes
R3KQP7YN Claude Code · claude-sonnet-4-5 · 8.1k tokens · 1m 15s · 1 change
──────────────────────────────────────────
Total: $0.27 · 4 changes covered · 20.5k tokens
Inspect a Specific Attestation
$ atomic agent attest --hash MBV7MPERY4WO
Attestation XMJZ3IPF
Agent: OpenCode
Session: agent-ses_3781fc7a6ffet5c6r1ILy1BEbv
Changes: 3 changes
Wall time: 3m 42s
Cost: $0.15
Tokens: 12.4k
Code: +116 -8
Model Breakdown:
claude-sonnet-4-5: 3.2k in / 9.2k out · $0.15
Changes Covered (3):
ABC12345
DEF45678
GHI78901
Coverage:
dev ████████████░░░░░░░░ 3/5 (60%)
Filter by View
$ atomic agent attest --view dev
Verbose Output
$ atomic agent attest --verbose
Shows per-model token breakdown and per-change details for every attestation.
How Attestations Are Created
Automatic Creation at Session End
When the TurnOrchestrator receives a session-end event and the session had at least one turn:
- Query the agent view history — get all change hashes on the agent's view
- Check for existing attestations — find which changes are already covered by prior attestations from the same session (for resumed sessions)
- Determine new changes — filter to changes not yet attested
- Load each change — read provenance entries (model, tokens, cost) and file operations (lines added/removed)
- Aggregate data:
- Per-model token and cost totals
- Total lines added and removed from the CRDT semantic layer
- Wall duration from session timestamps
- Build and save — create the attestation with all aggregated data
Data Sources
The attestation pulls data from two places:
| Data | Source | How It Gets There |
|---|---|---|
| Model name | change.provenance[].model | Set by build_turn_provenance() at record time |
| Token counts | change.provenance[].tokens | Set by build_turn_provenance() at record time |
| Cost | change.provenance[].cost | Set by build_turn_provenance() at record time |
| Lines added/removed | change.file_ops[].line_ops[] | Generated by the CRDT semantic layer during recording |
| Wall duration | session.started_at / session.ended_at | Tracked by the TurnOrchestrator |
| Agent identity | session.agent_name / session.agent_vendor | Set from hook events (OpenCode sends provider/model) |
Fallback Behavior
If no provenance data is found in the changes (e.g., changes recorded without the agent hooks), but the session knows the model name, the attestation still creates a minimal model entry. This ensures the attestation always names the model, even when token/cost data is unavailable.
Resumed Sessions
When an agent session is resumed (e.g., claude --resume or continuing an OpenCode session), the attestation system handles it correctly:
- Finds existing attestations for this session ID
- Determines which changes are new (not yet covered)
- Creates a new attestation covering only the new changes
- Chains to the previous attestation via the
previous_attestationfield
$ atomic agent attest --hash R3KQP7YN
Attestation R3KQP7YN
Agent: Claude Code
Previous: XMJZ3IPF # ← chains to the prior attestation
Notes: Resumed session (1 new change, 4 total in session)
...
This ensures every change is covered by exactly one attestation, with a clear chain showing the session's history.
On-Demand Generation
The server API also supports generating attestations on demand from the provenance data in changes:
POST /tenant/:id/portfolio/:id/project/:id/attestations/generate
This reads the provenance entries from every change on a view, aggregates them, and creates an attestation. This is useful when:
- Changes were pushed before the attestation was created locally
- You want to regenerate an attestation with updated data
- The local attestation was lost or corrupted
Storage and Transport
On Disk
Attestations are stored in the same two-level directory structure as changes:
.atomic/changes/
├── AB/
│ ├── ABCDEF1234567890.change # A change
│ └── ABCDEF1234567890.attest # An attestation
└── XM/
└── XMJZ3IPF...........attest # Another attestation
Content Addressing
Like all Atomic artifacts, attestations are content-addressed:
data = postcard::serialize(attestation)
hash = blake3(MAGIC_BYTES + data)
path = .atomic/changes/{hash[0:2]}/{hash}.attest
Push
When you push changes, Atomic automatically uploads attestations that cover the pushed changes:
$ atomic push origin
✓ Pushed 3 changes
✓ XMJZ3IPF attestation ($0.15, 3 covered)
An attestation is only uploaded when all of its covered changes have been pushed. This ensures the server never has an attestation referencing changes it doesn't have.
Data Model
Attestation
| Field | Type | Description |
|---|---|---|
version | u8 | Schema version for forward compatibility |
timestamp | i64 | Unix epoch seconds when created |
agent | AttestAgent | Agent identity (name, display name, vendor) |
session_id | String | Session this attestation covers |
cost_usd | f64 | Total cost across all models |
duration_api_ms | u64 | API processing time |
duration_wall_ms | u64 | Wall clock duration of the session |
code_changes | CodeChangeStats | Lines added and removed |
models | Vec<ModelUsage> | Per-model token and cost breakdown |
changes_covered | Vec<Hash> | Hashes of changes this attestation covers |
previous_attestation | Option<Hash> | For resumed sessions — chains to prior attestation |
notes | Option<String> | Human-readable context |
ModelUsage
| Field | Type | Description |
|---|---|---|
model | String | Model identifier (e.g., claude-sonnet-4-5) |
input_tokens | u64 | Prompt/input tokens |
output_tokens | u64 | Completion/output tokens |
cache_read_tokens | u64 | Tokens read from cache |
cache_write_tokens | u64 | Tokens written to cache |
cost_usd | f64 | Cost for this model's usage |
CodeChangeStats
| Field | Type | Description |
|---|---|---|
lines_added | u64 | Total lines added across all covered changes |
lines_removed | u64 | Total lines removed across all covered changes |
Web UI
The Atomic web UI renders attestations on the Attestations tab of each project. The display includes:
- Summary card — Agent name, cost, tokens, duration, change count, lines changed
- Models Used — Per-model breakdown with token counts and cost
- Provenance Graphs — Interactive visualization of the session's decision DAG (from the provenance graphs associated with covered changes)
- Timeline — Temporal view of the session's activity
See Also
- Provenance Graphs — How agent reasoning is captured as causal DAGs
- Agent Integration Overview — How the full agent lifecycle works
atomic agent attestcommand reference — CLI documentation