RFC: escalating review ladder (Sonnet -> Opus -> Fable), escalate-on-non-approval #308
Labels
No labels
Agent
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
joshtronic/igor#308
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
Replace the single-tier code review (one
claude_callonAGENT_MODEL_REVIEW) with an escalating ladder: the cheapest capable model reviews first, and a PR only climbs to a stronger (costlier) model when the current tier cannot get to APPROVE. A human is the final rung, reached only after every model tier is exhausted. Net effect: most PRs clear on Sonnet (cheaper + faster than always-Opus today); only the genuinely hard ones burn Opus/Fable. Better ceiling, lower average cost.Today (for grounding)
do_review_tickruns ONEclaude_callonAGENT_MODEL_REVIEW(currently Opus). Verdicts: APPROVE/COMMENT -> request the human; REQUEST_CHANGES -> assign the bot, drive the rework loop, capped at 3 rounds (rework_rounds) then escalate to the human. State lives per-PR under.review[<repo#num>]={sha, verdict, ci, patch_id, rework_rounds, rework_crashes, pending_rc_body, ...}. NoteAGENT_MODEL_REVIEWis ALSO read by two one-shot triage users: logwatch and maintenance-classify.Proposed
AGENT_MODEL_REVIEWbecomes a comma-ordered, cheapest-first ladder, e.g.claude-sonnet-5,claude-opus-4-8,claude-fable-5.do_review_ticksplits it into tiers. A single value = todays behavior (degrades gracefully). The one-shot triage users (logwatch, maintenance) take the first rung (${AGENT_MODEL_REVIEW%%,*}) — they dont rework/escalate, and cheapest-tier triage is the right call (also the Sonnet-for-triage you wanted)..review[key].tier(int, default 0). Review + rework calls useladder[tier].rework_roundshits the cap without APPROVE, bumptier, resetrework_rounds, re-review with the next model — instead of jumping straight to the human.tierand re-review the same head with the next model.rework_crashescap (igor#291) routes to tier-escalation too.AGENT_MODEL_SECURITY= Opus, NOT part of this ladder); igor never auto-merges itself (ladder is advisory there, human gates); per-SHA/patch-id dedup; CI-must-settle-before-review.Why this shape
Open questions (your calls)
AGENT_MODEL_REVIEW-as-CSV-ladder (my rec — one var; one-shot users take rung 0) vs. a separateAGENT_MODEL_REVIEW_LADDERvar vs. explicit per-tier vars (_1/_2/_3). This is the ".env rethink" you flagged.Not in scope
Curing the exit-1 (#306); the security gate; the human-merge gate. Purely the code-review model-routing + escalation.
Decisions from review (2026-07-01) + a new dimension
Locked:
AGENT_MODEL_REVIEW=sonnet,opus[,fable]. Flexible on which models / how many rungs. Fable is not available yet — stage it with the dual-line convention (active line + commented desired-end-state), Fable commented until its live.Riffed / proposed:
New dimension — effort ladder (great idea)
Today the harness sets no reasoning-effort at all. Proposal, orthogonal to the model ladder:
So two independent escalation axes:
Caveat to confirm before building: the
/effortyoure picturing is the interactive command — I need to verify the headlessclaude --printinvocation exposes a per-call effort flag. If it doesnt, the effort axis needs a different mechanism (or waits).--effortconfirmed + near-term shape (2026-07-01)claude --effort <level>is a real CLI arg (verified viaclaude --help, alongside--model). So the effort axis is buildable, and better: a ladder rung can be amodel:effortpair, which solves the "only 2 models while Fable is out" problem elegantly.Near-term shape (Josh): dont run a dynamic variable-length model ladder yet. Instead:
opus(default) ->opus:high->opus:xhigh, last rung a single "final boss" pass, then the human..env(no dynamic 3-model logic to reason about), while still giving real escalation —opus:defaultvsopus:highis a meaningful capability step.fableorfable:high).So the rung parser should accept
model[:effort]and map to--model <m> [--effort <e>]. Effort default when unspecified = whatever the harness default is (medium). The worker effort-scaling axis (med->high->xhigh per rework pass) is unchanged and independent.Rounds stay the hardcoded decreasing constant (3/2/1, last rung = 1). Igor has an opinion here; not a knob.
Ladder simplified (2026-07-01) — Opus + effort only, no Fable
Per Josh: drop Fable (too fragile — weird guardrails, even for interactive Igor). CoS-Igor stays Opus. The ladder collapses to ONE model + effort:
opus:maxterminal review before escalating to the human. MAX is justified because it's the tail — only PRs that resisted the loop reach it, and the alternative is Josh's time (the most expensive resource). Safe to bump here because it's terminal (no rework after it -> no goalpost drift). The ONE deliberate reviewer-effort bump.opus:maxfinal boss -> human.Net:
.envstays single-model Opus; escalation is pure effort. Simpler than the CSV model-ladder, nothing dynamic to reason about. The rung parser (model[:effort]) still generalizes if we ever re-add a model tier.Flow clarification + worker-effort (2026-07-01)
Every path ends at Josh — he's the merge gate for everything. The difference is why:
opus:maxfinal boss → if still no approve, to Josh to ADJUDICATE ("I couldn't get this to pass, your call").So "approve → you, don't keep escalating" is exactly right. Escalation (effort climb + final boss) only fires on non-approval.
Worker effort on tasks (Josh's Q)
v1: the worker (Sonnet) effort climbs on rework — default → high → xhigh — but the worker MODEL stays Sonnet. Two independent axes, both gated on non-approval:
opus:maxterminal.Deliberately NOT building a worker→Opus model bump yet. If Sonnet-at-max-effort keeps failing review, THAT's a real signal to add one — but we should see it in the data first, not assume it. Which is exactly what the telemetry ticket (#310) is for: model×effort×outcome per surface tells us empirically whether a worker Opus rung earns its cost. Tune from data, don't speculate. ("Don't be the KDE of agents.")