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

# Timeouts and long-running work

Runtype bounds each continuous run of a Flow with wall-clock timeouts. A continuous run is the stretch of active execution from the start of a Flow to its first pause, or between two pauses. The timeouts limit that stretch, not the total lifetime of the work. A Flow that pauses and resumes gets a fresh budget each time it resumes, so a job can span hours or days. This page lists the limits for each entry point and shows how to structure work that takes longer than one continuous run.

## Timeout limits by entry point

Each entry point accepts timeout overrides in its `options` object. The following table lists the settings, their defaults, and the accepted ranges:

| Entry point           | Setting                                     | Default    | Range                                             |
| --------------------- | ------------------------------------------- | ---------- | ------------------------------------------------- |
| `POST /v1/dispatch`   | `options.stepTimeoutMs` (per step)          | 5 minutes  | 1 second to 10 minutes attached; 30 minutes async |
| `POST /v1/dispatch`   | `options.flowTimeoutMs` (per Flow)          | 15 minutes | 1 second to 15 minutes attached; 30 minutes async |
| `POST /v1/batch/flow` | `options.timeoutMs` (per Record)            | 15 minutes | 1 second to 30 minutes                            |
| `POST /v1/batch/flow` | `options.flowTimeoutMs` (per Flow)          | unset      | 1 second to 30 minutes                            |
| `POST /v1/batch/flow` | `options.stepTimeoutMs` (per step)          | unset      | 1 second to 30 minutes                            |
| Schedules             | `executionOptions.timeoutMs` (per Record)   | 15 minutes | 1 minute to 30 minutes                            |
| Schedules             | `executionOptions.flowTimeoutMs` (per Flow) | unset      | 1 second to 15 minutes                            |
| Schedules             | `executionOptions.stepTimeoutMs` (per step) | unset      | 1 second to 10 minutes                            |

For batches and Schedules, `timeoutMs` bounds each Record's whole execution. The per-Flow and per-step settings bound the Flow and each step inside that execution, and the first limit that the run reaches stops it. When a per-Flow or per-step setting is unset, the execution uses the platform defaults of 5 minutes per step and 15 minutes per Flow. The exception is a Schedule that sets `timeoutMs` and leaves `flowTimeoutMs` unset: its per-Flow limit equals `timeoutMs`.

`POST /v1/client/chat`, the endpoint that browser clients call with a client token session, supports only the following body fields: `sessionId`, `messages`, `assistantMessageId`, `turnId`, `submitMode`, `metadata`, `inputs`, `clientTools`, `clientToolsFingerprint`, `identityProof`, and `voice`. The body has no `options` object, so a client chat request can't set `stepTimeoutMs` or `flowTimeoutMs`. To set either timeout for a run, call `POST /v1/dispatch` from your backend with an API key.

For the interaction between the step, Flow, and whole-agent budgets, and the smaller 30-second default on `execute-agent` steps, see [Debugging flows](/user-guide/flows/debugging-flows). A detached subagent has its own budget, separate from the parent's budgets. For more information, see [Run long phases as detached subagents](#run-long-phases-as-detached-subagents).

## Timeouts bound one continuous run, not the whole job

A timeout measures continuous execution, not elapsed calendar time. A human approval gate, a `wait-until` step, and an asynchronous `crawl` all pause the Flow, and the pause doesn't consume the timeout budget. When the Flow resumes, it starts with a fresh, full budget from the original `flowTimeoutMs`. An approval that waits two days for an answer doesn't time out the Flow when someone answers it.

The following patterns keep each continuous run within its budget and use pauses, Records, and separate executions to carry work across hours.

## Detach an interactive execution from the client request

When `POST /v1/dispatch` or `POST /v1/agents/{id}/execute` might outlive the caller's HTTP or MCP timeout, send the `Prefer: respond-async` header. Runtype queues the execution and immediately returns `202 Accepted` with an execution handle:

```json
{
  "executionId": "exec_...",
  "status": "queued",
  "statusUrl": "/v1/executions/exec_.../status",
  "targetKind": "flow",
  "targetId": "flow_..."
}
```

Poll `GET {statusUrl}` with the same account credential while `status` is `queued` or `running`. A terminal response has the status `completed`, `failed`, or `cancelled`, and includes the result or error. Polling the handle is safe. Retrying the original execution request can duplicate side effects.

A `paused` status means that the execution waits for input, such as an approval or a tool result. The response's `result` field holds the paused result, including its `pausedReason`. Polling doesn't resume a paused execution. To continue it, send the approval or tool output to the matching endpoint:

* For a dispatch run, call `POST /v1/dispatch/approve` or `POST /v1/dispatch/resume`.
* For a saved Agent, call `POST /v1/agents/{id}/approve` or `POST /v1/agents/{id}/resume`.

For the request shapes, see [Pause and resume on an agent dispatch](/developer-guides/guides/runtime-tools#pause-and-resume-on-an-agent-dispatch).

To read the status endpoint, an API key needs `FLOWS:READ` or `FLOWS:EXECUTE` for a Flow execution, or `AGENTS:READ` or `AGENTS:EXECUTE` for an Agent execution. For an execution that started through `POST /v1/dispatch`, `DISPATCH:*` also works. For an Agent execution, you can stream progress instead of polling. Call `GET /v1/executions/{executionId}/events` to replay the execution's Server-Sent Events and receive new ones as they arrive. After a disconnect, pass the last SSE `id` that you received as the `after` query parameter to continue without gaps. The events endpoint needs `DISPATCH:*`, `AGENTS:READ`, or `AGENTS:*`, and returns `404` for a Flow run or an inline Agent definition.

The MCP `dispatch`, `run_flow`, and `execute_agent` tools expose asynchronous execution as `async: true`. Pass the returned `executionId` to `get_execution_status` while the execution is queued or running.

### Raise the timeouts for a detached Flow

A detached Flow can set both `options.flowTimeoutMs` and `options.stepTimeoutMs` as high as `1800000` (30 minutes), which matches the batch per-Record ceiling. The attached limits stay at 15 minutes per Flow and 10 minutes per step. MCP `run_flow` exposes the same controls as `flow_timeout_ms` and `step_timeout_ms`. Values above the attached limits require `async: true`.

The higher limits apply only to a Flow target. The API returns a `400` validation error whose `details` array carries the code `ASYNC_REQUIRED_FOR_EXTENDED_TIMEOUT` in either of the following cases:

* An attached request sets `options.flowTimeoutMs` above 15 minutes or `options.stepTimeoutMs` above 10 minutes.
* An Agent dispatch sets either value above its attached limit, with or without `Prefer: respond-async`.

To fix the error, send `Prefer: respond-async` with a Flow target, or lower the value.

### Poll from the TypeScript SDK

The following TypeScript SDK code starts a detached dispatch, polls until the execution leaves the `queued` and `running` states, and then handles the result:

```typescript
const handle = await client.dispatch.executeAsync(request)

let execution = await client.executions.getStatus(handle.executionId)
while (execution.status === 'queued' || execution.status === 'running') {
  await new Promise((resolve) => setTimeout(resolve, 1_000))
  execution = await client.executions.getStatus(handle.executionId)
}

if (execution.status === 'paused') {
  // Send the approval or tool output, then poll again.
} else if (execution.status === 'completed') {
  console.log(execution.result)
} else {
  console.error(execution.error)
}
```

For a saved Agent, use `client.agents.executeAsync(agentId, request)` and poll the returned handle the same way.

### Requests that asynchronous dispatch refuses

Asynchronous dispatch doesn't store live credentials or replay stored history. It returns `400` for the following requests:

* A request with inline `secrets` returns `ASYNC_INLINE_SECRETS_UNSUPPORTED`. Store the credentials as managed Secrets and reference them with `{{secret:NAME}}`.
* A request with an `identity_proof` returns `ASYNC_IDENTITY_PROOF_UNSUPPORTED`. Send the request without `Prefer: respond-async`. For more information, see [End-user identity and credentials](/developer-guides/guides/end-user-identity).
* A request with `history: "stored"` returns `STORED_HISTORY_UNSUPPORTED`. Send the conversation messages in the request, or send the request without `Prefer: respond-async`.

A request to a saved Claude Managed Agent that includes a `conversationId` can send inline `secrets` or an `identity_proof` with `Prefer: respond-async`.

## Wait for slow external work with durable pauses

If a step spends most of its time waiting on an external system, use a `wait-until` step instead of holding a `prompt` or `api-call` step open. Configure the `poll` object with an HTTP request in `http`, an `intervalMs`, a `maxAttempts` cap, and a `success` criterion. The Flow pauses between poll attempts, and each attempt is a short request, not continuous execution.

The following step polls a job endpoint every 5 minutes until the job reports `done`:

```json
{
  "id": "wait-for-job",
  "name": "Wait for export job",
  "type": "wait-until",
  "order": 2,
  "config": {
    "poll": {
      "http": {
        "url": "https://api.example.com/jobs/{{jobId}}",
        "method": "GET"
      },
      "intervalMs": 300000,
      "maxAttempts": 288,
      "responseType": "json",
      "success": {
        "type": "json_path",
        "jsonPath": "$.status",
        "operator": "=",
        "expectedValue": "done"
      }
    },
    "outputVariable": "jobResult"
  }
}
```

In this example, `jobId` is a Flow variable that an earlier step sets. The step checks the job for about 24 hours (288 attempts at 5-minute intervals) and writes the final response to `jobResult`.

Durable pauses have the following bounds:

* A `wait-until` poll runs at most 1,000 attempts. Runtype clamps a larger `maxAttempts` value, and the Flow validator reports a warning.
* The worst-case duration of a `wait-until` or asynchronous `crawl` pause must be at most 30 days minus one hour. For a poll, the worst case is `maxAttempts` times each attempt's HTTP timeout (2 minutes when unset), plus `intervalMs` for each wait between attempts. For a long wait, raise `intervalMs`, not `maxAttempts`.
* A human approval gate holds for up to 7 days. If no one answers within 7 days, the pause expires and Runtype discards the paused state. A later approval returns `404` (paused execution not found), so start a new execution instead.

Interactive dispatch, such as `POST /v1/dispatch` attached or with `Prefer: respond-async`, can hold a `wait-until` or `crawl` pause. Batch runs, evals, and Schedules refuse a Flow with a `wait-until` or `crawl` step. An Agent's inline Flow tools can't contain a `wait-until` or `crawl` step on any entry point, so put the waiting phase in a plain Flow and dispatch that Flow. When a saved Flow can't run on every entry point, its validation response includes the `RUNTIME_LANE_INELIGIBLE` recommendation. For more information, see [Flow validation warnings](/user-guide/flows/flow-validation-warnings).

## Run long phases as detached subagents

An attached subagent call waits for the child agent to finish, so the parent's step budget bounds it. When a phase needs more time than that, run it detached.

A detached subagent runs as its own execution with its own budget. The spawning call returns a run ID immediately, and the parent continues. The budget defaults to 30 minutes and accepts values up to 24 hours:

* For subagents that the model spawns with `spawn_subagent`, set `detachedMaxBudgetMs` in the agent's `subagentConfig`.
* For a saved subagent tool, set `maxBudgetMs` in that tool's execution config.

To allow detached execution for spawned subagents, include `"detached"` in the `executionModes` array of `subagentConfig`. Set `defaultExecutionMode` to choose the mode when the model doesn't choose one. The following `tools` configuration lets the model run a spawned subagent for up to 2 hours:

```json
{
  "tools": {
    "toolIds": ["builtin:exa"],
    "subagentConfig": {
      "toolPool": ["builtin:exa"],
      "executionModes": ["attached", "detached"],
      "defaultExecutionMode": "attached",
      "defaultNotify": "narrate",
      "detachedMaxBudgetMs": 7200000
    }
  }
}
```

The completion policy controls what happens when a detached subagent finishes. For spawned subagents, set `defaultNotify` in `subagentConfig`. For a saved subagent tool, set `notify` in that tool's execution config. The policy accepts the following values:

* `none` stores the result.
* `narrate` posts status into the parent conversation. It's the default, and it requires a saved parent Agent and a `conversationId`.

A detached call that requests `react` is rejected.

To stop a detached subagent, call `POST /v1/agents/{id}/executions/{executionId}/cancel`. When the run belongs to a parent conversation, pass `conversationId` as a query parameter. Cancellation is cooperative, and repeating the call is safe. The endpoint returns `202 Accepted`.

For more information about subagent configuration, see [Runtime tools](/user-guide/tools/runtime-tools).

## Break large jobs into Records and batches

For a job with hours of model work, model each unit of work as a Record and process the units as batch runs:

1. Create a Record for each unit of work, with the unit's input in the Record fields.
2. Run each phase as a batch over those Records. Each Record's execution gets its own timeout of up to 30 minutes, and the batch runs Records concurrently.
3. Persist each phase's output back onto the Record with an `upsert-record` step, so the next phase reads its input from the Record rather than from a live execution.
4. Submit the next phase's batch when the preceding batch completes. Poll `GET /v1/batch/status/{id}` from your own system until the status is terminal: `completed`, `failed`, or `cancelled`. Submit the next batch on `completed`. On `failed` or `cancelled`, stop the chain and retry or recover the failed Records before you continue.

Schedules can also run phases on a fixed cadence. A Schedule fires by cron rather than on batch completion, so it can't wait for the preceding phase. If you chain phases with Schedules, make each phase idempotent. Start the phase's Flow with a check that the Record carries the preceding phase's output, and skip the Record otherwise. A run that fires before the preceding phase finishes then does no harm, and the next firing picks the Record up.

The total wall-clock time of the job has no limit, because each Record execution is its own continuous run. A 12-hour job becomes a sequence of phases, and each phase finishes in minutes per Record.

Size each phase so that one Record's work for that phase fits inside 30 minutes. If a single unit's phase exceeds that, split the phase into smaller phases or split the unit into smaller Records.

## Run multi-session agent tasks from the CLI

For a long agent task that you drive from your own machine, the `runtype` CLI's Marathon mode runs a saved Agent across many sessions with checkpoints, resumable state, and cost caps. For more information, see [Marathon: long-running agent tasks (CLI)](/user-guide/agents/marathon-long-running-tasks).

## Next steps

* [Running flows in batch](/user-guide/flows/running-flows-in-batch): process Records concurrently with per-Record timeouts
* [Using record steps (get/list/upsert)](/user-guide/flows/using-record-steps-upsert-retrieve): persist phase output between runs
* [Handling batch failures](/user-guide/flows/handling-batch-failures): retry and recover failed Records in a batch
* [Automating batch processing](/user-guide/schedules/automating-batch-processing): run batch phases on a Schedule
* [Using Wait Until steps](/user-guide/flows/using-wait-until-steps): pause a Flow for a delay or until a polled condition is met