Turn AI coding sessions into grounded, textbook-quality lessons via algorithmic knowledge-graph extraction.
Project description
lesson
An AI coding plugin that turns real working sessions into grounded, reusable lessons.
lesson watches tool activity inside a live AI session, keeps a compact record of the important turns, and turns the final arc into a lesson built from your actual files, commands, errors, and wrong assumptions.
Instead of generating a generic tutorial from scratch, it produces a lesson about what really happened:
- what you were trying to do
- where it broke
- which concept was actually missing
- why the misconception was believable
- what fixed it
- how to test whether you now understand it
/lesson react useEffect infinite loop when depending on an object
# work normally
/lesson-done
.claude/lessons/
├── sessions/<slug>/
│ ├── meta.json
│ ├── arc.jsonl
│ ├── session_graph.json # created after the first compression cycle
│ └── arc.jsonl.archive.N
└── output/
├── <slug>.md
├── <slug>.pdf # optional
├── index.html
└── map.html
Why This Exists
Most learning tools start with a topic and invent an explanation.
lesson starts with a real failure path.
That difference matters. A working session contains high-quality teaching signal: the commands you ran, the outputs you misread, the hypotheses you formed, the file edits you tried, and the observation that finally changed your mind. That is the raw material a serious lesson should be built from.
| Generic tutorial | lesson |
|---|---|
| Starts from an abstract topic | Starts from your actual session |
| Uses canned examples | Uses your files, errors, and commands |
| Explains the concept in general | Explains why you got stuck |
| Often guesses at relevance | Has a concrete arc to teach from |
Platform Support
Works across 10 AI coding platforms. One install script, one core format.
| Platform | Hook support | Config location | Command prefix |
|---|---|---|---|
| Claude Code | PostToolUse + Stop (automatic) | ~/.claude/hooks.json |
/lesson |
| Codex | None (manual logging) | ~/.codex/CODEX.md |
$lesson |
| Cursor | None (manual logging) | <project>/.cursor/rules/lesson.mdc |
/lesson |
| Gemini CLI | BeforeTool (optional) | ~/.gemini/GEMINI.md |
/lesson |
| GitHub Copilot CLI | None (manual logging) | ~/.github/copilot-instructions.md |
/lesson |
| OpenCode | None (manual logging) | ~/.opencode/OPENCODE.md |
/lesson |
| OpenClaw | None (manual logging) | ~/.claw/CLAW.md |
/lesson |
| Factory Droid | None (manual logging) | ~/.droid/DROID.md |
/lesson |
| Trae | None (manual logging) | ~/.trae/TRAE.md |
/lesson |
| Google Antigravity | None (manual logging) | <project>/.agent/lesson.md |
/lesson |
On platforms with hooks (Claude Code, Gemini), event logging is automatic. On all others, the AI logs significant events manually to arc.jsonl during the session.
What Ships Today
- Silent-by-default tracking. Once
/lessonstarts, the plugin never speaks to the main conversation until you call it back. No reminders, no exit blocks, no model nags. - Session tracking via hooks (Claude Code, Gemini) or manual LLM logging (all other platforms)
- Deterministic compression —
lesson compressCLI runsEventGraphBuilderin ~50 ms with zero LLM tokens. The PostToolUse hook spawns it in a detached subprocess at the 25-event threshold. - Session knowledge graph (
session_graph.json) — structured causal record of the session - Cross-session learner profile at
~/.claude/lessons/profile.json— tracks recurring misconceptions and concepts across all projects and platforms - "You've hit this before" callout when the same misconception recurs
- Per-session token tracking with cost estimates
- Canonical markdown lesson output with YAML frontmatter
- Best-effort PDF rendering with Mermaid diagrams rendered as SVG
/regenerate [notes]to rewrite the latest lesson with new guidance/lesson resumeto continue a paused session/lesson-profileto display your learning history and token usage/lesson-indexto build a browsable lesson list/lesson-mapto build a concept map across generated lessonslessonCLI — use the compression and graph tooling standalone, outside of any AI assistant- Eval framework — measure compression quality (node F1, edge accuracy, graph quality score)
Install
Requirements
- Python 3.10+
- An AI coding platform listed above
- Optional for PDF export:
npxwith@mermaid-js/mermaid-clipandocplusweasyprint,wkhtmltopdf,xelatex, orpdflatex- or a Chromium-based browser for the fallback renderer
Python Package
pip install lesson-ai # core (networkx, pydantic, typer, rich, plotly)
pip install "lesson-ai[nlp]" # + semantic deduplication (numpy, spacy, sentence-transformers)
Or install from source:
git clone https://github.com/OussemaBenAmeur/lesson.git
cd lesson
pip install -e . # core
pip install -e ".[nlp]" # + semantic deduplication
pip install -e ".[dev]" # + pytest, hypothesis
Claude Code — From GitHub Marketplace
Inside Claude Code:
/plugin marketplace add OussemaBenAmeur/lesson
/plugin install lesson
All Platforms — Install Script
# List supported platforms
python3 scripts/install.py --list
# Install for your platform
python3 scripts/install.py --platform claude-code
python3 scripts/install.py --platform cursor # run inside a project directory
python3 scripts/install.py --platform gemini
python3 scripts/install.py --platform codex
# ... etc
Restart your AI assistant after install. On Claude Code, hooks are registered at session start.
Quick Start
1. Start tracking
/lesson python asyncio task never awaited explain from scratch
The argument is optional but guides how the final lesson is written.
/lesson linux driver mismatch explain from first principles
/lesson react stale closure I know hooks basics, focus on the bug pattern
/lesson
2. Work normally
Read files. Edit code. Run commands. Fail. Recover. Try again.
On Claude Code and Gemini, the hook logs tool events into arc.jsonl automatically and — at the 25-event threshold — silently runs lesson compress as a detached subprocess. On other platforms, the AI logs events itself and calls lesson compress inline. Either way, the main conversation stays silent until you invoke a command.
3. Generate the lesson
/lesson-done
Writes the final markdown lesson to .claude/lessons/output/<slug>.md and (if PDF tooling is available) .claude/lessons/output/<slug>.pdf.
4. Refine or explore
/regenerate make the foundations deeper and the quiz harder
/lesson-profile
/lesson-index
/lesson-map --last 10
Command Reference
| Command | Purpose |
|---|---|
/lesson [notes] |
Start a tracked learning session |
/lesson-done |
Generate the lesson from the active session |
/regenerate [notes] |
Rebuild the most recent lesson with new instructions |
/lesson resume |
Resume tracking on the most recent session |
/lesson-profile |
Display learning history and token usage |
/lesson-index |
Build output/index.html listing generated lessons |
/lesson-map [flags] |
Build output/map.html connecting lessons by concepts |
Supported /lesson-map filters: --last N, --since YYYY-MM-DD, --slugs slug1,slug2,..., --tag keyword
CLI (lesson command)
The Python package ships a standalone CLI that works outside of any AI assistant. Useful for triggering compression from CI, inspecting graphs, or integrating with other tools.
lesson start "fix asyncio blocking issue" # create a new session
lesson compress # fold arc.jsonl into session_graph.json (~50ms, zero tokens)
lesson stats # print graph metrics
lesson graph # open interactive Plotly graph in browser
lesson graph --mermaid # print Mermaid syntax
lesson graph --dot # print DOT syntax
lesson resume # re-activate the last session
lesson done # final compression + instructions for /lesson-done
The lesson compress command runs EventGraphBuilder — a deterministic pipeline:
arc.jsonl events
→ SignificanceScorer (TF-IDF novelty × error signal × edit signal × version signal → float)
→ top candidates (score ≥ 0.25, max 12 per batch)
→ _classify() → NodeType
→ NodeEmbedder deduplication (optional, requires lesson[nlp])
→ edge inference from (prev.type, curr.type, is_error)
→ betweenness centrality → root_cause_id
This is the sole compression path across all platforms — no LLM subagent is involved.
What The Plugin Writes
All runtime state lives in the target project's .claude/lessons/ directory (or .cursor/lessons/ on Cursor). The learner profile is global at ~/.claude/lessons/profile.json.
.claude/lessons/
├── active-session
├── last-session
├── sessions/<slug>/
│ ├── meta.json # goal, notes, timestamps, cwd, platform, token_tracking
│ ├── arc.jsonl # raw event log since last compression
│ ├── session_graph.json # structured causal graph (created after first compression)
│ ├── counter
│ └── arc.jsonl.archive.<N>
└── output/
├── <slug>.md
├── <slug>.pdf # optional
├── index.html
└── map.html
~/.claude/lessons/
└── profile.json # cross-session learner memory (global, shared across platforms)
Recommended .gitignore entries:
.claude/lessons/active-session
.claude/lessons/sessions/
Version output/ if you want lessons to live with the repo.
What A Lesson Contains
The markdown template in templates/lesson.md.tmpl produces:
- YAML frontmatter: slug, concept, date, goal, root_cause, tags
- Recurring misconception callout if this pattern was seen before (
profile.json) - Session narrative grounded in the real failure
- Foundations section built from lower-level prerequisites, explained from scratch
- Concept explanation with citations when research was needed
- Mermaid concept diagram
- Mermaid debug-path flowchart derived from the session graph
- Fix explanation and verbatim fix snippet
- Quiz with immediately visible answers (no hidden spoilers)
- Optional resources section
Learner Profile
After each /lesson-done, the plugin updates ~/.claude/lessons/profile.json:
{
"schema_version": "1",
"total_sessions": 4,
"misconceptions": [
{
"concept": "kernel module / userspace version coupling",
"count": 2,
"last_seen": "2026-04-16",
"slug": "20260416-010610",
"project": "/home/user/myproject"
}
],
"learned_concepts": [
{ "concept": "DKMS", "date": "2026-04-16", "slug": "20260416-010610" }
],
"aggregate_tokens": {
"total_estimated": 187000,
"sessions": 4
}
}
When the same misconception appears in a new session, the lesson adds a callout:
Pattern detected: You've encountered this misconception before (last seen: 2026-04-10, session
20260310-153200). This lesson explains why it keeps appearing.
Run /lesson-profile to see your full history.
Token Tracking
Every session records token usage estimates in meta.json under token_tracking:
{
"arc_input_chars": 84000,
"compression_cycles": 3,
"graph_output_chars": 4200,
"estimated_tokens": {
"hook_logged": 21000,
"compression_input": 7500,
"lesson_done_input": 12400,
"lesson_done_output": 2450,
"total": 52150
}
}
/lesson-profile shows per-session and aggregate token estimates with cost at Sonnet pricing.
How It Works
Four rules govern the architecture:
- Silent by default. The plugin only speaks when explicitly invoked (
/lesson*,/lesson-done). No hook-driven model reminders, no blocking exit. - Hooks stay dumb. No LLM calls, no blocking, no side effects beyond writing
arc.jsonland spawning a detached compression subprocess. - The main AI conversation stays lean. The deterministic compressor folds raw events into the graph so the main context only ever sees structured nodes and edges.
- The lesson must be grounded or it must not be written. Generation stops rather than inventing content when session data is insufficient.
Flow (Claude Code)
/lesson
-> commands/lesson.md
-> writes session files + active-session marker
normal work
-> hooks/post_tool_use.py logs compact events to arc.jsonl
-> every N events: spawns `lesson compress` in a detached subprocess
(zero tokens, no output to the main conversation)
compression cycle
-> EventGraphBuilder reads arc.jsonl
-> extends session_graph.json with nodes and edges
-> archives consumed events to arc.jsonl.archive.N
/lesson-done
-> commands/lesson-done.md reads graph + tail events
-> quality guard: refuses to generate from thin sessions
-> reads ~/.claude/lessons/profile.json for recurring patterns
-> decides whether web grounding is needed
-> fills templates/lesson.md.tmpl
-> writes output/<slug>.md
-> scripts/render_pdf.py tries to create output/<slug>.pdf
-> updates profile.json + token_tracking
Flow (platforms without hooks)
Same flow, but the AI logs events to arc.jsonl manually after each significant tool call and calls lesson compress (or builds the graph inline) at the 25-event threshold and at /lesson-done time. No LLM subagent is involved on any platform.
Repo Components
| File | Purpose |
|---|---|
hooks/post_tool_use.py |
Append-only event logger; spawns the silent compression subprocess at threshold |
hooks/stop.py |
Passive session-end nudge (never blocks exit) |
commands/lesson.md |
Session initialization |
commands/lesson-done.md |
Final lesson generation |
commands/regenerate.md |
Regeneration flow |
commands/lesson-resume.md |
Session resume |
commands/lesson-profile.md |
Learner profile display |
commands/lesson-index.md |
Lesson listing page |
commands/lesson-map.md |
Cross-lesson concept map |
templates/lesson.md.tmpl |
Lesson markdown template |
scripts/render_pdf.py |
Mermaid-to-PDF pipeline |
scripts/install.py |
Multi-platform install dispatcher |
skills/skill-<platform>.md |
Per-platform skill file |
lesson/ |
Python package — algorithmic compression, CLI, graph algorithms |
eval/ |
Compression quality metrics and benchmark |
tests/ |
Unit + integration tests (pytest) |
docs/architecture.md |
Design notes and system rationale |
Configuration
All settings are optional environment variables.
| Variable | Default | Effect |
|---|---|---|
LESSON_COMPRESS_EVERY |
25 |
Events between compression runs |
LESSON_SILENT_HOOK |
1 |
When 1 (default) the PostToolUse hook spawns lesson compress silently. Set to 0 for debugging to restore the legacy additionalContext reminder. |
LESSON_STOP_MIN_EVENTS |
5 |
Min events before the Stop hook emits its passive nudge |
LESSON_MIN_EVENTS |
8 |
Min events before /lesson-done warns of thin session |
CLAUDE_PLUGIN_ROOT |
(auto) | Path to plugin root, used in PDF generation |
The lesson compress CLI also accepts --threshold (default 0.25) and --no-embed flags.
PDF Export
Markdown is the canonical output. PDF generation is optional and never blocks lesson creation.
scripts/render_pdf.py pipeline:
- Extract Mermaid blocks from the lesson markdown
- Render each diagram to SVG via
npx @mermaid-js/mermaid-cli - Convert processed markdown to PDF via
pandoc, or fall back to Chromium headless
If required tools are missing, the plugin succeeds with the markdown lesson only.
Privacy and Trust
- Hooks only read stdin and write under
.claude/lessons/— no network calls - Tracking is a no-op unless
.claude/lessons/active-sessionexists /lesson-donemay use WebSearch/WebFetch when the concept needs external grounding- The learner profile (
profile.json) lives only on your local filesystem - All prompts, templates, hook code, and the renderer are plain text and easy to audit
Troubleshooting
/lesson is not available after install
Restart your AI assistant. Hooks and commands are registered at session start.
/lesson-done says there is no active session
.claude/lessons/active-session is missing. Run /lesson to start a new session, or /lesson resume to bring back the last one.
The Stop hook keeps nudging me
The nudge is a single non-blocking line. If you want silence even at Stop, delete .claude/lessons/active-session to abandon the session, or raise LESSON_STOP_MIN_EVENTS so the nudge only fires on substantial sessions.
PDF export does not appear The markdown lesson is still valid. Install optional Mermaid and PDF tooling to get PDFs.
Running on a platform without hooks — events not being logged The AI should be logging events manually. If it isn't, mention it in your prompt or add a note to your platform's skill file.
Contributing
Issues and PRs are welcome.
Design constraints worth preserving:
- hooks must stay fast and LLM-free
- the main AI conversation should not read raw session logs directly
- graph compression should stay explicit and inspectable
- lessons should stay grounded in real session data
- graceful degradation is better than hidden failure
- platform skill files should share the same core lesson format
For a deeper technical overview, read docs/architecture.md.
Further Reading
- docs/architecture.md — design notes, data flow, schema reference, rationale
- docs/how-it-works.md — pedagogical deep dive from hook to lesson
- docs/blog-article.md — short manifesto on why grounded lessons beat generic tutorials
- CHANGELOG.md — release history
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lesson_ai-0.3.2.tar.gz.
File metadata
- Download URL: lesson_ai-0.3.2.tar.gz
- Upload date:
- Size: 34.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fa1c6fe041515f77dd345d0f4e0ede40f1f10f944634b2672800555ea000ed5d
|
|
| MD5 |
1aa9f0f45794770b725fc1a318e7316f
|
|
| BLAKE2b-256 |
0c46628169e0b8da3ea301b75cfa19685fe5c1683de114e899afbf2c8905c32f
|
File details
Details for the file lesson_ai-0.3.2-py3-none-any.whl.
File metadata
- Download URL: lesson_ai-0.3.2-py3-none-any.whl
- Upload date:
- Size: 31.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1ab9928f70545d15a0872c5ad630815034e807d1a1dab39721c4c256a568ce48
|
|
| MD5 |
2c9089422aa20d55ed2aaec12c76aca9
|
|
| BLAKE2b-256 |
bedca9d6347179ecf2f8741489dac1059d0d9cefa36f33c59b2ab07708c0b0dc
|