Observed across ten panel rounds on PRs #202 and #207, 2026-08-01. Severity MED.
The gap
fallback-review-panel.md specifies what every lens must be told — ten contract items plus the lens's focus from review.fallback_panel.lenses, plus the repo/branch/sha, plus the base and how its currency was established, plus the scratch-isolation rules.
Nothing assembles that. Each launch prompt is hand-authored by the cockpit, from a 598-line source (#213), once per lens per round. Twenty prompts this session.
What that costs
- An omitted contract item is invisible. Nothing compares the prompt against the contract, so a lens that was never told to mutation-test simply does not mutation-test, and its report reads as a clean pass with no mutation section. Contract item 8 asks the lens to state what it verified — but a lens cannot report the absence of an instruction it never received.
- Rounds are not comparable. I varied the framing deliberately between rounds — pointing later lenses at specific suspicions — which is useful, but it means "round 5 found less than round 4" cannot be read as "the code got better". Some of the variance is mine.
- Config that exists is not reliably applied.
review.fallback_panel.lens_compute sets model and effort per runtime. On the Claude path pr_followup_hook.py renders it into a reminder, but the cockpit still transcribes it by hand into each launch. A key can be set correctly in config and silently not reach a lens.
- The base-currency statement is re-derived by hand every time, and it is the field that most needs to be mechanical: a stale base yields a large, wrong, entirely plausible diff that satisfies every other check (the doctrine says so itself).
Why the existing pieces do not close it
pr_followup_hook.py renders a reminder to run the panel, not the prompt.
.agents/skills/pr-watch/SKILL.md names lens_compute.codex for the Codex adapter — again a pointer, not an assembler.
- The doctrine's step 2 says to state repo/branch/sha and gives the
git worktree add --detach recipe, so the inputs are specified. Nothing renders them.
Suggested
A small engine — <engine-dir>/panel_prompt.py or equivalent — that takes PR number, head sha, base sha and lens name, and emits the launch prompt: contract items verbatim from the doctrine, focus and compute from config/dev-model.yaml, and the provenance fields resolved mechanically (git rev-parse, git fetch origin <base> then compare, diffstat).
Two properties worth having, both cheap:
- It fails closed on a stale base rather than emitting a prompt that names one.
- It emits the same prompt for the same inputs, so a round's framing differences are deliberate additions the cockpit appends, not variance in what the contract delivered.
This also makes the contract mechanically consumable, which is the precondition for #212's "quote sources verbatim with file and line" and for any future check that a lens's report covers every item it was given.
Scope note
This is worth doing after #213 (splitting what executes from what explains), not before — an assembler that inlines 598 lines into every prompt is worse than the status quo. The executable contract is what it should render.
Related
#213 (the doctrine's length — this depends on it), #32 (the lens roster is self-reported; a rendered prompt does not fix that but is what a real check would build on), #212 (lens disagreement), #163 sink 1 (every lens rediscovering the same environment failure — a rendered prompt is where the environment answer would live once).
Observed across ten panel rounds on PRs #202 and #207, 2026-08-01. Severity MED.
The gap
fallback-review-panel.mdspecifies what every lens must be told — ten contract items plus the lens'sfocusfromreview.fallback_panel.lenses, plus the repo/branch/sha, plus the base and how its currency was established, plus the scratch-isolation rules.Nothing assembles that. Each launch prompt is hand-authored by the cockpit, from a 598-line source (#213), once per lens per round. Twenty prompts this session.
What that costs
review.fallback_panel.lens_computesets model and effort per runtime. On the Claude pathpr_followup_hook.pyrenders it into a reminder, but the cockpit still transcribes it by hand into each launch. A key can be set correctly in config and silently not reach a lens.Why the existing pieces do not close it
pr_followup_hook.pyrenders a reminder to run the panel, not the prompt..agents/skills/pr-watch/SKILL.mdnameslens_compute.codexfor the Codex adapter — again a pointer, not an assembler.git worktree add --detachrecipe, so the inputs are specified. Nothing renders them.Suggested
A small engine —
<engine-dir>/panel_prompt.pyor equivalent — that takes PR number, head sha, base sha and lens name, and emits the launch prompt: contract items verbatim from the doctrine,focusand compute fromconfig/dev-model.yaml, and the provenance fields resolved mechanically (git rev-parse,git fetch origin <base>then compare, diffstat).Two properties worth having, both cheap:
This also makes the contract mechanically consumable, which is the precondition for #212's "quote sources verbatim with file and line" and for any future check that a lens's report covers every item it was given.
Scope note
This is worth doing after #213 (splitting what executes from what explains), not before — an assembler that inlines 598 lines into every prompt is worse than the status quo. The executable contract is what it should render.
Related
#213 (the doctrine's length — this depends on it), #32 (the lens roster is self-reported; a rendered prompt does not fix that but is what a real check would build on), #212 (lens disagreement), #163 sink 1 (every lens rediscovering the same environment failure — a rendered prompt is where the environment answer would live once).