{"version":3,"file":"pgvectorStore.mjs","sources":["../../../src/memory/pgvectorStore.ts"],"sourcesContent":["/**\n * Postgres + pgvector implementation of {@link MemoryBackend}.\n *\n * ## Scoping model (two-tier, layered)\n *\n * Every read query applies the layered filter\n *\n *   WHERE agent_id = $1 AND (user_id IS NULL OR user_id = $2)\n *\n * so the caller sees:\n *   - agent-tier rows (`user_id IS NULL`) — shared operational knowledge,\n *     visible to every user of the agent\n *   - their own user-tier rows (`user_id = <caller>`) — private per-user\n *     personalization\n *\n * Another user's user-tier rows are invisible — the privacy boundary is\n * enforced in SQL, not just in the UI or route layer.\n *\n * ## Writes\n *\n * `append()` routes to a row based on the path's tier, resolved via\n * {@link assertWritablePath}:\n *\n *   - `memory/agent/*` → stored with `user_id = NULL` regardless of\n *     what scope the caller passed. Agent-tier content is inherently\n *     shared; scoping it per-user would defeat the point.\n *   - `memory/user/*`  → stored with `user_id = scope.userId`. A missing\n *     `scope.userId` throws — user-tier paths cannot be written from\n *     isolated/autonomous contexts.\n *\n * UPSERT key is `(agent_id, user_id, path)` with `NULLS NOT DISTINCT`,\n * so each user gets their own `user/preferences.md` row and there is\n * exactly one `agent/playbook.md` row shared across the whole user base.\n * Content accumulates via `\\n\\n` concatenation on conflict, with the\n * embedding regenerated over the merged content so search stays\n * consistent.\n */\nimport type { Pool } from 'pg';\nimport {\n  DEFAULT_MAX_SEARCH_RESULTS,\n  DEFAULT_MEMORY_TABLE,\n  DEFAULT_MIN_SCORE,\n  HYBRID_TEXT_WEIGHT,\n  HYBRID_VECTOR_WEIGHT,\n} from './constants';\nimport { getMemoryEmbedder, type EmbeddingProvider } from './embeddings';\nimport { applyMMRToMemoryHits } from './mmr';\nimport { applyTemporalDecayToHits } from './temporalDecay';\nimport { decorateCitations, shouldIncludeCitations } from './citations';\nimport { assertWritablePath, getTierForPath } from './paths';\nimport type {\n  MemoryAppendInput,\n  MemoryBackend,\n  MemoryEntry,\n  MemoryGetOptions,\n  MemoryHealth,\n  MemoryReadResult,\n  MemoryScope,\n  MemorySearchOptions,\n} from './types';\n\nexport interface PgvectorStoreOptions {\n  pool: Pool;\n  table?: string;\n  embedder?: EmbeddingProvider;\n}\n\nfunction assertScope(scope: MemoryScope): void {\n  if (!scope || !scope.agentId) {\n    throw new Error(\n      'MemoryScope { agentId } is required — agentId must be non-empty'\n    );\n  }\n}\n\n/** pgvector literal format: \"[0.1,0.2,...]\". */\nfunction toVectorLiteral(vec: number[]): string {\n  return `[${vec.join(',')}]`;\n}\n\n/**\n * Normalize caller userId for the layered read filter.\n *\n * The SQL filter is `(user_id IS NULL OR user_id = $2)`, so an empty\n * string from the caller must not match rows whose user_id was set.\n * We coerce empty/null/undefined to `null`, and pg treats `$2 = null`\n * as `false` — which is exactly what we want for isolated callers:\n * they see only agent-tier rows and nothing in the user tier.\n */\nfunction normalizeCallerId(scope: MemoryScope): string | null {\n  const raw = scope.userId;\n  if (raw == null || raw === '') return null;\n  return String(raw);\n}\n\nexport class PgvectorMemoryStore implements MemoryBackend {\n  readonly kind = 'vector' as const;\n  private pool: Pool;\n  private table: string;\n  private embedder: EmbeddingProvider;\n\n  constructor(opts: PgvectorStoreOptions) {\n    this.pool = opts.pool;\n    this.table = opts.table ?? DEFAULT_MEMORY_TABLE;\n    this.embedder = opts.embedder ?? getMemoryEmbedder();\n  }\n\n  async search(\n    scope: MemoryScope,\n    query: string,\n    opts: MemorySearchOptions = {}\n  ): Promise<MemoryEntry[]> {\n    assertScope(scope);\n    const trimmed = query.trim();\n    if (!trimmed) return [];\n\n    const maxResults = Math.max(\n      1,\n      opts.maxResults ?? DEFAULT_MAX_SEARCH_RESULTS\n    );\n    const minScore = opts.minScore ?? DEFAULT_MIN_SCORE;\n\n    const vector = await this.embedder.embed(trimmed);\n    const vectorLiteral = toVectorLiteral(vector);\n    const callerId = normalizeCallerId(scope);\n\n    // [memory-layered-search] debug: layered scope filter\n    //   agent-tier rows (user_id IS NULL) + this caller's user-tier rows.\n    //   Another user's rows are invisible at the SQL layer.\n    const sql = `\n      WITH scored AS (\n        SELECT\n          id,\n          path,\n          content,\n          created_at,\n          user_id,\n          (1 - (embedding <=> $1::vector)) AS vector_score,\n          ts_rank(tsv, plainto_tsquery('english', $2)) AS text_score\n        FROM ${this.table}\n        WHERE agent_id = $3\n          AND (user_id IS NULL OR user_id = $4)\n      )\n      SELECT\n        id,\n        path,\n        content,\n        created_at,\n        user_id,\n        (${HYBRID_VECTOR_WEIGHT} * vector_score + ${HYBRID_TEXT_WEIGHT} * text_score) AS score\n      FROM scored\n      WHERE (${HYBRID_VECTOR_WEIGHT} * vector_score + ${HYBRID_TEXT_WEIGHT} * text_score) >= $5\n      ORDER BY score DESC\n      LIMIT $6\n    `;\n\n    const { rows } = await this.pool.query(sql, [\n      vectorLiteral,\n      trimmed,\n      scope.agentId,\n      callerId,\n      minScore,\n      maxResults,\n    ]);\n\n    let hits: MemoryEntry[] = rows.map(\n      (row: {\n        id: string | number;\n        path: string;\n        content: string;\n        created_at: Date;\n        user_id: string | null;\n        score: string | number;\n      }): MemoryEntry => ({\n        id: String(row.id),\n        path: row.path,\n        content: row.content,\n        createdAt: new Date(row.created_at),\n        score: Number(row.score),\n        source: 'vector',\n        tier: getTierForPath(row.path),\n      })\n    );\n\n    // Phase 2: temporal decay (before MMR so diversity sees post-decay ranks)\n    if (opts.temporalDecay?.enabled) {\n      hits = applyTemporalDecayToHits(hits, opts.temporalDecay);\n      hits.sort((a, b) => b.score - a.score);\n    }\n\n    // Phase 2: MMR reranking\n    if (opts.mmr?.enabled) {\n      hits = applyMMRToMemoryHits(hits, opts.mmr);\n    }\n\n    // Phase 2: citations (decorate last — mutates content with Source: trailer)\n    const citationsMode = opts.citations ?? 'auto';\n    if (shouldIncludeCitations(citationsMode)) {\n      hits = decorateCitations(hits, true);\n    }\n\n    return hits;\n  }\n\n  async get(\n    scope: MemoryScope,\n    opts: MemoryGetOptions\n  ): Promise<MemoryReadResult | null> {\n    assertScope(scope);\n    if (!opts.path) return null;\n\n    const callerId = normalizeCallerId(scope);\n    const tier = getTierForPath(opts.path);\n\n    // Agent-tier rows always live under user_id=NULL. User-tier rows\n    // always carry the caller's id. Querying with a precise predicate\n    // is faster than leaving it open AND guarantees a user cannot\n    // read another user's row even if they know the path by heart.\n    const userClause = tier === 'agent' ? 'user_id IS NULL' : 'user_id = $3';\n\n    const params: unknown[] = [scope.agentId, opts.path];\n    if (tier === 'user') params.push(callerId);\n\n    const sql = `\n      SELECT content\n      FROM ${this.table}\n      WHERE agent_id = $1 AND path = $2 AND ${userClause}\n      ORDER BY updated_at DESC\n      LIMIT 1\n    `;\n    const { rows } = await this.pool.query(sql, params);\n    if (rows.length === 0) return null;\n\n    const text = String(rows[0].content ?? '');\n    if (opts.from == null && opts.lines == null) {\n      return { path: opts.path, text };\n    }\n\n    const allLines = text.split('\\n');\n    const fromIdx = Math.max(0, (opts.from ?? 1) - 1);\n    const count = Math.max(0, opts.lines ?? allLines.length - fromIdx);\n    const slice = allLines.slice(fromIdx, fromIdx + count).join('\\n');\n    return { path: opts.path, text: slice };\n  }\n\n  async append(scope: MemoryScope, input: MemoryAppendInput): Promise<void> {\n    assertScope(scope);\n    // Whitelist + tier + scope-compatibility check in one call. Throws\n    // with an actionable message for each failure mode.\n    const descriptor = assertWritablePath(input.path, scope);\n    const content = input.content.trim();\n    if (!content) {\n      throw new Error('memory_append content must be non-empty');\n    }\n\n    // Tier determines the row's user_id:\n    //   agent tier → NULL (shared across all users)\n    //   user tier  → the caller's id (assertWritablePath guarantees non-empty)\n    const rowUserId = descriptor.tier === 'agent' ? null : String(scope.userId);\n    const provenance = scope.userId != null ? String(scope.userId) : null;\n\n    // Read the existing row (if any) for THIS tier so we can embed the\n    // merged content. Agent-tier merges regardless of caller; user-tier\n    // merges only within the caller's own row.\n    const lookupSql = `\n      SELECT content FROM ${this.table}\n      WHERE agent_id = $1 AND path = $2 AND ${rowUserId === null ? 'user_id IS NULL' : 'user_id = $3'}\n      LIMIT 1\n    `;\n    const lookupParams: unknown[] =\n      rowUserId === null\n        ? [scope.agentId, input.path]\n        : [scope.agentId, input.path, rowUserId];\n    const existing = await this.pool.query(lookupSql, lookupParams);\n    const priorContent: string =\n      existing.rows.length > 0 ? String(existing.rows[0].content ?? '') : '';\n    const mergedContent = priorContent\n      ? `${priorContent.replace(/\\s+$/, '')}\\n\\n${content}`\n      : content;\n\n    const vector = await this.embedder.embed(mergedContent);\n    const vectorLiteral = toVectorLiteral(vector);\n\n    // UPSERT on (agent_id, user_id, path) with NULLS NOT DISTINCT so\n    // two NULL user_ids collide on the same agent+path (exactly one\n    // agent-tier row) while per-user rows on the same path coexist.\n    //\n    // Provenance note: `last_user_id` always records WHO wrote the\n    // latest append, even for agent-tier rows where the row's own\n    // `user_id` stays NULL. That gives the admin UI an audit trail\n    // (\"agent-tier row last updated by Alice\") without changing the\n    // scoping semantics.\n    const upsertSql = `\n      INSERT INTO ${this.table} (agent_id, user_id, path, content, embedding, last_user_id, updated_at)\n      VALUES ($1, $2, $3, $4, $5::vector, $6, NOW())\n      ON CONFLICT (agent_id, user_id, path) DO UPDATE\n      SET content      = EXCLUDED.content,\n          embedding    = EXCLUDED.embedding,\n          last_user_id = EXCLUDED.last_user_id,\n          updated_at   = NOW()\n    `;\n    await this.pool.query(upsertSql, [\n      scope.agentId,\n      rowUserId,\n      input.path,\n      mergedContent,\n      vectorLiteral,\n      provenance,\n    ]);\n  }\n\n  async health(): Promise<MemoryHealth> {\n    try {\n      await this.pool.query('SELECT 1');\n      return { ok: true, backend: 'vector' };\n    } catch (err) {\n      return {\n        ok: false,\n        backend: 'vector',\n        error: err instanceof Error ? err.message : String(err),\n      };\n    }\n  }\n}\n"],"names":[],"mappings":"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"}