A local, privacy-first RAG memory system for Claude Code, built on SQLite with Go.
Purpose: Replace JSON-based Memory MCP with SQLite-backed implementation offering superior search capabilities (FTS5 + future vector search).
Status: Phase 6 Complete — Core Intelligence
Key differentiators:
- Privacy-first: All data stays local (no cloud, no telemetry)
- Single-file portability: One
memory.dbfile for backup/sync - Drop-in replacement: Same MCP API as
@modelcontextprotocol/server-memory - Incremental complexity: FTS5 + vector hybrid search with RRF fusion
| Command | Purpose |
|---|---|
make build |
Build CLI binary with version info |
make build-server |
Build MCP server binary |
make build-all |
Build both CLI and server binaries |
make test |
Run tests with race detector |
make test-coverage |
Generate coverage report (HTML) |
make run |
Build and run CLI with test.db |
make lint |
Run golangci-lint |
make fmt |
Format code (go fmt + goimports) |
make tidy |
Clean up go.mod dependencies |
make clean |
Remove binaries and test artifacts |
make install |
Install CLI binary to ~/bin |
make install-server |
Install MCP server to ~/bin |
make install-all |
Install both binaries to ~/bin |
make install-plugin |
Build binaries to bin/ for plugin deployment |
make migrate |
Migrate from JSON Memory MCP to SQLite |
cmd/
├── memory/main.go → CLI entry point (cobra, lipgloss)
└── server/main.go → MCP server entry point (JSON-RPC over stdio)
internal/
├── storage/ → SQLite operations (sqlx-based)
│ ├── store.go → Database initialization, schema, lifecycle
│ ├── entity.go → Entity CRUD with versioning support
│ ├── observation.go → Observation add/delete with fact types
│ ├── relation.go → Relation CRUD (bidirectional queries)
│ ├── search.go → FTS5 search with BM25 ranking
│ ├── hybrid.go → Hybrid search (FTS5 + vector with RRF fusion)
│ ├── embedding.go → Ollama/DMR embedding client
│ ├── vector.go → Vector storage and cosine similarity
│ ├── fusion.go → RRF and weighted score fusion
│ ├── consolidate.go → Observation deduplication
│ ├── session.go → Session capture & recall (sessions as entities)
│ ├── migration.go → Goose migration runner
│ └── migrations/ → Goose Go migrations (001-008)
└── mcp/ → MCP protocol implementation
├── types.go → JSON-RPC 2.0 types, MCP protocol types
└── handlers.go → Tool handlers with hybrid search support
.claude-plugin/
├── plugin.json → Plugin metadata
└── hooks.json → Hook configuration (Go CLI commands)
.mcp.json → MCP server configuration
agents/ → Specialized agents (memory-updater)
skills/ → Skill definitions (memory-processor, codebase-analyzer)
commands/ → Command documentation (init, status, sync, calibrate)
Data flow: Claude Code (stdio) → MCP Server (Go, JSON-RPC) → Storage Layer → SQLite (FTS5 + embeddings)
Storage patterns:
- sqlx for struct scanning (db tags, no manual Scan calls)
- goose for migrations (versioned, idempotent, rollback support)
- Transactions for atomic operations
- Foreign key cascades (ON DELETE CASCADE)
- WAL mode for better concurrency
- FTS5 kept in sync via triggers
Phase 2 Features:
- Hybrid search with RRF fusion (k=60)
- Ollama embeddings (nomic-embed-text)
- Fact types: static, dynamic, session_turn
- Entity versioning (supersedes_id, is_latest, version)
Phase 3 Features:
- Auto-embed on write (new memories immediately vector-searchable)
- Recency-boosted context injection (exponential decay, ~30 day half-life)
- Observation consolidation (substring-based dedup)
- Go CLI hooks (
mark42 hook {session-start,post-tool-use,stop})
See docs/ARCHITECTURE.md for:
- Complete database schema (entities, observations, relations)
- FTS5 indexing strategy with triggers
- Hybrid search algorithm (keyword + vector)
- Phase 2 embedding pipeline design
Entity management:
mark42 entity create <name> <type> [--obs "observation"]- Create entity with observationsmark42 entity get <name>- Retrieve entity with observationsmark42 entity list [--type <type>]- List all entities, optionally filtered by typemark42 entity delete <name>- Delete entity (cascades to observations/relations)
Observation management:
mark42 obs add <entity-name> <content>- Add observation to entitymark42 obs delete <entity-name> <content>- Remove specific observation
Relation management:
mark42 rel create <from> <to> <type>- Create relation between entitiesmark42 rel list <entity-name>- List all relations (bidirectional)mark42 rel delete <from> <to> <type>- Delete specific relation
Search and exploration:
mark42 search <query>- FTS5 full-text search (BM25 ranked)mark42 graph- Export entire knowledge graph
Session management:
mark42 session capture <project>- Capture session from JSON stdinmark42 session list [--project P] [--limit N]- List captured sessionsmark42 session get <name>- Show session details + summarymark42 session recall [project] [--hours N] [--tokens N]- Recall recent session summaries
Utilities:
mark42 init- Initialize database schemamark42 stats- Show database statisticsmark42 version- Display version infomark42 migrate --from <json> --to <db>- Migrate from JSON Memory MCP
Default database: ~/.claude/memory.db (override with --db <path>)
- TDD Required: Write failing test first
- FTS5 Focus: Phase 1 is keyword search only
- API Compatibility: Match existing Memory MCP tools exactly
The project includes a complete Claude Code plugin implementation:
Agents (specialized behavior):
memory-updater.md- Orchestrates CLAUDE.md updates and knowledge extraction
Skills (reusable operations):
memory-processor/SKILL.md- Updates AUTO-MANAGED sections in CLAUDE.mdcodebase-analyzer/SKILL.md- Analyzes code patterns and conventions
Commands (user-facing):
init.md- Initialize plugin and databasestatus.md- Show memory system statussync.md- Synchronize dirty files to memorycalibrate.md- Tune memory extraction parameters
Hooks (lifecycle integration via Go CLI):
mark42 hook session-start- Injects session recall + knowledge graph contextmark42 hook post-tool-use- Tracks modified files + session eventsmark42 hook stop- Triggers session capture + memory sync
docs/ARCHITECTURE.md- System design, schema, search algorithmsdocs/DESIGN_DECISIONS.md- Rationale for SQLite, Go, FTS5-first, hybrid searchinternal/storage/store.go- Database schema definitions and initializationinternal/storage/search.go- FTS5 search implementation (BM25 ranking)internal/mcp/handlers.go- MCP tool implementations (JSON-RPC handlers)cmd/server/main.go- MCP server entry point (stdio communication)Makefile- Build commands with version tagging.gitignore- Excludes binary, test.db, coverage reports, IDE files
- Unit tests: Each package has
*_test.go - Integration tests:
test/integration/with real SQLite - Benchmark: Compare against JSON Memory MCP
| Package | Purpose |
|---|---|
modernc.org/sqlite |
Pure Go SQLite driver (no CGO) |
github.com/jmoiron/sqlx |
SQL extensions with struct scanning |
github.com/pressly/goose/v3 |
Database migrations |
github.com/spf13/cobra |
CLI framework for commands |
github.com/charmbracelet/lipgloss |
Terminal styling for output |
github.com/charmbracelet/log |
Structured logging |
| Tool | Storage Layer | MCP Handler | Status |
|---|---|---|---|
create_entities |
✅ CreateEntity | ✅ DONE | Implemented |
create_or_update_entities |
✅ CreateOrUpdateEntity | ✅ DONE | Versioning support |
create_relations |
✅ CreateRelation | ✅ DONE | Implemented |
add_observations |
✅ AddObservation | ✅ DONE | Implemented |
delete_entities |
✅ DeleteEntity | ✅ DONE | Implemented |
delete_observations |
✅ DeleteObservation | ✅ DONE | Implemented |
delete_relations |
✅ DeleteRelation | ✅ DONE | Implemented |
read_graph |
✅ ReadGraph | ✅ DONE | Implemented |
search_nodes |
✅ Search | ✅ DONE | Implemented |
open_nodes |
✅ GetEntity | ✅ DONE | Implemented |
get_context |
✅ GetContextForInjection | ✅ DONE | Context injection |
get_recent_context |
✅ GetRecentContext | ✅ DONE | Recency-first retrieval |
summarize_entity |
✅ GetEntity+ListRelations | ✅ DONE | Entity summary with metadata |
consolidate_memories |
✅ ConsolidateObservations | ✅ DONE | Observation deduplication |
capture_session |
✅ CreateSession+Events | ✅ DONE | Session capture with events |
recall_sessions |
✅ GetRecentSessionSummaries | ✅ DONE | Cross-session recall |
invalidate_observation |
✅ InvalidateObservation | ✅ DONE | Temporal validity |
get_entity_history |
✅ GetObservationHistory | ✅ DONE | Full observation history |
All 18 MCP tools implemented. Server communicates via JSON-RPC 2.0 over stdio.
Phase 1: Foundation ✅
- ✅ SQLite schema for knowledge graph (entities, observations, relations)
- ✅ FTS5 full-text search with BM25 ranking
- ✅ Storage layer complete (CRUD operations)
- ✅ MCP server with standard Memory API (JSON-RPC 2.0 over stdio)
- ✅ Claude Code plugin structure (agents, skills, commands, hooks)
Phase 2 (Complete): Semantic Search ✅
- ✅ Hybrid search infrastructure (FTS5 + vector with RRF fusion)
- ✅ Ollama embedding client (nomic-embed-text compatible)
- ✅ Static/dynamic fact types for context injection
- ✅ Entity versioning (version chains, is_latest flag)
- ✅ sqlx for struct scanning (eliminates manual SQL parsing)
- ✅ goose for database migrations (versioned, idempotent)
Phase 3 (Complete): Intelligence ✅
- ✅ Auto-embed on write (observations immediately vector-searchable via MCP)
- ✅ Recency-boosted context injection (
get_contextwith exponential decay scoring) - ✅
get_recent_contexttool for mid-session recency-first retrieval - ✅
summarize_entitytool with observations, relations, and version history - ✅
consolidate_memoriestool for observation deduplication - ✅ Go CLI hook commands (
mark42 hook {session-start,post-tool-use,stop}) - ✅ Stop hook fires every session (not just file-edit sessions)
- ✅
Embedderinterface for testable auto-embed (fake embedder in tests)
Phase 4 (Complete): Session Capture & Recall ✅
- ✅ Sessions modeled as entities (no new tables, reuses FTS5+vector infrastructure)
- ✅
capture_sessionMCP tool with events and summary - ✅
recall_sessionsMCP tool for cross-session context - ✅ CLI:
mark42 session capture|list|get|recall - ✅ Hook integration: post-tool-use tracks events, stop triggers capture, session-start injects recall
- ✅ New fact types:
session_event,session_summary
Phase 6 (Complete): Core Intelligence ✅
- ✅ access_count tracking wired into importance scoring (H2.6)
- ✅ Temporal validity: valid_from/valid_until, InvalidateObservation, GetObservationHistory (H2.3)
- ✅ Auto-detection of superseded observations on write (cosine similarity) (H2.3)
- ✅ invalidate_observation and get_entity_history MCP tools (H2.3)
- ✅ Semantic consolidation mode with embedding similarity (H2.4)
- ✅ Query-aware context injection in get_context (H2.5)
Phase 7: Analytics & Advanced Decay (Future)
- Automatic importance decay for stale memories
- Memory analytics (decay curves, most-accessed entities)
- Smarter consolidation with vector similarity
Error handling:
- Return
ErrNotFoundfor missing entities (defined inentity.go) - Return
ErrEntityExistsfor duplicate entity creation - Wrap errors with context:
fmt.Errorf("failed to X: %w", err) - Check
sql.ErrNoRowsand convert to domain error
sqlx patterns:
- Use
db.Get(&struct, query)for single-row queries - Use
db.Select(&slice, query)for multi-row queries - Add
db:"column_name"tags to structs for column mapping - Column aliases in SQL must match db tags
Migrations (goose):
- Go migrations in
internal/storage/migrations/ - All migrations must be idempotent (check before alter)
- Use
goose.AddMigrationContext()in init() - Run with
store.Migrate()orstore.MigrateWithLogging()
Transaction safety:
- Use
defer tx.Rollback()immediately afterBegin() - Explicit
tx.Commit()on success - Pattern: Begin → defer Rollback → operations → Commit
Testing:
- Table-driven tests in
*_test.gofiles - Integration tests use real SQLite (not mocks)
- Test files in same package (
storage_test) ExpectedMigrationCountconstant for migration tests
See docs/DESIGN_DECISIONS.md for detailed rationale:
- SQLite over alternatives: Zero config, single file, battle-tested, FTS5 + sqlite-vec extensions
- Go over TypeScript/Python/Rust: Single binary, fast startup, excellent SQLite bindings, cross-compilation
- FTS5 first, vectors second: 80% value with 20% effort, validates architecture before complexity
- Ollama for embeddings: Local privacy, simple setup, model flexibility, already common
- Hybrid search: Exact matches + semantic understanding, tunable weights
- Drop-in API compatibility: Zero migration effort, reversible, existing workflows preserved