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.
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