# Approval latency budget for safer agent actions

Agent approvals fail in two opposite ways. Make every action ask a person, and the agent spends its day waiting behind a review queue. Remove the decision point because waiting hurts, and the agent receives authority nobody can meaningfully supervise.

An approval latency budget prevents both failures. It states how long a human decision may take for a given class of agent action, measures where that time goes, and forces a redesign when routine work cannot fit. The budget is not a target for people to click faster. It is a constraint on the workflow and on the authority you hand to an agent.

I have watched teams treat an approval prompt as proof of control, then find a developer approving twenty near identical requests while trying to finish their own work. That person is not reviewing. They are acting as a slow relay. The fix is rarely a better reminder notification. It is usually less ambiguous authority, fewer needless calls, and a clearer escalation path for the actions that deserve friction.

## An approval latency budget is a deadline for a human decision

An approval latency budget is the maximum acceptable time between a request becoming reviewable and a final allow or deny decision. It must vary by action class because an agent reading a pull request, creating a temporary test resource, and changing a production access setting do not have the same urgency or consequence.

Treat the budget as part of the action contract. If an agent needs an answer inside two minutes to keep an interactive task moving, the system must either make that action easy to judge in two minutes or avoid asking for it during ordinary work. If a task can safely wait until morning, do not pretend it needs an interruptive prompt.

The budget has three parts:

- **Queue wait**: time from request creation until a reviewer opens it.
- **Decision time**: time from opening the request until allow or deny.
- **Dispatch time**: time from the decision until the action begins or fails.

Many teams combine these into one number and call it approval time. That hides the repair. A twenty minute queue wait calls for routing, ownership, or scheduling changes. A twenty minute decision time means the request lacks context, contains too much authority, or asks for a judgment that should never have reached a hurried human.

Set budgets around work deadlines, not around an abstract security tier. A reasonable starting table might look like this:

| Action class | Example | Decision budget | Timeout behavior |
| --- | --- | ---: | --- |
| Immediate, low consequence | Read a build status or list a repository branch | 5 minutes | Deny and let the agent report the block |
| Interactive, bounded write | Create a named test issue or update a draft comment | 10 minutes | Deny, preserve the request for later inspection |
| Scheduled maintenance | Rotate a nonurgent integration setting | 4 business hours | Hold for the assigned reviewer or reschedule |
| High consequence | Delete data, change access, publish externally | Explicitly assigned window | Deny on timeout and escalate to a named owner |

These are examples, not a universal policy. A deployment team with an on call rotation may have a different window than a solo developer. The point is to name the expectation before the queue forms.

Do not use a service level agreement for this unless you can staff it as one. A budget is a design limit. It tells you that a task cannot depend on an interactive decision if the people who can decide are asleep, in meetings, or handling an incident. The agent should know that too. It can prepare the request, choose a safe alternate path, or stop with an intelligible explanation. It should not keep issuing the same request every minute.

## Measure the request lifecycle rather than a button click

You cannot improve approval delay if your timestamps begin when a notification reaches a phone. Start when the system creates a reviewable action, then record each state transition using a request identifier that survives retries and UI refreshes.

Use a small event record like this. The fields are deliberately boring. Boring records sort, join, and survive an incident review.

```json
{
  "request_id": "req_7f31",
  "run_id": "run_241",
  "action_class": "bounded_write",
  "target": "issue tracker/project-amber",
  "created_at": "2025-03-08T14:02:01Z",
  "presented_at": "2025-03-08T14:02:03Z",
  "opened_at": "2025-03-08T14:09:18Z",
  "decided_at": "2025-03-08T14:10:06Z",
  "decision": "allow",
  "executed_at": "2025-03-08T14:10:07Z",
  "outcome": "success"
}
```

With this shape, calculate queue wait as `opened_at - presented_at`, decision time as `decided_at - opened_at`, and dispatch time as `executed_at - decided_at`. Keep `created_at` too. It catches a quieter defect: a broker that holds a request before anyone can see it.

A query can express the measurements without a complicated analytics stack:

```sql
SELECT
  action_class,
  percentile_cont(0.50) WITHIN GROUP (ORDER BY opened_at - presented_at) AS p50_queue_wait,
  percentile_cont(0.95) WITHIN GROUP (ORDER BY opened_at - presented_at) AS p95_queue_wait,
  percentile_cont(0.95) WITHIN GROUP (ORDER BY decided_at - opened_at) AS p95_decision_time,
  count(*) FILTER (WHERE decision = 'deny') AS denied,
  count(*) AS total
FROM approval_requests
WHERE created_at >= current_timestamp - interval '14 days'
GROUP BY action_class;
```

The exact percentile syntax differs among databases. The measurement does not. Report p50 and p95 for each action class, along with count and deny rate. An average makes a five second experience look fine beside a handful of requests that sat for ninety minutes. The p95 tells you whether the slow tail breaks real tasks.

Also record whether the agent cancelled, retried, or abandoned the task before the decision arrived. A late approval that executes after the agent has selected another path is worse than an ordinary denial. It creates an action that no longer matches the work in front of the developer.

Do not reward reviewers for reducing decision time alone. That turns thoughtful denials into an apparent performance problem. Review the distribution of allows, denials, expirations, and withdrawals together. A sudden fall in denials combined with very short read times often means people have learned that clicking allow clears an annoyance.

## Waiting time and review time point to different defects

Queue wait and decision time share a clock, but they have different causes and different owners. Treating them as one creates bad fixes.

Queue wait grows when requests reach the wrong person, too many people assume someone else will decide, notifications arrive outside working hours, or a reviewer has no reason to interrupt their current task. Adding more notifications often worsens this. It sprays the same ambiguous responsibility across more people.

Decision time grows when the card makes a reviewer reconstruct the agent's intent. A request such as "POST /v1/resources" is not reviewable. The reviewer needs to know the destination, the operation, the object count, the authority used, and the visible consequence. They should not need to open a terminal, inspect source code, and infer whether the request creates a draft or sends a message to customers.

A useful approval card answers five questions in plain language:

1. Which agent run made the request, and which signed process started that run?
2. What external target will receive the action?
3. What will change, or what data will leave the machine?
4. Which bounded authority permits it?
5. What happens if the reviewer denies it or does nothing?

Do not confuse more detail with better context. A full raw payload can bury the only field that matters. Show a concise consequence first, then let the reviewer inspect the command, endpoint, headers with secrets removed, and payload when needed. A reviewer approving a deletion needs the affected object names. A reviewer approving an HTTP read needs the host, path, and query scope. Each action needs evidence that matches its risk.

The recurring error is to optimize queue wait by granting a broad session approval, even though the reviewer was slow because the action was unclear. That converts a context problem into an authority problem. Fix the request description and action boundary first.

The reverse error also appears: teams demand a separate confirmation for each of a hundred ordinary reads because the queue feels unsafe. The queue is unsafe because it causes habituation. Repeated low consequence prompts train a reviewer to approve by shape and timing, not by content. That habit remains when a consequential request appears.

## A review queue is evidence that the workflow needs a different shape

A queue does not merely delay work. It changes the behavior of both the agent and the human. The agent retries, splits a task into smaller calls, or holds an incomplete plan. The human sees an expanding pile and starts clearing it in batches. Each response makes the queue seem less alarming until an unusual request hides among familiar ones.

Consider a common failure. An agent is asked to prepare a release note from issue tracker data. It first reads the project list, then fetches each issue, then reads comments for the selected issues, then creates a draft note. A workflow that requires approval for every HTTP call turns a modest task into dozens of prompts.

At 9:30, a developer approves the first few reads carefully. At 9:45, they have a meeting. By 10:30, the agent has queued retries and related calls. The developer returns, sees a wall of requests to the same service, and approves them quickly. One request creates a public comment instead of a draft note because the endpoint and intended outcome were buried in raw request text. The developer had no realistic chance to distinguish it in that queue.

The bad decision began before the public comment. The workflow made routine reads compete with a visible write. It also made the human carry state across a long interruption. That is a design defect, not a reviewer failure.

Repair it by grouping the work according to meaningful intent. A read session can cover a named service and task duration if its authority cannot mutate anything. A draft creation can request one explicit decision with the draft location and audience. A public publication should remain separate because its consequence changes the review question.

Do not solve this with a blanket instruction that an agent may "use the issue tracker." That sentence hides too much. It does not tell the reviewer whether the agent can read private issues, edit labels, comment publicly, or delete data. Names for scopes must correspond to actions people can recognize later.

The same logic applies to SSH. A request to inspect a service log and a request to run a migration can travel over the same connection, but they do not belong in the same approval class. Connection identity is not action identity.

## Approval scope should follow the consequence, not the transport

A good approval boundary describes what a person is agreeing to. HTTP versus SSH, a command versus an API call, and a local versus remote process are transport details. They matter for implementation, but they do not tell a reviewer whether the action is reversible, external, or expensive.

Start with consequence classes. Read operations may disclose data, so "read" does not automatically mean harmless. Writes differ too: creating a private draft, changing a production setting, and sending a message all mutate state but require different scrutiny. Separate these before deciding which interactions can share an approval.

Then constrain scope along the dimensions that a reviewer can verify:

- Target: a named host, repository, project, or environment.
- Operation: read, create draft, update a specified field, or execute a named command family.
- Object set: the specific records, files, or services involved.
- Duration: one action, one agent run, or a short scheduled window.
- Consequence: private, reversible, externally visible, or destructive.

Avoid scopes built around implementation trivia. "Allow POST requests" is a transport rule, not an approval boundary. A POST may create a draft or remove an account. "Allow command line access" has the same defect. It grants a medium of action rather than an understood outcome.

People often argue for broad session approval because individual requests interrupt flow. They are right about the interruption and wrong about the remedy when the session can mix unrelated operations. A session is appropriate only when its target and permitted consequence stay legible for its entire life. If an agent switches from gathering release notes to changing repository permissions, it needs a new decision.

Use a per action confirmation when the consequence stays high even inside an otherwise trusted run. Publishing, deleting, rotating access material, changing network reachability, and sending information outside the team usually belong here. Do not use per action confirmation as a penalty for unfamiliar work. Use it where each occurrence needs human judgment.

## Redesign routine work before you loosen review controls

When approval latency exceeds its budget, first remove requests that should never have become interactive. That does not mean allowing arbitrary actions. It means making routine work bounded enough that the human can approve the run or task instead of each mechanical subcall.

Work through a slow class in this order:

1. Sample the p95 requests and read the complete sequence around each one. Count retries and duplicate calls separately from necessary calls.
2. Mark the first action where consequence changes. That is often the correct point for an explicit decision.
3. Consolidate deterministic reads under a narrow task scope, with a known target and expiration when the run ends.
4. Split external publication, deletion, permission changes, and broad data export into separate requests.
5. Retest the task with a real reviewer who did not design the workflow. If they cannot state the expected result before approving, narrow the request again.

Batching helps only when the batch itself is reviewable. "Create these four draft issues in project Amber" is a reasonable batch if the card names the four issues and their destination. "Perform all remaining release work" is not a batch. It is an open ended delegation.

Do not make the agent decide its own batch boundary based only on convenience. Give it a task object: target, requested outcome, allowed data sources, and expiry. The agent can gather the calls under that object, but a switch in target or consequence should close the batch. This also improves incident review because the log reflects an actual work unit rather than a long stream of anonymous requests.

Scheduled work needs a different redesign. If a human must approve a nightly maintenance action during the night, the team has created a predictable failure. Either schedule a review window before execution, assign an on call owner with an appropriate budget, or defer the work. Do not disguise unattended approval as automation.

The ability to retry deserves special attention. An agent should reuse the same pending request identifier when the underlying action has not changed. Creating a fresh approval card for each retry manufactures queue volume and destroys the reviewer's sense of sequence. If the target, payload, authority, or intended consequence changes, create a new request and say what changed.

## The approval screen must make the right decision easy

The reviewer needs a compact statement of intent, not an invitation to reverse engineer an agent run. Build the screen around the decision they must make now, then offer deeper evidence without forcing them to dig for it.

Put the action outcome first: "Create a private draft release note in project Amber" says more than a method and path. Put the target beside it. State whether the action reads, changes, deletes, or sends data. If it uses SSH, name the host and show the command in a form that makes redirection, file writes, and privilege changes obvious.

Show the authority context too. A reviewer should know whether the request came from a fresh agent process or an already approved run, and whether this action has a special confirmation requirement. Process identity matters because an approval for one agent process should not silently bless an unrelated process that happens to speak the same protocol.

Sallyport uses a fixed decision ladder for this: a locked vault denies every action, a new agent process receives a session authorization by default, and a credential can require approval on every use. The first session card leads with the process code signing authority, which is the right detail to put in front of a person deciding whether this run is the one they intended to start.

Do not turn the screen into a rules editor. A reviewer facing a deadline should approve, deny, or inspect a concrete request. If a team repeatedly wants an exception, redesign the task scope or the credential boundary outside the moment of interruption. Putting a miniature policy language in the approval path only asks tired people to program security decisions under pressure.

Denial must be informative. Return a reason category the agent can act on, such as expired, wrong target, needs a narrower scope, or human review required. Do not return private reviewer commentary to an untrusted agent by default. The agent needs enough information to stop retrying or choose a safe alternate task, not a transcript of internal deliberation.

## Set ownership and escalation before an urgent request arrives

An approval budget without an owner becomes a wish. Every action class needs a person or rotation responsible for the decision during the period when it can run. A team can delegate review, but it cannot delegate the fact that someone must make the decision.

Define what happens at each budget boundary. At half the budget, the system may notify the assigned reviewer once if the request remains unseen. At the budget limit, a low consequence request can expire. A high consequence request should expire and notify the task owner or on call rotation, rather than remain pending forever. The exact timing is less important than making the state visible and finite.

Use business hours honestly. If a developer runs an agent locally at night and the action needs a teammate's approval, the task may have to wait. That is acceptable. The failure is when the interface implies immediate progress, then leaves the agent retrying against an unattended queue.

Escalation should never broaden authority. An escalated request goes to a better placed reviewer, not to an automatic allow path. This distinction matters during incidents, when urgency makes people tempted to bypass every control at once. Preapproved emergency procedures can exist, but they should describe a narrow action, named ownership, and later review. "Production is broken" is not an approval scope.

Review the people cost as well as the agent delay. If one developer receives nearly all prompts, the team has a routing problem even when median latency looks good. If every reviewer sees every request, the team has built a shared inbox with security labels. Both patterns produce fatigue and weak accountability.

## Audit the queue as a sequence of decisions

A useful audit trail lets you reconstruct more than the final action. It should show the agent run, the request presented to the reviewer, the decision, the actual external call, and any cancellation or retry. Without that sequence, a team cannot tell whether a late approval caused an obsolete action or whether the agent changed its plan after denial.

Keep approval evidence and action evidence connected by identifiers, but do not confuse them. The approval answers who authorized a stated intent. The action record answers what was actually attempted and what the target returned. An investigator needs both when an agent's request fails halfway through or a remote service interprets a payload unexpectedly.

Sallyport projects both a Sessions journal and an Activity journal from one encrypted, hash chained audit log. Its `sp audit verify` command checks the chain offline over ciphertext without needing the vault key, which is useful when someone needs to test log integrity without opening agent credentials.

Use the audit record in a weekly review of exceptions, not as a vault of data nobody reads. Pull the expired requests, the slowest p95 examples, the denied actions, and the actions that ran after a long wait. For each, ask one concrete question: did the delay come from ownership, unclear intent, oversized scope, or a task that should have been scheduled differently?

Do not grade people by approval speed. Grade the workflow by whether it places a comprehensible decision with an accountable person before the task deadline. If repeated review produces only habitual allows, remove that repetition. If a rare action needs careful thought, give it the time, context, and ownership that careful thought requires.

Start by collecting a week of lifecycle timestamps. Pick the single action class with the worst p95 queue wait, inspect ten complete request sequences, and change the boundary that created the most duplicate prompts. That work will tell you more than another dashboard ever will.
