# Overview

Interactive Agents — build, configure, and operate AI agents that hold conversations, run typed automations, call tools, and ground answers in your knowledge base.

> **Context** — This is the documentation root for **Interactive Agents**: the framework and runtime for deploying AI agents on the InteractiveAI platform. It is self-contained: everything you need to design, build, test, deploy, and operate an agent is in these pages. No source-code access is required or assumed.

## What an Interactive Agent is

An Interactive Agent is a containerized service (the **agent server**) that runs one AI agent. The agent's entire identity and behaviour is declared in configuration — there is no per-agent code:

* A **manifest** (YAML) declares who the agent is, which model(s) it uses, which content it loads, and which systems it connects to.
* **Policies** are condition → action rules that shape behaviour on every turn (safety, compliance, tone, business rules).
* **Routines** are multi-step state machines that walk the agent through structured flows (look up an order, book a car, verify a document).
* **Tools** are functions the agent can call, served by MCP servers you run.
* A **knowledge base** (optional) grounds answers in your documents.
* A versioned **system prompt** and **glossaries** define persona and domain vocabulary.

Agents operate in two modes, often simultaneously:

| Mode               | Trigger                                                | Result delivery                                          |
| ------------------ | ------------------------------------------------------ | -------------------------------------------------------- |
| **Conversational** | A customer message (via the SDK or your integration)   | Typed events streamed back (replies, tool calls, status) |
| **Autonomous**     | `POST /routines/{id}/trigger` or a third-party webhook | A typed JSON result delivered to your callback URL       |

## How this documentation is organized

| Section                                          | Read it when you want to…                                                                       |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| [Concepts](/agents/concepts/architecture)        | Understand how the system works — architecture, the turn lifecycle, every configuration concept |
| [Guides](/agents/guides/quickstart)              | Do something — build your first agent, author routines, connect tools, deploy                   |
| [Reference](/agents/reference/manifest)          | Look up an exact field, endpoint, env var, or default                                           |
| [Operations](/agents/operations/troubleshooting) | Run agents in production — troubleshooting, security, versioning                                |

Pages under `reference/` marked `generated: true` are produced directly from the runtime's source of truth on every release and are exact for the version they document.

## Reading paths

**"I want a working agent today"** → [Quickstart](/agents/guides/quickstart) → [Authoring routines](/agents/guides/authoring-routines) → [Integrating the SDK](/agents/guides/integrating-the-sdk) → [Deploying](/agents/guides/deploying)

**"How do my systems and the agent talk to each other?"** → [Integration overview](/agents/guides/integration-overview) — every traffic direction (SDK, event delivery, triggers, callbacks, webhooks, tools) on one page, with links

**"I need to understand the model before I build"** → [Architecture](/agents/concepts/architecture) → [Conversation lifecycle](/agents/concepts/conversation-lifecycle) → [Policies](/agents/concepts/policies) → [Routines](/agents/concepts/routines)

**"I'm wiring a backend automation, not a chat UI"** → [Autonomous routines](/agents/concepts/autonomous-routines) → [Authoring autonomous routines](/agents/guides/authoring-autonomous-routines) → [Events & callbacks reference](/agents/reference/events-and-callbacks)

**"I operate a deployed agent"** → [Observability](/agents/guides/observability) → [Troubleshooting](/agents/operations/troubleshooting) → [Security](/agents/operations/security)

## For AI agents reading these docs

* [`llms.txt`](https://github.com/Interactive-AI-Labs/interactive-agent/blob/main/docs/agent/llms.txt) is a one-line-per-page index with stable links.
* [`llms-full.md`](https://github.com/Interactive-AI-Labs/interactive-agent/blob/main/docs/agent/llms-full.md) is the entire documentation concatenated into a single file, regenerated on every release — fetch it once and you have the full corpus.
* Machine-readable JSON Schemas for the manifest, routines, policies, glossaries, conversation events, and autonomous callbacks are published per version; see [Versioning](/agents/operations/versioning) for the download locations.
* Every code block in these docs is complete and copy-pasteable — nothing is elided.

## The example used throughout

All guides share one worked example: **DriveAway**, a car-rental agent named Mercedes with six routines (car search, booking, manage booking, locations, loyalty lookup, member signup), seven policies (minimum driver age, licence requirement, stay-on-topic, pricing disclaimer, cross-border restrictions, one-way surcharge, incident handoff), and one MCP tool server backing the booking system. The [Quickstart](/agents/guides/quickstart) builds it from scratch.


# Architecture

How an Interactive Agent deployment fits together: the agent server, the InteractiveAI platform, the LLM router, MCP tool servers, storage, and the knowledge base.

> **Context** — Start here. This page names every moving part; the rest of the documentation assumes you know these names. No prior knowledge needed.
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## The pieces

```
                     ┌─────────────────────────────────────┐
                     │         InteractiveAI platform       │
                     │   content catalog · LLM router ·     │
                     │            traces backend            │
                     └───────────┬─────────────┬────────────┘
          context fetch (boot),  │             │  LLM calls (chat + evaluation),
          secrets, hosting       │             │  OTel traces (per turn)
                                 ▼             ▼
  ┌──────────────┐  SDK / REST   ┌───────────────────────────┐   MCP    ┌──────────────┐
  │     Your     │◄─────────────►│        Agent server        │◄────────►│   Your MCP   │
  │ integration  │  sessions,    │   one agent per container  │   tool   │ tool servers │
  │  (UI, CRM,   │  events,      │   engine · sessions ·      │   calls  └──────────────┘
  │ backend job) │  triggers     │   webhooks · /chat UI      │
  └──────────────┘               └───────┬─────────────┬──────┘
                                          │             │
                            Postgres /    │             │   collections /
                            in-memory     ▼             ▼   HTTP endpoint
                                ┌────────────────┐ ┌──────────────────┐
                                │  session store │ │  knowledge base  │
                                │   (optional)   │ │    (optional)    │
                                └────────────────┘ └──────────────────┘
```

| Component                  | Owned by                                                                | Role                                                                                                                                                                                                                                                    |
| -------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Agent server**           | InteractiveAI (the platform hosts and runs it)                          | The runtime. One container = one agent. Hosts the engine, the HTTP API, session state, and webhook entry points.                                                                                                                                        |
| **InteractiveAI platform** | InteractiveAI                                                           | Stores versioned content (system prompt, routines, policies, glossaries, macros), serves the **LLM router**, and receives traces. The agent fetches its content from the platform once at boot.                                                         |
| **LLM router**             | InteractiveAI                                                           | Single endpoint for all model calls. The agent never talks to a model provider directly — every inference call (chat, evaluation, embeddings) goes through `<platform base_url>/api/v1/` with your router API key.                                      |
| **MCP tool servers**       | You — a service you deploy in the platform, or a remote server you host | HTTP services implementing the Model Context Protocol. Each server's tools become callable by the agent under a namespace (`crm:search`, `cars:create_booking`). See [Tools](/agents/concepts/tools).                                                   |
| **Session store**          | You — a database in the platform, a remote Postgres, or in-memory       | Conversation history, customers, and context variables. Omit the `database` block for ephemeral in-memory storage; point it at Postgres (in the platform or remote) for persistence. See [Sessions, memory & state](/agents/concepts/memory-and-state). |
| **Knowledge base**         | You — a managed collection on the platform, or your own HTTP endpoint   | Optional retrieval grounding: a managed collection the agent queries via the deployment-operator search API, or an HTTP search endpoint you own. See [Knowledge base & retrieval](/agents/concepts/knowledge-base).                                     |
| **Your integration**       | You                                                                     | The service that connects a channel (web chat, Zendesk, Slack, IVR, a backend job) to the agent via the Python SDK or raw REST. See [Integrating the SDK](/agents/guides/integrating-the-sdk).                                                          |

## The engine

Inside the agent server sits the **Interactive Agents engine** — the loop that turns an incoming event into a reply or a typed result. Per turn, it:

1. Matches active **policies** against the conversation.
2. Evaluates which **routine** applies and which step of it comes next.
3. Decides whether to call **tools**, run a **think** step, or speak.
4. Iterates (up to `max_engine_iterations`, default 5) until the turn is complete, then emits typed **events**.

The full walkthrough is in [Conversation lifecycle](/agents/concepts/conversation-lifecycle).

The engine makes two distinct kinds of model calls — customer-facing **chat** calls and internal **evaluation** calls — routed to independently configured models. This split matters operationally; see [Models](/agents/concepts/models).

## Configuration model

Everything the agent is comes from one **manifest** plus the versioned content it references:

```yaml
name: DriveAway Demo
id: driveaway-demo
version: "1"
agent_config:
  runtime:
    api_key: ${AGENT_API_KEY}
  interactive_platform:
    public_key: ${INTERACTIVEAI_PUBLIC_KEY}
    secret_key: ${INTERACTIVEAI_SECRET_KEY}
  llms:
    default: interactive/anthropic/claude-haiku-4.5
    api_key: ${ROUTER_API_KEY}
  context:
    system_prompt:
      id: system-prompt
      version: 1
    language: match_user
    routines:
      - id: car-search
        version: 1
    policies:
      - id: stay-on-topic
        version: 1
  mcps:
    - id: cars
      hostname: http://cars-mcp
      port: 8765
      transport: streamable-http
```

Three rules govern the manifest:

1. **Content is referenced, not inlined.** Routines, policies, glossaries, and the system prompt live in the platform's versioned catalog — you create and publish them in the InteractiveAI platform, where each save produces a new immutable version, and the document's catalog name is the `id` the manifest references. The manifest pins exact versions; updating behaviour means publishing a new content version and bumping the pin.
2. **Secrets are env-refs, never literals.** Every credential field takes a `${VAR_NAME}` reference; the platform supplies the value from your secret bundle at boot. A missing variable fails the boot with the variable's name — there is no fallback path. See [Environment variables](/agents/reference/environment).
3. **One manifest, one agent, one container.** There is no multi-tenant mode. **Autoscaling is on by default and managed by the platform** — it adds and removes replicas of your agent internally as demand changes, so scaling an agent up under load (and back down) is handled for you rather than something you configure or operate.

The complete field-by-field schema is in [Manifest & content schemas](/agents/reference/manifest).

## Boot sequence

Understanding boot order explains most startup-time behaviour:

1. **Parse & validate the manifest.** Structural errors fail immediately with every violation listed (the platform also runs this validation when you upload the manifest).
2. **Resolve secrets** — every `${VAR}` env-ref is dereferenced against the secret bundle the platform injected. Any missing required variable aborts boot, naming the variable.
3. **Fetch content** — the server pulls every referenced routine, policy, glossary, macro, and prompt from the platform at the pinned versions (up to 5 fetches in parallel). A missing reference aborts boot.
4. **Connect tool servers** — each declared MCP server is contacted and its tools are catalogued.
5. **Apply configuration** — the agent, policies, routines, retrievers, and webhooks are wired into the engine.
6. **Routine evaluation runs** — the engine pre-computes behavioural metadata for every routine and policy (which steps speak vs. call tools, which depend on customer input, each node's reachable follow-ups). These are model calls and can take minutes on a cold cache; results are cached by content hash, so warm boots skip this entirely. The stages and their purpose: [Startup evaluation](/agents/concepts/startup-evaluation); caching and pre-warming: [Startup evaluation](/agents/concepts/startup-evaluation#caching-cold-vs-warm-boots).
7. **The agent starts serving** — the HTTP port binds and `GET /health/ready` begins answering `200` **only after evaluation settles**. The port isn't open before that, so a cold-cache evaluation delays when the agent becomes reachable at all — which is exactly why the evaluation cache is pre-warmed, so deploys come up fast rather than waiting through a full evaluation.

If startup fails at any stage the process exits non-zero (within 10 seconds) so the platform restarts it instead of leaving a half-configured agent serving traffic.

## Network surface

All inbound paths on the agent server:

| Path                                                                              | Auth                          | Purpose                                                                                                                        |
| --------------------------------------------------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `GET /health/live`, `GET /health/ready`, `GET /health`                            | none                          | Probes                                                                                                                         |
| `POST /sessions/{id}/events`                                                      | Bearer                        | Post a customer or system message into a session (this is how a turn is started)                                               |
| `GET /sessions/{id}/events`                                                       | Bearer                        | **Receive the agent's events** — the long-poll / SSE stream of replies, tool calls, and status, resumable from an offset       |
| Sessions & customers API (`POST /sessions`, `GET /sessions/{id}`, `/customers/…`) | Bearer                        | Create/read sessions and customers, set variables and metadata                                                                 |
| `POST /routines/{routine_id}/trigger`                                             | Bearer                        | Fire an autonomous routine                                                                                                     |
| `POST /webhooks/{name}`                                                           | HMAC over raw body            | Third-party webhook entry                                                                                                      |
| `POST /sessions/{session_id}/tool_events`                                         | Bearer                        | Inject external context as a synthetic tool result                                                                             |
| `GET /journeys/{journey_id}/graph`                                                | Bearer                        | Routine graph for dashboards (the `journeys` path segment is legacy wire naming for routines)                                  |
| `GET /chat`                                                                       | Bearer or `agent_auth` cookie | Built-in browser **chat UI** for trying the agent directly (cookie login via `/auth/login`; not meant as a production channel) |

The conversation surface — posting messages and receiving events — is normally driven through the SDK rather than called directly; see [Integrating the SDK](/agents/guides/integrating-the-sdk) and the full [HTTP API](/agents/reference/http-api) reference.

Bearer auth compares `Authorization: Bearer <token>` in constant time against the manifest's `runtime.api_key`. Outbound, the agent calls: the platform (boot-time content fetch), the LLM router (every turn), your MCP servers (tool calls), your knowledge base, your callback/webhook URLs, and the traces backend. There are **no other outbound calls** — in particular, the agent never pushes conversation replies to your integration unless you opt in to event webhook delivery (see [Integrating the SDK](/agents/guides/integrating-the-sdk)).

## Where to go next

* The turn-by-turn engine walkthrough: [Conversation lifecycle](/agents/concepts/conversation-lifecycle)
* Build something: [Quickstart](/agents/guides/quickstart)
* Field-level reference: [Manifest & content schemas](/agents/reference/manifest)


# Conversation lifecycle

What happens inside the engine on every turn: policy matching, routine advancement, tool execution, the iteration loop, and event emission.

> **Context** — This page explains the engine loop that powers every agent turn. It assumes you know the component names from [Architecture](/agents/concepts/architecture). Almost every behaviour you will observe — why a routine advanced or didn't, why a tool ran twice, why a turn ended early — traces back to this loop.

## The unit of work: a turn

A **turn** starts when the engine is woken by an event:

* a **customer message** (conversational mode), or
* a **system trigger** (autonomous mode — see [Autonomous routines](/agents/concepts/autonomous-routines)).

A turn ends when the agent has produced its reply (conversational) or called `built-in:emit_output` (autonomous), or when the iteration cap is reached. One turn can contain many model calls and many tool calls; it produces a stream of typed events as it goes.

## Anatomy of a turn

```
 incoming event
      │
      ▼
 ┌─────────────────────────────────────────────────────┐
 │ PREPARATION ITERATIONS  (at most max_engine_iterations, default 5)
 │                                                     │
 │  1. Policy matching      which policies apply now?  │
 │  2. Routine evaluation   which routine is active,   │
 │                          which node comes next?     │
 │  3. Tool / think calls   execute tool nodes and     │
 │                          think nodes; results join  │
 │                          the conversation history   │
 │                                                     │
 │  └── tools ran? ──► loop: re-evaluate with results  │
 │      nothing left to prepare? ──► exit loop         │
 └─────────────────────────────────────────────────────┘
      │
      ▼
 RESPONSE GENERATION   one chat-model call produces the reply,
                       honouring matched policies, the active
                       routine step, persona, and language
      │
      ▼
 EVENT EMISSION        message / tool / status events appended
                       to the session and streamed to consumers
```

### 1. Policy matching

Every turn begins by deciding which [policies](/agents/concepts/policies) apply. Policies are evaluated in batches (`policy_batch_size` per model call, default 5) by the **policy matcher** — an internal evaluation-model call that reads each policy's `condition` against the current conversation and answers: does this apply right now?

* Policies marked `always_match: true` skip the matcher and always apply.
* Matched policies' `action` texts become binding instructions for the rest of the turn.

### 2. Routine evaluation

Next, the engine determines routine state:

* **Activation** — a routine becomes active when one of its `conditions` matches the conversation (each condition is evaluated like a policy condition).
* **Advancement** — for the active routine, the engine selects the next node by evaluating the current node's outbound transitions (their `condition` fields). Nodes and transition semantics are defined in [Routines](/agents/concepts/routines).
* **Backtracking** — if the customer jumps back ("actually, change the pickup date"), the engine can re-enter an earlier node rather than ploughing forward.

These selection decisions are evaluation-model calls (see [Models](/agents/concepts/models)) — narrow, structured-output decisions the customer never sees.

### 3. Tool and think execution

If the selected node is a **tool node** or a **think node**, the engine executes it *within the same turn*:

* Tool calls go to your MCP servers; results are appended to the conversation history as tool events.
* Think nodes call the built-in `built-in:reason` tool: a model call that must return JSON matching the node's `output_schema`, stored under `session.metadata.step_outputs[<node-id>]`.

After execution the loop **iterates**: routine evaluation runs again with the new results in context, and policies listing the executed tool in `reevaluate_after` get re-matched instead of staying decided. This is how a routine chains `tool → tool → think → speak` inside a single turn.

### The iteration cap

The loop runs at most `max_engine_iterations` times (manifest field, default **5**). When the cap is hit:

* **Conversational turns** stop preparing and generate the best reply they can from what's gathered so far.
* **Autonomous runs** that never reached `emit_output` fail with error code `max_engine_iterations_reached` in the callback, telling you to raise the cap or shorten the routine.

Raise the cap when a single turn legitimately needs longer chains of tool/think nodes. Don't raise it to paper over a routine that loops — see [Troubleshooting](/agents/operations/troubleshooting).

### Response generation

When nothing is left to prepare, one **chat-model** call produces the customer-facing message. The prompt assembles, in the agent's voice, roughly in this order:

* the system prompt (persona, business rules),
* context variables (the resolved per-customer values),
* glossary terms,
* the matched policies' actions and the active routine node's instruction (`chat_state`) — one combined instructions section,
* the conversation history for this turn,
* staged tool results, including retrieved knowledge-base snippets,
* the language directive (see [Prompts, language & preamble](/agents/concepts/prompts)).

Two chat nodes can never run back-to-back in one turn — the first message ends the turn. The next customer message starts the next turn.

### Preambles

While the engine is still preparing, the agent may emit a short **preamble** ("Let me check…") so the customer isn't staring at silence during tool calls. Preambles are configured in the manifest (`context.preamble`), are tagged distinctly in the event stream (`kind: "preamble"`), and should be rendered like a typing indicator with text — not as a final reply. On the first turn, a configured greeting (`context.greeting`) replaces the preamble entirely.

With `context.preamble.announce_tools: true`, the agent additionally emits a short status message right after each batch of tool calls completes ("I've checked your account") while the full reply is still being composed. These announcements arrive as `kind: "preamble"` events too — render them the same way. They never reveal tool names or final results, and they always precede the `assistant_message`.

## What consumers observe

The turn produces an ordered stream of events on the session (each with a monotonically increasing `offset`):

| Event kind                                             | When                                                      |
| ------------------------------------------------------ | --------------------------------------------------------- |
| `status: acknowledged`                                 | Engine received the message                               |
| `status: typing` / `status: processing` (with `stage`) | Preparation in progress                                   |
| `preamble`                                             | Optional filler message mid-turn                          |
| `tool`                                                 | A batch of tool calls completed (ids, arguments, results) |
| `assistant_message`                                    | The reply                                                 |
| `status: ready`                                        | Turn finished — clear indicators                          |
| `status: error` (with `error_detail`)                  | Turn failed                                               |
| `status: cancelled`                                    | A newer customer message superseded this turn             |

Exact wire shapes: [Events & callbacks](/agents/reference/events-and-callbacks). Consumption patterns: [Integrating the SDK](/agents/guides/integrating-the-sdk).

## Timing and failure characteristics

* **A new customer message cancels the in-flight turn** for that session and starts a fresh one (consumers see `status: cancelled`).
* **Evaluation calls retry**: each internal decision call attempts up to 3 times on the evaluation model, then up to 3 more on the bigger evaluation-fallback model before the turn errors. See [Models](/agents/concepts/models).
* **Tool failures are visible to the model** — a failed tool call lands in history and the agent can explain or retry per its instructions; it does not crash the turn.
* **Knowledge-base failures soft-fail** — retrieval errors log a warning and the turn continues without retrieved context.

## See also

* [Policies](/agents/concepts/policies) — what the matcher matches
* [Routines](/agents/concepts/routines) — step kinds and completion semantics
* [Models](/agents/concepts/models) — which model serves which call
* [Observability](/agents/guides/observability) — watching all of this in traces and logs


# Policies

Policies are condition → action rules matched against every turn. They encode safety, compliance, tone, and business rules that apply across all routines.

> **Context** — Policies are one of the two behavioural building blocks of an agent (the other is [routines](/agents/concepts/routines)). This page defines the model; [Authoring policies](/agents/guides/authoring-policies) covers how to write good ones.
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## What a policy is

A policy is a standalone **condition → action** pair, written in natural language, stored as a versioned YAML document in the platform catalog and referenced from the manifest:

```yaml
id: minimum-driver-age
name: Enforce Minimum Driver Age
condition: >
  The user mentions a driver who is under 21 years old, or asks whether
  someone under 21 can rent.
action: >
  State plainly that the minimum driver age at DriveAway is 21, so the
  booking cannot proceed for that driver. If there is another adult in
  the party who is 21 or older, offer to book under that person's name
  instead. Do not invoke the booking tool with an under-age driver.
criticality: HIGH
```

On every turn, the **policy matcher** evaluates each policy's `condition` against the conversation (in batches of `policy_batch_size`, default 5, per evaluation-model call). When a condition matches, the policy's `action` becomes a binding instruction for the rest of the turn — layered on top of whatever routine is active.

## Fields

| Field              | Type                        | Required | Default            | Meaning                                                                                                                                                                                                                         |
| ------------------ | --------------------------- | -------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`               | string                      | yes      | —                  | Stable identifier the engine keys the policy on. For stored policies it matches the catalog name the document is published under (what the manifest's `context.policies[].id` references).                                      |
| `condition`        | string                      | yes      | —                  | Natural-language condition the matcher evaluates each turn. Required even with `always_match: true`.                                                                                                                            |
| `action`           | string                      | no       | —                  | What the agent must do when the condition matches. May include both tool usage and customer-facing messaging. Omit for an **observation** — a condition-only policy whose match informs the turn without prescribing an action. |
| `name`             | string                      | no       | falls back to `id` | Display label for the platform UI. Cosmetic — not used for matching.                                                                                                                                                            |
| `description`      | string                      | no       | —                  | Free-form rationale shown to operators; not used for matching.                                                                                                                                                                  |
| `criticality`      | `LOW` \| `MEDIUM` \| `HIGH` | no       | `MEDIUM`           | How strictly a matched policy is enforced. `HIGH`/`MEDIUM` are mandatory instructions; `LOW` is soft guidance the agent may deprioritise. Does **not** resolve conflicts — see [priorities](/agents/concepts/priorities).       |
| `always_match`     | boolean                     | no       | `false`            | Skip the matcher; the policy applies on every turn.                                                                                                                                                                             |
| `track`            | boolean                     | no       | `true`             | When `true`, a policy that has already applied is re-matched on later turns with a lighter "previously-applied" check instead of full matching. Set `false` to force full matching every turn.                                  |
| `tools`            | list of strings             | no       | —                  | Tool id(s) the agent may call when this policy applies (`service:tool` form).                                                                                                                                                   |
| `reevaluate_after` | list of strings             | no       | —                  | Tool id(s) whose execution re-triggers this policy's matching (see below).                                                                                                                                                      |
| `metadata`         | object                      | no       | —                  | Arbitrary key/values attached to the policy (e.g. `severity`, `category`); merged with server-derived keys.                                                                                                                     |

Machine-validated rules (the published JSON Schema enforces these): `id` and `condition` required; unknown fields rejected.

## Policies vs routines

The single most important distinction in the configuration model:

|               | Policy                                                                                | Routine                                                                 |
| ------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| Shape         | One condition → one action                                                            | Multi-node graph                                                        |
| State         | None — re-matched fresh every turn                                                    | Tracks the current node across turns                                    |
| Tool + speech | One `action` may both call a tool and speak — the model handles both in a single turn | Strictly separated: a node either calls tools **or** speaks, never both |
| Use for       | Cross-cutting rules: safety, compliance, tone, escalation triggers                    | Structured flows: collect → fetch → branch → respond                    |

If you find yourself writing a policy whose action is a sequence ("first ask X, then call Y, then confirm Z"), it should be a routine. If you find yourself adding the same guard node to every routine, it should be a policy.

## How matching behaves

* **Per-turn, stateless.** A policy that matched last turn has no special status this turn; the matcher re-reads the conversation.
* **Multiple policies can match simultaneously.** All matched actions are in force for the turn. When they pull in different directions, only an explicit [priority](/agents/concepts/priorities) resolves which wins — `criticality` does not (see below).
* **`always_match: true`** is for rules that must never depend on a model's judgement of relevance — e.g. regulatory disclaimers, hard prohibitions. Use sparingly: every always-on policy consumes prompt space in every turn.
* **`reevaluate_after`** re-runs this policy's match after the listed tool executes — whether it succeeds or errors — instead of relying on the match made at the start of the turn. Example: an authentication policy stops matching once `crm:authenticate_customer` has run — listing that tool in `reevaluate_after` makes the engine re-check as soon as the call lands. (The manifest-level `context.reevaluation_tools` is the same mechanism with routine-wide scope — see [Reevaluation tools](/agents/concepts/tools#reevaluation-tools).)

## Criticality: how strictly a policy is enforced

`criticality` controls **how a matched policy is presented to the model** — not how conflicts are resolved, and not how hard the policy is matched:

| Level              | How a matched policy is treated                                                                                         | Use for                                                                      |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `HIGH`             | Rendered as a mandatory instruction the agent must follow.                                                              | Safety, compliance, money, hard prohibitions.                                |
| `MEDIUM` (default) | Rendered as a mandatory instruction the agent must follow.                                                              | The bulk of behavioural rules.                                               |
| `LOW`              | Rendered as a soft "general principle"; the prompt tells the model to prioritise context-specific instructions over it. | Tone nudges and optional suggestions where an occasional miss is acceptable. |

Two things to know:

* **`HIGH` and `MEDIUM` behave identically today.** Both are matched every turn and enforced as mandatory instructions; the level is an authoring signal — and reserved for future weighting — not a current behavioural difference. Reach for `HIGH` on the rules whose importance you want to make explicit: safety, compliance, hard prohibitions.
* **`LOW` is the only level the agent may quietly skip.** A `LOW` policy is matched the same way but rendered as optional guidance, so the model is free to deprioritise it. Use it only where a miss is acceptable.

Criticality does **not** affect matching cost or conflict resolution. Every in-scope policy is matched each turn in batches of `policy_batch_size` regardless of level, so per-turn cost scales with the number of policies, not their criticality. And when two matched policies conflict, only an explicit [priority](/agents/concepts/priorities) decides which wins — a `HIGH` policy still needs a priority entry to categorically override a routine or another policy.

## Scoping: agent-wide vs routine-scoped

Policies referenced in the manifest's `context.policies` apply agent-wide. A routine can additionally declare its own `policies:` list — policies with the same field shape (an explicit `id` is required), active **only while that routine is active**:

```yaml
# inside a routine document
policies:
  - id: billing-check
    name: Billing Address Check
    condition: "About to finalize a purchase"
    action: >
      Verify the billing address is correct before proceeding with the
      purchase.
    always_match: true
```

Use routine-scoped policies for rules that only make sense mid-flow; keep genuinely global rules in the manifest list so they hold even when no routine is active. Routine-scoped policies **can** be referenced from the manifest's `relationships:` block ([priorities](/agents/concepts/priorities) and entailments) by their `id`, the same as top-level policies — the `id` must be globally unique across top-level and scoped declarations (a collision fails validation at boot).

## Referencing from the manifest

```yaml
agent_config:
  context:
    policies:
      - id: minimum-driver-age
        version: 1
      - id: stay-on-topic
        version: 1
```

Version pins are exact. Publishing a new policy version does nothing until a manifest pinning it is deployed — see [Versioning & compatibility](/agents/operations/versioning).

## See also

* [Authoring policies](/agents/guides/authoring-policies) — patterns and anti-patterns
* [Priorities](/agents/concepts/priorities) — resolving conflicts between policies and routines
* [Conversation lifecycle](/agents/concepts/conversation-lifecycle) — when matching runs


# Routines

Routines are graphs of nodes — chat, tool, and routing-only (fork) nodes connected by transitions. This page defines the node types and their runtime completion semantics.

> **Context** — Routines are the structured-flow building block of an agent (the cross-cutting one is [policies](/agents/concepts/policies)). This page is the definitive model; [Authoring routines](/agents/guides/authoring-routines) is the hands-on companion. If you read only one warning on this page, read [the node types](#node-types-read-this-first).
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## What a routine is

A routine is a versioned YAML document describing a **graph** the agent walks through across one or many turns: nodes carry actions, and each node lists its outbound transitions. Execution starts at the node named by `entry`.

```yaml
id: car-search
title: Car Search
conditions:
  - >
    The user wants to browse the fleet, find a car, or asks for car
    suggestions — phrases like "what cars do you have", "show me an SUV",
    "I need a 7-seater". Do NOT activate when the user is referring to a
    specific booking they already have.
description: >
  Help the user narrow down the fleet to one or two candidate cars by
  collecting their criteria, searching the catalog, and presenting results
  with prices.

entry: gather-criteria
nodes:
  - id: gather-criteria
    chat_state: >
      Ask the user for their preferences in one short message: category
      (economy / compact / SUV / van / luxury), minimum number of seats,
      transmission preference, and any daily-budget cap in EUR. Tell them
      any field is optional.
    transitions:
      - to: run-search
        condition: >
          The user has provided at least one preference, or has said
          "anything" / "no preference".

  - id: run-search
    tools: cars:search_cars
    tool_instruction: >
      Call search_cars with the filters the user provided. Pass null /
      omit fields the user did not mention. Do not invent constraints.
    transitions:
      - to: present-results

  - id: present-results
    chat_state: >
      Summarise the matching cars in a compact list — one line per car
      with make, model, category, transmission, and daily price in EUR.
      End by asking if the user wants to book one.
```

## Top-level fields

| Field         | Type                      | Required            | Default | Meaning                                                                                                                                                                       |
| ------------- | ------------------------- | ------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title`       | string                    | yes                 | —       | Display name in the platform UI, traces, and logs.                                                                                                                            |
| `conditions`  | string or list of strings | yes (≥ 1 non-empty) | —       | When the routine activates. Evaluated like policy conditions; the routine activates when **any** condition matches.                                                           |
| `entry`       | string                    | yes                 | —       | Id of the node where execution starts. Must match a declared node id.                                                                                                         |
| `nodes`       | list of node objects      | yes (≥ 1)           | —       | The graph (below). Each node is declared exactly once; node ids must be unique.                                                                                               |
| `id`          | string                    | no                  | `null`  | Optional in-document id. Identity actually comes from the catalog name the document is published under — that name is what the manifest's `context.routines[].id` references. |
| `description` | string                    | no                  | `null`  | Long-form purpose. Not used for matching.                                                                                                                                     |
| `policies`    | list of policy objects    | no                  | `null`  | Routine-scoped policies (each requires an explicit `id`) — see [Policies](/agents/concepts/policies#scoping-agent-wide-vs-routine-scoped).                                    |
| `autonomous`  | object                    | no                  | `null`  | Makes the routine triggerable as a typed automation — see [Autonomous routines](/agents/concepts/autonomous-routines).                                                        |

## Node fields

| Field              | Type                       | Required | Default | Meaning                                                                                                                                                                                       |
| ------------------ | -------------------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`               | string                     | yes      | —       | Unique within the routine. Referenced by `entry` and by `transitions[].to`.                                                                                                                   |
| `description`      | string                     | no       | `null`  | Author note; surfaced in traces, never shown to the customer.                                                                                                                                 |
| `tools`            | string or list of strings  | no       | `null`  | TOOL node: tool id(s) to call, `service:tool` form (a bare string is normalised to a one-element list).                                                                                       |
| `tool_instruction` | string                     | no       | `null`  | TOOL node: how to call the tool(s) — parameter mapping, derivation rules. Never shown to the customer. Requires `tools`.                                                                      |
| `chat_state`       | string                     | no       | `null`  | CHAT node: instruction for what the agent should say. Supports `${macro-id}` interpolation — see [Glossaries & macros](/agents/concepts/glossaries-and-macros#macros).                        |
| `transitions`      | list of `{to, condition?}` | no       | `[]`    | Outbound edges. **Omitted/empty = terminal node.** With 2+ entries, every transition requires a `condition`.                                                                                  |
| `think`            | string                     | no       | `null`  | **Autonomous routines only** — the typed-inference (THINK) node instruction. Not used in conversational routines; see [Autonomous routines](/agents/concepts/autonomous-routines#node-types). |
| `output_schema`    | object                     | no       | `null`  | **Autonomous routines only** — JSON Schema validating `think`'s structured output. Required on `think:` nodes; rejected on any other node.                                                    |

Machine-validated rules (enforced by the published JSON Schema): unique node ids; `entry` and every `transitions[].to` must name declared nodes; `tools` is mutually exclusive with `chat_state`; `tool_instruction` requires `tools`; a node must carry an action (`tools` or `chat_state`) **or** at least one transition; 2+ transitions ⇒ all conditioned; malformed `${…}` macro tokens in `chat_state` are rejected. (The schema also defines the `think`/`output_schema` node used by [autonomous routines](/agents/concepts/autonomous-routines).)

## Node types: read this first

A conversational routine is built from **three node types**. Each node is exactly one of them, determined by its action field:

| Node type                     | Set by                                      | What it is                                                                                                                                                 |
| ----------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **CHAT**                      | `chat_state`                                | Speaks to the customer. Completes when the agent sends a message fulfilling the instruction. May interpolate `${macro-id}`.                                |
| **TOOL**                      | `tools` (+ `tool_instruction`)              | Calls one or more tools. Completes when the tool executes — it produces no message of its own.                                                             |
| **Routing-only** (the *fork*) | no action field, 2+ conditional transitions | A pure branch point — no tool call, no message. The engine evaluates the outgoing conditions and takes the matching path. This is how a routine **forks**. |

> A fourth node type — **THINK** (typed inference) — exists only for [autonomous routines](/agents/concepts/autonomous-routines#node-types). It has no customer-facing output, so it is **not used in conversational routines**; if you need a routine to reason over typed data, that's an autonomous routine.

> ⚠️ **The classic bug.** `tools` and `chat_state` on the same node is rejected by the schema — and for good reason: "call the handoff tool *and* tell the customer they're being transferred" must be **two nodes**. A tool node's customer-facing output always belongs in a follow-up chat node.

## Runtime semantics by node kind

### CHAT node: completes when the agent speaks

The engine generates a message fulfilling the `chat_state` instruction; that message **ends the turn**. If the outbound transition's condition depends on the customer's answer, the routine waits on the customer before advancing. Two CHAT nodes can never run in the same turn.

### TOOL node: completes when the tool executes

The engine calls the tool(s) per `tool_instruction`, appends the results to history, and immediately re-evaluates — walking the node's transitions **within the same turn**. Chains of TOOL nodes execute back-to-back without customer interaction.

Each chained think/tool node consumes one iteration of the per-turn engine loop, bounded by `runtime.max_engine_iterations` (default 5). A routine that chains more nodes than that back-to-back before reaching a message or terminal node has its turn cut off mid-chain — the engine logs a WARNING and responds anyway with whatever it has, nothing surfaced to the caller. Keep such chains within the cap, or raise it.

(Need a node that reasons over typed data instead of speaking or calling a tool? That's a **THINK** node, which lives in [autonomous routines](/agents/concepts/autonomous-routines#node-types) — not in conversational routines.)

### Routing-only node (fork): completes immediately

No action — just conditional transitions. The engine evaluates the outgoing conditions and takes the matching branch. This is the routine's **fork**: use one wherever the flow splits on a decision rather than on a tool call or a message.

"Completes immediately" means it never produces a message or consumes a turn — it does not mean the branch choice is free. A fork with two or more outgoing transitions still requires a model call to pick between them, the same next-step selection used by other node kinds. Only a node with a single outgoing edge (fork or otherwise) takes the zero-model fast path.

```yaml
  - id: status-route
    transitions:
      - to: active-path
        condition: "account status is ACTIVE"
      - to: suspended-path
        condition: "account status is SUSPENDED"
```

## Transitions, terminals, and movement

* A transition's `condition` decides when that edge is followed. A node with exactly **one** transition may omit the condition (unconditional); a node with **2+** transitions must condition every one.
* **No `transitions` = terminal node.** When a terminal node completes, the routine is done. (In autonomous routines, terminal nodes must call `built-in:emit_output`.)
* **Fan-in and cycles are allowed** — any node id can be the target of multiple transitions, including loops back to earlier nodes ("ask again until the data validates").
* The engine can **backtrack**: if the customer revisits an earlier topic ("actually, change the dates"), evaluation can re-enter a previous node rather than forcing forward-only movement.
* Activation and advancement decisions are evaluation-model calls — see [Conversation lifecycle](/agents/concepts/conversation-lifecycle).

## Referencing from the manifest

```yaml
agent_config:
  context:
    routines:
      - id: car-search
        version: 1
      - id: book-a-car
        version: 1
```

Pins are exact versions from the platform catalog. At boot, the server also pre-computes behavioural metadata for every routine, in two sequential stages: customer-dependence is computed first, then node reachability, which reads the customer-dependence result. On a cold cache this is the slow part of startup — see [Startup evaluation](/agents/concepts/startup-evaluation).

## See also

* [Authoring routines](/agents/guides/authoring-routines) — patterns, anti-patterns, checklist
* [Autonomous routines](/agents/concepts/autonomous-routines) — typed input/output automations
* [Glossaries & macros](/agents/concepts/glossaries-and-macros) — `${macro-id}` interpolation
* [Priorities](/agents/concepts/priorities) — when routines compete with each other or with policies


# Autonomous routines

Autonomous routines run end-to-end without a conversation: typed JSON in via trigger endpoint or webhook, typed JSON out via signed callback.

> **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). Authoring details: [Authoring autonomous routines](/agents/guides/authoring-autonomous-routines); wire shapes: [Events & callbacks](/agents/reference/events-and-callbacks).
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## 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).                                                                                                                                                            |
| `callback_url_allowlist` | list of hostnames | no       | empty = any             | Hostnames callbacks may target. An entry starting with `.` matches the apex and subdomains (`.example.com` matches `example.com` and `api.example.com`); a full-URL entry matches by its hostname only. No wildcards — omit the field to allow any host. 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), 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. This is generic engine node-completion behavior, not specific to autonomous routines — THINK nodes chain the same way in conversational routines; a CHAT node is the only node type that ends the turn.

```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), 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) |
| `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:

> **`conditions:` only matters here.** Multi-routine webhook fan-out is the *only* path where an autonomous routine's `conditions:` are evaluated against anything. Direct `POST /routines/{id}/trigger` calls and single-routine (inline) webhooks activate their target routine unconditionally — the routine id itself is the hard gate, not the condition text. So `conditions:` on an autonomous routine is a no-op everywhere except manifest-level fan-out with 2+ routines.

```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).

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).

## See also

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


# Priorities & entailments

Relationship declarations between routines and policies: priorities resolve conflicts, entailments chain policy activations. Without them, everything is equal-priority and independent.

> **Context** — When several [policies](/agents/concepts/policies) match and a [routine](/agents/concepts/routines) is mid-flow on the same turn, something has to win. This page covers the default behaviour and the two explicit relationship mechanisms under `context.relationships`: **priorities** (who wins) and **entailments** (what else applies).
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## Default: everything is equal

With no priority configuration, all matched policies and the active routine have equal standing. The engine weighs them together — policy `criticality` (`LOW`/`MEDIUM`/`HIGH`) influences emphasis, but nothing categorically outranks anything else. For most agents this is fine: well-written policies and routines rarely conflict.

You need explicit priorities when conflicts are *by design*:

* A safety policy must override any routine that would continue the flow ("the customer mentioned self-harm" beats "continue the booking flow").
* Two routines can both plausibly activate and one should win (a dedicated escalation routine over a generic FAQ routine).
* A compliance policy must beat a softer formatting policy.

## Declaring priorities

Priorities live in the manifest under `context.relationships`:

```yaml
agent_config:
  context:
    relationships:
      priorities:
        # 1. A policy that beats every routine
        - higher: policy:self-exclusion-safety
          over_all_routines: true

        # 2. A policy that beats specific targets (routines and/or policies)
        - higher: policy:rg-permanent-safety
          over:
            - routine:account-reactivation
            - policy:email-format

        # 3. A routine that beats another routine
        - higher: routine:priority-path
          over:
            - routine:fallback-path
```

### Entry fields

| Field               | Type            | Meaning                                                                                                                                                         |
| ------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `higher`            | string          | The reference that wins. Form: `routine:<id>` or `policy:<id>`.                                                                                                 |
| `over`              | list of strings | The references `higher` outranks (same `routine:`/`policy:` form). Mutually exclusive with `over_all_routines`.                                                 |
| `over_all_routines` | `true`          | `higher` outranks **every routine** in the manifest. It does **not** outrank any policy, even when `higher` is itself a policy. Mutually exclusive with `over`. |

Exactly one of `over` / `over_all_routines` is required per entry (the schema enforces this). Ids must match routines/policies actually referenced in the manifest.

## Semantics

* A priority is a **pairwise relation**: `higher` beats each listed target. Pairs not covered by any entry remain equal-priority.
* Priorities are not transitive chains you must spell out — but they are also not inferred. If A must beat C, say so; A-beats-B plus B-beats-C does not imply it.
* A priority is a **hard block, not a tiebreak.** When the `higher` entity matches, each of its `over` targets is **deactivated for that turn** — it does not fire, even if it also matched, and even if it does not conflict with the winner. No instruction conflict is required to suppress the loser.
* `over_all_routines: true` is the standard pattern for safety policies: whatever flow is mid-flight, the safety action takes precedence and the active routine is **suppressed for that turn**. The routine's position is preserved, so it resumes on the next turn where the safety policy no longer matches — suppressed per turn, not permanently torn down.

## Worked example

DriveAway adds an incident-safety policy that must beat the booking flow:

```yaml
agent_config:
  context:
    policies:
      - id: incident-handoff
        version: 1
    routines:
      - id: book-a-car
        version: 1
    relationships:
      priorities:
        - higher: policy:incident-handoff
          over_all_routines: true
```

Mid-booking, the customer mentions they were just in an accident with a rental. `incident-handoff` matches; because it outranks all routines, the agent follows its action (express concern, hand off to a human) instead of pressing on with "and what's the driver's age?". Next turn, if the incident topic has passed, `book-a-car` resumes where it left off.

## Entailments

An **entailment** chains policy activations: whenever the `when` policy matches, every policy in `also_apply` is activated too — without its own condition having to match. Policy→policy only (routines cannot participate):

```yaml
agent_config:
  context:
    relationships:
      entailments:
        - when: policy:self-exclusion-request
          also_apply:
            - policy:rg-tone
            - policy:no-retention-offers
```

| Field        | Type                        | Meaning                                         |
| ------------ | --------------------------- | ----------------------------------------------- |
| `when`       | string (`policy:<id>`)      | Source policy whose match pulls in the targets. |
| `also_apply` | list of `policy:<id>` (≥ 1) | Policies activated whenever `when` matches.     |

Use entailments to keep condition text DRY: instead of copying "the customer mentioned self-exclusion" into the tone policy and the no-retention-offers policy, write the trigger once and entail the companions. Referenced ids may be top-level **or** routine-scoped policies (referenced by `id`) — a routine-scoped `id` resolves the same as a top-level one, provided each `id` is globally unique.

## See also

* [Policies](/agents/concepts/policies) — `criticality` and matching
* [Routines](/agents/concepts/routines) — activation conditions
* [Manifest & content schemas](/agents/reference/manifest) — full field reference


# Glossaries & macros

Glossaries inject domain vocabulary into every turn; macros are reusable text blocks interpolated into routine chat nodes via ${macro-id}.

> **Context** — Two small content types complete the configuration model: **glossaries** (domain vocabulary the agent must understand) and **macros** (reusable text blocks for routine chat nodes). Both live in the platform's versioned catalog, like [policies](/agents/concepts/policies) and [routines](/agents/concepts/routines).
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## Glossaries

A glossary defines domain-specific terms so the agent interprets customer language correctly and uses your vocabulary in replies:

```yaml
terms:
  self-exclusion:
    name: Self-exclusion
    description: >
      A voluntary account lock a customer chooses to restrict their own
      access for a fixed period. Distinct from a company-imposed
      suspension.
    synonyms:
      - self-ban
      - cooling-off lock
  one-way-rental:
    name: One-way rental
    description: >
      A rental where the drop-off location differs from the pickup
      location. Incurs a flat 50 EUR fee at DriveAway.
```

### Entry fields

| Field                     | Type                    | Required        | Default | Meaning                                         |
| ------------------------- | ----------------------- | --------------- | ------- | ----------------------------------------------- |
| `terms`                   | map of term-key → entry | yes (≥ 1 entry) | —       | The glossary body. Keys are stable identifiers. |
| `terms.<key>.name`        | string                  | yes             | —       | The term as written.                            |
| `terms.<key>.description` | string                  | yes (non-empty) | —       | The definition the agent works from.            |
| `terms.<key>.synonyms`    | list of strings         | no              | —       | Alternative phrasings customers use.            |
| `id`                      | string                  | no              | —       | Optional stable identifier for the set.         |

### Behaviour

* Glossaries are referenced from the manifest and **merged**: all terms from all referenced glossaries are combined into one term store at boot.

  ```yaml
  agent_config:
    context:
      glossaries:
        - id: rental-terms
          version: 2
  ```
* Each turn, the engine injects only the terms most relevant to the conversation — a semantic-similarity retrieval capped at 20 terms — not the entire merged glossary. A glossary set larger than 20 terms never injects all of them into a single turn's context.
* Terms influence both understanding (a customer saying "self-ban" is recognised as self-exclusion) and production (the agent uses your preferred names).
* The trace snapshot is built once at config-apply/boot and records which glossary sets are pinned (id, version, description, term keys) — it shows which glossaries are configured, not the per-turn retrieved subset or the full term definitions.
* Retrieved terms cost prompt space on every turn, and a larger merged glossary competes for the same 20-term retrieval budget. Keep glossaries to vocabulary that is genuinely ambiguous or business-specific. Product catalog data belongs in [tools](/agents/concepts/tools) or the [knowledge base](/agents/concepts/knowledge-base), not the glossary.

## Macros

A macro is one reusable block of message text:

```yaml
text: >
  Free cancellation applies up to 24 hours before pickup — you get a full
  refund. Within 24 hours of pickup, one day's rental rate is
  non-refundable and the remainder is returned to your original payment
  method within five business days.
```

| Field  | Type   | Required        | Default | Meaning                                   |
| ------ | ------ | --------------- | ------- | ----------------------------------------- |
| `text` | string | yes (non-empty) | —       | The macro body injected at the call site. |
| `id`   | string | no              | —       | Optional stable identifier.               |

### Declaring macros in the manifest

Macros are pinned in the manifest's catalog, alongside routines, policies, and glossaries:

```yaml
agent_config:
  context:
    macros:
      - id: cancellation-policy
        version: 3
      - id: handoff-followup
        version: 1
```

### Using a macro: `${macro-id}` interpolation in `chat_state`

A chat node interpolates a macro inline — the token is replaced by the macro's text body at the pinned version when the routine is compiled:

```yaml
  - id: explain-cancellation
    chat_state: |
      Here's how cancellation works for your booking:

      ${cancellation-policy}

      Would you like me to go ahead and cancel it?
    transitions:
      - to: await-decision
```

Rules:

* **Only `chat_state` interpolates.** `tool_instruction`, `think`, and `description` take literal text — a `${…}` there is not expanded.
* Macro ids must match `[a-zA-Z][a-zA-Z0-9_-]*`; a malformed token is rejected at validation time.
* Every interpolated id must be pinned in `context.macros`; an unresolved reference fails the boot.

### When to use a macro vs. repeating `chat_state`

Use a macro when the *same* carefully-worded explanation appears at several points (across nodes or across routines) and must stay consistent — regulatory wording, fee explanations, escalation scripts. For one-off instructions, plain `chat_state` is simpler and keeps the routine self-contained.

## See also

* [Routines](/agents/concepts/routines) — where macros plug in
* [Prompts, language & preamble](/agents/concepts/prompts) — the other text-shaping levers
* [Manifest & content schemas](/agents/reference/manifest) — reference syntax


# Prompts, language & preamble

The system prompt defines persona and ground rules; the language directive controls reply language; the greeting opens the first turn; the preamble shapes mid-turn filler.

> **Context** — This page covers the four manifest levers that shape the agent's voice: the **system prompt**, the **language** directive, the **greeting**, and the **preamble**. They live under `agent_config.context` alongside [policies](/agents/concepts/policies) and [routines](/agents/concepts/routines).
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## The system prompt

The system prompt is the agent's identity: who it is, what it does, the business facts it must know cold, and its style. It is a versioned document in the platform catalog, referenced by exact version:

```yaml
agent_config:
  context:
    system_prompt:
      id: system-prompt
      version: 1
```

The DriveAway example (stored as a prompt whose body is the `text` field):

```yaml
text: |
  You are Mercedes, a friendly booking specialist at DriveAway, a
  car-rental agency.

  Your job is to help customers find the right car, book it, and manage
  existing reservations. Keep answers short and concrete — confirm the
  important details (dates, pickup location, driver age, licence)
  before locking anything in.

  About DriveAway:
  - Fleet covers economy, compact, SUV, van, and luxury categories.
  - Minimum driver age is 21. Driver's licence held for at least one
    year, checked at pickup.
  - Free cancellation up to 24 hours before pickup.
  - One-way rentals (pickup ≠ return location) incur a flat 50 EUR fee.

  Style:
  - Warm, professional, no fluff. No emoji.
  - Always confirm pickup date, return date, location, and customer name
    before creating a booking.
  - If you don't have an answer, tell the user and offer a handoff to a
    human agent.
```

### Division of labour

The system prompt is **in context on every turn** — it frames every reply the agent generates, and every routine step and policy the model weighs at runtime. That makes it the right home for some things and the wrong home for others:

| Belongs in the system prompt                                            | Belongs elsewhere                                                                            |
| ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Persona, tone, formatting style                                         | Conditional behaviours → [policies](/agents/concepts/policies)                               |
| Stable business facts the agent cites constantly                        | Multi-step procedures → [routines](/agents/concepts/routines)                                |
| Global prohibitions phrased as identity ("you never give legal advice") | Reference content that varies / is large → [knowledge base](/agents/concepts/knowledge-base) |
| What the agent does and doesn't handle                                  | Vocabulary definitions → [glossaries](/agents/concepts/glossaries-and-macros)                |

A system prompt stuffed with procedures fights the routine engine — the engine drives flows step-by-step, and a prompt narrating a different procedure creates contradictions. Keep procedures in routines.

## Language

```yaml
agent_config:
  context:
    language: match_user
```

| Value                            | Behaviour                                                                                                                      |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `match_user`                     | Mirror the customer's language on every reply.                                                                                 |
| Any other string (e.g. `French`) | Handed to the model verbatim as the reply-language directive; the agent replies in that language regardless of the customer's. |

`language` is free text — `match_user` is the only special value. Anything else is passed to the model as-is, so you can express a nuance in prose (e.g. `French, but mirror the customer if they write in English`); there is no built-in parsing of comma-separated language lists.

The directive binds **replies**, including the greeting and preambles (which are adapted to the active language). It does not restrict what the agent can understand.

## Greeting and preamble

While the engine is mid-turn calling tools, seconds pass. The preamble lets the agent emit a short interim utterance so the customer sees life. The greeting is its sibling: a configured opening for the very first reply.

```yaml
agent_config:
  context:
    greeting: "Hi! I'm Mercedes from DriveAway — how can I help you with your car rental today?"
    preamble:
      examples:
        - "Let me check."
        - "One moment."
        - "Sure thing."
```

| Field                       | Type                  | Required                                  | Behaviour                                                                                                                                                                             |
| --------------------------- | --------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `context.greeting`          | string                | no                                        | First-turn opening: the agent's first message approximates this string, adapted to the active language. The preamble is suppressed on turn one — the greeting *is* the first message. |
| `context.preamble.examples` | list of strings (≥ 1) | yes, when the `preamble` block is present | Few-shot examples of mid-turn filler in the agent's voice. The model matches their tone and length; it does not quote them verbatim.                                                  |

Behavioural details:

* Preambles are **rate-based, not model-judged.** The engine attempts a preamble on the first couple of agent replies, then only when the customer's two most recent turns each kept them waiting (a few seconds or more) — so filler appears when someone has actually been left waiting, not on every turn.
* In the event stream a preamble is its own kind (`"preamble"`), distinct from `assistant_message`. Render it like a typing indicator with text; replace it when the real reply arrives. See [Events & callbacks](/agents/reference/events-and-callbacks).
* Omit the `preamble` block to disable mid-conversation preambles; omit `greeting` to skip the first-turn opening. The two are independent.

## How the pieces assemble at generation time

When the engine generates a reply, the prompt assembles roughly in this order: the system prompt → context variables → glossary terms → the matched policies' actions and the active routine step's instruction (one combined instructions section) → the conversation history → staged tool results (including retrieved knowledge-base snippets) → the language directive. Understanding this stack explains precedence intuitions: a routine step says *what to do now*; policies say *what must hold*; the system prompt says *who is speaking*.

## See also

* [Conversation lifecycle](/agents/concepts/conversation-lifecycle) — where generation sits in the turn
* [Glossaries & macros](/agents/concepts/glossaries-and-macros) — vocabulary and reusable text
* [Manifest & content schemas](/agents/reference/manifest) — field reference


# Tools

Tools are functions the agent calls — served by your MCP servers, plus two built-ins. Covers namespacing, declaration, auth, reevaluation tools, and tool-event injection.

> **Context** — Tools are how an agent acts on the world: look up an order, create a booking, hand off to a human. This page covers the tool model; [Connecting tools](/agents/guides/connecting-tools) walks through standing up a tool server.
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## The model

Tools are served over the **Model Context Protocol (MCP)** — an open standard for exposing typed, described functions over HTTP. You run one or more MCP servers; the agent connects to each at boot, catalogues its tools, and can call them whenever a [routine](/agents/concepts/routines) tool node or a [policy](/agents/concepts/policies) action says to.

Each MCP server is declared in the manifest with an `id` that becomes the **namespace** for its tools:

```yaml
agent_config:
  mcps:
    - id: cars
      hostname: http://cars-mcp
      port: 8765
      transport: streamable-http
    - id: crm
      hostname: https://crm-tools.internal.example.com
      port: 443
      transport: streamable-http
      path: /v2/mcp
      api_key: ${CRM_MCP_KEY}
```

A tool named `search_cars` on the `cars` server is referenced everywhere as `cars:search_cars`. The namespace keeps same-named tools on different servers unambiguous.

### Declaration fields

| Field       | Type              | Required | Default           | Meaning                                                                                                                |
| ----------- | ----------------- | -------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `id`        | string            | yes      | —                 | Namespace prefix for this server's tools.                                                                              |
| `hostname`  | string            | yes      | —                 | Host **including scheme** (`http://` or `https://`). Host only — no path, port, or query.                              |
| `port`      | integer 1–65535   | yes      | —                 | TCP port.                                                                                                              |
| `transport` | `streamable-http` | no       | `streamable-http` | The only supported MCP transport.                                                                                      |
| `path`      | string            | no       | `/mcp`            | URL path the MCP endpoint is mounted at. Set when the server exposes MCP somewhere other than `/mcp` (e.g. `/v2/mcp`). |
| `api_key`   | `${VAR}` env-ref  | no       | —                 | Sent to the MCP server as `Authorization: Bearer`. Omit for unauthenticated servers.                                   |

### Connection behaviour

* All declared servers are contacted at boot; an unreachable server fails startup (your tools are part of the agent's contract).
* Transient MCP failures at runtime trigger **automatic reconnection** — a flapping tool server degrades requests that needed it, not the whole agent.
* Tool definitions (names, descriptions, parameter schemas) come from the MCP server's own catalog. The agent passes the LLM your tool descriptions verbatim — well-described tools get called correctly; vague ones don't. See [Connecting tools](/agents/guides/connecting-tools#3-designing-tools-the-agent-calls-well).

## Where tools are called from

| Caller                       | Mechanism                                                                                                                                                                                                                  |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Routine TOOL node**        | `tools:` + `tool_instruction` — the instruction tells the model how to derive parameters. The node completes when the tool executes. See [Routines](/agents/concepts/routines#tool-node-completes-when-the-tool-executes). |
| **Policy action**            | A policy may name `tools:` and instruct their use in its `action`. Tool call and customer messaging can share one action (unlike routine nodes).                                                                           |
| **Think node**               | Internally a call to the built-in `built-in:reason` tool.                                                                                                                                                                  |
| **Autonomous terminal node** | A TOOL node calling `built-in:emit_output`.                                                                                                                                                                                |

Tool results land in the conversation history as **tool events** — visible to the model on subsequent iterations and to your integration in the event stream (`kind: "tool"`, carrying `tool_id`, `arguments`, `result`).

## Built-in tools

The runtime ships two synthetic tools under the `built-in` namespace:

| Tool                   | Purpose                                                                                                                                    |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `built-in:reason`      | Backs `think:` nodes — typed structured inference validated against the node's `output_schema`, stored in `session.metadata.step_outputs`. |
| `built-in:emit_output` | Terminates an autonomous run — validates the final JSON against the routine's `output_schema` and settles the run.                         |

Authors use them via routine syntax (`think:` nodes; `tools: built-in:emit_output` terminal nodes); the exact parameter contracts are in [Built-in tools](/agents/reference/built-in-tools).

## Reevaluation tools

Normally the engine decides which policies and routines apply **at the start of a turn**. But some tools change the state those decisions depend on *during* the turn — and once that state changes, the right next action may be different from what was chosen a moment ago. Reevaluation tells the engine to re-decide after such a tool runs, so the new action can happen in the **same turn** instead of waiting for the next one.

The motivating example: a customer asks a question that requires them to be authenticated. At the start of the turn they aren't, so the engine picks the "authenticate first" path and runs `crm:authenticate_customer`. Without reevaluation, the turn would end there — the agent would authenticate, then ask the customer to repeat themselves next turn. Marking `crm:authenticate_customer` as a reevaluation tool makes the engine re-decide the moment authentication succeeds: it now sees an authenticated customer, activates the routine that answers the original question, and responds — all in one turn.

Declare such tools globally so a successful call re-triggers routine and policy matching:

```yaml
agent_config:
  context:
    reevaluation_tools:
      - id: crm:authenticate_customer
      - id: crm:get_account_status
```

Each id must match a tool exposed by one of the manifest's `mcps` servers. Reserve this for tools whose result **meaningfully changes what the agent should do next** (authentication, account-status lookups, anything that unlocks or redirects a flow) — re-deciding has a cost, so don't mark every read-only lookup.

The per-policy [`reevaluate_after`](/agents/concepts/policies#fields) field is the **same mechanism scoped to one policy**: use the global list when a state change has broad effects (authentication unlocks much of the routine catalog), the per-policy field when only one rule's relevance flips.

## Injecting context as a tool event

Sometimes your integration has data the agent should treat as "a tool already fetched this" — a prior-conversation dump, a bank statement, a CRM export — without the agent calling anything. POST it as a synthetic tool event:

```
POST /sessions/{session_id}/tool_events
Authorization: Bearer <agent api key>
Content-Type: application/json
```

```json
{
  "tool_id": "injected:conversation_history",
  "arguments": {"source": "zendesk", "ticket": "12345"},
  "result": {"messages": [{"from": "customer", "text": "Where is my refund?"}]},
  "idempotency_key": "zendesk-12345-import-1",
  "trigger_processing": false
}
```

Semantics to know:

* `tool_id` is an opaque label — it need not name a real tool. Use the `namespace:name` convention (e.g. `injected:bank_statement`).
* **Visibility is next-turn only.** The engine snapshots history when a turn starts; an event injected mid-turn appears from the next turn on.
* `trigger_processing: true` starts a response turn immediately — and **cancels any turn currently in flight** for the session. Default `false`.
* `idempotency_key` makes retries safe: a duplicate key returns the original event's ids without creating a duplicate.
* Injected content is sanitised (control characters stripped) before it reaches prompts.

Full request/response schema: [HTTP API](/agents/reference/http-api).

## See also

* [Connecting tools](/agents/guides/connecting-tools) — build and wire an MCP server
* [Routines](/agents/concepts/routines) — tool nodes and instructions
* [Security](/agents/operations/security) — credentials and network posture for tool servers


# Knowledge base & retrieval

Retrieval grounding for the agent: managed collections on the platform, or an external HTTP search endpoint you own. Exactly one, selected by type.

> **Context** — Retrieval grounds the agent's answers in your documents (help-centre articles, product docs, policies-the-business-kind). It is optional: omit the `search` block entirely and the agent runs without retrieval. Setup walkthrough: [Setting up the knowledge base](/agents/guides/knowledge-base-setup).
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## One slot, two implementations

`agent_config.search` is a single slot selected by a `type` tag — you get exactly one of:

| `type`        | What it is                                                                                                                                                                                                                | Choose when                                                                                               |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `collections` | The managed retriever: the agent queries a collection on the deployment-operator search API — dense (vector), full-text (keyword), or a hybrid fusion of both, driven by a list of typed fields you declare.              | Your corpus lives in a platform-managed collection and you want the agent to own rewriting and embedding. |
| `external`    | An HTTP endpoint **you own** receives the recent conversation and returns text snippets — the integration path for an existing search system, reached as a plain **HTTP call**. You own rewriting, embedding, and search. | You already run a search/RAG service, need custom ranking, or your corpus lives behind an API.            |

Either way the knowledge base is yours: a collection you provision on the platform, or an external HTTP search service you integrate. The agent only needs to reach the host/endpoint the manifest declares.

## `type: collections`: managed collections

```yaml
agent_config:
  search:
    type: collections
    database: kb-prod
    collection: support_articles
    fields:
      - name: embedding
        type: vector
        llm_description:
          id: semantic-query-rewrite
          version: 3
      - name: keywords
        type: str
        llm_description:
          id: keyword-extract
          version: 1
    limit: 5
    history_limit: 5
    filter:
      kb_location: article
```

### How a retrieval runs

1. **Produce each field's text** — one prompt call per field (on the `llms.search_query` model, which inherits `llms.default`) turns the last `history_limit` customer messages into that field's query text. The prompt named by `llm_description` supplies your domain instructions.
2. **Embed vector fields** — for `type: vector` fields, the agent embeds the produced text using the model bound to the target collection slot (see below) — never a model you declare in the manifest.
3. **Search** — a single field runs one lane; several fields run each as its own lane (dense, keyword, or mixed) and the agent fuses the ranked results (reciprocal rank fusion, `fusion_k`).
4. **Ground** — the top `limit` results, with their full stored metadata, are appended to the agent's context for the turn.

### Field types and slots

Each entry in `fields` is either:

* **`type: str`** — the produced text is sent as a full-text keyword query. Requires the collection to have full-text search enabled.
* **`type: vector`** — the agent embeds the produced text and matches it against a collection **vector slot**. The field's `name` names the slot unless `slot` overrides it — set `slot` when you want several differently-prompted fields fused onto the same slot.

The embedding model and dimension are **never declared in the manifest**: the agent reads both from the target slot's embedding binding (a describe call against the collection) at boot. A vector field always needs a slot with a bound embedding model — inspect a collection's slots with `iai databases describe`.

### Collections fields

| Field               | Type                       | Required | Default                             | Meaning                                                                                                                                                                                          |
| ------------------- | -------------------------- | -------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `database`          | string                     | yes      | —                                   | Operator database hosting the collection.                                                                                                                                                        |
| `collection`        | string                     | yes      | —                                   | Collection (table) to query.                                                                                                                                                                     |
| `operator_base_url` | string                     | no       | `https://deployment.interactive.ai` | Deployment-operator host. Override only for non-standard deployments.                                                                                                                            |
| `fields`            | list of field objects, ≥ 1 | yes      | —                                   | The query: one or more typed fields.                                                                                                                                                             |
| `limit`             | integer 1–100              | no       | `5`                                 | Results returned to the agent (final top-k).                                                                                                                                                     |
| `history_limit`     | integer ≥ 1                | no       | `5`                                 | Recent customer messages the per-field prompts see (most-recent last).                                                                                                                           |
| `filter`            | object                     | no       | `{}`                                | Static metadata filter, Mongo-style operators. Applied to every lane.                                                                                                                            |
| `fusion_k`          | integer ≥ 1                | no       | `60`                                | Reciprocal-rank-fusion constant. Used only when more than one field.                                                                                                                             |
| `min_score`         | float                      | no       | —                                   | Drop results scoring below this. For a single `vector` field, enforced platform-side on the slot's similarity scale; for fused searches, applied by the agent on the RRF scale (\~1/`fusion_k`). |
| `exact`             | boolean                    | no       | `false`                             | Brute-force exhaustive search, bypassing the approximate index. Only valid with a single `vector` field.                                                                                         |

Each entry in `fields`:

| Field             | Type                           | Required | Default | Meaning                                                                                                   |
| ----------------- | ------------------------------ | -------- | ------- | --------------------------------------------------------------------------------------------------------- |
| `name`            | string, unique across `fields` | yes      | —       | Field label. For `type: vector` it also names the collection slot to search unless `slot` says otherwise. |
| `type`            | `vector` \| `str`              | yes      | —       | `vector` → dense lane on a collection slot. `str` → full-text keyword lane.                               |
| `llm_description` | versioned ref                  | yes      | —       | Prompt whose output is this field's text for the turn.                                                    |
| `slot`            | string                         | no       | `name`  | Collection vector slot this field searches. Only valid for `type: vector`.                                |
| `candidate_limit` | integer 1–1000                 | no       | —       | Candidates this lane retrieves before fusion. No effect when the search is a single `vector` field.       |

Authentication uses the agent's existing platform keys — no extra credentials; the organization and project are resolved automatically at startup.

### Boot validation

Startup describes the collection and checks the declared fields against it, failing boot with a specific error when:

* a `vector` field's slot doesn't exist on the collection,
* that slot has no bound embedding model,
* a `str` field is declared but the collection has full-text search disabled.

## `type: external`: bring your own search

```yaml
agent_config:
  search:
    type: external
    url: https://search.internal.example.com/agent-search
    api_key: ${SEARCH_API_KEY}
    top_k: 5
    max_messages: 20
    timeout_seconds: 5.0
```

### Request your endpoint receives

```json
{
  "session_id": "sess_abc123",
  "agent_id": "agt_xyz789",
  "top_k": 5,
  "messages": [
    {"role": "customer", "content": "What's your cancellation policy?"},
    {"role": "agent", "content": "Free cancellation up to 24 hours before pickup."},
    {"role": "customer", "content": "And for one-way rentals?"}
  ]
}
```

`messages` is the last `max_messages` turns, most-recent **last**, with `role` normalised to `customer` | `agent` | `tool`. When `api_key` is set the request carries `Authorization: Bearer <resolved key>`.

### Response your endpoint must return

A bare JSON array of strings — each one a snippet to ground on:

```json
[
  "One-way rentals can be cancelled free of charge up to 24 hours before pickup; the 50 EUR one-way fee is fully refunded.",
  "Cancellations within 24 hours of pickup forfeit one day's rental rate."
]
```

Snippets are joined with `\n\n---\n\n` and appended to the agent's context as-is. Empty/whitespace-only strings are dropped. Any other shape (object envelope, non-string items, non-list root) is treated as a failure.

### External fields

| Field             | Type             | Required | Default | Meaning                                                                                                |
| ----------------- | ---------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------ |
| `url`             | string           | yes      | —       | Full endpoint URL the runtime POSTs retrieval requests against. Scheme required; no query or fragment. |
| `api_key`         | `${VAR}` env-ref | no       | —       | Sent as `Authorization: Bearer`.                                                                       |
| `top_k`           | integer ≥ 1      | no       | `5`     | Forwarded in the envelope; your endpoint is expected to honour it.                                     |
| `max_messages`    | integer ≥ 1      | no       | `20`    | History cap per request.                                                                               |
| `timeout_seconds` | float > 0        | no       | `5.0`   | HTTP timeout.                                                                                          |

## Failure semantics: retrieval soft-fails

For both implementations, **retrieval failure never fails the turn**. Timeouts, connection errors, non-200 responses, malformed results — all log a warning and the turn proceeds with no retrieved context. The agent answers from the system prompt, policies, and history alone. Watch for retrieval warnings in logs if answers suddenly lose grounding — see [Troubleshooting](/agents/operations/troubleshooting).

## See also

* [Setting up the knowledge base](/agents/guides/knowledge-base-setup) — provisioning walkthrough
* [Models](/agents/concepts/models) — where the rewrite and embedding calls fit
* [Conversation lifecycle](/agents/concepts/conversation-lifecycle) — when retrieval happens in a turn


# Models

Per-stage model configuration: two lanes — customer-facing and evaluation — each with a default, per-stage overrides, and its own fallback mechanism. The lanes never cross.

> **Context** — Every model call an agent makes is configured by the manifest's `llms` block and routed through the InteractiveAI **LLM router** (the agent never calls a model provider directly). This page explains the two-lane, per-stage design — the most operationally important thing to understand about model configuration.
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## The `llms` block

```yaml
agent_config:
  llms:
    api_key: ${ROUTER_API_KEY}                   # router credential (required)

    # Customer-facing lane
    default: interactive/anthropic/claude-haiku-4.5          # lane default
    fallback:                                    # router-side alternates (ordered)
      - interactive/anthropic/claude-sonnet-4-6
    response: interactive/anthropic/claude-sonnet-4-6        # final message (optional)
    preamble: interactive/anthropic/claude-haiku-4.5         # preamble + tool announcement (optional)
    search_query: interactive/anthropic/claude-haiku-4.5     # KB search-query writing (optional)

    # Evaluation lane (internal engine inference)
    evaluation:
      default: interactive/google/gemini-3-flash-preview     # lane default
      fallback: interactive/google/gemini-3.1-pro-preview    # escalation on retry exhaustion
      tools: interactive/anthropic/claude-haiku-4.5          # tool-call argument inference
      startup: interactive/google/gemini-3.1-pro-preview     # boot-time routine evaluation
      policy_matching: interactive/google/gemini-3-flash-preview
      routine_navigation: interactive/google/gemini-3.1-pro-preview
```

| Field                           | Type                      | Required | Default                                       | Meaning                                                                                                                                                        |
| ------------------------------- | ------------------------- | -------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `api_key`                       | string (`${VAR}` env-ref) | yes      | —                                             | Router credential — shared by **all** calls: customer-facing, evaluation, and embeddings                                                                       |
| `default`                       | string                    | no       | `interactive/google/gemini-3-flash-preview`   | Customer-facing lane default                                                                                                                                   |
| `fallback`                      | list of string            | no       | `[interactive/google/gemini-3.1-pro-preview]` | Router-side alternates, ordered; set `[]` for a single-model deployment                                                                                        |
| `response`                      | string                    | no       | inherits `default`                            | Final customer-facing message                                                                                                                                  |
| `preamble`                      | string                    | no       | inherits `default`                            | Preamble **and** tool-call announcement (one generation stage drives both)                                                                                     |
| `search_query`                  | string                    | no       | inherits `default`                            | Writes the knowledge-base search queries (one call per query field per retrieval). No fallback chain — a failed rewrite falls back to the raw customer message |
| `evaluation`                    | object or string          | no       | all evaluation defaults                       | Evaluation lane; a bare string is shorthand for `evaluation.default`                                                                                           |
| `evaluation.default`            | string                    | no       | `interactive/google/gemini-3-flash-preview`   | Evaluation lane default                                                                                                                                        |
| `evaluation.fallback`           | string                    | no       | `interactive/google/gemini-3.1-pro-preview`   | Escalation model when an evaluation stage exhausts its retries                                                                                                 |
| `evaluation.tools`              | string                    | no       | inherits `evaluation.default`                 | Tool-call argument inference                                                                                                                                   |
| `evaluation.startup`            | string                    | no       | inherits `evaluation.default`                 | Boot-time routine evaluation                                                                                                                                   |
| `evaluation.policy_matching`    | string                    | no       | inherits `evaluation.default`                 | Per-turn policy matching + response analysis                                                                                                                   |
| `evaluation.routine_navigation` | string                    | no       | inherits `evaluation.default`                 | Next-step selection, backtrack check, backtrack step selection                                                                                                 |

Model names are `provider/model` aliases as served by the router's model catalog. There is no environment-variable override for model ids — change a model by editing the manifest and redeploying.

**Resolution:** a stage that is not set inherits its lane's default — `response`/`preamble`/`search_query` from `default`, the four `evaluation.*` stages from `evaluation.default`. Stages never inherit across lanes: leaving `evaluation` out entirely gives you the evaluation defaults above, not your customer-facing model.

## Two lanes, two jobs

### Customer-facing lane — what the customer reads

`default` (with the `fallback` list behind it) serves the **customer-visible** generations: the final reply (`response`) and the mid-turn niceties (`preamble` — which also produces the tool-call announcement). Optimise this lane for voice quality and instruction-following.

**Fallback semantics:** `fallback` is an ordered list forwarded to the router along with the stage's primary; when the primary fails, the router tries each alternate in order. One request, router-side failover.

### Evaluation lane — decisions the customer never sees

`evaluation.*` serves the engine's **internal structured-JSON decisions**:

* tool-call argument inference (`tools`) — which tools to run, with what arguments,
* policy matching and response analysis (`policy_matching`) — does this condition apply, and did the reply fulfil it?,
* routine navigation (`routine_navigation`) — next-step selection and backtrack checks (typically the largest, hardest prompts of a turn),
* routine metadata evaluation at startup (`startup`) — see [Startup evaluation](/agents/concepts/startup-evaluation).

These are narrow, high-volume, schema-constrained calls — a fast, inexpensive model is the right lane default. Quality shows up as *correct routing and correct arguments*, not prose. `tools` is the highest-stakes stage of the lane: a wrong argument does real damage, so it's the first stage to promote to a stronger model.

**Fallback semantics (different from the customer-facing lane!):** each evaluation call retries up to **3 attempts** on its stage's model; if all three fail (typically the model not conforming to the required output schema, or transport errors), the runtime swaps to `evaluation.fallback` and retries up to 3 more times on the bigger model. This is per call, not per process — one stubborn decision escalates alone. Every escalation is logged with a `[retry-fallback]` marker; see [Observability](/agents/guides/observability#retry-fallback-signals).

### The lanes never cross

A failed customer-facing call falls through the `fallback` list — never to the evaluation models. A failed evaluation call escalates to `evaluation.fallback` — never to a customer-facing model. This is deliberate:

* The customer-facing lane optimises for **voice**; the evaluation lane for **cheap, fast, narrow JSON**. A model great at one is often mediocre at the other.
* Their failure profiles are orthogonal. A throughput problem on the chat model shouldn't degrade routing decisions, and schema-conformance issues on the evaluation model shouldn't change the agent's voice.

## Embeddings

When the [knowledge base](/agents/concepts/knowledge-base) is `type: collections`, vector fields are embedded with the model bound to their target collection slot — never a model declared in the manifest — routed through the same router with the same `api_key`. There is no separate embeddings credential.

## When each stage fires

| Moment                                                | Stage                               | Notes                                                                                         |
| ----------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------- |
| Every reply                                           | `response`                          |                                                                                               |
| Preamble / tool announcement                          | `preamble`                          |                                                                                               |
| Search query rewrite                                  | `search_query` (inherits `default`) | One completion per query field per retrieval                                                  |
| Tool-call inference for your tools                    | `evaluation.tools`                  |                                                                                               |
| Policy matching + response analysis, each turn        | `evaluation.policy_matching`        | Batched: `policy_batch_size` policies per call                                                |
| Routine activation / next-step / backtrack, each turn | `evaluation.routine_navigation`     |                                                                                               |
| Routine metadata evaluation, at boot (cold cache)     | `evaluation.startup`                | The slow part of cold startup — see [Startup evaluation](/agents/concepts/startup-evaluation) |
| Knowledge-base embedding                              | Target slot's embedding binding     | `type: collections` vector fields only                                                        |

## Operational guidance

* **Token ceiling:** the operator env var `ROUTER_MAX_TOKENS` (default 100000) caps context size on router calls. See [Environment variables](/agents/reference/environment).
* **Watch the escalation rate.** Frequent `[retry-fallback]` lines mean a stage's model is struggling with your content's complexity — either simplify conditions or promote that stage to a stronger model. Both models exhausting (logged at ERROR) fails the turn.
* **Changing an evaluation model invalidates nothing**, but startup routine evaluation results are cached by *content* hash, so a model change does not bust the cache — re-evaluate deliberately if you change models and want fresh metadata (see [Startup evaluation](/agents/concepts/startup-evaluation)).
* **Credential:** one router key serves everything. Rotate by updating the secret and restarting; see [Security](/agents/operations/security).

## See also

* [Conversation lifecycle](/agents/concepts/conversation-lifecycle) — the calls in context
* [Limits & defaults](/agents/reference/limits-and-defaults) — every default in one table
* [Observability](/agents/guides/observability) — tracing model calls


# Sessions, memory & state

Sessions, customers, context variables, metadata, step outputs, and the storage backends that persist them.

> **Context** — This page maps every kind of state an agent holds, who writes it, who sees it, and where it lives. The distinction that trips everyone up at least once: **variables** are agent-visible, **metadata** is not.
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## The state model

```
Customer ──── has many ──── Sessions ──── contain ──── Events (offset-ordered)
   │                            │
   ├─ variables (agent-visible) ├─ metadata (integration-only)
   └─ metadata (integration-only)└─ mode: auto | manual
```

### Customers (optional)

A **customer** is the stable identity across conversations — keyed by an id your integration chooses (a CRM id, an email, a ticket-system user id). Customers carry:

* **Variables** — agent-visible context (see below).
* **Metadata** — integration-only key/values the agent never sees.
* A display name.

Customers are a convenience for **customer-facing** agents — they let you carry variables and group sessions across one end-user's conversations. An agent that isn't customer-facing doesn't need the concept at all: a [backend automation](/agents/concepts/autonomous-routines) is triggered with typed input and never references a customer, and even a simple chat integration can open sessions without modelling end-users as customers. Reach for customers only when you have a recurring end-user whose context should persist across sessions.

### Sessions

A **session** is one conversation. A customer can have many (a "new conversation" button, one per support ticket). Sessions carry:

* The **event log**: every message, tool call, and status change, each with a monotonically increasing integer `offset`. The offset is your resume-and-dedupe cursor — see [Integrating the SDK](/agents/guides/integrating-the-sdk).
* **Metadata** — integration-only. Well-known keys the runtime understands:

  | Key                 | Effect                                                                                                                                               |
  | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
  | `session_key`       | Stable human-meaningful id (e.g. a ticket id) used to name and group traces — see [Observability](/agents/guides/observability#trace-naming).        |
  | `event_webhook_url` | Where the agent POSTs events for webhook-mode delivery (the SDK writes this for you).                                                                |
  | `external_id`       | Convention for finding a session by your channel's conversation id.                                                                                  |
  | `step_outputs`      | Written by the runtime: validated outputs of [think nodes](/agents/concepts/autonomous-routines#node-types) (autonomous routines), keyed by node id. |
* **Mode** — `auto` (the engine replies to customer messages) or `manual` (a human has taken over; the engine stays silent while your integration posts human-authored messages). See the [handover section of the SDK guide](/agents/guides/integrating-the-sdk#8-human-handover).

## Variables vs metadata

|                      | Variables                                                                                                      | Metadata                                                                     |
| -------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| Visible to the agent | **Yes** — injected into context on the next turn                                                               | **No** — never reaches a prompt                                              |
| Lives on             | Customer                                                                                                       | Customer or session                                                          |
| Written by           | Your integration (SDK), any time                                                                               | Your integration; a few keys by the runtime                                  |
| Use for              | Anything the agent should *know*: plan tier, loyalty status, open-ticket count, "documents were just uploaded" | Anything you need to *look up later*: channel ids, webhook URLs, bookkeeping |

```python
# Agent-visible — readable from routines/policies on the next turn.
await client.customers.set_variables(
    customer.id,
    {"loyalty_tier": "gold", "open_tickets": 3},
)

# Integration-only — the agent does NOT see this.
await sess.update_metadata({"chatwoot_conversation_id": "789"})
```

Variables take effect on the **next turn** — the engine snapshots context when a turn starts. Any JSON-serialisable value works (values are stringified for the model). Variables are referenced naturally from content: a routine condition can say "the customer's `loyalty_tier` is gold".

Variables are configured per-deployment through the SDK at runtime — they are **not** declared in the manifest.

## Step outputs

Each [think node](/agents/concepts/autonomous-routines#node-types) persists its validated JSON to `session.metadata.step_outputs[<node-id>]`, merged across the nodes of a run. Downstream nodes see the inference call and result in history; your integration can read the typed values off the session after the fact — useful for auditing what the agent concluded mid-flow.

## Storage backends

Where all of this lives is one manifest decision:

| Configuration                 | Backend    | Survives restart | Use for                                        |
| ----------------------------- | ---------- | ---------------- | ---------------------------------------------- |
| omit `agent_config.database`  | In-memory  | **No**           | Demos, tests, stateless autonomous-only agents |
| `agent_config.database` block | PostgreSQL | Yes              | Production conversational agents               |

```yaml
agent_config:
  database:
    hostname: agent-postgres.internal.example.com
    port: 5432
    user: postgres
    password: ${DB_PASSWORD}
    dbname: postgres
    sslmode: require
```

(`hostname` and `password` are required; `port`/`user`/`dbname`/`sslmode` default to `5432`/`postgres`/`postgres`/`require`.)

With in-memory storage, a restart or redeploy erases every session — open conversations reset mid-dialogue. If customers ever come back to continue a conversation, use Postgres. Schema migrations run automatically at boot.

The knowledge base is a **separate** Postgres concern (`agent_config.search`, see [Knowledge base & retrieval](/agents/concepts/knowledge-base)) — the two may share a server but are configured independently.

## Lifecycle notes

* **Ephemeral autonomous sessions** — a triggered run without a `session_id` creates a throwaway customer + session and deletes both after the callback settles. Pass your own `session_id` to keep the run's history. See [Autonomous routines](/agents/concepts/autonomous-routines).
* **History is the agent's memory.** There is no hidden long-term memory beyond what this page lists: the event log, variables, metadata, and step outputs. What you see in the session is what the model can know.

## See also

* [Integrating the SDK](/agents/guides/integrating-the-sdk) — reading and writing all of this from code
* [Routines](/agents/concepts/routines) — think nodes and step outputs
* [Deploying](/agents/guides/deploying) — provisioning the database


# Startup evaluation

What the engine pre-computes about your routines and policies at boot, the stages it runs and the purpose of each, and why doing it once at startup keeps every turn fast.

> **Context** — Before an agent serves traffic, the engine studies every routine and policy and pre-computes the behavioural metadata it will lean on at runtime. This page explains the stages of that **startup evaluation** and the purpose of each, plus how it's cached and tuned. It assumes [Routines](/agents/concepts/routines) (nodes, transitions) and [Policies](/agents/concepts/policies); watching it run is in [Observability](/agents/guides/observability).
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## Why evaluation exists

Your routines and policies are written in natural language: a node says "Send it to the manual-review queue", a policy condition says "the customer asked about their balance". To run a turn quickly and consistently, the engine needs sharper, structured answers about that language — *is this step a pure tool call or does it speak? does it need the customer to reply before the routine moves on? which node can come next, and when?*

Working those answers out is itself model-driven and not cheap. Doing it on **every turn** would make every turn slow and its behaviour non-deterministic. So the engine does it **once, at startup**, derives the metadata, and [caches it](#caching-cold-vs-warm-boots). Runtime turns then read precomputed answers instead of re-deriving them.

## Where it sits in boot

Startup evaluation is the last phase of the [boot sequence](/agents/concepts/architecture#boot-sequence), and it runs **before the agent begins serving**: the HTTP port binds and health checks start passing only once evaluation settles. On a warm cache it's a no-op (every item is a cache hit), so the agent comes up immediately; on a cold cache it can take minutes for a content-heavy agent, and the agent is simply unreachable until it finishes. That's why the cache is [pre-warmed](#caching-cold-vs-warm-boots) — so a content change deploys fast instead of waiting through a full evaluation.

## What gets evaluated

Two things, independently:

* **Every routine**, node by node — each node's action and its outgoing transitions.
* **Every policy** — both the agent-wide policies and each routine's activation conditions.

Routines are evaluated in parallel with one another, and the work within a routine is parallelised too; concurrency is an implementation detail with one operator knob — see [Tuning live evaluation](#tuning-live-evaluation). What matters conceptually is **what** is derived and **why**.

## The stages

### Stage 1 — Understand each action

The engine reads each routine node and each policy and works out a handful of properties that decide how that step behaves at runtime:

| Derived property                       | What it answers                                                                                                     | Why the runtime needs it                                                                                                                                                                                                                        |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Clarified action**                   | What, precisely, is this step instructing — phrased unambiguously and aligned with the tool it calls?               | Raw wording is often vague ("handle it"). A sharpened instruction is what message generation and next-step selection actually work from.                                                                                                        |
| **Tool-only vs. speaks**               | Is this node a pure tool call with no customer-facing message?                                                      | If tool-only, the engine runs the tool and **skips message generation** — no reply to compose, no risk of inventing one. This is the [TOOL-node](/agents/concepts/routines#tool-node-completes-when-the-tool-executes) behaviour, decided here. |
| **Customer-dependent**                 | Does completing this step require the customer to respond, or is it purely agent-side?                              | If customer-dependent, the engine **waits for the customer's reply before advancing** instead of moving on the moment it has spoken. This is what makes a "ask, then act on the answer" routine pause at the right place.                       |
| **Continuous vs. one-shot** (policies) | Should this policy stay in force every turn, or apply once and retire?                                              | The matcher keeps continuous policies (e.g. "always speak formally") active across turns and drops one-shot ones after they fire — so a standing rule isn't forgotten after the first turn it applies.                                          |
| **Prospective condition** (policies)   | Does the condition describe something the agent is *about to do*, or something already in the conversation history? | The matcher evaluates a prospective condition against the agent's *next* move rather than searching the past for it — so "you are going to ask for ID" matches at the right moment.                                                             |

The first three apply to routine nodes; the last two additionally shape how policies match. Together they're why the same routine text produces "run the tool silently" in one node and "say this, then wait" in another — the distinction is computed here, not guessed per turn.

### Stage 2 — Make each action self-contained

Routine steps are often written relative to their neighbours — "send **it** to review", "notify the customer of **the outcome**". At runtime the engine fires one step's instruction without re-reading the whole routine, so a dangling reference would force it to reconstruct the antecedent (slow, and a chance to get it wrong).

This stage rewrites those steps to **stand alone** — "send the failed ID verification result to the manual-review queue" — resolving the reference using the routine's structure. Steps that were already self-contained are left untouched.

### Stage 3 — Map the routes

For each node, the engine computes its **reachable follow-ups**: which nodes can come next, and the exact condition under which each path is taken. This is the routine's routing table, derived from the nodes' outgoing transitions and their conditions, computed children before parents (a post-order traversal) so each node's map accounts for what lies beyond its immediate children.

At runtime, when a turn needs to decide where the routine goes next, the engine consults this precomputed table instead of re-analysing the whole graph on every turn — which would be both expensive and error-prone (easy to miss a transitive path or misread an edge condition). It's the machine-readable form of the `transitions` you author; see [Routines](/agents/concepts/routines#transitions-terminals-and-movement).

## What it produces, and where it shows up at runtime

The stages above attach, to each routine node and policy, the metadata the engine reads during a turn:

| Runtime decision                                         | Driven by                       |
| -------------------------------------------------------- | ------------------------------- |
| Skip composing a reply for a pure tool step              | Stage 1 — tool-only             |
| Wait for the customer before advancing a routine         | Stage 1 — customer-dependent    |
| Keep a standing policy in force across turns             | Stage 1 — continuous            |
| Evaluate a policy condition as a future intent           | Stage 1 — prospective condition |
| Fire a step's instruction without re-reading the routine | Stage 2 — self-contained action |
| Pick the next node when a turn advances                  | Stage 3 — reachable follow-ups  |

None of this changes *what you authored* — it's the engine's prepared reading of it. The per-turn mechanics that consume this metadata are in [Conversation lifecycle](/agents/concepts/conversation-lifecycle).

## Caching: cold vs. warm boots

Evaluation results are **keyed by content hash** — a policy by its condition, action, and tools; a routine by its id and the ordered ids of its nodes and transitions. At boot the engine looks each item up by hash:

* **Hit** → the metadata loads instantly, with no model calls.
* **Miss** → the item is evaluated live (the slow path), and the result is written back to the cache.

Because the key is the content, results stay valid until the content changes, and editing one routine invalidates only that routine — every other item is still a hit.

**The platform warms the cache as part of deploying content**: when you deploy a manifest pinning new content versions, the platform pre-computes their evaluations so the agent boots from a warm cache and comes up fast. If an agent ever boots cold (an item that wasn't pre-warmed), it still comes up correctly — it evaluates live on first boot, minutes rather than a failure — but, as noted above, it isn't reachable until that finishes. So a cold cache doesn't break anything; it just makes the deploy slower to go live.

### What invalidates the cache

The content hash makes the rules simple:

* **No effect** (cache stays valid): runtime upgrades, manifest tuning-knob changes, secret rotations — none change content hashes.
* **Invalidated automatically** (new hash → re-evaluated on next deploy): any edit to a policy's condition / action / tools, or a routine's nodes / transitions.
* **The one blind spot:** changing the evaluation *model* (`llms.evaluation`) does **not** change content hashes, so cached metadata is reused as-is. To recompute under a new evaluation model, ask your platform operator to force a re-evaluation of the content set.

## Tuning live evaluation

When evaluation does run live, one operator knob shapes it: `EVAL_NODE_PARALLELISM` (default 50) caps how many per-node evaluation calls run concurrently — higher is faster but bounded by the LLM router's rate budget; `1` forces fully sequential evaluation for debugging. It's a platform/operator setting (see [Environment variables](/agents/reference/environment)). The model doing the work is `llms.evaluation`, with the standard retry/fallback behaviour — see [Models](/agents/concepts/models).

## Watching it

Evaluation emits one log line per routine — `Routine '<title>' evaluated: N nodes in Xs` (`N=0` means it was served from cache) — with per-stage detail at debug level. See [Observability](/agents/guides/observability#boot-time-evaluation-logs).

## See also

* [Conversation lifecycle](/agents/concepts/conversation-lifecycle) — how a turn consumes this metadata
* [Routines](/agents/concepts/routines) and [Policies](/agents/concepts/policies) — the inputs being evaluated
* [Architecture](/agents/concepts/architecture#boot-sequence) — where evaluation sits in boot


# Quickstart

Build your first agent on the InteractiveAI platform with the iai CLI: author one policy and one routine, stand up a tool server, deploy the agent, and talk to it through the chat UI or the SDK.

> **Context** — This guide builds a minimal but real agent — **Mercedes**, a car-rental assistant for "DriveAway" — on the InteractiveAI platform. The platform hosts and runs the agent; you author its content, deploy it with the `iai` CLI, and talk to it over the chat UI or the SDK. You don't run any agent container yourself. Time: \~30 minutes.
>
> **Prerequisites:**
>
> * A **project created in the InteractiveAI platform** (an organization + project you can deploy into).
> * The **`iai` CLI** installed and authenticated — run `iai login`, then `iai organizations` / `iai projects` to select your org and project (or pass `-o`/`-p` on each command).
> * An **LLM router API key** and a **project API key pair** (Platform UI → Project Settings → API Keys).
> * Python 3.12+ for the tool server and the SDK client.
>
> Every `iai` subcommand has `--help` (e.g. `iai routines create --help`); use it for the full flag set.
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## What you'll build

```
  you author          platform hosts             you run / consume
  ──────────          ──────────────             ─────────────────
  system prompt  ┐
  policy         ├──► InteractiveAI ──► Mercedes ──chat UI / SDK──► you
  routine        ┘    (content +        (agent)   ◄──
  agent config        runtime)             │
                                           └──MCP──► your car-catalog tool server
```

Everything below is authored **in the platform** with `iai` — the platform catalog is the source of truth for your content; there's nothing to keep in sync locally. The steps: author the **content** (system prompt, policy, routine), stand up a **tool server**, then **deploy** the agent and chat with it.

## 1. Author the content in the platform

Content — the system prompt, routines, policies, glossaries, and macros — lives in the platform's versioned catalog. Each `iai … create` stores one item (and assigns it version 1); the agent config you write in step 3 references each by name and pins a version.

### System prompt

The agent's persona and ground rules — an unstructured **prompt**. Create it inline:

```bash
iai prompts create system-prompt --content "You are Mercedes, a friendly \
booking specialist at DriveAway, a car-rental agency. Help customers find \
the right car; keep answers short and concrete. Fleet: economy, compact, \
SUV, van, luxury. Daily rates ~35 EUR (economy) to ~140 EUR (luxury). \
Minimum driver age is 21. Warm, professional, no emoji; quote prices in \
EUR per day."
```

For anything longer, put the text in a file and use `--file system-prompt.txt` instead of `--content`.

### Policy

A condition → action rule applied on every turn. Put it in `stay-on-topic.yaml`:

```yaml
id: stay-on-topic
name: Stay On Topic
condition: >
  The user asks about something unrelated to car rental, the DriveAway
  fleet, bookings, or related logistics — e.g. life advice, news,
  unrelated products.
action: >
  Politely acknowledge the question, explain that you can only help with
  car rental at DriveAway, and offer one concrete next step — browsing
  the fleet or making a booking.
criticality: HIGH
```

```bash
iai policies create stay-on-topic --file stay-on-topic.yaml
```

(Concepts: [Policies](/agents/concepts/policies). More patterns: [Authoring policies](/agents/guides/authoring-policies). `iai policies schema` prints the full field set.)

### Routine

A multi-node flow. Put it in `car-search.yaml`:

```yaml
id: car-search
title: Car Search
conditions:
  - >
    The user wants to browse the fleet, find a car, or asks for car
    suggestions — phrases like "what cars do you have", "show me an SUV",
    "I need a 7-seater".
description: >
  Help the user narrow down the fleet by collecting their criteria,
  searching the catalog, and presenting results with prices.

entry: gather-criteria
nodes:
  - id: gather-criteria
    chat_state: >
      Ask the user for their preferences in one short message: category
      (economy / compact / SUV / van / luxury), minimum number of seats,
      transmission preference (manual or automatic), and any daily-budget
      cap in EUR. Tell them any field is optional.
    transitions:
      - to: run-search
        condition: >
          The user has provided at least one preference, or has said
          "anything" / "no preference".

  - id: run-search
    tools: cars:search_cars
    tool_instruction: >
      Call search_cars with the filters the user provided. Pass null /
      omit fields the user did not mention. Do not invent constraints.
    transitions:
      - to: present-results

  - id: present-results
    chat_state: >
      Summarise the matching cars in a compact list — one line per car
      with make, model, category, transmission, and daily price in EUR.
      If there are zero matches, suggest relaxing one specific filter.
      End by asking if the user wants to book one.
```

```bash
iai routines create car-search --file car-search.yaml
```

Note the structure: a **chat** node (ask), a **tool** node (search), a **chat** node (present), connected by transitions — never tools and speech on the same node. This is the single most important authoring rule; see [Routines](/agents/concepts/routines#node-types-read-this-first).

## 2. Stand up the tool server

The `run-search` node calls `cars:search_cars` — a tool your agent reaches over the Model Context Protocol. You host this server; the platform-run agent connects to it at the address you'll put in the agent config, so it must be reachable from the platform (a public URL or one your platform networking allows). `cars_mcp.py`:

```python
"""Minimal car-catalog MCP server for the DriveAway quickstart."""

from fastmcp import FastMCP

mcp = FastMCP("car-rental")

CATALOG = [
    {"car_id": "eco-1", "make": "Toyota", "model": "Yaris",
     "category": "economy", "seats": 5, "transmission": "manual",
     "daily_price_eur": 35},
    {"car_id": "suv-1", "make": "Volvo", "model": "XC60",
     "category": "suv", "seats": 5, "transmission": "automatic",
     "daily_price_eur": 95},
    {"car_id": "van-1", "make": "VW", "model": "Multivan",
     "category": "van", "seats": 7, "transmission": "automatic",
     "daily_price_eur": 110},
]


@mcp.tool
def search_cars(
    category: str | None = None,
    min_seats: int | None = None,
    transmission: str | None = None,
    max_daily_price_eur: int | None = None,
) -> dict:
    """Search the catalog for cars matching optional filters.

    Args:
        category: economy, compact, suv, van, or luxury.
        min_seats: minimum number of seats (e.g. 5, 7).
        transmission: manual or automatic.
        max_daily_price_eur: cap on daily rental price in EUR.

    Returns:
        {"results": [{car_id, make, model, category, seats,
        transmission, daily_price_eur}, ...]}
    """
    results = [
        car for car in CATALOG
        if (category is None or car["category"] == category.lower())
        and (min_seats is None or car["seats"] >= min_seats)
        and (transmission is None or car["transmission"] == transmission.lower())
        and (max_daily_price_eur is None
             or car["daily_price_eur"] <= max_daily_price_eur)
    ]
    return {"results": results}


if __name__ == "__main__":
    mcp.run(transport="streamable-http", host="0.0.0.0", port=8765)
```

```bash
pip install fastmcp
python cars_mcp.py
```

The docstring is not decoration — the agent's model reads it to decide how to call the tool. See [Connecting tools](/agents/guides/connecting-tools).

## 3. Write the agent config

The agent config is what the agent *does*: which model it uses, which content it loads, and which tool servers it connects to. It references the content you created by name and pins exact versions. `agent-config.yaml`:

```yaml
runtime:
  api_key: ${AGENT_API_KEY}
interactive_platform:
  public_key: ${INTERACTIVEAI_PUBLIC_KEY}
  secret_key: ${INTERACTIVEAI_SECRET_KEY}
llms:
  default: interactive/anthropic/claude-haiku-4.5
  api_key: ${ROUTER_API_KEY}

context:
  system_prompt:
    id: system-prompt
    version: 1

  language: match_user

  greeting: "Hi! I'm Mercedes from DriveAway — how can I help you with your car rental today?"

  preamble:
    examples:
      - "Let me check."
      - "One moment."

  routines:
    - id: car-search
      version: 1

  policies:
    - id: stay-on-topic
      version: 1

mcps:
  - id: cars
    hostname: https://cars-mcp.example.com
    port: 443
    transport: streamable-http
```

Things to notice:

* This file is the **`agent_config`** block — the agent's identity (name, agent type, runtime version, secrets, endpoint) is passed as CLI flags in step 4, not written here. (The [manifest reference](/agents/reference/manifest) documents the full object; the CLI splits it into config-file + flags.)
* Every secret is a `${VAR}` env-ref — the platform supplies the value at deploy time from the secret bundle; literal secrets are rejected.
* `context.*` entries reference the content you authored in step 1 by name and pin an exact version.
* `mcps[].hostname` is where the running agent reaches your tool server.
* `iai agents schema --schema-version 6.1.2` prints the full config schema.

## 4. Deploy the agent

Create the secret bundle carrying the four `${VAR}` values the config references:

```bash
iai secrets create driveaway-secrets \
  --data AGENT_API_KEY=devsecret \
  --data ROUTER_API_KEY=your-interactiveai-router-key \
  --data INTERACTIVEAI_PUBLIC_KEY=your-public-key \
  --data INTERACTIVEAI_SECRET_KEY=your-secret-key
```

Then create the agent. `--id` is the agent **type** from the marketplace (`interactive-agent`); `--version` is the runtime image version (run `iai agents catalog` to list available versions); `--endpoint` exposes a URL you can reach:

```bash
iai agents create driveaway-demo \
  --id interactive-agent \
  --version <runtime-version> \
  --file agent-config.yaml \
  --secret driveaway-secrets \
  --endpoint
```

The platform validates the config, fetches the pinned content, connects your tool server, and starts the agent. On first deploy it also runs [startup evaluation](/agents/concepts/startup-evaluation) (model calls — a minute or two for this one routine); the agent becomes reachable once that settles, and subsequent deploys reuse the evaluation cache.

To change anything later — new content versions, a different model, a runtime upgrade — edit and re-run with `iai agents update driveaway-demo --file agent-config.yaml` (or `--version <new>` for a runtime upgrade). Check progress and the assigned URL with `iai agents describe driveaway-demo`.

## 5. Talk to it

### Through the chat UI

With `--endpoint`, the agent is exposed at a URL like `https://driveaway-demo-<project-hash>.interactive.ai` (read the exact one from `iai agents describe driveaway-demo`). The built-in **chat UI** is that URL with `/chat` appended:

```
https://driveaway-demo-<project-hash>.interactive.ai/chat
```

Open it in a browser, sign in with the agent API key when prompted (the UI sets a cookie so its own requests authenticate), and chat with Mercedes directly — the fastest way to try the agent without writing code.

### Through the SDK

For a real integration, reach the agent over the SDK at the same endpoint URL, authenticating with the `AGENT_API_KEY`:

```bash
pip install "interactiveai[agent]"
```

`chat.py`:

```python
import asyncio
import os

from interactiveai.agent import (
    AssistantMessage,
    InteractiveAgentClient,
    Preamble,
    StatusEvent,
)


async def main() -> None:
    async with InteractiveAgentClient(
        base_url=os.environ["AGENT_URL"],
        api_key=os.environ["AGENT_API_KEY"],
    ) as client:
        sess = await client.sessions.open(id="quickstart-user")
        await sess.post_user_message("I need an automatic SUV for 5 people")

        async for ev in sess.events():
            if isinstance(ev, Preamble):
                print(f"mercedes (working): {ev.text}")
            elif isinstance(ev, AssistantMessage):
                print(f"mercedes: {ev.text}")
            elif isinstance(ev, StatusEvent) and ev.status == "ready":
                break


asyncio.run(main())
```

```bash
AGENT_URL=https://driveaway-demo-<project-hash>.interactive.ai \
  AGENT_API_KEY=devsecret python chat.py
```

Either way you should see the greeting, possibly a short preamble while the search tool runs, and a reply quoting the Volvo XC60 at 95 EUR/day. Try `"what's the meaning of life?"` to watch the stay-on-topic policy fire.

## 6. What just happened

1. `iai agents create` submitted your config; the platform resolved the `${VAR}` secrets from `driveaway-secrets` and loaded the prompt, routine, and policy from the catalog at their pinned versions.
2. The agent connected to your MCP server and catalogued `cars:search_cars`.
3. Your message activated the `car-search` routine (its condition matched); the engine walked chat → tool → chat across the turns, matching the policy set on every turn. The full mechanics: [Conversation lifecycle](/agents/concepts/conversation-lifecycle).

## Where to go next

| Goal                                                   | Guide                                                                         |
| ------------------------------------------------------ | ----------------------------------------------------------------------------- |
| Richer flows: branching, multi-tool                    | [Authoring routines](/agents/guides/authoring-routines)                       |
| More behaviour rules                                   | [Authoring policies](/agents/guides/authoring-policies)                       |
| Integrate your tools / other providers' tools over MCP | [Connecting tools](/agents/guides/connecting-tools)                           |
| A real channel (web, Zendesk, Slack)                   | [Integrating the SDK](/agents/guides/integrating-the-sdk)                     |
| Typed backend automations                              | [Authoring autonomous routines](/agents/guides/authoring-autonomous-routines) |
| Ground answers in documents                            | [Setting up the knowledge base](/agents/guides/knowledge-base-setup)          |
| The full platform deploy lifecycle                     | [Deploying](/agents/guides/deploying)                                         |


# Authoring policies

How to write policies that match when they should, act as intended, and don't fight your routines — patterns, anti-patterns, and a review checklist.

> **Context** — This guide assumes the [Policies](/agents/concepts/policies) concept page. It's organised as: when to reach for a policy, writing conditions, writing actions, choosing knobs, patterns, anti-patterns, checklist.
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## When to create a policy

Reach for a policy when a rule is **cross-cutting** — it should hold across many processes, regardless of which routine (if any) is active. Policies are the agent's standing rules; they're matched on every turn and layered on top of whatever flow is running.

Create a policy when the rule is:

* **A behaviour that spans routines** — "always answer in the customer's language", "never reveal another customer's data", "quote prices in EUR". You don't want to copy this into every routine; state it once as a policy.
* **A safety / compliance / escalation trigger** that can fire at any point — "if the user reports an accident, hand off to a human" — independent of where the conversation is.
* **A reaction to a condition**, not a sequence of steps — one situation, one response.

Reach for a [routine](/agents/guides/authoring-routines) instead when the behaviour is a **procedure**: an ordered, multi-step flow (collect → look up → branch → respond) with its own state. A rule that "happens to need three steps" is a routine wearing a policy's clothes — see the [one-action rule](#writing-actions-that-do-what-you-meant) below.

## Writing conditions that match correctly

The condition is read by the policy matcher every turn and answered as a yes/no: *does this apply to the conversation right now?* Write it for that reader:

**Be concrete about triggers.** Name the phrasings, not just the abstract category:

```yaml
condition: >
  The user mentions a driver who is under 21 years old, or asks whether
  someone under 21 can rent.
```

beats `condition: "The driver age requirement is relevant."` — abstractions make the matcher guess.

**Include the negative space when a sibling rule exists.** If two policies could plausibly both match, carve the boundary into the conditions themselves ("…but NOT when the user is asking about an existing booking").

**Reference state explicitly.** Conditions can read [variables](/agents/concepts/memory-and-state#variables-vs-metadata) and tool results from the conversation: `"player_info is not available AND the customer's full name and date of birth are present"` is a perfectly good condition.

## Writing actions that do what you meant

The action is a binding instruction for the turn. Four rules:

1. **Say what to do, then what not to do.** Models follow positive instructions better; reserve prohibitions for the genuinely dangerous part:

   ```yaml
   action: >
     State plainly that the minimum driver age at DriveAway is 21, so the
     booking cannot proceed for that driver. If there is another adult in
     the party who is 21 or older, offer to book under that person's name
     instead. Do not invoke the booking tool with an under-age driver.
   ```
2. **One policy, one rule.** An action that handles four unrelated cases should be four policies — each gets its own condition, criticality, and trace visibility.
3. **Tool + speech is fine — but it must be one action.** Unlike routine nodes, a policy action may call a tool *and* address the customer in one turn:

   ```yaml
   id: auth-on-identity
   name: Authenticate On Identity
   condition: >
     The customer is not yet authenticated and has provided their full
     name and full date of birth.
   action: >
     Call crm:authenticate_customer with the full name as a single string
     and the date of birth, then confirm to the customer that they're
     verified.
   tools:
     - crm:authenticate_customer
   reevaluate_after:
     - crm:authenticate_customer
   ```

   The catch is **single action**. "Call a tool and tell the customer the outcome" is one action. But if the action is really *two* — e.g. *ask the customer for their credentials* **and then** *authenticate them* — that's a sequence with a customer turn in the middle, which a single policy can't reliably drive. Reconsider it as:

   * a [**routine**](/agents/guides/authoring-routines) (ask → authenticate is a two-node flow with a customer reply between them), or
   * **two policies** (one that prompts for credentials when they're missing; one that authenticates once they're present, gated by `reevaluate_after` so it fires in the same turn the credentials arrive).

   If you can't phrase a policy's action as a single sentence without an "and then", it's a procedure — author it as a routine.
4. **Your line breaks reach the model exactly as authored.** Conditions and actions are rendered verbatim into the model's instructions (trailing whitespace trimmed), so YAML block style is part of the prompt:
   * Use a **literal block (`|`)** when the text has intentional structure — numbered steps, BAD/GOOD example pairs, paragraphs. Every line break you type is a line break the model reads.
   * Use a **folded block (`>`)** for a single flowing paragraph, and keep every continuation line at the same indentation. A line indented deeper than the rest keeps its literal line break and extra spaces, producing a mid-sentence break in the rendered instruction:

     ```yaml
     # BAD: the deeper-indented line keeps its break — the model sees
     # "…gather the following context\n  simultaneously:"
     action: >
       To answer the inquiry, you must gather the following context
         simultaneously:
     # GOOD: uniform indentation folds into one sentence
     action: >
       To answer the inquiry, you must gather the following context
       simultaneously:
     ```

## Choosing the knobs

| Knob                                                                  | Set it when                                                                                                                                                                         |
| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `criticality: HIGH`                                                   | Safety, compliance, money, hard prohibitions — rules you want flagged as mandatory. (`HIGH` and `MEDIUM` behave the same today; the level is a signal, not a conflict tie-breaker.) |
| `criticality: MEDIUM` (default)                                       | The bulk of ordinary business rules — enforced as mandatory instructions.                                                                                                           |
| `criticality: LOW`                                                    | Style/tone preferences the agent may deprioritise — rendered as soft guidance, so an occasional miss is acceptable.                                                                 |
| `always_match: true`                                                  | The rule must hold even if the matcher would judge it irrelevant — regulatory disclaimers, absolute prohibitions. Costs prompt space every turn; budget these.                      |
| `reevaluate_after: [tool]`                                            | The policy's relevance flips after that tool runs (auth, account status) — the match is re-run once the tool executes, whether it succeeds or errors.                               |
| `metadata`                                                            | Anything your team's tooling wants to read off traces (severity, owner, ticket).                                                                                                    |
| Routine-scoped (`policies:` inside a routine, explicit `id` required) | The rule only makes sense mid-flow of that routine.                                                                                                                                 |

When a policy must categorically beat routines or other policies, declare it in [priorities](/agents/concepts/priorities) — criticality does not resolve conflicts, so it is never a substitute for a priority.

## Patterns

**The guard** — block a specific dangerous action:

```yaml
id: no-pii-in-chat
name: Never Read Back Secrets
condition: >
  The user asks the agent to read back, confirm, or repeat a full card
  number, password, or other secret.
action: >
  Refuse clearly and briefly: explain you can never display full card
  numbers or passwords, and point the user to the secure account page
  for anything credential-related.
criticality: HIGH
```

**The redirector** — keep the agent in its lane (see `stay-on-topic` in the [Quickstart](/agents/guides/quickstart#policy)).

**The escalator** — hand off when out of depth:

```yaml
id: incident-handoff
name: Incident Handoff
condition: >
  The user reports an accident, injury, breakdown, theft, or any
  emergency involving a rental car.
action: >
  Express concern first. Provide the emergency assistance line
  (+800 5555 0199, 24/7) and offer to connect them to a human agent
  immediately. Do not attempt to assess fault or give legal advice.
criticality: HIGH
```

**The state-gated authenticator** — see `auth-on-identity` above: fires only while unauthenticated, self-disarms via `reevaluate_after`.

**The disclaimer** — `always_match: true` plus a short action that appends required wording when quoting prices/terms.

## Anti-patterns

| Anti-pattern                                                                                                  | Why it fails                                                                                              | Instead                                                                         |
| ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| **A procedure in an action** ("first ask X, then call Y, then confirm Z")                                     | Policies have no step state; the model gets the whole script every turn and improvises its position in it | Make it a [routine](/agents/guides/authoring-routines)                          |
| **Condition that needs the action's result** ("the lookup shows the account is locked" before any lookup ran) | The matcher reads the conversation as-is                                                                  | Split: one policy/node calls the tool; another conditions on its result         |
| **Mirror-image pairs** ("if X do A" + "if not-X do B")                                                        | Doubles matcher load; the negative usually belongs in the system prompt or the first policy's action      | One policy with both arms in the action                                         |
| **Everything `always_match`**                                                                                 | Burns prompt space; dilutes the genuinely critical                                                        | Trust the matcher for conditional rules                                         |
| **Vague meta-conditions** ("the conversation is going badly")                                                 | Unanchorable judgement → erratic matching                                                                 | Name observable triggers (user swears, repeats a question 3×, asks for a human) |

## Versioning workflow

Policies are versioned documents; the manifest pins exact versions. The working loop:

1. Publish the new policy version to the platform catalog.
2. Bump the pin in the manifest (`context.policies[].version`).
3. Deploy. The trace snapshot records which versions were live for every turn, so you can correlate behaviour changes with policy changes — see [Observability](/agents/guides/observability).

## Review checklist

* [ ] Condition names concrete triggers (phrases, observable state), not categories
* [ ] Condition carves boundaries against sibling policies that could co-match
* [ ] Action says what to do first, prohibitions second
* [ ] One rule per policy
* [ ] Tools listed under `tools:` if the action calls them
* [ ] `reevaluate_after` set if a tool flips this policy's relevance
* [ ] `always_match` only for must-always-hold rules
* [ ] Conflicts with routines resolved via [priorities](/agents/concepts/priorities), not hope


# Authoring routines

Building routines node by node: the golden rule, transitions and branching, tool chains, loops, common patterns, anti-patterns, and the pre-submit checklist.

> **Context** — This guide assumes the [Routines](/agents/concepts/routines) concept page (node kinds, classification, completion semantics). Here we build up a real routine and collect the patterns that keep flows reliable.
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## The golden rule

**A node does exactly one thing.** It calls tools, *or* speaks, *or* routes. Never two of these at once. (A fourth kind — the typed-inference **THINK** node — belongs to [autonomous routines](/agents/guides/authoring-autonomous-routines), not conversational ones.)

Wrong — the schema rejects `tools` + `chat_state` on one node, and for good reason:

```yaml
  - id: handoff
    tools: crm:initiate_human_handoff
    tool_instruction: "Initiate human handoff."
    chat_state: "Inform the customer they are being transferred."  # REJECTED
```

Right — two nodes connected by a transition:

```yaml
  - id: handoff-tool
    tools: crm:initiate_human_handoff
    tool_instruction: "Initiate human handoff."
    transitions:
      - to: handoff-msg

  - id: handoff-msg
    chat_state: >
      Inform the customer they are being transferred to a human agent.
```

## Activation conditions

`conditions` decide when the whole routine engages. Same craft as [policy conditions](/agents/guides/authoring-policies#writing-conditions-that-match-correctly): concrete phrasings, explicit boundaries against sibling routines.

```yaml
conditions:
  - >
    The user wants to make a reservation — phrases like "I want to book
    this", "let's reserve the Golf", "can you book it". Activates after
    the user has identified a specific car, or when they explicitly name
    a make/model they want to rent.
```

A booking routine and a search routine live side by side precisely because each condition names its own triggers and excludes the other's ("Do NOT activate when the user is referring to a specific booking they already have").

Conditions and `chat_state` texts reach the model exactly as authored, so YAML block style matters — see [policy rule 4](/agents/guides/authoring-policies#writing-actions-that-do-what-you-meant) for when to use a literal block (`|`) versus a folded block (`>`).

## The graph: entry, transitions, terminals

Execution starts at the node named by `entry`. Edges live on the node that produces them:

```yaml
entry: ask-dates
nodes:
  - id: ask-dates
    chat_state: >
      Ask for the pickup date and return date in one short message.
    transitions:
      - to: fetch-availability
        condition: "The user has provided both dates."

  - id: fetch-availability
    tools: cars:check_availability
    tool_instruction: >
      Call check_availability with pickup_date and return_date as
      YYYY-MM-DD strings.
    transitions:
      - to: present-availability

  - id: present-availability
    chat_state: >
      Present the available cars for those dates with daily prices in EUR.
```

The rules to internalise:

* **One transition → condition optional** (unconditional advance).
* **Two or more transitions → every one needs a condition.** The engine evaluates them and takes the matching branch.
* **No `transitions` at all → terminal node.** The routine completes there.
* **Loops are first-class.** Transition back to an earlier node id to re-ask until the answer validates — no special syntax.

## Tool nodes: instructions are parameter maps

`tool_instruction` tells the model **how to call the tool** — which arguments, derived from where, in what format. It is not customer-facing and should contain zero messaging:

```yaml
  - id: create-the-booking
    tools: cars:create_booking
    tool_instruction: >
      Call create_booking with all collected fields. Pass car_id,
      pickup_location, pickup_date and return_date (YYYY-MM-DD strings),
      customer_name, driver_age. Pass extras as a list of extra_ids from
      list_extras (omit if none). Pass member_email only if the member
      lookup succeeded.
    transitions:
      - to: announce-result
```

Multiple sequential tools chain naturally (each tool node advances in the same turn). Independent tools can share one node:

```yaml
  - id: fetch-data
    tools:
      - cars:get_booking_history
      - cars:get_loyalty_status
    tool_instruction: >
      Get the customer's past bookings and their loyalty status using the
      customer id. Execute both in parallel.
    transitions:
      - to: present-findings
```

## Branching: conditions on transitions

The flow splits wherever a node declares multiple conditioned transitions. Action nodes can branch directly:

```yaml
  - id: fetch-status
    tools: crm:get_account_status
    tool_instruction: "Get the account status using PARTYID."
    transitions:
      - to: active-path
        condition: "account status is ACTIVE"
      - to: suspended-path
        condition: "account status is SUSPENDED"

  - id: active-path
    chat_state: "Tell the customer their account is in good standing."

  - id: suspended-path
    chat_state: >
      Explain the account is suspended and offer to connect them with a
      human agent.
```

When a decision point needs no action of its own, use a **routing-only node** (transitions, no action):

```yaml
  - id: triage
    transitions:
      - to: vip-flow
        condition: "the customer's loyalty_tier is gold or platinum"
      - to: standard-flow
        condition: "the customer has no premium loyalty tier"
```

## Asking the customer and acting on the answer

A chat node whose transitions depend on the customer's reply is the question-and-dispatch idiom:

```yaml
  - id: ask-resubmit
    chat_state: >
      Ask the customer if they would like to resubmit their documents.
    transitions:
      - to: generate-link
        condition: "The customer wants to resubmit"
      - to: acknowledge-no
        condition: "The customer does not want to resubmit"

  - id: generate-link
    tools: crm:generate_upload_link
    tool_instruction: "Generate a document upload link using PARTYID."
    transitions:
      - to: share-link

  - id: share-link
    chat_state: "Share the upload link with the customer."

  - id: acknowledge-no
    chat_state: "Acknowledge and offer further help."
```

The engine waits on the customer after `ask-resubmit` (the transitions depend on their answer), then takes the matching branch — possibly several turns later, and it can [backtrack](/agents/concepts/routines#transitions-terminals-and-movement) if they change their mind.

## Reusable wording: macro interpolation

When the same explanation must appear verbatim in several flows, publish it as a [macro](/agents/concepts/glossaries-and-macros#macros), pin it in the manifest's `context.macros`, and interpolate it inside `chat_state` with `${macro-id}`:

```yaml
  - id: explain-cancellation
    chat_state: |
      Here's how cancellation works for your booking:

      ${cancellation-policy}

      Would you like me to go ahead and cancel it?
    transitions:
      - to: await-decision
```

Only `chat_state` interpolates macros — `tool_instruction` and `description` take literal text.

## Anti-patterns

| Anti-pattern                                                      | Symptom                                                                        | Fix                                                                              |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| Tools + `chat_state` on one node                                  | Schema rejects it                                                              | Split into tool node + chat node connected by a transition                       |
| Messaging inside `tool_instruction`                               | Tone leaks into tool calls; replies appear before data arrives                 | Move messaging to the following chat node                                        |
| Conditions narrating actions ("ask the user X if…")               | Transitions that half-do things                                                | Conditions gate; actions act                                                     |
| Mega-nodes ("collect dates, search, present, and offer extras")   | The model freelances the ordering                                              | One concern per node                                                             |
| Two chat nodes with no customer-dependent transition between them | Second one never runs in the same turn                                         | Merge the messages or make the dependency explicit                               |
| Duplicating a global rule in every routine                        | Drift between copies                                                           | Make it a [policy](/agents/guides/authoring-policies)                            |
| Deep tool chains exceeding the iteration cap                      | Turn ends mid-flow / autonomous runs fail with `max_engine_iterations_reached` | Shorten, parallelise independent tools, or raise `runtime.max_engine_iterations` |
| Unconditioned multi-way branches                                  | Schema rejects 2+ transitions without conditions                               | Condition every branch explicitly                                                |

## Testing a routine

1. Deploy it to a staging agent against a stub MCP server (the [Quickstart](/agents/guides/quickstart) builds one end to end) and walk the happy path through the chat UI.
2. Probe each branch: phrase inputs that should take every transition, including the "changed my mind" backtrack.
3. Watch the trace for the turn — it shows activation, node selection, and each tool call; see [Observability](/agents/guides/observability).
4. Check boot output: every routine logs `Routine '<title>' evaluated: N nodes in Xs` at startup — failures there mean structural problems.

## Pre-submit checklist

* [ ] `entry` names a declared node; every `transitions[].to` resolves
* [ ] No node combines `tools` with `chat_state`
* [ ] Every tool node that needs customer-facing output transitions to a chat node
* [ ] `tool_instruction` is parameter mapping, not messaging
* [ ] Every multi-way branch conditions all of its transitions
* [ ] Node ids unique
* [ ] Terminal nodes are intentional (no `transitions` means the routine ends there)
* [ ] Activation conditions name concrete triggers and exclude sibling routines
* [ ] Longest tool chain fits within `max_engine_iterations`
* [ ] Every `${macro-id}` in `chat_state` is pinned in `context.macros`


# Authoring autonomous routines

Build a typed automation end to end: schemas, the terminal emit\_output rule, timeouts, webhooks, triggering, and receiving the callback.

> **Context** — Assumes [Autonomous routines](/agents/concepts/autonomous-routines) (the run lifecycle and failure taxonomy) and [Authoring routines](/agents/guides/authoring-routines) (node craft — it all applies here). This guide builds one automation end to end: a KYC decision processor.
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## What we're building

A third-party KYC provider finishes verifying an applicant. We want the agent to assess the result, decide approve / reject / escalate, and deliver the decision to our backend — no human, no conversation.

## 1. Design the contract first

The schemas *are* the interface your callers code against. Write them before the nodes:

```yaml
autonomous:
  input_schema:
    type: object
    required: [applicant_id, verification_result]
    properties:
      applicant_id:
        type: string
      verification_result:
        type: object
        required: [status]
        properties:
          status:
            enum: [GREEN, AMBER, RED]
          checks:
            type: array
            items:
              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
```

Schema craft:

* **Closed enums on decision fields.** Your backend switches on `decision`; an enum makes drift impossible. (The runtime adds `additionalProperties: false` to every object node automatically.)
* **`required` everything you'll consume.** Optional output fields breed `KeyError`s downstream.
* **Validate inputs strictly.** A bad payload should be a `400` at the door (`input_validation_failed`), not a confused model mid-run.
* **Allowlist callbacks.** Without `callback_url_allowlist` any URL is accepted; with it, only the listed hostnames (a leading `.` matches the apex and subdomains; a full-URL entry matches by its hostname). Wildcards are not supported.

## 2. Write the nodes

An autonomous routine is built from **TOOL**, **THINK**, and **routing-only** nodes — there are **no CHAT nodes**, because there's no customer in the conversation to speak to; the result leaves the run as typed JSON via `emit_output`, not as a message. (Node-type details: [Autonomous routines](/agents/concepts/autonomous-routines#node-types).) Two extra rules beyond the usual node craft:

1. The validated input is visible to every node (it arrives as a tool result in history) — reference its fields by name in instructions.
2. **Every terminal node (no outbound `transitions`) must be a TOOL node calling `built-in:emit_output`.** Terminal THINK nodes are rejected at validation time.

```yaml
id: kyc-decision
title: KYC Decision
conditions:
  - The KYC verification result for an applicant needs to be processed.
description: >
  Assess a completed KYC verification and produce an approve / reject /
  escalate decision with a one-sentence explanation.

entry: assess
nodes:
  - id: assess
    think: >
      Assess the verification payload from the input. GREEN with no
      failed checks means approved. RED means rejected. AMBER, or GREEN
      with any failed check, means escalate. Produce the decision and a
      one-sentence explanation referencing the decisive check.
    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 set to a JSON object containing
      exactly the decision and explanation fields produced by the assess
      node.

autonomous:
  input_schema:
    type: object
    required: [applicant_id, verification_result]
    properties:
      applicant_id:
        type: string
      verification_result:
        type: object
        required: [status]
        properties:
          status:
            enum: [GREEN, AMBER, RED]
          checks:
            type: array
            items:
              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
```

Branched flows end every terminal node the same way — branch directly from the think node's transitions:

```yaml
  - id: assess
    think: >
      Assess the verification payload from the input. GREEN with no
      failed checks means approved. RED means rejected. AMBER, or GREEN
      with any failed check, means escalate.
    output_schema:
      type: object
      required: [decision, explanation]
      properties:
        decision:
          enum: [approved, rejected, escalate]
        explanation:
          type: string
    transitions:
      - to: emit-decision
        condition: "the decision is approved or rejected"
      - to: open-case
        condition: "the decision is escalate"

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

  - id: open-case
    tools: crm:create_review_case
    tool_instruction: >
      Create a manual-review case for applicant_id with the explanation
      as the case note.
    transitions:
      - to: emit-escalation

  - id: emit-escalation
    tools: built-in:emit_output
    tool_instruction: >
      Call emit_output with output_json containing decision "escalate"
      and the explanation from the assess node.
```

Budget the iteration cap: the cap counts per-turn engine preparation passes, not nodes. Each *sequential* think/tool step consumes roughly one iteration, and `emit_output` is itself a tool call — a `think → tool → emit` chain needs roughly 3. But a node whose tools run together in one parallel batch is still just one iteration, so this is an approximation that over-counts routines with parallel tool fan-out. The default cap is 5 (`runtime.max_engine_iterations`). Runs that hit the cap before `emit_output` fail with `max_engine_iterations_reached`.

## 3. Reference it from the manifest

Autonomous routines are listed like any other:

```yaml
agent_config:
  context:
    routines:
      - id: kyc-decision
        version: 1
```

The `autonomous:` block in the routine YAML is what activates the trigger endpoint — there is no separate manifest switch. The boot log confirms registration per routine (timeout and webhook status included).

## 4. Trigger it

```bash
curl -sS -X POST "https://agent.example.com/routines/kyc-decision/trigger" \
  -H "Authorization: Bearer $AGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": {
      "applicant_id": "app_123",
      "verification_result": {"status": "AMBER", "checks": [{"name": "selfie", "result": "WARN"}]}
    },
    "callback_url": "https://api.example.com/agent-callbacks",
    "idempotency_key": "kyc-app_123-attempt-1",
    "metadata": {"ticket": "OPS-441"}
  }'
```

Immediate `202`:

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

Always send an `idempotency_key` from a retry-capable caller — a duplicate key returns the existing run (HTTP 409) instead of running twice.

## 5. Receive the callback

Your endpoint gets one POST per run, authenticated with the agent's bearer key. Acknowledge fast; the server retries non-2xx responses with backoff (default 5 attempts).

```python
from fastapi import FastAPI, Request, Response

app = FastAPI()

AGENT_API_KEY = "devsecret"  # the same shared bearer token


@app.post("/agent-callbacks")
async def on_agent_callback(request: Request) -> Response:
    if request.headers.get("Authorization") != f"Bearer {AGENT_API_KEY}":
        return Response(status_code=401)

    payload = await request.json()

    if not mark_processed_once(payload["run_id"]):   # dedupe on run_id
        return Response(status_code=200)

    if payload["status"] == "succeeded":
        apply_kyc_decision(
            ticket=payload["metadata"]["ticket"],
            decision=payload["output"]["decision"],
            explanation=payload["output"]["explanation"],
        )
    else:
        alert_ops(
            ticket=payload["metadata"]["ticket"],
            code=payload["error"]["code"],
            message=payload["error"]["message"],
            trace_id=payload["trace_id"],
        )

    return Response(status_code=200)
```

Handle `status: "failed"` by `error.code` — the [failure taxonomy](/agents/concepts/autonomous-routines#failure-taxonomy) maps each code to the knob that fixes it. The full payload schema is in [Events & callbacks](/agents/reference/events-and-callbacks).

## 6. Optional: let the provider trigger it directly

Skip your own trigger plumbing by giving the routine a webhook:

```yaml
autonomous:
  # ... schemas as above ...
  webhook:
    secret_env: ${KYC_WEBHOOK_SECRET}
    header: X-Payload-Digest
    algorithm: sha256
    prefix: ""
```

Point the provider at `POST /webhooks/kyc-decision`, configured to sign the raw body with the shared secret. The provider's payload becomes the run's input (it must satisfy `input_schema`), provider retries dedupe automatically, and **no callback is sent** — webhook runs are fire-and-forget and always run in an ephemeral session; consume results from traces (set `traces.trace_id_field` so they're findable by your business key) or have a tool node before the terminal one write the outcome to your systems. For one URL dispatching to several routines, use manifest-level webhooks — see [Autonomous routines](/agents/concepts/autonomous-routines#webhook-entry-points).

To verify the wiring before pointing the real provider at it, sign a test body yourself:

```python
import hashlib
import hmac

secret = "the-shared-secret"
body = b'{"applicant_id": "app_123", "verification_result": {"status": "GREEN"}}'
signature = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
print(signature)
```

```bash
curl -sS -X POST "https://agent.example.com/webhooks/kyc-decision" \
  -H "X-Payload-Digest: <signature from above>" \
  -H "Content-Type: application/json" \
  --data-raw '{"applicant_id": "app_123", "verification_result": {"status": "GREEN"}}'
```

The body bytes must match the signed bytes exactly (the signature covers the raw body, not a re-serialisation). A `401` means signature mismatch — check algorithm, `prefix`, header name, and that both sides hold the same secret. Re-sending the same body returns the original run (HTTP 200) instead of firing twice.

## Testing checklist

* [ ] Happy path: trigger with a valid payload → callback `succeeded`, output matches `output_schema`
* [ ] Each branch reaches `emit_output` (trace shows the path taken)
* [ ] Invalid input → HTTP 400 at trigger time, no run started
* [ ] `timeout_seconds` is realistic: time the happy path, add headroom for model latency spikes
* [ ] Iteration budget: longest path's sequential tool+think step count (+1 for `emit_output`) ≤ `max_engine_iterations`
* [ ] Callback receiver dedupes on `run_id` and returns 200 fast
* [ ] Retried trigger with the same `idempotency_key` does not double-run
* [ ] If webhook-enabled: provider signature verifies; replayed body returns the same run


# Connecting tools

Stand up an MCP tool server, secure it, declare it in the manifest, and design tools the agent calls correctly.

> **Context** — Assumes the [Tools](/agents/concepts/tools) concept page. This guide covers the practical side: building a server, wiring it in, authentication, and — most importantly — designing tools a language model uses well.
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## 1. Build an MCP server

The agent consumes tools over the Model Context Protocol with the `streamable-http` transport. Any MCP-compliant server works; in Python, `fastmcp` is the shortest path:

```bash
pip install fastmcp
```

```python
"""CRM tool server example."""

from fastmcp import FastMCP

mcp = FastMCP("crm")


@mcp.tool
def get_account_status(party_id: str) -> dict:
    """Fetch the current account status for a customer.

    Args:
        party_id: The customer's PARTYID exactly as provided by
            authentication — do not transform it.

    Returns:
        {"status": "ACTIVE" | "SUSPENDED" | "CLOSED",
         "since": "YYYY-MM-DD",
         "reason": str | None}
    """
    record = lookup_account(party_id)
    return {
        "status": record.status,
        "since": record.status_since.isoformat(),
        "reason": record.status_reason,
    }


if __name__ == "__main__":
    mcp.run(transport="streamable-http", host="0.0.0.0", port=8001)
```

Requirements on your side:

* **Transport:** `streamable-http` only.
* **Reachability:** the platform-hosted agent must be able to reach the `hostname:port` your manifest declares — a public URL, or an address your platform networking allows. The agent connects out to your server; it never needs to reach in.
* **Statelessness between calls** is your design choice; the agent passes whatever parameters the tool declares, nothing more.

## 2. Declare it in the manifest

```yaml
agent_config:
  mcps:
    - id: crm
      hostname: https://crm-tools.internal.example.com
      port: 443
      transport: streamable-http
      path: /v2/mcp
      api_key: ${CRM_MCP_KEY}
```

| Field       | Type              | Required | Default           | Meaning                                                                                                                |
| ----------- | ----------------- | -------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `id`        | string            | yes      | —                 | Namespace prefix for this server's tools.                                                                              |
| `hostname`  | string            | yes      | —                 | Host **including scheme** (`http://` or `https://`). Host only — no path, port, or query.                              |
| `port`      | integer 1–65535   | yes      | —                 | TCP port.                                                                                                              |
| `transport` | `streamable-http` | no       | `streamable-http` | The only supported MCP transport.                                                                                      |
| `path`      | string            | no       | `/mcp`            | URL path the MCP endpoint is mounted at. Set when the server exposes MCP somewhere other than `/mcp` (e.g. `/v2/mcp`). |
| `api_key`   | `${VAR}` env-ref  | no       | —                 | Sent to the MCP server as `Authorization: Bearer`. Omit for unauthenticated servers.                                   |

* `id` becomes the namespace: this server's `get_account_status` is referenced everywhere as `crm:get_account_status`.
* `api_key` (optional) is a `${VAR}` env-ref; the agent sends it on every MCP request as `Authorization: Bearer`. Your server should verify it.
* The agent connects at **boot** and fails startup if the server is unreachable; at runtime, transient failures reconnect automatically.

## 3. Designing tools the agent calls well

The model sees three things: the tool's **name**, its **description**, and its **parameter schema** (names, types, defaults, per-parameter docs). It decides when and how to call based on those alone. Treat them as a prompt:

**Name by intent, verb-first.** `search_cars`, `create_booking`, `initiate_human_handoff` — not `cars_api_v2` or `do_action`.

**Document the contract, not the implementation.** Say what goes in, what comes out, and the gotchas:

```python
@mcp.tool
def create_booking(
    car_id: str,
    pickup_location: str,
    pickup_date: str,
    return_date: str,
    customer_name: str,
    driver_age: int,
    extras: list[str] | None = None,
    member_email: str | None = None,
    return_location: str | None = None,
) -> dict:
    """Create a car rental booking.

    Args:
        car_id: id from search_cars results (e.g. "suv-1").
        pickup_location: free-text location as the customer gave it.
        pickup_date: YYYY-MM-DD.
        return_date: YYYY-MM-DD; must be after pickup_date.
        customer_name: full name for the booking.
        driver_age: primary driver's age in years; must be >= 21.
        extras: optional list of extra_ids from list_extras.
        member_email: only when a member lookup succeeded; lowercase.
        return_location: only when dropping off somewhere else.

    Returns:
        On success: {"booking_id": str, "total_eur": int, ...}.
        On failure: {"error": str} with a human-readable reason
        (e.g. "driver_age below minimum of 21").
    """
```

**Return errors as data, not exceptions.** A structured `{"error": "driver_age below minimum of 21"}` lands in the conversation history where the model can read it, explain it, and recover. An exception gives it nothing to work with.

**Keep results lean and labelled.** The entire result enters the model's context. Return the fields the agent needs, with self-explanatory names and enum-like values (`"ACTIVE"`, not `2`). Page or summarise large sets — "the cheapest 5 of 23 matches" is a better tool result than 23 rows.

**Make state-changing tools idempotent where possible.** The model may retry after ambiguous failures; accepting an idempotency parameter or deduplicating server-side prevents double bookings.

**One capability per tool.** A `manage_booking(action=...)` multiplexer forces the model to learn your switch statement. Separate `cancel_booking`, `update_booking_dates`, `get_booking` tools each carry their own focused documentation.

## 4. The other half: tool use in content

The server defines what *can* be called; routines and policies define *when and how*:

* A routine [tool node](/agents/guides/authoring-routines#tool-nodes-instructions-are-parameter-maps) pairs `tools: crm:get_account_status` with a `tool_instruction` explaining parameter derivation in this flow's context.
* A [policy](/agents/guides/authoring-policies) lists `tools:` it may invoke from its action.
* If a tool changes what the engine should believe (authentication, status changes), register it in `context.reevaluation_tools` or the policy's `reevaluate_after` — see [Reevaluation tools](/agents/concepts/tools#reevaluation-tools).

Tool descriptions answer "how do I call this correctly?"; instructions answer "why now, with which values from this conversation?". Don't duplicate one into the other.

## 5. Verify the wiring

1. Boot the agent; startup fails fast with the server name if an MCP server is unreachable.
2. Send a message that should trigger the tool; watch the event stream for the `tool` event carrying `tool_id`, `arguments`, and `result` (`isinstance(ev, ToolEvent)` in the SDK).
3. Check the trace: each tool call appears with its arguments and result — see [Observability](/agents/guides/observability).
4. Test the failure path: stop your MCP server mid-conversation and confirm the agent degrades the way you want (the failed call is visible to the model, which explains per its instructions).

## Checklist

* [ ] `streamable-http` transport; reachable from the agent's network
* [ ] `api_key` verification on the server if it's not network-isolated
* [ ] Verb-first names; contract-grade docstrings; typed parameters
* [ ] Errors returned as structured data
* [ ] Results lean, labelled, paged
* [ ] State-changing tools idempotent
* [ ] Reevaluation registered for state-flipping tools


# Setting up the knowledge base

Provision retrieval grounding: a managed collection on the platform, or your own HTTP search endpoint. Includes field/slot contracts and verification steps.

> **Context** — Assumes [Knowledge base & retrieval](/agents/concepts/knowledge-base) (the two implementations and how a retrieval runs). This guide is the provisioning walkthrough for each, plus how to verify grounding actually works.
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## Choosing an implementation

Decide once per agent — `agent_config.search` holds exactly one:

* **`collections`** if your corpus lives in a managed collection on the platform. You define the search as a list of typed fields; the agent produces each field's text (and embeds the vector fields) and queries the collection — dense, keyword, or a hybrid of both.
* **`external`** if search already exists in your stack (Elasticsearch, a RAG service, a vendor API) or you need custom ranking. The agent sends conversation context; you return snippets.

## Option A: managed collections

### 1. Create the collection

Build a collection in a platform database and load your chunks (see the `iai collections` command reference). A collection can carry several vector slots and, optionally, a full-text index — the agent can query whichever of these you configured. Each vector slot has an embedding model bound to it at creation time — the manifest never re-declares that model.

### 2. Declare the search

The search is a list of **fields**. Each field names the prompt that turns the conversation into its text, and a `type`:

* **`str`** — the text is sent as a keyword query.
* **`vector`** — the agent embeds the text using the model bound to the target slot and matches it against that slot.

One field runs a single search; several fields run each as its own lane and fuse the results. A field searches the slot named by `name`, or by `slot` when you want several differently-prompted fields fused on one slot.

```yaml
agent_config:
  search:
    type: collections
    database: kb-prod
    collection: support_articles
    fields:
      - name: embedding                 # matches the collection slot
        type: vector
        llm_description: { id: semantic-query-rewrite, version: 3 }
      - name: broad                     # second lane fused on the same slot
        slot: embedding
        type: vector
        llm_description: { id: broad-topic-rewrite, version: 1 }
      - name: keywords
        type: str
        llm_description: { id: keyword-extract, version: 1 }
    limit: 5
    history_limit: 5
    filter: { kb_location: article }
```

Results reach the model with their full payload: every metadata key you stored on a chunk is rendered alongside its text, so a custom schema (prices, URLs, dates…) is usable context, not just the chunk body.

Authentication uses the agent's existing platform keys — no extra credentials, and the organization and project are resolved automatically at startup.

Startup describes the live collection and validates the declared fields against it — a `vector` field targeting a slot that doesn't exist, a slot with no bound embedding model, or a `str` field on a collection without full-text enabled all fail boot with an error naming the problem.

## Option B: external search endpoint

### 1. Implement the endpoint

One POST route; request and response contracts are fixed (full details in [Knowledge base & retrieval](/agents/concepts/knowledge-base#type-external-bring-your-own-search)):

```python
from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel

app = FastAPI()

SEARCH_API_KEY = "your-shared-secret"


class Message(BaseModel):
    role: str       # "customer" | "agent" | "tool"
    content: str


class SearchRequest(BaseModel):
    session_id: str
    agent_id: str
    top_k: int
    messages: list[Message]


@app.post("/agent-search")
async def agent_search(
    body: SearchRequest,
    authorization: str | None = Header(default=None),
) -> list[str]:
    if authorization != f"Bearer {SEARCH_API_KEY}":
        raise HTTPException(status_code=401)

    query = build_query(body.messages)          # your rewriting
    hits = run_search(query, limit=body.top_k)  # your engine
    return [hit.snippet for hit in hits]        # bare array of strings
```

Hard requirements: respond within the configured timeout (default 5s); return a **bare JSON array of strings**; treat `messages` as most-recent-last. Anything else — envelope objects, non-200s, slow responses — makes the retrieval soft-fail (the turn proceeds ungrounded).

### 2. Declare it

```yaml
agent_config:
  search:
    type: external
    url: https://search.internal.example.com/agent-search
    api_key: ${SEARCH_API_KEY}
    top_k: 5
    max_messages: 20
    timeout_seconds: 5.0
```

## Verifying grounding (both options)

1. **Boot check** — a slot/collection mismatch (`collections`) fails startup; read the error, it names the problem.
2. **Ask a question only the corpus can answer** ("what exactly happens if I cancel 12 hours before pickup?"). A grounded reply cites specifics from your documents; an ungrounded one generalises.
3. **Inspect the trace** — retrieval appears in the turn's trace with the query text and returned snippets; see [Observability](/agents/guides/observability).
4. **Test the soft-fail** — take the KB down and confirm the agent still answers (from prompt + history) while logs show retrieval warnings. That's the designed behaviour; alert on the warning rate, not the turn failure (there isn't one). See [Troubleshooting](/agents/operations/troubleshooting).

## Operational notes

* **Corpus hygiene beats `limit`/`top_k` tuning:** wrong-answer regressions are usually stale or contradictory documents. Raise the result count only when answers visibly miss available context — every extra snippet costs prompt space on every retrieval.
* **Fused searches:** `min_score` and `exact` interact with `fusion_k` and the number of fields — see the field table in [Knowledge base & retrieval](/agents/concepts/knowledge-base#collections-fields) before tuning either.


# Integration overview

The map of every way traffic flows between your systems and an agent — SDK conversations, event delivery, autonomous triggers and callbacks, third-party webhooks, and tool calls — with auth and links

> **Context** — One page mapping **every traffic direction** between your systems and a running agent. Use it to decide which integration surfaces you need, then follow the link for each. No prior reading needed.

## The map

```
 YOUR INTEGRATION                 AGENT SERVER                THIRD PARTIES
 (UI, CRM, backend)                                           (KYC, payments, …)
 ────────────────────             ─────────────               ──────────────────

 Conversational
   (1) open session,      ──────►
       post message (SDK)
   (2a) pull events       ◄──────  long-poll stream
        — or —
   (2b) receive events    ◄──────  POST to your event
        at your endpoint           webhook URL

 Autonomous
   (3) POST /routines/    ──────►
       {id}/trigger
   (4) receive result     ◄──────  POST to your
       at callback_url             callback_url
                                              ◄──────  (5) POST /webhooks/{name}
                                                           (HMAC-signed)

 Tools & data
   your MCP servers       ◄──────  (6) tool calls
   your KB / search       ◄──────  (7) retrieval
```

## The seven flows

| #  | Flow                                                                                                               | Direction            | Auth                                                | Where it's explained                                                                                                                                                                                                                                                              |
| -- | ------------------------------------------------------------------------------------------------------------------ | -------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1  | **Send a customer message** — `sessions.open()` + `post_user_message()` via the SDK                                | you → agent          | Bearer (agent api key)                              | [Integrating the SDK](/agents/guides/integrating-the-sdk) §1–4                                                                                                                                                                                                                    |
| 2a | **Receive replies by polling** — `sess.events()` long-poll stream, offset-resumable                                | agent → you (pulled) | Bearer                                              | [Integrating the SDK](/agents/guides/integrating-the-sdk) §5; wire shapes: [Events & callbacks](/agents/reference/events-and-callbacks)                                                                                                                                           |
| 2b | **Receive replies by webhook** — the agent POSTs each event to the URL you set at client construction (`webhook=`) | agent → you (pushed) | Bearer on the POST; `x-session-id` header routes it | [Integrating the SDK](/agents/guides/integrating-the-sdk) §5; wire shapes: [Events & callbacks](/agents/reference/events-and-callbacks)                                                                                                                                           |
| 3  | **Trigger an automation** — `POST /routines/{routine_id}/trigger` with typed JSON input                            | you → agent          | Bearer                                              | [Autonomous routines](/agents/concepts/autonomous-routines); endpoint spec: [HTTP API](/agents/reference/http-api#trigger-an-autonomous-routine)                                                                                                                                  |
| 4  | **Receive the typed result** — one POST to your `callback_url` per run, retried on failure                         | agent → you          | Bearer on the POST; dedupe by `run_id`              | Receiver code: [Authoring autonomous routines](/agents/guides/authoring-autonomous-routines) §5; payload schema + error codes: [Events & callbacks](/agents/reference/events-and-callbacks#autonomous-callback-payload)                                                           |
| 5  | **Third-party provider fires a routine** — `POST /webhooks/{name}` from a KYC vendor, payment gateway, etc.        | provider → agent     | HMAC signature over the raw body (bypasses bearer)  | Concept + declaration styles: [Autonomous routines](/agents/concepts/autonomous-routines#webhook-entry-points); setup + signature self-test: [Authoring autonomous routines](/agents/guides/authoring-autonomous-routines) §6; hardening: [Security](/agents/operations/security) |
| 6  | **The agent calls your tools** — MCP servers you run                                                               | agent → you          | Optional per-server Bearer (`mcps[].api_key`)       | [Connecting tools](/agents/guides/connecting-tools)                                                                                                                                                                                                                               |
| 7  | **The agent searches your knowledge base** — a managed collection or your HTTP search endpoint                     | agent → you          | Platform auth / optional Bearer                     | [Setting up the knowledge base](/agents/guides/knowledge-base-setup)                                                                                                                                                                                                              |

Two more you-to-agent surfaces that don't fit the picture above:

* **Inject external context** as a synthetic tool result (`POST /sessions/{session_id}/tool_events`) — for statements, history dumps, CRM exports the agent should treat as fetched data. See [Tools](/agents/concepts/tools#injecting-context-as-a-tool-event).
* **Write agent-visible context** — customer **variables** via the SDK (visible to the agent next turn), vs **metadata** (your bookkeeping, invisible to the agent). The distinction matters constantly: [Sessions, memory & state](/agents/concepts/memory-and-state#variables-vs-metadata).

## Choosing your shape

**A chat UI (web, mobile):** flows 1 + 2a. The browser talks to *your* backend; your backend holds the agent api key and relays over the SDK — the token never reaches the client. Polling fits because something is holding a connection anyway.

**A server-to-server channel (Zendesk, Slack, IVR):** flows 1 + 2b. No browser to hold a socket — have the agent push events to your endpoint and route them into your channel by `x-session-id`.

**A backend automation (no conversation):** flows 3 + 4. Typed JSON in, typed JSON out, schemas enforced at both ends. Add flow 5 if a third party should fire it directly instead of going through your service.

**Any of the above with business actions:** add flow 6 — tools are how the agent acts on your systems, whatever the conversation surface is.

## One credential model to internalize

Flows 1–4 and the tool-event/variables surfaces all ride **one shared bearer token** (the manifest's `runtime.api_key`): you send it inbound, and the agent sends the *same* token on its outbound POSTs to you (event webhooks, callbacks) — so your receivers must verify it. Flow 5 is the exception: the provider's HMAC signature *is* the auth. Flows 6–7 use credentials you choose per server. Full picture: [Security](/agents/operations/security).


# Integrating the SDK

Connect any channel — a web UI, Zendesk, Slack, an IVR — to a running agent with the InteractiveAI Python SDK: sessions, messages, both event delivery modes, handover, and production patterns.

> **Context** — This guide connects your channel to a running agent using the `interactiveai` Python SDK, in the order you'll write the code. (Not sure which integration surfaces you need? Start with the [Integration overview](/agents/guides/integration-overview).) It covers the agent-integration surface; the **full SDK API reference** (every class, method, and parameter) lives in the InteractiveAI Python SDK documentation — this page links the concepts, the SDK docs hold the signatures. You need the agent's base URL and its API key (the manifest's `runtime.api_key` value).

## 1. Install

```bash
pip install "interactiveai[agent]"
```

The `[agent]` extra pulls in the async client used to talk to the agent server.

## 2. Construct the client

```python
import asyncio

from interactiveai.agent import InteractiveAgentClient


async def main() -> None:
    async with InteractiveAgentClient(
        base_url="https://my-agent.example.com",
        api_key="<AGENT_API_KEY>",
    ) as client:
        sess = await client.sessions.open(id="customer-1")
        await sess.post_user_message("Hello!")


asyncio.run(main())
```

Use it as an async context manager so the underlying HTTP client closes cleanly. In a long-lived service, construct **one** client at startup and share it — it holds a connection pool, and there's no per-session cost to keeping it alive.

## 3. Open a session

Pick one stable id per end-user (a Zendesk user id, a signed cookie value, an internal account id) and hand it to `sessions.open`. The SDK registers the customer if needed, finds their session, or creates a new one — the call is idempotent, so call it freely:

```python
sess = await client.sessions.open(
    id="zendesk-42",
    variables={"plan": "pro", "region": "EU"},
)
```

> **Variables vs metadata** — two different things, easy to mix up:
>
> * **Variables** — agent-visible context, surfaced on the next turn. This is how you tell the agent who the customer is or what just happened. Any JSON-serialisable value works.
> * **Metadata** — opaque key/value storage on the customer or session. The agent **does not see it**. Use it for your own bookkeeping: channel info, external ids, anything you'll look up later.
>
> Full model: [Sessions, memory & state](/agents/concepts/memory-and-state).

Need more than one session per customer (a "new conversation" button, separate threads per topic)? Use the customer path — `client.customers.register(id=..., name=...)` then `customer.new_session()`. Both paths converge on the same customer record; mix them freely.

## 4. Post a user message

```python
await sess.post_user_message("Hi, I can't log in.")
```

That single call wakes the engine. The reply comes back as **events**, not as a return value.

## 5. Receive replies — pick a delivery mode

| Mode                  | Use when                                                                                     |
| --------------------- | -------------------------------------------------------------------------------------------- |
| **Polling** (default) | Your receiver can hold a long-lived connection — typically a browser-backed UI.              |
| **Webhook**           | Server-to-server channels (Zendesk, Slack, IVR). The agent POSTs events to a URL you expose. |

### Polling — stream events with `sess.events()`

```python
from interactiveai.agent import (
    AssistantMessage,
    Preamble,
    StatusEvent,
    ToolEvent,
)

async for ev in sess.events():
    if isinstance(ev, AssistantMessage):
        print("agent:", ev.text)
    elif isinstance(ev, Preamble):
        # Filler the agent emits while still working — render like a
        # typing indicator with text, not as a final reply.
        print("agent (preamble):", ev.text)
    elif isinstance(ev, ToolEvent):
        for call in ev.tool_calls:
            print("tool:", call.tool_id, call.arguments)
    elif isinstance(ev, StatusEvent) and ev.status == "error":
        print("agent error:", ev.error_detail)
```

`sess.events()` opens a long-poll stream and reconnects automatically on transient failures. The loop is open-ended — in a real integration you iterate for the life of the channel and `break` only on shutdown.

### Webhook — let the agent push events to you

Pass `webhook=` at construction:

```python
async with InteractiveAgentClient(
    base_url="https://my-agent.example.com",
    api_key="<AGENT_API_KEY>",
    webhook="https://my-integration.example.com/agent-events",
) as client:
    sess = await client.sessions.open(id="zendesk-42")
    await sess.post_user_message("Hi, I can't log in.")
```

The SDK writes that URL into `session.metadata["event_webhook_url"]` on every session it touches; the agent then POSTs its own output events there — `AssistantMessage`, `Preamble`, `ToolEvent`, `StatusEvent` — authenticated with the same bearer token. Customer messages are never delivered via webhook; read them back by polling the session's events instead. Your receiver:

```python
from fastapi import FastAPI, Request, Response

from interactiveai.agent import AssistantMessage, parse_webhook_event

app = FastAPI()

AGENT_API_KEY = "devsecret"


@app.post("/agent-events")
async def on_agent_event(request: Request) -> Response:
    if request.headers.get("Authorization") != f"Bearer {AGENT_API_KEY}":
        return Response(status_code=401)

    session_id = request.headers["x-session-id"]   # stamped by the agent
    event = parse_webhook_event(await request.json())
    if event is None:
        return Response(status_code=200)           # unknown kind — drop

    if isinstance(event, AssistantMessage):
        await send_to_zendesk(session_id, event.text)

    return Response(status_code=200)
```

`parse_webhook_event` returns the same typed union `sess.events()` yields, so dispatch code for agent-output events is identical in both modes — just remember webhook mode never delivers `UserMessage`.

## 6. Know your events

| Event              | Meaning                                                                                                 |
| ------------------ | ------------------------------------------------------------------------------------------------------- |
| `UserMessage`      | The customer sent a message. Delivered via polling only — never via webhook.                            |
| `AssistantMessage` | The agent's reply.                                                                                      |
| `Preamble`         | Optional filler emitted while the agent is still working on a turn — a typing indicator with text.      |
| `StatusEvent`      | Engine lifecycle: `acknowledged`, `typing`, `processing` (with `stage`), `ready`, `cancelled`, `error`. |
| `ToolEvent`        | One batch of tool calls the engine ran — render "checking inventory…" chips.                            |

Every event has an integer `offset`. Track the highest you've delivered, then pass `min_offset=last + 1` on reconnect to resume exactly where you left off — no replay, no skipped events. Wire-level field reference: [Events & callbacks](/agents/reference/events-and-callbacks).

### Status events drive your UI

| Status         | Meaning                                                    |
| -------------- | ---------------------------------------------------------- |
| `acknowledged` | Engine received the user message.                          |
| `typing`       | Show a typing indicator.                                   |
| `processing`   | Pre-message work; combine with `ev.stage` for a sub-label. |
| `ready`        | Turn finished — clear all indicators.                      |
| `cancelled`    | The turn was superseded (user sent a new message first).   |
| `error`        | Engine error — read `ev.error_detail`.                     |

## 7. Replay history when a UI reconnects

```python
messages = await sess.messages(limit=200)
for m in messages:
    print(m.source, m.content)   # source: "customer" | "ai_agent" | "human_agent" | "human_agent_on_behalf_of_ai_agent"

last_offset = max((m.offset for m in messages), default=-1)

async for ev in sess.events(min_offset=last_offset + 1):
    handle(ev)   # same dispatch as section 5
```

## 8. Human handover

When a human takes over in your channel, switch the session to manual mode so the engine stops auto-replying, and record the human's messages in the same session:

```python
await sess.set_manual_mode()
await sess.post_human_agent_message(
    "I've reset your password — try logging in now.",
    agent_name="Sara from Support",
)

# Hand back to the agent later:
await sess.set_automatic_mode()
```

`post_system_message` covers everything the customer didn't type — inactivity notices, channel switches. It posts under the `human_agent` source (there's no separate system source) so it reads back like an operator message and never triggers a bot reply:

```python
await sess.post_system_message("Conversation moved from email to chat.")
```

## 9. Handle errors

All failures surface as typed exceptions — you never see raw HTTP-library errors:

| Exception        | When                                    |
| ---------------- | --------------------------------------- |
| `AuthError`      | 401/403 — bad bearer token.             |
| `NotFoundError`  | 404 — session/customer doesn't exist.   |
| `ConflictError`  | 409/422 — duplicate or invalid payload. |
| `ServerError`    | 5xx after retries exhausted.            |
| `TransportError` | DNS, timeout, connection reset.         |

```python
from interactiveai.agent import NotFoundError, TransportError

try:
    sess = await client.sessions.get(id=session_id)
except NotFoundError:
    sess = await client.sessions.open(id=external_id)
except TransportError as exc:
    log.warning("agent unreachable: %s", exc)
    raise
```

## 10. Minimal end-to-end

```python
import asyncio
import os

from interactiveai.agent import (
    AssistantMessage,
    InteractiveAgentClient,
    StatusEvent,
)


async def main() -> None:
    async with InteractiveAgentClient(
        base_url=os.environ["INTERACTIVE_AGENT_BASE_URL"],
        api_key=os.environ["AGENT_API_KEY"],
    ) as client:
        sess = await client.sessions.open(id="alex@example.com")
        await sess.post_user_message("Can I rent a van for Saturday?")

        async for ev in sess.events():
            if isinstance(ev, AssistantMessage):
                print("agent:", ev.text)
            elif isinstance(ev, StatusEvent) and ev.status == "ready":
                break


asyncio.run(main())
```

Good for a smoke test — real integrations stay in the event loop indefinitely.

## Common patterns

### Resume a UI session from a cookie

`sessions.open` is idempotent — every HTTP handler opens-or-resumes with the same id:

```python
@app.post("/chat")
async def chat(req: Request, session_cookie: str = Cookie(...)) -> Response:
    sess = await client.sessions.open(id=session_cookie)
    body = await req.json()
    await sess.post_user_message(body["message"])
    return Response(status_code=202)
```

### Route an inbound third-party webhook into a session

```python
@app.post("/zendesk/webhook")
async def on_zendesk(req: Request) -> Response:
    payload = await req.json()
    customer_id = f"zendesk-{payload['user']['id']}"
    sess = await client.sessions.open(
        id=customer_id,
        name=payload["user"]["name"],
        variables={"plan": payload["user"]["plan"]},
    )
    await sess.post_user_message(payload["comment"]["body"])
    return Response(status_code=202)
```

### Find an existing session by an external id

Stamp your channel's conversation id into metadata at creation, look it up later instead of guessing:

```python
customer = await client.customers.register(id=user_id, name=user_name)

sess = await customer.find_session(external_id="chatwoot-conv-789")
if sess is None:
    sess = await customer.new_session(
        title="Chatwoot #789",
        metadata={"external_id": "chatwoot-conv-789"},
    )
```

### Push context into the conversation mid-flight

Agent should know something the customer didn't say? Write **variables** (visible next turn):

```python
await client.customers.set_variables(
    customer.id,
    {"loyalty_tier": "gold", "open_tickets": 3},
)
```

Bookkeeping only? Write **metadata** (invisible to the engine):

```python
await sess.update_metadata({"chatwoot_conversation_id": "789"})
```

Large payloads the agent should treat as fetched data (statements, history dumps) go in as injected tool events instead — see [Tools](/agents/concepts/tools#injecting-context-as-a-tool-event).

### Render tool calls in the UI

```python
if isinstance(ev, ToolEvent):
    for call in ev.tool_calls:
        ui.show_chip(f"🔧 {call.tool_id}")
        if call.result is not None:
            log.debug("tool result", tool_id=call.tool_id, result=call.result)
```

## Tune the event stream

`sess.events()` exposes reconnect/long-poll knobs; defaults suit browser-style UIs:

| Parameter                 | Default | What it does                                                               |
| ------------------------- | ------- | -------------------------------------------------------------------------- |
| `min_offset`              | auto    | First offset to deliver; `None` resumes from `sess.last_event_offset + 1`. |
| `max_reconnect_attempts`  | 5       | Transient failures before giving up.                                       |
| `reconnect_initial_delay` | 1.0s    | Initial backoff after a failure.                                           |
| `reconnect_backoff`       | 2.0×    | Backoff multiplier.                                                        |
| `idle_reconnect_delay`    | 1.0s    | Pause before reopening an idle-closed stream.                              |
| `wait_for_data`           | 60s     | Server-side long-poll window.                                              |

## Production notes

* **Same client across replicas.** The webhook URL travels on session metadata, not client memory — any replica's engine work finds it.
* **Idempotency by offset.** Both delivery modes carry `offset`; persist the largest processed per session and dedupe on `(session_id, offset)` — at-least-once becomes effectively exactly-once.
* **Webhook retries.** `429` and `5xx` from your `/agent-events` endpoint (plus connection/timeout errors) trigger a retry with exponential backoff. Other `4xx` responses (`400`/`401`/`403`/`404`) are treated as permanent and are not retried — return `200` once durably enqueued, process in the background.
* **Reuse the client; or bring your own HTTP client** (custom transport / instrumentation) via the `httpx_client=` constructor parameter — the SDK adds its auth without clobbering your hooks.
* **Don't poll aggressively.** The events endpoint is server-side rate-shaped: rapid identical polls get a delayed response instead of an immediate one. Long-poll with `sess.events()` rather than busy-looping your own polling.

## SDK surface map

What you'll touch, and where it's used above — full signatures in the SDK reference documentation:

| Entry point                                                                                                                                                                     | Used for     |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| `client.sessions` — `open`, `get`, `get_metadata`, `update_metadata`, `set_mode`                                                                                                | §3, §8       |
| `client.customers` — `register`, `retrieve`, `get_variables`, `set_variables`, `get_metadata`, `update_name`                                                                    | §3, patterns |
| `Customer` — `new_session`, `get_session`, `latest_session`, `find_session`, `list_sessions`                                                                                    | §3, patterns |
| `Session` — `post_user_message`, `post_human_agent_message`, `post_system_message`, `set_manual_mode`, `set_automatic_mode`, `update_metadata`, `messages`, `events`, `refresh` | §4–§8        |
| Event types — `UserMessage`, `AssistantMessage`, `Preamble`, `StatusEvent`, `ToolEvent`, `ToolCall`; `parse_webhook_event`                                                      | §5–§6        |
| Exceptions — `InteractiveAgentError`, `AuthError`, `NotFoundError`, `ConflictError`, `ServerError`, `TransportError`                                                            | §9           |

`post_human_handover` still exists as a deprecated alias for `post_human_agent_message` (same signature) — it raises a `DeprecationWarning`; use `post_human_agent_message` directly in new code.


# Deploying

The platform deploy lifecycle: prepare content and a manifest, declare the secret bundle, deploy on the InteractiveAI platform, and verify — plus updating, scaling, and a pre-flight checklist.

> **Context** — Assumes [Architecture](/agents/concepts/architecture) (components and boot sequence). The InteractiveAI platform hosts and runs the agent — you don't build images, manage containers, or operate Kubernetes. This guide covers the deploy lifecycle from your side: what you prepare, what the platform does, and how to verify.
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — the runtime version the platform runs determines which schema your manifest must satisfy; see [Versioning & compatibility](/agents/operations/versioning).

## What you provide vs. what the platform does

| You provide                                                                                  | The platform does                                                                         |
| -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Versioned **content** in the catalog (system prompt, routines, policies, glossaries, macros) | Fetches the pinned content at boot                                                        |
| A **manifest** declaring the agent                                                           | Validates it on upload; rejects structural errors with every violation listed             |
| A **secret bundle** of the `${VAR}` values the manifest references                           | Injects them as environment variables before boot                                         |
| Reachable **MCP tool servers** and (optionally) a **knowledge base** / **database**          | Connects to them at the addresses the manifest declares                                   |
| —                                                                                            | Runs the agent, applies config, scales it, and (when `endpoint: true`) provisions its URL |

There is no image to pull, no container to run, and no Kubernetes on your side — those are platform-managed. Your deploy artifact is the **manifest plus the content it pins**.

## The manifest

The manifest is the agent's definition. A production example:

```yaml
name: DriveAway Production
id: driveaway-prod
version: "12"
endpoint: true
secrets:
  - secret_name: driveaway-agent-secrets
agent_config:
  runtime:
    api_key: ${AGENT_API_KEY}
  interactive_platform:
    public_key: ${INTERACTIVEAI_PUBLIC_KEY}
    secret_key: ${INTERACTIVEAI_SECRET_KEY}
  llms:
    default: interactive/anthropic/claude-haiku-4.5
    api_key: ${ROUTER_API_KEY}
  database:
    hostname: agent-postgres.internal.example.com
    password: ${DB_PASSWORD}
  context:
    system_prompt:
      id: driveaway-system-prompt
      version: 7
    language: match_user
    routines:
      - id: car-search
        version: 4
      - id: book-a-car
        version: 9
    policies:
      - id: stay-on-topic
        version: 2
  mcps:
    - id: cars
      hostname: https://cars-mcp.example.com
      port: 443
      transport: streamable-http
      api_key: ${CARS_MCP_KEY}
```

* `endpoint: true` asks the platform to provision a public-facing URL for the agent; leave it `false` for an agent reached only through the platform's internal API.
* `version` is your free-form revision label — surfaced in logs and traces so you can correlate behaviour with a config release.
* The complete field reference is in [Manifest & content schemas](/agents/reference/manifest).
* **Deploying with the `iai` CLI splits this object:** the `--file` holds the `agent_config` block, while the agent name, type (`--id`), runtime `--version`, secrets (`--secret`), and `--endpoint` are passed as flags. See the [Quickstart](/agents/guides/quickstart#4-deploy-the-agent) for the exact `iai agents create` / `update` commands.

## Secrets

Every credential is a `${VAR_NAME}` env-ref in the manifest — never a literal. You declare a **secret bundle** in the manifest's top-level `secrets:` list (by its name in Interactive Secrets); the platform injects that bundle's key/value pairs as environment variables before the agent boots, which is exactly how the `${VAR}` refs resolve.

The bundle must cover every `${VAR}` the manifest references. At minimum: `AGENT_API_KEY`, `ROUTER_API_KEY`, `INTERACTIVEAI_PUBLIC_KEY`, `INTERACTIVEAI_SECRET_KEY`; plus whichever of `DB_PASSWORD`, an external search `api_key`, per-MCP keys, traces key, and webhook secrets your manifest declares. A missing required variable aborts boot, naming the variable — so a staging deploy surfaces gaps immediately. Operator tuning knobs (autonomous timeout bounds, router token ceiling, evaluation parallelism) are optional platform settings; see [Environment variables](/agents/reference/environment).

## Readiness and first-deploy evaluation

Routine evaluation runs at the end of boot, and the agent **only starts serving — port bound, health checks passing — once it finishes** (see the [boot sequence](/agents/concepts/architecture#boot-sequence)). On a warm cache that's immediate; on a cold cache (a fresh content version) the agent is unreachable for the minutes evaluation takes, which can stall a deploy. The platform pre-warms the evaluation cache so cold deploys come up fast — see [Startup evaluation](/agents/concepts/startup-evaluation#caching-cold-vs-warm-boots).

## Sizing & scaling

Scaling is platform-managed; what you should know about it:

* The agent is I/O-bound (model calls dominate), so it scales on concurrent-session count rather than CPU.
* The platform runs multiple replicas of an agent. With **Postgres** session storage replicas share state and scale horizontally; with in-memory storage each replica has its own sessions, so a session's traffic isn't guaranteed to land on the same replica — use Postgres for any conversational agent that must survive that. See [Sessions, memory & state](/agents/concepts/memory-and-state#storage-backends).
* The LLM router applies per-key rate limits — sustained scale-out multiplies model-call volume; watch for router-side throttling.

## Updating an agent

Two independent release axes:

1. **Content changes** (routines, policies, prompts): publish new content versions to the catalog, bump the pins in the manifest, redeploy the manifest. The runtime is unchanged.
2. **Runtime upgrades**: the platform runs a newer runtime version — check the compatibility matrix first, because a new runtime may require a new manifest-schema version; see [Versioning & compatibility](/agents/operations/versioning).

Both are rolling updates; with Postgres storage, in-flight sessions survive. Rolling back content is exact: redeploy the previous manifest (catalog versions are immutable).

## Graceful shutdown and rolling restarts

On `SIGTERM` (sent by Kubernetes during a rolling restart), the server stops reporting ready (`GET /health/ready` returns `503`) and waits for in-flight work — conversational turns, autonomous routine callbacks, and event webhooks — to finish before exiting. The wait is bounded by `SHUTDOWN_DRAIN_TIMEOUT_SECONDS` (default `120`); anything still running when the window closes is terminated as before.

> **Required cluster configuration.** The in-app drain only helps if the pod is given time to perform it. Set the deployment's `terminationGracePeriodSeconds` to at least `SHUTDOWN_DRAIN_TIMEOUT_SECONDS` plus the \~10s HTTP connection-drain and a safety margin, and add a `preStop` sleep so endpoint de-registration propagates before the drain begins. With the 120s default, use `terminationGracePeriodSeconds: 140` or higher (120s drain + \~10s connection-drain + margin). If the grace period is shorter than the drain window, Kubernetes will `SIGKILL` the pod mid-drain and work will still be lost.

## Verifying a deploy

1. **Manifest validates** — the platform reports structural errors on upload, listing every violation at once.
2. **Boot succeeds** — a missing secret or unresolved content/MCP reference fails boot with a specific message; read it in the platform's logs.
3. **A staging conversation works** — send a message through the SDK and confirm the expected routine/policy behaviour.
4. **Traces appear** — confirm the conversation shows up in the platform's traces view; see [Observability](/agents/guides/observability).

## Pre-flight checklist

* [ ] Manifest validates on upload (structural errors list every violation)
* [ ] Secret bundle covers every `${VAR}` the manifest references — a staging deploy names the first gap in its boot log
* [ ] `database:` block present (unless ephemeral sessions are intentional)
* [ ] MCP servers reachable from the platform; their `api_key`s in the bundle
* [ ] Runtime/schema version checked against the [compatibility matrix](/agents/operations/versioning)
* [ ] Evaluation cache warmed for the content versions being deployed (the platform warms it on deploy — see [Startup evaluation](/agents/concepts/startup-evaluation#caching-cold-vs-warm-boots))
* [ ] Traces visible after a staging conversation ([guide](/agents/guides/observability))
* [ ] Bearer key distribution: integrations hold the same `AGENT_API_KEY` ([security](/agents/operations/security))


# Observability

Traces, structured logs, trace naming with your business identifiers, the retry-fallback signals, and the grep recipes that answer "what did the agent do?".

> **Context** — Every turn an agent takes is traced (OpenTelemetry) and logged (structured JSON). This guide shows how to read both, how to make traces searchable by *your* identifiers, and which signals deserve alerts.
>
> YAML examples follow **manifest schema 8.1.0**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning).

## Traces

The agent exports OpenTelemetry traces over OTLP/HTTP. By default they go to the InteractiveAI platform's traces backend (derived from `interactive_platform.base_url`, authenticated with the platform keys) and appear in the platform's Traces view. A custom backend is one manifest block away:

```yaml
agent_config:
  traces:
    deployment_environment: production
    backend:
      url: https://otel.your-provider.com/v1/traces
      api_key: ${OTEL_API_KEY}
      api_key_scheme: bearer    # or "basic" for public:secret key pairs
```

`deployment_environment` (default `production`) tags every trace with `deployment.environment` — it's the environment filter in the trace UI, so staging and production agents with the same name stay separable.

### What a trace contains

One trace per turn (conversational) or per run (autonomous). Inside it: policy matching batches, routine evaluation decisions, every model call (chat and evaluation lanes), every tool call with arguments and results, knowledge-base retrievals with the rewritten query, and the final reply. Each turn's trace also carries a metadata snapshot: the session metadata verbatim and the resolved context variables exactly as fed to the model — so "what did the agent know?" is answerable months later.

The trace's **input** is the customer message; its **output** is the turn's reply messages (or the autonomous run's typed output).

### Trace naming

Traces group and name themselves off one resolved **resource id**, in precedence order:

1. **Autonomous runs** — the value of the input field named by `agent_config.traces.trace_id_field`. Set it to your business key:

   ```yaml
   agent_config:
     traces:
       trace_id_field: customer_id
   ```

   A run triggered with `{"input": {"customer_id": "cus_abc", ...}}` then traces as `{agent}-cus_abc` instead of a synthetic run id.
2. **Conversational sessions** — `session.metadata["session_key"]`, an optional key your integration sets when opening the session. Session ids are opaque server-generated hashes; `session_key` is how you attach a stable, human-meaningful identifier (a ticket id, a case number):

   ```python
   sess = await client.sessions.open(
       id="zendesk-42",
       session_key="TICKET-7841",
   )
   ```

   On a session that already exists, set it after the fact instead:

   ```python
   await sess.update_metadata({"session_key": "TICKET-7841"})
   ```

   Every turn of that conversation then groups under `TICKET-7841` in the trace UI's session view, named `{agent}-TICKET-7841`, with the user-id column filterable by the same key. The key must be exactly `session_key`.
3. **Fallback** — the first 8 characters of the session id, so grouping always works.

The resolved resource id is truncated to 256 characters before it's used in any of the naming above, so an oversized business key can't blow out trace names.

The autonomous callback's `trace_id` field links a delivered result back to its trace directly.

## Logs

Everything the process emits — agent, HTTP server, engine — is single-line JSON on stdout:

```json
{"timestamp": "2026-06-04T10:15:02.114Z", "level": "info", "message": "Received event from session_id: sess_abc for routine_id: kyc-decision", "logger": "agent_server.endpoints.autonomous_routines", "request_id": "8f2a…", "session_id": "sess_abc", "routine_id": "kyc-decision", "run_id": "run_9f8e…"}
```

Conventions worth knowing when querying:

| Field                                     | Meaning                                                                                       |
| ----------------------------------------- | --------------------------------------------------------------------------------------------- |
| `timestamp`, `level`, `message`, `logger` | Always present.                                                                               |
| `request_id`, `method`, `path`            | Bound for every HTTP request.                                                                 |
| `session_id`, `trace_id`                  | Bound during engine turns.                                                                    |
| `duration_ms` / `duration_s`              | Timings — milliseconds generally, **seconds** for evaluation-phase logs.                      |
| `phase`                                   | Marks special subsystems: `eval` (boot-time evaluation), `retry-fallback` (model escalation). |

The manifest's `runtime.log_level` (default `INFO`) drives verbosity; `DEBUG` adds per-decision detail. Health-probe and event-polling requests are excluded from access logs by design.

## Boot-time evaluation logs

Cold-cache routine evaluation is the noisy phase. At `INFO` you get one bookend per routine:

```
Routine 'Book A Car' evaluated: 11 nodes in 154.2s
```

(`0 nodes` = served from cache.) At `DEBUG`, per-stage and per-step lines appear, all tagged `phase="eval"` and message-prefixed `[eval]`. Failures log at WARNING/ERROR regardless of level.

```bash
# everything eval-related, one agent, last 30 minutes
iai agents logs <agent> --since 30m | grep '\[eval\]'

# structured: every eval line via the phase field
iai agents logs <agent> --since 30m | jq 'select(.phase == "eval")'
```

Slow boots → [Startup evaluation](/agents/concepts/startup-evaluation#caching-cold-vs-warm-boots).

## Retry-fallback signals

When an evaluation call exhausts its 3 attempts and escalates to `evaluation_fallback` (see [Models](/agents/concepts/models)), the runtime logs it with a `[retry-fallback]` message marker and `phase="retry-fallback"`, including which call site escalated and both model names — kick-in and success at WARNING, both-models-exhausted at ERROR.

```bash
iai agents logs <agent> --since 30m | grep '\[retry-fallback\]'
```

What to alert on:

| Signal                                       | Meaning                                                           | Action                                              |
| -------------------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------- |
| Occasional `[retry-fallback] … succeeded`    | Normal — the safety net working                                   | None                                                |
| Sustained escalation rate                    | Evaluation primary struggling with your content                   | Simplify conditions or promote `llms.evaluation`    |
| `[retry-fallback]` at ERROR (both exhausted) | A turn failed an internal decision                                | Investigate the trace; check router health          |
| `[eval] … FAILED`                            | A routine failed boot-time evaluation                             | Fix the routine; the boot log names it              |
| Retrieval warnings                           | Knowledge base unreachable/misbehaving — turns proceed ungrounded | Check KB health; answers degrade silently otherwise |

## A debugging workflow

"The agent did something odd in ticket 7841":

1. **Find the session** in the trace UI by `TICKET-7841` (you set `session_key`, right?). All its turns are grouped.
2. **Open the odd turn's trace.** Check, in order: which policies matched (and which surprisingly didn't), which routine/node was selected, what each tool returned, what the KB retrieval contributed.
3. **Check the config snapshot** on the trace — it records which policy and content versions were live, so "did yesterday's content release cause this?" is a lookup, not an archaeology dig.
4. **Correlate logs** by the trace id (`trace_id` field) for anything infrastructural (timeouts, reconnects, escalations).

Symptom-indexed problems live in [Troubleshooting](/agents/operations/troubleshooting).


# Manifest & content schemas

The annotated map of every manifest field, linking each block to its concept page, plus where the exact field-level schema reference lives.

> **Context** — This page is the navigational map of the manifest as of **manifest schema 8.1.0**: every block, what it does, where it's explained. The manifest shape is schema-versioned — which schema version applies to your runtime is the compatibility matrix's row for your image tag, and shapes differ across versions. The **exact field-level reference** (types, defaults, patterns, validation rules) is the generated Schemas reference — published per schema version alongside these docs, with machine-readable JSON Schemas (Draft 2020-12) in the artifact bucket (see [Versioning & compatibility](/agents/operations/versioning#machine-readable-artifacts)).

## The full shape, annotated

```yaml
# ── Identity ────────────────────────────────────────────────────────────
name: DriveAway Production          # display name (UI, logs)
id: driveaway-prod                  # immutable URL-safe slug
version: "12"                       # your revision label (logs, traces)
endpoint: false                     # platform flag: provision a public URL?
secrets:                            # platform secret bundles to inject as env vars
  - secret_name: driveaway-agent-secrets

agent_config:
  # ── Process & engine knobs ───────────────────── concepts/architecture.md
  runtime:
    api_key: ${AGENT_API_KEY}       # shared bearer token (inbound + callbacks)
    log_level: INFO                 # TRACE|DEBUG|INFO|WARNING|ERROR|CRITICAL
    max_engine_iterations: 5        # per-turn tool/think chain cap
    policy_batch_size: 5            # policies per matcher call

  # ── Platform connection ──────────────────────── concepts/architecture.md
  interactive_platform:
    base_url: https://app.interactive.ai   # also derives router + traces URLs
    public_key: ${INTERACTIVEAI_PUBLIC_KEY}
    secret_key: ${INTERACTIVEAI_SECRET_KEY}

  # ── Models ───────────────────────────────────── concepts/models.md
  llms:
    api_key: ${ROUTER_API_KEY}             # router credential (required)
    default: interactive/anthropic/claude-haiku-4.5    # customer-facing lane default
    fallback:                              # router-side alternates, ordered
      - interactive/anthropic/claude-sonnet-4-6
    response: interactive/anthropic/claude-sonnet-4-6  # final message (optional)
    preamble: interactive/anthropic/claude-haiku-4.5   # preamble + tool announcement (optional)
    search_query: interactive/anthropic/claude-haiku-4.5   # KB search-query writing (optional)
    evaluation:                            # internal engine inference (optional)
      default: interactive/google/gemini-3-flash-preview
      fallback: interactive/google/gemini-3.1-pro-preview   # escalation on retry exhaustion
      tools: interactive/anthropic/claude-haiku-4.5         # tool-call argument inference
      startup: interactive/google/gemini-3.1-pro-preview    # boot-time routine evaluation
      policy_matching: interactive/google/gemini-3-flash-preview
      routine_navigation: interactive/google/gemini-3.1-pro-preview

  # ── Behavioural context ──────────────────────── concepts/prompts.md
  context:
    system_prompt:                  # persona & ground rules (versioned ref)
      id: driveaway-system-prompt
      version: 7
    language: match_user            # string; or an explicit language, e.g. "French"
    greeting: "Hi! I'm Mercedes from DriveAway — how can I help?"
    preamble:                       # mid-turn filler
      examples:
        - "Let me check."
        - "One moment."
      announce_tools: false         # true → status message after each tool batch
    routines:                       # ─────────── concepts/routines.md
      - id: car-search
        version: 4
      - id: book-a-car
        version: 9
    policies:                       # ─────────── concepts/policies.md
      - id: stay-on-topic
        version: 2
    glossaries:                     # ─────────── concepts/glossaries-and-macros.md
      - id: rental-terms
        version: 2
    macros:                         # ─────────── concepts/glossaries-and-macros.md
      - id: cancellation-wording    #   interpolated via ${macro-id} in chat_state
        version: 3
    reevaluation_tools:             # ─────────── concepts/tools.md
      - id: crm:authenticate_customer
    relationships:                  # ─────────── concepts/priorities.md
      priorities:
        - higher: policy:incident-handoff
          over_all_routines: true
      entailments:
        - when: policy:self-exclusion-request
          also_apply:
            - policy:rg-tone

  # ── Tool servers ─────────────────────────────── concepts/tools.md
  mcps:
    - id: cars
      hostname: http://cars-mcp.agents.svc.cluster.local
      port: 8765
      transport: streamable-http
      path: /mcp                    # endpoint path; defaults to /mcp
      api_key: ${CARS_MCP_KEY}

  # ── Retrieval grounding (one of two types) ───── concepts/knowledge-base.md
  search:
    type: external                  # or: type: collections
    url: https://search.internal.example.com/agent-search
    api_key: ${SEARCH_API_KEY}
    top_k: 5

  # ── Conversation storage ─────────────────────── concepts/memory-and-state.md
  database:
    hostname: agent-postgres.internal.example.com
    port: 5432
    user: postgres
    password: ${DB_PASSWORD}
    dbname: postgres
    sslmode: require

  # ── Traces ───────────────────────────────────── guides/observability.md
  traces:
    deployment_environment: production
    trace_id_field: customer_id     # names autonomous traces by input field
    backend:                        # omit for the platform default backend
      url: https://otel.your-provider.com/v1/traces
      api_key: ${OTEL_API_KEY}
      api_key_scheme: bearer

  # ── Third-party webhook fan-out ──────────────── concepts/autonomous-routines.md
  webhooks:
    - name: kyc-events
      secret_env: ${KYC_WEBHOOK_SECRET}
      header: X-Payload-Digest
      algorithm: sha256
      prefix: ""
      routines:
        - kyc-decision
```

Required blocks: `runtime`, `interactive_platform`, `llms`, `context` (within it, `system_prompt` and `language`). Everything else is optional with the defaults documented on each concept page and aggregated in [Limits & defaults](/agents/reference/limits-and-defaults).

### Field types by block

Top-level manifest fields:

| Field      | Type                            | Required | Default | Meaning                                       |
| ---------- | ------------------------------- | -------- | ------- | --------------------------------------------- |
| `name`     | string                          | yes      | —       | Display name (UI, logs)                       |
| `id`       | string (URL-safe pattern)       | yes      | —       | Immutable slug                                |
| `version`  | string                          | yes      | —       | Revision label (logs, traces)                 |
| `endpoint` | boolean                         | no       | `false` | Platform flag: provision a public URL         |
| `secrets`  | list of `{secret_name: string}` | no       | `[]`    | Platform secret bundles to inject as env vars |

`runtime`:

| Field                   | Type                                                                  | Required | Default | Meaning                                   |
| ----------------------- | --------------------------------------------------------------------- | -------- | ------- | ----------------------------------------- |
| `api_key`               | string (`${VAR}` env-ref)                                             | yes      | —       | Shared bearer token (inbound + callbacks) |
| `log_level`             | string enum: `TRACE`\|`DEBUG`\|`INFO`\|`WARNING`\|`ERROR`\|`CRITICAL` | no       | `INFO`  | Process log level                         |
| `max_engine_iterations` | integer (≥ 1)                                                         | no       | `5`     | Per-turn tool/think chain cap             |
| `policy_batch_size`     | integer (≥ 1)                                                         | no       | `5`     | Policies per matcher call                 |

`interactive_platform`:

| Field        | Type                                 | Required | Default                      | Meaning                                          |
| ------------ | ------------------------------------ | -------- | ---------------------------- | ------------------------------------------------ |
| `base_url`   | string (`https?://` scheme required) | no       | `https://app.interactive.ai` | Platform host; also derives router + traces URLs |
| `public_key` | string (`${VAR}` env-ref)            | yes      | —                            | InteractiveAI public key                         |
| `secret_key` | string (`${VAR}` env-ref)            | yes      | —                            | InteractiveAI secret key                         |

`llms` — see [Models](/agents/concepts/models#the-llms-block) for the full field table.

`context`:

| Field                | Type                                                                 | Required | Default           | Meaning                                                                                    |
| -------------------- | -------------------------------------------------------------------- | -------- | ----------------- | ------------------------------------------------------------------------------------------ |
| `system_prompt`      | object `{id: string, version: integer ≥ 1}`                          | yes      | —                 | Prompt reference                                                                           |
| `language`           | string                                                               | yes      | —                 | `match_user`, or a single free-text language name/instruction passed to the model verbatim |
| `greeting`           | string                                                               | no       | `None`            | First-turn greeting                                                                        |
| `preamble`           | object `{examples: list[string] (≥1), announce_tools: boolean}`      | no       | `None` (disabled) | Mid-turn filler config; `announce_tools` defaults `false`                                  |
| `routines`           | list of `{id: string, version: integer ≥ 1}`                         | no       | `[]`              | Routine references                                                                         |
| `policies`           | list of `{id: string, version: integer ≥ 1}`                         | no       | `[]`              | Policy references                                                                          |
| `glossaries`         | list of `{id: string, version: integer ≥ 1}`                         | no       | `[]`              | Glossary references                                                                        |
| `macros`             | list of `{id: string, version: integer ≥ 1}`                         | no       | `[]`              | Macro references, interpolated via `${macro-id}` in `chat_state`                           |
| `reevaluation_tools` | list of `{id: string}`                                               | no       | `[]`              | Tool ids that force re-evaluation after they execute (whether they succeed or error)       |
| `relationships`      | object `{priorities: list[Priority], entailments: list[Entailment]}` | no       | `None`            | Priority/entailment overrides — see [Priorities](/agents/concepts/priorities)              |

`mcps[]`:

| Field       | Type                                 | Required | Default           | Meaning                                  |
| ----------- | ------------------------------------ | -------- | ----------------- | ---------------------------------------- |
| `id`        | string                               | yes      | —                 | Namespace prefix for this server's tools |
| `hostname`  | string (`https?://` scheme required) | yes      | —                 | MCP server host, scheme included         |
| `port`      | integer (1–65535)                    | yes      | —                 | TCP port                                 |
| `transport` | string literal `streamable-http`     | no       | `streamable-http` | MCP transport protocol                   |
| `path`      | string (starts with `/`)             | no       | `/mcp`            | MCP endpoint path                        |
| `api_key`   | string (`${VAR}` env-ref)            | no       | `None`            | Bearer credential for this MCP server    |

`search` (`type: external`):

| Field             | Type                                 | Required | Default    | Meaning                                                   |
| ----------------- | ------------------------------------ | -------- | ---------- | --------------------------------------------------------- |
| `type`            | string literal `external`            | no       | `external` | Discriminator                                             |
| `url`             | string (`https?://` scheme required) | yes      | —          | Full endpoint URL the runtime POSTs retrieval requests to |
| `api_key`         | string (`${VAR}` env-ref)            | no       | `None`     | Bearer credential for the endpoint                        |
| `top_k`           | integer (≥ 1)                        | no       | `5`        | Records requested per retrieval                           |
| `max_messages`    | integer (≥ 1)                        | no       | `20`       | Cap on recent messages sent in the request payload        |
| `timeout_seconds` | float (> 0)                          | no       | `5.0`      | HTTP timeout; expiry soft-fails the retrieval             |

`search` (`type: collections` — managed collections over the platform search API):

| Field               | Type                         | Required | Default                             | Meaning                                                                                                                                                                           |
| ------------------- | ---------------------------- | -------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`              | string literal `collections` | no       | `collections`                       | Discriminator                                                                                                                                                                     |
| `database`          | string                       | yes      | —                                   | Database hosting the collection                                                                                                                                                   |
| `collection`        | string                       | yes      | —                                   | Collection to query                                                                                                                                                               |
| `operator_base_url` | string                       | no       | `https://deployment.interactive.ai` | Search-API host; override only for non-standard deployments                                                                                                                       |
| `fields`            | list of field objects (≥ 1)  | yes      | —                                   | The query — one entry per search signal (see below)                                                                                                                               |
| `limit`             | integer (1–100)              | no       | `5`                                 | Results returned per retrieval                                                                                                                                                    |
| `history_limit`     | integer (≥ 1)                | no       | `5`                                 | Recent customer messages the per-field prompts see (most-recent last)                                                                                                             |
| `filter`            | object (JSON)                | no       | `{}`                                | Static metadata filter (operators like `$in`/`$gt`/`$or`), applied to every field                                                                                                 |
| `fusion_k`          | integer (≥ 1)                | no       | `60`                                | Rank-fusion constant; used only when more than one field                                                                                                                          |
| `min_score`         | float                        | no       | `None`                              | Drop results scoring below this — enforced platform-side (similarity scale) for a single `vector` field, applied by the agent on the rank-fusion scale (\~1/`fusion_k`) otherwise |
| `exact`             | boolean                      | no       | `false`                             | Exhaustive search, bypassing the approximate index; only valid with a single `vector` field                                                                                       |

Each entry in `fields`:

| Field             | Type                                        | Required | Default | Meaning                                                                                             |
| ----------------- | ------------------------------------------- | -------- | ------- | --------------------------------------------------------------------------------------------------- |
| `name`            | string (identifier, unique across `fields`) | yes      | —       | Field label; for `type: vector` it also names the slot to search unless `slot` is set               |
| `type`            | string enum: `vector`\|`str`                | yes      | —       | `vector` runs a dense lane against a slot; `str` uses the prompt output as a keyword query          |
| `llm_description` | object `{id: string, version: integer ≥ 1}` | yes      | —       | Prompt that turns the conversation into this field's text                                           |
| `slot`            | string (lowercase slot name)                | no       | `name`  | Collection slot this field searches; set it to fuse several fields on one slot (`vector` only)      |
| `candidate_limit` | integer (1–1000)                            | no       | —       | Candidates this field retrieves before fusion; no effect when the search is a single `vector` field |

`vector` fields declare no embedding model or dimension — the agent reads both from the target slot's embedding binding at boot, so a manifest/slot mismatch is impossible by construction.

Authenticates with the agent's existing platform keys; the database's organization and project are resolved automatically at startup.

`database`:

| Field      | Type                                                                             | Required | Default    | Meaning            |
| ---------- | -------------------------------------------------------------------------------- | -------- | ---------- | ------------------ |
| `hostname` | string (bare host, no scheme validation enforced)                                | yes      | —          | Postgres host      |
| `port`     | integer (1–65535)                                                                | no       | `5432`     | Postgres port      |
| `user`     | string                                                                           | no       | `postgres` | Postgres role      |
| `password` | string (`${VAR}` env-ref)                                                        | yes      | —          | Postgres password  |
| `dbname`   | string                                                                           | no       | `postgres` | Database name      |
| `sslmode`  | string enum: `disable`\|`allow`\|`prefer`\|`require`\|`verify-ca`\|`verify-full` | no       | `require`  | Postgres `sslmode` |

`traces`:

| Field                    | Type               | Required | Default      | Meaning                                            |
| ------------------------ | ------------------ | -------- | ------------ | -------------------------------------------------- |
| `deployment_environment` | string             | no       | `production` | OTel `deployment.environment` tag                  |
| `trace_id_field`         | string             | no       | `None`       | Input-payload key naming autonomous traces         |
| `backend`                | object (see below) | no       | `None`       | Custom OTLP backend; omit for the platform default |

`traces.backend`:

| Field            | Type                                 | Required | Default  | Meaning                                     |
| ---------------- | ------------------------------------ | -------- | -------- | ------------------------------------------- |
| `url`            | string (`https?://` scheme required) | no\*     | `None`   | OTLP HTTP traces endpoint                   |
| `api_key`        | string (`${VAR}` env-ref)            | no       | `None`   | Backend credential; requires `url` when set |
| `api_key_scheme` | string enum: `bearer`\|`basic`       | no       | `bearer` | Auth header scheme for `api_key`            |

\* `url` is required for the `backend` block to be meaningful, but only `api_key` enforces the dependency at validation time.

`webhooks[]`:

| Field        | Type                                    | Required | Default  | Meaning                                           |
| ------------ | --------------------------------------- | -------- | -------- | ------------------------------------------------- |
| `name`       | string                                  | yes      | —        | URL slug under `POST /webhooks/{name}`            |
| `secret_env` | string (`${VAR}` env-ref)               | yes      | —        | HMAC shared secret                                |
| `header`     | string                                  | yes      | —        | HTTP header carrying the signature                |
| `algorithm`  | string enum: `sha256`\|`sha1`\|`sha512` | no       | `sha256` | HMAC digest algorithm                             |
| `prefix`     | string                                  | no       | `""`     | Literal prefix before the hex digest              |
| `routines`   | list of string (≥ 1)                    | yes      | —        | Autonomous routine ids this webhook dispatches to |

## Content document schemas

The documents the manifest references are validated against their own schemas, summarised on their concept pages:

| Document        | Shape defined in                                                     | Schema name (JSON Schema artifact) |
| --------------- | -------------------------------------------------------------------- | ---------------------------------- |
| Routine         | [Routines](/agents/concepts/routines)                                | `routines`                         |
| Policy          | [Policies](/agents/concepts/policies)                                | `policies`                         |
| Glossary        | [Glossaries & macros](/agents/concepts/glossaries-and-macros)        | `glossaries`                       |
| Macro           | [Glossaries & macros](/agents/concepts/glossaries-and-macros#macros) | `macros`                           |
| Variable set¹   | [Sessions, memory & state](/agents/concepts/memory-and-state)        | `variables`                        |
| Manifest itself | this page                                                            | `agent-manifest`, `agent-config`   |

¹ Variable sets are platform-catalog documents but are **not referenced from the manifest** — variable values are written per customer at runtime through the SDK.

The JSON Schemas include the cross-field rules prose can't keep honest — step-field mutual exclusions, "exactly one of `over`/`over_all_routines`", a routine node's `tools`/`chat_state`/`think` being mutually exclusive with a routing-only node requiring at least one `transitions` entry — so validating in CI catches authoring errors before deploy.

## Universal validation rules

* Credential-bearing fields accept only `${VAR_NAME}` env-refs (uppercase letters, digits, underscores; not starting with a digit) — literals are rejected at validation time.
* `min_length` enforces a character count only; it does not trim or reject whitespace-only values.
* `id` (manifest): URL-safe — letters, digits, hyphens, underscores only.
* Hostname fields: MCP hostnames (`mcps[].hostname`) must include an `https?://` scheme. The database hostname (`database.hostname`) is a plain host string with no scheme validation enforced and does not accept a path, port, or query string inline (port is a separate field). The external search endpoint (`search.url` when `type: external`) is a single URL field requiring a scheme, unlike the other host fields.
* Ports: 1–65535. Versions in refs: integers ≥ 1.


# HTTP API

The agent server's inbound HTTP surface: auth, health, autonomous trigger, webhooks, tool-event injection, routine graph, and the conversation API.

> **Context** — The agent server's complete inbound HTTP surface for runtime version `0.11.0`. Conversation traffic is normally driven through the SDK ([Integrating the SDK](/agents/guides/integrating-the-sdk)); this page is the wire-level contract. The machine-readable OpenAPI spec for this surface is published per version — see [Versioning & compatibility](/agents/operations/versioning#machine-readable-artifacts).

## Authentication

| Paths                         | Auth                                                                                                                                          |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `/health/*`, `/health`        | none                                                                                                                                          |
| `/webhooks/{name}`            | HMAC signature over the raw body (per-webhook config)                                                                                         |
| `/auth/login`, `/auth/logout` | none (the login flow itself)                                                                                                                  |
| everything else               | `Authorization: Bearer <agent api key>` (constant-time compared against the manifest's `runtime.api_key`), or the `agent_auth` cookie (below) |

Failed bearer auth returns `401`. Failed HMAC verification returns `401` with no detail; unknown webhook names return `404` indistinguishable from unconfigured ones.

**Browser cookie login** — for the built-in chat UI (`GET /chat`), where a browser can't attach an `Authorization` header to the UI's own requests: an unauthenticated browser navigation redirects to `GET /auth/login`; `POST /auth/login` validates the typed-in agent api key and sets an `HttpOnly` `agent_auth` cookie carrying the same key, which the browser replays on every same-origin call; `POST /auth/logout` clears it. Server-to-server integrations should keep using the bearer header.

## Health

| Method & path       | Response                                                                             |
| ------------------- | ------------------------------------------------------------------------------------ |
| `GET /health/live`  | always `200` `{"status": "ok", "service": "agent-server"}` — liveness                |
| `GET /health/ready` | `200` once configuration is applied, `503 {"status": ...}` while booting — readiness |
| `GET /health`       | alias of `/health/ready`                                                             |

Readiness intentionally precedes boot-time routine evaluation — see the [boot sequence](/agents/concepts/architecture#boot-sequence).

## Trigger an autonomous routine

```
POST /routines/{routine_id}/trigger
Authorization: Bearer <agent api key>
Content-Type: application/json
```

Runs the routine asynchronously; the typed result is delivered later to `callback_url` — see [Autonomous routines](/agents/concepts/autonomous-routines).

### Request body — `TriggerRequest`

Body of `POST /routines/{routine_id}/trigger`.

| Field             | Type                     | Required | Default | Description                                                                                                               |
| ----------------- | ------------------------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------- |
| `input`           | dict\[string, JsonValue] | no       | —       | Validated against the routine's input\_schema.                                                                            |
| `callback_url`    | string                   | yes      | —       | URL that receives the AutonomousCallbackPayload when the run settles.                                                     |
| `session_id`      | string                   | no       | `null`  | Reuse an existing session. If omitted an ephemeral session + customer are created and deleted after the callback settles. |
| `idempotency_key` | string                   | no       | `null`  | Replay-safe key — duplicates return the prior run.                                                                        |
| `metadata`        | dict\[string, JsonValue] | no       | —       | Opaque caller metadata echoed back in the callback payload.                                                               |

`input` must satisfy the routine's `input_schema`; `callback_url`'s hostname must match an entry of the routine's `callback_url_allowlist` when one is set.

**Response `202`** — run accepted:

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

| Status | Meaning                                                                                                   |
| ------ | --------------------------------------------------------------------------------------------------------- |
| `202`  | Accepted; callback follows asynchronously.                                                                |
| `400`  | Input failed `input_schema` validation (body carries `error` + `path`), or `callback_url` not allowed.    |
| `404`  | No autonomous routine with this id.                                                                       |
| `409`  | Duplicate `idempotency_key` — body echoes the prior run (`run_id`, `status`, `session_id`, `created_at`). |
| `500`  | Session preparation or engine dispatch failed.                                                            |
| `503`  | Server not ready.                                                                                         |

## Third-party webhook entry

```
POST /webhooks/{name}
<signature header>: <prefix><hex HMAC of raw body>
Content-Type: application/json
```

`{name}` is either a manifest-level webhook `name` (fan-out to one or more routines) or a routine id whose YAML declares `autonomous.webhook`. The raw JSON body becomes the run input. Webhook runs are fire-and-forget — no callback. Replays of an identical body dedupe to the existing run.

**Response `200`** (manifest-level fan-out):

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

Per-routine webhooks return the same `202` envelope as the trigger endpoint (a replayed body returns `200` with the existing run).

| Status        | Meaning                                              |
| ------------- | ---------------------------------------------------- |
| `200` / `202` | Dispatched (or deduped replay).                      |
| `400`         | Body is not a JSON object.                           |
| `401`         | Missing/invalid signature.                           |
| `404`         | Unknown webhook name.                                |
| `502`         | Routine matching failed — the provider should retry. |
| `503`         | Server not ready.                                    |

## Inject a tool event

```
POST /sessions/{session_id}/tool_events
Authorization: Bearer <agent api key>
Content-Type: application/json
```

Appends a synthetic tool result to a session's history — for supplying large external context (statements, history dumps) as if a tool had fetched it. Semantics: [Tools](/agents/concepts/tools#injecting-context-as-a-tool-event).

### Request body — `ToolEventRequest`

Body of `POST /sessions/{session_id}/tool_events`.

| Field                | Type                     | Required | Default | Description                                                                                                                                                                  |
| -------------------- | ------------------------ | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tool_id`            | string                   | yes      | —       | Opaque tool identifier surfaced to the LLM prompt. Canonical form is 'service\_name:tool\_name' (single colon), e.g. 'injected:conversation\_history'.                       |
| `arguments`          | dict\[string, JsonValue] | no       | —       | Tool call arguments — becomes the tool call's arguments field.                                                                                                               |
| `result`             | JsonValue                | yes      | —       | Tool result payload — becomes the tool result's data.                                                                                                                        |
| `result_metadata`    | dict\[string, JsonValue] | no       | —       | Becomes the tool result's metadata (inside the tool\_calls entry).                                                                                                           |
| `event_metadata`     | dict\[string, JsonValue] | no       | —       | Becomes the event's top-level metadata.                                                                                                                                      |
| `idempotency_key`    | string                   | no       | `null`  | Optional key for safe retries. If an injected tool event with this key already exists in the session, returns it without creating a duplicate. Stored in the event metadata. |
| `trigger_processing` | boolean                  | no       | `false` | If true, runs a response turn immediately after injection. WARNING: this cancels any turn currently processing for this session.                                             |

**Response `200`**:

```json
{
  "event_id": "evt_4f6a2b",
  "offset": 17,
  "creation_utc": "2026-06-04T10:15:00.412000+00:00",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
```

| Status | Meaning                                                  |
| ------ | -------------------------------------------------------- |
| `200`  | Event created (or idempotent replay of an existing one). |
| `404`  | Session not found.                                       |
| `503`  | Server not ready.                                        |

## Routine graph

```
GET /journeys/{journey_id}/graph
Authorization: Bearer <agent api key>
```

Returns the full routine state machine — `{"journey": {...}, "nodes": [...], "edges": [...]}` — for dashboards and debugging UIs. The `journeys` path segment is legacy wire naming for routines; ids are the engine-assigned routine ids surfaced in traces. `404` for unknown ids, `503` while booting.

## Conversation & customer API

The session/customer surface is consumed through the SDK, which owns its ergonomics (idempotent open, typed events, reconnecting streams). The routes it drives:

| Surface   | Routes                                                                                                                                                          |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Sessions  | create / read; `GET /sessions/{id}/events` (long-poll, `min_offset`-resumable) — the event stream; `POST /sessions/{id}/events` — post customer/system messages |
| Customers | register / read / update; customer variables and metadata                                                                                                       |

Use [Integrating the SDK](/agents/guides/integrating-the-sdk) for this surface; event wire shapes are in [Events & callbacks](/agents/reference/events-and-callbacks). The server rate-shapes aggressive event polling via an `x-polling-backoff` response header (seconds to wait) that well-behaved clients honour.


# Events & callbacks

Exact wire shapes: conversation events (polling and webhook delivery) and the autonomous callback payload, with status and error-code tables.

> **Context** — The exact wire shapes the agent emits: conversation events (delivered via the polling stream **and** via event webhooks — same JSON either way) and the autonomous callback payload. JSON Schemas for everything here are published per version — see [Versioning & compatibility](/agents/operations/versioning#machine-readable-artifacts).

## Conversation events

A session's event log is an ordered stream; every event carries:

| Field        | Type               | Meaning                                                                                                   |
| ------------ | ------------------ | --------------------------------------------------------------------------------------------------------- |
| `kind`       | string             | Discriminator — one of the kinds below.                                                                   |
| `offset`     | integer            | Monotonic position in the session. Resume with `min_offset = last + 1`; dedupe on `(session_id, offset)`. |
| `created_at` | RFC 3339 timestamp | Server-side creation time.                                                                                |

In webhook delivery mode each event is POSTed individually to your endpoint with `Authorization: Bearer <agent api key>` and the originating session in the `x-session-id` header. Retries with backoff on non-2xx.

### `user_message`

| Field        | Type    | Required | Default          | Description                                                                                                                             |
| ------------ | ------- | -------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `offset`     | integer | yes      | —                | Monotonic position in the session's event log. Resume polling with min\_offset = last seen offset + 1; dedupe on (session\_id, offset). |
| `created_at` | string  | yes      | —                | Server-side event creation time.                                                                                                        |
| `text`       | string  | yes      | —                | Message body.                                                                                                                           |
| `kind`       | string  | no       | `"user_message"` |                                                                                                                                         |

```json
{
  "kind": "user_message",
  "offset": 4,
  "created_at": "2026-06-04T10:15:00Z",
  "text": "Can I rent a van for Saturday?"
}
```

### `preamble`

| Field        | Type    | Required | Default      | Description                                                                                                                             |
| ------------ | ------- | -------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| `offset`     | integer | yes      | —            | Monotonic position in the session's event log. Resume polling with min\_offset = last seen offset + 1; dedupe on (session\_id, offset). |
| `created_at` | string  | yes      | —            | Server-side event creation time.                                                                                                        |
| `text`       | string  | yes      | —            | Message body.                                                                                                                           |
| `kind`       | string  | no       | `"preamble"` |                                                                                                                                         |

```json
{
  "kind": "preamble",
  "offset": 5,
  "created_at": "2026-06-04T10:15:01Z",
  "text": "Let me check."
}
```

### `assistant_message`

| Field        | Type    | Required | Default               | Description                                                                                                                             |
| ------------ | ------- | -------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `offset`     | integer | yes      | —                     | Monotonic position in the session's event log. Resume polling with min\_offset = last seen offset + 1; dedupe on (session\_id, offset). |
| `created_at` | string  | yes      | —                     | Server-side event creation time.                                                                                                        |
| `text`       | string  | yes      | —                     | Message body.                                                                                                                           |
| `kind`       | string  | no       | `"assistant_message"` |                                                                                                                                         |

```json
{
  "kind": "assistant_message",
  "offset": 7,
  "created_at": "2026-06-04T10:15:06Z",
  "text": "Yes \u2014 the VW Multivan is available at 110 EUR/day."
}
```

### `status`

Engine lifecycle signal — `typing`, `processing`, `ready`, etc.

| Field          | Type    | Required | Default    | Description                                                                                                                             |
| -------------- | ------- | -------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `offset`       | integer | yes      | —          | Monotonic position in the session's event log. Resume polling with min\_offset = last seen offset + 1; dedupe on (session\_id, offset). |
| `created_at`   | string  | yes      | —          | Server-side event creation time.                                                                                                        |
| `kind`         | string  | no       | `"status"` |                                                                                                                                         |
| `status`       | string  | yes      | —          | Lifecycle signal: acknowledged, typing, processing, ready, cancelled, or error.                                                         |
| `stage`        | string  | no       | `null`     | Sub-stage label shown alongside 'processing' (e.g. 'Thinking').                                                                         |
| `error_detail` | string  | no       | `null`     | Failure detail, populated only when status is 'error'.                                                                                  |

```json
{
  "kind": "status",
  "offset": 8,
  "created_at": "2026-06-04T10:15:06Z",
  "status": "ready",
  "stage": null,
  "error_detail": null
}
```

### `tool`

One tool event — contains every tool call from that batch.

| Field        | Type            | Required | Default  | Description                                                                                                                             |
| ------------ | --------------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `offset`     | integer         | yes      | —        | Monotonic position in the session's event log. Resume polling with min\_offset = last seen offset + 1; dedupe on (session\_id, offset). |
| `created_at` | string          | yes      | —        | Server-side event creation time.                                                                                                        |
| `kind`       | string          | no       | `"tool"` |                                                                                                                                         |
| `tool_calls` | list\[ToolCall] | yes      | —        | Every tool call the engine ran in this batch.                                                                                           |

```json
{
  "kind": "tool",
  "offset": 6,
  "created_at": "2026-06-04T10:15:04Z",
  "tool_calls": [
    {
      "tool_id": "cars:search_cars",
      "arguments": {
        "category": "van",
        "min_seats": 7
      },
      "result": {
        "results": [
          {
            "car_id": "van-1",
            "make": "VW",
            "model": "Multivan",
            "daily_price_eur": 110
          }
        ]
      }
    }
  ]
}
```

`status` values: `acknowledged`, `typing`, `processing` (with optional `stage` sub-label), `ready`, `cancelled`, `error` (with `error_detail`). UI mapping: [Integrating the SDK](/agents/guides/integrating-the-sdk#status-events-drive-your-ui).

Unknown kinds and message events without text should be ignored by consumers (the SDK's parsers already do).

## Autonomous trigger request

Body of `POST /routines/{routine_id}/trigger` — see the [HTTP API](/agents/reference/http-api#trigger-an-autonomous-routine) for the endpoint semantics:

| Field             | Type                     | Required | Default | Description                                                                                                               |
| ----------------- | ------------------------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------- |
| `input`           | dict\[string, JsonValue] | no       | —       | Validated against the routine's input\_schema.                                                                            |
| `callback_url`    | string                   | yes      | —       | URL that receives the AutonomousCallbackPayload when the run settles.                                                     |
| `session_id`      | string                   | no       | `null`  | Reuse an existing session. If omitted an ephemeral session + customer are created and deleted after the callback settles. |
| `idempotency_key` | string                   | no       | `null`  | Replay-safe key — duplicates return the prior run.                                                                        |
| `metadata`        | dict\[string, JsonValue] | no       | —       | Opaque caller metadata echoed back in the callback payload.                                                               |

## Autonomous callback payload

POSTed once to the trigger's `callback_url` when the run settles (succeeds, fails, or times out), with `Authorization: Bearer <agent api key>`. Dedupe deliveries by `run_id`.

### `AutonomousCallbackPayload`

Payload delivered to `callback_url` when a run settles.

| Field             | Type                     | Required | Default          | Description                                                                                                                                                                             |
| ----------------- | ------------------------ | -------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `schema_version`  | integer                  | no       | `1`              | Wire schema version of this payload.                                                                                                                                                    |
| `run_id`          | string                   | yes      | —                | Unique id of this run — dedupe callbacks on it.                                                                                                                                         |
| `routine_id`      | string                   | yes      | —                | Id of the routine that was triggered.                                                                                                                                                   |
| `status`          | AutonomousRunStatus      | yes      | —                | Settled run status. Callbacks only carry the terminal values 'succeeded' or 'failed'.                                                                                                   |
| `output`          | dict\[string, JsonValue] | no       | `null`           | Routine's typed output, matching its output\_schema. Present only when status is 'succeeded'.                                                                                           |
| `error`           | AutonomousRunError       | no       | `null`           | Structured error detail. Present only when status is 'failed'.                                                                                                                          |
| `session_id`      | string                   | yes      | —                | Session the run executed in — the caller's session\_id if reused, else the server-created ephemeral one.                                                                                |
| `trace_id`        | string                   | no       | `null`           | Tracing id for correlating this run's logs/traces.                                                                                                                                      |
| `started_at`      | string                   | yes      | —                | When the run began executing.                                                                                                                                                           |
| `completed_at`    | string                   | yes      | —                | When the run settled (success, failure, or timeout).                                                                                                                                    |
| `metadata`        | dict\[string, JsonValue] | no       | —                | Caller-supplied metadata from the trigger request, echoed back verbatim.                                                                                                                |
| `idempotency_key` | string                   | no       | —                | Random id generated for this callback delivery; stable across the delivery's own HTTP retries. Unrelated to the trigger request's idempotency\_key, which dedupes runs, not deliveries. |
| `origin_service`  | string                   | no       | `"agent-server"` | Identifies the service that sent this callback.                                                                                                                                         |

`status` values: `accepted`, `running`, `succeeded`, `failed` (callbacks carry only the settled states `succeeded` / `failed`).

### Error codes (`error.code` when `status` is `failed`)

| Code                            | Meaning                                                                                |
| ------------------------------- | -------------------------------------------------------------------------------------- |
| `timeout`                       | Run exceeded its `timeout_seconds`.                                                    |
| `engine_error`                  | Internal engine failure during the run.                                                |
| `tool_error`                    | A tool call failed irrecoverably.                                                      |
| `output_validation_failed`      | The final output violated the routine's `output_schema` (details carry the JSON path). |
| `input_validation_failed`       | The input payload violated `input_schema`.                                             |
| `session_error`                 | Session preparation or engine dispatch failed.                                         |
| `max_engine_iterations_reached` | The engine hit `max_engine_iterations` before `emit_output` was called.                |

`error` is an object: `{code, message, details}` — `details` is a code-specific object (e.g. the violating JSON `path` for validation failures).

**Example — success:**

```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"
}
```

**Example — failure:**

```json
{
  "schema_version": 1,
  "run_id": "run_1b2c3d4e5f60718293a4b5c6",
  "routine_id": "kyc-decision",
  "status": "failed",
  "output": null,
  "error": {
    "code": "output_validation_failed",
    "message": "'decision' is a required property",
    "details": {
      "path": [],
      "schema_path": [
        "required"
      ]
    }
  },
  "session_id": "sess_def456",
  "trace_id": "00f067aa0ba902b7a3ce929d0e0e4736",
  "started_at": "2026-06-04T11:02:00+00:00",
  "completed_at": "2026-06-04T11:02:09+00:00",
  "metadata": {},
  "idempotency_key": "0f1e2d3c4b5a69788796a5b4c3d2e1f0",
  "origin_service": "agent-server"
}
```


# Environment variables

Environment configuration the platform sets on the agent: manifest env-refs (secrets) and fixed-name operator knobs, with defaults.

> **Context** — The platform sets two kinds of environment configuration on the agent at deploy time: **manifest env-refs** (the `${VAR}` placeholders your manifest declares — names are yours to choose) and **operator variables** (fixed names that tune runtime behaviour). Generated from the runtime source of truth for version `0.11.0` (manifest schema 8.1.0).

## Manifest env-refs (secrets)

Every credential-bearing manifest field takes a `${VAR_NAME}` reference; the platform supplies the value from the secret bundle your manifest declares (see [Deploying](/agents/guides/deploying)). **Required** fields abort boot when the variable is unset (the error names it); optional fields resolve to unset. Webhook secrets are re-read per request (rotation without restart) — all others resolve once at boot.

| Manifest field                                 | Required           | Purpose                                                                                                                                                                                                      |
| ---------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `agent_config.database.password`               | when block present | `${VAR}` env-ref for the Postgres password.                                                                                                                                                                  |
| `agent_config.interactive_platform.public_key` | yes                | `${VAR}` env-ref for the InteractiveAI public key.                                                                                                                                                           |
| `agent_config.interactive_platform.secret_key` | yes                | `${VAR}` env-ref for the InteractiveAI secret key.                                                                                                                                                           |
| `agent_config.llms.api_key`                    | yes                | `${VAR}` env-ref for the InteractiveAI LLM router API key.                                                                                                                                                   |
| `agent_config.mcps[].api_key`                  | no                 | `${VAR}` env-ref for the MCP server's API key. Sent as `Authorization: Bearer`. Omit when the server doesn't require auth.                                                                                   |
| `agent_config.runtime.api_key`                 | yes                | Shared bearer token. The agent verifies inbound `Authorization: Bearer` headers against it and sends the same header on autonomous-routine webhook callbacks. `${VAR}` env-ref; literal values are rejected. |
| `agent_config.search (type: external).api_key` | no                 | `${VAR}` env-ref for the endpoint API key. Sent as `Authorization: Bearer`. Omit when the endpoint requires no auth.                                                                                         |
| `agent_config.traces.backend.api_key`          | no                 | `${VAR}` env-ref for the traces backend API key. Sent per `api_key_scheme`. Omit for endpoints that don't require auth.                                                                                      |
| `agent_config.webhooks[].secret_env`           | when block present | `${VAR}` env-ref for the HMAC shared secret. Re-read per request, so rotation picks up without restart.                                                                                                      |

Conventional variable names (`AGENT_API_KEY`, `ROUTER_API_KEY`, `INTERACTIVEAI_PUBLIC_KEY`, `INTERACTIVEAI_SECRET_KEY`, `DB_PASSWORD`, `EXTERNAL_SEARCH_KEY`, …) are just convention — the manifest is the source of truth for which names it references.

## Operator variables

Fixed-name knobs that tune runtime behaviour, set by the platform or operator at deploy time. All optional.

| Variable                             | Default  | Purpose                                                                                                                                                                                                                                                         |
| ------------------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ROUTER_MAX_TOKENS`                  | `100000` | Token ceiling per LLM-router call (context window cap).                                                                                                                                                                                                         |
| `STREAMING`                          | `False`  | Operator-only switch. When true, the agent streams chat replies token-by-token (agent output\_mode=STREAM); default false preserves whole-message delivery.                                                                                                     |
| `AUTONOMOUS_DEFAULT_TIMEOUT_SECONDS` | `120`    | Per-run timeout for autonomous routines that don't declare their own timeout\_seconds.                                                                                                                                                                          |
| `AUTONOMOUS_MAX_TIMEOUT_SECONDS`     | `600`    | Hard cap on any autonomous routine's timeout\_seconds — routine-declared values above this are clamped.                                                                                                                                                         |
| `AUTONOMOUS_CALLBACK_MAX_RETRIES`    | `5`      | Delivery attempts (with backoff) for autonomous callbacks and event webhooks before giving up.                                                                                                                                                                  |
| `SHUTDOWN_DRAIN_TIMEOUT_SECONDS`     | `120`    | On SIGTERM, seconds to wait for in-flight engine turns, autonomous callbacks, and event webhooks to finish before the process exits. MUST be below the K8s terminationGracePeriodSeconds (minus the \~10s HTTP connection-drain) or SIGKILL hits mid-drain.     |
| `STORE_GC_DELAY_SECONDS`             | `120`    | Seconds after readiness before the stale-store GC sweep runs (once per pod boot). Only fires when storage is PostgreSQL.                                                                                                                                        |
| `EVAL_NODE_PARALLELISM`              | `50`     | Concurrent per-step model calls during boot-time routine evaluation. 1 = fully sequential (debugging); higher = faster cold boots, bounded by the router rate budget.                                                                                           |
| `LLM_PROVIDER_ORDER`                 | unset    | Comma-separated provider slugs pinning which upstream providers serve all inference models (chat and evaluation), in preference order. Fallbacks are allowed: a request degrades to other providers when the pinned ones can't serve the model. Unset = no pin. |

## Fixed runtime facts (not configurable via env)

* HTTP listen port: `8080`.
* Startup failures exit the process non-zero within `10` seconds.
* Model ids are manifest-only (`agent_config.llms.*`) — there is no env-var override. See [Models](/agents/concepts/models).


# Built-in tools

Call contracts for the two built-in tools: emit\_output (terminates autonomous runs) and reason (typed think-step inference), with every error code.

> **Context** — The runtime ships two synthetic tools under the `built-in` namespace. Authors normally meet them through routine syntax (`think:` steps; autonomous terminal steps); this page is the exact call contract — what the model is asked to supply and every result the handler can return.

Both tools take nested JSON **as a JSON-encoded string parameter** (`output_json`) — tool parameters are primitive-typed, so objects are marshalled through strings. Both return `{"ok": true, ...}` on success and `{"ok": false, "error": <code>, ...}` on failure; a failed call is visible to the model, which can retry with corrected output within the turn's iteration budget.

## `built-in:emit_output`

Terminates an [autonomous run](/agents/concepts/autonomous-routines) with its typed result. Every terminal node of an autonomous routine (a node with no outbound transitions) must be a TOOL node calling it.

| Parameter     | Type                         | Meaning                                                                                                                                       |
| ------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `output_json` | string (JSON-encoded object) | The run's final output. Must parse as JSON and validate against the routine's `output_schema` (tightened with `additionalProperties: false`). |

On success the run transitions to `succeeded` and the callback watcher fires. Schema violations transition the run to `failed` with error code `output_validation_failed` (the callback's `error.details` carries the violating path).

| Result `error`             | Meaning                                                                  |
| -------------------------- | ------------------------------------------------------------------------ |
| `output_validation_failed` | `output_json` didn't parse, or violated `output_schema` — the run fails. |
| `not_an_autonomous_run`    | Called in a session that isn't an autonomous run.                        |
| `routine_not_registered`   | Session metadata names a routine the runtime doesn't know.               |
| `session_not_found`        | The session disappeared mid-run.                                         |
| `not_configured`           | Autonomous runtime not wired (server misconfiguration).                  |

Usage in a routine:

```yaml
- id: finish
  tools: built-in:emit_output
  tool_instruction: >
    Call emit_output with output_json set to a JSON object
    containing exactly the decision and explanation fields
    produced by the assess node.
```

## `built-in:reason`

Backs the `think:` node kind — typed structured inference that does **not** end the turn. The loader compiles each `think:` + `output_schema:` node into a TOOL node calling this tool with the node's id baked into the instruction.

| Parameter     | Type                         | Meaning                                                                                                                                        |
| ------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `step_id`     | string                       | The think node's id (the parameter keeps its historical wire name; the value is compiled into the node's instruction — not chosen at runtime). |
| `output_json` | string (JSON-encoded object) | The inference result. Must validate against the node's `output_schema` (tightened with `additionalProperties: false`).                         |

On success the validated object is merged into `session.metadata.step_outputs[<node-id>]` and returned in the tool result, so downstream steps see the typed fields in history.

| Result `error`             | Meaning                                                                                                               |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `output_validation_failed` | `output_json` didn't parse or violated the node's schema — the model can correct and retry.                           |
| `missing_step_id`          | Empty `step_id` argument.                                                                                             |
| `step_not_registered`      | No `output_schema` registered for this routine + node id.                                                             |
| `routine_not_resolved`     | The session has no `routine_id` metadata. Autonomous runs set it automatically, so this indicates a misconfiguration. |
| `session_not_found`        | The session disappeared.                                                                                              |
| `persist_failed`           | Writing `step_outputs` to the session failed.                                                                         |
| `not_configured`           | Reason-step registry not wired (server misconfiguration).                                                             |

Authoring guidance for think nodes (autonomous routines only): [Autonomous routines](/agents/concepts/autonomous-routines#node-types).


# Limits & defaults

Every default and cap in one table: manifest field defaults, operator env-var defaults, hardcoded model defaults, and behavioural constants.

> **Context** — Every default value and cap in one place, introspected from runtime version `0.11.0` (manifest schema 8.1.0). Field semantics live on the concept pages; this is the lookup table.

## Manifest field defaults

| Field                                                 | Default                                         |
| ----------------------------------------------------- | ----------------------------------------------- |
| `agent_config.runtime.log_level`                      | `"INFO"`                                        |
| `agent_config.runtime.max_engine_iterations`          | `5`                                             |
| `agent_config.runtime.policy_batch_size`              | `5`                                             |
| `agent_config.llms.default`                           | `"interactive/google/gemini-3-flash-preview"`   |
| `agent_config.llms.fallback`                          | `["interactive/google/gemini-3.1-pro-preview"]` |
| `agent_config.database.port`                          | `5432`                                          |
| `agent_config.database.user`                          | `"postgres"`                                    |
| `agent_config.database.dbname`                        | `"postgres"`                                    |
| `agent_config.database.sslmode`                       | `"require"`                                     |
| `agent_config.search (external).type`                 | `"external"`                                    |
| `agent_config.search (external).top_k`                | `5`                                             |
| `agent_config.search (external).max_messages`         | `20`                                            |
| `agent_config.search (external).timeout_seconds`      | `5.0`                                           |
| `agent_config.search (collections).type`              | `"collections"`                                 |
| `agent_config.search (collections).operator_base_url` | `"https://deployment.interactive.ai"`           |
| `agent_config.search (collections).limit`             | `5`                                             |
| `agent_config.search (collections).history_limit`     | `5`                                             |
| `agent_config.search (collections).filter`            | `{}`                                            |
| `agent_config.search (collections).fusion_k`          | `60`                                            |
| `agent_config.search (collections).exact`             | `false`                                         |
| `agent_config.traces.deployment_environment`          | `"production"`                                  |
| `agent_config.traces.backend.api_key_scheme`          | `"bearer"`                                      |
| `agent_config.webhooks[].algorithm`                   | `"sha256"`                                      |
| `agent_config.webhooks[].prefix`                      | `""`                                            |

## Operator env-var defaults

| Variable                             | Default  |
| ------------------------------------ | -------- |
| `ROUTER_MAX_TOKENS`                  | `100000` |
| `STREAMING`                          | `False`  |
| `AUTONOMOUS_DEFAULT_TIMEOUT_SECONDS` | `120`    |
| `AUTONOMOUS_MAX_TIMEOUT_SECONDS`     | `600`    |
| `AUTONOMOUS_CALLBACK_MAX_RETRIES`    | `5`      |
| `SHUTDOWN_DRAIN_TIMEOUT_SECONDS`     | `120`    |
| `STORE_GC_DELAY_SECONDS`             | `120`    |
| `EVAL_NODE_PARALLELISM`              | `50`     |
| `LLM_PROVIDER_ORDER`                 | unset    |

## Hardcoded model defaults

| Slot                                                       | Default                                     |
| ---------------------------------------------------------- | ------------------------------------------- |
| Customer-facing (`llms.default` absent)                    | `interactive/google/gemini-3-flash-preview` |
| Customer-facing router alternates (`llms.fallback` absent) | `interactive/google/gemini-3.1-pro-preview` |
| Evaluation (`llms.evaluation.default` absent)              | `interactive/google/gemini-3-flash-preview` |
| Evaluation escalation (`llms.evaluation.fallback` absent)  | `interactive/google/gemini-3.1-pro-preview` |

## Behavioural constants

| Constant                         | Value           | Meaning                                                                                                                       |
| -------------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| HTTP port                        | `8080`          | Container listen port.                                                                                                        |
| Evaluation retry attempts        | `3` + `3`       | Per internal decision call: 3 on the primary evaluation model, then 3 on the fallback. See [Models](/agents/concepts/models). |
| Autonomous timeout default / cap | `120s` / `600s` | Routine `timeout_seconds` falls back to the default and is clamped to the cap.                                                |
| Callback delivery attempts       | `5`             | Autonomous callbacks / event webhooks, with backoff.                                                                          |
| Startup failure exit window      | `10s`           | Boot errors exit the process non-zero within this window.                                                                     |


# Troubleshooting

Symptom-indexed fixes: boot failures, readiness stuck at 503, wrong agent behaviour, tool problems, autonomous failures, and performance issues.

> **Context** — Indexed by symptom. Each entry: what you see → what it means → what to do. Diagnosis tooling (traces, logs, markers) is covered in [Observability](/agents/guides/observability).

## Boot & readiness

### The container exits immediately

Read the last log lines — startup failures are loud and specific, and the process exits non-zero within 10 seconds by design:

| Log says                                                           | Cause                                                                                                                                                    | Fix                                                                                       |
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Validation error listing manifest fields                           | Structural manifest problem (bad hostname pattern, port range, missing required field, literal where a `${VAR}` ref is required)                         | Fix every listed violation; the schema reports them all at once                           |
| `env var X is unset` (or a `ValueError` naming a variable)         | A `${VAR}` ref has no backing env var                                                                                                                    | Add the variable to the injected secrets — check the manifest's `secrets:` list covers it |
| Router key missing/empty                                           | `ROUTER_API_KEY` (or your chosen name) unset or blank                                                                                                    | Set it; the agent refuses to start rather than send unauthenticated model traffic         |
| Content reference not found (a routine/policy/prompt id + version) | The pinned version doesn't exist in the platform catalog                                                                                                 | Publish that version to the catalog, or fix the `id`/`version` pin in the manifest        |
| MCP server unreachable                                             | A declared `mcps[]` entry can't be contacted                                                                                                             | Fix the address/network, or remove the entry — tool servers are part of the boot contract |
| Collections field/slot mismatch                                    | A `vector` field targets a slot that doesn't exist, or has no bound embedding model; a `str` field is declared on a collection without full-text enabled | Align the manifest's `search.fields` with the collection (`iai databases describe`)       |
| Webhook references unknown routine                                 | `webhooks[].routines` names a routine that isn't autonomous or isn't referenced                                                                          | Fix the cross-reference                                                                   |

### `/health/ready` stays 503

Readiness flips only after content fetch + configuration apply. Stuck means one of those is hanging — usually platform connectivity or a slow MCP handshake. The log shows the last completed stage. Note that routine evaluation happens **after** readiness — a ready-but-slow-to-respond agent on first turns is the evaluation cache warming, not a readiness problem (see below).

### Boot succeeds but takes minutes; first turns are slow

Cold evaluation cache. Verify with the per-routine bookend lines (`Routine '<title>' evaluated: N nodes in Xs` — nonzero nodes = cache miss). Fix permanently by [startup evaluation](/agents/concepts/startup-evaluation#caching-cold-vs-warm-boots); mitigate by raising `EVAL_NODE_PARALLELISM` if your router budget allows.

## Wrong behaviour

### A policy doesn't fire when it should

1. Check the trace: the matching decision is recorded per policy per turn.
2. Usual causes, in order of frequency:
   * **Abstract condition** — rewrite with concrete triggers ([guide](/agents/guides/authoring-policies#writing-conditions-that-match-correctly)).
   * **Condition depends on data not yet in the conversation** (e.g. on a tool result no step has fetched).
   * **Stale state** — relevance flipped mid-turn after a tool ran; add the tool to `reevaluate_after` or `context.reevaluation_tools`.
   * **Lost a conflict** — a higher-priority policy/routine took precedence; check [priorities](/agents/concepts/priorities).
3. For must-always-hold rules, `always_match: true` removes the matcher from the equation.

### A routine activates at the wrong time (or never)

Activation is condition matching — same diagnosis as policies. The classic miss: two routines with overlapping conditions where the wrong one wins. Carve mutual boundaries into both conditions ("Do NOT activate when the user refers to an existing booking") and/or add an explicit [priority](/agents/concepts/priorities).

### The agent calls a tool and says nothing

A node combines `tools` with `chat_state` — the schema rejects the combination, and older content that slipped through behaves as a tool node with the chat text ignored. Split into two nodes connected by a transition. This is the #1 authoring bug: [the node types](/agents/concepts/routines#node-types-read-this-first).

### The agent ignores routine structure / freelances the flow

* The system prompt narrates a competing procedure — move procedure out of the prompt, keep persona ([guide](/agents/concepts/prompts#division-of-labour)).
* Mega-nodes invite improvisation — one concern per node ([anti-patterns](/agents/guides/authoring-routines#anti-patterns)).

### Answers lost their grounding (KB seems ignored)

Retrieval **soft-fails by design** — turns proceed without context. Check logs for retrieval warnings; check the trace's retrieval span (is the produced query sane? did snippets come back?). Common causes: KB down/unreachable, `filter` excluding everything, external endpoint returning a non-bare-array shape. See [knowledge-base verification](/agents/guides/knowledge-base-setup#verifying-grounding-both-options).

### Replies in the wrong language

Check `context.language`. `match_user` mirrors the customer; a fixed language overrides them; a list prefers the customer's if listed. The greeting/preamble adapt to the same directive.

## Tools

### Tool calls fail at runtime

The failed call is visible in the trace (arguments + error) and in the event stream. Distinguish:

* **Transport failures** — the runtime reconnects to MCP servers automatically; sustained failures mean the server is down.
* **Tool-level errors** — your server returned an error result; the model reads it and follows its instructions. If it handles errors badly, the tool's error payload probably isn't self-explanatory — [return errors as data](/agents/guides/connecting-tools#3-designing-tools-the-agent-calls-well).
* **Wrong arguments** — improve the parameter docs and the node's `tool_instruction`; the model only knows what those say.

## Autonomous runs

Diagnose by the callback's `error.code` (full taxonomy: [Autonomous routines](/agents/concepts/autonomous-routines#failure-taxonomy)):

| Code                            | First check                                                                                                                                                                                                                    |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `timeout`                       | Trace shows where time went — slow tool? model latency? Raise `timeout_seconds` only after the trace says the path is legitimately long.                                                                                       |
| `max_engine_iterations_reached` | Count the longest path's tool+think nodes (+1 for `emit_output`) against `runtime.max_engine_iterations`.                                                                                                                      |
| `output_validation_failed`      | Error details carry the JSON path; the terminal `tool_instruction` and `output_schema` disagree.                                                                                                                               |
| `engine_error`                  | Open the trace via the callback's `trace_id`; check `[retry-fallback]` ERRORs around the timestamp.                                                                                                                            |
| HTTP 400 at trigger             | Input vs `input_schema` — the response body names the path.                                                                                                                                                                    |
| HTTP 404 at trigger             | Routine id wrong, or the routine YAML has no `autonomous:` block.                                                                                                                                                              |
| No callback arrives             | Check `callback_url_allowlist`; check your receiver returned 2xx. The server retries on `429`/`5xx`/connection failures (5× with backoff, then gives up — delivery status is logged); other non-2xx responses are not retried. |
| Webhook returns 401             | Signature mismatch: algorithm, prefix, header name, or secret value differ from the provider's signing config. The secret is re-read per request, so rotation is instant — make sure both sides rotated.                       |

## Performance

| Symptom                    | Likely cause                                                          | Lever                                                                                                                                                                                                 |
| -------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Slow replies, always       | Long tool chains per turn; chatty MCP servers; oversized tool results | Trace the turn; trim results; parallelise independent tools in one node                                                                                                                               |
| Slow replies, occasionally | Evaluation-model retries/escalations                                  | `[retry-fallback]` rate; see [Models](/agents/concepts/models#operational-guidance)                                                                                                                   |
| Router throttling          | Scale-out multiplied call volume                                      | Router-side limits; reduce `EVAL_NODE_PARALLELISM` during boots                                                                                                                                       |
| Polling clients hammered   | Busy-loop event polling                                               | The events endpoint is server-side rate-shaped (short delay on repeated identical polls); custom clients should long-poll normally and avoid tight busy-loops — no client-side header handling needed |

## Sessions & state

| Symptom                                              | Cause                                                           | Fix                                                                                                |
| ---------------------------------------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| Conversations reset after deploys                    | In-memory storage                                               | Add the `database:` block — [storage backends](/agents/concepts/memory-and-state#storage-backends) |
| Agent doesn't know what the integration just learned | Wrote metadata, not variables; or expected same-turn visibility | Write **variables**; they surface next turn                                                        |
| Replies land on the wrong replica's clients          | In-memory storage + multiple replicas                           | Postgres storage, or webhook event delivery (the URL travels on session metadata)                  |

## When you escalate

Capture: the trace id (or `session_key`/run id), the agent's runtime version (boot banner), manifest content versions (in the trace snapshot), and the relevant log excerpt. That tuple reproduces almost everything.


# Security

The agent's security model: bearer auth, HMAC webhooks, secret handling and rotation, callback allowlists, and network posture.

> **Context** — Everything security-relevant about running an agent, in one place: who can call what, how each credential flows, and what to lock down. Assumes the [network surface](/agents/concepts/architecture#network-surface) overview.

## Authentication surfaces

### Inbound: bearer token (everything except health, webhooks & login)

All API routes except the health probes, `/webhooks/*`, and the `/auth/login` flow require:

```
Authorization: Bearer <agent api key>
```

The token is the manifest's `runtime.api_key` (resolved from `${AGENT_API_KEY}` or your chosen variable). Comparison is constant-time.

For the built-in chat UI only, the same key can be presented as an `HttpOnly` `agent_auth` cookie, set by typing the key into `POST /auth/login` (browsers can't attach bearer headers to the UI's own requests) and cleared by `POST /auth/logout`. The cookie carries the same secret with the same powers — everything below applies to it equally.

Properties to design around:

* **One shared token per agent.** There is no per-client identity, no scopes, no expiry. Anyone holding the token has the full API: read any session, post messages, trigger autonomous routines.
* Therefore: the token belongs to your **integration tier only**. Browsers and end-user devices must never see it — your integration authenticates end users its own way and holds the agent token server-side (the [SDK guide](/agents/guides/integrating-the-sdk)'s architecture).
* Treat it like a database password: secret store, rotation procedure, never in URLs or logs.

### Inbound: HMAC signatures (`/webhooks/*`)

Third-party webhook entry points bypass bearer auth — the provider's signature over the raw body *is* the authentication:

* Signature = `prefix + hex(HMAC(secret, raw_body))`, algorithm `sha256` (or `sha1`/`sha512` where a provider requires it), carried in the configured header. Verified constant-time; any failure → 401 with no detail.
* Unknown webhook names and routines without webhook config return an identical 404 — the public surface doesn't reveal what exists.
* **Rotation without restart:** the secret env var is re-read on every request. Update the secret in the platform's secret store and both old and new traffic windows behave predictably (one secret is valid at a time — coordinate the provider-side switch).
* Replay safety: identical deliveries dedupe to the same run via a body-hash idempotency key — a replayed webhook cannot double-fire a routine.

### Outbound credentials

| Destination                                               | Credential                                     | Header                             |
| --------------------------------------------------------- | ---------------------------------------------- | ---------------------------------- |
| LLM router                                                | `llms.api_key`                                 | Bearer                             |
| InteractiveAI platform (boot fetch)                       | `interactive_platform.{public_key,secret_key}` | platform auth                      |
| MCP servers                                               | per-server `mcps[].api_key`                    | Bearer                             |
| Knowledge base (collections)                              | `interactive_platform.{public_key,secret_key}` | Platform auth                      |
| External search                                           | `search.api_key`                               | Bearer                             |
| Traces backend                                            | `traces.backend.api_key`                       | Bearer or Basic (`api_key_scheme`) |
| Autonomous callbacks & event webhooks to your integration | the agent api key                              | Bearer                             |

Your callback/webhook receivers **must verify** that bearer header — otherwise anyone who can reach them can forge "agent results".

## Secret handling rules

The manifest cannot contain a secret value — credential fields only accept `${VAR_NAME}` references, enforced by schema validation:

* **Resolution at boot, fail-fast:** missing variables abort startup with the variable name. No fallback path, no partial boots.
* **Exception — webhook secrets** are read per request (rotation support, above).
* You declare a secret bundle in the manifest's `secrets:` list (by its name in Interactive Secrets); the platform injects that bundle's key/value pairs as environment variables before boot. The value exists only in the agent's environment, never in the manifest.
* **Rotation procedure** (all except webhook secrets): update the secret in the platform's secret store; the platform rolls the agent to pick it up. The old credential must stay valid until the rollout completes.
* Logs and config dumps redact credential fields; Postgres URIs are logged with credentials masked.

## Autonomous-surface hardening

* **`callback_url_allowlist`** on every autonomous routine in production. Without it, any caller holding the bearer token can point results at any URL. With it, only listed hostnames (a leading `.` allows the apex and subdomains) receive callbacks.
* **Operator timeout bounds** are a backstop against runaway runs: a routine's `timeout_seconds` is capped by `AUTONOMOUS_MAX_TIMEOUT_SECONDS` (default 600s) regardless of what the YAML asks for.
* **Input schemas are a security control** — strict `input_schema`s reject malformed payloads at the door (HTTP 400), before any model sees them.

## Prompt-level exposure

What reaches the model is what's in the session: messages, tool results, variables, retrieved snippets. Practical consequences:

* **Anything a tool returns can end up in a reply.** Don't return fields the customer must never see; filter server-side in the MCP tool, not in instructions.
* **Injected tool events** (`/sessions/{id}/tool_events`) render verbatim into prompts (control characters are stripped). The endpoint is bearer-gated, but treat injected payloads with the same trust you'd give tool results.
* **Variables are agent-visible by definition** — never put secrets in variables. Integration-private data goes in metadata, which never reaches a prompt.
* **Traces capture conversations, tool results, and variables** and the trace metadata snapshot is not redacted. Apply access control on the traces backend accordingly, and point `traces.backend` at your own collector if platform-default storage doesn't meet your data policy.

## Network posture checklist

* [ ] Agent reached only through the platform-managed endpoint; `endpoint: true` only when a public URL is genuinely required
* [ ] Bearer token held exclusively by the integration tier; rotation procedure documented
* [ ] Third-party providers reach only `/webhooks/*`; every webhook HMAC-verified
* [ ] MCP servers not publicly reachable beyond what the agent needs; per-server `api_key` set when network reachability alone isn't enough
* [ ] Postgres (sessions) reachable from the agent only; `sslmode: require` or stricter
* [ ] Callback receivers verify the bearer header and dedupe by `run_id`
* [ ] `callback_url_allowlist` set on every production autonomous routine
* [ ] Trace backend access restricted to operators who may read conversations


# Versioning & compatibility

The four version axes of an agent deployment, the compatibility matrix, the upgrade procedure, and where machine-readable schemas and docs are published.

> **Context** — An agent deployment has four independently versioned layers. This page defines them, explains how compatibility between them is published, and gives the upgrade procedure.

## The four version axes

| Axis                  | Example                   | Who controls it                      | Changes when                                            |
| --------------------- | ------------------------- | ------------------------------------ | ------------------------------------------------------- |
| **Runtime**           | `0.7.x`                   | The platform                         | The platform upgrades the agent runtime                 |
| **Manifest schema**   | `5.0.0`                   | Implicit — determined by the runtime | The manifest format itself evolves                      |
| **Manifest revision** | `version: "12"`           | You — manifest top-level `version`   | You change the agent's configuration                    |
| **Content versions**  | `book-a-car` `version: 9` | You — each `context.*` reference pin | You publish new routine/policy/prompt/glossary versions |

Key properties:

* **You own the bottom two axes; the platform owns the runtime.** You pin content versions and stamp a manifest revision; the platform runs and upgrades the runtime, and the runtime determines which manifest schema applies.
* **Content pins are exact.** Publishing version 10 of a routine changes nothing until a manifest pinning `version: 10` is deployed. This makes content rollout deliberate and rollback trivial (redeploy the previous manifest).
* **The manifest `version` label is yours** — a free-form revision marker surfaced in logs and traces so you can correlate behaviour with config releases.

## The compatibility matrix

Each runtime version validates manifests against one manifest-schema version — the manifest and content document shapes **differ across schema versions**, so an example written for one schema may be invalid under another. All examples in this documentation set follow **manifest schema 8.1.0** (the schema of the runtime version these docs were generated for). The published **compatibility matrix** maps runtime to schema:

```json
{
  "0.6.4": "4.0.0",
  "0.6.3": "4.0.0",
  "0.5.1": "3.0.0",
  "0.4.2": "2.1.0"
}
```

It is published at a stable location alongside the schemas (ask your platform operator for the bucket; the canonical object is `compatibility-matrix.json` at the bucket root) and rendered in the schema reference docs. When the platform moves your agent to a new runtime version, check whether the schema version changes — same schema version means your manifest is valid as-is.

## Machine-readable artifacts

Per published version, these artifacts exist for tooling and AI agents:

| Artifact                        | Contents                                                                                                                                   | Location pattern                                               |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- |
| Manifest & content JSON Schemas | `agent-manifest`, `agent-config`, `routines`, `policies`, `glossaries`, `macros`, `variables` — Draft 2020-12, including cross-field rules | `<schemas bucket>/<schema-version>/<name>.json`                |
| Agent API artifacts             | OpenAPI spec for the agent's HTTP surface and JSON Schemas for conversation events and autonomous callbacks                                | `docs/agent/json/<runtime-version>/` in the runtime repository |
| Compatibility matrix            | runtime → schema-version map                                                                                                               | `<schemas bucket>/compatibility-matrix.json`                   |

Validate manifests in CI with any JSON Schema library against the matching schema version — the same validation the platform applies on upload.

## Upgrade procedures

### Content release (most frequent)

1. Publish new content versions to the platform catalog.
2. Bump the pins in the manifest; bump the manifest `version` label.
3. Deploy the manifest. The platform validates it, warms the evaluation cache for the changed items (see [Startup evaluation](/agents/concepts/startup-evaluation#caching-cold-vs-warm-boots)), and rolls the agent.
4. Verify via traces — the per-turn config snapshot shows the new versions live.

**Rollback:** redeploy the previous manifest. Content versions are immutable in the catalog, so rollback is exact.

### Runtime upgrade

The platform performs runtime upgrades; your job is to keep your manifest compatible across the schema boundary:

1. Read the matrix: does the new runtime require a new manifest-schema version?
   * **Same schema** → nothing to do; your manifest is valid as-is.
   * **New schema** → validate your manifest against the new JSON Schema and adjust any changed fields, so it's ready when the upgrade lands.
2. If your platform offers a staging environment, deploy your manifest there against the new runtime first — it applies cleanly or fails loudly with every violation listed.
3. With Postgres session storage, in-flight conversations survive the platform's rollout.

**Rollback:** runtime rollback is platform-side; from your side, the matching manifest revision is what pairs with each runtime.

### Documentation versioning

These docs are regenerated per runtime release. Reference pages marked `generated: true` are produced from the runtime's source of truth and cannot drift from it.

## Pinning discipline (recommendations)

* Pin exact content versions in the manifest and stamp a manifest revision — never float references.
* Keep your content changes and a platform runtime upgrade on separate deploys where you can, so behaviour changes attribute cleanly to one axis.
* Keep manifests in version control even though the platform stores them; the diff history of pins is your behaviour-change ledger, and traces carry the manifest `version` label to join against it.


# iai

### iai

InteractiveAI's CLI

#### Synopsis

InteractiveAI's CLI to interact with its platform.

Use the subcommands below to manage your organizations, projects, agents, services, secrets, prompts, routines, policies, variables, glossaries, macros, and other components.

### Install

The CLI is distributed through Go's package manager, so it must first be installed. Click on [this](https://go.dev/doc/install) link and follow the instructions to do so.

To validate the installation run:

```bash
go version
```

Once Go is installed, ensure Go binaries are in your PATH:

```bash
export PATH=$PATH:$(go env GOPATH)/bin
```

Add this line to your shell profile (\~/.bashrc, \~/.zshrc, etc.) to make it permanent.

Now install InteractiveAI's CLI with the following command:

```bash
go install github.com/Interactive-AI-Labs/interactive-cli/cmd/iai@latest
```

Verify the installation by running:

```bash
iai --help
```

***

### Structured output

Many commands support `--json` and `--yaml` for automation. Human-readable table/detail output is the default.

For API-backed commands such as `traces`, `observations`, `datasets`, `queues`, `scores`, and `sessions`, structured output preserves the API envelope:

```json
{
  "success": true,
  "data": {
    "traces": []
  }
}
```

Use the resource under `data` as the stable payload, for example:

```bash
iai traces list --json | jq '.data.traces[]'
iai traces get <trace-id> --yaml
```

Prompt resources (`prompts`, `routines`, `policies`, `variables`, `glossaries`, `macros`, `skills`) expose structured output on `list` and `describe/get`. List output is wrapped as:

```json
{
  "prompts": [],
  "totalCount": 0
}
```

#### Options

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
  -h, --help                         help for iai
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

#### SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools
* [iai api-keys](/cli/iai_api-keys) - Project API keys
* [iai collections](/cli/iai_collections) - Knowledge bases (searchable tables of chunks) inside a pgvector database
* [iai comments](/cli/iai_comments) - Annotate traces, observations, and sessions
* [iai completion](/cli/iai_completion) - Generate the autocompletion script for the specified shell
* [iai databases](/cli/iai_databases) - PostgreSQL instances with extension support, including pgvector
* [iai dataset-items](/cli/iai_dataset-items) - Manage items in evaluation datasets
* [iai dataset-runs](/cli/iai_dataset-runs) - Run evaluations against datasets
* [iai datasets](/cli/iai_datasets) - Create and list evaluation datasets
* [iai glossaries](/cli/iai_glossaries) - Domain vocabularies for consistent term interpretation
* [iai images](/cli/iai_images) - Manage container images
* [iai login](/cli/iai_login) - Authenticate with InteractiveAI
* [iai logout](/cli/iai_logout) - Clear local session
* [iai macros](/cli/iai_macros) - Pre-approved response templates used in routines
* [iai mcps](/cli/iai_mcps) - Deploy and manage MCP servers
* [iai metrics](/cli/iai_metrics) - Query aggregated observability metrics
* [iai observations](/cli/iai_observations) - Inspect spans within traces
* [iai organizations](/cli/iai_organizations) - Switch or list organizations
* [iai policies](/cli/iai_policies) - Single-step behavioral rules for agents
* [iai projects](/cli/iai_projects) - Switch or list projects
* [iai prompts](/cli/iai_prompts) - Versioned prompts for agents, evaluators, and guardrails
* [iai queue-items](/cli/iai_queue-items) - Manage items in annotation queues
* [iai queues](/cli/iai_queues) - Annotation queues for human review workflows
* [iai replicas](/cli/iai_replicas) - Inspect service replicas
* [iai router](/cli/iai_router) - Inspect the inference router, keys, and models
* [iai routines](/cli/iai_routines) - Multi-step behavioral processes for agents
* [iai run-items](/cli/iai_run-items) - Inspect results of evaluation runs
* [iai score-configs](/cli/iai_score-configs) - Define scoring schemas for evaluation
* [iai scores](/cli/iai_scores) - Read and write evaluation scores
* [iai secrets](/cli/iai_secrets) - Encrypted key-value pairs for services and agents
* [iai services](/cli/iai_services) - Deploy and manage HTTP services
* [iai sessions](/cli/iai_sessions) - Browse trace-derived conversation sessions
* [iai skills](/cli/iai_skills) - Manage Interactive Copilot skills (not to be confused with context items that configure the Interactive Agent)
* [iai stacks](/cli/iai_stacks) - Declarative resource sync from config files
* [iai traces](/cli/iai_traces) - Browse agent decision traces with full attribution
* [iai update](/cli/iai_update) - Update iai to the latest version
* [iai variables](/cli/iai_variables) - Contextual attributes referenced in policies and routines


# iai agents

Deploy AI agents with policies, routines, and tools

## Synopsis

Manage deployment of agents to InteractiveAI projects.

## Options

```
  -h, --help   help for agents
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai](/cli) - InteractiveAI's CLI
* [iai agents activate](/cli/iai_agents_activate) - Activate a deactivated agent in a project
* [iai agents catalog](/cli/iai_agents_catalog) - List available agent types and versions
* [iai agents compatibility-matrix](/cli/iai_agents_compatibility-matrix) - Show agent version to schema version compatibility
* [iai agents create](/cli/iai_agents_create) - Create an agent in a project
* [iai agents deactivate](/cli/iai_agents_deactivate) - Deactivate an agent in a project
* [iai agents delete](/cli/iai_agents_delete) - Delete an agent from a project
* [iai agents describe](/cli/iai_agents_describe) - Describe an agent in detail
* [iai agents diff](/cli/iai_agents_diff) - Compare two revisions of an agent
* [iai agents list](/cli/iai_agents_list) - List agents in a project
* [iai agents log-fields](/cli/iai_agents_log-fields) - List available fields in structured logs
* [iai agents logs](/cli/iai_agents_logs) - Show logs for an agent
* [iai agents port-forward](/cli/iai_agents_port-forward) - Forward a local port to an agent
* [iai agents restart](/cli/iai_agents_restart) - Restart an agent in a project
* [iai agents revisions](/cli/iai_agents_revisions) - List revisions of an agent
* [iai agents schema](/cli/iai_agents_schema) - Display the JSON Schema for agent configuration
* [iai agents update](/cli/iai_agents_update) - Update an agent in a project


# iai agents activate

Activate a deactivated agent in a project

## Synopsis

Activate a deactivated agent, restoring it to its previous configuration.

```
iai agents activate <agent_name> [flags]
```

## Examples

```
  iai agents activate my-agent
```

## Options

```
  -h, --help                  help for activate
  -o, --organization string   Organization name
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai agents catalog

List available agent types and versions

## Synopsis

List agent types available in the catalog.

Without arguments, lists all available agent IDs. With an agent ID argument, lists available versions for that agent.

```
iai agents catalog [agent_id] [flags]
```

## Examples

```
  iai agents catalog
  iai agents catalog interactive-agent
```

## Options

```
  -h, --help   help for catalog
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai agents compatibility-matrix

Show agent version to schema version compatibility

## Synopsis

Display the compatibility matrix between agent versions and schema versions.

Each agent version requires a specific config schema. Use this command to find the schema version for your target agent version, then run 'iai agents schema --schema-version ' to see the expected config fields.

Prompt types (routines, policies, etc.) also support versioned schemas — use --schema-version on their create/update commands to validate against the matching version.

By default, output is a formatted table. Use --json for machine-readable output.

```
iai agents compatibility-matrix [flags]
```

## Examples

```
  iai agents compatibility-matrix
  iai agents compatibility-matrix --json
```

## Options

```
  -h, --help   help for compatibility-matrix
      --json   Output raw JSON instead of a formatted table
      --yaml   Output structured YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai agents create

Create an agent in a project

## Synopsis

Create an agent in a specific project.

The --file flag takes a YAML file matching the agent\_config schema. Pass the agent name as the positional argument and id/version/env/secrets/endpoint/schedule via flags; do not include them inside the file.

The config schema depends on the agent version. Run 'iai agents compatibility-matrix' to find which schema version applies, then 'iai agents schema --schema-version ' to see the expected fields.

Routines and policies referenced in the config must already exist in the project and should be validated against the matching schema version (see --schema-version on their create/update commands).

```
iai agents create <agent_name> [flags]
```

## Examples

```
  iai agents create chat-agent --id interactive-agent --version 0.0.1 --file agent-config.yaml
  iai agents create chat-agent --id interactive-agent --version 0.0.1 --file agent-config.yaml --endpoint
  iai agents create chat-agent --id interactive-agent --version 0.0.1 --file agent-config.yaml --secret api-keys --env LOG_LEVEL=info
```

## Options

```
      --endpoint                   Expose the agent at <agent-name>-<project-hash>.interactive.ai
      --env stringArray            Environment variable (NAME=VALUE); can be repeated
      --file string                Path to YAML file matching the agent_config schema (run 'iai agents schema' to see it)
  -h, --help                       help for create
      --id string                  Agent type from the marketplace (e.g. interactive-agent)
      --mcp stringArray            Attach an MCP by name (see 'iai mcps list'); can be repeated
  -o, --organization string        Organization name
  -p, --project string             Project name
      --schedule-downtime string   When the agent should be scaled down (mutually exclusive with --schedule-uptime). Format: comma-separated entries of DAY_FROM-DAY_TO HH:MM-HH:MM. Example: 'Sat-Sun 00:00-24:00'
      --schedule-timezone string   IANA timezone for the schedule (e.g. Europe/Berlin, US/Eastern, UTC); required with --schedule-uptime or --schedule-downtime
      --schedule-uptime string     When the agent should be running (mutually exclusive with --schedule-downtime). Format: comma-separated entries of DAY_FROM-DAY_TO HH:MM-HH:MM. Example: 'Mon-Fri 07:30-20:30'
      --secret stringArray         Secret to inject as environment variables; can be repeated
      --stack-id string            Stack ID to assign the agent to
      --version string             Agent image version to deploy (e.g. 0.0.1)
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai agents deactivate

Deactivate an agent in a project

## Synopsis

Deactivate an agent, stopping all running instances. The current configuration is preserved and will be restored when the agent is activated again.

```
iai agents deactivate <agent_name> [flags]
```

## Examples

```
  iai agents deactivate my-agent
```

## Options

```
  -h, --help                  help for deactivate
  -o, --organization string   Organization name
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai agents delete

Delete an agent from a project

## Synopsis

Delete an agent from a specific project.

```
iai agents delete <agent_name> [flags]
```

## Examples

```
  iai agents delete my-agent
```

## Options

```
  -h, --help                  help for delete
  -o, --organization string   Organization name
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai agents describe

Describe an agent in detail

## Synopsis

Show detailed information about a specific agent including its configuration.

Use --revision to view a specific past revision instead of the current state. Past revision output includes server-recorded actor and source attribution when available.

```
iai agents describe <agent_name> [flags]
```

## Examples

```
  iai agents describe my-agent
  iai agents describe my-agent --revision 3
  iai agents describe my-agent --yaml
```

## Options

```
  -h, --help                  help for describe
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --revision int          Show a specific past revision instead of the current state
  -w, --watch                 Poll and refresh every 2s until interrupted
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai agents diff

Compare two revisions of an agent

## Synopsis

Show the differences between two revisions of an agent.

```
iai agents diff <agent_name> <revision_a> <revision_b> [flags]
```

## Examples

```
  iai agents diff my-agent 1 3
```

## Options

```
  -h, --help                  help for diff
  -o, --organization string   Organization name
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai agents get

Get details of a agent

## Synopsis

Get details of a specific agent, including its full content.

By default returns the version labeled "production". Use --version to retrieve a specific version number, or --label to resolve a different label.

Examples: iai agents get support-agent iai agents get support-agent --version 3 iai agents get support-agent --label staging

```
iai agents get <name> [flags]
```

## Options

```
  -h, --help                  help for get
      --label string          Retrieve the version with this label (default: server resolves 'production')
  -o, --organization string   Organization name that owns the project
  -p, --project string        Project name that owns the prompts
      --version int           Retrieve a specific version number
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Manage agents


# iai agents list

List agents in a project

## Synopsis

List agents in a specific project.

```
iai agents list [flags]
```

## Examples

```
  iai agents list
  iai agents list -p my-project
  iai agents list --json
```

## Options

```
  -h, --help                  help for list
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
  -w, --watch                 Poll and refresh the list every 2s until interrupted
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai agents log-fields

List available fields in structured logs

## Synopsis

Scan recent logs and list the extra top-level fields present in structured (JSON) log entries.

Use the reported field names with 'iai agents logs --fields' to include them in output.

```
iai agents log-fields <agent_name> [flags]
```

## Examples

```
  iai agents log-fields my-agent
  iai agents log-fields my-agent --since 1h
```

## Options

```
  -h, --help                  help for log-fields
  -o, --organization string   Organization name
  -p, --project string        Project name
      --since string          Relative duration to scan (e.g. 5m, 1h) (default "1h")
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai agents logs

Show logs for an agent

## Synopsis

Show logs for an agent in a project.

Returns up to 1000 log entries in chronological order by default; use --limit to request up to 5000.

Structured (JSON) logs are automatically formatted: the level and message fields are extracted and displayed as "LEVEL message". Use --fields or --all-fields to include additional top-level fields after the message. Use --raw for exact server JSON, or --decode to decode embedded JSON strings into nested JSON values.

```
iai agents logs <agent_name> [flags]
```

## Examples

```
  iai agents logs my-agent
  iai agents logs my-agent --follow
  iai agents logs my-agent --since 30m
  iai agents logs my-agent --timestamps
  iai agents logs my-agent --start-time 2026-01-01T00:00:00Z --end-time 2026-01-01T01:00:00Z
```

## Options

```
      --all-fields            Show all extra top-level fields from structured (JSON) logs after the message
      --decode                Decode embedded JSON strings into nested JSON values; outputs raw JSON
      --end-time string       Absolute RFC3339 end timestamp (e.g. 2026-02-24T12:00:00Z); requires --start-time; mutually exclusive with --since and --follow
      --fields strings        Additional fields to show after the message for structured (JSON) logs (e.g. --fields logger,pid); ignored for plain-text logs; use --raw for exact server JSON
  -f, --follow                Stream new log entries as they arrive; mutually exclusive with --end-time
  -h, --help                  help for logs
      --limit int             Maximum number of log entries to return (1-5000); defaults to 1000
  -o, --organization string   Organization name
  -p, --project string        Project name
      --raw                   Output exact server JSON lines without formatting
      --since string          Relative duration to look back (e.g. 30m, 1h, 3d, 1w); default 1h; max 72h; mutually exclusive with --start-time and --end-time
      --start-time string     Absolute RFC3339 start timestamp (e.g. 2026-02-24T10:00:00Z); mutually exclusive with --since; max 72h window
      --timestamps            Include platform log timestamps
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai agents port-forward

Forward a local port to an agent

## Synopsis

Open a local TCP listener and tunnel traffic through the deployment operator to an agent running in the cluster.

The remote port defaults to the agent's configured port. Use --port to override. Use --local-port to choose the local listening port (defaults to --port when set, or an available OS-assigned port otherwise).

```
iai agents port-forward <agent_name> [flags]
```

## Examples

```
  iai agents port-forward my-agent
  iai agents port-forward my-agent --port 8080
  iai agents port-forward my-agent --port 8080 --local-port 9090
```

## Options

```
  -h, --help                  help for port-forward
      --local-port int        Local port to listen on (defaults to the remote port)
  -o, --organization string   Organization name
      --port int              Remote port on the agent (defaults to the agent's configured port)
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai agents restart

Restart an agent in a project

## Synopsis

Restart an agent in a specific project.

```
iai agents restart <agent_name> [flags]
```

## Examples

```
  iai agents restart my-agent
```

## Options

```
  -h, --help                  help for restart
  -o, --organization string   Organization name
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai agents revision

Describe a specific revision of an agent

## Synopsis

Show the configuration of a specific past revision of an agent.

Examples: iai agents revision my-agent 1 iai agents revision my-agent 3

```
iai agents revision <agent_name> <revision> [flags]
```

## Options

```
  -h, --help                  help for revision
  -o, --organization string   Organization name
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Manage agents


# iai agents revisions

List revisions of an agent

## Synopsis

Show past revisions of an agent, sorted newest-first. Up to 50 revisions are retained per agent. Server-recorded actor and source metadata is shown when available.

```
iai agents revisions <agent_name> [flags]
```

## Examples

```
  iai agents revisions my-agent
```

## Options

```
  -h, --help                  help for revisions
  -o, --organization string   Organization name
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai agents schema

Display the JSON Schema for agent configuration

## Synopsis

Fetch and display the JSON Schema for the agent\_config block.

Defaults to the latest schema version. Use --schema-version to fetch a specific version (run 'iai agents compatibility-matrix' to see available versions).

Use --json or --yaml for structured schema output.

```
iai agents schema [flags]
```

## Examples

```
  iai agents schema
  iai agents schema --schema-version 2.1.0
  iai agents schema --json
```

## Options

```
  -h, --help                    help for schema
      --json                    Output schema response as JSON
      --schema-version string   Schema version to fetch (defaults to latest stable)
      --yaml                    Output schema response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai agents update

Update an agent in a project

## Synopsis

Update an agent in a specific project.

Only the flags you pass are applied; everything else is left at its current value.

\--file takes a YAML file matching the agent\_config schema and replaces the entire agent config in full when provided (no per-field merge). The config schema depends on the agent version — run 'iai agents compatibility-matrix' to find which schema version applies, then 'iai agents schema --schema-version ' to see the expected fields.

When upgrading to a new agent version with a different schema, update your routines and policies first using --schema-version on their create/update commands, then update the agent with the new config and version.

Lists (--env, --secret) replace the entire current list when provided — pass every value you want to keep.

For schedules, passing --schedule-uptime auto-clears any existing downtime, and --schedule-downtime auto-clears any existing uptime. Pass --schedule-timezone alongside either to change the timezone.

Use --clear-env, --clear-secret, --clear-schedule, or --clear-stack-id to remove those configurations entirely.

\--detach-mcp removes an mcp reference (bare or resolved) by name; combine with --mcp in the same command to swap one for another. Detach an mcp before deleting it — 'iai mcps delete' blocks by default while an agent still references it.

Before applying, the CLI prints deploy-awareness output to stderr: the live revision this update replaces; the names of any env vars or secret refs that --env/--secret would drop from the live agent (the flags replace the entire list); and — when the update replaces the agent config — a summary of content pin changes (a stale local manifest silently reverts colleagues' work). The update is refused when it would downgrade or remove a live content pin, or drop live env vars or secret refs via --env/--secret; pass --force to apply anyway. --clear-env and --clear-secret never trigger the gate: clearing is explicit intent. Changes in unrecognized pin-shaped config sections warn without blocking. The checks fail open when live state cannot be fetched; use --expect-revision to fail instead when the live revision differs from what you expect, and --show-diff for a full live-vs-incoming config diff.

```
iai agents update <agent_name> [flags]
```

## Examples

```
  iai agents update chat-agent --version 0.0.3
  iai agents update chat-agent --file agent-config.yaml
  iai agents update chat-agent --file agent-config.yaml --expect-revision 13
  iai agents update chat-agent --file agent-config.yaml --show-diff
  iai agents update chat-agent --endpoint=false
  iai agents update chat-agent --schedule-uptime "Mon-Fri 07:30-20:30" --schedule-timezone Europe/Berlin
  iai agents update chat-agent --clear-schedule
  iai agents update chat-agent --stack-id my-stack
  iai agents update chat-agent --clear-stack-id
  iai agents update chat-agent --mcp github --mcp stripe
  iai agents update chat-agent --detach-mcp stripe
```

## Options

```
      --clear-env                  Remove all environment variables from the agent
      --clear-schedule             Remove the schedule configuration from the agent
      --clear-secret               Remove all secret references from the agent
      --clear-stack-id             Remove the agent from its stack
      --detach-mcp stringArray     Detach an MCP by name; can be repeated. Without --file, removes from the agent's current mcps (applied before --mcp)
      --endpoint                   Expose the agent at <agent-name>-<project-hash>.interactive.ai
      --env stringArray            Environment variable (NAME=VALUE); can be repeated
      --expect-revision int        Fail without applying unless the live revision equals this value; 0 is valid and matches a never-updated agent (opt-in staleness guard)
      --file string                Path to YAML file matching the agent_config schema (run 'iai agents schema' to see it)
      --force                      Apply even when the update would downgrade/remove live content pins or drop live env vars or secret refs
  -h, --help                       help for update
      --id string                  Agent type from the marketplace (e.g. interactive-agent)
      --mcp stringArray            Attach an MCP by name (see 'iai mcps list'); can be repeated. Without --file, appends to the agent's current mcps
  -o, --organization string        Organization name
  -p, --project string             Project name
      --schedule-downtime string   When the agent should be scaled down (mutually exclusive with --schedule-uptime). Format: comma-separated entries of DAY_FROM-DAY_TO HH:MM-HH:MM. Example: 'Sat-Sun 00:00-24:00'
      --schedule-timezone string   IANA timezone for the schedule (e.g. Europe/Berlin, US/Eastern, UTC); required with --schedule-uptime or --schedule-downtime
      --schedule-uptime string     When the agent should be running (mutually exclusive with --schedule-downtime). Format: comma-separated entries of DAY_FROM-DAY_TO HH:MM-HH:MM. Example: 'Mon-Fri 07:30-20:30'
      --secret stringArray         Secret to inject as environment variables; can be repeated
      --show-diff                  Print a live-vs-incoming agent config diff to stderr before applying; requires --file, --mcp, or --detach-mcp
      --stack-id string            Stack ID to assign the agent to
      --version string             Agent image version to deploy (e.g. 0.0.1)
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai agents](/cli/iai_agents) - Deploy AI agents with policies, routines, and tools


# iai api-keys

Project API keys

## Synopsis

Manage project API keys. Requires iai login or JWT authentication. API key authentication is not supported.

Project API keys authenticate platform/API access for reading and writing project context, such as prompts, routines, policies, variables, glossaries, macros, traces, scores, datasets, and for creating infrastructure resources such as agents, services, and databases.

## Options

```
  -h, --help   help for api-keys
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai](/cli) - InteractiveAI's CLI
* [iai api-keys create](/cli/iai_api-keys_create) - Create a project API key
* [iai api-keys delete](/cli/iai_api-keys_delete) - Delete a project API key
* [iai api-keys list](/cli/iai_api-keys_list) - List project API keys
* [iai api-keys update](/cli/iai_api-keys_update) - Update a project API key


# iai api-keys create

Create a project API key

## Synopsis

Create a project API key.

Project API keys authenticate platform/API access for reading and writing project context, such as prompts, routines, policies, variables, glossaries, macros, traces, scores, datasets, and for creating infrastructure resources such as agents, services, and databases.

```
iai api-keys create [flags]
```

## Options

```
  -h, --help                  help for create
      --json                  Output response as JSON
      --note string           API key note
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai api-keys](/cli/iai_api-keys) - Project API keys


# iai api-keys delete

Delete a project API key

```
iai api-keys delete <id> [flags]
```

## Options

```
  -h, --help                  help for delete
      --json                  Output response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai api-keys](/cli/iai_api-keys) - Project API keys


# iai api-keys list

List project API keys

```
iai api-keys list [flags]
```

## Options

```
      --columns strings       Columns to display for table output only (comma-separated, default: id,public_key,secret,note,created_at). Cannot be used with --json or --yaml.
                              Available: id,public_key,secret,note,status,expires_at,last_used_at,created_at
  -h, --help                  help for list
      --json                  Output response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai api-keys](/cli/iai_api-keys) - Project API keys


# iai api-keys update

Update a project API key

```
iai api-keys update <id> [flags]
```

## Options

```
  -h, --help                  help for update
      --json                  Output response as JSON
      --note string           API key note
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai api-keys](/cli/iai_api-keys) - Project API keys


# iai collections

Knowledge bases (searchable tables of chunks) inside a pgvector database

## Synopsis

Manage collections within a database.

A collection is a table of chunks (rows) — each chunk is text plus its vector embedding(s) — that you search by meaning or keyword; it's what backs a knowledge base. It lives inside an existing pgvector database, so every command requires --database. Use 'iai databases create' first to provision the database.

Run 'iai collections schema' to see the body format for every --file command.

## Options

```
  -h, --help   help for collections
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai](/cli) - InteractiveAI's CLI
* [iai collections chunks](/cli/iai_collections_chunks) - Manage the chunks (rows) in a collection
* [iai collections create](/cli/iai_collections_create) - Create a collection from a config file
* [iai collections delete](/cli/iai_collections_delete) - Delete a collection and all its data
* [iai collections describe](/cli/iai_collections_describe) - Describe a collection's configuration
* [iai collections documents](/cli/iai_collections_documents) - Inspect documents (chunks grouped by documentId)
* [iai collections list](/cli/iai_collections_list) - List collections in a database
* [iai collections patch](/cli/iai_collections_patch) - Update a collection's mutable config from a file
* [iai collections schema](/cli/iai_collections_schema) - Show the file schemas for the --file-based collection commands
* [iai collections search](/cli/iai_collections_search) - Search a collection (single-lane vector search)
* [iai collections slots](/cli/iai_collections_slots) - Manage a collection's vector slots and their indexes
* [iai collections stats](/cli/iai_collections_stats) - Show a collection's chunk count, size, and index status


# iai collections chunks

Manage the chunks (rows) in a collection

## Synopsis

Upsert, inspect, and delete the chunks stored in a collection.

## Options

```
  -h, --help   help for chunks
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections](/cli/iai_collections) - Knowledge bases (searchable tables of chunks) inside a pgvector database
* [iai collections chunks bulk-delete](/cli/iai_collections_chunks_bulk-delete) - Delete many chunks by ids, metadata filter, or all
* [iai collections chunks count](/cli/iai_collections_chunks_count) - Count chunks, optionally scoped by a metadata filter or id prefix
* [iai collections chunks delete](/cli/iai_collections_chunks_delete) - Delete a single chunk by id
* [iai collections chunks get](/cli/iai_collections_chunks_get) - Get a single chunk
* [iai collections chunks list](/cli/iai_collections_chunks_list) - List chunks (keyset-paginated)
* [iai collections chunks patch](/cli/iai_collections_chunks_patch) - Update a chunk's metadata and/or text from a file
* [iai collections chunks upsert](/cli/iai_collections_chunks_upsert) - Upsert chunks from a file


# iai collections chunks bulk-delete

Delete many chunks by ids, metadata filter, or all

## Synopsis

Delete chunks by exactly one selector: --ids, --filter, or --all.

\--all deletes every chunk and requires confirmation.

```
iai collections chunks bulk-delete <collection> [flags]
```

## Examples

```
  iai collections chunks bulk-delete docs -d my-db --ids a,b,c
  iai collections chunks bulk-delete docs -d my-db --filter '{"lang":"en"}'
  iai collections chunks bulk-delete docs -d my-db --all
```

## Options

```
      --all                   Delete every chunk (requires confirm)
  -d, --database string       Database that holds the collection (required)
      --filter string         Metadata filter as a JSON object
  -h, --help                  help for bulk-delete
      --ids strings           Comma-separated chunk ids to delete
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
      --yes                   Skip the --all confirmation prompt
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections chunks](/cli/iai_collections_chunks) - Manage the chunks (rows) in a collection


# iai collections chunks count

Count chunks, optionally scoped by a metadata filter or id prefix

```
iai collections chunks count <collection> [flags]
```

## Examples

```
  iai collections chunks count docs -d my-db --filter '{"lang":"en"}'
```

## Options

```
  -d, --database string       Database that holds the collection (required)
      --filter string         Metadata filter as a JSON object
  -h, --help                  help for count
  -o, --organization string   Organization name
      --prefix string         Only count chunks with this id prefix
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections chunks](/cli/iai_collections_chunks) - Manage the chunks (rows) in a collection


# iai collections chunks delete

Delete a single chunk by id

```
iai collections chunks delete <collection> <id> [flags]
```

## Examples

```
  iai collections chunks delete docs chunk-1 -d my-db
```

## Options

```
  -d, --database string       Database that holds the collection (required)
  -h, --help                  help for delete
  -o, --organization string   Organization name
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections chunks](/cli/iai_collections_chunks) - Manage the chunks (rows) in a collection


# iai collections chunks get

Get a single chunk

```
iai collections chunks get <collection> <id> [flags]
```

## Examples

```
  iai collections chunks get docs chunk-1 -d my-db
  iai collections chunks get docs chunk-1 -d my-db --include-vector
```

## Options

```
  -d, --database string       Database that holds the collection (required)
  -h, --help                  help for get
      --include-vector        Include the stored vector(s)
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections chunks](/cli/iai_collections_chunks) - Manage the chunks (rows) in a collection


# iai collections chunks list

List chunks (keyset-paginated)

```
iai collections chunks list <collection> [flags]
```

## Examples

```
  iai collections chunks list docs -d my-db --limit 20
  iai collections chunks list docs -d my-db --cursor <token>
```

## Options

```
      --cursor string         Opaque cursor from a previous page
  -d, --database string       Database that holds the collection (required)
      --filter string         Metadata filter as a JSON object
  -h, --help                  help for list
      --json                  Output raw API response as JSON
      --limit int             Page size (1-1000, default 100)
  -o, --organization string   Organization name
      --prefix string         Only chunks whose id has this prefix
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections chunks](/cli/iai_collections_chunks) - Manage the chunks (rows) in a collection


# iai collections chunks patch

Update a chunk's metadata and/or text from a file

```
iai collections chunks patch <collection> <id> [flags]
```

## Examples

```
  iai collections chunks patch docs chunk-1 -d my-db --file patch.json
```

## Options

```
  -d, --database string       Database that holds the collection (required)
      --file string           Path to a YAML/JSON patch file
  -h, --help                  help for patch
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections chunks](/cli/iai_collections_chunks) - Manage the chunks (rows) in a collection


# iai collections chunks upsert

Upsert chunks from a file

## Synopsis

Upsert a batch of chunks from a YAML or JSON file (--file).

Chunks with text and no client vector are embedded server-side (set defer\_embedding=true with client-supplied vectors to skip embedding).

```
iai collections chunks upsert <collection> [flags]
```

## Examples

```
  iai collections chunks upsert docs -d my-db --file chunks.json
```

## Options

```
  -d, --database string       Database that holds the collection (required)
      --dry-run               Validate the batch without embedding or storing
      --file string           Path to a YAML/JSON chunks file
  -h, --help                  help for upsert
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections chunks](/cli/iai_collections_chunks) - Manage the chunks (rows) in a collection


# iai collections create

Create a collection from a config file

## Synopsis

Create a vector collection from a YAML or JSON config file (--file).

The config declares the vector slot(s) — either an embedding-backed slot ("embedding": {model, dimension}) or a raw vector slot ({type, dimension, distance}) — and optional full-text search.

Slot type, dimension, distance, and the embedding model are IMMUTABLE after creation; fixing a wrong value means deleting and recreating the collection.

Run 'iai collections schema' for the config file format.

```
iai collections create <collection> [flags]
```

## Examples

```
  iai collections create docs -d my-db --file collection.yaml
```

## Options

```
  -d, --database string       Database that holds the collection (required)
      --dry-run               Validate the config without creating the collection
      --file string           Path to a YAML/JSON collection config
  -h, --help                  help for create
  -o, --organization string   Organization name
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections](/cli/iai_collections) - Knowledge bases (searchable tables of chunks) inside a pgvector database


# iai collections delete

Delete a collection and all its data

```
iai collections delete <collection> [flags]
```

## Examples

```
  iai collections delete docs -d my-db
```

## Options

```
  -d, --database string       Database that holds the collection (required)
  -h, --help                  help for delete
  -o, --organization string   Organization name
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections](/cli/iai_collections) - Knowledge bases (searchable tables of chunks) inside a pgvector database


# iai collections describe

Describe a collection's configuration

```
iai collections describe <collection> [flags]
```

## Examples

```
  iai collections describe docs -d my-db
  iai collections describe docs -d my-db --json
```

## Options

```
  -d, --database string       Database that holds the collection (required)
  -h, --help                  help for describe
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections](/cli/iai_collections) - Knowledge bases (searchable tables of chunks) inside a pgvector database


# iai collections documents

Inspect documents (chunks grouped by documentId)

## Synopsis

A document groups chunks by documentId; these commands read or delete them.

## Options

```
  -h, --help   help for documents
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections](/cli/iai_collections) - Knowledge bases (searchable tables of chunks) inside a pgvector database
* [iai collections documents delete](/cli/iai_collections_documents_delete) - Delete a document (all chunks sharing the documentId)
* [iai collections documents get](/cli/iai_collections_documents_get) - Get a document's chunks
* [iai collections documents list](/cli/iai_collections_documents_list) - List documents in a collection


# iai collections documents delete

Delete a document (all chunks sharing the documentId)

```
iai collections documents delete <collection> <documentId> [flags]
```

## Examples

```
  iai collections documents delete docs support-faq -d my-db
```

## Options

```
  -d, --database string       Database that holds the collection (required)
  -h, --help                  help for delete
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections documents](/cli/iai_collections_documents) - Inspect documents (chunks grouped by documentId)


# iai collections documents get

Get a document's chunks

```
iai collections documents get <collection> <documentId> [flags]
```

## Examples

```
  iai collections documents get docs support-faq -d my-db
```

## Options

```
      --cursor string         Opaque cursor from a previous page
  -d, --database string       Database that holds the collection (required)
  -h, --help                  help for get
      --include-vector        Include the stored vector(s)
      --json                  Output raw API response as JSON
      --limit int             Page size (1-1000, default 100)
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections documents](/cli/iai_collections_documents) - Inspect documents (chunks grouped by documentId)


# iai collections documents list

List documents in a collection

```
iai collections documents list <collection> [flags]
```

## Examples

```
  iai collections documents list docs -d my-db
```

## Options

```
      --cursor string         Opaque cursor from a previous page
  -d, --database string       Database that holds the collection (required)
      --filter string         Metadata filter as a JSON object
  -h, --help                  help for list
      --json                  Output raw API response as JSON
      --limit int             Page size (1-1000, default 100)
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections documents](/cli/iai_collections_documents) - Inspect documents (chunks grouped by documentId)


# iai collections list

List collections in a database

```
iai collections list [flags]
```

## Examples

```
  iai collections list -d my-db
  iai collections list -d my-db --json
```

## Options

```
  -d, --database string       Database that holds the collection (required)
  -h, --help                  help for list
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections](/cli/iai_collections) - Knowledge bases (searchable tables of chunks) inside a pgvector database


# iai collections patch

Update a collection's mutable config from a file

## Synopsis

Update a collection's mutable configuration from a YAML or JSON file (--file): full-text settings and per-slot ef\_search\_default. Slot type/dimension/distance and the embedding model are immutable.

```
iai collections patch <collection> [flags]
```

## Examples

```
  iai collections patch docs -d my-db --file patch.yaml
```

## Options

```
  -d, --database string       Database that holds the collection (required)
      --file string           Path to a YAML/JSON patch config
  -h, --help                  help for patch
  -o, --organization string   Organization name
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections](/cli/iai_collections) - Knowledge bases (searchable tables of chunks) inside a pgvector database


# iai collections schema

Show the file schemas for the --file-based collection commands

## Synopsis

Print the expected shape of every --file body: collection create/patch, chunks upsert/patch, slots add/reindex, and search batch/hybrid. Use --json or --yaml for structured output.

```
iai collections schema [flags]
```

## Examples

```
  iai collections schema
  iai collections schema --json
```

## Options

```
  -h, --help   help for schema
      --json   Output the schemas as JSON
      --yaml   Output the schemas as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections](/cli/iai_collections) - Knowledge bases (searchable tables of chunks) inside a pgvector database


# iai collections search

Search a collection (single-lane vector search)

## Synopsis

Run a single-lane search: --query (text, embedded server-side) or --vector (comma-separated floats). --exact runs an exhaustive scan instead of the index.

Sub-commands cover the other modes: batch, by-id, hybrid. A collection named after a sub-command (batch, by-id, hybrid) can't be searched via this command (the sub-command wins); rename it or query it through the API.

```
iai collections search <collection> [flags]
```

## Examples

```
  iai collections search docs -d my-db --query "reset my password"
  iai collections search docs -d my-db --query "..." --exact --limit 5
```

## Options

```
  -d, --database string       Database that holds the collection (required)
      --exact                 Exhaustive scan instead of the index
      --filter string         Metadata filter as a JSON object
  -h, --help                  help for search
      --json                  Output raw API response as JSON
      --limit int             Max results
  -o, --organization string   Organization name
  -p, --project string        Project name
      --query string          Query text (embedded server-side)
      --using string          Vector slot to search (omit for the server default, "default")
      --vector string         Query vector as comma-separated floats
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections](/cli/iai_collections) - Knowledge bases (searchable tables of chunks) inside a pgvector database
* [iai collections search batch](/cli/iai_collections_search_batch) - Run several searches in one request (from a file)
* [iai collections search by-id](/cli/iai_collections_search_by-id) - Find neighbors of an existing chunk by its stored vector
* [iai collections search hybrid](/cli/iai_collections_search_hybrid) - Run a multi-lane hybrid search (RRF) from a file


# iai collections search batch

Run several searches in one request (from a file)

```
iai collections search batch <collection> [flags]
```

## Examples

```
  iai collections search batch docs -d my-db --file searches.json
```

## Options

```
  -d, --database string       Database that holds the collection (required)
      --file string           Path to a YAML/JSON batch-search file
  -h, --help                  help for batch
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections search](/cli/iai_collections_search) - Search a collection (single-lane vector search)


# iai collections search by-id

Find neighbors of an existing chunk by its stored vector

```
iai collections search by-id <collection> [flags]
```

## Examples

```
  iai collections search by-id docs -d my-db --id chunk-1 --exclude-self
```

## Options

```
  -d, --database string       Database that holds the collection (required)
      --exclude-self          Exclude the seed chunk
      --filter string         Metadata filter as a JSON object
  -h, --help                  help for by-id
      --id string             Seed chunk id (required)
      --json                  Output raw API response as JSON
      --limit int             Max results
  -o, --organization string   Organization name
  -p, --project string        Project name
      --using string          Vector slot to search
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections search](/cli/iai_collections_search) - Search a collection (single-lane vector search)


# iai collections search hybrid

Run a multi-lane hybrid search (RRF) from a file

## Synopsis

Run a hybrid search from a YAML/JSON file. The command dispatches to the hybrid path automatically (it sets "mode":"hybrid" for you).

The body holds a "queries" array and an optional "fusion" config. Each lane supplies exactly one of: query (dense, embedded server-side), vector (pre-computed dense), sparse\_vector, or full\_text (keyword search; requires full-text enabled on the collection). Note: "using" selects the vector slot — it does NOT select the full-text lane; set "full\_text" for that. Lanes are fused with RRF.

Schema: { "queries": \[ {"query": "text", "using": "default", "candidate\_limit": 50}, {"full\_text": "keyword query", "candidate\_limit": 30} ], "fusion": {"method": "rrf", "k": 60}, "limit": 10 }

```
iai collections search hybrid <collection> [flags]
```

## Examples

```
  iai collections search hybrid docs -d my-db --file hybrid.json
```

## Options

```
  -d, --database string       Database that holds the collection (required)
      --file string           Path to a YAML/JSON hybrid-search file
  -h, --help                  help for hybrid
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections search](/cli/iai_collections_search) - Search a collection (single-lane vector search)


# iai collections slots

Manage a collection's vector slots and their indexes

## Synopsis

Add, reindex, vacuum, inspect, and remove a collection's vector slots.

A slot is a named vector space (a column) on a collection: if a collection is a table and a chunk is a row, a slot is a vector column down every row. A collection can have several — e.g. a dense slot for embeddings and a sparse slot for keywords — and each chunk holds one vector per slot. The slot's index is what makes searching that column fast.

## Options

```
  -h, --help   help for slots
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections](/cli/iai_collections) - Knowledge bases (searchable tables of chunks) inside a pgvector database
* [iai collections slots add](/cli/iai_collections_slots_add) - Add a vector slot
* [iai collections slots delete](/cli/iai_collections_slots_delete) - Delete a vector slot
* [iai collections slots progress](/cli/iai_collections_slots_progress) - Show a slot's index build progress
* [iai collections slots reindex](/cli/iai_collections_slots_reindex) - Rebuild a slot's index (online)
* [iai collections slots vacuum](/cli/iai_collections_slots_vacuum) - Vacuum a slot (reclaim space, refresh stats)


# iai collections slots add

Add a vector slot

## Synopsis

Add a vector slot. Provide a raw vector slot via flags (--type, --dimension, --distance) or a full slot config via --file (e.g. for an embedding-backed slot or custom index tuning). --file takes precedence.

```
iai collections slots add <collection> <slot> [flags]
```

## Examples

```
  iai collections slots add docs title -d my-db --dimension 1536
  iai collections slots add docs title -d my-db --file slot.yaml
```

## Options

```
  -d, --database string       Database that holds the collection (required)
      --dimension int         Vector dimension (required unless --file is provided)
      --distance string       Distance metric (omit for the server default, cosine)
      --file string           Path to a YAML/JSON slot config
  -h, --help                  help for add
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --type string           Vector slot type (float32, float16, binary, or sparse; default: float32) (default "float32")
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections slots](/cli/iai_collections_slots) - Manage a collection's vector slots and their indexes


# iai collections slots delete

Delete a vector slot

```
iai collections slots delete <collection> <slot> [flags]
```

## Examples

```
  iai collections slots delete docs title -d my-db
```

## Options

```
  -d, --database string       Database that holds the collection (required)
  -h, --help                  help for delete
  -o, --organization string   Organization name
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections slots](/cli/iai_collections_slots) - Manage a collection's vector slots and their indexes


# iai collections slots progress

Show a slot's index build progress

```
iai collections slots progress <collection> <slot> [flags]
```

## Examples

```
  iai collections slots progress docs title -d my-db
```

## Options

```
  -d, --database string       Database that holds the collection (required)
  -h, --help                  help for progress
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections slots](/cli/iai_collections_slots) - Manage a collection's vector slots and their indexes


# iai collections slots reindex

Rebuild a slot's index (online)

## Synopsis

Rebuild a slot's index. With no --file it rebuilds with the current config; --file (YAML/JSON) can change index params or quantization.

```
iai collections slots reindex <collection> <slot> [flags]
```

## Examples

```
  iai collections slots reindex docs title -d my-db
  iai collections slots reindex docs title -d my-db --file reindex.yaml
```

## Options

```
  -d, --database string       Database that holds the collection (required)
      --file string           Path to a YAML/JSON reindex config
  -h, --help                  help for reindex
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections slots](/cli/iai_collections_slots) - Manage a collection's vector slots and their indexes


# iai collections slots vacuum

Vacuum a slot (reclaim space, refresh stats)

```
iai collections slots vacuum <collection> <slot> [flags]
```

## Examples

```
  iai collections slots vacuum docs title -d my-db
```

## Options

```
  -d, --database string       Database that holds the collection (required)
  -h, --help                  help for vacuum
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections slots](/cli/iai_collections_slots) - Manage a collection's vector slots and their indexes


# iai collections stats

Show a collection's chunk count, size, and index status

```
iai collections stats <collection> [flags]
```

## Examples

```
  iai collections stats docs -d my-db
  iai collections stats docs -d my-db --json
```

## Options

```
  -d, --database string       Database that holds the collection (required)
  -h, --help                  help for stats
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai collections](/cli/iai_collections) - Knowledge bases (searchable tables of chunks) inside a pgvector database


# iai comments

Annotate traces, observations, and sessions

## Synopsis

Manage comments on traces, observations, sessions, and prompts.

## Options

```
  -h, --help   help for comments
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai](/cli) - InteractiveAI's CLI
* [iai comments create](/cli/iai_comments_create) - Create a comment
* [iai comments get](/cli/iai_comments_get) - Get a comment
* [iai comments list](/cli/iai_comments_list) - List comments


# iai comments create

Create a comment

## Synopsis

Create a new comment on a trace, observation, session, or prompt.

This command requires API key authentication.

```
iai comments create [flags]
```

## Examples

```
  iai comments create --object-type TRACE --object-id trace-abc123 --content "Investigated this run"
  iai comments create --object-type OBSERVATION --object-id obs-456 --content "Looks correct" --author-user-id user-42
  iai comments create --object-type PROMPT --object-id prompt-789 --content "Needs review" --json
```

## Options

```
      --author-user-id string   Author user ID
      --content string          Comment content (required)
  -h, --help                    help for create
      --json                    Output raw API response as JSON
      --object-id string        Object ID (required)
      --object-type string      Object type: TRACE, OBSERVATION, SESSION, or PROMPT (required)
  -o, --organization string     Organization name that owns the project
  -p, --project string          Project name
      --yaml                    Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai comments](/cli/iai_comments) - Annotate traces, observations, and sessions


# iai comments get

Get a comment

## Synopsis

Get full details of a comment.

```
iai comments get <id> [flags]
```

## Examples

```
  iai comments get comment-abc123
  iai comments get comment-abc123 --json
  iai comments get comment-abc123 --yaml
```

## Options

```
  -h, --help                  help for get
      --json                  Output raw API response as JSON
  -o, --organization string   Organization name that owns the project
  -p, --project string        Project name
      --yaml                  Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai comments](/cli/iai_comments) - Annotate traces, observations, and sessions


# iai comments list

List comments

## Synopsis

List comments with optional filters.

```
iai comments list [flags]
```

## Examples

```
  iai comments list
  iai comments list --object-type TRACE --object-id trace-abc123
  iai comments list --author-user-id user-42 --limit 50 --page 2
  iai comments list --json
```

## Options

```
      --author-user-id string   Filter by author user ID
      --columns strings         Columns to display for table output only (comma-separated). Cannot be used with --json or --yaml
  -h, --help                    help for list
      --json                    Output raw API response as JSON
      --limit int               Items per page (max 100)
      --object-id string        Filter by object ID
      --object-type string      Filter by object type (TRACE/OBSERVATION/SESSION/PROMPT)
  -o, --organization string     Organization name that owns the project
      --page int                Page number (starts at 1) (default 1)
  -p, --project string          Project name
      --yaml                    Output raw API response as YAML
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai comments](/cli/iai_comments) - Annotate traces, observations, and sessions


# iai completion

Generate the autocompletion script for the specified shell

## Synopsis

Generate the autocompletion script for iai for the specified shell. See each sub-command's help for details on how to use the generated script.

## Options

```
  -h, --help   help for completion
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai](/cli) - InteractiveAI's CLI
* [iai completion bash](/cli/iai_completion_bash) - Generate the autocompletion script for bash
* [iai completion fish](/cli/iai_completion_fish) - Generate the autocompletion script for fish
* [iai completion powershell](/cli/iai_completion_powershell) - Generate the autocompletion script for powershell
* [iai completion zsh](/cli/iai_completion_zsh) - Generate the autocompletion script for zsh


# iai completion bash

Generate the autocompletion script for bash

## Synopsis

Generate the autocompletion script for the bash shell.

This script depends on the 'bash-completion' package. If it is not installed already, you can install it via your OS's package manager.

To load completions in your current shell session:

```
source <(iai completion bash)
```

To load completions for every new session, execute once:

### Linux:

```
iai completion bash > /etc/bash_completion.d/iai
```

### macOS:

```
iai completion bash > $(brew --prefix)/etc/bash_completion.d/iai
```

You will need to start a new shell for this setup to take effect.

```
iai completion bash
```

## Options

```
  -h, --help              help for bash
      --no-descriptions   disable completion descriptions
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai completion](/cli/iai_completion) - Generate the autocompletion script for the specified shell


# iai completion fish

Generate the autocompletion script for fish

## Synopsis

Generate the autocompletion script for the fish shell.

To load completions in your current shell session:

```
iai completion fish | source
```

To load completions for every new session, execute once:

```
iai completion fish > ~/.config/fish/completions/iai.fish
```

You will need to start a new shell for this setup to take effect.

```
iai completion fish [flags]
```

## Options

```
  -h, --help              help for fish
      --no-descriptions   disable completion descriptions
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai completion](/cli/iai_completion) - Generate the autocompletion script for the specified shell


# iai completion powershell

Generate the autocompletion script for powershell

## Synopsis

Generate the autocompletion script for powershell.

To load completions in your current shell session:

```
iai completion powershell | Out-String | Invoke-Expression
```

To load completions for every new session, add the output of the above command to your powershell profile.

```
iai completion powershell [flags]
```

## Options

```
  -h, --help              help for powershell
      --no-descriptions   disable completion descriptions
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai completion](/cli/iai_completion) - Generate the autocompletion script for the specified shell


# iai completion zsh

Generate the autocompletion script for zsh

## Synopsis

Generate the autocompletion script for the zsh shell.

If shell completion is not already enabled in your environment you will need to enable it. You can execute the following once:

```
echo "autoload -U compinit; compinit" >> ~/.zshrc
```

To load completions in your current shell session:

```
source <(iai completion zsh)
```

To load completions for every new session, execute once:

### Linux:

```
iai completion zsh > "${fpath[1]}/_iai"
```

### macOS:

```
iai completion zsh > $(brew --prefix)/share/zsh/site-functions/_iai
```

You will need to start a new shell for this setup to take effect.

```
iai completion zsh [flags]
```

## Options

```
  -h, --help              help for zsh
      --no-descriptions   disable completion descriptions
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai completion](/cli/iai_completion) - Generate the autocompletion script for the specified shell


# iai databases

PostgreSQL instances with extension support, including pgvector

## Synopsis

Manage PostgreSQL databases in InteractiveAI projects.

Databases are managed PostgreSQL instances that can also be used as vector stores. The "vector" extension (pgvector) is installed by default, enabling vector similarity search for AI/ML workloads such as RAG and embeddings.

Each database automatically creates a secret named \<database\_name>-app with connection credentials (host, port, username, password, URI).

## Options

```
  -h, --help   help for databases
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai](/cli) - InteractiveAI's CLI
* [iai databases activate](/cli/iai_databases_activate) - Activate a deactivated database in a project
* [iai databases backup](/cli/iai_databases_backup) - Trigger an on-demand backup
* [iai databases backups](/cli/iai_databases_backups) - List backups for a database
* [iai databases create](/cli/iai_databases_create) - Create a database in a project
* [iai databases deactivate](/cli/iai_databases_deactivate) - Deactivate a database in a project
* [iai databases delete](/cli/iai_databases_delete) - Delete a database from a project
* [iai databases describe](/cli/iai_databases_describe) - Describe a database in detail
* [iai databases list](/cli/iai_databases_list) - List databases in a project
* [iai databases log-fields](/cli/iai_databases_log-fields) - List available fields in structured logs
* [iai databases logs](/cli/iai_databases_logs) - Show logs for a database
* [iai databases port-forward](/cli/iai_databases_port-forward) - Forward a local port to a database
* [iai databases restore](/cli/iai_databases_restore) - Restore a new database from a backup
* [iai databases update](/cli/iai_databases_update) - Update a database in a project


# iai databases activate

Activate a deactivated database in a project

## Synopsis

Activate a deactivated database, restoring it from hibernation.

```
iai databases activate <database_name> [flags]
```

## Examples

```
  iai databases activate my-db
  iai databases activate my-db -p my-project
```

## Options

```
  -h, --help                  help for activate
  -o, --organization string   Organization name
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai databases](/cli/iai_databases) - PostgreSQL instances with extension support, including pgvector


# iai databases backup

Trigger an on-demand backup

## Synopsis

Trigger an on-demand backup for a database. The database must have backups enabled.

```
iai databases backup <database_name> [flags]
```

## Examples

```
  iai databases backup my-db
  iai databases backup my-db -p my-project
```

## Options

```
  -h, --help                  help for backup
  -o, --organization string   Organization name
  -p, --project string        Project name
```

## Options inherited from parent commands

```
      --api-key string               API key for authentication
      --cfg-file string              Path to YAML config file with organization, project, and optional service definitions
      --deployment-hostname string   Hostname for the deployment API (default "https://deployment.interactive.ai")
      --hostname string              Hostname for the API (default "https://app.interactive.ai")
```

## SEE ALSO

* [iai databases](/cli/iai_databases) - PostgreSQL instances with extension support, including pgvector




---

[Next Page](/llms-full.txt/1)

