text blobs.
This separation ensures that when an agent queries what is the current routing strategy?, it only reads from the Decision Kernel. When it asks why did we choose this strategy?, it queries the Evidence Vault. Cross-contamination is architecturally impossible.
import Database from 'better-sqlite3';
export class ContextEngine {
private db: Database.Database;
constructor(dbPath: string) {
this.db = new Database(dbPath, { verbose: console.log });
this.db.pragma('journal_mode = WAL');
this.db.pragma('synchronous = NORMAL');
this.initializeSchema();
}
private initializeSchema(): void {
this.db.exec(`
CREATE TABLE IF NOT EXISTS decision_kernel (
id TEXT PRIMARY KEY,
subject TEXT NOT NULL,
decision TEXT NOT NULL,
valid_from TEXT NOT NULL,
valid_until TEXT,
superseded_by TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS evidence_vault (
id TEXT PRIMARY KEY,
content_hash TEXT UNIQUE NOT NULL,
raw_text TEXT NOT NULL,
source_session TEXT,
indexed_at TEXT DEFAULT (datetime('now'))
);
CREATE VIRTUAL TABLE IF NOT EXISTS evidence_fts USING fts5(
raw_text,
content='evidence_vault',
content_rowid='id'
);
CREATE TRIGGER IF NOT EXISTS after_evidence_insert AFTER INSERT ON evidence_vault
BEGIN
INSERT INTO evidence_fts(rowid, raw_text) VALUES (new.id, new.raw_text);
END;
`);
}
}
Why this choice: SQLite's WAL mode enables concurrent reads without blocking writes, critical for agent workflows that stream context while logging new evidence. FTS5 triggers ensure full-text indexing stays synchronized without application-level overhead.
Automated extraction of facts from agent conversations corrupts long-term memory with hallucinated claims. The system enforces a human-in-the-loop ratification workflow. Suggestions are staged in a pending queue; only explicitly approved entries migrate to the Decision Kernel.
export class RatificationQueue {
private db: Database.Database;
constructor(db: Database.Database) {
this.db = db;
}
stageProposal(subject: string, proposedDecision: string): string {
const id = crypto.randomUUID();
const stmt = this.db.prepare(`
INSERT INTO pending_proposals (id, subject, proposed_decision, status, created_at)
VALUES (?, ?, ?, 'pending', datetime('now'))
`);
stmt.run(id, subject, proposedDecision);
return id;
}
ratify(proposalId: string, kernel: ContextEngine): boolean {
const proposal = this.db.prepare('SELECT * FROM pending_proposals WHERE id = ?').get(proposalId) as any;
if (!proposal || proposal.status !== 'pending') return false;
const insertDecision = kernel.db.prepare(`
INSERT INTO decision_kernel (id, subject, decision, valid_from)
VALUES (?, ?, ?, datetime('now'))
`);
insertDecision.run(proposalId, proposal.subject, proposal.proposed_decision);
this.db.prepare('UPDATE pending_proposals SET status = ? WHERE id = ?').run('ratified', proposalId);
return true;
}
}
Why this choice: Decoupling suggestion from persistence prevents unvetted LLM inferences from polluting the truth store. The queue acts as a deterministic filter, ensuring only human-ratified facts establish long-term constraints.
3. Graph-Aware Recall Pipeline
Pure vector retrieval fails to capture temporal validity and cross-entity relationships. The recall engine combines three mechanisms:
- FTS5 Full-Text Search: Fast lexical matching against the Evidence Vault.
- Reciprocal Rank Fusion (RRF): Merges multiple result sets (keyword, entity, temporal) into a single ranked list, penalizing noise.
- Entity Expansion: Walks
subject β related_decision β supersede_chain to surface conflicting historical claims before they cause invariant breakage.
export class RecallEngine {
private db: Database.Database;
constructor(db: Database.Database) {
this.db = db;
}
async resolveContext(query: string): Promise<ContextResult[]> {
const keywordResults = this.db.prepare(`
SELECT v.id, v.raw_text, rank
FROM evidence_fts f
JOIN evidence_vault v ON f.rowid = v.id
WHERE f.raw_text MATCH ?
ORDER BY rank
LIMIT 5
`).all(query);
const entityResults = this.db.prepare(`
SELECT id, subject, decision, valid_from
FROM decision_kernel
WHERE subject LIKE ? AND valid_until IS NULL
LIMIT 5
`).all(`%${query}%`);
const rrf = this.reciprocalRankFusion(keywordResults, entityResults);
const expanded = await this.expandContradictions(rrf);
return expanded;
}
private reciprocalRankFusion(keyword: any[], entity: any[]): any[] {
const k = 60;
const scores = new Map<string, number>();
keyword.forEach((item, i) => scores.set(item.id, (scores.get(item.id) || 0) + 1 / (k + i + 1)));
entity.forEach((item, i) => scores.set(item.id, (scores.get(item.id) || 0) + 1 / (k + i + 1)));
return Array.from(scores.entries())
.sort(([, a], [, b]) => b - a)
.map(([id]) => id);
}
private async expandContradictions(ids: string[]): Promise<ContextResult[]> {
const placeholders = ids.map(() => '?').join(',');
return this.db.prepare(`
SELECT dk.id, dk.subject, dk.decision, dk.valid_from, dk.superseded_by
FROM decision_kernel dk
WHERE dk.id IN (${placeholders})
ORDER BY dk.valid_from DESC
`).all(...ids) as ContextResult[];
}
}
Why this choice: RRF balances lexical recall with structural precision. The contradiction expansion step explicitly surfaces superseded decisions, preventing agents from applying deprecated constraints to new code. This eliminates the "confidently wrong" outputs that plague pure embedding systems.
4. Host Abstraction Facade
The memory engine must remain agnostic to the agent host. A single TypeScript facade exposes standardized methods that map to CLI verbs, MCP tool definitions, or REST endpoints. The underlying kernel remains unchanged regardless of whether the caller is Claude Code, Codex, or a custom IDE plugin.
export class ContextFacade {
private engine: ContextEngine;
private recall: RecallEngine;
private queue: RatificationQueue;
constructor(dbPath: string) {
this.engine = new ContextEngine(dbPath);
this.recall = new RecallEngine(this.engine.db);
this.queue = new RatificationQueue(this.engine.db);
}
async injectContext(query: string): Promise<string> {
const results = await this.recall.resolveContext(query);
return JSON.stringify(results, null, 2);
}
async logEvidence(sessionId: string, text: string): Promise<void> {
const hash = crypto.createHash('sha256').update(text).digest('hex');
const exists = this.engine.db.prepare('SELECT id FROM evidence_vault WHERE content_hash = ?').get(hash);
if (!exists) {
this.engine.db.prepare('INSERT INTO evidence_vault (id, content_hash, raw_text, source_session) VALUES (?, ?, ?, ?)').run(
crypto.randomUUID(), hash, text, sessionId
);
}
}
}
Why this choice: Centralizing state management behind a facade eliminates dual-write risks and ensures consistent retrieval semantics across all agent hosts. The same database file follows the developer across sessions, branches, and tools.
Pitfall Guide
1. Kernel Contamination
Explanation: Storing raw conversation logs and ratified decisions in the same table or query path causes retrieval noise. Agents conflate discarded experiments with locked-in invariants, leading to hallucinated continuity.
Fix: Enforce physical table separation. Route decision queries exclusively to the Decision Kernel and evidence queries to the Evidence Vault. Never join them during recall.
Explanation: Allowing LLMs to automatically extract and persist "facts" without human ratification corrupts the truth store. Unvetted claims become permanent constraints that break future refactors.
Fix: Implement a pending proposal queue. Require explicit developer approval before any suggestion migrates to the Decision Kernel. Log all rejected proposals for audit trails.
3. Vector-Only Retrieval
Explanation: Embedding-based search optimizes for lexical proximity, not decision intent or temporal validity. Without structural expansion, agents miss cross-entity relationships and historical contradictions.
Fix: Combine FTS5 full-text search with Reciprocal Rank Fusion and entity graph traversal. Weight temporal validity and supersede chains higher than raw similarity scores.
4. Silent Constraint Overwrites
Explanation: Failing to surface conflicting historical claims results in agents applying deprecated invariants to new code. This breaks scoring strategies, routing rules, and architectural boundaries.
Fix: Always enable contradiction lookup during recall. Return superseded decisions alongside current ones, explicitly marking validity windows and replacement chains.
5. Externalized State Management
Explanation: Offloading context to cloud memory services introduces network latency, recurring costs, and privacy violations. For proprietary repositories, this trade-off is unacceptable.
Fix: Use local-first SQLite with WAL mode. Keep all context on disk, encrypted if necessary. Expose state through a local MCP server or CLI facade to maintain zero external dependencies.
6. Monolithic Context Files
Explanation: Relying on a single MEMORY.md across multiple projects or complex refactors hits context window limits and loses structural integrity. Flat files cannot enforce temporal validity or entity relationships.
Fix: Replace flat files with a relational schema. Use subject-based partitioning, validity windows, and explicit supersede chains to maintain multi-project context without degradation.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Solo developer, private repo | Local SQLite + Dual-Kernel | Zero latency, full privacy, no recurring costs | $0 infrastructure |
| Team collaboration, shared context | Local SQLite + Git-synced DB | Version-controlled context, conflict resolution via PRs | Minimal CI/CD overhead |
| Enterprise compliance, audit trails | Local SQLite + Encrypted Vault + Ratification Logs | Meets data sovereignty, provides immutable decision history | Storage + encryption overhead |
| High-throughput agent fleet | Local SQLite + Read Replicas (WAL) | Scales concurrent reads without blocking writes | Connection pooling complexity |
Configuration Template
// context.config.ts
export const ContextConfig = {
dbPath: process.env.CONTEXT_DB_PATH || '~/.context-store/project.db',
walMode: true,
synchronous: 'NORMAL',
fts5: {
tokenizer: 'unicode61',
removeAccents: true,
contentTable: 'evidence_vault',
contentRowId: 'id'
},
recall: {
rrfK: 60,
maxResults: 10,
enableContradictionLookup: true,
validityWindowDays: 90
},
promotion: {
requireHumanRatification: true,
autoArchiveRawLogs: true,
retentionDays: 180
},
host: {
mcpServerPort: 3000,
cliVerbPrefix: 'ctx',
jsonOutput: true
}
};
Quick Start Guide
- Install dependencies:
npm install better-sqlite3 (Node β₯ 22 recommended for native node:sqlite fallback)
- Initialize the store: Run
npx ts-node init-db.ts --path ./project-context.db to create tables, triggers, and WAL configuration
- Start the facade: Execute
npx ts-node server.ts --port 3000 --db ./project-context.db to expose MCP/CLI endpoints
- Inject first context: Use
ctx inject --query "authentication strategy" to test recall pipeline
- Validate ratification: Run
ctx propose --subject "auth" --decision "JWT with refresh rotation" followed by ctx ratify --id <proposal_id> to confirm promotion gate
This architecture eliminates session amnesia without sacrificing speed, privacy, or developer control. By enforcing structural separation, explicit ratification, and graph-aware retrieval, AI agents maintain persistent, accurate context across sessions, branches, and tools.