memory_search
Search stored memories from previous sessions using keywords or semantic similarity. Filter by type, project, tags, and importance to quickly retrieve relevant context.
Instructions
Search stored memories using FTS5 full-text search or semantic/embedding similarity. Default mode returns compact index entries (id, type, truncated content) to save tokens — set detail=true for full content. Supports filtering by type, project, tags, and minimum importance. Read-only. Use memory_get to fetch full content for specific IDs found in search results. Use memory_related to explore graph connections from a known memory.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Natural language search query | |
| type | No | Filter by memory type (default: "all") | |
| project | No | Filter by project (optional) | |
| tags | No | Filter by tags — matches if any tag is present (optional) | |
| semantic | No | Use embedding-based semantic search instead of keyword FTS5 (default: false) | |
| minImportance | No | Minimum importance threshold 1-10 (default: 1) | |
| limit | No | Max results to return (default: 10) | |
| detail | No | Return full content per memory (default: false — returns compact index only) |
Implementation Reference
- src/tools-v2/memory.ts:232-253 (handler)Main handler for memory_search tool. Validates input via MemorySearchSchema, then delegates to performSemanticSearch (if semantic=true) or performFTSSearch.
export async function handleMemorySearch(args: unknown): Promise<CallToolResult> { return logger.withTool('memory_search', async () => { // 입력 검증 const parsed = MemorySearchSchema.safeParse(args); if (!parsed.success) { return { content: [{ type: 'text' as const, text: `Validation error: ${parsed.error.message}` }], isError: true }; } const { query, type, project, semantic, limit, minImportance, detail } = parsed.data; // 시맨틱 검색 if (semantic) { return performSemanticSearch(query, type, project, limit, minImportance, detail); } // FTS 검색 return performFTSSearch(query, type, project, limit, minImportance, detail); }, args as Record<string, unknown>); } - src/tools-v2/memory.ts:255-348 (helper)FTS (Full-Text Search) implementation. Uses SQLite FTS5 MATCH with decay-based re-ranking, returns results with progressive disclosure (summary or detail).
async function performFTSSearch( query: string, type?: string, project?: string, limit: number = 10, minImportance: number = 1, detail: boolean = false ): Promise<CallToolResult> { const ftsQuery = query.split(/\s+/).filter(w => w.length > 1).join(' OR '); let sql = ` SELECT m.id, m.content, m.memory_type, m.tags, m.project, m.importance, m.created_at, m.access_count FROM memories_fts fts JOIN memories m ON m.id = fts.rowid WHERE memories_fts MATCH ? AND m.importance >= ? `; const params: unknown[] = [ftsQuery || query, minImportance]; if (type) { sql += ` AND m.memory_type = ?`; params.push(type); } if (project) { sql += ` AND m.project = ?`; params.push(project); } // Fetch more rows for decay re-ranking sql += ` ORDER BY m.importance DESC, m.accessed_at DESC LIMIT ?`; params.push(limit * 3); const stmt = db.prepare(sql); const rows = stmt.all(...params) as Array<{ id: number; content: string; memory_type: string; tags: string | null; project: string | null; importance: number; created_at: string; access_count: number; }>; // Decay 적용 후 re-rank const scored = rows.map(row => ({ ...row, decayedScore: calculateDecayedScore(row.importance, row.memory_type, row.created_at, row.access_count) })).sort((a, b) => b.decayedScore - a.decayedScore).slice(0, limit); // 접근 카운트 업데이트 const updateStmt = db.prepare(` UPDATE memories SET accessed_at = CURRENT_TIMESTAMP, access_count = access_count + 1 WHERE id = ? `); scored.forEach(row => updateStmt.run(row.id)); const memories = scored.map(row => { if (detail) { return { id: row.id, content: row.content.length > 300 ? row.content.slice(0, 300) + '...' : row.content, type: row.memory_type, tags: parseTags(row.tags), project: row.project, importance: row.importance, createdAt: row.created_at }; } // Progressive disclosure: index-only mode (~50 tokens per result) return { id: row.id, summary: row.content.slice(0, 80) + (row.content.length > 80 ? '...' : ''), type: row.memory_type, tags: parseTags(row.tags), importance: row.importance, createdAt: row.created_at }; }); return { content: [{ type: 'text' as const, text: JSON.stringify({ query, searchType: 'fts', found: memories.length, hint: detail ? undefined : 'Use memory_get({ ids: [...] }) to fetch full content', memories }, null, 2) }] }; } - src/tools-v2/memory.ts:350-450 (helper)Semantic search implementation. Generates query embedding, compares via cosine similarity (>0.3 threshold), returns ranked results with progressive disclosure.
async function performSemanticSearch( query: string, type?: string, project?: string, limit: number = 10, minImportance: number = 1, detail: boolean = false ): Promise<CallToolResult> { // 쿼리 임베딩 생성 const queryEmbedding = await generateEmbedding(query); if (!queryEmbedding) { return { content: [{ type: 'text' as const, text: 'Failed to generate query embedding' }], isError: true }; } // 필터 조건 구성 let sql = ` SELECT m.id, m.content, m.memory_type, m.tags, m.project, m.importance, m.created_at, e.embedding FROM memories m JOIN embeddings e ON m.id = e.memory_id WHERE m.importance >= ? `; const params: unknown[] = [minImportance]; if (type) { sql += ` AND m.memory_type = ?`; params.push(type); } if (project) { sql += ` AND m.project = ?`; params.push(project); } const stmt = db.prepare(sql); const rows = stmt.all(...params) as Array<{ id: number; content: string; memory_type: string; tags: string | null; project: string | null; importance: number; created_at: string; embedding: Buffer; }>; // 유사도 계산 및 정렬 const scored = rows.map(row => ({ ...row, similarity: cosineSimilarity(queryEmbedding, bufferToEmbedding(row.embedding)) })) .filter(r => r.similarity > 0.3) // 최소 유사도 .sort((a, b) => b.similarity - a.similarity) .slice(0, limit); // 접근 카운트 업데이트 const updateStmt = db.prepare(` UPDATE memories SET accessed_at = CURRENT_TIMESTAMP, access_count = access_count + 1 WHERE id = ? `); scored.forEach(row => updateStmt.run(row.id)); const memories = scored.map(row => { if (detail) { return { id: row.id, content: row.content.length > 300 ? row.content.slice(0, 300) + '...' : row.content, type: row.memory_type, tags: parseTags(row.tags), project: row.project, importance: row.importance, similarity: Math.round(row.similarity * 100) / 100, createdAt: row.created_at }; } return { id: row.id, summary: row.content.slice(0, 80) + (row.content.length > 80 ? '...' : ''), type: row.memory_type, importance: row.importance, similarity: Math.round(row.similarity * 100) / 100, createdAt: row.created_at }; }); return { content: [{ type: 'text' as const, text: JSON.stringify({ query, searchType: 'semantic', found: memories.length, hint: detail ? undefined : 'Use memory_get({ ids: [...] }) to fetch full content', memories }, null, 2) }] }; } - src/schemas.ts:56-64 (schema)Zod schema for memory_search input validation. Fields: query (required), type, project, semantic, limit (default 10), minImportance (default 1), detail.
export const MemorySearchSchema = z.object({ query: z.string().min(1).max(500).describe('검색 쿼리'), type: MemoryTypeSchema.optional(), project: ProjectNameSchema.optional(), semantic: z.boolean().default(false).describe('시맨틱 검색 사용'), limit: z.number().min(1).max(50).default(10).describe('최대 결과 수'), minImportance: z.number().min(1).max(10).default(1).describe('최소 중요도'), detail: z.boolean().default(false).describe('true면 전체 content 반환, false면 요약 인덱스만') }).describe('메모리 검색 (FTS 또는 시맨틱)'); - src/tools-v2/memory.ts:81-102 (registration)Tool definition registration for memory_search with name, description, and inputSchema (JSON Schema) specifying query, type, project, semantic, limit, minImportance.
{ name: 'memory_search', description: `메모리 검색. FTS 또는 시맨틱 검색 지원. - query: 검색 쿼리 (필수) - type: 메모리 유형 필터 - project: 프로젝트 필터 - semantic: true면 시맨틱 검색 (의미 기반) - limit: 최대 결과 수 (기본 10) - minImportance: 최소 중요도 (기본 1)`, inputSchema: { type: 'object', properties: { query: { type: 'string', description: '검색 쿼리' }, type: { type: 'string', description: '메모리 유형 필터' }, project: { type: 'string', description: '프로젝트 필터' }, semantic: { type: 'boolean', description: '시맨틱 검색 사용' }, limit: { type: 'number', description: '최대 결과 수' }, minImportance: { type: 'number', description: '최소 중요도' } }, required: ['query'] } },