agentsmesh lessons
Query and capture lessons from .agentsmesh/lessons/lessons.json — the single source of truth for the recall/capture subsystem. Every harness (Claude, Cursor, Codex, plain shell) calls the same two commands.
The contract is delivered in two tiers. A minimal always-on trigger is injected into .agentsmesh/rules/_root.md, so it reaches every target through canonical rule generation (rules are native everywhere). The full operating manual — every subcommand below, the topic workflow, trigger flags, and the MCP fallback — is seeded as a lessons skill (.agentsmesh/skills/lessons/SKILL.md) and surfaced on demand on skill-capable targets. Targets without skills still get the trigger, so the binding contract is universal while the manual stays out of always-on context.
Getting started
From an empty project to a working recall + capture loop:
# 1. Scaffold the subsystem (graph, config, skill, ritual, recall hook).agentsmesh init --lessonsagentsmesh generate # project the ritual into every target
# 2. Capture your first lesson after a failure (creates the topic on the fly).agentsmesh lessons add "Run tsc --noEmit before committing type changes." \ --topic build --new-topic --topic-summary "Build & typecheck rules." \ --trigger-file "src/**/*.ts" --evidence commit:abc1234
# 3. Recall it before the next edit / command.agentsmesh lessons query --file src/index.ts --cmd "git commit -m wip"
# 4. List what's there.agentsmesh lessons topicsRun lessons commands from the project root (the directory holding .agentsmesh). Run from a subdirectory and the CLI warns it found no graph there — cd to the root. Mistyped a flag? The command errors and names the unknown flag rather than silently ignoring it (a typoed --trigger-flie would otherwise drop a trigger).
If you initialized agentsmesh without --lessons, the lessons commands still run but tell you the subsystem isn’t wired: reads (query, topics, journal) print a one-line hint to run agentsmesh init --lessons, and lessons add still captures to the graph but warns that recall isn’t wired into your AI tools yet (no hook, ritual, or skill) until you run init --lessons + generate. Activate the full subsystem and the hints disappear.
Usage
agentsmesh lessons <subcommand> [args] [flags]Subcommands
| Subcommand | Purpose |
|---|---|
query | Recall primitive — return active lessons whose triggers match the supplied file / command / keyword predicates. |
add | Capture primitive — atomically add a new lesson, deduplicating triggers against the graph. |
topics | List every topic with its summary. |
show <topic|lesson-id> | Render a topic’s lessons, OR a single lesson by id — its rule, status, topics, and every trigger resolved to its pattern (the diagnosis view for an irrelevant recall). Read-only. |
deprecate <id> | Mark a lesson deprecated. With --superseded-by <id>, mark it superseded. |
merge <loser-id> <keeper-id> | Fold a duplicate lesson into its canonical twin: union the loser’s triggers, topics, and evidence onto the keeper, then mark the loser superseded. Preserves recall reachability across topics. |
untrigger <lesson-id> <trigger-id> | Detach one trigger from a lesson in place (e.g. drop a dead LOW_SIGNAL_KEYWORD keyword, then re-add a short one). Garbage-collects the trigger node when no lesson references it anymore. Refuses to remove the only trigger of an active lesson. |
strip-markers | Remove dead legacy provenance markers (See L123, (L69), [L3], “(also relevant …)”) from rule prose. --dry-run reports without writing. |
journal | Render lessons chronologically (sorted by createdAt then id). |
validate | Schema + integrity + trigger-liveness checks (dead file_globs that match no file in the working tree, runner-anchored command_patterns). Non-zero exit on errors; warnings do not affect exit code. |
stats | Summarize the opt-in recall and capture telemetry logs: no-match rate, returned-token percentiles, cumulative recall cost vs. the whole-active-set preload baseline (break-even), keyword-only reachability gap, and a Capture block (total captures, blocked count, new vs. upsert split, trigger-kind breakdown, recall:capture ratio). --json for raw output. Requires AGENTSMESH_LESSONS_TELEMETRY=1. |
prune | Curate the graph — trim over-cap lessons (drop the least-specific triggers), detach dead file_glob triggers (matching no on-disk file) from any lesson that keeps ≥1 other trigger, remove dead triggers, and GC orphan topics. Lessons whose every trigger is a dead glob are reported as unreachable, not stripped (that would strand them). Dry-run by default; --apply writes, --cap <n> overrides the per-lesson cap (default 8). |
import-md | One-shot migrator from legacy index.yaml + topics/*.md + journal.md. |
Recall ritual
Before any edit or shell command, agents call:
agentsmesh lessons query --file <path-about-to-edit> --cmd <command-about-to-run>Omit a flag when not applicable. Add --keyword <text> for task-description matches. Default --format plain prints one rule per line — the cheapest shape for agents to paste back into context.
# Examplesagentsmesh lessons query --file src/cli/lessons.tsagentsmesh lessons query --cmd "pnpm test:e2e"agentsmesh lessons query --keyword "windows path normalization"agentsmesh lessons query --file src/x.ts --format mdagentsmesh lessons query --file src/x.ts --format jsonCapture ritual
Immediately after any failure, agents call:
agentsmesh lessons add "<imperative rule>" --topic <id> --trigger-file <glob> --evidence <commit-sha|lesson-id>Each --trigger-* value is opaque — pass the flag multiple times for multiple triggers; commas are kept verbatim (so regex/globs like ^foo{1,3}$ or src/{a,b}/** are safe). --evidence is comma-separable. The CLI dedupes triggers against the graph and assigns a stable lesson id.
# Examplesagentsmesh lessons add "Always normalize CLI display paths to forward slashes." \ --topic windows-paths \ --trigger-file "src/cli/**/*.ts" \ --evidence commit:abc1234
agentsmesh lessons add "Treat the cache as advisory." \ --topic perf \ --new-topic --topic-summary "Performance-related rules." \ --trigger-kw "cache,latency"Hook mode (deterministic recall)
The recall ritual asks the agent to run recall before each mutating action — an extra model turn every time, and only as reliable as the agent’s compliance. On harnesses that support context-injecting tool-call hooks, recall is deterministic instead: a PreToolUse hook runs recall automatically and injects the matching lessons into the model’s context before the action — zero extra model turn, zero compliance dependence.
agentsmesh init --lessons wires this automatically. It injects the recall hook under four events in .agentsmesh/hooks.yaml, so generate projects each to every hook-capable target that supports it (Claude Code, Cursor, Copilot, …); targets that can’t represent an event drop it silently, and targets with no hook support fall back to the always-on lessons paragraph, whose ritual drives the same recall via the CLI / MCP lessons_query. The injected entries:
PreToolUse: - matcher: Edit|Write|Bash type: command command: agentsmesh lessons hookUserPromptSubmit: - matcher: '*' type: command command: agentsmesh lessons hookPostToolUseFailure: - matcher: '*' type: command command: agentsmesh lessons hookSessionStart: - matcher: '*' type: command command: agentsmesh lessons hookPreToolUseguards the first touch — recall injects before the edit. With the outcome log enabled it also runs the recurrence gate: when the exact action about to run has already failed twice or more and a captured lesson covers it, the covering rule is re-injected above the regular bullets with the failure count (RECURRENT FAILURE: this exact action has failed N× before …) — and it cuts through session dedup, since a rule the agent saw but did not apply must be shown again. Advisory context only, once per action per session.- No
PostToolUserecall.PreToolUsefires before every tool call, not only the first touch, so aPostToolUserecall for the same action only re-ran recall after the fact: a second process and a second context block per call, carrying advice that could no longer be applied (in field data, 63% of recalls arrived within 3s of a same-shaped one). No target injects onPostToolUsewithout also supportingPreToolUse, so nothing is lost. An entry left by an older scaffold is removed the next timeinit --lessonsruns; your ownPostToolUsehooks are untouched. UserPromptSubmitrecalls against the task text itself. The tool-call events only ever see a file path or command, so akeyword(conceptual / general) lesson can otherwise fire only when its concept happens to appear as a path/command token.UserPromptSubmitis the only event that carries the prompt, so it is where keyword lessons recall against actual intent — and where the universal always-on lessons ride onto every task. It is also where lexical retrieval runs: the prompt is scored against the wording of every active rule, so a conceptual lesson fires even when the prompt says it in different words than its keyword trigger (see the reference). Wording matches rank below triggered ones and are labelledlexical: truein--json.PostToolUseFailurefires when a tool call fails (PostToolUseis success-only) and injects an advisory capture-decision nudge, pre-filled with a ready-to-paste trigger — the failed file path, or for commands the concrete command class (--trigger-cmd '\bgit commit\b'— the program plus the subcommand that directly follows it, escaped and word-bounded sormcannot fire onpnpm run format; field graphs starve on command triggers when the author has to invent the regex) — plus the rule shape that makes lessons worth reading (cite the symptom; say why the obvious fix is wrong). Once per session.SessionStartresets recall dedup for every source exceptresume. Dedup suppresses a lesson already delivered this session — butcompact/clearsummarize or wipe the context and can drop an earlier injection, andstartupmeans a whole new chat, while dedup would keep suppressing. Onlyresumekeeps the set, because it restores the very context those lessons were delivered into; an unknown or missing source resets too, since re-showing a rule is far cheaper than hiding one from a context that never saw it. Both the harness session’s set and the CLI/MCP--session autobucket are cleared, because they are separate stores. So dedup tracks the context lifecycle, not the wall clock.
The last three are agentsmesh recall refinements; a hook-capable target that can’t represent one simply misses that refinement (no warning — recall falls back to the other events).
(Scaffolded lessons before this was automatic? Re-run agentsmesh init --lessons — it adds the hooks idempotently — or paste the block above into your hooks.yaml.)
agentsmesh lessons hook reads the harness’s hook payload from stdin, branches on hook_event_name (recall for tool-call / prompt events; the capture nudge for PostToolUseFailure), recalls lessons for the touched file_path / notebook_path / command or the submitted prompt, and emits the harness context-injection JSON (hookSpecificOutput.additionalContext). It uses the harness session_id for dedup (namespaced per project), so a lesson is injected at most once per session — and stamps the same id on recall/outcome telemetry rows, so stats groups by real harness sessions and a failure only impeaches deliveries from its own session (no AGENTSMESH_SESSION_ID export needed on hook-driven recalls).
It is safe everywhere: the command is harness-adaptive and a silent no-op (exit 0, no output) on any payload it doesn’t recognize, so projecting the hook to a target whose hooks can’t inject context (or to an unrecognized event) does nothing rather than breaking the run.
It is also cheap on the command firehose: the hook recalls on every shell command, but most command-only recalls provably match nothing (field-measured at >80% no-match), so a temp-dir fast-path cache of the active command-reachable trigger patterns — stamped against the graph file and refreshed automatically — lets a provable no-match exit after one stat + one tiny read instead of a full graph parse. Stale, missing, or corrupt cache always falls back to the full path: the worst failure mode is “no speedup”, never a skipped match.
Capture stays model-driven. A hook can’t author a rule or judge “this was a failure worth a lesson” — it can only run, not reason — so the agent still issues lessons add itself. The PostToolUseFailure nudge is advisory only: it removes the “I forgot to consider capturing” failure mode by reminding at the exact teachable moment, without ever authoring the lesson (single-party authorship stays the agent’s).
Non-hook targets — conceptual recall via CLI / MCP
Hook-capable tools recall keyword/conceptual lessons dynamically (via UserPromptSubmit). Targets with no hook rely on the always-on paragraph, which drives recall the agent runs itself: --file/--cmd before each mutation, plus a task-start --keyword "<the task's terms>" --always to surface conceptual rules no path or command names and the universal always-on lessons — both with --session auto, so ritual recalls dedup within the working session instead of re-delivering the matched set every call (no shell → the lessons_query MCP tool, keyword + always fields; dedup is on by default there). That queries the whole graph, filtered to the task — complete and always live — rather than baking a static, cap-limited digest into the instruction file.
Always-on lessons (--scope always)
Some standards apply to every task and are named in no prompt — a comment convention, “write the test first,” a house style. Keyword recall can’t surface them (the concept isn’t in the request), and a broad file_glob would dilute triggered precision. Capture them as always-on lessons instead:
agentsmesh lessons add "Write comments per the repo style." --topic style --new-topic --topic-summary "Style." --scope alwaysAn always-on lesson needs no trigger and is excluded from triggered recall. It is delivered on every task instead: the UserPromptSubmit hook injects it automatically on hook-capable tools, and agentsmesh lessons query --always (or lessons_query with always: true) returns it for CLI/MCP recall — no predicate required. It lives in the graph like any lesson (tracked, deduped, deprecatable), so the whole agent memory stays in one place. Keep the always-on set small — it is a standing context cost on every task; recall bounds it by a token budget.
One-shot upgrade migration
If you’re coming from a previous release that used index.yaml +
topics/*.md + journal.md, the first lessons subcommand auto-migrates:
$ agentsmesh lessons query --file src/x.ts⚠ lessons.json was auto-migrated from index.yaml on first invocation.…Auto-migration deletes the legacy files after a successful import so the
project lands in a clean state. Run agentsmesh lessons import-md explicitly
if you prefer to migrate at a specific point in time. New projects skip this
entirely — agentsmesh init --lessons creates the graph directly, alongside
.agentsmesh/lessons/config.json with every tunable at its default
(recallLimit, recallMaxTokens, autoPrune, repairTriggers, telemetry) so they are
discoverable and editable. An existing config is never overwritten — your edits
are preserved.
Flag reference
query
| Flag | Description |
|---|---|
--file <path> | Project-relative path of the file about to be edited. Matched against file_glob triggers. |
--cmd <command> | Shell command about to run. Matched against command_pattern triggers (regex). |
--keyword <text> | Free-form task description. Matched against keyword triggers as a contiguous whole-token run (case-insensitive; art does not fire on start, and read only needs the two words adjacent). keyword triggers also match --file/--cmd on token boundaries, so conceptual lessons surface without an explicit --keyword. |
--always | Prepend the universal always-on lessons (--scope always) — standards delivered on every task, excluded from triggered recall. Needs no other predicate; combine with --keyword at task start to pull both. |
--format plain|md|json | Output shape. Default plain (one rule per line). |
--top <n> | Keep only the top n relevance-ranked matches. Default 10. |
--all | Return every match (disable both the limit and the token budget). |
--max-tokens <n> | Cap results by cumulative estimated rule-token cost. Approximate — per-rule cost is estimated as rule.length / 4, not a real tokenizer. Defaults to ~400 when omitted. |
--session <id>|auto | Session correlator for recall dedup. Lessons already delivered earlier in the same session are suppressed, so each recall carries only what is new. auto resolves to AGENTSMESH_SESSION_ID when exported, else a project-scoped day key — the right default for prose-driven (non-hook) recall, which otherwise re-delivers the whole matched set on every call. Because the CLI is never told that a chat ended, that bucket is bounded two ways: the whole session resets after 30 minutes of no deliveries (a quiet gap means the previous chat is over), and any single entry expires 1 hour after delivery. An explicit id gives exact, untimed control; with no flag and no env, recall is fully stateless (unchanged). The per-session set of delivered lesson ids lives in the OS temp dir, never the project. |
--no-dedup | Force dedup off for this call even when a session id is set — return the full ranked set including already-seen lessons. |
--ids | Prefix each plain/md line with the lesson id, so an irrelevant recall can be traced to lessons show <id> and retired with lessons deprecate <id>. Off by default to keep recall output paste-clean and token-lean (--format json always includes ids). |
Pass at least one predicate (or --always) — a query with none of --file/--cmd/--keyword/--always is rejected (exit 2). Always anchor recall to the concrete --file you’re about to edit (and --cmd you’re about to run); keyword-only recall is the anti-pattern — most lessons are keyed to a file_glob/command_pattern and silently won’t surface, so the CLI prints a warning when you query keyword-only.
Results are relevance-ranked (BM25 over rule text fused with trigger specificity) and capped by default to the top 10 and a ~400-token budget, so mandatory recall stays lean; the single most-relevant result is always returned even if it alone exceeds the budget. A truncation notice on stderr reports how many matched. Pass --all (or a larger --top/--max-tokens) to see the rest.
Session dedup. Recall is deterministic, so the same --file returns the identical rules every time — N recalls touching one area re-deliver the same rules N times (one field deployment measured 58.7% of all delivered rule-tokens as intra-session repeats before dedup). Set --session auto (the form the scaffolded ritual now uses: env id when exported, else a project-scoped day key), or an explicit --session <id>, and lessons already delivered earlier in that session are suppressed before ranking, so the caps fill with what is new; a stderr note reports how many repeats were hidden. Dedup happens pre-rank so a fresh lesson is never crowded out by a seen one. It is opt-in: with no session id recall is fully stateless, exactly as before. When dedup hides every match, plain/md output prints (no new matches: N already shown this session) instead of (no matches), with the usual dedup notice on stderr.
How a new chat gets its rules back. Suppressing a rule is only safe while the agent that saw it still has it in context, and the CLI is never told that a chat ended — so --session auto is bounded on three sides. A hook-capable target resets the bucket on any SessionStart except resume — startup is the exact signal that a new chat began, and compact/clear mean the context was summarized or wiped. Without a hook there is no such signal, so a 30-minute idle gap resets the whole session (a pause means the previous chat is over), and every entry expires 1 hour after delivery regardless. That leaves one residual case: a brand-new chat opened within minutes of the last one, on a target with no hook. Pass --no-dedup to re-show everything for one call — worth doing as the first recall of a new chat on those targets, and worth knowing about if recall ever looks emptier than expected.
add
| Flag | Description |
|---|---|
--rule "<text>" | Required. Imperative rule that prevents recurrence. Must be ≤2000 characters — a rule is one sentence, not a pasted log; a longer one is rejected (OVERSIZED_RULE, exit 2), and an empty or whitespace-only rule is rejected before any write (EMPTY_RULE, exit 2). Also accepted positionally: lessons add "<rule>" …. |
--topic <id> | Required. Topic id. Pass --new-topic --topic-summary "..." to create one. |
--trigger-file <glob> | file_glob trigger. Opaque — repeat the flag for multiple (commas kept). |
--trigger-cmd <regex> | command_pattern trigger. Opaque — repeat for multiple. Matched by a non-backtracking linear engine, so any ReDoS-shaped pattern ((a+)+, a+a+) is safe; only backreferences and lookarounds (which the engine can’t run) are rejected at capture. A pattern that matches the empty string or nearly every command (.*, , \w, (git)?) is also rejected (BROAD_COMMAND_PATTERN, exit 2) — key it on the action, e.g. \bgit commit\b. |
--trigger-kw <text> | keyword trigger. Opaque — repeat the flag for multiple (commas kept). |
--evidence <ref> | Evidence reference (commit:SHA, lesson:id, …). Comma-separate for multiple. |
--rationale <text> | One-line “why” behind the rule. |
--new-topic | Allow creating a new topic if missing. Requires --topic-summary. |
--topic-summary "<text>" | One-line summary when --new-topic creates a topic. |
--scope always | Capture a universal always-on lesson (a standard that applies to every task). Needs no trigger and is delivered on every task instead of matched — see Always-on lessons. |
At least one EFFECTIVE trigger is required (unless --scope always) — add errors (UNRECALLABLE_LESSON, exit 2) when every trigger on the resulting lesson is dead on the mandatory --file/--cmd recall path. A trigger is dead when it is a keyword whose needle loses all tokens to stopword filtering (e.g. a stopword-only keyword cannot fire on the mandatory —file/—cmd recall path), or a command_pattern rejected by the write barrier as invalid or ReDoS-shaped. A lesson with a mix of live and dead triggers is NOT rejected — the dead trigger surfaces as a non-blocking warning. Prefer a precise --trigger-file glob: it is the most reliable trigger, since it fires on the --file recall before every edit.
Beyond that hard requirement, add prints non-blocking guardrail warnings to stderr (capture still succeeds):
OVERSIZED_LESSON_TRIGGERS— the lesson has too many triggers (cap 8).BROAD_GLOB_TRIGGER— a glob matches large swaths of the tree; prefer a path specific to the lesson.KEYWORD_ONLY_LESSON— every trigger is a keyword. These fire on--file/--cmdrecall only when the keyword appears as a path/command token — less reliable than a precise glob.DEAD_GLOB— a glob matches no file in the working tree (likely a rename or typo); re-point it or the lesson is unreachable via that glob.NEAR_DUPLICATE_LESSON— the rule closely paraphrases an existing active lesson (token-Jaccard ≥ 0.6); consider updating the existing lesson instead.
Prefer a few specific triggers. See the guardrails reference.
deprecate
| Flag | Description |
|---|---|
--superseded-by <id> | Replacement lesson id. Without it, the lesson is marked deprecated; with it, superseded. |
merge
agentsmesh lessons merge <loser-id> <keeper-id> — both ids are positional and required. The keeper must be active; the loser must not already be superseded.
untrigger
agentsmesh lessons untrigger <lesson-id> <trigger-id> — both ids are positional and required. Detaches the trigger from the lesson and, if no remaining lesson references it, deletes the trigger node (no ORPHAN_TRIGGER left behind). Refuses (exit 1) to remove the only trigger of an active lesson, since that would make it unreachable — add a replacement trigger first, then untrigger the old one. The graph is git-tracked, so the change is reviewable and revertible. This is the clean way to replace a LOW_SIGNAL_KEYWORD keyword without the rename/corpse cost of deprecate→re-add.
strip-markers
| Flag | Description |
|---|---|
--dry-run | Report which lessons would change without writing. |
prune
| Flag | Description |
|---|---|
--apply | Write the curation through the transactional path. Without it, prune is a dry run that prints the plan and changes nothing. |
--cap <n> | Per-lesson trigger cap (positive integer; default 8). Over-cap active lessons keep their n most-specific triggers; the highest-fanout (least specific) ones are dropped first. |
Dead triggers (referenced by no active lesson) and orphan topics (referenced by no lesson at all) are removed regardless of --cap — validate only warns about these (ORPHAN_TRIGGER / ORPHAN_TOPIC); prune actually removes them. It also detaches dead file_glob triggers (the ones validate flags DEAD_FILE_GLOB) from any lesson that keeps another trigger, then GCs the now-orphaned glob — automating the rename-rot cleanup. A lesson whose every trigger is a dead glob is reported as unreachable and left intact (stripping its last trigger would strand it); re-point a trigger or deprecate it by hand. The graph is git-tracked, so an applied prune is reviewable in the diff and revertible. See the curation reference.
stats
| Flag | Description |
|---|---|
--json | Emit the raw report object instead of the human summary. |
Recall runs before each edit and each state-changing command (pure-read commands and the recall query itself are exempt), so its frequency — not its per-call payload — is the real token cost. Set AGENTSMESH_LESSONS_TELEMETRY=1 to record one append-only row per recall to .agentsmesh/lessons/recall-log.jsonl (field-presence booleans, match counts, returned-lesson ids, a bypassed flag, and an optional AGENTSMESH_SESSION_ID — never the file / command / keyword text). The same flag enables a symmetric capture log at .agentsmesh/lessons/capture-log.jsonl — one row per agentsmesh lessons add / captureLesson call, recording isNewLesson, isNewTopic, newTriggerCount, triggerKinds counts, blocked (true when the capture was rejected as UNRECALLABLE_LESSON), warningCodes, and optional session / lessonId — never the rule text. Both logs are size-capped — they self-truncate to the most recent records once they grow past the cap, so they never accumulate unbounded in a committed .agentsmesh/. stats then reports the no-match rate, returned-token percentiles, the per-session preload break-even (preload costs the whole active set once per session, so the comparison multiplies by the session count and excludes --all dumps), the intra-session redundancy rate (repeat-delivered rule-tokens — the dedup opportunity), and the keyword-only reachability gap. It also prints a Capture block (included under a capture key in --json output, shown even when only a capture log exists): total captures, blocked count, new-lesson vs. upsert split, new-topics count, warned count, trigger-kind breakdown, and a recall:capture ratio. It also prints an Effectiveness block (effectiveness key in --json) from the outcome log — total deliveries, the coarse held rate (deliveries with no recorded repeat on the same action — a weak upper bound, never presented as proof of prevention), the ineffective-lesson count, and failures observed, with a pointer to lessons validate for the actionable list. When the numbers show one of the two known field pathologies, stats appends an advice line naming the cause and the fix (advice key in --json): high redundancy with session-less recalls → “session dedup is inert; pass --session auto”, and a no-match rate dominated by command-only recalls against a starved command_pattern population → “author --trigger-cmd on command-shaped lessons”. The logs are telemetry, not the canonical graph; agentsmesh init --lessons adds .agentsmesh/lessons/recall-log.jsonl, .agentsmesh/lessons/capture-log.jsonl, and .agentsmesh/lessons/outcome-log.jsonl to your project’s .gitignore automatically, so opted-in logs never dirty your worktree. (Scaffolded lessons before this was automatic? Add those two lines to .gitignore yourself — it is git’s ignore file, not .agentsmesh/ignore, which only controls what AI tools see.)
import-md
| Flag | Description |
|---|---|
--merge | Fold legacy lessons INTO an existing lessons.json (rules dedup by text, triggers content-address and dedup, topics union). The recovery path when a legacy index.yaml is stranded alongside a populated graph — no data loss. |
--force | Overwrite an existing lessons.json. Default behavior (with neither --merge nor --force) is to refuse. |
--migrated-at <ISO date> | Date stamped onto every imported lesson’s createdAt. Defaults to today. |
Diagnosing a recall
When a recall surfaces a rule that does not belong, you do not need to open lessons.json:
agentsmesh lessons query --file <path> --ids— re-run the recall with lesson ids attached (or use--format json).agentsmesh lessons show <lesson-id>— inspect that lesson: its rule, status, topics, and every trigger resolved to its pattern, so you can see exactly which trigger fired.- Fix it in place —
agentsmesh lessons untrigger <lesson-id> <trigger-id>to drop an over-broad trigger, oragentsmesh lessons deprecate <lesson-id>to retire a rule that is wrong or no longer true (a deprecated lesson stops being recalled immediately).
Team workflow
lessons.json is a single git-tracked file, serialized deterministically (stable key order, trailing newline) so lesson changes show up as readable, reviewable diffs in a PR. Two caveats for teams:
-
Parallel captures on separate branches would otherwise conflict at merge time (both edit the same JSON tables). Wire the bundled union merge driver and git resolves them automatically — each branch’s new lessons/topics/triggers are merged by key, with a deprecated lesson winning a divergent edit.
agentsmesh init --lessonsalready commits the.gitattributesbinding for you:.agentsmesh/lessons/lessons.json merge=agentsmesh-lessonsso the whole team inherits it once one person commits it. Each clone then enables the per-clone half once (git cannot auto-run this on clone) —
init --lessonsprints exactly these commands:Terminal window git config merge.agentsmesh-lessons.name "agentsmesh lessons union"git config merge.agentsmesh-lessons.driver "agentsmesh lessons merge-driver %O %A %B"The driver writes the union of both branches whenever it can build one, because a failing driver does not make git re-merge the file with conflict markers — git leaves your side untouched and marks the path unmerged, which would discard every lesson the other branch captured. An empty ancestor (both branches created the graph) and validation errors that already existed on either side are merged cleanly. If the merge itself introduces a validation error, the union is still written and the driver exits non-zero so you review it before staging; only an unreadable side leaves the file untouched.
agentsmesh lessons validate(run in CI viaagentsmesh lint) is the backstop. -
Worktrees / discarded branches: the graph lives in the working tree, so a lesson captured in a worktree or branch that is later discarded is lost with it. Commit captures you want to keep.
See also
- Lessons reference — graph schema, validation codes, and programmatic API.
- agentsmesh init — scaffold the lessons subsystem with
--lessons.