M2.8: switch over to the new loop
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m16s
CI / e2e (pull_request) Successful in 10m51s

nisse-dev now runs only the conversational loop. The gated state machine
it was built alongside is gone:

- Removed control.py, planner.py, executor.py, approvals.py, gates.py
  (command_form moves to loop/reader.py), publish.py and poller.py, the
  old assembly in agent.py (its shared helpers move to loop/build.py),
  the store's state-machine view (get_state, transition), their tests,
  the old end-to-end and live planning/milestone/gate tests, and
  specs/orchestrator.qnt with its scenarios.
- Roles are driver, helper, expert and classifier; driver is required.
  loop:, roles.planner, roles.executor and tuning.executor are refused
  with what to use instead; tuning.driver takes the turn cap and failure
  limit.
- The benchmark and the capability matrix measure the driver: the
  loop's own system prompt and tools, the task's plan as the agreed
  restatement. Task plans end with finish_milestone, so earlier runs
  show as (old); stored runs' executor tuning reads as the driver's.
- A delegation on the loop records what its helper's alias resolved to
  (L9), as the old path's did.
- The dump shows a work item's revision and what it waits for.
- Behaviour the old tests covered and the loop shares stays covered:
  driver runs that fail on a model call, stop mid-turn, reset the
  failure count; go-aheads ignoring case and punctuation; a request too
  big to fit is never sent; the assembly tests, now on build_loop_agent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-08 03:12:58 +00:00
1 parent 09232dcbdd
commit 6ae2375194
89 files changed
+951 -8592

No files matched your search

+11 -14
View File
@@ -37,7 +37,6 @@ from nisse_dev.canonical import (
from nisse_dev.config import GENERAL
from nisse_dev.delegation import DELEGATION_INTENT, Delegation
from nisse_dev.expert import BudgetExhausted, ExpertDesk
from nisse_dev.gates import command_form
from nisse_dev.git import GitError
from nisse_dev.gitea import GiteaClient, GiteaError, Issue
from nisse_dev.llm import LLMError, RoleMap
@@ -61,7 +60,7 @@ from nisse_dev.loop.items import (
save,
)
from nisse_dev.loop.milestones import OPEN_PR, Milestones
from nisse_dev.loop.reader import GoAheadReader, is_question
from nisse_dev.loop.reader import GoAheadReader, command_form, is_question
from nisse_dev.loop.tools import (
ASK_HUMAN,
CLOSE_ITEM,
@@ -75,7 +74,7 @@ from nisse_dev.loop.tools import (
)
from nisse_dev.oplog import OpLog
from nisse_dev.state import StateStore, WorkItem
from nisse_dev.tools import EXECUTOR_TOOLS, Workspace
from nisse_dev.tools import EXECUTION_TOOLS, Workspace
from nisse_dev.transport import InboundMessage, Transport
from nisse_dev.worker import Worker
@@ -113,7 +112,7 @@ _MARKER = re.compile(r"<!-- nisse-item: ([A-Za-z0-9-]+) -->")
# Before the go-ahead the driver may look around (read, list, run a command
# to reproduce a bug) but not change anything (D1); after it, everything.
_LOOK = [t for t in EXECUTOR_TOOLS if t["function"]["name"] != "write_file"]
_LOOK = [t for t in EXECUTION_TOOLS if t["function"]["name"] != "write_file"]
SYSTEM = """You are nisse-dev, a software agent working with the operator on
the repository {repo}. You are on one work item: issue #{issue}, "{title}".
@@ -1411,12 +1410,7 @@ class LoopAgent:
default = (
self._helpers.roles
if self._helpers
else RoleMap(
executor="",
planner=self._roles.get("planner", ""),
helper="",
expert="",
)
else RoleMap(driver=self._roles.get("driver", ""), helper="", expert="")
)
pinned = item.data.get("roles") or self._roles
return RoleMap.from_pinned(dict(pinned), default)
@@ -1454,6 +1448,9 @@ class LoopAgent:
workspace,
milestones.git,
model=helper,
backing_model=(
self._helpers.backing(helper) if self._helpers.backing else ""
),
caps=self._helpers.caps,
cancel=self._cancel,
reader=self._helpers.reader(roles),
@@ -1737,7 +1734,7 @@ class LoopAgent:
*([WAIT_FOR_REVIEW] if loop.pr else []),
*([DELEGATE, ASK_EXPERT] if self._helpers else []),
*([REPLY] if loop.back_to else []),
*EXECUTOR_TOOLS,
*EXECUTION_TOOLS,
]
),
]
@@ -1826,7 +1823,7 @@ class LoopAgent:
text = str(args.get("text", "")).strip()
rev = loop.rev + 1
self._store.transcript(item.id, "driver").append(
PlanSubmitted(plan=text, model=self._roles.get("planner", ""))
PlanSubmitted(plan=text, model=self._roles.get("driver", ""))
)
self._fact(item, f"restatement rev-{rev} put up for a go-ahead")
self._shown[loop.thread] = rev
@@ -1869,7 +1866,7 @@ class LoopAgent:
summary = str(args.get("summary", "")).strip()
rev = loop.rev + 1
self._store.transcript(item.id, "driver").append(
PlanSubmitted(plan=summary, model=self._roles.get("planner", ""))
PlanSubmitted(plan=summary, model=self._roles.get("driver", ""))
)
self._fact(item, f"work proposed as rev-{rev}: {project.repo}, {title}")
self._shown[loop.thread] = rev
@@ -1899,7 +1896,7 @@ class LoopAgent:
brief = f"{purpose}\n\nDecisions:\n{decisions}" if decisions else purpose
rev = loop.rev + 1
self._store.transcript(item.id, "driver").append(
PlanSubmitted(plan=brief, model=self._roles.get("planner", ""))
PlanSubmitted(plan=brief, model=self._roles.get("driver", ""))
)
self._fact(item, f"new project proposed as rev-{rev}: {name}")
self._shown[loop.thread] = rev