Manage flows as code
Store your Flow definitions in your repository. Review and version them with the rest of your code, then use ensure to converge them to Runtype at deploy time. This page explains how to apply the same protocol to Flows.
Choose between ensure and upsert
Use these two code-first Flow methods:
flows.upsert()saves and runs a Flow in one dispatch. It creates or updates the Flow by name, then executes it against a record in the same request. Use it in your application when the Flow definition lives beside the code that runs it.flows.ensure()converges the saved Flow definition without executing it. It creates, updates, or leaves the Flow unchanged. Use it in continuous integration and continuous delivery (CI/CD) jobs.
flows.upsert() is not deprecated or an older version of flows.ensure(). Run a Flow with flows.upsert() or by dispatching it by ID. Converge the saved definition with flows.ensure().
Define a Flow
defineFlow constructs a definition locally without network requests. It validates the definition shape and returns the canonical definition object.
Keep the definition portable across environments. defineFlow rejects account-scoped resource references that use tool_, agent_, or flow_ prefixes. Use portable builtin:, platform:, or mcp: tool references, or reference saved resources by name with tool:TOOL_NAME, agent:AGENT_NAME, or flow:FLOW_NAME. Replace each placeholder with the exact saved resource name.
Define a Flow with a name and ordered steps:
The definition includes a name, a steps array, and an optional evals array of inline eval suites. ensure converges inline eval suites with the Flow. For more information, see Colocate evals with a flow. ensure uses the API key’s organization or personal scope, and the API endpoint selects the environment.
Renaming a definition does not rename the Flow. ensure creates a Flow under the renamed name and
leaves the old Flow in place. It never deletes a Flow. Treat the name as the stable identity.
Reference saved Tools by name
Built-in, platform, and MCP Tools have portable IDs that you can hardcode. Saved Tools receive an ID for each account and environment. These IDs start with tool_ and cannot appear in a portable definition. Reference a saved Tool by its exact name. Use the tool:TOOL_NAME form in a prompt or transform-data step’s tools.toolIds, and in a tool-call step’s toolId.
Define a Flow that references a saved Tool by name:
The text after tool: must match the Tool’s exact name. Runtype includes the name in the canonical content hash and resolves it to the saved Tool during each dispatch. ensure, including dryRun, validates each reference during convergence. A name that matches no saved Tool fails validation. A name that matches multiple Tools fails validation and identifies the matches.
When you use pull on a dashboard-built Flow, Runtype converts saved Tool IDs to tool:TOOL_NAME when possible. If the Tool was deleted or its name conflicts, pull leaves the raw ID and adds an entry to warnings.
Reference saved Agents and Flows by name
Use name references when a Flow points to another saved resource. Use these forms:
agent:AGENT_NAME: Use this form for anexecute-agentstep’sagentIdor a saved-subagent runtime Tool’sconfig.agentId.flow:FLOW_NAME: Use this form for a flow-as-tool runtime Tool’sconfig.flowId.
Define a Flow that references a saved Agent and Flow by name:
Runtype includes these names in the canonical hash and resolves them during each dispatch. ensure, including dryRun, rejects a name that matches no saved Agent or Flow or matches more than one. Raw IDs with agent_ or flow_ prefixes remain rejected by ensure. When you use pull, Runtype converts raw IDs to name forms when possible and adds a warnings entry for each reference that it cannot convert.
Converge at deploy time
Configure the SDK and converge the Flow definition:
The result contains result, flowId, versionId, and contentHash. result is unchanged, created, or updated. When the definition declares inline eval suites, the result also contains evals with one outcome for each suite.
ensure first sends a hash-only probe with the Flow name and content hash. If the hash differs, it sends the full definition, writes the saved Flow, and appends an immutable version snapshot. Runtype does not mutate version history, so an overwritten dashboard edit remains available there.
The content hash covers the Flow’s steps and matches the hash that the upsert protocol uses. The contentHash in each response comes from the server’s canonical, normalized form. The SDK uses that server hash in later probes.
Publish a version
By default, ensure writes the saved Flow and a draft version. To update the published-version pointer to the version that ensure creates, pass release: 'publish':
Label 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.
Detect drift in CI
Use a dry run to return a plan without writing:
The plan contains result: 'plan', a changes value of none, create, or update, and the contentHash. When a remote Flow exists, it also contains remoteHash. changedKeys lists step changes such as steps.added.STEP_NAME, steps.removed.STEP_NAME, and steps.modified.STEP_NAME. Replace STEP_NAME with the affected step name.
Use expectNoChanges to fail the check when the plan reports a change:
To apply the definition only when the remote state matches the dry run, pass the plan’s remoteHash as expectedRemoteHash:
Handle dashboard edits
Runtype records the source of each Flow write. After a dashboard edit or API write changes an ensure-managed Flow, the next ensure fails with a 409 conflict by default. The SDK raises a FlowEnsureConflictError with code: 'external_modification'.
The dashboard shows a Managed in code badge for a Flow that a code definition last converged. The badge appears in the Flow editor header and on the Flows list. When you click Save, click Publish with unsaved changes, or publish a version from Version History, the dashboard opens a confirmation dialog. Click Save anyway to continue.
Saving changes the Flow’s provenance from sdk to dashboard, so the next ensure detects the edit. The default behavior fails with a conflict. Pass onConflict: 'overwrite' to overwrite the edit during convergence. Publishing from the editor header with no unsaved edits only promotes the existing draft and does not trigger the warning. Publishing a version from Version History always writes that version’s definition to the Flow, so it always asks first. To make a lasting change, update the Flow definition in your code.
Choose one of these approaches:
- Repository wins: Run
ensureagain withonConflict: 'overwrite'. The saved Flow changes, and the dashboard edit remains in version history. - Dashboard wins: Run
pull, review the definition as agitdiff, then commit or revert the change.
Use this call to pull the canonical definition and its provenance:
What ensure does and does not manage
ensure converges the Flow definition’s name and steps, using the same canonical validator as Flow creation. When the definition declares inline evals, ensure also converges each eval suite through the eval endpoint after the Flow converges. The result contains the suite outcomes in result.evals.
ensure does not manage the Flow’s description, schedules, Surfaces, Records, or executions. It does not delete or rename Flows.
Wire protocol (direct API use)
Use this protocol when you call the Runtype API without the TypeScript SDK. The protocol uses one endpoint: POST /v1/flows/ensure.
Follow this sequence:
- Send a probe with
{ "name": "Onboarding Digest", "contentHash": "YOUR_CONTENT_HASH" }. ReplaceYOUR_CONTENT_HASHwith the canonical SHA-256 hash for the Flow’ssteps. - If the response is
{ "result": "definitionRequired" }, resend the request with the fulldefinitionand omitcontentHash. A matching hash returns{ "result": "unchanged" }with200. - Use the server’s returned hash in later probes. The server recomputes the canonical hash over each submitted definition and returns it in every response.
- Handle
409conflicts forexternal_modificationandremote_changed. A submittedcontentHashthat differs from the server’s recomputed hash returns422withcontent_hash_mismatch. OmitcontentHashfrom full requests to avoid this validation error.
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.
GET /v1/flows/pull?name=Onboarding%20Digest returns the canonical definition and its provenance: contentHash, lastModifiedSource, updatedAt, and versionId.
Next steps
Continue with these guides:
- Manage agents as code: apply the same protocol to Agent definitions.
- Manage tools as code: converge Tool definitions from your repository.
- Manage evals as code: converge eval suites and run them as a CI gate.
- Flow versioning and publishing: manage draft and published versions.
- Creating and editing flows: edit Flows in the dashboard.