Audit log
The durable, append-only record of a run, including prompts, outputs, usage, and timing
The audit log is the durable record of a run. When a run is audited, chit writes one append-only event log plus the full prompts and outputs, so you can read back what each agent was asked and what it returned, with token usage and timing, after the run is over. It is the receipt layer: which agent, with what input, what output, in what order. The bodies are kept too, but they are opt-in to read.
What ships today: the event schema (@chit-run/core), the node store, audit on all three run surfaces, retention, the chit audit reader, the Studio audit view (open a run from a loop's auditRef in the Loops drawer), and preservation of both adapters' observable event streams as adapter.event records on audited runs. That covers the raw JSONL Codex emits and the Claude stream-json stream (system, stream_event deltas, assistant, result): tool events, command executions, and reasoning summaries the CLIs emit. Events are surfaced live as they arrive (each line is read incrementally and recorded with a real arrival timestamp), not buffered until the call completes. They are the observable CLI event stream, never hidden model reasoning.
Source: packages/core/src/audit/events.ts (schema), apps/cli/src/audit/ (store, recorder, wrapper), apps/cli/src/cli/audit.ts (reader).
What it captures
Each audited run is a directory under the local state dir:
~/.local/state/chit/audit/runs/<runId>/
events.jsonl append-only, one event per line
blobs/<sha256> content-addressed bodies (prompts, outputs, raw events)The events are a run timeline: run.started, then per step step.started and step.completed (or step.failed), per agent call adapter.call.started and adapter.call.completed, an adapter.event for each raw event the adapter surfaced live during the call (Codex JSONL and Claude stream-json), and finally run.completed. Large bodies are not inlined. A rendered prompt, a step output, an agent's returned text, and each raw event body are written to blobs/ and referenced by their sha256, so the same content is stored once. Every step's output is captured, including format steps that have no agent call, so a run can be read back in full.
run.started also records a snapshot of the config each participant ran with: agent id, adapter, session, filesystem permission, and the effective model, reasoning effort, strict-MCP, and timeouts. The audit shows what the run actually used, not what the registry says now (agent profiles can change after a run). Env values are never recorded, only their key names. The snapshot is optional, so a run from before it existed reads back as "recorded config unavailable".
adapter.call.completed carries token usage when the CLI reports it. claude reports input, output, and cache tokens plus a cost. codex reports tokens and no cost, so a summed cost is a known floor, not a guaranteed total. Token counts across providers are a volume signal, not one billing unit.
When a run is audited
Audit is opt-in everywhere except the autonomous loop, because the bodies can hold secrets (see Sensitivity).
chit convergeaudits by default. Each iteration's run is recorded, and the loop iteration links to it through the loop log'sauditRef.chit run --auditrecords one run. The run id is printed on stderr.- The MCP surface records a run when
chit_startis called withaudit: true, and every background run is audited. Each audited run (or loop iteration) gets anaudit_refyou read back withchit_audit_show(a one-shot run'saudit_refequals itsrun_id; a loop has one per iteration, surfaced inchit_trace).
A run with no run.completed event is incomplete: it failed, was cancelled, or was abandoned. The reader never treats a missing terminal event as success.
Reading the log
chit audit list
chit audit show <runId>list prints the runs, newest first, with status and a usage summary. show prints one run's recorded participant config, then its timeline and a usage summary. Both take --json. show takes --blobs (alias --include-bodies) to print the blob bodies (rendered prompts, outputs, and raw adapter event bodies); without it, only the event timeline is shown.
chit audit show 9b41-...
run 9b41-...
manifest: converge surface: converge scope: audit-cli
started: 2026-05-31T10:00:00.000Z
status: ok
tokens: in 22232, out 66, cached 21788, reasoning 19; reported cost: $0.0658
participants (recorded config):
implementer agent=claude session=per_scope filesystem=write read_only_enforcement=not requested (adapter supports) adapter=claude-cli
config model=default effort=default callTimeout=default noProgress=off strictMcp=on passModelOnResume=no
timeline:
run.started manifest=converge surface=converge scope=audit-cli
step.started implement (call) implementer/claude-cli
adapter.call.started implement implementer/claude-cli
adapter.call.completed implement ok 41200ms tokens: in 6590, out 40; reported cost: $0.0658
step.completed implement 41250ms
...
run.completed ok 95000msRetention
Audit transcripts accumulate, so each audited run prunes the store after it finishes. The defaults keep:
- runs newer than 30 days,
- the newest 1000 runs,
- the newest runs whose total size fits under 1 GiB.
A run is removed if it exceeds any cap. Pruning runs only at a terminal point, after the run is fully written, and never removes the run that just finished. Pruning is best-effort: a prune failure never fails the run and never affects whether the run was recorded.
Retention does not redact. It bounds how much is kept, not what each kept run contains.
Sensitivity
Audit blobs hold the full rendered prompts and full agent outputs. Those can contain source code, file contents, credentials, and anything else that flowed through the run. Treat the audit store as sensitive:
- It lives under your local state dir, readable by anything with access to your account. It is not encrypted.
- Do not commit it, and do not share a transcript without reading what is in it.
chit audit show <runId> --blobsprints exactly what would be shared. - Plain
chit runand MCP runs do not audit unless you ask.chit convergeaudits by default because the loop log already points at the transcript.