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
@@ -0,0 +1,70 @@
|
||||
"""Configuration any agent builds on: references resolved from the
|
||||
environment, people lists, tuning sections of the agent's own, the LLM's
|
||||
numbers, and the vault; every problem collected, none raised one by one."""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
from nisse_core.config import (
|
||||
flatten,
|
||||
llm_numbers,
|
||||
parse_tuning,
|
||||
parse_vault,
|
||||
people,
|
||||
resolve,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Review:
|
||||
max_rounds: int = 3
|
||||
wait_s: float = 60.0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Tuning:
|
||||
review: Review = field(default_factory=Review)
|
||||
|
||||
|
||||
def test_an_agents_own_tuning_sections() -> None:
|
||||
errors: list[str] = []
|
||||
tuning = parse_tuning({"review": {"max_rounds": 2}}, Tuning, errors)
|
||||
assert tuning == Tuning(Review(max_rounds=2)) and errors == []
|
||||
assert parse_tuning(None, Tuning, errors) == Tuning()
|
||||
parse_tuning(
|
||||
{"review": {"max_rounds": 0, "x": 1}, "old": {}, "new": {}},
|
||||
Tuning,
|
||||
errors,
|
||||
retired={"old": "use review"},
|
||||
)
|
||||
assert errors == [
|
||||
"tuning.review.max_rounds: must be a positive number",
|
||||
"tuning.review.x: unknown setting",
|
||||
"tuning.old: retired — use review",
|
||||
"tuning.new: unknown section",
|
||||
]
|
||||
|
||||
|
||||
def test_references_and_people() -> None:
|
||||
errors: list[str] = []
|
||||
env = {"TOKEN": "s3cret"}
|
||||
assert resolve("${TOKEN}", "gitea.token", env, errors) == "s3cret"
|
||||
resolve("${NOPE}", "llm.token", env, errors)
|
||||
assert errors == ["llm.token: references ${NOPE}, which is unset or empty"]
|
||||
assert people("don, ,ann,") == {"don", "ann"}
|
||||
assert flatten({"llm": {"base_url": "u"}, "tuning": {"a": {}}}) == {
|
||||
"llm.base_url": "u",
|
||||
"tuning": {"a": {}},
|
||||
}
|
||||
|
||||
|
||||
def test_llm_numbers_and_the_vault(tmp_path: Path) -> None:
|
||||
errors: list[str] = []
|
||||
assert llm_numbers({"llm.max_output_tokens": 100}, errors)[0] == 100
|
||||
llm_numbers({"llm.request_timeout_s": -1}, errors)
|
||||
assert errors == ["llm.request_timeout_s: must be a positive number of seconds"]
|
||||
vault = parse_vault({"root": str(tmp_path), "folders": {"notes": "ro"}}, [])
|
||||
assert vault is not None and vault.folders == {"notes": "ro"}
|
||||
bad: list[str] = []
|
||||
assert parse_vault({"root": "/v", "folders": {"../x": "rw"}}, bad) is None
|
||||
assert bad == ["vault.folders.../x: a folder inside the vault"]
|
||||
Reference in new issue
Block a user