Skip to main content

Data Model

All data lives in data/. The SQLite database (lessons.db) is the source of truth; JSON files are either generated artifacts or human-editable config. See also Architecture: Data Model for the system-design view of the same records, and the Schema Reference for the authoritative field-by-field JSON Schema.

FileEdit directly?Purpose
lessons.dbNo (use CLI)Source of truth — all lesson and candidate records (SQLite)
lesson-manifest.jsonNoPre-compiled runtime manifest (run lessons build)
config.jsonYesInjection and scanning configuration
scan-state.jsonNoPer-file byte offsets for incremental scanning

There is no lessons.json file — this page shows lesson records as JSON for readability, but they are rows in lessons.db, edited through the CLI (add, edit, promote) rather than by hand.


Lesson record

{
// ULID, generated on add
"id": "01JQSEED00000000000000001",

// kebab-case summary + 4-char random suffix
"slug": "pytest-tty-hanging-k9m2",

// candidate | reviewed | active | disabled | archived
"status": "active",

// directive | guard | hint | protocol — the authoritative injection behavior
"type": "guard",

// One-line description, max 120 chars
"summary": "pytest hangs in non-interactive envs due to TTY detection",

// Root cause (required, min 20 chars)
"problem": "Running bare `pytest` in Claude Code causes the process to hang...",

// Concrete fix (required, min 20 chars)
"solution": "Use `python -m pytest --no-header -p no:faulthandler`",

// Exact tool name match — fires on any use of these tools
"toolNames": ["Bash"],

// Regex patterns matched against the Bash command string
// Invalid regex is rejected at add time
"commandPatterns": ["\\bpytest\\b(?!.*(--no-header|-p no:faulthandler))"],

// "full" (default) or "executable" — executable strips quoted strings
// before matching commandPatterns, so guards don't fire on quoted JSON values
"commandMatchTarget": "executable",

// Regex patterns AND-gated against the command or file path
"modelPatterns": [],

// Glob patterns matched against Read/Edit/Write file paths
"pathPatterns": [],

// null = global (default). A project-ID string scopes to that project only
"scope": null,

// 1-10; higher priority wins budget conflicts
"priority": 8,

// 0.0-1.0; lessons below config.minConfidence excluded from manifest
"confidence": 0.95,

// Excludes the lesson from the manifest unless the named artifact is installed
"requires": null,

// Excludes the lesson from the manifest when the named artifact IS installed
"duplicatedBy": null,

// category:value taxonomy
"tags": ["lang:python", "tool:pytest", "severity:hang"],

// structured | heuristic | manual
"source": "manual",

// Session IDs where this mistake was observed
"sourceSessionIds": [],

// How many times this pattern was seen across all scans
"occurrenceCount": 0,
"sessionCount": 0,
"projectCount": 0,

"createdAt": "2026-08-01T00:00:00Z",
"updatedAt": "2026-08-01T00:00:00Z",

// sha256 of (problem + solution + triggers) — used for dedup at scan time
"contentHash": "sha256:...",
}

For guard-type lessons, the rendered message supports a {command} placeholder substituted with the actual command (truncated to 120 chars) at block time.

Field constraints

FieldConstraint
summary>= 20 chars, no ... suffix, no template placeholders
problem>= 20 chars, no template placeholders
solution>= 20 chars, no template placeholders
commandPatternsValid regex — invalid patterns rejected at add time, dropped (with a warning) at build time
type = 'protocol'Use sparingly — fires on every session startup with no per-call dedup
confidenceBelow minConfidence → excluded from the manifest at build time

Lesson manifest

Generated by lessons build. This is what the injection hook reads at runtime.

{
"type": "lessons-learned-manifest",
"version": 1,
"generatedAt": "2026-08-01T00:00:00Z",
"config": {
"injectionBudgetBytes": 4096,
"maxLessonsPerInjection": 3,
"minConfidence": 0.5,
"minPriority": 1,
"compactionReinjectionThreshold": 7,
},
"lessons": {
"<ulid>": {
"slug": "pytest-tty-hanging-k9m2",
"type": "guard",
"priority": 8,
"toolNames": ["Bash"],
// Regex stored as {source, flags} for JSON-safe serialization
// Reconstructed with new RegExp(source, flags) at match time
"commandRegexSources": [{ "source": "\\bpytest\\b(?!...)", "flags": "" }],
"commandMatchTarget": "executable",
"pathRegexSources": [],
"modelRegexSources": [],
"tags": ["lang:python", "tool:pytest", "severity:hang"],
"scope": null,
"message": "## REQUIRED: pytest flags...",
"summary": "pytest hangs in non-interactive envs due to TTY detection",
"problem": "...",
"solution": "...",
},
},
}

Exclusion criteria — lessons are excluded from the manifest if any of:

  • confidence < config.minConfidence
  • priority < config.minPriority
  • a duplicatedBy artifact is detected as installed
  • a requires artifact is NOT detected as installed
  • status === 'disabled'

Regex serializationcommandRegexSources stores { source, flags } objects rather than regex strings. RegExp is not JSON-serializable. At match time, the hook reconstructs regex objects with new RegExp(source, flags).


Config

{
// Max bytes of additionalContext per tool call
"injectionBudgetBytes": 4096,

// Max lessons injected per tool call
"maxLessonsPerInjection": 3,

// Exclude lessons below this confidence from manifest
"minConfidence": 0.5,

// Exclude lessons below this priority from manifest
"minPriority": 1,

// Lessons above this priority re-inject after context compaction
"compactionReinjectionThreshold": 7,

"scanPaths": ["~/.claude/projects/"],
"autoScanIntervalHours": 24,
"maxCandidatesPerScan": 50,

"scoring": {
"multiSessionBonus": 2,
"multiProjectBonus": 1,
"hangTimeoutBonus": 1,
"userCorrectionBonus": 1,
"singleOccurrencePenalty": -1,
},
}

Candidate records

Candidates are ordinary rows in lessons.db with status='candidate' — there is no separate candidate file. node scripts/lessons.mjs scan aggregate reads them and prints a ranked JSON view to stdout; /lessons:review and promote --ids <id> act on the same rows.

{
"generatedAt": "2026-08-01T00:00:00Z",
"totalCandidates": 1,
"candidates": [
{
// 1-based position in THIS output only — not stored, not a valid handle for any command
"index": 1,
// the real, stable handle — use this with `promote --ids`
"id": "01JQSEED00000000000000001",
"slug": "git-stash-untracked-5x3q",
"tool": "Bash",
"confidence": 0.75,
"priority": 6,
"occurrenceCount": 3,
// Distinct sessions where this pattern was observed
"sessionCount": 2,
// Distinct projects where this pattern was observed
"projectCount": 1,
"problem": "...",
"solution": "...",
"tags": [],
"sourceSessionIds": ["..."],
"createdAt": "...",
},
],
}

Scope inference (informal, for review purposes):

  • projectCount >= 2 → likely a global lesson candidate
  • projectCount === 1 → likely a project-specific lesson candidate (/lessons:scope can scope an already-active lesson after the fact)

To promote a candidate, use node scripts/lessons.mjs promote --ids <id>scan promote <index> has been removed.


Scan state

{
"files": {
"/abs/path/to/session.jsonl": 184320,
},
"lastFullScanAt": "2026-08-01T00:00:00Z",
}

Each file maps to the last byte offset read. The scanner resumes from this point on the next incremental scan. A --full flag resets all offsets to 0 for a complete re-scan.


Tag taxonomy

Tags follow category:value format:

CategoryExamples
langpython, typescript, javascript, go
toolpytest, git, npm, docker
severityhang, data-loss, silent-failure, error
topictesting, auth, networking, types, agents
candidatenode-gotchas-skill — flagged for future skill aggregation

See the full tag reference.