Workflows
Workflows define triggers, manual inputs, and lifecycle stages, then run inside a published workspace.
How workflows work
A workflow watches an external system or accepts a manual trigger, creates an agent with scoped access from its workspace, injects event context, and tracks the work through issue, code, PR, review, and completion states.
Author workflow YAML under .elasticclaw/workflows/, then publish it to a workspace with elasticclaw workflow push.
Deterministic steps and capabilities
Not every stage step needs the agent to decide. ElasticClaw Server can advance the workflow graph, run commands, inspect structured output, and call issue/PR APIs without interpreting chat. Prefer these hub-owned steps whenever the condition is known and testable.
Full stage field reference lives on the Stages page. This section is a capability map of what is deterministic today.
Hub-owned stage triggers
These conditions transition into a stage without asking the model to restate what happened:
pr_merged — Tracked pull request merges.
pr_closed — Tracked pull request closes without merge.
pr_conditions — Compound PR state. Supports ci: passing (checks success or skipped), reviews: clean (no changes-requested), and optional quiet_for (e.g. 30m, 1h) with no new comments in that window.
gate_result — Earlier stage gate verdict is pass or fail.
output_matches — Named pipeline output at a JSON path matches any value in any_of.
judge_verdict — Most recent judge stage reported pass or fail. The transition is deterministic; the verdict itself came from a model review.
stages:
- id: ready_to_merge
label: Ready to merge
triggers:
- pr_conditions:
ci: passing
reviews: clean
quiet_for: 30m
on_enter:
inject: |
CI is green and reviews are clean. Merge when ready.
- id: merged
label: Merged
terminal: true
triggers:
- pr_merged: {}
on_enter:
add_labels: [done]
remove_labels: [needs-review]
- id: closed_no_merge
label: Closed without merge
terminal: true
triggers:
- pr_closed: {}Deterministic gates over command output
After on_enter, a stage may evaluate a gate against JSON produced by a run (or other action that persists output). Gates do not ask a model to reinterpret logs.
run — Shell command in the sandbox; optional output stores parsed JSON for later stages and templates ({{ .Outputs.name.field }}).
gate — Pass/fail over a named output path with pass / fail value lists, required, and treat_skipped_as_pass.
gate_result — Branch on that verdict from another stage.
stages:
- id: validation
label: Validation
on_enter:
run:
command: python3 scripts/validate.py
output: validation
timeout: 15m
gate:
output: validation
pass:
path: status
values: [clean]
fail:
path: status
values: [issues, error]
required: true
- id: create_pr
triggers:
- gate_result:
stage: validation
verdict: pass
on_enter:
inject: |
Validation status: {{ .Outputs.validation.status }}. Open the PR.
- id: fix_validation
triggers:
- gate_result:
stage: validation
verdict: fail
on_enter:
inject: |
Validation failed: {{ .Outputs.validation.reason }}
Fix, re-run checks, then continue.Plan approval: freeform default vs deterministic plan_gate
Before implementation, ElasticClaw can require a visible plan. There are two modes — pick one path per workflow so plans are never double-approved:
Default (freeform) — If the workflow has no plan_gate: true stage, issue and workflow agents get a hub prompt to write a plan in chat. The hub accepts a substantial assistant message, then injects proceed. Existing installs keep this behavior without YAML changes.
Deterministic (plan_gate) — Opt in by setting plan_gate: true on a stage that also has a gate: block. Freeform plan approval is skipped entirely for that pipeline. Plan acceptance is a normal gate over structured JSON (for example a plan.json schema check), then gate_result advances to implementation.
plan_gate: true do not disable freeform plan approval. Only an explicit plan gate opts out.Recommended pattern: agent writes a plan artifact, emits a signal token, hub runs a schema validator, gate pass injects proceed.
stages:
- id: plan
label: Plan
entry: true
on_enter:
move_issue: In Progress
inject: |
Issue: {{.Issue.Identifier}} — {{.Issue.Title}}
URL: {{.Issue.URL}}
Before implementing, write .elasticclaw/plan.json with:
understanding (string)
area (string)
steps (array of strings)
verification (string)
Then say exactly: [PLAN_READY]
Do not edit product code until the next stage injects proceed.
- id: plan_validate
label: Validate plan
plan_gate: true # opts out of freeform hub plan approval
triggers:
- message_contains: "[PLAN_READY]"
on_enter:
run:
# Schema-only check — no NLP. Print JSON: {"status":"ok"|"incomplete",...}
command: |
python3 - <<'PY'
import json, pathlib, sys
p = pathlib.Path(".elasticclaw/plan.json")
try:
data = json.loads(p.read_text())
except Exception as e:
print(json.dumps({"status": "incomplete", "reason": f"unreadable: {e}"}))
sys.exit(0)
if not isinstance(data, dict):
print(json.dumps({
"status": "incomplete",
"reason": "plan.json must contain a JSON object",
}))
sys.exit(0)
# Require non-empty strings (not just truthy values like true/1).
string_fields = ("understanding", "area", "verification")
invalid = [
k for k in string_fields
if not isinstance(data.get(k), str) or not data[k].strip()
]
steps = data.get("steps")
if invalid:
print(json.dumps({"status": "incomplete", "reason": f"invalid {invalid}"}))
elif not isinstance(steps, list) or not steps or any(
not isinstance(step, str) or not step.strip() for step in steps
):
print(json.dumps({
"status": "incomplete",
"reason": "steps must be a non-empty list of strings",
}))
else:
print(json.dumps({"status": "ok"}))
PY
output: plan
timeout: 2m
gate:
output: plan
pass:
path: status
values: [ok]
fail:
path: status
values: [incomplete]
required: true
- id: implement
label: Implement
triggers:
- gate_result:
stage: plan_validate
verdict: pass
on_enter:
inject: |
Plan accepted (status {{ .Outputs.plan.status }}).
Implement now. When the PR is ready, say [DONE] with the PR URL.
- id: fix_plan
label: Fix plan
triggers:
- gate_result:
stage: plan_validate
verdict: fail
on_enter:
inject: |
Plan incomplete: {{ .Outputs.plan.reason }}
Update .elasticclaw/plan.json, then say [PLAN_READY] again.On plan_gate pass, the hub marks the plan accepted so freeform approval cannot re-fire if something races. Proceed is the implement stage's inject — not a keyword match on chat prose.
Full stage field notes (including plan_gate) are on the Stages page.
Hub-owned on-enter actions
These run on the server when a stage is entered (no chat marker required):
run — Deterministic command execution (command, timeout, continue_on_error, output).
dependency_updates — Ecosystem dependency bumps with structured JSON output. See Dependency updates.
add_labels / remove_labels — GitHub issue labels.
move_issue — Linear, Jira, Shortcut, or GitHub issue status (string or { status, issue_id }).
close_issue — Close the associated GitHub issue.
merge_pr — Request merge of the tracked PR through the server GitHub path.
notify — Send a message via a hub-configured notifier (for example Slack).
Skip rules
skip_if and skip_unless jump to another stage before on_enter when issue labels match (or do not match). Evaluation is deterministic over tracker labels only—not pipeline outputs.
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
terminal: trueNot deterministic (agent or model)
message_contains — Matches agent chat text such as [DONE]. Convenient, but prose is not trusted evidence of a PR, CI result, or side effect.
inject — Sends instructions to the agent; useful, but not a proof that work completed.
judge — Model-backed review with bounded inputs. Use for subjective quality; use gate for tool JSON and pr_conditions for CI/review state.
Workflow file
schema_version: v1
name: triage
enabled: true
trigger:
github_issues:
event: issue_labeled
repositories:
- my-org/my-app
states:
- open
labels:
- agent-ready
exclude_labels:
- do-not-automate
labelers:
- "*"
provider: daytona
tags: ["triage"]
color: teal
secret_refs:
GITHUB_TOKEN: github_app
enable_manual_trigger: true
inputs:
- name: issue
type: string
required: true
stages:
- id: working
label: Working
entry: true
on_enter:
remove_labels: [agent-ready]
add_labels: [agent-working]
inject: |
Issue: {{.Issue.Identifier}} — {{.Issue.Title}}
URL: {{.Issue.URL}}
Read CONTEXT.md and start working.
- id: pr_opened
label: PR Opened
triggers:
- message_contains: "[DONE]"
on_enter:
add_labels: [needs-review]
remove_labels: [agent-working]
- id: merged
label: Merged
triggers:
- pr_merged: {}
terminal: trueWorkflow fields
name — Workflow identifier inside the workspace.
enabled — Set false to pause the workflow.
trigger.github_issues — GitHub Issues source. Supports issue events, repositories, states, required labels, excluded labels, labelers, and assignee filters.
trigger.linear — Linear source. Supports status-change events, states, team, projects, required labels, excluded labels, and assignee filters.
trigger.jira — Jira source. Supports status-change events, project keys, states, required labels, excluded labels, and assignee filters.
trigger.shortcut — Shortcut source. Supports status-change events, workspace, states, required labels, excluded labels, and assignee filters.
provider — Sandbox provider override for agents created by this workflow.
tags and color — Dashboard metadata for created agents.
secret_refs — Environment variable to workspace secret name map.
inputs — Manual trigger inputs.
concurrency_group — Limit parallel agents by group.
working_status — Move the source issue to this status when the agent starts.
enable_manual_trigger — Allow dashboard and CLI manual triggers.
analytics_enabled — Enable run analytics for this workflow (defaults to true for new workflows).
stages — Lifecycle stages used by the workflow.
Issue trigger label filters
Issue-tracker triggers can require labels and reject labels at the same time. All configured labels must be present, and no configured exclude_labels may be present. Matching is case-insensitive and ignores leading or trailing whitespace.
trigger:
github_issues:
event: issue_labeled
repositories:
- my-org/my-app
labels:
- agent-ready
exclude_labels:
- do-not-automate
- blockedUse exclude_labels for labels such as blocked, security-hold, or needs-human-review that should prevent automatic agent creation even when the normal trigger labels are present.
Linear and Jira project filters
Linear and Jira triggers can restrict creation to specific projects. Use project IDs or names for Linear; use project keys for Jira.
trigger:
linear:
event: status_changed
team: ADV
projects:
- Adversary Labs
- 123e4567-e89b-12d3-a456-426614174000
states:
- Todo
jira:
event: status_changed
projects:
- ENG
- OPS
states:
- "To Do"Run commands and gates
Workflow stages can run deterministic commands in the agent workspace, persist structured output, and use gates to choose the next stage. This is useful for tests, security scanners, deploy previews, CodeBuild jobs, or any tool that can print JSON.
stages:
- id: validation
label: Validation
triggers:
- message_contains: "[DONE]"
on_enter:
run:
command: python3 scripts/validate.py
output: validation
timeout: 30m
continue_on_error: false
gate:
output: validation
pass:
path: status
values:
- clean
fail:
path: status
values:
- issues
- error
required: true
treat_skipped_as_pass: true
- id: create_pr
label: Create PR
triggers:
- gate_result:
stage: validation
verdict: pass
on_enter:
inject: |
Validation status: {{ .Outputs.validation.status }}.
Create the PR now.
- id: fix_validation
label: Fix Validation
triggers:
- gate_result:
stage: validation
verdict: fail
on_enter:
inject: |
Validation failed: {{ .Outputs.validation.reason }}
Fix the issue, commit locally, then say [DONE].{"status":"clean"}. treat_skipped_as_pass is for missing or skipped outputs that should continue through gate_result: pass.Failure feedback
If a workflow-created agent stops because provisioning, bootstrap, workspace setup, a workflow command, a required gate, or an issue action fails, ElasticClaw marks the run failed and posts a sanitized issue-tracker comment when the workflow has issue context. The comment summarizes what failed and suggests the next diagnostic step without dumping raw logs or secrets.
Failure comments are best-effort. If the tracker token cannot comment, the dashboard and run status still show the failure.
Review stages
A judge stage runs a model-backed review over bounded inputs such as the issue, current diff, captured test output, or selected files. Use judge stages for subjective review, and gates for deterministic tool results.
stages:
- id: review
label: Review
triggers:
- message_contains: "[READY_FOR_REVIEW]"
on_enter:
judge:
model: anthropic/claude-sonnet-4-6
inputs:
- issue
- git_diff
- test_output
output: review_result
instructions: |
Decide whether the implementation satisfies the issue.
require:
verdict: pass
- id: fix_review
triggers:
- judge_verdict: fail
on_enter:
inject: |
Review failed. Apply the requested fixes and say [READY_FOR_REVIEW].judge_verdict matches the most recent judge verdict for the workflow. Keep judge branches unambiguous; use deterministic gates when a transition must be scoped to a specific tool stage.Skip rules
Stages can skip based on issue labels before the stage is entered. Use skip_if to jump to another stage when the issue has any of the listed labels, or skip_unless to jump when the issue has none of them. The target stage is set with go_to.
stages:
- id: working
label: 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: trueRun history and logs
Every workflow trigger (cron, manual, or issue tracker) creates a run record. Use the CLI to list recent runs and inspect agent logs for a specific run.
elasticclaw workflow runs triage --workspace my-app --limit 20
elasticclaw workflow logs triage 5f35f8f6-7a2a-4f32-bb50-6e0cbd53c6ef --workspace my-appRun records include status, trigger type, timestamps, and the linked agent ID. The dashboard also shows run history and agent activity logs.
Workflow v2 schema
schema_version: 2 workflows replace the transcript-driven v1 stage model with a deterministic state machine. The workflow declares states, transitions, commands, CI/review policies, delivery constraints, and cron triggers. Authority comes from the paired workspace v2, so the workflow no longer embeds repositories, credentials, or provider settings.
schema_version: 2.Example v2 workflow
schema_version: 2
name: pull-request-delivery
enabled: true
initial_state: implementing
states:
implementing:
description: Implement the change and open a verified pull request.
phase: build
on_enter:
effects:
- agent.task:
prompt: |
Implement the requested change in the workspace, commit the
result, and open a pull request. The workflow advances when the
source-control connection reports the verified PR.
awaiting_ci:
description: A verified pull request exists and CI is unresolved.
phase: pr
invariant:
pull_request:
state: open
fixing:
description: Address verified CI or review failures and push fixes.
phase: build
on_enter:
effects:
- agent.task:
prompt: |
Inspect the CI or review failures for the current PR, apply
fixes, commit, and push to the PR branch. The workflow advances
when the PR head changes.
awaiting_review:
description: CI policy is satisfied.
phase: review
ready_to_merge:
description: Ready.
phase: review
completed:
phase: done
terminal: true
cancelled:
phase: done
terminal: true
transitions:
pr_opened:
from: implementing
on: pull_request.verified_open
when:
pull_request:
state: open
to: awaiting_ci
ci_satisfied:
from: awaiting_ci
on: ci.policy.evaluated
when:
ci:
policy: merge_ready
status: satisfied
to: awaiting_review
ci_failed:
from: awaiting_ci
on: ci.policy.evaluated
when:
ci:
policy: merge_ready
status: unsatisfied
to: fixing
fixes_pushed:
from: fixing
on: pull_request.head_changed
to: awaiting_ci
review_satisfied:
from: awaiting_review
on: review.policy.evaluated
when:
review:
policy: required_review
status: satisfied
to: ready_to_merge
review_unsatisfied:
from: awaiting_review
on: review.policy.evaluated
when:
review:
policy: required_review
status: unsatisfied
to: fixing
pull_request_merged:
from: ready_to_merge
on: pull_request.merged
to: completed
commands:
cancel:
from: [implementing, awaiting_ci, fixing, awaiting_review, ready_to_merge]
to: cancelled
require_reason: true
ci:
policies:
merge_ready:
all:
- pipeline: github-pr
checks: [lint, unit-tests]
- pipeline: depot-container
checks: [container-build]
satisfied_for: current_pr_head
review:
policies:
required_review:
all:
- connection: github-reviews
approvals:
minimum: 1
invalidate_on_new_head: true
delivery:
pull_requests:
required: true
minimum: 1
ci_policy: merge_ready
review_policy: required_review
completion: all_merged
events:
ci.run.completed:
clauses:
- from: awaiting_ci
when:
all:
- pipeline:
equals: depot-container
- conclusion:
equals: failure
assert:
work.ci_failure_investigation_requested: true
effects:
- agent.task:
prompt: Investigate the Depot CI failure.The workflow is triggered per delivery item (for example, a linked issue or a manual start). Active build states such as implementing and fixing start durable agent.task effects via on_enter; waiting states such as awaiting_ci, awaiting_review, and ready_to_merge advance only on verified external events.
Core v2 concepts
states — State machine nodes. Every enabled state must declare a phase from setup, context, plan, build, test, pr, review, or done. Terminal states set terminal: true.
transitions — Directed edges between states. from is the source state (or list); on is an event kind; when is a predicate tree; to is the destination. Transitions for the same from + on must have disjoint when clauses.
commands — Operator-initiated transitions such as cancel or retry.
ci.policies and review.policies — Named policies referenced by delivery. CI policies list required pipeline checks; review policies list required approvals.
delivery.pull_requests — Dynamic delivery constraints. The hub verifies every PR through the workspace source-control connection before it becomes visible to the state machine.
events — Custom event definitions. Each event kind has ordered clauses matched by from state + when predicate.
Effects and facts
v2 workflows act through durable effects instead of chat markers. Allowed effects include:
agent.task — Start a durable agent task with a prompt.
exec.run — Run a shell command in the workspace. Requires the execution provider to grant execute_command.
dependency.update — Run a deterministic dependency update pass. Requires the execution provider to grant dependency_update and a repository with permissions: write.
ci.trigger, ci.retry, ci.cancel — CI pipeline actions. The CI connection must not have restricted the required capability.
issue.comment — Post a comment through a configured issue tracker connection.
Effects and hub adapters write facts into protected namespaces (ci.*, pull_request.*, review.*, effects.*, workflow.*, operator.*, exec.*). Workflows may only write work.* or custom.* facts with assert or set.
Predicate language
when and invariant clauses support a small, deterministic set of operators:
equals, not_equals, in, not_in, exists
all (conjunction) and any (disjunction)
[DONE] and message_contains are never trusted as control signals in v2. Use explicit events, transitions, and commands instead.Converting from v1 to v2
Use the CLI conversion command to draft a v2 workspace or workflow. Review warnings and pair-validate the workspace + workflow before enabling.
elasticclaw workspace convert .elasticclaw/workspaces/my-ws
elasticclaw workflow convert ./wf.yaml --workspace ./ws -o wf.v2.yamlThe converter produces a draft with warnings. Common mappings: v1 provider/nix/docker move to execution; v1 stages become v2 states; v1 entry: true becomes initial_state; v1 dependency_updates becomes the dependency.update effect; v1 cron triggers map 1:1 to v2 trigger.cron.
CLI commands
elasticclaw workspace create --name my-app
elasticclaw workspace push my-app
elasticclaw workflow push --workspace my-app .elasticclaw/workflows/triage.yaml
elasticclaw workflow list --workspace my-app
elasticclaw workflow show triage --workspace my-app
elasticclaw workflow trigger triage --workspace my-app --input issue=ENG-123
elasticclaw workflow runs triage --workspace my-app --limit 20
elasticclaw workflow logs triage <run-id> --workspace my-app