Skip to main content
Every proposal that reaches the decision path is appended to .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:
Actions are proposals; refusals carry the reason; tokens are input plus output, with cache reads shown separately because they bill at a different rate and usually dwarf the rest. Cost and token counts come from what the caller reported in --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

The server binds 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 the bounds.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.