Files
nisse/nisse-core/tests/test_knowledge.py
T
dongarandClaude Opus 5.5 f32ccf9881
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m21s
CI / e2e (pull_request) Successful in 11m45s
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
2026-10-13 21:31:29 +00:00

70 lines
2.7 KiB
Python

"""The vault and memory as a loop offers them (L10, L6.4), apart from any
agent: tools only for what exists, labelled results, failures as results,
notes only about the item's own project, provenance recorded."""
import io
from pathlib import Path
from nisse_core.knowledge.handlers import Knowledge
from nisse_core.knowledge.memory import Memory
from nisse_core.knowledge.vault import Vault, split_frontmatter
from nisse_core.loop.tools import names
from nisse_core.oplog import OpLog
FOLDER = "memory/agent"
def knowledge(tmp_path: Path, *, memory: bool = True) -> Knowledge:
(tmp_path / "notes").mkdir()
(tmp_path / "notes" / "Style.md").write_text("Use split.\n")
vault = Vault(tmp_path, {FOLDER: "rw", "notes": "ro"}, agent="agent")
return Knowledge(
vault, Memory(vault, FOLDER) if memory else None, OpLog("t", io.StringIO())
)
def test_tools_and_prompt_follow_what_exists(tmp_path: Path) -> None:
both = knowledge(tmp_path)
assert names(both.tools()) == {
"read_note",
"list_notes",
"search_notes",
"remember",
"forget",
}
assert "knowledge vault" in both.prompt() and "Your memory" in both.prompt()
none = Knowledge(None, None, OpLog("t", io.StringIO()))
assert none.tools() == [] and none.prompt() == "" and none.index("x", None) is None
assert none.handlers("x", project=None, provenance={}, say=print) == {}
def test_notes_are_read_labelled_and_failures_are_results(tmp_path: Path) -> None:
handlers = knowledge(tmp_path, memory=False).handlers(
"x", project=None, provenance={}, say=print
)
assert "remember" not in handlers
assert handlers["read_note"]({"path": "notes/Style.md"}) == (
"[vault: notes/Style.md]\nUse split.\n",
True,
)
assert handlers["read_note"]({"path": "notes/No.md"})[1] is False
def test_memory_is_about_the_items_own_project_only(tmp_path: Path) -> None:
k = knowledge(tmp_path)
said: list[str] = []
handlers = k.handlers(
"x", project="demo", provenance={"source": "work item x"}, say=said.append
)
fact = {"name": "short-prs", "description": "Small PRs", "fact": "Don likes them"}
assert handlers["remember"]({**fact, "scope": "project"}) == (
"remembered short-prs",
True,
)
note = (tmp_path / FOLDER / "projects" / "demo" / "short-prs.md").read_text()
assert split_frontmatter(note)[0]["provenance"]["project"] == "demo"
assert said == ["Remembered: Small PRs"]
assert "short-prs" in (k.index("x", "demo") or "")
general = k.handlers("y", project=None, provenance={}, say=said.append)
assert general["remember"]({**fact, "scope": "project"})[1] is False