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

# Approvals

> Configure human-in-the-loop approval for tool calls, MCP invocations, sub-agent delegation, and per-run budget limits

Before registry tools, MCP tools, or sub-agent delegation execute, the SDK can pause and wait for **human approval**. One agent-level policy governs all three paths. Budget pauses use the same handler and stream `Approve` path, but they are not gated by the tool approval policy — see [Budget approval](#budget-approval).

When an approval is required, the run **pauses at that tool call** and delivers an `ApprovalRequest` to your handler (or a `CUSTOM` event on the stream). The run resumes only after `req.Respond()` or `AgentStream.Approve()` is called with `Approved` or `Rejected`. If rejected, the tool call is skipped and the LLM receives a clear refusal message, then continues generating a response without that tool result. The overall run does not fail on rejection — only on timeout.

## Policies

Set with [`WithToolApprovalPolicy`](/getting-started/configuration):

| Policy | Behavior |
| - | - |
| `RequireAllToolApprovalPolicy{}` (default) | Every tool call, MCP call, and sub-agent delegation requires approval |
| `AutoToolApprovalPolicy()` | Nothing requires approval — use only when you fully trust the agent |
| `AllowlistToolApprovalPolicy(config)` | Listed tools/skills/sub-agents skip approval; everything else still requires it |

<Warning>
  When you omit `WithToolApprovalPolicy`, the default is **require-all**. If your agent has tools registered, every run will pause for approval unless you set a policy. For automated or trusted agents, use `AutoToolApprovalPolicy()`.
</Warning>

```go theme={null}
approvalPol, err := agent.AllowlistToolApprovalPolicy(agent.AllowlistToolApprovalConfig{
    ToolNames:     []string{"calculator"},
    SubAgentNames: []string{"math-specialist"},
    MCPTools:      map[string][]string{"remote": {"search"}}, // optional
})
if err != nil {
    log.Fatal(err)
}

a, err := agent.NewAgent(
    agent.WithToolApprovalPolicy(approvalPol),
    agent.WithApprovalHandler(approvalHandler),
    agent.WithLLMClient(llmClient),
)
```

Custom tools may implement [`ToolApproval`](https://pkg.go.dev/github.com/agenticenv/agent-sdk-go/pkg/interfaces#ToolApproval) — the configured agent policy overrides tool-level hints when set.

Implement [`AgentToolApprovalPolicy`](https://pkg.go.dev/github.com/agenticenv/agent-sdk-go/pkg/interfaces#AgentToolApprovalPolicy) for fully custom policy logic.

## Run

Set [`WithApprovalHandler`](/getting-started/configuration) whenever approvals can occur:

```go theme={null}
a, err := agent.NewAgent(
    agent.WithApprovalHandler(func(ctx context.Context, req *agent.ApprovalRequest) {
        // Prompt the user, then:
        _ = req.Respond(agent.ApprovalStatusApproved) // or ApprovalStatusRejected
    }),
    agent.WithToolApprovalPolicy(agent.RequireAllToolApprovalPolicy{}),
    agent.WithLLMClient(llmClient),
)

agentRun, err := a.Run(ctx, prompt, nil)
if err != nil {
    return err
}
result, err := agentRun.Get(ctx)
```

For a **non-blocking** wait, use the same handler and select on `agentRun.Done()` before `Get` — see [Non-blocking Run](/examples/nonblocking-run).

### Approval request types

| `ApprovalRequest.Name` | Parse with | Fields |
| - | - | - |
| Tool approval | `ParseToolApproval(req)` | `ToolName`, `AgentName` |
| Sub-agent delegation | `ParseDelegationApproval(req)` | `SubAgentName`, `AgentName` |
| Budget | `ParseBudgetApproval(req)` | `TotalTokens`, `CostUSD`, `AgentName`, `ApprovalToken` |

### Error handling

`Respond` (and stream `Approve`) succeed once per approval token. A second call returns `ErrApprovalAlreadyResolved` — already answered or timed out; usually safe to ignore:

```go theme={null}
a, err := agent.NewAgent(
    agent.WithApprovalHandler(func(ctx context.Context, req *agent.ApprovalRequest) {
        err := req.Respond(agent.ApprovalStatusApproved)
        if errors.Is(err, agent.ErrApprovalAlreadyResolved) {
            return // already answered or timed out
        }
        if err != nil {
            log.WithError(err).Error("approval response failed")
        }
    }),
    // ...
)
```

On Temporal, approval state is durable across reconnects — calling `Respond` / `Approve` again after a crash returns the same sentinel.

## Stream

On `Stream`, approval and delegation requests arrive as `AgentEventTypeCustom` events — not via `WithApprovalHandler`. Parse the event, then call [`Approve`](https://pkg.go.dev/github.com/agenticenv/agent-sdk-go/pkg/agent#AgentStream.Approve) on the stream handle:

```go theme={null}
stream, err := a.Stream(ctx, prompt, nil)
if err != nil {
    return err
}
eventCh, err := stream.Events(ctx)
if err != nil {
    return err
}

for ev := range eventCh {
    if ev == nil || ev.Type() != agent.AgentEventTypeCustom {
        continue
    }
    ce, ok := ev.(*agent.AgentCustomEvent)
    if !ok {
        continue
    }
    if v, err := agent.ParseCustomEventApproval(ce); err == nil {
        err := stream.Approve(ctx, v.ApprovalToken, agent.ApprovalStatusApproved)
        if errors.Is(err, agent.ErrApprovalAlreadyResolved) {
            continue
        }
        if err != nil {
            log.WithError(err).Error("Approve failed")
        }
    } else if d, err := agent.ParseCustomEventDelegation(ce); err == nil {
        err := stream.Approve(ctx, d.ApprovalToken, agent.ApprovalStatusApproved)
        if errors.Is(err, agent.ErrApprovalAlreadyResolved) {
            continue
        }
        if err != nil {
            log.WithError(err).Error("Approve failed")
        }
    } else if b, err := agent.ParseCustomEventBudget(ce); err == nil {
        err := stream.Approve(ctx, b.ApprovalToken, agent.ApprovalStatusApproved)
        if errors.Is(err, agent.ErrApprovalAlreadyResolved) {
            continue
        }
        if err != nil {
            log.WithError(err).Error("Approve failed")
        }
    }
}
```

<Warning>
  `Run` uses `req.Respond()` in `WithApprovalHandler`. `Stream` uses `AgentStream.Approve` with the token from the CUSTOM event. Do not mix the two paths.
</Warning>

## Budget approval

[`WithBudget`](/features/budget) can pause the run when the token or cost cap is reached. That pause is **not** a tool-approval policy decision: the agent still needs `WithApprovalHandler` on `Run` (or `AgentStream.Approve` on `Stream`), and nested sub-agent budget pauses are delivered to the **parent** caller.

Rejecting a budget approval stops the run with `ErrBudgetExceeded`. Approving continues from the current usage; the next pause fires only after another full limit from that point.

Full API, payload fields, and Stream example: [Budget](/features/budget).

## Sub-agent approval

Parent and specialist agents have **independent** policies:

```text theme={null}
Main agent:  RequireAll  → delegate to math-specialist → user approval on main stream
Math agent:  Auto        → calculator inside specialist → no approval
Math agent:  RequireAll  → calculator inside specialist → approval (fan-in on main stream)
```

<Note>
  The main agent's policy governs whether delegation itself requires approval. The specialist's policy governs tool calls **inside** the specialist. Set each independently.
</Note>

## Approval timeout

[`WithApprovalTimeout`](/advanced/timeouts-and-modes) (default: agent timeout − 30s) limits how long the user has to respond. If they do not respond in time:

| Method | On timeout |
| - | - |
| `Run` | `Get` returns error |
| `Stream` | Emits `AgentEventTypeRunError` on the channel |

Approval timeout must be **less than** the agent timeout. See [Timeouts & Modes](/advanced/timeouts-and-modes).

## Reconnecting after approval

On Temporal, if the process crashes while an approval is pending, reconnect the stream and answer if you have not already:

```go theme={null}
savedRunID, savedOffset := db.LoadSavedRun()
stream, err := a.GetAgentStream(ctx, savedRunID)
if err != nil {
    return err
}
eventCh, err := stream.Events(ctx, agent.WithOffset(savedOffset))
if err != nil {
    return err
}

for ev := range eventCh {
    if ev == nil || ev.Type() != agent.AgentEventTypeCustom {
        continue
    }
    ce, ok := ev.(*agent.AgentCustomEvent)
    if !ok {
        continue
    }
    if v, err := agent.ParseCustomEventApproval(ce); err == nil {
        err := stream.Approve(ctx, v.ApprovalToken, agent.ApprovalStatusApproved)
        if errors.Is(err, agent.ErrApprovalAlreadyResolved) {
            continue // answered before the crash
        }
        if err != nil {
            return err
        }
    }
}
```

The CUSTOM approval event is delivered once. If you already responded before the crash, `Approve` returns `ErrApprovalAlreadyResolved`. If you crashed before responding, the token stays open until you answer.

## Example

<CardGroup cols={2}>
  <Card title="Tools" icon="play" href="/examples/tools" horizontal>
    approval and authorizer sub-examples
  </Card>

  <Card title="Non-blocking Run" icon="play" href="/examples/nonblocking-run" horizontal>
    Non-blocking run with approval handler
  </Card>
</CardGroup>

## Related

<CardGroup cols={2}>
  <Card title="Tools" icon="wrench" href="/features/tools" horizontal>
    ToolAuthorizer for programmatic gates before approval
  </Card>

  <Card title="Sub-agents" icon="sitemap" href="/features/sub-agents" horizontal>
    Delegation approval flow
  </Card>

  <Card title="Budget" icon="gauge" href="/features/budget" horizontal>
    Pause a run when the token or cost cap is reached
  </Card>
</CardGroup>


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