82 lines
12 KiB
Plaintext
82 lines
12 KiB
Plaintext
[2026-05-14T19:13:52.587Z] RESULT
|
||
============================================================
|
||
{
|
||
"success": true,
|
||
"output": "This project has no application source code — it's a pure runtime configuration for the `agentic-flow` orchestration framework. The only analyzable \"code\" is `data/memory/schema.sql`. I'll give you a real, actionable analysis of what actually exists.\n\n---\n\n## Performance Analysis: `data/memory/schema.sql`\n\n### 1. Missing Composite Indexes (most impactful)\n\nThe schema has separate single-column indexes, but the most common query patterns combine columns. These will force SQLite to pick one index and filter the rest in memory.\n\n**Issue — `memory_entries`:** Queries filtering active records by namespace are likely the hottest path, but there's no composite index:\n\n```sql\n-- Common query, currently hits two separate indexes (or neither):\nSELECT * FROM memory_entries\nWHERE namespace = 'patterns' AND status = 'active'\nORDER BY last_accessed_at DESC;\n\n-- Fix: add composite indexes for the hot paths\nCREATE INDEX IF NOT EXISTS idx_memory_ns_status\n ON memory_entries(namespace, status);\n\nCREATE INDEX IF NOT EXISTS idx_memory_ns_type_status\n ON memory_entries(namespace, type, status);\n```\n\n**Issue — `patterns`:** Confidence-ranked lookups by type and status have the same problem:\n\n```sql\n-- Fix:\nCREATE INDEX IF NOT EXISTS idx_patterns_type_status_conf\n ON patterns(pattern_type, status, confidence DESC);\n```\n\n**Issue — `trajectory_steps`:** The only index is on `trajectory_id`, but step retrieval is always ordered:\n\n```sql\n-- Replace:\n-- CREATE INDEX IF NOT EXISTS idx_steps_trajectory ON trajectory_steps(trajectory_id);\n\n-- With a covering composite:\nCREATE INDEX IF NOT EXISTS idx_steps_trajectory_step\n ON trajectory_steps(trajectory_id, step_number);\n```\n\n---\n\n### 2. No Index on `expires_at` (TTL cleanup is a full-table scan)\n\n```sql\n-- Expiry cleanup query will scan every row:\nDELETE FROM memory_entries\nWHERE expires_at IS NOT NULL AND expires_at < strftime('%s', 'now') * 1000;\n\n-- Fix: add a partial index (SQLite supports WHERE clauses on indexes)\nCREATE INDEX IF NOT EXISTS idx_memory_expires\n ON memory_entries(expires_at)\n WHERE expires_at IS NOT NULL;\n```\n\n---\n\n### 3. Embeddings Stored as JSON Text (deserialization overhead)\n\n`embedding TEXT` in both `memory_entries` and `patterns` means every vector read/write round-trips through JSON parsing. This is unavoidable in plain SQLite, but the HNSW config acknowledges it (`enableHNSW: true` in the config). Make sure the in-process HNSW index is always used for similarity search — never fall back to scanning `embedding` TEXT values in SQL. A full scan like this is a silent perf killer:\n\n```sql\n-- NEVER do this (would parse all JSON embeddings row by row):\nSELECT id, embedding FROM memory_entries WHERE namespace = 'default';\n-- Do similarity search in the HNSW layer, then fetch by IDs:\nSELECT id, content, metadata FROM memory_entries WHERE id IN (?, ?, ...);\n```\n\n---\n\n### 4. `pattern_history` Has No Pruning Mechanism (unbounded growth)\n\nThe table accumulates every confidence update, decay event, and merge. It has no TTL, no max-rows guard, and no index on `created_at`. Long-running systems will see this table dominate the DB size.\n\n```sql\n-- Add a created_at index to make pruning cheap:\nCREATE INDEX IF NOT EXISTS idx_pattern_history_created\n ON pattern_history(created_at);\n\n-- Prune periodically (keep last 90 days):\nDELETE FROM pattern_history\nWHERE created_at < (strftime('%s', 'now') - 90 * 86400) * 1000;\n```\n\n---\n\n### 5. Configuration: `cacheSize` May Be Undersized\n\nIn `claude-flow.config.json`:\n\n```json\n\"memory\": {\n \"cacheSize\": 1000\n}\n```\n\nIf the memory backend serves pattern lookups on every agent dispatch, 1000 entries may cause frequent cache evictions. Consider bumping to `5000`–`10000` if RAM allows — the entire `patterns` table (plus hot `memory_entries`) should fit in the LRU cache to avoid DB reads on hot paths.\n\n---\n\n### What Doesn't Apply Here\n\nThe other requested categories — **N+1 ORM queries**, **React re-renders**, **JS memory leaks**, **redundant computations** — require application source code. This project has none; it is a configuration-only installation of `agentic-flow`. If you add application code later, those patterns will be worth revisiting.\n\n---\n\n**Summary of actionable changes (all in `schema.sql`):**\n\n| Fix | Impact |\n|-----|--------|\n| Composite indexes on `namespace+status`, `type+status+confidence` | High — eliminates index merge on hot queries |\n| Composite index on `trajectory_steps(trajectory_id, step_number)` | Medium — faster step retrieval |\n| Partial index on `expires_at WHERE IS NOT NULL` | Medium — O(1) TTL cleanup |\n| Periodic prune + index on `pattern_history.created_at` | Medium — prevents table bloat |\n| HNSW-only for similarity search (no SQL embedding scans) | High — avoids JSON parse on every row |\n| Raise `cacheSize` to 5000–10000 | Medium — reduces DB read pressure |\n",
|
||
"parsedOutput": {
|
||
"sections": [
|
||
{
|
||
"title": "Performance Analysis: `data/memory/schema.sql`",
|
||
"content": "\n",
|
||
"level": 2
|
||
},
|
||
{
|
||
"title": "1. Missing Composite Indexes (most impactful)",
|
||
"content": "\nThe schema has separate single-column indexes, but the most common query patterns combine columns. These will force SQLite to pick one index and filter the rest in memory.\n\n**Issue — `memory_entries`:** Queries filtering active records by namespace are likely the hottest path, but there's no composite index:\n\n```sql\n-- Common query, currently hits two separate indexes (or neither):\nSELECT * FROM memory_entries\nWHERE namespace = 'patterns' AND status = 'active'\nORDER BY last_accessed_at DESC;\n\n-- Fix: add composite indexes for the hot paths\nCREATE INDEX IF NOT EXISTS idx_memory_ns_status\n ON memory_entries(namespace, status);\n\nCREATE INDEX IF NOT EXISTS idx_memory_ns_type_status\n ON memory_entries(namespace, type, status);\n```\n\n**Issue — `patterns`:** Confidence-ranked lookups by type and status have the same problem:\n\n```sql\n-- Fix:\nCREATE INDEX IF NOT EXISTS idx_patterns_type_status_conf\n ON patterns(pattern_type, status, confidence DESC);\n```\n\n**Issue — `trajectory_steps`:** The only index is on `trajectory_id`, but step retrieval is always ordered:\n\n```sql\n-- Replace:\n-- CREATE INDEX IF NOT EXISTS idx_steps_trajectory ON trajectory_steps(trajectory_id);\n\n-- With a covering composite:\nCREATE INDEX IF NOT EXISTS idx_steps_trajectory_step\n ON trajectory_steps(trajectory_id, step_number);\n```\n\n---\n\n",
|
||
"level": 3
|
||
},
|
||
{
|
||
"title": "2. No Index on `expires_at` (TTL cleanup is a full-table scan)",
|
||
"content": "\n```sql\n-- Expiry cleanup query will scan every row:\nDELETE FROM memory_entries\nWHERE expires_at IS NOT NULL AND expires_at < strftime('%s', 'now') * 1000;\n\n-- Fix: add a partial index (SQLite supports WHERE clauses on indexes)\nCREATE INDEX IF NOT EXISTS idx_memory_expires\n ON memory_entries(expires_at)\n WHERE expires_at IS NOT NULL;\n```\n\n---\n\n",
|
||
"level": 3
|
||
},
|
||
{
|
||
"title": "3. Embeddings Stored as JSON Text (deserialization overhead)",
|
||
"content": "\n`embedding TEXT` in both `memory_entries` and `patterns` means every vector read/write round-trips through JSON parsing. This is unavoidable in plain SQLite, but the HNSW config acknowledges it (`enableHNSW: true` in the config). Make sure the in-process HNSW index is always used for similarity search — never fall back to scanning `embedding` TEXT values in SQL. A full scan like this is a silent perf killer:\n\n```sql\n-- NEVER do this (would parse all JSON embeddings row by row):\nSELECT id, embedding FROM memory_entries WHERE namespace = 'default';\n-- Do similarity search in the HNSW layer, then fetch by IDs:\nSELECT id, content, metadata FROM memory_entries WHERE id IN (?, ?, ...);\n```\n\n---\n\n",
|
||
"level": 3
|
||
},
|
||
{
|
||
"title": "4. `pattern_history` Has No Pruning Mechanism (unbounded growth)",
|
||
"content": "\nThe table accumulates every confidence update, decay event, and merge. It has no TTL, no max-rows guard, and no index on `created_at`. Long-running systems will see this table dominate the DB size.\n\n```sql\n-- Add a created_at index to make pruning cheap:\nCREATE INDEX IF NOT EXISTS idx_pattern_history_created\n ON pattern_history(created_at);\n\n-- Prune periodically (keep last 90 days):\nDELETE FROM pattern_history\nWHERE created_at < (strftime('%s', 'now') - 90 * 86400) * 1000;\n```\n\n---\n\n",
|
||
"level": 3
|
||
},
|
||
{
|
||
"title": "5. Configuration: `cacheSize` May Be Undersized",
|
||
"content": "\nIn `claude-flow.config.json`:\n\n```json\n\"memory\": {\n \"cacheSize\": 1000\n}\n```\n\nIf the memory backend serves pattern lookups on every agent dispatch, 1000 entries may cause frequent cache evictions. Consider bumping to `5000`–`10000` if RAM allows — the entire `patterns` table (plus hot `memory_entries`) should fit in the LRU cache to avoid DB reads on hot paths.\n\n---\n\n",
|
||
"level": 3
|
||
},
|
||
{
|
||
"title": "What Doesn't Apply Here",
|
||
"content": "The other requested categories — **N+1 ORM queries**, **React re-renders**, **JS memory leaks**, **redundant computations** — require application source code. This project has none; it is a configuration-only installation of `agentic-flow`. If you add application code later, those patterns will be worth revisiting.\n\n---\n\n**Summary of actionable changes (all in `schema.sql`):**\n\n| Fix | Impact |\n|-----|--------|\n| Composite indexes on `namespace+status`, `type+status+confidence` | High — eliminates index merge on hot queries |\n| Composite index on `trajectory_steps(trajectory_id, step_number)` | Medium — faster step retrieval |\n| Partial index on `expires_at WHERE IS NOT NULL` | Medium — O(1) TTL cleanup |\n| Periodic prune + index on `pattern_history.created_at` | Medium — prevents table bloat |\n| HNSW-only for similarity search (no SQL embedding scans) | High — avoids JSON parse on every row |\n| Raise `cacheSize` to 5000–10000 | Medium — reduces DB read pressure |",
|
||
"level": 3
|
||
}
|
||
],
|
||
"codeBlocks": [
|
||
{
|
||
"language": "sql",
|
||
"code": "-- Common query, currently hits two separate indexes (or neither):\nSELECT * FROM memory_entries\nWHERE namespace = 'patterns' AND status = 'active'\nORDER BY last_accessed_at DESC;\n\n-- Fix: add composite indexes for the hot paths\nCREATE INDEX IF NOT EXISTS idx_memory_ns_status\n ON memory_entries(namespace, status);\n\nCREATE INDEX IF NOT EXISTS idx_memory_ns_type_status\n ON memory_entries(namespace, type, status);"
|
||
},
|
||
{
|
||
"language": "sql",
|
||
"code": "-- Fix:\nCREATE INDEX IF NOT EXISTS idx_patterns_type_status_conf\n ON patterns(pattern_type, status, confidence DESC);"
|
||
},
|
||
{
|
||
"language": "sql",
|
||
"code": "-- Replace:\n-- CREATE INDEX IF NOT EXISTS idx_steps_trajectory ON trajectory_steps(trajectory_id);\n\n-- With a covering composite:\nCREATE INDEX IF NOT EXISTS idx_steps_trajectory_step\n ON trajectory_steps(trajectory_id, step_number);"
|
||
},
|
||
{
|
||
"language": "sql",
|
||
"code": "-- Expiry cleanup query will scan every row:\nDELETE FROM memory_entries\nWHERE expires_at IS NOT NULL AND expires_at < strftime('%s', 'now') * 1000;\n\n-- Fix: add a partial index (SQLite supports WHERE clauses on indexes)\nCREATE INDEX IF NOT EXISTS idx_memory_expires\n ON memory_entries(expires_at)\n WHERE expires_at IS NOT NULL;"
|
||
},
|
||
{
|
||
"language": "sql",
|
||
"code": "-- NEVER do this (would parse all JSON embeddings row by row):\nSELECT id, embedding FROM memory_entries WHERE namespace = 'default';\n-- Do similarity search in the HNSW layer, then fetch by IDs:\nSELECT id, content, metadata FROM memory_entries WHERE id IN (?, ?, ...);"
|
||
},
|
||
{
|
||
"language": "sql",
|
||
"code": "-- Add a created_at index to make pruning cheap:\nCREATE INDEX IF NOT EXISTS idx_pattern_history_created\n ON pattern_history(created_at);\n\n-- Prune periodically (keep last 90 days):\nDELETE FROM pattern_history\nWHERE created_at < (strftime('%s', 'now') - 90 * 86400) * 1000;"
|
||
},
|
||
{
|
||
"language": "json",
|
||
"code": "\"memory\": {\n \"cacheSize\": 1000\n}"
|
||
}
|
||
]
|
||
},
|
||
"durationMs": 96478,
|
||
"model": "sonnet",
|
||
"sandboxMode": "permissive",
|
||
"workerType": "optimize",
|
||
"timestamp": "2026-05-14T19:13:52.586Z",
|
||
"executionId": "optimize_1778785936108_mlqh1u"
|
||
}
|