MCP Protocol Reference
Realm exposes 10 MCP tools. This document covers the full protocol: tool call patterns, response envelope fields, and error recovery.
Connecting
Section titled “Connecting”The server ships inside the CLI. Install it, and realm is the command an MCP client spawns:
npm install -g @sensigo/realm-cliWorkflows come from realm workflow register (see cli-commands), or an agent
mints one live with create_workflow.
The client config is the same everywhere:
{ "mcpServers": { "realm": { "command": "realm", "args": ["mcp"] } }}Where that file lives differs per client — the README and getting-started name the paths.
Project configuration. A registered workflow that declares extensions: or relies on a
realm.yaml deployment manifest resolves both from its own stored trust root. Nothing to
configure — registration recorded where the workflow came from. Only a definition with no
stored trust root, such as one minted live through create_workflow, needs an anchor, and that
is what --project <dir> supplies:
{ "command": "realm", "args": ["mcp", "--project", "/path/to/deployment"] }There is deliberately no default. Under stdio the working directory belongs to the client, so a
default would let any repo a client happens to open resolve its own realm.yaml — reading
secrets and importing code. That is a recorded security decision, not an omission: the manifest
loads only when an operator types the flag.
Hosted platforms that cannot spawn a local subprocess use realm serve instead, an HTTP MCP
server behind a Bearer token — see cli-commands.
The standalone realm-mcp bin (from @sensigo/realm-mcp) runs the same server and exposes
the same ten tools, but it constructs no registry provider: it resolves neither workflow-declared
project extensions nor the deployment manifest, so custom adapters, secret bindings, and gate
notifiers are all absent. It exists for embedding and testing. Interactive operators want
realm mcp.
| Tool | Description |
|---|---|
list_workflows |
Returns all registered workflow IDs and names, and names every registered copy it could not read (unreadable[]: file, class, errno where the OS gave one, reason, repair; warnings carries the count, and the hint says not to create a replacement until an operator has looked); when the registry directory itself cannot be read it returns status: error with error_code: STATE_WORKFLOW_UNREADABLE. Call this first to discover what is available. |
get_workflow_protocol |
Returns the full agent briefing for a workflow: step list, input schemas, instructions, rules, and quick_start. Read this before start_run. |
start_run |
Starts a new run for a workflow. Accepts workflow_id, optional params, optional idempotency_key (if supplied and a run with that key already exists for the workflow, the existing run is returned instead of creating a new one), and optional on_terminal_match / on_live_match policy params — see Idempotency re-encounter policy. params are validated against the workflow’s params_schema; a violation returns VALIDATION_INPUT_SCHEMA with no run created. |
start_run_batch |
Atomically enqueues multiple runs of the same workflow. Accepts a single workflow_id, an items array (each item has params and optional idempotency_key), optional parent_run_id, and optional max_items (default 100). All items are validated before any run is created. |
execute_step |
Submits agent output for the current step. Accepts run_id, command (step name), and params. |
submit_human_response |
Submits a human gate response. Accepts run_id, gate_id, and choice. |
get_run_state |
Returns the current state, evidence chain, and terminal status of a run. When run_phase is aborted, also returns abort_context (guard step ID, evaluated conditions, optional abort message). When this run superseded another under the same idempotency key, also returns rerun_of (the superseded run’s id). Also returns next_actions + next_actions_status (see below). |
abandon_run |
Abandons a non-terminal run — marks it terminal with phase abandoned, releasing every claim in the same write. Accepts run_id and optional reason (default Abandoned via abandon_run). Idempotent; refuses already-terminal runs (STATE_RUN_TERMINAL) and runs with an open gate (pending_gate — the gate itself, never the persisted label; STATE_TRANSITION_DENIED — the error names the submit_human_response call that answers the gate, with the run’s real gate_id and choices). The note states the kill contract: declared finalizers did NOT run and will not for this run; the graceful path is the workflow’s own guard step (abort_unless), which runs them; there is no operator abort command. See Operating & recovering runs. |
create_workflow |
Registers a dynamic workflow from a steps array and immediately starts a run. No YAML file or realm register required. |
append_trace |
Submits trace entries incrementally during an agent step, before execute_step. Accepts run_id, step_id, entries, and an optional writer_nonce. Entries are buffered and merged with any submitted at execute_step finalization — durable once this call returns, canonical only once the step completes. Valid only for an agent step that has not yet been claimed; a terminal run is refused (its entries could never be adopted). Advises the caller when the buffer passes 80% of either capacity bound. |
get_run_state diagnostics
Section titled “get_run_state diagnostics”get_run_state is read-only and returns, in addition to the state summary, the run’s eligible
agent/handler steps as next_actions plus a next_actions_status that classifies the run.
rerun_of is present on the summary only when this run SUPERSEDED a terminal run under the same
idempotency key (on_terminal_match: 'rerun' / 'rerun_if_failed'); it carries that run’s id. It is
absent on a first run and on a reuse, and it is never present on the run that was superseded.
next_actions_status |
Meaning |
|---|---|
ok |
next_actions is authoritative — agent steps to call, or a healthy run with nothing pending now. |
auto_pending |
Eligible steps exist but are all auto — the engine drives them; the run is not awaiting the agent. |
awaiting_human |
A human gate is open — answer with submit_human_response. |
skipped_terminal |
The run is terminal; nothing to do. |
workflow_unresolved |
The workflow definition could not be loaded (unregistered / no workflow store) — next_actions not computed. |
auto_pending vs an empty ok distinguishes a genuinely-parked run from one with no pending agent
action. See Operating & recovering runs.
The seal fields (issue #367)
Section titled “The seal fields (issue #367)”A terminal run’s summary carries the recorded fact that ended it. sealed_by_arm is the first
thing to read; three companions qualify it:
| Field | Type | Present when |
|---|---|---|
sealed_by_arm |
string | The run is sealed. An arm this binary does not recognise arrives as unrecognized arm '<value>' — disclosed, never dropped. |
sealed_by_step |
string | The arm makes the step the seal’s IDENTITY — guard_*, gate_*, handler_abort. See the warning below. |
sealed_by_classified |
true |
The run predates the seal substrate and its arm was recovered on read rather than read from a stamp. |
sealed_by_adjudicated |
object | An operator has ruled on the seal. The full ruling verbatim: { by, at, previous_arm, reason? }. |
Every key is ABSENT when its underlying field is absent — never null. The one place null
appears is inside a ruling: previous_arm: null is a positive fact, meaning the ruling was the
record’s first stamp and there was no prior arm to name. Absence and null are different answers here
and the surface keeps them different.
sealed_by_step is deliberately withheld on complete and step_failure seals even when the
record carries a step. On those arms the recorded step is whichever one settled last — a
settle-order artifact, not a cause. A run that failed in three places has one such step recorded,
and an agent reading it beside the run’s outcome would take it as THE culprit. Read
terminal_reason and failed_steps for what actually failed.
by is a recorded CLAIM of identity, not a verified one: there is no auth model behind it, so
treat it as attribution-by-assertion. A ruling never rewrites the run’s own prose — terminal_reason
remains what the engine said at the time, and the ruling resolves the disagreement without
falsifying the record.
structured_output note (issue #236): a step declaring structured_output: strict that is
driven via execute_step by anything other than realm agent (an external agent calling the MCP
tool directly, for instance) never receives Anthropic grammar-constrained decoding — that
mechanism is realm agent’s own provider layer, not part of the MCP protocol surface. The
attempt’s evidence still discloses this honestly (downgrade_reason: 'external_agent', visible
via realm run inspect) — but get_run_state does not carry per-step evidence at all, so an
MCP-only consumer combines the run’s sealed_by_arm (issue #367 — the recorded fact, and the
first thing to read) + terminal_reason (multi-failure runs list all failed
steps) + failed_steps + derived run_phase
instead of reading a per-attempt field. See
structured_output.
drive_failing run-health finding + drive_failures (issue #401): a failure while
realm agent drives a step — a connection error, a timeout, an MCP discovery failure, a missing
SDK, an engine throw, a schema-rejection wedge — is now RECORDED on the run rather than only
printed to the console. get_run_state carries drive_failures verbatim
({ first_failed_at, total, entries }, at most five entries, most-recent-last; total and
first_failed_at do not reset when the ring rolls), and run_health gains a drive_failing
finding whose reason names when the last attempt failed, its class, and what the provider said.
The finding fires only while the failure is still the LATEST thing that happened to the run: if a
sibling step settled since, or a gate opened, or the failing step itself has since settled, the run
moved on and the finding goes quiet. It is deliberately NOT exclusive with never_claimed_idle —
past the idle threshold a failed-then-silent run trips both, and suppressing either would hide a
fact.
Because recording a failure writes to the run, it also bumps updated_at. That is intentional: a
run whose drive is dying stops looking untouched at the moment it stops being driven.
definition_unresolvable run-health finding (issue #558): this run’s registered workflow copy
cannot be resolved — it is missing, unreadable (a permission, a directory where a file belongs),
empty, corrupt, a legacy record, or its registry directory itself cannot be read. evidence
carries workflow_id, the error code, and class when the failure had one. Scope, stated:
get_run_state produces it for LIVE, non-gate-waiting runs only — a terminal run’s run_health
is hard-zeroed by the frozen #279-R3 guard (#331 is its own revisit condition) and a run with an
open pending_gate takes the awaiting_human branch, which never reads the definition. The CLI’s
realm run inspect and realm run list --stuck mint it for every LIVE run, gate-waiting
included — the --stuck label points at realm run inspect for every one, the surface whose
sentence carries the repair, the consequence and the way out (for a gate-waiting run, the answer
command; abandon refuses it); a terminal run’s copy matters only to replay/drain, which
name the repair themselves. The finding’s reason is that same composed sentence — the class,
the consequence, the way out and the repair — on get_run_state as on the CLI. The way out is the one the CLI prints beside the refusal: end the run with
realm run abandon <run-id>, or — if the run is waiting on a human gate — answer the gate.
structured_output_downgraded run-health finding (issue #316): the remedy for the
per-step-evidence gap above, for the OTHER downgrade causes (a live API rejection, a gate
ineligibility, an unsupported provider — anything except the external_agent case, which is
excluded by design since it is never realm’s own doing to report on). get_run_state’s
run_health array carries a structured_output_downgraded finding, and its warnings array
gains a summary line, whenever a live run has one or more steps that requested strict but ran
without it — no per-step evidence access required. Residual, stated honestly: a downgrade on
a run’s FINAL step can seal before a watchdog’s next poll notices it, and get_run_state zeroes
run_health entirely on a terminal run (the frozen #279-R3 guard — see
operating-runs.md) — so a pure-MCP poller that only ever calls get_run_state
can miss a last-window downgrade. The evidence itself retains the disclosure permanently
regardless (it is never deleted on seal), and realm run inspect always shows it — the finding is
a live-visibility improvement, not a replacement for inspecting a terminal run’s evidence when
that matters.
Strict tool-call arguments (issue #311): a step declaring both structured_output: strict and
tools gets strict on its TOOL-CALL ARGUMENTS (per tool, assessed at runtime), while its own
output stays unconstrained. Two consequences for an MCP consumer:
- That step fires
structured_output_downgradedwithunsupported_context_toolson EVERY run, by design — the output dimension genuinely is unconstrained. Treat it as a baseline; alert on the reason list rather than on the finding being present. - The tool-arguments dimension contributes exactly ONE marked literal,
tool_args:api_rejected_schema, and only when the API rejected a tool schema realm had assessed as eligible. The full per-tool block (diagnostics.structured_output.tool_args— which tools carried strict, which were skipped and why, and any mid-attempt drop) is evidence-only and therefore reachable viarealm run inspect/realm run export, notget_run_state. This is the same per-step-evidence gap described above, and the narrow finding is the deliberate remedy: the per-tool detail would drown a poller in routine outcomes.
Standard loop
Section titled “Standard loop”- Call
list_workflows— discover registered workflow IDs. - Call
get_workflow_protocolwith the matchedworkflow_id— read the briefing. - Call
start_run— the engine auto-chains through initial auto steps and returns at the first agent step. - Call
execute_stepwithparamsshaped tonext_actions[0].input_schema— repeat untilstatusisokandnext_actionsis empty, orstatusisconfirm_required. - When
status: confirm_required— presentgate.displayto the user, collect their choice, callsubmit_human_response.
Response envelope
Section titled “Response envelope”Every tool call returns a ResponseEnvelope:
| Field | Type | Description |
|---|---|---|
command |
string | The step name that was executed. |
run_id |
string | Stable run identifier. |
run_version |
number | Integer version of the run record. Observability only — not required as input to any tool. |
status |
string | ok, error, blocked, or confirm_required. |
data |
object | Step output from the handler or adapter. |
evidence |
array | Evidence snapshots produced by this call. |
warnings |
string[] | Non-fatal notices (e.g. a recovery path was taken — check context_hint for details). |
diagnostics |
array | Structured, code-tagged counterpart to warnings — { code, severity, message, scope, id?, step?, key?, did_you_mean? } per entry (id names the workflow on a workflow-scoped entry, step the step on a step-scoped one). Additive-optional; only create_workflow populates it today (see below), every other tool omits it. Lets an agent branch on code/key/did_you_mean instead of parsing warnings text. |
errors |
string[] | Error messages when status is error or blocked. |
context_hint |
string | Human-readable description of what just happened and the current run state. Present on every response including errors. |
next_actions |
array | Steps available for execution. Empty on terminal or unrecoverable states. Multiple items signal parallel fan-out. |
agent_action |
string | Error recovery instruction. Present only when status is error or blocked. |
chained_auto_steps |
array | Ordered record of auto steps the engine ran silently in this call. Omitted when no auto steps were chained. |
gate |
object | Gate data. Present only when status is confirm_required. |
next_actions
Section titled “next_actions”next_actions is an array. For linear workflows it contains a single item; for parallel fan-out it contains multiple. Always check the length before deciding how to proceed.
Each item has the following fields:
| Field | Description |
|---|---|
orientation |
Forward-looking state description — what state the run is in and what step comes next. Distinct from context_hint, which describes what just happened. |
prompt |
The resolved task prompt for the current agent step. Read this and act on it. |
instruction.tool |
The tool to call next (execute_step or submit_human_response). |
instruction.call_with |
Ready-to-use argument object. For agent steps, call_with.params is a schema skeleton — placeholder strings for enums, zero values for scalars. Fill in your values and call the tool. |
input_schema |
JSON Schema for the params this step expects. |
chained_auto_steps
Section titled “chained_auto_steps”When start_run or execute_step chains through auto steps before returning, chained_auto_steps records each one in order:
"chained_auto_steps": [ { "step": "validate_fields", "run_phase": "running" }, { "step": "confirm_submission", "run_phase": "gate_waiting" }]branched_via is present when a DAG branch was taken (e.g. a trigger_rule: one_failed recovery step was auto-executed).
Guard steps (execution: guard) also appear in chained_auto_steps. A passing guard has run_phase: running; a guard that fires and aborts the run has run_phase: aborted, and the outer response will have next_actions: [].
Gate response (status: confirm_required)
Section titled “Gate response (status: confirm_required)”When the engine opens a gate:
- Read
gate.agent_hint— if present, it contains instructions on how to present the gate. - Present
gate.displayto the user verbatim. - Collect the user’s choice from
gate.response_spec.choices. - Call
submit_human_responseusingnext_actions[0].instruction.call_withwith the choice filled in.
| Field | Description |
|---|---|
gate.display |
The human-facing content. Resolved from gate.message if configured; falls back to step.prompt resolved. Present verbatim. |
gate.agent_hint |
Optional presentation instructions from the step’s instructions field. |
gate.response_spec.choices |
Valid choice values (e.g. ["approve", "reject"]). |
gate.preview |
Full step output at gate opening, for reference and debugging. |
A duplicate submit_human_response call with the SAME gate_id and the SAME choice (e.g. a
retried tool call) is an idempotent no-op — it returns the same ok outcome the original
submission committed, not an error. Submitting a DIFFERENT choice against a gate that another
attempt already resolved is refused (STATE_BLOCKED) with the winning choice named, so a racing
caller learns what was actually recorded rather than silently overwriting it.
Error recovery (agent_action)
Section titled “Error recovery (agent_action)”status: error and status: blocked responses always include agent_action. Do not parse error message text to decide recovery — use agent_action.
agent_action |
Meaning | What to do |
|---|---|---|
stop |
Terminal failure. Cannot recover. | Report to user. Do not retry. |
report_to_user |
Engine state inconsistent (e.g. version conflict). | Surface to user. Do not retry. |
provide_input |
Submitted params were rejected by schema validation. |
Fix params and retry execute_step with the same command. Use next_actions[0].instruction.call_with for the corrected call shape. |
resolve_precondition |
A precondition failed or the step is not eligible for the current run state. | Follow next_actions[0] to an eligible step, or read blocked_reason.eligible_steps for what can run. |
wait_for_human |
A gate is open and waiting for a choice. | Call submit_human_response with the user’s choice. |
When agent_action is provide_input or resolve_precondition and next_actions is non-empty, follow the first item exactly as after a successful step.
A provide_input rejection (the agent’s execute_step output failed schema validation) is a pre-claim, write-free path: nothing is persisted to the run record and version is not bumped, so it leaves no run-record trail. The MCP server emits a metadata-only agent_step_attempt_failed stderr event for these rejections so operators retain a post-mortem trail — see Operating & recovering runs → Failed agent attempts.
start_run_batch
Section titled “start_run_batch”Use start_run_batch to enqueue multiple runs of the same workflow atomically. All items are validated before any run is created — if any item fails schema validation, no runs are created and the tool returns a VALIDATION_BATCH_ITEMS error with a failures array listing each failing item by index.
{ "workflow_id": "ticket-classifier", "parent_run_id": "<orchestrating-run-id>", "max_items": 10, "items": [ { "params": { "ticket_id": "T-001", "body": "Login broken" } }, { "params": { "ticket_id": "T-002", "body": "Billing error" }, "idempotency_key": "T-002" } ]}Arguments
Section titled “Arguments”| Field | Required | Description |
|---|---|---|
workflow_id |
Yes | The workflow to start runs for. All items share the same workflow. |
items |
Yes | Array of run inputs. Each item has params (required) and an optional idempotency_key. If idempotency_key is set and a run with that key already exists, it is returned as-is rather than creating a new run. |
parent_run_id |
No | Run ID of the orchestrating parent. Stored on each child run record for traceability. |
max_items |
No | Maximum number of items allowed in a single call. Defaults to 100. If items.length exceeds this cap, the call fails immediately with VALIDATION_BATCH_TOO_LARGE before any validation or creation. |
on_terminal_match |
No | Idempotency policy when a key matches a terminal run, applied to every item. One of reuse (default), reject, rerun_if_failed, rerun. See Idempotency re-encounter policy. |
on_live_match |
No | Idempotency policy when a key matches a non-terminal run, applied to every item. One of use_existing (default), fail. |
Response
Section titled “Response”On success returns { started, failed }:
started: Array<{ run_id, params, idempotency_key?, deduped, run_phase, terminal_reason?, warnings }>— one entry per accepted item.dedupedistruewhen an existing run matched the key;run_phaseis the matched/created run’s phase;warningscarries observational active-match / param-mismatch notes.failed: Array<{ index, idempotency_key?, params, error, error_code? }>— items rejected by the idempotency policy (on_terminal_match: 'reject'oron_live_match: 'fail').indexcorrelates back toitems[]. A rejected item does not abort the batch — accepted items still appear instarted[].
On failure (before any item is processed) returns a ResponseEnvelope with status: error and agent_action: provide_input.
| Error code | Cause |
|---|---|
VALIDATION_BATCH_TOO_LARGE |
items.length exceeds max_items. No runs were created. |
VALIDATION_BATCH_ITEMS |
One or more items failed params_schema validation. No runs were created. |
STATE_WORKFLOW_NOT_FOUND |
workflow_id is not registered. |
STATE_WORKFLOW_UNREADABLE |
The registered copy exists (or its registry does) but cannot be read — a permission, a directory where a file belongs, or an unreadable registry directory (issue #558). The message names the errno and the path. |
RESOURCE_FORMAT_INVALID |
On a run-context tool: the registered copy is empty, is not parseable JSON, or parsed as something that is not a workflow object (issue #558). |
Idempotency re-encounter policy
Section titled “Idempotency re-encounter policy”When an idempotency_key matches an existing run, the caller decides what happens via two
orthogonal, optional parameters on start_run and start_run_batch. Both default to today’s
behavior — omit them and a match returns the existing run unchanged (deduped: true).
on_terminal_match — key matches a terminal run (completed / failed / aborted / abandoned):
| Value | Behavior |
|---|---|
reuse (default) |
Return the existing run (deduped: true). Nothing is re-driven. |
reject |
Throw STATE_IDEMPOTENCY_KEY_USED — the key was already used and its run is terminal. |
rerun_if_failed |
A failed / aborted / abandoned match is superseded by a fresh run (deduped: false), stamped rerun_of: <superseded id>; a completed match is reused (benign skip — re-running a closed-ticket batch should not error on already-succeeded items). |
rerun |
Always supersede the matched run with a fresh run (deduped: false), stamped rerun_of: <superseded id>. |
A superseded run is never rewritten — the link points one way, from the fresh run back to the one it
replaced. rerun_of is stamped by the store at creation (the one place both ids are in hand), is echoed by the start_run response that created the fresh run — whose context_hint also says which run it superseded (no second call needed) — and is
never caller-settable; get_run_state returns it, and realm run inspect prints Rerun of: <id>.
on_live_match — key matches a non-terminal run (running / gate_waiting):
| Value | Behavior |
|---|---|
use_existing (default) |
Return the live run (deduped: true) plus an observational warning that it is still active. |
fail |
Throw STATE_RUN_ALREADY_ACTIVE — the key is owned by a still-active run (concurrency-safe). |
Supersede repoints the idempotency key to the fresh run via a single atomic pointer overwrite; the superseded run remains on disk by id (auditable) but no longer owns the key.
terminate (abort the live run, then restart) is intentionally unsupported: a daemonless
file store cannot actually stop a detached realm agent, so marking its record aborted while it
keeps writing would be a race. Recovering a genuinely-zombie run is a deliberate operator action,
not an automatic policy.
create_workflow
Section titled “create_workflow”Use create_workflow when no registered workflow matches the task. It registers a dynamic workflow and starts a run in one call — do not call start_run afterward.
{ "steps": [ { "id": "research_problem", "description": "Audit all JSDoc comments and list files with missing or inaccurate docs." }, { "id": "generate_fixes", "description": "For each file identified, generate corrected JSDoc.", "depends_on": ["research_problem"], "input_schema": { "type": "object", "properties": { "audit_summary": { "type": "string" } }, "required": ["audit_summary"] } } ], "metadata": { "name": "jsdoc-audit", "task_description": "Audit and fix JSDoc across the codebase." }}Step fields
Section titled “Step fields”| Field | Required | Description |
|---|---|---|
id |
Yes | Unique step identifier. Snake_case verb-noun (e.g. research_problem). No spaces. |
description |
Yes | Acceptance criterion for the step — what correct output looks like, not how to produce it. |
depends_on |
No | Array with at most one step ID this step depends on. Controls execution order. Omit for the first step. |
input_schema |
No | JSON Schema for the fields this step’s params must include. Used to validate execute_step submissions. |
llm_timeout_seconds |
No | Positive integer seconds. The PER-ATTEMPT ceiling on this step’s model request, applied by realm’s own drive (realm agent). From it realm derives the whole-request bound — attempts × this value, plus the SDK’s backoff and a download allowance; see the yaml-schema derivation. Absent ⇒ 600 seconds per attempt. timeout_seconds was removed from this tool (#412): every step it creates is execution: agent, where nothing enforced it. Inert for runs driven over MCP execute_step (this tool’s own runs included) — it bounds realm’s OWN drive (realm agent) of the workflow. |
An unrecognized key is never rejected — the field is dropped and the workflow is still created.
This holds at all three levels: a top-level argument, a field inside metadata, and a field
inside a step. Each is surfaced both in warnings (the rendered text, e.g.
⚠ step 'x': unknown key 'dependson' — ignored (did you mean 'depends_on'?)) and as a structured
entry (code: "UNKNOWN_CREATE_WORKFLOW_KEY") in diagnostics — an authoring agent can self-correct
on the next call instead of repeating the same typo. A dropped key can never reach the registered
definition, so a warning here always means the field had no effect.
A top-level x- key is not the author-extension namespace here (issue #559) — this tool’s
create_workflow surface has no YAML anchors to host and no source file to carry them verbatim
into, so an x- argument is dropped and warned about exactly like any other unrecognized one. The
namespace exists for realm workflow validate/register/watch, which parse an actual file.
Two top-level cases carry a targeted message instead of the generic text. workflow_id is the one
an agent most plausibly invents: this tool mints its own id and returns it as data.workflow_id,
so a start_run with a self-chosen id fails STATE_WORKFLOW_NOT_FOUND — set metadata.name to
influence the id’s slug instead. And a metadata field sent at the top level (name,
description, task_description, model, agent) is named as misplaced rather than unknown —
it belongs under metadata.
The sample above carries no (line N), and that is permanent rather than pending
(issue #392): create_workflow builds a
definition from structured arguments, so no source file exists for a line number to point into.
Diagnostics from a PARSED workflow do carry line/column/endLine/endColumn; these
structurally cannot, and their absence is the honest answer rather than a gap.
Metadata fields
Section titled “Metadata fields”| Field | Required | Description |
|---|---|---|
name |
No | Short kebab-case slug used to derive the workflow ID. |
description |
No | Declarative — what this workflow is for / when to use it. Becomes the workflow’s description, surfaced in the agent protocol (get_workflow_protocol) and echoed by realm workflow validate/register. No synthesized default: omit it (or submit only whitespace) and the workflow simply has none. |
task_description |
No | Imperative — how to begin driving this run (the quick-start instruction). Becomes protocol.quick_start. Distinct from description above — one says what the workflow is, this says how to start it. |
description and task_description map to different places and are never blurred: description is
the declarative “what it’s for,” task_description is the imperative “how to begin.”
Response and continuation
Section titled “Response and continuation”The response has the same shape as a start_run response. Check next_actions[0] immediately and proceed with execute_step — the run is already live when create_workflow returns.