> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runtype.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runtype.com/_mcp/server.

# Route a busy message automatically

> Let a classifier decide whether a message sent during a running agent turn steers that turn or queues as the next one.

Use `auto` when you don't know whether a message sent mid-run belongs to the
work in progress. While a durable native agent turn is running, a classifier
compares the running request with the incoming message and picks one route:

* **Steer**: The message joins the running execution at the next safe
  boundary, like `x-runtype-concurrency: steer`. Corrections, constraints, extra
  facts, and changes of approach usually steer.
* **Queue**: The message runs as its own turn after the current one finishes,
  like `x-runtype-concurrency: queue`. A clearly independent task usually queues.

When nothing is running, an `auto` message is an ordinary turn. Runtype makes
no classifier call and charges nothing for routing. `auto` is opt-in. The
default admission policy stays `reject`.

## Before you begin

`auto` routes only where [steering](/developer-guides/guides/live-message-steering)
works. You need the following:

* A saved native Runtype agent that runs durable turns.
* A `conversationId` on every send.
* New user messages only, not the full transcript.

## Send a message with auto routing

From a server, send `x-runtype-concurrency: auto` on `POST /v1/dispatch` or
`POST /v1/agents/{agentId}/execute`. The body has the same shape as a steer:
one or more new user messages for the same agent and conversation. Use a
different idempotency key for each logical message.

To send a message with the TypeScript SDK and read where it went, use this example:

**`TypeScript SDK`**

```typescript title="TypeScript SDK"
import { RuntypeClient } from '@runtypelabs/sdk'

const client = new RuntypeClient({ apiKey: process.env.RUNTYPE_API_KEY! })
const result = await client.dispatch.execute(
  {
    agent: { agentId: 'agent_YOUR_AGENT_ID' },
    conversationId: 'conversation-123',
    messages: [{ role: 'user', content: 'Use the metric units instead.' }],
    options: { streamResponse: false },
  },
  { concurrency: 'auto', idempotencyKey: 'user-message-43' }
)
console.log(result.executionId, result.busyMessageRoute)
```

Replace `agent_YOUR_AGENT_ID` with your saved native agent's ID.

The Python example uses a dedicated client, so its admission headers apply
only to this message:

**`Python SDK`**

```python title="Python SDK"
import os
from runtype import RuntypeClient

with RuntypeClient(
  api_key=os.environ["RUNTYPE_API_KEY"],
  headers={
    "x-runtype-concurrency": "auto",
    "Idempotency-Key": "user-message-43",
  },
) as client:
  result = client.dispatch(
    {
      "agent": {"agentId": "agent_YOUR_AGENT_ID"},
      "conversationId": "conversation-123",
      "messages": [
        {"role": "user", "content": "Use the metric units instead."}
      ],
    },
    stream=False,
  )
  print(result["executionId"], result.get("busyMessageRoute"))
```

To send the same message with cURL and print the response headers, use this
request:

**`cURL`**

```bash title="cURL"
curl -i https://api.runtype.com/v1/dispatch \
  -H "Authorization: Bearer $RUNTYPE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'x-runtype-concurrency: auto' \
  -H 'Idempotency-Key: user-message-43' \
  -d '{
    "agent": { "agentId": "agent_YOUR_AGENT_ID" },
    "conversationId": "conversation-123",
    "messages": [
      { "role": "user", "content": "Use the metric units instead." }
    ],
    "options": { "streamResponse": false }
  }'
```

Replace `agent_YOUR_AGENT_ID` with your saved native agent's ID, and set
`RUNTYPE_API_KEY` to your Runtype API key. Don't send `Prefer: respond-async`
with `auto`: an asynchronous request keeps its `202` handle and isn't routed.

## Send a message with auto routing from a browser

Browser clients send `submitMode: "auto"` on `POST /v1/client/chat`. The setup
and the request shape are the same as for `submitMode: "steer"`: enable durable
turns, send exactly one user message, and set that message's `id` to the
request's `turnId`. For the full browser setup, see
[Steer from a browser client](/developer-guides/guides/live-message-steering#steer-from-a-browser-client).

The following example sends one message with auto routing:

**`Browser admission`**

```typescript title="Browser admission"
const turnId = crypto.randomUUID()
const response = await fetch(`${apiUrl}/v1/client/chat`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Visitor-Token': visitorToken,
  },
  body: JSON.stringify({
    sessionId,
    turnId,
    assistantMessageId: crypto.randomUUID(),
    submitMode: 'auto',
    messages: [{ id: turnId, role: 'user', content: 'Use the metric units instead.' }],
  }),
})
const route = response.headers.get('X-Runtype-Busy-Message-Route')
```

While a turn runs, a steered message returns `202` with the steer delivery
receipt. A queued message returns `202` with the queued execution's handle.
When nothing is running, the message starts a turn and streams its answer.
Retry an uncertain send with the same turn ID and message ID.

## Read the routing decision

When a turn is running, or `auto` cannot route on this request, the answer
reports where the message went. An `auto` message that arrives while nothing is
running is an ordinary turn and carries no report. The following table lists the
response headers:

| Header                               | Value                                                    | Present                              |
| ------------------------------------ | -------------------------------------------------------- | ------------------------------------ |
| `X-Runtype-Busy-Message-Route`       | `steer` or `queue`                                       | Whenever a report is present         |
| `X-Runtype-Busy-Message-Source`      | Why this route applied (see the following list)          | Whenever a report is present         |
| `X-Runtype-Busy-Message-Probability` | Probability that the message belongs to the running turn | Only when the source is `classifier` |
| `X-Runtype-Busy-Message-Model`       | The classifier model that answered                       | Only when the source is `classifier` |

The source takes one of these values:

* **`classifier`**: The classifier answered, and its answer chose the route.
* **`timeout`**: The classifier didn't answer in time. The message queued.
* **`error`**: The classifier call failed. The message queued.
* **`unanswered`**: The classifier returned no usable answer. The message
  queued.
* **`abstained`**: The decision model declined to answer. The message
  queued.
* **`circuit_open`**: Runtype skipped the classifier after repeated failures.
  The message queued.
* **`disabled`**: Routing didn't run, because the agent's mode is `off` or the
  request can't be routed.

A JSON response body repeats the decision as a `busyMessageRoute` object with
`route`, `source`, and, for a classifier answer, `probability` and `model`.
Streamed responses carry the headers only. The following excerpt shows a
steered message's receipt, with its other fields omitted:

```json
{
  "executionId": "aex_01m1yr0hf1e65vpnb0zp8qj8jm",
  "busyMessageRoute": {
    "route": "steer",
    "source": "classifier",
    "probability": 0.91,
    "model": "typesafe-ai/jev"
  }
}
```

The object also carries `steerUnavailable: true` when the classifier chose
steer but the running turn could no longer accept input. In that case the
message queued instead, and `route` is `queue`. If the running turn finished
while the classifier was deciding, the message runs as the next turn. Neither
route loses the message.

## Configure routing on the agent

The agent's `config.busyMessageRouting` object controls the classifier. Every
field is optional:

* `mode`: How Runtype applies the decision.
  * `enforced` (default): The decision routes the message.
  * `off`: Every busy `auto` message queues without a classifier call. The
    answer reports source `disabled`.
* `classifierModel`: The decision model that classifies the message. Use an
  `id` from `GET /v1/decision-models`. Runtype rejects any other value when you
  save the agent. It defaults to Jev (`typesafe-ai/jev`). A model that needs
  your own provider key, such as OpenAI Decisions, fails without one, and
  those messages queue with source `error`.

The following agent configuration routes with Clef Flash:

```json
{
  "busyMessageRouting": {
    "mode": "enforced",
    "classifierModel": "@cf/cloudflare/clef-flash"
  }
}
```

Runtype bills each classifier call to your organization, in the same way as
other model usage. A message sent while nothing is running makes no call.

## Fallbacks and limits

Review these behaviors before you rely on `auto` routing:

#### Timeouts and repeated failures

The classifier has 3 seconds to answer, including one quick retry after a
provider server error. A timeout or an error queues the message, because
queueing never changes work that's already running.

After three timeouts or provider server errors in a row for one classifier
model on one credential, Runtype stops calling that model for 60 seconds.
Models on Runtype's platform credentials share one count across
organizations, because a provider outage affects all of them. Your own
provider key has its own count. Credential and spending errors never
count.
During that time, busy `auto` messages queue at once with source
`circuit_open`. After the pause, Runtype lets one call through. An answer
resumes normal routing, and another failure starts a new pause.

#### A second message while one waits

A conversation holds one waiting message. A second `auto` message sent
while the first is still being classified, or is already queued, returns
`409` with code `CONVERSATION_QUEUE_FULL`. Retry it after the waiting
message starts.

#### Requests that auto can't route

`auto` never returns a `4xx` error because a request can't be routed.
It keeps the lane's own behavior and reports source `disabled` instead.

On the server API, the following requests keep the admission they would
get without the `x-runtype-concurrency` header:

* A request that runs in process, or that targets a Claude Managed agent.
* A request that runs a flow.
* A request without a `conversationId`.
* A request with `history: "stored"`.
* A request with `x-runtype-coalesce: true`.
* A request with `Prefer: respond-async`. It still gets its `202` handle.

A server API request whose messages can't steer, such as a full
transcript, queues when the conversation is busy.

On `POST /v1/client/chat`, a request that can't be routed runs as
`submitMode: "normal"`. That includes a surface without durable turns and
a request that isn't one user message whose `id` equals `turnId`.

## Next steps

* [Steer an active agent execution](/developer-guides/guides/live-message-steering): delivery receipts, error codes, and browser setup.
* [Concurrency modes across agent frameworks](/developer-guides/guides/concurrency-modes-across-frameworks): compare `auto` with other admission policies.
* [Creating and configuring agents](/user-guide/agents/creating-and-configuring-agents): set `busyMessageRouting` on a saved agent.