claude_memory 0.12.1 → 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.
- checksums.yaml +4 -4
- data/.claude/memory.sqlite3 +0 -0
- data/.claude/rules/claude_memory.generated.md +6 -1
- data/.claude/settings.local.json +2 -1
- data/.claude-plugin/marketplace.json +2 -2
- data/.claude-plugin/plugin.json +2 -2
- data/CHANGELOG.md +28 -0
- data/CLAUDE.md +11 -6
- data/README.md +35 -0
- data/db/migrations/019_add_observations.rb +43 -0
- data/db/migrations/020_add_observation_promotion.rb +33 -0
- data/docs/GETTING_STARTED.md +38 -0
- data/docs/api_stability.md +16 -5
- data/docs/architecture.md +18 -6
- data/docs/audit_runbook.md +67 -0
- data/docs/dashboard.md +28 -0
- data/docs/improvements.md +94 -1
- data/docs/influence/mastra-observational-memory.md +198 -0
- data/docs/influence/strands-agent-sops.md +163 -0
- data/docs/quality_review.md +45 -0
- data/lib/claude_memory/audit/checks.rb +149 -0
- data/lib/claude_memory/audit/runner.rb +4 -0
- data/lib/claude_memory/commands/census_command.rb +1 -1
- data/lib/claude_memory/commands/hook_command.rb +16 -3
- data/lib/claude_memory/commands/initializers/hooks_configurator.rb +3 -1
- data/lib/claude_memory/commands/install_skill_command.rb +4 -0
- data/lib/claude_memory/commands/observations_command.rb +367 -0
- data/lib/claude_memory/commands/registry.rb +1 -0
- data/lib/claude_memory/commands/skills/reflect.md +68 -0
- data/lib/claude_memory/commands/stats_command.rb +60 -1
- data/lib/claude_memory/dashboard/api.rb +4 -0
- data/lib/claude_memory/dashboard/index.html +154 -2
- data/lib/claude_memory/dashboard/observations.rb +115 -0
- data/lib/claude_memory/dashboard/server.rb +1 -0
- data/lib/claude_memory/distill/extraction.rb +6 -4
- data/lib/claude_memory/distill/null_distiller.rb +86 -3
- data/lib/claude_memory/distill/reference_material_detector.rb +4 -1
- data/lib/claude_memory/domain/observation.rb +118 -0
- data/lib/claude_memory/embeddings/generator.rb +1 -1
- data/lib/claude_memory/hook/context_injector.rb +100 -2
- data/lib/claude_memory/mcp/handlers/management_handlers.rb +113 -2
- data/lib/claude_memory/mcp/handlers/query_handlers.rb +48 -1
- data/lib/claude_memory/mcp/instructions_builder.rb +1 -0
- data/lib/claude_memory/mcp/query_guide.rb +28 -0
- data/lib/claude_memory/mcp/tool_definitions.rb +58 -0
- data/lib/claude_memory/mcp/tools.rb +3 -0
- data/lib/claude_memory/observe/observations_renderer.rb +49 -0
- data/lib/claude_memory/observe/reflector.rb +91 -0
- data/lib/claude_memory/publish.rb +53 -1
- data/lib/claude_memory/resolve/resolver.rb +45 -8
- data/lib/claude_memory/store/schema_manager.rb +1 -1
- data/lib/claude_memory/store/sqlite_store.rb +181 -0
- data/lib/claude_memory/sweep/maintenance.rb +15 -1
- data/lib/claude_memory/sweep/sweeper.rb +7 -1
- data/lib/claude_memory/version.rb +1 -1
- data/lib/claude_memory.rb +5 -0
- metadata +11 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 93e553fc87bd36ebe27b8709a28dea9bd206a669491327fc8b6340e6078a8f67
|
|
4
|
+
data.tar.gz: c19722c5028ed5746f58792bb34e57939600deaa0a59b6ff00a421b3ed829bdc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7bd3a98c636c525e2bfc579e9e265ecef3a07311d4ba543d692abc092ddc0822df86832b9e5b53d802597a6dbe7495aa192210d6d4e5961c21383daac22cef09
|
|
7
|
+
data.tar.gz: b587239c11c342551f35937ecdbb108254269306fa98fbaac6680d23f8e427a32f26cbb1524c8f19a08da1773e292b6d75df92e101cb724d59fd65017f275832
|
data/.claude/memory.sqlite3
CHANGED
|
Binary file
|
|
@@ -1,13 +1,18 @@
|
|
|
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-
|
|
4
|
+
Generated: 2026-06-16T18:31:16Z
|
|
5
5
|
-->
|
|
6
6
|
|
|
7
7
|
# Project Memory
|
|
8
8
|
|
|
9
9
|
## Current Decisions
|
|
10
10
|
|
|
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.
|
|
11
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.
|
|
12
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).
|
|
13
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.
|
data/.claude/settings.local.json
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
"plugins": [
|
|
8
8
|
{
|
|
9
9
|
"name": "claude-memory",
|
|
10
|
-
"version": "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
|
]
|
data/.claude-plugin/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-memory",
|
|
3
|
-
"version": "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"
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,34 @@ 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
|
+
|
|
7
35
|
## [0.12.1] - 2026-06-05
|
|
8
36
|
|
|
9
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.
|
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:
|
|
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.
|
|
@@ -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 (
|
|
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)
|
|
@@ -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 (
|
|
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
|
|
@@ -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
|
data/docs/GETTING_STARTED.md
CHANGED
|
@@ -119,6 +119,44 @@ claude-memory promote <fact_id>
|
|
|
119
119
|
"Remember that I prefer descriptive commit messages - make that a global preference"
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
## Two Kinds of Memory: Facts and Observations
|
|
123
|
+
|
|
124
|
+
ClaudeMemory remembers two complementary things:
|
|
125
|
+
|
|
126
|
+
- **Facts** answer *"what is true"* — durable, structured truths about your
|
|
127
|
+
project (`uses_database: sqlite`, conventions, decisions). This is the
|
|
128
|
+
semantic layer the sections above describe.
|
|
129
|
+
- **Observations** answer *"what happened"* — an append-only narrative log of
|
|
130
|
+
the moments in your sessions ("decided to add a corroboration gate so
|
|
131
|
+
fleeting mentions don't harden into facts"). This is the **episodic** layer
|
|
132
|
+
(0.13.0+).
|
|
133
|
+
|
|
134
|
+
| | Facts | Observations |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| Captures | Durable truths | Events / narrative |
|
|
137
|
+
| Changes | Explicitly (supersession, rejection) | Automatically (dedup, consolidation, expiry) |
|
|
138
|
+
| Promotion | — | Promoted to a fact after corroboration (≥2 sightings) |
|
|
139
|
+
|
|
140
|
+
**Why this matters:** the distiller used to commit a fact the first time it
|
|
141
|
+
saw a claim — so a database named once in a comparison could become a false
|
|
142
|
+
`uses_database`. Observations make repeated sighting the gate: an observation
|
|
143
|
+
graduates to a fact only after it recurs. That's an anti-hallucination defense
|
|
144
|
+
built into the memory model.
|
|
145
|
+
|
|
146
|
+
Observations are managed for you — deduplicated and consolidated automatically
|
|
147
|
+
on `PreCompact`/`SessionEnd` at no extra API cost. To see or curate them:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Inspect the episodic log (counts, promotion readiness, compression, recent)
|
|
151
|
+
claude-memory observations
|
|
152
|
+
|
|
153
|
+
# Promote a corroborated observation to a fact
|
|
154
|
+
claude-memory observations promote <id> --predicate uses_database --object sqlite
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The dashboard's **Observations** panel shows the same at a glance, and the
|
|
158
|
+
`/reflect` skill runs a guided survey → consolidate → promote pass.
|
|
159
|
+
|
|
122
160
|
## Setting Up Your First Project
|
|
123
161
|
|
|
124
162
|
### Scenario 1: Fresh Install (New Project)
|
data/docs/api_stability.md
CHANGED
|
@@ -114,7 +114,7 @@ Renaming or repurposing a code is a major-version change.
|
|
|
114
114
|
|
|
115
115
|
## 3. Public MCP tool surface
|
|
116
116
|
|
|
117
|
-
All
|
|
117
|
+
All 28 tools registered via `MCP::ToolDefinitions.all` — 25 stable + 3 experimental (the observational-layer tools below). Argument schemas, return shapes (both `content` and `structuredContent`), and tool-annotation hints (`readOnlyHint`, `idempotentHint`, `destructiveHint`) are **stable** for the listed stable tools.
|
|
118
118
|
|
|
119
119
|
### Stable MCP tools
|
|
120
120
|
|
|
@@ -134,7 +134,7 @@ All 23 tools registered via `MCP::ToolDefinitions.all`. Argument schemas, return
|
|
|
134
134
|
| `memory.facts_by_context` | Context | Stable. |
|
|
135
135
|
| `memory.promote` | Management | Stable. |
|
|
136
136
|
| `memory.reject_fact` | Management | Stable since 0.10.0. |
|
|
137
|
-
| `memory.store_extraction` | Management | Argument schema (`facts`, `entities`, `decisions`) stable. |
|
|
137
|
+
| `memory.store_extraction` | Management | Argument schema (`facts`, `entities`, `decisions`) stable. The `observations` field (Layer-2 observer) is **experimental** while the observational layer is built out. |
|
|
138
138
|
| `memory.undistilled` | Distillation | Stable since 0.10.0. |
|
|
139
139
|
| `memory.mark_distilled` | Distillation | Stable since 0.10.0. |
|
|
140
140
|
| `memory.status` | Monitoring | Stable. |
|
|
@@ -146,6 +146,16 @@ All 23 tools registered via `MCP::ToolDefinitions.all`. Argument schemas, return
|
|
|
146
146
|
| `memory.check_setup` | Discovery | Stable. |
|
|
147
147
|
| `memory.list_projects` | Discovery | Stable since 0.10.0. |
|
|
148
148
|
|
|
149
|
+
### Experimental MCP tools
|
|
150
|
+
|
|
151
|
+
These are registered in `MCP::ToolDefinitions.all` but **not yet covered by the stability guarantees above** — argument schema and return shape may change while the feature is built out.
|
|
152
|
+
|
|
153
|
+
| Tool | Group | Status |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| `memory.observations` | Observational layer | Experimental (0.13.0+). Read-only listing of episodic observations by status/kind/priority with corroboration counts. Return shape may change. |
|
|
156
|
+
| `memory.promote_observation` | Observational layer | Experimental (0.13.0+). Promotes a corroborated observation (≥2 sightings) into a fact; refuses uncorroborated or already-promoted ones (anti-hallucination gate). Args/shape may change. |
|
|
157
|
+
| `memory.consolidate_observations` | Observational layer | Experimental (0.13.0+). Merges related observations into one synthesized row (corroboration combines, sources tombstoned via `consolidated_into`). Args/shape may change. |
|
|
158
|
+
|
|
149
159
|
### Stability of tool responses
|
|
150
160
|
|
|
151
161
|
Both response shapes are stable:
|
|
@@ -201,7 +211,7 @@ Adding a new field to `detail_json` is a stable-surface addition (non-breaking).
|
|
|
201
211
|
|
|
202
212
|
Current covered events (0.11.0):
|
|
203
213
|
|
|
204
|
-
- `hook_context`: `context_length`, `context_tokens` (since 0.11.0), `top_fact_ids`, `fact_count`.
|
|
214
|
+
- `hook_context`: `context_length`, `context_tokens` (since 0.11.0), `top_fact_ids`, `fact_count`. (`observation_count`, added with the observational layer, is additive and **experimental** — not yet on the smoke-gate manifest.)
|
|
205
215
|
- `roi_nudge`: `n`, `used`, `pct`, `prior_count` (all since 0.11.0).
|
|
206
216
|
|
|
207
217
|
`hook_ingest`, `hook_sweep`, `hook_publish` event detail fields are currently **internal** (not on the smoke-gate manifest). Promoting them to stable is a 0.12.x or later task.
|
|
@@ -255,7 +265,7 @@ If you need a feature from one of the internal classes, **open an issue** so we
|
|
|
255
265
|
|
|
256
266
|
### Schema migrations
|
|
257
267
|
|
|
258
|
-
Schema is at v18
|
|
268
|
+
Schema is at v20 (v18 shipped in 0.12.0; v19–v20 add the observational `observations` table in 0.13.0) with 20 migrations under `db/migrations/`. Migrations remain forward-compatible per the round-trip-spec convention (`feedback_round_trip_migration_specs.md`): each release's specs verify that DBs from the prior 3 schema boundaries can be migrated into the current schema without data loss.
|
|
259
269
|
|
|
260
270
|
**What's stable:**
|
|
261
271
|
|
|
@@ -268,6 +278,7 @@ Schema is at v18 as of 0.12.0 with 18 migrations under `db/migrations/`. Migrati
|
|
|
268
278
|
|
|
269
279
|
- The `vec0` virtual-table internals — sqlite-vec evolution may shift representation.
|
|
270
280
|
- `mcp_tool_calls` retention behavior (currently 90 days, configurable); the column set is stable, the retention default is not.
|
|
281
|
+
- The `observations` table (v19–v20, incl. `corroboration_count`/`promoted_at`/`promoted_fact_id`) — episodic layer. Column set may still change while the layer is experimental.
|
|
271
282
|
|
|
272
283
|
**What's internal:**
|
|
273
284
|
|
|
@@ -324,7 +335,7 @@ Listed here for honesty — these surfaces look public but are not.
|
|
|
324
335
|
|
|
325
336
|
- **Dashboard JSON HTTP API.** The `claude-memory dashboard` server's endpoints are an internal interface for the bundled UI. Don't build scripts against `GET /api/trust` etc. — endpoints, response shapes, and even URL paths may change without notice.
|
|
326
337
|
- **`activity_events.detail_json` fields not in `spec/smoke/expected_fields.yml`.** Inspecting a missing field during debugging is fine; relying on it in scripts is not.
|
|
327
|
-
- **The exact text of `additionalContext`.** The Markdown sections (`## Decisions`, `## Conventions`, `## Architecture`, `## Pending Knowledge Extraction`, `## Auto-Memory Mirror`) and their order are stable; the per-fact rendering format inside each section is tuned for prompt quality and may change.
|
|
338
|
+
- **The exact text of `additionalContext`.** The Markdown sections (`## Decisions`, `## Conventions`, `## Architecture`, `## Pending Knowledge Extraction`, `## Auto-Memory Mirror`) and their order are stable; the per-fact rendering format inside each section is tuned for prompt quality and may change. The `## Observations` and `## Observation Reflection` sections (observational layer) and the published `.claude/rules/claude_memory.observations.md` snapshot are **experimental** while the layer is built out.
|
|
328
339
|
- **Internal env vars** (anything not listed in `Configuration` instance methods or in this doc). Examples that exist but are internal: `CLAUDE_MEMORY_LOG_LEVEL`, debug flags surfaced during development.
|
|
329
340
|
- **Test/spec/fixture infrastructure.** `spec/benchmarks/`, `spec/evals/`, `spec/support/` are not public APIs.
|
|
330
341
|
- **Plugin-format paths.** `.claude-plugin/`, `scripts/serve-mcp.sh`, etc. are part of the Claude Code plugin format integration; treat them as opaque.
|
data/docs/architecture.md
CHANGED
|
@@ -14,20 +14,22 @@ ClaudeMemory is architected using Domain-Driven Design (DDD) principles with cle
|
|
|
14
14
|
│
|
|
15
15
|
┌──────────────────────▼──────────────────────────────────────┐
|
|
16
16
|
│ Core Domain Layer │
|
|
17
|
-
│ Domain Models: Fact, Entity, Provenance, Conflict
|
|
17
|
+
│ Domain Models: Fact, Entity, Provenance, Conflict, │
|
|
18
|
+
│ Observation (episodic) │
|
|
18
19
|
│ Value Objects: SessionId, TranscriptPath, FactId │
|
|
19
20
|
│ Null Objects: NullFact, NullExplanation │
|
|
20
21
|
└──────────────────────┬──────────────────────────────────────┘
|
|
21
22
|
│
|
|
22
23
|
┌──────────────────────▼──────────────────────────────────────┐
|
|
23
24
|
│ Business Logic Layer │
|
|
24
|
-
│ Recall → Resolve → Distill → Ingest → Publish
|
|
25
|
-
│
|
|
25
|
+
│ Recall → Resolve (semantic) → Distill → Ingest → Publish │
|
|
26
|
+
│ Observe → Reflect (episodic) → Sweep → Embeddings → │
|
|
27
|
+
│ MCP → Hook │
|
|
26
28
|
└──────────────────────┬──────────────────────────────────────┘
|
|
27
29
|
│
|
|
28
30
|
┌──────────────────────▼──────────────────────────────────────┐
|
|
29
31
|
│ Infrastructure Layer │
|
|
30
|
-
│ Store (SQLite
|
|
32
|
+
│ Store (SQLite v20 + WAL) → FileSystem → Index (FTS5+Vector)│
|
|
31
33
|
│ Templates │
|
|
32
34
|
└─────────────────────────────────────────────────────────────┘
|
|
33
35
|
```
|
|
@@ -129,10 +131,19 @@ end
|
|
|
129
131
|
- `DualQueryTemplate`: Query template handling for dual-database queries
|
|
130
132
|
|
|
131
133
|
#### Resolve (`resolve/`)
|
|
132
|
-
- Truth maintenance and conflict resolution
|
|
134
|
+
- Truth maintenance and conflict resolution (the **semantic** "what is true" layer)
|
|
133
135
|
- **Transaction safety**: Multi-step operations wrapped in DB transactions
|
|
134
136
|
- PredicatePolicy: Controls single vs. multi-value predicates
|
|
135
137
|
- Handles supersession and conflict detection
|
|
138
|
+
- Also persists observations from the extraction inside the same transaction (see Observe & Reflect)
|
|
139
|
+
|
|
140
|
+
#### Observe & Reflect (`observe/`)
|
|
141
|
+
- The **episodic** "what happened" layer, complementing the semantic fact store
|
|
142
|
+
- **Observer**: `NullDistiller` emits high-precision Layer-1 observations (regex); Claude-as-observer enriches them via the SessionStart context hook (Layer-2, no extra API cost)
|
|
143
|
+
- **Reflector** (`Observe::Reflector`): deterministic dedup + TTL-expiry of info-level observations runs on `PreCompact`/`SessionEnd`; semantic consolidation (Claude-as-reflector) rides the next turn's context hook
|
|
144
|
+
- **Append-only/tombstoning**: superseded observations are linked via `consolidated_into`, never deleted — preserving provenance
|
|
145
|
+
- **Promotion bridge**: observations are promoted to facts only after corroboration (≥2 sightings) — an anti-hallucination gate
|
|
146
|
+
- `ObservationsRenderer` formats the injected log; `Domain::Observation` is the immutable value object
|
|
136
147
|
|
|
137
148
|
#### Distill (`distill/`)
|
|
138
149
|
- Extracts facts and entities from transcripts
|
|
@@ -164,7 +175,7 @@ end
|
|
|
164
175
|
|
|
165
176
|
#### MCP (`mcp/`)
|
|
166
177
|
- Model Context Protocol server
|
|
167
|
-
- Exposes
|
|
178
|
+
- Exposes 28 tools including: recall, explain, promote, status, decisions, conventions, architecture, semantic search, check_setup, the observational-layer tools (observations, promote_observation, consolidate_observations), and more
|
|
168
179
|
- `ResponseFormatter`: Consistent MCP response formatting
|
|
169
180
|
- `SetupStatusAnalyzer`: Initialization and version status analysis
|
|
170
181
|
|
|
@@ -212,6 +223,7 @@ end
|
|
|
212
223
|
- `Reuse`: most-used facts within window
|
|
213
224
|
- `Health`: db / hooks / vec checks with actionable fix strings
|
|
214
225
|
- `Timeline`: 30-day daily rollup
|
|
226
|
+
- `Observations` (0.13.0+): episodic-layer panel — counts by status/kind/priority, corroboration + promotion readiness, source→observation compression ratio, recent timeline (first-class main-sidebar panel + Advanced tab)
|
|
215
227
|
- `FactPresenter`, `ScopedFactResolver`: shared rendering / scope-aware ID resolution
|
|
216
228
|
- Connections released after every request — no held WAL writer locks across page loads
|
|
217
229
|
- See [docs/dashboard.md](dashboard.md) for the user-facing guide
|
data/docs/audit_runbook.md
CHANGED
|
@@ -178,6 +178,73 @@ Exit code is `0` when `ok: true`, `1` otherwise. `--no-exit` always returns `0`.
|
|
|
178
178
|
3. Clean up: `claude-memory reject` the historical disputed/superseded rows (or accept them as historical record).
|
|
179
179
|
4. Re-audit.
|
|
180
180
|
|
|
181
|
+
### C011 — Orphaned observations
|
|
182
|
+
|
|
183
|
+
**Severity:** warn
|
|
184
|
+
|
|
185
|
+
**Scope:** both the project and global DBs (observations may be scoped either way).
|
|
186
|
+
|
|
187
|
+
**Triggered when:** an observation has `source_content_item_id` set but no `content_items` row with that id exists.
|
|
188
|
+
|
|
189
|
+
**Why it matters:** An observation's `source_content_item_id` is its provenance link back to the transcript chunk it was distilled from. A dangling pointer means the source row was pruned (or never existed), so the observation can no longer be explained — breaking the same provenance guarantee facts enjoy. Observations with a `nil` source (e.g. consolidated ones synthesized from several sources) are *not* flagged.
|
|
190
|
+
|
|
191
|
+
**Remediation:**
|
|
192
|
+
- Inspect with `memory.observations` (or the dashboard Observations tab).
|
|
193
|
+
- The table is append-only — do **not** delete. If the provenance is genuinely unrecoverable, let the Reflector consolidate or expire the row on the next PreCompact/SessionEnd pass.
|
|
194
|
+
|
|
195
|
+
### C012 — Observation promotion consistency
|
|
196
|
+
|
|
197
|
+
**Severity:** error
|
|
198
|
+
|
|
199
|
+
**Scope:** both DBs.
|
|
200
|
+
|
|
201
|
+
**Triggered when:** any of the following promotion-state invariants is violated —
|
|
202
|
+
- `promoted_at` is set but `promoted_fact_id` is `NULL`;
|
|
203
|
+
- `promoted_fact_id` points at a fact that does not exist;
|
|
204
|
+
- `promoted_fact_id` points at a fact that is **not** active (rejected/superseded);
|
|
205
|
+
- `promoted_fact_id` is set but `promoted_at` is `NULL`.
|
|
206
|
+
|
|
207
|
+
**Why it matters:** Promotion is meant to be atomic — `mark_observation_promoted` sets both `promoted_at` and `promoted_fact_id` pointing at a freshly-created, active fact. Half-set state means the write ran partially, or the target fact was later rejected/superseded, leaving the observation pointing at nothing usable. The promotion bridge keys off these columns, so an inconsistent row either re-promotes (duplicate facts) or is silently stuck.
|
|
208
|
+
|
|
209
|
+
**Remediation:**
|
|
210
|
+
1. `claude-memory explain <fact_id>` on the `promoted_fact_id` to see why the fact is missing/inactive.
|
|
211
|
+
2. If the fact was intentionally rejected, re-open the observation for re-promotion via `memory.promote_observation`.
|
|
212
|
+
3. If `mark_observation_promoted` half-ran, re-run promotion so both columns are set together.
|
|
213
|
+
|
|
214
|
+
### C013 — Observation tombstone-chain validity
|
|
215
|
+
|
|
216
|
+
**Severity:** error
|
|
217
|
+
|
|
218
|
+
**Scope:** both DBs.
|
|
219
|
+
|
|
220
|
+
**Triggered when:** any of the following tombstone invariants is violated —
|
|
221
|
+
- `consolidated_into` points at a non-existent observation;
|
|
222
|
+
- `consolidated_into` is a self-link (`consolidated_into == id`);
|
|
223
|
+
- a row is `status='active'` yet carries a `consolidated_into` target;
|
|
224
|
+
- a row is `status='consolidated'` yet has no `consolidated_into` keeper.
|
|
225
|
+
|
|
226
|
+
**Why it matters:** Supersession is append-only: a merged-away observation gets `status='consolidated'` and `consolidated_into` pointing at the surviving keeper, preserving lineage instead of hard-deleting (unlike Mastra's lossy drop). A broken chain corrupts that lineage — recall could surface a tombstoned row, or a consolidated row could orphan its history. A self-link or active-but-tombstoned row is a Reflector bug, not user error.
|
|
227
|
+
|
|
228
|
+
**Remediation:**
|
|
229
|
+
- Inspect with `memory.observations`.
|
|
230
|
+
- Re-running the deterministic Reflector (fires on PreCompact/SessionEnd) re-derives consolidation for dangling links.
|
|
231
|
+
- A self-link or `active` + `consolidated_into` row signals a Reflector defect — file it rather than hand-editing the append-only table.
|
|
232
|
+
|
|
233
|
+
### C014 — Observation status / corroboration sanity
|
|
234
|
+
|
|
235
|
+
**Severity:** warn
|
|
236
|
+
|
|
237
|
+
**Scope:** both DBs.
|
|
238
|
+
|
|
239
|
+
**Triggered when:** an observation has a `status` outside `active`/`consolidated`/`expired`, or a `corroboration_count` less than 1.
|
|
240
|
+
|
|
241
|
+
**Why it matters:** Every observation should carry a known lifecycle status and at least one sighting (a fresh insert counts as 1; the migration default is 1). An unknown status means a migration or an external writer bypassed `insert_observation`; a `corroboration_count < 1` means `increment_corroboration` math went negative. Both break downstream behavior — recall filters key off `status`, and the promotion gate keys off `corroboration_count`.
|
|
242
|
+
|
|
243
|
+
**Remediation:**
|
|
244
|
+
- Inspect with `memory.observations`.
|
|
245
|
+
- For a bad `corroboration_count`, re-derive sighting counts via the Reflector's dedup pass.
|
|
246
|
+
- For an unknown status, find the writer that bypassed `insert_observation` (the only sanctioned insert path).
|
|
247
|
+
|
|
181
248
|
## Adding a new check
|
|
182
249
|
|
|
183
250
|
The audit is extensible by design.
|