Agent Office v0.2.3 · POC Get started
Menu: Concepts

Concepts

The ledger

An append-only, hash-chained audit log written only by the office process, with periodic anchors in the policy repo.

The ledger is the office's audit trail. Every state-changing action writes one entry, whoever performed it: a human signing in, an agent calling a tool, a run being refused, the reconciler provisioning an account. Every call to the agent and human MCP endpoints is recorded too (mcp.agent.<tool>, mcp.human.<tool>), including reads.

Who writes it#

Only the office process. The ledger is stored in the office's SQLite database, in the state directory (paths.state, mode 0700, owned by the office's OS user). Agents' OS users can't read that directory, let alone write to it. Checks 1 and 6 of the verify suite test this.

What an entry looks like#

FieldMeaning
seqPosition in the chain
tsTimestamp
actorWho acted: human:<email>, agent:<slug> or office
on_behalf_ofOptional. Whose authority the action used, e.g. policy@<sha> for reconciler steps
actionDotted name, e.g. run.denied, agent.provisioned, approval.decided
targetOptional. What was acted on
dataDetails. Never a secret or token value.
prev_hash, hashThe chain

All actions are listed in the ledger actions reference.

The hash chain#

Each entry's hash covers the previous entry's hash plus the entry itself:

hash = sha256(prev_hash + "\n" + canonicalJson({seq, ts, actor, on_behalf_of, action, target, data}))

The first entry's prev_hash is 64 zeros. Each entry is appended in one SQLite transaction, and database triggers reject any update or delete of a ledger row. Changing, inserting or deleting a past entry directly in the file breaks the chain, and verification reports the first broken position.

Anchors#

Every ledger.anchor_every_minutes (60 by default), if there is anything new, the office appends the current chain head (seq, hash, ts) to ledger/anchors.jsonl in the policy repo and commits it with its forge admin account (a direct push, which policy main allows for that account only; if the push is rejected it falls back to a pull request it merges itself). It records the anchor in its database and ledgers ledger.anchored. Anchors sit outside the office's database, in git history, so rewriting the ledger would also mean rewriting the policy repo's history.

Transcripts#

The full stream of every run is stored as <state>/transcripts/<id>.jsonl (mode 0600), outside the agent's reach. Its SHA-256 goes in the run record and in the run.finished entry, so you can prove a transcript hasn't been edited since the run ended.

Reading and verifying#

  • Audit page (/audit): the ledger with actor, action (prefix) and target filters, and the chain status (entry count, whether the chain verifies, the last anchor, whether it still matches, and its commit). Requires ledger.read (admin).
  • Run page (/runs/<id>): the transcript hash, and whether the ledger chain verifies.
  • CLI: officectl doctor and officectl restore verify the chain; officectl status shows the head.
  • Human MCP: ledger_query and ledger_verify.
  • Verify suite: check 6 of officectl verify checks the chain and the latest anchor.

Next#