Workflow Schema Reference

Workflow Schema Reference

Workflow Schema Reference

Reliant workflows are defined in YAML files. This reference documents every field in the workflow schema.

Top-Level Fields

The top level of a workflow file contains metadata and the core structure.

FieldTypeRequiredDescription
namestringYesUnique identifier for the workflow
apiVersionstringNoSchema version (currently 0.0.5)
versionstringNoSemantic version (e.g., v0.0.11)
descriptionstringNoHuman-readable description
statusstringNoWorkflow visibility: draft, published, or internal
tagstringNoTag for preset matching (e.g., agent, orchestration)
entrystring or string[]YesEntry point node ID(s)
inputsmapNoInput parameter definitions
outputsmapNoOutput expressions evaluated on completion
groupsmapNoNamed input parameter groups
nodesNode[]YesWorkflow execution nodes
edgesEdge[]ConditionalConnections between nodes (required unless single-node with entry)
threadThreadConfigNoTop-level thread configuration
uiUIMetadataNoVisual editor positioning data

Entry Points

The entry field specifies which node(s) start execution. It replaces the deprecated from: started edge pattern.

Single entry point:

entry: agent_loop

Parallel entry (multiple nodes start simultaneously):

entry: [racer_1, racer_2, racer_3]

Status Values

ValueDescription
draftWork in progress, not listed in workflow picker
publishedAvailable to users
internalSystem workflow, hidden from users

Example

name: agent
apiVersion: "0.0.5"
version: v0.0.11
description: Interactive agent workflow with trigger model
status: published
tag: agent

entry: agent_loop

inputs:
  mode:
    type: enum
    enum: ["agent", "plan", "manual"]
    default: "agent"
    description: Execution mode

outputs:
  response_text: "{{nodes.agent_loop.response_text}}"
  iterations: "{{nodes.agent_loop.iterations}}"

nodes:
  - id: agent_loop
    # ... node definition

Input Schema

Inputs define configurable parameters for the workflow. Each input has a type and optional validation constraints.

FieldTypeRequiredDescription
typestringYesData type (see Input Types below)
requiredbooleanNoIf true, must be provided by caller
defaultanyNoDefault value if not provided
descriptionstringNoHuman-readable description
uistringNoUI visibility: hidden or omit for visible

Input Types

TypeDescriptionUI Rendering
stringText valueText input
integerWhole numberNumber input
numberDecimal numberNumber input or slider (if min/max)
booleanTrue/falseToggle switch
enumSelection from allowed valuesDropdown
modelLLM model identifierModel selector dropdown
toolsTool filter expressionsTool multi-select
attachmentsFile attachment referencesFile picker
anyAny JSON valueGeneric input

String Validation

FieldTypeDescription
patternstringRegex pattern to match
enumstring[]Allowed values (for enum type)
multibooleanAllow multiple selections (enum only)
min_lengthintegerMinimum string length
max_lengthintegerMaximum string length

Number Validation

FieldTypeDescription
minnumberMinimum allowed value
maxnumberMaximum allowed value

UI Hints

Use ui: hidden for internal parameters that shouldn’t appear in the configuration panel:

inputs:
  # Shown in params panel
  model:
    type: model
    default: ""
    description: LLM model to use

  # Hidden from UI - internal use only
  chat_id:
    type: string
    ui: hidden
    default: ""

Validation Rules

Every input must satisfy one of these conditions:

  • Have required: true, OR
  • Have a non-null default value

This prevents runtime “no such key” errors in CEL expressions.

Example

inputs:
  mode:
    type: enum
    enum: ["manual", "agent", "plan"]
    default: "agent"
    description: |
      Execution mode:
      - manual: Requires approval for tool calls
      - agent: Auto-approves all tool calls
      - plan: Read-only tools only

  temperature:
    type: number
    default: 1.0
    min: 0
    max: 1
    description: Response randomness (0 = focused, 1 = creative)

  max_turns:
    type: integer
    default: 100
    min: 1
    max: 500
    description: Maximum agent loop iterations

  tools:
    type: tools
    default: ["tag:default"]
    description: Available tools for the agent

  spawn_presets:
    type: enum
    multi: true
    enum:
      - general
      - researcher
      - code_reviewer
    default:
      - general
      - researcher
    description: Presets available for spawn tool

Nodes

Nodes are the execution units in a workflow. Each node must have exactly one type.

Common Node Fields

FieldTypeRequiredDescription
idstringYesUnique node identifier
argsmapNoArguments passed to the node (supports CEL expressions)
threadThreadConfigNoThread configuration for this node
timeoutstringNoActivity timeout (e.g., 5m, 1h)
save_messageSaveMessageConfigNoAuto-save output as message

Node Types

Each node must have exactly one of these type fields:

FieldDescription
actionExecute a registered activity
workflowExecute a child workflow
loopExecute a sub-workflow repeatedly
runExecute a shell command (shorthand)
joinSynchronization point

Action Nodes

Execute a registered activity:

- id: call_llm
  action: CallLLM
  args:
    thread: "{{thread.id}}"
    model: "{{inputs.model}}"
    temperature: "{{inputs.temperature}}"

Workflow Nodes

Execute a child workflow:

- id: impl_1
  workflow: builtin://agent
  thread:
    mode: new()
    key: impl_1
    inject:
      role: user
      content: "Implement the feature..."
  args:
    mode: "{{inputs.mode}}"
    model: "{{inputs.model}}"

Workflow references:

  • builtin://agent - Built-in workflow
  • my-workflow - Workflow file in same directory

Loop Nodes

Execute a sub-workflow repeatedly until a condition is met or max iterations reached.

- id: agent_loop
  loop:
    max: "{{inputs.max_turns}}"
    until: outputs.tool_calls == null || size(outputs.tool_calls) == 0
    inline:
      entry: call_llm
      inputs:
        model:
          type: model
          default: ""
      outputs:
        tool_calls: "{{nodes.call_llm.tool_calls}}"
      nodes:
        - id: call_llm
          action: CallLLM
          args:
            thread: "{{thread.id}}"
      edges:
        - from: call_llm
          cases:
            - to: execute_tools
              condition: nodes.call_llm.tool_calls != null

Loop Configuration:

FieldTypeRequiredDescription
maxintegerYesMaximum iterations
untilstringNoCEL condition to exit early (uses outputs.* namespace)
workflowstringNoExternal workflow reference
inlineInlineLoopBodyNoInline workflow definition
entrystringNoEntry node for inline loops

Must specify either workflow OR inline, not both.

Loop Outputs:

After loop completion, these outputs are available:

OutputTypeDescription
iterationsintegerNumber of iterations executed
maxintegerConfigured max iterations
succeededbooleanTrue if exited via until, false if hit max
<output_name>anyLast iteration’s workflow outputs

Loop Context:

Inside loops, additional context is available:

  • iter.iteration - Current iteration (0-indexed)
  • iter.max - Maximum iterations

Run Nodes

Shorthand for shell command execution:

- id: run_tests
  run: npm test

Join Nodes

Synchronization point for parallel branches:

- id: all_complete
  join: all  # Wait for all incoming edges

- id: race_winner
  join: any  # First one wins, others abandoned
ModeDescription
allWait for all incoming edges to complete
anyContinue when any one incoming edge completes

Save Message Configuration

Auto-save node output as a chat message:

- id: call_llm
  action: CallLLM
  save_message:
    role: "{{output.message.role}}"
    content: "{{output.message.text}}"
    tool_calls: "{{output.tool_calls}}"
    input_tokens: "{{output.input_tokens}}"
    output_tokens: "{{output.output_tokens}}"
FieldTypeDescription
conditionstringCEL expression - only save if true
rolestringMessage role: user, assistant, tool, system
contentstringMessage text content
threadstringThread to save to (defaults to workflow thread)
tool_callsstringTool calls for assistant messages
tool_resultsstringTool results for tool messages
input_tokensstringLLM input token count
output_tokensstringLLM output token count
cache_creation_tokensstringCache write tokens
cache_read_tokensstringCache read tokens
thinkingstringExtended thinking/reasoning content
context_sequencestringContext sequence override
attachmentsstringAttachment IDs for user messages

The save_message block has access to the output.* namespace containing the node’s execution output.

Example: Complete Node

- id: compact
  action: Compact
  timeout: "10m"
  save_message:
    condition: "{{output.compacted}}"
    role: "{{output.message.role}}"
    content: "{{output.message.text}}"
    context_sequence: "{{output.context_sequence}}"
  args:
    thread: "{{inputs.thread}}"

Edges

Edges define the flow between nodes using conditional routing.

FieldTypeRequiredDescription
fromstringYesSource node ID
casesEdgeCase[]YesRouting cases

Edge Cases

FieldTypeRequiredDescription
tostringYesTarget node ID
conditionstringNoCEL expression (omit for default case)
labelstringNoLabel for visualization

Conditional Routing

Edges support CEL expressions for conditional routing. The first matching case wins:

edges:
  - from: call_llm
    cases:
      # Conditional case - tool calls present, needs approval
      - to: approval
        condition: nodes.call_llm.tool_calls != null && size(nodes.call_llm.tool_calls) > 0 && inputs.mode == 'manual'
        label: "require_approval"

      # Conditional case - tool calls present, auto-approve
      - to: execute_tools
        condition: nodes.call_llm.tool_calls != null && size(nodes.call_llm.tool_calls) > 0 && inputs.mode != 'manual'
        label: "auto_approve"

      # Default case - no condition means fallback
      - to: done
        label: "complete"

Parallel Execution

Multiple edges from the same node execute their targets in parallel:

# Three worktrees created simultaneously
- from: save_improved_prompt
  cases:
    - to: create_worktree_1

- from: save_improved_prompt
  cases:
    - to: create_worktree_2

- from: save_improved_prompt
  cases:
    - to: create_worktree_3

CEL Expression Context

Edge conditions have access to these namespaces:

NamespaceDescription
inputs.*Workflow input values
nodes.<id>.*Outputs from completed nodes
workflow.*Workflow metadata (id, name, chat_id)

Validation Rules

  • Source node (from) must exist in nodes
  • Target node (to) must exist in nodes
  • from: started is deprecated - use entry field instead
  • Don’t use .completed suffix on node IDs

Thread Modes

Thread configuration controls message isolation between workflow components.

Thread Config Structure

thread:
  mode: new()
  key: impl_1
  memo: true
  inject:
    role: user
    content: "Review the above."
FieldTypeDefaultDescription
modestringinheritThread relationship mode
keystringstaticThread identity key
memobooleantrueReuse thread across loop iterations
injectInjectConfig-Message to add to thread

Mode Values

ModeDescription
inheritUse parent workflow’s thread (default)
new()Create new empty thread
forkCopy parent thread context, then diverge

Shorthand Syntax

Thread mode can be specified as a simple string:

# Shorthand
thread: inherit
thread: new()
thread: fork

# Full form (equivalent)
thread:
  mode: inherit

Inject Configuration

Add a message to the thread before execution:

FieldTypeDefaultDescription
rolestringuserMessage role
contentstringRequiredMessage content (CEL expression)
attachmentsstring-Attachment IDs (CEL expression)

Examples

Inherit parent thread (default):

- id: call_llm
  action: CallLLM
  thread: inherit  # Uses parent's thread

New isolated thread:

- id: impl_1
  workflow: builtin://agent
  thread:
    mode: new()
    key: impl_1
    inject:
      role: user
      content: |
        You are Implementation Candidate #1.
        Task: {{nodes.improve_prompt.message.text}}

Fork with context:

- id: review
  workflow: builtin://agent
  thread:
    mode: fork
    key: reviewer
    inject:
      role: user
      content: |
        Review the implementations above.
        Pick the winner.

Thread Keys

Thread keys provide stable identities for deterministic thread IDs:

PatternUse Case
key: advocateDebate workflows with named participants
key: impl_1Parallel implementations
key: reviewerReviewer forks

Without a key, threads use static (single thread) or iteration index (in loops).

Memo Behavior

In loops, memo controls thread reuse:

thread:
  mode: new()
  memo: true   # Same thread reused across iterations (default)

thread:
  mode: new()
  memo: false  # Fresh thread each iteration

Groups

Groups organize inputs into named collections, enabling preset reuse across different workflow contexts.

Group Structure

FieldTypeDescription
descriptionstringHuman-readable description
tagstringTag for preset matching (e.g., agent)
inputsmapInput definitions for this group

Example

groups:
  Implementer:
    tag: agent
    description: Settings for implementer agents
    inputs:
      model:
        type: model
        default: ""
        description: Model for implementers
      temperature:
        type: number
        default: 1.0
        min: 0
        max: 2
      max_turns:
        type: integer
        default: 100

  Reviewer:
    tag: agent
    description: Settings for the reviewer agent
    inputs:
      model:
        type: model
        default: ""
      temperature:
        type: number
        default: 0.5

Accessing Grouped Inputs

Grouped inputs are accessed as nested objects in CEL expressions:

# Access grouped input
model: "{{inputs.Implementer.model}}"
temperature: "{{inputs.Reviewer.temperature}}"

# Fallback to workflow default
model: "{{inputs.Implementer.model != '' ? inputs.Implementer.model : inputs.model}}"

Tag-Based Preset Matching

Groups with the same tag share preset options. A preset with tag: agent can apply to:

  • Top-level inputs (if workflow has tag: agent)
  • Implementer group (if it has tag: agent)
  • Reviewer group (if it has tag: agent)

Validation Rules

  • Group names must be valid identifiers (alphanumeric + underscore)
  • Group names cannot conflict with top-level input names
  • All grouped inputs must have either required: true or a default value

CEL Expression Reference

CEL (Common Expression Language) expressions are used throughout workflows for dynamic values.

Syntax

Expressions are wrapped in {{...}}:

content: "{{nodes.call_llm.message.text}}"
condition: "{{inputs.mode == 'manual'}}"

Available Namespaces

NamespaceDescriptionExample
inputs.*Workflow input valuesinputs.model, inputs.temperature
nodes.<id>.*Outputs from completed nodesnodes.call_llm.tool_calls
workflow.*Workflow metadataworkflow.id, workflow.name
thread.*Thread informationthread.id, thread.message.content
trigger.*Trigger event datatrigger.message.content
output.*Current node output (save_message only)output.message.text
iter.*Loop iteration context (loops only)iter.iteration, iter.max
outputs.*Sub-workflow outputs (loop until only)outputs.exit_code

Common Functions

FunctionDescriptionExample
size()Length of array/stringsize(nodes.call_llm.tool_calls) > 0
has()Check if field existshas(inputs.model) && inputs.model != ''

Conditional Expressions

# Ternary operator
model: "{{inputs.override_model != '' ? inputs.override_model : inputs.model}}"

# Boolean logic
condition: "{{nodes.call_llm.tool_calls != null && size(nodes.call_llm.tool_calls) > 0}}"

# Equality
condition: "{{inputs.mode == 'manual'}}"

String Interpolation

Multi-value templates in content fields:

content: |
  Task: {{trigger.message.content}}
  
  Implementation: {{nodes.impl.response_text}}

Outputs

Workflow outputs define values available to parent workflows after completion.

Structure

outputs:
  message: "{{nodes.agent_loop.message}}"
  response_text: "{{nodes.agent_loop.response_text}}"
  iterations: "{{nodes.agent_loop.iterations}}"
  succeeded: "{{nodes.agent_loop.succeeded}}"

Validation Rules

  • Must use nodes.<id>.<field> format to reference node outputs
  • Referenced nodes must exist in the workflow
  • Referenced fields must be declared in the child workflow’s outputs (for workflow nodes)

Accessing Outputs

Parent workflows access child outputs via nodes.<node_id>.<output_name>:

# Child workflow outputs
outputs:
  summary: "{{nodes.summarize.text}}"

# Parent workflow uses them
- id: final_save
  action: SaveMessage
  args:
    content: "{{nodes.child_workflow.summary}}"

Complete Example

A workflow demonstrating multiple features:

name: parallel-compete
version: v0.0.11
description: Three agents compete to implement task, reviewer picks winner
status: published
tag: orchestration

entry: improve_prompt

inputs:
  mode:
    type: enum
    enum: ["manual", "agent", "plan"]
    default: "agent"
    description: Execution mode
  model:
    type: model
    default: ""
    description: Default LLM model

groups:
  Implementer:
    tag: agent
    description: Settings for implementer agents
    inputs:
      model:
        type: model
        default: ""
      temperature:
        type: number
        default: 1.0
        min: 0
        max: 2

  Reviewer:
    tag: agent
    description: Settings for reviewer agent
    inputs:
      model:
        type: model
        default: ""
      temperature:
        type: number
        default: 1.0

nodes:
  - id: improve_prompt
    action: CallLLM
    thread:
      mode: new()
    args:
      thread: "{{thread.id}}"
      model: "{{inputs.model}}"
      tools: false
      messages:
        - role: user
          content: |
            Improve this task into a clear specification:
            {{trigger.message.content}}

  - id: impl_1
    workflow: builtin://agent
    thread:
      mode: new()
      key: impl_1
      inject:
        role: user
        content: |
          You are Implementation Candidate #1.
          Task: {{nodes.improve_prompt.message.text}}
    args:
      mode: "{{inputs.mode}}"
      model: "{{inputs.Implementer.model != '' ? inputs.Implementer.model : inputs.model}}"
      temperature: "{{inputs.Implementer.temperature}}"

  - id: impl_2
    workflow: builtin://agent
    thread:
      mode: new()
      key: impl_2
      inject:
        role: user
        content: |
          You are Implementation Candidate #2.
          Task: {{nodes.improve_prompt.message.text}}
    args:
      mode: "{{inputs.mode}}"
      model: "{{inputs.Implementer.model != '' ? inputs.Implementer.model : inputs.model}}"

  - id: implementations_done
    join: all

  - id: review
    workflow: builtin://agent
    thread:
      mode: fork
      key: reviewer
      inject:
        role: user
        content: |
          Review both implementations and pick the winner.
    args:
      mode: "{{inputs.mode}}"
      model: "{{inputs.Reviewer.model != '' ? inputs.Reviewer.model : inputs.model}}"

edges:
  - from: improve_prompt
    cases:
      - to: impl_1
        label: "start_impl_1"
      - to: impl_2
        label: "start_impl_2"

  - from: impl_1
    cases:
      - to: implementations_done

  - from: impl_2
    cases:
      - to: implementations_done

  - from: implementations_done
    cases:
      - to: review

ui:
  positions:
    improve_prompt: { x: 250, y: 200 }
    impl_1: { x: 500, y: 100 }
    impl_2: { x: 500, y: 300 }
    implementations_done: { x: 750, y: 200 }
    review: { x: 1000, y: 200 }

Validation Summary

Workflows are validated at parse time. Common validation errors:

ErrorCauseFix
workflow name is requiredMissing name fieldAdd name: my-workflow
entry field is requiredNo entry point definedAdd entry: start_node
step ID is requiredNode missing idAdd id: my_step
duplicate step IDSame ID used twiceUse unique IDs
must have one of: run, agent, action...Node has no typeAdd action:, workflow:, etc.
can only have one of: run, agent...Node has multiple typesKeep only one type
references non-existent source stepEdge from invalidCheck step ID spelling
references non-existent target stepEdge to invalidCheck step ID spelling
must be either required or have a defaultInput without required/defaultAdd required: true or default: value
from: started is no longer supportedUsing deprecated patternUse entry: field instead