Workflows
The workflow engine is the agent’s durable loop. An operator does not script tasks; the coordinator starts workflow runs, and each run advances one step at a time. A task is a unit of work the agent drives exclusively through the current step of its run.
Runs, steps, and mutexes
Section titled “Runs, steps, and mutexes”- A run is stored durably with its current step, status, retry count, and
step_state. - Each step has a retry policy, a timeout, a poll interval, and optional mutexes.
- Step timeout is the total wall-clock budget for one step across all attempts, including retry backoff. Schedule delay (waiting for the workflow’s parallelism limit) and worker-slot waits do not count against it.
- Retry backoff is the wait between a failed attempt and the next; the first retry waits exactly the strategy’s base.
- Mutexes such as
deploy:staginganddeploy:productionserialize deploys. A stale mutex — one held by a run that is no longer running — is reconciled at startup and on the watchdog cadence, so it stops blocking within one watchdog interval instead of at the next restart. - A fail-fast step has a zero timeout: it times out on its first observed tick before its action runs, so an unstarted fail-fast step fails at queue time.
Retries are recorded as step transitions. A run that exhausts its retry budget produces one retry row per errored attempt, then a failed row.
Workflow kinds
Section titled “Workflow kinds”| Kind | Role |
|---|---|
task_lifecycle | Issue delivery: implement, CI, staging, merge, production |
github_sync | Issue pickup and external PR review |
harness_converge | Diff the repo against the merged harness pack, open one sf/harness PR |
docs_converge | Daily docs contract scan; a coding-runtime worker fixes drift and the agent opens one sf/docs PR |
production_monitor | Poll environment health; escalate to investigation on threshold |
investigate_incidents | Spawn an investigator and file issues for findings |
optimize_performance | Observe → hypothesize → open PR-gated performance issues |
pentest | Periodic security audit (Cloudflare security-audit skill) of the repo source; files confirmed and needs-validation security issues |
self_heal | Turn agent failures, red default-branch CI, and code-health findings into pickup issues (source profile) |
landing | Serialize landing: take the head of the per-repo landing queue and rebase, verify, and merge one PR at a time |
task_lifecycle steps
Section titled “task_lifecycle steps”backlog → waiting_for_info → waiting_for_worker → implementing → waiting_for_ci_checks → deploying_to_staging → staging_healthchecks → merging_pr → [waiting_for_rebase_worker / rebasing_from_main] [waiting_for_ci_fix_worker / fixing_ci] → deploying_to_prod → prod_healthchecks → [reverting_pr] → donedelivery_profile=source short-circuits staging and production: once CI is green the run merges to main using pr_merge_method and is done. delivery_profile=live runs the full staging → production path. pr_merge_method (squash default, rebase/merge allowed) picks the merge method.
The workflow runs at most 3 in parallel and tolerates 5 failure recoveries.
Failure recovery
Section titled “Failure recovery”- A worker death with no observed result is a transient failure: the run probes the PR first. If the PR still needs that worker (CI failed, or the branch still needs a rebase) it returns to the matching
waiting_for_*step and respawns a fresh sandbox, resuming the run’s Pi Coding session so the worker continues its conversation; if the worker’s goal was already achieved (its push turned CI green) it continues atwaiting_for_ci_checkswithout spending a failure recovery. - A completed coding-runtime session that opened no PR also respawns rather than escalating on the first occurrence.
- Transient GitHub API failures (
error connecting to api.github.com, HTTP 5xx, rate limits) keep the CI steps pending and retry on the next poll instead of permanently failing the run. Auth, permission, and validation errors still fail. - Dead commit statuses do not park the CI wait: a legacy status (
StatusContext— deploy-preview bots and the like) that has been pending past a 40-minute window is treated as a lost webhook, not a running check. When nothing else is non-green the PR counts as clean and the run proceeds. GitHub reports such PRs asUNSTABLE(mergeable; a non-required check is not green) and Steelframe still honorsBLOCKED, which is how required checks or reviews surface. CheckRuns are never treated this way: Actions reports its own transitions and enforces its own job timeout. - A recorded non-transient error (for example, an API error) fails the run.
- Triage can park a task: an epic or a request for clarification labels the issue
steelframe:needs_humanand stops the run. Parking is the intended outcome, not a defect, and the label holds pickup until a human responds. The park is durable: if the comment or label write fails,github_syncretries it and holds the issue out of pickup until the label lands. waiting_for_inforesumes on a human reply or a Decision comment (<!-- steelframe-decision -->, including GitHub App authorship). Ifsteelframe:needs_humandelete fails after the comment,github_syncretries pending unpark (does not hold pickup).- If a run still fails, the issue may be labeled
steelframe:needs_human; failure is terminal by design. Start again from a new issue or run.
Step errors and failure recoveries are appended to the run’s step log, viewable in the run detail.
Notifications
Section titled “Notifications”Discord carries alerts and their resolutions only; routine operations stay in the run view and the journal.
⚠️ #N needs a human— a task stopped on something only a human can clear (a real blocker; the issue is labelledsteelframe:needs_human).🔧 #N stopped on agent infrastructure— the host, not the task: missing or outdated CLI, full disk, exec format. The issue is not parked.▶️ #N resumed— the resolution of either alert:needs_humancleared, or a failed run retried.- Environment degraded / healthy again, and background-workflow failures (debounced per workflow).
Not sent: successful task completions, and idle memory recycles. Both stay
log-only — journalctl -u steelframe carries agent memory every five minutes
and recycling idle agent when the guard trips.
Operating runs
Section titled “Operating runs”| Method | Path | Purpose |
|---|---|---|
GET | /api/workflows | Summary counts by kind and status |
GET | /api/workflows/{kind} | Runs of one kind (?limit= &offset=) |
GET | /api/workflows/{kind}/runs/{id} | Run plus step log |
POST | /api/workflows/{kind}/runs/{id}/terminate | Terminate a running run |
POST | /api/workflows/optimize/trigger | Start or report an optimize_performance run |
POST | /api/tasks/sync | Force a github_sync run |
Termination sets the run’s error to terminated by @<user> via API/TUI, records workflow.terminate, and returns { "terminated": true }. Terminating a run that is not running returns terminated: false with a reason.
Gotchas
Section titled “Gotchas”- Singleton workflows (
github_sync,harness_converge,docs_converge,self_heal,optimize_performance,pentest,production_monitor,landing) have max parallelism 1; their cadence keys are minutes or hours, and0disables them.landingis continuous (it waits on an empty queue), so it has no cadence key. delivery_profile=sourcenever startsproduction_monitorand never startsself_healwhen the profile islive.- A
task_lifecyclerun atmerging_pr(source) ordeploying_to_stagingwithstaging_deploy_strategy=merge_pr(live) waits on thelandingqueue:repo_merge_strategy=steelframe_landing(default) serializes the merge,github_merge_queuebypasses the queue and auto-merges as before. - A run terminated or failed after partial work leaves its sandbox cleaned up, not resumable.