Skip to main content
When a grant resolves to ask, the agent does not fail and it does not guess. The control plane suspends the call, parks it durably, waits for a human decision, and then resumes the exact call that was paused. This is the durable approval substrate: the mechanism a human-in-the-loop control needs to stay trustworthy under real conditions, including process crashes and network reconnects.
This page describes the control plane and the agent runtime beneath it: the layers that evaluate the ask decision, park the call, and resume it. Workbench builds an approver experience on top, but the parking and resuming described here are not app features. The ask decision itself, and who may resolve it, are covered in Guardrails.

The ask decision suspends the call

The grant engine’s effect decides what happens to a tool call: ask is the effect that suspends. The call is parked and the agent’s turn pauses cleanly, and the model is never handed a refusal it could try to work around.

The lifecycle of an approval

1

Snapshot

At the moment of suspension the control plane captures an approval snapshot: the tool’s name, its description, its input schema, and the actual arguments of the call being made. The approver reviews the real operation, not an opaque id. These fields are captured at suspension and stored with the approval, so the record is complete the instant it exists.
2

Durable park

The suspended call is persisted, not held in memory. Two records are written in one transaction: an approval row and a correlation that ties the eventual decision back to the exact parked call. An approval’s status is pending, and resolves to one of approved, rejected, timeout, or expired. Because the park is durable, the pending approval outlives the process that created it.
3

Resolve

A decision is made through the API: approve or reject. Two properties keep this correct. It is exactly once: the resolver claims the correlation and flips the approval status in a single transaction, so a redelivered decision cannot resolve the same approval twice. And it leaks no ids: an approval id from another tenant is reported as not found before authorization runs, so a caller never learns a foreign id exists by getting a different error.
4

Resume

Approval does not ask the model to try again. The run re-dispatches the exact call it parked, with the snapshot arguments, so the approved operation is the one that runs. A one-shot, in-memory token lets that call clear its own gate once and only once. The agent keeps its place rather than re-inferring and hoping to reissue the same call.
Who may resolve an approval, and how visibility is scoped, is part of the grant model: resolving is itself a grant-gated action, it is pull-based, and nothing is pushed to a human. That is covered under the ask effect in Guardrails.

Rejection and timeout do not strand the agent

A rejected or timed-out call does not leave a dangling request in the conversation. The control plane closes the open tool call with a synthetic error result and lets the model infer once more, so the agent continues from a coherent history instead of a stranded one.

Durable under failure

The substrate is built to survive the failures a long-running approval will actually meet.
  • Child crash. If the process running the agent dies while a call is parked, a fresh run re-drives its durable log and re-offers the same park. The pending approval is not lost with the process.
  • Hub reconnect. When a supervisor reconnects, it re-queries its durably parked correlations and re-registers them. The hub deduplicates on a unique constraint, so a reconnect cannot create a second copy of the same pending approval.

Resolving through the API

Approvals are resolved through the tenant-scoped API under /api/tenants/:tenantId/approvals: The admin UI has a list view and a detail view, and the detail view is read-only by design: it shows the snapshot and status but carries no approve or reject control. Resolving is API-only.

What this substrate does not do

Documenting the control means documenting its edges. Today, on the agent-step path:
  • There is no standing “approve always.” The control plane resolves an approval as approve-once or reject; the API rejects scope: "always". Durable auto-approval exists one layer up, as a Workbench capability, and is described in Guardrails.
  • Approvals are pull, not push. Nothing is routed, assigned, or notified to a human. Approvers find pending work by reading the list, and the appropriate approver is defined by who holds the resolve grant. Workbench adds an inbox and notifications on top; the control plane does not.
  • There is no approval deadline. An agent-step suspend parks with no timeout and holds indefinitely. The reactor’s own in-process gate has a one-hour default, but that governs an in-process wait, not the durable park, and nothing populates a per-approval deadline on this path.
  • The UI cannot resolve. The detail page is read-only; resolution is API-only.
  • It carries approvals only. The correlation machinery currently supports a single kind of signal, the approval. It is a durable approval substrate, not yet a general signal system.
  • A failed decision delivery is not retried. The decision is persisted, but redelivering it to a parked run after a failed push is deferred.
“What if nobody approves?” The call holds. An agent-step approval parks indefinitely and waits: it is never silently allowed, and it is not auto-expired on this path. It stays pending until a human approves or rejects it.