Workflow Stages

Workflow stages define the lifecycle of a workflow-created agent: entry instructions, done signals, issue transitions, PR events, and terminal stages.

What are stages?

Stages are the state machine that drives a workflow-created agent through its lifecycle. Each stage has entry conditions, actions, and transitions. ElasticClaw Server injects instructions into the agent as messages at each stage transition.

Stage structure

yaml
stages:
  - id: working
    label: "Working"
    entry: true
    on_enter:
      inject: |
        Read your CONTEXT.md and start working on the issue.
        Narrate your progress as you go. Keep me updated.

  - id: pr_opened
    label: "PR Opened"
    triggers:
      - message_contains: "[DONE]"
    on_enter:
      move_issue: "In Review"
      inject: |
        PR created. Watch for CI results and review comments.

  - id: merged
    label: "Merged"
    triggers:
      - pr_merged:
    terminal: true

  - id: closed_no_merge
    label: "Closed Without Merge"
    triggers:
      - pr_closed:
    on_enter:
      inject: |
        PR was closed without merging. Decide: reopen, new PR, or ask the user.

Tool gates

Stages can run a command and evaluate its structured output before deciding what happens next. The command runs in the agent workspace, and the named output is available to later templates as{{ .Outputs.<name>.<field> }}.

yaml
stages:
  - id: validate
    label: Validate
    triggers:
      - message_contains: "[DONE]"
    on_enter:
      run:
        command: python3 scripts/check.py
        output: check
        timeout: 10m
    gate:
      output: check
      pass:
        path: status
        values: [passed, skipped]
      fail:
        path: status
        values: [failed, error]
      required: true

  - id: create_pr
    triggers:
      - gate_result:
          stage: validate
          verdict: pass
    on_enter:
      inject: |
        Validation passed with status {{ .Outputs.check.status }}.

  - id: fix
    triggers:
      - gate_result:
          stage: validate
          verdict: fail
    on_enter:
      inject: |
        Validation failed: {{ .Outputs.check.reason }}
Gates are deterministic. They inspect command output; they do not ask a model to reinterpret logs.

Plan approval gates

Issue and workflow agents normally get a freeform hub prompt to write a plan in chat before implementing. To replace that with a deterministic gate, mark a stage with plan_gate: true and configure a gate: on that stage.

  • When any stage has plan_gate: true with a gate, the hub skips freeform plan approval for that pipeline (no keyword scoring, no double proceed).
  • Validation-only gates without plan_gate leave freeform plan approval in place (existing workflows stay compatible).
  • On plan-gate pass, the hub records plan accepted so freeform cannot re-fire; the next stage's inject is the proceed signal.
yaml
stages:
  - id: plan
    entry: true
    on_enter:
      inject: |
        Write .elasticclaw/plan.json (understanding, area, steps, verification),
        then say [PLAN_READY]. Do not implement until proceed is injected.

  - id: plan_validate
    plan_gate: true
    triggers:
      - message_contains: "[PLAN_READY]"
    on_enter:
      run:
        command: python3 scripts/validate_plan.py
        output: plan
    gate:
      output: plan
      pass:
        path: status
        values: [ok]
      fail:
        path: status
        values: [incomplete]
      required: true

  - id: implement
    triggers:
      - gate_result:
          stage: plan_validate
          verdict: pass
    on_enter:
      inject: Plan accepted. Proceed with implementation.

See the full worked example under Workflows → Plan approval.

Stage fields

id — Unique stage identifier (kebab-case)

label — Human-readable label shown in UI

entry: true — Marks the initial stage when an agent is created

terminal: true — Marks a terminal stage (the agent will be terminated)

triggers — Conditions that transition into this stage

on_enter — Actions to run when entering this stage

gate — Optional deterministic pass/fail evaluation over a named run output

plan_gate: true — Marks this stage's gate as plan approval. When present with a gate block, freeform hub plan approval is skipped for the workflow. Ordinary gates without this flag do not change plan behavior.

Triggers

Each trigger defines a condition. Exactly one field should be set per trigger:

message_contains: "[DONE]" — Matches when an agent message contains this substring (case-insensitive)

gate_result — Matches a previous gate verdict, such as pass or fail

judge_verdict — Matches a model review verdict from a judge stage

output_matches — Matches a persisted output path against one or more expected values

pr_merged: — Triggers when the tracked PR is merged (key presence alone activates it)

pr_closed: — Triggers when the tracked PR is closed without merging

pr_conditions: — Compound conditions that must all pass:

  • ci: "passing" — All check runs are success or skipped
  • reviews: "clean" — No CHANGES_REQUESTED reviews
  • quiet_for: "1h" — No new comments in the last hour

Stage skip rules

A stage can be skipped before its on_enter actions run based on the source issue labels. skip_if jumps when the issue has any of the listed labels; skip_unless jumps when the issue has none of them. The target stage is given by go_to.

yaml
stages:
  - id: working
    entry: true
    skip_if:
      issue_labels:
        labels:
          - skip-agent
      go_to: skipped
    on_enter:
      inject: Read CONTEXT.md and start working.

  - id: skipped
    label: Skipped
    terminal: true
Skip rules are evaluated before the stage is entered. They cannot reference pipeline outputs, only the issue labels on the triggering issue.

On-enter actions

inject — Sends a user message to the agent

run — Runs a command in the agent workspace and can persist stdout as structured output. Supports command, output, timeout, and continue_on_error.

dependency_updates — Updates Go and npm dependencies with native tooling and persists structured output

judge — Runs a model-backed review over bounded inputs and persists a verdict. Supports model, inputs, instructions, output, require.verdict, max_tokens, timeout, and continue_on_error.

move_issue — Moves the associated Linear, Jira, Shortcut, or GitHub issue. It accepts a status string or { status, issue_id }.

close_issue: true — Closes the associated GitHub issue

add_labels — Adds labels to the associated GitHub issue

remove_labels — Removes labels from the associated GitHub issue

merge_pr: true — Attempts to merge the tracked PR through ElasticClaw Server's GitHub PR merge path

Template variables

inject messages and mapped move_issue.issue_id values can use Go template variables. Automatic issue-triggered workflows expose {{.Issue.Identifier}}, {{.Issue.Title}}, {{.Issue.URL}}, and{{.Issue.Description}}. Manual triggers expose{{.Inputs.name}} values from the workflow inputs.

Default stages

A typical issue workflow has four stages:

  1. working — The agent starts here. Injected with "read CONTEXT.md and start working."
  2. pr_opened — Triggered by [DONE] message. Moves issue to done status.
  3. merged — Triggered by PR merge. Terminal — the agent terminates.
  4. closed_no_merge — Triggered by PR close without merge. Injected with guidance.
Stages are pushed as part of workflow YAML. Edit the workflow file, then run elasticclaw workflow push --workspace <workspace> <file-or-dir>.