fix: bound the per-unit reviewer read scope to the unit plus shared contracts (2.2.16) - #538
Conversation
|
Since filing #534 I ran the same 12-unit intent through Construction nfr-requirements with the #533 model fix applied locally (all six reviews confirmed on 1. The growth reproduces, and it's worse than linear. Six nfr-requirements reviews in dispatch order:
Duration tracks review order, not unit complexity — each later reviewer has more READY siblings to cross-check, and each sibling artifact has grown (appended 2. Why the cost compounds: per-call latency scales hard with context. Pooling all ~560 API calls across the six runs and bucketing time-from-tool-result-to-next-response by total input tokens:
There's a knee near 100k tokens (~5× jump), and ~30× total degradation by 200k. In the U03 review, ~85 of 89 minutes were time-to-first-token on calls emitting ~30 output tokens. Implication for this PR: bounding sibling reads doesn't just save the reads — it keeps every subsequent call in the review below the knee. The benefit is superlinear, which strengthens the case for merging. 3. The gap: the prose bound doesn't cover the access pattern the reviewer actually used. The scope sentence forbids reading "other units' (alongside 32 Reads touching 30 distinct files across five units, and 97 Bash calls total). A Suggested follow-up (not a blocker): a deterministic PreToolUse hook in One more observation supporting the "no lost coverage" section: in my runs the conductor's dispatch prompts already named the specific sibling invariants by ID (e.g. "Sibling consistency: U04 (EP-1 publish envelope, EC-1 footprint formula, ER-7) and U02 (metrics-doc convention, rung mechanics)"). The orchestrator demonstrably knows the contract-level facts without the reviewer sweeping prose to rediscover them — which is the PR's coverage argument, confirmed from the other side. It also validates patching all four SKILL.md bullets: the conductor composes scope-expanding prompt text on its own, so the protocol edit alone wouldn't have reached it. MethodologyDurations and API-call counts come from the subagent transcripts under |
|
Thanks for running this on a second stage and bringing transcript-level numbers - the latency-knee table is the strongest argument for this PR that anyone has produced, and the point that bounding sibling reads keeps every subsequent call below the knee (so the benefit is superlinear) is now part of how I'd frame the change. On the gap you found: you're right, and it's fixed. The bound as first shipped said "must not read other units' On the deterministic PreToolUse hook: agreed, and filed as #539 rather than folded in here - the hook needs a deterministic way to know "the active subagent is the reviewer for unit X" (the dispatch is conductor-driven prose today), and the blocking capabilities differ per harness (Kiro CLI can hard-block from its preToolUse seam; Codex likely degrades to advisory), so it deserves its own design pass rather than a rider on a prose PR. Prose-vs-persona is a contest prose eventually loses with this reviewer, as you note - the hook makes the bound self-enforcing and is where this should end up. Your observation that the conductor's dispatch prompts already name sibling invariants by ID is a useful confirmation of the coverage argument from the side we couldn't test - and yes, it validates patching the four SKILL.md bullets rather than the protocol alone. |
leandrodamascena
left a comment
There was a problem hiding this comment.
APPROVE
I traced the runtime path: buildRunStageDirective sets consumes: present from splitConsumesByPresence(resolveConsumes(...)) (aidlc-orchestrate.ts:1145), and the consumed inception artifacts resolve to the shared producer dir, not per-unit. So the contract paths the reviewer is told to check against actually reach it — the bound isn't dead prose.
Cross-harness parity holds (package.ts --check green, all four SKILLs carry the same reviewer-step wording). Enforcement is prose-only, but it's restated as RFC-2119 MUST NOT across protocol §12a, the persona, the knowledge file, and the four orchestrators, and it's tool-agnostic (names grep/glob/shell so a construction/*/ glob counts as a sibling read, not a search). There's no way to express "only the current unit" as a static path scope since every unit lives under aidlc/spaces/**, so prose is the only option here. I'm fine with the coverage trade-off: an unresolved cross-unit question becomes a finding against the design or the shared contracts, both in scope.
Two non-blocking test gaps in t217:
- The persona assertions (t217:47-54) pin the
## Review Scopeheader, the reworded cross-reference line, andspot-check, but not the "do NOT access sibling units' construction" sentence or the grep/glob wording on that surface. So that sentence could be deleted from the persona and t217 stays green. Add a/grep, glob, or shell patterns/assertion to the persona block. - Same gap in the knowledge block (t217:57-67): the tool-agnostic clause on reviewing.md:27 is unpinned. Worth an assertion there too.
Neither blocks the merge — the wording is present on all surfaces today, just under-guarded on two of them.
…ontracts (2.2.16) On for_each: unit-of-work design stages the section-12a reviewer received a constant-size pass-list but nothing bounded its read scope, and the persona pushed a cross-reference-everything sweep, so reviewing unit N read sibling units' construction directories and per-unit review cost grew linearly with completed-unit count. - Stage-protocol 12a step 1 extends the per-unit pass-list with the resolved directive.consumes paths (shared inception contracts, paths only) and adds an explicit reviewer read-scope bound; step 2 mirrors the bound into the reviewer-executes enumeration. - The architecture-reviewer persona gains a Review Scope section; the cross-reference line is scoped to the artifacts under review plus passed contracts; the knowledge checklist verifies cross-unit boundaries against the shared inception contracts, with a named-integration-point spot-check carve-out (one file, not a sweep). - All four harness orchestrator skills carry the same bound in their reviewer-step bullet. - New t217 pins the bound on all four surfaces. Fixes #534
Second-round review (independent Claude and gpt-5.5 passes) found four issues in the new bound: - The persona's cross-reference principle read 'if it exists in what you were passed, it should be referenced', which post-fix includes the app-wide shared contracts - instructing the reviewer to flag every other unit's contract entries as unreferenced in the current unit's design (a false NOT-READY generator). The should-be-referenced direction is now scoped to the artifacts under review, with an explicit instruction not to flag other units' contract entries. - The 12a pass-list described directive.consumes as 'the shared inception contracts'; it actually carries all upstream artifacts the stage declares. Wording corrected. - The spot-check carve-out let a reviewer browse a sibling directory to locate an integration point named only by identifier. All three sites (protocol, persona, knowledge) now require resolving the identifier to its owning file via the shared contracts, never by browsing. - t217's orchestrator assertion read only the shipped Claude SKILL; the four harness SKILL files are independently authored, so it now checks all four. Header note added: the persona is the load-bearing surface on Claude Code and Codex - keep the bound there, not knowledge-only.
Field transcripts on a second per-unit stage (nfr-requirements, with the model-tier fix applied) showed the bound as shipped has a bypass: sibling access was dominated by recursive greps with cross-unit globs such as construction/*/*/*.md, which never 'read a directory' as the prose defined it. All six surfaces (protocol read-scope paragraph and reviewer-executes bullet, persona, knowledge checklist, four orchestrator skills) now state that grep, glob, and shell patterns spanning sibling unit paths count as sibling reads, and the spot-check carve-out excludes searching as well as browsing. t217 pins the tool-agnostic wording on the protocol and all four authored skill files.
631a08e to
e97e4f1
Compare
fix: bound the per-unit reviewer read scope to the unit plus shared contracts (2.2.16)
Fixes #534
Problem
On
for_each: unit-of-workdesign stages (functional-design, nfr-requirements, nfr-design, infrastructure-design, code-generation), stage-protocol section 12a passes the reviewer a constant-size pass-list but nothing bounds what it may read. The persona ("Cross-reference everything") and the reviewing knowledge checklist ("Cross-unit contract boundaries respected?") actively push it into sibling units'construction/<other-unit>/directories, so reviewing unit N reads the designs of units 1..N-1 and per-unit review cost grows linearly - multiplied by the NOT-READY iteration loop. The issue's field data: a 12-unit intent spent over 24 hours in functional-design, with reviews of 20-90 minutes each and direct quotes of three sibling units' artifacts in one unit's review.Change
Cross-unit contracts already live in the shared inception artifacts each stage
consumes:(components.md,component-methods.md,services.md,unit-of-work.md), and the engine already resolves those to absolute paths in the directive. This PR routes cross-unit verification through them:directive.unitpresent) the pass-list now also carries the resolveddirective.consumespaths (paths only, per the existing context-budget rule), plus an explicit "Reviewer read scope" paragraph: current unit's artifacts + passed contracts; other units'construction/directories only to spot-check an integration point the current unit's design explicitly names, and only that file. Step 2's "Reviewer executes" enumeration mirrors the bound.aidlc-architecture-reviewer-agent.md): new## Review Scopesection; "Cross-reference everything" is scoped to the artifacts under review and the passed contracts.reviewing.md): Stance gains a scope bullet; the Functional Design checklist item names the shared inception contracts as the verification source, with the one-file spot-check carve-out.t217-reviewer-read-scope.test.tspins the bound on all four surfaces (protocol, persona, knowledge, orchestrator skill). All 17 assertions fail on the pre-fix bytes and pass post-fix.aidlc-product-lead-agentneeds no edit: it reviews no per-unit stage, and the protocol wording keys offdirective.unit, so the bound applies agent-agnostically if that ever changes.dist/regenerated,bun scripts/package.ts --checkgreen.Why this does not lose review coverage
A sweep of sibling designs was verifying claims the shared contracts already pin. Where a unit's design names a sibling's internal identifier, the spot-check carve-out still opens that one file. Where a design hand-waves ("integrates with earlier units") the reviewer now raises a finding instead of doing the reader's work - and a contract detail that exists only in a sibling's design prose is itself a gap in the shared contracts, which surfaces as a finding against the design or the contracts, both in scope.
Evidence
directive.consumesis emitted with resolved absolute paths; the persona reaches the reviewer on all four harnesses, including via Kiro'sfile://prompt references and the Codex TOML embed) and found four wording/coverage issues, fixed in the second commit: a persona clause that would have flagged other units' shared-contract entries as unreferenced (false NOT-READY generator), an inaccurate characterization ofdirective.consumes, a spot-check carve-out loose enough to permit sibling-directory browsing, and t217 covering only the Claude SKILL surface when the four harness SKILL files are independently authored.Notes for reviewers
modelOverride:but Claude Code's field ismodel:— every delegated agent silently inherits the session model (the sonnet tier never engages) #533 claims 2.2.15; the usual second-to-merge re-bump convention applies.modelOverride:but Claude Code's field ismodel:— every delegated agent silently inherits the session model (the sonnet tier never engages) #533, fixed separately).