Workflow configuration
A workflow is a directed flowchart that routes each issue from the
first commit to a merged PR. You define states (nodes), the
actions or events that drive each state, and the transitions between
them. Run erg configure inside your repo to scaffold
a starting point, or omit the file entirely to use the built-in
defaults.
The settings block controls daemon-level behaviour:
concurrency limits, branch naming, merge strategy, and session
constraints. The source block selects the issue provider
and filter. The states block is the flowchart itself.
Pipeline overview
The recommended workflow begins with a planning step — Claude posts an implementation plan for human approval before writing any code.
plan.user_replied loops back for re-planning on feedback
· retry with exponential backoff ·
timeout enforced on wait states
Example config
source: provider: github # github | asana | linear filter: label: queued # GitHub/Linear: issue label; Asana: tag name (optional when section is set) section: Todo # Asana only: poll tasks in this board section instead of by tag states: coding: type: task action: ai.code params: max_turns: 50 max_duration: 30m system_prompt: file:.erg/prompt.md before: # runs before step; failure blocks - run: "make deps" after: # runs after; fire-and-forget - run: "make lint" retry: # retry with exponential backoff - max_attempts: 3 interval: 30s backoff_rate: 2.0 next: open_pr error: failed await_review: type: wait event: pr.reviewed timeout: 48h # enforced at runtime timeout_next: nudge_reviewers # dedicated timeout edge params: auto_address: true max_feedback_rounds: 3 merge: type: task action: github.merge params: method: squash # rebase | squash | merge
settings block reference
The optional settings block at the top level of
workflow.yaml controls daemon-level behaviour for the
repo. All fields are optional — omit any field to use the default.
| Field | Type | Default | Description |
|---|---|---|---|
max_concurrent |
int | 3 | Maximum number of sessions running simultaneously for this repo. |
branch_prefix |
string | none | Prefix applied to auto-generated branch names (e.g. bot/). |
cleanup_merged |
bool | false | Delete the branch and worktree automatically after a PR is merged or closed. |
max_turns |
int | 50 | Maximum autonomous turns per AI session before the session is stopped. |
max_duration |
int (minutes) | 30 | Maximum wall-clock time in minutes for a single AI session. |
auto_merge |
bool | false | Automatically merge the PR once CI passes and all required approvals are met. |
merge_method |
string | rebase | Merge strategy when auto-merging: rebase, squash, or merge. |
container_image |
string | built-in | Custom Docker image to use for containerized Claude sessions. |
settings: max_concurrent: 3 # max parallel sessions branch_prefix: bot/ # all branches start with bot/ cleanup_merged: true # delete branch/worktree after merge max_turns: 50 # stop session after 50 turns max_duration: 30 # stop session after 30 minutes auto_merge: true # merge automatically when CI passes merge_method: squash # rebase | squash | merge
source.filter keys
The filter block under source controls which
issues or tasks erg picks up. Available keys depend on the provider.
| Key | Provider | Description |
|---|---|---|
label |
GitHub, Asana, Linear |
GitHub and Linear: issue label to poll. Asana: tag name to
filter by. Optional for Asana when section is set —
use it to narrow within a section if needed.
|
project |
Asana |
Asana project GID. Required for all Asana workflows. Found in
the project URL:
app.asana.com/0/{gid}/list.
|
section |
Asana |
Board section name to poll. When set, erg fetches tasks from
that specific section rather than the whole project, enabling
kanban-style workflows where section position — not tags —
drives the lifecycle. Matched case-insensitively. If
label is also set, it is applied as an additional
tag filter on top of the section results.
|
team |
Linear | Linear team ID. Required for Linear workflows. |
State types
Each state in the workflow has a type that determines its behavior.
| Type | Description |
|---|---|
task |
Execute an action (e.g. ai.code,
github.merge)
|
wait |
Poll for an event with an enforced timeout (e.g.
pr.reviewed, ci.complete)
|
choice |
Evaluate rules against step data for conditional branching |
pass |
Inject data for downstream states to read |
succeed |
Terminal state — marks the work item as complete |
fail |
Terminal state — marks the work item as failed |
task
A task state executes a registered action and then
transitions to next on success or error on
failure. Every action that calls an external API (GitHub, Asana,
Linear, git remotes) should define a retry block. Network
failures are transient — without retry, a momentary outage permanently
fails the work item.
open_pr: type: task action: github.create_pr retry: - max_attempts: 3 interval: 15s backoff_rate: 2.0 # waits 15s, 30s, 60s next: await_review error: failed
Use catch to route specific error types to recovery
states instead of the generic error edge. Use
before hooks to run setup scripts (blocking) and
after hooks for teardown (fire-and-forget).
wait
A wait state polls for an external event on each daemon
tick. It does not advance until the event fires. Always set a
timeout to prevent indefinite waits — use
timeout_next to route the timeout to a dedicated state
(e.g. post a comment) rather than the generic error edge.
await_review: type: wait event: pr.reviewed timeout: 72h timeout_next: nudge_reviewers params: auto_address: true # Claude addresses feedback while waiting max_feedback_rounds: 3 next: merge error: failed
The pr.mergeable event is a shorthand that combines
pr.reviewed and ci.complete into a single
wait state. Use it for simple pipelines where you want both conditions
satisfied before proceeding.
choice
A choice state reads values from the accumulated step
data and branches to different states based on rules. Each rule
specifies a variable, an equals value, and a
next state. The default route fires when no
rule matches.
check_ci: type: choice choices: - variable: ci_passed equals: true next: request_reviewer - variable: ci_failed equals: true next: fix_ci default: failed
Choice states are the branching points of your flowchart. Combine them
with pass states to make decisions on injected
configuration flags, or with CI/review wait states to route on
outcomes.
pass
A pass state injects static data into the workflow
context and immediately transitions to next. Use it at
the start of a workflow to set configuration flags that downstream
choice states can inspect.
configure: type: pass data: close_issue_on_merge: true max_ci_fix_rounds: 3 next: coding
template
A template state embeds a reusable sub-workflow inline.
At load time, template states are expanded: each template's internal
states are copied into the calling workflow with a unique namespace
prefix, and exit ports are wired to the caller's mapped local states.
ci: type: template use: builtin:ci exits: success: review failure: notify_failed
The use field accepts either a built-in reference
(e.g. builtin:ci, builtin:review)
or a relative file path to a template YAML file (e.g.
.erg/templates/my-template.yaml). The exits
field maps the template's declared exit ports to local states in the
calling workflow.
Template file structure
A template file defines a self-contained workflow fragment with a single entry point and named exit ports.
template: code-review-merge entry: coding exits: success: done # internal terminal state failure: failed # internal terminal state params: - name: max_rounds default: 3 states: coding: type: task action: ai.code next: push # ... more states ... done: type: succeed failed: type: fail
Parameters
Templates can declare parameters with defaults. Callers override them
via the params field. Inside template states,
{{param_name}} placeholders in string values are
substituted with the resolved parameter value.
do_work: type: template use: .erg/templates/code-review-merge.yaml params: max_rounds: 5 exits: success: close_issue failure: notify_slack
Built-in templates
Erg ships with modular built-in templates that can be composed to
build any workflow. Each has success and
failure exits.
builtin:plan
Planning phase: AI generates a plan → waits for user feedback → re-plans on rejection (loop until approved). Includes 72h timeout with expiry notification.
builtin:code
AI coding session with preset turn and duration limits. Runs containerized by default.
builtin:pr
Creates a pull request linked to the source issue. Includes retry on transient failures.
builtin:ci
Waits for CI → fixes failures (bounded loop) → resolves merge conflicts via rebase or AI. Includes timeout and unfixable notifications.
builtin:review
Waits for PR review → addresses feedback (bounded loop). Handles approval, changes-requested, and external merge. Includes 48h timeout notification.
builtin:merge
Merges the PR (rebase by default) and cleans up the branch. Includes retry on transient failures.
Templates can be nested (a template can reference another template) and circular references are automatically detected. Template file paths must be relative to the repo root — absolute paths are rejected.
Error handling
retry
Task states support retry with configurable
max_attempts, interval, and exponential
backoff_rate.
catch
Route specific errors to recovery states with
catch blocks. Match by error type or message pattern.
timeout
Wait states enforce timeout durations at runtime.
timeout_next provides a dedicated transition edge.
error state
Every task state can define an error transition for
unrecoverable failures. Defaults to the
failed terminal state.
Hooks
before hooks run before step execution — if they
fail, the step is blocked. after hooks run after
completion and are fire-and-forget.
Hooks receive the following environment variables:
$ERG_BRANCH, $ERG_PR_URL,
$ERG_ISSUE_URL, $ERG_REPO_PATH,
$ERG_SESSION_ID, $ERG_ISSUE_ID,
$ERG_ISSUE_TITLE, $ERG_WORKTREE, and
$ERG_PROVIDER.