M7.8 (L2): a driver run that loops now ends early, escalated, instead of running to its turn cap. Plan agreed 2026-10-26, with a helper first for nisse-dev.
When a run ends as circling
Driver.run checks each turn with nisse_core.loop.circles, the rules the status page already flags with. A run ends when, for circle_repeats turns in a row (default 3):
the same tool calls get much the same back. The calls must be identical, and results and text alike at circle_similar (0.9). A review's "noted (2 in the draft)" counter still counts as the same.
one harness correction is given that often ("no such tool: X", a check's problem, a refusal), in a row or not.
the model apologises turn after turn. That covers Flash-Next's numbered "please disregard (n)" comments.
The ending writes an orchestrator fact into the transcript saying why, and RunOutcome.note carries what it was doing.
Changes from the plan, found by tests:
Near-identical calls only flag on the page. git add a.py, b.py, c.py are alike and each gets the same empty answer, but they're progress. The same goes for sibling files with near-identical contents.
Because of that, apologies in a row end a run; scattered ones stay page-only.
Agents
nisse-dev: treated like stuck. A helper takes over once per milestone; if it circles again, the item waits on the operator with "I stopped short: …".
nisse-review: a failed run that counts toward attempts. Its drafts aren't posted, since they're the repeats.
Benchmark and toolchain step: they already treat anything but a wait as unfinished; a circling task now fails at turn 3 instead of at the cap.
Tuning
tuning.driver.circle_repeats / circle_similar (nisse-dev, also used by the benchmark) and tuning.review.* (nisse-review). 0 turns it off; 1 is refused, and so is a similarity above 1. Documented in docs/Tuning-Guide.md and both example YAMLs.
Tests
Core:
the Flash-Next pattern ends at the third post;
an invented tool ends on the repeated correction;
the same read repeated ends the run;
sibling files, git add of several files and scattered apologies don't end it;
it can be turned off and tuned;
circles: the exact-calls mode, apologies in a row, a counter in the results.
nisse-dev: a helper on circling, then the operator; config checks.
nisse-review: a circling run is a failed attempt with nothing posted, ending at turn 3; config checks.
Three existing tests that used one repeated call to test a turn cap now vary their calls or turn circles off.
Mutation checks (8), all caught: identical-calls check dropped, exact results dropped on the page side, apologies counted anywhere, corrections not recorded, scattered apologies ending runs, turning off ignored, nisse-dev's helper trigger dropped, circle_repeats: 1 allowed.
Deferred
The helper's own loop (delegation.py) keeps its turn, token and wall-clock caps.
M7.8 (L2): a driver run that loops now ends early, escalated, instead of running to its turn cap. Plan agreed 2026-10-26, with a helper first for nisse-dev.
## When a run ends as `circling`
`Driver.run` checks each turn with `nisse_core.loop.circles`, the rules the status page already flags with. A run ends when, for `circle_repeats` turns in a row (default 3):
- **the same tool calls get much the same back.** The calls must be identical, and results and text alike at `circle_similar` (0.9). A review's "noted (2 in the draft)" counter still counts as the same.
- **one harness correction is given that often** ("no such tool: X", a check's problem, a refusal), in a row or not.
- **the model apologises turn after turn.** That covers Flash-Next's numbered "please disregard (n)" comments.
The ending writes an orchestrator fact into the transcript saying why, and `RunOutcome.note` carries what it was doing.
**Changes from the plan, found by tests:**
- Near-identical calls only flag on the page. `git add a.py`, `b.py`, `c.py` are alike and each gets the same empty answer, but they're progress. The same goes for sibling files with near-identical contents.
- Because of that, apologies in a row end a run; scattered ones stay page-only.
## Agents
- **nisse-dev:** treated like `stuck`. A helper takes over once per milestone; if it circles again, the item waits on the operator with "I stopped short: …".
- **nisse-review:** a failed run that counts toward `attempts`. Its drafts aren't posted, since they're the repeats.
- **Benchmark and toolchain step:** they already treat anything but a wait as unfinished; a circling task now fails at turn 3 instead of at the cap.
## Tuning
`tuning.driver.circle_repeats` / `circle_similar` (nisse-dev, also used by the benchmark) and `tuning.review.*` (nisse-review). 0 turns it off; 1 is refused, and so is a similarity above 1. Documented in docs/Tuning-Guide.md and both example YAMLs.
## Tests
- **Core:**
- the Flash-Next pattern ends at the third post;
- an invented tool ends on the repeated correction;
- the same read repeated ends the run;
- sibling files, `git add` of several files and scattered apologies don't end it;
- it can be turned off and tuned;
- circles: the exact-calls mode, apologies in a row, a counter in the results.
- **nisse-dev:** a helper on circling, then the operator; config checks.
- **nisse-review:** a circling run is a failed attempt with nothing posted, ending at turn 3; config checks.
- Three existing tests that used one repeated call to test a turn cap now vary their calls or turn circles off.
- Mutation checks (8), all caught: identical-calls check dropped, exact results dropped on the page side, apologies counted anywhere, corrections not recorded, scattered apologies ending runs, turning off ignored, nisse-dev's helper trigger dropped, `circle_repeats: 1` allowed.
## Deferred
- The helper's own loop (`delegation.py`) keeps its turn, token and wall-clock caps.
- The page could use tool results in its flags too.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
Driver.run checks each turn with nisse_core.loop.circles and ends the run
as "circling" when the same calls get much the same back, one harness
correction is given again and again, or the model apologises, for
circle_repeats turns in a row (default 3). Near-identical calls (git add
a.py, b.py) and scattered apologies stay flags on the status page. A
fact in the transcript says why the run ended.
nisse-dev calls in a helper as for a stuck run, then asks the operator;
nisse-review counts a failed run and posts none of its drafts. Tuning:
tuning.driver / tuning.review circle_repeats (0 off) and circle_similar.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
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.
M7.8 (L2): a driver run that loops now ends early, escalated, instead of running to its turn cap. Plan agreed 2026-10-26, with a helper first for nisse-dev.
When a run ends as
circlingDriver.runchecks each turn withnisse_core.loop.circles, the rules the status page already flags with. A run ends when, forcircle_repeatsturns in a row (default 3):circle_similar(0.9). A review's "noted (2 in the draft)" counter still counts as the same.The ending writes an orchestrator fact into the transcript saying why, and
RunOutcome.notecarries what it was doing.Changes from the plan, found by tests:
git add a.py,b.py,c.pyare alike and each gets the same empty answer, but they're progress. The same goes for sibling files with near-identical contents.Agents
stuck. A helper takes over once per milestone; if it circles again, the item waits on the operator with "I stopped short: …".attempts. Its drafts aren't posted, since they're the repeats.Tuning
tuning.driver.circle_repeats/circle_similar(nisse-dev, also used by the benchmark) andtuning.review.*(nisse-review). 0 turns it off; 1 is refused, and so is a similarity above 1. Documented in docs/Tuning-Guide.md and both example YAMLs.Tests
git addof several files and scattered apologies don't end it;circle_repeats: 1allowed.Deferred
delegation.py) keeps its turn, token and wall-clock caps.🤖 Generated with Claude Code
https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk