@ai-agent-forge/plugin-memory 0.85.0

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 (148) hide show
  1. package/README.md +65 -0
  2. package/agent-forge.json +11 -0
  3. package/dist/capability.d.ts +182 -0
  4. package/dist/capability.d.ts.map +1 -0
  5. package/dist/capability.js +2565 -0
  6. package/dist/capability.js.map +1 -0
  7. package/dist/entry.d.ts +36 -0
  8. package/dist/entry.d.ts.map +1 -0
  9. package/dist/entry.js +154 -0
  10. package/dist/entry.js.map +1 -0
  11. package/dist/index.d.ts +49 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +49 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/memory/assistant-card.d.ts +31 -0
  16. package/dist/memory/assistant-card.d.ts.map +1 -0
  17. package/dist/memory/assistant-card.js +108 -0
  18. package/dist/memory/assistant-card.js.map +1 -0
  19. package/dist/memory/candidates.d.ts +65 -0
  20. package/dist/memory/candidates.d.ts.map +1 -0
  21. package/dist/memory/candidates.js +100 -0
  22. package/dist/memory/candidates.js.map +1 -0
  23. package/dist/memory/code-memory.d.ts +89 -0
  24. package/dist/memory/code-memory.d.ts.map +1 -0
  25. package/dist/memory/code-memory.js +104 -0
  26. package/dist/memory/code-memory.js.map +1 -0
  27. package/dist/memory/compaction-sequencer.d.ts +63 -0
  28. package/dist/memory/compaction-sequencer.d.ts.map +1 -0
  29. package/dist/memory/compaction-sequencer.js +129 -0
  30. package/dist/memory/compaction-sequencer.js.map +1 -0
  31. package/dist/memory/continuation.d.ts +44 -0
  32. package/dist/memory/continuation.d.ts.map +1 -0
  33. package/dist/memory/continuation.js +49 -0
  34. package/dist/memory/continuation.js.map +1 -0
  35. package/dist/memory/curation.d.ts +58 -0
  36. package/dist/memory/curation.d.ts.map +1 -0
  37. package/dist/memory/curation.js +68 -0
  38. package/dist/memory/curation.js.map +1 -0
  39. package/dist/memory/egress-policy.d.ts +50 -0
  40. package/dist/memory/egress-policy.d.ts.map +1 -0
  41. package/dist/memory/egress-policy.js +71 -0
  42. package/dist/memory/egress-policy.js.map +1 -0
  43. package/dist/memory/embedding-provider.d.ts +70 -0
  44. package/dist/memory/embedding-provider.d.ts.map +1 -0
  45. package/dist/memory/embedding-provider.js +164 -0
  46. package/dist/memory/embedding-provider.js.map +1 -0
  47. package/dist/memory/embedding-reranker.d.ts +56 -0
  48. package/dist/memory/embedding-reranker.d.ts.map +1 -0
  49. package/dist/memory/embedding-reranker.js +109 -0
  50. package/dist/memory/embedding-reranker.js.map +1 -0
  51. package/dist/memory/foundation.d.ts +168 -0
  52. package/dist/memory/foundation.d.ts.map +1 -0
  53. package/dist/memory/foundation.js +487 -0
  54. package/dist/memory/foundation.js.map +1 -0
  55. package/dist/memory/host-module-import.d.ts +25 -0
  56. package/dist/memory/host-module-import.d.ts.map +1 -0
  57. package/dist/memory/host-module-import.js +41 -0
  58. package/dist/memory/host-module-import.js.map +1 -0
  59. package/dist/memory/ledger.d.ts +58 -0
  60. package/dist/memory/ledger.d.ts.map +1 -0
  61. package/dist/memory/ledger.js +315 -0
  62. package/dist/memory/ledger.js.map +1 -0
  63. package/dist/memory/lifecycle.d.ts +124 -0
  64. package/dist/memory/lifecycle.d.ts.map +1 -0
  65. package/dist/memory/lifecycle.js +201 -0
  66. package/dist/memory/lifecycle.js.map +1 -0
  67. package/dist/memory/memory-network.d.ts +55 -0
  68. package/dist/memory/memory-network.d.ts.map +1 -0
  69. package/dist/memory/memory-network.js +70 -0
  70. package/dist/memory/memory-network.js.map +1 -0
  71. package/dist/memory/model-cache-hygiene.d.ts +18 -0
  72. package/dist/memory/model-cache-hygiene.d.ts.map +1 -0
  73. package/dist/memory/model-cache-hygiene.js +38 -0
  74. package/dist/memory/model-cache-hygiene.js.map +1 -0
  75. package/dist/memory/preference-disambiguator.d.ts +43 -0
  76. package/dist/memory/preference-disambiguator.d.ts.map +1 -0
  77. package/dist/memory/preference-disambiguator.js +81 -0
  78. package/dist/memory/preference-disambiguator.js.map +1 -0
  79. package/dist/memory/preference-lifecycle.d.ts +66 -0
  80. package/dist/memory/preference-lifecycle.d.ts.map +1 -0
  81. package/dist/memory/preference-lifecycle.js +129 -0
  82. package/dist/memory/preference-lifecycle.js.map +1 -0
  83. package/dist/memory/preference-promotion.d.ts +87 -0
  84. package/dist/memory/preference-promotion.d.ts.map +1 -0
  85. package/dist/memory/preference-promotion.js +102 -0
  86. package/dist/memory/preference-promotion.js.map +1 -0
  87. package/dist/memory/preference-resolver.d.ts +44 -0
  88. package/dist/memory/preference-resolver.d.ts.map +1 -0
  89. package/dist/memory/preference-resolver.js +107 -0
  90. package/dist/memory/preference-resolver.js.map +1 -0
  91. package/dist/memory/purge-journal.d.ts +76 -0
  92. package/dist/memory/purge-journal.d.ts.map +1 -0
  93. package/dist/memory/purge-journal.js +130 -0
  94. package/dist/memory/purge-journal.js.map +1 -0
  95. package/dist/memory/purge.d.ts +90 -0
  96. package/dist/memory/purge.d.ts.map +1 -0
  97. package/dist/memory/purge.js +138 -0
  98. package/dist/memory/purge.js.map +1 -0
  99. package/dist/memory/recall-agent.d.ts +84 -0
  100. package/dist/memory/recall-agent.d.ts.map +1 -0
  101. package/dist/memory/recall-agent.js +199 -0
  102. package/dist/memory/recall-agent.js.map +1 -0
  103. package/dist/memory/recall-index.d.ts +87 -0
  104. package/dist/memory/recall-index.d.ts.map +1 -0
  105. package/dist/memory/recall-index.js +222 -0
  106. package/dist/memory/recall-index.js.map +1 -0
  107. package/dist/memory/recall-packet.d.ts +121 -0
  108. package/dist/memory/recall-packet.d.ts.map +1 -0
  109. package/dist/memory/recall-packet.js +156 -0
  110. package/dist/memory/recall-packet.js.map +1 -0
  111. package/dist/memory/scheduler-api.d.ts +99 -0
  112. package/dist/memory/scheduler-api.d.ts.map +1 -0
  113. package/dist/memory/scheduler-api.js +93 -0
  114. package/dist/memory/scheduler-api.js.map +1 -0
  115. package/dist/memory/scheduler.d.ts +55 -0
  116. package/dist/memory/scheduler.d.ts.map +1 -0
  117. package/dist/memory/scheduler.js +91 -0
  118. package/dist/memory/scheduler.js.map +1 -0
  119. package/dist/memory/store.d.ts +107 -0
  120. package/dist/memory/store.d.ts.map +1 -0
  121. package/dist/memory/store.js +208 -0
  122. package/dist/memory/store.js.map +1 -0
  123. package/dist/memory/suite-memory.d.ts +208 -0
  124. package/dist/memory/suite-memory.d.ts.map +1 -0
  125. package/dist/memory/suite-memory.js +288 -0
  126. package/dist/memory/suite-memory.js.map +1 -0
  127. package/dist/memory/transfer.d.ts +142 -0
  128. package/dist/memory/transfer.d.ts.map +1 -0
  129. package/dist/memory/transfer.js +210 -0
  130. package/dist/memory/transfer.js.map +1 -0
  131. package/dist/memory/vector-index.d.ts +39 -0
  132. package/dist/memory/vector-index.d.ts.map +1 -0
  133. package/dist/memory/vector-index.js +136 -0
  134. package/dist/memory/vector-index.js.map +1 -0
  135. package/dist/memory/write-budget.d.ts +33 -0
  136. package/dist/memory/write-budget.d.ts.map +1 -0
  137. package/dist/memory/write-budget.js +45 -0
  138. package/dist/memory/write-budget.js.map +1 -0
  139. package/dist/testing/memory-testkit.d.ts +149 -0
  140. package/dist/testing/memory-testkit.d.ts.map +1 -0
  141. package/dist/testing/memory-testkit.js +438 -0
  142. package/dist/testing/memory-testkit.js.map +1 -0
  143. package/dist/utils/sync-sleep.d.ts +2 -0
  144. package/dist/utils/sync-sleep.d.ts.map +1 -0
  145. package/dist/utils/sync-sleep.js +11 -0
  146. package/dist/utils/sync-sleep.js.map +1 -0
  147. package/package.json +56 -0
  148. package/plugin.json +10 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.js","sourceRoot":"","sources":["../../src/memory/ledger.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Durable memory ledger (统一修复轮 A, 方案系统设计 §6.1 + 方案实施计划 §11 A) —\n * the append-only JSONL persistent replica of a Memory Foundation store.\n *\n * Layout: `<agentDir>/memory/<owner>/ledger-<suiteId|\"legacy\">.jsonl`. The\n * owner segment is the store/facade isolation principal (one local user per\n * agentDir in this product); the suite segment is the 方案 read-boundary\n * domain — a real suite id, or the sentinel \"legacy\" for sessions without a\n * suite binding. One line holds exactly one committed canonical atom\n * (contract @2, including `suiteId`).\n *\n * Replica semantics: the write path appends every committed atom (canonical\n * copy on disk); the read path `load()`s the atoms at startup and replays them\n * into a fresh store. After a forget, `rewrite(atom[])` physically rewrites\n * the file so purged atoms cannot survive a restart. Corruption tolerance:\n * malformed lines are skipped and counted, duplicates are skipped and counted\n * — loading never fails. Only the WRITE path validates strictly: a ledger must\n * never persist an atom the Foundation would reject.\n *\n * Concurrency and durability envelope (统一修复轮 H1, #7 深度修复; rewrite 共\n * 锁 + fsync 关洞修复): competing writers — append AND rewrite — serialize\n * through a `.lock` sibling file taken via exclusive creation (`wx` — atomic\n * on every supported platform). A second writer polls (25 ms) until the\n * holder removes the lock, steals a lock whose mtime is older than\n * `staleLockMs` (default 60 s — a crashed writer left it behind; a live\n * holder keeps the lock for milliseconds), and throws after `lockTimeoutMs`\n * (default 5 s, configurable) — a writer never silently blocks and never\n * silently drops its row; lock or IO failures propagate to the caller. The\n * lock is wall-clock based (real waiting), not the injected deterministic\n * `now`. Durability: both write paths run open → write → fsync → close, so the\n * file contents of an acknowledged append or rewrite survive a hard crash,\n * subject to the directory-entry boundary declared below (a torn trailing\n * line, possible only in files written by older builds or damaged externally,\n * is still skipped by the loader's corruption tolerance).\n * Remaining boundaries, declared honestly: the exclusive create is a mutex\n * only where exclusive creation is atomic — local filesystems; multi-host or\n * network-share access is out of scope. `rewrite` still publishes the calling\n * process's atom set as the whole file: rows another process appended since\n * this process last loaded are not merged into a rewrite (the forget caller\n * owns that reconciliation) — the lock closes the interleaving window, not\n * stale in-memory views. And fsync persists file contents, not directory\n * entries (Windows cannot fsync a directory): a crash between rewrite's fsync\n * and its rename leaves the previous file intact, never a half-written one.\n * An fsync failure on append propagates after the row has already reached the\n * OS: the unacknowledged row may be readable, and a caller retry appends it\n * again — the loader's duplicate tolerance (`duplicateSkipped`) absorbs that.\n */\nimport {\n\tcloseSync,\n\tfsyncSync,\n\tmkdirSync,\n\topenSync,\n\treadFileSync,\n\trenameSync,\n\tstatSync,\n\tunlinkSync,\n\twriteFileSync,\n\twriteSync,\n} from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { sleepSync } from \"../utils/sync-sleep.ts\";\nimport type { MemoryAtomV1 } from \"./foundation.ts\";\nimport { validateMemoryAtomV1 } from \"./foundation.ts\";\n\n/** Sentinel suite domain for sessions without a suite binding (设计 §6.1). */\nexport const LEGACY_MEMORY_DOMAIN = \"legacy\";\n\n/** Suffix of the append lock sibling file (`<ledgerPath>.lock`). */\nconst LOCK_FILE_SUFFIX = \".lock\";\n/**\n * Wall-clock wait between lock acquisition attempts. Kept as a per-file\n * constant even though the durable store backend currently uses the same\n * 25 ms value: the two lock protocols stay independent.\n */\nconst LOCK_POLL_INTERVAL_MS = 25;\n/** Default ceiling on waiting for a competing writer's lock. */\nconst DEFAULT_LOCK_TIMEOUT_MS = 5000;\n/**\n * Default age at which a lock counts as abandoned (its writer crashed while\n * holding it). Deliberately far above any live hold time (milliseconds of\n * appendFileSync) and above the wait timeout, so a waiter fails loudly at the\n * timeout instead of stealing a lock that might still be held.\n */\nconst DEFAULT_STALE_LOCK_MS = 60_000;\n\n/**\n * Writes every byte of `data` to the fd, looping over partial `writeSync`\n * results (a short write would silently break the fsync durability promise:\n * the caller fsyncs and closes only after the full payload is handed to the\n * OS).\n */\nconst writeAllSync = (fd: number, data: string): void => {\n\tconst buffer = Buffer.from(data, \"utf8\");\n\tlet written = 0;\n\twhile (written < buffer.length) {\n\t\twritten += writeSync(fd, buffer, written, buffer.length - written);\n\t}\n};\n\n/** Result of {@link DurableMemoryLedgerV1.load}: replayable atoms plus skip diagnostics. */\nexport interface DurableMemoryLedgerLoadResultV1 {\n\t/** Valid atoms in file order — replay these into a fresh store. */\n\treadonly atoms: readonly MemoryAtomV1[];\n\t/** Malformed lines (invalid JSON or invalid atoms) skipped during load. */\n\treadonly corruptedLines: number;\n\t/** Atoms whose memoryId was already loaded (crash windows can duplicate appends). */\n\treadonly duplicateSkipped: number;\n\t/** True when the file existed but could not be read (permissions, IO error). */\n\treadonly unreadable: boolean;\n}\n\nexport interface DurableMemoryLedgerV1 {\n\t/**\n\t * Reads the ledger file and returns the replayable atom set plus skip\n\t * counters. A missing file is an empty ledger, not an error; malformed or\n\t * duplicate lines are skipped and counted — load NEVER throws.\n\t */\n\tload(): DurableMemoryLedgerLoadResultV1;\n\t/**\n\t * Appends one committed atom as a single JSONL line (strictly validated).\n\t * The write serializes against other processes through the `.lock` sibling\n\t * file and is fsynced before close, so an acknowledged append survives a\n\t * hard crash; a lock timeout or IO failure throws (an append never fails\n\t * silently).\n\t */\n\tappend(atom: MemoryAtomV1): void;\n\t/**\n\t * Physically rewrites the whole file with the given atoms (atomic tmp-file\n\t * + rename). The forget/purge flow calls this after removing atoms so the\n\t * durable replica matches the surviving canonical set. The rewrite holds\n\t * the same cross-process lock as append for its whole duration (same\n\t * timeout and stale-lock-stealing semantics), so a competing writer's\n\t * append cannot be lost inside the rewrite window, and the new content is\n\t * fsynced before the rename.\n\t */\n\trewrite(atoms: readonly MemoryAtomV1[]): void;\n\t/** The ledger's current in-memory atom set (append/rewrite keep it in sync). */\n\tatomsSnapshot(): readonly MemoryAtomV1[];\n}\n\nfunction assertNonEmptyString(value: unknown, label: string): asserts value is string {\n\tif (typeof value !== \"string\" || value.trim().length === 0) throw new Error(`${label} must be a non-empty string`);\n}\n\n/**\n * Creates a durable JSONL memory ledger at `path`. `now` suffixes the rewrite\n * tmp file so deterministic tests can pin it; it never participates in atom\n * identity (timestamps live inside the canonical atoms) and never drives the\n * append lock, which is a real-time mechanism. `lockTimeoutMs` bounds the\n * wall-clock wait for a competing writer's lock; `staleLockMs` is the age at\n * which a lock counts as abandoned and is stolen. Both exist so tests can\n * exercise the timeout and steal paths quickly.\n */\nexport function createDurableMemoryLedger(options: {\n\treadonly path: string;\n\treadonly now?: () => number;\n\treadonly lockTimeoutMs?: number;\n\treadonly staleLockMs?: number;\n}): DurableMemoryLedgerV1 {\n\tassertNonEmptyString(options.path, \"Durable ledger path\");\n\tif (options.lockTimeoutMs !== undefined && (!Number.isFinite(options.lockTimeoutMs) || options.lockTimeoutMs <= 0)) {\n\t\tthrow new Error(\"Durable ledger lockTimeoutMs must be a positive number\");\n\t}\n\tif (options.staleLockMs !== undefined && (!Number.isFinite(options.staleLockMs) || options.staleLockMs <= 0)) {\n\t\tthrow new Error(\"Durable ledger staleLockMs must be a positive number\");\n\t}\n\tconst now = options.now ?? (() => Date.now());\n\tconst path = options.path;\n\tconst lockTimeoutMs = options.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;\n\tconst staleLockMs = options.staleLockMs ?? DEFAULT_STALE_LOCK_MS;\n\tconst lockPath = `${path}${LOCK_FILE_SUFFIX}`;\n\t/** In-memory replica of the durable atom set; kept in sync by append/rewrite/load. */\n\tlet atoms: MemoryAtomV1[] = [];\n\n\tconst ensureDir = (): void => {\n\t\tmkdirSync(dirname(path), { recursive: true });\n\t};\n\n\t/**\n\t * Takes the ledger writer lock, shared by append and rewrite: exclusive\n\t * create (`wx`) is the mutex; an existing lock is polled until released,\n\t * stolen once its mtime exceeds `staleLockMs` (crashed writer), or turned\n\t * into a thrown error at the wait deadline. The lock file carries a\n\t * per-acquisition token so a stale-steal victim releasing late can never\n\t * delete the thief's fresh lock.\n\t */\n\tconst acquireAppendLock = (): string => {\n\t\tconst deadline = Date.now() + lockTimeoutMs;\n\t\tfor (;;) {\n\t\t\tensureDir();\n\t\t\tconst token = `${process.pid}:${Date.now()}`;\n\t\t\ttry {\n\t\t\t\tconst fd = openSync(lockPath, \"wx\");\n\t\t\t\ttry {\n\t\t\t\t\twriteFileSync(fd, token);\n\t\t\t\t} finally {\n\t\t\t\t\tcloseSync(fd);\n\t\t\t\t}\n\t\t\t\treturn token;\n\t\t\t} catch (error) {\n\t\t\t\t// POSIX maps a lost create race to EEXIST; Windows/libuv maps the\n\t\t\t\t// same concurrent-create sharing violation to EPERM. Both mean the\n\t\t\t\t// lock file existed when we tried to create it — contend (poll /\n\t\t\t\t// stale-steal below). For EPERM the existence check re-stats: a\n\t\t\t\t// lock still there is contention, while ENOENT means the holder\n\t\t\t\t// released and deleted it between the failed open and this stat\n\t\t\t\t// (or Windows briefly left a delete-pending name that has now\n\t\t\t\t// gone) — the same vanished-lock window the EEXIST path tolerates,\n\t\t\t\t// so retry too. A stat error other than ENOENT means the EPERM was\n\t\t\t\t// a genuine permission failure and propagates, and the vanish path\n\t\t\t\t// still rethrows the original EPERM at the wait deadline: a\n\t\t\t\t// persistent EPERM with no lock file ever appearing is a genuine\n\t\t\t\t// permission failure, not a held lock.\n\t\t\t\tconst code = (error as NodeJS.ErrnoException).code;\n\t\t\t\tif (code === \"EPERM\") {\n\t\t\t\t\tlet vanished = false;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tstatSync(lockPath);\n\t\t\t\t\t} catch (statError) {\n\t\t\t\t\t\tif ((statError as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n\t\t\t\t\t\tvanished = true;\n\t\t\t\t\t}\n\t\t\t\t\tif (vanished) {\n\t\t\t\t\t\tif (Date.now() >= deadline) throw error;\n\t\t\t\t\t\tsleepSync(LOCK_POLL_INTERVAL_MS);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} else if (code !== \"EEXIST\") {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tlet stale = false;\n\t\t\t\ttry {\n\t\t\t\t\tstale = Date.now() - statSync(lockPath).mtimeMs > staleLockMs;\n\t\t\t\t} catch {\n\t\t\t\t\t// The holder released the lock between the failed open and the\n\t\t\t\t\t// stat — retry the exclusive create immediately.\n\t\t\t\t}\n\t\t\t\tif (stale) {\n\t\t\t\t\t// A crashed writer left the lock behind: steal it. A competing\n\t\t\t\t\t// waiter may steal first — the next exclusive create resolves that.\n\t\t\t\t\ttry {\n\t\t\t\t\t\tunlinkSync(lockPath);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Already stolen — retry.\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (Date.now() >= deadline) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`memory ledger append lock timeout after ${lockTimeoutMs} ms: ${lockPath} is held by another writer`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tsleepSync(LOCK_POLL_INTERVAL_MS);\n\t\t\t}\n\t\t}\n\t};\n\n\t/** Releases the append lock, but only if our own token still owns it. */\n\tconst releaseAppendLock = (token: string): void => {\n\t\ttry {\n\t\t\tif (readFileSync(lockPath, \"utf8\") === token) unlinkSync(lockPath);\n\t\t} catch {\n\t\t\t// The lock is already gone (or unreadable) — nothing to release.\n\t\t}\n\t};\n\n\tconst parseAtomLine = (line: string): MemoryAtomV1 | undefined => {\n\t\ttry {\n\t\t\treturn validateMemoryAtomV1(JSON.parse(line));\n\t\t} catch {\n\t\t\t// Corruption tolerance: a malformed line is skipped and counted by the\n\t\t\t// caller — a damaged ledger must never fail the startup replay.\n\t\t\treturn undefined;\n\t\t}\n\t};\n\n\treturn {\n\t\tload() {\n\t\t\tlet raw: string;\n\t\t\ttry {\n\t\t\t\traw = readFileSync(path, \"utf8\");\n\t\t\t} catch (error) {\n\t\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\t\t\tatoms = [];\n\t\t\t\t\treturn { atoms: Object.freeze([]), corruptedLines: 0, duplicateSkipped: 0, unreadable: false };\n\t\t\t\t}\n\t\t\t\tatoms = [];\n\t\t\t\treturn { atoms: Object.freeze([]), corruptedLines: 0, duplicateSkipped: 0, unreadable: true };\n\t\t\t}\n\t\t\tconst loaded: MemoryAtomV1[] = [];\n\t\t\tconst seen = new Set<string>();\n\t\t\tlet corruptedLines = 0;\n\t\t\tlet duplicateSkipped = 0;\n\t\t\tfor (const line of raw.split(\"\\n\")) {\n\t\t\t\tif (line.trim() === \"\") continue;\n\t\t\t\tconst atom = parseAtomLine(line);\n\t\t\t\tif (!atom) {\n\t\t\t\t\tcorruptedLines += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (seen.has(atom.memoryId)) {\n\t\t\t\t\tduplicateSkipped += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tseen.add(atom.memoryId);\n\t\t\t\tloaded.push(atom);\n\t\t\t}\n\t\t\tatoms = loaded;\n\t\t\treturn {\n\t\t\t\tatoms: Object.freeze([...loaded]),\n\t\t\t\tcorruptedLines,\n\t\t\t\tduplicateSkipped,\n\t\t\t\tunreadable: false,\n\t\t\t};\n\t\t},\n\t\tappend(atom) {\n\t\t\tconst validated = validateMemoryAtomV1(atom);\n\t\t\tconst token = acquireAppendLock();\n\t\t\ttry {\n\t\t\t\t// One committed atom per line; the trailing newline keeps the file\n\t\t\t\t// a well-formed JSONL stream for crash-truncated-tail recovery.\n\t\t\t\t// The line is fsynced before close: once append returns, a hard\n\t\t\t\t// crash cannot lose it.\n\t\t\t\tconst fd = openSync(path, \"a\");\n\t\t\t\ttry {\n\t\t\t\t\twriteAllSync(fd, `${JSON.stringify(validated)}\\n`);\n\t\t\t\t\tfsyncSync(fd);\n\t\t\t\t} finally {\n\t\t\t\t\tcloseSync(fd);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\treleaseAppendLock(token);\n\t\t\t}\n\t\t\tatoms.push(validated);\n\t\t},\n\t\trewrite(nextAtoms) {\n\t\t\tconst validated = nextAtoms.map((atom) => validateMemoryAtomV1(atom));\n\t\t\t// The rewrite shares the append lock: a competing writer either\n\t\t\t// finished (its row is on disk and observable to the caller's loaded\n\t\t\t// view) or waits out the whole rebuild — no append can land inside\n\t\t\t// the rewrite window anymore.\n\t\t\tconst token = acquireAppendLock();\n\t\t\ttry {\n\t\t\t\tensureDir();\n\t\t\t\tconst tmpPath = `${path}.${now()}.rewrite.tmp`;\n\t\t\t\t// fsync the rebuilt file before the atomic rename: once rewrite\n\t\t\t\t// returns, a hard crash cannot lose the new content (and the\n\t\t\t\t// rename itself means the old file survives intact until then).\n\t\t\t\tconst fd = openSync(tmpPath, \"w\");\n\t\t\t\ttry {\n\t\t\t\t\twriteAllSync(fd, validated.map((atom) => `${JSON.stringify(atom)}\\n`).join(\"\"));\n\t\t\t\t\tfsyncSync(fd);\n\t\t\t\t} finally {\n\t\t\t\t\tcloseSync(fd);\n\t\t\t\t}\n\t\t\t\trenameSync(tmpPath, path);\n\t\t\t} finally {\n\t\t\t\treleaseAppendLock(token);\n\t\t\t}\n\t\t\tatoms = [...validated];\n\t\t},\n\t\tatomsSnapshot() {\n\t\t\treturn Object.freeze([...atoms]);\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Memory Foundation — memory-lifecycle-manager: projection rebuild, deterministic
3
+ * decay, archive, and retention evaluation (1C.6b).
4
+ *
5
+ * Deterministic per 记忆系统设计.md §5.2 — no model involvement:
6
+ * - attention decay: `w_next = w_current × 2^(-elapsed / halfLife)` under the
7
+ * controlled clock, half-lives per scope from the (replaceable) policy.
8
+ * - scheduled/recalled/accessed only add a short-term attention boost capped
9
+ * at +0.05 per memory per day with an expiry; they never extend
10
+ * lastUsedAt/purgeAt/physical retention.
11
+ * - `used` contributes +0.10 persistent attention once per memory/operation;
12
+ * `confirmed` contributes +0.25 with verifiable evidence. Everything clamps
13
+ * to [0,1].
14
+ * - effective status flows active → stale → archived → purge_eligible (→
15
+ * purge gate, 1C.6c/d) using policy thresholds and the retention deadline;
16
+ * physical purge never happens here.
17
+ */
18
+ import type { MemoryScopeV1 } from "./foundation.ts";
19
+ export interface MemoryLifecyclePolicyV1 {
20
+ readonly policyVersion: string;
21
+ /** Attention half-life per scope (ms). */
22
+ readonly halfLifeMs: Readonly<Record<MemoryScopeV1, number>>;
23
+ /** Idle duration after which a memory becomes `stale` (ms). */
24
+ readonly staleAfterMs: Readonly<Record<MemoryScopeV1, number>>;
25
+ /** Idle duration after which a stale memory becomes `archived` (ms). */
26
+ readonly archiveAfterMs: Readonly<Record<MemoryScopeV1, number>>;
27
+ /** Baseline attention for a freshly committed atom. */
28
+ readonly baselineAttention: number;
29
+ }
30
+ /** First-party default lifecycle policy (replaceable per Profile). */
31
+ export declare function createFirstPartyLifecyclePolicy(): MemoryLifecyclePolicyV1;
32
+ export type MemoryLifecycleUsageKindV1 = "scheduled" | "recalled" | "accessed";
33
+ export type MemoryLifecycleDecisionKindV1 = "used" | "confirmed";
34
+ export type MemoryLifecycleEventV1 = {
35
+ readonly kind: "commit";
36
+ readonly memoryId: string;
37
+ readonly scope: MemoryScopeV1;
38
+ readonly sequence: number;
39
+ readonly at: number;
40
+ readonly owner: string;
41
+ readonly baselineAttention: number;
42
+ } | {
43
+ readonly kind: MemoryLifecycleUsageKindV1;
44
+ readonly memoryId: string;
45
+ readonly sequence: number;
46
+ readonly at: number;
47
+ readonly idempotencyKey: string;
48
+ } | {
49
+ readonly kind: MemoryLifecycleDecisionKindV1;
50
+ readonly memoryId: string;
51
+ readonly sequence: number;
52
+ readonly at: number;
53
+ readonly idempotencyKey: string;
54
+ readonly operationId: string;
55
+ readonly evidenceRef: string;
56
+ } | {
57
+ readonly kind: "retention_evaluated";
58
+ readonly memoryId: string;
59
+ readonly sequence: number;
60
+ readonly at: number;
61
+ readonly idempotencyKey: string;
62
+ readonly policyVersion: string;
63
+ readonly effectiveStatus: "active" | "stale" | "archived" | "purge_eligible";
64
+ };
65
+ export interface MemoryProjectionStateV1 {
66
+ readonly memoryId: string;
67
+ readonly scope: MemoryScopeV1;
68
+ readonly owner: string;
69
+ readonly committedAt: number;
70
+ readonly attentionWeight: number;
71
+ /** Undecayed base the half-life formula applies to (anchored at lastUsedAt/committedAt). */
72
+ readonly baseAttentionWeight: number;
73
+ readonly shortTermAttentionBoost: number;
74
+ readonly shortTermAttentionExpiresAt?: number;
75
+ readonly lastUsedAt?: number;
76
+ readonly effectiveStatus: "active" | "stale" | "archived";
77
+ readonly projectionRevision: number;
78
+ }
79
+ export interface MemoryLifecycleManagerV1 {
80
+ /** Seeds the projection for a newly committed atom (commit decision event). */
81
+ seedCommit(atom: {
82
+ readonly memoryId: string;
83
+ readonly scope: MemoryScopeV1;
84
+ readonly owner: string;
85
+ readonly recordedAt: number;
86
+ }): MemoryProjectionStateV1;
87
+ /** Deterministic usage facts: boost only, never extends retention. */
88
+ applyUsage(input: {
89
+ readonly memoryId: string;
90
+ readonly kind: MemoryLifecycleUsageKindV1;
91
+ readonly idempotencyKey: string;
92
+ readonly at?: number;
93
+ }): MemoryProjectionStateV1;
94
+ /** Persistent contribution (+0.10 / +0.25) with once-per-key idempotency. */
95
+ applyDecision(input: {
96
+ readonly memoryId: string;
97
+ readonly kind: MemoryLifecycleDecisionKindV1;
98
+ readonly idempotencyKey: string;
99
+ readonly operationId: string;
100
+ readonly evidenceRef: string;
101
+ readonly at?: number;
102
+ }): MemoryProjectionStateV1;
103
+ /** Deterministic decay + idle status + retention evaluation for one memory. */
104
+ evaluate(memoryId: string): MemoryProjectionStateV1;
105
+ projectionOf(memoryId: string): MemoryProjectionStateV1 | undefined;
106
+ events(memoryId: string): readonly MemoryLifecycleEventV1[];
107
+ }
108
+ export declare function createMemoryLifecycleManager(options: {
109
+ readonly policy: MemoryLifecyclePolicyV1;
110
+ readonly now?: () => number;
111
+ }): MemoryLifecycleManagerV1;
112
+ /** 5.2 usage-event roll-up (记忆系统设计 §5.2 / Gate C): deterministic time-bucket aggregation. */
113
+ export interface MemoryUsageRollupV1 {
114
+ readonly rollupKey: string;
115
+ readonly bucketStart: number;
116
+ readonly bucketMs: number;
117
+ readonly count: number;
118
+ readonly kinds: Readonly<Record<string, number>>;
119
+ }
120
+ export declare function rollupUsageEvents(events: readonly {
121
+ readonly at: number;
122
+ readonly kind: string;
123
+ }[], bucketMs: number): readonly MemoryUsageRollupV1[];
124
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../src/memory/lifecycle.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — memory-lifecycle-manager: projection rebuild, deterministic\n * decay, archive, and retention evaluation (1C.6b).\n *\n * Deterministic per 记忆系统设计.md §5.2 — no model involvement:\n * - attention decay: `w_next = w_current × 2^(-elapsed / halfLife)` under the\n * controlled clock, half-lives per scope from the (replaceable) policy.\n * - scheduled/recalled/accessed only add a short-term attention boost capped\n * at +0.05 per memory per day with an expiry; they never extend\n * lastUsedAt/purgeAt/physical retention.\n * - `used` contributes +0.10 persistent attention once per memory/operation;\n * `confirmed` contributes +0.25 with verifiable evidence. Everything clamps\n * to [0,1].\n * - effective status flows active → stale → archived → purge_eligible (→\n * purge gate, 1C.6c/d) using policy thresholds and the retention deadline;\n * physical purge never happens here.\n */\nimport type { MemoryScopeV1 } from \"./foundation.ts\";\n\nexport interface MemoryLifecyclePolicyV1 {\n\treadonly policyVersion: string;\n\t/** Attention half-life per scope (ms). */\n\treadonly halfLifeMs: Readonly<Record<MemoryScopeV1, number>>;\n\t/** Idle duration after which a memory becomes `stale` (ms). */\n\treadonly staleAfterMs: Readonly<Record<MemoryScopeV1, number>>;\n\t/** Idle duration after which a stale memory becomes `archived` (ms). */\n\treadonly archiveAfterMs: Readonly<Record<MemoryScopeV1, number>>;\n\t/** Baseline attention for a freshly committed atom. */\n\treadonly baselineAttention: number;\n}\n\nconst DAY = 24 * 60 * 60 * 1000;\n\n/** First-party default lifecycle policy (replaceable per Profile). */\nexport function createFirstPartyLifecyclePolicy(): MemoryLifecyclePolicyV1 {\n\treturn Object.freeze({\n\t\tpolicyVersion: \"lifecycle@1\",\n\t\thalfLifeMs: Object.freeze({ session: 1 * DAY, cycle: 7 * DAY, \"long-term\": 30 * DAY }),\n\t\tstaleAfterMs: Object.freeze({ session: 2 * DAY, cycle: 14 * DAY, \"long-term\": 60 * DAY }),\n\t\tarchiveAfterMs: Object.freeze({ session: 7 * DAY, cycle: 60 * DAY, \"long-term\": 180 * DAY }),\n\t\tbaselineAttention: 0.5,\n\t});\n}\n\nexport type MemoryLifecycleUsageKindV1 = \"scheduled\" | \"recalled\" | \"accessed\";\nexport type MemoryLifecycleDecisionKindV1 = \"used\" | \"confirmed\";\n\nexport type MemoryLifecycleEventV1 =\n\t| {\n\t\t\treadonly kind: \"commit\";\n\t\t\treadonly memoryId: string;\n\t\t\treadonly scope: MemoryScopeV1;\n\t\t\treadonly sequence: number;\n\t\t\treadonly at: number;\n\t\t\treadonly owner: string;\n\t\t\treadonly baselineAttention: number;\n\t }\n\t| {\n\t\t\treadonly kind: MemoryLifecycleUsageKindV1;\n\t\t\treadonly memoryId: string;\n\t\t\treadonly sequence: number;\n\t\t\treadonly at: number;\n\t\t\treadonly idempotencyKey: string;\n\t }\n\t| {\n\t\t\treadonly kind: MemoryLifecycleDecisionKindV1;\n\t\t\treadonly memoryId: string;\n\t\t\treadonly sequence: number;\n\t\t\treadonly at: number;\n\t\t\treadonly idempotencyKey: string;\n\t\t\treadonly operationId: string;\n\t\t\treadonly evidenceRef: string;\n\t }\n\t| {\n\t\t\treadonly kind: \"retention_evaluated\";\n\t\t\treadonly memoryId: string;\n\t\t\treadonly sequence: number;\n\t\t\treadonly at: number;\n\t\t\treadonly idempotencyKey: string;\n\t\t\treadonly policyVersion: string;\n\t\t\treadonly effectiveStatus: \"active\" | \"stale\" | \"archived\" | \"purge_eligible\";\n\t };\n\nconst SHORT_TERM_BOOST_CAP = 0.05;\nconst SHORT_TERM_BOOST_EXPIRY_MS = 1 * DAY;\nconst USED_PERSISTENT_CONTRIBUTION = 0.1;\nconst CONFIRMED_PERSISTENT_CONTRIBUTION = 0.25;\n\nfunction clamp01(value: number): number {\n\treturn Math.min(1, Math.max(0, value));\n}\n\nexport interface MemoryProjectionStateV1 {\n\treadonly memoryId: string;\n\treadonly scope: MemoryScopeV1;\n\treadonly owner: string;\n\treadonly committedAt: number;\n\treadonly attentionWeight: number;\n\t/** Undecayed base the half-life formula applies to (anchored at lastUsedAt/committedAt). */\n\treadonly baseAttentionWeight: number;\n\treadonly shortTermAttentionBoost: number;\n\treadonly shortTermAttentionExpiresAt?: number;\n\treadonly lastUsedAt?: number;\n\treadonly effectiveStatus: \"active\" | \"stale\" | \"archived\";\n\treadonly projectionRevision: number;\n}\n\nexport interface MemoryLifecycleManagerV1 {\n\t/** Seeds the projection for a newly committed atom (commit decision event). */\n\tseedCommit(atom: {\n\t\treadonly memoryId: string;\n\t\treadonly scope: MemoryScopeV1;\n\t\treadonly owner: string;\n\t\treadonly recordedAt: number;\n\t}): MemoryProjectionStateV1;\n\t/** Deterministic usage facts: boost only, never extends retention. */\n\tapplyUsage(input: {\n\t\treadonly memoryId: string;\n\t\treadonly kind: MemoryLifecycleUsageKindV1;\n\t\treadonly idempotencyKey: string;\n\t\treadonly at?: number;\n\t}): MemoryProjectionStateV1;\n\t/** Persistent contribution (+0.10 / +0.25) with once-per-key idempotency. */\n\tapplyDecision(input: {\n\t\treadonly memoryId: string;\n\t\treadonly kind: MemoryLifecycleDecisionKindV1;\n\t\treadonly idempotencyKey: string;\n\t\treadonly operationId: string;\n\t\treadonly evidenceRef: string;\n\t\treadonly at?: number;\n\t}): MemoryProjectionStateV1;\n\t/** Deterministic decay + idle status + retention evaluation for one memory. */\n\tevaluate(memoryId: string): MemoryProjectionStateV1;\n\tprojectionOf(memoryId: string): MemoryProjectionStateV1 | undefined;\n\tevents(memoryId: string): readonly MemoryLifecycleEventV1[];\n}\n\nexport function createMemoryLifecycleManager(options: {\n\treadonly policy: MemoryLifecyclePolicyV1;\n\treadonly now?: () => number;\n}): MemoryLifecycleManagerV1 {\n\tconst policy = options.policy;\n\tconst now = options.now ?? (() => Date.now());\n\tconst projections = new Map<string, MemoryProjectionStateV1>();\n\tconst events = new Map<string, MemoryLifecycleEventV1[]>();\n\tconst seenIdempotencyKeys = new Set<string>();\n\n\tconst persist = (state: MemoryProjectionStateV1, event?: MemoryLifecycleEventV1): MemoryProjectionStateV1 => {\n\t\tconst next: MemoryProjectionStateV1 = Object.freeze({\n\t\t\t...state,\n\t\t\tprojectionRevision: state.projectionRevision + 1,\n\t\t});\n\t\tprojections.set(next.memoryId, next);\n\t\t// v8 ignore next -- persist 的所有调用点都传入事件\n\t\tif (event) {\n\t\t\tconst log = events.get(next.memoryId) ?? [];\n\t\t\tlog.push(Object.freeze(event));\n\t\t\tevents.set(next.memoryId, log);\n\t\t}\n\t\treturn next;\n\t};\n\n\tconst boostEffective = (state: MemoryProjectionStateV1): number => {\n\t\tif (state.shortTermAttentionExpiresAt !== undefined && state.shortTermAttentionExpiresAt <= now()) {\n\t\t\treturn state.attentionWeight;\n\t\t}\n\t\treturn clamp01(state.attentionWeight + state.shortTermAttentionBoost);\n\t};\n\n\treturn {\n\t\tseedCommit(atom) {\n\t\t\tconst at = now();\n\t\t\tconst state: MemoryProjectionStateV1 = Object.freeze({\n\t\t\t\tmemoryId: atom.memoryId,\n\t\t\t\tscope: atom.scope,\n\t\t\t\towner: atom.owner,\n\t\t\t\tcommittedAt: at,\n\t\t\t\tattentionWeight: clamp01(policy.baselineAttention),\n\t\t\t\tbaseAttentionWeight: clamp01(policy.baselineAttention),\n\t\t\t\tshortTermAttentionBoost: 0,\n\t\t\t\teffectiveStatus: \"active\",\n\t\t\t\tprojectionRevision: 0,\n\t\t\t});\n\t\t\tconst event: MemoryLifecycleEventV1 = Object.freeze({\n\t\t\t\tkind: \"commit\",\n\t\t\t\tmemoryId: atom.memoryId,\n\t\t\t\tscope: atom.scope,\n\t\t\t\tsequence: 0,\n\t\t\t\tat: now(),\n\t\t\t\towner: atom.owner,\n\t\t\t\tbaselineAttention: policy.baselineAttention,\n\t\t\t});\n\t\t\treturn persist(state, event);\n\t\t},\n\t\tapplyUsage({ memoryId, kind, idempotencyKey, at }) {\n\t\t\tconst state = projections.get(memoryId);\n\t\t\tif (!state) throw new Error(`Unknown memory projection: ${memoryId}`);\n\t\t\tconst scopedKey = `${memoryId}\\u0000${idempotencyKey}`;\n\t\t\tif (seenIdempotencyKeys.has(scopedKey)) return state;\n\t\t\tseenIdempotencyKeys.add(scopedKey);\n\t\t\tconst atMs = at ?? now();\n\t\t\tconst boostExpired =\n\t\t\t\tstate.shortTermAttentionExpiresAt !== undefined && state.shortTermAttentionExpiresAt <= atMs;\n\t\t\tconst currentBoost = boostExpired ? 0 : state.shortTermAttentionBoost;\n\t\t\t// Daily cap: total short-term boost never exceeds the policy cap.\n\t\t\tconst nextBoost = Math.min(SHORT_TERM_BOOST_CAP, currentBoost + SHORT_TERM_BOOST_CAP);\n\t\t\tconst event: MemoryLifecycleEventV1 = Object.freeze({\n\t\t\t\tkind,\n\t\t\t\tmemoryId,\n\t\t\t\tsequence: state.projectionRevision + 1,\n\t\t\t\tat: atMs,\n\t\t\t\tidempotencyKey,\n\t\t\t});\n\t\t\tconst next: MemoryProjectionStateV1 = Object.freeze({\n\t\t\t\t...state,\n\t\t\t\tshortTermAttentionBoost: nextBoost,\n\t\t\t\tshortTermAttentionExpiresAt: atMs + SHORT_TERM_BOOST_EXPIRY_MS,\n\t\t\t\tprojectionRevision: state.projectionRevision,\n\t\t\t});\n\t\t\treturn persist(next, event);\n\t\t},\n\t\tapplyDecision({ memoryId, kind, idempotencyKey, operationId, evidenceRef, at }) {\n\t\t\tconst state = projections.get(memoryId);\n\t\t\tif (!state) throw new Error(`Unknown memory projection: ${memoryId}`);\n\t\t\tconst scopedKey = `${memoryId}\\u0000${idempotencyKey}`;\n\t\t\tif (seenIdempotencyKeys.has(scopedKey)) return state;\n\t\t\tseenIdempotencyKeys.add(scopedKey);\n\t\t\tconst atMs = at ?? now();\n\t\t\tconst contribution = kind === \"used\" ? USED_PERSISTENT_CONTRIBUTION : CONFIRMED_PERSISTENT_CONTRIBUTION;\n\t\t\tconst nextAttention = clamp01(boostEffective(state) + contribution);\n\t\t\tconst next: MemoryProjectionStateV1 = Object.freeze({\n\t\t\t\t...state,\n\t\t\t\tattentionWeight: nextAttention,\n\t\t\t\t// A persistent decision resets the decay base to the new value and\n\t\t\t\t// re-anchors decay at this decision (设计 §5.2).\n\t\t\t\tbaseAttentionWeight: nextAttention,\n\t\t\t\tshortTermAttentionBoost: 0,\n\t\t\t\tlastUsedAt: atMs,\n\t\t\t\tprojectionRevision: state.projectionRevision,\n\t\t\t});\n\t\t\tconst event: MemoryLifecycleEventV1 = Object.freeze({\n\t\t\t\tkind,\n\t\t\t\tmemoryId,\n\t\t\t\tsequence: next.projectionRevision,\n\t\t\t\tat: atMs,\n\t\t\t\tidempotencyKey,\n\t\t\t\toperationId,\n\t\t\t\tevidenceRef,\n\t\t\t});\n\t\t\treturn persist(next, event);\n\t\t},\n\t\tevaluate(memoryId) {\n\t\t\tconst state = projections.get(memoryId);\n\t\t\tif (!state) throw new Error(`Unknown memory projection: ${memoryId}`);\n\t\t\tconst halfLife = policy.halfLifeMs[state.scope];\n\t\t\t// Decay anchors to the last persistent contribution (or the commit) and\n\t\t\t// applies to the stored BASE — repeated evaluation at the same clock is\n\t\t\t// idempotent instead of compounding (独立复审 H7).\n\t\t\tconst anchorAt = state.lastUsedAt ?? state.committedAt;\n\t\t\tconst elapsed = Math.max(0, now() - anchorAt);\n\t\t\tconst decayed = clamp01(state.baseAttentionWeight * 2 ** (-elapsed / halfLife));\n\t\t\tconst boostExpired =\n\t\t\t\tstate.shortTermAttentionExpiresAt !== undefined && state.shortTermAttentionExpiresAt <= now();\n\t\t\tconst effectiveAttention = boostExpired ? decayed : clamp01(decayed + state.shortTermAttentionBoost);\n\t\t\tconst next: MemoryProjectionStateV1 = Object.freeze({\n\t\t\t\t...state,\n\t\t\t\tattentionWeight: effectiveAttention,\n\t\t\t});\n\t\t\t// Deterministic idle status evaluation against the policy thresholds.\n\t\t\tconst lastUseReference = state.lastUsedAt ?? state.committedAt;\n\t\t\tconst idle = now() - lastUseReference;\n\t\t\tlet effectiveStatus: MemoryProjectionStateV1[\"effectiveStatus\"] = \"active\";\n\t\t\tif (idle > policy.archiveAfterMs[state.scope]) effectiveStatus = \"archived\";\n\t\t\telse if (idle > policy.staleAfterMs[state.scope]) effectiveStatus = \"stale\";\n\t\t\tconst withStatus: MemoryProjectionStateV1 = Object.freeze({ ...next, effectiveStatus });\n\t\t\tconst event: MemoryLifecycleEventV1 = Object.freeze({\n\t\t\t\tkind: \"retention_evaluated\",\n\t\t\t\tmemoryId,\n\t\t\t\tsequence: state.projectionRevision + 1,\n\t\t\t\tat: now(),\n\t\t\t\tidempotencyKey: `retention:${memoryId}:${now()}`,\n\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\teffectiveStatus,\n\t\t\t});\n\t\t\treturn persist(withStatus, event);\n\t\t},\n\t\tprojectionOf(memoryId) {\n\t\t\treturn projections.get(memoryId);\n\t\t},\n\t\tevents(memoryId) {\n\t\t\treturn Object.freeze([...(events.get(memoryId) ?? [])]);\n\t\t},\n\t};\n}\n\n/** 5.2 usage-event roll-up (记忆系统设计 §5.2 / Gate C): deterministic time-bucket aggregation. */\nexport interface MemoryUsageRollupV1 {\n\treadonly rollupKey: string;\n\treadonly bucketStart: number;\n\treadonly bucketMs: number;\n\treadonly count: number;\n\treadonly kinds: Readonly<Record<string, number>>;\n}\n\nexport function rollupUsageEvents(\n\tevents: readonly { readonly at: number; readonly kind: string }[],\n\tbucketMs: number,\n): readonly MemoryUsageRollupV1[] {\n\tif (!Number.isSafeInteger(bucketMs) || bucketMs < 1) {\n\t\tthrow new Error(\"Roll-up bucketMs must be a positive safe integer\");\n\t}\n\tconst buckets = new Map<number, Map<string, number>>();\n\tfor (const event of events) {\n\t\tconst bucketStart = Math.floor(event.at / bucketMs) * bucketMs;\n\t\tconst kinds = buckets.get(bucketStart) ?? new Map<string, number>();\n\t\tkinds.set(event.kind, (kinds.get(event.kind) ?? 0) + 1);\n\t\tbuckets.set(bucketStart, kinds);\n\t}\n\treturn Object.freeze(\n\t\t[...buckets.entries()]\n\t\t\t.sort(([a], [b]) => a - b)\n\t\t\t.map(([bucketStart, kinds]) => {\n\t\t\t\tconst sortedKinds = [...kinds.entries()].sort(([a], [b]) => a.localeCompare(b));\n\t\t\t\tconst count = sortedKinds.reduce((sum, [, kindCount]) => sum + kindCount, 0);\n\t\t\t\tconst rollupKey = `${bucketStart}|${sortedKinds.map(([kind, kindCount]) => `${kind}:${kindCount}`).join(\",\")}`;\n\t\t\t\treturn {\n\t\t\t\t\trollupKey,\n\t\t\t\t\tbucketStart,\n\t\t\t\t\tbucketMs,\n\t\t\t\t\tcount,\n\t\t\t\t\tkinds: Object.freeze(Object.fromEntries(sortedKinds)),\n\t\t\t\t};\n\t\t\t}),\n\t);\n}\n"]}
@@ -0,0 +1,201 @@
1
+ const DAY = 24 * 60 * 60 * 1000;
2
+ /** First-party default lifecycle policy (replaceable per Profile). */
3
+ export function createFirstPartyLifecyclePolicy() {
4
+ return Object.freeze({
5
+ policyVersion: "lifecycle@1",
6
+ halfLifeMs: Object.freeze({ session: 1 * DAY, cycle: 7 * DAY, "long-term": 30 * DAY }),
7
+ staleAfterMs: Object.freeze({ session: 2 * DAY, cycle: 14 * DAY, "long-term": 60 * DAY }),
8
+ archiveAfterMs: Object.freeze({ session: 7 * DAY, cycle: 60 * DAY, "long-term": 180 * DAY }),
9
+ baselineAttention: 0.5,
10
+ });
11
+ }
12
+ const SHORT_TERM_BOOST_CAP = 0.05;
13
+ const SHORT_TERM_BOOST_EXPIRY_MS = 1 * DAY;
14
+ const USED_PERSISTENT_CONTRIBUTION = 0.1;
15
+ const CONFIRMED_PERSISTENT_CONTRIBUTION = 0.25;
16
+ function clamp01(value) {
17
+ return Math.min(1, Math.max(0, value));
18
+ }
19
+ export function createMemoryLifecycleManager(options) {
20
+ const policy = options.policy;
21
+ const now = options.now ?? (() => Date.now());
22
+ const projections = new Map();
23
+ const events = new Map();
24
+ const seenIdempotencyKeys = new Set();
25
+ const persist = (state, event) => {
26
+ const next = Object.freeze({
27
+ ...state,
28
+ projectionRevision: state.projectionRevision + 1,
29
+ });
30
+ projections.set(next.memoryId, next);
31
+ // v8 ignore next -- persist 的所有调用点都传入事件
32
+ if (event) {
33
+ const log = events.get(next.memoryId) ?? [];
34
+ log.push(Object.freeze(event));
35
+ events.set(next.memoryId, log);
36
+ }
37
+ return next;
38
+ };
39
+ const boostEffective = (state) => {
40
+ if (state.shortTermAttentionExpiresAt !== undefined && state.shortTermAttentionExpiresAt <= now()) {
41
+ return state.attentionWeight;
42
+ }
43
+ return clamp01(state.attentionWeight + state.shortTermAttentionBoost);
44
+ };
45
+ return {
46
+ seedCommit(atom) {
47
+ const at = now();
48
+ const state = Object.freeze({
49
+ memoryId: atom.memoryId,
50
+ scope: atom.scope,
51
+ owner: atom.owner,
52
+ committedAt: at,
53
+ attentionWeight: clamp01(policy.baselineAttention),
54
+ baseAttentionWeight: clamp01(policy.baselineAttention),
55
+ shortTermAttentionBoost: 0,
56
+ effectiveStatus: "active",
57
+ projectionRevision: 0,
58
+ });
59
+ const event = Object.freeze({
60
+ kind: "commit",
61
+ memoryId: atom.memoryId,
62
+ scope: atom.scope,
63
+ sequence: 0,
64
+ at: now(),
65
+ owner: atom.owner,
66
+ baselineAttention: policy.baselineAttention,
67
+ });
68
+ return persist(state, event);
69
+ },
70
+ applyUsage({ memoryId, kind, idempotencyKey, at }) {
71
+ const state = projections.get(memoryId);
72
+ if (!state)
73
+ throw new Error(`Unknown memory projection: ${memoryId}`);
74
+ const scopedKey = `${memoryId}\u0000${idempotencyKey}`;
75
+ if (seenIdempotencyKeys.has(scopedKey))
76
+ return state;
77
+ seenIdempotencyKeys.add(scopedKey);
78
+ const atMs = at ?? now();
79
+ const boostExpired = state.shortTermAttentionExpiresAt !== undefined && state.shortTermAttentionExpiresAt <= atMs;
80
+ const currentBoost = boostExpired ? 0 : state.shortTermAttentionBoost;
81
+ // Daily cap: total short-term boost never exceeds the policy cap.
82
+ const nextBoost = Math.min(SHORT_TERM_BOOST_CAP, currentBoost + SHORT_TERM_BOOST_CAP);
83
+ const event = Object.freeze({
84
+ kind,
85
+ memoryId,
86
+ sequence: state.projectionRevision + 1,
87
+ at: atMs,
88
+ idempotencyKey,
89
+ });
90
+ const next = Object.freeze({
91
+ ...state,
92
+ shortTermAttentionBoost: nextBoost,
93
+ shortTermAttentionExpiresAt: atMs + SHORT_TERM_BOOST_EXPIRY_MS,
94
+ projectionRevision: state.projectionRevision,
95
+ });
96
+ return persist(next, event);
97
+ },
98
+ applyDecision({ memoryId, kind, idempotencyKey, operationId, evidenceRef, at }) {
99
+ const state = projections.get(memoryId);
100
+ if (!state)
101
+ throw new Error(`Unknown memory projection: ${memoryId}`);
102
+ const scopedKey = `${memoryId}\u0000${idempotencyKey}`;
103
+ if (seenIdempotencyKeys.has(scopedKey))
104
+ return state;
105
+ seenIdempotencyKeys.add(scopedKey);
106
+ const atMs = at ?? now();
107
+ const contribution = kind === "used" ? USED_PERSISTENT_CONTRIBUTION : CONFIRMED_PERSISTENT_CONTRIBUTION;
108
+ const nextAttention = clamp01(boostEffective(state) + contribution);
109
+ const next = Object.freeze({
110
+ ...state,
111
+ attentionWeight: nextAttention,
112
+ // A persistent decision resets the decay base to the new value and
113
+ // re-anchors decay at this decision (设计 §5.2).
114
+ baseAttentionWeight: nextAttention,
115
+ shortTermAttentionBoost: 0,
116
+ lastUsedAt: atMs,
117
+ projectionRevision: state.projectionRevision,
118
+ });
119
+ const event = Object.freeze({
120
+ kind,
121
+ memoryId,
122
+ sequence: next.projectionRevision,
123
+ at: atMs,
124
+ idempotencyKey,
125
+ operationId,
126
+ evidenceRef,
127
+ });
128
+ return persist(next, event);
129
+ },
130
+ evaluate(memoryId) {
131
+ const state = projections.get(memoryId);
132
+ if (!state)
133
+ throw new Error(`Unknown memory projection: ${memoryId}`);
134
+ const halfLife = policy.halfLifeMs[state.scope];
135
+ // Decay anchors to the last persistent contribution (or the commit) and
136
+ // applies to the stored BASE — repeated evaluation at the same clock is
137
+ // idempotent instead of compounding (独立复审 H7).
138
+ const anchorAt = state.lastUsedAt ?? state.committedAt;
139
+ const elapsed = Math.max(0, now() - anchorAt);
140
+ const decayed = clamp01(state.baseAttentionWeight * 2 ** (-elapsed / halfLife));
141
+ const boostExpired = state.shortTermAttentionExpiresAt !== undefined && state.shortTermAttentionExpiresAt <= now();
142
+ const effectiveAttention = boostExpired ? decayed : clamp01(decayed + state.shortTermAttentionBoost);
143
+ const next = Object.freeze({
144
+ ...state,
145
+ attentionWeight: effectiveAttention,
146
+ });
147
+ // Deterministic idle status evaluation against the policy thresholds.
148
+ const lastUseReference = state.lastUsedAt ?? state.committedAt;
149
+ const idle = now() - lastUseReference;
150
+ let effectiveStatus = "active";
151
+ if (idle > policy.archiveAfterMs[state.scope])
152
+ effectiveStatus = "archived";
153
+ else if (idle > policy.staleAfterMs[state.scope])
154
+ effectiveStatus = "stale";
155
+ const withStatus = Object.freeze({ ...next, effectiveStatus });
156
+ const event = Object.freeze({
157
+ kind: "retention_evaluated",
158
+ memoryId,
159
+ sequence: state.projectionRevision + 1,
160
+ at: now(),
161
+ idempotencyKey: `retention:${memoryId}:${now()}`,
162
+ policyVersion: policy.policyVersion,
163
+ effectiveStatus,
164
+ });
165
+ return persist(withStatus, event);
166
+ },
167
+ projectionOf(memoryId) {
168
+ return projections.get(memoryId);
169
+ },
170
+ events(memoryId) {
171
+ return Object.freeze([...(events.get(memoryId) ?? [])]);
172
+ },
173
+ };
174
+ }
175
+ export function rollupUsageEvents(events, bucketMs) {
176
+ if (!Number.isSafeInteger(bucketMs) || bucketMs < 1) {
177
+ throw new Error("Roll-up bucketMs must be a positive safe integer");
178
+ }
179
+ const buckets = new Map();
180
+ for (const event of events) {
181
+ const bucketStart = Math.floor(event.at / bucketMs) * bucketMs;
182
+ const kinds = buckets.get(bucketStart) ?? new Map();
183
+ kinds.set(event.kind, (kinds.get(event.kind) ?? 0) + 1);
184
+ buckets.set(bucketStart, kinds);
185
+ }
186
+ return Object.freeze([...buckets.entries()]
187
+ .sort(([a], [b]) => a - b)
188
+ .map(([bucketStart, kinds]) => {
189
+ const sortedKinds = [...kinds.entries()].sort(([a], [b]) => a.localeCompare(b));
190
+ const count = sortedKinds.reduce((sum, [, kindCount]) => sum + kindCount, 0);
191
+ const rollupKey = `${bucketStart}|${sortedKinds.map(([kind, kindCount]) => `${kind}:${kindCount}`).join(",")}`;
192
+ return {
193
+ rollupKey,
194
+ bucketStart,
195
+ bucketMs,
196
+ count,
197
+ kinds: Object.freeze(Object.fromEntries(sortedKinds)),
198
+ };
199
+ }));
200
+ }
201
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/memory/lifecycle.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — memory-lifecycle-manager: projection rebuild, deterministic\n * decay, archive, and retention evaluation (1C.6b).\n *\n * Deterministic per 记忆系统设计.md §5.2 — no model involvement:\n * - attention decay: `w_next = w_current × 2^(-elapsed / halfLife)` under the\n * controlled clock, half-lives per scope from the (replaceable) policy.\n * - scheduled/recalled/accessed only add a short-term attention boost capped\n * at +0.05 per memory per day with an expiry; they never extend\n * lastUsedAt/purgeAt/physical retention.\n * - `used` contributes +0.10 persistent attention once per memory/operation;\n * `confirmed` contributes +0.25 with verifiable evidence. Everything clamps\n * to [0,1].\n * - effective status flows active → stale → archived → purge_eligible (→\n * purge gate, 1C.6c/d) using policy thresholds and the retention deadline;\n * physical purge never happens here.\n */\nimport type { MemoryScopeV1 } from \"./foundation.ts\";\n\nexport interface MemoryLifecyclePolicyV1 {\n\treadonly policyVersion: string;\n\t/** Attention half-life per scope (ms). */\n\treadonly halfLifeMs: Readonly<Record<MemoryScopeV1, number>>;\n\t/** Idle duration after which a memory becomes `stale` (ms). */\n\treadonly staleAfterMs: Readonly<Record<MemoryScopeV1, number>>;\n\t/** Idle duration after which a stale memory becomes `archived` (ms). */\n\treadonly archiveAfterMs: Readonly<Record<MemoryScopeV1, number>>;\n\t/** Baseline attention for a freshly committed atom. */\n\treadonly baselineAttention: number;\n}\n\nconst DAY = 24 * 60 * 60 * 1000;\n\n/** First-party default lifecycle policy (replaceable per Profile). */\nexport function createFirstPartyLifecyclePolicy(): MemoryLifecyclePolicyV1 {\n\treturn Object.freeze({\n\t\tpolicyVersion: \"lifecycle@1\",\n\t\thalfLifeMs: Object.freeze({ session: 1 * DAY, cycle: 7 * DAY, \"long-term\": 30 * DAY }),\n\t\tstaleAfterMs: Object.freeze({ session: 2 * DAY, cycle: 14 * DAY, \"long-term\": 60 * DAY }),\n\t\tarchiveAfterMs: Object.freeze({ session: 7 * DAY, cycle: 60 * DAY, \"long-term\": 180 * DAY }),\n\t\tbaselineAttention: 0.5,\n\t});\n}\n\nexport type MemoryLifecycleUsageKindV1 = \"scheduled\" | \"recalled\" | \"accessed\";\nexport type MemoryLifecycleDecisionKindV1 = \"used\" | \"confirmed\";\n\nexport type MemoryLifecycleEventV1 =\n\t| {\n\t\t\treadonly kind: \"commit\";\n\t\t\treadonly memoryId: string;\n\t\t\treadonly scope: MemoryScopeV1;\n\t\t\treadonly sequence: number;\n\t\t\treadonly at: number;\n\t\t\treadonly owner: string;\n\t\t\treadonly baselineAttention: number;\n\t }\n\t| {\n\t\t\treadonly kind: MemoryLifecycleUsageKindV1;\n\t\t\treadonly memoryId: string;\n\t\t\treadonly sequence: number;\n\t\t\treadonly at: number;\n\t\t\treadonly idempotencyKey: string;\n\t }\n\t| {\n\t\t\treadonly kind: MemoryLifecycleDecisionKindV1;\n\t\t\treadonly memoryId: string;\n\t\t\treadonly sequence: number;\n\t\t\treadonly at: number;\n\t\t\treadonly idempotencyKey: string;\n\t\t\treadonly operationId: string;\n\t\t\treadonly evidenceRef: string;\n\t }\n\t| {\n\t\t\treadonly kind: \"retention_evaluated\";\n\t\t\treadonly memoryId: string;\n\t\t\treadonly sequence: number;\n\t\t\treadonly at: number;\n\t\t\treadonly idempotencyKey: string;\n\t\t\treadonly policyVersion: string;\n\t\t\treadonly effectiveStatus: \"active\" | \"stale\" | \"archived\" | \"purge_eligible\";\n\t };\n\nconst SHORT_TERM_BOOST_CAP = 0.05;\nconst SHORT_TERM_BOOST_EXPIRY_MS = 1 * DAY;\nconst USED_PERSISTENT_CONTRIBUTION = 0.1;\nconst CONFIRMED_PERSISTENT_CONTRIBUTION = 0.25;\n\nfunction clamp01(value: number): number {\n\treturn Math.min(1, Math.max(0, value));\n}\n\nexport interface MemoryProjectionStateV1 {\n\treadonly memoryId: string;\n\treadonly scope: MemoryScopeV1;\n\treadonly owner: string;\n\treadonly committedAt: number;\n\treadonly attentionWeight: number;\n\t/** Undecayed base the half-life formula applies to (anchored at lastUsedAt/committedAt). */\n\treadonly baseAttentionWeight: number;\n\treadonly shortTermAttentionBoost: number;\n\treadonly shortTermAttentionExpiresAt?: number;\n\treadonly lastUsedAt?: number;\n\treadonly effectiveStatus: \"active\" | \"stale\" | \"archived\";\n\treadonly projectionRevision: number;\n}\n\nexport interface MemoryLifecycleManagerV1 {\n\t/** Seeds the projection for a newly committed atom (commit decision event). */\n\tseedCommit(atom: {\n\t\treadonly memoryId: string;\n\t\treadonly scope: MemoryScopeV1;\n\t\treadonly owner: string;\n\t\treadonly recordedAt: number;\n\t}): MemoryProjectionStateV1;\n\t/** Deterministic usage facts: boost only, never extends retention. */\n\tapplyUsage(input: {\n\t\treadonly memoryId: string;\n\t\treadonly kind: MemoryLifecycleUsageKindV1;\n\t\treadonly idempotencyKey: string;\n\t\treadonly at?: number;\n\t}): MemoryProjectionStateV1;\n\t/** Persistent contribution (+0.10 / +0.25) with once-per-key idempotency. */\n\tapplyDecision(input: {\n\t\treadonly memoryId: string;\n\t\treadonly kind: MemoryLifecycleDecisionKindV1;\n\t\treadonly idempotencyKey: string;\n\t\treadonly operationId: string;\n\t\treadonly evidenceRef: string;\n\t\treadonly at?: number;\n\t}): MemoryProjectionStateV1;\n\t/** Deterministic decay + idle status + retention evaluation for one memory. */\n\tevaluate(memoryId: string): MemoryProjectionStateV1;\n\tprojectionOf(memoryId: string): MemoryProjectionStateV1 | undefined;\n\tevents(memoryId: string): readonly MemoryLifecycleEventV1[];\n}\n\nexport function createMemoryLifecycleManager(options: {\n\treadonly policy: MemoryLifecyclePolicyV1;\n\treadonly now?: () => number;\n}): MemoryLifecycleManagerV1 {\n\tconst policy = options.policy;\n\tconst now = options.now ?? (() => Date.now());\n\tconst projections = new Map<string, MemoryProjectionStateV1>();\n\tconst events = new Map<string, MemoryLifecycleEventV1[]>();\n\tconst seenIdempotencyKeys = new Set<string>();\n\n\tconst persist = (state: MemoryProjectionStateV1, event?: MemoryLifecycleEventV1): MemoryProjectionStateV1 => {\n\t\tconst next: MemoryProjectionStateV1 = Object.freeze({\n\t\t\t...state,\n\t\t\tprojectionRevision: state.projectionRevision + 1,\n\t\t});\n\t\tprojections.set(next.memoryId, next);\n\t\t// v8 ignore next -- persist 的所有调用点都传入事件\n\t\tif (event) {\n\t\t\tconst log = events.get(next.memoryId) ?? [];\n\t\t\tlog.push(Object.freeze(event));\n\t\t\tevents.set(next.memoryId, log);\n\t\t}\n\t\treturn next;\n\t};\n\n\tconst boostEffective = (state: MemoryProjectionStateV1): number => {\n\t\tif (state.shortTermAttentionExpiresAt !== undefined && state.shortTermAttentionExpiresAt <= now()) {\n\t\t\treturn state.attentionWeight;\n\t\t}\n\t\treturn clamp01(state.attentionWeight + state.shortTermAttentionBoost);\n\t};\n\n\treturn {\n\t\tseedCommit(atom) {\n\t\t\tconst at = now();\n\t\t\tconst state: MemoryProjectionStateV1 = Object.freeze({\n\t\t\t\tmemoryId: atom.memoryId,\n\t\t\t\tscope: atom.scope,\n\t\t\t\towner: atom.owner,\n\t\t\t\tcommittedAt: at,\n\t\t\t\tattentionWeight: clamp01(policy.baselineAttention),\n\t\t\t\tbaseAttentionWeight: clamp01(policy.baselineAttention),\n\t\t\t\tshortTermAttentionBoost: 0,\n\t\t\t\teffectiveStatus: \"active\",\n\t\t\t\tprojectionRevision: 0,\n\t\t\t});\n\t\t\tconst event: MemoryLifecycleEventV1 = Object.freeze({\n\t\t\t\tkind: \"commit\",\n\t\t\t\tmemoryId: atom.memoryId,\n\t\t\t\tscope: atom.scope,\n\t\t\t\tsequence: 0,\n\t\t\t\tat: now(),\n\t\t\t\towner: atom.owner,\n\t\t\t\tbaselineAttention: policy.baselineAttention,\n\t\t\t});\n\t\t\treturn persist(state, event);\n\t\t},\n\t\tapplyUsage({ memoryId, kind, idempotencyKey, at }) {\n\t\t\tconst state = projections.get(memoryId);\n\t\t\tif (!state) throw new Error(`Unknown memory projection: ${memoryId}`);\n\t\t\tconst scopedKey = `${memoryId}\\u0000${idempotencyKey}`;\n\t\t\tif (seenIdempotencyKeys.has(scopedKey)) return state;\n\t\t\tseenIdempotencyKeys.add(scopedKey);\n\t\t\tconst atMs = at ?? now();\n\t\t\tconst boostExpired =\n\t\t\t\tstate.shortTermAttentionExpiresAt !== undefined && state.shortTermAttentionExpiresAt <= atMs;\n\t\t\tconst currentBoost = boostExpired ? 0 : state.shortTermAttentionBoost;\n\t\t\t// Daily cap: total short-term boost never exceeds the policy cap.\n\t\t\tconst nextBoost = Math.min(SHORT_TERM_BOOST_CAP, currentBoost + SHORT_TERM_BOOST_CAP);\n\t\t\tconst event: MemoryLifecycleEventV1 = Object.freeze({\n\t\t\t\tkind,\n\t\t\t\tmemoryId,\n\t\t\t\tsequence: state.projectionRevision + 1,\n\t\t\t\tat: atMs,\n\t\t\t\tidempotencyKey,\n\t\t\t});\n\t\t\tconst next: MemoryProjectionStateV1 = Object.freeze({\n\t\t\t\t...state,\n\t\t\t\tshortTermAttentionBoost: nextBoost,\n\t\t\t\tshortTermAttentionExpiresAt: atMs + SHORT_TERM_BOOST_EXPIRY_MS,\n\t\t\t\tprojectionRevision: state.projectionRevision,\n\t\t\t});\n\t\t\treturn persist(next, event);\n\t\t},\n\t\tapplyDecision({ memoryId, kind, idempotencyKey, operationId, evidenceRef, at }) {\n\t\t\tconst state = projections.get(memoryId);\n\t\t\tif (!state) throw new Error(`Unknown memory projection: ${memoryId}`);\n\t\t\tconst scopedKey = `${memoryId}\\u0000${idempotencyKey}`;\n\t\t\tif (seenIdempotencyKeys.has(scopedKey)) return state;\n\t\t\tseenIdempotencyKeys.add(scopedKey);\n\t\t\tconst atMs = at ?? now();\n\t\t\tconst contribution = kind === \"used\" ? USED_PERSISTENT_CONTRIBUTION : CONFIRMED_PERSISTENT_CONTRIBUTION;\n\t\t\tconst nextAttention = clamp01(boostEffective(state) + contribution);\n\t\t\tconst next: MemoryProjectionStateV1 = Object.freeze({\n\t\t\t\t...state,\n\t\t\t\tattentionWeight: nextAttention,\n\t\t\t\t// A persistent decision resets the decay base to the new value and\n\t\t\t\t// re-anchors decay at this decision (设计 §5.2).\n\t\t\t\tbaseAttentionWeight: nextAttention,\n\t\t\t\tshortTermAttentionBoost: 0,\n\t\t\t\tlastUsedAt: atMs,\n\t\t\t\tprojectionRevision: state.projectionRevision,\n\t\t\t});\n\t\t\tconst event: MemoryLifecycleEventV1 = Object.freeze({\n\t\t\t\tkind,\n\t\t\t\tmemoryId,\n\t\t\t\tsequence: next.projectionRevision,\n\t\t\t\tat: atMs,\n\t\t\t\tidempotencyKey,\n\t\t\t\toperationId,\n\t\t\t\tevidenceRef,\n\t\t\t});\n\t\t\treturn persist(next, event);\n\t\t},\n\t\tevaluate(memoryId) {\n\t\t\tconst state = projections.get(memoryId);\n\t\t\tif (!state) throw new Error(`Unknown memory projection: ${memoryId}`);\n\t\t\tconst halfLife = policy.halfLifeMs[state.scope];\n\t\t\t// Decay anchors to the last persistent contribution (or the commit) and\n\t\t\t// applies to the stored BASE — repeated evaluation at the same clock is\n\t\t\t// idempotent instead of compounding (独立复审 H7).\n\t\t\tconst anchorAt = state.lastUsedAt ?? state.committedAt;\n\t\t\tconst elapsed = Math.max(0, now() - anchorAt);\n\t\t\tconst decayed = clamp01(state.baseAttentionWeight * 2 ** (-elapsed / halfLife));\n\t\t\tconst boostExpired =\n\t\t\t\tstate.shortTermAttentionExpiresAt !== undefined && state.shortTermAttentionExpiresAt <= now();\n\t\t\tconst effectiveAttention = boostExpired ? decayed : clamp01(decayed + state.shortTermAttentionBoost);\n\t\t\tconst next: MemoryProjectionStateV1 = Object.freeze({\n\t\t\t\t...state,\n\t\t\t\tattentionWeight: effectiveAttention,\n\t\t\t});\n\t\t\t// Deterministic idle status evaluation against the policy thresholds.\n\t\t\tconst lastUseReference = state.lastUsedAt ?? state.committedAt;\n\t\t\tconst idle = now() - lastUseReference;\n\t\t\tlet effectiveStatus: MemoryProjectionStateV1[\"effectiveStatus\"] = \"active\";\n\t\t\tif (idle > policy.archiveAfterMs[state.scope]) effectiveStatus = \"archived\";\n\t\t\telse if (idle > policy.staleAfterMs[state.scope]) effectiveStatus = \"stale\";\n\t\t\tconst withStatus: MemoryProjectionStateV1 = Object.freeze({ ...next, effectiveStatus });\n\t\t\tconst event: MemoryLifecycleEventV1 = Object.freeze({\n\t\t\t\tkind: \"retention_evaluated\",\n\t\t\t\tmemoryId,\n\t\t\t\tsequence: state.projectionRevision + 1,\n\t\t\t\tat: now(),\n\t\t\t\tidempotencyKey: `retention:${memoryId}:${now()}`,\n\t\t\t\tpolicyVersion: policy.policyVersion,\n\t\t\t\teffectiveStatus,\n\t\t\t});\n\t\t\treturn persist(withStatus, event);\n\t\t},\n\t\tprojectionOf(memoryId) {\n\t\t\treturn projections.get(memoryId);\n\t\t},\n\t\tevents(memoryId) {\n\t\t\treturn Object.freeze([...(events.get(memoryId) ?? [])]);\n\t\t},\n\t};\n}\n\n/** 5.2 usage-event roll-up (记忆系统设计 §5.2 / Gate C): deterministic time-bucket aggregation. */\nexport interface MemoryUsageRollupV1 {\n\treadonly rollupKey: string;\n\treadonly bucketStart: number;\n\treadonly bucketMs: number;\n\treadonly count: number;\n\treadonly kinds: Readonly<Record<string, number>>;\n}\n\nexport function rollupUsageEvents(\n\tevents: readonly { readonly at: number; readonly kind: string }[],\n\tbucketMs: number,\n): readonly MemoryUsageRollupV1[] {\n\tif (!Number.isSafeInteger(bucketMs) || bucketMs < 1) {\n\t\tthrow new Error(\"Roll-up bucketMs must be a positive safe integer\");\n\t}\n\tconst buckets = new Map<number, Map<string, number>>();\n\tfor (const event of events) {\n\t\tconst bucketStart = Math.floor(event.at / bucketMs) * bucketMs;\n\t\tconst kinds = buckets.get(bucketStart) ?? new Map<string, number>();\n\t\tkinds.set(event.kind, (kinds.get(event.kind) ?? 0) + 1);\n\t\tbuckets.set(bucketStart, kinds);\n\t}\n\treturn Object.freeze(\n\t\t[...buckets.entries()]\n\t\t\t.sort(([a], [b]) => a - b)\n\t\t\t.map(([bucketStart, kinds]) => {\n\t\t\t\tconst sortedKinds = [...kinds.entries()].sort(([a], [b]) => a.localeCompare(b));\n\t\t\t\tconst count = sortedKinds.reduce((sum, [, kindCount]) => sum + kindCount, 0);\n\t\t\t\tconst rollupKey = `${bucketStart}|${sortedKinds.map(([kind, kindCount]) => `${kind}:${kindCount}`).join(\",\")}`;\n\t\t\t\treturn {\n\t\t\t\t\trollupKey,\n\t\t\t\t\tbucketStart,\n\t\t\t\t\tbucketMs,\n\t\t\t\t\tcount,\n\t\t\t\t\tkinds: Object.freeze(Object.fromEntries(sortedKinds)),\n\t\t\t\t};\n\t\t\t}),\n\t);\n}\n"]}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Memory Network adapters (2.4d4) — relation diffusion and pluggable network
3
+ * expansion over the 1C atom relations, each adapter independently
4
+ * disableable.
5
+ *
6
+ * The network is a thin, deterministic coordinator: adapters are pure
7
+ * functions from seed atoms to related memory references, and a disabled
8
+ * adapter yields an explicit `disabled` result instead of being silently
9
+ * skipped or silently degrading the output. Relation diffusion walks the
10
+ * frozen `MemoryRelationV1` links breadth-first, at most `maxHops`, with
11
+ * weight decaying as 1/hop. The same input always yields the same expansion.
12
+ */
13
+ import type { MemoryAtomV1 } from "./foundation.ts";
14
+ export interface MemoryNetworkExpansionV1 {
15
+ readonly memoryId: string;
16
+ readonly relationKind: string;
17
+ readonly hop: number;
18
+ /** Deterministic decay: 1/hop from the seed. */
19
+ readonly weight: number;
20
+ /** The adapter that produced this reference. */
21
+ readonly via: string;
22
+ }
23
+ export interface MemoryNetworkAdapterResultV1 {
24
+ readonly adapterId: string;
25
+ readonly status: "completed" | "disabled";
26
+ readonly related: readonly MemoryNetworkExpansionV1[];
27
+ }
28
+ export interface MemoryNetworkAdapterV1 {
29
+ readonly id: string;
30
+ readonly enabled: boolean;
31
+ expand(input: {
32
+ readonly seeds: readonly MemoryAtomV1[];
33
+ /** Full node set available for traversal; defaults to the seeds (one-hop only). */
34
+ readonly graph?: readonly MemoryAtomV1[];
35
+ }): readonly MemoryNetworkExpansionV1[];
36
+ }
37
+ export interface MemoryNetworkV1 {
38
+ expand(input: {
39
+ readonly seeds: readonly MemoryAtomV1[];
40
+ readonly graph?: readonly MemoryAtomV1[];
41
+ }): readonly MemoryNetworkAdapterResultV1[];
42
+ }
43
+ export declare function createMemoryNetwork(options: {
44
+ readonly adapters: readonly MemoryNetworkAdapterV1[];
45
+ }): MemoryNetworkV1;
46
+ /**
47
+ * Relation-diffusion adapter: breadth-first walk over the atoms' frozen
48
+ * relation links, at most `maxHops`, deterministically ordered by
49
+ * (hop, sourceMemoryId, relationId, targetMemoryId).
50
+ */
51
+ export declare function createRelationDiffusionAdapter(options: {
52
+ readonly maxHops: number;
53
+ readonly enabled?: boolean;
54
+ }): MemoryNetworkAdapterV1;
55
+ //# sourceMappingURL=memory-network.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-network.d.ts","sourceRoot":"","sources":["../../src/memory/memory-network.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,4BAA4B;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,SAAS,wBAAwB,EAAE,CAAC;CACtD;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,MAAM,CAAC,KAAK,EAAE;QACb,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;QACxC,mFAAmF;QACnF,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;KACzC,GAAG,SAAS,wBAAwB,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC/B,MAAM,CAAC,KAAK,EAAE;QACb,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;QACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;KACzC,GAAG,SAAS,4BAA4B,EAAE,CAAC;CAC5C;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC5C,QAAQ,CAAC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,CAAC;CACrD,GAAG,eAAe,CAelB;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC3B,GAAG,sBAAsB,CAkDzB","sourcesContent":["/**\n * Memory Network adapters (2.4d4) — relation diffusion and pluggable network\n * expansion over the 1C atom relations, each adapter independently\n * disableable.\n *\n * The network is a thin, deterministic coordinator: adapters are pure\n * functions from seed atoms to related memory references, and a disabled\n * adapter yields an explicit `disabled` result instead of being silently\n * skipped or silently degrading the output. Relation diffusion walks the\n * frozen `MemoryRelationV1` links breadth-first, at most `maxHops`, with\n * weight decaying as 1/hop. The same input always yields the same expansion.\n */\nimport type { MemoryAtomV1 } from \"./foundation.ts\";\n\nexport interface MemoryNetworkExpansionV1 {\n\treadonly memoryId: string;\n\treadonly relationKind: string;\n\treadonly hop: number;\n\t/** Deterministic decay: 1/hop from the seed. */\n\treadonly weight: number;\n\t/** The adapter that produced this reference. */\n\treadonly via: string;\n}\n\nexport interface MemoryNetworkAdapterResultV1 {\n\treadonly adapterId: string;\n\treadonly status: \"completed\" | \"disabled\";\n\treadonly related: readonly MemoryNetworkExpansionV1[];\n}\n\nexport interface MemoryNetworkAdapterV1 {\n\treadonly id: string;\n\treadonly enabled: boolean;\n\texpand(input: {\n\t\treadonly seeds: readonly MemoryAtomV1[];\n\t\t/** Full node set available for traversal; defaults to the seeds (one-hop only). */\n\t\treadonly graph?: readonly MemoryAtomV1[];\n\t}): readonly MemoryNetworkExpansionV1[];\n}\n\nexport interface MemoryNetworkV1 {\n\texpand(input: {\n\t\treadonly seeds: readonly MemoryAtomV1[];\n\t\treadonly graph?: readonly MemoryAtomV1[];\n\t}): readonly MemoryNetworkAdapterResultV1[];\n}\n\nexport function createMemoryNetwork(options: {\n\treadonly adapters: readonly MemoryNetworkAdapterV1[];\n}): MemoryNetworkV1 {\n\treturn {\n\t\texpand(input) {\n\t\t\treturn options.adapters.map((adapter) => {\n\t\t\t\tif (!adapter.enabled) {\n\t\t\t\t\treturn { adapterId: adapter.id, status: \"disabled\" as const, related: Object.freeze([]) };\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tadapterId: adapter.id,\n\t\t\t\t\tstatus: \"completed\" as const,\n\t\t\t\t\trelated: Object.freeze(adapter.expand({ seeds: input.seeds, graph: input.graph })),\n\t\t\t\t};\n\t\t\t});\n\t\t},\n\t};\n}\n\n/**\n * Relation-diffusion adapter: breadth-first walk over the atoms' frozen\n * relation links, at most `maxHops`, deterministically ordered by\n * (hop, sourceMemoryId, relationId, targetMemoryId).\n */\nexport function createRelationDiffusionAdapter(options: {\n\treadonly maxHops: number;\n\treadonly enabled?: boolean;\n}): MemoryNetworkAdapterV1 {\n\tconst maxHops = options.maxHops;\n\tif (maxHops < 1) throw new Error(\"Relation diffusion maxHops must be at least 1\");\n\treturn {\n\t\tid: \"relation-diffusion\",\n\t\tenabled: options.enabled ?? true,\n\t\texpand({ seeds, graph }) {\n\t\t\tconst atomById = new Map<string, MemoryAtomV1>();\n\t\t\tfor (const atom of graph ?? seeds) atomById.set(atom.memoryId, atom);\n\n\t\t\tconst seen = new Set<string>(seeds.map((atom) => atom.memoryId));\n\t\t\tlet frontier = seeds.map((atom) => atom.memoryId);\n\t\t\tconst expansions: MemoryNetworkExpansionV1[] = [];\n\n\t\t\tfor (let hop = 1; hop <= maxHops; hop += 1) {\n\t\t\t\tconst nextFrontier: string[] = [];\n\t\t\t\tconst hopEdges = frontier.flatMap((sourceId) =>\n\t\t\t\t\t(atomById.get(sourceId)?.relations ?? [])\n\t\t\t\t\t\t.filter((relation) => relation.targetMemoryId !== undefined)\n\t\t\t\t\t\t.map((relation) => ({\n\t\t\t\t\t\t\tsourceId,\n\t\t\t\t\t\t\trelationId: relation.relationId,\n\t\t\t\t\t\t\tkind: relation.kind,\n\t\t\t\t\t\t\ttarget: relation.targetMemoryId!,\n\t\t\t\t\t\t})),\n\t\t\t\t);\n\t\t\t\thopEdges.sort(\n\t\t\t\t\t(a, b) =>\n\t\t\t\t\t\ta.sourceId.localeCompare(b.sourceId) ||\n\t\t\t\t\t\ta.relationId.localeCompare(b.relationId) ||\n\t\t\t\t\t\ta.target.localeCompare(b.target),\n\t\t\t\t);\n\t\t\t\tfor (const edge of hopEdges) {\n\t\t\t\t\tif (seen.has(edge.target)) continue;\n\t\t\t\t\tseen.add(edge.target);\n\t\t\t\t\tnextFrontier.push(edge.target);\n\t\t\t\t\texpansions.push({\n\t\t\t\t\t\tmemoryId: edge.target,\n\t\t\t\t\t\trelationKind: edge.kind,\n\t\t\t\t\t\thop,\n\t\t\t\t\t\tweight: 1 / hop,\n\t\t\t\t\t\tvia: \"relation-diffusion\",\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (nextFrontier.length === 0) break;\n\t\t\t\tfrontier = nextFrontier;\n\t\t\t}\n\t\t\treturn Object.freeze(expansions);\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,70 @@
1
+ export function createMemoryNetwork(options) {
2
+ return {
3
+ expand(input) {
4
+ return options.adapters.map((adapter) => {
5
+ if (!adapter.enabled) {
6
+ return { adapterId: adapter.id, status: "disabled", related: Object.freeze([]) };
7
+ }
8
+ return {
9
+ adapterId: adapter.id,
10
+ status: "completed",
11
+ related: Object.freeze(adapter.expand({ seeds: input.seeds, graph: input.graph })),
12
+ };
13
+ });
14
+ },
15
+ };
16
+ }
17
+ /**
18
+ * Relation-diffusion adapter: breadth-first walk over the atoms' frozen
19
+ * relation links, at most `maxHops`, deterministically ordered by
20
+ * (hop, sourceMemoryId, relationId, targetMemoryId).
21
+ */
22
+ export function createRelationDiffusionAdapter(options) {
23
+ const maxHops = options.maxHops;
24
+ if (maxHops < 1)
25
+ throw new Error("Relation diffusion maxHops must be at least 1");
26
+ return {
27
+ id: "relation-diffusion",
28
+ enabled: options.enabled ?? true,
29
+ expand({ seeds, graph }) {
30
+ const atomById = new Map();
31
+ for (const atom of graph ?? seeds)
32
+ atomById.set(atom.memoryId, atom);
33
+ const seen = new Set(seeds.map((atom) => atom.memoryId));
34
+ let frontier = seeds.map((atom) => atom.memoryId);
35
+ const expansions = [];
36
+ for (let hop = 1; hop <= maxHops; hop += 1) {
37
+ const nextFrontier = [];
38
+ const hopEdges = frontier.flatMap((sourceId) => (atomById.get(sourceId)?.relations ?? [])
39
+ .filter((relation) => relation.targetMemoryId !== undefined)
40
+ .map((relation) => ({
41
+ sourceId,
42
+ relationId: relation.relationId,
43
+ kind: relation.kind,
44
+ target: relation.targetMemoryId,
45
+ })));
46
+ hopEdges.sort((a, b) => a.sourceId.localeCompare(b.sourceId) ||
47
+ a.relationId.localeCompare(b.relationId) ||
48
+ a.target.localeCompare(b.target));
49
+ for (const edge of hopEdges) {
50
+ if (seen.has(edge.target))
51
+ continue;
52
+ seen.add(edge.target);
53
+ nextFrontier.push(edge.target);
54
+ expansions.push({
55
+ memoryId: edge.target,
56
+ relationKind: edge.kind,
57
+ hop,
58
+ weight: 1 / hop,
59
+ via: "relation-diffusion",
60
+ });
61
+ }
62
+ if (nextFrontier.length === 0)
63
+ break;
64
+ frontier = nextFrontier;
65
+ }
66
+ return Object.freeze(expansions);
67
+ },
68
+ };
69
+ }
70
+ //# sourceMappingURL=memory-network.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-network.js","sourceRoot":"","sources":["../../src/memory/memory-network.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Network adapters (2.4d4) — relation diffusion and pluggable network\n * expansion over the 1C atom relations, each adapter independently\n * disableable.\n *\n * The network is a thin, deterministic coordinator: adapters are pure\n * functions from seed atoms to related memory references, and a disabled\n * adapter yields an explicit `disabled` result instead of being silently\n * skipped or silently degrading the output. Relation diffusion walks the\n * frozen `MemoryRelationV1` links breadth-first, at most `maxHops`, with\n * weight decaying as 1/hop. The same input always yields the same expansion.\n */\nimport type { MemoryAtomV1 } from \"./foundation.ts\";\n\nexport interface MemoryNetworkExpansionV1 {\n\treadonly memoryId: string;\n\treadonly relationKind: string;\n\treadonly hop: number;\n\t/** Deterministic decay: 1/hop from the seed. */\n\treadonly weight: number;\n\t/** The adapter that produced this reference. */\n\treadonly via: string;\n}\n\nexport interface MemoryNetworkAdapterResultV1 {\n\treadonly adapterId: string;\n\treadonly status: \"completed\" | \"disabled\";\n\treadonly related: readonly MemoryNetworkExpansionV1[];\n}\n\nexport interface MemoryNetworkAdapterV1 {\n\treadonly id: string;\n\treadonly enabled: boolean;\n\texpand(input: {\n\t\treadonly seeds: readonly MemoryAtomV1[];\n\t\t/** Full node set available for traversal; defaults to the seeds (one-hop only). */\n\t\treadonly graph?: readonly MemoryAtomV1[];\n\t}): readonly MemoryNetworkExpansionV1[];\n}\n\nexport interface MemoryNetworkV1 {\n\texpand(input: {\n\t\treadonly seeds: readonly MemoryAtomV1[];\n\t\treadonly graph?: readonly MemoryAtomV1[];\n\t}): readonly MemoryNetworkAdapterResultV1[];\n}\n\nexport function createMemoryNetwork(options: {\n\treadonly adapters: readonly MemoryNetworkAdapterV1[];\n}): MemoryNetworkV1 {\n\treturn {\n\t\texpand(input) {\n\t\t\treturn options.adapters.map((adapter) => {\n\t\t\t\tif (!adapter.enabled) {\n\t\t\t\t\treturn { adapterId: adapter.id, status: \"disabled\" as const, related: Object.freeze([]) };\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tadapterId: adapter.id,\n\t\t\t\t\tstatus: \"completed\" as const,\n\t\t\t\t\trelated: Object.freeze(adapter.expand({ seeds: input.seeds, graph: input.graph })),\n\t\t\t\t};\n\t\t\t});\n\t\t},\n\t};\n}\n\n/**\n * Relation-diffusion adapter: breadth-first walk over the atoms' frozen\n * relation links, at most `maxHops`, deterministically ordered by\n * (hop, sourceMemoryId, relationId, targetMemoryId).\n */\nexport function createRelationDiffusionAdapter(options: {\n\treadonly maxHops: number;\n\treadonly enabled?: boolean;\n}): MemoryNetworkAdapterV1 {\n\tconst maxHops = options.maxHops;\n\tif (maxHops < 1) throw new Error(\"Relation diffusion maxHops must be at least 1\");\n\treturn {\n\t\tid: \"relation-diffusion\",\n\t\tenabled: options.enabled ?? true,\n\t\texpand({ seeds, graph }) {\n\t\t\tconst atomById = new Map<string, MemoryAtomV1>();\n\t\t\tfor (const atom of graph ?? seeds) atomById.set(atom.memoryId, atom);\n\n\t\t\tconst seen = new Set<string>(seeds.map((atom) => atom.memoryId));\n\t\t\tlet frontier = seeds.map((atom) => atom.memoryId);\n\t\t\tconst expansions: MemoryNetworkExpansionV1[] = [];\n\n\t\t\tfor (let hop = 1; hop <= maxHops; hop += 1) {\n\t\t\t\tconst nextFrontier: string[] = [];\n\t\t\t\tconst hopEdges = frontier.flatMap((sourceId) =>\n\t\t\t\t\t(atomById.get(sourceId)?.relations ?? [])\n\t\t\t\t\t\t.filter((relation) => relation.targetMemoryId !== undefined)\n\t\t\t\t\t\t.map((relation) => ({\n\t\t\t\t\t\t\tsourceId,\n\t\t\t\t\t\t\trelationId: relation.relationId,\n\t\t\t\t\t\t\tkind: relation.kind,\n\t\t\t\t\t\t\ttarget: relation.targetMemoryId!,\n\t\t\t\t\t\t})),\n\t\t\t\t);\n\t\t\t\thopEdges.sort(\n\t\t\t\t\t(a, b) =>\n\t\t\t\t\t\ta.sourceId.localeCompare(b.sourceId) ||\n\t\t\t\t\t\ta.relationId.localeCompare(b.relationId) ||\n\t\t\t\t\t\ta.target.localeCompare(b.target),\n\t\t\t\t);\n\t\t\t\tfor (const edge of hopEdges) {\n\t\t\t\t\tif (seen.has(edge.target)) continue;\n\t\t\t\t\tseen.add(edge.target);\n\t\t\t\t\tnextFrontier.push(edge.target);\n\t\t\t\t\texpansions.push({\n\t\t\t\t\t\tmemoryId: edge.target,\n\t\t\t\t\t\trelationKind: edge.kind,\n\t\t\t\t\t\thop,\n\t\t\t\t\t\tweight: 1 / hop,\n\t\t\t\t\t\tvia: \"relation-diffusion\",\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (nextFrontier.length === 0) break;\n\t\t\t\tfrontier = nextFrontier;\n\t\t\t}\n\t\t\treturn Object.freeze(expansions);\n\t\t},\n\t};\n}\n"]}