Step types reference
Every workflow step type: agent, shell, python, email, notify, resolve, conditional, parallel, goto, output, approvals, a2a, and orchestration patterns.
Every Revka workflow is an ordered list of steps. Each step shares a common envelope — id, depends_on, retry, timeout, and the rest documented in the Workflow YAML reference — plus exactly one type-specific config block named after its type. This page is the reference for those config blocks: what each step type does, its fields, and a copy-pasteable example.
Use this page when you are writing the body of a step and need to know which keys it accepts. For the top-level schema, action shorthand, and the validator, see the YAML reference. For ${...} interpolation and ${{ ... }} expressions used inside these fields, see Variables, expressions & triggers. If you are brand new, start with Your first workflow.
The step types at a glance
Section titled “The step types at a glance”The type field accepts these values. Each maps to a config block of the same name.
| Category | Types |
|---|---|
| Agent & code | agent, shell, python |
| Communication | email, notify, human_approval, human_input |
| Control flow | conditional, parallel, goto |
| Output & data | output, resolve |
| External | a2a |
| Orchestration | map_reduce, supervisor, group_chat, handoff |
Spawns an LLM agent that runs a prompt, optionally uses tools, and returns text. This is the workhorse step type. The agent can be a claude or codex backend, and its output can be captured loosely or held to a strict structured-output contract.
- id: research type: agent depends_on: [] agent: agent_type: claude # claude or codex role: researcher # coder, researcher, reviewer, tester, etc. prompt: | Research ${inputs.topic} and summarize the findings. output_fields: [summary, score] # optional structured-output contract model: null # optional model override timeout: 300 # seconds template: my-template # optional agent pool template skills: - "kref://CognitiveMemory/Skills/some-skill.skilldef" retry: 1 # retry once on failure retry_delay: 10 # seconds between retries| Field | Type | Default | Meaning |
|---|---|---|---|
agent_type | enum | claude | Agent backend: claude or codex. |
role | string | — | Agent persona (coder, researcher, reviewer, tester, …). |
prompt | string | — | The LLM prompt. Supports ${...} interpolation. |
output_fields | list<string> | [] | When set, declares required structured output fields. |
model | string | null | Optional model override. |
timeout | number | 300 | Execution timeout in seconds. |
template | string | — | Agent pool template name to inherit from. |
skills, retry, and retry_delay are part of the shared step envelope, not the agent block.
Agent Structured Output & output_fields
Section titled “Agent Structured Output & output_fields”When you set agent.output_fields, the executor appends structured-output instructions to the prompt and requires every declared field to be present in the response. Any missing field fails the step with structured_output_missing. This turns an agent into a typed function you can branch on.
- id: auditor type: agent agent: role: reviewer prompt: "Review canon consistency and report a verdict." output_fields: [verdict, production_ready]The agent may satisfy the contract in any of three accepted formats:
- Full JSON object —
{"verdict":"APPROVED","production_ready":true} - Fenced JSON block — a
```jsoncode block containing the object FINAL_OUTPUT:block — a trailing YAML block introduced byFINAL_OUTPUT:
Parsed fields are available downstream as ${auditor.output_data.verdict}, or inside an expression as auditor.output_data.verdict:
- id: gate type: conditional depends_on: [auditor] conditional: branches: - condition: "${{ auditor.output_data.production_ready == true }}" goto: publish - condition: default goto: fixRuns a shell command in a subprocess. The exit code controls success: a non-zero exit fails the step unless allow_failure is set.
- id: build type: shell shell: command: "cd ${inputs.project_dir} && npm run build" timeout: 60 allow_failure: false # true = a non-zero exit doesn't fail the workflow| Field | Type | Default | Meaning |
|---|---|---|---|
command | string | — (required) | Command to run. Supports ${...} interpolation. |
timeout | number | inherited | Timeout in seconds. |
allow_failure | bool | false | When true, a non-zero exit code does not fail the step. |
python
Section titled “python”Executes a Python script file or inline Python code. Arguments are passed in as keyword arguments resolved from workflow state.
- id: transform type: python python: script: "scripts/transform.py" # or use `code:` for inline Python args: topic: "${inputs.topic}" timeout: 60| Field | Type | Default | Meaning |
|---|---|---|---|
script | string | — | Path to a Python script file. |
code | string | — | Inline Python source (use instead of script). |
args | map | {} | Key/value pairs passed as keyword arguments; values support ${...}. |
timeout | number | inherited | Timeout in seconds. |
Provide either script or code, not both.
Sends an outbound email over SMTP using the configured email provider. Set dry_run: true to render and log the message without actually sending it — useful while testing.
- id: outreach type: email email: subject: "Revka report" body: "${report.output}" dry_run: true| Field | Type | Default | Meaning |
|---|---|---|---|
to | string | — | Recipient address. |
subject | string | — | Subject line; supports ${...}. |
body | string | — | Message body; supports ${...}. |
dry_run | bool | false | When true, the message is rendered and logged but not sent. |
See Email, iMessage, Linq & automation for configuring the email provider.
notify
Section titled “notify”Sends a non-blocking notification to one or more channels and continues immediately — it never pauses the workflow. Use it for progress pings; use human_approval or human_input when you actually need to wait.
- id: heads_up type: notify notify: channels: [dashboard, slack] title: "Workflow update" message: "Run ${run_id} completed."| Field | Type | Default | Meaning |
|---|---|---|---|
channels | list<string> | — | Channel names to deliver to (e.g. dashboard, slack). |
title | string | — | Notification title. |
message | string | — | Notification body; supports ${...}. |
See Channels overview for available channel names.
resolve
Section titled “resolve”Looks up a Kumiho entity by kind, tag, and optional filters — with no LLM call. It is the deterministic counterpart to an agent step, used to find prior run state (for multi-run continuity) or to inject a dependency entity.
- id: resolve_cursor type: resolve resolve: kind: "qs-episode-final" # entity kind (exact match) tag: "published" # revision tag (exact match) name_pattern: "" # optional glob on entity name space: "" # space path filter (default: Revka/WorkflowOutputs) mode: latest # latest = single newest | all = list metadata_source: revision # revision | item | artifact fields: [part, episode_number] # metadata fields to extract (empty = all) fail_if_missing: false # false = don't fail if nothing is found| Field | Type | Default | Meaning |
|---|---|---|---|
kind | string | — | Entity kind to match (exact). Required for an entity match. |
tag | string | "ready" | Revision tag to match (exact). |
name_pattern | string | "" | Optional glob filter on the entity name. |
space | string | Revka/WorkflowOutputs | Optional space-path prefix filter. |
mode | enum | latest | latest returns the single newest match; all returns a list. |
metadata_source | enum | revision | Where to read metadata: revision, item, or artifact. |
fields | list<string> | [] | Specific metadata fields to extract; empty = all. |
fail_if_missing | bool | false | When true, the step fails if nothing matches. |
Output data: found, item_kref, revision_kref, name, metadata_source, plus one entry per field in fields (e.g. ${resolve_cursor.output_data.episode_number}).
conditional
Section titled “conditional”Evaluates boolean expressions against step outputs and routes execution to a target step. Branches are checked in order; the first match wins. Use a default branch as the catch-all.
- id: gate type: conditional depends_on: [review] conditional: branches: - condition: "${review.output} contains APPROVED" goto: publish - condition: "${review.status} == 'failed'" goto: fix - condition: default # catch-all goto: fix| Field | Type | Meaning |
|---|---|---|
branches | list | Ordered list of {condition, goto, value?} items. |
branches[].condition | string | Expression to evaluate, or the literal default. |
branches[].goto | string | Step id to jump to, or "end" to terminate the run. |
branches[].value | string | Optional value expression recorded with the matched branch. |
Supported operators in a condition: ==, !=, contains, >, <, >=, <=. For richer logic, use a ${{ ... }} expression — see Variables, expressions & triggers.
Run output fields: a completed conditional records matched_branch_index, matched_branch_label, matched_goto, matched_condition, matched_value_expr, and matched_output. The dashboard graph and step inspector display the matched branch and target. Branch goto targets must point to real steps (or end), which the validator checks.
parallel
Section titled “parallel”Runs several steps concurrently and joins on a configurable strategy. The named sub-steps are owned by this wrapper — they are excluded from the normal top-level execution order so they don’t also run on their own.
- id: fan_out type: parallel parallel: steps: [step_a, step_b, step_c] join: all # all | any | majority max_concurrency: 5 # 1-10| Field | Type | Default | Meaning |
|---|---|---|---|
steps | list<string> | — | Step ids to run in parallel (must be ≥2 existing steps that don’t cross-depend). |
join | enum | all | all waits for all and fails if any fails; any returns on the first success; majority needs >50% to succeed. |
max_concurrency | int (1–10) | 5 | Maximum simultaneous sub-steps. |
Jumps back to an earlier step, enabling iterative loops with a hard safety cap. The jump only happens while condition is true and the iteration count is under max_iterations.
- id: retry_loop type: goto depends_on: [check_quality] goto: target: improve # step id to jump back to condition: "${check_quality.output} contains NEEDS_WORK" max_iterations: 3 # safety cap (1-20)| Field | Type | Default | Meaning |
|---|---|---|---|
target | string | — | Step id to jump back to (must exist). |
condition | string | — | Expression that must be true to loop. |
max_iterations | int (1–20) | — | Hard cap on loops to prevent runaway execution. |
The current loop count is available as ${loop.iteration} inside the loop body.
output
Section titled “output”Renders a template and optionally publishes the result as a Kumiho entity. Publishing tags a revision, which fires a revision.tagged event that can trigger downstream workflows — this is how workflow chaining works.
- id: report type: output depends_on: [analyze] output: format: markdown # text | json | markdown template: | # Analysis Report ${analyze.output} entity_name: "analysis-${inputs.topic}" entity_kind: "analysis-report" entity_tag: "ready" entity_space: "Revka/WorkflowOutputs" metadata_target: item # item | revision | artifact entity_metadata: topic: "${inputs.topic}" summary: "${analyze.output}"| Field | Type | Default | Meaning |
|---|---|---|---|
format | enum | text | Render format: text, json, or markdown. |
template | string | — | Rendered content; supports ${...}. |
entity_name | string | — | Entity name. Set entity_name + entity_kind (+entity_tag) to publish. |
entity_kind | string | — | Entity kind for downstream resolve/trigger matching. |
entity_tag | string | — | Revision tag applied on publish. |
entity_space | string | Revka/WorkflowOutputs | Kumiho space path to publish into. |
metadata_target | enum | item | Where entity_metadata is written: item, revision, or artifact. |
entity_metadata | map | {} | Key/value metadata; supports ${...}. |
Output data: entity_kref, entity_revision_kref.
human_approval
Section titled “human_approval”Pauses the workflow and waits for an operator to approve or reject. A checkpoint is written before pausing, so the run survives a restart. This is the canonical human-in-the-loop gate.
- id: approve type: human_approval human_approval: message: "Deploy to production?" timeout: 3600 # seconds (1 hour)| Field | Type | Meaning |
|---|---|---|
message | string | Prompt shown to the approver. |
timeout | number | Seconds to wait before the gate times out. |
The run pauses with status paused. Resolve it from the dashboard, from a channel, or via the API:
POST /api/workflows/runs/{run_id}/approveAuthorization: Bearer <token>Content-Type: application/json
{ "approved": true, "feedback": "LGTM" }human_input
Section titled “human_input”Pauses the workflow and waits for a freeform text response from the operator. The reply becomes available downstream as ${ask_user.output}, so you can fold human guidance into later prompts.
- id: ask_user type: human_input human_input: message: "What changes do you want?" channel: dashboard timeout: 3600| Field | Type | Meaning |
|---|---|---|
message | string | Question shown to the operator. |
channel | string | Channel to ask on (e.g. dashboard). |
timeout | number | Seconds to wait for a reply. |
Calls an external agent over the A2A (agent-to-agent) protocol using HTTP. The remote agent runs the task and returns a result that becomes this step’s output.
- id: external type: a2a a2a: url: "https://agent.example.com/a2a" skill_id: "analyze-data" message: "Analyze: ${inputs.data}" timeout: 300| Field | Type | Default | Meaning |
|---|---|---|---|
url | string | — | A2A endpoint of the external agent. |
skill_id | string | — | Skill on the remote agent to invoke. |
message | string | — | Task message; supports ${...}. |
timeout | number | inherited | Timeout in seconds. |
Orchestration patterns
Section titled “Orchestration patterns”Four higher-level step types coordinate multiple agents in one step, instead of you wiring agents and conditionals by hand. They wrap the operator’s multi-agent patterns — see Spawning & coordinating agents and Built-in workflows & patterns for the full mechanics.
| Type | Purpose |
|---|---|
map_reduce | Fan out over N splits to parallel mapper agents, then a reducer agent synthesizes all results. Ideal for reviewing many files at once. |
supervisor | Dynamic delegation loop — a supervisor agent chooses the next specialist based on results so far, up to a max-iteration cap. |
group_chat | Moderated multi-agent discussion with turn-taking; returns a transcript, summary, and conclusion. |
handoff | Extract findings and context from one agent and inject them into another agent’s prompt, tracking the chain in Kumiho. |
- id: review_all type: map_reduce map_reduce: task: "Review each module for correctness and safety." splits: ["src/auth", "src/api", "src/db"] mapper: reviewer # role/template for the parallel mappers reducer: summarizer # role/template that synthesizes results concurrency: 3 # parallel mappers (max 10)The dashboard step inspector renders the group_chat transcript and per-agent results so you can audit how a multi-agent step reached its conclusion.