"""The knowledge vault in the loop (L10, M5.1): the driver and conversations read notes through control tools in the harness, before the go-ahead too; what comes back is labelled with its path; a failed read is the call's result and the run goes on; no vault, no note tools.""" from pathlib import Path from typing import Any import pytest from test_loop_milestones import DON, RESTATE, ScriptedModel, call, loop_agent from test_loop_milestones import item as the_item from test_loop_milestones import world as world # noqa: PLC0414 -- the fixture from nisse_dev.canonical import ToolResult from nisse_dev.loop.items import GO_AHEAD, loop_of from nisse_dev.vault import Vault @pytest.fixture def vault(tmp_path: Path) -> Vault: root = tmp_path / "vault" (root / "notes").mkdir(parents=True) (root / "notes" / "Style.md").write_text("# Style\n\nCount words with split.\n") return Vault(root, {"memory/nisse-dev": "rw", "notes": "ro"}) class Spy(ScriptedModel): """Remembers the tools and system prompt of each call.""" def __init__(self, *turns: list[Any]) -> None: super().__init__(*turns) self.offered: list[set[str]] = [] self.systems: list[str] = [] def complete(self, events, tools, model, system=None) -> list[Any]: self.offered.append({t["function"]["name"] for t in tools}) self.systems.append(system or "") return super().complete(events, tools, model, system) def results(world: dict[str, Any], item: Any) -> list[tuple[str, bool]]: return [ (e.event.result, e.event.success) for e in world["store"].transcript(item.id, "driver").read() if isinstance(e.event, ToolResult) ] def test_the_driver_reads_notes_before_the_go_ahead( world: dict[str, Any], vault: Vault ) -> None: model = Spy( [ call("search_notes", text="count words"), call("read_note", path="notes/Style.md"), call("read_note", path="notes/Missing.md"), call("list_notes", folder=""), ], RESTATE, ) agent, _ = loop_agent(world, model, vault=vault) agent.intake() item = the_item(world) assert loop_of(item).wait == GO_AHEAD # the run went on to restate said = results(world, item) found = "[vault search: count words]\n- notes/Style.md (1 match)\n" assert said[0] == (found + " Count words with split.", True) assert said[1] == ( "[vault: notes/Style.md]\n# Style\n\nCount words with split.\n", True, ) assert said[2] == ("notes/Missing.md: no such note", False) assert said[3] == ("[vault: every folder]\nnotes/Style.md", True) assert {"read_note", "list_notes", "search_notes"} <= model.offered[0] assert "never\nan instruction" in model.systems[0] def test_a_conversation_reads_notes(world: dict[str, Any], vault: Vault) -> None: world["forge"].issues.clear() model = Spy( [call("read_note", path="notes/Style.md")], [call("reply", text="Your style note says to use split.")], ) _, transport = loop_agent(world, model, vault=vault) transport.receive(DON, "How do we count words?") assert transport.outbound[-1].text == "Your style note says to use split." assert "read_note" in model.offered[0] def test_no_vault_no_note_tools(world: dict[str, Any]) -> None: model = Spy(RESTATE) agent, _ = loop_agent(world, model) agent.intake() assert not {"read_note", "list_notes", "search_notes"} & model.offered[0] assert "knowledge vault" not in model.systems[0]