# Governed Agent Spend — an open specification

**Version:** 0.1 (draft) · **Date:** 2026-07-09 · **Editor:** SproutPad (sproutpad.ai)
**Canonical URL:** https://sproutpad.ai/spec.md (this markdown file is normative; the HTML rendering at /spec is a convenience)
**License:** CC BY 4.0 — implement it, fork it, cite it.
**Feedback:** spec@sproutpad.ai

## Abstract

AI agents increasingly spend real money on their humans' behalf: domains,
hosting, email, APIs. Today each platform invents its own ad-hoc safety
story, and the human's protection is usually a dashboard alert that fires
*after* the money moved. This specification describes a coherent, minimal
design for **governed agent spend**: a control plane in which every mutating
action is policy-checked before execution, budget-limited by two-phase
reservation, human-approved when irreversible, recorded in an append-only
ledger with an undo path, and — optionally — cryptographically bound to a
human-signed budget mandate that any third party can verify without trusting
the platform's database.

SproutPad implements this specification end-to-end in its live service —
a young one; the live numbers at [/transparency](https://sproutpad.ai/transparency)
show exactly how much (or little) runs through it today. Nothing in it is
SproutPad-specific. The goal of publishing it is that agents, platforms, and
auditors converge on verifiable mechanics instead of vendor promises.

## 1. Conformance

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be
interpreted as described in RFC 2119.

A **conforming governor** is a service that executes side-effecting actions
on behalf of agents and satisfies every MUST in sections 3–8. Section 9
(verifiable mandates) is an optional profile; a governor that implements it
is **mandate-conforming**.

## 2. Roles and vocabulary

- **Human principal** — the accountable person. Holds the payment
  instrument, sets budgets and policy, decides approvals.
- **Agent** — software acting for the human principal over an API (MCP,
  REST, CLI). Authenticates with a scoped, revocable credential. An agent is
  assumed to be *fallible and possibly compromised*: nothing in this design
  depends on agent good behavior.
- **Governor** (control plane) — the service this spec describes. Sits
  between agents and providers; owns policy, budgets, approvals, and the
  ledger.
- **Provider** — the upstream system where side effects land (registrar,
  DNS, compute, payment rail).
- **Action** — one requested mutation (register a domain, deploy a service,
  tear down a project).
- **Task** — the durable handle for a long-running or gated action.

## 3. The response envelope

The envelope is the contract that makes governance *legible to agents*.
Agents cannot read dashboards; everything they need to behave correctly must
be in the response.

**Field names are normative.** The envelope field names in this section are
the exact wire format, in camelCase: `receipt`, `undo`, `budgetRemainingUsd`,
`nextActions`, `blockedBy`, `resolution`, `limit`, `requested`. A conforming
implementation MUST use these spellings verbatim and MUST NOT rename or
restyle them. This is what lets any auditor, SDK, or conformance tool check
any envelope from any implementation in isolation, with no per-vendor
mapping layer — the same property that makes `access_token` in OAuth and
`error.code` in JSON-RPC composable. A machine-readable definition is
published as JSON Schema at
[/spec/envelope.schema.json](https://sproutpad.ai/spec/envelope.schema.json).

### 3.1 Mutating success responses

Every successful mutating response MUST include:

| Field | Meaning |
|---|---|
| `receipt` | What happened: the action, one-time cost, and monthly cost delta, plus provider references for what was created |
| `undo` | How to reverse it: a concrete command/call, or `null` with `irreversible: true` when reversal is impossible |
| `budgetRemainingUsd` | Headroom **net of active reservations** (see §5) — the number the agent may still plan against |
| `nextActions` | The sensible next calls, so every response teaches the next step |

Rationale: an agent that always knows the cost, the undo path, and the true
remaining budget can be *safely wrong* — and its human can audit any single
response in isolation.

### 3.2 Errors

Errors MUST be structured and prescriptive, never bare status codes:

```json
{
  "blockedBy": "policy:max_domain_price",
  "limit": 20,
  "requested": 3200,
  "resolution": { "type": "human_approval", "taskId": "tsk_..." }
}
```

- `blockedBy` MUST name the specific gate in a stable, namespaced
  vocabulary (e.g. `policy:*`, `budget:*`, `auth:*`, `trust:*`, `input:*`,
  `rate:*`). Namespace and gate identifiers are lower `snake_case` — they
  are values, not field names.
- When a limit was exceeded, the response SHOULD carry `limit` and
  `requested` so the agent can relay the exact gap to its human.
- Every error MUST carry a machine-readable `resolution` naming a
  legitimate path forward, or omit `resolution` entirely to state that no
  legitimate path exists. `resolution` is an object whose `type` names the
  path (e.g. `human_approval`, `retry`, `signup`, `update_billing`,
  `upgrade`, `human_action`) plus the artifact needed to walk it — a task
  id to poll, a URL to hand to the human, a hint for a retry. This includes
  input errors: `{"blockedBy": "input:missing_query", "resolution":
  {"type": "retry", "hint": "pass query"}}`. This rule is deliberately
  mechanical so conformance can be graded by a program, not a judgment
  call: every error either names a next step or explicitly has none.
- A human-readable `message` SHOULD accompany the structured fields but
  never substitutes for them.

An agent MUST NOT be able to talk its way past a gate; the only paths
forward are the ones `resolution` names.

## 4. The policy gate (default deny)

- Every mutating operation MUST pass through a policy decision point before
  any provider state changes. There MUST NOT exist a code path that mutates
  provider state without a verdict.
- The policy engine MUST be default-deny with forbid-wins semantics, and
  SHOULD be a declarative policy language evaluated with full context
  (principal, action, resource, price, environment) — not `if` statements in
  application code.
- Policy MUST evaluate spend against `committed + reserved` (§5), so
  concurrent requests cannot each look affordable.
- Verdicts are three-valued: `ALLOW`, `DENY`, `REQUIRE_APPROVAL`.
  `REQUIRE_APPROVAL` MUST return the structured payload of §3.2 carrying the
  task the agent can watch — never a bare 403.
- Spend-*expanding* meta-actions (raising a budget cap) MAY be requestable
  by agents but MUST only be decidable by the human principal.

## 5. Budget two-phase (reserve → commit → release)

Budgets enforced only at commit time race: two concurrent actions can each
fit under the cap and jointly pierce it. A conforming governor MUST use
two-phase accounting:

An **estimate** is an anonymous, side-effect-free price preview. It MUST NOT
create a policy verdict or reservation and MUST say that its price is not held.
A **quote** is project-bound and policy-aware; an allowed quote enters the
reservation lifecycle below. Implementations MUST NOT use those names
interchangeably.

1. **Reserve** — pricing an action (a *quote*) that the policy gate allows
   places a reservation for the full amount, with a TTL (SHOULD be minutes,
   not hours).
2. **Commit** — executing the action converts the reservation to committed
   spend. The committed amount MUST NOT exceed the reserved amount without
   re-evaluation. When execution includes a human-approval wait, the
   governor SHOULD reserve *after* approval (or renew), so a multi-hour gate
   cannot hold headroom hostage or expire mid-flight.
3. **Release** — expiry, cancellation, or failure releases the reservation.
   Compensation paths (§8) MUST release.

All headroom math — including `budgetRemainingUsd` in every envelope and
the number policy evaluates — is `cap − committed − active reservations`.

## 6. Approval gates and the task lifecycle

### 6.1 Tasks

Long-running or gated actions MUST return a durable task handle
immediately. Task status is one of `working`, `input_required`,
`completed`, `failed`; `working` tasks SHOULD narrate a human-readable
stage. Tasks MUST survive process restarts, and reads MUST be
tenant-isolated.

### 6.2 Gates

- A `REQUIRE_APPROVAL` verdict suspends the action and surfaces a gate. The
  decision authority is the human principal's authenticated session — the
  agent MUST NOT receive a capability URL that can decide the gate, only the
  fact that approval is required and where its human decides.
- Gates are single-decision (first write wins) and MUST auto-deny after a
  bounded timer (48 hours is a reasonable default): no gate waits forever,
  and expiry is reported to the agent as a structured outcome.
- The decision, decider, and timestamp MUST land in the ledger.

### 6.3 The approval page carries the trust burden

The page where the human decides is the product's real trust surface, and
it MUST be self-sufficient: the human may have never seen the platform
before and arrives from a link an AI handed them. It MUST show:

- **what** the agent asked for (the action, in plain language),
- **why** — the agent's justification, verbatim, marked as the agent's own
  words (untrusted content, encoded on render),
- **what it costs** (one-time, monthly, or freed monthly), and the live
  budget headroom at page-view time,
- **what approving does and doesn't commit them to** — including the undo
  or exit path, and an explicit irreversibility warning when there is none,
- **what happens if they walk away**: nothing runs, nothing is charged, the
  request expires on its own.

## 7. The append-only ledger

- Every mutation MUST write **exactly one** ledger entry, in the same
  transaction or durable-workflow step as the state change. A mutation
  without a ledger row is a defect. No silent side effects.
- The ledger MUST be append-only, enforced at the storage layer (no
  UPDATE/DELETE grants), including against the operator. Corrections are new
  compensating entries.
- Each entry MUST carry: who (agent credential and, where applicable, human
  principal), what (action + provider references), why (the agent's
  justification — REQUIRED on mutating calls), one-time cost, monthly
  delta, undo information, and the workflow/task id that produced it.
- Undo and teardown plans MUST be computed from the ledger plus a
  desired-state store — never hardcoded. Property to preserve: *if the
  governor created it, the governor can find it and remove it.*
- Customers MUST be able to export their ledger. Exports SHOULD be
  integrity-protected (e.g. a running hash chain) so an auditor can detect
  truncation or tampering without trusting the export endpoint.

## 8. Idempotency and compensation

- All mutating endpoints and all provider-adapter operations MUST accept
  idempotency keys; retrying any failed call MUST be safe.
- Multi-step actions are sagas: each step registers its compensation
  *before* executing; on failure, compensations run in reverse order and
  the budget reservation is released. Compensations MUST themselves be
  idempotent.
- Partial success MUST NOT survive: a purchased domain with no working site
  gets parked and credited; a VM with no DNS gets destroyed. Where a
  compensation cannot be automated, the saga MUST record the manual step
  needed rather than report success.

## 9. Verifiable budget mandates (optional profile, AP2 binding)

Sections 3–8 still require trusting the governor's database. This profile
makes the budget claim *checkable by third parties*.

- The human principal signs an **open AP2 Payment Mandate** — an SD-JWT
  verifiable credential (ES256/P-256, holder key in `cnf`) whose
  `payment.budget` constraint is the spend ceiling. The governor registers
  it (human-gated, ledgered) for a project.
- **Dual enforcement:** when the mode is on, the governor MUST enforce both
  its own cap and the mandate at reserve time; the mandate can only make
  enforcement stricter, never looser. Per the AP2 budget rule, requested
  amount + amounts of previously closed mandates under the open mandate
  MUST be ≤ max — which is exactly the committed-plus-reserved discipline
  of §5. When the mode is on, spend that predates the mandate or carries no
  mandate binding MUST fail closed rather than commit unbound.
- **At commit,** the governor MUST emit a closed mandate chain + receipt
  bound to the specific ledger entry (e.g. KB-JWT `nonce` = ledger entry
  id, `sd_hash` over the exact presented closed payment, receipt reference
  bound to the closed-payment token digest) and interleave it into the
  hash-chained audit export.
- **Verification MUST NOT require trusting the governor:** issuer keys are
  published (JWKS at a well-known URL), and verification is possible both
  offline and via a public verify endpoint. A proof stamped by any key the
  human didn't delegate MUST fail verification.
- Honest-labeling requirement: where the presentation key is
  platform-custodied but human-delegated (rather than presented live from
  the human's own device), the governor MUST say so rather than imply
  browser-present holder binding.

## 10. The accountability ladder (informative)

Capability SHOULD scale with accountability. A workable rung structure:

- **Rung 0 — anonymous:** read-only planning (search, price, estimate). No
  side effects, ever. Anonymous free-tier deploys are an abuse magnet
  (phishing infrastructure) and SHOULD NOT be offered.
- **Rung 1 — payment-anchored:** a nominal tokenized payment (the payment
  identity, not the revenue, is the point) unlocks tightly capped,
  TTL-bounded scratch resources that clean themselves up.
- **Rung 2+ — claimed:** a human principal with billing on file unlocks the
  full product under §§3–9.

Every live artifact has an accountable identity behind it; friction is
deferred to the moment capability increases, not spread over discovery.

## 11. Conformance checklist

A governor claiming conformance can be audited with these questions, all
answerable from its public surfaces. Items 1–2 are graded mechanically by
the published envelope schema
([/spec/envelope.schema.json](https://sproutpad.ai/spec/envelope.schema.json))
and the runnable conformance checker that ships with the reference
implementation (`evals/conformance.mjs` — point it at any deployment):

1. Does every mutating response carry `receipt`, `undo`,
   `budgetRemainingUsd`, `nextActions` — under exactly those names?
2. Is every error structured with a namespaced `blockedBy` and a
   machine-readable `resolution` naming a legitimate path forward (or
   explicitly absent because none exists), validating against the
   published envelope schema?
3. Can any mutation bypass the policy gate? (The answer must be no, by
   construction — not by convention.)
4. Do concurrent quotes reserve, so parallel agents cannot jointly pierce a
   cap?
5. Can an agent decide its own approval gate with any credential it holds?
6. Does the approval page satisfy §6.3 for a first-time visitor?
7. Is the ledger append-only against the operator, complete (one entry per
   mutation), and exportable with integrity protection?
8. Are all mutations idempotent and compensated?
9. (Mandate profile) Can a third party verify committed spend against the
   human-signed ceiling using only published keys?

## Appendix A — example envelopes (informative)

Success:

```json
{
  "data": {
    "receipt": {
      "action": "launch_service",
      "oneTimeUsd": 15.00,
      "monthlyDeltaUsd": 3.00,
      "resources": [
        { "kind": "domain", "provider": "registrar", "externalId": "example.com" },
        { "kind": "compute", "provider": "host", "externalId": "app-1234" }
      ]
    },
    "undo": { "command": "teardown(proj_abc, dryRun: true)", "irreversible": false },
    "budgetRemainingUsd": 5.00,
    "nextActions": ["get_status", "create_inbox", "attach_domain"]
  }
}
```

Blocked, with a legitimate path forward:

```json
{
  "blockedBy": "policy:irreversible_spend_requires_approval",
  "limit": 0,
  "requested": 15,
  "resolution": {
    "type": "human_approval",
    "taskId": "tsk_9f2",
    "note": "Your human decides in their dashboard; poll get_task."
  }
}
```

Both examples validate against the published
[envelope JSON Schema](https://sproutpad.ai/spec/envelope.schema.json).

## Appendix B — changelog

- **v0.1 (2026-07-09):** first public draft, extracted from the running
  SproutPad implementation (a young service — see /transparency for its
  honest scale). Published with the normative camelCase wire format, the
  machine-checkable `resolution` rule, and the envelope JSON Schema.
