ClaudeMemory is architected using Domain-Driven Design (DDD) principles with clear separation of concerns across multiple layers. The codebase has undergone significant refactoring to improve maintainability, testability, and performance.
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ CLI (Router) → Commands (32 classes) → Configuration │
└──────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────────────┐
│ Core Domain Layer │
│ Domain Models: Fact, Entity, Provenance, Conflict │
│ Value Objects: SessionId, TranscriptPath, FactId │
│ Null Objects: NullFact, NullExplanation │
└──────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────────────┐
│ Business Logic Layer │
│ Recall → Resolve → Distill → Ingest → Publish │
│ Sweep → Embeddings → MCP → Hook │
└──────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────────────┐
│ Infrastructure Layer │
│ Store (SQLite v17 + WAL) → FileSystem → Index (FTS5+Vector)│
│ Templates │
└─────────────────────────────────────────────────────────────┘
Purpose: Handle user interaction and command routing
Components:
- CLI (
cli.rb): Thin router that dispatches to command classes - Commands (
commands/): 34 command classes, each handling one CLI command - Configuration (
configuration.rb): Centralized ENV access and path calculation
Key Principles:
- Single Responsibility: Each command does one thing
- Dependency Injection: I/O isolated for testing (stdout, stderr, stdin)
- Command Pattern: Uniform interface via
BaseCommand#call(args) - Registry Pattern: Dynamic command lookup via
Commands::Registry
Example:
# Thin router
class CLI
def run
command_class = Commands::Registry.find(@args.first || "help")
command = command_class.new(stdout: @stdout, stderr: @stderr, stdin: @stdin)
command.call(@args[1..-1] || [])
end
end
# Individual command
class DoctorCommand < BaseCommand
def call(args)
# All logic here, fully testable
end
endPurpose: Encapsulate business logic and domain concepts
Components:
-
Fact: Subject-predicate-object triples with validation
- Methods:
active?,superseded?,global?,project? - Validates: Required fields, confidence 0-1
- Methods:
-
Entity: Named entities (databases, frameworks, people)
- Methods:
database?,framework?,person? - Validates: Required type, name, slug
- Methods:
-
Provenance: Evidence linking facts to sources
- Methods:
stated?,inferred? - Validates: Required fact_id, content_item_id
- Methods:
-
Conflict: Contradictions between facts
- Methods:
open?,resolved? - Validates: Required fact IDs
- Methods:
- SessionId: Type-safe session identifiers
- TranscriptPath: Type-safe file paths
- FactId: Type-safe positive integer IDs
- TextBuilder: Searchable text construction from entities/facts/decisions
- ResultSorter: Result ranking and sorting logic
- FactQueryBuilder: SQL query construction for fact retrieval
- All are immutable (frozen) and self-validating
- NullFact: Represents non-existent fact (eliminates nil checks)
- NullExplanation: Represents non-existent explanation
- Result: Success/Failure for consistent error handling
Key Principles:
- Immutability: All domain objects are frozen
- Self-validation: Invalid objects cannot be constructed
- Rich behavior: Business logic in domain objects, not scattered
- Tell, Don't Ask: Objects have behavior, not just data
Purpose: Implement core memory operations
Components:
- Queries facts from global and project databases
- Optimization: Batch queries to eliminate N+1 issues
- Before: 2N+1 queries for N facts
- After: 3 queries total (FTS + batch facts + batch receipts)
- Supports scope filtering (project, global, all)
- Returns facts with provenance receipts
DualQueryTemplate: Query template handling for dual-database queries
- Truth maintenance and conflict resolution
- Transaction safety: Multi-step operations wrapped in DB transactions
- PredicatePolicy: Controls single vs. multi-value predicates
- Handles supersession and conflict detection
- Extracts facts and entities from transcripts
- Pluggable design (currently NullDistiller stub)
- Detects scope hints (global vs. project)
- Delta-based transcript ingestion
- Tracks cursor position to avoid reprocessing
- Handles file shrinking (compaction)
- Generates markdown snapshots
- FileSystem abstraction: Testable without disk I/O
- Modes: shared (repo), local (uncommitted), home (user dir)
- Maintenance and pruning
- Time-bounded execution
- Cleans up old content and expired facts
Generator: Built-in TF-IDF embedding generation (always available, no dependencies)FastembedAdapter: High-quality local embeddings via fastembed-rb (BAAI/bge-small-en-v1.5)- 384-dimensional normalized vectors (both generators produce same dimensionality)
- Asymmetric query/passage encoding (FastEmbed) for better retrieval accuracy
Similarity: Cosine similarity calculations and top-k ranking- Dependency injection:
Recall.new(store, embedding_generator: adapter)
- Model Context Protocol server
- Exposes 19 tools including: recall, explain, promote, status, decisions, conventions, architecture, semantic search, check_setup, and more
ResponseFormatter: Consistent MCP response formattingSetupStatusAnalyzer: Initialization and version status analysis
- Reads JSON from stdin
- Routes to ingest/sweep/publish
Purpose: Handle external systems and I/O
Components:
- SQLiteStore: Direct database access via Sequel (schema v17)
- StoreManager: Manages dual databases (global + project)
- Transaction safety: Atomic multi-step operations
- WAL mode: Write-Ahead Logging for better concurrency
- Schema migrations with per-migration transactions
- FileSystem: Real filesystem wrapper
- InMemoryFileSystem: Fast in-memory testing
- Interface:
exist?,read,write,file_hash - Enables testing without tempdir cleanup
- SQLite FTS5 for lexical full-text search
- Vector embeddings for semantic similarity (384-dimensional vectors)
- Hybrid search modes: text-only, vector-only, or both (FTS5 + vector)
- Hook configuration examples (
hooks.example.json) - Output style templates (
output-styles/memory-aware.md) - Setup and configuration scaffolding
- Server: WEBrick HTTP server (default port 3377), starts via
claude-memory dashboard - API: HTTP-shape glue + per-endpoint formatting; routes/delegates to panel classes
- Panels (each backed by a dedicated class with focused responsibility):
Trust: weekly moments, fingerprint, utilization, feedback ratio, needs-review, token_budget (p50/p95/avg over 30d, 0.11.0+), quality_score (live 30-day window + historical baseline, 0.11.0+)Moments: feed-first activity stream with kind classificationKnowledge: predicate-grouped fact summary (incl. References section)Conflicts: display-layer dedup with bulk-reject helperReuse: most-used facts within windowHealth: db / hooks / vec checks with actionable fix stringsTimeline: 30-day daily rollupFactPresenter,ScopedFactResolver: shared rendering / scope-aware ID resolution
- Connections released after every request — no held WAL writer locks across page loads
- See docs/dashboard.md for the user-facing guide
Key Principles:
- Ports and Adapters: Clear interfaces for external systems
- Dependency Injection: Real vs. test implementations
- Transaction boundaries: ACID guarantees
- Each CLI command is a separate class
- Uniform interface:
call(args) → exit_code - Easy to add new commands without modifying router
Commands::Registrymaps command names to classes- Dynamic dispatch
- Easy to see all available commands
NullFact,NullExplanationeliminate nil checks- Prevents NilClass errors
- More expressive:
explanation.is_a?(NullExplanation)vsexplanation.nil?
SessionId,TranscriptPath,FactIdprevent primitive obsession- Self-validating at construction
- Type safety in method signatures
Store::SQLiteStoreabstracts data access- Could be extended with explicit repository layer
PredicatePolicydetermines fact resolution behavior- Pluggable distillers
BaseCommandprovides common functionality- Subclasses override
call(args)
Transcript File
↓
TranscriptReader (delta detection)
↓
Ingester (content storage)
↓
Distiller (fact extraction)
↓
Resolver (truth maintenance)
↓
SQLiteStore (persistence)
User Query
↓
Recall (FTS search)
↓
Batch Queries (facts + receipts)
↓
Result Assembly
↓
Response
SQLiteStore (active facts)
↓
Publish (snapshot generation)
↓
FileSystem (write)
↓
.claude/rules/claude_memory.generated.md
Problem: Recall queried each fact and its receipts individually Solution: Batch query all facts, batch query all receipts Impact: 2N+1 queries → 3 queries (7x faster for 10 facts)
Problem: Tests hit disk for every file operation Solution: InMemoryFileSystem for tests Impact: ~10x faster test suite
Problem: Multi-step operations could leave inconsistent state Solution: Wrap in database transactions Impact: Data integrity guaranteed
Problem: Database locks prevented concurrent reads during writes Solution: Enable Write-Ahead Logging (WAL) mode in SQLite Impact: MCP server and hooks can operate concurrently without blocking
Problem: Traditional semantic search requires cloud API calls for embedding generation Solution: Local ONNX model via fastembed-rb (BAAI/bge-small-en-v1.5, 384-dimensional vectors) Impact: High-quality semantic search with no API costs, no network dependency after initial model download
- Commands: Test with mocked I/O
- Domain models: Test validation and behavior
- Value objects: Test construction and equality
- Store operations: Use real SQLite database
- Recall queries: Test with seeded data
- InMemoryFileSystem: No disk I/O
- Mocked stores: Avoid database setup
- Dependency injection throughout
- No global state
- Each test independent
- CLI: 881 lines (god object)
- Tests: 277 examples
- N+1 queries in Recall
- Direct File I/O
- Primitive obsession
- Scattered ENV access
- CLI: 41 lines (thin router, 95% reduction from original)
- Tests: 988 examples (257% increase)
- Batch queries (3 total)
- FileSystem abstraction
- Value objects (SessionId, TranscriptPath, FactId)
- Centralized Configuration
- 4 domain models with business logic
- 34 command classes
- 25 MCP tools
- Semantic search with local embeddings (FastEmbed + TF-IDF fallback)
- Schema v17 with WAL mode
- Proper Sequel migrations (vs. hand-rolled)
- Explicit Repository layer
- More domain models (Explanation, ContentItem)
- GraphQL API for external access
- Event sourcing for fact history
- CQRS: Separate read/write models
- Background job processing
- Multi-database support (PostgreSQL, MySQL)
- Distributed memory across multiple Claude instances
- SOLID Principles: Single Responsibility, Open/Closed, Dependency Inversion
- Domain-Driven Design: Rich domain models, ubiquitous language
- Ports and Adapters: Infrastructure abstractions
- Tell, Don't Ask: Behavior in objects
- Sandi Metz - Practical Object-Oriented Design in Ruby
- Eric Evans - Domain-Driven Design
- Martin Fowler - Patterns of Enterprise Application Architecture
- Avdi Grimm - Confident Ruby
- Gary Bernhardt - Boundaries talk
The refactored architecture provides:
- ✅ Clear separation of concerns
- ✅ High testability (988 tests)
- ✅ Type safety (value objects)
- ✅ Null safety (null objects)
- ✅ Performance (batch queries, in-memory FS, WAL mode)
- ✅ Maintainability (small, focused classes)
- ✅ Extensibility (easy to add commands/tools)
- ✅ Semantic search (local FastEmbed ONNX model, TF-IDF fallback)
The codebase now follows best practices for Ruby applications and is well-positioned for future growth.