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

# Self-hosting (BYOC)

> Export a flow or agent as a self-contained runtime and run it on your own infrastructure

Use self-hosting to export a saved flow or agent as a self-contained
`@runtypelabs/runtime` definition. Run the definition on Cloudflare Workers,
Cloud Run, Node, Vercel Functions, or another environment with a runtime
adapter. The runtime calls model providers with your own provider keys, through
the Runtype platform-key proxy, or both, depending on the key mode you
configure. It can also call hosted Runtype endpoints for asset storage and
other platform capabilities.

## Before you begin

You need the following:

* **An Enterprise plan.** Self-hosting (BYOC, or bring your own cloud)
  requires one. When an account without it calls an export endpoint with a
  correctly scoped API key, it receives `HTTP 403` with the code
  `BYOC_PLAN_REQUIRED`. Contact support to enable self-hosting for your
  account.
* **An API key with the Runtime Export permission.** In the **Create New API
  Key** dialog, select **Runtime Export** in the **Permissions** list. A
  resource wildcard such as `AGENTS:*` or `FLOWS:*` doesn't grant this scope.
  Only the `RUNTIME:EXPORT` scope itself or the global `*` scope, which the
  **Full Access** permission template grants, does. For more information, see
  [Managing API keys](/user-guide/settings/managing-api-keys).

## Export a flow or agent

The export endpoints return fully resolved definitions that the
`@runtypelabs/runtime` package loads at startup. The flow export inlines step
configurations. The agent export inlines capabilities, flows, and nested
sub-agents through up to three levels, and replaces MCP credential values with
secret-name references.

An inline MCP server with `api_key` or `custom_header` auth sends its token
reference under the server's configured header name. Tools of a server with
`basic` auth are not listed in the export. The runtime discovers them when the
agent starts and builds the `Authorization: Basic` header from the username and
password secrets.

The export never copies a credential that an author typed into a tool
configuration or an MCP server. The export returns `422` with code
`raw_credential_in_export` in these cases:

* A literal value sits under a credential-shaped field, such as `apiKey` or an
  `X-Api-Key` header.
* A value in a known secret format, such as an OpenAI, Anthropic, or GitHub
  key, sits under any field.

Each entry in the response's `details` array names the tool or MCP server, the
field, and the JSON `path`. The response never includes the value. To fix the
export, store the value as a Runtype secret, replace the literal with a
`{{secret:NAME}}` reference, and provide `NAME` to your runtime.

You can export agents that run with a model and system prompt, and external
(A2A) agents. You can't export a Claude Managed agent: exporting one returns
`404` with `Agent not found`. If an agent you export references a Claude
Managed agent as a sub-agent, the export returns `200` without that
sub-agent and lists it in `warnings`. A flow that references a Claude Managed
agent gets a stub that the runtime can't run, which `warnings` also lists. The
flow export is in limited preview.

To export a flow, send this `GET` request:

**`Export a flow`**

```bash title="Export a flow"
curl https://api.runtype.com/v1/flows/FLOW_ID/export-runtime \
  -H "Authorization: Bearer YOUR_API_KEY"
```

To export an agent, send this `GET` request:

**`Export an agent`**

```bash title="Export an agent"
curl https://api.runtype.com/v1/agents/AGENT_ID/export-runtime \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Replace the placeholders in both requests as follows:

* `FLOW_ID`: ID of the flow to export.
* `AGENT_ID`: ID of the agent to export.
* `YOUR_API_KEY`: Runtype API key with the `RUNTIME:EXPORT` scope.

The TypeScript SDK exposes the same exports as `flows.exportRuntime(id)` and
`agents.exportRuntime(id, selector)`. The Runtype MCP server exposes them as
the `export_flow_runtime` and `export_agent_runtime` tools. The
`export_agent_runtime` tool takes the selector as `version_id` or `alias`.

An export is a snapshot. Without a selector, the agent export follows your
organization's live-default setting. With the setting on, the export captures
the version that the agent's `live` alias points at, which is the version
that hosted execution runs. Saved changes that you haven't published or
deployed to `live` aren't included. With the setting off, the export captures
the agent's current saved configuration. Each referenced sub-agent follows the
same setting. The deployed runtime makes no lookups back to Runtype for its
definition, so after you change the agent, a flow, or a tool in Runtype,
publish the change, export again, and redeploy.

### Export a specific version or alias

To export a published version, or the version that a release alias points at,
add one selector to the agent export:

**`Export the version behind an alias`**

```bash title="Export the version behind an alias"
curl "https://api.runtype.com/v1/agents/AGENT_ID/export-runtime?alias=staging" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**`Export a published version`**

```bash title="Export a published version"
curl "https://api.runtype.com/v1/agents/AGENT_ID/export-runtime?versionId=VERSION_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

* `alias`: name of a release alias, such as `staging`, `live`, or `pr-42`. The
  export captures the version that the alias points at when you call it, even
  when `live` points at a different version.
* `versionId`: ID of a published version of the agent.

Pass at most one selector. A request with both returns `400`. The alias
resolves the same way as the execution selector `agent: { agentId, alias }`.
In the SDK, pass `agents.exportRuntime(id, { alias: 'staging' })` or
`agents.exportRuntime(id, { versionId })`.

An export that contains a published version carries a `release` object that
records which version it contains:

```json
{
  "id": "agent_01k9x2q0000000000000000000",
  "name": "Research assistant",
  "release": {
    "versionId": "agentver_01k9x2q0000000000000000000",
    "alias": "staging",
    "aliasRevision": 4
  }
}
```

* `versionId`: the version the export contains.
* `alias` and `aliasRevision`: the alias that selected the version and the
  alias revision at the time of the export. Present only when an alias
  selected the version, including the `live` alias that an export without a
  selector follows.

An export of the agent's current saved configuration has no `release` object.

### Export a vendorable bundle

Add `format=bundle` to the agent export to download one zip archive that
holds everything a self-hosted deployment needs. You don't need access to the
`runtypelabs/core` repository or to an npm registry for Runtype packages.
The `versionId` and `alias` selectors work the same way as for the JSON export.

**`Export a bundle`**

```bash title="Export a bundle"
curl -o agent-bundle.zip \
  "https://api.runtype.com/v1/agents/AGENT_ID/export-runtime?format=bundle&alias=live" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

The archive contains these files:

* `agent.json`: the same definition that the JSON export returns.
* `runtime/`: the prebuilt `@runtypelabs/runtime` package for the platform's
  current runtime version. It is minified ESM with its dependencies bundled
  in. To use it, add `"@runtypelabs/runtime": "file:./runtime"` to your
  `package.json`.
* `manifest.json`: the runtime version and source commit, the agent
  `versionId` and alias, the entrypoints, `hostDependencies`, and
  `contentHash`.
* `LICENSE`: the terms for the vendored runtime code. The runtime ships under
  the Functional Source License, Version 1.1, with an Apache 2.0 future license
  (FSL-1.1-ALv2).

Two exports of the same version return byte-identical archives.
`contentHash` is the SHA-256 of the `sha256sum` listing of every file in the
archive except `manifest.json`: one `<sha256>  <path>` line per file, sorted
by path. The response also sends it in the `X-Runtype-Bundle-Content-Hash`
header. Pin this value to detect any change to the definition or the runtime.

With the CLI, run `runtype agents export-runtime AGENT_ID --format bundle`. In
the SDK, call `agents.exportRuntimeBundle(id, selector)`. The MCP tool
`export_agent_runtime` with `format: "bundle"` reports the content hash and
the runtime version, but it doesn't return the archive bytes.

If the platform has no prebuilt runtime artifact for its current version, a
bundle export returns `503` with code `RUNTIME_ARTIFACT_UNAVAILABLE` and
`reason: "artifact_missing"`.

The following table lists the errors that the export endpoints return:

| Status | Response                                                                          | Cause                                                                                          |
| ------ | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `400`  | `Invalid agent ID format` or `Invalid flow TypeID`                                | The ID in the path is not a valid agent or flow ID.                                            |
| `403`  | `Insufficient permissions. Required: RUNTIME:EXPORT`                              | The API key lacks the `RUNTIME:EXPORT` scope.                                                  |
| `403`  | Code `BYOC_PLAN_REQUIRED`                                                         | The account doesn't have an Enterprise plan.                                                   |
| `400`  | `Pass either versionId or alias, not both`                                        | The agent export received both selectors.                                                      |
| `404`  | `Agent not found` or `Flow not found`                                             | The resource doesn't exist, your account doesn't own it, or it is Claude Managed.              |
| `404`  | Code `agent_not_found`                                                            | The agent that a release selector or default target resolves doesn't exist.                    |
| `404`  | Code `version_not_found`, `alias_not_found`, `alias_archived`, or `alias_expired` | The selected version or alias doesn't exist on the agent, or the alias is archived or expired. |
| `422`  | Code `selector_unsupported_agent_type`                                            | The agent type can't export a selected version.                                                |
| `422`  | Code `raw_credential_in_export`                                                   | A tool configuration or MCP server holds a literal credential. `details` names each field.     |
| `500`  | The skill's resolution error message                                              | A skill bound to the agent failed to resolve.                                                  |
| `503`  | Code `RUNTIME_ARTIFACT_UNAVAILABLE`                                               | A bundle export can't use the prebuilt runtime artifact. The `reason` field says why.          |

A bundle export returns `503` with one of these `reason` values:
`artifact_missing`, `artifact_invalid`, `artifact_integrity`,
`binding_missing`, or `source_commit_unknown`.

A child that the export can't embed doesn't fail the export. The export
returns `200` and lists the child in `warnings`. This includes sub-agents and
flows that nest deeper than three levels.

### Export warnings

Both export responses carry `warnings`. Each entry names a sub-agent, flow,
or question set that the export left out of the artifact, or embedded only as
a stub, and gives the reason. The tool or capability that references a dropped
child is absent from the artifact. The array is empty when the export embedded every
child. A bundle export lists the same entries in the `warnings` field of
`manifest.json`.

**`A dropped sub-agent`**

```json title="A dropped sub-agent"
{
  "code": "child_dropped",
  "reason": "claude_managed_unsupported",
  "childKind": "agent",
  "childId": "agent_01jv0000000000000000000000",
  "parentId": "agent_01jv0000000000000000000001",
  "toolName": "research",
  "message": "Tool \"research\" was left out of the export: claude_managed agents cannot be exported to runtime yet"
}
```

The following table lists the fields of an entry:

| Field       | Description                                                                                                                                                                                                                                                                                                                                        |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `code`      | `child_dropped` when the artifact doesn't contain the child. `child_stubbed` when the artifact contains a stub that the runtime can't run.                                                                                                                                                                                                         |
| `reason`    | `not_found`, `claude_managed_unsupported`, `max_depth_exceeded` (nesting deeper than three levels), `invalid_flow_config` (a malformed step), `unresolved_tool_ref` (a `transform-data` tool that doesn't resolve), `unsupported_mcp_auth`, or `question_set_unresolved` (a configured Decision judgment tool whose question set doesn't resolve). |
| `childKind` | `agent`, `flow`, or `question_set`.                                                                                                                                                                                                                                                                                                                |
| `childId`   | The ID of the child, when the reference names one. An inline flow has no ID.                                                                                                                                                                                                                                                                       |
| `parentId`  | The agent whose capability referenced the child, when a capability referenced it.                                                                                                                                                                                                                                                                  |
| `toolName`  | The capability or tool name that referenced the child.                                                                                                                                                                                                                                                                                             |
| `message`   | A readable description.                                                                                                                                                                                                                                                                                                                            |

Check `warnings` before you deploy an export. An empty array means that the
artifact contains every sub-agent, flow, and question set that the definition
references.

On an account without an Enterprise plan, both endpoints return this response:

```json
{
  "error": "Feature Not Available",
  "message": "Self-hosting (BYOC) requires an Enterprise plan. Contact support to enable it for your account.",
  "code": "BYOC_PLAN_REQUIRED"
}
```

### Host dependencies

Some things a flow or agent can do are not self-contained. The runtime hands
them to your host instead of performing them itself. An export is never
refused for containing one. Instead, both export responses carry
`hostDependencies`. It lists the host hooks that you wire before the artifact
can run everything it describes.

The following table lists the dependency kinds:

| `kind`                       | Runtime interface                                  | What to wire                                                                                                                                                                                        |
| ---------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `durable-pause-host`         | `ExecuteFlowOptions.onDurablePause`                | Durable-class steps (`wait-until`, `crawl`) park a pause instead of blocking. The runtime hands you the pause snapshot and never persists or schedules it. Store it and resume the execution later. |
| `background-run-coordinator` | `RuntimeToolExecutorDeps.backgroundRunCoordinator` | Detached subagents run past the parent's turn. The coordinator owns admission, persistence, wake and re-drive, and terminal delivery. The runtime hands it a child request and takes a handle.      |

`ExecuteAgentOptions.onDurablePause` is the agent equivalent of the flow hook,
with the same contract. You meet the durable-pause dependency in one of two
ways:

* Wire `onDurablePause` on every `executeFlow` or `executeAgent` call.
* Consume the `durable-paused` `FlowStreamOutcome` yourself on the
  `executeFlowToWriter` path, if your host already arms its own scheduler.

The runtime has no default implementation of either hook. The following
failures occur when a hook is missing:

* If you wire neither durable-pause channel, a run that reaches a durable step
  fails with `execution_error` and the code
  `DURABLE_PAUSE_HOST_NOT_CONFIGURED`.
* If you wire no coordinator, spawning a detached subagent throws
  `BackgroundRunCoordinatorNotConfiguredError`.

To catch a missing dependency at startup instead of mid-run, call
`runtime.assertHostDependencies(definition)` at boot. It throws
`HostDependenciesNotConfiguredError` (code
`HOST_DEPENDENCIES_NOT_CONFIGURED`) with the full list. To report the gaps
without throwing, for example in a health check, call
`runtime.unmetHostDependencies(definition)`, which returns the list.

The boot check counts the durable-pause dependency as met only when you set
`durablePauseHost: true` in the `createRuntime` config. That setting declares
that every `executeFlow` and `executeAgent` call supplies `onDurablePause`. A
host that wires the hook but leaves the setting unset fails the check.

`hostDependencies` is always present. An empty array means the artifact is
fully self-contained. The following response shows a flow that needs a
durable-pause host:

```json
{
  "id": "flow_01k9x2q0000000000000000000",
  "name": "Competitor watch",
  "steps": [],
  "hostDependencies": [
    {
      "kind": "durable-pause-host",
      "interface": "ExecuteFlowOptions.onDurablePause",
      "reason": "Durable-class steps park their pause on a host-provided durable executor. ...",
      "steps": [
        {
          "stepId": "scrape_pricing",
          "stepType": "crawl",
          "ownerKind": "agent",
          "ownerId": "agent_01k9x2q0000000000000000000",
          "ownerName": "Research assistant"
        }
      ]
    }
  ]
}
```

Each entry names the sites that caused it. A `durable-pause-host` entry lists
them in `steps`, each with `stepId`, `stepType`, and owner fields. A
`background-run-coordinator` entry lists them in `agents`, each with owner
fields, plus `stepId` when the site is a step's tool block.

The scan covers the artifact's entire executable closure, not only its own
top-level steps. It includes conditional branches, embedded agents'
capabilities, and the flow or sub-agent definitions carried inline by a runtime
tool. A site therefore often names a step that does not appear in the flow you
exported. That is why each site carries `ownerKind` (`flow`, `agent`,
`capability`, or `inline-tool`), with `ownerId` and `ownerName` where they
exist.

Treat the list as open-ended. New `kind` values can appear, so render
`interface` and `reason` for a kind that you don't recognize instead of
failing. An unrecognized `kind` deserializes as an entry that carries `kind`,
`interface`, and `reason`, plus any kind-specific evidence. A client on an
older SDK keeps working and can still tell you what to wire.

### Bound skills

An agent export includes the skills bound to the agent in `skills`. Each
entry holds the skill's description, its body, and its resolved capability
tools. It also records the skill it came from (`skillId`) and the skill
version that supplied it (`skillVersionId`), so you can see exactly which
skill versions an artifact contains.

The runtime loads an exported agent's `skills` on every run. You don't need
to wire anything in your host. The export is a snapshot: a skill you edit,
publish, bind, or unbind afterward doesn't reach the artifact until you export
again. If a bound skill's tools can't be resolved when you export, the export
fails instead of leaving the skill out. Only the exported agent's own skills
are included; sub-agents run without their bound skills, as they do on
Runtype.

To change the skills for one run, pass `skills` in `ExecuteAgentOptions` (or
in the dispatch host's `executeOptions`, which applies to every agent the host
serves). A skill you pass replaces the exported skill with the same slug, and
a skill with a new slug is added. Pass an empty array to run the agent with no
skills.

Capability tools that a skill brings count toward `hostDependencies`. A
skill's sites are attributed to the skill, with `ownerName` set to
`skill:<slug>`.

### Managed external-agent exports

An exported external agent with `skillOrchestration: 'managed'` is
self-contained. At execution, the runtime turns the agent-card skills that the
agent uses as tools into A2A runtime tools. It then runs the configured
`skillOrchestrator` model through the standard agent loop. To give the
orchestrator more tools, pass them in
`managedSkillOrchestration.additionalTools`.

The export never embeds external credentials as plaintext. It handles them as
follows:

* If the credential was already an exact `{{secret:NAME}}` reference, the
  export keeps `NAME`.
* If Runtype stored the credential, the export writes the reference
  `{{secret:RUNTYPE_AGENT_EXTERNAL_AUTH_AGENT_ID}}`, where `AGENT_ID` is the
  agent's ID.

Provide that secret name through your runtime adapter's `getSecret` and
`resolveSecretReferences` methods, mapped to the matching secret in your own
secret store.

## Deploy an agent with the CLI

The `runtype deploy` command exports one or more agents and writes a
deployment project for Cloud Run, Cloudflare Workers, or Vercel Functions. It
signs in with your stored CLI API key, which needs the `RUNTIME:EXPORT` scope.

To scaffold a Cloudflare Workers deployment, run this command:

```bash
runtype deploy --agent AGENT_ID --target cloudflare
```

The command accepts these options:

* `--agent`: ID of an agent to export. Repeat the option to deploy several
  agents.
* `--target`: `cloud-run`, `cloudflare`, or `vercel`. Defaults to `cloud-run`.
* `--output`: directory for the project. Defaults to `./runtype-deploy`.
* `--name`: project name for `package.json`. Defaults to the output directory
  name.
* `--alias`: export the version that this release alias points at for each
  agent, such as `staging`.
* `--version-id`: export this published version. Requires exactly one
  `--agent`. You can't combine it with `--alias`.

To build a staging image from the version behind the `staging` alias, run this
command:

```bash
runtype deploy --agent AGENT_ID --alias staging --target cloud-run
```

For each agent, the command prints the version it exported and, when an alias
selected it, the alias and its revision.

When it finishes, `runtype deploy` prints the next steps for your target: run
`./setup.sh` in the output directory, then the local test and deploy commands.
The script builds `@runtypelabs/runtime` and installs dependencies. The package
is not published to npm, so the script builds and packs it from the Runtype
source checkout that contains the directory where you ran `runtype deploy`.

The generated server registers a streaming `POST /agent/AGENT_NAME` endpoint
for each agent, where `AGENT_NAME` is the agent's name as it appears in
Runtype. `GET /health` lists the registered agent names.

`runtype deploy` scaffolds agents only. To deploy a flow, attach it to an
agent as a flow runtime tool (`toolType: 'flow'` with `config.flowId`), then
deploy that agent.

### Provide secrets and provider keys

The runtime reads each secret through the adapter's `getSecret` method. This
covers model provider keys such as `OPENAI_API_KEY`, `{{secret:NAME}}`
references, and MCP secret names in the export. The Cloudflare, Cloud Run, and
Vercel adapters read each name from the deployment's environment. A Cloudflare
Worker reads it from its `env` bindings.

`runtype deploy` lists the required secrets in the generated `README.md`, with
a command to set each one, and prints the list when it finishes. The following
commands set a secret for each target:

* Cloudflare Workers: `wrangler secret put SECRET_NAME`
* Vercel Functions: `vercel env add SECRET_NAME`
* Cloud Run: `gcloud run deploy` with `--set-secrets`, or an exported
  environment variable for local runs

Replace `SECRET_NAME` with a name from the list. A deployment without a
required secret fails on its first model or MCP call.

## Run an export in your own code

To host an export without the scaffold, load the export JSON, create a
runtime, and call it. The following sample runs an exported agent on Node:

**`server.ts`**

```ts title="server.ts"
import { readFile } from 'node:fs/promises'
import {
  CloudRunAdapter,
  NoOpRecordStore,
  RuntimeContextStepExecutor,
  RuntimeToolExecutor,
  createRuntime,
  exportedAgentSchema,
  noopLoggerProvider,
} from '@runtypelabs/runtime'

const agent = exportedAgentSchema.parse(
  JSON.parse(await readFile('./agents/research-assistant.json', 'utf-8'))
)

const adapter = new CloudRunAdapter()
const contextStepExecutor = new RuntimeContextStepExecutor({
  loggerProvider: noopLoggerProvider,
  recordStore: new NoOpRecordStore(),
  adapter,
  toolExecutor: () => toolExecutor,
})
const toolExecutor = new RuntimeToolExecutor({
  adapter,
  flowEngineDeps: { adapter, contextStepExecutor },
})

const runtime = createRuntime({
  agents: { [agent.name]: agent },
  keys: { mode: 'own' },
  adapter,
  contextStepExecutor,
  toolExecutor,
})

runtime.assertHostDependencies(agent)

const response = await runtime.executeAgent(agent.name, {
  messages: [{ role: 'user', content: 'Summarize the pricing page at example.com.' }],
})
```

The runtime registers each agent under its key in `agents`, and `executeAgent`
resolves to a streaming `Response`. For a flow export, pass `flows` and call `executeFlow`.

`RuntimeToolExecutor` runs the agent's HTTP, MCP, A2A, sub-agent, and flow
tools. Pass it to `createRuntime` as `toolExecutor`. Without it, every tool call
returns an error to the model, and the run still ends with `execution_complete`.

The runtime and the tool executor share one `RuntimeContextStepExecutor`, so the
steps inside a flow tool, such as `tool-call` and record steps, run the same way
as the agent's own steps. Pass stores and providers, such as `recordStore`,
`memoryStore`, or a `sandbox` for code steps, to `RuntimeContextStepExecutor`
instead of `createRuntime`. To run custom code tools, also pass a `sandbox` to
`RuntimeToolExecutor`.

The `keys.mode` setting chooses how the runtime pays for model calls:

* `own`: The runtime calls each provider directly with the provider key that
  `getSecret` returns.
* `platform`: The runtime sends every model call to the Runtype platform-key
  proxy.
* `hybrid`: The runtime uses your key for the model's direct provider when one
  is set, and falls back to the proxy when it's missing. A model family with no
  direct provider in the runtime, such as `glm-5.2`, always uses the proxy in
  this mode, even when `VERCEL_AI_GATEWAY_API_KEY` is set.

The `platform` and `hybrid` modes require `keys.platformKeyEndpoint`, set to
`https://api.runtype.com/v1/runtime/proxy`, and `keys.platformApiKey`, set to
a Runtype API key with the `RUNTIME:EXECUTE` scope. To create one, select
**Self-hosted Runtime** in the **Permission Template** list of the
**Create New API Key** dialog. For the scope each hosted endpoint requires, see
[Hosted capability endpoints](#hosted-capability-endpoints).

### Declare host-owned agent settings

Some agent settings need your host to enforce them. The runtime rejects an
agent that uses one of these settings before it streams, unless you pass the
setting's name in `handledAgentConfig` on each `executeAgent` call:

```ts
const response = await runtime.executeAgent(name, {
  messages,
  handledAgentConfig: ['piiRedaction', 'tenancyStrategy'],
})
```

The following table lists each value and the agent configuration that
requires it:

| `handledAgentConfig` value | Required when the agent sets |
| -------------------------- | ---------------------------- |
| `artifacts`                | `config.artifacts`           |
| `loggingPolicy`            | `loggingPolicy: 'off'`       |
| `memory`                   | `memory.enabled: true`       |
| `piiRedaction`             | `piiRedaction: 'redact'`     |
| `sandbox`                  | `sandbox.enabled: true`      |
| `temporal`                 | `config.temporal`            |
| `tenancyStrategy`          | `config.tenancyStrategy`     |

An agent that runs with a model and system prompt doesn't need `artifacts`
declared. Declare a value only after your host implements that behavior.

Declaring `tenancyStrategy` tells the runtime that `hostContext` carries the
identity your host admitted. The runtime then enforces the strategy against that
identity before the first model call. For details, see
[Record storage and tenancy scoping](#record-storage-and-tenancy-scoping).

Nested calls don't need `temporal` declared. An `execute-agent` flow step and
an attached subagent tool have no previous turn, so the runtime applies
`config.temporal` itself: `groundNow` adds the current time to the delegated
message, and `injectElapsed` adds nothing. A detached subagent tool still
rejects `config.temporal`, because your `BackgroundRunCoordinator` runs that
child.

For an agent that runs with a model and system prompt, the runtime also
rejects `config.tools.perToolLimits`, and no declaration changes that. Remove
per-tool limits from such an agent before you export it.

If an agent fails before it streams, catch `UnsupportedAgentConfigError` and
read its `unsupportedPaths` property. It names each configuration path, such as
`config.memory`, that the runtime couldn't accept.

## Serve SDK clients that run local tools

An SDK client drives an exported agent whose tools are `toolType: 'local'`
through `POST /v1/dispatch`, a `paused` envelope, and
`POST /v1/dispatch/resume`. `RuntimeToolExecutor` raises
`ClientToolRequiredError` for such a tool. A custom `ToolExecutor` receives
local tools like any other tool.

The `@runtypelabs/runtime/host/dispatch` module provides that host as a WHATWG
`fetch` handler. It parks each local tool call in memory, answers with the same
events that the Runtype API streams, and continues the turn when the resume
arrives. To use it, point an unchanged SDK client at the host by changing the
client's base URL. When you set `upstream`, the host forwards any request that
it does not handle to the Runtype API with the caller's API key.

The following sample creates a dispatch host that serves one set of exported
agents:

```ts
import { CloudRunAdapter, createRuntime } from '@runtypelabs/runtime'
import { createDispatchHost } from '@runtypelabs/runtime/host/dispatch'

const host = createDispatchHost({
  runtime: (deps) =>
    createRuntime({
      ...deps,
      agents: AGENTS,
      keys: { mode: 'own' },
      adapter: new CloudRunAdapter(),
    }),
  authorize: async (request) =>
    request.headers.get('authorization') === `Bearer ${process.env.HOST_TOKEN}`,
  upstream: { baseUrl: 'https://api.runtype.com' },
})
export default { fetch: host.fetch }
```

Replace the following:

* `AGENTS`: the exported agents, keyed by agent name, as in
  [Run an export in your own code](#run-an-export-in-your-own-code).
* `HOST_TOKEN`: an environment variable that holds the token your SDK clients send.

The host does not validate the API key that the SDK client sends, so it
refuses every request with `401` until you set `authorize`. Pass your own
check, or the literal `'allow-unauthenticated'` for a host that only a trusted
network can reach.

The host also accepts these options:

* `checkpointStore`: A `CheckpointStore` that makes local-tool pauses durable,
  so a resume works after a process restart.
* `concurrency`: How the host handles overlapping turns in one conversation:
  `reject`, `supersede`, or `queue`. The `x-runtype-concurrency` request header
  overrides it.
* `executeOptions`: Timeouts and other options that the host passes to every
  `executeAgent` call, including `handledAgentConfig`. For the settings that
  `handledAgentConfig` declares, see
  [Declare host-owned agent settings](#declare-host-owned-agent-settings).
* `resolveHostContext`: A function that returns the end user and tenant for one
  request. For details, see
  [Give each request its own end user](#give-each-request-its-own-end-user).

### Give each request its own end user

Tools that reference `{{_endUser.*}}` or `{{_tenant.*}}`, and tools that send
`{{_identity.token}}`, need a verified identity for each request. The host
ignores the `endUser`, `tenant`, and `identityProof` fields in the dispatch
body, because any caller that passes `authorize` can set them. To admit an
identity, verify the caller in `resolveHostContext` and return the identity as
a `hostContext`:

```ts
const host = createDispatchHost({
  // ...runtime and authorize as in the previous sample
  resolveHostContext: async (request, dispatch) => {
    const session = await verifySession(request.headers.get('authorization'))
    if (!session) return new Response('Unauthorized', { status: 401 })
    return {
      endUser: { id: session.userId, assurance: 'verified' },
      tenant: { id: session.orgId, assurance: 'verified' },
    }
  },
})
```

Replace `verifySession` with your own check. It receives the request and the
parsed dispatch body.

The function can return one of the following values:

* A `hostContext` object: The host merges it over `executeOptions.hostContext`
  key by key. A key that you omit keeps the host-wide value.
* `undefined`: The turn runs with `executeOptions.hostContext` only.
* A `Response`: The host returns that response and does not start the turn.

If the function throws, the host returns `500` with the code
`HOST_CONTEXT_RESOLUTION_FAILED`.

The host calls `resolveHostContext` once, when the turn starts. A resume runs
with the context that the turn started with, including the host-wide part, even
if `executeOptions.hostContext` has changed since. With a
`checkpointStore`, the host saves that identity in the checkpoint, so a resume
after a restart also uses it. To make `{{_identity.token}}` name the end user
or tenant, set `assurance` on each identity, and pass an identity signer to
your adapter as `mintIdentityAssertion`.

The same identities satisfy an agent's `tenancyStrategy`. The runtime reads the
`assurance` that you set on each identity when it checks the strategy's
assurance floor. If `hostContext` doesn't carry the scope that the strategy
requires, the turn fails with `TenancyLockdownRefusedError` before the first
model call. For details, see
[Record storage and tenancy scoping](#record-storage-and-tenancy-scoping).

The following sample serves an agent whose `tenancyStrategy` uses the
`end-user-isolated` preset and whose tools send `{{_identity.token}}`. It
combines the three pieces that such an agent needs: `resolveHostContext` admits
the end user, `executeOptions.handledAgentConfig` declares that the host
enforces `tenancyStrategy`, and `mintIdentityAssertion` signs the token.

```ts
const host = createDispatchHost({
  runtime: (deps) =>
    createRuntime({
      ...deps,
      agents: AGENTS,
      keys: { mode: 'own' },
      adapter: new CloudRunAdapter({ mintIdentityAssertion: signIdentity }),
    }),
  authorize: async (request) =>
    request.headers.get('authorization') === `Bearer ${process.env.HOST_TOKEN}`,
  executeOptions: { handledAgentConfig: ['tenancyStrategy'] },
  resolveHostContext: async (request) => {
    const session = await verifySession(request.headers.get('x-session-token'))
    if (!session) return new Response('Unauthorized', { status: 401 })
    return { endUser: { id: session.userId, assurance: 'verified' } }
  },
})
```

Replace `signIdentity` with your identity signer and `verifySession` with your
own check. Without `handledAgentConfig`, every turn of the agent fails with
`UnsupportedAgentConfigError`. If `resolveHostContext` admits no end user, the
turn fails with the code `end_user_scope_required` before the first model call.
A JSON response carries that code with status `403`. A streamed response has
already sent status `200`, so it carries the code in an `execution_error` event.

The host does not serve `POST /v1/dispatch/approve` or `history: 'stored'`
turns. It persists no transcript, so a stored-history dispatch returns
`STORED_HISTORY_UNSUPPORTED`. Send the full conversation in `messages`, or keep
that conversation on the Runtype API.

## Execution timeouts

Set execution deadlines on each `executeFlow` or `executeAgent` call:

```ts
const response = await runtime.executeAgent(name, {
  messages,
  stepTimeoutMs: 30_000,
  flowTimeoutMs: 60_000,
  wallClockTimeoutMs: 300_000,
})
```

* `stepTimeoutMs` limits one step attempt. An authored fallback retry receives a fresh attempt
  budget.
* `flowTimeoutMs` limits each virtual-flow, reflection, or final-reply turn. Each
  turn receives a fresh relative budget. For a delegated external agent, it limits the whole A2A
  call, including retries.
* `wallClockTimeoutMs` is the hard budget for the whole agent invocation. Every primary,
  reflection, and final-reply turn receives only the execution's remaining time, and the runtime
  releases the caller even when a provider or tool promise ignores cancellation. Expiry during a
  primary turn fails the execution with `AGENT_TURN_DEADLINE_EXCEEDED`. Expiry between completed
  turns remains a normal `stopReason: 'timeout'` loop stop. Delegated external agents apply the
  same hard cap to their A2A call. When it and `flowTimeoutMs` both apply, the earlier finite
  deadline wins.

An external-agent input checkpoint persists `flowTimeoutMs`. The resumed peer
turn receives a fresh relative budget, so time spent waiting for human input is
not charged. To turn off only `flowTimeoutMs`, pass `Infinity`.

## Hosted capability endpoints

The exported runtime uses these hosted endpoints for platform capabilities.
Authenticate with a Runtype API key in the `Authorization: Bearer` header. The
API derives tenant identity from the authenticated API key, not from request
body fields.

The following table lists the hosted endpoint families, their capabilities, and
the API key scope each one requires:

| Endpoint                       | Capability                                                                  | Required scope    |
| ------------------------------ | --------------------------------------------------------------------------- | ----------------- |
| `POST /v1/runtime/proxy`       | Platform-key proxy for model calls in `platform` and `hybrid` key modes     | `RUNTIME:EXECUTE` |
| `POST /v1/runtime/memory/*`    | Hosted `MemoryStore` for saving, recalling, and summarizing memories        | `RUNTIME:EXECUTE` |
| `POST /v1/runtime/records/*`   | Hosted `RecordStore` for upserting, getting, querying, and updating records | `RUNTIME:EXECUTE` |
| `POST /v1/runtime/crawl`       | Hosted `SiteCrawler` for synchronous crawls                                 | `RUNTIME:EXECUTE` |
| `POST /v1/runtime/email/send`  | Hosted `EmailSender` for sending a rendered email                           | `RUNTIME:EXECUTE` |
| `POST /v1/runtime/search`      | Hosted `WebSearcher` for Exa searches                                       | `RUNTIME:EXECUTE` |
| `POST /v1/runtime/assets`      | Hosted `AssetStore` for storing an asset                                    | `ASSETS:WRITE`    |
| `POST /v1/runtime/judgments`   | Hosted Decision judgments in `platform` and `hybrid` key modes              | `RUNTIME:EXECUTE` |
| `POST /v1/runtime/extractions` | Hosted extract-step field extraction in `platform` and `hybrid` key modes   | `RUNTIME:EXECUTE` |

The **Self-hosted Runtime** permission template grants `RUNTIME:EXECUTE` and
`TELEMETRY:WRITE`, which covers every endpoint in the table except
`POST /v1/runtime/assets`. `RUNTIME:EXECUTE` lets a key call these endpoints and
nothing else. Unlike `FLOWS:EXECUTE` or `AGENTS:EXECUTE`, it can't run your
hosted Flows or Agents.

Keys created before `RUNTIME:EXECUTE` existed keep working. The proxy, memory,
records, judgments, and extractions endpoints also accept `FLOWS:EXECUTE` or `AGENTS:EXECUTE`, and the
crawl, email, and search endpoints also accept `FLOWS:EXECUTE`. Runtype records
each call that one of these older scopes admits, so plan to move such keys to
`RUNTIME:EXECUTE`.

> **Warning**
>
> Until November 9, 2026, `POST /v1/runtime/proxy` still accepts an API key without an execution
> scope, such as a key with only `TELEMETRY:WRITE`. It returns the `x-runtype-warning` response
> header, and the runtime logs that warning once per process. From November 9, 2026, the proxy
> refuses such a key with `HTTP 403`. Add `RUNTIME:EXECUTE` to the key before then.

Every `/v1/runtime/*` endpoint requires an Enterprise plan and
returns `HTTP 403` with `BYOC_PLAN_REQUIRED` for an account without one. For
the full request and response schemas, see the
[API Reference](/api-reference).

`POST /v1/runtime/crawl` serves a synchronous crawl only. It is not a host for
the `crawl` flow step, which parks a durable pause and declares a
`durable-pause-host` dependency instead. For more information, see
[Host dependencies](#host-dependencies).

Use an API key with `RUNTIME:EXECUTE` for proxy, memory, records, judgments,
extractions, crawl, email, and search requests. Use an API key with `ASSETS:WRITE` for
`POST /v1/runtime/assets`.

The public `POST /v1/assets` endpoint is separate from self-hosting and is
available on every plan. The runtime endpoint, `POST /v1/runtime/assets`,
requires an Enterprise plan.

## Report telemetry

`RuntimeConfig.telemetry` reports the deployed runtime's executions to Runtype
as OpenTelemetry traces, posted to `POST /v1/otel/v1/traces`. Reporting is not
part of the hosted endpoint families and does not require an Enterprise plan.

A reported run reaches the same fidelity as one Runtype ran itself: it appears
in the **Runs** view with its trace tree, transcript, tool arguments and
results, token usage, and a display-only cost estimate, and it can be captured
as an eval case.

```ts
const runtime = createRuntime({
  // ...
  telemetry: {
    enabled: true,
    apiKey: process.env.RUNTYPE_API_KEY,
  },
})
```

| Field             | Effect                                                                                                                                                                                         |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `enabled`         | Turns reporting on.                                                                                                                                                                            |
| `apiKey`          | The Runtype API key that authorizes the export.                                                                                                                                                |
| `baseUrl`         | The Runtype API origin. Defaults to `https://api.runtype.com`.                                                                                                                                 |
| `content`         | Sends messages, tool arguments, and tool results. Defaults to `true`. Set it to `false` to report a run without conversation content, which also removes the transcript and eval capture.      |
| `contentMaxChars` | Caps each content value, in characters. Defaults to 262,144, the most the ingest stores, and can only be lowered. The system prompt has its own budget, so a long history doesn't truncate it. |
| `sampleRate`      | Fraction of runs to report, decided once per run.                                                                                                                                              |
| `piiRedaction`    | See [Redacting PII from telemetry](#redacting-pii-from-telemetry).                                                                                                                             |

The scaffolds that `runtype deploy` generates read the same settings from the
environment, so you can change them without editing code. A Cloudflare Worker
reads them from its `env` binding instead of `process.env`.

| Variable                        | Effect                                                          |
| ------------------------------- | --------------------------------------------------------------- |
| `RUNTYPE_API_KEY`               | Turns reporting on.                                             |
| `RUNTYPE_TELEMETRY=off`         | Stops reporting even when the key is set.                       |
| `RUNTYPE_TELEMETRY_CONTENT=off` | Reports runs without messages, tool arguments, or tool results. |
| `RUNTYPE_API_URL`               | Overrides the Runtype API origin.                               |

Use a separate API key with the `TELEMETRY:WRITE` scope. To create it, select
**Telemetry Ingest** in the **Permission Template** list of the **Create New
API Key** dialog. Ingest also accepts keys with `AGENTS:WRITE`, but ingest is
append-only, so give this API key no other scopes.

Use a separate API key for the hosted endpoints. A shared API key also needs
the execution scopes for those endpoints, which gives it more access than
telemetry requires. If you use one key for both, create it from the
**Self-hosted Runtime** template.

> **Warning**
>
> `POST /v1/telemetry/ingest` is the deprecated lifecycle endpoint. It carried pipeline rows only:
> no run, no trace tree, and no transcript. The runtime posts to it only when you set
> `telemetry.endpoint` explicitly, which no current deployment needs. Remove that field to report to
> the OpenTelemetry endpoint.

### Redacting PII from telemetry

Telemetry payloads carry step output, tool results, and agent completions, so
they can contain end-user personal data. Set `telemetry.piiRedaction` to
`'redact'` to have Runtype mask detected PII (emails, phone numbers, and the
rest of the detector set) before it is stored:

```ts
const runtime = createRuntime({
  // ...
  telemetry: {
    enabled: true,
    apiKey: process.env.RUNTYPE_TELEMETRY_API_KEY,
    piiRedaction: 'redact',
  },
})
```

An individual agent's `config.piiRedaction` takes precedence over this
deployment-wide setting, in both directions: an agent exported with
`piiRedaction: 'redact'` is redacted even when the deployment sets nothing, and
an agent exported with `piiRedaction: 'off'` is not redacted even when the
deployment sets `'redact'`. `'default'` on an agent defers to the deployment
setting.

If an exported agent sets `piiRedaction: 'redact'`, pass
`handledAgentConfig: ['piiRedaction']` on each `executeAgent` call. Without
it, the runtime rejects the agent before it streams. For more information, see
[Declare host-owned agent settings](#declare-host-owned-agent-settings). The
deployment-wide `telemetry.piiRedaction` setting needs no declaration, because
it is not agent config.

The runtime resolves the policy per execution and sends it with each event, so
one deployment can run redacted and unredacted agents side by side. Omitting
both settings leaves redaction off. Secret redaction is always applied and is
not affected by this setting.

If you need to report on an agent that runs entirely outside Runtype, see
[Reporting telemetry from an external agent](/developer-guides/guides/reporting-external-telemetry).

## Configure runtime providers

Some steps need a provider that you supply in the `createRuntime` config. No
hosted `/v1/runtime/*` endpoint handles managed fetch, embedding models outside
the built-ins, or collection validation.

### Steps that reach an unconfigured provider

If you omit a provider, `createRuntime` uses a no-op default that raises a
`ProviderNotConfiguredError`, such as its subclass
`RecordStoreNotConfiguredError`. The error's `providerKind` names the
`RuntimeConfig` field to set. The following table shows how a step's
`errorHandling` setting handles that error:

| `errorHandling` | Result                                                                                                             |
| --------------- | ------------------------------------------------------------------------------------------------------------------ |
| Not set         | The step returns `success: false` and writes no output variable. The flow continues and counts the step as failed. |
| `'continue'`    | The step returns `success: true`, writes `defaultValue` to its output variable, and reports the error in `error`.  |
| `'fail'`        | The step fails and stops the flow.                                                                                 |

When `errorHandling` is unset, a failed step's metadata carries
`providerNotConfigured` with the provider kind. An explicit `'fail'` stops the
flow without adding this metadata field. Without `errorHandling`, a missing
provider can't look like a successful step.

The following table lists the steps and the provider each one needs:

| Steps                                                   | Provider (`RuntimeConfig` field)    |
| ------------------------------------------------------- | ----------------------------------- |
| `upsert-record`, `update-record`                        | `recordStore`                       |
| `save-memory`, `recall-memory`, `memory-summary`        | `memoryStore`                       |
| `store-asset`                                           | `assetStore`                        |
| `generate-pdf`                                          | `documentRenderer` and `assetStore` |
| `send-email`                                            | `emailSender`                       |
| `crawl`                                                 | `siteCrawler`                       |
| `search` with a search-API provider                     | `webSearcher`                       |
| `fetch-url` with `fetchMethod` `firecrawl` or `massive` | `managedFetchExecutor`              |
| `extract`                                               | `extractionProvider`                |

Other steps handle a missing provider in their own way:

* `get-record` and `list-records` return `success: false` for any
  `errorHandling` setting.
* `generate-embedding`, `store-vector`, and `vector-search` return
  `success: false` unless you set `errorHandling` to `'continue'`.
* A `template` step without a `templateRenderer` returns `success: false` for
  any `errorHandling` setting.
* A `transform-data` step without a `sandbox` returns `success: true` with
  `defaultValue` unless you set `errorHandling` to `'fail'`.

### Firecrawl and Massive fetch methods

Standard HTTP requests in `fetch-url` steps run through the runtime adapter.
For Firecrawl or Massive, supply a `ManagedFetchExecutor` through
`RuntimeConfig.managedFetchExecutor`. The executor resolves credentials,
enforces provider policy, and sends the requests.

If you omit `ManagedFetchExecutor`, the default context-step executor returns a
not-supported error instead of calling the provider. With `errorHandling` not
set, the step returns `success: false`. For details, see
[Steps that reach an unconfigured provider](#steps-that-reach-an-unconfigured-provider).
If you supply a custom `contextStepExecutor`, that executor handles the
managed-fetch integration.

### Embedding models beyond the built-ins

`generate-embedding` steps and the query embedding inside `vector-search` use
the runtime adapter for these OpenAI and Google model IDs:
`text-embedding-3-small`, `text-embedding-3-large`, `text-embedding-ada-002`,
`text-embedding-004`, and `gemini-embedding-001`. The adapter resolves
`OPENAI_API_KEY` or `GOOGLE_API_KEY` for these models.

For any other embedding model ID, supply an `EmbeddingProvider` through
`RuntimeConfig.embeddingProvider`. This includes Weaviate vectorizer IDs such
as `text2vec-openai`, `text2vec-cohere`, `text2vec-huggingface`, and the
`Snowflake/snowflake-arctic-embed-*` family. Your Weaviate instance produces
these vectors, so the export does not include their credentials or
configuration.

If you omit an `EmbeddingProvider`, the affected steps fail with
`EmbeddingProviderNotConfiguredError` and name the model. They do not fall
back to another model. Built-in models continue to use their native provider
when you configure an `EmbeddingProvider`.

For tests and offline runs, use `StaticEmbeddingProvider`. It is not a real
embedder. It returns a vector derived from a hash of the input text.

> **Warning**
>
> Inspect each step's `embeddingModel` before deployment. A flow can use a built-in model in one
> step and require an `EmbeddingProvider` in another.

### Decision judgments

The Decision judgment tool (`builtin:decision_judge`) runs in the runtime, as
an agent tool and from a `tool-call` step. A configured instance judges with
the question set that the export embeds in the tool's `config.questionSet`.
For what the export embeds, the
credentials that each decision model needs, and how each key mode routes a
judgment, see
[Run judgments in a self-hosted runtime](/user-guide/flows/judgments#run-judgments-in-a-self-hosted-runtime).

To judge with your own implementation, supply a `DecisionProvider` through
`RuntimeConfig.decisionProvider`. It implements `judge(request)`. To judge a
configured instance whose question set the export didn't embed, it can also
implement `judgeByReference(request)`. For tests and offline runs, use `StaticDecisionProvider`. It makes no network
call and answers each `boolean` question with `0.9`, each `choice` question
with its first option, and each `score` question with its highest level. To
set an answer, pass a map of question name to answer to its constructor.

If you set no `decisionProvider` and the key mode gives the runtime no
credential and no platform key for the model, the tool call fails with
`DecisionProviderNotConfiguredError`. The message names the secret or binding
that the model needs. The agent receives the error as a failed tool call, and
the run continues.

If you pass a `builtinToolExecutor` to `RuntimeToolExecutor`, that executor
runs the Decision judgment tool and the runtime doesn't use a
`DecisionProvider`.

### Extract step

The `extract` step reads typed fields through the runtime's
`ExtractionProvider`. If you set no `extractionProvider`, the key mode picks
one. With `own` keys, the runtime extracts with the Workers AI binding that
`adapter.getBinding("AI")` returns. With `platform` keys, it calls
`POST /v1/runtime/extractions`. With `hybrid` keys, it tries the binding first
and then Runtype. For tests and offline runs, use `StaticExtractionProvider`.
It makes no network call and answers each field with its override, or `null`.

If no provider can serve the step, it fails with
`ExtractionProviderNotConfiguredError`, and `errorHandling` applies as described in
[Steps that reach an unconfigured provider](#steps-that-reach-an-unconfigured-provider).
For the models and key modes, see
[Self-hosted runtimes](/user-guide/flows/extraction#self-hosted-runtimes).

### Record storage and tenancy scoping

The record steps, including `upsert-record`, `get-record`, `list-records`, and
`update-record`, use `RuntimeConfig.recordStore`. The runtime includes
`InMemoryRecordStore` for stateless agents and tests, and `HostedRecordStore`
for the hosted `POST /v1/runtime/records/*` endpoints. The default
`NoOpRecordStore` raises `RecordStoreNotConfiguredError` when a record step
runs. With `errorHandling` not set, the step returns `success: false`.

If an exported flow or agent includes `tenancyStrategy`, the runtime enforces
it on every top-level `executeAgent` and `executeFlow` call. An agent also needs
`tenancyStrategy` in `handledAgentConfig`. Without it, the runtime rejects the
agent with `UnsupportedAgentConfigError`.

Pass the identity that your host verified for the execution as
`hostContext.tenant` and `hostContext.endUser`. Before the first model call,
the runtime checks that identity against the strategy's preset:

* `internal`: No identity is required. Records use the product scope.
* `tenant-isolated`: Requires `hostContext.tenant`. Records are scoped to that
  tenant.
* `end-user-isolated`: Requires `hostContext.endUser`. Records are scoped to
  that end user, within `hostContext.tenant` when you pass one.

The runtime reads each identity's assurance level from
`hostContext.assurance.tenant` or `hostContext.assurance.endUser`. If you don't
set that, it uses the `assurance` field on the identity object itself, such as
`endUser: { id: 'u_123', assurance: 'asserted' }`. An identity with neither
counts as `verified`, because your host admitted it. A strategy's assurance
floor is `verified` unless the strategy lowers it to `asserted`.

When the check fails, the call rejects with `TenancyLockdownRefusedError` and
makes no model call. The error's `code` property is one of the following values:

* `tenant_scope_required`: The strategy requires a tenant, and `hostContext`
  has none.
* `tenant_assurance_below_floor`: The tenant's assurance is below the floor.
* `end_user_scope_required`: The strategy requires an end user, and
  `hostContext` has none.
* `end_user_assurance_below_floor`: The end user's assurance is below the floor.

The runtime derives the record scope from the strategy and the admitted
identity. It keys each scope by the identity's `projectedId`, or by its `id`
when `projectedId` is absent. An agent execution passes its scope to the flows
that it runs. For an agent with `tenancyStrategy`, the runtime doesn't read
`_tenant` or `_endUser` from the `variables` that the caller passes. These
values come only from `hostContext`.

If your host runs its own tenancy check and resolves a record scope itself,
pass that scope as `ExecuteAgentOptions.recordScope` or
`ExecuteFlowOptions.recordScope`. The runtime then skips its own check and uses
your scope unchanged. A `null` scope makes every record step fail instead of
falling back to the global namespace.

Your `RecordStore` must also support scopes. Set `supportsRecordScope = true`
on your implementation, and honor the `scope` descriptor on every call. Filter
every read, including the `update-record` target lookup and the record input of
`generate-embedding`. Add the scope columns on insert. Partition upsert conflict
resolution by scope, so that two tenants can each have a record with the same
`(type, name)`.

> **Warning**
>
> If an execution carries a resolved scope but the store does not declare support for it, the record
> step fails with `RecordScopeUnsupportedError`. The step does not read or write the global
> namespace. `HostedRecordStore` does not declare record-scope support because the hosted API does
> not accept a scope descriptor, so the server cannot verify the execution's tenant.

If you omit `tenancyStrategy`, the runtime uses the plain owner namespace.

### Collection schema validation

If you register a record type as a collection with a metadata schema, the
`upsert-record` and `update-record` steps call
`RuntimeConfig.collectionValidator` before each write. `upsert-record` merges
on conflict inside the store, so validation checks the incoming payload for
each batch item. `update-record` reads the target row first, so validation
checks the post-merge metadata.

The validator exposes two methods:

* `validate(type, metadata)`: Returns the verdict for writing `metadata` as a
  record of `type`. It does not throw. If the collection lookup fails, it
  returns `{ status: 'lookup_failed', type }`. Do not return `unevaluated` for
  a failed lookup, because the write would then skip validation.
* `prime(type)`: Warms the collection lookup without validating data. Call it
  before validation inside an open transaction. A cold lookup in that
  transaction can deadlock a single-session driver. If the lookup fails during
  `prime`, keep returning `lookup_failed` for that type so the transaction does
  not query again.

With an `enforce` or `lookup_failed` verdict, the step rejects the write. A
`lookup_failed` rejection contains `COLLECTION_LOOKUP_FAILED`. The step's
`errorHandling` setting controls the failure, as it does for a payload-limit
violation. With a `warn` verdict, the step writes and adds `schemaWarnings` to
the step metadata. The `upsert-record` batch path does not report warnings.

The verdict is stored in the `schemaValid` write field. A store that persists
that field records the same validation result as Runtype. For validation modes
and violation details, see [Defining a schema](/user-guide/records/defining-a-schema).

The hosted record endpoints do not evaluate collection schemas. If your
self-hosted deployment needs enforcement, supply a validator backed by your
collection definitions.

> **Warning**
>
> If you omit the validator, every verdict is `unevaluated`. Writes proceed without a `schemaValid`
> stamp. This behavior is intentional because validation is an owner-configured contract for
> metadata shape, not a security boundary. A collection set to `enforce` in the dashboard is not
> enforced by an exported flow or agent until you wire a validator.

## Next steps

Continue with these guides:

* [Runtime tools](/developer-guides/guides/runtime-tools): attach a flow to an agent as a runtime tool
* [Reporting telemetry from an external agent](/developer-guides/guides/reporting-external-telemetry): report runs from agents that Runtype didn't author
* [Agent versioning and publishing](/user-guide/agents/agent-versioning-and-publishing): control which sub-agent version an export captures
* [API Reference](/api-reference): inspect full request and response schemas
* [Logging policy and PII redaction](/user-guide/logs/logging-policy-and-redaction): control verbose logging and PII redaction, and see which setting wins