PR 1 of the state-machine review agreed after the first live run.
The rule:PLANNING_ARCH, MILESTONE_PLANNING and EXECUTING are only right while work is running. When a job ends, however it ends, the agent must be in a state that can wait for the operator, and some reply must lead on from there. The dead ends found live (#57, #63) broke this. So did every unexpected exception: the worker only prints them (worker.py), leaving the state wherever it was.
Changes (nisse-dev/src/nisse_dev/control.py)
_settled() wraps every job run for an operator message or a Gitea comment. If a working state is left behind, whether from an exception or a path that never transitioned, it moves to AWAIT_INPUT with the continue / re-plan choice from #57 and names the reason. An exception in a waiting state is reported (could not handle …; still <state>) and the state kept, since that state's commands still work.
A sign-off state without its gate record asks to plan again, rather than crashing on every reply.
Stub configurations (no executor, or no publisher) complete the milestone and wait in PR_OPEN, so they no longer rest in EXECUTING. Three test_control cases that expected the old resting state are updated.
Invariant test (nisse-dev/tests/test_no_stranding.py): a whole two-milestone project, with one fault injected per run at a planner, executor or publisher call. The faults are unexpected exceptions, planning errors, questions, each executor outcome, and Gitea outages. After every message the agent must not be in a working state, and the project must still reach project complete, answering continue when asked. Plus: the missing gate, a failing review response (PR_OPEN kept and reported), and the stub flow. 9 of the 15 fail on main; the other 6 are faults main already handled, kept as regression cover.
449 unit tests and the orchestrator integration tests (gate restart, crash durability, entry points) pass; lint and format clean.
Next in the review: PR 2, PR_OPEN handling (review failures return to PR_OPEN, restart in PR_OPEN, pollers on boot: #58, #59). PR 3, an abandon command.
PR 1 of the state-machine review agreed after the first live run.
**The rule:** `PLANNING_ARCH`, `MILESTONE_PLANNING` and `EXECUTING` are only right while work is running. When a job ends, however it ends, the agent must be in a state that can wait for the operator, and some reply must lead on from there. The dead ends found live (#57, #63) broke this. So did every unexpected exception: the worker only prints them (`worker.py`), leaving the state wherever it was.
**Changes** (`nisse-dev/src/nisse_dev/control.py`)
- **`_settled()`** wraps every job run for an operator message or a Gitea comment. If a working state is left behind, whether from an exception or a path that never transitioned, it moves to `AWAIT_INPUT` with the `continue` / `re-plan` choice from #57 and names the reason. An exception in a waiting state is reported (`could not handle …; still <state>`) and the state kept, since that state's commands still work.
- **A sign-off state without its gate record** asks to plan again, rather than crashing on every reply.
- **Stub configurations** (no executor, or no publisher) complete the milestone and wait in `PR_OPEN`, so they no longer rest in `EXECUTING`. Three `test_control` cases that expected the old resting state are updated.
**Invariant test** (`nisse-dev/tests/test_no_stranding.py`): a whole two-milestone project, with one fault injected per run at a planner, executor or publisher call. The faults are unexpected exceptions, planning errors, questions, each executor outcome, and Gitea outages. After every message the agent must not be in a working state, and the project must still reach `project complete`, answering `continue` when asked. Plus: the missing gate, a failing review response (`PR_OPEN` kept and reported), and the stub flow. 9 of the 15 fail on `main`; the other 6 are faults `main` already handled, kept as regression cover.
449 unit tests and the orchestrator integration tests (gate restart, crash durability, entry points) pass; lint and format clean.
**Next in the review:** PR 2, `PR_OPEN` handling (review failures return to `PR_OPEN`, restart in `PR_OPEN`, pollers on boot: #58, #59). PR 3, an `abandon` command.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
The first live run found four dead ends one at a time: states where no
operator message could move the agent. Most had one shape: work ended,
by an exception or by a path that never transitioned, and left the agent
in PLANNING_* or EXECUTING, where every message is answered "busy".
The worker only prints an exception, so that would keep happening.
- Every job the orchestrator runs for an operator message or a Gitea
comment now goes through _settled(). If the job leaves a working state
behind, whatever the reason, the agent moves to AWAIT_INPUT with the
continue / re-plan choice a restart offers, and says why. An exception
in a waiting state is reported and the state kept, since its commands
still lead on.
- A sign-off state whose gate record is missing asks to plan again
instead of crashing on every reply.
- The stub configurations (no executor, no publisher) complete the
milestone and wait in PR_OPEN instead of resting in EXECUTING.
test_no_stranding.py drives a whole two-milestone project with a fault
injected at each planner, executor and publisher call, and asserts after
every message that the agent is not in a working state and that the
project still reaches 'project complete'.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
It drove the orchestrator until EXECUTING and accepted EXECUTING as the
end state, which with no publisher configured was the stranding this
branch removes: the milestone now finishes in PR_OPEN. Return once
'start milestone 1' is sent, since the milestone runs inside it, and
expect PR_OPEN or AWAIT_INPUT.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
PR 1 of the state-machine review agreed after the first live run.
The rule:
PLANNING_ARCH,MILESTONE_PLANNINGandEXECUTINGare only right while work is running. When a job ends, however it ends, the agent must be in a state that can wait for the operator, and some reply must lead on from there. The dead ends found live (#57, #63) broke this. So did every unexpected exception: the worker only prints them (worker.py), leaving the state wherever it was.Changes (
nisse-dev/src/nisse_dev/control.py)_settled()wraps every job run for an operator message or a Gitea comment. If a working state is left behind, whether from an exception or a path that never transitioned, it moves toAWAIT_INPUTwith thecontinue/re-planchoice from #57 and names the reason. An exception in a waiting state is reported (could not handle …; still <state>) and the state kept, since that state's commands still work.PR_OPEN, so they no longer rest inEXECUTING. Threetest_controlcases that expected the old resting state are updated.Invariant test (
nisse-dev/tests/test_no_stranding.py): a whole two-milestone project, with one fault injected per run at a planner, executor or publisher call. The faults are unexpected exceptions, planning errors, questions, each executor outcome, and Gitea outages. After every message the agent must not be in a working state, and the project must still reachproject complete, answeringcontinuewhen asked. Plus: the missing gate, a failing review response (PR_OPENkept and reported), and the stub flow. 9 of the 15 fail onmain; the other 6 are faultsmainalready handled, kept as regression cover.449 unit tests and the orchestrator integration tests (gate restart, crash durability, entry points) pass; lint and format clean.
Next in the review: PR 2,
PR_OPENhandling (review failures return toPR_OPEN, restart inPR_OPEN, pollers on boot: #58, #59). PR 3, anabandoncommand.🤖 Generated with Claude Code
https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
New commits pushed, approval review dismissed automatically according to repository settings