M2.8: switch over to the new loop
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:
1 parent
09232dcbdd
commit
6ae2375194
89 files changed
+951
-8592
No files matched your search
@@ -12,7 +12,6 @@ resolved against external truth on boot).
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
import uuid
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
@@ -42,10 +41,9 @@ Migration = Callable[[sqlite3.Connection], None]
|
||||
# Maps version N to the migration that takes the schema from N to N+1.
|
||||
MIGRATIONS: Mapping[int, Migration] = {}
|
||||
|
||||
IDLE = "IDLE"
|
||||
|
||||
# Who produced an event (L7): the operator, the orchestrator's own code, or a
|
||||
# model role (L9). planner and executor become driver in the new loop (M2.3).
|
||||
# model role (L9). planner and executor are the old loop's (until M2.8), kept
|
||||
# so its records still read.
|
||||
ACTORS = frozenset(
|
||||
{
|
||||
"operator",
|
||||
@@ -64,17 +62,6 @@ class StateSchemaError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AgentState:
|
||||
"""The state machine's view of the active work item (control.py, until
|
||||
Development Plan M2.6 retires it)."""
|
||||
|
||||
state: str
|
||||
project: str | None
|
||||
data: dict[str, Any]
|
||||
updated_at: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class WorkItem:
|
||||
id: str
|
||||
@@ -406,61 +393,6 @@ class StateStore:
|
||||
)
|
||||
return len(rows)
|
||||
|
||||
# --- the state machine's view (control.py, until M2.6) -----------------
|
||||
|
||||
def get_state(self) -> AgentState | None:
|
||||
"""The active work item as the state machine sees it: its position
|
||||
is the state, IDLE when there is none. None only for a store the
|
||||
agent has never started on."""
|
||||
agent = self._conn.execute("SELECT * FROM agent WHERE id = 1").fetchone()
|
||||
if agent is None:
|
||||
return None
|
||||
item = self.active_item()
|
||||
if item is None:
|
||||
return AgentState(IDLE, None, {}, agent["updated_at"])
|
||||
return AgentState(item.position, item.scope, item.data, item.updated_at)
|
||||
|
||||
def transition(
|
||||
self,
|
||||
state: str,
|
||||
*,
|
||||
cause: str,
|
||||
project: str | None = None,
|
||||
data: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""The state machine's transition, over work items: IDLE closes and
|
||||
deactivates the active item (its outcome should already be recorded
|
||||
with close_work_item); any other state updates the active item,
|
||||
opening one if there is none."""
|
||||
self._ensure_agent_row()
|
||||
active = self.active_item()
|
||||
if state == IDLE:
|
||||
if active is not None:
|
||||
if active.status == "open":
|
||||
self.close_work_item(active.id, outcome="closed", cause=cause)
|
||||
self.set_active(None, cause=cause)
|
||||
self.oplog.emit(
|
||||
"state.transition",
|
||||
cause=cause,
|
||||
**{
|
||||
"from": active.position if active else None,
|
||||
"to": IDLE,
|
||||
"project": project,
|
||||
},
|
||||
)
|
||||
return
|
||||
if active is None:
|
||||
item_id = str((data or {}).get("task_id") or f"task-{uuid.uuid4().hex[:8]}")
|
||||
active = self.open_work_item(
|
||||
item_id,
|
||||
title=project or item_id,
|
||||
scope=project,
|
||||
position=IDLE,
|
||||
cause=cause,
|
||||
)
|
||||
self.set_active(item_id, cause=cause)
|
||||
self.update_work_item(active.id, position=state, data=data or {}, cause=cause)
|
||||
|
||||
# --- intents (L8) ------------------------------------------------------
|
||||
|
||||
def begin_intent(
|
||||
|
||||
Reference in new issue
Block a user