Research / Agents and LLMs

P55 · empirical · draft

Approval as a Type Judgment: Auto-Approve-Unless-Irreversible for Agent Tool Use

partial empirical Agents and LLMs

State of the work. Mechanism, tests and a formal model row are on main; there is no measurement of prompt burden or of false auto-approvals yet.

This is a draft, not peer reviewed. Repository references link to revl at commit 45bec431f.

Planned to be combined with P45 (Quorum Approval With a Durable Decision Graph and an Override That Is Not a Vote), P26 (Spent Authority Must Outlive the Generation: Seven Hot-Swap and Reload Bypasses in an Agent Approval Gate) into a single paper.

Pitch

An agent harness can decide "ask the human or not" from the checked effect class of each call at one chokepoint, bind every yes to a hash of exactly what it approves, and so prompt only for actions the checker cannot show are revertible or deferrable.

Abstract

Agent harnesses decide which tool calls need a human with allowlists of tool names or with self-declared hints such as "read-only". Both fail the same question: which actions escaped? We present a permission policy in which the decision is a type judgment. A call's class is the worst class over every boundary crossing its checked reach contains: (a) revertible, because the checker found a registered inverse; (b) deferrable, because its typing forbids it from firing before a commit prompt; (c) immediate, an emission with neither. Policy rules name boundary capabilities and component globs, not tool names, and a new tool is classified by the checker rather than by a list entry. Class (a) proceeds silently, (b) is enumerated at commit, and (c) returns a ticket whose hash covers the call, its arguments and the reach closure of the code it runs; a yes is single-use, expires, dies when any provider in the closure changes, and is spent durably before the action fires. The decision sits in one function, so replay and activation bodies cannot route around it. Approval is also a typed effect in the language (await approval), and repeated approvals in the ledger can be offered back as a typed, budgeted, revocable policy rule with a dry-run blast radius. We describe the mechanism and the tests that pin it, and we state plainly that this paper has no prompt-count measurement and no safety-side measurement yet.

Problem and claim

Current permission systems for tool-using agents are lists. A list of safe tool names cannot enumerate the side effects of a permitted tool, and a hint such as read-only is a claim by the tool author. The user then sees either a prompt for every call, which trains approval by reflex, or a broad standing grant, which removes the check. A prompt whose answer is a boolean held in harness memory can also be replayed for a different call, outlive the code it approved, or be spent twice.

The claim is narrow. If the effect class of a call is derived by the checker, and the policy can read only that class, then (1) the calls that proceed without a per-call prompt are those the checker proves revertible (class a) or deferrable (class b, which waits for the commit prompt; class a also depends on its inverse succeeding, docs/design/246-auto-approve.md:137-142), and (2) an action missing from the enumeration is a compiler bug, not a policy gap (docs/design/246-auto-approve.md:577). The yes is as checked as the no: it is bound to a hash, so what the human approved is exactly what fires.

We do not claim fewer prompts than every alternative on every workload; no such measurement exists here. The design doc names its own caveats: reversal does not undo observation, concurrent-writer races are time-of-check bounded, a compensated crossing still left the system (docs/design/246-auto-approve.md:599-606).

Contributions

  1. A decision table over three derived action classes, evaluated at a single chokepoint inside Session.call (not keyed on tool names), with the class map computed per generation over every provide-method and every activation body, so load and swap are gated as boundary calls too (docs/design/246-auto-approve.md:108-250; src/revl/mcp/approval.py:214).
  2. A ticket and ledger discipline: exact-hash tickets over a reach closure, outstanding-ticket table that refuses hashes the server never issued, consume-before-fire durable spend in the WAL, cross-session and cross-component replay refused, a join of spend to emission so an owed action is visible (docs/design/246-auto-approve.md:251-300,476-542; src/revl/wal.py:244).
  3. Approval as a typed effect: capability C requires approval owned by the operator's policy file, await approval[C] yielding an unforgeable Approval[C], and a checker obligation that a crossing carries a covering with edge; the formal model states the same floor (docs/design/246-auto-approve.md:359-475; formal/RevL/Theorems/G4_ApprovalFloor.lean; tests/test_approval_typed.py).
  4. Approval distillation: repeated approval shapes in the ledger are offered back as a typed AutoApproveRule that an operator could have written by hand, with a never-standing clause, a budget, attribution, revocation and a blast radius computed by a pure fold over past decisions; prompts per session is the intended evaluation metric (docs/design/251-approval-distillation.md:99-450; src/revl/distill.py:370).

Evidence on main

Claimpath:line or testStatusNote
Decision table, three classes, worst class over reachdocs/design/246-auto-approve.md:108-143; src/revl/mcp/approval.py:214 (ClassMap)shippedDesign doc header says "design proposed, revised after review"; code and tests exist
Class map classifies each op and activation reach from checked factstests/test_approval_policy.py:134,143shipped
Witnessed effect auto-approves silently; all-(a)/(b) session fully autotests/test_approval_policy.py:169,184shipped
Class (c) prompts once, fires once, second identical call re-promptstests/test_approval_policy.py:205shipped
Compensated emission still class (c)tests/test_approval_policy.py:386shipped
Activation body cannot dodge the prompttests/test_approval_policy.py:429,493,525shippedCloses the "move the emission into activation" bypass
Replay path goes through the same chokepointtests/test_approval_policy.py:602; src/revl/mcp/session.py:5159shipped
Unknown ticket hash refused; enabled policy requires journalingtests/test_approval_policy.py:635,648shippedDesign: "Refuse, don't degrade"
No policy configured is byte-identicaltests/test_approval_policy.py:660shipped
Double approve never over-mintstests/test_approval_policy.py:243,354shippedFound by the 2026-09 MCP audit, advisory text in docs/v2.0-roadmap.md:1639
Typed approval surface (requires approval, await approval, with)tests/test_approval_typed.py (15 tests); docs/design/246-auto-approve.md:359-475shipped (name-is-the-capability spelling; the dotted emission[production.payment] form is not)Design doc itself flags this at line 376 ("Shipped reality")
Formal floor for approval-required crossingstests/test_formal_approval_row.py:1; formal/RevL/Theorems/G4_ApprovalFloor.leanshippedNot re-run; the module docstring was read
Gate default-on in revl mcp serve, three policies (auto, advisory, off)docs/harness-gate-guide.md:21-23; tests/test_approval_gate_default_1706.py (13 tests)shipped
Spend/emission join in the WALsrc/revl/wal.py:244; tests/test_approval_emission_session_path_1781.py (6 tests)shipped
Standing grants bounded by uses and TTL, never-standing clauseCHANGELOG.md:176-180,231-238; tests/test_standing_approval.py (17), tests/test_distill_enforcement.py (31)shipped
Distiller: shape key, typed rule, cannot-distill verdicts, blast foldsrc/revl/distill.py:370,467; tests/test_distill.py (18), tests/test_distill_metrics.pyshippedDesign 251 header still says "No implementation"; the code and CHANGELOG disagree with it, the code wins
MCP verb for distillation offerssrc/revl/mcp/server.py:2618,5119 (revl_distillation_offers)shippedOnly the offers verb was opened; the verbs for approve, revoke, escalate and quorum were not independently opened

Numbers that exist

Paper outline

  1. Introduction: prompt fatigue and the allowlist's blind spot; the claim.
  2. Background: action classes (witnessed, deferred, immediate), the audit surface, the commit protocol.
  3. The decision table and its single chokepoint; activation and replay as bypass routes.
  4. Tickets: hash over reach closure, single-use, consume-before-fire, crash cuts.
  5. Approval as a typed effect and the operator-owned requirement; the formal floor.
  6. Approval distillation: shape key, typed rule, blast radius, soundness.
  7. Evaluation (to be done): prompt counts against independent baselines, a safety-side suite.
  8. Limits and related work.

Threats to validity and limits

Fact-check

Claims in this draft were checked against revl main at commit 45bec431f: 29 checked, 5 corrected.

Cite this draft

@misc{revlp55,
  author       = {Thomas Moussajee},
  title        = {Approval as a Type Judgment: Auto-Approve-Unless-Irreversible for Agent Tool Use},
  year         = {2026},
  howpublished = {\url{https://inso1337.github.io/revl/research/P55.html}},
  note         = {Research draft, not peer reviewed. Written against revl commit 45bec431f. Licensed CC BY 4.0.}
}

Text licensed CC BY 4.0, author Thomas Moussajee.

← All research drafts