34895b3d54
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
231 lines
18 KiB
Markdown
231 lines
18 KiB
Markdown
# 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.kt` — `fileRepairOutcomes`
|
||
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.
|
||
|
||
- **#299 ✅** (commit `c5289420`). `route()` now splits the two cases the old code conflated: a
|
||
capability **nobody was ever configured with** still fails fast, but a capability that *is*
|
||
configured whose candidates are all currently unhealthy gets a bounded wait (3 × 2s, each round
|
||
re-checking `refreshedHealth` rather than the TTL cache) before `NoEligibleProvider`. A crash-plus-
|
||
restart no longer collapses a retryable failure into a session kill.
|
||
- **#300 ✅** (commit `bf362527`). Made health *gating* instead of reactive: new
|
||
`InferenceRouter.reportFailure(providerId, reason)` (default no-op) writes `Unavailable` straight
|
||
into the health cache, bypassing `healthCheck()`/TTL, and `SessionOrchestrator.kt:454` calls it from
|
||
the inference catch when `isConnectionLevelFailure(e)`. The next `route()` sees the drop
|
||
immediately instead of ~18s later. Recovery needs no extra path — TTL expiry or #299's bounded-wait
|
||
re-check picks the provider back up.
|
||
|
||
**Goal 3 remaining:** none. #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 status calls — 2026-07-27.**
|
||
- **#260 ✅** (commit `516af1ca`). Both halves are in the prompts: `discovery.md` requires inspecting
|
||
the whole decision surface and batching *all* operator-only questions after inspection (no
|
||
stop-at-first-uncertainty), and `analyst_freestyle.md` makes the `dod` artifact the handoff contract
|
||
— atomic, yes/no-checkable criteria, plus one criterion carrying the named task into the impl plan
|
||
and one per material failure path. #3 (architect re-plans on better input) fell out for free.
|
||
- **#305 stays open.** Store 1 (Observations) shipped (`5df35879` et al) and is real, but Stores 2
|
||
(fix lifecycle) and 3 (plan-shapes + discovery warm-start read path) are unbuilt — and Store 3 is
|
||
exactly what Goal 2's exit criterion ("discovery carries accreted knowledge from a prior run")
|
||
names. Not closable; carries past this sprint.
|
||
- **#261 blocked by #267, correctly.** Its own body defers it until the simpler pipeline is proven on
|
||
a clean end-to-end run — which is the #267 acceptance run. Do #267 first.
|
||
|
||
---
|
||
|
||
## 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. |
|