Skip to content

state.json.iteration off-by-one during iter-1 (reads 0 throughout) #194

@sriumcp

Description

@sriumcp

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

  1. nous status --line shows iter 0 while artifacts are clearly in iter-1/. Operators distrust the status output.

  2. 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.

  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions