Human approvals
One human decision, bound to one exact payload, spendable once.
An approval not bound to a payload is a rubber stamp.
You added a human gate because somebody should look before an agent moves money. The failure mode is not that nobody approved — it is that the thing approved and the thing that ran were different things. An approval authorising a refund, rather than this refund of £240 on this order, is a standing licence for every refund that agent proposes afterwards.
The second failure is what makes a team switch the gate off. An agent waiting on a human has no callback, so it re-submits, and a gateway minting a fresh approval per re-submission buries the reviewer under duplicates of the one thing they are being asked to decide. The queue fills with rows that are individually valid and collectively unusable.
How it works
Step 7 of the request path. The order is encoded in the evaluator, and where each step sits is the reason the guarantee holds.
- 01
A policy asks for a human, and the action is hashed
The first require_approval match is held; a block anywhere in the same pass wins outright, so nobody is asked to approve what another rule already refuses. The action and its execution context are hashed into one canonical object, the trace is parked, and the request is refused with a 403 carrying the approval id, a status URL and a resume object. - 02
A human decides, with a reason
Deciding needs the operator role or above and an evidence scope covering the trace’s team, and the console requires a written reason. The write is a compare-and-set on the pending state, so when two people decide at once only one is recorded as the decider. - 03
The agent retries the identical request
The retry is hashed again and compared. Consumption is a guarded update, so a second retry is refused as already consumed and a pending re-poll takes no second reservation. Anything undecided expires, and expiring the row and releasing what it held are one transaction.
For traffic routed through the gateway, configured policies can allow, redact, hold or block a request. Detection is heuristic and can miss sensitive data or misclassify a payload.
The request passes the configured checks and proceeds to its provider. Its trace records the policy decision.
A configured rule matches a value and masks or tokenises it before forwarding the payload to the provider. Unmatched values can still pass through.
A policy requires human approval. The approval binds to this payload, expires and can be used once; the caller receives a typed refusal with the approval identifier.
The gateway denies the routed request and records the blocking policy and outcome in its trace.
Shadow mode records policy decisions without changing the request. Review false positives and missed detections before enforcing the policy on live traffic.
An approval is bound to one payload and one context.
Canonicalisation makes the comparison meaningful across two separate HTTP attempts: grants sorted, identities lower-cased, tags sorted. A retry differing only in map ordering hashes identically; one differing in substance does not. The context is in the hash because an approval granted to an agent holding one set of roles must not survive that agent being granted more.
- The action
- The sanitised model request, or the tool name and its arguments. Under an Effect Contract the contract id, version and digests are bound too, so an approval cannot survive the contract changing.
- The subject
- Id, kind, team, tags and effective role grants, normalised so ordering cannot change the hash. A permission added to a role after the human decided produces a mismatch.
- The chain and the human
- The ordered delegation identities with each hop’s grants, plus the on-behalf-of identity and the caller’s session id: everything altering routing, authority or attribution.
- Excluded on purpose
- Request ids, trace ids, credentials and generated session ids. A reconnect must not invalidate a reviewed action, and a fresh trace per attempt must not make an approval unredeemable.
{
version: 2,
subject: { id, kind: agent | seat, team, tags[], roles[] },
delegation: [ { subjectId, roles[] } ], // origin -> caller
onBehalfOf, sessionId, requestTags[], taskTierHint,
semanticHeaders: { … }, // allowlisted
action: { kind: tool_call, tool, args } | { kind: model_request }
}
canonicalJson(object) -> SHA-256 -> payloadHash
Sample data in the real Token Observe interface, recorded from a local evaluation with a temporary database, a mock provider and one request. No live provider key was used.
Approved is a permission, not an execution.
Four states (pending, approved, denied, expired) plus one orthogonal fact: the timestamp recording that it has been spent. Denial is terminal. Expiry is terminal after approval as well as before it, because an approval granted at 09:00 with a 60-minute lifetime must not still authorise the action at 17:00. An expired approval is not a denial: the verdict returns to require-approval and a fresh request is raised.
A parked request keeps its conservatively priced estimate reserved against the agent’s hour, day and month windows, so the queue cannot be used to get past a ceiling. Denial and expiry release the hold in the transaction that ends the wait; an approved but unredeemed approval keeps it, because the money is still about to be spent.
- beat 01Name the caller
- 1Authenticate
- 2Resolve the agent
Nothing is decided for an anonymous caller. The key resolves to one registered agent with a named owner, and its roles, its ceilings and any stop switch in force are read live from the store on every request, not from a cached copy, so a suspension takes effect on the next call instead of after a redeploy.
- beat 02Start the record, then read what is being sent
- 3Open the trace
- 4Sanitise
- 5Scan
The trace is opened before anything can refuse the request, which is why a blocked call is recorded as thoroughly as one that went through. Only then is the text normalised and scanned, in that order, so the detectors see what the model will read rather than what a human reviewer can see.
- beat 03Decide once
- 6Govern
- 6bIntersect with the named human
- 7Enact
One function returns one verdict: allow, block, or hold it for a named person to approve. Every check lives in that one place rather than scattered along the path, which is what makes it possible to read your rules and know what they do.
- beat 04Send it, and only then spend the money
- 8Route
- 9Call upstream
Routing fixes which provider and model will serve the call, so where an agent has a USD ceiling it is tested against the price you will pay, not the one the caller typed. Routing is also the last step that can still refuse locally: after it, the request has left your network.
- beat 05Govern the answer, then count the cost
- 10Govern the response
- 11Meter and record
A tool call the model proposes is judged before it reaches your agent. Metering runs last and on every ending, including the refusals and the streams a client abandoned halfway, because the tokens were spent either way and a partial trace is still evidence.
Step names and their grouping are the product’s own, and the order is encoded in the evaluator rather than drawn for the page.
Each line closes off an assumption a reader would otherwise carry into a proof of concept.
What this does not doWhat this leans on
Policy engine
One deterministic verdict on every governed request: allow, block, redact, or park it for a human.
Spend controls
Hard USD ceilings, per-minute rate limits and a kill switch, all decided before the request leaves your network.
MCP gateway
The half of an agent that moves money is a tool call, not a sentence. This is the checkpoint that call goes through.
Where this comes up
Jobs that draw on it
What is an approval bound to?
The SHA-256 of one canonical object holding the action and its execution context: the subject with its team, tags and effective role grants, the ordered delegation chain, the on-behalf-of identity and the caller’s session id. Request ids, trace ids and credentials are excluded, so a reconnect does not invalidate a reviewed action while a widened permission does.
Does approving in the console make the agent carry on?
No. Token Observe has no channel that reaches an agent; event delivery goes to webhooks, Slack, Teams and email, all human channels. An approval is a permission the agent redeems by repeating the identical request with the approval id, so clicking approve moves nothing until it tries again. The console says so, and shows an approved-but-unspent approval in its own state.
Can the same approval be used twice?
No. Consumption is a guarded update stamping the row only if it is still approved, unspent and unexpired; the loser changes nothing, is told the approval was already consumed, and executes nothing.
Prefer to ask a person? Write to James
Bring us the agent you are least comfortable with.
Create a cloud account and workspace. No card or server installation. Model calls and Google BigQuery scans can incur separate provider charges.