Appearance
The ledger
Every effect in a project, an event received, a key created, an email sent, a decision taken, writes one entry to the project's ledger in the same transaction as the effect itself.
An entry
json
{
"id": "led_…",
"chain_id": "prj_…",
"seq": 128,
"ts": "2026-10-07T09:13:02.551Z",
"actor": { "key_id": "key_…", "agent": "claude-code", "platform": "claude-code", "human": null },
"op": "email.queued",
"subject": "msg_…",
"input_digest": "sha256:…",
"outcome": "ok",
"policy": { "rule": "#2", "effect": "require_approval", "version": 3, "request_id": "req_…" },
"units": { "events": 0, "emails": 0, "approvals": 0 },
"prev_hash": "sha256:…",
"hash": "sha256:…"
}When the email leaves, an email.sent entry on the same msg_… subject counts emails: 1.
The ledger holds ids and digests only: never email addresses, tokens or payloads in clear. actor says who acted: the key, the agent and platform it declared (with session_hello or the X-Brynth-Agent and X-Brynth-Platform headers), and the person, for decisions.
Query
text
ledger_query({subject: "evt_…"})
ledger_query({agent: "codex", since: "2026-10-01T00:00:00Z", limit: 100})Filters: op, agent, platform, subject, since, until. Pages of 50 by default, up to 500. From the terminal: npx brynth logs --limit 20.
Verify
Each entry carries the hash of the previous one, so changing or removing an entry breaks the chain from that point on.
text
ledger_verify()returns {"ok": true, "checked": …, "last_seq": …, "last_hash": …}, or {"ok": false, "checked": …, "first_bad_seq": …, "reason": …} with the first entry that does not match; reason is seq_gap, prev_hash_mismatch, hash_mismatch or head_mismatch. from_seq and to_seq check a range.