Auto Iteration Loop: Adversarial Review with Bounded Back-Edges
Autonomously iterate over /auto-verify's per-claim verdicts: review → implement fixes (variant-integrity, main-experiment-script, or claim-stage rewrite) → re-review, until the external reviewer gives a positive assessment with all FAIL / INCONCLUSIVE / ZERO_ELIGIBLE_VARIANTS claims resolved, or the iteration budget is exhausted.
Context: $ARGUMENTS
Constants
MAX_ITERATIONS = 6 — Hard upper bound on total back-edge actions across the whole loop. Each of the following consumes 1 iteration, regardless of which Phase A→E cycle they fall in:
- ① variant-only fix → re-run
/auto-verify <claim-id> — resume: true - ② main-experiment-script /
EXPERIMENT_PLAN.mdfix → re-run/auto-experiment(optionally followed by/auto-verifychain) - ③ claim-stage re-entry → rewrite claim (full
/auto-claiminvocation OR lightweight in-loop rewrite) → re-run downstream stages
Pure reviewer cycles that change nothing on disk (no new experiments, no claim changes) do not consume iterations. PASS-claim consistency checks and deferred-claim recording do not consume iterations. New claims produced by ③ inherit the same budget — they do not get a fresh
MAX_ITERATIONSof their own. Override viaMAX_ITERATIONS:arg fromagents/iteration.md.Resource-Fidelity invariant (all back-edges). If
refine-logs/FINAL_PROPOSAL.md/refine-logs/EXPERIMENT_PLAN.mdcarry aresource_fidelity: strictmarker (the reproduction combination —behavior-source:given+mechanism:given), every back-edge MUST preserve it: when editingEXPERIMENT_PLAN.md(type ②) or any claim rewrite (type ③), keep the top-of-fileresource_fidelity:line verbatim — never drop, comment out, or change it. Dropping the marker silently disables the harness, re-opening the door to smaller-model swaps and data subsetting the harness exists to prevent. For type-③ full re-entry, detect the original run's axes fromidea-stage/IDEA_REPORT.md's**Behavior-source**:/**Mechanism**:headers (or directly from theresource_fidelity: strictmarker) and pass bothbehavior_sourceandmechanisminto the queuedauto-claimcall so a reproduction-combo re-entry staysgiven+given(the new run re-stamps theresource_fidelity: strictmarker automatically — it is marker-driven, not a flag).- ① variant-only fix → re-run
MAX_CLAIM_REENTRIES = 2 — Sub-budget within
MAX_ITERATIONSfor action type ③ (claim-stage re-entry). Prevents the failure mode where the reviewer keeps asking "rewrite the claim" without anyone actually fixing the experiments behind it. Whenclaim_reentries_consumed >= MAX_CLAIM_REENTRIES, the loop refuses further ③ actions even if iterations remain — at that point the only legal back-edges are ① and ②, and if those are also exhausted the loop terminates and writes the final report.TARGET_SCORE = 6 — Stop when reviewer score >= this AND verdict matches a
POSITIVE_VERDICT_TERMSentry AND no claim is still in FAIL / INCONCLUSIVE / ZERO_ELIGIBLE_VARIANTS (three-dimensional STOP rule — see Phase B).POSITIVE_VERDICT_TERMS = [ready, almost] — Case-insensitive substrings that count the verdict as positive. Phase B canonicalizes the reviewer's free-form verdict to one of
{ready, almost, not ready}before this check, so any reviewer-supplied synonym (e.g.,accept,sufficient,good enough) must be mapped toready/almostby Phase B's parser — see Phase B's STOP rule for the canonical check.REVIEW_DOC =
review-stage/AUTO_REVIEW.md— Per-iteration append-only audit log (fall back to./AUTO_REVIEW.mdfor legacy projects). This is the raw audit trail; the narrative report isFINAL_REPORT_DOCbelow.REVIEWER_MEMORY_DOC =
review-stage/REVIEWER_MEMORY.md— Reviewer's persistent suspicion log across iterations. Append-only: each iteration's Phase B.5 adds a section under## Iteration N, never deletes prior iterations (audit trail). Phase A of iteration 2+ prepends the full file to the reviewer prompt so the reviewer can check whether prior-iteration suspicions were genuinely addressed or sidestepped. Counters the failure mode where Claude curates a comfortable subset of context for the reviewer each iteration.FINAL_REPORT_DOC =
review-stage/AUTO_ITERATION_FINAL_REPORT.md— Narrative final report written once in Termination (after the loop ends for any reason). Organized per-claim by their original/auto-verifycategory (PASS / FAIL / INCONCLUSIVE / ZERO_ELIGIBLE_VARIANTS / DEFERRED), with the per-claim iteration journey, experiment/script diffs, and any claim rewrites laid out inside each claim's section. Distinct fromREVIEW_DOC(which is the chronological audit log). See "Final Report" section below for the full template.GPU_ID =
auto— GPU device(s) to pin every Phase-C/run-experimentdispatch to.autolets the launcher / environment decide (no extra export). A single id (0) or comma-list (4,5,6,7) causes this skill to passCUDA_VISIBLE_DEVICES=<GPU_ID>as the first positional arg to every/run-experimentinvocation (the run-experiment skill exports it internally; do not treat as a shell prefix), and record the effectiveCUDA_VISIBLE_DEVICESin each run'srun.sh. Forwarded fromagents/iteration.md'sgpu_id:arg (which the orchestrator forwards from/auto'sGPU_ID).RESUME = false — When
true, readreview-stage/REVIEW_STATE.jsonand pick up fromiterations_consumed + 1instead of starting fresh. Append new iterations to the existingAUTO_REVIEW.mdrather than overwriting it. Iteration budget and claim-reentry sub-budget are inherited across resume — they are never reset. IfREVIEW_STATE.jsonalready showsstatus: completed(positive verdict, iterations exhausted, or claim-reentry budget exhausted), return immediately without running another iteration. Ifstatus: awaiting_upstream, the orchestrator (seeauto/SKILL.md) is expected to run the pending upstream calls first, then resume this loop. Defaultfalse= always start fresh and overwrite prior review state. Resume never deletes pre-existing review state. Schema fields read here:iterations_consumed,claim_reentries_consumed,status,last_verdict,last_score— see "State Persistence" below.
Reviewer LLM Configuration (mandatory, read first)
This skill calls an external LLM reviewer. Never hardcode a model name and never read the reviewer model from task.md / project READMEs / source comments. Project-level files may list available API keys for unrelated purposes (e.g., LLM-as-judge inside experiment code); those are not the reviewer config.
Resolve LLM_MODEL, LLM_BASE_URL, LLM_API_KEY strictly in this priority order before any reviewer call:
- Project MCP config —
${PROJECT_ROOT}/.mcp.json, fieldmcpServers["llm-chat"].env.{LLM_MODEL,LLM_BASE_URL,LLM_API_KEY}. - User MCP config —
~/.claude/settings.json, same field. - Shell environment —
$LLM_MODEL,$LLM_BASE_URL,$LLM_API_KEY.
Pre-flight check (run before Phase A of every iteration, mandatory)
LLM_MODEL_SRC=""
if [ -f .mcp.json ] && jq -e '.mcpServers["llm-chat"].env.LLM_MODEL' .mcp.json >/dev/null 2>&1 ; then
export LLM_MODEL=$(jq -r '.mcpServers["llm-chat"].env.LLM_MODEL' .mcp.json)
export LLM_BASE_URL=$(jq -r '.mcpServers["llm-chat"].env.LLM_BASE_URL' .mcp.json)
export LLM_API_KEY=$(jq -r '.mcpServers["llm-chat"].env.LLM_API_KEY' .mcp.json)
LLM_MODEL_SRC="project .mcp.json"
elif [ -f ~/.claude/settings.json ] && jq -e '.mcpServers["llm-chat"].env.LLM_MODEL' ~/.claude/settings.json >/dev/null 2>&1 ; then
export LLM_MODEL=$(jq -r '.mcpServers["llm-chat"].env.LLM_MODEL' ~/.claude/settings.json)
export LLM_BASE_URL=$(jq -r '.mcpServers["llm-chat"].env.LLM_BASE_URL' ~/.claude/settings.json)
export LLM_API_KEY=$(jq -r '.mcpServers["llm-chat"].env.LLM_API_KEY' ~/.claude/settings.json)
LLM_MODEL_SRC="user ~/.claude/settings.json"
elif [ -n "$LLM_MODEL" ] && [ -n "$LLM_BASE_URL" ] && [ -n "$LLM_API_KEY" ] ; then
LLM_MODEL_SRC="shell env"
fi
echo "[reviewer-config] LLM_MODEL=$LLM_MODEL LLM_BASE_URL=$LLM_BASE_URL source=$LLM_MODEL_SRC"
Hard-fail rule: If LLM_MODEL is empty after this resolution (none of the three sources provides it), the skill MUST abort with:
"Reviewer model not configured. Add
mcpServers.llm-chat.env.{LLM_MODEL,LLM_BASE_URL,LLM_API_KEY}to.mcp.json(project) or~/.claude/settings.json(user)."
Do not guess a default. Do not fall back to a model name read from task.md or any other project file.
The earlier
## LLM Configurationexample block in older versions of this skill (which duplicated the samellm-chatMCP setup) has been removed — the authoritative config resolution is the priority order above. For a worked.mcp.jsonexample, seemcp-servers/llm-chat/in the project repo.
API Call Method
Primary: MCP Tool
mcp__llm-chat__chat:
prompt: |
[Review prompt content]
model: "${LLM_MODEL}" # resolved per "Reviewer LLM Configuration" priority order above — never hardcode
system: "You are a senior ML reviewer..."
Fallback: curl
curl -s "${LLM_BASE_URL}/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${LLM_API_KEY}" \
-d '{
"model": "${LLM_MODEL}",
"messages": [
{"role": "system", "content": "You are a senior ML reviewer..."},
{"role": "user", "content": "[review prompt]"}
],
"max_tokens": 4096
}'
State Persistence (Compact Recovery)
Persist state to review-stage/REVIEW_STATE.json after each iteration (i.e., every Phase E):
{
"iterations_consumed": 2,
"claim_reentries_consumed": 1,
"status": "in_progress",
"last_score": 5.0,
"last_verdict": "not ready",
"consecutive_noop_count": 0,
"iteration_breakdown": [
{"i": 1, "type": "variant_fix", "target_claims": ["C1"], "produced_claims": []},
{"i": 2, "type": "claim_reentry", "target_claims": ["C2"], "produced_claims": ["C2_v2"]}
],
"claim_stage_reentries": ["C2"],
"pending_upstream_calls": [],
"runs_total": 5,
"gpu_hours_total": 12.4,
"thread_id": "th_01ABC123def456",
"timestamp": "2026-03-15T10:00:00"
}
iterations_consumedis the cumulative count of back-edge actions (types ①/②/③) since iteration 1 — what's compared againstMAX_ITERATIONS.claim_reentries_consumedis the cumulative count of type ③ actions only — what's compared againstMAX_CLAIM_REENTRIES.iteration_breakdownrecords each iteration's action type, the claim(s) it targeted, and any new claim ids produced by claim-stage re-entry (so the final report can attribute new claims to the right ancestor).claim_stage_reentriesis the flat list of claim ids that triggered ③ — surfaced toagents/iteration.mdso the orchestrator can record which original claims got rewritten in the Ledger'sjourney_summary.iterationline andopen_items[].pending_upstream_callsis non-empty only whenstatus = awaiting_upstream: a list of{skill, args}calls the orchestrator must execute before resuming this loop.runs_total/gpu_hours_totalare cumulative across all iterations; they monotonically increase and are the canonical budget sourceagents/iteration.mdreports back to the orchestrator.
Field consumer map (so future maintainers know which fields are external API vs internal state):
| Field | Written by | Read by | Notes |
|---|---|---|---|
iterations_consumed |
this skill | auto/SKILL.md (inherits on resume), agents/iteration.md |
external; compared against MAX_ITERATIONS |
claim_reentries_consumed |
this skill | auto/SKILL.md (inherits on resume), agents/iteration.md |
external; compared against MAX_CLAIM_REENTRIES |
status |
this skill | auto/SKILL.md resume check, agents/iteration.md |
external; values in_progress / awaiting_upstream / completed |
last_verdict |
this skill | auto/SKILL.md resume check |
external; matched against POSITIVE_VERDICT_TERMS |
last_score |
this skill | auto/SKILL.md resume check |
external; numeric 1–10 |
iteration_breakdown |
this skill | this skill (Termination, final-report assembly); agents/iteration.md summary |
external; ordered list, append-only |
claim_stage_reentries |
this skill | auto/SKILL.md (for CLAIMS_LEDGER.md journey_summary + open_items[]), Termination |
external |
pending_upstream_calls |
this skill | auto/SKILL.md orchestrator only |
external; consumed (cleared) by orchestrator before resuming this skill |
runs_total |
this skill | agents/iteration.md summary, auto/SKILL.md budget tracking |
external |
gpu_hours_total |
this skill | agents/iteration.md summary, auto/SKILL.md budget tracking |
external |
timestamp |
this skill | this skill's resume gate (24h staleness check — see Workflow Initialization) | internal — drives the stale-state cutoff; archive instead of delete on age-out |
thread_id |
this skill (Phase A iteration 1) | this skill (Phase A iteration 2+) | internal — opaque conversation handle returned by mcp__llm-chat__chat after iteration 1's call. Phase A iteration 2+ passes it back to the MCP server so the reviewer keeps the same conversation thread (cheaper context + better cross-iteration coherence than re-pasting summaries). Absent / null when the iteration-1 reviewer call used the curl fallback (curl doesn't return a thread handle), or when the MCP server rejected thread continuation — in either case Phase A iteration 2+ silently falls back to the paste-summary path. |
Write this file at the end of every Phase E (after documenting the iteration).
On completion, set "status": "completed" and continue to Termination (which generates FINAL_REPORT_DOC).
On back-edge handoff to orchestrator (Phase C decided a fix requires upstream skills the iteration loop cannot run inline — e.g., full /auto-claim re-invocation), set "status": "awaiting_upstream", populate pending_upstream_calls, and return. The orchestrator runs them, then resumes this skill with RESUME=true.
Workflow
Upstream artifacts pre-flight (mandatory, run first)
This skill iterates over existing artifacts produced by upstream stages — it does not generate them from scratch. $ARGUMENTS (the optional research-direction string) is treated as reviewer context only, never as a starting point.
Before doing anything else, verify the project root contains at least one of:
idea-stage/IDEA_REPORT.mdrefine-logs/FINAL_PROPOSAL.mdrefine-logs/EXPERIMENT_RESULTS.mdverify/VERIFY_REPORT.md
If none of these exist, abort with:
[pre-flight] no upstream artifacts found at idea-stage/ refine-logs/ verify/.
/auto-iteration-loop reviews existing work — it does not start from a topic.
Run /auto (or at minimum /auto-claim then /auto-experiment) first.
Aborting.
Do not invoke the reviewer, do not write review-stage/AUTO_REVIEW.md. Abort cleanly.
Build five disjoint claim buckets from verify/VERIFY_REPORT.md
If verify/VERIFY_REPORT.md is present, read it once at the top of every iteration alongside verify/INTEGRITY_AUDIT.md and build five disjoint claim buckets that drive Phase A's reviewer prompt. The five states mirror /auto-verify's terminal states exactly (see auto-verify/SKILL.md Phase 11 column glossary):
verify_passed— claim ids whose state isPASS. Main-experiment verdict is robust under swaps. Routing: brief consistency check only (see below). No experiments fired.verify_failed— claim ids whose state isFAIL(robustness < ROBUSTNESS_THRESHOLDover enough eligible variants; the main experiment was fragile). Routing: two-phase (variant-integrity fix → optional claim-stage re-entry).verify_inconclusive— claim ids whose state isINCONCLUSIVE(Phase 2 main-experiment-integrity FAIL; variants never ran, or were policy-skipped under audit-only mode — semantics identical). Routing: fix the main experiment only (EXPERIMENT_PLAN.mdstep or main-experiment script).verify_zero_eligible_variants— claim ids whose state isZERO_ELIGIBLE_VARIANTS(Phase 2 the main experiment was clean, but every Phase 9 variant FAILed integrity). Routing: fix variant scripts only (do not touch the main experiment).verify_integrity_only— claim ids whose state isINTEGRITY_ONLY(Phase 2 pass/warn, Stage 2 intentionally skipped). Two producers, distinguished per-claim bystage2_skip_reasoninROBUSTNESS.md:swap_variants_false(global audit-only mode) ormax_verify_claims_cap(this claim was admitted by Stage 1 but not the top-K picked at Phase 3 step 0). Routing: no back-edge action — record in Open Items with per-stage2_skip_reasonupgrade suggestion. See per-bucket contract below.
Plus the orthogonal (legacy):
deferred_claims— Legacy bucket, always empty under the current architecture. Verify no longer cuts claims before audit; claims that would have been cut byMAX_VERIFY_CLAIMSin the old design now land inverify_integrity_onlywithstage2_skip_reason: max_verify_claims_cap. Retained as an empty placeholder for backward compatibility with olderVERIFY_REPORT.mdfiles. New runs never populate it.
The five state buckets are disjoint by construction (a claim has exactly one /auto-verify state); the legacy deferred_claims bucket is empty in new runs, so no overlap concerns arise. Under SWAP_VARIANTS=false, only two buckets can be non-empty: verify_inconclusive and verify_integrity_only (with stage2_skip_reason: swap_variants_false) — verify_passed / verify_failed / verify_zero_eligible_variants all require Phase 9 to have run and are always empty.
Per-bucket routing contract
verify_passed — brief consistency check. For each claim id in verify_passed, the reviewer does narrative + numeric consistency only:
- Does the claim wording match the supporting numbers in
refine-logs/EXPERIMENT_RESULTS.mdandverify/<claim_dir>/ROBUSTNESS.md? - Are there caveats (main-experiment-WARN tag) that need to surface in the final paper but not in the iteration loop's actions?
Record one line per claim under the iteration's ### Verify-Passed Claims (brief audit) section in AUTO_REVIEW.md. Does NOT consume an iteration — the reviewer's consistency check is part of the same Phase A reviewer call.
verify_failed — two-phase routing. For each claim id in verify_failed:
Phase 1 — Variant integrity check. Read this claim's
verify/<claim_dir>/variant_audit/{EXPERIMENT,MECHANISM}_AUDIT.mdand each surviving variant'sverdict.json.integrity_breakdown. If any surviving variant had a WARN/FAIL that could have biased the consistency check, that's the lead — fix that variant's script (deletion + targeted re-run is fine; full sweep is not required) and re-invoke/auto-verify <claim-id> — resume: true. Consumes 1 iteration (type ①).Phase 2 — Claim-stage re-entry. If Phase 1 was clean (or Phase 1 was already executed in a prior iteration and the claim still came back FAIL with clean variants), the divergence is real and the claim itself needs work. Choose one of:
- Lightweight in-loop rewrite (preferred when the reviewer has a concrete narrower claim in mind): write the new claim text directly into a
claim_rewrite:block inAUTO_REVIEW.md, then chain/auto-experiment+/auto-verifytargeted to the rewritten claim only. No/auto-claiminvocation. - Full claim-stage re-entry: set
status = awaiting_upstreamand queuepending_upstream_calls = [{skill: "auto-claim", args: {focus: "<claim-id> — verify-failed context: ...", behavior_source: "<inherit original>", mechanism: "<inherit original>"}}, {skill: "auto-experiment", args: {target_claims: ["<new-id>"]}}, {skill: "auto-verify", args: {target_claims: ["<new-id>"]}}]. Carry the original run'sbehavior_source+mechanisminto theauto-claimargs so a reproduction-combo (given+given) re-entry re-stampsresource_fidelity: strictautomatically (marker-driven — per the invariant above). The orchestrator runs them and resumes this loop with the new claim's/auto-verifyresult.
Either form consumes 1 iteration (type ③) AND 1 claim-reentry sub-budget. The new claim id (when produced) is recorded in
iteration_breakdown[i].produced_claimsso the final report attributes it to the original claim's section, not a fresh top-level entry.Crucially: when
claim_reentries_consumed >= MAX_CLAIM_REENTRIES, Phase 2 is not allowed even if the reviewer requests it — record the request, decline it in the iteration's writeup, and either return to Phase 1 (if there are still dirty variants left to fix) or terminate with the claim still in FAIL state (it gets surfaced in the final report'sOpen Itemssection).The reviewer may NOT choose "narrow scope" or "pivot" as standalone local actions — those are subsumed by Phase 2's claim rewrite.
- Lightweight in-loop rewrite (preferred when the reviewer has a concrete narrower claim in mind): write the new claim text directly into a
verify_inconclusive — main-experiment-only fix. For each claim id in verify_inconclusive:
- Read this claim's
verify/<claim_dir>/main_experiment_audit/{EXPERIMENT,MECHANISM}_AUDIT.mdto identify which sub-audit FAILed (theROBUSTNESS.mdstub'sinconclusive_reasonfield summarizes this). - Fix the failing main-experiment surface: edit the relevant step in
refine-logs/EXPERIMENT_PLAN.md(preserving the top-of-fileresource_fidelity:marker verbatim — see the Resource-Fidelity invariant above), modify the main-experiment script (experiments/<name>/), then re-invoke/auto-experiment(withtarget_claims: [<id>]so only this claim's runs re-execute, if supported by the upstream skill) followed by/auto-verify. Consumes 1 iteration (type ②). - The reviewer is not allowed to "narrow scope", "pivot", or rewrite the claim for an INCONCLUSIVE — the main-experiment methodology is what's broken, not the claim's framing. To rewrite the claim, the claim must first become FAIL (i.e., the main experiment must be fixed so a robustness verdict is computable).
- Per-claim ② cap (mirror of
MAX_CLAIM_REENTRIESfor ③). A stubborn INCONCLUSIVE main experiment must not monopolise the sharedMAX_ITERATIONSbudget and starve other claims. Count this claim's prior type-② actions initeration_breakdown(no separate counter needed). If it has already had 2 ② attempts and is still INCONCLUSIVE, do not attempt a 3rd: record the reviewer's request, decline it in the iteration writeup, and leave the claim INCONCLUSIVE in the final report'sOpen Items(requires manual main-experiment repair). This frees the remaining budget for other claims; if no other claim has an actionable fix, terminate. (The cap is on iteration-loop ② attempts — at most 2 — not on the original original /auto-experiment run.)
verify_zero_eligible_variants — variant-only fix. For each claim id in verify_zero_eligible_variants:
- Read this claim's
verify/<claim_dir>/variant_audit/{EXPERIMENT,MECHANISM}_AUDIT.mdand thezero_eligible_reasonfield inROBUSTNESS.md. - Fix each failing variant's script (variant
eval.py, mechanism harness, etc.), delete the failed variant directories underverify/<claim_dir>/variants/, then re-invoke/auto-verify <claim-id> — resume: truestandalone (Phase 1's argument parser and Phase 2's main-experiment audit will skip on resume; only variant phases re-execute). Consumes 1 iteration (type ①, same as FAIL Phase 1). - Do NOT invoke
/auto-experiment— the the main experiment already passed Phase 2; rerunning it wastes compute and risks main-experiment drift.
verify_integrity_only — no-action-with-upgrade-suggestion contract. For each claim id in verify_integrity_only the reviewer must NOT propose any back-edge action — main-experiment integrity was validated at Phase 2 (main_experiment_integrity ∈ {pass, warn} in ROBUSTNESS.md) and the swap stress test was intentionally skipped. Read the per-claim stage2_skip_reason field to pick the right upgrade string:
stage2_skip_reason: swap_variants_false(global audit-only mode) → upgrade suggestion:to stress-test under method/dataset/model swaps: /auto-verify <id> — swap-variants: true, resume: true (Phase 2 audits reused via RESUME; only Stages 2–3 run)stage2_skip_reason: max_verify_claims_cap(Stage 1 admitted but not top-K picked) → upgrade suggestion:to stress-test this admitted claim: /auto-verify <id> — resume: true (single-claim mode; Phase 2 audit reused via RESUME; only Stages 2–3 run for this claim)
Three requirements: (a) record each claim under ### Open Items — Unverified Under Swaps in AUTO_REVIEW.md with its stage2_skip_reason and the matching upgrade suggestion; (b) when main_experiment_integrity: warn, surface warn_source on the same line so the paper text can carry the caveat; (c) exclude from score-weighting reasoning — neither a positive nor a negative signal, just unfinished. The iteration agent's final return message surfaces this list to the orchestrator so it lands in CLAIMS_LEDGER.md's open_items[]. Does NOT consume an iteration.
deferred_claims — legacy no-op. New verify runs never populate this bucket (cap-cut claims land in verify_integrity_only with stage2_skip_reason: max_verify_claims_cap). On a legacy VERIFY_REPORT.md that still carries a ## Deferred Claims section, treat each id under the same no-action contract as verify_integrity_only (record under ### Open Items — Unverified Under Swaps with requires standalone /auto-verify <id> before this claim can enter the iteration loop; exclude from score-weighting). Does NOT consume an iteration.
Phase A reviewer prompt — explicit bucket list
When constructing Phase A's reviewer prompt, include all five state buckets explicitly so the reviewer can address them separately. Empty buckets are stated as none, not omitted (the reviewer needs to know "no claims FAILed" is different from "I forgot to tell you"):
verify_passed: [<comma-separated claim ids, or "none">] # brief consistency check only, no experiment action
verify_failed: [<comma-separated claim ids, or "none">] # two-phase routing — see contract
verify_inconclusive: [<comma-separated claim ids, or "none">] # main-experiment-only fix
verify_zero_eligible_variants: [<comma-separated claim ids, or "none">] # variant-only fix
verify_integrity_only: [<comma-separated claim ids, or "none">] # NO back-edge; record in Open Items with stage2_skip_reason-dispatched upgrade suggestion
deferred_claims is a legacy empty bucket in new runs and is not part of the reviewer prompt.
Flexibility note on upstream back-edges
When this loop triggers a back-edge (variant re-run, main-experiment re-run, or claim-stage re-entry), the iteration agent may choose to run a lightweight subset rather than the full upstream skill:
- For
/auto-experiment: only the runs corresponding to the target claim need re-execute. Iftarget_claimsis honored by the upstream skill, pass it; if not, the lightweight path is to manually re-run the specific experiment scripts and update the correspondingruns/iteration_round_<N>/<run-id>/directory. - For
/auto-verify: pass— resume: trueso completed phases skip. (Iteration-loop invocations are always single-claim —/auto-verify <claim-id>— so the claim scoping is already implicit.) - For
/auto-claim: lightweight path is to write the rewritten claim text directly intoAUTO_REVIEW.mdand bypass the/auto-claimcall entirely.
Write results back, every time. A lightweight path skips the owning skill, so the skill's own supersede-in-place never fires — the iteration agent inherits it. Whenever a re-run changes a claim's numbers or verdict, update that stage's documents in place before calling anything downstream: refine-logs/ for a main-experiment re-run, verify/ for a verify re-run, review-stage/ for this loop's own record. Keep a failed pre-registered test on the record as a failure; never relabel it.
Each lightweight or full variant of these actions consumes the same iteration count (and the same claim-reentry sub-budget when applicable). The agent is given the choice for compute efficiency, not for budget gaming.
Initialization
- Run the Pre-flight check in the "Reviewer LLM Configuration" section above. Abort with the hard-fail message if
LLM_MODELcannot be resolved. Log the resolved values and source. - Check
review-stage/REVIEW_STATE.jsonfor recovery (fall back to./REVIEW_STATE.jsonif not found — legacy path) - Resume gate (only when
RESUME = true). Read fields from the schema documented under "State Persistence" above (iterations_consumed,claim_reentries_consumed,status,last_verdict,last_score,timestamp):- If
REVIEW_STATE.jsonis missing or empty → start fresh (iterations_consumed = 0,claim_reentries_consumed = 0), log[resume] no prior state — starting fresh. - Staleness check (first, before any other branch — but exempt
awaiting_upstream): parsetimestampas ISO 8601. Skip this check entirely whenstatus == "awaiting_upstream"— that is a deliberate, live handoff waiting on orchestrator-run upstream calls (a type-③ claim re-entry's/auto-claim → /auto-experiment → /auto-verify, which routinely exceeds 24h on GPU experiments). Ageing it out would discard the just-produced upstream results and reset the inherited budget counters, orphaning live work; the long elapsed time is expected and the upstream calls produced fresh results, so the drift rationale does not apply. For any other status, ifnow - timestamp > 24h(86400 seconds), the prior state is too old to safely resume — log[resume] state older than 24h (timestamp=<ts>, age=<H>h) — treating as stale, ignoring REVIEW_STATE.json and starting fresh, archive the stale file asREVIEW_STATE.<ts>.stale.json(so the audit trail survives — don't delete it), and proceed as ifRESUME=false. Rationale: beyond a day the codebase, results files, and reviewer model versions have likely drifted enough that "picking up from iteration N" would conflate stale context with fresh work — a concern that applies to an interruptedin_progressrun, not to a deliberateawaiting_upstreamhandoff. If thetimestampfield is absent (legacy state files), skip the staleness check and continue to the branches below — the caller accepts the risk. - If
status == "completed"ANDlast_verdictmatches aPOSITIVE_VERDICT_TERMSentry ANDlast_score >= TARGET_SCORE→ log[resume] prior run already satisfied target (score=<last_score>, verdict=<last_verdict>) — returning without invoking reviewerand return immediately with the existing state. (Termination should already have producedFINAL_REPORT_DOCin this case; if missing, re-run Termination's report-assembly step before returning.) - Else if
status == "completed"ANDiterations_consumed >= MAX_ITERATIONS→ log[resume] prior run exhausted MAX_ITERATIONS (iterations_consumed=<n>) — returning without invoking reviewerand return. - Else if
status == "completed"ANDclaim_reentries_consumed >= MAX_CLAIM_REENTRIESAND the only remaining unresolved claims would require action type ③ → log[resume] prior run exhausted MAX_CLAIM_REENTRIES — returning without invoking reviewerand return. - Else if
status == "awaiting_upstream"→ the orchestrator should have runpending_upstream_callsbefore resuming. Verify they completed (all expected output artifacts present on disk per the upstream skills' own contracts). If they did, clearpending_upstream_calls, flipstatusto"in_progress", and continue into Phase A of the next iteration withiterations_consumedandclaim_reentries_consumedalready incremented (Phase C of the prior iteration incremented them when it queued the upstream calls). If they did not, abort with[resume] awaiting_upstream but pending_upstream_calls outputs missing — orchestrator did not complete upstream work; aborting. Do not silently retry — the orchestrator failure must be visible. - Else if
status == "in_progress"ANDiterations_consumed >= MAX_ITERATIONS→ no iterations left; flipstatusto"completed", log[resume] in-progress at MAX_ITERATIONS — closing out without new iteration, run Termination, and return. - Otherwise (
status == "in_progress"ANDiterations_consumed < MAX_ITERATIONS) → the next iteration's display number isiterations_consumed + 1(this is a working value for logs / Phase E heading only — it is never persisted; the only persistent counter isiterations_consumed, which Phase C increments at its end after a real ①/②/③ action). OpenAUTO_REVIEW.mdin append mode and continue. Log[resume] picking up from iteration <iterations_consumed+1>/<MAX_ITERATIONS> (claim-reentry sub-budget: <claim_reentries_consumed>/<MAX_CLAIM_REENTRIES>).
- If
- Read project context and prior reviews
- Initialize iteration counter (skip if step 2 already set it)
Loop (up to MAX_ITERATIONS)
Each cycle below is one Phase A→E iteration. The iteration counter (iterations_consumed) is incremented at the end of Phase C, only if Phase C actually queued or executed a back-edge action of type ① / ② / ③. A pure-review iteration whose only action is type ⓪ (narrative-only) or has no action at all does not consume budget. Stall guard against ⓪-only spin. Because ⓪ is free (it does not consume budget and runs no experiment), a reviewer that keeps proposing narrative-only fixes for a still-unresolved weakness would loop forever — burning a reviewer call each round while neither the STOP rule (score below target) nor the budget exit (iterations_consumed frozen) ever fires. The consecutive_noop_count field in REVIEW_STATE.json closes this: at Phase E, increment it when this iteration was ⓪-only (or had no action) and last_score + last_verdict are unchanged from the prior persisted state; reset it to 0 whenever a real ①/②/③ action ran or the score/verdict changed. When it reaches 2 (two consecutive non-converging no-op iterations), the loop is stalled on the reviewer side — terminate it (see Phase B's STOP conditions). The common legitimate case for a ⓪-only iteration is the final one that confirms a positive verdict after a real ①/②/③ in the prior iteration — that single ⓪ is fine; only an unchanged-score repeat trips the guard.
Phase A: Review
Iteration 1 uses the prompt shape below (no Reviewer Memory yet — file is created at the end of iteration 1 by Phase B.5). The budget line at the top of the prompt reads 0/<MAX> used for both budgets on iteration 1; that is correct and expected, not an error.
Iteration 2+ prepends the contents of REVIEWER_MEMORY_DOC plus a "Previous Review Summary" block before the body — see "Prompt Template for Iteration 2+" near the end of this file for the full shape. The memory block is mandatory from iteration 2 onward and is what gives this loop adversarial continuity across iterations.
The reviewer prompt must include the five state buckets explicitly per the contract in "Build five disjoint claim buckets" above. The reviewer is told which routing options are available per bucket and which are forbidden (e.g., no claim rewrite for INCONCLUSIVE; no action for INTEGRITY_ONLY).
If MCP available:
mcp__llm-chat__chat:
system: "You are a senior ML reviewer (NeurIPS/ICML level)."
prompt: |
[Iteration N/MAX_ITERATIONS of autonomous review loop]
[Iteration budget: <iterations_consumed>/<MAX_ITERATIONS> used; claim-reentry sub-budget: <claim_reentries_consumed>/<MAX_CLAIM_REENTRIES> used]
[Full research context: claims, methods, results, known weaknesses]
[Changes since last iteration, if any]
## Per-claim verify state (from /auto-verify)
verify_passed: [<ids or "none">] # brief consistency check only
verify_failed: [<ids or "none">] # two-phase: variant-integrity check, then optional claim rewrite
verify_inconclusive: [<ids or "none">] # main-experiment-only fix; you may NOT rewrite the claim
verify_zero_eligible_variants: [<ids or "none">] # variant-only fix; do NOT touch the main experiment
verify_integrity_only: [<ids or "none">] # NO back-edge action; record in Open Items with stage2_skip_reason-dispatched upgrade suggestion
Tasks:
1. Score this work 1-10 for a top venue.
2. For each FAIL / INCONCLUSIVE / ZERO_ELIGIBLE_VARIANTS claim, name the MINIMUM
fix and tag it with the routing type (①/②/③). If you want to use ③ (claim rewrite)
on a FAIL claim, also provide the proposed new claim text — verify that the
claim-reentry sub-budget has remaining capacity before recommending ③.
Do NOT propose fixes or ①/②/③ actions for INTEGRITY_ONLY claims — those are
swap-test-skipped outputs (audit passed) with no robustness data to fix; iteration
records them as unfinished stress-tests, not as findings that need repair.
3. For each PASS claim, do a brief narrative + numeric consistency check.
Flag any caveat (main-experiment-WARN) that should surface in the final paper.
4. State clearly: is this READY for submission? Yes/No/Almost. READY requires all
FAIL / INCONCLUSIVE / ZERO_ELIGIBLE_VARIANTS claims to be resolved.
INTEGRITY_ONLY claims do NOT block READY (they are outside the stress-test
scope by policy or cap) — but note their presence as a caveat in the assessment.
5. (Iteration 1 only) Record any initial suspicions or concerns you want to
track in future iterations — output as a `## Memory update` section so
Phase B.5 can seed `REVIEWER_MEMORY.md` with them.
Be brutally honest. If the work is ready, say so clearly.
Iteration 1 — save thread_id. When the MCP call returns, capture the response's thread_id (or equivalent conversation handle the MCP server uses) and persist it to REVIEW_STATE.json's thread_id field. This is what iteration 2+ uses to continue the same reviewer thread instead of pasting summaries. If the MCP server does not return a thread handle (some configurations don't), or the call used the curl fallback, write null and let iteration 2+ fall back to the paste-summary path.
Iteration 2+ — pass thread_id back when present. If REVIEW_STATE.json has a non-null thread_id, pass it as the thread_id arg on the mcp__llm-chat__chat call. The iteration-2+ prompt body becomes shorter (no need to re-paste prior reviews — the thread already has them), but the Reviewer Memory prepend block is still mandatory (it carries the structured suspicion account that the thread itself doesn't make queryable). On any error indicating the thread is unknown / expired (server returns 4xx with a thread-related message), clear thread_id to null in state and retry once without it — falling back to the paste-summary template at the end of this file.
If MCP NOT available:
curl -s "${LLM_BASE_URL}/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${LLM_API_KEY}" \
-d '{
"model": "${LLM_MODEL}",
"messages": [
{"role": "system", "content": "You are a senior ML reviewer (NeurIPS/ICML level)."},
{"role": "user", "content": "[Full review prompt with all four buckets explicit]"}
],
"max_tokens": 4096
}'
Phase B: Parse Assessment
CRITICAL: Save the FULL raw response verbatim. Then ex
…(truncated)