Skip to content

Repair-on-prepare and aggressive entity upgrade — self-healing posture vs strict downshift

Status: open/proposed

  • Current posture is mostly detect-and-defer: task-ensure-ready analyzes a task spec, flags drift from the readiness contract, downshifts to planning/needs-definition with a definition_gap field, and exits. The one repair path today is the autonomy gate: autonomy: autonomous/pr tasks get a single /sdlc:task-auto-define fill-and-re-verify pass before any downshift. /sdlc:entities-migrate applies deterministic-mechanical fixes only (schema_version stamps plus the versioned body transforms); non-mechanical drift surfaces and waits for human attention.
  • Proposed posture is active-repair-with-confidence-gate: both surfaces gain an LLM-orchestrated repair pass that attempts to fix the drift inline. Mechanical fixes ship without ceremony; non-mechanical fixes either ship if the LLM’s confidence clears a gate, or fall back to today’s downshift/PR-for-review behavior.
  • Three concrete surfaces affected: (1) task-ensure-ready (or a new task-prepare step) gains a repair mode that fixes spec drift before downshifting; (2) /sdlc:entities-migrate extends to handle non-mechanical upgrades via LLM orchestration; (3) /sdlc:task-work invokes the repair-mode prepare step so a single dispatch can repair-and-continue without an out-of-band cycle.
  • This ADR sets the posture and the confidence-gate principle. Specific repair recipes, gate thresholds, and skill-or-flag shape are downstream tasks.

The 2026-05-30 /loop /sdlc:orchestrate session surfaced a sharp version of the friction this ADR addresses. Seven freshly-authored M0001 tasks (T0002, T0004, T0005, T0006, T0009, T0011, T0012) were dispatched to /sdlc:task-work. All seven hit task-ensure-ready and downshifted to planning/needs-definition with definition_gap populated. Each gap was almost identical in shape: the ## Today section used a pre-v3-contract layout (bulleted-legacy, missing entirely, or a 3-column variant instead of the required | Location | Role today | 2-column shape).

These tasks were not wrong relative to their authoring moment — they predated the v3 readiness contract’s tightening. Schema evolved, instances did not. The downshift correctly surfaces the drift, but the operator burden is now:

  1. Read each downshift’s definition_gap field.
  2. Open a /sdlc:task-define session per task (interactive prompts).
  3. Edit each Today section, plus any other drift items in the gap.
  4. Re-validate, re-dispatch task-work.
  5. Wait for the next round of escapes.

The same pattern exists for entity instances. /sdlc:entities-audit flags drift; /sdlc:entities-migrate mechanically fixes the auto-fixable subset (schema_version stamps and the deterministic v_n→v_n+1 body transforms); everything else surfaces for manual edits. When a schema bump adds a required field, every existing instance is “broken” until a human revisits each one.

In both cases the drift is often deterministic-ish — most readiness-contract drift is reformatting; most schema-bump drift is adding-a-field-with-a-default-or-empty-value. An LLM with the spec and the gap could repair the high-confidence cases without operator round-tripping. The low-confidence cases keep today’s surface-and-defer behavior, so operator trust isn’t sacrificed for autonomy.

The current rigid posture also costs the orchestrator: every wasted parallel dispatch consumes a sub-agent run, lease churn, worktree creation, and operator attention to triage. Repairing inline within a single task-work pass would convert 6 downshift round-trips into 6 inline repairs.

  1. Adopt an active-repair posture for two surfaces: task readiness (task-ensure-ready or a new task-prepare step) and entity-instance migration (/sdlc:entities-migrate non-mechanical extension). Both default to repair-attempt-first, downshift-or-PR-on-low-confidence fallback.

  2. Confidence-gate every repair. A repair attempt classifies its proposed change as one of:

    • high-confidence-mechanical — pure structural reformat against a known contract (e.g., bulleted-list → 2-col table when the spec already cites paths in backticks). Ships inline; commits with a repair= annotation.
    • medium-confidence-content — content edits with clear intent (e.g., filling a ## Today section based on the rest of the spec’s Files-to-touch + Goal). Ships inline iff a per-skill threshold is met; otherwise opens a PR for review.
    • low-confidence-or-ambiguous — substantive content authorship. Falls back to today’s behavior (downshift + definition_gap, or audit-flagged-not-migrated).
  3. Preserve the downshift signal. Repair never silently succeeds-but-wrongly: every shipped repair leaves a recoverable diff (commit annotation, definition_gap-resolved field, or PR), and every low-confidence case downshifts as today. The operator always has a trail.

  4. Repair-and-continue inside task-work. /sdlc:task-work invokes repair-mode prepare BEFORE deciding to dispatch implementation. A successful inline repair flows directly into Steps 5b–10 without a separate cycle. A repair that downshifts halts task-work as today.

  5. Entity-migrate handles non-mechanical upgrades. /sdlc:entities-migrate gains an LLM-orchestration mode that attempts confidence-gated content repair on instance drift (filling new required fields with inferred values, rewriting deprecated field shapes, etc.). The mechanical-only path remains for the unsupervised auto-fix use case; the LLM mode is opt-in with --repair (default off until trust is established).

  6. Per-entity repair recipes. Each entity’s definition.md may declare repair recipes per known drift class (e.g., ## Today reformat). The recipes are advisory hints the LLM consumes; absence of a recipe falls through to general-purpose repair.

  7. Per-instance allow_repair: opt-in. Every entity instance carries an optional allow_repair: field in frontmatter. Three values:

    • none — repair is disabled for this instance; the existing strict-downshift behavior is the only path. Use for safety-critical or human-authority artifacts.
    • mechanical — only high-confidence-mechanical repairs are attempted; medium/content drifts downshift.
    • content — full repair pass including medium-confidence content edits; only low-confidence ambiguous cases downshift.

    The default is project-level: sdlc.yaml’s repair_mode: (none / mechanical / content) sets the project default; the per-instance field overrides. Initial recommended defaults: repair_mode: mechanical at project; allow_repair: unset at instance (inherits project).

  8. Codified repair audit trail at .sdlc/repair-log.md. Every repair attempt — successful, downshifted, or skipped — appends one structured line to a gitignored project-local log. The line carries:

    • timestamp (ISO 8601 UTC)
    • skill name (e.g., task-ensure-ready, entities-migrate)
    • target entity (type + id/basename)
    • contract violation class (the rule name that triggered the repair attempt, drawn from a fixed enum per skill — see “Violation taxonomy” in the implementation task)
    • repair classification (mechanical / content / ambiguous)
    • outcome (shipped / downshifted / skipped-by-opt-in / error)
    • confidence summary (if applicable; LLM self-report or heuristic)
    • sub-agent run id (cross-reference to dispatch logs)

    The log is the canonical source for the post-mortem feedback loop (item 9). Format mirrors the orchestrator’s tick digest at .sdlc/orchestrator-log.md; one tool reads both.

  9. Post-mortem feedback loop via /sdlc:repair-postmortem. A new skill aggregates .sdlc/repair-log.md over a configurable window (default: last 30 days) and surfaces:

    • Recurring violation classes — which contract rules trigger the most repair attempts. High-frequency rules are candidates for promotion to deterministic enforcement (move the rule from LLM-judgment into a parser like parse-touchpoints.ts).
    • Repair success rate per violation class — which violations are reliably auto-repaired vs which keep downshifting. Reliable repairs → widen the confidence gate; unreliable ones → tighten the prompt or clarify the contract.
    • Per-entity repair pressure — entity types accumulating downshifts faster than fixes signal a schema/contract that’s drifted too far ahead of its instances; surfaces as a candidate for an aggressive entities-migrate --repair sweep.
    • Recipe coverage gaps — violations with no documented per-entity recipe (item 6) get flagged; the post-mortem proposes recipe additions.

    The post-mortem output is itself a markdown artifact (e.g., docs/planning/repair-postmortems/<date>.md) reviewable as a planning input, not a one-shot console dump. Running it is the project’s mechanism for closing the loop between observed friction and adjusted policy — the same posture this ADR proposes at the per-repair level, applied at the corpus level.

TodayCost
Every drift-surface event requires an operator round-trip (review the gap → run task-define → re-validate → re-dispatch)Triage burden scales with schema/contract churn
Parallel orchestrator dispatch wastes sub-agent runs when half the queue is silently driftedToken spend + operator-attention spend
/sdlc:entities-migrate ignores non-mechanical driftSchema bumps strand instances; the operator becomes the migration engine
Sub-agents that COULD have repaired the drift instead correctly diagnose it and bailWasted sub-agent runs + the verdict-contract-escape failure mode amplifies (sub-agents stop at intermediate markers when they didn’t reach the work)
ProposedWin
High-confidence drift repairs inline, no round-tripOperator round-trips drop by ~the high-confidence rate
Low-confidence drift still downshifts (preserves signal)Trust posture unchanged for genuinely-ambiguous cases
Entity-migrate handles non-mechanical via LLM mode (opt-in)Schema bumps become a single supervised PR vs N manual edits
Repair-and-continue inside task-workOne dispatch per task instead of {dispatch → downshift → triage → re-dispatch}

(Captured here so the implementation tasks don’t relitigate them; resolve in followups.)

  1. Confidence-gate calibration. Mechanical-vs-content is a continuum, not a binary. Initial thresholds will be conservative; we’ll widen as evidence accumulates. The gate’s measurement is open — could be LLM self-report (“I’m 90% confident”), heuristics (number of fields touched, change size), or a separate critic pass.

  2. Skill shape: extend or new? Three options for where the repair lives:

    • Extend task-ensure-ready with a --repair mode flag.
    • Promote a new task-prepare skill that wraps ensure-ready + the repair pass.
    • Make repair an Operation (per D0004) that any entity can declare and the framework dispatches. The last option is the most principled but the largest scope; the first is the smallest. Decide per skill.
  3. Violation taxonomy per skill. Decision item 8 commits to logging a “contract violation class” per repair event; that requires a fixed per-skill enum naming each rule. The taxonomy is the work of the implementation tasks (one enum per skill that runs repair: task-ensure-ready, entities-migrate, etc.). Naming consistency across skills is the open call.

  4. Cascade with --ready (T0015). Repair-on-prepare reduces the need for --ready filtering by removing some drift before dispatch. But T0015’s --ready still applies for actual unmet deps. The two compose: --ready filters dispatchable candidates; prepare repairs whatever it can on the remaining ones.

  5. Failure mode coupling with verdict contract. A repair that goes wrong inside a sub-agent risks the same verdict-contract escape we’ve seen — sub-agent does repair, emits intermediate marker, parent thinks no progress made. The repair design should specify its own terminal verdict shape (per the namespacing convention).

  6. Post-mortem cadence. Decision item 9 ships the /sdlc:repair-postmortem skill, but doesn’t fix how often to run it. Three modes: (a) manual on demand, (b) scheduled via /loop or /schedule, (c) auto-fired by the orchestrator when the repair-log accumulates N entries since the last post-mortem. Initial recommendation: manual + scheduled at milestone-close cadence.

  7. Promotion path from LLM-rule to deterministic-rule. When the post-mortem flags a recurring violation as “always-mechanical-always-shipped,” the recommendation is to promote that rule into deterministic enforcement (e.g., add a check to parse-touchpoints.ts). This is a planning input, not an automatic action; the post-mortem proposes, a human authors the migration. Worth codifying as a Standard later.

(Non-binding; the implementation task(s) will scope concretely.)

SurfaceRepair scopeSkill shape
Task readinessReformat ## Today per v3 contract; normalize Files to touch Kind values; rewrite obsolete dep references when schemas tell us the canonical shapetask-ensure-ready --repair flag (small) or task-prepare skill (medium)
Entity instance migrationAdd required fields with type-appropriate defaults; rewrite deprecated field shapes; LLM-fill content for new required body sections/sdlc:entities-migrate --repair flag
Task-work pre-flightInvoke repair-mode prepare; on success → continue to Step 5b; on confidence-fall-through → today’s downshift/sdlc:task-work Step 5a internal
  1. Ship the smallest repair surface first: task-ensure-ready --repair for the ## Today reformat recipe. Validate against the current backlog of needs-definition tasks (live test of T0002, T0004, T0005, T0006, T0009, T0011, T0012). Document hit/miss rate.
  2. Extend the repair recipes per surfaced drift class.
  3. Wire /sdlc:task-work to invoke repair-mode prepare by default.
  4. Mirror to /sdlc:entities-migrate --repair.
  5. Calibrate confidence-gate thresholds based on accumulated evidence; adjust defaults.
  • A formal repair-recipe DSL. Repairs are LLM-orchestrated prose-driven; structured DSL is a future iteration once recipes stabilize.
  • Replacing the validator with the repair pass. Validators stay deterministic and authoritative; repair is an attempt-to-pass-the-validator helper.
  • Cross-entity repair coordination (e.g., a task’s repair affects a milestone’s tasks: list). Each surface repairs its own entity; cross-references are out of scope here.
  • Removing the operator from the trust chain. Every shipped repair leaves a reviewable trail; this ADR does not propose silent autonomous mutation.

Evidence and discovery — 2026-05-30 session

Section titled “Evidence and discovery — 2026-05-30 session”

The case for this ADR is concrete. A single /loop /sdlc:orchestrate session surfaced a cluster of friction events that share one root pattern: rigid contracts + immediate-strict-failure + no repair pass.

Where the rules actually live (the drift surface)

Section titled “Where the rules actually live (the drift surface)”

Readiness rules for tasks are scattered across five layers, each authored independently:

LayerFileAuthorityDrift risk
Contract docplugin/lib/model/entities/task/implementation-ready.mdSource-of-truth prose the LLM reads each runHigh — single doc, but its intent is LLM-interpreted
Skill proseplugin/skills/task-ensure-ready/SKILL.mdTells the LLM how to walk the contract; restates several rules inlineHigh — duplicates contract content; drift between the two is exactly the bug class
Body parserplugin/lib/model/entities/task/ops/parse-touchpoints.tsDeterministic — validates table shape, location grammar, prose-vs-table detectionMedium — code-vs-prose drift, but at least testable
Placeholder scannerplugin/lib/model/entities/task/ops/scan-placeholders.tsDeterministic — finds <placeholder> and thin sectionsMedium
Frontmatter schemaplugin/lib/model/entities/task/schema.tsDeterministic frontmatter schemaLow — the validator binds to it directly

Three flavors of drift were observed live:

Drift classObserved
Contract ↔ SKILL.md (two prose docs)Both document the Files-to-touch existence check, with subtly different phrasings; the LLM working from one may infer different scope than working from the other
Contract ↔ deterministic parser (spec vs code)The contract enumerates 5 Location forms; parse-touchpoints.ts implements them. Either side can change without the other catching up
Contract ↔ LLM inference (spec letter vs spirit)“Today rows must resolve to existing files” is not in the contract text. The sub-agent inferred it from “describing the relevant area today.” Different sub-agent runs could legitimately infer different rules from the same contract document. This is invisible — no test catches it

Failure-mode catalogue from a single session

Section titled “Failure-mode catalogue from a single session”

Each row is a real event with a commit reference or PR number:

SurfaceEventRootResolution this session
Validator pathThe frontmatter validator’s default --entities-dir was narrowed by PR #176 but task/backlog/epic entities weren’t migrated to the new path → every task-ensure-ready invocation failed silentlyMigration gapMigration promoted into M0001 — commit 6328178
Lease protocolsdlc lease task claim writes a random UUID into owner; sub-agent’s discover_lease() compares against stable current_host_id() → always mismatch → spurious LEASE-CONFLICT for every orchestrator-pre-claimTwo subcommands diverged (slice-1 claim placeholder vs slice-2 acquire)Switched orchestrate spec to task acquire — commit 287b0c4
Verdict contractSub-agents emit intermediate sub-skill markers (ENSURE-READY-OK:) as their terminal verdict instead of TASK-WORK-DONE pr=#<N>LLM literal-mindedness; namespacing helped but didn’t close gapAdded lenient last-match scan for verdict-buried-in-prose — commit af52619. The wrong-marker-class escape still recurs (T0001, T0004 lost their post-Step-5a work this session)
Readiness strictnessVerifier accepts */?/[...] globs but not {a,b,c} brace expansion; Today rows must resolve to existing files; one downshift becomes triage-edit-revalidate-redispatch with no convergence guaranteeContract is strict, parser enforces what it can, LLM infers the restHand-fixed 7 needs-definition tasks; 4-of-4 re-dispatches downshifted on previously-unknown sub-rules
Sub-agent reasoningTwo sub-agents (T0004 first dispatch, T0005 first dispatch) confidently claimed “no file exists for T000X” when the file did existFile-resolution misread; no programmatic guardVerdict shape was valid (ERROR), so orchestrator dispatched on it without catching the wrong premise
State driftMultiple stuck leases per session; stale worktrees from escaped sub-agents; commits-needing-push from sub-agents that didn’t get thereCumulative effect of every other failure modeManual cleanup; archive subcommand exists but no release
  • 0 task-work PRs opened despite ~10 sub-agent dispatches against the 14-task M0001 queue.
  • 7 task specs flagged planning/needs-definition for near-identical Today-section drift.
  • 2 verdict-contract escapes lost their entire post-readiness work.
  • 3 sibling repairs landed on main that should arguably have been the system’s job, not the operator’s:
    1. Migration of task/backlog/epic to the new entities location.
    2. Lease subcommand switch in the orchestrate spec.
    3. Lenient parser for verdict-buried-in-prose.
  • 2 sibling planning artifacts opened as separate PRs: T-0015 (sort --ready filter, PR #180), this ADR (D0005, PR #181).

The friction is the case for this ADR’s repair-on-prepare posture. The post-mortem loop (Decision item 9) is the case for never letting that friction recur silently.

The promotion path (LLM-rule → deterministic-rule)

Section titled “The promotion path (LLM-rule → deterministic-rule)”

A through-line in the evidence: rules that started as “the LLM infers them from the contract” end up causing repeated friction because two sub-agent runs may infer differently. The natural lifecycle is:

  1. Rule lives in implementation-ready.md as prose — LLM-judged, flexible, but inconsistent.
  2. Recurring violations of the rule surface in the repair-log — the post-mortem (Decision item 9) flags it as a recurring class.
  3. Rule promotes to deterministic enforcement — added to parse-touchpoints.ts (or a sibling), removed from the LLM-judgment surface.
  4. Contract doc updates to reference the deterministic check, not duplicate the rule.

This is exactly the inverse of how rules typically accumulate: usually they get added everywhere “just to be sure.” The repair-log is what makes the promotion path observable.

  • Surfaced 2026-05-30 from the operator-side friction in the M0001 dispatch wave (7-of-7 task-work runs downshifted on similar drift). The user-quoted shape of the design: “a setting on a task to allow a prepare step to not just analyze the task but instead attempt an LLM-orchestrated repair or upgrade of its state and then letting it continue on to be worked” — extended in the same session to “our entity migration needs to handle non-mechanical upgrades as well” and “we may need to add a task field that indicates whether we allow auto-repair and then we codify where we capture found errors and repairs performed so we can do a post mortem on what problems seem to keep coming up.”
  • Implementation milestone: M-0001.1-task-execution-consistency. This session’s friction seeded the “task execution consistency” milestone slotting between M0001 (entity shapes + roster) and M0002 (full S0005 surface for legacy entities). The repair-on-prepare work, the --ready filter (T0015), and a verdict-contract structural fix belong together as one milestone’s worth of consistency-focused work.
  • Complementary to T-0015 (sort --ready dep-satisfaction filter). T0015 filters out candidates that aren’t actionable; this ADR repairs candidates that could be actionable if their drift is fixable. Together they reduce the rate of wasted parallel dispatches.
  • Complementary to T-NP7H-task-work-sub-agent-verdict-contract-escape-recurrence. The verdict-contract recurrence is partly downstream of this: when repair lands, fewer sub-agents bail at the readiness gate, fewer verdict-contract escapes occur. Solving them in the same milestone tightens the loop.

← Back to Decisions