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

+33 -25
View File
@@ -22,7 +22,14 @@ from pathlib import Path
from typing import Any
import pytest
from e2e_support import dependent_task, e2e_roles, fresh_repo, operator_gitea, task
from e2e_support import (
build_host,
dependent_task,
e2e_roles,
fresh_repo,
operator_gitea,
task,
)
from nisse_dev.config import AgentConfig, LoopTuning
from nisse_dev.execution import SSHHost
@@ -54,8 +61,6 @@ def running(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> Iterator[tuple[RunningLoop, InMemoryTransport, str]]:
from test_end_to_end import build_host
run = uuid.uuid4().hex[:8]
stack = contextlib.ExitStack()
# Kept outside pytest's temp directories, which pytest prunes, so a
@@ -66,15 +71,18 @@ def running(
operator = operator_gitea(live_config.gitea_url)
account = GiteaClient(live_config.gitea_url, live_config.gitea_token).whoami()
fresh = stack.enter_context(fresh_repo(operator, run, keep / "keys", agent=account))
[project] = live_config.projects
project = replace(
project,
repo=fresh.name,
base_branch="main",
push_key=fresh.push_key,
workspace=Path(f"/tmp/nisse-e2e-{run}/sandbox"),
build_host=build_host(ssh_host),
)
config = replace(
live_config.with_project(
repo=fresh.name,
base_branch="main",
push_key=fresh.push_key,
workspace=Path(f"/tmp/nisse-e2e-{run}/sandbox"),
build_host=build_host(ssh_host),
),
loop="new",
live_config,
projects=(project,),
roles=e2e_roles(),
state_db=keep / "state.db",
# Short, so a bug's grace period runs out within the test.
@@ -97,10 +105,10 @@ def running(
if agent is not None:
agent.stop()
host = SSHHost(
config.build_host.address,
key=config.build_host.key,
user=config.build_host.user,
port=config.build_host.port,
project.build_host.address,
key=project.build_host.key,
user=project.build_host.user,
port=project.build_host.port,
state_dir=tmp_path / "ssh",
)
host.execute(f"rm -rf /tmp/nisse-e2e-{run}")
@@ -197,7 +205,7 @@ class Operator:
def merge_and_close(self, issue: int) -> None:
"""Approve and merge the item's PR, and see the agent close the item."""
gitea, repo = self.agent.gitea, self.agent.config.gitea_repo
gitea, repo = self.agent.gitea, self.agent.config.projects[0].repo
as_operator = operator_gitea(self.agent.config.gitea_url)
pr = loop_of(self.item(issue)).pr
with contextlib.suppress(GiteaError): # approval may not be required
@@ -222,7 +230,7 @@ class Operator:
def tidy(self, issues: list[int], prs: list[int]) -> None:
"""Leave the sandbox tidy: whatever is still open gets closed."""
gitea, repo = self.agent.gitea, self.agent.config.gitea_repo
gitea, repo = self.agent.gitea, self.agent.config.projects[0].repo
as_operator = operator_gitea(self.agent.config.gitea_url)
for issue in issues:
item = self.item(issue)
@@ -245,7 +253,7 @@ def test_the_slice_end_to_end(
) -> None:
agent, transport, run = running
operator = Operator(agent, transport)
gitea, repo = agent.gitea, agent.config.gitea_repo
gitea, repo = agent.gitea, agent.config.projects[0].repo
as_operator = operator_gitea(agent.config.gitea_url)
issue = None
pr = 0
@@ -297,7 +305,7 @@ def test_an_issue_blocked_on_another_waits_for_it(
closes; then B starts from a workspace that has A's work."""
agent, transport, run = running
operator = Operator(agent, transport)
gitea, repo = agent.gitea, agent.config.gitea_repo
gitea, repo = agent.gitea, agent.config.projects[0].repo
as_operator = operator_gitea(agent.config.gitea_url)
owner, name = repo.split("/")
issues: list[int] = []
@@ -360,7 +368,7 @@ def test_a_bug_goes_ahead_after_its_grace_period(
the grace period runs out, with no go from the operator."""
agent, transport, run = running
operator = Operator(agent, transport)
repo, base = agent.config.gitea_repo, agent.config.base_branch
repo, base = agent.config.projects[0].repo, agent.config.projects[0].base_branch
as_operator = operator_gitea(agent.config.gitea_url)
issues: list[int] = []
prs: list[int] = []
@@ -412,7 +420,7 @@ def test_a_plain_yes_is_a_go_ahead(
and the agent says so before acting."""
agent, transport, run = running
operator = Operator(agent, transport)
repo = agent.config.gitea_repo
repo = agent.config.projects[0].repo
as_operator = operator_gitea(agent.config.gitea_url)
issues: list[int] = []
prs: list[int] = []
@@ -454,7 +462,7 @@ def test_a_two_milestone_feature(
it; the item closes after both merge."""
agent, transport, run = running
operator = Operator(agent, transport)
gitea, repo = agent.gitea, agent.config.gitea_repo
gitea, repo = agent.gitea, agent.config.projects[0].repo
as_operator = operator_gitea(agent.config.gitea_url)
issues: list[int] = []
prs: list[int] = []
@@ -510,7 +518,7 @@ def test_a_conversation_becomes_filed_work(
is proposed, agreed, filed as a record issue, and started as agreed."""
agent, transport, run = running
operator = Operator(agent, transport)
gitea, repo = agent.gitea, agent.config.gitea_repo
gitea, repo = agent.gitea, agent.config.projects[0].repo
issues: list[int] = []
prs: list[int] = []
@@ -602,7 +610,7 @@ def test_a_conversation_becomes_a_new_project(
with fresh_repo(
as_operator, f"{run}-new", keep / "keys", agent=agent.account, seed=False
) as repo:
host = agent.config.build_host
host = agent.config.projects[0].build_host
filled = (
entry.group(1)
.replace(f"<owner>/{name}", repo.name)
@@ -646,7 +654,7 @@ def test_a_question_leaves_the_restatement_up(
nothing: the same revision stays up, and a go binds to it."""
agent, transport, run = running
operator = Operator(agent, transport)
repo = agent.config.gitea_repo
repo = agent.config.projects[0].repo
as_operator = operator_gitea(agent.config.gitea_url)
issues: list[int] = []
try: