Skip to content

03 - Workflow Configuration

This chapter covers how to design workflows: step definitions, execution scopes, loop policies, finalize rules, and safety configuration.

Workflow Structure

A workflow is defined under spec with three main sections:

yaml
apiVersion: orchestrator.dev/v2
kind: Workflow
metadata:
  name: my_workflow
spec:
  steps: [...]        # ordered list of steps
  loop: {...}         # loop policy
  finalize: {...}     # item terminal-state rules (optional)
  safety: {...}       # safety limits (optional)
  max_parallel: 4     # default parallelism for item-scoped segments (optional)

Step Definition

Each step is a unit of work in the workflow pipeline.

Complete Field Reference

yaml
- id: plan                          # (required) unique step identifier
  type: plan                        # (optional) step type — defaults to id value
  scope: task                       # (optional) "task" or "item" — defaults based on id
  enabled: true                     # (required) whether this step runs
  repeatable: true                  # (optional) can re-run in subsequent cycles (default: true)
  required_capability: plan         # (optional) agent capability needed (auto-inferred from id)
  template: plan                    # (optional) StepTemplate name for prompt injection
  builtin: self_test                # (optional) builtin step handler name
  command: "cargo check"            # (optional) direct shell command (no agent needed)
  is_guard: false                   # (optional) marks loop-termination guard steps
  tty: false                        # (optional) allocate TTY for interactive agents
  max_parallel: 2                   # (optional) per-step parallelism override
  timeout_secs: 600                 # (optional) per-step timeout in seconds
  cost_preference: balance          # (optional) "performance" | "quality" | "balance"
  prehook: {...}                    # (optional) conditional execution — see chapter 04
  behavior: {...}                   # (optional) on_failure, captures, post_actions
  store_inputs: [...]               # (optional) read from workflow stores before execution
  store_outputs: [...]              # (optional) write to workflow stores after execution

Step Execution Modes

A step can execute in one of four modes, resolved automatically:

ModeTriggerDescription
Builtinbuiltin: self_test or known idHandled by the engine internally
Agentrequired_capability: planDispatched to a matching agent
Commandcommand: "cargo check"Direct shell execution, no agent
Chainchain_steps: [...]Sequential child-step container with inherited pipeline vars

If you don't specify builtin or required_capability, the engine infers from the step id:

  • Known builtin IDs (init_once, loop_guard, ticket_scan, self_test, self_restart, item_select) → auto-builtin
  • Known agent IDs (plan, implement, qa, fix, etc.) → auto-capability

Chain runtime contract:

  • The parent step is a container; it does not directly run its own agent or command once chain_steps is present.
  • Child steps run serially and inherit the current pipeline_vars.
  • Child outputs should be promoted via normal captures and pipeline variables, not hidden special cases.
  • A child step applies its own behavior.on_failure first; the parent step then applies its own behavior.on_failure to the aggregated chain result.

Execution Profiles

execution_profile selects the runtime boundary for an agent step:

  • if omitted, the step uses implicit host
  • only agent steps may set this field
  • the referenced profile must exist in the same project

Recommended defaults:

  • implement / ticket_fix -> sandbox profile
  • qa_testing -> host profile

Example:

yaml
apiVersion: orchestrator.dev/v2
kind: ExecutionProfile
metadata:
  name: sandbox_write
spec:
  mode: sandbox
  fs_mode: workspace_rw_scoped
  writable_paths:
    - src
    - docs
  network_mode: deny

Defaults when omitted: mode: host, fs_mode: inherit, network_mode: inherit.

yaml
- id: implement
  type: implement
  required_capability: implement
  execution_profile: sandbox_write

Runtime notes:

  • On the current macOS backend, network_mode: deny may surface as DNS failure or connection failure; both map to sandbox_network_blocked.
  • On Linux linux_native, network_mode: allowlist is supported when the daemon runs as root, ip and nft are present, and the profile uses fs_mode: inherit.
  • Sandbox events now carry a stable reason_code; use that for automation before falling back to free-form stderr_excerpt.
  • network_target is best-effort metadata and may be empty for some error shapes.
  • network_mode: allowlist still is not supported on macOS; it fails fast with reason_code=unsupported_backend_feature instead of silently degrading.
  • network_mode: allowlist entries must be exact hostname/IP values with an optional port, for example api.example.com, api.example.com:443, 10.203.0.1, or [::1]:8443.

Sandbox Capability Matrix

FeaturemacOS (Seatbelt)Linux (native)Notes
mode: sandboxYesYesLinux requires ip/nft and root
fs_mode: inheritYesYes
fs_mode: workspace_readonlyYesNoLinux requires fs_mode: inherit [^1]
fs_mode: workspace_rw_scopedYesNoLinux requires fs_mode: inherit [^1]
network_mode: denyYesYes
network_mode: allowlistNoYesmacOS fails fast with reason_code=unsupported_backend_feature
writable_pathsYesNoRequires non-inherit fs_mode [^1]
Resource limits (max_memory_mb, etc.)YesYes

[^1]: Linux linux_native currently requires fs_mode: inherit until a filesystem isolation backend is implemented. Run orchestrator check to detect this at preflight time.

Tip: Run orchestrator check to detect platform-specific gaps before runtime. orchestrator manifest validate checks structural correctness; orchestrator check additionally detects platform-specific runtime gaps.

Known Step IDs

IDDefault ScopeDefault ModeDescription
init_oncetaskbuiltinOne-time initialization
plantaskagentImplementation planning
qa_doc_gentaskagentGenerate QA test documents
implementtaskagentCode generation
self_testtaskbuiltincargo check + cargo test --lib
self_restarttaskbuiltinRebuild binary + restart process
reviewtaskagentCode review
buildtaskagentBuild step
testtaskagentTest step
linttaskagentLint step
align_teststaskagentAlign tests after refactoring
doc_governancetaskagentAudit QA doc quality
git_opstaskagentGit operations
qaitemagentQA execution (per file)
qa_testingitemagentQA scenario execution (per file)
ticket_scanitembuiltinScan for active tickets
ticket_fixitemagentFix QA tickets
fixitemagentApply fixes
retestitemagentRe-test after fix
evaluatetaskagentEvaluate results
item_selecttaskbuiltinWP03: Select items by strategy
loop_guardtaskbuiltinLoop termination check
smoke_chaintaskagentChained smoke test

Execution Scope

Steps execute in one of two scopes:

  • task scope: Runs once per cycle. Used for planning, implementing, testing.
  • item scope: Runs once per task item (QA file). Used for QA testing, ticket fixing.

Steps are grouped into contiguous scope segments. Within an item-scoped segment, items can execute in parallel up to max_parallel.

┌─── Task Segment ────────────┐  ┌── Item Segment ──┐  ┌── Task Segment ──┐
plan + implement + self_test     qa_testing + ticket_fix  align_tests + doc_governance

Behavior Configuration

The behavior block controls what happens on step success/failure and how to extract results.

on_failure / on_success

yaml
behavior:
  on_failure:
    action: continue       # default — keep going
  # OR
  on_failure:
    action: set_status
    status: "build_failed"
  # OR
  on_failure:
    action: early_return
    status: "aborted"

  on_success:
    action: continue       # default
  # OR
  on_success:
    action: set_status
    status: "verified"

captures

Extract values from step results into pipeline variables:

yaml
behavior:
  captures:
    - var: build_output
      source: stdout       # stdout | stderr | exit_code | failed_flag | success_flag

post_actions

Run actions after a step completes:

yaml
behavior:
  post_actions:
    - type: create_ticket          # create a failure ticket
    - type: scan_tickets           # scan ticket directory
    - type: store_put              # write to workflow store (WP01)
      store: context
      key: finding
      from_var: plan_output
    - type: spawn_task             # spawn a child task (WP02)
      goal: "verify-changes"
      workflow: verify_workflow
    - type: generate_items         # generate dynamic items (WP03)
      from_var: candidates

Loop Policy

The loop policy controls how many cycles a workflow runs.

yaml
loop:
  mode: once              # run one cycle and stop (default)
yaml
loop:
  mode: fixed             # run exactly N cycles
  max_cycles: 2
  enabled: true
  stop_when_no_unresolved: false   # false = always run all cycles (default: true)
yaml
loop:
  mode: infinite          # run until guard stops or max_cycles hit
  max_cycles: 10          # safety cap

Loop Modes

ModeBehavior
onceSingle cycle, then stop
fixedExactly max_cycles cycles
infiniteRepeat until loop_guard step decides to stop, capped by max_cycles

The loop_guard builtin step should be the last step in infinite/fixed workflows. It evaluates whether unresolved items remain and decides whether to continue.

Finalize Rules

Finalize rules determine the terminal status of each task item at the end of a cycle. They use CEL expressions (same engine as prehooks).

yaml
finalize:
  rules:
    - id: qa_passed_no_tickets
      engine: cel
      when: "active_ticket_count == 0 && qa_ran"
      status: qa_passed
      reason: "QA passed with no active tickets"

    - id: fix_verified
      engine: cel
      when: "fix_ran && retest_success"
      status: fix_verified
      reason: "Fix applied and retest passed"

    - id: fallback_pending
      engine: cel
      when: "true"
      status: pending
      reason: "Default fallback"

Rules are evaluated in order; the first match wins. See Chapter 04 for finalize-context variables.

Safety Configuration

The safety block protects against runaway or destructive workflows.

yaml
safety:
  max_consecutive_failures: 3     # auto-rollback after N failures (default: 3)
  auto_rollback: true             # enable automatic rollback
  checkpoint_strategy: git_tag    # none | git_tag | git_stash
  binary_snapshot: true           # snapshot binary at cycle start (self-bootstrap)
  step_timeout_secs: 1800         # global step timeout (30 min)
  max_spawned_tasks: 10           # WP02: max child tasks per parent
  max_spawn_depth: 3              # WP02: max parent→child→grandchild depth
  invariants:                     # WP04: immutable safety assertions
    - id: no_delete_main
      check:
        command: "git branch --list main | wc -l"
        expect: "1"
      on_violation: abort

Putting It Together

A complete self-bootstrap-style workflow:

yaml
apiVersion: orchestrator.dev/v2
kind: Workflow
metadata:
  name: self-bootstrap
spec:
  max_parallel: 4

  steps:
    # ── Task segment: plan → implement → self_test ──
    - id: plan
      scope: task
      template: plan
      enabled: true
      repeatable: false

    - id: implement
      scope: task
      template: implement
      enabled: true

    - id: self_test
      scope: task
      builtin: self_test
      enabled: true

    # ── Item segment: qa_testing → ticket_fix ──
    - id: qa_testing
      scope: item
      template: qa_testing
      enabled: true
      prehook:
        engine: cel
        when: "is_last_cycle"
        reason: "QA deferred to final cycle"

    - id: ticket_fix
      scope: item
      template: ticket_fix
      enabled: true
      max_parallel: 2
      prehook:
        engine: cel
        when: "is_last_cycle && active_ticket_count > 0"

    # ── Loop guard ──
    - id: loop_guard
      builtin: loop_guard
      enabled: true
      is_guard: true

  loop:
    mode: fixed
    max_cycles: 2

  safety:
    max_consecutive_failures: 3
    auto_rollback: true
    checkpoint_strategy: git_tag

Next Steps