> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Supervise pending tool approvals before they run

> Run the supervisor decision loop on the tool-approval queue: mark high-risk tools for human sign-off, watch pending approvals, approve or deny with a reason, and read the audit trail.

# Supervise pending tool approvals

A tool your agent calls is either **unguarded** (it runs as soon as the model
seeks it) or **approval-gated** (it pauses until a human signs off). This
guide walks the full supervisor loop for gated tools: declare which tools
require approval, watch the pending queue, decide approve or deny, and read
the audit trail. The decision recurs hourly in production, so the loop is
what matters — not the one-time setup.

The endpoint paths below are relative. Send them against
`https://api.orbit.devotel.io/api/v1`. The concepts behind the queue are in
[Human-in-the-loop oversight](/agents/human-in-the-loop-oversight); this guide
is the operational walk.

## 1. When gate tracking acts: pre-execution vs post-hoc

The approval gate is **synchronous pre-execution**: when the model emits a
gated tool call, the executor writes a `pending` approval row and returns a
blocked stub to the model **instead of running the tool**. Nothing touches
the action until a human approves it. After approval the resume worker runs
the deferred call and the conversation continues. That is the only safe
timing that meets "a human saw this first".

**Async post-hoc review** is the opposite shape and is cheaper: let the tool
run now and review the record after. Text-only guardrail violations
(`warn`-severity) work like that, because a wrong reply is recoverable. For
a gated tool call, post-hoc is wrong — a payment capture or a PII egress has
already happened by the time you review it.

Choose pre-execution approval for the calls that hurt; choose post-hoc
warning for the rest. The worked pattern in section 8 shows both in one
agent.

## 2. Prerequisites

* **A supervisor identity.** Approve and reject calls run behind the
  `owner` or `admin` role — reading the list or the dashboard view is fine
  for a `supervisor` role, but the decide step requires `owner`/`admin`.
  Authenticate every call with an API key (`X-API-Key: dv_live_sk_...`) or a
  dashboard session.
* **Tool policy in Agent Studio.** The agent's tools are declared in [Agent
  Studio](/agents/creating-agents) with a per-tool confirmation flag, or
  through the agent API's `safety_config`. Either path marks a tool as
  gated; both feed the same queue.
* **Your tools exist.** The gate fires on a tool the agent actually has. A
  typo in the tool id silently produces an unguarded call — verify the id
  before you rely on the gate.

## 3. Declare which tools require approval

Gate a tool per-call by setting its `confirmation` to `"always"` in the
tool definition. Gate **every** tool on the agent by setting
`safety_config.approval_required: true`. The two flags are additive; either
one firing puts the call in the queue.

```bash theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/agents/:agentId \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "safety_config": {
      "approval_required": true
    }
  }'
```

Prefer per-tool gating. `approval_required: true` stops **every** tool call
and quickly buries the queue under harmless lookups — see the worked pattern
in section 8.

## 4. Surface pending approvals to a supervisor

The pending queue lives on the dashboard at **`/agents`** → the
**tool-approvals** tab, and programmatically at
`GET /api/v1/agents/tool-approvals?status=pending&limit=50`:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/agents/tool-approvals?status=pending&limit=50" \
  -H "X-API-Key: dv_live_sk_..."
```

Each row carries the tool the agent wanted (`tool_id`), the arguments the
model proposed (`tool_args`), the agent and conversation refs, `requested_at`,
and a `cost_estimate` when the tool priced the call in advance — an SMS
fan-out or a payment capture shows the projected spend right on the pause
card. Filter by `status` (`pending`, `approved`, `rejected`) and age the
queue by `requested_at`.

Wire the same `GET` into your own alerting path (an oncall webhook or a
polling supervisor screen) — the JSON shape above is the contract. The
badge-count on the **tool-approvals** tab in the dashboard reads the same
pending list, so the queue is visible without manual polling.

A row that stays `pending` is the failure mode to watch: the turn is
blocked and the customer experience hangs while no one decides. Treat an
aged pending queue as an alarm, not an inbox.

## 5. Resolve with approve or deny

**Approve** lets the deferred call run. `reason` is optional on approve:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/agents/tool-approvals/:id/approve \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Verified the caller on the line." }'
```

```json theme={null}
{ "approval_id": "tapprv_...", "status": "approved", "resumed": true, "warning": null }
```

`resumed: true` confirms the deferred-turn resume job was accepted. If the
resume queue is briefly unavailable the decision still saves
(`resumed: false` + a `warning` string) and the platform retries — the
approval record is the source of truth, no decision is lost.

**Reject** blocks the call. `reason` is **required** so the refusal is
documented:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/agents/tool-approvals/:id/reject \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Refund exceeds the amount on the order." }'
```

Common responses a supervisor sees:

| Status | Meaning                                                                    |
| ------ | -------------------------------------------------------------------------- |
| `404`  | No approval with that id in your organization.                             |
| `409`  | Already decided (approved or rejected) — treat as "not a retryable error". |
| `422`  | You called reject without a `reason`.                                      |

## 6. Timeout policy: what an unresolved pending row means

There is no auto-approve timeout by design: a `pending` row **never**
resolves itself, and an aged pending row simply pauses the conversation
until someone decides. That is the deliberate, safe default — an
auto-approve after N minutes would defeat the point of the gate.

So the **timeout you actually operate is on the queue, not the row**: a
pending approval that sits past a few minutes is a stalled turn, and the
correct action is to escalate the queue to a supervisor, not to let it
auto-resolve. The `warning` string on `resumed: false` is your signal to
route a hard-deny or a manual callback.

If your risk posture truly wants an auto-approve-after-N-minutes rule, the
fall-back lives **outside the gate**: add a scheduled sweep that reads the
pending list (step 4), rejects or approves with a named reason (step 5), and
is explicit in the audit trail (step 7). Run that sweep from your own
operator tooling — never inside the agent.

## 7. Audit — every decision writes the supervisor identity

Both decide endpoints append an audit record with the deciding user, the
tool id, and whether a reason was attached. The audit entry is written
**after** the status update persists, so the trail never claims a decision
that didn't commit. Approvals carry `approved` with an optional-reason flag;
rejections carry `rejected` with the reason length recorded (never the free
text verbatim, which shouldn't spill to a warn-path log). Read the trail in
the [audit log](/guides/audit-log).

## 8. Common pattern — gate high-risk, leave low-risk unguarded

The single most useful shape: gate the few calls that move money or leak
data; let everything else run.

* **Gate**: `payment_capture`, `issue_refund`, `pii_egress` (export / send),
  `cancel_subscription` — anything irreversible or paid.
* **Unguarded**: `lookup_order_status`, `search_knowledge_base`,
  `get_weather` — read-only or recoverable.

Set the gated ones with per-tool `confirmation: "always"` (step 3). If you
set `approval_required: true` on the agent instead, every safe lookup goes
to the queue too and a supervisor spends the shift on trivia. Pick the gate
on the high-risk tools, not the agent as a whole.

Where a `cost_estimate` exists on the gated tool, the pause card shows the
projected spend — use it to reject the obviously-wrong fan-out before the
carrier touches it.

## Related reading

* [Human-in-the-loop oversight](/agents/human-in-the-loop-oversight) — the
  concept page: whisper, approve, take-over.
* [Audit log](/guides/audit-log) — read the decision trail.
* [Custom guardrail DSL](/guides/custom-guardrail-dsl) — declare `warn` /
  `block` / `redact` rules (post-hoc tracking) alongside this gate.
