apprval.md human approval for agent actions

Try the loop

Local simulation: the buttons change this card only. No request is sent, no token is minted, and no log is written.

approval.requested · seq 41 · agent:claude-admin expires 13:07 UTC

Send the deposit chaser to agency@example.co.uk

Computed by the runtime

  • action_keyaprv-042:chaser:2026-09-05
  • classcommunicate.email.external
  • resolvedmanual, by the irreversibility floor
  • reversiblefalse
  • payload_hash6f9b042133d9b1f0a66e4566c901acc2…
  • chain headseq 40, 3e539bd4c77d…

Claimed by the agent, unverified

  • est_cost_usd0
  • summaryChase the outstanding deposit, one recipient, no attachment
$ approval log tail {"event":"task.registered","seq":39,"actor":"agent:claude-admin"} {"event":"approval.requested","seq":41,"actor":"agent:claude-admin"}

The feature set

/ or ⌘K
shipped

Attestation and hash-mismatch

approval policy attest stores the file's SHA-256; a later edit refuses with policy-not-attested, detail hash-mismatch.

README: define what needs approval →
shipped

Five autonomy levels

human-only, manual, supervised-live, supervised-retro, autonomous; bare supervised is an alias of supervised-retro.

schema/policy.schema.json →
shippedspec

The irreversibility floor

An action declared reversible: false raises nonmanual autonomy to manual; human-only stays denied.

SPEC.md section 7 →
shipped

Policy loading fails closed

An unparseable policy resolves every class to manual, an unmatched action takes defaults.autonomy, unknown keys fail validation.

README: define what needs approval →
shipped

Single-use execution tokens

Minted on grant, delivered directly or sealed to the requester, and logged by hash; a second spend refuses token-consumed.

README: put approvals on your phone →
shipped

Class comes from the envelope

Class, cost and reversibility are read from the registered envelope, never from a flag at request time.

The envelope, worked through →
shipped

Budgets computed from the log

Class limits and budgets scopes are conjunctive, and consumption is computed from the log over rolling windows.

README: the dictionary →
shipped

Amend a policy with the verb

approval policy amend lands new policy bytes and their attestation together, printing a semantic diff of what changed.

CLI reference: policy amend →
human-only

The open window

approval gate open requires a terminal and typed understood; gated shell and protected-file bypasses append gate.bypassed.

CLI reference: gate →
shipped

Import your AGENTS.md prose

approval import agents-md turns permissions prose into a draft policy block with a fixed keyword table, no model.

CLI reference: import agents-md →
behind a policy key

Paced or burst delivery

channels.telegram.delivery is paced by default, showing one pending request at a time, or burst.

README: the dictionary →
behind a policy key

Prompt rows a policy orders

channels.<name>.prompt.rows sets order only, never a whitelist, and .hide is refused for rows a decision needs.

CLI reference: channel →
shipped

A terminal channel, and withdrawal

approval channel cli decides at a terminal, and approval withdraw takes a pending request back.

CLI reference: channel cli →
shipped

A channel in the other direction

approval journal write, approval values and approval feedback carry operator guidance that never reaches enforcement.

CLI reference: journal →
newshipped in 0.2.0

A supported API for adapter authors

approval-md/adapters exports the execution contract, credential provider and conformance runner with TypeScript declarations.

Guide: the public adapter API →
shipped

Email over SMTP, inside the token window

approval adapter email verifies the token, re-hashes the payload, opens the vault, sends over STARTTLS, closes it.

Example: real mail, end to end →
shipped

AgentMail, and the two-key split

The agent holds an AgentMail key without draft_send, and the key that can send waits in the vault.

Example: an AgentMail draft →
newshipped in 0.2.0

ZZZ threads and replies, gated

After consuming the execution grant, approval adapter zzz sends with a vaulted ZZZ credential and bound payload.

README: first-class zzz.bot messages →
shipped

A draft is mutable, so the grant binds its bytes

approval payload agentmail-draft snapshots the draft, and one edited after the grant refuses agentmail-draft-drifted.

Example: an AgentMail draft →
shipped

Setup fills the vault and proves it

approval setup adapter email reads the adapter's credential manifest, then probes the server without sending anything.

CLI reference: setup adapter →
shipped

The Claude Code PreToolUse hook

approval hook claude-code classifies commands the harness runs itself and answers allow or deny, never ask.

docs: the Claude Code hook →
shipped

The Cursor hook

approval hook cursor classifies Shell, Write and Delete, and requires failClosed: true.

docs: the Cursor hook →
experimentalshipped in 0.2.0

Codex direct patches, normal activation blocked

approval hook codex experimentally gates direct patches; hidden execution directories keep Bash denied and outcomes diagnostic.

docs: the Codex hook experiment →
shipped

See how a command classifies first

approval hook classify prints a command line's class, the union over its segments, and touches nothing.

README: gate your coding agent →
shipped

A human commits the hook entry

The entry lives in .claude/settings.json or .cursor/hooks.json, and the classifier treats both as gate configuration.

docs: the Claude Code hook →
shipped

Two edges, stated up front

The hook never creates a log, denying hook-log-unreachable, and a hook grant mints no token.

docs: the Claude Code hook →
shipped

The harness path is budgeted and sampled

A covered gated action appends execution.started marked execution: "harness", and budgets and the sampler both draw on it.

docs: the Claude Code hook →
shipped

Codex over the supported stdio MCP route

Codex can use approval mcp serve for gate tools; it does not intercept ordinary tools, and repository setup awaits human installation and attestation.

CLI reference: mcp serve →
shipped

An MCP server over stdio

approval mcp serve publishes the agent's verbs as tools built from the registry behind approval instructions --schemas.

Example: an MCP client asks →
shipped

Grant never travels over MCP

grant, reject, revoke, policy attest and vault set are absent from the tool list.

CLI reference: mcp serve →
shipped

A tool call cannot name an actor

--as is deleted from every published input schema, and the server's identity is appended last to every argv.

CLI reference: mcp serve →
behind a flag

Streamable HTTP, one session each

--http serves streamable HTTP on 127.0.0.1, one transport per session, capped at 20 at once.

CLI reference: mcp serve →
behind a flag

Guest mode, one stranger per connection

--guest mints agent:guest-<6 hex> per session, allowlists nine tools, and clamps wait to five seconds.

Example: the Grok Bot connector →
shipped

One command answers for the chain

approval log verify walks it end to end and reports clean, torn-tail or corrupt, repairing nothing.

CLI reference: log verify →
behind a flag

Anchoring, the check a file cannot make about itself

approval log verify --anchor checks the working file against the newest committed copy this checkout can see.

CLI reference: log verify →
behind a policy keyhuman-only

Human-signed checkpoints

approval log checkpoint signs the head with an Ed25519 key from audit.checkpoint_keys; a due checkpoint warns, never refuses.

CLI reference: log checkpoint →
behind a flag

Git evidence, a second independent record

approval daemon run --git-evidence commits per tick in the log's own repository, changing no verdict.

docs: git evidence →
shipped

Read and verify the log

tail prints the last N records and export streams every stored line byte for byte, both read-only.

CLI reference: log →
shipped

Syncing a log two writers touched

Hash chains do not survive a git merge, so approval log sync reconciles a committed copy that moved.

CLI reference: log sync →
shipped

The whole gate in one foreground process

approval up runs the daemon loop and every channel the policy configures, restarting one that falls over.

CLI reference: up →
shipped

A preflight that fast-forwards and rebuilds

approval up fetches, fast-forwards when safe, rebuilds a stale dist/, then re-execs the freshly built binary.

CLI reference: up →
shipped

It refuses rather than choosing for you

The preflight makes exactly two writes, a --ff-only merge and npm run build; anything else stops it.

CLI reference: up →
behind a policy key

Supervised-live draws

supervised-live with a live_rate blocks that fraction on the gate, selected by HMAC over the payload_hash.

SPEC.md section 5.2 →
behind a policy key

Retrospective sampling on a secret you hold

audit.supervised_sample_rate escalates a fraction of supervised actions for review, under a secret the policy only names.

README: the dictionary →
behind a policy key

Incremental read proof

daemon.read_proof: incremental hashes only the appended bytes, re-proving in full on a configured cadence.

README: the dictionary →
shipped

Start it at login, having read the unit

approval setup service writes the launchd or systemd user unit and prints it for you to read.

CLI reference: setup service →
shipped

Secrets live in the OS keystore

approval setup channel telegram stores the token in the OS keystore, and .approval/env records only where it lives.

CLI reference: env →
shipped

doctor: one command for the machine

approval doctor checks the build, identity, attestation, the chain, the channels, the vault and more, repairing nothing.

CLI reference: doctor →
shipped

status reports the system, doctor the machine

approval status answers from the log; approval doctor answers for this machine, where a stale binary shows up.

CLI reference: status →
shipped

coverage joins witnesses this project does not write

approval coverage reports effects witnessed by git, gh and providers, each with an evidence seq or none.

CLI reference: coverage →
shipped

Conformance vectors for a second implementation

Six language-neutral vector files cover policy, chain, gate verdicts, JCS, refusal unions and schema, under a SHA-256 manifest.

The conformance suite →
shipped

Exit codes an agent branches on

Seven frozen CLI codes; ordinary gate refusals use 1, while hook protocols also use 0 and 2.

The table, below →
shipped

Three check tiers, classified from the diff

npm run check:changed classifies the working tree into light, records or full, and ambiguity resolves to full.

README: running the checks →
shipped

Validate at the write boundary

Every event and envelope passes its JSON Schema before it is appended, and the policy schema is closed.

schema/event.schema.json →
example

Request, a tap on a phone, an executed run

A walkthrough against real Telegram, with a scripted twin that runs in CI against a mock Bot API.

examples/telegram-demo.md →
example

An agent's chaser, sent as real mail

The gate, the binding, the vault, the token window and the chain, against a real mail provider.

examples/email-demo.md →
example

The same grant, over AgentMail

The mail is composed as a draft, and the key that can send it waits in the vault.

examples/agentmail-demo.md →
example

An MCP client asks, the phone grants

Claude Code on one side and a phone on the other, including the refused grant tool call.

examples/mcp-demo.md →
example

A Backlog.md board behind the gate

One envelope on one task file, then register, request, wait and run, with what each prints.

examples/backlog-md-project →
example

Demo day: a web agent behind the gate

One laptop, one phone, one projector, with the queue and the log on screen for the room.

examples/web-agent-demo →
example

A Grok Bot agent on the far end

An agent in xAI's cloud asks for a branch push and an email, then tries skipping the gate.

examples/grok-bot-connector →

How this compares

Four kinds of tool already cover part of this problem. Each entry says what it gives you and what it leaves to you.

  • 1Harness-native permissions: Harness permission and isolation controls are product-specific. This project's Claude Code and Cursor hooks append its portable log for covered actions; Codex direct patches remain experimental.
  • 2AGENTS.md permissions prose: approval import agents-md converts permissions prose into a draft policy. Enforcement still depends on wired hooks and adapters.
  • 3Framework approval steps: A framework integration can call approval.md at its existing pause or approval step. Policy, human channels and the verifiable log remain in approval.md.
  • 4Hosted approval services: With approval.md, you operate the daemon and channels and keep the log yourself. Hosted services vary in routing, retention and operational support.
  • 5What is different here: An attested markdown policy and a hash-chained local log, with optional vault-backed adapter credentials and single-use grants on paths that require human approval.

Global invariants

SPEC.md section 11.1 holds these across every surface of the runtime. They bind every task, and each is pinned by a named test file.

  • 1Enforcement paths read only verified records. Gate decisions are computed from log state that has passed chain verification, and a surface that cannot reach the log fails closed.
  • 2Gate-typed events never accept caller timestamps. ts is assigned by the runtime at the write boundary, and a caller-supplied value is refused.
  • 3Raw secrets never appear in the log. What appears is a hash, or ciphertext sealed to a recipient key the log does not hold.
  • 4Self-reported fields never reduce scrutiny. Values authored by the party under oversight may raise scrutiny and never lower it.
  • 5Every check-then-append passes through compare-and-append. No path reads decision-relevant state and appends on it without the atomic head check.
  • 6Refusals are machine-readable and distinct, every code union is pinned by a test, and the unions are frozen public API.
  • 7Configuration is never loaded implicitly from the working tree. The environment a gate operation runs under is established by the human who launched the process.
  • 8A gate verdict whose event cannot be appended is a refusal. No surface returns proceed, prints an allow, or hands back a token before the record exists.
  • 9human-only classes are inert to agents. Every gate verb refuses one with the same stable code, class-human-only, and appends no record on that path.
  • 10Guidance never reaches enforcement. The values block and the feedback reactions are human-authored guidance, and no path computing a verdict, a draw, a budget or a token reads either.

Exit codes

An agent branches on the exit code before it reads stdout, so these numbers are frozen. Code 1 and code 4 stay apart because a tampered log and an unreadable one are different facts.

CodeMeaning
0success; harness pre-execution allow or deny verdict
1integrity failure, or an ordinary gate/runtime refusal
2usage error, or a post-execution hook report that did not land
3torn tail
4I/O error
5missing execution token (approval run only)
6timeout (approval wait only)

What this does not defend

From SPEC.md section 11. This is an oversight layer for broadly cooperative agents. Hard enforcement applies where an adapter alone holds the credential. Human identity in v0.1 is config-declared, so the trust boundary is the local machine, and anyone who can set that configuration and write to the log is inside it. Attestation proves that someone with local control signed off, and never who.

Undefended: an agent with direct credential access outside the adapters, a compromised host, collusion between an agent and a careless approver, and persuasion about whether bytes you did see should be approved. You run the daemon and listener yourself. The optional command sandbox has platform limits; it does not isolate a whole agent harness. The vault and adapter protect actions routed through them; single-use grants bind the payloads that require human approval.

Read on