Skip to content

How workflows work

How workflows work

Every workflow — default or custom — is built from a small set of primitives: states classified by kind, declared roles, and transitions between states. Forge attaches automatic behavior based on those primitives so you don’t have to wire dispatch, retry tracking, or cleanup yourself.

State kinds

Each state has a kind that controls what Forge does for you automatically:

backlog — A parking lot. Tasks here are not claimable by agents. Only a user or planning agent can promote a task to todo.

initial — Exactly one per workflow. Tasks start here when created. This is where the claim flow and the auto-dispatch scheduler look for work. The default initial state is todo.

active — Work is in progress. Forge attaches heartbeat monitoring and crash recovery to every active state. If the agent’s process goes silent, the task resets to the initial state and the error is recorded on the audit log.

gate — A validation or processing checkpoint. Retry budget tracking auto-attaches. When a gate is rejected beyond the configured budget, the task moves to blocked. The review, merging, and planning states are all gates in the default workflow. See Gates and retries for details.

terminal — Absorbing states with no outbound transitions. Reaching a terminal state is permanent. done triggers dependency satisfaction (unblocking dependent tasks) and immediate worktree cleanup. cancelled triggers deferred cleanup.

custom — No built-in behavior. You control everything through hooks and triggers. Use this when none of the above kinds fit.

Roles

Roles name the agents or users responsible at each state. The default workflow defines three:

RoleDefault stateWhat it does
plannerplanningAnalyzes the task and writes a plan or creates subtasks.
coderin_progressImplements the work.
reviewerreviewReviews the diff and emits a pass/fail verdict.

Role assignments are per-task and stored separately from the workflow definition. Assigning a role is declarative — it records who will do the work but does not trigger dispatch immediately. Dispatch happens reactively when the task enters the state that references the role.

There is also an engine-reserved identity called assignee (sometimes executor in logs). When an agent claims a task, it is auto-bound as the coder assignee for that task if no explicit coder assignment already exists. This is how claim-based work wires up without requiring a manual role assignment step.

A custom workflow can declare any roles it wants — designer, qa, triager, etc. — and reference them from state.role. See Defining a workflow.

Dispatch: reactive and scheduled

Forge dispatches work in two complementary ways.

Reactive dispatch — When a task enters a state that has a dispatch_role_agent hook, Forge immediately tries to dispatch the agent assigned to that state’s role. If the agent is at capacity, dispatch is skipped and the task waits until capacity frees up.

Auto-dispatch scheduler — A background service polls for tasks in initial-kind states (default: todo) that have an assigned coder and an available agent. It transitions those tasks forward, and the reactive hooks take over from there. Tasks are evaluated in priority order (highest first), then by creation time (oldest first) within the same priority.

Together these ensure that once a task has a coder assigned and the agent has capacity, work starts without any manual push.

Column groups for board rendering

Each state has a column field (a string label). States that share the same column value render together in the same kanban column. This lets the engine operate on fine-grained states while the board stays readable.

In the default workflow, review, merging, and merge_failed all share the "Review" column. Tasks in any of those states appear in that column with a sub-state badge (e.g. “merging”). Dragging a card into a column targets the column’s primary state — the first state declared for that column.

Manual workflow advance

If you want to override the current state of a task and move it forward without waiting for the agent, use the manual advance action in the UI or API. Manual advance:

  • Stops any running execution (recorded as status = cancelled, stopped_by = user:manual_advance).
  • Moves the task to the next logical forward state, skipping the configured before_exit guards (since this is an explicit user decision).
  • Fires the target state’s normal on_enter and after_enter hooks.
  • Clears any carried error annotation from the source state.

Manual advance resolves the target state from the workflow graph — it picks the nearest forward transition that is not a self-loop, not the gate’s reject target, and not the cancellation state.

For approving or rejecting a gate explicitly (which respects retry budgets and fires the gate’s normal transitions), see Gates and retries instead.