Run the compression pipeline now
memory_compressCluster similar project memories by embedding similarity, merge each cluster into a canonical memory, and mark originals as compressed. Use after bulk import or to sanity-check compression. Requires compression.enabled=true.
Instructions
Force one compression cycle now: cluster similar memories by embedding similarity, merge each cluster into a canonical memory, and mark originals as compressed. Use after a bulk import or to sanity-check compression. Compression normally runs on the maintenance schedule. Side effects: writes merged memories, updates originals' compressed_into pointer, may call embeddings + LLM providers. Requires compression.enabled=true; otherwise returns a no-op message.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| project_path | No | Absolute project path to compress. Empty string (default) compresses every project. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| message | Yes | Summary line per project, e.g. `Compressed 3 cluster(s) in project <id>`, or `No clusters to compress.` Returns a no-op message when `compression.enabled=false`. |
Implementation Reference
- src/tools/memory-compress.ts:6-63 (handler)Main handler for the memory_compress tool. Accepts an optional project_path parameter, resolves projects, runs runCompressionCycle for each, and returns a summary of compression results (clusters, token reductions).
export async function handleMemoryCompress( db: Database.Database, config: Config, params: { project_path?: string }, ): Promise<string> { if (!config.compression.enabled) { return "Compression is disabled in config (compression.enabled = false)."; } const compCfg = toCompressionConfig(config); let projectIds: string[]; if (params.project_path) { const row = db .prepare("SELECT id FROM projects WHERE root_path = ?") .get(params.project_path) as { id: string } | undefined; if (!row) { return `No project found for path ${params.project_path}.`; } projectIds = [row.id]; } else { projectIds = (db.prepare("SELECT id FROM projects").all() as Array<{ id: string }>).map(r => r.id); } if (projectIds.length === 0) { return "No projects to compress."; } let totalClusters = 0; let totalTokensBefore = 0; let totalTokensAfter = 0; const perProject: string[] = []; for (const projectId of projectIds) { const results = runCompressionCycle(db, projectId, compCfg); if (results.length === 0) continue; totalClusters += results.length; const tokensBefore = results.reduce((acc, r) => acc + r.tokens_before, 0); const tokensAfter = results.reduce((acc, r) => acc + r.tokens_after, 0); totalTokensBefore += tokensBefore; totalTokensAfter += tokensAfter; perProject.push(` - project ${projectId}: ${results.length} cluster(s), ${tokensBefore}→${tokensAfter} tokens`); } if (totalClusters === 0) { return "No clusters found to compress."; } const savings = totalTokensBefore > 0 ? Math.round((1 - totalTokensAfter / totalTokensBefore) * 100) : 0; return ( `Compressed ${totalClusters} cluster(s) across ${perProject.length} project(s).\n` + `Tokens: ${totalTokensBefore} → ${totalTokensAfter} (${savings}% reduction).\n` + perProject.join("\n") ); } - src/index.ts:596-617 (schema)Tool registration with input/output schemas: input expects project_path (string, default ''), output returns a message string describing compression results.
server.registerTool( "memory_compress", { title: "Run the compression pipeline now", description: [ "Force one compression cycle now: cluster similar memories by embedding similarity, merge each cluster into a canonical memory, and mark originals as compressed.", "Use after a bulk import or to sanity-check compression. Compression normally runs on the maintenance schedule.", "Side effects: writes merged memories, updates originals' `compressed_into` pointer, may call embeddings + LLM providers. Requires `compression.enabled=true`; otherwise returns a no-op message.", ].join(" "), inputSchema: { project_path: z.string().default("").describe("Absolute project path to compress. Empty string (default) compresses every project."), }, annotations: { title: "Run the compression pipeline now", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true, }, outputSchema: { message: z.string().describe("Summary line per project, e.g. `Compressed 3 cluster(s) in project <id>`, or `No clusters to compress.` Returns a no-op message when `compression.enabled=false`."), }, - src/index.ts:596-620 (registration)Registration of the 'memory_compress' tool on the MCP server, with title, description, input/output schemas, annotations, and the handler callback.
server.registerTool( "memory_compress", { title: "Run the compression pipeline now", description: [ "Force one compression cycle now: cluster similar memories by embedding similarity, merge each cluster into a canonical memory, and mark originals as compressed.", "Use after a bulk import or to sanity-check compression. Compression normally runs on the maintenance schedule.", "Side effects: writes merged memories, updates originals' `compressed_into` pointer, may call embeddings + LLM providers. Requires `compression.enabled=true`; otherwise returns a no-op message.", ].join(" "), inputSchema: { project_path: z.string().default("").describe("Absolute project path to compress. Empty string (default) compresses every project."), }, annotations: { title: "Run the compression pipeline now", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true, }, outputSchema: { message: z.string().describe("Summary line per project, e.g. `Compressed 3 cluster(s) in project <id>`, or `No clusters to compress.` Returns a no-op message when `compression.enabled=false`."), }, }, async (params) => textResult(await handleMemoryCompress(db, config, params)) ); - src/engine/compressor.ts:486-514 (helper)runCompressionCycle: selects non-compressed memories, clusters them, merges each cluster, and applies compression within a database transaction.
export function runCompressionCycle( db: Database.Database, projectId: string, config: CompressionConfig, ): CompressionResult[] { const results: CompressionResult[] = []; const tx = db.transaction(() => { const rows = db .prepare( ` SELECT * FROM memories WHERE project_id = ? AND deleted_at IS NULL AND source != 'compression' ORDER BY created_at DESC LIMIT 200 `, ) .all(projectId) as MemoryRecord[]; const clusters = clusterMemories(rows, config); for (const cluster of clusters) { const merged = mergeCluster(cluster); applyCompression(db, merged); results.push(merged); } }); tx(); return results; } - src/lib/compression-config.ts:8-15 (helper)toCompressionConfig: maps the user-facing config to the engine's CompressionConfig type used by the compression pipeline.
export function toCompressionConfig(config: Config): CompressionConfig { return { cluster_similarity_threshold: config.compression.clusterSimilarityThreshold, min_cluster_size: config.compression.minClusterSize, max_body_ratio: config.compression.maxBodyRatio, temporal_window_hours: config.compression.temporalWindowHours, }; }