Recovery model

Updated Jul 27, 2026

Do not try to resume a failed workflow from transient cache state. Treat origin/<base> as the source of truth, inspect the result, clean up or repair the target project as needed, and relaunch from the sidecar. The workflow re-selects unblocked roadmap work from the current roadmap state.

When a normal task or addendum fails or halts after its worktree exists, the ODW workflow runs a read-only assessment of the surviving task branch unless assessPartialBranches=false. The per-task result may include an assessment object and the top-level result includes an assessments summary array. The classification is one of:

  • adopt-complete: the branch appears to satisfy the task and can continue through the ordinary review and integration path after gates are verified.
  • adopt-partial: the branch contains a coherent useful slice, but the roadmap task must remain unchecked.
  • continue-manual: the branch needs operator judgement before any merge.
  • discard: the branch is stale, unsafe, incoherent, or too incomplete to keep.

Assessment is report-only. It never marks roadmap checkboxes, pushes, merges, or cherry-picks. Use it to decide whether to preserve, manually finish, park, or discard the branch before relaunching from origin/<base>.

After an adopt-partial or continue-manual verdict — and also after an infrastructure fault such as schema-retry exhaustion — the workflow commits any dirty docs/execplans/*.md artefacts onto the task branch so they survive worktree cleanup. This is artefact salvage. It never merges, pushes, or ticks a roadmap checkbox; it commits only under a deterministic machine identity (df12-build). Paths outside the docs/execplans/ tree, symlinks, and paths that escape the worktree are all rejected before the commit. Salvage does not run for adopt-complete (which proceeds through the ordinary path) or discard (thrown away); it also skips when host evidence collection failed and is therefore untrustworthy (the deterministic continue-manual from a collection-error), or when no worktree path is present in the assessment evidence. The per-task result carries a result.salvage field ({ classification, committed, skipped, sha, detail }) for every branch where salvage ran or was skipped; the top-level run result includes a salvages summary array and the summary string appends | salvaged artefacts on N branch(es) when N > 0. Salvage runs only when partial-branch assessment is enabled (assessPartialBranches=true), the same switch that governs assessment; it does not run when that setting is disabled. Auth failures, provider outages such as 429, 500, or 529, worktree-creation failures, dry runs, successful tasks, and manual-merge-ready branches are not assessed. Provider outages also suppress the final remediation flush, so transient adapter failures do not create roadmap work.

Infrastructure faults are classified separately from product failures. When a stage agent's process dies — an ODW adapter timeout or crash, or schema-retry exhaustion — the failure carries no evidence about the task branch, so the workflow retries the stage agent in place up to stageAttempts total attempts. A persistent fault terminates the task with status infra-fault rather than failed: no assessment agent is spawned, remediation triage writes are skipped (as with provider faults), and the halt detail directs the operator to relaunch with resumePartialBranches=true and resumeMode="continue". Integration is the exception: a fault there is never retried, because a hidden-success first attempt may already have pushed — inspect origin/<base> and the roadmap before relaunching. The run result's faultMetrics object counts retries and terminal faults per class (infraRetries, infraFaults, providerFaults, authFaults).

That host-level caution is distinct from the integration agent's own retry loop, which is idempotent by construction. Because sibling tasks merge through a single merge lock, origin/<base> can advance between a task's rebase and its squash push. The agent lands each squash on a throwaway branch created with git switch --discard-changes -C integrate-<slug> origin/<base>: the -C force-resets integrate-<slug> onto the freshly fetched base whether or not the branch already exists, and --discard-changes restores the index and working tree to that base, throwing away any half-finished squash a prior attempt left staged. (The earlier git switch -c form instead failed with a branch named 'integrate-<slug>' already exists the moment a first attempt had created the branch.) So when the push is rejected non-fast-forward, the agent re-fetches, re-rebases the task branch onto the new origin/<base>, re-runs that reset, and retries the push — with no manual cleanup, no collision with the branch the previous attempt left behind, and no stale squash carried forward. It retries until the push lands. The host simply does not extend that idempotence across a process crash, where a hidden success may already have merged.

Separately from that in-agent retry loop, the host validates the integration agent's report before counting a task done. All five fields — ok, rebased, squashMerged, pushed, and roadmapMarkedDone — must be truthy. A branch that was squash-merged and pushed but never rebased onto origin/<base> (so rebased is false or absent) halts the task at stage: integrate. The detail field reports the integration agent's conflicts text or its summary, whichever is set first; only when both are absent does it fall back to the generic 'integration incomplete (need ok+rebased+squashMerged+pushed+roadmapMarkedDone)'. Inspect the halted task result's detail field, rebase the branch manually if it is salvageable, and relaunch; otherwise discard the branch and relaunch from origin/<base>.

Addendum implementations have one extra recovery state. If an addendum agent reports all work items complete, green gates, and no open issues, but fails to set the strict ok=true schema field, the workflow returns manual-merge-ready instead of treating the branch as ordinary failed work. That preserves throughput without auto-merging ambiguous output: an operator must rerun gates, confirm review evidence, reconcile the roadmap checkbox, and then integrate or discard the branch.

Fresh-run recovery

A fresh launch can also discover branches that survived an earlier failed run. Set resumePartialBranches=true and choose the maximum action with resumeMode before launching:

  • Assess-only (resumeMode="assess", the default): the workflow maps surviving roadmap-* branches back to roadmap ids, assesses each candidate with the same ADR 002 contract used for in-run failures, and reports the outcome in a top-level recovery object. Nothing is merged, pushed, ticked, or deleted, and processed is unchanged. Use this mode first: it guarantees the target project is not written to, though recovered branch content still flows to the assessment adapter (see the security guide's data-flow and prompt-injection notes).
  • Review-mode resume (resumeMode="review"): in addition to assessment, a candidate classified adopt-complete that is clean, committed, task-scoped, and carries validation evidence re-enters the ordinary dual review and merge-lock integration path without re-running implementation. Everything else is still report-only, and an adopt-complete verdict with incomplete evidence is downgraded to continue-manual with an explicit skip reason (dirty-worktree, no-committed-work, not-task-scoped, missing-validation-evidence, evidence-collection-error, or addendum-branch). Blocking evidence gaps (missingEvidence) still trigger that downgrade, but advisory residual risk (residualRisk) never does: it is non-blocking and is instead carried forward into the resumed dual review and integration prompts as an explicit, clearly delimited advisory section for the reviewer or integrator to weigh. Review-mode resume mutates the target project exactly as ordinary integration does, so grant it the same permissions and trust.
  • Continue-mode resume (resumeMode="continue"): no judgement agent at all. The workflow collects host git evidence and reads the committed ExecPlan Status: line, then dispatches deterministically: DRAFT (or a missing plan) re-enters planning, APPROVED or IN PROGRESS re-enters implementation, COMPLETE re-enters the dual review, and BLOCKED is reported. The downstream gates and reviewers are the judgement. Dirty worktrees, addendum branches, evidence-collection failures, and plans the host cannot read are reported instead of resumed (reasons such as dirty-worktree, plan-blocked, and plan-unreadable). Continue-mode resume mutates the target project exactly as ordinary work does.

The recovery result object indexes the pass for operators: candidates, assessed, resumed, per-candidate results (classification and action), and skipped entries with machine-readable reasons (including unmapped-branch, already-complete, missing-worktree, and candidate-cap from discovery). Ids with surviving branches are held out of normal selection for the rest of the run, so the pool cannot collide with an existing branch; hoover the branch or resume it before expecting normal selection to rebuild that task. Survivor branches that are still unresolved when the run ends are listed in recovery.unresolved (id, branch, last classification, action, and reason), and the run reports halted: needs-operator-recovery … instead of a clean stop, so a blocked roadmap frontier is never mistaken for finished work. A fatal auth preflight blocks recovery entirely (recovery.blocked = "auth-preflight-failed"), and dry runs never resume.

Use the df12-build-supervisor skill for the detailed operator playbook: failure-mode diagnosis, orphan worktree cleanup, remediation triage, stash hygiene, and deciding when a roadmap frontier is actually dry.

Read docs/security-and-permissions.md before granting a workshop write, network, or GitHub access. It names the permissions and external services a run can use, and explains why roadmap, design, audit, and review text must be treated as prompt-injection input rather than trusted control logic.