# LLMemory - AI Agent Memory System ## Overview LLMemory is a persistent memory/journal system for AI agents, providing grep-like search with fuzzy matching for efficient knowledge retrieval across sessions. ## Core Requirements ### Storage - Store memories with metadata: `created_at`, `entered_by`, `expires_at`, `tags` - Local SQLite database (no cloud dependencies) - Content limit: 10KB per memory - Tag-based organization with normalized schema ### Retrieval - Grep/ripgrep-like query syntax (familiar to AI agents) - Fuzzy matching with configurable threshold - Relevance ranking (BM25 + edit distance + recency) - Metadata filtering (tags, dates, agent) - Token-efficient: limit results, prioritize quality over quantity ### Interface - Global CLI tool: `memory [command]` - Commands: `store`, `search`, `list`, `prune`, `stats`, `export`, `import` - `--agent-context` flag for comprehensive agent documentation - Output formats: plain text, JSON, markdown ### Integration - OpenCode plugin architecture - Expose API for programmatic access - Auto-extraction of `*Remember*` patterns from agent output ## Implementation Strategy ### Phase 1: MVP (Simple LIKE Search) **Goal:** Ship in 2-3 days, validate concept with real usage **Features:** - Basic schema (memories, tags tables) - Core commands (store, search, list, prune) - Simple LIKE-based search with wildcards - Plain text output - Tag filtering - Expiration handling **Success Criteria:** - Can store and retrieve memories - Search works for exact/prefix matches - Tags functional - Performance acceptable for <500 memories **Database:** ```sql CREATE TABLE memories ( id INTEGER PRIMARY KEY, content TEXT NOT NULL CHECK(length(content) <= 10000), created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), entered_by TEXT, expires_at INTEGER ); CREATE TABLE tags ( id INTEGER PRIMARY KEY, name TEXT UNIQUE COLLATE NOCASE ); CREATE TABLE memory_tags ( memory_id INTEGER, tag_id INTEGER, PRIMARY KEY (memory_id, tag_id), FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE, FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE ); ``` **Search Logic:** ```javascript // Simple case-insensitive LIKE with wildcards WHERE LOWER(content) LIKE LOWER('%' || ? || '%') AND (expires_at IS NULL OR expires_at > strftime('%s', 'now')) ORDER BY created_at DESC ``` ### Phase 2: FTS5 Migration **Trigger:** Dataset > 500 memories OR query latency > 500ms **Features:** - Add FTS5 virtual table - Migrate existing data - Implement BM25 ranking - Support boolean operators (AND/OR/NOT) - Phrase queries with quotes - Prefix matching with `*` **Database Addition:** ```sql CREATE VIRTUAL TABLE memories_fts USING fts5( content, content='memories', content_rowid='id', tokenize='porter unicode61 remove_diacritics 2' ); -- Triggers to keep in sync CREATE TRIGGER memories_ai AFTER INSERT ON memories BEGIN INSERT INTO memories_fts(rowid, content) VALUES (new.id, new.content); END; -- ... (update/delete triggers) ``` **Search Logic:** ```javascript // FTS5 match with BM25 ranking SELECT m.*, mf.rank FROM memories_fts mf JOIN memories m ON m.id = mf.rowid WHERE memories_fts MATCH ? ORDER BY mf.rank ``` ### Phase 3: Fuzzy Layer **Goal:** Handle typos and inexact matches **Features:** - Trigram indexing - Levenshtein distance calculation - Intelligent cascade: exact (FTS5) → fuzzy (trigram) - Combined relevance scoring - Configurable threshold (default: 0.7) **Database Addition:** ```sql CREATE TABLE trigrams ( trigram TEXT NOT NULL, memory_id INTEGER NOT NULL, position INTEGER NOT NULL, FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE ); CREATE INDEX idx_trigrams_trigram ON trigrams(trigram); ``` **Search Logic:** ```javascript // 1. Try FTS5 exact match let results = ftsSearch(query); // 2. If <5 results, try fuzzy if (results.length < 5) { const fuzzyResults = trigramSearch(query, threshold); results = mergeAndDedupe(results, fuzzyResults); } // 3. Re-rank by combined score results.forEach(r => { r.score = 0.4 * bmr25Score + 0.3 * trigramSimilarity + 0.2 * editDistanceScore + 0.1 * recencyScore; }); ``` ## Architecture ### Technology Stack - **Language:** Node.js (JavaScript/TypeScript) - **Database:** SQLite with better-sqlite3 - **CLI Framework:** Commander.js - **Output Formatting:** chalk (colors), marked-terminal (markdown) - **Date Parsing:** date-fns - **Testing:** Vitest ### Directory Structure ``` llmemory/ ├── src/ │ ├── cli.js # CLI entry point │ ├── commands/ │ │ ├── store.js │ │ ├── search.js │ │ ├── list.js │ │ ├── prune.js │ │ ├── stats.js │ │ └── export.js │ ├── db/ │ │ ├── connection.js # Database setup │ │ ├── schema.js # Schema definitions │ │ ├── migrations.js # Migration runner │ │ └── queries.js # Prepared statements │ ├── search/ │ │ ├── like.js # Phase 1: LIKE search │ │ ├── fts.js # Phase 2: FTS5 search │ │ ├── fuzzy.js # Phase 3: Fuzzy matching │ │ └── ranking.js # Relevance scoring │ ├── utils/ │ │ ├── dates.js │ │ ├── tags.js │ │ ├── formatting.js │ │ └── validation.js │ └── extractors/ │ └── remember.js # Auto-extract *Remember* patterns ├── test/ │ ├── search.test.js │ ├── fuzzy.test.js │ ├── integration.test.js │ └── fixtures/ ├── docs/ │ ├── ARCHITECTURE.md │ ├── AGENT_GUIDE.md # For --agent-context │ ├── CLI_REFERENCE.md │ └── API.md ├── bin/ │ └── memory # Executable ├── package.json ├── SPECIFICATION.md # This file ├── IMPLEMENTATION_PLAN.md └── README.md ``` ### CLI Interface #### Commands ```bash # Store a memory memory store [options] --tags Comma-separated tags --expires Expiration date (ISO 8601 or natural language) --entered-by Agent/user identifier --file Read content from file # Search memories memory search [options] --tags Filter by tags (AND) --any-tag Filter by tags (OR) --after Created after date --before Created before date --entered-by Filter by creator --limit Max results (default: 10) --offset Pagination offset --fuzzy Enable fuzzy matching (default: auto) --no-fuzzy Disable fuzzy matching --threshold <0-1> Fuzzy match threshold (default: 0.7) --json JSON output --markdown Markdown output # List recent memories memory list [options] --limit Max results (default: 20) --offset Pagination offset --tags Filter by tags --sort Sort by: created, expires, content --order Sort order (default: desc) # Prune expired memories memory prune [options] --dry-run Show what would be deleted --force Skip confirmation --before Delete before date (even if not expired) # Show statistics memory stats [options] --tags Show tag frequency --agents Show memories per agent # Export/import memory export Export to JSON memory import Import from JSON # Global options --agent-context Display agent documentation --db Custom database location --verbose Detailed logging --quiet Suppress non-error output ``` #### Query Syntax ```bash # Basic memory search "docker compose" # Both terms (implicit AND) memory search "docker AND compose" # Explicit AND memory search "docker OR podman" # Either term memory search "docker NOT swarm" # Exclude term memory search '"exact phrase"' # Phrase search memory search "docker*" # Prefix matching # With filters memory search "docker" --tags devops,networking memory search "error" --after "2025-10-01" memory search "config" --entered-by investigate-agent # Fuzzy (automatic typo tolerance) memory search "dokcer" # Finds "docker" memory search "kuberntes" # Finds "kubernetes" ``` ### Data Schema #### Complete Schema (All Phases) ```sql -- Core tables CREATE TABLE memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL CHECK(length(content) <= 10000), created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), entered_by TEXT, expires_at INTEGER, CHECK(expires_at IS NULL OR expires_at > created_at) ); CREATE INDEX idx_memories_created ON memories(created_at DESC); CREATE INDEX idx_memories_expires ON memories(expires_at) WHERE expires_at IS NOT NULL; CREATE INDEX idx_memories_entered_by ON memories(entered_by); CREATE TABLE tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE COLLATE NOCASE, created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) ); CREATE INDEX idx_tags_name ON tags(name); CREATE TABLE memory_tags ( memory_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, PRIMARY KEY (memory_id, tag_id), FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE, FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE ); CREATE INDEX idx_memory_tags_tag ON memory_tags(tag_id); -- Phase 2: FTS5 CREATE VIRTUAL TABLE memories_fts USING fts5( content, content='memories', content_rowid='id', tokenize='porter unicode61 remove_diacritics 2' ); CREATE TRIGGER memories_ai AFTER INSERT ON memories BEGIN INSERT INTO memories_fts(rowid, content) VALUES (new.id, new.content); END; CREATE TRIGGER memories_ad AFTER DELETE ON memories BEGIN DELETE FROM memories_fts WHERE rowid = old.id; END; CREATE TRIGGER memories_au AFTER UPDATE ON memories BEGIN DELETE FROM memories_fts WHERE rowid = old.id; INSERT INTO memories_fts(rowid, content) VALUES (new.id, new.content); END; -- Phase 3: Trigrams CREATE TABLE trigrams ( trigram TEXT NOT NULL, memory_id INTEGER NOT NULL, position INTEGER NOT NULL, FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE ); CREATE INDEX idx_trigrams_trigram ON trigrams(trigram); CREATE INDEX idx_trigrams_memory ON trigrams(memory_id); -- Metadata CREATE TABLE metadata ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); INSERT INTO metadata (key, value) VALUES ('schema_version', '1'); INSERT INTO metadata (key, value) VALUES ('created_at', strftime('%s', 'now')); -- Useful view CREATE VIEW memories_with_tags AS SELECT m.id, m.content, m.created_at, m.entered_by, m.expires_at, GROUP_CONCAT(t.name, ',') as tags FROM memories m LEFT JOIN memory_tags mt ON m.id = mt.memory_id LEFT JOIN tags t ON mt.tag_id = t.id GROUP BY m.id; ``` ## Search Algorithm Details ### Phase 1: LIKE Search ```javascript function searchWithLike(query, filters = {}) { const { tags = [], after, before, enteredBy, limit = 10 } = filters; let sql = ` SELECT DISTINCT m.id, m.content, m.created_at, m.entered_by, m.expires_at, GROUP_CONCAT(t.name, ',') as tags FROM memories m LEFT JOIN memory_tags mt ON m.id = mt.memory_id LEFT JOIN tags t ON mt.tag_id = t.id WHERE LOWER(m.content) LIKE LOWER(?) AND (m.expires_at IS NULL OR m.expires_at > strftime('%s', 'now')) `; const params = [`%${query}%`]; // Tag filtering if (tags.length > 0) { sql += ` AND m.id IN ( SELECT memory_id FROM memory_tags WHERE tag_id IN (SELECT id FROM tags WHERE name IN (${tags.map(() => '?').join(',')})) GROUP BY memory_id HAVING COUNT(*) = ? )`; params.push(...tags, tags.length); } // Date filtering if (after) { sql += ' AND m.created_at >= ?'; params.push(after); } if (before) { sql += ' AND m.created_at <= ?'; params.push(before); } // Agent filtering if (enteredBy) { sql += ' AND m.entered_by = ?'; params.push(enteredBy); } sql += ' GROUP BY m.id ORDER BY m.created_at DESC LIMIT ?'; params.push(limit); return db.prepare(sql).all(...params); } ``` ### Phase 2: FTS5 Search ```javascript function searchWithFTS5(query, filters = {}) { const ftsQuery = buildFTS5Query(query); let sql = ` SELECT m.id, m.content, m.created_at, m.entered_by, m.expires_at, GROUP_CONCAT(t.name, ',') as tags, mf.rank as relevance FROM memories_fts mf JOIN memories m ON m.id = mf.rowid LEFT JOIN memory_tags mt ON m.id = mt.memory_id LEFT JOIN tags t ON mt.tag_id = t.id WHERE memories_fts MATCH ? AND (m.expires_at IS NULL OR m.expires_at > strftime('%s', 'now')) `; const params = [ftsQuery]; // Apply filters (same as Phase 1) // ... sql += ' GROUP BY m.id ORDER BY mf.rank LIMIT ?'; params.push(limit); return db.prepare(sql).all(...params); } function buildFTS5Query(query) { // Handle quoted phrases if (query.includes('"')) { return query; // Already FTS5 compatible } // Handle explicit operators if (/\b(AND|OR|NOT)\b/i.test(query)) { return query.toUpperCase(); } // Implicit AND between terms const terms = query.split(/\s+/).filter(t => t.length > 0); return terms.join(' AND '); } ``` ### Phase 3: Fuzzy Search ```javascript function searchWithFuzzy(query, threshold = 0.7, limit = 10) { const queryTrigrams = extractTrigrams(query); if (queryTrigrams.length === 0) return []; // Find candidates by trigram overlap const sql = ` SELECT m.id, m.content, m.created_at, m.entered_by, m.expires_at, COUNT(DISTINCT tr.trigram) as trigram_matches FROM memories m JOIN trigrams tr ON tr.memory_id = m.id WHERE tr.trigram IN (${queryTrigrams.map(() => '?').join(',')}) AND (m.expires_at IS NULL OR m.expires_at > strftime('%s', 'now')) GROUP BY m.id HAVING trigram_matches >= ? ORDER BY trigram_matches DESC LIMIT ? `; const minMatches = Math.ceil(queryTrigrams.length * threshold); const candidates = db.prepare(sql).all(...queryTrigrams, minMatches, limit * 2); // Calculate edit distance and combined score const scored = candidates.map(c => { const editDist = levenshtein(query.toLowerCase(), c.content.toLowerCase().substring(0, query.length * 3)); const trigramSim = c.trigram_matches / queryTrigrams.length; const normalizedEditDist = 1 - (editDist / Math.max(query.length, c.content.length)); return { ...c, relevance: 0.6 * trigramSim + 0.4 * normalizedEditDist }; }); return scored .filter(r => r.relevance >= threshold) .sort((a, b) => b.relevance - a.relevance) .slice(0, limit); } function extractTrigrams(text) { const normalized = text .toLowerCase() .replace(/[^\w\s]/g, ' ') .replace(/\s+/g, ' ') .trim(); if (normalized.length < 3) return []; const padded = ` ${normalized} `; const trigrams = []; for (let i = 0; i < padded.length - 2; i++) { const trigram = padded.substring(i, i + 3); if (trigram.trim().length === 3) { trigrams.push(trigram); } } return [...new Set(trigrams)]; // Deduplicate } function levenshtein(a, b) { if (a.length === 0) return b.length; if (b.length === 0) return a.length; let prevRow = Array(b.length + 1).fill(0).map((_, i) => i); for (let i = 0; i < a.length; i++) { let curRow = [i + 1]; for (let j = 0; j < b.length; j++) { const cost = a[i] === b[j] ? 0 : 1; curRow.push(Math.min( curRow[j] + 1, // deletion prevRow[j + 1] + 1, // insertion prevRow[j] + cost // substitution )); } prevRow = curRow; } return prevRow[b.length]; } ``` ### Intelligent Cascade ```javascript function search(query, filters = {}) { const { fuzzy = 'auto', threshold = 0.7 } = filters; // Phase 2 or Phase 3 installed? const hasFTS5 = checkTableExists('memories_fts'); const hasTrigrams = checkTableExists('trigrams'); let results; // Try FTS5 if available if (hasFTS5) { results = searchWithFTS5(query, filters); } else { results = searchWithLike(query, filters); } // If too few results and fuzzy available, try fuzzy if (results.length < 5 && hasTrigrams && (fuzzy === 'auto' || fuzzy === true)) { const fuzzyResults = searchWithFuzzy(query, threshold, filters.limit); results = mergeResults(results, fuzzyResults); } return results; } function mergeResults(exact, fuzzy) { const seen = new Set(exact.map(r => r.id)); const merged = [...exact]; for (const result of fuzzy) { if (!seen.has(result.id)) { merged.push(result); seen.add(result.id); } } return merged; } ``` ## Memory Format Guidelines ### Good Memory Examples ```bash # Technical discovery with context memory store "Docker Compose: Use 'depends_on' with 'condition: service_healthy' to ensure dependencies are ready. Prevents race conditions in multi-container apps." \ --tags docker,docker-compose,best-practices # Configuration pattern memory store "Nginx reverse proxy: Set 'proxy_set_header X-Real-IP \$remote_addr' to preserve client IP through proxy. Required for rate limiting and logging." \ --tags nginx,networking,security # Error resolution memory store "Node.js ENOSPC: Increase inotify watch limit with 'echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p'. Affects webpack, nodemon." \ --tags nodejs,linux,troubleshooting # Version-specific behavior memory store "TypeScript 5.0+: 'const' type parameters preserve literal types. Example: 'function id(x: T): T'. Better inference for generic functions." \ --tags typescript,types # Temporary info with expiration memory store "Staging server: https://staging.example.com:8443. Credentials in 1Password. Valid through Q1 2025." \ --tags staging,infrastructure \ --expires "2025-04-01" ``` ### Anti-Patterns to Avoid ```bash # Too vague ❌ memory store "Fixed Docker issue" ✅ memory store "Docker: Use 'docker system prune -a' to reclaim space. Removes unused images, containers, networks." # Widely known ❌ memory store "Git is a version control system" ✅ memory store "Git worktree: 'git worktree add -b feature ../feature' creates parallel working dir without cloning." # Sensitive data ❌ memory store "DB password: hunter2" ✅ memory store "Production DB credentials stored in 1Password vault 'Infrastructure'" # Multiple unrelated facts ❌ memory store "Docker uses namespaces. K8s has pods. Nginx is fast." ✅ memory store "Docker container isolation uses Linux namespaces: PID, NET, MNT, UTS, IPC." ``` ## Auto-Extraction: *Remember* Pattern When agents output text containing `*Remember*: [fact]`, automatically extract and store: ```javascript function extractRememberPatterns(text, context = {}) { const rememberRegex = /\*Remember\*:?\s+(.+?)(?=\n\n|\*Remember\*|$)/gis; const matches = [...text.matchAll(rememberRegex)]; return matches.map(match => { const content = match[1].trim(); const tags = autoExtractTags(content, context); const expires = autoExtractExpiration(content); return { content, tags, expires, entered_by: context.agentName || 'auto-extract' }; }); } function autoExtractTags(content, context) { const tags = new Set(); // Technology patterns const techPatterns = { 'docker': /docker|container|compose/i, 'kubernetes': /k8s|kubernetes|kubectl/i, 'git': /\bgit\b|github|gitlab/i, 'nodejs': /node\.?js|npm|yarn/i, 'postgresql': /postgres|postgresql/i, 'nixos': /nix|nixos|flake/i }; for (const [tag, pattern] of Object.entries(techPatterns)) { if (pattern.test(content)) tags.add(tag); } // Category patterns if (/error|bug|fix/i.test(content)) tags.add('troubleshooting'); if (/performance|optimize/i.test(content)) tags.add('performance'); if (/security|vulnerability/i.test(content)) tags.add('security'); return Array.from(tags); } function autoExtractExpiration(content) { const patterns = [ { re: /valid (through|until) (\w+ \d{4})/i, parse: m => new Date(m[2]) }, { re: /expires? (on )?([\d-]+)/i, parse: m => new Date(m[2]) }, { re: /temporary|temp/i, parse: () => addDays(new Date(), 90) }, { re: /Q([1-4]) (\d{4})/i, parse: m => quarterEnd(m[1], m[2]) } ]; for (const { re, parse } of patterns) { const match = content.match(re); if (match) { try { return parse(match).toISOString(); } catch {} } } return null; } ``` ## Migration Strategy ### Phase 1 → Phase 2 (LIKE → FTS5) ```javascript async function migrateToFTS5(db) { console.log('Migrating to FTS5...'); // Create FTS5 table db.exec(` CREATE VIRTUAL TABLE memories_fts USING fts5( content, content='memories', content_rowid='id', tokenize='porter unicode61 remove_diacritics 2' ); `); // Populate from existing data db.exec(` INSERT INTO memories_fts(rowid, content) SELECT id, content FROM memories; `); // Create triggers db.exec(` CREATE TRIGGER memories_ai AFTER INSERT ON memories BEGIN INSERT INTO memories_fts(rowid, content) VALUES (new.id, new.content); END; CREATE TRIGGER memories_ad AFTER DELETE ON memories BEGIN DELETE FROM memories_fts WHERE rowid = old.id; END; CREATE TRIGGER memories_au AFTER UPDATE ON memories BEGIN DELETE FROM memories_fts WHERE rowid = old.id; INSERT INTO memories_fts(rowid, content) VALUES (new.id, new.content); END; `); // Update schema version db.prepare('UPDATE metadata SET value = ? WHERE key = ?').run('2', 'schema_version'); console.log('FTS5 migration complete!'); } ``` ### Phase 2 → Phase 3 (Add Trigrams) ```javascript async function migrateToTrigrams(db) { console.log('Adding trigram support...'); // Create trigrams table db.exec(` CREATE TABLE trigrams ( trigram TEXT NOT NULL, memory_id INTEGER NOT NULL, position INTEGER NOT NULL, FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE ); CREATE INDEX idx_trigrams_trigram ON trigrams(trigram); CREATE INDEX idx_trigrams_memory ON trigrams(memory_id); `); // Populate from existing memories const memories = db.prepare('SELECT id, content FROM memories').all(); const insertTrigram = db.prepare('INSERT INTO trigrams (trigram, memory_id, position) VALUES (?, ?, ?)'); const insertMany = db.transaction((memories) => { for (const memory of memories) { const trigrams = extractTrigrams(memory.content); trigrams.forEach((trigram, position) => { insertTrigram.run(trigram, memory.id, position); }); } }); insertMany(memories); // Update schema version db.prepare('UPDATE metadata SET value = ? WHERE key = ?').run('3', 'schema_version'); console.log('Trigram migration complete!'); } ``` ## Performance Targets ### Latency - Phase 1 (LIKE): <50ms for <500 memories - Phase 2 (FTS5): <100ms for 10K memories - Phase 3 (Fuzzy): <200ms for 10K memories with fuzzy ### Storage - Base: ~500 bytes per memory (average) - FTS5 index: +30% overhead (~150 bytes) - Trigrams: +200% overhead (~1KB) - prune common trigrams ### Scalability - Phase 1: Up to 500 memories - Phase 2: Up to 50K memories - Phase 3: Up to 100K+ memories ## Testing Strategy ### Unit Tests - Search algorithms (LIKE, FTS5, fuzzy) - Trigram extraction - Levenshtein distance - Tag filtering - Date parsing - Relevance scoring ### Integration Tests - Store → retrieve flow - Search with various filters - Expiration pruning - Export/import - Migration Phase 1→2→3 ### Performance Tests - Benchmark with 1K, 10K, 100K memories - Query latency measurement - Index size monitoring - Memory usage profiling ## OpenCode Integration ### Plugin Structure ```javascript // plugin.js - OpenCode plugin entry point export default { name: 'llmemory', version: '1.0.0', description: 'Persistent memory system for AI agents', commands: { 'memory': './src/cli.js' }, api: { store: async (content, options) => { const { storeMemory } = await import('./src/db/queries.js'); return storeMemory(content, options); }, search: async (query, options) => { const { search } = await import('./src/search/index.js'); return search(query, options); }, extractRemember: async (text, context) => { const { extractRememberPatterns } = await import('./src/extractors/remember.js'); return extractRememberPatterns(text, context); } }, onInstall: async () => { const { initDatabase } = await import('./src/db/connection.js'); await initDatabase(); console.log('LLMemory installed! Try: memory --agent-context'); } }; ``` ### Usage from Other Plugins ```javascript import llmemory from '@opencode/llmemory'; // Store a memory await llmemory.api.store( 'NixOS: flake.lock must be committed for reproducible builds', { tags: ['nixos', 'build-system'], entered_by: 'investigate-agent' } ); // Search const results = await llmemory.api.search('nixos builds', { tags: ['nixos'], limit: 5 }); // Auto-extract from agent output const memories = await llmemory.api.extractRemember(agentOutput, { agentName: 'optimize-agent', currentTask: 'performance-tuning' }); ``` ## Next Steps 1. ✅ Create project directory and documentation 2. **Implement MVP (Phase 1)**: Basic CLI, LIKE search, core commands 3. **Test with real usage**: Validate concept, collect metrics 4. **Migrate to FTS5 (Phase 2)**: When dataset > 500 or latency issues 5. **Add fuzzy layer (Phase 3)**: For production-quality search 6. **OpenCode integration**: Plugin API and auto-extraction 7. **Documentation**: Complete agent guide, CLI reference, API docs ## Success Metrics - **Usability**: Agents can store/retrieve memories intuitively - **Quality**: Search returns relevant results, not noise - **Performance**: Queries complete in <100ms for typical datasets - **Adoption**: Agents use memory system regularly in workflows - **Token Efficiency**: Results are high-quality, limited in quantity