Skip to content

Principal Approvals

Marcus Richards edited this page Oct 7, 2026 · 2 revisions

Principal Approvals

A permit can carry an approval threshold. Spends at or below it go through the normal gate. Spends above it wait for a human.

Flow

  1. The agent spends more than the threshold.
  2. The pipeline runs a read-only eligible() probe. If the gate would refuse anyway, the spend falls through to check() and gets an ordinary BLOCKED receipt. No approval request is made.
  3. If the probe passes, an approval request is created. Receipts: APPROVAL_REQUESTED and APPROVAL_PENDING. Result: pending_principal_approval with an approval_id. No cap is reserved.
  4. The principal approves or denies. Receipt: APPROVAL_DECIDED.
  5. The agent calls complete_approved_spend(approval_id). This re-runs the full spend with the stored permit, amount, merchant, predicate and artifact hash.

Rules (from code)

Rule Code fact
Approval is a word, not a lock Completion re-runs check(). If the budget moved, it fails closed
Binding spend(..., approval_id) rejects any mismatch on permit, amount or merchant: invalid_approval
Single use Status becomes consumed once the spend reserves
Denied is final Only approved can complete (approval_not_approved)
Expiry Default TTL 15 minutes (ttl_minutes=15). Expired lazily, recorded with actor system
Failed recheck An unconsumed approval stays approved. The agent may retry
Statuses pending, approved, denied, expired, consumed
Tighten-only A threshold can be set or lowered, never raised. See Tighten-Only Narrowing

Where the principal decides

  • Dashboard (dashboard/server.py): POST /api/approvals/<id>/approve or /deny. Recorded with actor dashboard.
  • The JSON service server.py has no approval endpoints. Its POST /api/permits does not accept a threshold either. On the service, the only way to set a threshold is POST /api/permits/<id>/tighten with approval_threshold_cents.

Demo and tests

  • demo_approval.py (LLM agent, dashboard on port 8474): auto-allow, approve, deny, then approve followed by a cap tighten, which fails closed.
  • tests/test_approval.py: 18 tests. That includes test_approval_fails_closed_when_parent_revoked (PR #10): the permit is revoked while an approved request is pending, completion is refused, PayPal is not called, and the approval stays approved.

Clone this wiki locally