Files
correx/docs/sprints/2026-07-23.md
T

16 KiB

Sprint: "Close the loop" — 2 weeks

Dates: 2026-07-23 → 2026-08-06 Source: Vikunja Correx project (id 4), 30 open tasks reviewed Theme: Real per-stage validation, warm-discovery freestyle runs, robust orchestration under failure


Goal 1 — Real per-stage validation: land the LSP gate + fix build-gate wiring

Why. Multiple freestyle QA runs hit the same wall: the build-gate only fires at the terminal stage, so 8-10 hopeful writes stack before the truth-check, and when it fails there's no budget left (#167 epic calls this the "open-loop" disease). #80 is the filed fix and its deferral condition was observed on 2026-07-18 run 4a41417b — promote now.

Tasks.

  • #80 [EPIC] LSP validation gate — real per-stage gate + terminal typecheck replacement. Design finalized in the task body; break into the 5 sub-tasks listed there. Container for the work below.
    • #310 LSP diagnostics runner: anchor read on server readiness, not the 750ms timer (kills phantom unresolved-import).
    • #311 LSP gate: lint-class diagnostics (unused/deprecated) must not fail the run — classify by DiagnosticTag, not severity.
  • #263 Auto build-gate never fires on real freestyle scaffold — promotion attaches to no reachable stage. Blocks the terminal safety net #80 doesn't replace.
  • #267 Verify build-gate actually fires + re-scope #40 (LSP obsoletes the typecheck alias) — one live run settles both. Acceptance gate for the whole goal; do last.

Exit. One live freestyle run where every write stage gets LSP diagnostics scoped to its write blast-radius, terminal build-gate fires, and a stranded-scaffold case goes to recovery instead of WorkflowFailed.

Sequencing.

  • Mon: #80 sub-task 1 (cheap floor — populate the existing per-stage static_analysis seam with file-local one-shots in the freestyle compiler). Same-day ship; de-risks everything below.
  • Week 1: parallel track on #263 (build-gate wiring).
  • Week 2: #80 sub-tasks 2/3/4 — LSP4J wiring, pull-diagnostics, server pool, per-stage integration.
  • Week 2 tail: #310, #311 (readiness anchor + DiagnosticTag classification), then #267 as the acceptance live run.

Goal 2 — Make freestyle runs start warm and converge: discovery prompts, impl decomposition, ACR

Why. The kernel executes what it's told; near-term leverage is what it's told. #260/#261 sharpen upstream (discovery + per-feature stages), #305/#306 make run N+1 actually carry forward from the event log — the two halves of the ACR thesis still unimplemented. #297 kills a budget-burning failure mode mid-discovery.

Tasks.

  • #260 Freestyle SDLC: prompt-only upgrades — exhaustive discovery prompt + analyst DoD artifact (the contract the rest of the run is judged against).
  • #261 Freestyle SDLC: decompose impl stages into features/sub-tasks (structural, deferred — but it's what makes per-stage LSP from Goal 1 actually bound blast-radius).
  • #305 ACR: accrete task knowledge externally so discovery starts warm (model-agnostic) — warm-start half of #168's remaining follow-on.
  • #306 Sticky ACR steer-away hint: fires every turn + coarse signature collapse — makes delivered concepts usable; without this ACR delivery is noise.
  • #297 Analyst CoT indecision loop burns full reasoning budget, emits nothing — recovery hygiene in the stage the goal is sharpening.

Exit. A second freestyle run on a fresh repo where DoD is recorded, impl stages are feature-bounded (so Goal 1's LSP scope is real), and the discovery stage carries accreted task knowledge from a prior run on an adjacent repo.

Sequencing.

  • Day 1 (parallel): #260 — files-only, doesn't block on anything.
  • Week 1 mid: #297 (analyst CoT fix — same stage family #260 touches).
  • Week 2: #261 (impl decomposition — depends on #260's DoD existing to decompose against), #305/#306 (ACR — pairs with the warmed discovery stage).

Goal 3 — Orchestration & recovery robustness: stop the unrecoverable kills and runaway recoveries

Why. Two failure modes currently end runs that shouldn't end: a single provider going down mid-run (#299), and the recovery stage burning its budget against a stale failure-cap it can't clear (#304). Both waste full event logs. #307 is the cheap observability floor that makes the rest debuggable.

Tasks.

  • #299 Single provider death → unrecoverable session kill (NoEligibleProvider on retry) — re-route, don't die.
  • #300 HealthMonitor detects provider loss ~18s too late (reactive, not gating) — gates #299's recovery path on a fast signal.
  • #304 Recovery stage runs expensively then run dies on stale failure-cap — wasted work; the cap must reset on the recovery's own progress.
  • #309 Recovery stage: apply same-fingerprint loop-breaker + repair-ledger — the runaway root cause; closes the loop Goal 3 started. Now lands on top of #312/#313: recoveryTicket is USER-role in the trailing slot at highest precedence, and the loop-breaker is the agreed place to escalate a repeated tool failure out of tool-role (per-failure sites stay tool-role).
  • #307 Observability: no event records the assembled stage-context manifest — cheap event, makes every above failure diagnosable post-run.

Stretch (if #307 lands fast): #308 Background-process execution + monitor tool for long-running shell commands — unblocks real test gates but not load-bearing for the goals above.

Exit. A live run where provider downtime is logged + recovered around, and a recovery stage that either converges or breaks the loop with a recorded repair ledger rather than dying on a stale cap.

Sequencing.

  • Week 1: #307 (manifest event — cheap, unblocks debugging of everything below).
  • Week 2: #299 + #300 together (provider-death path), #304 + #309 together (recovery runaway path).

Cross-goal sequencing

Week Track A (validation) Track B (freestyle content) Track C (robustness)
1 M #80 sub1 — static_analysis seam populated #260 — discovery prompts #307 — manifest event
1 W-F #263 — build-gate wiring #297 — analyst CoT loop
2 M #80 sub2/3/4 — LSP4J + server pool + per-stage #261 — impl decomposition #299 + #300 — provider death
2 W #80 sub4 — blast-radius filter #305 + #306 — ACR external + sticky hint #304 + #309 — recovery runaway
2 F #310, #311, #267 — readiness anchor + tag class + acceptance run

Goal 1 progress — 2026-07-26

  • #310 (commit a95475be). awaitDiagnostics waits for one push per URI then a quiescent period anchored to the last server publication (awaitAll + awaitQuiet), not a 750ms timer started at didOpen. Kills the half-loaded-project phantom unresolved-import.
  • #311 (commit f61864ff). LspDiagnostic.tags (lowercased DiagnosticTag names) is carried from LSP4J through the event; SessionOrchestratorGates2 gates on severity == error && !isLint. A noUnusedLocals tsconfig promoting TS6133 to error no longer hard-fails a run that no rewrite could clear. Lint diagnostics stay recorded and visible, just non-gating. Classification by protocol tag, not a TS-code whitelist. Test: core/events/.../LspDiagnosticTest.kt.
  • #263 (commit 867e99d1). Two findings on trace:
    • The reported selection bug was already fixed by 159b3f1e (#277) — autoGateStages is every write-declaring stage plus terminalStageId(plan), so a non-writing review terminal is gated and runExecutionGate promotes it to PROJECT off the real FileWritten manifest. The 2026-07-18 evidence was stale.
    • The hole that remained: any stage declaring build_expectation: project|tests zeroed the whole auto-gate set, so a plan building at stage 3 of 9 had nothing verifying the six stages written after it. A declared build now suppresses only the redundant per-writing-stage gates; the terminal floor always stays.
    • Left deliberately: the gate chain still short-circuits before the execution gate when the contract gate fails. The stage fails either way — cheap gates first, no COMPLETE-lie.

Goal 1 remaining: #267 — the acceptance live run. Fold the unverified #312/#313 trailing-mandate check into the same run.

Precondition, handled. #191 (dependency resolution before scaffold accept) closed on 2026-07-21: runSetupCommand runs the profile alias setup before every build gate and #40 resolves it per toolchain. But setup is operator-declared, and the repo profile didn't declare one — so with frontend/ cleared before each QA run, the now-firing terminal gate would run npm run build against an absent node_modules and fail on deps instead of on the code. Added setup = "npm --prefix frontend install" to .correx/project.toml (install, not ci — a fresh scaffold has no lockfile). The general case — a workspace whose operator declared no setup — is #314 (toolchain-default fallback).

#40 follow-through (commit fb8141d6). #40 (toolchain-aware command resolution — this repo hosts Kotlin at root and a Node app in frontend/, and one flat alias can't serve both) closed on 2026-07-21 with [commands.<toolchain>] support in 6e844ef1. The repo profile was never migrated: every flat alias still pointed at npm, so a Kotlin run's auto-gate would have run npm --prefix frontend run build — the latent misfire #40's own body predicted. Profile now carries [commands.jvm] + [commands.node] with jvm as the flat default. Parsing verified against ProjectProfileLoader; the gate prefers <toolchain>.<alias> and falls back to flat. #267's "re-scope #40" is now just the live confirmation.

Goal 3 + Goal 2 progress — 2026-07-26

  • #307 (commits c742656e, 68b5392e). ContextAssembledEvent records the injected manifest — {sourceType, sourceId, tokenEstimate, layer, role} per entry, no content (that stays in CAS). Scope check confirmed nothing existing carried it; ContextTruncatedEvent only reports drop counts. Emitted at all four contextPackBuilder.build sites, not just the stage's first: the motivating question ("did the steer-away hint fire on 7 consecutive turns?") is a per-rebuild question, and one event per stage couldn't answer it. Turned out to be the delivery-tracking substrate #306 needed.
  • #304 (commit 2b13f610). Chose option (b) — recovery is a real second chance. detectRepeatedToolFailure windows its fold to events after the most recent FailureTicketOpenedEvent naming the stage. Route budgets are charged off the ticket event by the reducer, so the reset can't open an infinite route-in/route-out cycle: 2+2 route cycles, each needing 6 fresh failures to re-trip.
  • #309 (commits ee69f9be, 9db4e3dd). RecoveryFileLoopBreak.ktfileRepairOutcomes correlates each FileWrittenEvent with the next LspDiagnosticsCompletedEvent per path, feeding both consumers off one fold: the in-recovery guard (a path rewritten 3x without clearing opens FailureTicketOpened(gate=recovery_loop_break, escalated=true) and fails terminally instead of looping) and the ledger annotation in buildRetryFeedbackEntry, still EntryRole.USER with #313's precedence untouched. Correction landed on top: the fold conflated "no diagnostic run since this write" with "ran clean" — so the ledger said "done, leave it" about unverified files, and the breaker could kill a run on the absence of evidence. Now a distinct unchecked state; the breaker requires !unchecked.
    • Left deliberately: the guard is terminal, not an operator-approval pause. Recovery is the last tier, so there is nowhere to route; it opens the ticket for the record, then fails. Human-in-the-loop there is a follow-up if wanted.
  • #306 (commits bc5afa51, f78c7f15). Two halves, and only both together fix the report. Across stage entries: the hint is keyed to its RetryAttemptedEvent occurrence, delivery derived by folding prior ContextAssembledEvent manifests (sourceType="unconfirmedFix", sourceId=classKey) — no new state, pure fold (invariant #9). Within one stage entry: the guard alone was not enough — unconfirmedFixEntries is called once at stage entry and its result folded into accumulatedEntries, which every pushBack rebuild re-uses, so the hint rode into every turn regardless. That is the actual 7-turn symptom; the entry is now dropped once its first pack is built.
    • Directions 2/3 from the ticket needed no code: classKey already derives from the current retry's own class, and routing dead-ends carry gate="stage" while every other path carries its real gate, so "$gate:$signature" can't collapse them. Locked in with a regression test rather than a rewrite.

Goal 3 remaining: #299 + #300 (provider-death path). #308 partly pre-empted by ac460156 — a shell timeout is now recoverable and coaches nohup & detach, so there is no background-process registry to build unless a real gate needs one.

Goal 2 remaining: #261 (impl decomposition), plus status calls on #260 and #305 — both have landed commits (516af1ca; 5df35879/8806de16/12775d56/f5aaa255) but neither ticket is closed.


Landed out-of-band — context message-type sweep (#312, #313)

Not in the original three goals; pulled in on 2026-07-26 because it is upstream of Goal 1's gate verdicts and Goal 3's recovery tickets — both deliver their findings through the context builders this touched. #312 was listed as deferred-behind-#307; that turned out to be unnecessary, placement is statically determined by PromptRenderer + each producer's role, so no run ground truth was needed.

Rule established: the system block carries only what does not change during a run. Anything the run mutates is a user message — a mutating system prefix defeats prompt caching, and models under-weight system-folded content against the trailing user turn. role = which chat message type, layer = pinning/prune eligibility; those were tangled and are now separate.

  • #312 Audit — report at docs/audits/2026-07-26-context-role-audit.md (commit 514aeae7).
  • #313 Implementation (commit a4f6cf05, ./gradlew check green). Ten entries re-roled SYSTEM→USER; trailing repair-mandate slot now emits exactly one mandate by precedence (recoveryTicket > retryFeedback > groundingFeedback > rejectionFeedback) with remainingDelta appended, so the slot stays scarce as members were added.
  • Bug fixed en route: the renderer's layer == L0 clause was overriding role on four L0+USER packs — InferenceSummarizer, SemanticReviewerImpl, CapabilityGapReflectorImpl, Talkie session-naming — all four were sending a system-only request with no user turn at all.

Not verified live. Needs one freestyle run to confirm the trailing mandate lands as intended. Fold into the Goal 1 acceptance run (#267) rather than spending a separate run.


Intentionally deferred (seen, not dropped)

ID Title Why out
#167 / #168 Closed-loop + ACR design epics Bodies marked IMPLEMENTED for landed slices; remaining work folded into Goal 1 (#80) and Goal 2 (#305/#306). Keep open as epic containers.
#193 Frontier-parity design-review round Design/review work, not a 2-week deliverable. Next cycle after Goals land.
#31 Interactive workflow creation TUI/web-ui Visible polish; not load-bearing for run reliability. Schedule its own sprint.
#265 TUI clarification modal not dismissed on external resolve TUI cluster; pair with the next TUI sprint.
#295 TUI token usage display for router/talkie TUI cluster.
#296 TUI execution plan viewer TUI cluster.
#298 TUI output: show CoT/reasoning on artifact + tool-call turns TUI cluster.
#301 Escalate repeated scope/manifest write-block to user approval Falls under Goal 2 once DoD lands; premature now.
#302 Mid-run hard steering (drop inference, inject operator message, restart) Bigger surface; pairs with the steering-channel design, post-Goal 3 reliability.
#303 Auto-repair collapsed-argv shell calls Nice-to-have shell hygiene.
#25 Backlog (deferred/spec-level from memory) Meta-task; verify-against-code before any sub-item is promoted.