Agents

Turns, retries and hand-off

What happens from dispatch to hand-off, how failures are retried, and how to stop, retry or resume a task by hand.

A task's run follows the Symphony state machine: dispatch, turns, then either a hand-off, a terminal state, or a retry. SI Fabrik adds a graceful hand-off drain, stall detection and guardrails on top.

From dispatch to hand-off#

  1. Dispatch

    On each poll, eligible tasks are sorted by priority, then age, then identifier, and dispatched while slots are free: agent.max_concurrent_agents, any per-state cap in agent.max_concurrent_agents_by_state, and the host-wide Max agents running at once. Before dispatch, a preflight checks credentials, the gateway, links, Docker or the Cloudflare bridge; a failing preflight blocks the whole project with a reason in its header.

  2. Prepare

    The workspace is created or reused, the task branch checked out, origin/<base> refreshed, and before_run run. Linked services and the app preview start. The prompt is rendered.

  3. Turns

    The first turn gets the rendered prompt. After each turn, SI Fabrik refreshes the task from the tracker. While it's still active and under agent.max_turns, the next turn starts with a short continuation message. Budgets are checked before every turn.

  4. After the attempt

    after_run runs, screenshots are collected, and the branch is saved into the project repository. If the task is still active, a continuation check runs a second later to start the next attempt.

Hand-off and terminal states#

Reconciliation runs on every poll and compares running tasks with the tracker:

The task moved toSI Fabrik
A hand-off state (not active, not terminal), such as Human ReviewLets the in-flight turn finish so its summary and usage are kept, starts no more turns, and force-stops the run after 90 seconds. Then it pushes the branch and opens or updates the pull request. The card shows Handing off.
A terminal state, such as Done or CanceledStops the run immediately, saves the branch and removes the workspace.
Gone from the trackerStops the run.

Failures and retries#

An attempt fails when the agent process exits with an error, a turn fails, a hook fails, the silence timeout is hit, or the run stalls. Failed attempts are retried with exponential backoff:

Failed attemptDelay
1st10 s
2nd20 s
3rd40 s
nth10 s × 2ⁿ⁻¹, up to agent.max_retry_backoff_ms (default 5 minutes)

A retry that finds no free slot is re-queued without counting as a real failure.

Two watchdogs apply to both runners:

  • Silence timeout (codex.turn_timeout_ms, default 1 hour): a turn with no output for this long fails.
  • Stall detection (codex.stall_timeout_ms, default 5 minutes, 0 to turn off): a run with no agent activity for this long is stopped and retried with outcome stalled.

Without guardrails, the spec retries forever. SI Fabrik parks a task after limits.max_attempts_per_issue failures in a row (default 5), and pauses a project after limits.pause_after_consecutive_failures failures in a row across tasks (default 5). See Parking, pausing and credential errors.

Run outcomes#

Every attempt is recorded in Runs with one of these outcomes:

OutcomeMeaning
succeededThe attempt finished normally
failedThe agent, a turn or a hook failed
timedOutThe silence timeout was hit
stalledNo activity for stall_timeout_ms
canceledStopped by you, by a terminal state, or by a hand-off drain

Act on a task by hand#

You want toDo
Start a task nowBoard → right-click → Dispatch Now
Stop a running agentStop in Live Agents, or Stop Agent on the card
Retry without waiting for the backoffRetry now in Mission Control's retry queue
Give up the claim on a queued retryThe × next to it in the retry queue
Resume a parked taskResume Retries on the card or in the project banner
Resume a paused projectResume in the project header
Stop dispatching but let agents finishPause the project (the pause button in its header)
Stop everything in a projectStop in its header

Restarts#

SI Fabrik recovers from the tracker and the filesystem: on startup it removes workspaces of tasks already in a terminal state, and active tasks are dispatched again on the first poll. Spend, parked tasks and automatic pauses are kept across restarts, so a crash loop can't start burning tokens again on its own.

See also#