M7.2: share the loop's parts
Shared parts, not a shared scheduler (agreed): nisse-review will have its own small scheduler; a common one waits until two exist to compare. - nisse_core.loop.driver: the driver run, moved; the agent names its waiting tools per run (nisse-dev's are WAITING_TOOLS), and execution goes to any Executor (ToolOutcome moves with it). - nisse_core.loop.tools: the tool helper and the tools any agent offers (notes, memory, memory_done, ask_expert); nisse_core.loop.expert. - nisse_core.knowledge.handlers: Knowledge, the vault and memory as control tools, the index and prompt text, out of LoopAgent. - nisse_core.config: file reading, references, people, tuning sections of the agent's own, LLM numbers, the vault. - New core tests: a driver run with a non-workspace executor and the agent's own waiting set; Knowledge alone; the shared config pieces. Docs: the Development Plan marks M7.2 done; Architecture Part L and the nisse-core README name the new parts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
This commit is contained in:
1 parent
90afc4f8dd
commit
f32ccf9881
34 files changed
+899
-485
No files matched your search
@@ -17,16 +17,16 @@ from nisse_core.log.canonical import (
|
||||
ToolCall,
|
||||
ToolResult,
|
||||
)
|
||||
from nisse_core.loop.driver import Driver, ToolOutcome
|
||||
from nisse_core.models.llm import LLMError
|
||||
from nisse_core.oplog import OpLog
|
||||
from nisse_core.state.cancel import Cancel
|
||||
from nisse_core.state.worker import InlineWorker
|
||||
from nisse_dev.loop.agent import LoopAgent
|
||||
from nisse_dev.loop.driver import Driver
|
||||
from nisse_dev.loop.items import CLOSED, GO_AHEAD, READY, RUNNING, WAITING, loop_of
|
||||
from nisse_dev.loop.reader import command_form
|
||||
from nisse_dev.loop.tools import WAITING_TOOLS
|
||||
from nisse_dev.state import StateStore
|
||||
from nisse_dev.tools import ToolOutcome
|
||||
|
||||
DON = "@don:test"
|
||||
|
||||
@@ -357,7 +357,11 @@ def test_calls_after_a_waiting_tool_are_not_run(tmp_path: Path) -> None:
|
||||
)
|
||||
log = driver_log(tmp_path)
|
||||
outcome = Driver(model, "m").run(
|
||||
log, system="s", tools=_tools("ask_human", "run_command"), workspace=None
|
||||
log,
|
||||
system="s",
|
||||
tools=_tools("ask_human", "run_command"),
|
||||
waiting=WAITING_TOOLS,
|
||||
workspace=None,
|
||||
)
|
||||
assert (outcome.kind, outcome.tool) == ("wait", "ask_human")
|
||||
results = [e.event.result for e in log.read() if hasattr(e.event, "result")]
|
||||
@@ -368,7 +372,11 @@ def test_an_invented_tool_is_answered_not_run(tmp_path: Path) -> None:
|
||||
model = ScriptedModel([call("fly")], [call("ask_human", question="?")])
|
||||
log = driver_log(tmp_path)
|
||||
outcome = Driver(model, "m").run(
|
||||
log, system="s", tools=_tools("ask_human"), workspace=None
|
||||
log,
|
||||
system="s",
|
||||
tools=_tools("ask_human"),
|
||||
waiting=WAITING_TOOLS,
|
||||
workspace=None,
|
||||
)
|
||||
assert outcome.kind == "wait"
|
||||
assert any(
|
||||
@@ -379,7 +387,11 @@ def test_an_invented_tool_is_answered_not_run(tmp_path: Path) -> None:
|
||||
def test_the_turn_cap_ends_a_run(tmp_path: Path) -> None:
|
||||
model = ScriptedModel(*[[call("read_file", path="a")]] * 3)
|
||||
outcome = Driver(model, "m", max_turns=3).run(
|
||||
driver_log(tmp_path), system="s", tools=_tools("read_file"), workspace=None
|
||||
driver_log(tmp_path),
|
||||
system="s",
|
||||
tools=_tools("read_file"),
|
||||
waiting=WAITING_TOOLS,
|
||||
workspace=None,
|
||||
)
|
||||
assert outcome.kind == "capped"
|
||||
|
||||
@@ -388,7 +400,11 @@ def test_stop_ends_a_run_at_the_next_boundary(tmp_path: Path) -> None:
|
||||
cancel = Cancel()
|
||||
cancel.set()
|
||||
outcome = Driver(ScriptedModel(), "m", cancel=cancel).run(
|
||||
driver_log(tmp_path), system="s", tools=_tools("read_file"), workspace=None
|
||||
driver_log(tmp_path),
|
||||
system="s",
|
||||
tools=_tools("read_file"),
|
||||
waiting=WAITING_TOOLS,
|
||||
workspace=None,
|
||||
)
|
||||
assert outcome.kind == "stopped"
|
||||
|
||||
@@ -399,7 +415,11 @@ def test_a_failed_model_call_fails_the_run(tmp_path: Path) -> None:
|
||||
raise LLMError("proxy down")
|
||||
|
||||
outcome = Driver(Down(), "m").run( # type: ignore[arg-type]
|
||||
driver_log(tmp_path), system="s", tools=_tools("read_file"), workspace=None
|
||||
driver_log(tmp_path),
|
||||
system="s",
|
||||
tools=_tools("read_file"),
|
||||
waiting=WAITING_TOOLS,
|
||||
workspace=None,
|
||||
)
|
||||
assert outcome.kind == "failed" and "proxy down" in outcome.note
|
||||
|
||||
@@ -419,6 +439,7 @@ def test_stop_mid_turn_skips_the_rest_of_its_calls(tmp_path: Path) -> None:
|
||||
log,
|
||||
system="s",
|
||||
tools=_tools("start_milestone", "read_file"),
|
||||
waiting=WAITING_TOOLS,
|
||||
workspace=None,
|
||||
handlers={"start_milestone": start},
|
||||
)
|
||||
@@ -443,6 +464,7 @@ def test_a_command_that_works_resets_the_failure_count(tmp_path: Path) -> None:
|
||||
driver_log(tmp_path),
|
||||
system="s",
|
||||
tools=_tools("run_command", "ask_human"),
|
||||
waiting=WAITING_TOOLS,
|
||||
workspace=Commands(), # type: ignore[arg-type]
|
||||
)
|
||||
assert outcome.kind == "wait" # never two failures in a row
|
||||
|
||||
Reference in new issue
Block a user