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:
teknium1
2026-09-14 17:58:25 -07:00
committed by Teknium
parent 7d292af875
commit 238720e928
4 changed files with 30 additions and 15 deletions

View File

@@ -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})")

View File

@@ -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

View File

@@ -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**:

View File

@@ -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 |