M7.2: share the loop's parts
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m21s
CI / e2e (pull_request) Successful in 11m45s

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:
dongarandClaude Opus 5.5 committed 2026-10-13 21:31:29 +00:00
1 parent 90afc4f8dd
commit f32ccf9881
34 files changed
+899 -485

No files matched your search

+29 -7
View File
@@ -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