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

# Long-Running Operations

# Long-Running Operations

Agents don't always complete work in a single turn. Sometimes an agent needs to pause — waiting for a human decision, external data, credentials, or plan approval — before continuing. Svantic handles this natively through **long-running operations**.

A long-running operation is any agent action that cannot be resolved immediately. The agent pauses, the platform persists the pending state, an external party provides a resolution, and the agent resumes from exactly where it left off.

Human-in-the-loop (HITL) is the most common example, but the mechanism is the same regardless of what the agent is waiting for.

***

## How It Works

```mermaid theme={null}
flowchart TD
    A[Agent running] --> B[Calls a long-running tool]
    B --> C[Tool returns undefined]
    C --> D[pending_request written to DB]
    D --> E[Turn ends — agent paused]
    E --> F[Notifications sent via channels]
    F --> G[External party resolves request]
    G --> H[Mesh detects resolution on next turn]
    H --> I[Agent resumes from where it paused]
```

The agent, the resolver, and the notification system are fully decoupled. The agent doesn't know who resolved the request. The resolver doesn't need to know anything about the agent's session internals — they just provide an answer.

***

## Types of Long-Running Operations

### 1. User Input Collection

The agent needs information from a human — credentials, configuration values, categorization choices, file paths.

**How it triggers**: The agent calls `request_user_input` with a list of form fields.

```mermaid theme={null}
flowchart TD
    A["Agent: I need database credentials"] --> B["request_user_input with form fields"]
    B --> C["Form rendered in Dashboard / Slack / Email"]
    C --> D["User fills in the form"]
    D --> E["Agent receives credentials and continues"]
```

**Sensitive fields** (type `password`) are never shown in email or Slack notifications. Those channels display a "Complete in Dashboard" link instead. The dashboard form submits directly to a secure endpoint.

### 2. Tool Approval

A policy requires human approval before a specific tool can execute. The agent doesn't explicitly pause — the policy engine intercepts the tool call and creates the approval gate.

**How it triggers**: A policy with `enforcement: "require_approval"` matches the tool call.

```mermaid theme={null}
flowchart TD
    A["Agent calls: bulk_delete"] --> B["PolicyEngine evaluates"]
    B --> C["enforcement = require_approval"]
    C --> D["Approval form shown"]
    D --> E["Human clicks Approve"]
    E --> F["Agent executes and continues"]
```

### 3. Plan Approval

The orchestrator created a multi-step plan and the approval policy requires sign-off before execution.

**How it triggers**: The planner agent produces a plan, and `PlanApprovalPolicy.evaluate()` determines it needs approval based on the tenant's configuration.

```mermaid theme={null}
flowchart TD
    U["User: Migrate the staging database"] --> P["Planner produces 4-step plan"]
    P --> PA["PlanApprovalPolicy: requires approval"]
    PA --> R["Plan rendered in Dashboard"]
    R --> H["Human reviews and approves"]
    H --> X["Plan compiled and executed"]
```

***

## Resolution Surfaces

A pending request can be resolved from any of these surfaces. The resolver only needs to provide the decision — no session context, no agent internals.

### Dashboard

Navigate to **Approvals** in the sidebar. Pending requests appear as actionable cards. Click Approve/Deny for simple approvals, or fill in form fields for structured input.

### Slack

When a Slack notification channel is configured and linked to the policy, the message includes interactive buttons. Clicking a button resolves the request directly from Slack. For complex forms (multi-field input or sensitive data), the Slack message shows a "Complete in Dashboard" link.

### API

```bash theme={null}
# Authenticated (with JWT)
POST /messages/{message_id}/resolve
Authorization: Bearer <jwt>
{
  "resolution": { "decision": "approve" }
}

# Unauthenticated (with callback token)
POST /messages/{message_id}/resolve
{
  "callback_token": "<token-from-notification>",
  "resolution": { "decision": "approve" }
}
```

The `callback_token` is included in every outbound webhook and email notification. It allows external systems to resolve requests without Svantic credentials.

### Webhook Callback

If a webhook notification channel is configured, the outbound payload includes:

```json theme={null}
{
  "type": "approval_required",
  "message_id": "msg-abc123",
  "session_id": "session-456",
  "callback_token": "rAnDoM_base64url_token",
  "resolve_url": "https://your-gateway/messages/msg-abc123/resolve",
  "a2ui_spec": { ... }
}
```

The receiving system can POST back to `resolve_url` with the `callback_token` and resolution to complete the request.

### Programmatic

Another agent or service can resolve a pending request via the gateway API. This enables automated escalation chains — if no human responds within N minutes, a fallback agent auto-approves or denies.

***

## Expiration

Pending requests can have an `expires_at` timestamp. If no resolution arrives before expiration, the `PendingRequestReaper` marks the request as resolved with `resolved_by: "system:expired"`. The agent receives an expiration signal on the next turn.

***

## Race Conditions

If the same request is resolved simultaneously from multiple surfaces (dashboard and Slack, for example), the first resolution wins. The database uses an atomic `UPDATE ... WHERE resolved_at IS NULL` to prevent double-resolution. Subsequent attempts receive a `409 Conflict` response.

***

## For SDK Developers

If you're building an agent with the Svantic SDK, long-running operations are handled automatically:

1. **Tool gating**: If your agent's tool calls are subject to approval policies, the SDK handles the pause/resume transparently. You don't write any special code.

2. **Requesting user input**: Use the `request_user_input` tool in your agent's instructions. The SDK creates the form, writes the pending request, and resumes when the user responds.

3. **Custom long-running tools**: Build your own `LongRunningFunctionTool` using Google ADK. Return `undefined` from the tool handler to signal that the tool is waiting for external input. The mesh's turn resumption system will detect the pending call and inject the response when it arrives.

***

## Configuring What Triggers a Pause

Long-running operations are governed by **policies**. See the [Policies Guide](./policies) for details on:

* Creating policies that require approval for specific tools
* Setting up plan approval thresholds
* Configuring notification channels for escalation
* Using built-in evaluator plugins

***

## How Notifications Work

When a long-running operation starts, notifications are sent through the channels linked to the matching policy. See the [Notification Channels Guide](./channels) for details on:

* Configuring email, Slack, webhook, SMS, and phone channels
* Template customization per event type and channel
* Default notification mappings
* Delivery tracking and retry behavior

***

## Further Reading

* [Approvals Guide](./approvals) — step-by-step walkthrough of triggering and resolving approvals
* [Policies Guide](./policies) — creating rules that trigger long-running operations
* [Channels Guide](./channels) — configuring where notifications are delivered
* [A2UI](../concepts/a2ui) — the UI specification format for forms and approvals
* [Guardrails & Safety](../concepts/guardrails) — the safety layers that trigger approvals
