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

# Authoring routines

> **Context** — This guide assumes the [Routines](/agents/concepts/routines.md) 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 6.1.1**. Manifest and content shapes are schema-versioned and differ across runtime versions — see [Versioning & compatibility](/agents/operations/versioning.md).

## 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.md), 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.md#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").

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


---

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

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

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

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

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

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