Stop repeating Claude's mistakes. Every session, automatically.
Real /lessons:manage conversation and a real PreToolUse injection, not staged — see website/static/demo.tape to reproduce it.
137 active lessons · 87 eval scenarios measuring whether injection actually changes behavior · 37 pages of docs · 4 platforms (Claude Code, Codex CLI, Gemini CLI, opencode)
# Step 1 — add the marketplace (once per machine)
claude plugin marketplace add joeblackwaslike/agent-marketplace
# Step 2 — install the plugin
claude plugin install lessons-learned@agent-marketplace- Captures mistakes from session logs — structured tags and heuristic scanning
- Injects relevant warnings before tool calls at the exact moment they're needed
- Compounds — every session adds to a persistent store that follows you across projects
Claude is stateless. Every session forgets every correction, every footgun hit, every git stash that silently dropped untracked files. Over long agentic runs, the same class of mistake appears again and again — because nothing carries forward.
lessons-learned creates a persistent, compounding memory of failure patterns. Mistakes are captured automatically from session logs. A background scanner extracts candidates, you promote the ones worth keeping, and the next time Claude is about to make the same move, a warning surfaces at the exact tool call where it matters.
The feedback loop tightens over time. The more sessions, the stronger the prevention.
Step 1 — Claude makes a mistake and emits a lesson tag: this one is real —
genuinely emitted mid-session while building the recording below, after
Claude Code's own Safety Net guard blocked a cd-then-rm -rf Bash call
twice in a row:
#lesson
tool: Bash
trigger: cd <dir> && rm -rf <path>
problem: Combining `cd` into a target directory with `rm -rf` in the same Bash
call gets blocked by Claude Code's Safety Net guard, even when the
resulting path is otherwise a legitimate scratch/temp location — the
guard flags the cd+rm-rf co-occurrence pattern itself, not just
literal out-of-cwd path resolution.
solution: Never combine `cd` and `rm -rf` in the same Bash invocation. Either
`cd` first as its own call (the working directory persists across
Bash calls), then issue `rm -rf` as a separate, later call — or use
`rm -rf <absolute-or-explicit-relative-path>` directly, no `cd`.
tags: tool:bash, tool:safety-net, severity:blocked-workflow
#/lesson
Step 2 — Next session, discover and promote it — captured live and
unstaged (real Claude Code, real plugin, a real scratch database) via
/lessons:manage, one of several /lessons:* commands for browsing,
promoting, and auditing lessons conversationally, no path to your checkout
and no flags to remember:
Reproduce it with vhs website/static/demo-live.tape (requires
vhs and a claude login — budget a
few minutes; it's a real model call, not a replay, so timing and wording vary
between runs).
Equivalent CLI, for local development against a cloned checkout
$ node scripts/lessons.mjs scan --verbose
[scan] New candidates: 1 | Duplicates skipped: 0 | Total in DB: 47
$ node scripts/lessons.mjs review
1 pass, 0 fail
$ node scripts/lessons.mjs promote --ids 01KZHWHQY4MHBKBZR0245NDVV9
Promoted 1 lesson(s). Built manifest: 1 lessons included, 0 excluded.
scripts/lessons.mjs only exists inside a clone of this repo — it's the
plugin's own internals, not something an installed plugin exposes a path to.
Everyday use is /lessons:*; see Slash Commands
for the full set.
Step 3 — Warning fires before the next cd+rm -rf (this is the actual
additionalContext a PreToolUse hook injects, extracted with jq):
<details>
<summary>[lessons-learned] 1 lesson matched for `cd /tmp/scratch && rm -rf old-build` — <em>Why am I seeing this?</em></summary>
The **[lessons-learned](https://github.com/joeblackwaslike/lessons-learned)**
plugin matched this tool call against known pitfall patterns and injected
the following warnings for Claude to consider before executing.
---
## Lesson: Combining `cd` into a target directory with `rm -rf` in the same Bash call gets blocked by Claude Code's Safety Net...
Combining `cd` into a target directory with `rm -rf` in the same Bash call
gets blocked by Claude Code's Safety Net guard, even when the resulting path
is otherwise a legitimate scratch/temp location -- the guard flags the
cd+rm-rf co-occurrence pattern itself, not just literal out-of-cwd path
resolution.
**Fix**: Never combine `cd` and `rm -rf` in the same Bash invocation. Either
`cd` first as its own call (the working directory persists across Bash
calls), then issue `rm -rf` as a separate, later call -- or use
`rm -rf <absolute-or-explicit-relative-path>` directly, no `cd`.
</details>
The demo GIF at the top of this page is one continuous real /lessons:manage
conversation — discovering and promoting a genuinely captured #lesson tag —
then asking that same live session to clean up a scratch build directory.
The just-promoted lesson is genuinely active in that session's manifest, so
real injected context (not a simulation) steers the agent away from
combining cd and rm -rf, the same prevention Step 3 shows the raw
injected text for above, demonstrated live instead of extracted. Reproduce
it with vhs website/static/demo.tape (requires
vhs and a claude login on
PATH; budget a few minutes and real API usage — it's a real model session,
not a replay, so timing and exact wording vary between runs). For reviewing
a full batch of pending candidates one at a time, /lessons:review is the
recommended day-to-day command.
| Platform | Install |
|---|---|
| Claude Code | claude plugin marketplace add joeblackwaslike/agent-marketplace then claude plugin install lessons-learned@agent-marketplace |
| Codex CLI | codex plugin marketplace add joeblackwaslike/agent-marketplace then codex plugin install lessons-learned@agent-marketplace |
| Gemini CLI | Clone repo, run LESSONS_AGENT_PLATFORM=gemini node scripts/lessons.mjs onboard |
| opencode | Same as Claude Code — tool names match |
| Cursor | node scripts/lessons.mjs list --json > .cursorrules |
| Manual/MCP | Coming soon (see Roadmap) |
Requirements: Node.js ≥ 22.5
For manual hook wiring and platform-specific config, see Installation.
graph LR
subgraph "Session Start"
SS1["Inject protocol\nand directives"]
SS2["Background scan\nTier 1/2/3/4"]
SS3["Reset dedup state"]
end
subgraph "Per Tool Call"
PT["matchLessons()\ntool + pattern + path"]
PO["Context monitor\nre-inject at 30/52/70%"]
end
subgraph "Data"
DB[(lessons.db)]
MF["lesson-manifest.json"]
end
SS2 --> DB
DB --> MF
PT --> MF
PO --> MF
- Capture — Claude emits
#lessontags in responses; the background scanner processes previous session JSONL files on startup using up to 4 tiers (structured tags, heuristic patterns, structural insights, LLM deep scan) - Review & promote — Candidates land in
lessons.db;lessons reviewvalidates them (PASS/FAIL, read-only) andlessons promote --ids ...moves the ones worth keeping to active - Build —
lessons buildpre-compiles regexes intolesson-manifest.jsonfor zero-latency runtime lookup - Inject — At each
PreToolUseevent,matchLessons()checks tool name + command patterns + file paths; matching lessons prepend asadditionalContextbefore the tool runs
| Platform | Status | Notes |
|---|---|---|
| Claude Code | First-class | Bash, Read, Edit, Write, Glob |
| Codex CLI | Supported | Same tool names as Claude Code |
| Gemini CLI | Supported | Set LESSONS_AGENT_PLATFORM=gemini |
| opencode | Supported | Same tool names as Claude Code |
| Cursor | Export only | node scripts/lessons.mjs list --json > .cursorrules |
| MCP | Roadmap | Universal adapter planned |
| Feature | Status |
|---|---|
| PreToolUse lesson injection | ✅ |
| Session-start protocol injection | ✅ |
| Guard lessons (block tool calls) | ✅ |
| 4-tier background scanning (T1/T2/T3/T4 LLM) | ✅ |
| Incremental scanning with byte offsets | ✅ |
| Confidence and priority scoring | ✅ |
| 3-layer atomic dedup | ✅ |
| Budget-aware injection (3 lessons / 4 KB) | ✅ |
| PostToolUse context re-injection at 30/52/70% | ✅ |
| Subagent lesson protocol | ✅ |
| Cross-platform (CC / Codex / Gemini / opencode) | ✅ |
| MCP server adapter | 🗺 Roadmap |
| LLM-assisted candidate classification | 🗺 Roadmap |
| Project stack auto-detection | 🗺 Roadmap |
Injecting a warning is easy. Knowing whether it changed the agent's behavior is the hard part — so lessons-learned has an eval harness for that, not just for the plugin's plumbing.
87 hand-crafted scenarios in evals/, each graded on 3 tiers:
| Tier | Checks | How |
|---|---|---|
| 1 | Filesystem/command outcome | A deterministic hidden-checks/verify.mjs |
| 2 | Tool-call sequence | Declarative trajectory rules in scenario.json |
| 3 | Did the lesson change behavior? | An LLM judge compares the agent with vs. without the lesson injected |
Every scenario runs a control arm (no lesson) against a treatment arm (lesson injected) so a pass means the lesson caused the fix, not that the model would have gotten it right anyway.
That measurement infrastructure has caught real bugs in itself. The eval
provider once silently ran the agent arm on a different, stronger model than
the run was labeled and cached under — so a 2026-06 full-suite result reading
"~65% of lessons are obsolete" was confounded, and every archive decision from
that run had to be treated as provisional until re-validated on a pinned
model. That re-validation is now complete. From
evals/FINDINGS.md:
30/35 originally-archived lessons reproduced
CONTROL_CORRECTonclaude-sonnet-4-6(genuinely obsolete, not an Opus artifact) ... One lesson (H12, bareexcept) was restored and rewritten —except Exceptionis still a real hazard on Sonnet, not obsolete.
The fix (pin the agent model, clear the cache, re-validate) is now a
documented, repeatable process — see Pruning Obsolete Lessons.
Lessons the model has genuinely outgrown are archived, not deleted, into an
append-only obsoleted-lessons.json ledger so
a future model regression can restore them.
Edit data/config.json directly. Every field has a LESSONS_* env var equivalent that takes precedence.
| Field | Default | Description |
|---|---|---|
injectionBudgetBytes |
4096 |
Max bytes per injection payload |
maxLessonsPerInjection |
3 |
Max lessons per tool call |
minConfidence |
0.5 |
Exclude lessons below this confidence |
minPriority |
1 |
Exclude lessons below this priority |
compactionReinjectionThreshold |
7 |
Re-inject after N tool calls |
scanPaths |
~/.claude/projects/ |
Where to find session JSONL files |
Tier 4 LLM deep scan fires automatically at session start when an API key is available:
echo "sk-ant-..." > data/.api-key # gitignored; scoped to deep scan onlyCost: ~$0.10–0.25/day at Haiku rates. Throttled to once per 24 hours.
npm test # 297 tests at time of writing
npm run test:unit # unit tests only (fast)
npm run test:integration # integration tests
npm run lint # eslint
npm run typecheck # tsc --noEmitFor evals (auth via your claude login session, no env vars required):
cd evals
npx promptfoo eval --config promptfooconfig.yaml --filter-pattern "TC-H1"See docs for the full reference and CONTRIBUTING.md for the dev workflow.
MIT © Joe Black

