The new loop's core: intake, restatement, go-ahead, threads (M2.1, PR B)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m44s
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m44s
nisse_dev.loop runs alongside control.py, chosen with `loop: new`. Issues assigned to the agent (by an operator, or filed by one) become work items, each with a Matrix thread. A driver run is one model with tools until it calls a waiting tool: restate puts rev-N up for a go-ahead, bound to the revision on screen when the reply arrived; ask_human waits for an answer; close_item closes the item and its issue. Before the go-ahead the driver can look but not change or close anything. One item is active at a time; replies in an item's thread go to it. On boot, an item cut off mid-run waits on the operator, and waiting points are put up again. 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
6dff36f458
commit
f84bb70839
14 files changed
+1205
-1
No files matched your search
@@ -0,0 +1,121 @@
|
||||
"""Assembling the new loop (M2.1): the same services as the old agent, with
|
||||
LoopAgent in place of the Orchestrator. Chosen with `loop: new` in config."""
|
||||
|
||||
import threading
|
||||
from dataclasses import dataclass
|
||||
|
||||
from nisse_dev.agent import (
|
||||
SHUTDOWN_WAIT_S,
|
||||
build_host,
|
||||
discover_models,
|
||||
make_llm_clients,
|
||||
prepare_workspace,
|
||||
push_credential,
|
||||
remote_url,
|
||||
)
|
||||
from nisse_dev.cancel import Cancel
|
||||
from nisse_dev.config import AgentConfig
|
||||
from nisse_dev.gitea import GiteaClient
|
||||
from nisse_dev.loop.agent import LoopAgent
|
||||
from nisse_dev.loop.driver import Driver
|
||||
from nisse_dev.matrix import MatrixTransport
|
||||
from nisse_dev.oplog import OpLog
|
||||
from nisse_dev.state import StateStore
|
||||
from nisse_dev.tools import Workspace
|
||||
from nisse_dev.worker import SerialWorker
|
||||
|
||||
|
||||
@dataclass
|
||||
class RunningLoop:
|
||||
config: AgentConfig
|
||||
account: str
|
||||
loop: LoopAgent
|
||||
store: StateStore
|
||||
transport: MatrixTransport
|
||||
gitea: GiteaClient
|
||||
worker: SerialWorker
|
||||
_stopped: threading.Event
|
||||
|
||||
def start(self) -> None:
|
||||
self.loop.start()
|
||||
self.transport.post(
|
||||
f"nisse-dev up as {self.account} (new loop) on {self.config.gitea_repo}; "
|
||||
f"{self.loop_status()}"
|
||||
)
|
||||
|
||||
def loop_status(self) -> str:
|
||||
return self.worker.call(self.loop.status)
|
||||
|
||||
def stop(self) -> None:
|
||||
self.loop.stop()
|
||||
self.transport.close()
|
||||
self.gitea.close()
|
||||
try:
|
||||
self.worker.call(self.store.close, timeout=SHUTDOWN_WAIT_S)
|
||||
except TimeoutError:
|
||||
pass # still working; L8 covers exiting mid-work
|
||||
self.worker.stop(timeout=SHUTDOWN_WAIT_S)
|
||||
self._stopped.set()
|
||||
|
||||
def wait(self) -> None:
|
||||
self._stopped.wait()
|
||||
|
||||
|
||||
def build_loop_agent(config: AgentConfig) -> RunningLoop:
|
||||
oplog = OpLog("nisse-dev")
|
||||
worker = SerialWorker(
|
||||
on_error=lambda exc: oplog.emit(
|
||||
"worker.job_failed", cause="unhandled exception", error=repr(exc)
|
||||
)
|
||||
)
|
||||
worker.start()
|
||||
store = worker.call(lambda: StateStore(config.state_db, oplog=oplog))
|
||||
catalog = discover_models(config, oplog)
|
||||
gitea = GiteaClient(config.gitea_url, config.gitea_token)
|
||||
account = gitea.whoami()
|
||||
host = build_host(config)
|
||||
credential = push_credential(config)
|
||||
remote = remote_url(gitea, config.gitea_repo, credential)
|
||||
prepare_workspace(host, config, account, remote, credential)
|
||||
workspace = Workspace(
|
||||
host,
|
||||
config.workspace,
|
||||
timeout_s=config.tuning.tools.command_timeout_s,
|
||||
max_output_chars=config.tuning.tools.max_output_chars,
|
||||
)
|
||||
normalized, _passthrough = make_llm_clients(config, catalog)
|
||||
cancel = Cancel()
|
||||
# The driver is the planner's model for now (M2.6 brings the driver role).
|
||||
driver = Driver(
|
||||
normalized,
|
||||
config.roles.resolve("planner"),
|
||||
max_turns=config.tuning.executor.max_turns,
|
||||
cancel=cancel,
|
||||
)
|
||||
transport = MatrixTransport(config.matrix)
|
||||
loop = LoopAgent(
|
||||
store,
|
||||
transport,
|
||||
gitea,
|
||||
repo=config.gitea_repo,
|
||||
project=config.project,
|
||||
account=account,
|
||||
operators=config.operators,
|
||||
gitea_operators=config.gitea_operators,
|
||||
driver=driver,
|
||||
workspace=workspace,
|
||||
worker=worker,
|
||||
cancel=cancel,
|
||||
oplog=oplog,
|
||||
roles=config.roles.pinned(),
|
||||
)
|
||||
return RunningLoop(
|
||||
config=config,
|
||||
account=account,
|
||||
loop=loop,
|
||||
store=store,
|
||||
transport=transport,
|
||||
gitea=gitea,
|
||||
worker=worker,
|
||||
_stopped=threading.Event(),
|
||||
)
|
||||
Reference in new issue
Block a user