.attesso/ledger.jsonl, one signed JSON line each. Allowed and refused entries are the same shape; the decision and the code tell them apart.
Integrity
verify recomputes the hash chain and every signature and reports the first defect rather than a boolean: which entry does not chain, which one fails its signature, whether the last entry matches the recorded head. Change one byte and it names the entry; reorder two and the chain breaks; delete the last one and the head no longer matches.
Each append is fsynced and the head is written after its line. On Linux that is a local-disk guarantee. On macOS, Node cannot issue F_FULLFSYNC, so a power cut can still lose the last write even on APFS; a network filesystem has the same limit. In that case the recorded head refuses the next append instead of guessing, which is why an interrupted write is visible rather than silent.
verify --pin extends the check to a checkpoint you kept elsewhere. Receipts and packets covers what a pin does and does not prove.
Read it
show lists what was allowed, what was refused and why. report is the cost view, per task:
--meta or from an llm.session entry written by attesso usage (or another recorder) from the agent’s own usage record. The report never invents a number.
Watch and steer
watch follows new decisions in a second terminal. --from-seq 0 starts at the beginning of the ledger; --task narrows to one task.
pause refuses the next calls of one task; stop keeps refusing until resume releases it. Both write a signed control entry with TASK_PAUSED or TASK_STOPPED and the task id. A pause blocks even when the hook was started in observe mode, because a human asked for it.
The state lives in control.json beside the ledger. It is steering state, not a proof of human authority: anyone who can write that file can pause or release a task.
The local control surface
127.0.0.1:8197, mints a per-process token and serves the page from the installed package. It shows the day’s observed flow and the exact calls behind it, follows new decisions as they are appended, and writes mode and path changes to the same files the hook reads, so a change applies to the next call. It has no account, no hosted data and no snapshot file; Ctrl-C stops it.
Explain one entry
explain prints one entry in full: the verdict, the rule, the step that tainted the path, and what to do about it. The advice is specific to the code, so an agent that reads it can change its path instead of retrying the same call.
In-process embedders
On Node 20 and later the decision is also published to thebounds.decision diagnostics channel. The event carries seq, at, action, decision, code, rule, capability, trust, args_hash, the policy, rules, tools and MCP digests, tool_pin, entry_hash and kid. It never carries the subject, the task, the amount, the arguments themselves, or your meta. Subscribe to it if you run the engine inside your own process and want a live stream without reading the file.
