Repository navigation
v0.244.0
A parallel review stamped three phases DONE without executing them — and six more findings from the first full-fan-out field run
A third field evaluation — the first real five-lane parallel review on a downstream project — filed six problem findings, then (in calibration mode, answering targeted questions) disproved two of its own diagnoses. What survived validation against the code and the run's artifacts became this batch; what didn't shaped what was deliberately NOT built.
Three mandatory steps were invisible because a comment is not a step. The run stamped phase=present_findings status=DONE with verify, auto_curator, and the present_findings body all unexecuted. The three SHARED-STEP pointers were HTML comments at the very end of the file, after consolidate closes with its own DONE stamp — structurally invisible to an orchestrator scanning for <step> elements. Sharper: the auto_curator pointer was itself the fix for a prior field report ("parallel path had no auto_curator step"), shipped in a form the next run could not see. Both parents' pointer stubs are now real <step> elements (bodies stay single-sourced; K285's residency leg repointed from the step tag to a body-only marker), and the header note records why the form is load-bearing.
Terminal-only enforcement had a reachability hole. assert-verifier-ran + assert-auto-curator-considered did exist for exactly this skip — at the complete transition, which nothing compels an orchestrator to ever take. Both gates now ALSO fire at present_findings-entry for both review types (both pipelines run verify → auto_curator → present_findings, so the evidence must exist by then). The field's exact stamp is refused in a fixture; a healthy run and non-review workflow types pass untouched. The registry's "intermediate phases get ONLY the raw-dispatch gate" rationale gains the staged exception it now has one of.
The retry path was the one dispatch the render pipeline didn't build. Round-1 envelopes on disk were fully populated; the two envelope_health: degraded records came from the operator's hand-assembled retries, which dropped scope_trust/scope_hint/memory_signal — the redispatch prose said "use ALL the same context blocks" and left the assembly to hand. The evaluator's own correction: the helper substantially existed (--lane-<id>-focus has been honored all along) and wasn't found — a documentation defect, not a missing feature. render-lanes gains --only=<ids> (subset render against the FULL registry, neighbor map intact; unknown id is a loud error, never a silently-empty render), and redispatch_lanes now routes through the CLI — the narrowed ask rides as <lane_focus> on a machine-built envelope.
Breadth killed the lanes, not test suites — so the envelope carries a budget, not a ban. Five first-round lanes died at the turn wall on 25–41 commands of evidence-gathering with zero findings written; the report's "two lanes died running test suites" was disproven by its own transcripts (two dead lanes ran no test command at all), and the single highest-value act of the round was a lane's own targeted mutation test. Every lane envelope now carries <lane_budget>: write-early-then-append, a ~15-command exploration budget with a stop-loss, at most 2 graphify queries, highest-signal-findings cap — and explicit license for targeted verification while naming whole-suite runs as how lanes die with nothing written. A flat test ban was deliberately not built; incremental checkpointing was not rebuilt either (the directive has shipped in the agent body since the robustness trio — the budget block attacks why lanes had nothing to checkpoint).
<graphify_status> had consumers but never a producer. code-reviewer and tester have parsed {skipped, reason?, impact_map?} and branched three ways since the skip-awareness work — but nothing ever wrote graphify_status_json, so every envelope rendered {}, which the evaluation flagged as a permanently-broken feature. The substitution builder now derives it from the same loader that inlines <graph_impact> (present → skipped:false + map path; skip-reason → skipped:true + the reason; evicted/absent → the honest skipped:null), so the two blocks cannot disagree.
envelope_health had no schema, so two lanes reporting the same degradation were mutually unparseable — one bare string, one object. The review sidecar schema now checks the structured form (status enum + block-name arrays, surfaced as schema_warnings), and the reviewer's contract pins the emit shape — including self-detection when a hand-assembled dispatch carries no <envelope_health> block at all.
Orphan-above-the-watermark: a smaller partition than the previous run leaves stale lane sidecars forever. Same-id registration overwrites; ids above the new lane count survive in lane-files/ (and the registry, within a workflow) because soft reset never descends into the sidecar dir — the run found sidecars from two partitions back describing an entirely different split. A register-lanes batch now DECLARES the full partition: ids outside it are pruned from registry + sidecars + lane-diff artifacts together (pruning either surface alone creates the inverse inconsistency), with loud pruned_stale telemetry. Single register-lane calls still merge.
K349 locks the skip class (staged-gate registry + the field's exact stamp refused/healthy in a fixture + step-element pointer form in both parents); K350 locks the rest (--only filter + neighbor retention + unknown-id error + prose wiring, lane_budget + graphify_status producer, envelope_health schema both directions, declared-partition prune). Falsifiability, probed by reverting each fix in isolation: un-staging the registry gates fails the refused-stamp leg; demoting one pointer to a comment fails the pointer-form leg naming the file; reverting the graphify_status producer fails the skip-state leg; neutralizing the prune fails the stale-registry leg. K334 caught the first draft of the redispatch fence using $LANES_JSON across fence boundaries — separate shells — and the fence now re-derives it.
Deferred by design: the partitioner findings (half a realistic branch in one ungrouped bucket; a change-coupled fix+test split across community lanes) are validated but need genuine design judgment — auto-proposed release-artifacts lens lane, coupling-aware cohesion — and get their own pass rather than a batch slot.
Version-selection policy in every template: latest-stable adoption, a toolchain .0 embargo, and suggest-only upgrades
All six templates' coding-standards.md gain an identical Version Selection & Upgrades section (operator-set policy): new dependencies adopt the latest stable at introduction; core language toolchains never adopt an x.y.0 initial release — wait 3–4 weeks and target x.y.1 or the first patch in that window; and upgrades to existing dependencies are suggest-only — an agent surfaces current → available and why it matters, and never bumps automatically or as a side effect of unrelated work. The third clause is the load-bearing one for agent behavior: unrequested version churn is scope creep with real breakage risk, so upgrade timing belongs to the operator.
K348 anchors both load-bearing sentences across all six templates — the same drift class K336 guards for golden rules, where an agnostic rule had silently shipped in only two of six templates. Probed: mutating one anchor in one template turns the gate red naming the file and the missing clause.
An operator asked for parallel review in writing and was silently single-routed — twice over
A second field evaluation (a five-lane review run on a downstream project) filed six findings; four survived verification against the code and the run's own artifacts, one was disproven by the run's own hook trace, and one had already been half-built. The confirmed ones share a theme: gates and routing that mislead about their own state.
The operator-intent short-circuit lived behind the very bar it existed to override. The task text said "use multiple devt agents" — explicit parallel intent — and the review ran single-dispatch anyway, twice over. First: the intent regexes were only loaded when the offer bar was crossed AND graphify was ready, so below the bar the operator's written word was never even read ("The common path… never loads this", in the workflow's own words). Second: even if they had run, multiple agents was a contiguous alternative, and the field phrase has an interior word — the same interior-word gap the split alternative was widened for while its neighbor was left exact. Either fix alone leaves the field case broken; hoisting without widening loads the classifier, matches nothing, and routes single with extra steps.
The classifier now runs resident in scope_check via a new state scope-intent CLI (single-sourced in state-lanes.cjs, so the smoke gate tests the shipped code path instead of a sed-extracted copy). Detected intent routes on every run: single → single; parallel → the parallel-offer section's delegation tail in DELEGATE mode, any graphify state (partition_lanes self-selects community / service-boundary / path partition). The widening is symmetric — multiple[^.]{0,24}agents on both the parallel side and the negation tail of the single side — because widening only one side re-creates the routing inversion one level down: "no multiple devt agents" would miss the single test and then hit the widened parallel one, fan-ing out on a phrase that declines it. Before: "use multiple devt agents" → silent single-dispatch. After: → parallel, no question asked; "no multiple devt agents" → single.
assert-scope-check-handled could not tell a skipped step from an unmet condition — by its own admission. The marker was written only when the offer bar fired, so an absent marker returned ok:true, evidence:"unknown" — a vacuous pass. scope_check now records its evaluation unconditionally (first token required or not-required), which converts absence into a real verdict: the step did not run, BLOCK. The marker gets the same prior-workflow freshness check the answer artifact always had, and legacy-shaped content parses as required — the demanding direction.
Six gate reasons asserted WHO failed when the gate only observes state. The field run hit assert-preflight-fresh mid-wrapper: review-context-init regenerates the brief internally, a read between the workflow stamp and the regenerate saw the prior brief, and the gate announced "orchestrator skipped preflight generate" about a run that had skipped nothing — self-healed 81 seconds later. A blame-shaped reason on a self-healing transient devalues every other gate message. All six orchestrator skipped reasons (plus one orchestrator wrote both) are rewritten state-shaped — observed state, candidate causes, what makes it pass — and the comment above the preflight gate no longer documents the disproven inference that an older brief means a skipped step. A sweep leg keeps bin/modules at zero occurrences.
gate-trace.jsonl records carry a boolean ok beside verdict. The evaluation's author mis-parsed the schema twice — .ok, the natural first guess, read undefined→falsy on every record, and a clean run briefly graded as failing every gate. The field is redundant with verdict by design; warn counts as ok, matching the gate result it mirrors. The council / arch-scan / graphify-fallback event records are unchanged — they record occurrences, not verdicts, and a synthetic ok:true on them would be the same misleading-schema class in reverse.
Falsifiability, probed by reverting each fix in isolation: narrowing the regex fails the K178 field-phrase case; restoring the vacuous unknown-pass fails G1a and the K347 trace leg; removing the ok field fails both K347 trace legs. The K347 sweep leg itself needed the CON-003 defuse — grep -rc exits 1 exactly when the sweep is clean, so under pipefail the undefused gate aborted the suite the moment its subject was fixed, able to pass only while the defect it checks for was still present.
Not built, and why: the "guard cannot see Agent-tool dispatches" finding is disproven by the run's own trace — dispatch-hygiene-guard fired on all five lane dispatches with ~6 KB of stdin each and stayed silent because the envelopes were compliant, and the guard has matched both Task and Agent since before the run.
Cleanup pass over the two changes above — including a live mis-classification one of them introduced
The empty-drill marker had a second reader, and widening one copy left the other wrong. compose-drilldowns emits two forms of "this drill found nothing", and the substance gate was taught to accept both. Its twin — the drill-waste classifier that decides yielded_data — kept a private regex demanding the closing )_, so a DI-hinted empty drill was scored as returned data nobody cited (a drill-value problem) when it had in fact returned nothing (a drill-selection problem). That is exactly the conflation the classifier's own comment forbids: "they're different waste classes demanding different fixes… Collapsing them hides the lever." One module-scoped constant now serves both readers, and reverting it fails both gate legs together.
state register-lane --lens was a silent no-op. The flag reached registerLane's signature and the batch path, but the CLI's single-lane branch never passed it — so the documented flag did nothing and the feature was reachable only through register-lanes --from=. A fourth instance of the half-wired class this batch exists to close, in the fix for it.
The corpus rule moved to where every reviewer sees it. <search_discipline> was injected only into lane envelopes, but all three review shapes are the same agent, and a single-dispatch reviewer deleting a symbol hits the stale-copy trap identically — /devt:review without fan-out is the common path, and it had no such rule. It now lives in the code-reviewer body, which also stops ~530 bytes riding every lane envelope on a fan-out (5% of a lane envelope, on the one surface where rules and rubric were deliberately moved by-reference to hold size down). The gate asserts both its presence in the agent AND its absence from the envelope, so it cannot drift back into per-lane duplication. Placing it cost the file's last budget line; an adjacent paragraph was rewrapped rather than raising the cap to fit one edit.
Also: the lens predicate is one function instead of two literal copies (a third accepted spelling added at one site would have exempted a lane from the overlap warning while leaving it without the block explaining why); the disjointness scan drops a throwaway field and a third pipeline stage; the three parallel has*Marker booleans become one array, so a fourth exemption is an entry rather than another &&; the registration row builds once and aliases visibly instead of carrying two values under two keys each; and F66's --fresh leg now drives its behavioral checks and its pin from one expression, closing a split where the gate validated a private copy of a regex while separately grepping for the real one.
Skipped deliberately: listLaneOutputs' hand-rolled block parser duplicates parseSimpleYaml, which the same file already calls on the same data 700 lines away and which returns every field with correct int and boolean coercion. Replacing it deletes ~25 lines and retires the "teach every new lane property a new regex" tax — but the surrounding cid-chain, id-alias and staleness logic is field-hardened, so it belongs in its own change rather than folded into a calibration batch.
The suite was writing test fixtures into devt's own state, and the session banner reported them as real
Every prompt in this repo carried 1107 raw_dispatch … in this workflow and a workflow that had been open for ten days. Neither was real. bash scripts/smoke-test.sh added exactly six raw_dispatch records to .devt/state/dispatch-warnings.jsonl per run — measured, not inferred: 1107 → 1113 across one controlled run, with previews reading smoke-trace probe, do a thing, Review X. The hooks and the CLI resolve their state dir by walking UP from cwd, so any gate invoking them from the repo root wrote into the live project instead of its fixture. One of those gates seeded an ACTIVE workflow at the repo root to satisfy :auto envelope resolution; nothing owned it afterwards, so it stayed open and the banner reported it every turn for ten days.
The cost is the one the field report named as the worst outcome for any advisory: a number that is always wrong trains the reader to stop reading it. It had — the count sat in every prompt of this session and was correctly ignored each time.
Two of the leaking gates already backed up what they touched — one restored run-hook.jsonl, another restored .devt/config.json — so the pollution was understood and half-cleaned. Neither backup covered the guard's own append, which happened in the same invocation. The probes now run inside mktemp fixtures carrying their own .devt/state, the way the L1a–L1e hygiene gates always have, which also retires a backup-restore-rmdir dance that only ever protected one of the two files it needed to.
KSTATE is the standing tripwire, scoped to the two invariants with proven operator impact: the suite must seed no raw_dispatch record and no active workflow into devt's own state. It is deliberately narrower than KCORPUS's whole-directory digest — other gates still write assorted state from the repo root, and a tripwire firing on writes nobody has triaged is the same cry-wolf failure it exists to prevent. Widening it to full hermeticity is a sweep, not a one-line change, and is not claimed here.
The residue is cleared: state reset-soft closed the phantom workflow and rotated the 1115 fabricated records into dispatch-warnings.archive-*.jsonl — archived rather than deleted, since a forensic log that a cleanup can silently drop is worth less than one that grows.
Two things caught in the building, both worth recording because both are the class this batch is about. The first falsifiability probe reverted a call site that turned out never to have leaked — an earlier gate leaves cwd in a temp dir, so that probe passed for a reason unrelated to the fix and briefly read as "the gate cannot fail". The second was real: KSTATE's counter used grep -c with an || echo 0 fallback, and grep -c prints 0 and exits 1 on zero matches, so a clean ledger produced a two-line count that differed from itself between two identical readings. The gate failed on its own arithmetic the moment the thing it guards became clean — passing only while the bug it was built to catch was still present. Defused per CON-003.
A field evaluation's five real findings — and the three that did not survive verification
A calibration run on a downstream project graded a five-lane parallel review and filed eight numbered findings. Each was checked against the code and against that run's own artifacts before anything was built; the two it ranked highest turned out to be a no-op and a hook devt does not ship, while its lowest-ranked item was the root cause of both.
A declared cross-cutting lane is now a first-class concept. A lens lane — plan compliance, changelog agreement, contract audit — reads files the logic lanes own, on purpose. Registration answered that with N file(s) assigned to multiple lanes; lanes are expected to be disjoint, correct for an accidental double-assignment and wrong for a declared one, and the warning is the only signal separating the two. lens: true (or disjoint: false) now marks the lane: only lanes that claim a file's logic count toward the disjointness warning, and the lane's envelope gains a <lane_lens> block. The envelope half mattered more than the suppression — <lane_neighbors> tells every lane that another lane's files are THEIRS, which for a lens lane forbids precisely the work it exists to do, so that block now states the ownership rule in terms of logic rather than files. The operator had been writing that paragraph by hand every run.
The flag had to be taught to three separate parsers to survive one hop each: the partition-file reader (its own minimal YAML parser, which names five keys and silently drops the rest), the registry writer, and listLaneOutputs's line-based read-back. Two of the three were found only because the first end-to-end test rendered an envelope with no <lane_lens> in it despite lens: true sitting in workflow.yaml — a consumer added without checking the producer could deliver, which is the same defect this batch exists to close. lens is emitted unconditionally as a boolean, like stale: a key present on some lanes and absent on others makes a reader's .lens indistinguishable between "not a lens" and "this field does not exist".
Registration answers in the vocabulary it was asked in. A partition file declares scope: and files:; the registry stores them as community and file_count and answered only in its own names. Reading .scope and .files off the response returned null for every lane and was reported from the field as 0 lanes registered / files=0 — registration had in fact succeeded completely. Both names are now returned, and entry.community is accepted on input, matching the dual-convention handling repo_root/repoRoot already had. This is also the real content of the evaluation's top-ranked finding: list-lane-outputs was reported as blind to its own artifacts, but re-running it against the very state that produced the report returns file_exists: true and a real byte count for all five lanes. The query had asked for .present and .bytes; the fields are file_exists and file_size_bytes. The prescribed fix — "stat the review_file path" — is what the function already does, including an id-form alias fallback.
A thin drill-down is no longer assumed to be a fake one. The substance gate demanded ≥200 bytes per drill-down section, exempting a truncation marker and an empty marker. Neither covers a section that is short because the subgraph is short — one real caller behind 32 filtered noise nodes — so accurately reporting a sparse neighborhood failed a gate built to catch fabrication. compose-drilldowns has been emitting _(filtered: noise=N, …)_ from the query result all along and nothing read it; the gate now credits it. Reading that code turned up a second unread marker: the empty marker has a DI-factory-hint variant whose own text carries parentheses, so the pattern demanding a closing )_ credited the bare form and failed the more informative one. Matched by prefix now. Padding that cites neither still counts as thin.
Lane briefs name the corpus an existence claim has to rest on. A deletion review lost time to local-history snapshots, coverage HTML, and type-checker caches — stale copies of the very files under review, which can appear to disprove a "no remaining readers" finding using the source the branch just deleted. Stated as a corpus rule rather than a path list, because the noise directories differ per project and "tracked" does not: <search_discipline> tells lanes to ground existence and absence claims in git-tracked content. Checked against the reporting project first — two of its three noise directories were already gitignored (so rg skips them); the one that actually bit was 84 MB of coverage HTML that is not.
--fresh stops being persisted as scope text. The flag is consumed at substep 0 and was then left in $ARGUMENTS, which substep 1 persists verbatim as workflow.yaml::task — so it reached the preflight topic query, the memory signal, and every later taskChanged comparison. --range= is deliberately left alone: the parallel path reads it back out of the persisted task. Gate F66.
Validated non-issues from the same report (recorded so they are not re-fixed)
- The graphify PreToolUse hook it asks devt to scope is not devt's. It lives in the reporting project's own
.claude/settings.local.json— two hand-rolled hooks emitting theMANDATORYtext quoted in the report. devt ships noRead|Globmatcher and no graphify Bash hook; its only Bash hook is the safety guard. - The two rubric files being byte-identical is the configuration, not a copy step.
code_review_parallelpinscode_review.v2.mddeliberately — the verifier grades the consolidated artifact — and 18504 bytes is that file's size. - The raw-dispatch gate is not vacuous. That run's own
gate-trace.jsonlrecordeddispatch-warnings.jsonl absent — dispatch-hygiene-guard.sh last ran at <ts> in this window and recorded nothing, withevidence: guard_fired. The report quotes the first clause and stops before the one that answers it. - Two of three "workarounds that should become defaults" already are. Rotating
workflow_typebefore lane registration is the shipped step order, and lane read-back chain-matches so an out-of-order lane still readscurrent; the scope artifact the parallel path skips is pre-written byscope_checkunder gate K313, with self-recovery if absent.