Files
hermes-agent/tests/agent/test_learning_graph.py
teknium1 238720e928 test(journey): one end-to-end invariant for foreground-created skills; docs + list-unmanaged label
Replace the predicate unit test with an invariant on the real builder: a skill recorded
by a foreground create is in build_learning_graph() with zero uses, and an unmarked
never-used local skill is not. `hermes curator list-unmanaged` prints the actual
marker (created_by:learn) instead of hard-coding created_by:null. Docs: curator.md and
memory.md describe the learn marker and what the journey shows.
2026-09-15 05:38:31 -07:00

103 lines
3.6 KiB
Python

"""Behavior contracts for the learning-graph assembler.
Asserts invariants (edges resolve to real nodes, clusters cover every node,
memory cards are represented consistently), never a snapshot of the live skill
catalog — that catalog grows every release and a count assertion would be a
change-detector.
"""
from __future__ import annotations
from agent import learning_graph
from hermes_constants import reset_hermes_home_override, set_hermes_home_override
def _node(name: str, category: str, related=None):
n = learning_graph.SkillNode(name=name, category=category)
n.related = list(related or [])
return n
def test_density_stats_count_isolated_nodes():
nodes = {
"a": _node("a", "x", related=["b"]),
"b": _node("b", "x", related=["a"]),
"c": _node("c", "y"),
}
stats = learning_graph.density_stats(nodes, learning_graph.build_edges(nodes))
assert stats["nodes"] == 3
assert stats["linked_nodes"] == 2
assert stats["isolated_pct"] == round(100 / 3, 1)
def test_memory_is_cards_split_on_separator(tmp_path):
home = tmp_path / ".hermes"
(home / "memories").mkdir(parents=True)
(home / "memories" / "MEMORY.md").write_text(
"Project uses pytest with xdist\n§\nUser prefers concise responses",
encoding="utf-8",
)
token = set_hermes_home_override(home)
try:
graph = learning_graph.build_learning_graph()
finally:
reset_hermes_home_override(token)
titles = [c["title"] for c in graph["memory"]]
assert "Project uses pytest with xdist" in titles
assert "User prefers concise responses" in titles
# Memory cards remain typed cards and also appear as memory-kind nodes.
assert all(c["source"] in {"memory", "profile"} for c in graph["memory"])
assert all("timestamp" in c for c in graph["memory"])
assert any(n["kind"] == "memory" for n in graph["nodes"])
def test_full_payload_shape_and_edge_integrity(tmp_path):
home = tmp_path / ".hermes"
home.mkdir()
token = set_hermes_home_override(home)
try:
graph = learning_graph.build_learning_graph()
finally:
reset_hermes_home_override(token)
ids = {n["id"] for n in graph["nodes"]}
assert all(e["source"] in ids and e["target"] in ids for e in graph["edges"])
# Every node's category appears in the cluster list.
cluster_cats = {c["category"] for c in graph["clusters"]}
assert all(n["category"] in cluster_cats for n in graph["nodes"])
skill_nodes = [n for n in graph["nodes"] if n["kind"] == "skill"]
assert graph["stats"]["nodes"] == len(skill_nodes)
assert graph["stats"]["memory_nodes"] == len(graph["memory"])
assert all("timestamp" in n for n in graph["nodes"])
def test_foreground_created_skill_is_in_journey_before_first_use(tmp_path):
"""A skill created in the foreground (/learn, skill_manage) shows in the journey with zero
uses, while an unmarked never-used local skill (hand-written) stays out."""
from tools import skill_usage
home = tmp_path / ".hermes"
for name in ("fresh-learn-skill", "hand-written"):
(home / "skills" / "demo" / name).mkdir(parents=True)
(home / "skills" / "demo" / name / "SKILL.md").write_text(
f"---\nname: {name}\ndescription: d.\n---\n\n# {name}\n", encoding="utf-8")
token = set_hermes_home_override(home)
try:
skill_usage.record_created("fresh-learn-skill", agent_created=False)
skill_nodes = {n["id"] for n in learning_graph.build_learning_graph()["nodes"] if n["kind"] == "skill"}
finally:
reset_hermes_home_override(token)
assert "fresh-learn-skill" in skill_nodes
assert "hand-written" not in skill_nodes