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

# Runtime tools

Define runtime tools inline in a dispatch request instead of saving them to your account. Use them for SDK-driven workflows, temporary configurations, testing, and one-off tasks. Runtime tools support five types: external, custom, Flow, subagent, and local.

## When to use runtime tools

Use runtime tools in the following cases:

* Define tools dynamically in an SDK-driven workflow.
* Test a tool configuration before you save it.
* Keep a temporary tool out of your account's saved tool definitions.
* Define a self-referential tool that calls the Runtype API.

## Tool types

Every runtime tool needs a `name`, a `description`, a `toolType`, and a `parametersSchema`. The `parametersSchema` must be a JSON Schema object with `"type": "object"` at the top level. Every type except `local` also needs a `config` object.

Choose one of the following runtime tool types.

### External

Call an HTTP API. Set `config.url` and `config.method`, and optionally set `config.headers` and `config.body`. The `method` is `GET`, `POST`, `PUT`, or `DELETE`. The `body` is a JSON string, not an object. Use template variables such as `{{city}}` for tool parameters and `{{secret:WEATHER_API_KEY}}` for managed secrets in the URL, headers, and body.

The following example defines an external tool that sends a JSON body:

**`External tool`**

```json title="External tool"
{
  "name": "create_ticket",
  "description": "Create a support ticket",
  "toolType": "external",
  "parametersSchema": {
    "type": "object",
    "properties": {
      "subject": { "type": "string" },
      "priority": { "type": "string", "enum": ["low", "high"] }
    },
    "required": ["subject", "priority"]
  },
  "config": {
    "url": "https://api.example.com/v1/tickets",
    "method": "POST",
    "headers": {
      "Authorization": "Bearer {{secret:TICKETS_API_KEY}}",
      "Content-Type": "application/json"
    },
    "body": "{\"subject\": \"{{subject}}\", \"priority\": \"{{priority}}\"}"
  }
}
```

The `subject` and `priority` parameters supply the `{{subject}}` and `{{priority}}` values. Replace `TICKETS_API_KEY` with the name of the managed secret that stores the API key.

### Custom

Run sandboxed JavaScript, TypeScript, or Python code. Set `config.code`, and optionally set `config.language`, `config.sandboxProvider`, and `config.timeout`. In the default environment, your code reads the tool inputs from the `parameters` object.

The following example calculates a discounted price with JavaScript:

**`Custom tool`**

```json title="Custom tool"
{
  "name": "calculate_discount",
  "description": "Calculate a discounted price",
  "toolType": "custom",
  "parametersSchema": {
    "type": "object",
    "properties": {
      "price": { "type": "number" },
      "discount": { "type": "number" }
    },
    "required": ["price", "discount"]
  },
  "config": {
    "code": "return { discounted_price: parameters.price * (1 - parameters.discount / 100) }",
    "timeout": 5000,
    "language": "javascript"
  }
}
```

The `timeout` value is in milliseconds, up to `30000`. Set `language` to `javascript`, `typescript`, or `python`. The default is `javascript`.

The `sandboxProvider` field selects the execution environment. The default, `cloudflare-worker`, runs JavaScript only. For TypeScript or Python, set `sandboxProvider` to `runtype-sandbox` or `daytona`. Otherwise, the tool call fails with an unsupported-language error. For how each environment passes parameters, see [Creating custom tools](/user-guide/tools/creating-custom-tools).

Custom tool code has no network access unless the tool sets `networkAccess`. To send a managed secret, declare it in `secrets`: the code receives a placeholder token as `secrets.NAME`, and the egress proxy replaces it with the real value only on requests to the declared host. For the fields, see [Call an API from custom code](/user-guide/tools/creating-custom-tools#call-an-api-from-custom-code).

### Flow

Run a Flow as a tool. Set `config.flowId` to the saved Flow ID and use `parameterMapping` to map tool inputs to Flow inputs.

By default, the tool returns the value that the Flow's final executed step wrote to its output variable. For example, it returns what a trailing `transform-data` step returned. The following optional fields narrow that value:

* `outputVariable` returns a named Flow variable instead of the final step output. The variable must be assigned during the run, and names that begin with `_` are reserved.
* `outputMapping` is a dot path into the value that the tool returns. Suppose the final step writes `{ "score": 0.9 }` to its output variable `analysis`. Both `score` and `analysis.score` return `0.9`.

The following example maps a tool input to a saved Flow:

**`Flow tool`**

```json title="Flow tool"
{
  "name": "analyze_sentiment",
  "description": "Run sentiment analysis on text",
  "toolType": "flow",
  "parametersSchema": {
    "type": "object",
    "properties": {
      "text": { "type": "string" }
    },
    "required": ["text"]
  },
  "config": {
    "flowId": "YOUR_FLOW_ID",
    "parameterMapping": { "input_text": "text" },
    "outputMapping": "sentiment_result"
  }
}
```

Replace `YOUR_FLOW_ID` with the ID of the Flow that you want to run.

On an agent dispatch, a Flow tool can also define the Flow inline. Replace `flowId` with a `flow` object that has a non-empty `steps` array. Runtype rejects an inline `flow` on a Flow dispatch prompt step and on an agent execute request. Reference a saved Flow with `flowId` there.

### Subagent

Delegate a focused task to a child agent. The child runs in its own context window and returns only its final result to the parent. By default, the child doesn't receive the parent's conversation. Set `inheritMessages` to `true` to pass the current messages to the child.

Subagent tools use exactly one of `agentId` or `agent`. Set `allowedTools` to limit the child to a subset of its own configured tools.

The following example runs a saved agent as a subagent tool:

**`Subagent tool`**

```json title="Subagent tool"
{
  "name": "research_topic",
  "description": "Research a topic and return a summary",
  "toolType": "subagent",
  "parametersSchema": {
    "type": "object",
    "properties": {
      "task": { "type": "string" }
    },
    "required": ["task"]
  },
  "config": {
    "agentId": "YOUR_AGENT_ID",
    "allowedTools": ["builtin:exa", "builtin:firecrawl"],
    "maxTurns": 5,
    "outputFormat": "text"
  }
}
```

Replace `YOUR_AGENT_ID` with the ID of the saved agent. To define the child inline, replace `agentId` with an `agent` object and keep only one of these fields.

The following fields configure a subagent tool:

| Field             | Default                    | Description                                                                                                                                                                                                |
| ----------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agentId`         | None                       | Saved agent ID. Set this or `agent`, not both.                                                                                                                                                             |
| `agent`           | None                       | Inline agent definition. Set this or `agentId`, not both.                                                                                                                                                  |
| `allowedTools`    | Required for inline agents | Tool IDs from the child's own tools that the child can use. `[]` grants no tools. When you set it, the child also loses its MCP servers. Leave it unset on a saved agent to keep all of the child's tools. |
| `maxTurns`        | The child's own limit      | Maximum number of agent-loop iterations for the child.                                                                                                                                                     |
| `maxCost`         | Parent budget              | Spend cap for the child. The cost counts toward the parent's budget.                                                                                                                                       |
| `timeoutMs`       | `300000`                   | Time limit for an attached child in milliseconds.                                                                                                                                                          |
| `outputFormat`    | `text`                     | Return `text`, `json`, or `last_message`.                                                                                                                                                                  |
| `inheritMessages` | `false`                    | Pass the parent's current messages to the child when set to `true`.                                                                                                                                        |
| `taskTemplate`    | None                       | Template the child's first message from the tool-call parameters.                                                                                                                                          |
| `execution`       | `{ "mode": "attached" }`   | Run the child attached or detached. For the fields, see [Run a subagent in the background](#run-a-subagent-in-the-background).                                                                             |

#### Run a subagent in the background

By default, a subagent tool runs attached: the parent waits for the child's result. Set `execution.mode` to `detached` to start the child in the background. The tool call returns a run handle right away, and the parent continues its turn.

The following fields configure a detached subagent:

| Field                | Default    | Description                                                                                               |
| -------------------- | ---------- | --------------------------------------------------------------------------------------------------------- |
| `mode`               | `attached` | `attached` or `detached`.                                                                                 |
| `notify`             | `narrate`  | `narrate` records the child's completion in the parent's conversation. `none` sends no completion notice. |
| `maxBudgetMs`        | `1800000`  | Maximum run time for the detached child, from `1000` to `86400000` milliseconds.                          |
| `noProgressBudgetMs` | None       | Stop the detached child when it makes no progress for this long, from `1000` to `86400000` milliseconds.  |

The `narrate` notice requires a saved parent agent and a `conversationId` on the request. Without them, set `notify` to `none`.

### Local

Run a local tool in the client that drives the dispatch. A local tool doesn't need a `config` object. When the model calls the tool, Runtype pauses the run and streams the tool name and parameters to your client. Your client runs the handler and resumes the execution with the result.

Use local tools for actions that must run in the caller's environment, such as prompting the end user or reading local state. An SDK process or CLI session can run local tools. The Runtype dashboard doesn't run your local tools.

The following example defines a local tool:

**`Local tool`**

```json title="Local tool"
{
  "name": "ask_user",
  "description": "Ask the end user a clarifying question",
  "toolType": "local",
  "parametersSchema": {
    "type": "object",
    "properties": {
      "question": { "type": "string" }
    },
    "required": ["question"]
  }
}
```

The TypeScript SDK runs the pause-and-resume loop for you. The following example runs a saved agent with one local tool:

**`Local tool with the TypeScript SDK`**

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

const client = new RuntypeClient({ apiKey: process.env.RUNTYPE_API_KEY! })

const result = await client.agents.executeWithLocalTools(
  'YOUR_AGENT_ID',
  { messages: [{ role: 'user', content: 'Plan my trip to Lisbon' }] },
  {
    ask_user: {
      description: 'Ask the end user a clarifying question',
      parametersSchema: {
        type: 'object',
        properties: { question: { type: 'string' } },
        required: ['question'],
      },
      execute: async ({ question }) => promptInTerminal(String(question)),
    },
  }
)
```

Replace `YOUR_AGENT_ID` with the ID of the saved agent, and replace `promptInTerminal` with your own function that returns the answer. To handle the pause and resume yourself, see [Pause and resume on an agent dispatch](/developer-guides/guides/runtime-tools#pause-and-resume-on-an-agent-dispatch).

Local tools run only when a live caller drives the execution. On an unattended run, such as a messaging channel, a webhook, or a batch run, Runtype removes local tools from the turn and logs a warning that names them. Use an external, custom, or Flow tool for work that must run unattended.

## Let an agent spawn subagents

Let an agent choose what to delegate at runtime by adding `subagentConfig` to its `tools` configuration. Runtype adds the built-in `spawn_subagent` tool. For each call, the model chooses the task, the tools from `toolPool`, and an optional system prompt.

The following `tools` configuration enables dynamic subagent spawning:

**`Dynamic subagent configuration`**

```json title="Dynamic subagent configuration"
{
  "tools": {
    "toolIds": ["builtin:exa", "mcp:linear:create_issue"],
    "subagentConfig": {
      "toolPool": ["builtin:exa", "mcp:linear:*"],
      "maxTurnsLimit": 10,
      "maxSpawnsPerRun": 5,
      "allowNesting": false
    }
  }
}
```

The `toolPool` must be a subset of the parent's resolved tools. Set `allowNesting` to `true` to let a subagent spawn its own subagents.

The following fields control dynamic subagent spawning:

| Field                        | Default        | Description                                                                                            |
| ---------------------------- | -------------- | ------------------------------------------------------------------------------------------------------ |
| `toolPool`                   | Required       | Tools that the parent can grant to spawned subagents.                                                  |
| `maxTurnsLimit`              | `10`           | Maximum turn cap that the model can request for a spawned subagent.                                    |
| `defaultMaxTurns`            | None           | Accepted, but has no effect. A spawned subagent uses a turn cap only when the model requests one.      |
| `maxSpawnsPerRun`            | `5`            | Maximum number of spawned subagents per parent run.                                                    |
| `defaultModel`               | Parent model   | Model that spawned subagents use by default.                                                           |
| `allowNesting`               | `false`        | Whether a subagent can spawn its own subagents.                                                        |
| `defaultTimeoutMs`           | `300000`       | Default time limit for each spawned subagent in milliseconds.                                          |
| `executionModes`             | `["attached"]` | Execution modes that the model can request. Add `detached` to allow background subagents.              |
| `defaultExecutionMode`       | `attached`     | Mode to use when the model doesn't request one.                                                        |
| `defaultNotify`              | `narrate`      | Completion notice for detached subagents: `narrate` or `none`.                                         |
| `detachedMaxBudgetMs`        | None           | Maximum run time for a detached subagent, from `1000` to `86400000` milliseconds.                      |
| `detachedNoProgressBudgetMs` | None           | Stop a detached subagent that makes no progress for this long, from `1000` to `86400000` milliseconds. |

## Pass runtime tools in a dispatch request

Add runtime tools to the `tools.runtimeTools` array on a prompt step.

The following request defines one runtime external tool on the `Assistant` prompt step:

**`Dispatch request`**

```json title="Dispatch request"
{
  "flow": {
    "name": "My Assistant",
    "steps": [
      {
        "id": "step-1",
        "type": "prompt",
        "name": "Assistant",
        "order": 1,
        "enabled": true,
        "config": {
          "model": "claude-haiku-4-5",
          "systemPrompt": "You are a helpful assistant.",
          "tools": {
            "runtimeTools": [
              {
                "name": "search_web",
                "description": "Search the web",
                "toolType": "external",
                "parametersSchema": {
                  "type": "object",
                  "properties": {
                    "query": { "type": "string" }
                  },
                  "required": ["query"]
                },
                "config": {
                  "url": "https://api.example.com/search?q={{query}}",
                  "method": "GET"
                }
              }
            ]
          }
        }
      }
    ]
  },
  "options": {
    "streamResponse": true,
    "flowMode": "virtual"
  }
}
```

The `{{query}}` variable resolves from the tool input. The `flowMode` value `virtual` runs the inline Flow without saving it.

Combine runtime tools with saved tools and built-in tools in the same prompt step.

The following configuration combines a saved tool, a built-in tool, and a runtime tool:

**`Combined tools`**

```json title="Combined tools"
{
  "tools": {
    "toolIds": ["YOUR_TOOL_ID", "builtin:exa"],
    "runtimeTools": [
      {
        "name": "my_runtime_tool",
        "description": "Run a runtime tool",
        "toolType": "external",
        "parametersSchema": {
          "type": "object",
          "properties": {}
        },
        "config": {
          "url": "https://api.example.com/status",
          "method": "GET"
        }
      }
    ]
  }
}
```

Replace `YOUR_TOOL_ID` with the ID of the saved tool.

### Add runtime tools to an agent dispatch

To give a saved agent extra tools for one request, add them to `agent.tools.runtimeTools`. The agent's saved tools stay available.

The following request adds one runtime tool to a saved agent:

**`Agent dispatch with a runtime tool`**

```json title="Agent dispatch with a runtime tool"
{
  "agent": {
    "agentId": "YOUR_AGENT_ID",
    "tools": {
      "runtimeTools": [
        {
          "name": "get_order_status",
          "description": "Get the status of an order",
          "toolType": "external",
          "parametersSchema": {
            "type": "object",
            "properties": {
              "orderId": { "type": "string" }
            },
            "required": ["orderId"]
          },
          "config": {
            "url": "https://api.example.com/orders/{{orderId}}",
            "method": "GET"
          }
        }
      ]
    }
  },
  "messages": [{ "role": "user", "content": "Where is order 1042?" }]
}
```

Replace `YOUR_AGENT_ID` with the ID of the saved agent. An agent execute request (`POST /v1/agents/{id}/execute`) also accepts runtime tools in `tools.runtimeTools`.

When an agent with **Agent loop** turned on has 20 or more tools, Runtype holds its runtime tools back behind a `tool_search` tool, and the model loads a runtime tool by searching for it. To send every runtime tool on every turn, add `"toolSearch": { "enabled": false }` to `agent.tools`. For the full behavior, see [Tool search](/user-guide/agents/agent-tools#tool-search).

## Secrets

Reference a managed secret by name in an external tool's URL, headers, or body. To add a managed secret, open **Settings** and select **Secrets**.

The following example references a managed secret in an external tool header:

**`Managed secret reference`**

```json title="Managed secret reference"
{
  "headers": {
    "Authorization": "Bearer {{secret:WEATHER_API_KEY}}"
  }
}
```

Replace `WEATHER_API_KEY` with the name of the managed secret. Other tool types can't reference managed secrets.

The API key that sends the dispatch request must have the `SECRETS:READ` or `SECRETS:*` permission to resolve secret references. Otherwise, the request fails with a 403 error.

Dispatch requests don't accept a top-level `secrets` map. A non-empty map returns a 400 error. An agent dispatch returns `RUNTIME_AGENT_TRANSIENT_SECRETS_UNSUPPORTED`, and a Flow dispatch returns `RUNTIME_FLOW_TRANSIENT_SECRETS_UNSUPPORTED`. An empty map is ignored. Runtype encrypts managed secret values at rest and doesn't log them or return them in responses.

## Hide parameters from the model

Add `hiddenParameterNames` to a runtime tool to fill a parameter from an execution variable instead of from the model. Runtype removes each listed parameter from the schema that the model sees. When the tool runs, Runtype sets the parameter to the execution variable with the same name, and that value replaces any value the model sent.

The following example hides the `_tenant` parameter and uses it in a request header:

**`Hidden parameter`**

```json title="Hidden parameter"
{
  "name": "list_orders",
  "description": "List recent orders for the current account",
  "toolType": "external",
  "parametersSchema": {
    "type": "object",
    "properties": {
      "status": { "type": "string" }
    },
    "required": ["status"]
  },
  "hiddenParameterNames": ["_tenant"],
  "config": {
    "url": "https://api.example.com/v1/orders?status={{status}}",
    "method": "GET",
    "headers": {
      "Authorization": "Bearer {{secret:ORDERS_API_KEY}}",
      "X-Tenant-Id": "{{_tenant.id}}"
    }
  }
}
```

Replace `ORDERS_API_KEY` with the name of the managed secret that authenticates Runtype to your API. Runtype merges hidden values into the tool arguments before it fills the URL, headers, and body, so `{{_tenant.id}}` resolves from the hidden `_tenant` value.

Runtype resolves a hidden parameter based on its name:

| Name                                    | Where the value comes from                                                                                                                                                                            | If no value is found                                                                                |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| No leading `_`, for example `accountId` | The execution variable with that name. If it is not set, the field with that name in `_record.metadata`, then the field with that name inside any other object-valued variable without a leading `_`. | The parameter stays in the schema, and the model supplies it.                                       |
| Leading `_`, for example `_tenant`      | Only the top-level execution variable with that name.                                                                                                                                                 | Runtype doesn't call the tool. The model receives an error as the tool result.                      |
| Leading `_internal`                     | Never resolved.                                                                                                                                                                                       | Runtype rejects the name in dispatch requests, saved agent definitions, and agent execute requests. |

Use `_tenant` and `_endUser` for identity values. Runtype sets these variables from the execution's end-user identity, not from the model, so the model can't change them. For how Runtype establishes that identity, see [End-user identity and credentials](/developer-guides/guides/end-user-identity).

> **Warning**
>
> Only `_tenant` and `_endUser` come from Runtype's identity handling. Do not rely on another name
> with a leading `_` to keep caller-supplied values out of a tool call.

Also authenticate Runtype to your backend with a `{{secret:NAME}}` header, so your API does not trust a request on the hidden value alone.

Runtype replaces resolved hidden values with `[REDACTED]` in the `tool_start` and `tool_input_complete` stream events. The `hiddenParameterNames` field on those events lists the redacted parameters. The tool receives the real values.

You can set `hiddenParameterNames` on runtime tools in dispatch requests, in saved agent definitions, and in the `tools.runtimeTools` override of an agent execute request.

### Use the verified identity in a saved tool

A tool saved in your account and attached by tool ID has no `hiddenParameterNames` field. Instead, Runtype treats `_endUser` and `_tenant` as hidden in a tool that references them in either of these ways:

* An external tool's `url`, `headers`, or `body` contains a template such as `{{_endUser.id}}` or `{{_tenant.id}}`.
* The tool's `parametersSchema` has a property named `_endUser` or `_tenant`.

For such a tool, Runtype applies the rules for a hidden name with a leading `_`:

* Runtype removes the property from the schema that the model sees.
* Runtype fills the value only from the execution's `_endUser` or `_tenant` variable. The model's tool arguments and the request `metadata` cannot supply it.
* If the execution has no value, Runtype doesn't call the tool. The model receives an error as the tool result. The exception is a root that the tool references only before a template fallback, such as `{{_endUser.id || 'guest'}}`, `{{_endUser.id ?? 'guest'}}`, or `{{_endUser.id|guest}}`. Runtype then calls the tool and the template renders the fallback.
* Runtype sends the value only where the tool's templates reference it. Runtype doesn't add it to the query string or to a body it builds from the remaining arguments.

The same rules apply to an inline runtime tool that references `_endUser` or `_tenant` without declaring it in `hiddenParameterNames`, and to a saved tool that a Flow's `tool-call` step calls. A `tool-call` step's own `_endUser` or `_tenant` parameter never replaces the execution's value.

A Flow that runs as a tool gets `_endUser` and `_tenant` from the execution that called it, never from the tool arguments. If that execution has no verified identity, the nested Flow has no `_endUser` or `_tenant` variable.

### Pass the verified identity to an MCP server

To send the verified tenant or end user to a remote MCP server, add a header template to the server in the agent's `tools.mcpServers`. Use the `headers` auth type:

**`MCP server with an identity header`**

```json title="MCP server with an identity header"
{
  "id": "erp",
  "name": "ERP orders",
  "url": "https://mcp.example.com/mcp",
  "auth": {
    "type": "headers",
    "headers": {
      "Authorization": "Bearer {{secret:ERP_MCP_TOKEN}}",
      "X-Tenant-Id": "{{_tenant.id}}"
    }
  }
}
```

Replace `ERP_MCP_TOKEN` with the name of the managed secret that authenticates Runtype to your MCP server. You do not declare `hiddenParameterNames` for an MCP server. Runtype treats every header value that references a variable with a leading `_` as a hidden identity value:

* Runtype fills `{{_tenant.id}}` only from the execution's `_tenant` variable. The model's tool arguments and the request `metadata` cannot supply it.
* If the execution has no `_tenant`, Runtype does not call the MCP tool. The model receives an error as the tool result.
* If the model sends an argument named `_tenant`, Runtype removes it from the call.
* Runtype sends the header on tool calls only. The `tools/list` request that discovers the server's tools does not include it.

This applies to MCP servers on an agent and on a Flow's prompt step. It does not apply to MCP servers saved in your account and attached by tool ID, because Runtype resolves those headers from the saved server.

To let the MCP server verify the identity itself, use `"Authorization": "Bearer {{_identity.token}}"`. Runtype signs a short-lived assertion for the server's host on each tool call. For the claims and how to verify them, see [Send a verifiable identity assertion to your API](/developer-guides/guides/end-user-identity#send-a-verifiable-identity-assertion-to-your-api).

MCP credentials go on the server, never on a saved MCP tool. A saved MCP tool only points at a server, so its config rejects `{{secret:NAME}}` references. Put the credential in one of these places instead:

* The `auth` of a server you register with `POST /v1/mcp/servers`. Attach the server's tools to the agent by tool ID.
* The `auth.headers` of the agent's `tools.mcpServers` entry, as in the preceding example.

Both accept `{{secret:NAME}}` references. Only the `tools.mcpServers` entry can also carry `{{_tenant.id}}`.

## Retry tools safely after an interrupted run

A run can be interrupted while a tool call is in progress. When the run resumes, Runtype doesn't call that tool again automatically, because it can't tell whether the first call finished. If the model calls the tool again, it receives an error that asks it to check the result before it retries. The model then decides whether to check, retry, or move on.

A tool that only reads data is safe to call again. To let a resumed run call it again, set `idempotent` to `true`:

**`Tool that is safe to call again`**

```json title="Tool that is safe to call again"
{
  "name": "lookup_order",
  "description": "Look up an order by ID",
  "toolType": "external",
  "idempotent": true,
  "parametersSchema": {
    "type": "object",
    "properties": { "orderId": { "type": "string" } },
    "required": ["orderId"]
  },
  "config": { "url": "https://api.example.com/orders/{{orderId}}", "method": "GET" }
}
```

Leave `idempotent` unset for any tool that writes, sends, charges, or creates. Built-in tools that only read or search are already marked safe to call again. MCP tools follow the server's `idempotentHint` and `readOnlyHint` annotations.

## Require a committed intent before a tool runs

Before an Agent calls a server-side tool, Runtype records an intent: a durable note that the call is about to start. By default the intent is best-effort. If Runtype can't record it, the tool still runs.

Set `tools.intent` to `mandatory` on an Agent so that a tool that isn't `idempotent` runs only after its intent is recorded:

**`Agent that requires a committed intent`**

```json title="Agent that requires a committed intent"
{
  "tools": {
    "intent": "mandatory",
    "toolIds": ["builtin:runtype_record_upsert"]
  }
}
```

`tools.intent` accepts `mandatory` or `best-effort`. The default is `best-effort`.

With `mandatory`, if Runtype can't record the intent, it doesn't call the tool. The tool call fails, and the model receives an error that starts with `TOOL_INTENT_NOT_COMMITTED`:

```text
TOOL_INTENT_NOT_COMMITTED: the tool call was not started because its intent could not be recorded durably. It had no effect; it is safe to call it again.
```

The call had no effect, so the model can call the tool again.

The setting has these limits:

* It applies to Agents only. A flow prompt step doesn't record tool intents, so the flow validator warns with `TOOL_INTENT_POLICY_AGENT_ONLY` when a prompt step sets it.
* It applies only where the run keeps a tool journal, which durable Agent runs do. A run without a tool journal calls the tool as it would without the setting.
* Tools marked `idempotent` keep the best-effort intent, because calling them again is safe.
* A dispatch request can set `tools.intent` to `mandatory` for a saved Agent. A request can't lower a saved Agent's `mandatory` to `best-effort`.

Each intent also records whether the tool was `idempotent` when the call started. When a run resumes after an interruption, Runtype calls an interrupted tool again only if it was idempotent then and is still idempotent now. Marking a tool `idempotent` later doesn't make an interrupted call run again. Removing `idempotent` stops an interrupted call from running again.

## Ask the end user for tool inputs

An external or custom tool can ask the end user for some of its inputs before it runs. The model fills the other parameters, and the end user fills the requested fields in a form.

To ask for input, add `config.elicit` with a `message`, a `requestedSchema`, and `"merge": "parameters"`. The following example asks the end user for the meeting date:

**`Tool that asks the end user for input`**

```json title="Tool that asks the end user for input"
{
  "name": "schedule_meeting",
  "description": "Schedule a meeting with the customer",
  "toolType": "external",
  "parametersSchema": {
    "type": "object",
    "properties": {
      "topic": { "type": "string" },
      "date": { "type": "string", "title": "Meeting date" }
    },
    "required": ["topic", "date"],
    "additionalProperties": false
  },
  "config": {
    "url": "https://api.example.com/v1/meetings",
    "method": "POST",
    "body": "{\"topic\": \"{{topic}}\", \"date\": \"{{date}}\"}",
    "elicit": {
      "message": "Which date works for you?",
      "requestedSchema": {
        "type": "object",
        "properties": {
          "date": { "type": "string", "title": "Meeting date" }
        },
        "required": ["date"],
        "additionalProperties": false
      },
      "merge": "parameters"
    }
  }
}
```

The model sees only the `topic` parameter. When it calls the tool, the end user sees the message and a form for `date`. Runtype merges the answer into the tool parameters and then calls the API.

The tool definition must follow these rules:

* Set `"additionalProperties": false` on both `parametersSchema` and `requestedSchema`.
* Define each requested field exactly as it appears in `parametersSchema`, with the same required setting.
* Use only `string`, `number`, `integer`, and `boolean` fields. Each field can have a title, a description, an enum, string length bounds, and numeric bounds.
* Don't request a field whose name starts with `_` or that is listed in `hiddenParameterNames`.

If the end user cancels or declines the form, Runtype doesn't call the tool. The model receives a cancellation result instead.

Tool input requests work on AG-UI surfaces for top-level agent calls that use a Surface key. They don't work inside subagent calls, in Claude Managed Agents, or in Persona. For the stream events and the resume request, see [Ask for input before dispatch](/developer-guides/guides/runtime-tools#ask-for-input-before-dispatch).

## Limits

Runtime tools have the following limits:

* **Runtime tools per request**: no limit on the number of tools. The `runtimeTools` in a request can be at most 1 MB in total, across all steps, when serialized as JSON.
* **External tool request timeout**: 30 seconds.
* **Custom tool `timeout`**: 30,000 ms at most. Saved custom tools accept longer timeouts. For more information, see [Creating custom tools](/user-guide/tools/creating-custom-tools).
* **Subagent tool timeout**: `timeoutMs`, 5 minutes by default.
* **Inline MCP servers per prompt step**: 5.

A model provider can reject a long tool list even though Runtype accepts it. For example, OpenAI accepts at most 128 tools. Runtype reports that rejection with the error code `TOOL_COUNT_REFUSED_BY_PROVIDER`. For a large tool set, use a multi-turn Agent with [tool search](/user-guide/agents/agent-tools#tool-search), which loads runtime tools only when the model searches for them.

For client tool limits and MCP tool call timeouts, see [Limits](/developer-guides/guides/runtime-tools#limits) in the developer guide.

## Troubleshoot validation errors

Runtype validates runtime tools before the run starts. An invalid tool returns an HTTP error with an `error` message, and nothing runs. The most common causes are the following:

* A missing `name`, `description`, `toolType`, `parametersSchema`, or non-local `config`.
* A `parametersSchema` without `"type": "object"` at the top level.
* A tool name that matches the reserved `runtype_set_state` name after Runtype lowercases it and replaces other characters with `_`.
* A custom tool `timeout` greater than `30000`.
* An inline subagent `agent` without `allowedTools`.

For the full list of error messages and fixes, see [Troubleshooting](/developer-guides/guides/runtime-tools#troubleshooting) in the developer guide.

## Next steps

Continue with one of the following pages:

* [Creating custom tools](/user-guide/tools/creating-custom-tools): save sandboxed code tools
* [Creating external tools](/user-guide/tools/creating-external-tools): save HTTP API tools
* [Managing secrets](/user-guide/settings/managing-secrets): add the secrets that external tools reference
* [Runtime tools developer guide](/developer-guides/guides/runtime-tools): pause and resume executions, grant tool approvals, and use client-side tools
* [What are Tools?](/user-guide/tools/what-are-tools): compare tool types
* [Tool approvals](/user-guide/tools/tool-approvals): require approval before a tool runs, and approve or resume from the API