> For the complete documentation index, see [llms.txt](https://docs.interactive.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.interactive.ai/agents/concepts/autonomous-routines.md).

# Autonomous routines

> **Context** — This page covers the autonomous execution mode: what happens between `POST /routines/{id}/trigger` and the callback hitting your service. It assumes you know [routines](/agents/concepts/routines.md). Authoring details: [Authoring autonomous routines](/agents/guides/authoring-autonomous-routines.md); wire shapes: [Events & callbacks](/agents/reference/events-and-callbacks.md).
>
> YAML examples follow **manifest schema 6.1.1**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning.md).

## What changes in autonomous mode

A routine becomes autonomous by declaring an `autonomous:` block:

```yaml
id: kyc-decision
title: KYC Decision
conditions:
  - The KYC verification result for an applicant needs to be processed.
entry: assess
nodes:
  - id: assess
    think: >
      Assess the verification payload. Decide approved, rejected, or
      escalate, with a one-sentence explanation.
    output_schema:
      type: object
      required: [decision, explanation]
      properties:
        decision:
          enum: [approved, rejected, escalate]
        explanation:
          type: string
    transitions:
      - to: finish

  - id: finish
    tools: built-in:emit_output
    tool_instruction: >
      Call emit_output with output_json containing the decision and
      explanation produced by the assess node.

autonomous:
  input_schema:
    type: object
    required: [applicant_id, verification_result]
    properties:
      applicant_id:
        type: string
      verification_result:
        type: object
  output_schema:
    type: object
    required: [decision, explanation]
    properties:
      decision:
        enum: [approved, rejected, escalate]
      explanation:
        type: string
  timeout_seconds: 60
  callback_url_allowlist:
    - api.example.com
```

The same engine runs the same node machinery — but:

|                 | Conversational              | Autonomous                                         |
| --------------- | --------------------------- | -------------------------------------------------- |
| Woken by        | Customer message            | Trigger endpoint or webhook                        |
| Input           | Free text                   | JSON validated against `input_schema`              |
| Output          | Messages streamed as events | One JSON object validated against `output_schema`  |
| Terminates      | Agent speaks                | A terminal node calls `built-in:emit_output`       |
| Result delivery | Event stream                | Signed HTTP callback to your URL                   |
| Session         | Yours, long-lived           | Ephemeral by default (created and deleted per run) |

## The `autonomous` block

| Field                    | Type              | Required | Default                 | Meaning                                                                                                                                              |
| ------------------------ | ----------------- | -------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `input_schema`           | JSON Schema       | yes      | —                       | Validates the trigger/webhook payload. Rejection → HTTP 400, no run.                                                                                 |
| `output_schema`          | JSON Schema       | yes      | —                       | Validates the final output at `emit_output` time. The runtime tightens it with `additionalProperties: false` on all object nodes.                    |
| `timeout_seconds`        | integer ≥ 1       | no       | operator default (120s) | Per-run deadline. Capped by the operator maximum (600s). See [Limits & defaults](/agents/reference/limits-and-defaults.md).                          |
| `callback_url_allowlist` | list of hostnames | no       | empty = any             | Hostnames callbacks may target. An entry starting with `.` matches subdomains (`.example.com` matches `api.example.com`). Disallowed URL → HTTP 400. |
| `webhook`                | object            | no       | —                       | Opts this routine into its own `POST /webhooks/{routine_id}` entry — see [webhook entry points](#webhook-entry-points).                              |

**The terminal rule:** every terminal node of an autonomous routine (a node with no outbound `transitions`) must be a TOOL node calling `built-in:emit_output`. Terminal THINK nodes are rejected at validation time. A run that ends any other way never succeeds — it times out or hits the iteration cap and the callback reports failure.

## Node types

An autonomous routine is built from the same graph of nodes as a [conversational routine](/agents/concepts/routines.md), with two differences driven by the fact that **there is no customer in the conversation**: it uses no CHAT nodes, and it ends by emitting typed output rather than by speaking.

| Node type               | Set by                                      | What it does                                                                                                                                                              |
| ----------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **TOOL**                | `tools` (+ `tool_instruction`)              | Calls one or more tools. Completes when the tool executes. The terminal node is always a TOOL node calling `built-in:emit_output`.                                        |
| **THINK**               | `think` (+ `output_schema`)                 | Typed inference — a model call that returns JSON matching `output_schema`, with no message. Does **not** end the turn, so THINK and TOOL nodes chain freely within a run. |
| **Routing-only** (fork) | no action field, 2+ conditional transitions | A pure branch point — the engine evaluates the outgoing conditions and takes the matching path.                                                                           |

> **No CHAT nodes.** An autonomous run has no conversational customer to speak to — its result is delivered as typed JSON via `emit_output`, not as a message. `chat_state` nodes don't belong in an autonomous routine.

### THINK node — typed inference

A THINK node runs a model call (the built-in `built-in:reason` tool) whose output must match the node's `output_schema`. The runtime tightens that schema with `additionalProperties: false` on every object so the model can't smuggle in extra fields, and a validation failure is a hard error. The validated result is stored at `session.metadata.step_outputs[<node-id>]` and is visible in history, so later nodes — and your `emit_output` step — can reference the typed fields. Because it doesn't end the turn, you chain `think → tool → think → emit_output` within a single run.

```yaml
  - id: assess
    think: >
      Decide approved, rejected, or escalate from the verification
      payload, with a one-sentence explanation.
    output_schema:
      type: object
      required: [decision, explanation]
      properties:
        decision:
          enum: [approved, rejected, escalate]
        explanation:
          type: string
    transitions:
      - to: emit-result
```

A THINK node can never be a routine's terminal node — the run's typed output comes from `emit_output`, so a THINK must always transition onward.

## Run lifecycle

```
caller                       agent server                          engine
  │  POST /routines/{id}/trigger │                                    │
  │ ───────────────────────────► │ validate input against schema      │
  │                              │ check callback_url allowlist       │
  │                              │ create ephemeral customer+session  │
  │                              │ inject input event, wake engine ──►│ run nodes
  │  ◄─── 202 {run_id, ...} ──── │                                    │ …
  │                              │        watcher waits               │ emit_output
  │                              │ ◄── run settles (or timeout) ──────│ (validated)
  │  ◄── POST callback_url ───── │ signed payload, retries on failure │
  │      200 OK ───────────────► │ delete ephemeral session           │
```

1. **Trigger** — `POST /routines/{routine_id}/trigger` with:

   ```json
   {
     "input": {"applicant_id": "app_123", "verification_result": {"status": "GREEN"}},
     "callback_url": "https://api.example.com/agent-callbacks",
     "idempotency_key": "kyc-app_123-2026-06-04",
     "metadata": {"ticket": "OPS-441"}
   }
   ```

   `session_id` may be supplied to run inside an existing session instead of an ephemeral one. `idempotency_key` makes retries safe: a duplicate key returns the prior run (HTTP 409) instead of starting a new one. `metadata` is opaque and echoed verbatim in the callback.
2. **Accepted** — the response is immediate:

   ```json
   {
     "run_id": "run_9f8e7d6c5b4a3f2e1d0c9b8a",
     "routine_id": "kyc-decision",
     "status": "accepted",
     "session_id": "sess_abc123",
     "created_at": "2026-06-04T10:15:00+00:00"
   }
   ```

   HTTP `202`. Everything after this is asynchronous.
3. **Execution** — the engine walks the routine's graph exactly as described in [Conversation lifecycle](/agents/concepts/conversation-lifecycle.md), with the validated input visible to every node as a tool result in history.
4. **Completion** — the routine calls `built-in:emit_output` with the final JSON. The runtime validates it against `output_schema`; on success the run is `succeeded`, on schema violation it is `failed` with `output_validation_failed`.
5. **Callback** — the server POSTs the result to your `callback_url` with `Authorization: Bearer <agent api key>` (the same key you use inbound), retrying with backoff on non-2xx (default 5 attempts). Payload:

   ```json
   {
     "schema_version": 1,
     "run_id": "run_9f8e7d6c5b4a3f2e1d0c9b8a",
     "routine_id": "kyc-decision",
     "status": "succeeded",
     "output": {"decision": "approved", "explanation": "Document and selfie match."},
     "error": null,
     "session_id": "sess_abc123",
     "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
     "started_at": "2026-06-04T10:15:00+00:00",
     "completed_at": "2026-06-04T10:15:21+00:00",
     "metadata": {"ticket": "OPS-441"},
     "idempotency_key": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
     "origin_service": "agent-server"
   }
   ```

   Dedupe deliveries on `run_id`. Return `200` as soon as you've durably enqueued the payload; do heavy work afterwards.
6. **Cleanup** — ephemeral session and customer are deleted after the callback settles.

### Failure taxonomy

`status: "failed"` carries a structured `error`:

| `error.code`                    | Meaning                                                                        | Your usual fix                                                                  |
| ------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
| `timeout`                       | Run exceeded `timeout_seconds`                                                 | Raise the timeout, or shorten/parallelise the routine                           |
| `max_engine_iterations_reached` | Iteration cap hit before `emit_output`                                         | Raise `runtime.max_engine_iterations`, or shorten the routine                   |
| `output_validation_failed`      | `emit_output` payload violated `output_schema` (details include the JSON path) | Fix the schema or the terminal node's `tool_instruction`                        |
| `input_validation_failed`       | Input rejected (also surfaces as HTTP 400 at trigger time)                     | Fix the caller's payload                                                        |
| `engine_error`                  | Internal engine failure during the run                                         | Check traces/logs; see [Troubleshooting](/agents/operations/troubleshooting.md) |
| `tool_error`                    | A tool call failed irrecoverably                                               | Check the MCP server                                                            |
| `session_error`                 | Session preparation/dispatch failed                                            | Check server health; retry with the same `idempotency_key`                      |

## Webhook entry points

Third-party providers (KYC vendors, payment gateways) can fire autonomous routines directly by POSTing to the agent — authenticated by an HMAC signature over the raw body instead of the bearer token. Webhook-triggered runs are **fire-and-forget**: no callback is sent; results live in traces and the run store.

Two declaration styles:

**Per-routine** (`autonomous.webhook` in the routine YAML) — exposes `POST /webhooks/{routine_id}`; the routine fires unconditionally on a valid signature:

```yaml
autonomous:
  input_schema:
    type: object
  output_schema:
    type: object
  webhook:
    secret_env: ${KYC_WEBHOOK_SECRET}
    header: X-Payload-Digest
    algorithm: sha256
    prefix: ""
```

**Manifest-level fan-out** (`agent_config.webhooks[]`) — one URL, `POST /webhooks/{name}`, dispatching to one or more autonomous routines. With multiple routines, the engine evaluates each routine's `conditions` against the payload and fires the one(s) that match:

```yaml
agent_config:
  webhooks:
    - name: kyc-events
      secret_env: ${KYC_WEBHOOK_SECRET}
      header: X-Payload-Digest
      algorithm: sha256
      prefix: ""
      routines:
        - kyc-decision
        - kyc-retry
```

Shared mechanics:

* Signature = `prefix + hex(HMAC(secret, raw_body))` using `algorithm` (`sha256`, `sha1`, or `sha512`), carried in `header`. Verification is constant-time; failure → `401`.
* The secret env var is **re-read on every request**, so rotating it needs no restart.
* The raw JSON body becomes the run's input and must satisfy the routine's `input_schema` (a fan-out match whose schema rejects the body is dropped with a warning).
* Replays dedupe automatically: the idempotency key is derived from the webhook name, routine id, and a hash of the body, so a provider retry returns the existing run (HTTP 200) instead of firing twice.
* These paths bypass bearer auth — the signature *is* the auth. See [Security](/agents/operations/security.md).

The fan-out response enumerates what fired:

```json
{
  "webhook": "kyc-events",
  "matched": ["kyc-decision"],
  "runs": [
    {"routine_id": "kyc-decision", "run_id": "run_0a1b2c3d4e5f60718293a4b5", "status": "accepted"}
  ]
}
```

## Tracing autonomous runs

Each run is one trace. By default the trace is named with a synthetic run id; set `agent_config.traces.trace_id_field` to an input-payload key (e.g. `customer_id`) to name traces after your business identifier instead. The `trace_id` in the callback links the result back to the trace. See [Observability](/agents/guides/observability.md).

## See also

* [Authoring autonomous routines](/agents/guides/authoring-autonomous-routines.md) — step-by-step build
* [Built-in tools](/agents/reference/built-in-tools.md) — `emit_output` and `reason` contracts
* [HTTP API](/agents/reference/http-api.md) — exact endpoint specifications


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.interactive.ai/agents/concepts/autonomous-routines.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
