Runtime tools
Define runtime tools directly in a dispatch request without saving them to your account. Use them for dynamic configurations, testing, multi-tenant applications, and one-off tasks.
When to use runtime tools
Use runtime tools for the following cases:
Basic example
The following example adds an external runtime tool to a flow prompt:
Set RUNTYPE_API_KEY to your Runtype API key. Replace WEATHER_API_KEY with the
name of the managed secret that stores the weather API key.
The Python SDK equivalent is:
Set RUNTYPE_API_KEY to your Runtype API key. Replace WEATHER_API_KEY with the
name of the managed secret that stores the weather API key.
The cURL equivalent is:
Replace YOUR_API_KEY with your Runtype API key.
Tool types
Runtime tools support the following types:
External tools
Call an HTTP API with an external tool:
Replace API_TOKEN with the name of the managed secret that stores the API token.
Receive binary results from other tools
When a tool returns binary media, such as a browser screenshot or a generated image, Runtype stores the bytes and adds a runtype-asset:// handle to the tool result. The model passes the handle to the next tool as a plain string instead of re-typing the base64 content.
To receive those bytes in an external tool, declare contentEncoding: 'base64' on the parameter. Runtype resolves a handle in that parameter to the stored bytes, as base64, before the request is dispatched:
A parameter without contentEncoding receives the handle string unchanged. Handles are scoped to your organization and expire after 7 days. A handle that is missing, expired, or from another organization fails that tool call with an error. The same rule applies to MCP server tools that declare the keyword in their input schema, and to the built-in store_asset.content, publish_page.content, and send_email.attachments[].content parameters.
Custom tools
Run code in a sandbox with a custom tool:
Flow tools
Run another Runtype flow as a tool:
Replace YOUR_FLOW_ID with the ID of the flow that you want to run.
Subagent tools
Delegate a self-contained task to a focused child agent. The child runs in its own context window and cannot see the parent conversation. It returns only its final answer. Runtype intersects the child’s allowedTools with the parent’s resolved tools.
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.
To let an agent decide what to delegate at runtime, add subagentConfig to its
tools configuration. Runtype synthesizes a spawn_subagent tool. The model can
use it to choose the task, tools from toolPool, and an optional system prompt:
The parent stream shows one tool bubble for each subagent call. The child’s internal turns and
tool calls do not appear in the parent stream. If a child tool requires approval, the parent
pauses with an approval_start event. The event includes a subagent field that identifies the
parent subagent tool and the child agent’s name. The toolName and parameters fields identify
the inner tool that needs approval. Each subagent call counts as one tool call against the
parent’s maxToolCalls, and the child’s cost counts toward the parent’s total.
Local tools
Run a local tool in your client instead of on Runtype. Set toolType to local.
The dispatch pauses and emits await, then your client runs the handler and
resumes the run. A local tool does not require a config object:
Pass secrets
Store sensitive values in Settings > Secrets. Then use a managed reference in supported HTTP tool fields:
Replace API_TOKEN with the name of the managed secret that stores the API token.
The top-level dispatch secrets map is retired on BOTH dispatch targets: a non-empty map is refused with 400 RUNTIME_AGENT_TRANSIENT_SECRETS_UNSUPPORTED on an agent dispatch and 400 RUNTIME_FLOW_TRANSIENT_SECRETS_UNSUPPORTED on a flow dispatch. An empty map is accepted and does nothing. Use {{secret:NAME}} for credentials in hosted FLOW execution. Managed values are encrypted at rest and are not logged or returned in API responses.
Variable substitution
Use the following template variables in tool configurations:
For example, configure a request with record, flow, and user values:
Replace API_TOKEN with the name of the managed secret that stores the API token.
Combine with saved tools
Combine runtime tools with saved tools in one tools configuration:
Replace YOUR_TOOL_ID with the ID of the saved tool.
SDK helper functions
Use the TypeScript SDK helper function to create an external tool:
Replace WEATHER_API_KEY with the name of the managed secret that stores the
weather API key.
Client-side tools (WebMCP)
Runtime tools run on Runtype servers. Runtype calls the external URL, runs the
sandboxed code, or invokes the nested flow. Client tools run in your client,
such as a browser page or an SDK process. Define them per request in the top-level
clientTools field. The field works with POST /v1/dispatch and
POST /v1/agents/:id/execute, with the same admission rules. When the model calls
a client tool, Runtype pauses the run and streams a unified await event. Run the
tool locally, then resume the run with the result. For the pause event details, see
Pause and resume on an agent dispatch.
Each entry has a name, description, parametersSchema, and an origin:
origin: 'sdk': a tool that your SDK process executes. Runtype accepts it automatically.origin: 'webmcp': a tool that a browser page registers through WebMCP (document.modelContext). On the API-key paths (/v1/dispatchand/v1/agents/:id/execute), Runtype accepts WebMCP tools by default. Include a tool inclientToolsto opt in.
Reference a Runtype MCP catalog tool
When the tool is one Runtype’s own MCP catalog already defines, send a reference instead of a full manifest:
Runtype resolves description and parametersSchema from its own copy of the
catalog, so you do not send (or keep in sync) either one. A name the catalog does
not define returns a 400 rather than being dropped. The resolved entry is admitted
as origin: 'webmcp' and goes through the same allowlist, naming, and duplicate
rules as any other entry.
References and full manifests mix freely in one array:
The size limits below apply to manifests you send. A reference carries no description or schema of its own, so it is not measured against them.
Set untrustedContentHint: true when a tool’s output contains untrusted
third-party data. Runtype wraps that result in a nonce-delimited envelope with the
UNTRUSTED_TOOL_OUTPUT marker before the model reads it. WebMCP output receives
this treatment without the hint. Set the hint for SDK tools whose results can
contain user-supplied or externally fetched content. Tool descriptions are limited
to 2 KB.
Client tools merge into the model’s tool set with the precedence
saved < runtimeTools < clientTools. A later turn can override a saved tool with
the same name without editing the flow.
The API-key /v1/dispatch examples use the lower-level path. Use the Persona
widget for browser-embedded chat. Use direct dispatch when you build a non-Persona
chat UI, run local tools from an SDK or native process, or proxy browser tool calls
through a server that stores the Runtype API key. Do not expose a Runtype API key in
a browser page that you do not control.
The following TypeScript example runs a local tool through the SDK:
Replace YOUR_FLOW_ID with the ID of the flow that you want to run. Set
RUNTYPE_API_KEY to your Runtype API key.
The following request registers a browser tool for an API-key dispatch:
Replace YOUR_API_KEY with your Runtype API key and YOUR_AGENT_ID with the ID of
the saved agent.
Restrict which client tools are admitted
Runtype accepts every submitted client tool by default. To narrow the accepted
origin: 'webmcp' tools, pass clientToolsPolicy with an allowlist of glob
patterns. Use an exact name or a name with one trailing *. The allowlist does not
gate SDK-origin tools.
Configure the policy as follows:
The policy accepts search_products and drops delete_account. Omit
clientToolsPolicy to admit every WebMCP tool. Replace YOUR_AGENT_ID with the ID
of the saved agent.
clientToolsPolicy applies to API-key callers on both POST /v1/dispatch and
POST /v1/agents/:id/execute. It applies across the request and matches bare tool
names. It does not provide an origin-scoped surface policy. Persona and other
client-token chat surfaces use behavior.webmcp.allowlist instead.
Persona chat surfaces and WebMCP
Persona chat widgets use the public client-token path (/v1/client/chat), so the
chat surface’s behavior.webmcp policy controls WebMCP admission. Enable page-tool
discovery with webmcp: { enabled: true } in the Persona configuration.
Client Chat can pause for a browser-side client tool and resume that same execution through POST /v1/client/resume. This is not a human tool-approval channel. If the selected Agent, Flow, nested
Flow, saved tool, or subagent can reach a human approval gate, POST /v1/client/chat rejects the
request before execution with HTTP 501 and code APPROVAL_MODE_UNSUPPORTED. Use an
authenticated dispatch or dashboard execution host when a person must approve a server-side tool.
Enable WebMCP in the Persona configuration as follows:
Replace YOUR_CLIENT_TOKEN with the client token for the chat surface.
Set the WebMCP policy on the chat surface as follows:
On a Persona surface, the following rules apply:
- The embedding page registers tools with
document.modelContext.registerTool(toolDefinition). - Persona snapshots those tools per turn and forwards them as
clientTools[]withorigin: 'webmcp'. - Runtype applies a server-owned
webmcp:prefix before the model sees the tool. behavior.webmcp.enabledmust be true, and anyallowlistrules must match the validated request origin and the bare tool name.- The client token’s
allowedOriginsremains the enforced CORS boundary for which browser origins may call the surface. - Persona also has SDK-owned client tools that require no page registration:
expose
ask_user_questionwithfeatures.askUserQuestion.expose: trueorsuggest_replieswithfeatures.suggestReplies.expose: true.
The dashboard WebMCP tab shows page tools and origins observed on the chat surface over the trailing 7 days. Use the tab to promote a discovered tool to an origin-scoped allowlist rule.
Pause and resume on an agent dispatch
When you dispatch with an agent or flow payload, the execution uses these
pause events:
- A client-tool pause emits
await. - A tool-approval pause emits
approval_start. - A question for the person emits
awaitwith anawaitReasonand anelicitationpayload. An MCP server usesmcp_elicitation. An external A2A agent usesa2a_input_requiredora2a_auth_required.
Use the fields in the await event to run the tool and resume the execution:
Replace YOUR_EXECUTION_ID, YOUR_TOOL_ID, and YOUR_TOOL_CALL_ID with the
values from the event.
A tool-approval pause uses approval_start. When a child agent calls the gated
tool, the event also carries a subagent object. The toolName and parameters
fields describe the inner tool that needs approval. The subagent.toolName field
names the parent subagent tool:
Replace the placeholders with the IDs from the event. Use dana@example.com only
as an example recipient.
Values injected for protected parameters appear as [REDACTED] in approval
payloads. Display the placeholder as protected context; approving or denying
the call does not require resubmitting those values.
Approve an approval pause through the approval endpoint. For a client-tool pause,
run the tool locally, then post its result to /v1/dispatch/resume:
Replace YOUR_API_KEY, YOUR_EXECUTION_ID, YOUR_TOOL_CALL_ID, and
YOUR_TOOL_RESULT with values from your request and the tool result.
Use these fields when you resume a client-tool pause:
executionId: the value from theawaitevent. It identifies the paused turn.toolOutputs: an object keyed by the per-calltoolCallIdfrom the event. You can use the tool name for a single call for compatibility with legacy clients.- The resumed stream continues with events such as
text_deltaand ends withexecution_completeorexecution_error, or pauses with anotherawait. The latter can identify an unanswered call from the current batch or a new call.
Partial batches and resume conflicts
When an agent pauses with multiple client-tool calls, you can submit results for
only the calls that have finished. Runtype retains accepted answers and re-emits
the outstanding calls without requesting another model response. It resumes the
model only after every call in that batch has an answer. Match each result to its
exact toolCallId, even when several calls use the same tool name; do not resend
already accepted answers or treat unknown call IDs as completed work.
Serialize resume submissions for a flow execution. Concurrent flow resumes now
receive HTTP 409 rather than both executing. Check the response body’s code:
CONTINUATION_IN_PROGRESS: another request owns this continuation. Wait for that request’s response or stream. If it fails before advancing the checkpoint, you can retry the still-pending calls; do not blindly retry while it is running.RESUME_STATE_CHANGED: the submitted pause was replaced or already consumed. Do not replay its tool results. Use the latestawaitevent or JSON paused response from the winning continuation and answer only its outstanding calls.- HTTP
404means no resumable checkpoint remains, including a completed or expired execution. Replaying old results cannot restart it.
These checks also apply when streamResponse is true: a rejected admission is
an HTTP error, not a successful resumed stream. Resume is not a general
idempotency key for side effects; keep the original tool result while recovering
rather than rerunning the local action automatically.
For client-token authentication, use the browser resume endpoint instead of sending a secret API key from a page.
Elicitation pauses: when the question is for the human
Not every await event represents a tool that you run. An MCP server can request
input during a tools/call. An external A2A
agent can return
input-required or auth-required. In both cases, the peer asks the person,
not your process. These cases use the same await event. Distinguish them with
awaitReason:
Replace YOUR_EXECUTION_ID, YOUR_TASK_ID, and YOUR_CONTEXT_ID with the values
from the event.
Use these fields to handle the elicitation:
elicitation.messageis peer-controlled, display-only text. Show it to the person, but do not branch your logic on its content. TreatawaitReasonas UX context, not a control signal.elicitation.modeisform(answer with text) orurl(send the human toelicitation.urlto authenticate or consent, then continue). A2A pauses useform. MCP servers can useurl.externalAgentcarries the peer’s conversation handles (contextId/taskId) so you can correlate the paused run with the external agent’s conversation. You do not need to send these handles in the resume request. The resume uses the saved conversation checkpoint.
Resume an MCP-server elicitation
An MCP-server elicitation (awaitReason: "mcp_elicitation") resumes through
POST /v1/dispatch/resume, not through a re-dispatch. Key toolOutputs by the
toolCallId from the await event, the same key a client-tool resume uses, and
send the person’s answer as an mcpElicitation entry:
Replace YOUR_API_KEY, YOUR_EXECUTION_ID, and YOUR_TOOL_CALL_ID with your API
key and the identifiers from the await event. Replace YOUR_FIELD and
YOUR_VALUE with a property from elicitation.requestedSchema and the person’s
answer.
The entry differs from a client-tool result in three ways:
kindmust be the literal stringmcpElicitation. The value marks the entry as a person’s answer. A plain{ "result": ... }entry is consumed as the tool’s output instead, which sends the wrong value to the model.response.actionisaccept,decline, orcancel. Sendcontentonly withaccept, and match its properties toelicitation.requestedSchemafrom theawaitevent. Adeclineorcancelresponse carries nocontent.pauseCountechoeselicitation.pauseCountfrom theawaitevent. Runtype retains the checkpoint’s cumulative count; a smaller submitted count cannot reset the per-call cap.
Form acceptance requires an object that validates against the saved request
schema. Runtype does not coerce values or insert defaults, and rejects
unsupported schema constraints rather than ignoring them. Invalid answers do
not advance the paused invocation. For URL acceptance, send action: "accept"
without content: this acknowledges the out-of-band interaction, and the MCP
server must confirm its outcome when the call is re-issued. It does not grant
tool approval or certify successful authentication.
Runtype re-issues the paused MCP call with the answer as input to that call. The
answer does not become the tool’s result, so the model sees whatever the
re-issued call returns. The server can elicit again on the re-issued call, which
pauses the run once more with a new await event. Answer each pause the same
way, keyed by the toolCallId of the event you received.
One tool call can pause for input at most three times without making progress. After that, Runtype declines the elicitation and fails the tool call with a message that asks the person to complete the requested action out of band and try again.
Resume an A2A elicitation
An external agent elicitation (awaitReason: "a2a_input_required" or
"a2a_auth_required") resumes by re-dispatching the agent with the person’s
answer as the message and resumeFrom naming the paused execution. Do not send
toolOutputs because no local tool runs:
Replace YOUR_API_KEY, YOUR_AGENT_ID, and YOUR_EXECUTION_ID with your API key,
the saved agent ID, and the paused execution ID.
The continuation sends your answer to the external agent on the same
conversation and streams the rest of the run. Checkpoints expire after 7 days,
and a peer that pauses without assigning a contextId cannot continue the saved
conversation. The await event still lets your UI display the question. A
re-dispatch starts a fresh conversation.
Pause and resume without streaming
You can handle a pause without consuming the SSE stream. Set
streamResponse: false when you dispatch through POST /v1/agents/:id/execute
or POST /v1/dispatch with an agent payload. A paused run returns a JSON
envelope with status: "paused" and a pausedReason object:
Replace the YOUR_* values with identifiers from the paused execution.
The API sets success to true for a resumable run. Branch on
status === "paused" or stopReason === "paused", not on success.
Protected values in pausedReason.parameters also appear as [REDACTED].
Resolve a paused run according to the pausedReason fields:
-
Approval gate (
awaitReason: "approval_required", anapprovalIdis present): submit the decision toPOST /v1/agents/:id/approvewith theexecutionIdandapprovalIdfrompausedReason:cURLReplace
YOUR_AGENT_ID,YOUR_API_KEY,YOUR_EXECUTION_ID, andYOUR_APPROVAL_IDwith the values for the paused run. -
Local/client tool call (no
awaitReason/approvalId): run the tool yourself. Then resume with the result throughPOST /v1/dispatch/resume, using theexecutionIdfrompausedReasonand the tool call:cURLReplace
YOUR_API_KEY,YOUR_EXECUTION_ID,YOUR_TOOL_CALL_ID, andYOUR_TOOL_RESULTwith values from the paused run and the local tool. -
A question for the human (
awaitReason: "a2a_input_required","a2a_auth_required", or"mcp_elicitation"):pausedReason.elicitationcarries the peer’s question. For an external agent, the envelope also carriesexternalAgentwith the conversation handles at the top level, alongsidestatus, not insidepausedReason:Non-streaming paused response (external agent asked a question)Replace the
YOUR_*values with identifiers from the paused response.Resume an external agent (
a2a_input_requiredora2a_auth_required) by re-dispatching with the person’s answer andresumeFrom, as described in Resume an A2A elicitation. Resume an MCP server (mcp_elicitation) with anmcpElicitationentry onPOST /v1/dispatch/resumeinstead, as described in Resume an MCP-server elicitation.
Set streamResponse: false on the approval or resume request to receive the
continuation as JSON instead of an SSE stream.
Resume from a browser (client-token path)
With an API key, complete a paused tool call by posting its result to
/v1/dispatch/resume. That route requires a secret API key with DISPATCH:*
scope. A browser page, including the embedded Persona widget, cannot use it.
Browsers authenticate with a client token through the /v1/client/* routes.
To complete a paused local-tool turn, post the result to /v1/client/resume:
Replace YOUR_SESSION_ID, YOUR_EXECUTION_ID, YOUR_TOOL_CALL_ID, and
YOUR_TOOL_RESULT with values from the client session, paused event, and local
tool result.
Use these fields to resume the browser session:
sessionId: the session from/v1/client/init. It authenticates the request with an active session, an active client token, and a matchingOrigin.executionId: the value from theawaitevent. It scopes the resume to the session’s user. A client token can resume only that user’s executions.toolOutputs: an object keyed by the per-calltoolCallIdfrom theawaitevent. You can use the tool name for a single call for compatibility with legacy clients.assistantMessageId: an optional ID, up to 128 characters, for the assistant message your page renders from this resumed leg. Sending an ID you already minted lets a later re-send deduplicate by ID instead of text.- The resume request does not consume additional execution quota. The turn was counted when it started.
For agent batches and flow conflict recovery, follow Partial batches and resume conflicts. The same per-call identity rules apply to this client-token endpoint.
What the resumed leg produces is persisted to the conversation. Runtype assembles
the leg’s assistant text from the stream and appends it as an assistant message,
and it resolves the paused tool calls the conversation already holds from the
toolOutputs you submit. The completion is stored whether or not you send
assistantMessageId; without that field, it is stored under a server-minted ID.
Do not resend the clientTools definitions on resume unless the page tool set
changes. Runtype carries the definitions from the originating /v1/client/chat
dispatch forward unchanged.
Refresh page tools mid-run
If a paused page tool navigates, the destination page registers its own WebMCP
tools on document.modelContext. The dispatch-time snapshot does not include
those tools. To make them callable on the next model turn, send the page’s current
registry with the resume. Use the same send-once protocol as /v1/client/chat:
- Send the full registry: Include
clientToolswith the page’s complete registry and includeclientToolsFingerprintfor the set. - Send only the fingerprint: If the registry is unchanged since the last full
send, send only
clientToolsFingerprint. If it does not match the stored registry, the request returns409 { "error": "client_tools_resend_required" }. Retry with the fullclientToolsarray and the fingerprint.
Replace YOUR_SESSION_ID, YOUR_EXECUTION_ID, YOUR_TOOL_CALL_ID, and
YOUR_CLIENT_TOOLS_FINGERPRINT with the values for the active client session,
paused event, tool call, and current registry.
The refreshed set replaces the run’s persisted tool set for the rest of the
run. It does not merge with the saved set, so a dispatch-time tool that you omit
is no longer callable. Runtype revalidates and regates the set against the
surface’s behavior.webmcp policy for the request Origin, as it does during
dispatch.
The Persona chat widget sends the /v1/client/resume request in client-token mode. You implement
only the local tools. This endpoint is documented for custom client-token integrations.
Detached streams
A pause is not the only reason an agent stream ends early. The socket an agent
turn streams over carries a watch lease, not the turn’s lifetime. When the
lease expires, Runtype emits an await event with awaitReason: "detached" and
closes the stream. The run is still going server-side. Treat it as “still
working”, never as an error and never as a finished turn:
To keep receiving events, reopen the run through its events route with the last
SSE id: you saw as the cursor. The server replays strictly after that cursor,
so you get no duplicates:
Replace YOUR_AGENT_ID, YOUR_EXECUTION_ID, and YOUR_LAST_EVENT_ID with the
agent, the executionId from the detached frame, and the last SSE id: value
your client received.
The TypeScript SDK does this for you. client.agents.executeStream() follows a
detached turn automatically and keeps yielding frames until a real terminal
arrives, so executeWithCallbacks() inherits the behavior. The local-tool loop
gets it on both legs: its first stream comes from executeStream(), and each
resume after a local tool is followed the same way, so a lease that expires
mid-continuation never ends the session early. The detach frame is still
forwarded, so a UI that renders await states keeps rendering it:
This applies to agent streams from client.agents.executeStream() and POST /v1/agents/:id/execute. Flow dispatch streams do not detach. Raw HTTP callers and the Python SDK
reattach manually with the ?after= request above.
Durable turns and the in-process fast lane
An agent turn is durable by default. A durable turn keeps running after the
socket closes, which is what makes the detach, ?after= reconnect, and cancel
behavior above possible. Setting it up costs about a second before the first
token.
A turn that is provably short skips that setup and runs in-process instead.
A turn qualifies only when every one of these is true: it carries no
conversationId, the agent attaches no tools, skills, or sandbox, no approval
gate can be reached, the request sends no client tools and no
Prefer: respond-async, and the agent’s durability budget is no longer than the
five-minute in-process limit. Such a turn is one model call, so it answers
faster. In exchange it does not detach, cannot be reconnected with ?after=,
and a wedged run is cancelled cooperatively rather than immediately. Anything
that might run long or pause stays durable.
Pin either lane for one request with durability. A request pin outranks the
agent’s saved durability.forced setting:
forced is "durable" or "in_process". watchLeaseMs sets how long the
attached stream stays open before the turn detaches, from 10 seconds to 30
minutes; the default is 10 minutes. Raising it keeps one socket open for longer
and does not change how long the run itself may take. Durability is not the same
choice as Prefer: respond-async: a durable turn still streams to your request
unless you ask for an async response.
POST /v1/dispatch accepts the saved-agent durability config on its agent
input, so a saved configuration round-trips into an inline dispatch agent
unchanged. The request-pin semantics above are live on POST /v1/dispatch for
forced and watchLeaseMs when the request names a saved agent by agentId
and carries nothing the durable session cannot honor: no resumeFrom and no
clientToolOutputs, no Claude Managed agent and no local inference, no client
tools, no inline secrets, inputs, credentialProxies, or environment
override, no stepTimeoutMs or flowTimeoutMs, storeResults and cache not
disabled, and no approval gate the run could reach. An inline agent definition
and every one of those shapes keep the in-process path, where the pin still
selects the fast lane or the standard one.
For a run you started through POST /v1/dispatch with a saved agent, or any run
whose agent you do not have at hand, GET /v1/executions/{executionId}/events?after=
reconnects by execution id alone with the same replay-and-tail contract. An execution
that names no agent (an inline definition) answers 404, because it addresses no
durable session.
Tool approval grants
When you enable tool approval, a gated tool call pauses the run for a person to approve or deny. If the approval prompt offers Always allow, the person can select it instead of Allow once. The selection creates a durable grant, so future dispatches skip the approval prompt for that tool.
A grant stores an Always allow decision for the owner, agent, and approving end user. An account-level grant applies to every end user and appears as All users in the dashboard.
For an agent with a Tenancy Strategy, the end-user key is the durable projected end-user ID (eu_YOUR_END_USER_ID). Without a Tenancy Strategy, the key is the raw endUser.id that you pass at dispatch. The lookup matches both forms for compatibility. If you remove an agent’s Tenancy Strategy, grants recorded with the strategy no longer match, so the person approves the tool again. Scoped memory and records use the same re-scoping behavior.
A grant skips only the approval prompt, not authorization. Tool resolution, ownership scoping, and secret access are unchanged. Runtype does not remember skill-load approvals, so loading a skill prompts for approval.
Create a grant
Select Always allow in an approval prompt to create a grant.
With the raw API, pass remember: true when you resolve the approval. The authenticated dashboard
execution UI sets this field when the person selects Always allow. The public Client Chat path
does not host human approval prompts.
Replace YOUR_AGENT_ID, YOUR_API_KEY, YOUR_EXECUTION_ID, and
YOUR_APPROVAL_ID with the values for the paused run.
To offer the choice, enable tools.approval.choices.alwaysAllow in the saved
agent configuration or set it per dispatch. See Dispatch-time approval overrides.
List grants
List active grants for the authenticated owner. Pass agentId to filter the results
to one agent.
Replace YOUR_AGENT_ID with the ID of the agent to filter. Set
RUNTYPE_API_KEY to your Runtype API key.
The following request lists grants for one agent:
Replace YOUR_AGENT_ID with the agent ID and YOUR_API_KEY with your Runtype API
key.
The response is { "data": [] }. Each grant has these fields:
Revoke a grant
Revoke a grant to prompt for approval again on future dispatches. The API returns
{ "revoked": true }.
Replace YOUR_GRANT_ID with the ID of the grant to revoke.
Replace YOUR_GRANT_ID with the ID of the grant and YOUR_API_KEY with your
Runtype API key.
You can also view and revoke grants from the dashboard in the agent editor Safety section, under Remembered approvals.
Dispatch-time approval overrides
When you dispatch an agent, agentInput.tools.approval overrides the saved
approval configuration for that run. It accepts the following fields:
require: tools that need approval. Set it totruefor all tools, or to an array of tool names and patterns such as["send_email", "mcp:*"].requestReason: whether the prompt asks the model for a reason for the call.choices: choices that the prompt offers. Setchoices.alwaysAllow: trueto show Always allow for this dispatch.
Configure an approval override as follows:
Replace YOUR_AGENT_ID with the ID of the saved agent.
Limits
The following limits apply to runtime and client tools:
API format
The API uses camelCase for field names. The following request shows the format:
Best practices
Apply these practices when you configure runtime tools:
Save frequently used tools
If you use the same runtime tool repeatedly, save it to your account through the API or dashboard to keep the configuration in one place.
Use lowercase headers
Use lowercase header names. For example, use authorization instead of Authorization to avoid
issues with automatic case conversion.
Test before production
Use runtime tools to test configurations, then save working tools for production use.
Validate schemas
Validate that parametersSchema is valid JSON Schema. Invalid schemas cause tool calls to fail.
Ask for input before dispatch
Saved and inline external and custom tools can declare
config.elicit: { message, requestedSchema, merge: 'parameters' }. The final
parametersSchema and requestedSchema must be closed objects. Requested fields
belong to the human: their primitive types, constraints and requiredness must
match the final schema. Supported field types are string, number, integer and
boolean, with titles, descriptions, enums, string length bounds and numeric
bounds. References, unsupported keywords, cross-field constraints, reserved
names and hidden parameter overlap are rejected.
The model sees only the remaining parameters. The runtime validates those arguments, collects the human-owned fields, validates the merged object, then requests any ordinary approval for the final arguments. Approval resume reuses the validated input. Changing the tool definition or model arguments requires restarting the invocation. Cancel or decline skips dispatch and returns a cancellation result for the original tool call.
This requires a host that can persist and resume input. The portable runtime
host opts in with enableToolElicitation: true; both agent and flow lanes support
it. Authenticated root AG-UI agents use the existing form interrupt and resume
endpoint. Nested product execution, managed agents and dashboard Persona clients
without form-answer support remain disabled. Server tool arguments stay private
unless the builder opts the tool in through the AG-UI surface’s
behavior.discloseToolArguments.tools, for example ['schedule_meeting'].
That rule exposes model-owned arguments in interrupt metadata and tool-call
events. Hidden values are still removed before persistence and disclosure.
Unified streaming emits the existing await event with
awaitReason: 'tool_elicitation' and the existing form elicitation payload.
There is no new event type. Resume authorization and routing use the server’s
checkpoint, not the displayed reason. MCP elicitation keeps its existing reason
and resume behavior.
Next steps
Continue with one of these resources: