{"version":3,"file":"memory.mjs","names":[],"sources":["../../../../../../../ai/src/memory/memory.ts"],"sourcesContent":["import { resolveDefaultStore } from \"../config\";\nimport type {\n  MemoryConfig,\n  WorkingMemoryConfig,\n} from \"../contracts/memory/memory-config.type\";\nimport type {\n  MemoryItem,\n  MemoryTier,\n  RecalledMemory,\n} from \"../contracts/memory/memory-item.type\";\nimport type { MemoryContract } from \"../contracts/memory/memory.contract\";\nimport type { RecallOptions } from \"../contracts/memory/recall-options.type\";\nimport { EpisodicMemory } from \"./episodic-memory\";\nimport { ProceduralMemory } from \"./procedural-memory\";\nimport { SemanticMemory } from \"./semantic-memory\";\nimport { WorkingMemory } from \"./working-memory\";\n\nconst DEFAULT_NAME = \"memory\";\nconst DEFAULT_SEMANTIC_NAMESPACE = \"ai.memory.semantic\";\nconst DEFAULT_EPISODIC_NAMESPACE = \"ai.memory.episodic\";\nconst DEFAULT_PROCEDURAL_NAMESPACE = \"ai.memory.procedural\";\nconst DEFAULT_K = 5;\nconst DEFAULT_THRESHOLD = 0.7;\nconst DEFAULT_RECENCY_WEIGHT = 0.3;\nconst DEFAULT_HALF_LIFE_MS = 7 * 24 * 60 * 60 * 1000;\nconst DEFAULT_REINFORCEMENT_WEIGHT = 0.3;\n\n/**\n * Entries the in-process working buffer holds before it starts evicting\n * its oldest (4.15.0 — security fix for unbounded growth). Sized to hold\n * a deep multi-session scratch history while capping the tier's worst\n * case at a few MB of resident text rather than \"everything this process\n * has ever been told.\"\n */\nconst DEFAULT_WORKING_MAX_ITEMS = 1000;\n\n/**\n * Create an agent memory store (memory core M2).\n *\n * Wires up to four tiers behind the {@link MemoryContract}: **working**\n * (in-run scratch, recency), **semantic** (durable facts by cosine\n * similarity), **episodic** (durable events, similarity blended with\n * recency), and **procedural** (durable how-tos, similarity blended with\n * reinforcement). The working tier is on by default; the other three each\n * activate only when their config is supplied. The three vector tiers\n * mirror how `semanticCache` delegates similarity to the cache driver's\n * `.similar()`.\n *\n * Resolution happens once here, at construction (loud), rather than per\n * call (silent until first use): a vector-tier config with no `store` and\n * no `ai.config({ defaultStore })` throws now; enabling no tier at all\n * throws now.\n *\n * TTL-based decay / forgetting remains deferred. The working tier is\n * size-bounded (`working: { maxItems }`, default `1000`, oldest-written\n * evicted first) because it is the one tier that holds everything it is\n * told in process memory for the life of the instance; the durable tiers\n * delegate retention to their `CacheDriver`.\n *\n * **Isolation (4.15.0).** `remember({ scope })` / `recall(query, { scope })`\n * carry an opaque tenant / session key that every tier enforces as an\n * exact-equality filter before scoring — one scope's memories never\n * surface in another's recall, and identical text under two scopes stays\n * two entries. Unscoped writes form a shared pool that only an unscoped\n * recall can read; there is no \"all scopes\" query. `ai.orchestrator()`\n * derives this from the turn's `sessionId` automatically.\n *\n * @example\n * import { ai } from \"@warlock.js/ai\";\n * import { MemoryCacheDriver } from \"@warlock.js/cache\";\n *\n * const store = new MemoryCacheDriver();\n * store.setOptions({});\n *\n * const mem = ai.memory({\n *   semantic: { embedder, store },\n *   defaultTier: \"semantic\",\n * });\n *\n * await mem.remember({ text: \"User prefers concise answers.\" });\n * const hits = await mem.recall(\"how should I respond?\", { k: 3 });\n */\nexport function memory(config: MemoryConfig = {}): MemoryContract {\n  const name = config.name ?? DEFAULT_NAME;\n  const workingConfig = config.working ?? true;\n  const defaultK = config.k ?? DEFAULT_K;\n  const defaultThreshold = config.threshold ?? DEFAULT_THRESHOLD;\n\n  const working =\n    workingConfig === false\n      ? undefined\n      : new WorkingMemory(resolveWorkingMaxItems(workingConfig, name));\n\n  const semantic = config.semantic\n    ? buildSemanticTier(config.semantic, name)\n    : undefined;\n\n  const episodic = config.episodic\n    ? buildEpisodicTier(config.episodic, name)\n    : undefined;\n\n  const procedural = config.procedural\n    ? buildProceduralTier(config.procedural, name)\n    : undefined;\n\n  const tiers: Tiers = { working, semantic, episodic, procedural };\n\n  if (!working && !semantic && !episodic && !procedural) {\n    throw new Error(\n      `memory(\"${name}\"): no tier enabled — enable \\`working\\` (default) or pass a \\`semantic\\` / \\`episodic\\` / \\`procedural\\` config; a memory with no tiers can neither store nor recall`,\n    );\n  }\n\n  const defaultTier: MemoryTier = config.defaultTier ?? \"working\";\n\n  assertTierEnabled(defaultTier, tiers, name);\n\n  return {\n    name,\n    async remember(items: MemoryItem | MemoryItem[]): Promise<void> {\n      const list = Array.isArray(items) ? items : [items];\n\n      const writes: Promise<void>[] = [];\n\n      for (const item of list) {\n        const tier = item.tier ?? defaultTier;\n\n        assertTierEnabled(tier, tiers, name);\n\n        if (tier === \"working\") {\n          working!.remember(item);\n\n          continue;\n        }\n\n        if (tier === \"semantic\") {\n          writes.push(semantic!.remember(item));\n\n          continue;\n        }\n\n        if (tier === \"episodic\") {\n          writes.push(episodic!.remember(item));\n\n          continue;\n        }\n\n        writes.push(procedural!.remember(item));\n      }\n\n      await Promise.all(writes);\n    },\n    async recall(\n      query: string,\n      options: RecallOptions = {},\n    ): Promise<RecalledMemory[]> {\n      const k = options.k ?? defaultK;\n      const threshold = options.threshold ?? defaultThreshold;\n\n      if (options.tier) {\n        assertTierEnabled(options.tier, tiers, name);\n      }\n\n      const wants = (tier: MemoryTier): boolean =>\n        !options.tier || options.tier === tier;\n\n      // `options.scope` is the isolation key — each tier applies it as an\n      // exact-equality filter internally, BEFORE its own scoring and\n      // slicing, so nothing outside the scope reaches this merge.\n      const scope = options.scope;\n\n      const [workingHits, semanticHits, episodicHits, proceduralHits] =\n        await Promise.all([\n          working && wants(\"working\")\n            ? Promise.resolve(working.recall(k, scope))\n            : Promise.resolve([] as RecalledMemory[]),\n          semantic && wants(\"semantic\")\n            ? semantic.recall(query, k, threshold, scope)\n            : Promise.resolve([] as RecalledMemory[]),\n          episodic && wants(\"episodic\")\n            ? episodic.recall(query, k, threshold, scope)\n            : Promise.resolve([] as RecalledMemory[]),\n          procedural && wants(\"procedural\")\n            ? procedural.recall(query, k, threshold, scope)\n            : Promise.resolve([] as RecalledMemory[]),\n        ]);\n\n      return [\n        ...workingHits,\n        ...semanticHits,\n        ...episodicHits,\n        ...proceduralHits,\n      ]\n        .sort((first, second) => second.score - first.score)\n        .slice(0, k);\n    },\n    async clear(tier?: MemoryTier): Promise<void> {\n      const clears: Promise<void>[] = [];\n\n      if (working && (!tier || tier === \"working\")) {\n        working.clear();\n      }\n\n      if (semantic && (!tier || tier === \"semantic\")) {\n        clears.push(semantic.clear());\n      }\n\n      if (episodic && (!tier || tier === \"episodic\")) {\n        clears.push(episodic.clear());\n      }\n\n      if (procedural && (!tier || tier === \"procedural\")) {\n        clears.push(procedural.clear());\n      }\n\n      await Promise.all(clears);\n    },\n  };\n}\n\n/** The four tier instances a `memory()` composes; `undefined` when off. */\ntype Tiers = {\n  working: WorkingMemory | undefined;\n  semantic: SemanticMemory | undefined;\n  episodic: EpisodicMemory | undefined;\n  procedural: ProceduralMemory | undefined;\n};\n\n/**\n * Resolve the working tier's size bound from the `working` config\n * (`true` / a `{ maxItems }` object), validating it at construction the\n * same way every other tier's wiring fails loud-and-now rather than on\n * first use. There is deliberately no unbounded setting — the buffer is\n * process-resident for the life of the memory instance, so \"no cap\" is\n * a memory-exhaustion vector, not a configuration choice.\n */\nfunction resolveWorkingMaxItems(\n  workingConfig: true | WorkingMemoryConfig,\n  name: string,\n): number {\n  const maxItems =\n    workingConfig === true\n      ? DEFAULT_WORKING_MAX_ITEMS\n      : (workingConfig.maxItems ?? DEFAULT_WORKING_MAX_ITEMS);\n\n  if (!Number.isInteger(maxItems) || maxItems < 1) {\n    throw new Error(\n      `memory(\"${name}\"): working tier \\`maxItems\\` must be an integer >= 1 — received ${String(maxItems)}`,\n    );\n  }\n\n  return maxItems;\n}\n\n/**\n * Resolve the semantic tier's store (explicit `store` wins, else the\n * global `ai.config({ defaultStore })`) and build the tier. Throws at\n * construction when neither is available — the same loud-now contract\n * `semanticCache` follows.\n */\nfunction buildSemanticTier(\n  semanticConfig: NonNullable<MemoryConfig[\"semantic\"]>,\n  name: string,\n): SemanticMemory {\n  const store = semanticConfig.store ?? resolveDefaultStore();\n\n  if (!store) {\n    throw new Error(\n      `memory(\"${name}\"): semantic tier has no store — pass \\`semantic.store\\` or call \\`ai.config({ defaultStore })\\` at app boot before constructing the memory`,\n    );\n  }\n\n  return new SemanticMemory(\n    semanticConfig.embedder,\n    store,\n    semanticConfig.namespace ?? DEFAULT_SEMANTIC_NAMESPACE,\n  );\n}\n\n/**\n * Resolve the episodic tier's store (explicit `store` wins, else the\n * global default) and build the tier with its recency knobs. Throws at\n * construction when neither store is available — the same loud-now\n * contract the semantic tier follows.\n */\nfunction buildEpisodicTier(\n  episodicConfig: NonNullable<MemoryConfig[\"episodic\"]>,\n  name: string,\n): EpisodicMemory {\n  const store = episodicConfig.store ?? resolveDefaultStore();\n\n  if (!store) {\n    throw new Error(\n      `memory(\"${name}\"): episodic tier has no store — pass \\`episodic.store\\` or call \\`ai.config({ defaultStore })\\` at app boot before constructing the memory`,\n    );\n  }\n\n  return new EpisodicMemory(\n    episodicConfig.embedder,\n    store,\n    episodicConfig.namespace ?? DEFAULT_EPISODIC_NAMESPACE,\n    episodicConfig.recencyWeight ?? DEFAULT_RECENCY_WEIGHT,\n    episodicConfig.halfLifeMs ?? DEFAULT_HALF_LIFE_MS,\n    episodicConfig.now ?? (() => Date.now()),\n  );\n}\n\n/**\n * Resolve the procedural tier's store and build the tier with its\n * reinforcement knob. Throws at construction when no store is available.\n */\nfunction buildProceduralTier(\n  proceduralConfig: NonNullable<MemoryConfig[\"procedural\"]>,\n  name: string,\n): ProceduralMemory {\n  const store = proceduralConfig.store ?? resolveDefaultStore();\n\n  if (!store) {\n    throw new Error(\n      `memory(\"${name}\"): procedural tier has no store — pass \\`procedural.store\\` or call \\`ai.config({ defaultStore })\\` at app boot before constructing the memory`,\n    );\n  }\n\n  return new ProceduralMemory(\n    proceduralConfig.embedder,\n    store,\n    proceduralConfig.namespace ?? DEFAULT_PROCEDURAL_NAMESPACE,\n    proceduralConfig.reinforcementWeight ?? DEFAULT_REINFORCEMENT_WEIGHT,\n  );\n}\n\n/**\n * Guard that a tier referenced by config / a call is actually enabled,\n * failing fast with an actionable message instead of a downstream\n * `undefined` dereference.\n */\nfunction assertTierEnabled(tier: MemoryTier, tiers: Tiers, name: string): void {\n  if (tier === \"working\" && !tiers.working) {\n    throw new Error(\n      `memory(\"${name}\"): working tier is disabled — set \\`working: true\\` (the default) to use it`,\n    );\n  }\n\n  if (tier === \"semantic\" && !tiers.semantic) {\n    throw new Error(\n      `memory(\"${name}\"): semantic tier is not configured — pass \\`semantic\\` config to use it`,\n    );\n  }\n\n  if (tier === \"episodic\" && !tiers.episodic) {\n    throw new Error(\n      `memory(\"${name}\"): episodic tier is not configured — pass \\`episodic\\` config to use it`,\n    );\n  }\n\n  if (tier === \"procedural\" && !tiers.procedural) {\n    throw new Error(\n      `memory(\"${name}\"): procedural tier is not configured — pass \\`procedural\\` config to use it`,\n    );\n  }\n}\n"],"mappings":"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"}