claude_memory 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/memory.sqlite3 +0 -0
  3. data/.claude/rules/claude_memory.generated.md +44 -48
  4. data/.claude/settings.local.json +2 -1
  5. data/.claude-plugin/marketplace.json +2 -2
  6. data/.claude-plugin/plugin.json +3 -5
  7. data/CHANGELOG.md +52 -0
  8. data/CLAUDE.md +13 -8
  9. data/README.md +46 -0
  10. data/db/migrations/019_add_observations.rb +43 -0
  11. data/db/migrations/020_add_observation_promotion.rb +33 -0
  12. data/docs/GETTING_STARTED.md +38 -0
  13. data/docs/api_stability.md +23 -7
  14. data/docs/architecture.md +18 -6
  15. data/docs/audit_runbook.md +67 -0
  16. data/docs/dashboard.md +28 -0
  17. data/docs/improvements.md +94 -1
  18. data/docs/influence/mastra-observational-memory.md +198 -0
  19. data/docs/influence/strands-agent-sops.md +163 -0
  20. data/docs/quality_review.md +45 -0
  21. data/docs/soak/audit_2026-06-03_agent-training-program.json +53 -0
  22. data/docs/soak/audit_2026-06-03_agentic.json +31 -0
  23. data/docs/soak/audit_2026-06-03_ai-software-architect.json +19 -0
  24. data/docs/soak/audit_2026-06-03_chaos_to_the_rescue.json +60 -0
  25. data/docs/soak/audit_2026-06-03_claude_memory.json +55 -0
  26. data/docs/soak/audit_2026-06-03_daily-vibe.json +59 -0
  27. data/docs/soak/audit_2026-06-03_minerva-sky.json +19 -0
  28. data/docs/soak/audit_2026-06-03_nowreading.dev.json +19 -0
  29. data/docs/soak/audit_2026-06-03_ups.dev.json +55 -0
  30. data/docs/soak/baseline_2026-06-03.md +145 -0
  31. data/lib/claude_memory/audit/checks.rb +149 -0
  32. data/lib/claude_memory/audit/runner.rb +4 -0
  33. data/lib/claude_memory/commands/census_command.rb +1 -1
  34. data/lib/claude_memory/commands/checks/embeddings_check.rb +97 -0
  35. data/lib/claude_memory/commands/doctor_command.rb +1 -0
  36. data/lib/claude_memory/commands/hook_command.rb +16 -3
  37. data/lib/claude_memory/commands/initializers/hooks_configurator.rb +3 -1
  38. data/lib/claude_memory/commands/install_skill_command.rb +4 -0
  39. data/lib/claude_memory/commands/observations_command.rb +367 -0
  40. data/lib/claude_memory/commands/registry.rb +2 -0
  41. data/lib/claude_memory/commands/setup_vectors_command.rb +182 -0
  42. data/lib/claude_memory/commands/skills/reflect.md +68 -0
  43. data/lib/claude_memory/commands/stats_command.rb +60 -1
  44. data/lib/claude_memory/dashboard/api.rb +4 -0
  45. data/lib/claude_memory/dashboard/index.html +154 -2
  46. data/lib/claude_memory/dashboard/observations.rb +115 -0
  47. data/lib/claude_memory/dashboard/server.rb +1 -0
  48. data/lib/claude_memory/distill/extraction.rb +6 -4
  49. data/lib/claude_memory/distill/null_distiller.rb +86 -3
  50. data/lib/claude_memory/distill/reference_material_detector.rb +4 -1
  51. data/lib/claude_memory/domain/observation.rb +118 -0
  52. data/lib/claude_memory/embeddings/generator.rb +1 -1
  53. data/lib/claude_memory/hook/context_injector.rb +100 -2
  54. data/lib/claude_memory/mcp/handlers/management_handlers.rb +113 -2
  55. data/lib/claude_memory/mcp/handlers/query_handlers.rb +48 -1
  56. data/lib/claude_memory/mcp/instructions_builder.rb +1 -0
  57. data/lib/claude_memory/mcp/query_guide.rb +28 -0
  58. data/lib/claude_memory/mcp/tool_definitions.rb +58 -0
  59. data/lib/claude_memory/mcp/tools.rb +3 -0
  60. data/lib/claude_memory/observe/observations_renderer.rb +49 -0
  61. data/lib/claude_memory/observe/reflector.rb +91 -0
  62. data/lib/claude_memory/publish.rb +53 -1
  63. data/lib/claude_memory/resolve/resolver.rb +45 -8
  64. data/lib/claude_memory/store/schema_manager.rb +1 -1
  65. data/lib/claude_memory/store/sqlite_store.rb +181 -0
  66. data/lib/claude_memory/sweep/maintenance.rb +15 -1
  67. data/lib/claude_memory/sweep/sweeper.rb +7 -1
  68. data/lib/claude_memory/version.rb +1 -1
  69. data/lib/claude_memory.rb +7 -0
  70. metadata +23 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 935474b9efd1d9fd317c410728ed36b1cedb0854d1db6e71cd45ce6372253b9c
4
- data.tar.gz: 1514d8b44e8ee25d139cd0544ab527bfa00ff54ace9786b7c47709afc0095024
3
+ metadata.gz: 93e553fc87bd36ebe27b8709a28dea9bd206a669491327fc8b6340e6078a8f67
4
+ data.tar.gz: c19722c5028ed5746f58792bb34e57939600deaa0a59b6ff00a421b3ed829bdc
5
5
  SHA512:
6
- metadata.gz: f70d8858628ecbd350e32f89da3dcdd8ff99ff2c61c85dc938a512ab79f9d6bae46d87677168a7d9059fdace727657e388aeed48a451ae8340ac2af5dd9384b8
7
- data.tar.gz: 3164ae23db7e7b8fd66ece557a84953727181e2b06d2db80c4a807678015b064f196489768fb50c980c7d1de8f732b86dd5c0666d9f766a0d05ce52757f1b23d
6
+ metadata.gz: 7bd3a98c636c525e2bfc579e9e265ecef3a07311d4ba543d692abc092ddc0822df86832b9e5b53d802597a6dbe7495aa192210d6d4e5961c21383daac22cef09
7
+ data.tar.gz: b587239c11c342551f35937ecdbb108254269306fa98fbaac6680d23f8e427a32f26cbb1524c8f19a08da1773e292b6d75df92e101cb724d59fd65017f275832
Binary file
@@ -1,69 +1,65 @@
1
1
  <!--
2
2
  This file is auto-generated by claude-memory.
3
3
  Do not edit manually - changes will be overwritten.
4
- Generated: 2026-06-01T11:23:10Z
4
+ Generated: 2026-06-16T18:31:16Z
5
5
  -->
6
6
 
7
7
  # Project Memory
8
8
 
9
9
  ## Current Decisions
10
10
 
11
- - MCP tool-call telemetry stores minimal columns (tool_name, duration_ms, result_count, scope, error_class) deliberately no query_text or query_hash. YAGNI: hashes are write-only without the raw text, and raw text adds privacy concerns without clear value beyond existing shortcut tools (memory.decisions, memory.conventions).
12
- - From QMD 2026-02-02 restudy: adopt Claude Code plugin format, MCP structured content pattern, MCP query guide prompt, inline status checks. Carry forward sqlite-vec, RRF, docids, smart expansion from 2026-01-26. Reject custom fine-tuned models, LLM reranking, YAML collections.
13
- - From claude-supermemory study: adopt SessionStart hook context injection (hookSpecificOutput.additionalContext), tool-specific observation compression, and relative time formatting. Reject cloud storage dependency and no-test approach.
11
+ - From Mastra Observational Memory study: add an episodic observation layer that augments (not replaces) the dynamic-recall semantic fact store because facts answer 'what is true' and observations answer 'what happened', and claude_memory currently lacks the episodic half; recall stays for targeted lookups.
12
+ - From Mastra Observational Memory study: make observation reflection automatic via the PreCompact and SessionEnd hooks rather than a manual-only skill because Claude Code exposes no timer/cron hook, but PreCompact fires on context pressure (the analog of Mastra's token threshold) and rides the existing session at no extra API cost.
13
+ - From Mastra Observational Memory study: run the Reflector's deterministic GC shell-side in Ruby and its semantic consolidation via PreCompact additionalContext (Claude-as-reflector inline) — to keep automatic reflection within the no-extra-API-cost convention, explicitly rejecting Claude Code Routines and subagents because each incurs a separate token budget.
14
+ - From Mastra Observational Memory study: tombstone superseded observations via a consolidated_into link rather than hard-deleting them (unlike Mastra's lossy drop) — to preserve claude_memory's provenance guarantee while still bounding context size.
15
+ - From Mastra Observational Memory study: promote an observation to a structured fact only after corroboration across multiple observations — because requiring repeated sightings before commitment doubles as an anti-hallucination gate against reject-churn from one-off doc/example text.
16
+ - MCP tool-call telemetry stores minimal columns (tool_name, duration_ms, result_count, scope, error_class) — deliberately no query_text or query_hash. YAGNI: hashes are write-only without the raw text, and raw text adds privacy concerns without clear value beyond existing shortcut tools (memory.decisions, memory.conventions). — to avoid storing query hashes that are write-only without the raw text.
17
+ - From QMD 2026-02-02 restudy: adopt Claude Code plugin format, MCP structured content pattern, MCP query guide prompt, inline status checks. Carry forward sqlite-vec, RRF, docids, smart expansion from 2026-01-26. Reject custom fine-tuned models, LLM reranking, YAML collections. — to align with our pure-Ruby/SQLite constraints (no GGUF, no LLM-side fine-tuning).
18
+ - From claude-supermemory study: adopt SessionStart hook context injection (hookSpecificOutput.additionalContext), tool-specific observation compression, and relative time formatting. Reject cloud storage dependency and no-test approach. — to avoid cloud-storage lock-in and no-test fragility identified in the study.
14
19
 
15
20
  ## Conventions
16
21
 
17
- - A/B testing methodology for memory plugin evaluation — How to test with/without memory using claude CLI — --plugin-dir doesn't work with --bare, use --mcp-config instead — To A/B test memory's impact on Claude Code responses: (imported from project auto-memory; see source file for full reasoning)
18
- - do...end blocks over braces when args repeat or block is non-trivial — Block syntax preference for multi-argument or multi-expression blocks — When a block call has repeated argument names, multiple expressions, or reads awkwardly on one line, use `do...end` form rather than `{ }`. One-liners with simple single-expression bodies and short argument lists are fine with braces. (imported from project auto-memory; see source file for full reasoning)
19
- - Always commit .claude/memory.sqlite3 — Per user direction (2026-05-21), .claude/memory.sqlite3 should be staged and committed alongside any change that updates project memory — even though it's a binary SQLite DB with WAL artifacts. — Always include `.claude/memory.sqlite3` in commits that touch project memory or knowledge. (imported from project auto-memory; see source file for full reasoning)
22
+ - A/B testing methodology for memory plugin evaluation — How to test with/without memory using claude CLI — --plugin-dir doesn't work with --bare, use --mcp-config instead — To A/B test memory's impact on Claude Code responses: (imported from project auto-memory; see source file for full reasoning) — to enable head-to-head measurement of memory's impact on Claude Code responses.
23
+ - do...end blocks over braces when args repeat or block is non-trivial — Block syntax preference for multi-argument or multi-expression blocks — When a block call has repeated argument names, multiple expressions, or reads awkwardly on one line, use `do...end` form rather than `{ }`. One-liners with simple single-expression bodies and short argument lists are fine with braces. (imported from project auto-memory; see source file for full reasoning) — to keep multi-line blocks readable and avoid brace/multi-expression awkwardness.
24
+ - Always commit .claude/memory.sqlite3 — Per user direction (2026-05-21), .claude/memory.sqlite3 should be staged and committed alongside any change that updates project memory — even though it's a binary SQLite DB with WAL artifacts. — Always include `.claude/memory.sqlite3` in commits that touch project memory or knowledge. (imported from project auto-memory; see source file for full reasoning) — to ensure project memory is reproducible across collaborators.
20
25
  - Commit workflow preferences — How the user prefers commits to be structured and when to make them — Wait for the user to ask for commits — don't commit proactively. When asked, group changes into logical atomic commits: (imported from project auto-memory; see source file for full reasoning)
21
- - Data-driven analysis before design changes — User expects thorough multi-project data surveys and critical questioning of assumptions before committing to architectural changes — When proposing design changes (especially to schemas, vocabularies, or policies), gather real usage data first and present a critical analysis before implementing. (imported from project auto-memory; see source file for full reasoning)
22
- - Fix hallucination triggers at the source, not via repeated reject churn — When the distiller repeatedly produces the same wrong fact, trace it to the CLAUDE.md / docs example text; fixing the source stops re-appearance — When the dashboard's Conflicts tab accumulates clusters of the same kind of bad fact (e.g. many `uses_database` contradictions against `sqlite`), the root cause is almost always **example text in documentation** that the distiller is interpreting as a literal claim about the current repo. Single-value predicates (`uses_database`, `deployment_platform`, `auth_method`) are especially vulnerable becau... (imported from project auto-memory; see source file for full reasoning)
23
- - Hooks run the installed gem, not the working copy — always `rake install` after editing hook/MCP code — .claude/settings.json hooks invoke `claude-memory` via PATH, so changes on a branch only take effect after `bundle exec rake install` — `.claude/settings.json` hooks call bare `claude-memory hook ingest` / `claude-memory hook context` / etc. That resolves via PATH to the installed gem, not the working-copy `./exe/claude-memory`. After editing any hook/MCP/distiller code on a branch, the change does NOT reach Claude Code until `bundle exec rake install` rebuilds and reinstalls the gem (which overwrites the prior install at the same ... (imported from project auto-memory; see source file for full reasoning)
24
- - No extra API costs for features — User strongly prefers using Claude Code itself (skills, context hooks) over separate API calls that cost extra money — Do not add features that require separate Anthropic API calls (e.g., via anthropic-rb gem) when Claude Code itself can perform the same task. Use skills, context hook injection, and MCP tools to leverage the existing Claude Code session instead. (imported from project auto-memory; see source file for full reasoning)
25
- - Quality review update cycle — Keep quality_review.md current as items are resolved — don't let it drift — When completing quality review items, update `docs/quality_review.md` immediately: (imported from project auto-memory; see source file for full reasoning)
26
+ - Data-driven analysis before design changes — User expects thorough multi-project data surveys and critical questioning of assumptions before committing to architectural changes — When proposing design changes (especially to schemas, vocabularies, or policies), gather real usage data first and present a critical analysis before implementing. (imported from project auto-memory; see source file for full reasoning) — to avoid premature architectural commitments before real usage data is in hand.
27
+ - Fix hallucination triggers at the source, not via repeated reject churn — When the distiller repeatedly produces the same wrong fact, trace it to the CLAUDE.md / docs example text; fixing the source stops re-appearance — When the dashboard's Conflicts tab accumulates clusters of the same kind of bad fact (e.g. many `uses_database` contradictions against `sqlite`), the root cause is almost always **example text in documentation** that the distiller is interpreting as a literal claim about the current repo. Single-value predicates (`uses_database`, `deployment_platform`, `auth_method`) are especially vulnerable becau... (imported from project auto-memory; see source file for full reasoning) — because reject-only is treating symptoms; the root cause is doc/example text the distiller mistakes for project claims.
28
+ - Hooks run the installed gem, not the working copy — always `rake install` after editing hook/MCP code — .claude/settings.json hooks invoke `claude-memory` via PATH, so changes on a branch only take effect after `bundle exec rake install` — `.claude/settings.json` hooks call bare `claude-memory hook ingest` / `claude-memory hook context` / etc. That resolves via PATH to the installed gem, not the working-copy `./exe/claude-memory`. After editing any hook/MCP/distiller code on a branch, the change does NOT reach Claude Code until `bundle exec rake install` rebuilds and reinstalls the gem (which overwrites the prior install at the same ... (imported from project auto-memory; see source file for full reasoning) — to ensure code changes reach the production hooks, which invoke the installed gem via PATH.
29
+ - No extra API costs for features — User strongly prefers using Claude Code itself (skills, context hooks) over separate API calls that cost extra money — Do not add features that require separate Anthropic API calls (e.g., via anthropic-rb gem) when Claude Code itself can perform the same task. Use skills, context hook injection, and MCP tools to leverage the existing Claude Code session instead. (imported from project auto-memory; see source file for full reasoning) — to avoid hidden ongoing spend that bypasses the Claude Code session's existing budget.
30
+ - Quality review update cycle — Keep quality_review.md current as items are resolved — don't let it drift — When completing quality review items, update `docs/quality_review.md` immediately: (imported from project auto-memory; see source file for full reasoning) — to prevent quality_review.md from drifting out of date.
26
31
  - Refactoring approach preferences — How to approach god object extraction and structural refactoring in this codebase — Use module inclusion (not class extraction) when breaking up god objects. Include modules directly into the existing class so the public API is unchanged and zero tests need modification. This was validated three times:
27
- - Round-trip migration specs cover each prior release boundary — For pre-release prep, write end-to-end migration specs from every distinct schema boundary back through ~3 prior releases — Before cutting a release that includes migrations, add round-trip specs that fixture an older DB at each distinct prior release's schema version, open via `SQLiteStore.new`, and assert the full upgrade path: schema_info advancement, data preservation across entities/facts/content_items/provenance, additive table/column creation, predicate-rewrite effects where applicable, and idempotency on re-open... (imported from project auto-memory; see source file for full reasoning)
32
+ - Round-trip migration specs cover each prior release boundary — For pre-release prep, write end-to-end migration specs from every distinct schema boundary back through ~3 prior releases — Before cutting a release that includes migrations, add round-trip specs that fixture an older DB at each distinct prior release's schema version, open via `SQLiteStore.new`, and assert the full upgrade path: schema_info advancement, data preservation across entities/facts/content_items/provenance, additive table/column creation, predicate-rewrite effects where applicable, and idempotency on re-open... (imported from project auto-memory; see source file for full reasoning) — to ensure migrations remain forward-compatible across release boundaries.
28
33
  - Codify behavioral contracts in tests, not just comments — When code has a deliberate scope limitation (one-shot, advisory-only, intentionally non-idempotent), write a test that fails if someone "fixes" it into being more general — When code has a deliberate scope limitation — a one-shot data migration, an advisory-only field, a method intentionally not idempotent for new inputs — write a test that exercises a scenario which would *break* if someone tried to make it more general. (imported from project auto-memory; see source file for full reasoning)
29
- - Treat UX gaps as architecture smells — user inspection/debugging questions expose god classes and missing abstractions — When users ask "can I see/debug/act on X in the dashboard?", the answer is almost always "we need a new class or route, not a new button" — Across three architectural reviews in the 2026-04-17 → 20 session, every concrete UX gap the user identified traced back to a structural issue the code already had, not a frontend-only fix. Treating critique as a forcing function for refactoring produced cleaner results than either extracting preemptively (premature) or patching only the surface (symptom). (imported from project auto-memory; see source file for full reasoning)
30
- - "database disk image is malformed" from FTS5 `ORDER BY rank` after sqlite3 .recover — sqlite3 .recover restores rows but can leave contentless FTS5 auxiliary indexes in a state where basic MATCH works but ORDER BY rank throws "malformed"; fix is `claude-memory compact` to rebuild the FTS index — A DB recovered via `sqlite3 corrupt.db .recover > dump.sql && sqlite3 fresh.db < dump.sql` can end up with an FTS5 index that's *partially* functional: (imported from project auto-memory; see source file for full reasoning)
31
- - `rake install` uses `git ls-files`; untracked files silently disappear from the gem — Running `bundle exec rake install` before staging new files produces a gem missing those files, causing LoadError in hooks and MCP server — The claude_memory gemspec builds its file list via `IO.popen(%w[git ls-files -z], ...)` (claude_memory.gemspec:24). Any file that hasn't been `git add`ed at build time is **invisible to the gem** even though it exists on disk. The local working copy keeps running fine (dashboard server uses `./exe/claude-memory` against the repo directly), but the installed gem at `~/.gem/ruby/*/gems/claude_memory-... (imported from project auto-memory; see source file for full reasoning)
32
- - Distiller scope_hint is advisory, not a routing signal — NullDistiller emits scope_hint: "global" for text matching GLOBAL_SCOPE_PATTERNS, but the resolver never routes writes between stores — scope_hint must not override fact.scope — `Distill::NullDistiller#global_scope_signal?` matches text like "always" / "my preference" / "in all projects" and stamps `scope_hint: "global"` on every fact extracted from that text. The hint is advisory metadata for downstream promotion decisions. It is NOT a routing signal — the resolver writes to whichever `SQLiteStore` was injected into it (always the project DB in the normal ingest path), re... (imported from project auto-memory; see source file for full reasoning)
33
- - Sequel DB reads must use the extralite adapter — Opening a SQLite DB for ad-hoc reads requires the extralite adapter URI; Sequel.sqlite silently depends on an ungem'd sqlite3 — Never use `Sequel.sqlite(db_path)` or `Sequel.sqlite(db_path, readonly: true)` in this codebase. The gemspec lists only `extralite (~> 2.14)` — it does **not** depend on the `sqlite3` gem. `Sequel.sqlite` routes through Sequel's `sqlite` adapter which requires `gem "sqlite3"` and will raise `Sequel::AdapterNotFound: LoadError: cannot load such file -- sqlite3` at runtime. (imported from project auto-memory; see source file for full reasoning)
34
- - Never `git checkout --` an active SQLite DB with WAL mode — Using `git checkout --` on .claude/memory.sqlite3 while readers/writers are open corrupts the DB via WAL/main file mismatch — Never run `git checkout -- .claude/memory.sqlite3` (or any SQLite DB in WAL mode) while any process has it open. Git replaces only the main DB file, leaving the WAL/SHM sidecar files referencing pages that no longer exist in the replaced file. Next read → "Extralite::Error: database disk image is malformed" and integrity_check shows btree errors across multiple trees. (imported from project auto-memory; see source file for full reasoning)
35
- - SQLiteStore silently creates in-memory DB for relative paths — `SQLiteStore.new('.claude/memory.sqlite3')` with a relative path opens an empty in-memory DB, not the file — always pass absolute paths in tests/probes — `SQLiteStore.new(path)` builds a Sequel URI as `extralite:#{path}`. With a relative path like `.claude/memory.sqlite3`, the resulting URI `extralite:.claude/memory.sqlite3` is parsed with an empty database component, so Extralite opens an in-memory database. Schema migrations run against the in-memory DB (so `schema_version` reports the current version), but ALL queries return 0 rows and the real f... (imported from project auto-memory; see source file for full reasoning)
36
- - Two tool_calls tables exist — don't conflate them — tool_calls (v3) stores transcript-observed Claude Code tool usage; mcp_tool_calls (v13) stores MCP server telemetry — There are **two** tables with similar names serving different purposes: (imported from project auto-memory; see source file for full reasoning)
37
- - Distiller hallucination from CLAUDE.md example text — The scope-system example in CLAUDE.md causes recurring false fact extraction — reject + re-ingest creates rejection churn — CLAUDE.md contains a scope-system explanation with example text: (imported from project auto-memory; see source file for full reasoning)
38
- - PredicatePolicy is the single source of truth for predicate vocabulary — All predicate knowledge (vocabulary, cardinality, sections, synonyms, LLM guidance) derives from PredicatePolicy — never hardcode predicate names elsewhere — As of 0.9.0, `PredicatePolicy` in `lib/claude_memory/resolve/predicate_policy.rb` is the authoritative source for all predicate-related behavior. This was a deliberate consolidation after finding the same predicate list duplicated in 4 files that drifted independently. (imported from project auto-memory; see source file for full reasoning)
34
+ - Treat UX gaps as architecture smells — user inspection/debugging questions expose god classes and missing abstractions — When users ask "can I see/debug/act on X in the dashboard?", the answer is almost always "we need a new class or route, not a new button" — Across three architectural reviews in the 2026-04-17 → 20 session, every concrete UX gap the user identified traced back to a structural issue the code already had, not a frontend-only fix. Treating critique as a forcing function for refactoring produced cleaner results than either extracting preemptively (premature) or patching only the surface (symptom). (imported from project auto-memory; see source file for full reasoning) — because frontend patches usually mask structural debt that surfaces later.
35
+ - "database disk image is malformed" from FTS5 `ORDER BY rank` after sqlite3 .recover — sqlite3 .recover restores rows but can leave contentless FTS5 auxiliary indexes in a state where basic MATCH works but ORDER BY rank throws "malformed"; fix is `claude-memory compact` to rebuild the FTS index — A DB recovered via `sqlite3 corrupt.db .recover > dump.sql && sqlite3 fresh.db < dump.sql` can end up with an FTS5 index that's *partially* functional: (imported from project auto-memory; see source file for full reasoning) — fix is claude-memory compact to rebuild the FTS index without doing another .recover.
36
+ - `rake install` uses `git ls-files`; untracked files silently disappear from the gem — Running `bundle exec rake install` before staging new files produces a gem missing those files, causing LoadError in hooks and MCP server — The claude_memory gemspec builds its file list via `IO.popen(%w[git ls-files -z], ...)` (claude_memory.gemspec:24). Any file that hasn't been `git add`ed at build time is **invisible to the gem** even though it exists on disk. The local working copy keeps running fine (dashboard server uses `./exe/claude-memory` against the repo directly), but the installed gem at `~/.gem/ruby/*/gems/claude_memory-... (imported from project auto-memory; see source file for full reasoning) — to ensure new files reach the installed gem (the gemspec uses git ls-files for the manifest).
37
+ - Distiller scope_hint is advisory, not a routing signal — NullDistiller emits scope_hint: "global" for text matching GLOBAL_SCOPE_PATTERNS, but the resolver never routes writes between stores — scope_hint must not override fact.scope — `Distill::NullDistiller#global_scope_signal?` matches text like "always" / "my preference" / "in all projects" and stamps `scope_hint: "global"` on every fact extracted from that text. The hint is advisory metadata for downstream promotion decisions. It is NOT a routing signal — the resolver writes to whichever `SQLiteStore` was injected into it (always the project DB in the normal ingest path), re... (imported from project auto-memory; see source file for full reasoning) — to prevent scope drift between resolver-determined store and distiller-stamped hint.
38
+ - Sequel DB reads must use the extralite adapter — Opening a SQLite DB for ad-hoc reads requires the extralite adapter URI; Sequel.sqlite silently depends on an ungem'd sqlite3 — Never use `Sequel.sqlite(db_path)` or `Sequel.sqlite(db_path, readonly: true)` in this codebase. The gemspec lists only `extralite (~> 2.14)` — it does **not** depend on the `sqlite3` gem. `Sequel.sqlite` routes through Sequel's `sqlite` adapter which requires `gem "sqlite3"` and will raise `Sequel::AdapterNotFound: LoadError: cannot load such file -- sqlite3` at runtime. (imported from project auto-memory; see source file for full reasoning) — to avoid Sequel::AdapterNotFound at runtime (this gem doesn't depend on the sqlite3 adapter).
39
+ - Never `git checkout --` an active SQLite DB with WAL mode — Using `git checkout --` on .claude/memory.sqlite3 while readers/writers are open corrupts the DB via WAL/main file mismatch — Never run `git checkout -- .claude/memory.sqlite3` (or any SQLite DB in WAL mode) while any process has it open. Git replaces only the main DB file, leaving the WAL/SHM sidecar files referencing pages that no longer exist in the replaced file. Next read → "Extralite::Error: database disk image is malformed" and integrity_check shows btree errors across multiple trees. (imported from project auto-memory; see source file for full reasoning) — to avoid WAL/main file mismatch corruption.
40
+ - SQLiteStore silently creates in-memory DB for relative paths — `SQLiteStore.new('.claude/memory.sqlite3')` with a relative path opens an empty in-memory DB, not the file — always pass absolute paths in tests/probes — `SQLiteStore.new(path)` builds a Sequel URI as `extralite:#{path}`. With a relative path like `.claude/memory.sqlite3`, the resulting URI `extralite:.claude/memory.sqlite3` is parsed with an empty database component, so Extralite opens an in-memory database. Schema migrations run against the in-memory DB (so `schema_version` reports the current version), but ALL queries return 0 rows and the real f... (imported from project auto-memory; see source file for full reasoning) — to ensure SQLiteStore opens the real file rather than a silent in-memory shadow.
41
+ - Two tool_calls tables exist — don't conflate them — tool_calls (v3) stores transcript-observed Claude Code tool usage; mcp_tool_calls (v13) stores MCP server telemetry — There are **two** tables with similar names serving different purposes: (imported from project auto-memory; see source file for full reasoning) — to avoid joining unrelated rows from disjoint telemetry tables.
42
+ - Distiller hallucination from CLAUDE.md example text — The scope-system example in CLAUDE.md causes recurring false fact extraction — reject + re-ingest creates rejection churn — CLAUDE.md contains a scope-system explanation with example text: (imported from project auto-memory; see source file for full reasoning) — to prevent re-extraction churn from documentation example text that the distiller takes literally.
43
+ - PredicatePolicy is the single source of truth for predicate vocabulary — All predicate knowledge (vocabulary, cardinality, sections, synonyms, LLM guidance) derives from PredicatePolicy — never hardcode predicate names elsewhere — As of 0.9.0, `PredicatePolicy` in `lib/claude_memory/resolve/predicate_policy.rb` is the authoritative source for all predicate-related behavior. This was a deliberate consolidation after finding the same predicate list duplicated in 4 files that drifted independently. (imported from project auto-memory; see source file for full reasoning) — to avoid divergent predicate lists across files that drifted independently before consolidation.
39
44
  - Hook-telemetry features need a manual hook trigger to verify in production, not just specs — `bundle exec rake install` AND fire a real hook AND check `sqlite3 ... json_extract(detail_json, '$.<field>')`, because specs assert against working-tree code but `.claude/settings.json` hooks invoke the installed gem via PATH so the asserted field can be silently absent in production. Hit on 2026-04-30 shipping #47 token-budget telemetry: 156 specs green but `context_tokens` was missing from 24h of real activity_events.
40
- - When verifying any new field on activity_events.detail_json, the canonical smoke test is: `echo '{"hook_event_name":"SessionStart","session_id":"smoketest","source":"startup","cwd":"$(pwd)"}' | claude-memory hook context` then inspect via `sqlite3 .claude/memory.sqlite3 "SELECT json_extract(detail_json, '$.<field>') FROM activity_events WHERE event_type='hook_context' ORDER BY id DESC LIMIT 1"`. If null after rake install, the installed gem code hasn't picked up the change.
45
+ - When verifying any new field on activity_events.detail_json, the canonical smoke test is: `echo '{"hook_event_name":"SessionStart","session_id":"smoketest","source":"startup","cwd":"$(pwd)"}' | claude-memory hook context` then inspect via `sqlite3 .claude/memory.sqlite3 "SELECT json_extract(detail_json, '$.<field>') FROM activity_events WHERE event_type='hook_context' ORDER BY id DESC LIMIT 1"`. If null after rake install, the installed gem code hasn't picked up the change. — to ensure the installed gem actually picks up new detail_json fields.
41
46
  - Treat UX gaps as architecture smells: when a user asks "can I see/debug/act on X in the dashboard?" the answer is almost always a missing class or route, not a new button. Every UX critique in this project's dashboard work traced to a structural gap — god-class growth, four drifting fact serializers, scope_hint as silent scope override, no fact detail endpoint. Pattern: the surface question is usually a router into the architecture. Before reaching for frontend fixes, ask "what server-side data shape would make this easy?" — if that shape doesn't exist cleanly, treat it as the real work. Commit refactor separately from feature it enables ([Refactor]/[Feature]/[Fix] prefixes) so the critique→structural fix→UX fix chain is visible in git log.
42
47
  - Four-surface staleness: after any change that touches UI + backend + plugin-launched binaries, refresh all four or the change looks broken. (1) bundle exec rake install so the installed gem catches up (hooks + MCP launched by Claude Code run from PATH). (2) Ctrl-C and re-run ./exe/claude-memory dashboard — server is long-lived Ruby, no live-reload. (3) /mcp reconnect in Claude Code so the MCP subprocess respawns. (4) Hard-refresh browser (Cmd-Shift-R) so cached index.html JS reloads. Skipping any produces confusing "my fix doesn't work." rspec green does NOT mean end-to-end works; before declaring UI-affecting changes done, curl the endpoint and verify shape matches frontend expectation. curl alt-port dashboard (--port 3388 --no-open in background) is fastest smoke test without disturbing user's running dashboard.
43
- - fact.scope MUST match the DB the fact lives in. The distiller may emit scope_hint="global" from GLOBAL_SCOPE_PATTERNS but scope_hint is advisory only it never routes writes or overrides the destination store's scope. Using scope_hint as a scope override produced orphaned scope=global rows inside the project DB that global recall couldn't see. Users move project facts to global via claude-memory promote (StoreManager#promote_fact does the proper cross-store copy). Sweep::Maintenance#fix_scope_leakage cleans drift in existing DBs. Invariant documented in gotcha_scope_hint_not_routing memory.
44
- - "disk image is malformed" from an FTS5 ORDER BY rank query after a sqlite3 .recover restore is usually FTS auxiliary-index corruption, not real DB damage. Diagnostic chain: PRAGMA integrity_check on fresh connection (ok means main DB is fine), plain MATCH (works means b-tree is fine), ORDER BY rank (fails means FTS internals rotted). Fix with claude-memory compact rebuilds FTS from source content in a few seconds. Do NOT reach for sqlite3 .recover a second time; that's the class of action that leaves FTS in this broken state. Three distinct malformed-error flavors in this project: real corruption (use .recover), WAL stale-cache phantom from long-lived readers (release connections per request), FTS5 rank rot (compact).
45
- - Name collision: claude-memory recover (CLI) resets stuck operations from OperationTracker; it does NOT repair corrupt SQLite. For real disk-image corruption use sqlite3 corrupt.db ".recover" > dump.sql && sqlite3 fresh.db < dump.sql then open via SQLiteStore for migrations. Recovery can leave contentless FTS5 in a partial state where plain MATCH works but ORDER BY rank fails follow up with claude-memory compact. Verified recoverable on 2026-04-16 76 facts / 56 entities / 183 content_items restored from a DB with multiple btree errors.
46
- - Hooks in .claude/settings.json invoke bare 'claude-memory' which resolves via PATH to the installed gem, not the working-copy ./exe/claude-memory. After editing Hook::Handler, MCP::Tools, MCP::Server, ActivityLog, Distill::*, or any commands/ file on a branch, run 'bundle exec rake install' before expecting hooks or the Claude-Code-launched MCP server to see the change. The dashboard server (./exe/claude-memory dashboard) is the exception it runs from the working copy directly. When reinstalling while servers are running, also restart them (Ctrl-C or /mcp reconnect) since old code stays in memory.
47
- - Never 'git checkout --' .claude/memory.sqlite3 (or any WAL-mode SQLite DB) while a reader/writer has it open. Git replaces the main DB file but leaves the -wal and -shm sidecar files referencing stale pages, which corrupts the DB on next read. Safe sequence: stop all holders (MCP server, dashboard, hooks), delete the -wal and -shm sidecars, then checkout. Recovery path: sqlite3 corrupt.db .recover > dump.sql then reimport into a fresh file.
48
- - SQLiteStore.new with a relative path silently opens an empty in-memory DB instead of the file. The URI built in retry_handler.rb is extralite:<path>, and extralite:.claude/memory.sqlite3 parses with an empty database option so Extralite treats it as in-memory. Schema migrations run so schema_version looks correct but all queries return 0 rows. Production callers go through StoreManager with absolute paths (via Configuration) so this does not bite in normal use. In ad-hoc probes always pass File.expand_path(path). Diagnostic: if store.facts.count returns 0 while sqlite3 CLI shows rows, suspect this before suspecting corruption.
49
- - Never use Sequel.sqlite for DB reads; this gem only depends on extralite. Use Sequel.connect("extralite://#{db_path}") or SQLiteStore.new. Sequel.sqlite requires the ungem'd sqlite3 adapter and fails at runtime.
50
- - Two distinct tool_calls tables exist: tool_calls (v3) for transcript-observed Claude Code tool usage, and mcp_tool_calls (v13) for MCP server telemetry. Disjoint purposes, never join.
51
- - MCP tool-call telemetry is recorded via MCP::Telemetry wrapping Server#handle_tools_call. Writes to mcp_tool_calls table in the project DB. Swallows DB errors so telemetry never breaks a real tool response. Viewable via 'claude-memory stats --tools [--since DAYS]'.
52
- - mcp_tool_calls retention is 90 days, enforced by Sweep::Maintenance#prune_old_mcp_tool_calls wired into Sweeper#run!. Configurable via mcp_tool_call_retention_days in Maintenance DEFAULT_CONFIG.
53
- - CLAUDE_CONFIG_DIR env var overrides the default ~/.claude location for Configuration#global_db_path. Access via Configuration#claude_config_dir. Additive, backwards compatible.
54
- - Registry::COMMANDS stores {class:, description:} entries as the single source of truth for command name, class reference, and shell-completion description. Class constants stored directly (no const_get). Registry.descriptions feeds CompletionCommand; adding a new command requires updating only this hash.
55
- - Prefer do...end over braces when a block has repeated argument names, multiple expressions, or non-trivial body. Single-expression simple blocks can still use braces.
56
- - EXPECTED_HOOKS constant must stay in sync with events in HooksConfigurator#build_hooks_config. Adding a new hook event without updating EXPECTED_HOOKS causes false doctor warnings.
57
- - Tests using --db for hook context only set the project DB path. StoreManager still connects to the real global DB. Must stub Configuration to return a temp global path for isolation.
58
- - Version must be updated in three places: lib/claude_memory/version.rb, .claude-plugin/plugin.json, .claude-plugin/marketplace.json. Runtime code uses ClaudeMemory::VERSION dynamically.
59
- - Use module inclusion (not class extraction) to break up god objects — preserves public API so zero tests need modification
60
- - Configuration class has instance methods only — use Configuration.new.global_db_path, not Configuration.global_db_path. Stub with instance_double + allow(Configuration).to receive(:new).and_return(config)
61
- - OperationTracker.reset_stuck_operations only resets operations older than 24 hours (STALE_THRESHOLD_SECONDS). Tests must backdate started_at to trigger resets.
62
- - SCHEMA_VERSION constant lives in Store::SchemaManager module but is accessible as SQLiteStore::SCHEMA_VERSION via Ruby include-based constant lookup
63
- - MCP tools return dual content (text summary) + structuredContent (JSON) via TextSummary module and Server#handle_tools_call. Compact mode (compact: true) omits receipts for ~60% smaller responses.
64
- - ContentSanitizer strips system-reminder, local-command-caveat, command-message, command-name, command-args tags in addition to private/no-memory/secret/claude-memory-context.
65
- - Core::RelativeTime module provides progressive time formatting: just now → Xm ago → Xh ago → Xd ago → YYYY-MM-DD. Used in ResponseFormatter for *_ago fields.
66
- - MCP server registers memory_guide prompt via prompts/list and prompts/get endpoints. QueryGuide module holds prompt content.
48
+ - MCP tool-call telemetry is recorded via MCP::Telemetry wrapping Server#handle_tools_call. Writes to mcp_tool_calls table in the project DB. Swallows DB errors so telemetry never breaks a real tool response. Viewable via 'claude-memory stats --tools [--since DAYS]'. to ensure telemetry never breaks a real tool response (DB errors are swallowed).
49
+ - mcp_tool_calls retention is 90 days, enforced by Sweep::Maintenance#prune_old_mcp_tool_calls wired into Sweeper#run!. Configurable via mcp_tool_call_retention_days in Maintenance DEFAULT_CONFIG. — long enough to support quarterly tool-usage retrospectives without unbounded DB growth.
50
+ - CLAUDE_CONFIG_DIR env var overrides the default ~/.claude location for Configuration#global_db_path. Access via Configuration#claude_config_dir. Additive, backwards compatible. — to support test isolation and air-gapped installs that don't touch ~/.claude.
51
+ - Registry::COMMANDS stores {class:, description:} entries as the single source of truth for command name, class reference, and shell-completion description. Class constants stored directly (no const_get). Registry.descriptions feeds CompletionCommand; adding a new command requires updating only this hash. — to ensure adding a command requires only this hash update (no const_get, no description duplication).
52
+ - EXPECTED_HOOKS constant must stay in sync with events in HooksConfigurator#build_hooks_config. Adding a new hook event without updating EXPECTED_HOOKS causes false doctor warnings. without this sync the doctor reports false hook-mismatch warnings.
53
+ - Tests using --db for hook context only set the project DB path. StoreManager still connects to the real global DB. Must stub Configuration to return a temp global path for isolation. to ensure test isolation from the user's real global DB.
54
+ - Version must be updated in three places: lib/claude_memory/version.rb, .claude-plugin/plugin.json, .claude-plugin/marketplace.json. Runtime code uses ClaudeMemory::VERSION dynamically. to ensure plugin/marketplace metadata matches the gem version on release.
55
+ - Use module inclusion (not class extraction) to break up god objects preserves public API so zero tests need modification to avoid breaking the public API and forcing test updates during god-object cleanup.
56
+ - Configuration class has instance methods only use Configuration.new.global_db_path, not Configuration.global_db_path. Stub with instance_double + allow(Configuration).to receive(:new).and_return(config) to make Configuration stubbable in tests via instance_double.
57
+ - OperationTracker.reset_stuck_operations only resets operations older than 24 hours (STALE_THRESHOLD_SECONDS). Tests must backdate started_at to trigger resets. — to prevent the reset from clobbering operations still legitimately in flight.
58
+ - SCHEMA_VERSION constant lives in Store::SchemaManager module but is accessible as SQLiteStore::SCHEMA_VERSION via Ruby include-based constant lookup — accessible via include because Ruby's constant lookup walks the inclusion chain.
59
+ - MCP tools return dual content (text summary) + structuredContent (JSON) via TextSummary module and Server#handle_tools_call. Compact mode (compact: true) omits receipts for ~60% smaller responses. so that compact-mode callers pay ~60% fewer tokens per response.
60
+ - ContentSanitizer strips system-reminder, local-command-caveat, command-message, command-name, command-args tags in addition to private/no-memory/secret/claude-memory-context. to prevent harness-internal tags from being ingested as user content.
61
+ - Core::RelativeTime module provides progressive time formatting: just now → Xm ago → Xh ago → Xd ago → YYYY-MM-DD. Used in ResponseFormatter for *_ago fields. to make stale-fact warnings legible across both recent and old timestamps.
62
+ - MCP server registers memory_guide prompt via prompts/list and prompts/get endpoints. QueryGuide module holds prompt content. to ensure memory_guide is discoverable via the standard MCP prompt registry.
67
63
 
68
64
  ## Technical Constraints
69
65
 
@@ -53,6 +53,7 @@
53
53
  "mcp__memory__memory_conflicts"
54
54
  ]
55
55
  },
56
- "enableAllProjectMcpServers": true,
56
+ "enableAllProjectMcpServers": false,
57
+ "disabledMcpjsonServers": ["memory"],
57
58
  "outputStyle": "memory-aware"
58
59
  }
@@ -7,9 +7,9 @@
7
7
  "plugins": [
8
8
  {
9
9
  "name": "claude-memory",
10
- "version": "0.12.0",
10
+ "version": "0.13.0",
11
11
  "source": "./",
12
- "description": "Long-term memory for Claude Code. Recalls architecture, conventions, and decisions across sessions — so Claude explains your codebase without file traversal, follows your patterns, and never re-asks what it already learned.",
12
+ "description": "Long-term memory for Claude Code. Recalls architecture, conventions, and decisions across sessions, plus an episodic observation log of what happened — so Claude explains your codebase without file traversal, follows your patterns, learns from corrections, and never re-asks what it already learned.",
13
13
  "repository": "https://github.com/codenamev/claude_memory"
14
14
  }
15
15
  ]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-memory",
3
- "version": "0.12.0",
4
- "description": "Long-term memory for Claude Code. Recalls architecture, conventions, and decisions across sessions — so Claude explains your codebase without file traversal, follows your patterns, and never re-asks what it already learned.",
3
+ "version": "0.13.0",
4
+ "description": "Long-term memory for Claude Code. Recalls architecture, conventions, and decisions across sessions, plus an episodic observation log of what happened — so Claude explains your codebase without file traversal, follows your patterns, learns from corrections, and never re-asks what it already learned.",
5
5
  "author": {
6
6
  "name": "Valentino Stoll",
7
7
  "email": "v@codenamev.com"
@@ -16,7 +16,5 @@
16
16
  "args": []
17
17
  }
18
18
  },
19
- "skills": "./skills/",
20
- "commands": "./commands/",
21
- "outputStyles": "./output-styles/"
19
+ "commands": "./commands/"
22
20
  }
data/CHANGELOG.md CHANGED
@@ -4,6 +4,58 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.13.0] - 2026-06-18
8
+
9
+ Theme: **Episodic memory — a second kind of memory.** ClaudeMemory gains an append-only *observation* layer ("what happened") that complements the semantic fact store ("what is true"), modeled on [Mastra's Observational Memory](docs/influence/mastra-observational-memory.md). Observations accrue automatically, are deduplicated/consolidated by reflection, and are promoted to facts only after corroboration — making repeated sighting an anti-hallucination gate built into the memory model. Schema advances to v20 (additive; no breaking changes to existing facts/queries).
10
+
11
+ ### Added
12
+
13
+ **Episodic Observation Layer**
14
+
15
+ - **`observations` table** (schema v19–v20) — append-only episodic rows complementing facts. Columns include `body`, `kind`, `priority` (1=🔴/2=🟡/3=🟢), `scope`, `source_content_item_id` (provenance), `consolidated_into` (tombstone lineage — superseded observations are preserved, never deleted), `corroboration_count`, `promoted_at`/`promoted_fact_id`, `status`.
16
+ - **Observer** — Layer-1 `NullDistiller` (regex) plus Layer-2 Claude-as-observer (SessionStart context hook, zero extra API cost) emit observations alongside facts; persisted by the `Resolver` inside the existing extraction transaction.
17
+ - **Reflector** (`Observe::Reflector`) — deterministic dedup + TTL-expiry of info-level observations runs shell-side on `PreCompact`/`SessionEnd`; semantic consolidation (Claude-as-reflector) rides the next turn's `PreCompact` `additionalContext`. No separate API spend.
18
+ - **Two-block SessionStart injection** — Block 1 is the stable observation log (🔴-marked); Block 2 is the undistilled "pending knowledge" tail. Wrapped in `<claude-memory-context>` so the injected log isn't re-ingested as new observations.
19
+ - **Observation→fact promotion bridge** — facts are created from observations only after corroboration (≥2 sightings, `Domain::Observation::PROMOTION_THRESHOLD`); refuses uncorroborated or already-promoted rows.
20
+ - **MCP tools** (28 total): `memory.observations` (read the episodic log), `memory.promote_observation` (corroboration-gated promotion), `memory.consolidate_observations` (semantic merge; corroboration combines, sources tombstoned).
21
+ - **`/reflect` skill** — guided survey → consolidate → promote → report pass over the observation log.
22
+ - **`claude-memory observations`** command — list/inspect the log (counts by status/kind/priority, promotion readiness, compression), plus `promote` and `consolidate` subcommands; `claude-memory stats --observations` summarizes counts.
23
+ - **Dashboard Observations panel** (first-class, main sidebar + Advanced tab) — counts by kind/priority, corroboration + promotion readiness, source→observation compression ratio, recent timeline.
24
+ - **`claude-memory audit` observation health checks** — orphaned observations, promotion consistency, and tombstone-chain validity, documented in `docs/audit_runbook.md`.
25
+
26
+ ### Changed
27
+
28
+ - **Dependencies updated** to current within constraints — `sequel` 5.105, `standard` 1.55, `rubocop` 1.87, `json`, `psych`, `parallel` 2.x, among others. No runtime-gem major bumps required.
29
+
30
+ ### Fixed
31
+
32
+ - **`consolidate_observations` read-modify-write race** — the source read and corroboration sum now run inside the transaction, with `status='active'` re-asserted on the tombstone update, so concurrent `PreCompact`/`SessionEnd` reflectors can't double-count or double-tombstone.
33
+ - **`go` language false positive** — the distiller no longer extracts the English word "go" as the Go language (case-sensitive match + `golang` normalization).
34
+
35
+ ## [0.12.1] - 2026-06-05
36
+
37
+ Theme: **Upgrade-experience patches surfaced by the 0.12.0 soak.** Four small but high-impact fixes — all uncovered by one user upgrading a single project — closing visibility gaps in the doctor and the plugin manifest. No schema changes, no breaking changes.
38
+
39
+ ### Added
40
+
41
+ - **`claude-memory setup-vectors` command** — the documented opt-in path for end users who want vector recall via the BAAI/bge-small-en-v1.5 model. fastembed remains a dev/test gem dependency by design (the default install stays light); this command verifies the chosen provider is loadable (gracefully prompts to `gem install fastembed` if not), writes `CLAUDE_MEMORY_EMBEDDING_PROVIDER` (and optional `CLAUDE_MEMORY_EMBEDDING_MODEL`) to the project's `.claude/settings.json` env block — the same mechanism Claude Code uses for OTel — and re-indexes existing facts via the existing `IndexCommand` (skip with `--no-reindex`). Supports `--status` for current config and `--dry-run` for inspection. Preserves unrelated settings.json keys.
42
+ - **`Checks::EmbeddingsCheck`** in `claude-memory doctor` — surfaces the active embedding provider name and dimensions, hints to set `CLAUDE_MEMORY_EMBEDDING_PROVIDER=fastembed` when on tfidf default and fastembed is loadable, and reports dimension mismatches between stored vectors and the current provider. Closes the visibility gap where a user could see `sqlite-vec available ✓` while silently running on tfidf without knowing.
43
+
44
+ ### Fixed
45
+
46
+ - **`plugin.json` declared `skills: "./skills/"` and `outputStyles: "./output-styles/"` pointing at non-existent directories.** Per Claude Code's plugin reference, `distill-transcripts.md` is correctly a flat *command* (not a skill); both forms register as `/<name>` slash commands. Dead keys removed. Plugin spec rewritten as deletion-safe ("every directory key in plugin.json points at an existing directory") so this can't regress.
47
+
48
+ ### Documentation
49
+
50
+ - **README "Upgrading" section** now documents the marketplace-refresh + `/reload-plugins` flow explicitly. After `/plugin marketplace update <name>` users must run `/reload-plugins` or restart Claude Code for new slash commands to appear — this bit one user upgrading to 0.12.0 looking for `/distill-transcripts`. Includes `/audit-memory` and `/distill-transcripts` as named examples.
51
+
52
+ ### Upgrade Notes
53
+
54
+ - No DB migrations. Schema stays at v18.
55
+ - After `gem update claude_memory`, run `/plugin marketplace update claude-memory && /reload-plugins` (or restart Claude Code) to see the new `/distill-transcripts` and `/audit-memory` slash commands.
56
+ - Existing fact bases continue to use whatever embedding provider they were indexed under. To opt into fastembed, run `claude-memory setup-vectors` — it handles provider switching + re-index in one step.
57
+ - `claude-memory doctor` will now emit a warning on tfidf default with fastembed loadable. This is informational, not an error; the system continues to function on tfidf.
58
+
7
59
  ## [0.12.0] - 2026-05-29
8
60
 
9
61
  Theme: **Release Discipline + Observability + Self-Audit** — the infrastructure that makes a 1.0 semver promise defensible. This release locks down the public API surface, adds the observability primitives (OTel ingestion, dashboard Telemetry) and the self-audit toolkit (`claude-memory audit`) that serve the visibility pillar, and ships the negative-fact harm benchmark + staleness guard that make the long-horizon-quality claim measurable rather than aspirational.
data/CLAUDE.md CHANGED
@@ -113,7 +113,7 @@ bin/run-evals --comparative --setup-competitors # Install + run in one step
113
113
 
114
114
  NullDistiller (regex, Layer 1):
115
115
  - Concept Recall: 0.952 (regex-detectable entities/facts)
116
- - Fact Precision: 1.000, Fact Recall: 1.000 (on 31 test cases)
116
+ - Fact Precision: 0.935, Fact Recall: 1.000 (on 31 test cases) — deliberately high-recall ("extract every mention, filter downstream"); see improvements.md #70
117
117
  - Pipeline latency: P95 < 5ms (medium text)
118
118
 
119
119
  Claude Code (LLM, Layers 2+3):
@@ -150,7 +150,7 @@ Transcripts → Ingest → Index (FTS5)
150
150
  The distillation pipeline operates at three levels of depth:
151
151
 
152
152
  - **Layer 1: NullDistiller** (automatic, regex, free) — Runs in the ingest pipeline on every hook event. Extracts entities, facts, and scope hints using pattern matching. P95 latency < 5ms.
153
- - **Layer 2: Context Hook Injection** (automatic, LLM, zero extra cost) — At SessionStart, undistilled content is injected into the session via `hookSpecificOutput.additionalContext` with extraction instructions. Claude Code itself acts as the distiller, extracting structured facts at no additional API cost.
153
+ - **Layer 2: Context Hook Injection** (automatic, LLM, zero extra cost) — At SessionStart, undistilled content is injected into the session via `hookSpecificOutput.additionalContext` with extraction instructions. Claude Code itself acts as the distiller, extracting structured facts at no additional API cost. The same prompt also asks Claude to emit episodic **observations** (the Layer-2 Claude-as-observer) in the `observations` field of its `memory.store_extraction` call — coerced/validated at the handler border and persisted via the resolver alongside facts.
154
154
  - **Layer 3: `/distill-transcripts` Skill** (manual, on-demand) — Deep extraction triggered by the user. Processes undistilled content with depth-aware prompts (initial extraction, consolidation, contradiction resolution).
155
155
 
156
156
  New MCP tools `memory.undistilled` and `memory.mark_distilled` support the pipeline by tracking which content items have been deeply distilled.
@@ -167,7 +167,7 @@ New MCP tools `memory.undistilled` and `memory.mark_distilled` support the pipel
167
167
  - Each command is a separate class (HelpCommand, DoctorCommand, etc.)
168
168
  - All commands inherit from BaseCommand
169
169
  - Dependency injection for I/O (stdout, stderr, stdin)
170
- - 34 commands total, each focused on single responsibility
170
+ - 38 commands total, each focused on single responsibility
171
171
 
172
172
  - **`Configuration`**: Centralized ENV access (`configuration.rb`)
173
173
  - Single source of truth for paths and environment variables
@@ -233,7 +233,7 @@ New MCP tools `memory.undistilled` and `memory.mark_distilled` support the pipel
233
233
  - Modes: shared (repo), local (uncommitted), home (user directory)
234
234
 
235
235
  - **`MCP`**: Model Context Protocol server and tools (`mcp/`)
236
- - Exposes memory tools to Claude Code (23 tools total)
236
+ - Exposes memory tools to Claude Code (28 tools total)
237
237
  - `Telemetry`: Records tool invocations to `mcp_tool_calls` table for usage stats
238
238
  - Dual content/structuredContent responses with compact mode
239
239
 
@@ -256,6 +256,7 @@ Key tables (defined in `sqlite_store.rb`):
256
256
  - `mcp_tool_calls`: MCP server tool invocation telemetry (schema v13)
257
257
  - `activity_events`: Hook/recall/context/sweep/nudge telemetry (schema v15) — powers the dashboard timeline, moments feed, efficacy reports. Event types: `hook_ingest`, `hook_context` (carries `context_tokens` since 0.11.0), `hook_sweep`, `hook_publish`, `recall`, `store_extraction`, `roi_nudge` (since 0.11.0).
258
258
  - `moment_feedback`: Per-moment 👍/👎 verdicts with optional notes (schema v16) — unique on event_id, repeat clicks upsert
259
+ - `observations`: Episodic "what happened" layer (schema v19–v20) — append-only narrative rows complementing facts ("what is true"). Columns: `body`, `kind` (decision/preference/event/…), `priority` (1=🔴/2=🟡/3=info), `scope`, `source_content_item_id` (provenance), `consolidated_into` (Reflector tombstone lineage — never hard-deleted), `token_count`, `status`, `corroboration_count` (folded by dedup; the promotion-gate signal), `promoted_at`/`promoted_fact_id` (set when promoted to a fact). Written by the Resolver from `Extraction#observations` (NullDistiller is the Layer-1 Observer). The full observational layer (Observer → injection → deterministic Reflector → promotion bridge) is in `lib/claude_memory/observe/`; see [docs/influence/mastra-observational-memory.md](docs/influence/mastra-observational-memory.md).
259
260
 
260
261
  Facts include:
261
262
  - `scope`: "global" or "project" (determines applicability)
@@ -339,7 +340,7 @@ Also update `SECTION_MAP` if the predicate should appear in a specific snapshot
339
340
 
340
341
  - `lib/claude_memory.rb`: Main module, requires, database path helpers
341
342
  - `lib/claude_memory/cli.rb`: Thin command router (41 lines)
342
- - `lib/claude_memory/commands/`: Individual command classes (34 commands)
343
+ - `lib/claude_memory/commands/`: Individual command classes (38 commands)
343
344
  - `lib/claude_memory/configuration.rb`: Centralized configuration and ENV access
344
345
  - `lib/claude_memory/domain/`: Domain models (Fact, Entity, Provenance, Conflict)
345
346
  - `lib/claude_memory/core/`: Value objects and null objects
@@ -354,7 +355,7 @@ Also update `SECTION_MAP` if the predicate should appear in a specific snapshot
354
355
 
355
356
  The gem includes an MCP server (`claude-memory serve-mcp`) that exposes memory operations as tools. Configuration should be in `.mcp.json` at project root.
356
357
 
357
- Available MCP tools (23 total):
358
+ Available MCP tools (28 total):
358
359
  - **Query & Recall**: `memory.recall`, `memory.recall_index`, `memory.recall_details`, `memory.recall_semantic`, `memory.search_concepts`
359
360
  - **Provenance**: `memory.explain`, `memory.fact_graph`
360
361
  - **Shortcuts**: `memory.decisions`, `memory.conventions`, `memory.architecture`
@@ -362,6 +363,7 @@ Available MCP tools (23 total):
362
363
  - **Management**: `memory.promote`, `memory.reject_fact`, `memory.store_extraction`
363
364
  - **Distillation**: `memory.undistilled`, `memory.mark_distilled`
364
365
  - **Monitoring**: `memory.status`, `memory.stats`, `memory.changes`, `memory.conflicts`, `memory.activity`
366
+ - **Observational layer** (experimental): `memory.observations` (read-only episodic log), `memory.promote_observation` (corroboration-gated observation→fact promotion), `memory.consolidate_observations` (semantic reflection: merge related observations, combine corroboration)
365
367
  - **Maintenance**: `memory.sweep_now`
366
368
  - **Discovery**: `memory.check_setup`, `memory.list_projects`
367
369
 
@@ -373,13 +375,16 @@ ClaudeMemory integrates with Claude Code via hooks in `.claude/settings.json`:
373
375
  - Calls `claude-memory hook ingest` with stdin JSON
374
376
  - Reads transcript delta and updates both global and project databases
375
377
 
376
- - **Context hook**: Triggers on SessionStart
378
+ - **Context hook**: Triggers on SessionStart (and PreCompact — see below)
377
379
  - Calls `claude-memory hook context`
378
380
  - Injects recent facts via `hookSpecificOutput.additionalContext`
381
+ - Two-block layout (observational layer): Block 1 = the episodic observation log (`Observe::ObservationsRenderer`, 🔴-marked), Block 2 = the undistilled "Pending Knowledge Extraction" tail. `ContextInjector#emitted_observation_count` feeds the `hook_context` telemetry.
382
+ - On **PreCompact** the same `claude-memory hook context` injects only the reflection nudge (`ContextInjector#reflection_context` — the promote/consolidate instructions for corroborated/related observations), not the full snapshot, since PreCompact is context-pressure (Mastra's token-threshold analog). `HooksConfigurator` wires it into the PreCompact hook set alongside ingest + sweep.
379
383
 
380
384
  - **Sweep hook**: Triggers on PreCompact/SessionEnd events
381
385
  - Runs time-bounded maintenance on both databases
382
386
  - Cleans up vec0 entries for superseded/expired facts
387
+ - Runs the deterministic observation Reflector (`Observe::Reflector` via `Maintenance#reflect_observations`): dedupes near-identical observations + expires stale 🟢 info-level ones (TTL `observation_info_ttl_days`). Free/no-LLM, provenance-preserving (tombstone). Context-pressure-triggered — the analog of Mastra's token-threshold reflection.
383
388
 
384
389
  - **Nudge hook** (0.11.0+): Triggers on SessionEnd, fires after ingest+sweep
385
390
  - Calls `claude-memory hook nudge`
@@ -394,7 +399,7 @@ Hook commands read JSON payloads from stdin for robustness. Supports `--async` f
394
399
 
395
400
  Local web UI for inspecting memory state. Started via `claude-memory dashboard` (default port 3377). Reads from both global and project databases; no write side effects from page loads.
396
401
 
397
- The dashboard is a thin web layer over the same `Recall`/`Conflicts`/`Trust`/`Moments`/`Knowledge`/`Reuse`/`Health`/`Timeline` classes the MCP server uses. Each panel is backed by a dedicated module under `lib/claude_memory/dashboard/`; `Dashboard::API` holds HTTP-shape glue and per-endpoint formatting (delegating non-trivial logic to the panel classes).
402
+ The dashboard is a thin web layer over the same `Recall`/`Conflicts`/`Trust`/`Moments`/`Knowledge`/`Reuse`/`Health`/`Timeline`/`Observations` classes the MCP server uses. Each panel is backed by a dedicated module under `lib/claude_memory/dashboard/`; `Dashboard::API` holds HTTP-shape glue and per-endpoint formatting (delegating non-trivial logic to the panel classes). The `Observations` panel (`/api/observations`, Advanced → Observations tab) surfaces the episodic layer: counts by status/kind/priority, corroboration + promotion readiness, a compression ratio (source content tokens ÷ observation tokens), and a recent timeline.
398
403
 
399
404
  Connections are released after each request — never holds a WAL writer lock open across page loads.
400
405
 
data/README.md CHANGED
@@ -12,9 +12,13 @@ It automatically:
12
12
  - ✅ Remembers project-specific and global knowledge
13
13
  - ✅ Provides instant recall without manual prompting
14
14
  - ✅ Maintains truth (handles conflicts, supersession)
15
+ - ✅ Tracks *what happened*, not just *what's true* — an episodic observation log alongside the facts (0.13.0+)
16
+ - ✅ Promotes an observation to a fact only after it recurs — a corroboration gate against one-off noise
15
17
 
16
18
  **No API keys. No configuration. Just works.**
17
19
 
20
+ ClaudeMemory now has **two complementary halves**: a *semantic* fact store ("what is true" — your stack, conventions, decisions) and an *episodic* observation layer ("what happened" — the narrative of your sessions). Observations are deduplicated and consolidated automatically, and only graduate to facts once corroborated — so fleeting mentions never harden into false memory. See [Episodic Memory](#episodic-memory-observations).
21
+
18
22
  ## Quick Start
19
23
 
20
24
  ### 1. Install the Gem
@@ -137,6 +141,7 @@ File-searchable questions ("what version is this?") and one-shot code generation
137
141
  - **Progressive Disclosure**: Lightweight queries before full details
138
142
  - **Semantic Shortcuts**: Quick access to decisions, conventions, architecture
139
143
  - **Truth Maintenance**: Automatic conflict resolution
144
+ - **Episodic Memory** (0.13.0+): An append-only observation log of *what happened* alongside the semantic fact store. Auto-consolidated via deterministic + LLM reflection on `PreCompact`/`SessionEnd`; corroborated observations are promoted to facts (anti-hallucination gate). See **[Episodic Memory →](#episodic-memory-observations)**.
140
145
  - **Claude-Powered**: Uses Claude's intelligence to extract facts (no API key needed)
141
146
  - **Token Efficient**: 10x reduction in memory queries with progressive disclosure
142
147
  - **Database Maintenance**: Compact, export, and backup commands
@@ -152,6 +157,36 @@ File-searchable questions ("what version is this?") and one-shot code generation
152
157
 
153
158
  Only metrics and event names are captured by default — verbatim prompts and bodies stay off until you explicitly opt in via `claude-memory otel --capture-prompts`. The receiver binds to `127.0.0.1` only.
154
159
 
160
+ ## Episodic Memory (Observations)
161
+
162
+ Facts answer **"what is true"** (your stack, conventions, decisions). Observations answer **"what happened"** — a narrative log of the moments in your sessions. ClaudeMemory now keeps both, modeled on [Mastra's Observational Memory](docs/influence/mastra-observational-memory.md).
163
+
164
+ | | Facts (semantic) | Observations (episodic) |
165
+ |---|---|---|
166
+ | Capture | Durable truths — `uses_database: sqlite` | Narrative events — "decided to add a corroboration gate to avoid reject-churn" |
167
+ | Change | Explicitly, via supersession/rejection | Automatically — deduped, consolidated, low-priority ones expire |
168
+ | Promotion | — | Promoted to a fact only after corroboration (≥2 sightings) |
169
+
170
+ **Why it's a leap forward:** the distiller used to commit a fact the first time it saw a claim — so a database mentioned once in a comparison could harden into a false `uses_database`. The observation layer makes repeated sighting the gate: an observation becomes a fact only after it recurs. That's an **anti-hallucination defense built into the memory model**, not a cleanup afterthought.
171
+
172
+ **How it runs (no extra API cost):**
173
+ - **Observer** — a regex Layer-1 pass plus Claude-as-observer in the SessionStart context hook emit observations as sessions happen.
174
+ - **Reflector** — deterministic dedup + TTL-expiry runs on `PreCompact`/`SessionEnd`; semantic consolidation rides the next turn's context hook (Claude-as-reflector). Superseded observations are *tombstoned*, never deleted, preserving provenance.
175
+ - **Promotion bridge** — corroborated observations graduate to facts on the corroboration gate.
176
+
177
+ **See it / use it:** the dashboard's **Observations** panel (counts by kind/priority, corroboration + promotion readiness, source→observation compression ratio, recent timeline); the `claude-memory observations` CLI; the `memory.observations` / `memory.promote_observation` / `memory.consolidate_observations` MCP tools; and the `/reflect` skill for a guided survey→consolidate→promote pass.
178
+
179
+ ## What's New in 0.13.0
180
+
181
+ **Episodic Observation Layer** — ClaudeMemory gains a second kind of memory (see [Episodic Memory](#episodic-memory-observations) above):
182
+
183
+ - New `observations` table (schema v19–v20), append-only with `consolidated_into` tombstone lineage and `corroboration_count` / `promoted_at` / `promoted_fact_id` promotion tracking.
184
+ - Two-block SessionStart injection: a stable observation log (🔴-marked) + the undistilled "pending knowledge" tail.
185
+ - Automatic reflection on `PreCompact` (context-pressure, Mastra's token-threshold analog) and `SessionEnd` — deterministic GC shell-side in Ruby, semantic consolidation via the context hook (no extra API spend).
186
+ - Corroboration-gated observation→fact promotion — repeated sightings required before commitment, an anti-hallucination gate against reject-churn from one-off doc/example text.
187
+ - New surfaces: dashboard **Observations** panel, `claude-memory observations` command (+ `claude-memory stats --observations`), `claude-memory audit` observation health checks, three `memory.*observation*` MCP tools, and the `/reflect` skill.
188
+ - Dependencies refreshed to current (sequel, standard, rubocop, and others).
189
+
155
190
  ## What's New in 0.11.0
156
191
 
157
192
  Five user-visible signals so you can answer "is memory still worth it?" with
@@ -238,6 +273,17 @@ gem update claude_memory
238
273
 
239
274
  All database migrations happen automatically. Run `claude-memory doctor` to verify.
240
275
 
276
+ ### After upgrading: refresh the Claude Code plugin
277
+
278
+ If you installed claude-memory as a Claude Code plugin (via the marketplace), pull the latest plugin spec **and reload it in your current session**:
279
+
280
+ ```
281
+ /plugin marketplace update claude-memory
282
+ /reload-plugins
283
+ ```
284
+
285
+ `/plugin marketplace update` refreshes the plugin manifest from the source, but **the new slash commands won't appear until you run `/reload-plugins` (or restart Claude Code)** — slash commands are loaded once at session start. This bites every release that adds a new command (e.g. `/distill-transcripts` in 0.11, `/audit-memory` in 0.12); if a documented slash command doesn't autocomplete after upgrade, `/reload-plugins` is the fix.
286
+
241
287
  See [CHANGELOG.md](CHANGELOG.md) for detailed release notes.
242
288
 
243
289
  ## Troubleshooting
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Migration v19: Add observations table — the episodic memory layer.
4
+ #
5
+ # Facts answer "what is true" (semantic memory); observations answer "what
6
+ # happened" (episodic memory). This is the storage half of Phase 1 of the
7
+ # observational layer (see docs/influence/mastra-observational-memory.md).
8
+ #
9
+ # Observations are append-only: the Reflector consolidates by writing a new
10
+ # observation and pointing superseded ones at it via consolidated_into,
11
+ # rather than hard-deleting — preserving provenance (unlike Mastra's lossy
12
+ # drop). source_content_item_id links each observation back to the raw
13
+ # transcript chunk it was distilled from.
14
+ Sequel.migration do
15
+ up do
16
+ create_table?(:observations) do
17
+ primary_key :id
18
+ String :body, text: true, null: false # dense narrative text — the observation itself
19
+ String :kind, null: false, default: "event" # user_statement | agent_action | tool_result | preference | decision | event
20
+ Integer :priority, null: false, default: 3 # 1=important (🔴), 2=maybe (🟡), 3=info (🟢)
21
+ String :scope, null: false, default: "project" # "project" or "global"
22
+ String :project_path # set for project-scoped observations
23
+ Integer :source_content_item_id # provenance: raw transcript chunk
24
+ Integer :consolidated_into # Reflector lineage: id of the observation this was merged into
25
+ Integer :token_count # for budget / compression math (Phase 2)
26
+ String :status, null: false, default: "active" # "active" or "consolidated"
27
+ String :session_id # session that produced the observation
28
+ String :observed_at, null: false # ISO 8601 event time
29
+ String :created_at, null: false # ISO 8601 row creation time
30
+ String :reflected_at # ISO 8601 — set when the Reflector last touched it
31
+ end
32
+
33
+ run "CREATE INDEX IF NOT EXISTS idx_observations_status ON observations(status)"
34
+ run "CREATE INDEX IF NOT EXISTS idx_observations_scope ON observations(scope)"
35
+ run "CREATE INDEX IF NOT EXISTS idx_observations_observed_at ON observations(observed_at)"
36
+ run "CREATE INDEX IF NOT EXISTS idx_observations_source ON observations(source_content_item_id)"
37
+ run "CREATE INDEX IF NOT EXISTS idx_observations_consolidated_into ON observations(consolidated_into)"
38
+ end
39
+
40
+ down do
41
+ drop_table?(:observations)
42
+ end
43
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Migration v20: observation→fact promotion bridge (Phase 4 of the
4
+ # observational layer).
5
+ #
6
+ # - corroboration_count: how many times this observation has been sighted.
7
+ # Starts at 1; the deterministic Reflector's dedup pass folds duplicates'
8
+ # counts into the keeper instead of just dropping them. This count is the
9
+ # "repeated sightings" signal the promotion gate requires — an observation
10
+ # is only eligible to become a structured fact once corroborated, which
11
+ # doubles as an anti-hallucination gate against one-off doc/example text.
12
+ # - promoted_at / promoted_fact_id: set when an observation has been promoted
13
+ # to a fact, so it is not re-suggested. The observation row is preserved
14
+ # (provenance), it just stops appearing as a promotion candidate.
15
+ Sequel.migration do
16
+ up do
17
+ alter_table(:observations) do
18
+ add_column :corroboration_count, Integer, null: false, default: 1
19
+ add_column :promoted_at, String # ISO 8601, set on promotion
20
+ add_column :promoted_fact_id, Integer # the fact this was promoted into
21
+ end
22
+
23
+ run "CREATE INDEX IF NOT EXISTS idx_observations_promoted_at ON observations(promoted_at)"
24
+ end
25
+
26
+ down do
27
+ alter_table(:observations) do
28
+ drop_column :corroboration_count
29
+ drop_column :promoted_at
30
+ drop_column :promoted_fact_id
31
+ end
32
+ end
33
+ end