UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

922 lines 72.9 kB
/** * AgentDB MCP Tools — Phase 6 of ADR-053 * * Exposes AgentDB v3 controller operations as MCP tools. * Provides direct access to ReasoningBank, CausalGraph, SkillLibrary, * AttestationLog, and bridge health through the MCP protocol. * * Security: All handlers validate input types, enforce length bounds, * and sanitize error messages before returning to MCP callers. * * @module v3/cli/mcp-tools/agentdb-tools */ import { validateIdentifier, validateText } from './validate-input.js'; // ===== Shared validation helpers ===== const MAX_STRING_LENGTH = 100_000; // 100KB max for any string input const MAX_BATCH_SIZE = 500; // Max entries per batch operation const MAX_TOP_K = 100; // Max results per query function validateString(value, name, maxLen = MAX_STRING_LENGTH) { if (typeof value !== 'string' || value.length === 0) return null; if (value.length > maxLen) return null; return value; } function validatePositiveInt(value, defaultVal, max) { if (typeof value !== 'number' || !Number.isFinite(value)) return defaultVal; const n = Math.floor(value); return n > 0 ? Math.min(n, max) : defaultVal; } function validateScore(value, defaultVal) { if (typeof value !== 'number' || !Number.isFinite(value)) return defaultVal; return Math.max(0, Math.min(1, value)); } function sanitizeError(error) { if (error instanceof Error) { // Strip filesystem paths from error messages return error.message.replace(/\/[^\s:]+\//g, '<path>/').substring(0, 500); } return 'Internal error'; } // Lazy-cached bridge module let bridgeModule = null; async function getBridge() { if (!bridgeModule) { bridgeModule = await import('../memory/memory-bridge.js'); } return bridgeModule; } // ===== agentdb_health — Controller health check ===== export const agentdbHealth = { name: 'agentdb_health', description: 'Get AgentDB v3 controller health status including cache stats and attestation count Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: {}, }, handler: async () => { try { const bridge = await getBridge(); const health = await bridge.bridgeHealthCheck(); if (!health) return { available: false, error: 'AgentDB bridge not available' }; return health; } catch (error) { return { available: false, error: sanitizeError(error) }; } }, }; // ===== agentdb_controllers — List all controllers ===== export const agentdbControllers = { name: 'agentdb_controllers', description: 'List all AgentDB v3 controllers and their initialization status Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: {}, }, handler: async () => { try { const bridge = await getBridge(); const controllers = await bridge.bridgeListControllers(); if (!controllers) return { available: false, controllers: [], error: 'AgentDB bridge not available — @claude-flow/memory not installed or missing controller-registry. Use memory_store/memory_search tools instead.' }; return { available: true, controllers, total: controllers.length, active: controllers.filter((c) => c.enabled).length, }; } catch (error) { return { available: false, error: sanitizeError(error) }; } }, }; // ===== agentdb_pattern_store — Store via ReasoningBank ===== export const agentdbPatternStore = { name: 'agentdb_pattern-store', description: 'Store a pattern directly via ReasoningBank controller Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { pattern: { type: 'string', description: 'Pattern description' }, type: { type: 'string', description: 'Pattern type (e.g., task-routing, error-recovery)' }, confidence: { type: 'number', description: 'Confidence score (0-1)' }, }, required: ['pattern'], }, handler: async (params) => { try { const vPattern = validateText(params.pattern, 'pattern', 100_000); if (!vPattern.valid) return { success: false, error: vPattern.error }; if (params.type) { const vType = validateIdentifier(params.type, 'type'); if (!vType.valid) return { success: false, error: vType.error }; } const pattern = validateString(params.pattern, 'pattern'); if (!pattern) return { success: false, error: 'pattern is required (non-empty string, max 100KB)' }; const type = validateString(params.type, 'type', 200) ?? 'general'; const confidence = validateScore(params.confidence, 0.8); const bridge = await getBridge(); const result = await bridge.bridgeStorePattern({ pattern, type, confidence }); if (result) return result; // ADR-093 F4: when the ReasoningBank controller registry returns // null (the cause of audit-reported "AgentDB bridge not available" // even though `agentdb_health.reasoningBank.enabled === true`), fall // back to a direct memory_store write so the caller's pattern still // persists. Surface the controller as `memory-store-fallback` so the // path is observable instead of silently lost. try { const { storeEntry } = await import('../memory/memory-initializer.js'); const patternId = `pattern-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; const value = JSON.stringify({ pattern, type, confidence, _fallback: 'reasoningBank-unavailable' }); await storeEntry({ key: patternId, value, namespace: 'pattern', tags: [type, 'reasoning-pattern', 'fallback'], }); return { success: true, patternId, controller: 'memory-store-fallback', note: 'ReasoningBank controller registry unavailable. Pattern persisted via memory_store. Run `agentdb_health` to inspect controller registration.', }; } catch (fallbackErr) { return { success: false, error: 'Pattern store failed: both ReasoningBank bridge and memory_store fallback unavailable', fallbackError: sanitizeError(fallbackErr), recommendation: 'Run agentdb_health to inspect controller registration and check that .swarm/memory.db is writable.', }; } } catch (error) { return { success: false, error: sanitizeError(error) }; } }, }; // ===== agentdb_pattern_search — Search via ReasoningBank ===== export const agentdbPatternSearch = { name: 'agentdb_pattern-search', description: 'Search patterns via ReasoningBank controller with BM25+semantic hybrid Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query' }, topK: { type: 'number', description: 'Number of results (default: 5)' }, minConfidence: { type: 'number', description: 'Minimum score threshold (0-1)' }, }, required: ['query'], }, handler: async (params) => { try { const vQuery = validateText(params.query, 'query', 10_000); if (!vQuery.valid) return { results: [], error: vQuery.error }; const query = validateString(params.query, 'query', 10_000); if (!query) return { results: [], error: 'query is required (non-empty string, max 10KB)' }; const topK = validatePositiveInt(params.topK, 5, MAX_TOP_K); const minConfidence = validateScore(params.minConfidence, 0.3); const bridge = await getBridge(); const result = await bridge.bridgeSearchPatterns({ query, topK, minConfidence }); if (result && Array.isArray(result.results) && result.results.length > 0) { return result; } // #1889 — symmetric fallback. pattern-store writes to the `pattern` // namespace via memory_store when ReasoningBank is unavailable; the // search path used to return an empty list with `controller: 'unavailable'` // even though the user's pattern was sitting in that namespace. We now // tier the fallback so freshly-written entries are findable before HNSW // catches up: // 1. Try semantic search via searchEntries (HNSW-backed) // 2. If that returns 0, list the namespace and substring-match the query // against each entry's pattern text. Deterministic; survives // embedding-index latency and threshold tuning. try { const { searchEntries, listEntries, getEntry } = await import('../memory/memory-initializer.js'); const parseEntry = (e) => { const raw = typeof e.content === 'string' ? e.content : e.value; if (typeof raw !== 'string') return null; try { const parsed = JSON.parse(raw); const confidence = typeof parsed.confidence === 'number' ? parsed.confidence : 0.8; if (confidence < minConfidence) return null; return { patternId: e.key ?? e.id, pattern: parsed.pattern, type: parsed.type ?? 'general', confidence, score: typeof e.score === 'number' ? e.score : undefined, }; } catch { return null; } }; // Tier 1 — semantic let results = []; let tier = 'semantic'; try { const semantic = await searchEntries({ query, namespace: 'pattern', limit: topK }); results = (semantic?.results ?? []) .map(parseEntry) .filter((r) => r !== null); } catch { /* fall through to tier 2 */ } // Tier 2 — substring scan (catches just-written entries before HNSW indexes them). // #2226: listEntries returns metadata only (no content/value — see open #2014), // so parseEntry would always null out here. Fetch each entry's content by key via // getEntry (which DOES return content) before parsing/matching. This is the path // that actually runs when the ReasoningBank controller is unavailable (the common // real-world state), so a stored pattern is now findable by search. if (results.length === 0) { tier = 'substring'; const all = await listEntries({ namespace: 'pattern', limit: 200 }); const qLower = query.toLowerCase(); const matched = []; for (const e of (all?.entries ?? [])) { const meta = e; let entry = meta; // If the listing lacks content, hydrate it from the keyed store. if (typeof meta.content !== 'string' && typeof meta.value !== 'string') { const key = typeof meta.key === 'string' ? meta.key : (typeof meta.id === 'string' ? meta.id : null); if (!key) continue; const got = await getEntry({ key, namespace: 'pattern' }); if (!got?.found || !got.entry) continue; entry = got.entry; } const parsed = parseEntry(entry); if (!parsed) continue; const text = typeof parsed.pattern === 'string' ? parsed.pattern.toLowerCase() : ''; if (text.includes(qLower)) matched.push(parsed); if (matched.length >= topK) break; } results = matched; } // #1889 — controller label must match pattern-store's so the smoke // round-trip sees both ends agree. The store reports // `memory-store-fallback`; we use the same name + a `tier` field // to expose which sub-strategy fired. return { results, controller: 'memory-store-fallback', tier, note: result ? `ReasoningBank returned 0 results; tier=${tier} from pattern namespace.` : `ReasoningBank controller unavailable; tier=${tier} from pattern namespace.`, }; } catch (fallbackErr) { return { results: [], controller: 'unavailable', fallbackError: sanitizeError(fallbackErr) }; } } catch (error) { return { results: [], error: sanitizeError(error) }; } }, }; // ===== agentdb_feedback — Record task feedback ===== export const agentdbFeedback = { name: 'agentdb_feedback', description: 'Record task feedback for learning via LearningSystem + ReasoningBank controllers Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { taskId: { type: 'string', description: 'Task identifier' }, success: { type: 'boolean', description: 'Whether task succeeded' }, quality: { type: 'number', description: 'Quality score (0-1)' }, agent: { type: 'string', description: 'Agent that performed the task' }, }, required: ['taskId'], }, handler: async (params) => { try { const vTaskId = validateIdentifier(params.taskId, 'taskId'); if (!vTaskId.valid) return { success: false, error: vTaskId.error }; if (params.agent) { const vAgent = validateIdentifier(params.agent, 'agent'); if (!vAgent.valid) return { success: false, error: vAgent.error }; } const taskId = validateString(params.taskId, 'taskId', 500); if (!taskId) return { success: false, error: 'taskId is required (non-empty string, max 500 chars)' }; const bridge = await getBridge(); const result = await bridge.bridgeRecordFeedback({ taskId, success: params.success === true, quality: validateScore(params.quality, 0.85), agent: validateString(params.agent, 'agent', 200) ?? undefined, }); return result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' }; } catch (error) { return { success: false, error: sanitizeError(error) }; } }, }; // ===== ADR-130 Phase 1: graph_edges helpers ===== /** Valid domain prefixes for unified node namespace */ const VALID_DOMAINS = new Set(['mem', 'agent', 'task', 'entity', 'span', 'pattern']); /** * Ensure a node ID uses the domain:uuid prefix format (ADR-130 §Phase 1). * IDs without a ':' separator are legacy unprefixed IDs — auto-prefixed as * "mem:" and a deprecation warning is logged. */ function ensureDomainPrefix(id) { const colonIdx = id.indexOf(':'); if (colonIdx > 0) { const domain = id.slice(0, colonIdx); if (VALID_DOMAINS.has(domain)) { return { id, wasLegacy: false }; } } // Legacy ID or unknown prefix — treat as "mem:" namespace return { id: `mem:${id}`, wasLegacy: true }; } /** * Fire-and-forget write of a graph edge to the sql.js graph_edges table. * Non-blocking: errors are silently discarded (ADR-130 §Phase 1 semantics). */ async function writeGraphEdge(opts) { try { const { insertGraphEdge } = await import('../memory/graph-edge-writer.js'); // Generate 384-dim embedding for the edge text (async, ~50ms with ONNX) let embedding; try { const { generateEmbedding } = await import('../memory/memory-initializer.js'); const edgeText = `${opts.relation}: ${opts.sourceId} -> ${opts.targetId}`; const embResult = await generateEmbedding(edgeText); if (embResult && embResult.embedding.length > 0) { embedding = embResult.embedding; } } catch { /* embedding not available — store without embedding_ref */ } await insertGraphEdge({ sourceId: opts.sourceId, targetId: opts.targetId, relation: opts.relation, weight: opts.weight, confidence: opts.confidence, decayRate: opts.decayRate, witnessId: opts.witnessId, embedding, metadata: opts.metadata, }); } catch { /* non-fatal: graph_edges write failure must never break callers */ } } // ===== agentdb_causal_edge — Record causal relationships ===== export const agentdbCausalEdge = { name: 'agentdb_causal-edge', description: 'Record a causal edge between two memory entries via CausalMemoryGraph Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { sourceId: { type: 'string', description: 'Source entry ID' }, targetId: { type: 'string', description: 'Target entry ID' }, relation: { type: 'string', description: 'Relationship type (e.g., caused, preceded, succeeded)' }, weight: { type: 'number', description: 'Edge weight (0-1)' }, }, required: ['sourceId', 'targetId', 'relation'], }, handler: async (params) => { try { const vSourceId = validateIdentifier(params.sourceId, 'sourceId'); if (!vSourceId.valid) return { success: false, error: vSourceId.error }; const vTargetId = validateIdentifier(params.targetId, 'targetId'); if (!vTargetId.valid) return { success: false, error: vTargetId.error }; const vRelation = validateIdentifier(params.relation, 'relation'); if (!vRelation.valid) return { success: false, error: vRelation.error }; const sourceId = validateString(params.sourceId, 'sourceId', 500); const targetId = validateString(params.targetId, 'targetId', 500); const relation = validateString(params.relation, 'relation', 200); if (!sourceId) return { success: false, error: 'sourceId is required (non-empty string)' }; if (!targetId) return { success: false, error: 'targetId is required (non-empty string)' }; if (!relation) return { success: false, error: 'relation is required (non-empty string)' }; // ADR-130 Phase 1: apply domain prefix, warn on legacy IDs const srcPrefixed = ensureDomainPrefix(sourceId); const tgtPrefixed = ensureDomainPrefix(targetId); const prefixedSourceId = srcPrefixed.id; const prefixedTargetId = tgtPrefixed.id; const legacyWarning = (srcPrefixed.wasLegacy || tgtPrefixed.wasLegacy) ? `[DEPRECATION] Unprefixed node IDs auto-prefixed as "mem:". Use domain:id format (mem/agent/task/entity/span/pattern).` : undefined; // ADR-130 Phase 1: fire-and-forget write to unified graph_edges table const edgeWeight = typeof params.weight === 'number' ? validateScore(params.weight, 0.5) : 1.0; writeGraphEdge({ sourceId: prefixedSourceId, targetId: prefixedTargetId, relation, weight: edgeWeight, }).catch(() => { }); // Try native graph-node backend first (ADR-087) try { const graphBackend = await import('../ruvector/graph-backend.js'); if (await graphBackend.isGraphBackendAvailable()) { const graphResult = await graphBackend.recordCausalEdge(sourceId, targetId, relation, typeof params.weight === 'number' ? validateScore(params.weight, 0.5) : undefined); if (graphResult.success) { // Also record in AgentDB bridge for compatibility const bridge = await getBridge(); await bridge.bridgeRecordCausalEdge({ sourceId, targetId, relation, weight: typeof params.weight === 'number' ? validateScore(params.weight, 0.5) : undefined }).catch(() => { }); return { ...graphResult, _graphNodeBackend: true, ...(legacyWarning && { warning: legacyWarning }) }; } } } catch { /* graph-node not available, fall through */ } const bridge = await getBridge(); const result = await bridge.bridgeRecordCausalEdge({ sourceId, targetId, relation, weight: typeof params.weight === 'number' ? validateScore(params.weight, 0.5) : undefined, }); const baseResult = result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' }; return legacyWarning ? { ...baseResult, warning: legacyWarning } : baseResult; } catch (error) { return { success: false, error: sanitizeError(error) }; } }, }; // ===== agentdb_route — Route via SemanticRouter ===== export const agentdbRoute = { name: 'agentdb_route', description: 'Route a task via AgentDB SemanticRouter or LearningSystem recommendAlgorithm Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { task: { type: 'string', description: 'Task description to route' }, context: { type: 'string', description: 'Additional context' }, }, required: ['task'], }, handler: async (params) => { try { const vTask = validateText(params.task, 'task', 10_000); if (!vTask.valid) return { route: 'general', confidence: 0.5, agents: ['coder'], controller: 'error', error: vTask.error }; if (params.context) { const vCtx = validateText(params.context, 'context', 10_000); if (!vCtx.valid) return { route: 'general', confidence: 0.5, agents: ['coder'], controller: 'error', error: vCtx.error }; } const task = validateString(params.task, 'task', 10_000); if (!task) return { route: 'general', confidence: 0.5, agents: ['coder'], controller: 'error', error: 'task is required (non-empty string)' }; const bridge = await getBridge(); const result = await bridge.bridgeRouteTask({ task, context: validateString(params.context, 'context', 10_000) ?? undefined, }); return result ?? { route: 'general', confidence: 0.5, agents: ['coder'], controller: 'fallback' }; } catch (error) { return { route: 'general', confidence: 0.5, agents: ['coder'], controller: 'error', error: sanitizeError(error) }; } }, }; // ===== agentdb_session_start — Session with ReflexionMemory ===== export const agentdbSessionStart = { name: 'agentdb_session-start', description: 'Start a session with ReflexionMemory episodic replay Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Session identifier' }, context: { type: 'string', description: 'Session context for pattern retrieval' }, }, required: ['sessionId'], }, handler: async (params) => { try { const vSessionId = validateIdentifier(params.sessionId, 'sessionId'); if (!vSessionId.valid) return { success: false, error: vSessionId.error }; if (params.context) { const vCtx = validateText(params.context, 'context', 10_000); if (!vCtx.valid) return { success: false, error: vCtx.error }; } const sessionId = validateString(params.sessionId, 'sessionId', 500); if (!sessionId) return { success: false, error: 'sessionId is required (non-empty string)' }; const bridge = await getBridge(); const result = await bridge.bridgeSessionStart({ sessionId, context: validateString(params.context, 'context', 10_000) ?? undefined, }); return result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' }; } catch (error) { return { success: false, error: sanitizeError(error) }; } }, }; // ===== agentdb_session_end — End session + NightlyLearner ===== export const agentdbSessionEnd = { name: 'agentdb_session-end', description: 'End session, persist to ReflexionMemory, trigger NightlyLearner consolidation Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Session identifier' }, summary: { type: 'string', description: 'Session summary' }, tasksCompleted: { type: 'number', description: 'Number of tasks completed' }, }, required: ['sessionId'], }, handler: async (params) => { try { const vSessionId = validateIdentifier(params.sessionId, 'sessionId'); if (!vSessionId.valid) return { success: false, error: vSessionId.error }; if (params.summary) { const vSummary = validateText(params.summary, 'summary', 50_000); if (!vSummary.valid) return { success: false, error: vSummary.error }; } const sessionId = validateString(params.sessionId, 'sessionId', 500); if (!sessionId) return { success: false, error: 'sessionId is required (non-empty string)' }; const bridge = await getBridge(); const result = await bridge.bridgeSessionEnd({ sessionId, summary: validateString(params.summary, 'summary', 50_000) ?? undefined, tasksCompleted: validatePositiveInt(params.tasksCompleted, 0, 10_000), }); return result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' }; } catch (error) { return { success: false, error: sanitizeError(error) }; } }, }; // ===== agentdb_hierarchical_store — Store to hierarchical memory ===== export const agentdbHierarchicalStore = { name: 'agentdb_hierarchical-store', description: 'Store to hierarchical memory with tier (working, episodic, semantic) Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { key: { type: 'string', description: 'Memory entry key' }, value: { type: 'string', description: 'Memory entry value' }, tier: { type: 'string', description: 'Memory tier (working, episodic, semantic)', enum: ['working', 'episodic', 'semantic'], default: 'working', }, }, required: ['key', 'value'], }, handler: async (params) => { try { const vKey = validateIdentifier(params.key, 'key'); if (!vKey.valid) return { success: false, error: vKey.error }; const vValue = validateText(params.value, 'value'); if (!vValue.valid) return { success: false, error: vValue.error }; if (params.tier) { const vTier = validateIdentifier(params.tier, 'tier'); if (!vTier.valid) return { success: false, error: vTier.error }; } const key = validateString(params.key, 'key', 1000); const value = validateString(params.value, 'value'); if (!key) return { success: false, error: 'key is required (non-empty string, max 1KB)' }; if (!value) return { success: false, error: 'value is required (non-empty string, max 100KB)' }; const tier = validateString(params.tier, 'tier', 20) ?? 'working'; if (!['working', 'episodic', 'semantic'].includes(tier)) { return { success: false, error: `Invalid tier: ${tier}. Must be working, episodic, or semantic` }; } const bridge = await getBridge(); const result = await bridge.bridgeHierarchicalStore({ key, value, tier }); return result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' }; } catch (error) { return { success: false, error: sanitizeError(error) }; } }, }; // ===== agentdb_hierarchical_recall — Recall from hierarchical memory ===== export const agentdbHierarchicalRecall = { name: 'agentdb_hierarchical-recall', description: 'Recall from hierarchical memory with optional tier filter Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Recall query' }, tier: { type: 'string', description: 'Filter by tier (working, episodic, semantic)' }, topK: { type: 'number', description: 'Number of results (default: 5)' }, }, required: ['query'], }, handler: async (params) => { try { const vQuery = validateText(params.query, 'query', 10_000); if (!vQuery.valid) return { results: [], error: vQuery.error }; if (params.tier) { const vTier = validateIdentifier(params.tier, 'tier'); if (!vTier.valid) return { results: [], error: vTier.error }; } const query = validateString(params.query, 'query', 10_000); if (!query) return { results: [], error: 'query is required (non-empty string, max 10KB)' }; const tier = validateString(params.tier, 'tier', 20); if (tier && !['working', 'episodic', 'semantic'].includes(tier)) { return { results: [], error: `Invalid tier: ${tier}. Must be working, episodic, or semantic` }; } const bridge = await getBridge(); const result = await bridge.bridgeHierarchicalRecall({ query, tier: tier ?? undefined, topK: validatePositiveInt(params.topK, 5, MAX_TOP_K), }); return result ?? { results: [], error: 'AgentDB bridge not available. Use memory_search instead.' }; } catch (error) { return { results: [], error: sanitizeError(error) }; } }, }; // ===== agentdb_consolidate — Run memory consolidation ===== export const agentdbConsolidate = { name: 'agentdb_consolidate', description: 'Run memory consolidation to promote entries across tiers and compress old data Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { minAge: { type: 'number', description: 'Minimum age in hours since store (optional)' }, maxEntries: { type: 'number', description: 'Maximum entries to consolidate (optional)' }, }, }, handler: async (params) => { try { const bridge = await getBridge(); const result = await bridge.bridgeConsolidate({ minAge: typeof params.minAge === 'number' ? Math.max(0, params.minAge) : undefined, maxEntries: validatePositiveInt(params.maxEntries, 1000, 10_000), }); return result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' }; } catch (error) { return { success: false, error: sanitizeError(error) }; } }, }; // ===== agentdb_batch — Batch operations (insert, update, delete) ===== export const agentdbBatch = { name: 'agentdb_batch', description: 'Batch operations on AgentDB episodes (insert, update, delete). Note: entries are stored in the AgentDB episodes table, not the memory_search namespace. Use memory_store for entries that should be searchable via memory_search. Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { operation: { type: 'string', description: 'Batch operation type', enum: ['insert', 'update', 'delete'], }, entries: { type: 'array', description: 'Array of {key, value} entries to operate on', items: { type: 'object', properties: { key: { type: 'string' }, value: { type: 'string' }, }, required: ['key'], }, }, }, required: ['operation', 'entries'], }, handler: async (params) => { try { const vOp = validateIdentifier(params.operation, 'operation'); if (!vOp.valid) return { success: false, error: vOp.error }; const operation = validateString(params.operation, 'operation', 20); if (!operation) return { success: false, error: 'operation is required (string)' }; if (!['insert', 'update', 'delete'].includes(operation)) { return { success: false, error: `Invalid operation: ${operation}. Must be insert, update, or delete` }; } if (!Array.isArray(params.entries) || params.entries.length === 0) { return { success: false, error: 'entries is required (non-empty array)' }; } if (params.entries.length > MAX_BATCH_SIZE) { return { success: false, error: `Too many entries: ${params.entries.length}. Max is ${MAX_BATCH_SIZE}` }; } // Validate each entry const validatedEntries = []; for (let i = 0; i < params.entries.length; i++) { const entry = params.entries[i]; if (!entry || typeof entry !== 'object') { return { success: false, error: `entries[${i}] must be an object` }; } const key = validateString(entry.key, `entries[${i}].key`, 1000); if (!key) return { success: false, error: `entries[${i}].key is required (non-empty string)` }; const value = validateString(entry.value, `entries[${i}].value`); validatedEntries.push({ key, value: value ?? undefined }); } const bridge = await getBridge(); const result = await bridge.bridgeBatchOperation({ operation, entries: validatedEntries, }); return result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' }; } catch (error) { return { success: false, error: sanitizeError(error) }; } }, }; // ===== agentdb_context_synthesize — Synthesize context from memories ===== export const agentdbContextSynthesize = { name: 'agentdb_context-synthesize', description: 'Synthesize context from stored memories for a given query Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Query to synthesize context for' }, maxEntries: { type: 'number', description: 'Maximum entries to include (default: 10)' }, }, required: ['query'], }, handler: async (params) => { try { const vQuery = validateText(params.query, 'query', 10_000); if (!vQuery.valid) return { success: false, error: vQuery.error }; const query = validateString(params.query, 'query', 10_000); if (!query) return { success: false, error: 'query is required (non-empty string, max 10KB)' }; const bridge = await getBridge(); const result = await bridge.bridgeContextSynthesize({ query, maxEntries: validatePositiveInt(params.maxEntries, 10, MAX_TOP_K), }); return result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' }; } catch (error) { return { success: false, error: sanitizeError(error) }; } }, }; // ===== agentdb_semantic_route — Route via SemanticRouter ===== export const agentdbSemanticRoute = { name: 'agentdb_semantic-route', description: 'Route an input via AgentDB SemanticRouter for intent classification Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { input: { type: 'string', description: 'Input text to route' }, }, required: ['input'], }, handler: async (params) => { try { const vInput = validateText(params.input, 'input', 10_000); if (!vInput.valid) return { route: null, error: vInput.error }; const input = validateString(params.input, 'input', 10_000); if (!input) return { route: null, error: 'input is required (non-empty string, max 10KB)' }; const bridge = await getBridge(); const result = await bridge.bridgeSemanticRoute({ input }); return result ?? { route: null, error: 'AgentDB bridge not available. Use hooks route instead.' }; } catch (error) { return { route: null, error: sanitizeError(error) }; } }, }; // ===== #1784: Delete tools — symmetry for hierarchical-store + causal-edge ===== export const agentdbHierarchicalDelete = { name: 'agentdb_hierarchical-delete', description: 'Delete a hierarchical-memory entry by key. Returns controller="native-unsupported" when the entry is in a backend without a public delete API. Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { key: { type: 'string', description: 'Memory entry key to delete' }, tier: { type: 'string', description: 'Optional tier filter (working, episodic, semantic)', enum: ['working', 'episodic', 'semantic'], }, }, required: ['key'], }, handler: async (params) => { try { const vKey = validateIdentifier(params.key, 'key'); if (!vKey.valid) return { success: false, deleted: false, error: vKey.error }; if (params.tier) { const vTier = validateIdentifier(params.tier, 'tier'); if (!vTier.valid) return { success: false, deleted: false, error: vTier.error }; } const key = validateString(params.key, 'key', 1000); if (!key) return { success: false, deleted: false, error: 'key is required (non-empty string, max 1KB)' }; const tier = validateString(params.tier, 'tier', 20); if (tier && !['working', 'episodic', 'semantic'].includes(tier)) { return { success: false, deleted: false, error: `Invalid tier: ${tier}. Must be working, episodic, or semantic` }; } const bridge = await getBridge(); const result = await bridge.bridgeDeleteHierarchical({ key, tier: tier ?? undefined }); return result ?? { success: false, deleted: false, error: 'AgentDB bridge not available' }; } catch (error) { return { success: false, deleted: false, error: sanitizeError(error) }; } }, }; export const agentdbCausalEdgeDelete = { name: 'agentdb_causal-edge-delete', description: 'Delete a causal edge between two memory entries. Returns controller="native-unsupported" when the edge lives in graph-node native storage (no public delete API). Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { sourceId: { type: 'string', description: 'Source entry ID' }, targetId: { type: 'string', description: 'Target entry ID' }, relation: { type: 'string', description: 'Optional relationship type filter' }, }, required: ['sourceId', 'targetId'], }, handler: async (params) => { try { const vSourceId = validateIdentifier(params.sourceId, 'sourceId'); if (!vSourceId.valid) return { success: false, deleted: false, error: vSourceId.error }; const vTargetId = validateIdentifier(params.targetId, 'targetId'); if (!vTargetId.valid) return { success: false, deleted: false, error: vTargetId.error }; const sourceId = validateString(params.sourceId, 'sourceId', 500); const targetId = validateString(params.targetId, 'targetId', 500); if (!sourceId) return { success: false, deleted: false, error: 'sourceId is required (non-empty string)' }; if (!targetId) return { success: false, deleted: false, error: 'targetId is required (non-empty string)' }; const relation = validateString(params.relation, 'relation', 200) ?? undefined; const bridge = await getBridge(); const result = await bridge.bridgeDeleteCausalEdge({ sourceId, targetId, relation }); return result ?? { success: false, deleted: false, error: 'AgentDB bridge not available' }; } catch (error) { return { success: false, deleted: false, error: sanitizeError(error) }; } }, }; export const agentdbCausalNodeDelete = { name: 'agentdb_causal-node-delete', description: 'Cascade-delete a causal node and all its incident edges from the SQL fallback. Native graph-node entries are unaffected (no delete API in the binding). Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.', inputSchema: { type: 'object', properties: { nodeId: { type: 'string', description: 'Node ID to delete (cascades to all incident edges)' }, }, required: ['nodeId'], }, handler: async (params) => { try { const vNodeId = validateIdentifier(params.nodeId, 'nodeId'); if (!vNodeId.valid) return { success: false, deletedNode: false, deletedEdges: 0, error: vNodeId.error }; const nodeId = validateString(params.nodeId, 'nodeId', 500); if (!nodeId) return { success: false, deletedNode: false, deletedEdges: 0, error: 'nodeId is required (non-empty string)' }; const bridge = await getBridge(); const result = await bridge.bridgeDeleteCausalNode({ nodeId }); return result ?? { success: false, deletedNode: false, deletedEdges: 0, error: 'AgentDB