{"version":3,"file":"compositeBackend.cjs","sources":["../../../src/memory/compositeBackend.ts"],"sourcesContent":["/**\n * Composite memory backend — the architectural seam for a future graph layer.\n *\n * Today, memory is pgvector only. Tomorrow, a Graphiti or Neo4j-agent-memory\n * adapter can implement the same {@link MemoryBackend} interface and be\n * composed with the vector store here — without touching the tool\n * definitions, prompts, or host wiring.\n *\n * Fan-out strategy (simple on purpose for Phase 1):\n * - `search`: query every backend in parallel, merge, dedupe by id, sort by\n *   score, cap at maxResults. Graph hits and vector hits are interleaved by\n *   score — the LLM sees one ranked list.\n * - `get`: the vector store owns file paths; graph stores don't. First\n *   backend that returns a non-null result wins. In practice this will almost\n *   always be the vector store, since append paths live there.\n * - `append`: fan out to every backend. Vector store persists the note,\n *   graph backend runs its own extraction pipeline. An append failure in any\n *   single backend is surfaced — we fail loud rather than silently dropping\n *   writes.\n */\nimport type {\n  MemoryAppendInput,\n  MemoryBackend,\n  MemoryEntry,\n  MemoryGetOptions,\n  MemoryHealth,\n  MemoryReadResult,\n  MemoryScope,\n  MemorySearchOptions,\n} from './types';\n\nexport class CompositeMemoryBackend implements MemoryBackend {\n  readonly kind = 'composite' as const;\n  private readonly backends: MemoryBackend[];\n\n  constructor(backends: MemoryBackend[]) {\n    if (backends.length === 0) {\n      throw new Error('CompositeMemoryBackend requires at least one backend');\n    }\n    this.backends = backends;\n  }\n\n  async search(\n    scope: MemoryScope,\n    query: string,\n    opts?: MemorySearchOptions\n  ): Promise<MemoryEntry[]> {\n    const perBackend = await Promise.all(\n      this.backends.map((backend) => backend.search(scope, query, opts))\n    );\n    const merged = new Map<string, MemoryEntry>();\n    for (const entries of perBackend) {\n      for (const entry of entries) {\n        const key = `${entry.source ?? 'unknown'}:${entry.id}`;\n        const existing = merged.get(key);\n        if (!existing || entry.score > existing.score) {\n          merged.set(key, entry);\n        }\n      }\n    }\n    const limit = Math.max(1, opts?.maxResults ?? 10);\n    return Array.from(merged.values())\n      .sort((a, b) => b.score - a.score)\n      .slice(0, limit);\n  }\n\n  async get(\n    scope: MemoryScope,\n    opts: MemoryGetOptions\n  ): Promise<MemoryReadResult | null> {\n    for (const backend of this.backends) {\n      const result = await backend.get(scope, opts);\n      if (result) return result;\n    }\n    return null;\n  }\n\n  async append(scope: MemoryScope, input: MemoryAppendInput): Promise<void> {\n    // Serial fan-out — a failure in an earlier backend means we stop and\n    // bubble. We do NOT want a partial write where the vector row landed but\n    // the graph backend quietly dropped the note.\n    for (const backend of this.backends) {\n      await backend.append(scope, input);\n    }\n  }\n\n  async health(): Promise<MemoryHealth> {\n    const healths = await Promise.all(this.backends.map((b) => b.health()));\n    const failed = healths.find((h) => !h.ok);\n    if (failed) {\n      return {\n        ok: false,\n        backend: 'composite',\n        error: `${failed.backend}: ${failed.error ?? 'unhealthy'}`,\n      };\n    }\n    return { ok: true, backend: 'composite' };\n  }\n}\n"],"names":[],"mappings":"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"}