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.
This commit is contained in:
@@ -246,7 +246,7 @@ def _cmd_list_unmanaged(args) -> int:
|
||||
return 0
|
||||
print(f"unmanaged skills ({len(rows)}):")
|
||||
for r in sorted(rows, key=lambda x: x["name"]):
|
||||
why = "created_by:null" if r.get("has_provenance_key") else "no marker"
|
||||
why = f"created_by:{r.get('created_by') or 'null'}" if r.get("has_provenance_key") else "no marker"
|
||||
print(
|
||||
f" {r['name']:44s} activity={r.get('activity_count', 0):4d} "
|
||||
f"last_activity={_fmt_ts(r.get('last_activity_at')):14s} ({why})")
|
||||
|
||||
@@ -81,13 +81,22 @@ def test_full_payload_shape_and_edge_integrity(tmp_path):
|
||||
assert all("timestamp" in n for n in graph["nodes"])
|
||||
|
||||
|
||||
def test_learning_signal_predicate_includes_user_taught_skills():
|
||||
"""created_by='learn' (foreground /learn, zero uses) is graph-worthy; an unmarked
|
||||
unused skill is not; 'installed' alone is not a learning signal."""
|
||||
from agent.learning_graph import SkillNode, _has_learning_signal
|
||||
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
|
||||
|
||||
assert _has_learning_signal(SkillNode(name="a", category="x", created_by="learn"))
|
||||
assert _has_learning_signal(SkillNode(name="b", category="x", created_by="agent"))
|
||||
assert _has_learning_signal(SkillNode(name="c", category="x", use_count=3))
|
||||
assert not _has_learning_signal(SkillNode(name="d", category="x"))
|
||||
assert not _has_learning_signal(SkillNode(name="e", category="x", created_by="installed"))
|
||||
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
|
||||
|
||||
@@ -207,14 +207,17 @@ agent turns). The background fork runs with a write origin of `"background_revie
|
||||
`mark_agent_created()` call in `skill_manage`.
|
||||
|
||||
Skills the foreground agent creates via `skill_manage(action="create")` during a
|
||||
conversation are **not** marked as agent-created — they are considered
|
||||
user-directed and the curator intentionally leaves them alone.
|
||||
conversation (including `/learn`) are **not** marked as agent-created — they are
|
||||
recorded as `created_by: learn`, which makes them show up in the
|
||||
[learning journey](./memory.md#learning-journey-journey) right away but is not a
|
||||
curator opt-in. They are considered user-directed and the curator intentionally
|
||||
leaves them alone.
|
||||
|
||||
:::warning Your hand-written skills are NOT curated
|
||||
If you manually created a `SKILL.md` or pointed Hermes at an external skill
|
||||
directory, that skill will have a `.usage.json` entry with `created_by: null`
|
||||
(or the field absent). The curator will not touch it. The same applies to
|
||||
skills the foreground agent created at your request.
|
||||
skills the foreground agent created at your request (`created_by: learn`).
|
||||
|
||||
**To see which skills the curator actually manages**, run `hermes curator status`.
|
||||
If the agent-created count is 0, no skills are currently in the curator's
|
||||
@@ -245,8 +248,9 @@ for one of two reasons:
|
||||
- **pre-dates marker** — the record was written before `created_by` existed, so
|
||||
it carries no provenance signal at all. Authorship is genuinely unknowable
|
||||
from the record.
|
||||
- **foreground-created** — a foreground `skill_manage(create)` left the marker
|
||||
unset by design, since skills you ask for belong to you.
|
||||
- **foreground-created** — a foreground `skill_manage(create)` recorded
|
||||
`created_by: learn` (older records: unset) by design, since skills you ask for
|
||||
belong to you.
|
||||
|
||||
A large library can therefore look fully curated while most of it is
|
||||
untouchable. `adopt` closes that gap by **declaration**:
|
||||
|
||||
@@ -226,6 +226,8 @@ The learning journey is a timeline view of everything Hermes has learned — sav
|
||||
- **TUI** — `/journey` (aliases: `/learning`, `/memory-graph`) opens the timeline as an overlay.
|
||||
- **Desktop app** — `/journey` opens the Star Map / memory-graph panel, an interactive visual of the same nodes.
|
||||
|
||||
A skill appears on the timeline as soon as it has a learning signal: it was created in this profile (a `/learn` result or a foreground `skill_manage` create), created by the background review, or used at least once. Bundled skills and hand-written skills that have never been used stay out of the timeline.
|
||||
|
||||
Beyond viewing, the journey is also where you **prune and correct** what Hermes has learned:
|
||||
|
||||
| Command | What it does |
|
||||
|
||||
Reference in New Issue
Block a user