Skip to content

Panel launch prompts are hand-authored per lens per round — nothing assembles the contract, so an omitted item is invisible #214

Description

@topij

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:

  1. It fails closed on a stale base rather than emitting a prompt that names one.
  2. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions