Skip to content

prompts/critic: a REVISE names one defect and stops — 24 of 27 resolutions were rewrites, and #15 spent 3h35m across three claim cycles for 23m of work #206

Description

@melihucar

Gate: bootstrap
Agent: claude

Found by retrospective 38 (orchestrator/logs/retro-38.md, Finding 1).

This is not #188 (the right size axis counts subsystems rather than
behaviours), not #44 (the planner bundles work into one issue), not #62
(a critic's PROCEED notes never reach the implementer) and not #45 (the
attempt budget resets on requeue). Those are all about what the critic judges
or what happens after. This is about the critic being allowed to stop after the
first defect it finds.

Evidence

The critic is nearly always right, and nearly always incomplete.

$ jq -Rc 'fromjson? // empty' orchestrator/logs/events.jsonl \
    | jq -r 'select(.event=="resolved")|.decision' | sort | uniq -c
  24 rewrite
   3 proceed

24 of 27 resolutions are rewrites: the resolver, which researches the objection
independently, sustains the critic 89% of the time. So REVISE verdicts are not
the problem.

Rounds per issue are. 29 critic_revise across 19 distinct issues, and six
issues were revised more than once — #11 x4, #8 x3, #20 x3, #106 x2, #168 x2,
#15 x2. 16 of the 29 revisions (55%) are a repeat pass over an issue the
critic had already revised once.

The repeats are not re-litigation — each pass finds a genuinely new, genuinely
correct defect. #15's two objections, from the journal excerpts:

pass objection became
1 (12:09:50) split out "GET / must execute index.php", per caddy/php-server.go:133's try_files; #15's acceptance cannot pass without it #187, merged 14:39:35
2 (14:48:43) the conformance leg arms itself from image_exists() host state; replace with an explicit opt-in before #15 can assert a gate result #199, merged 15:16:11

Both correct. Both merged. Both were visible from #15's acceptance criteria on
the first pass.

The cost

orchestrator/logs/supervisor-20260815T090737Z.log + events.jsonl, #15:

12:05:06  claim 1  critic -> 12:09:50 REVISE -> 12:16:24 resolved(rewrite, round 1)
14:39:55  claim 2  critic -> 14:48:43 REVISE -> 14:55:54 resolved(rewrite, round 2)
15:16:26  claim 3  critic -> PROCEED -> 15:29:02 gate ok -> 15:39:15 pass/pass
15:40:07  merged

3h35m01s wall-clock; 23m41s of it was #15's own implement + gate + review.

Claim 2 began 20 seconds after #187 merged; claim 3 began 15 seconds
after #199 merged. Each rewrite added a Depends on: the previous pass had not
named, parking #15 in fr:waiting until that dependency was built. The
discovery was strictly serial: one missing prerequisite per claim cycle, each
costing the full build-and-merge time of the thing it discovered.

#168 shows the cheaper shape of the same defect — critic 09:12:38 -> rewrite ->
re-claim 09:19:33 -> rewrite -> re-claim 09:27:09. No new dependencies, so no
latency, but three critics and two resolvers consumed for one issue.

Aggregate over the run: 24 rewrites are 24 extra claim cycles and ~48 extra
critic + resolver invocations, against 37 merges.

Diagnosis

orchestrator/prompts/critic.md never asks for completeness. The REVISE branch
of ## Output asks for

  • what is wrong, concretely, with evidence from the code

which reads as singular, and the closing bias paragraph pushes on the wrong
lever entirely:

A REVISE costs a full cycle and needs re-triage, so spend it on issues that
are actually wrong

That optimises the number of REVISE verdicts. The measurement above says
the verdicts are almost all correct and the cost driver is the number of
rounds per issue
. A critic that reports the first blocking defect and stops is
following this prompt exactly.

There is also no axis that asks the obvious question #15 failed twice: does
this issue declare everything that must already exist for its own acceptance
criteria to be reachable?

What to change

orchestrator/prompts/critic.md only.

  1. In the REVISE branch of ## Output, require enumeration: every blocking
    defect found this pass, as a numbered list. State plainly that the next pass
    is a fresh critic with no memory of this one, and that a defect left unlisted
    costs another full claim cycle — cite bench: docker/frankenrust.Dockerfile producing frankenrust:bench #15's 3h35m for 23m of work.
  2. Add a sixth judging axis, "Does it declare everything it needs?" — walk
    the acceptance criteria and the named Gate: profile and list every artefact
    (file, binary, image, gate step, upstream behaviour) that must exist before
    they can be evaluated. Anything missing belongs in Depends on: or is a
    piece to split out, named in this pass. Use bench: docker/frankenrust.Dockerfile producing frankenrust:bench #15 -> server: GET / never executes index.php -- the request path is handed to PHP as a directory #187/conformance: the frankenrust replay leg arms itself from host-global Docker state, and today fails all 25 cases on header casing alone #199 as the worked
    example.
  3. Rewrite the bias paragraph to separate the two decisions it currently
    conflates: be reluctant to REVISE at all; once you are revising, be
    exhaustive. Keep the existing "spend it without hesitation on anything
    touching unsafe, FFI lifetimes, or thread affinity" sentence verbatim.

Acceptance

  • orchestrator/prompts/critic.md contains an explicit completeness
    requirement in the REVISE branch, and a prerequisite-enumeration judging axis.
  • The PROCEED/REVISE verdict tokens are unchanged — loop.py:1460 greps for
    VERDICT: REVISE and VERDICT: PROCEED literally.
  • bash scripts/gate.sh bootstrap passes.

Out of scope

resolver.md, MAX_REVISIONS, the requeue path in loop.py, and #131's
revision-counter reset. Do not touch loop.py or gh.py.

Metadata

Metadata

Assignees

No one assigned

    Labels

    fr:metaImprovement to the loop itselffr:p2Normal -- the default when untriagedfr:readyClaimable by an agent

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions