> ## Documentation Index
> Fetch the complete documentation index at: https://warden.buildlab.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Approvals

> Blocking and async modes, what the reviewer sees, and what happens between a decision and a commit.

When policy says a change needs a human, the agent's operation stops and a proposal appears for review. How the agent waits is your choice.

## Blocking — the default

The agent's HTTP call stays open until someone decides, or `limits.approval_timeout_ms` expires. Simple to integrate: one request, one answer.

Right when a person is watching (Debug Mode, a developer at a terminal) or approvals take seconds.

## Async

```yaml theme={null}
connections:
  - name: shop
    approval:
      mode: async
      async_timeout: 1h
```

Or per request, which overrides the connection default:

```json theme={null}
{ "statement": "UPDATE …", "reason": "…", "mode": "async" }
```

The call returns immediately with HTTP 202:

```json theme={null}
{
  "status": "proposed",
  "operation_id": "op_01M2…",
  "deadline": "2026-09-23T11:00:00Z",
  "poll": "/api/v1/gateway/operations/op_01M2…"
}
```

The agent collects the outcome later:

```bash theme={null}
curl -H "Authorization: Bearer $AK" \
  "http://127.0.0.1:9470/api/v1/gateway/operations/op_01M2…?wait=30"
```

`wait` long-polls up to 60 seconds. While pending you get `proposed`; afterwards, exactly what a blocking call would have returned — `committed` with its `approval`, or `rejected`, `expired`, `stale`, or `denied`.

> [!NOTE]
>
> **Debug Mode always blocks**, whatever the mode says. A person stepping through an agent's traffic live is not waiting on a poll.

## What the agent can rely on

* **A retry returns the same proposal.** Re-sending with the same `idempotency_key` while one is pending gives the same `operation_id`, so a lost ID is recoverable and a reviewer never sees two copies of one change.
* **Nothing runs until a person approves.** Every other outcome — rejection, expiry, a stopped debug session — leaves the database untouched.
* **An approval is re-checked before it runs.** An agent frozen by a circuit breaker while its change waited is refused even though a human said yes, and its database identity is resolved again so the change never runs on a lease that expired while someone was deciding.
* **A stale approval is refused.** If the records moved between the preview and the commit, the result is `stale` and nothing is written. See [previews and fences](https://warden.buildlab.in/concepts/previews-and-fences.md).

> [!WARNING]
>
> **A pending proposal does not survive a gateway restart.** It never ran, so nothing is half-done, but an agent retrying that idempotency key afterwards is told the outcome is unknown and should use a new key. Persisting proposals is on the roadmap.

## Reviewing

Through the browser at `/` ([operator view](https://warden.buildlab.in/operate/operator-view.md)), the terminal (`warden debug attach`), or the control plane:

```bash theme={null}
curl -H "Authorization: Bearer $OK" http://127.0.0.1:9470/api/v1/pending

curl -X POST -H "Authorization: Bearer $OK" -H 'Content-Type: application/json' \
  -d '{"reason":"verified with the customer"}' \
  http://127.0.0.1:9470/api/v1/pending/$ID/approve
```

Approving and rejecting need the **review** capability. Every decision is recorded with the reviewer's name and their reason, and the agent's response says who approved it — an agent told nothing will report an approved change as having run unattended.

## Retention

Outcomes of async proposals are readable for 24 hours after they resolve, then evicted. Pending proposals are never evicted; they end by decision or by deadline.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.