Problem
state.json.iteration reports 0 throughout the entire first iteration:
{ "phase": "DESIGN", "iteration": 0, ... } // iter-1's DESIGN
{ "phase": "HUMAN_DESIGN_GATE", "iteration": 0, ... } // iter-1's gate
{ "phase": "EXECUTE_ANALYZE", "iteration": 0, ... } // iter-1's EXECUTE
But the orchestrator calls run_iteration(iteration=1) from the start, and writes artifacts at runs/iter-1/. So state's iteration counter and the run's iter-N path numbering disagree throughout the first iteration. The counter only advances to 1 on the DONE → DESIGN(iter+1) transition — which makes it look correct from iter-2 onward, but iter-1 always shows 0.
User-visible breakage
-
nous status --line shows iter 0 while artifacts are clearly in iter-1/. Operators distrust the status output.
-
nous resume warns state.json has iteration=0 (< 1); starting fresh every time someone resumes a campaign that was killed during iter-1. The warning is alarming (suggests data loss) but the resume actually preserves the existing artifacts and continues correctly. Misleading.
-
Off-by-one breaks any external tool reading state.json.iteration. The TUI, the nous status --watch panel, the meta-findings log path — all see the wrong number for iter-1.
Repro
# Wipe state, run a campaign for one iteration, observe state.json mid-run
rm -rf <repo>/.nous/<run>
nous run <campaign> --max-iterations 1 --auto-approve
# In another shell, while DESIGN is running:
cat <repo>/.nous/<run>/state.json # shows "iteration": 0
nous status <repo>/.nous/<run> --line # shows "iter 0"
ls <repo>/.nous/<run>/runs/ # shows "iter-1/"
Root cause
orchestrator/engine.py increments state.iteration only on DONE → DESIGN transitions, never on INIT → DESIGN. The fix is one of:
- (A) Increment on
INIT → DESIGN so iter-1 reads iteration=1 from the start.
- (B) Make
run_iteration(iteration=N) write state.iteration=N directly when it claims iter-N. The engine's transition logic stays in lockstep with the artifact paths.
Option B is more robust: the iteration parameter passed to run_iteration is the source of truth. The engine should reflect it, not maintain a parallel counter.
Files to touch
orchestrator/engine.py — write iteration into state on DESIGN entry.
orchestrator/iteration.py — at the top of run_iteration, ensure engine state matches the iteration parameter.
orchestrator/campaign.py — drop the "iteration=0 (< 1); starting fresh" warning OR rephrase it to "no prior iteration progress; starting at iter-1" once the off-by-one is fixed.
tests/test_engine.py — assert state.iteration matches runs/iter-N path on every transition.
Discovered in
paper-burst friction-test, 2026-05-26.
Problem
state.json.iterationreports0throughout the entire first iteration:{ "phase": "DESIGN", "iteration": 0, ... } // iter-1's DESIGN { "phase": "HUMAN_DESIGN_GATE", "iteration": 0, ... } // iter-1's gate { "phase": "EXECUTE_ANALYZE", "iteration": 0, ... } // iter-1's EXECUTEBut the orchestrator calls
run_iteration(iteration=1)from the start, and writes artifacts atruns/iter-1/. So state's iteration counter and the run's iter-N path numbering disagree throughout the first iteration. The counter only advances to 1 on theDONE → DESIGN(iter+1)transition — which makes it look correct from iter-2 onward, but iter-1 always shows 0.User-visible breakage
nous status --lineshowsiter 0while artifacts are clearly initer-1/. Operators distrust the status output.nous resumewarnsstate.json has iteration=0 (< 1); starting freshevery time someone resumes a campaign that was killed during iter-1. The warning is alarming (suggests data loss) but the resume actually preserves the existing artifacts and continues correctly. Misleading.Off-by-one breaks any external tool reading
state.json.iteration. The TUI, thenous status --watchpanel, the meta-findings log path — all see the wrong number for iter-1.Repro
Root cause
orchestrator/engine.pyincrementsstate.iterationonly onDONE → DESIGNtransitions, never onINIT → DESIGN. The fix is one of:INIT → DESIGNso iter-1 readsiteration=1from the start.run_iteration(iteration=N)writestate.iteration=Ndirectly when it claims iter-N. The engine's transition logic stays in lockstep with the artifact paths.Option B is more robust: the iteration parameter passed to
run_iterationis the source of truth. The engine should reflect it, not maintain a parallel counter.Files to touch
orchestrator/engine.py— write iteration into state on DESIGN entry.orchestrator/iteration.py— at the top ofrun_iteration, ensure engine state matches the iteration parameter.orchestrator/campaign.py— drop the "iteration=0 (< 1); starting fresh" warning OR rephrase it to "no prior iteration progress; starting at iter-1" once the off-by-one is fixed.tests/test_engine.py— assert state.iteration matches runs/iter-N path on every transition.Discovered in
paper-burst friction-test, 2026-05-26.