Manage Agents as code
Keep your Agent definitions in your repository. Review and version them with your code, then converge them at deploy time. The dashboard displays the definition that your deployment writes.
Why manage Agents as code?
When you edit an Agent in the dashboard, its system prompt and configuration live outside your codebase. You cannot review those changes with your code, and definitions can drift across environments. Managing Agents as code gives CI a repeatable update path: CI converges the definition, and the dashboard displays the deployed result.
The SDK method ensure is a convergence operation, not a save operation. It makes the platform definition match the object you provide and does nothing when they already match. After the definition matches, repeating the same call makes no content changes.
Define the Agent
defineAgent is a local constructor that does not make network requests. It validates the definition shape and returns the canonical definition object. To keep the definition portable across environments, use builtin:, platform:, or mcp: references, or reference a saved tool with tool:<name>. The constructor rejects account-scoped IDs that start with tool_. For more information, see Reference saved tools by name.
The following example defines a pricing assistant:
To bootstrap a definition from an Agent that you built in the dashboard, open the Agent editor. Press ⌘K on macOS or Ctrl+K on other platforms to open the command palette, then run Get code. The dialog includes your current configuration, including unsaved edits, and generates a defineAgent block with agents.ensure and drift-check examples.
If the Agent references a saved tool by an account-scoped ID that starts with tool_, the generated code marks each reference with a comment. Replace each one with a portable reference or a tool:<name> reference before you converge. Get code supports standard Runtype Agents, not external A2A (Agent-to-Agent) or Claude-managed Agents. The flow editor provides the same command for flows.
An Agent’s identity is its name and your account scope, which is organization or personal. The API endpoint and credentials determine the environment. Point the client at staging and use staging credentials to converge staging. Point it at production and use production credentials to converge production. You do not need per-environment ID files or a state file.
Renaming a definition does not rename the Agent. ensure creates an Agent under that name and
leaves the old Agent in place because it never deletes. Treat the name as the stable identity.
Reference saved tools by name
Built-in, platform, and Model Context Protocol (MCP) tools have portable IDs that you can hardcode. Saved tools receive a per-account, per-environment ID with the tool_ prefix. This includes external HTTP tools, custom code tools, and flow tools that you build in the dashboard. Do not include those IDs in a portable definition. Reference a saved tool by its exact name with tool:<name>:
Set approval.choices.alwaysAllow to true to show an Always allow button in the approval
prompt. Runtype stores a persistent grant when an end user selects it, so future dispatches for
that Agent skip the prompt for that tool.
View or revoke grants in the Agent editor under Safety, or use GET /v1/tool-approval-grants and DELETE /v1/tool-approval-grants/{id}. For more information, see Tool approval grants.
tool:<name> uses the tool’s exact, case-sensitive name, including spaces or colons. The name is the canonical reference used for hashing. The same name and definition produce the same content hash in each environment. Runtype resolves the name to the saved tool at runtime on every dispatch.
Converge the tool before or after the Agent. You can recreate the tool without running ensure again because the reference resolves at runtime.
Use ensure, including dryRun, to validate each named reference during convergence:
- A missing name:
ensurefails with an error. Create the tool before you run the agent deployment step so the missing dependency appears in CI. - A duplicate name:
ensurefails and identifies both tools. Rename one tool before you converge the Agent. - A duplicate created after a successful converge: dispatch continues using the older tool and logs a warning instead of failing a live conversation.
Use tool:<name> anywhere you reference the tool. Use it in toolIds, the keys of toolConfigs and perToolLimits, approval.require, and the subagentConfig and codeModeConfig tool pools. The ensure surface checks that the referenced tool exists when the name appears in toolIds, subagentConfig.toolPool, or codeModeConfig.toolPool. It treats approval.require references as runtime name patterns, so it accepts wildcards such as mcp:* without checking existence at converge time. Raw IDs that start with tool_ remain rejected.
When you pull an Agent built in the dashboard, pull reverse-maps saved-tool IDs to tool:<name>. You can paste the returned definition into your code. If pull cannot safely emit a name, it leaves the raw ID and adds a warnings entry. This happens when the tool was deleted or its name is shadowed by an older same-named tool. The warning explains the fix.
Migrating an existing Agent from IDs to names changes its stored configuration, so the first ensure after the migration returns updated.
Saved subagents and flow tools
When an Agent delegates to a saved subagent or calls a saved flow as a tool, reference those resources by name:
agent:<name>: the saved subagent’sconfig.agentId.flow:<name>: the flow-as-tool runtime tool’sconfig.flowId.
The following example references a saved subagent and a saved flow:
Raw IDs with the agent_ or flow_ prefix are rejected on the ensure surface. ensure checks that each referenced Agent or Flow exists and has one matching name at converge time. pull maps those IDs to the name forms. An inline subagent or inline flow embeds its definition, so it needs no reference.
Converge at deploy time
To converge the definition, configure the SDK with an API key and call Runtype.agents.ensure:
The result includes result, agentId, versionId, and contentHash. The result value is unchanged, created, or updated.
ensure starts with a hash-only probe. When the definition matches, the SDK writes nothing. When it differs, ensure writes the live Agent configuration and appends an immutable version snapshot. A dashboard edit that you overwrite remains recoverable in version history.
The server computes contentHash from the canonical, normalized definition. The SDK uses that hash for later probes. Formatting differences in your local object do not cause repeated updates.
Deploying the converged version
By default, ensure writes the saved configuration and a draft version, and moves no release alias. To deploy the version that ensure creates, name the alias to activate:
deploy is the canonical input. release: 'publish' is the compatibility spelling of deploy: { alias: 'live' } and release: 'none' of omitting both, and they keep working. Sending release and deploy in the same call is refused with 400.
The response carries a deployment object describing what the pointer now selects: the alias, its versionId and versionNumber, the alias revision to quote back on a later compare-and-swap, the receiptId of the deployment receipt this converge appended, and changed, which is false when the pointer already selected this version.
Labeling the version
Pass an optional version object to stamp provenance on the version row that ensure appends, so version history answers which commit produced it:
Both fields are optional. label holds up to 100 characters (typically a git SHA or a release tag) and notes up to 2000. Omit version to keep the default sdk-ensure label and the server-generated notes.
Deploy a preview
Name any alias other than live and the converge becomes a preview deployment: it saves a candidate version and aims that pointer at it, and it touches nothing else. The saved configuration, the content hash, the capabilities, and the draft pointer all stay where they were, so live keeps serving what it served before.
The same converge from the CLI:
Things to know before you wire this into a pipeline:
- A preview converge decides “unchanged” against the preview. It compares the definition with the version this alias was last deployed from, not with the live configuration, so a preview that already matches reports
unchangedand moves nothing. onConflictandexpectedRemoteHashare refused with400on a preview deploy. Both describe writes to the live configuration, and a preview deploy performs none. Drop them, or deploy tolive.- An unchanged converge still renews the preview. The 14-day expiry restarts on every successful converge, including one that changed nothing.
- A preview deploy of an unknown Agent creates the Agent. That Agent is created undeployed: the preview alias points at the candidate and
liveis not aimed at anything. - Aliases are an organization feature. A personal-account credential gets
400withcode: "alias_requires_organization". - Preview quotas apply. Past 50 active previews in the organization or 10 on one Agent, the converge answers
429withcode: "PREVIEW_ALIAS_LIMIT".
To make a preview’s tools call that pull request’s own environment while the version content stays identical to what live runs, pass deploy.bindings. See Per-alias secret bindings.
A pull-request workflow
Two jobs give a pull request its own running Agent and clean up after itself. The first converges a preview and grades it; the second archives the pointer when the pull request closes.
The grading step assumes the repository already holds at least one *.eval.ts suite targeting this Agent, because runtype eval run discovers suites from those files and --alias only chooses which version they run against. Write one first, following Manage evals as code, or drop that step.
Give the workflow’s credential the AGENTS:DEPLOY:PREVIEW scope and not AGENTS:DEPLOY:LIVE. A preview pipeline that cannot move live is the guarantee, not a policy the workflow chooses to honor.
agents aliases archive ALIAS --all archives that alias name on every Agent in the organization, which is what a closed pull request wants when one branch converged several Agents. Add --dry-run to see what it would archive first. To archive one pointer on one Agent, pass both: runtype agents aliases archive AGENT_ID ALIAS.
Archiving is not a delete. The alias keeps its deployment history, executions already admitted run to completion, and the memory the preview wrote stays where it is. See the namespace ladder for keeping preview memory out of production in the first place.
Per-environment values
Keep the definition byte-identical across environments and let the values vary underneath it:
- Reference credentials and environment-specific endpoints as
{{secret:NAME}}and resolve them from the organization’s secrets. The definition then carries the name, not the value, and its content hash is the same everywhere. - Reference saved tools as
tool:NAME, saved Agents asagent:NAME, and Flows asflow:NAME, so each organization resolves its own row at run time. - When one preview genuinely needs a different value for the same name, bind it to the alias rather than editing the definition. See Per-alias secret bindings.
Rendering a different value into the definition itself, such as an inline URL or a different model, produces a different artifact. An evaluation of the staging artifact says nothing about the production one, so do not carry an eval claim across a render.
Promote across organizations
Hard isolation between environments is the organization boundary: separate organizations, separate credentials, separate data. To move a definition from one to the other, use the four-step promotion recipe. Each step takes an explicit source and target credential, read from environment variables so a key never appears in a command line or a CI log.
Replace the following:
YOUR_SOURCE_API_KEY: an API key for the organization the definition comes from, such as stagingYOUR_TARGET_API_KEY: an API key for the organization you are deploying into, such as productionYOUR_SUITE_ID: an eval suite in the target organizationPricing Assistant: the Agent name, which is the ensure identity in both organizations
What each step does:
preparepulls the definition from the source organization and stages it at a preview alias in the target. The target’sliveis untouched. It writespromotion.json, which records the Agent name, the source Agent id, version id, content hash and commit, and the target Agent id, staged version id, alias, and alias revision. Pass--manifestto write it somewhere else.validatere-plans the staged candidate in the target organization and reports every named reference it could not resolve there, such as atool:NAMEthat exists in staging and not in production. It exits non-zero when any reference is unresolved.evaluateruns an eval suite in the target organization pinned to the exact staged version. It reports the result. Nothing gates activation on it, so read the result and decide.activatedeploys that exact staged version id at the target pointer. It reads the destination alias first and quotes that alias’s current revision asIf-Match, not the staged preview’s revision from the manifest, then links the source ids, hashes, and commit on the deployment receipt.--aliasdefaults tolive.--idempotency-keydefaults to one derived from the manifest, so a retried job replays rather than deploying twice.
Credentials never cross the boundary. Nothing in the manifest is a secret value, and the target organization’s own credentials decide what the activation is allowed to do. The manifest is provenance, not authority.
Limitations
State these before you build a release process on top of the recipe:
- Flows have no aliases. Only Agents have the version pointer plane. An Agent that calls a Flow by name resolves whichever Flow answers to that name at run time.
externaland Claude Managed Agents accept no version selector. They answer422withcode: "selector_unsupported_agent_type", and they keep running their saved configuration.- Personal-account Agents have no aliases. Deploying one is refused with
400andcode: "alias_requires_organization". - Previews are configuration previews, not sandboxes. A preview shares the organization’s connections, Records, and secrets with
live. Per-alias bindings override individual secret values; they isolate nothing else. - A version pins the Agent’s own definition, not its dependencies. Sub-Agents, Flows, saved tools, MCP servers, Skills, and secrets referenced by name resolve at run time. A deployment receipt records their fingerprints as they stood at activation, and a matching fingerprint proves that a definition is unchanged, never that its behavior is.
- A sub-Agent resolves through its own
livealias. A parent version does not pin the versions of the Agents it delegates to, whether they are reached through a capability, a Skill, or a nested reference. - Rollback restores configuration only. It does not undo external side effects, data already written, or edits made since to the resources the version references by name.
Detect drift in CI
A dry run sends the full definition without persisting changes. It returns a plan that reports whether the definition would change and which keys would change:
The plan includes result: 'plan', a changes value of none, create, or update, changedKeys, contentHash, and an optional remoteHash.
To make a drift check fail when the plan is not none, pass expectNoChanges: true:
The SDK throws AgentDriftError when the plan reports create or update.
To bind an apply step to the exact state that the dry run inspected, pass its remoteHash as expectedRemoteHash:
Conflicts with dashboard edits
The platform records the source of each Agent write. An external edit in the dashboard, API, or MCP changes that source. If the edit changes an Agent managed by ensure, the API returns status code 409 by default. The SDK exposes this response as an AgentEnsureConflictError with code: 'external_modification'.
The dashboard shows this state. When you open an Agent managed by ensure, the dashboard shows a Managed in code badge. The badge appears in the Agent editor header, the edit slide-over, and next to the Agent’s name on the Agents list page. To find every Agent managed by code, open Agents and filter by Managed in code. Clicking Save, or publishing a version from Version History, opens a confirmation dialog. Click Save anyway to continue.
Saving changes the provenance from sdk, so the next ensure detects the drift. By default, ensure fails with a conflict. Pass onConflict: 'overwrite' to overwrite the dashboard edit. Update the Agent definition in code to make a lasting change.
You can resolve the conflict in either of these ways:
- Repository wins: pass
onConflict: 'overwrite'. The live Agent configuration uses the repository definition, and the dashboard edit remains in version history. In CI, pair this option with a dry-run gate to surface drift. - Dashboard wins: run
pull, review the returned definition as a git diff, then commit or revert the change.
To pull the definition by name, call Runtype.agents.pull:
What ensure does and does not manage
ensure converges the Agent definition: its name, description, icon, and runtime configuration. This includes the model, system prompt, sampling, tool configuration, loop settings, and error handling. It does not manage related state such as capabilities, skill bindings, memory, conversations, or executions. It never deletes or renames Agents. ensure applies only to standard Runtype Agents, not external A2A (Agent-to-Agent) or Claude-managed Agents.
Call the API directly
If you call the API directly, use POST /v1/agents/ensure. The protocol has three steps:
- Send a hash-only request with
nameandcontentHash. A matching hash returns{ "result": "unchanged" }. A nonmatching hash returns HTTP status code200with{ "result": "definitionRequired" }. Resend the request with the fulldefinition. - For a full request, let the server recompute the canonical hash. The response includes
contentHash; use it in later probes. - Handle conflicts as follows. The API returns status code
409forexternal_modificationorremote_changed. If the submittedcontentHashdoes not match the server’s hash for the definition, the API returns422withcontent_hash_mismatch. OmitcontentHashon full requests to avoid this mismatch.
Every full request also accepts an optional version object, { "label": "a1b2c3d", "notes": "Promoted from CI run 4821." }, recorded on the version row that the request appends. Both fields are optional; label holds up to 100 characters and notes up to 2000. The server trims both, and treats a blank value as omitted. Omitting version keeps the default sdk-ensure label and the server-generated notes. A typical label is a git SHA or a release tag.
A full request also accepts deploy, { "alias": "pr-482" }, which activates the version this converge appends at that release alias and returns a deployment object describing the result. release remains accepted as the compatibility spelling; sending both returns 400.
To pull an Agent definition directly, send GET /v1/agents/pull with the name query parameter. The API returns the canonical definition and these fields: contentHash, lastModifiedSource, updatedAt, and versionId.
Next steps
Continue with these guides:
- Agent versioning and publishing: release aliases, deployment receipts, and what runs when a request names no version.
- Manage Flows as code: apply the same protocol to Flow definitions.
- Manage evals as code: converge eval suites and run them as CI gates.