Reference
Ledger actions
The shape of a ledger entry, how the hash chain and anchors work, and the actions the office records.
The ledger is the office's append-only audit log. Only the office process writes it, and it lives in the office's state directory, which agents' OS users cannot read. Every state-changing action, by a human, an agent or the office itself, writes one entry. See The ledger for the idea and Audit & the ledger for day-to-day use.
Entry shape#
| Field | Type | Meaning |
|---|---|---|
seq | integer | Position in the chain, starting at 1, no gaps. |
ts | timestamp | When the entry was written. |
actor | principal | Who acted: human:<email>, agent:<slug> or office. |
on_behalf_of | string or null | Whose authority the action rests on. Reconciler steps use policy@<sha>, the policy commit that caused them; run entries use agent:<slug>; officectl commands use cli:<OS user>. |
action | dotted lowercase name | What happened, for example run.started. |
target | string or null | What it happened to. |
data | JSON | Details. Never contains secrets or token values. |
prev_hash | hex | The previous entry's hash. |
hash | hex | This entry's hash. |
The hash chain#
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 append happens in one SQLite transaction (BEGIN IMMEDIATE, so concurrent writers line up behind the head), and database triggers reject any UPDATE or DELETE on the ledger table.
Changing, inserting, reordering or deleting any entry breaks the chain from that point. Verification recomputes it and returns ok, checked (entries checked), and on failure brokenAt (the first bad seq) with a reason. It runs on the Audit page (chain status), in the human MCP tool ledger_verify, in officectl doctor, after officectl restore, and as check 6 of officectl verify.
Anchors#
Every ledger.anchor_every_minutes (default 60), if anything new was ledgered, the office appends the current chain head, {"seq", "hash", "ts"}, to ledger/anchors.jsonl in the policy repo. It commits with its forge admin account, which officectl init allows to push directly to the policy repo's main (if that push is rejected it falls back to a pull request it merges itself). It records the anchor in its anchors table and writes a ledger.anchored entry with the commit. ledger_verify and verify check 6 confirm the latest anchor still matches the database.
Anchors put the chain head somewhere outside the office's database, so rewriting history in the database no longer matches what the forge recorded.
Actions#
Every action this release writes, grouped by area. Actor is who appears in actor; "human" means human:<email>, "agent" means agent:<slug>.
Sign-in and people#
| Action | Actor | When |
|---|---|---|
auth.login | human | A human signs in (password, or by accepting an invite). |
auth.login_failed | office | A sign-in with bad credentials. |
auth.logout | human | A human signs out. |
auth.invite_accepted | human | A human sets a password from an invite link. |
human.invited | office or human | An invite link is created (by the reconciler, officectl init / officectl invite, or an admin on the People page). Never the link itself. |
human.access.changed | office | The reconciler adds a human, re-enables one, or changes their roles from access.yaml. |
human.disabled | office | A human was removed from access.yaml and is disabled. |
token.minted / token.revoked | human | A personal MCP token is created or revoked on /account (data.kind: mcp.human). |
Policy and agents (reconciler)#
All written by office with on_behalf_of: policy@<sha>.
| Action | When |
|---|---|
policy.proposed | A policy PR is opened (hire, change, suspend/resume, access). Actor: the human or agent who proposed it. |
policy.invalid | The policy has validation errors (once per distinct set); valid agents still apply. |
policy.applied | A reconcile finished: reason, commit, number of steps, changed agents, error count. |
agent.recorded | A proposed agent, or a suspended/retired one that was never provisioned, is recorded. |
agent.os_user.ensured | The agent's OS user exists. |
agent.forge_user.ensured | The agent's forge user exists and is in the agents team. |
agent.vault.ensured | The vault repo, access, labels, branch protection and webhook are in place. |
agent.claude_md.rendered | CLAUDE.md was rendered and committed to the vault (commit and sha256). |
agent.grants.applied | Repository grants from forge.read / forge.write applied; stale ones removed. |
token.minted / token.revoked | The short-lived token used to clone the vault onto the host. |
agent.vault.checked_out | The vault checkout in the agent's home exists. |
agent.provisioned | First full provisioning of a hired agent. |
agent.updated | A hired agent's personnel or charter changed and was re-applied. |
agent.resumed | A suspended or retired agent was set back to hired. |
agent.suspended / agent.retired | The agent was deactivated: forge login disabled, OS user locked, running runs killed (and for retired, vault archived). |
agent.removed_from_policy | The agent's files disappeared from the policy; it is suspended. |
agent.reconcile_failed | Provisioning one agent failed; the others continue. |
Runs#
| Action | Actor | When |
|---|---|---|
run.queued | whoever asked (office for mail, scheduled and verify runs; the human for chat and manual runs) | A run is queued. |
run.refused | office | A run fails admission, at enqueue or just before it starts. data.reason is one of: agent is <status>, not hired, personnel status is <status>, not hired, no personnel file in the current policy, unknown agent <slug>, trigger "<kind>" not allowed (run.triggers: …), budget exhausted: …, model key missing. |
run.started | office | A run starts: kind, model, credential kind (api_key or subscription), whether it is isolated, budget cap. |
run.tool | agent | The agent used a tool (from the harness output stream). |
run.denied | agent | A tool use was denied, by Claude Code's settings (via: harness) or by the office MCP (via: mcp). |
run.killed | human or office | A run is stopped (queued or running). Also when an agent is suspended. |
run.finished | office | A run ends: status, cost, turns, the transcript's sha256, credential kind. Also written as failed: office restarted for runs a crash left running. |
token.minted / token.revoked | office | The run's forge token run-<runId>: user, name and scopes, never the value. Revoked whatever happens. |
mcp.agent.<tool> | agent | Every office MCP call by an agent, allowed or not, with a safe argument summary. |
Mail, chat, approvals#
| Action | Actor | When |
|---|---|---|
mail.sent | human or agent | A new mail thread is opened (also the automatic notification when an agent's mail gets a reply). |
mail.replied | human or agent | A reply on a thread. |
mail.closed | agent or human | A thread is closed. |
mail.loop_guard | office | A reply notification past 3 hops was delivered without waking the recipient. |
chat.sent | human | A human sends a chat message (chat page or agent_chat): thread id, run id (or queued_behind when it waits for the thread's live run), length and channel. Never the text. |
approval.requested | agent | An agent asks for approval. |
approval.decided | human | A human approves or denies. |
capability.window.opened | human | An approved window opens: capabilities, end time. |
capability.window.revoked | human | A human ends an open window early (End now on Needs you): window id, capabilities, the end time it would have had. |
forge.pr.opened | agent | An agent opened a pull request with forge_open_pr (or got its already-open PR for that branch back): repo, number, head, base, run. |
mcp.human.<tool> | human | Every human MCP call, including refused ones. |
Artifacts#
| Action | Actor | When |
|---|---|---|
artifact.published | agent | The office committed an artifact the agent published with artifact_publish. Target artifact:<id>; data: agent, run, title, kind, vault path and repo, commit, size and sha256. Never the content. |
Office operations#
| Action | Actor | When |
|---|---|---|
office.initialized | office | Ledger genesis, written by officectl init. |
forge.bootstrapped | office | officectl init set up the forge org, teams and repos. |
secret.created / secret.updated | office | officectl secrets set stored a value (the name only). |
webhook.received | office | A signed forge webhook arrived, with what the office did about it. |
ledger.anchored | office | The chain head was committed to the policy repo. |
verify.attempt | office | One check of officectl verify ran, with its result (token.minted / token.revoked for the tokens it uses). |
office.upgraded | office | officectl upgrade changed the version and ran migrations. |
office.restored | office | officectl restore restored a backup whose chain verified. |
There is no budget-stop action: budgets are enforced at admission (run.refused) and by the per-run cap passed to Claude Code.
Run events are written by the office from the harness's output stream (stream-json), outside the sandbox. The agent cannot write ledger entries directly; entries with an agent as actor are written by the office about what it observed.