@claude-flow/cli 3.32.25 → 3.32.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.claude/helpers/.helpers-version +1 -1
  2. package/.claude/helpers/auto-memory-hook.mjs +430 -430
  3. package/.claude/helpers/helpers.manifest.json +6 -6
  4. package/.claude/helpers/hook-handler.cjs +565 -565
  5. package/.claude/helpers/intelligence.cjs +1058 -1058
  6. package/.claude/helpers/statusline.cjs +1060 -1060
  7. package/catalog-manifest.json +4 -4
  8. package/dist/src/commands/index.d.ts +1 -0
  9. package/dist/src/commands/index.js +5 -3
  10. package/dist/src/commands/memory.js +49 -5
  11. package/dist/src/commands/metaharness.js +100 -2
  12. package/dist/src/commands/policy.d.ts +4 -0
  13. package/dist/src/commands/policy.js +107 -0
  14. package/dist/src/index.js +18 -0
  15. package/dist/src/mcp-client.js +25 -1
  16. package/dist/src/mcp-tools/capability-brain.d.ts +134 -0
  17. package/dist/src/mcp-tools/capability-brain.js +697 -0
  18. package/dist/src/mcp-tools/guidance-tools.d.ts +2 -0
  19. package/dist/src/mcp-tools/guidance-tools.js +369 -37
  20. package/dist/src/mcp-tools/index.d.ts +4 -1
  21. package/dist/src/mcp-tools/index.js +3 -1
  22. package/dist/src/mcp-tools/memory-tools.js +26 -0
  23. package/dist/src/mcp-tools/metaharness-tools.js +106 -1
  24. package/dist/src/mcp-tools/policy-tools.d.ts +3 -0
  25. package/dist/src/mcp-tools/policy-tools.js +121 -0
  26. package/dist/src/memory/memory-bridge.d.ts +11 -0
  27. package/dist/src/memory/memory-bridge.js +100 -21
  28. package/dist/src/memory/memory-initializer.d.ts +22 -1
  29. package/dist/src/memory/memory-initializer.js +184 -39
  30. package/dist/src/services/bounded-worker-pool.d.ts +28 -0
  31. package/dist/src/services/bounded-worker-pool.js +90 -0
  32. package/dist/src/services/flywheel-proposer.d.ts +87 -0
  33. package/dist/src/services/flywheel-proposer.js +165 -0
  34. package/dist/src/services/flywheel-receipt.d.ts +136 -0
  35. package/dist/src/services/flywheel-receipt.js +309 -0
  36. package/dist/src/services/flywheel-transaction.d.ts +77 -0
  37. package/dist/src/services/flywheel-transaction.js +378 -0
  38. package/dist/src/services/harness-flywheel-runtime.d.ts +11 -0
  39. package/dist/src/services/harness-flywheel-runtime.js +85 -2
  40. package/dist/src/services/harness-flywheel.d.ts +27 -1
  41. package/dist/src/services/harness-flywheel.js +138 -27
  42. package/dist/src/services/policy-runtime.d.ts +38 -0
  43. package/dist/src/services/policy-runtime.js +340 -0
  44. package/package.json +23 -5
  45. package/plugins/ruflo-metaharness/scripts/smoke.sh +22 -14
  46. package/plugins/ruflo-metaharness/scripts/test-mcp-tools.mjs +3 -1
  47. package/.claude/.proven-config-version +0 -1
  48. package/.claude/proven-config.json +0 -42
@@ -8,6 +8,17 @@
8
8
  *
9
9
  * @module v3/cli/memory-initializer
10
10
  */
11
+ /**
12
+ * ADR-323 — typed memory provenance. Distinguishes WHO/WHAT wrote a memory
13
+ * entry (a user's stated claim vs an agent's own output vs a tool result vs
14
+ * a raw system observation) so retrieval can filter by trust level instead
15
+ * of treating every entry in a shared namespace as equally authoritative.
16
+ * `unknown` is the backward-compatible default for entries written before
17
+ * this field existed, and for callers that don't pass one.
18
+ */
19
+ export declare const PROVENANCE_TYPES: readonly ["user_claim", "agent_output", "system_observation", "tool_result", "unknown"];
20
+ export type ProvenanceType = (typeof PROVENANCE_TYPES)[number];
21
+ export declare function isValidProvenanceType(value: unknown): value is ProvenanceType;
11
22
  export declare function getMemoryRoot(): string;
12
23
  /** For tests + the `memory configure` flow that mutates the config at runtime. */
13
24
  export declare function _resetMemoryRootCache(): void;
@@ -24,7 +35,7 @@ export declare function resolveDbPath(cliFlag?: string): string;
24
35
  * Enhanced schema with pattern confidence, temporal decay, versioning
25
36
  * Vector embeddings enabled for semantic search
26
37
  */
27
- export declare const MEMORY_SCHEMA_V3 = "\n-- RuFlo V3 Memory Database\n-- Version: 3.0.0\n-- Features: Pattern learning, vector embeddings, temporal decay, migration tracking\n\nPRAGMA journal_mode = WAL;\nPRAGMA synchronous = NORMAL;\nPRAGMA foreign_keys = ON;\n\n-- ============================================\n-- CORE MEMORY TABLES\n-- ============================================\n\n-- Memory entries (main storage)\nCREATE TABLE IF NOT EXISTS memory_entries (\n id TEXT PRIMARY KEY,\n key TEXT NOT NULL,\n namespace TEXT DEFAULT 'default',\n content TEXT NOT NULL,\n type TEXT DEFAULT 'semantic' CHECK(type IN ('semantic', 'episodic', 'procedural', 'working', 'pattern')),\n\n -- Vector embedding for semantic search (stored as JSON array)\n embedding TEXT,\n embedding_model TEXT DEFAULT 'local',\n embedding_dimensions INTEGER,\n\n -- Metadata\n tags TEXT, -- JSON array\n metadata TEXT, -- JSON object\n owner_id TEXT,\n\n -- Timestamps\n created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n expires_at INTEGER,\n last_accessed_at INTEGER,\n\n -- Access tracking for hot/cold detection\n access_count INTEGER DEFAULT 0,\n\n -- Status\n status TEXT DEFAULT 'active' CHECK(status IN ('active', 'archived', 'deleted')),\n\n UNIQUE(namespace, key)\n);\n\n-- Indexes for memory entries\nCREATE INDEX IF NOT EXISTS idx_memory_namespace ON memory_entries(namespace);\nCREATE INDEX IF NOT EXISTS idx_memory_key ON memory_entries(key);\nCREATE INDEX IF NOT EXISTS idx_memory_type ON memory_entries(type);\nCREATE INDEX IF NOT EXISTS idx_memory_status ON memory_entries(status);\nCREATE INDEX IF NOT EXISTS idx_memory_created ON memory_entries(created_at);\nCREATE INDEX IF NOT EXISTS idx_memory_accessed ON memory_entries(last_accessed_at);\nCREATE INDEX IF NOT EXISTS idx_memory_owner ON memory_entries(owner_id);\n\n-- ============================================\n-- PATTERN LEARNING TABLES\n-- ============================================\n\n-- Learned patterns with confidence scoring and versioning\nCREATE TABLE IF NOT EXISTS patterns (\n id TEXT PRIMARY KEY,\n\n -- Pattern identification\n name TEXT NOT NULL,\n pattern_type TEXT NOT NULL CHECK(pattern_type IN (\n 'task-routing', 'error-recovery', 'optimization', 'learning',\n 'coordination', 'prediction', 'code-pattern', 'workflow'\n )),\n\n -- Pattern definition\n condition TEXT NOT NULL, -- Regex or semantic match\n action TEXT NOT NULL, -- What to do when pattern matches\n description TEXT,\n\n -- Confidence scoring (0.0 - 1.0)\n confidence REAL DEFAULT 0.5,\n success_count INTEGER DEFAULT 0,\n failure_count INTEGER DEFAULT 0,\n\n -- Temporal decay\n decay_rate REAL DEFAULT 0.01, -- How fast confidence decays\n half_life_days INTEGER DEFAULT 30, -- Days until confidence halves without use\n\n -- Vector embedding for semantic pattern matching\n embedding TEXT,\n embedding_dimensions INTEGER,\n\n -- Versioning\n version INTEGER DEFAULT 1,\n parent_id TEXT REFERENCES patterns(id),\n\n -- Metadata\n tags TEXT, -- JSON array\n metadata TEXT, -- JSON object\n source TEXT, -- Where the pattern was learned from\n\n -- Timestamps\n created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n last_matched_at INTEGER,\n last_success_at INTEGER,\n last_failure_at INTEGER,\n\n -- Status\n status TEXT DEFAULT 'active' CHECK(status IN ('active', 'archived', 'deprecated', 'experimental'))\n);\n\n-- Indexes for patterns\nCREATE INDEX IF NOT EXISTS idx_patterns_type ON patterns(pattern_type);\nCREATE INDEX IF NOT EXISTS idx_patterns_confidence ON patterns(confidence DESC);\nCREATE INDEX IF NOT EXISTS idx_patterns_status ON patterns(status);\nCREATE INDEX IF NOT EXISTS idx_patterns_last_matched ON patterns(last_matched_at);\n\n-- Pattern evolution history (for versioning)\nCREATE TABLE IF NOT EXISTS pattern_history (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n pattern_id TEXT NOT NULL REFERENCES patterns(id),\n version INTEGER NOT NULL,\n\n -- Snapshot of pattern state\n confidence REAL,\n success_count INTEGER,\n failure_count INTEGER,\n condition TEXT,\n action TEXT,\n\n -- What changed\n change_type TEXT CHECK(change_type IN ('created', 'updated', 'success', 'failure', 'decay', 'merged', 'split')),\n change_reason TEXT,\n\n created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)\n);\n\nCREATE INDEX IF NOT EXISTS idx_pattern_history_pattern ON pattern_history(pattern_id);\n\n-- ============================================\n-- LEARNING & TRAJECTORY TABLES\n-- ============================================\n\n-- Learning trajectories (SONA integration)\nCREATE TABLE IF NOT EXISTS trajectories (\n id TEXT PRIMARY KEY,\n session_id TEXT,\n\n -- Trajectory state\n status TEXT DEFAULT 'active' CHECK(status IN ('active', 'completed', 'failed', 'abandoned')),\n verdict TEXT CHECK(verdict IN ('success', 'failure', 'partial', NULL)),\n\n -- Context\n task TEXT,\n context TEXT, -- JSON object\n\n -- Metrics\n total_steps INTEGER DEFAULT 0,\n total_reward REAL DEFAULT 0,\n\n -- Timestamps\n started_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n ended_at INTEGER,\n\n -- Reference to extracted pattern (if any)\n extracted_pattern_id TEXT REFERENCES patterns(id)\n);\n\n-- Trajectory steps\nCREATE TABLE IF NOT EXISTS trajectory_steps (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n trajectory_id TEXT NOT NULL REFERENCES trajectories(id),\n step_number INTEGER NOT NULL,\n\n -- Step data\n action TEXT NOT NULL,\n observation TEXT,\n reward REAL DEFAULT 0,\n\n -- Metadata\n metadata TEXT, -- JSON object\n\n created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)\n);\n\nCREATE INDEX IF NOT EXISTS idx_steps_trajectory ON trajectory_steps(trajectory_id);\n\n-- ============================================\n-- MIGRATION STATE TRACKING\n-- ============================================\n\n-- Migration state (for resume capability)\nCREATE TABLE IF NOT EXISTS migration_state (\n id TEXT PRIMARY KEY,\n migration_type TEXT NOT NULL, -- 'v2-to-v3', 'pattern', 'memory', etc.\n\n -- Progress tracking\n status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'in_progress', 'completed', 'failed', 'rolled_back')),\n total_items INTEGER DEFAULT 0,\n processed_items INTEGER DEFAULT 0,\n failed_items INTEGER DEFAULT 0,\n skipped_items INTEGER DEFAULT 0,\n\n -- Current position (for resume)\n current_batch INTEGER DEFAULT 0,\n last_processed_id TEXT,\n\n -- Source/destination info\n source_path TEXT,\n source_type TEXT,\n destination_path TEXT,\n\n -- Backup info\n backup_path TEXT,\n backup_created_at INTEGER,\n\n -- Error tracking\n last_error TEXT,\n errors TEXT, -- JSON array of errors\n\n -- Timestamps\n started_at INTEGER,\n completed_at INTEGER,\n created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)\n);\n\n-- ============================================\n-- SESSION MANAGEMENT\n-- ============================================\n\n-- Sessions for context persistence\nCREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n\n -- Session state\n state TEXT NOT NULL, -- JSON object with full session state\n status TEXT DEFAULT 'active' CHECK(status IN ('active', 'paused', 'completed', 'expired')),\n\n -- Context\n project_path TEXT,\n branch TEXT,\n\n -- Metrics\n tasks_completed INTEGER DEFAULT 0,\n patterns_learned INTEGER DEFAULT 0,\n\n -- Timestamps\n created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n expires_at INTEGER\n);\n\n-- ============================================\n-- VECTOR INDEX METADATA (for HNSW)\n-- ============================================\n\n-- Track HNSW index state\nCREATE TABLE IF NOT EXISTS vector_indexes (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n\n -- Index configuration\n dimensions INTEGER NOT NULL,\n metric TEXT DEFAULT 'cosine' CHECK(metric IN ('cosine', 'euclidean', 'dot')),\n\n -- HNSW parameters\n hnsw_m INTEGER DEFAULT 16,\n hnsw_ef_construction INTEGER DEFAULT 200,\n hnsw_ef_search INTEGER DEFAULT 100,\n\n -- Quantization\n quantization_type TEXT CHECK(quantization_type IN ('none', 'scalar', 'product')),\n quantization_bits INTEGER DEFAULT 8,\n\n -- Statistics\n total_vectors INTEGER DEFAULT 0,\n last_rebuild_at INTEGER,\n\n created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)\n);\n\n-- ============================================\n-- GRAPH EDGES (ADR-130 Phase 1)\n-- Unified knowledge graph backend \u2014 sql.js canonical store\n-- ============================================\n\n-- Unified graph edges table (ADR-130)\n-- Node IDs use domain-prefixed format: {domain}:{uuid}\n-- where domain in (mem, agent, task, entity, span, pattern)\nCREATE TABLE IF NOT EXISTS graph_edges (\n id TEXT PRIMARY KEY, -- edge-{uuid}\n source_id TEXT NOT NULL, -- domain-prefixed node ID\n target_id TEXT NOT NULL, -- domain-prefixed node ID\n relation TEXT NOT NULL, -- e.g. \"caused\", \"depends-on\", \"imports\"\n weight REAL DEFAULT 1.0,\n -- Temporal / reliability semantics (ADR-130 \u00A7\"graph that forgets\" property)\n confidence REAL DEFAULT 1.0, -- [0,1]; updated by JUDGE step\n decay_rate REAL DEFAULT 0.0, -- per-day exponential decay applied at read time\n last_reinforced TEXT, -- ISO-8601; set when CONSOLIDATE re-touches edge\n witness_id TEXT, -- FK to verification/witness-fixes.json (ADR-103)\n -- Embedding storage: \"inline:{base64}\" | \"vector_indexes:{id}\" | NULL\n embedding_ref TEXT,\n metadata TEXT, -- JSON blob for plugin-specific fields\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_graph_edges_source ON graph_edges (source_id);\nCREATE INDEX IF NOT EXISTS idx_graph_edges_target ON graph_edges (target_id);\nCREATE INDEX IF NOT EXISTS idx_graph_edges_relation ON graph_edges (relation);\nCREATE INDEX IF NOT EXISTS idx_graph_edges_reinforced ON graph_edges (last_reinforced);\n\n-- ============================================\n-- SYSTEM METADATA\n-- ============================================\n\nCREATE TABLE IF NOT EXISTS metadata (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n updated_at INTEGER DEFAULT (strftime('%s', 'now') * 1000)\n);\n";
38
+ export declare const MEMORY_SCHEMA_V3 = "\n-- RuFlo V3 Memory Database\n-- Version: 3.0.0\n-- Features: Pattern learning, vector embeddings, temporal decay, migration tracking\n\nPRAGMA journal_mode = WAL;\nPRAGMA synchronous = NORMAL;\nPRAGMA foreign_keys = ON;\n\n-- ============================================\n-- CORE MEMORY TABLES\n-- ============================================\n\n-- Memory entries (main storage)\nCREATE TABLE IF NOT EXISTS memory_entries (\n id TEXT PRIMARY KEY,\n key TEXT NOT NULL,\n namespace TEXT DEFAULT 'default',\n content TEXT NOT NULL,\n type TEXT DEFAULT 'semantic' CHECK(type IN ('semantic', 'episodic', 'procedural', 'working', 'pattern')),\n\n -- Vector embedding for semantic search (stored as JSON array)\n embedding TEXT,\n embedding_model TEXT DEFAULT 'local',\n embedding_dimensions INTEGER,\n\n -- Metadata\n tags TEXT, -- JSON array\n metadata TEXT, -- JSON object\n owner_id TEXT,\n\n -- ADR-323: who/what produced this entry \u2014 lets shared-namespace retrieval\n -- filter by trust level instead of conflating a user's stated claim with\n -- an agent's own output or a raw tool/system observation.\n provenance_type TEXT DEFAULT 'unknown' CHECK(provenance_type IN (\n 'user_claim', 'agent_output', 'system_observation', 'tool_result', 'unknown'\n )),\n\n -- Timestamps\n created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n expires_at INTEGER,\n last_accessed_at INTEGER,\n\n -- Access tracking for hot/cold detection\n access_count INTEGER DEFAULT 0,\n\n -- Status\n status TEXT DEFAULT 'active' CHECK(status IN ('active', 'archived', 'deleted')),\n\n UNIQUE(namespace, key)\n);\n\n-- Indexes for memory entries\nCREATE INDEX IF NOT EXISTS idx_memory_namespace ON memory_entries(namespace);\nCREATE INDEX IF NOT EXISTS idx_memory_key ON memory_entries(key);\nCREATE INDEX IF NOT EXISTS idx_memory_type ON memory_entries(type);\nCREATE INDEX IF NOT EXISTS idx_memory_status ON memory_entries(status);\nCREATE INDEX IF NOT EXISTS idx_memory_created ON memory_entries(created_at);\nCREATE INDEX IF NOT EXISTS idx_memory_accessed ON memory_entries(last_accessed_at);\nCREATE INDEX IF NOT EXISTS idx_memory_owner ON memory_entries(owner_id);\n\n-- ============================================\n-- PATTERN LEARNING TABLES\n-- ============================================\n\n-- Learned patterns with confidence scoring and versioning\nCREATE TABLE IF NOT EXISTS patterns (\n id TEXT PRIMARY KEY,\n\n -- Pattern identification\n name TEXT NOT NULL,\n pattern_type TEXT NOT NULL CHECK(pattern_type IN (\n 'task-routing', 'error-recovery', 'optimization', 'learning',\n 'coordination', 'prediction', 'code-pattern', 'workflow'\n )),\n\n -- Pattern definition\n condition TEXT NOT NULL, -- Regex or semantic match\n action TEXT NOT NULL, -- What to do when pattern matches\n description TEXT,\n\n -- Confidence scoring (0.0 - 1.0)\n confidence REAL DEFAULT 0.5,\n success_count INTEGER DEFAULT 0,\n failure_count INTEGER DEFAULT 0,\n\n -- Temporal decay\n decay_rate REAL DEFAULT 0.01, -- How fast confidence decays\n half_life_days INTEGER DEFAULT 30, -- Days until confidence halves without use\n\n -- Vector embedding for semantic pattern matching\n embedding TEXT,\n embedding_dimensions INTEGER,\n\n -- Versioning\n version INTEGER DEFAULT 1,\n parent_id TEXT REFERENCES patterns(id),\n\n -- Metadata\n tags TEXT, -- JSON array\n metadata TEXT, -- JSON object\n source TEXT, -- Where the pattern was learned from\n\n -- Timestamps\n created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n last_matched_at INTEGER,\n last_success_at INTEGER,\n last_failure_at INTEGER,\n\n -- Status\n status TEXT DEFAULT 'active' CHECK(status IN ('active', 'archived', 'deprecated', 'experimental'))\n);\n\n-- Indexes for patterns\nCREATE INDEX IF NOT EXISTS idx_patterns_type ON patterns(pattern_type);\nCREATE INDEX IF NOT EXISTS idx_patterns_confidence ON patterns(confidence DESC);\nCREATE INDEX IF NOT EXISTS idx_patterns_status ON patterns(status);\nCREATE INDEX IF NOT EXISTS idx_patterns_last_matched ON patterns(last_matched_at);\n\n-- Pattern evolution history (for versioning)\nCREATE TABLE IF NOT EXISTS pattern_history (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n pattern_id TEXT NOT NULL REFERENCES patterns(id),\n version INTEGER NOT NULL,\n\n -- Snapshot of pattern state\n confidence REAL,\n success_count INTEGER,\n failure_count INTEGER,\n condition TEXT,\n action TEXT,\n\n -- What changed\n change_type TEXT CHECK(change_type IN ('created', 'updated', 'success', 'failure', 'decay', 'merged', 'split')),\n change_reason TEXT,\n\n created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)\n);\n\nCREATE INDEX IF NOT EXISTS idx_pattern_history_pattern ON pattern_history(pattern_id);\n\n-- ============================================\n-- LEARNING & TRAJECTORY TABLES\n-- ============================================\n\n-- Learning trajectories (SONA integration)\nCREATE TABLE IF NOT EXISTS trajectories (\n id TEXT PRIMARY KEY,\n session_id TEXT,\n\n -- Trajectory state\n status TEXT DEFAULT 'active' CHECK(status IN ('active', 'completed', 'failed', 'abandoned')),\n verdict TEXT CHECK(verdict IN ('success', 'failure', 'partial', NULL)),\n\n -- Context\n task TEXT,\n context TEXT, -- JSON object\n\n -- Metrics\n total_steps INTEGER DEFAULT 0,\n total_reward REAL DEFAULT 0,\n\n -- Timestamps\n started_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n ended_at INTEGER,\n\n -- Reference to extracted pattern (if any)\n extracted_pattern_id TEXT REFERENCES patterns(id)\n);\n\n-- Trajectory steps\nCREATE TABLE IF NOT EXISTS trajectory_steps (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n trajectory_id TEXT NOT NULL REFERENCES trajectories(id),\n step_number INTEGER NOT NULL,\n\n -- Step data\n action TEXT NOT NULL,\n observation TEXT,\n reward REAL DEFAULT 0,\n\n -- Metadata\n metadata TEXT, -- JSON object\n\n created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)\n);\n\nCREATE INDEX IF NOT EXISTS idx_steps_trajectory ON trajectory_steps(trajectory_id);\n\n-- ============================================\n-- MIGRATION STATE TRACKING\n-- ============================================\n\n-- Migration state (for resume capability)\nCREATE TABLE IF NOT EXISTS migration_state (\n id TEXT PRIMARY KEY,\n migration_type TEXT NOT NULL, -- 'v2-to-v3', 'pattern', 'memory', etc.\n\n -- Progress tracking\n status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'in_progress', 'completed', 'failed', 'rolled_back')),\n total_items INTEGER DEFAULT 0,\n processed_items INTEGER DEFAULT 0,\n failed_items INTEGER DEFAULT 0,\n skipped_items INTEGER DEFAULT 0,\n\n -- Current position (for resume)\n current_batch INTEGER DEFAULT 0,\n last_processed_id TEXT,\n\n -- Source/destination info\n source_path TEXT,\n source_type TEXT,\n destination_path TEXT,\n\n -- Backup info\n backup_path TEXT,\n backup_created_at INTEGER,\n\n -- Error tracking\n last_error TEXT,\n errors TEXT, -- JSON array of errors\n\n -- Timestamps\n started_at INTEGER,\n completed_at INTEGER,\n created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)\n);\n\n-- ============================================\n-- SESSION MANAGEMENT\n-- ============================================\n\n-- Sessions for context persistence\nCREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n\n -- Session state\n state TEXT NOT NULL, -- JSON object with full session state\n status TEXT DEFAULT 'active' CHECK(status IN ('active', 'paused', 'completed', 'expired')),\n\n -- Context\n project_path TEXT,\n branch TEXT,\n\n -- Metrics\n tasks_completed INTEGER DEFAULT 0,\n patterns_learned INTEGER DEFAULT 0,\n\n -- Timestamps\n created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n expires_at INTEGER\n);\n\n-- ============================================\n-- VECTOR INDEX METADATA (for HNSW)\n-- ============================================\n\n-- Track HNSW index state\nCREATE TABLE IF NOT EXISTS vector_indexes (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n\n -- Index configuration\n dimensions INTEGER NOT NULL,\n metric TEXT DEFAULT 'cosine' CHECK(metric IN ('cosine', 'euclidean', 'dot')),\n\n -- HNSW parameters\n hnsw_m INTEGER DEFAULT 16,\n hnsw_ef_construction INTEGER DEFAULT 200,\n hnsw_ef_search INTEGER DEFAULT 100,\n\n -- Quantization\n quantization_type TEXT CHECK(quantization_type IN ('none', 'scalar', 'product')),\n quantization_bits INTEGER DEFAULT 8,\n\n -- Statistics\n total_vectors INTEGER DEFAULT 0,\n last_rebuild_at INTEGER,\n\n created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)\n);\n\n-- ============================================\n-- GRAPH EDGES (ADR-130 Phase 1)\n-- Unified knowledge graph backend \u2014 sql.js canonical store\n-- ============================================\n\n-- Unified graph edges table (ADR-130)\n-- Node IDs use domain-prefixed format: {domain}:{uuid}\n-- where domain in (mem, agent, task, entity, span, pattern)\nCREATE TABLE IF NOT EXISTS graph_edges (\n id TEXT PRIMARY KEY, -- edge-{uuid}\n source_id TEXT NOT NULL, -- domain-prefixed node ID\n target_id TEXT NOT NULL, -- domain-prefixed node ID\n relation TEXT NOT NULL, -- e.g. \"caused\", \"depends-on\", \"imports\"\n weight REAL DEFAULT 1.0,\n -- Temporal / reliability semantics (ADR-130 \u00A7\"graph that forgets\" property)\n confidence REAL DEFAULT 1.0, -- [0,1]; updated by JUDGE step\n decay_rate REAL DEFAULT 0.0, -- per-day exponential decay applied at read time\n last_reinforced TEXT, -- ISO-8601; set when CONSOLIDATE re-touches edge\n witness_id TEXT, -- FK to verification/witness-fixes.json (ADR-103)\n -- Embedding storage: \"inline:{base64}\" | \"vector_indexes:{id}\" | NULL\n embedding_ref TEXT,\n metadata TEXT, -- JSON blob for plugin-specific fields\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_graph_edges_source ON graph_edges (source_id);\nCREATE INDEX IF NOT EXISTS idx_graph_edges_target ON graph_edges (target_id);\nCREATE INDEX IF NOT EXISTS idx_graph_edges_relation ON graph_edges (relation);\nCREATE INDEX IF NOT EXISTS idx_graph_edges_reinforced ON graph_edges (last_reinforced);\n\n-- ============================================\n-- SYSTEM METADATA\n-- ============================================\n\nCREATE TABLE IF NOT EXISTS metadata (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n updated_at INTEGER DEFAULT (strftime('%s', 'now') * 1000)\n);\n";
28
39
  interface HNSWEntry {
29
40
  id: string;
30
41
  key: string;
@@ -419,6 +430,8 @@ export declare function storeEntry(options: {
419
430
  ttl?: number;
420
431
  dbPath?: string;
421
432
  upsert?: boolean;
433
+ /** ADR-323: defaults to 'unknown' when omitted. */
434
+ provenanceType?: string;
422
435
  }): Promise<{
423
436
  success: boolean;
424
437
  id: string;
@@ -438,6 +451,10 @@ export declare function searchEntries(options: {
438
451
  limit?: number;
439
452
  threshold?: number;
440
453
  dbPath?: string;
454
+ /** ADR-323: restrict results to these provenance types (e.g. exclude
455
+ * 'user_claim' when retrieving for fact-checking, per MemSyco-Bench's
456
+ * sycophancy finding). Omit/empty = no filtering (all types). */
457
+ provenanceFilter?: string[];
441
458
  }): Promise<{
442
459
  success: boolean;
443
460
  results: {
@@ -446,6 +463,7 @@ export declare function searchEntries(options: {
446
463
  content: string;
447
464
  score: number;
448
465
  namespace: string;
466
+ provenanceType?: string;
449
467
  }[];
450
468
  searchTime: number;
451
469
  error?: string;
@@ -460,6 +478,8 @@ export declare function listEntries(options: {
460
478
  dbPath?: string;
461
479
  /** #2073: When true, include the entry's full `content` string in each result. */
462
480
  includeContent?: boolean;
481
+ /** ADR-323: restrict rows to these provenance types. */
482
+ provenanceFilter?: string[];
463
483
  }): Promise<{
464
484
  success: boolean;
465
485
  entries: {
@@ -473,6 +493,7 @@ export declare function listEntries(options: {
473
493
  hasEmbedding: boolean;
474
494
  /** #2073: Present when `includeContent: true` was requested. */
475
495
  content?: string;
496
+ provenanceType?: string;
476
497
  }[];
477
498
  total: number;
478
499
  error?: string;
@@ -13,6 +13,24 @@ import * as path from 'path';
13
13
  import { createRequire } from 'node:module';
14
14
  import { readFileMaybeEncrypted, writeFileAtomic, writeFileRestricted } from '../fs-secure.js';
15
15
  import { restoreMemoryDbFromBackup } from '../services/memory-backup.js';
16
+ /**
17
+ * ADR-323 — typed memory provenance. Distinguishes WHO/WHAT wrote a memory
18
+ * entry (a user's stated claim vs an agent's own output vs a tool result vs
19
+ * a raw system observation) so retrieval can filter by trust level instead
20
+ * of treating every entry in a shared namespace as equally authoritative.
21
+ * `unknown` is the backward-compatible default for entries written before
22
+ * this field existed, and for callers that don't pass one.
23
+ */
24
+ export const PROVENANCE_TYPES = [
25
+ 'user_claim',
26
+ 'agent_output',
27
+ 'system_observation',
28
+ 'tool_result',
29
+ 'unknown',
30
+ ];
31
+ export function isValidProvenanceType(value) {
32
+ return typeof value === 'string' && PROVENANCE_TYPES.includes(value);
33
+ }
16
34
  /**
17
35
  * #2356 — cached, synchronous capability probe for @ruvector/core. `getHNSWStatus`
18
36
  * is sync and is called by `neural status` in a fresh process that never warms
@@ -193,6 +211,13 @@ CREATE TABLE IF NOT EXISTS memory_entries (
193
211
  metadata TEXT, -- JSON object
194
212
  owner_id TEXT,
195
213
 
214
+ -- ADR-323: who/what produced this entry — lets shared-namespace retrieval
215
+ -- filter by trust level instead of conflating a user's stated claim with
216
+ -- an agent's own output or a raw tool/system observation.
217
+ provenance_type TEXT DEFAULT 'unknown' CHECK(provenance_type IN (
218
+ 'user_claim', 'agent_output', 'system_observation', 'tool_result', 'unknown'
219
+ )),
220
+
196
221
  -- Timestamps
197
222
  created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
198
223
  updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
@@ -1017,7 +1042,13 @@ export async function ensureSchemaColumns(dbPath) {
1017
1042
  { name: 'expires_at', definition: 'expires_at INTEGER' },
1018
1043
  { name: 'last_accessed_at', definition: 'last_accessed_at INTEGER' },
1019
1044
  { name: 'access_count', definition: 'access_count INTEGER DEFAULT 0' },
1020
- { name: 'status', definition: "status TEXT DEFAULT 'active'" }
1045
+ { name: 'status', definition: "status TEXT DEFAULT 'active'" },
1046
+ // ADR-323: older DBs predate provenance typing entirely — backfilled
1047
+ // as 'unknown' via the DEFAULT, same convention as 'type'/'status'
1048
+ // above (no CHECK on the ALTER; enforcement happens in storeEntry()/
1049
+ // bridgeStoreEntry() so an invalid value gets a CLI-friendly error
1050
+ // instead of a raw SQLite constraint failure).
1051
+ { name: 'provenance_type', definition: "provenance_type TEXT DEFAULT 'unknown'" }
1021
1052
  ];
1022
1053
  let modified = false;
1023
1054
  for (const col of requiredColumns) {
@@ -2305,6 +2336,16 @@ export async function verifyMemoryInit(dbPath, options) {
2305
2336
  * This bypasses MCP and writes directly to the database
2306
2337
  */
2307
2338
  export async function storeEntry(options) {
2339
+ // ADR-323: validate before touching either backend so an invalid value
2340
+ // gets one clear error instead of a raw SQLite CHECK-constraint failure
2341
+ // from whichever path (bridge vs sql.js) happens to run.
2342
+ if (options.provenanceType !== undefined && !isValidProvenanceType(options.provenanceType)) {
2343
+ return {
2344
+ success: false,
2345
+ id: '',
2346
+ error: `Invalid provenance type "${options.provenanceType}" — must be one of: ${PROVENANCE_TYPES.join(', ')}`,
2347
+ };
2348
+ }
2308
2349
  // ADR-053: Try AgentDB v3 bridge first
2309
2350
  const bridge = await getBridge();
2310
2351
  if (bridge) {
@@ -2324,7 +2365,7 @@ export async function storeEntry(options) {
2324
2365
  }
2325
2366
  }
2326
2367
  // Fallback: raw sql.js
2327
- const { key, value, namespace = 'default', generateEmbeddingFlag = true, tags = [], ttl, dbPath: customPath, upsert = false } = options;
2368
+ const { key, value, namespace = 'default', generateEmbeddingFlag = true, tags = [], ttl, dbPath: customPath, upsert = false, provenanceType } = options;
2328
2369
  const swarmDir = getMemoryRoot();
2329
2370
  const dbPath = customPath ? path.resolve(customPath) : path.join(swarmDir, 'memory.db');
2330
2371
  try {
@@ -2353,6 +2394,19 @@ export async function storeEntry(options) {
2353
2394
  const SQL = await initSqlJs();
2354
2395
  const fileBuffer = readFileMaybeEncrypted(dbPath, null);
2355
2396
  const db = new SQL.Database(fileBuffer);
2397
+ let persistedProvenance = provenanceType ?? 'unknown';
2398
+ if (upsert && provenanceType === undefined) {
2399
+ try {
2400
+ const stmt = db.prepare('SELECT provenance_type FROM memory_entries WHERE namespace = ? AND key = ? LIMIT 1');
2401
+ stmt.bind([namespace, key]);
2402
+ if (stmt.step()) {
2403
+ const existingType = stmt.get()[0];
2404
+ persistedProvenance = isValidProvenanceType(existingType) ? existingType : 'unknown';
2405
+ }
2406
+ stmt.free();
2407
+ }
2408
+ catch { /* legacy schema or new row — keep unknown */ }
2409
+ }
2356
2410
  const id = `entry_${Date.now()}_${Math.random().toString(36).substring(7)}`;
2357
2411
  const now = Date.now();
2358
2412
  // Generate embedding if requested
@@ -2379,13 +2433,13 @@ export async function storeEntry(options) {
2379
2433
  ? `INSERT OR REPLACE INTO memory_entries (
2380
2434
  id, key, namespace, content, type,
2381
2435
  embedding, embedding_dimensions, embedding_model,
2382
- tags, metadata, created_at, updated_at, expires_at, status
2383
- ) VALUES (?, ?, ?, ?, 'semantic', ?, ?, ?, ?, ?, ?, ?, ?, 'active')`
2436
+ tags, metadata, provenance_type, created_at, updated_at, expires_at, status
2437
+ ) VALUES (?, ?, ?, ?, 'semantic', ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active')`
2384
2438
  : `INSERT INTO memory_entries (
2385
2439
  id, key, namespace, content, type,
2386
2440
  embedding, embedding_dimensions, embedding_model,
2387
- tags, metadata, created_at, updated_at, expires_at, status
2388
- ) VALUES (?, ?, ?, ?, 'semantic', ?, ?, ?, ?, ?, ?, ?, ?, 'active')`;
2441
+ tags, metadata, provenance_type, created_at, updated_at, expires_at, status
2442
+ ) VALUES (?, ?, ?, ?, 'semantic', ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active')`;
2389
2443
  db.run(insertSql, [
2390
2444
  id,
2391
2445
  key,
@@ -2396,6 +2450,7 @@ export async function storeEntry(options) {
2396
2450
  embeddingModel,
2397
2451
  tags.length > 0 ? JSON.stringify(tags) : null,
2398
2452
  '{}',
2453
+ persistedProvenance,
2399
2454
  now,
2400
2455
  now,
2401
2456
  ttl ? now + (ttl * 1000) : null
@@ -2433,6 +2488,17 @@ export async function storeEntry(options) {
2433
2488
  * Uses HNSW index for 150x faster search when available
2434
2489
  */
2435
2490
  export async function searchEntries(options) {
2491
+ if (options.provenanceFilter?.length) {
2492
+ const invalid = options.provenanceFilter.filter(p => !isValidProvenanceType(p));
2493
+ if (invalid.length > 0) {
2494
+ return {
2495
+ success: false,
2496
+ results: [],
2497
+ searchTime: 0,
2498
+ error: `Invalid provenance filter value(s): ${invalid.join(', ')} — must be one of: ${PROVENANCE_TYPES.join(', ')}`,
2499
+ };
2500
+ }
2501
+ }
2436
2502
  // ADR-053: Try AgentDB v3 bridge first
2437
2503
  const bridge = await getBridge();
2438
2504
  if (bridge) {
@@ -2441,7 +2507,7 @@ export async function searchEntries(options) {
2441
2507
  return bridgeResult;
2442
2508
  }
2443
2509
  // Fallback: raw sql.js
2444
- const { query, namespace, limit = 10, threshold = 0.3, dbPath: customPath } = options;
2510
+ const { query, namespace, limit = 10, threshold = 0.3, dbPath: customPath, provenanceFilter } = options;
2445
2511
  const effectiveNamespace = namespace || 'all';
2446
2512
  const swarmDir = getMemoryRoot();
2447
2513
  const dbPath = customPath ? path.resolve(customPath) : path.join(swarmDir, 'memory.db');
@@ -2467,10 +2533,18 @@ export async function searchEntries(options) {
2467
2533
  const db = new SQL.Database(fileBuffer);
2468
2534
  const reranked = [];
2469
2535
  for (const candidate of rabitqCandidates) {
2470
- const stmt = db.prepare('SELECT content, embedding FROM memory_entries WHERE id = ? AND status = ?');
2536
+ // ADR-323: provenance_type is fetched in the same per-candidate
2537
+ // query (no extra round-trip) so a provenance-filtered search
2538
+ // still gets RaBitQ's speedup instead of falling back to brute
2539
+ // force.
2540
+ const stmt = db.prepare('SELECT content, embedding, provenance_type FROM memory_entries WHERE id = ? AND status = ?');
2471
2541
  stmt.bind([candidate.id, 'active']);
2472
2542
  if (stmt.step()) {
2473
- const [content, embeddingJson] = stmt.get();
2543
+ const [content, embeddingJson, provenanceTypeVal] = stmt.get();
2544
+ if (provenanceFilter?.length && !provenanceFilter.includes(provenanceTypeVal || 'unknown')) {
2545
+ stmt.free();
2546
+ continue;
2547
+ }
2474
2548
  let score = 0;
2475
2549
  if (embeddingJson) {
2476
2550
  try {
@@ -2486,13 +2560,17 @@ export async function searchEntries(options) {
2486
2560
  content: (content || '').substring(0, 60) + ((content || '').length > 60 ? '...' : ''),
2487
2561
  score,
2488
2562
  namespace: candidate.namespace,
2563
+ provenanceType: provenanceTypeVal || 'unknown',
2489
2564
  });
2490
2565
  }
2491
2566
  }
2492
2567
  stmt.free();
2493
2568
  }
2494
2569
  db.close();
2495
- if (reranked.length > 0) {
2570
+ // A filtered ANN candidate window can underfill even when qualifying
2571
+ // rows exist outside that window. Fall through to the authoritative
2572
+ // filtered SQL scan unless ANN filled the requested page.
2573
+ if (reranked.length > 0 && (!provenanceFilter?.length || reranked.length >= limit)) {
2496
2574
  reranked.sort((a, b) => b.score - a.score);
2497
2575
  return { success: true, results: reranked.slice(0, limit), searchTime: Date.now() - startTime };
2498
2576
  }
@@ -2500,15 +2578,56 @@ export async function searchEntries(options) {
2500
2578
  }
2501
2579
  catch { /* RaBitQ unavailable, fall through */ }
2502
2580
  // Try HNSW search (150x faster than brute-force)
2503
- const hnswResults = await searchHNSWIndex(queryEmbedding, { k: limit, namespace: effectiveNamespace });
2581
+ const hnswResults = await searchHNSWIndex(queryEmbedding, {
2582
+ k: provenanceFilter?.length ? Math.max(limit * 4, limit + 32) : limit,
2583
+ namespace: effectiveNamespace,
2584
+ });
2504
2585
  if (hnswResults && hnswResults.length > 0) {
2505
2586
  // Filter by threshold
2506
- const filtered = hnswResults.filter(r => r.score >= threshold);
2507
- return {
2508
- success: true,
2509
- results: filtered,
2510
- searchTime: Date.now() - startTime
2511
- };
2587
+ let filtered = hnswResults.filter(r => r.score >= threshold);
2588
+ // ADR-323: the in-memory HNSW index doesn't carry provenance_type on
2589
+ // its entries, so resolve it from SQLite for a consistent result shape
2590
+ // and apply any trust filter before returning.
2591
+ if (filtered.length > 0) {
2592
+ try {
2593
+ const initSqlJs = (await import('sql.js')).default;
2594
+ const SQL = await initSqlJs();
2595
+ const fileBuffer = readFileMaybeEncrypted(dbPath, null);
2596
+ const db = new SQL.Database(fileBuffer);
2597
+ const provenanceByKey = new Map();
2598
+ for (const r of filtered) {
2599
+ const stmt = db.prepare('SELECT provenance_type FROM memory_entries WHERE namespace = ? AND key = ? LIMIT 1');
2600
+ stmt.bind([r.namespace, r.key]);
2601
+ if (stmt.step()) {
2602
+ provenanceByKey.set(`${r.namespace}::${r.key}`, stmt.get()[0] || 'unknown');
2603
+ }
2604
+ stmt.free();
2605
+ }
2606
+ db.close();
2607
+ filtered = filtered
2608
+ .map(r => ({ ...r, provenanceType: provenanceByKey.get(`${r.namespace}::${r.key}`) || 'unknown' }));
2609
+ if (provenanceFilter?.length) {
2610
+ filtered = filtered.filter(r => provenanceFilter.includes(r.provenanceType));
2611
+ }
2612
+ }
2613
+ catch {
2614
+ // A requested trust filter fails closed. Unfiltered callers retain
2615
+ // backward-compatible results with an explicit unknown label.
2616
+ filtered = provenanceFilter?.length
2617
+ ? []
2618
+ : filtered.map(r => ({ ...r, provenanceType: 'unknown' }));
2619
+ }
2620
+ }
2621
+ if (!provenanceFilter?.length || filtered.length >= limit) {
2622
+ return {
2623
+ success: true,
2624
+ results: filtered.slice(0, limit),
2625
+ searchTime: Date.now() - startTime
2626
+ };
2627
+ }
2628
+ // The ANN window did not contain enough matching provenance rows.
2629
+ // Continue into the filtered SQL path to avoid false-empty/underfilled
2630
+ // results caused by post-filtering only the unfiltered top-k.
2512
2631
  }
2513
2632
  // Fall back to brute-force SQLite search
2514
2633
  const initSqlJs = (await import('sql.js')).default;
@@ -2516,11 +2635,21 @@ export async function searchEntries(options) {
2516
2635
  const fileBuffer = readFileMaybeEncrypted(dbPath, null);
2517
2636
  const db = new SQL.Database(fileBuffer);
2518
2637
  // Get entries with embeddings
2519
- const searchStmt = db.prepare(effectiveNamespace !== 'all'
2520
- ? `SELECT id, key, namespace, content, embedding FROM memory_entries WHERE status = 'active' AND namespace = ? LIMIT 1000`
2521
- : `SELECT id, key, namespace, content, embedding FROM memory_entries WHERE status = 'active' LIMIT 1000`);
2638
+ // ADR-323: build the WHERE clause incrementally so namespace and
2639
+ // provenance filters compose (both, either, or neither).
2640
+ const whereClauses = [`status = 'active'`];
2641
+ const whereParams = [];
2522
2642
  if (effectiveNamespace !== 'all') {
2523
- searchStmt.bind([effectiveNamespace]);
2643
+ whereClauses.push('namespace = ?');
2644
+ whereParams.push(effectiveNamespace);
2645
+ }
2646
+ if (provenanceFilter?.length) {
2647
+ whereClauses.push(`provenance_type IN (${provenanceFilter.map(() => '?').join(',')})`);
2648
+ whereParams.push(...provenanceFilter);
2649
+ }
2650
+ const searchStmt = db.prepare(`SELECT id, key, namespace, content, embedding, provenance_type FROM memory_entries WHERE ${whereClauses.join(' AND ')} LIMIT 1000`);
2651
+ if (whereParams.length > 0) {
2652
+ searchStmt.bind(whereParams);
2524
2653
  }
2525
2654
  const searchRows = [];
2526
2655
  while (searchStmt.step()) {
@@ -2531,7 +2660,7 @@ export async function searchEntries(options) {
2531
2660
  const results = [];
2532
2661
  if (entries[0]?.values) {
2533
2662
  for (const row of entries[0].values) {
2534
- const [id, key, ns, content, embeddingJson] = row;
2663
+ const [id, key, ns, content, embeddingJson, provenanceTypeVal] = row;
2535
2664
  let score = 0;
2536
2665
  if (embeddingJson) {
2537
2666
  try {
@@ -2557,7 +2686,8 @@ export async function searchEntries(options) {
2557
2686
  key: key || id.substring(0, 15),
2558
2687
  content: (content || '').substring(0, 60) + ((content || '').length > 60 ? '...' : ''),
2559
2688
  score,
2560
- namespace: ns || 'default'
2689
+ namespace: ns || 'default',
2690
+ provenanceType: provenanceTypeVal || 'unknown',
2561
2691
  });
2562
2692
  }
2563
2693
  }
@@ -2605,6 +2735,17 @@ function cosineSim(a, b) {
2605
2735
  * List all entries from the memory database
2606
2736
  */
2607
2737
  export async function listEntries(options) {
2738
+ if (options.provenanceFilter?.length) {
2739
+ const invalid = options.provenanceFilter.filter(p => !isValidProvenanceType(p));
2740
+ if (invalid.length > 0) {
2741
+ return {
2742
+ success: false,
2743
+ entries: [],
2744
+ total: 0,
2745
+ error: `Invalid provenance filter value(s): ${invalid.join(', ')} — must be one of: ${PROVENANCE_TYPES.join(', ')}`,
2746
+ };
2747
+ }
2748
+ }
2608
2749
  // ADR-053: Try AgentDB v3 bridge first
2609
2750
  const bridge = await getBridge();
2610
2751
  if (bridge) {
@@ -2613,7 +2754,7 @@ export async function listEntries(options) {
2613
2754
  return bridgeResult;
2614
2755
  }
2615
2756
  // Fallback: raw sql.js
2616
- const { namespace, limit = 20, offset = 0, dbPath: customPath } = options;
2757
+ const { namespace, limit = 20, offset = 0, dbPath: customPath, provenanceFilter } = options;
2617
2758
  const swarmDir = getMemoryRoot();
2618
2759
  const dbPath = customPath || path.join(swarmDir, 'memory.db');
2619
2760
  try {
@@ -2630,12 +2771,20 @@ export async function listEntries(options) {
2630
2771
  // that predate the status column may have NULL after migration.
2631
2772
  // See memory-bridge.ts:bridgeListEntries for full context.
2632
2773
  // Get total count
2633
- const countStmt = namespace
2634
- ? db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE (status = 'active' OR status IS NULL) AND namespace = ?`)
2635
- : db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE (status = 'active' OR status IS NULL)`);
2774
+ const whereClauses = [`(status = 'active' OR status IS NULL)`];
2775
+ const whereParams = [];
2636
2776
  if (namespace) {
2637
- countStmt.bind([namespace]);
2777
+ whereClauses.push('namespace = ?');
2778
+ whereParams.push(namespace);
2779
+ }
2780
+ if (provenanceFilter?.length) {
2781
+ whereClauses.push(`provenance_type IN (${provenanceFilter.map(() => '?').join(',')})`);
2782
+ whereParams.push(...provenanceFilter);
2638
2783
  }
2784
+ const whereSql = whereClauses.join(' AND ');
2785
+ const countStmt = db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE ${whereSql}`);
2786
+ if (whereParams.length > 0)
2787
+ countStmt.bind(whereParams);
2639
2788
  const countRows = [];
2640
2789
  while (countStmt.step()) {
2641
2790
  countRows.push(countStmt.get());
@@ -2647,15 +2796,10 @@ export async function listEntries(options) {
2647
2796
  const safeLimit = parseInt(String(limit), 10) || 100;
2648
2797
  const safeOffset = parseInt(String(offset), 10) || 0;
2649
2798
  // #2120 — same NULL-as-active acceptance as the count above.
2650
- const listStmt = namespace
2651
- ? db.prepare(`SELECT id, key, namespace, content, embedding, access_count, created_at, updated_at FROM memory_entries WHERE (status = 'active' OR status IS NULL) AND namespace = ? ORDER BY updated_at DESC LIMIT ? OFFSET ?`)
2652
- : db.prepare(`SELECT id, key, namespace, content, embedding, access_count, created_at, updated_at FROM memory_entries WHERE (status = 'active' OR status IS NULL) ORDER BY updated_at DESC LIMIT ? OFFSET ?`);
2653
- if (namespace) {
2654
- listStmt.bind([namespace, safeLimit, safeOffset]);
2655
- }
2656
- else {
2657
- listStmt.bind([safeLimit, safeOffset]);
2658
- }
2799
+ const listStmt = db.prepare(`SELECT id, key, namespace, content, embedding, access_count, created_at, updated_at, provenance_type
2800
+ FROM memory_entries WHERE ${whereSql}
2801
+ ORDER BY updated_at DESC LIMIT ? OFFSET ?`);
2802
+ listStmt.bind([...whereParams, safeLimit, safeOffset]);
2659
2803
  const listRows = [];
2660
2804
  while (listStmt.step()) {
2661
2805
  listRows.push(listStmt.get());
@@ -2665,7 +2809,7 @@ export async function listEntries(options) {
2665
2809
  const entries = [];
2666
2810
  if (result[0]?.values) {
2667
2811
  for (const row of result[0].values) {
2668
- const [id, key, ns, content, embedding, accessCount, createdAt, updatedAt] = row;
2812
+ const [id, key, ns, content, embedding, accessCount, createdAt, updatedAt, provenanceTypeVal] = row;
2669
2813
  const entry = {
2670
2814
  // #2073: don't truncate id when content is requested — callers
2671
2815
  // (notably memory_export) need the full id to round-trip via import.
@@ -2676,7 +2820,8 @@ export async function listEntries(options) {
2676
2820
  accessCount: accessCount || 0,
2677
2821
  createdAt: createdAt || new Date().toISOString(),
2678
2822
  updatedAt: updatedAt || new Date().toISOString(),
2679
- hasEmbedding: !!embedding && embedding.length > 10
2823
+ hasEmbedding: !!embedding && embedding.length > 10,
2824
+ provenanceType: provenanceTypeVal || 'unknown'
2680
2825
  };
2681
2826
  if (options.includeContent) {
2682
2827
  entry.content = content || '';
@@ -0,0 +1,28 @@
1
+ export interface BoundedTask<T> {
2
+ id: string;
3
+ run: (signal: AbortSignal) => Promise<T>;
4
+ }
5
+ export interface BoundedTaskResult<T> {
6
+ id: string;
7
+ status: 'fulfilled' | 'rejected' | 'cancelled';
8
+ value?: T;
9
+ error?: string;
10
+ }
11
+ export interface BoundedPoolResult<T> {
12
+ results: BoundedTaskResult<T>[];
13
+ peakConcurrency: number;
14
+ durationMs: number;
15
+ }
16
+ /**
17
+ * Deterministic bounded worker pool for Codex/MetaHarness fanout.
18
+ *
19
+ * Completion order never affects result order. The caller-supplied AbortSignal
20
+ * and timeout cancel both queued and cooperative running work. No unbounded
21
+ * Promise.all is used.
22
+ */
23
+ export declare function runBoundedPool<T>(tasks: readonly BoundedTask<T>[], options: {
24
+ maxConcurrency: number;
25
+ timeoutMs?: number;
26
+ signal?: AbortSignal;
27
+ }): Promise<BoundedPoolResult<T>>;
28
+ //# sourceMappingURL=bounded-worker-pool.d.ts.map
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Deterministic bounded worker pool for Codex/MetaHarness fanout.
3
+ *
4
+ * Completion order never affects result order. The caller-supplied AbortSignal
5
+ * and timeout cancel both queued and cooperative running work. No unbounded
6
+ * Promise.all is used.
7
+ */
8
+ export async function runBoundedPool(tasks, options) {
9
+ const started = Date.now();
10
+ if (!Number.isInteger(options.maxConcurrency) || options.maxConcurrency < 1) {
11
+ throw new Error('maxConcurrency must be a positive integer');
12
+ }
13
+ const ids = new Set();
14
+ for (const task of tasks) {
15
+ if (!task.id || ids.has(task.id))
16
+ throw new Error(`duplicate or empty task id: ${task.id}`);
17
+ ids.add(task.id);
18
+ }
19
+ const maxConcurrency = Math.min(options.maxConcurrency, tasks.length || 1);
20
+ const controller = new AbortController();
21
+ const onAbort = () => controller.abort(options.signal?.reason ?? new Error('cancelled'));
22
+ options.signal?.addEventListener('abort', onAbort, { once: true });
23
+ const timer = options.timeoutMs && options.timeoutMs > 0
24
+ ? setTimeout(() => controller.abort(new Error('worker-pool-timeout')), options.timeoutMs)
25
+ : undefined;
26
+ const results = new Map();
27
+ let cursor = 0;
28
+ let active = 0;
29
+ let peakConcurrency = 0;
30
+ const worker = async () => {
31
+ while (cursor < tasks.length) {
32
+ const taskIndex = cursor++;
33
+ const task = tasks[taskIndex];
34
+ if (controller.signal.aborted) {
35
+ results.set(task.id, { id: task.id, status: 'cancelled', error: String(controller.signal.reason ?? 'cancelled') });
36
+ continue;
37
+ }
38
+ active += 1;
39
+ peakConcurrency = Math.max(peakConcurrency, active);
40
+ let rejectOnAbort;
41
+ try {
42
+ const abort = new Promise((_, reject) => {
43
+ if (controller.signal.aborted) {
44
+ reject(controller.signal.reason ?? new Error('cancelled'));
45
+ return;
46
+ }
47
+ rejectOnAbort = () => reject(controller.signal.reason ?? new Error('cancelled'));
48
+ controller.signal.addEventListener('abort', rejectOnAbort, { once: true });
49
+ });
50
+ // Promise.race enforces the caller-visible wall time even when a
51
+ // third-party task ignores AbortSignal. Cooperative tasks should still
52
+ // stop their underlying work when the signal fires.
53
+ const value = await Promise.race([task.run(controller.signal), abort]);
54
+ results.set(task.id, controller.signal.aborted
55
+ ? { id: task.id, status: 'cancelled', error: String(controller.signal.reason ?? 'cancelled') }
56
+ : { id: task.id, status: 'fulfilled', value });
57
+ }
58
+ catch (error) {
59
+ results.set(task.id, {
60
+ id: task.id,
61
+ status: controller.signal.aborted ? 'cancelled' : 'rejected',
62
+ error: error instanceof Error ? error.message : String(error),
63
+ });
64
+ }
65
+ finally {
66
+ if (rejectOnAbort)
67
+ controller.signal.removeEventListener('abort', rejectOnAbort);
68
+ active -= 1;
69
+ }
70
+ }
71
+ };
72
+ try {
73
+ await Promise.all(Array.from({ length: maxConcurrency }, () => worker()));
74
+ }
75
+ finally {
76
+ if (timer)
77
+ clearTimeout(timer);
78
+ options.signal?.removeEventListener('abort', onAbort);
79
+ }
80
+ return {
81
+ results: tasks.map((task) => results.get(task.id) ?? {
82
+ id: task.id,
83
+ status: 'cancelled',
84
+ error: 'not-started',
85
+ }),
86
+ peakConcurrency,
87
+ durationMs: Date.now() - started,
88
+ };
89
+ }
90
+ //# sourceMappingURL=bounded-worker-pool.js.map