@fgv/ts-agent-memory 5.1.0-49 → 5.1.0-50

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 (164) hide show
  1. package/dist/packlets/index/memoryIndex.js +23 -16
  2. package/dist/packlets/index/memoryIndex.js.map +1 -1
  3. package/dist/packlets/ingest/orchestrator.js +13 -1
  4. package/dist/packlets/ingest/orchestrator.js.map +1 -1
  5. package/dist/packlets/retrieve/linkTraversalRetriever.js +12 -26
  6. package/dist/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
  7. package/dist/packlets/retrieve/recencyRetriever.js +7 -7
  8. package/dist/packlets/retrieve/recencyRetriever.js.map +1 -1
  9. package/dist/packlets/retrieve/retriever.js +91 -10
  10. package/dist/packlets/retrieve/retriever.js.map +1 -1
  11. package/dist/packlets/retrieve/semanticRetriever.js +16 -16
  12. package/dist/packlets/retrieve/semanticRetriever.js.map +1 -1
  13. package/dist/packlets/retrieve/structuredFilterRetriever.js +7 -7
  14. package/dist/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
  15. package/dist/packlets/retrieve/tagRetriever.js +7 -7
  16. package/dist/packlets/retrieve/tagRetriever.js.map +1 -1
  17. package/dist/packlets/retrieve/temporalRetrievers.js +23 -20
  18. package/dist/packlets/retrieve/temporalRetrievers.js.map +1 -1
  19. package/dist/packlets/store/coverage.js +6 -0
  20. package/dist/packlets/store/coverage.js.map +1 -0
  21. package/dist/packlets/store/fileTreeMemoryStore.js +221 -79
  22. package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
  23. package/dist/packlets/store/index.js +4 -0
  24. package/dist/packlets/store/index.js.map +1 -1
  25. package/dist/packlets/store/listSelection.js +36 -0
  26. package/dist/packlets/store/listSelection.js.map +1 -0
  27. package/dist/packlets/store/memoryStore.js +6 -0
  28. package/dist/packlets/store/memoryStore.js.map +1 -0
  29. package/dist/packlets/store/reconcile.js +6 -0
  30. package/dist/packlets/store/reconcile.js.map +1 -0
  31. package/dist/packlets/store/storeCoverage.js +102 -0
  32. package/dist/packlets/store/storeCoverage.js.map +1 -0
  33. package/dist/packlets/store/storeReconcile.js +122 -0
  34. package/dist/packlets/store/storeReconcile.js.map +1 -0
  35. package/dist/packlets/store/vectorMaintenance.js +116 -8
  36. package/dist/packlets/store/vectorMaintenance.js.map +1 -1
  37. package/dist/packlets/store/vectorRecordSource.js +44 -0
  38. package/dist/packlets/store/vectorRecordSource.js.map +1 -0
  39. package/dist/packlets/tools/memoryTools.js +25 -2
  40. package/dist/packlets/tools/memoryTools.js.map +1 -1
  41. package/dist/packlets/types/envelope.js +25 -0
  42. package/dist/packlets/types/envelope.js.map +1 -1
  43. package/dist/packlets/types/index.js +1 -0
  44. package/dist/packlets/types/index.js.map +1 -1
  45. package/dist/packlets/types/recordResolver.js +6 -0
  46. package/dist/packlets/types/recordResolver.js.map +1 -0
  47. package/dist/packlets/types/temporal.js.map +1 -1
  48. package/dist/packlets/vector/inMemoryCosineIndex.js +39 -18
  49. package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -1
  50. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js +67 -12
  51. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
  52. package/dist/packlets/vector/rebuildHelpers.js +38 -0
  53. package/dist/packlets/vector/rebuildHelpers.js.map +1 -0
  54. package/dist/packlets/vector/vectorIndex.js.map +1 -1
  55. package/dist/ts-agent-memory.d.ts +1035 -106
  56. package/lib/packlets/index/memoryIndex.d.ts +118 -27
  57. package/lib/packlets/index/memoryIndex.d.ts.map +1 -1
  58. package/lib/packlets/index/memoryIndex.js +23 -16
  59. package/lib/packlets/index/memoryIndex.js.map +1 -1
  60. package/lib/packlets/ingest/orchestrator.d.ts.map +1 -1
  61. package/lib/packlets/ingest/orchestrator.js +13 -1
  62. package/lib/packlets/ingest/orchestrator.js.map +1 -1
  63. package/lib/packlets/retrieve/linkTraversalRetriever.d.ts +3 -10
  64. package/lib/packlets/retrieve/linkTraversalRetriever.d.ts.map +1 -1
  65. package/lib/packlets/retrieve/linkTraversalRetriever.js +11 -25
  66. package/lib/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
  67. package/lib/packlets/retrieve/recencyRetriever.d.ts +3 -3
  68. package/lib/packlets/retrieve/recencyRetriever.d.ts.map +1 -1
  69. package/lib/packlets/retrieve/recencyRetriever.js +6 -6
  70. package/lib/packlets/retrieve/recencyRetriever.js.map +1 -1
  71. package/lib/packlets/retrieve/retriever.d.ts +88 -7
  72. package/lib/packlets/retrieve/retriever.d.ts.map +1 -1
  73. package/lib/packlets/retrieve/retriever.js +94 -9
  74. package/lib/packlets/retrieve/retriever.js.map +1 -1
  75. package/lib/packlets/retrieve/semanticRetriever.d.ts +3 -5
  76. package/lib/packlets/retrieve/semanticRetriever.d.ts.map +1 -1
  77. package/lib/packlets/retrieve/semanticRetriever.js +15 -15
  78. package/lib/packlets/retrieve/semanticRetriever.js.map +1 -1
  79. package/lib/packlets/retrieve/structuredFilterRetriever.d.ts +3 -3
  80. package/lib/packlets/retrieve/structuredFilterRetriever.d.ts.map +1 -1
  81. package/lib/packlets/retrieve/structuredFilterRetriever.js +6 -6
  82. package/lib/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
  83. package/lib/packlets/retrieve/tagRetriever.d.ts +3 -3
  84. package/lib/packlets/retrieve/tagRetriever.d.ts.map +1 -1
  85. package/lib/packlets/retrieve/tagRetriever.js +6 -6
  86. package/lib/packlets/retrieve/tagRetriever.js.map +1 -1
  87. package/lib/packlets/retrieve/temporalRetrievers.d.ts +7 -5
  88. package/lib/packlets/retrieve/temporalRetrievers.d.ts.map +1 -1
  89. package/lib/packlets/retrieve/temporalRetrievers.js +22 -19
  90. package/lib/packlets/retrieve/temporalRetrievers.js.map +1 -1
  91. package/lib/packlets/store/coverage.d.ts +102 -0
  92. package/lib/packlets/store/coverage.d.ts.map +1 -0
  93. package/lib/packlets/store/coverage.js +7 -0
  94. package/lib/packlets/store/coverage.js.map +1 -0
  95. package/lib/packlets/store/fileTreeMemoryStore.d.ts +53 -166
  96. package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
  97. package/lib/packlets/store/fileTreeMemoryStore.js +221 -79
  98. package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
  99. package/lib/packlets/store/index.d.ts +4 -0
  100. package/lib/packlets/store/index.d.ts.map +1 -1
  101. package/lib/packlets/store/index.js +4 -0
  102. package/lib/packlets/store/index.js.map +1 -1
  103. package/lib/packlets/store/listSelection.d.ts +101 -0
  104. package/lib/packlets/store/listSelection.d.ts.map +1 -0
  105. package/lib/packlets/store/listSelection.js +40 -0
  106. package/lib/packlets/store/listSelection.js.map +1 -0
  107. package/lib/packlets/store/memoryStore.d.ts +237 -0
  108. package/lib/packlets/store/memoryStore.d.ts.map +1 -0
  109. package/lib/packlets/store/memoryStore.js +7 -0
  110. package/lib/packlets/store/memoryStore.js.map +1 -0
  111. package/lib/packlets/store/reconcile.d.ts +82 -0
  112. package/lib/packlets/store/reconcile.d.ts.map +1 -0
  113. package/lib/packlets/store/reconcile.js +7 -0
  114. package/lib/packlets/store/reconcile.js.map +1 -0
  115. package/lib/packlets/store/storeCoverage.d.ts +45 -0
  116. package/lib/packlets/store/storeCoverage.d.ts.map +1 -0
  117. package/lib/packlets/store/storeCoverage.js +105 -0
  118. package/lib/packlets/store/storeCoverage.js.map +1 -0
  119. package/lib/packlets/store/storeReconcile.d.ts +41 -0
  120. package/lib/packlets/store/storeReconcile.d.ts.map +1 -0
  121. package/lib/packlets/store/storeReconcile.js +125 -0
  122. package/lib/packlets/store/storeReconcile.js.map +1 -0
  123. package/lib/packlets/store/vectorMaintenance.d.ts +74 -0
  124. package/lib/packlets/store/vectorMaintenance.d.ts.map +1 -1
  125. package/lib/packlets/store/vectorMaintenance.js +117 -8
  126. package/lib/packlets/store/vectorMaintenance.js.map +1 -1
  127. package/lib/packlets/store/vectorRecordSource.d.ts +36 -0
  128. package/lib/packlets/store/vectorRecordSource.d.ts.map +1 -0
  129. package/lib/packlets/store/vectorRecordSource.js +47 -0
  130. package/lib/packlets/store/vectorRecordSource.js.map +1 -0
  131. package/lib/packlets/tools/memoryTools.d.ts.map +1 -1
  132. package/lib/packlets/tools/memoryTools.js +25 -2
  133. package/lib/packlets/tools/memoryTools.js.map +1 -1
  134. package/lib/packlets/types/envelope.d.ts +24 -2
  135. package/lib/packlets/types/envelope.d.ts.map +1 -1
  136. package/lib/packlets/types/envelope.js +26 -0
  137. package/lib/packlets/types/envelope.js.map +1 -1
  138. package/lib/packlets/types/index.d.ts +1 -0
  139. package/lib/packlets/types/index.d.ts.map +1 -1
  140. package/lib/packlets/types/index.js +1 -0
  141. package/lib/packlets/types/index.js.map +1 -1
  142. package/lib/packlets/types/recordResolver.d.ts +39 -0
  143. package/lib/packlets/types/recordResolver.d.ts.map +1 -0
  144. package/lib/packlets/types/recordResolver.js +7 -0
  145. package/lib/packlets/types/recordResolver.js.map +1 -0
  146. package/lib/packlets/types/temporal.d.ts +26 -6
  147. package/lib/packlets/types/temporal.d.ts.map +1 -1
  148. package/lib/packlets/types/temporal.js.map +1 -1
  149. package/lib/packlets/vector/inMemoryCosineIndex.d.ts +9 -2
  150. package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -1
  151. package/lib/packlets/vector/inMemoryCosineIndex.js +40 -19
  152. package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -1
  153. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts +6 -3
  154. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map +1 -1
  155. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js +66 -11
  156. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
  157. package/lib/packlets/vector/rebuildHelpers.d.ts +30 -0
  158. package/lib/packlets/vector/rebuildHelpers.d.ts.map +1 -0
  159. package/lib/packlets/vector/rebuildHelpers.js +42 -0
  160. package/lib/packlets/vector/rebuildHelpers.js.map +1 -0
  161. package/lib/packlets/vector/vectorIndex.d.ts +270 -15
  162. package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
  163. package/lib/packlets/vector/vectorIndex.js.map +1 -1
  164. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"vectorMaintenance.js","sourceRoot":"","sources":["../../../src/packlets/store/vectorMaintenance.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport { MemoryEmbedOutcome } from '../observe';\nimport { IEdgeTarget, IMemoryRecord, Kind, MemoryId, MemoryScopeKey } from '../types';\nimport {\n FragmentEmbedder,\n IEmbeddedFragment,\n IFragmentVectorIndex,\n IVectorIndex,\n MemoryEmbedder\n} from '../vector';\n\n/**\n * The internal outcome of record-level embed-on-write: the record to persist,\n * plus — only when the embedder declined a record that already carried an\n * `embeddingRef` — the index entry that reference superseded.\n *\n * `stale` is carried out to the caller rather than acted on in place because the\n * prune belongs on the far side of `_persist`: a persist that fails leaves the\n * PREVIOUS content on disk, and the superseded vector is still an accurate\n * embedding of that content.\n * @internal\n */\nexport interface IEmbedOnWriteOutcome {\n readonly record: IMemoryRecord<string>;\n readonly stale?: { readonly index: IVectorIndex; readonly target: IEdgeTarget };\n /**\n * What the record-granular index did, surfaced on the write observation.\n * `undefined` when the question does not apply (nothing wired).\n */\n readonly embed?: MemoryEmbedOutcome;\n}\n\n/**\n * Project a record the embedder **declined** into its written form: the same\n * record with no `embeddingRef`, plus the vector (if any) that reference\n * superseded, for the caller to prune after the commit.\n *\n * @remarks\n * A decline says \"this record is intentionally not embedded\". A re-put of a\n * record that *was* embedded (or a caller who supplied an `embeddingRef` — the\n * field is store-derived by contract but nothing strips it) arrives here\n * carrying an inherited reference, so returning it unchanged would persist\n * `embeddingRef` on a record the store just decided not to embed.\n *\n * Clearing the reference alone would be cosmetic and arguably worse: the index\n * entry keyed on this target would survive, so a semantic query would keep\n * returning the record — scored on its **previous** content — while the record\n * itself claimed not to be indexed. So the vector goes too, via\n * `pruneStaleVector` once the write has committed.\n * `stale` is set only when a reference was actually inherited, which keeps the\n * common decline (a record that was never embedded) free of an index round\n * trip.\n *\n * Pure and static: the decision needs nothing from the instance, and deferring\n * the index call to the caller is what lets it run on the far side of\n * `_persist`.\n */\nfunction declineEmbedding(\n built: IMemoryRecord<string>,\n index: IVectorIndex,\n target: IEdgeTarget,\n embed: MemoryEmbedOutcome\n): IEmbedOnWriteOutcome {\n if (built.envelope.embeddingRef === undefined) {\n return { record: built, embed };\n }\n // Rest-spread rather than `embeddingRef: undefined`: the envelope is YAML-\n // serialized, and an explicitly-undefined key is a serializer-dependent way\n // to say \"absent\" where dropping the key is not.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { embeddingRef, ...envelope } = built.envelope;\n return { record: { envelope, body: built.body }, stale: { index, target }, embed };\n}\n\n/**\n * What {@link VectorMaintenance} needs from the store it serves. Every field is\n * the store's own, passed in rather than reached for, so this collaborator holds\n * no reference back to the store and cannot quietly grow one.\n * @internal\n */\nexport interface IVectorMaintenanceParams {\n readonly vectorIndex?: IVectorIndex;\n readonly embed?: MemoryEmbedder;\n readonly fragmentIndex?: IFragmentVectorIndex;\n readonly fragmentEmbedder?: FragmentEmbedder;\n /** The store's swallowed-failure logger; every vector fault is best-effort. */\n readonly warn: (message: string) => void;\n /**\n * The store's per-kind record-index participation predicate\n * (`IMemoryStore.embedsKind`). Passed in rather than re-derived so the store's\n * declaration and the gate that enforces it cannot disagree.\n */\n readonly embedsKind: (kind: Kind) => boolean;\n}\n\n/**\n * The store's record- and fragment-vector maintenance, extracted from\n * {@link FileTreeMemoryStore} as a collaborator.\n *\n * @remarks\n * Everything here is **best-effort by contract**: the durable record store is\n * authoritative and both indexes are derived, rebuildable views, so a failed\n * embed, add or remove is logged and the write still succeeds. Nothing in this\n * class can turn a committed write into a `Failure` — which is exactly why it\n * separates cleanly from the store's write path, where every step is fallible\n * and fatal.\n *\n * The split is behavior-preserving: these are the same methods the store used to\n * carry as privates, with the same call order and the same logging. What moved is\n * where they live, not what they do.\n * @internal\n */\nexport class VectorMaintenance {\n private readonly _vectorIndex: IVectorIndex | undefined;\n private readonly _embed: MemoryEmbedder | undefined;\n private readonly _fragmentIndex: IFragmentVectorIndex | undefined;\n private readonly _fragmentEmbedder: FragmentEmbedder | undefined;\n private readonly _warn: (message: string) => void;\n private readonly _embedsKind: (kind: Kind) => boolean;\n\n public constructor(params: IVectorMaintenanceParams) {\n this._vectorIndex = params.vectorIndex;\n this._embed = params.embed;\n this._fragmentIndex = params.fragmentIndex;\n this._fragmentEmbedder = params.fragmentEmbedder;\n this._warn = params.warn;\n this._embedsKind = params.embedsKind;\n }\n\n /**\n * Best-effort removal of everything the two indexes hold for one record. Used\n * by the delete path and by cull-oldest eviction, both of which have already\n * committed by the time they call it.\n */\n public async removeAll(target: IEdgeTarget): Promise<void> {\n await this._removeVectorBestEffort(target);\n await this._removeFragmentsBestEffort(target);\n }\n\n /**\n * Best-effort embed-on-write. When a vector index AND an embedder are wired,\n * embeds the built record, `add`s the vector (replace semantics handle a same-id\n * re-embed — no explicit remove), and stamps the returned `embeddingRef`. A\n * failure (returned `fail` OR a thrown/rejected hook) is logged and the\n * unembedded record is returned unchanged — the put still persists, and the\n * derived index is reconciled by a later `rebuild`. A pass-through no-op when\n * unwired (byte-identical record).\n *\n * A **decline** is not a failure and is handled differently: see\n * `declineEmbedding`.\n *\n * Always succeeds (`Result` is the chain's shape, never a vector-induced\n * failure).\n */\n public async embedOnWrite(\n built: IMemoryRecord<string>,\n scope: MemoryScopeKey\n ): Promise<Result<IEmbedOnWriteOutcome>> {\n if (this._vectorIndex === undefined || this._embed === undefined) {\n // No outcome: with nothing wired there is no index for the record to be\n // absent from, so reporting one would invent a coverage question the\n // deployment has not asked.\n return succeed({ record: built });\n }\n const vectorIndex: IVectorIndex = this._vectorIndex;\n const embed: MemoryEmbedder = this._embed;\n const target: IEdgeTarget = { scope, id: built.envelope.id };\n // Gate BEFORE the embedder call, which is the whole point: a `MemoryEmbedder`\n // decline still pays the round trip, and on a locally-hosted model that round\n // trip IS the cost. A kind excluded here is never handed to the embedder.\n //\n // An exclusion reaches the same conclusion as a decline — this record is\n // intentionally not embedded — so it takes the same path: an inherited\n // `embeddingRef` is dropped and the vector it named is pruned after the\n // commit. Otherwise narrowing `embedKinds` on an existing vault would leave\n // every previously-embedded record of the excluded kind claiming an\n // embedding the store no longer maintains.\n if (!this._embedsKind(built.envelope.kind)) {\n return succeed(declineEmbedding(built, vectorIndex, target, 'excluded'));\n }\n const embedded: Result<Float32Array | undefined> = await this._tryVectorOp(\n () => embed(built),\n `embedding '${built.envelope.id}'`\n );\n if (embedded.isFailure()) {\n return succeed({ record: built, embed: 'failed' });\n }\n // A deliberate decline (`undefined`) stores the record with no embedding\n // reference. Deliberately NOT logged, unlike the failure path above: a warning\n // per write would make routine policy look like a recurring fault, which is\n // the confusion this return value exists to end. It is still *reported* — as\n // `embed: 'declined'` on the write observation — because saying nothing at all\n // is what left `embeddingRef` absence three-ways ambiguous.\n if (embedded.value === undefined) {\n return succeed(declineEmbedding(built, vectorIndex, target, 'declined'));\n }\n // Hoisted: the `undefined` check above does not narrow across the callback\n // boundary below, and a local keeps the non-null assertion out of the code.\n const vector: Float32Array = embedded.value;\n const added: Result<string> = await this._tryVectorOp(\n () => vectorIndex.add(target, vector),\n `vector add for '${built.envelope.id}'`\n );\n if (added.isFailure()) {\n return succeed({ record: built, embed: 'failed' });\n }\n return succeed({\n record: { envelope: { ...built.envelope, embeddingRef: added.value }, body: built.body },\n embed: 'embedded'\n });\n }\n\n /**\n * Prune the vector a decline superseded. Best-effort like the rest of the\n * vector path: a failed `remove` is logged and the (already-persisted) record\n * still carries no `embeddingRef`, because the record's own claim about itself\n * should be true even when the derived index is momentarily stale — that is\n * exactly what a later `rebuild` reconciles.\n *\n * The index travels with the target rather than being re-read from the instance\n * so the prune lands on the same index the decline was made against, and so\n * there is no second \"is a vector index wired?\" check whose false branch cannot\n * be reached.\n */\n public async pruneStaleVector(stale: IEmbedOnWriteOutcome['stale']): Promise<void> {\n if (stale === undefined) {\n return;\n }\n await this._tryVectorOp(\n () => stale.index.remove(stale.target),\n `vector remove for declined '${stale.target.id}'`\n );\n }\n\n /**\n * Best-effort fragment-embed-on-write. When a fragment index AND a fragment\n * embedder are wired, chunks + embeds the built record and replaces its\n * fragments in the index (`addFragments` is whole-record-replace, so a re-authored\n * document never leaves stale fragments behind — no explicit remove needed). A\n * failure (returned `fail` OR a thrown/rejected hook) is logged and the record is\n * returned unchanged — the put still persists, and the fragment index is a derived\n * view a later `rebuild` reconciles. Unlike `embedOnWrite`\n * it stamps nothing on the record (fragments have no per-record `embeddingRef`\n * analog). A pass-through no-op when unwired (byte-identical record).\n */\n public async embedFragmentsOnWrite(\n built: IMemoryRecord<string>,\n scope: MemoryScopeKey\n ): Promise<Result<IMemoryRecord<string>>> {\n if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {\n return succeed(built);\n }\n const fragmentIndex: IFragmentVectorIndex = this._fragmentIndex;\n const fragmentEmbedder: FragmentEmbedder = this._fragmentEmbedder;\n const target: IEdgeTarget = { scope, id: built.envelope.id };\n const embedded: Result<ReadonlyArray<IEmbeddedFragment>> = await this._tryVectorOp(\n () => fragmentEmbedder(built),\n `fragment embedding '${built.envelope.id}'`\n );\n if (embedded.isFailure()) {\n return succeed(built);\n }\n await this._tryVectorOp(\n () => fragmentIndex.addFragments(target, embedded.value),\n `fragment add for '${built.envelope.id}'`\n );\n return succeed(built);\n }\n\n /**\n * Best-effort fragment removal. A no-op unless the full fragment lifecycle is\n * wired (both an index AND an embedder), so an unwired store does no fragment\n * work and behaves byte-identically. Failures are logged, never surfaced — a\n * committed delete/eviction must not fail because a derived fragment index could\n * not be pruned.\n */\n private async _removeFragmentsBestEffort(target: IEdgeTarget): Promise<void> {\n if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {\n return;\n }\n const fragmentIndex: IFragmentVectorIndex = this._fragmentIndex;\n await this._tryVectorOp(() => fragmentIndex.remove(target), `fragment removal for '${target.id}'`);\n }\n\n /**\n * Best-effort vector removal for each evicted record (never fails the put).\n * Every evicted record is in the same `scope` as the incoming write (the\n * cull-oldest cohort is the incoming record's `(scope, kind)` cohort), so that\n * scope qualifies each removal target.\n */\n public async removeEvictedVectors(evicted: ReadonlyArray<MemoryId>, scope: MemoryScopeKey): Promise<void> {\n for (const id of evicted) {\n await this.removeAll({ scope, id });\n }\n }\n\n /**\n * Run a consumer-supplied vector hook, normalizing a thrown/rejected hook into a\n * `Failure` and logging any failure at `warn`. Best-effort: the caller proceeds\n * regardless, since the index is rebuildable.\n */\n private async _tryVectorOp<T>(op: () => Promise<Result<T>>, label: string): Promise<Result<T>> {\n let result: Result<T>;\n try {\n result = await op();\n } catch (err) {\n result = fail(`${label} threw: ${String(err)}`);\n }\n if (result.isFailure()) {\n this._warn(`memory: ${label} failed (best-effort; derived index left for rebuild): ${result.message}`);\n }\n return result;\n }\n\n /**\n * Best-effort vector removal. A no-op unless the full vector lifecycle is wired\n * (both an index AND an embedder), so an unwired store does no vector work and\n * behaves byte-identically. Failures are logged, never surfaced — a committed\n * delete/eviction must not fail because a derived index could not be pruned.\n */\n private async _removeVectorBestEffort(target: IEdgeTarget): Promise<void> {\n if (this._vectorIndex === undefined || this._embed === undefined) {\n return;\n }\n const vectorIndex: IVectorIndex = this._vectorIndex;\n await this._tryVectorOp(() => vectorIndex.remove(target), `vector removal for '${target.id}'`);\n }\n}\n"]}
1
+ {"version":3,"file":"vectorMaintenance.js","sourceRoot":"","sources":["../../../src/packlets/store/vectorMaintenance.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport { MemoryEmbedOutcome } from '../observe';\nimport { IEdgeTarget, IMemoryRecord, Kind, MemoryId, MemoryScopeKey, embeddingRefOf } from '../types';\nimport {\n FragmentEmbedder,\n IEmbeddedFragment,\n IFragmentVectorIndex,\n IVectorIndex,\n MemoryEmbedder\n} from '../vector';\n\n/**\n * What a repair-path re-embed established: how many vectors/fragments were\n * written, and — record lane only — the reference the index returned, which is\n * what the store stamps onto the envelope.\n */\nexport interface IReembedOutcome {\n readonly count: number;\n /** The index-supplied reference. Empty on the fragment lane, which has none. */\n readonly ref: string;\n}\n\n/**\n * The internal outcome of record-level embed-on-write: the record to persist,\n * plus — only when the embedder declined a record that already carried an\n * `embeddingRef` — the index entry that reference superseded.\n *\n * `stale` is carried out to the caller rather than acted on in place because the\n * prune belongs on the far side of `_persist`: a persist that fails leaves the\n * PREVIOUS content on disk, and the superseded vector is still an accurate\n * embedding of that content.\n * @internal\n */\nexport interface IEmbedOnWriteOutcome {\n readonly record: IMemoryRecord<string>;\n readonly stale?: { readonly index: IVectorIndex; readonly target: IEdgeTarget };\n /**\n * What the record-granular index did, surfaced on the write observation.\n * `undefined` when the question does not apply (nothing wired).\n */\n readonly embed?: MemoryEmbedOutcome;\n}\n\n/**\n * Project a record the embedder **declined** into its written form: the same\n * record with no `embeddingRef`, plus the vector (if any) that reference\n * superseded, for the caller to prune after the commit.\n *\n * @remarks\n * A decline says \"this record is intentionally not embedded\". A re-put of a\n * record that *was* embedded (or a caller who supplied an `embeddingRef` — the\n * field is store-derived by contract but nothing strips it) arrives here\n * carrying an inherited reference, so returning it unchanged would persist\n * `embeddingRef` on a record the store just decided not to embed.\n *\n * Clearing the reference alone would be cosmetic and arguably worse: the index\n * entry keyed on this target would survive, so a semantic query would keep\n * returning the record — scored on its **previous** content — while the record\n * itself claimed not to be indexed. So the vector goes too, via\n * `pruneStaleVector` once the write has committed.\n * `stale` is set only when a reference was actually inherited, which keeps the\n * common decline (a record that was never embedded) free of an index round\n * trip.\n *\n * Pure and static: the decision needs nothing from the instance, and deferring\n * the index call to the caller is what lets it run on the far side of\n * `_persist`.\n */\nfunction declineEmbedding(\n built: IMemoryRecord<string>,\n index: IVectorIndex,\n target: IEdgeTarget,\n embed: MemoryEmbedOutcome\n): IEmbedOnWriteOutcome {\n if (embeddingRefOf(built.envelope) === undefined) {\n return { record: built, embed };\n }\n // Rest-spread rather than `embeddingRef: undefined`: the envelope is YAML-\n // serialized, and an explicitly-undefined key is a serializer-dependent way\n // to say \"absent\" where dropping the key is not.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { embeddingRef, ...envelope } = built.envelope;\n return { record: { envelope, body: built.body }, stale: { index, target }, embed };\n}\n\n/**\n * Run a consumer-supplied hook, normalizing a synchronous throw or a rejected\n * promise into a `Failure`.\n *\n * @remarks\n * **Every vector-lane hook belongs to the consumer** — the embedder, the fragment\n * embedder, and every member of both index seams — so any of them may throw\n * rather than fail. Left unwrapped, that escapes as a rejected promise out of\n * `IMemoryStore.reconcile`, which the repo's Result contract says cannot happen.\n *\n * **Module-level and exported rather than a private of {@link VectorMaintenance},\n * because the repair loop in `storeReconcile.ts` needs it too.** It calls\n * `index.has` per record, which is a consumer hook like any other; when the four\n * embed/add hooks were wrapped, that fifth one was missed, and a second private\n * copy would have made the next omission just as easy. One hook, one helper.\n *\n * Kept separate from {@link VectorMaintenance._tryVectorOp} (which delegates\n * here and adds a warn) because that warning says \"best-effort; derived index\n * left for rebuild\" — true of a write, false of a repair. The repair *is* the\n * rebuild, and it returns its failures to the caller who asked for them rather\n * than logging them past a success.\n * @internal\n */\nexport async function captureVectorHook<T>(op: () => Promise<Result<T>>, label: string): Promise<Result<T>> {\n try {\n return await op();\n } catch (err) {\n return fail(`${label} threw: ${String(err)}`);\n }\n}\n\n/**\n * What {@link VectorMaintenance} needs from the store it serves. Every field is\n * the store's own, passed in rather than reached for, so this collaborator holds\n * no reference back to the store and cannot quietly grow one.\n * @internal\n */\nexport interface IVectorMaintenanceParams {\n readonly vectorIndex?: IVectorIndex;\n readonly embed?: MemoryEmbedder;\n readonly fragmentIndex?: IFragmentVectorIndex;\n readonly fragmentEmbedder?: FragmentEmbedder;\n /** The store's swallowed-failure logger; every vector fault is best-effort. */\n readonly warn: (message: string) => void;\n /**\n * The store's per-kind record-index participation predicate\n * (`IMemoryStore.embedsKind`). Passed in rather than re-derived so the store's\n * declaration and the gate that enforces it cannot disagree.\n */\n readonly embedsKind: (kind: Kind) => boolean;\n}\n\n/**\n * The store's record- and fragment-vector maintenance, extracted from\n * {@link FileTreeMemoryStore} as a collaborator.\n *\n * @remarks\n * Everything here is **best-effort by contract**: the durable record store is\n * authoritative and both indexes are derived, rebuildable views, so a failed\n * embed, add or remove is logged and the write still succeeds. Nothing in this\n * class can turn a committed write into a `Failure` — which is exactly why it\n * separates cleanly from the store's write path, where every step is fallible\n * and fatal.\n *\n * The split is behavior-preserving: these are the same methods the store used to\n * carry as privates, with the same call order and the same logging. What moved is\n * where they live, not what they do.\n * @internal\n */\nexport class VectorMaintenance {\n private readonly _vectorIndex: IVectorIndex | undefined;\n private readonly _embed: MemoryEmbedder | undefined;\n private readonly _fragmentIndex: IFragmentVectorIndex | undefined;\n private readonly _fragmentEmbedder: FragmentEmbedder | undefined;\n private readonly _warn: (message: string) => void;\n private readonly _embedsKind: (kind: Kind) => boolean;\n\n public constructor(params: IVectorMaintenanceParams) {\n this._vectorIndex = params.vectorIndex;\n this._embed = params.embed;\n this._fragmentIndex = params.fragmentIndex;\n this._fragmentEmbedder = params.fragmentEmbedder;\n this._warn = params.warn;\n this._embedsKind = params.embedsKind;\n }\n\n /**\n * Best-effort removal of everything the two indexes hold for one record. Used\n * by the delete path and by cull-oldest eviction, both of which have already\n * committed by the time they call it.\n */\n public async removeAll(target: IEdgeTarget): Promise<void> {\n await this._removeVectorBestEffort(target);\n await this._removeFragmentsBestEffort(target);\n }\n\n /**\n * The wired record-vector index, or `undefined`. Read-only, and exposed solely\n * so the store's {@link IMemoryStore.coverage} can report an index-side count\n * without a second copy of the wiring — absent here IS the \"lane not wired\"\n * answer that coverage reports as `undefined` rather than as zero.\n */\n public get vectorIndex(): IVectorIndex | undefined {\n return this._vectorIndex;\n }\n\n /** The wired fragment index, or `undefined`. See {@link VectorMaintenance.vectorIndex}. */\n public get fragmentIndex(): IFragmentVectorIndex | undefined {\n return this._fragmentIndex;\n }\n\n /**\n * The wired record embedder, or `undefined`. Exposed alongside the index\n * because a lane is only usable when BOTH halves are present — an index with\n * no embedder is a legal store whose writes simply do not embed, and a repair\n * has to say so rather than failing every record.\n */\n public get embedder(): MemoryEmbedder | undefined {\n return this._embed;\n }\n\n /** The wired fragment embedder, or `undefined`. See {@link VectorMaintenance.embedder}. */\n public get fragmentEmbedder(): FragmentEmbedder | undefined {\n return this._fragmentEmbedder;\n }\n\n /**\n * Re-embed one record into the record-vector index — the repair path.\n *\n * @remarks\n * Distinct from {@link VectorMaintenance.embedOnWrite} in the one way that\n * matters: **this is not best-effort.** Embed-on-write swallows a failure\n * because a vault record is the source of truth and a write must not be\n * rejected over a derived artifact; a repair was *asked for* by a caller who\n * wants to know whether it worked, so a failure is returned.\n *\n * `undefined` means the embedder declined — intentionally not embedded, which\n * is neither a repair nor a fault.\n */\n public async reembedRecord(\n record: IMemoryRecord<unknown>,\n target: IEdgeTarget\n ): Promise<Result<IReembedOutcome | undefined>> {\n if (this._vectorIndex === undefined || this._embed === undefined) {\n return fail('the record-vector lane is not wired');\n }\n const index: IVectorIndex = this._vectorIndex;\n const embed: MemoryEmbedder = this._embed;\n const embedded: Result<Float32Array | undefined> = await captureVectorHook(\n () => embed(record),\n `re-embedding '${record.envelope.id}'`\n );\n if (embedded.isFailure()) {\n return fail(embedded.message);\n }\n if (embedded.value === undefined) {\n return succeed(undefined);\n }\n // Hoisted: the `undefined` check above does not narrow across the callback\n // boundary below.\n const vector: Float32Array = embedded.value;\n // `add`'s return value IS the reference the store stamps — synthesizing one\n // here would diverge from the write path for any index whose reference is\n // not the scoped key.\n return (\n await captureVectorHook(() => index.add(target, vector), `vector add for '${record.envelope.id}'`)\n ).onSuccess((ref) => succeed({ count: 1, ref }));\n }\n\n /**\n * Re-embed one record's fragments — the repair path, returning the fragment\n * count written. See {@link VectorMaintenance.reembedRecord} for why this is\n * not best-effort. An empty fragment array is this lane's decline and reports\n * `undefined`, though the whole-record-replace still runs so stale fragments\n * are cleared.\n */\n public async reembedFragments(\n record: IMemoryRecord<unknown>,\n target: IEdgeTarget\n ): Promise<Result<IReembedOutcome | undefined>> {\n if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {\n return fail('the fragment lane is not wired');\n }\n const index: IFragmentVectorIndex = this._fragmentIndex;\n const fragmentEmbedder: FragmentEmbedder = this._fragmentEmbedder;\n const embedded: Result<ReadonlyArray<IEmbeddedFragment>> = await captureVectorHook(\n () => fragmentEmbedder(record),\n `re-embedding fragments of '${record.envelope.id}'`\n );\n if (embedded.isFailure()) {\n return fail(embedded.message);\n }\n const fragments: ReadonlyArray<IEmbeddedFragment> = embedded.value;\n return (\n await captureVectorHook(\n () => index.addFragments(target, fragments),\n `fragment add for '${record.envelope.id}'`\n )\n ).onSuccess((n) =>\n // The fragment lane has no envelope reference, so `ref` is empty and unused.\n succeed(n === 0 ? undefined : { count: n, ref: '' })\n );\n }\n\n /**\n * Best-effort embed-on-write. When a vector index AND an embedder are wired,\n * embeds the built record, `add`s the vector (replace semantics handle a same-id\n * re-embed — no explicit remove), and stamps the returned `embeddingRef`. A\n * failure (returned `fail` OR a thrown/rejected hook) is logged and the\n * unembedded record is returned unchanged — the put still persists, and the\n * derived index is reconciled by a later `rebuild`. A pass-through no-op when\n * unwired (byte-identical record).\n *\n * A **decline** is not a failure and is handled differently: see\n * `declineEmbedding`.\n *\n * Always succeeds (`Result` is the chain's shape, never a vector-induced\n * failure).\n */\n public async embedOnWrite(\n built: IMemoryRecord<string>,\n scope: MemoryScopeKey\n ): Promise<Result<IEmbedOnWriteOutcome>> {\n if (this._vectorIndex === undefined || this._embed === undefined) {\n // No outcome: with nothing wired there is no index for the record to be\n // absent from, so reporting one would invent a coverage question the\n // deployment has not asked.\n return succeed({ record: built });\n }\n const vectorIndex: IVectorIndex = this._vectorIndex;\n const embed: MemoryEmbedder = this._embed;\n const target: IEdgeTarget = { scope, id: built.envelope.id };\n // Gate BEFORE the embedder call, which is the whole point: a `MemoryEmbedder`\n // decline still pays the round trip, and on a locally-hosted model that round\n // trip IS the cost. A kind excluded here is never handed to the embedder.\n //\n // An exclusion reaches the same conclusion as a decline — this record is\n // intentionally not embedded — so it takes the same path: an inherited\n // `embeddingRef` is dropped and the vector it named is pruned after the\n // commit. Otherwise narrowing `embedKinds` on an existing vault would leave\n // every previously-embedded record of the excluded kind claiming an\n // embedding the store no longer maintains.\n if (!this._embedsKind(built.envelope.kind)) {\n return succeed(declineEmbedding(built, vectorIndex, target, 'excluded'));\n }\n const embedded: Result<Float32Array | undefined> = await this._tryVectorOp(\n () => embed(built),\n `embedding '${built.envelope.id}'`\n );\n if (embedded.isFailure()) {\n return succeed({ record: built, embed: 'failed' });\n }\n // A deliberate decline (`undefined`) stores the record with no embedding\n // reference. Deliberately NOT logged, unlike the failure path above: a warning\n // per write would make routine policy look like a recurring fault, which is\n // the confusion this return value exists to end. It is still *reported* — as\n // `embed: 'declined'` on the write observation — because saying nothing at all\n // is what left `embeddingRef` absence three-ways ambiguous.\n if (embedded.value === undefined) {\n return succeed(declineEmbedding(built, vectorIndex, target, 'declined'));\n }\n // Hoisted: the `undefined` check above does not narrow across the callback\n // boundary below, and a local keeps the non-null assertion out of the code.\n const vector: Float32Array = embedded.value;\n const added: Result<string> = await this._tryVectorOp(\n () => vectorIndex.add(target, vector),\n `vector add for '${built.envelope.id}'`\n );\n if (added.isFailure()) {\n return succeed({ record: built, embed: 'failed' });\n }\n return succeed({\n record: { envelope: { ...built.envelope, embeddingRef: added.value }, body: built.body },\n embed: 'embedded'\n });\n }\n\n /**\n * Prune the vector a decline superseded. Best-effort like the rest of the\n * vector path: a failed `remove` is logged and the (already-persisted) record\n * still carries no `embeddingRef`, because the record's own claim about itself\n * should be true even when the derived index is momentarily stale — that is\n * exactly what a later `rebuild` reconciles.\n *\n * The index travels with the target rather than being re-read from the instance\n * so the prune lands on the same index the decline was made against, and so\n * there is no second \"is a vector index wired?\" check whose false branch cannot\n * be reached.\n */\n public async pruneStaleVector(stale: IEmbedOnWriteOutcome['stale']): Promise<void> {\n if (stale === undefined) {\n return;\n }\n await this._tryVectorOp(\n () => stale.index.remove(stale.target),\n `vector remove for declined '${stale.target.id}'`\n );\n }\n\n /**\n * Best-effort fragment-embed-on-write. When a fragment index AND a fragment\n * embedder are wired, chunks + embeds the built record and replaces its\n * fragments in the index (`addFragments` is whole-record-replace, so a re-authored\n * document never leaves stale fragments behind — no explicit remove needed). A\n * failure (returned `fail` OR a thrown/rejected hook) is logged and the record is\n * returned unchanged — the put still persists, and the fragment index is a derived\n * view a later `rebuild` reconciles. Unlike `embedOnWrite`\n * it stamps nothing on the record (fragments have no per-record `embeddingRef`\n * analog). A pass-through no-op when unwired (byte-identical record).\n */\n public async embedFragmentsOnWrite(\n built: IMemoryRecord<string>,\n scope: MemoryScopeKey\n ): Promise<Result<IMemoryRecord<string>>> {\n if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {\n return succeed(built);\n }\n const fragmentIndex: IFragmentVectorIndex = this._fragmentIndex;\n const fragmentEmbedder: FragmentEmbedder = this._fragmentEmbedder;\n const target: IEdgeTarget = { scope, id: built.envelope.id };\n const embedded: Result<ReadonlyArray<IEmbeddedFragment>> = await this._tryVectorOp(\n () => fragmentEmbedder(built),\n `fragment embedding '${built.envelope.id}'`\n );\n if (embedded.isFailure()) {\n return succeed(built);\n }\n await this._tryVectorOp(\n () => fragmentIndex.addFragments(target, embedded.value),\n `fragment add for '${built.envelope.id}'`\n );\n return succeed(built);\n }\n\n /**\n * Best-effort fragment removal. A no-op unless the full fragment lifecycle is\n * wired (both an index AND an embedder), so an unwired store does no fragment\n * work and behaves byte-identically. Failures are logged, never surfaced — a\n * committed delete/eviction must not fail because a derived fragment index could\n * not be pruned.\n */\n private async _removeFragmentsBestEffort(target: IEdgeTarget): Promise<void> {\n if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {\n return;\n }\n const fragmentIndex: IFragmentVectorIndex = this._fragmentIndex;\n await this._tryVectorOp(() => fragmentIndex.remove(target), `fragment removal for '${target.id}'`);\n }\n\n /**\n * Best-effort vector removal for each evicted record (never fails the put).\n * Every evicted record is in the same `scope` as the incoming write (the\n * cull-oldest cohort is the incoming record's `(scope, kind)` cohort), so that\n * scope qualifies each removal target.\n */\n public async removeEvictedVectors(evicted: ReadonlyArray<MemoryId>, scope: MemoryScopeKey): Promise<void> {\n for (const id of evicted) {\n await this.removeAll({ scope, id });\n }\n }\n\n /**\n * Run a consumer-supplied vector hook, normalizing a thrown/rejected hook into a\n * `Failure` and logging any failure at `warn`. Best-effort: the caller proceeds\n * regardless, since the index is rebuildable.\n */\n private async _tryVectorOp<T>(op: () => Promise<Result<T>>, label: string): Promise<Result<T>> {\n const result: Result<T> = await captureVectorHook(op, label);\n if (result.isFailure()) {\n this._warn(`memory: ${label} failed (best-effort; derived index left for rebuild): ${result.message}`);\n }\n return result;\n }\n\n /**\n * Best-effort vector removal. A no-op unless the full vector lifecycle is wired\n * (both an index AND an embedder), so an unwired store does no vector work and\n * behaves byte-identically. Failures are logged, never surfaced — a committed\n * delete/eviction must not fail because a derived index could not be pruned.\n */\n private async _removeVectorBestEffort(target: IEdgeTarget): Promise<void> {\n if (this._vectorIndex === undefined || this._embed === undefined) {\n return;\n }\n const vectorIndex: IVectorIndex = this._vectorIndex;\n await this._tryVectorOp(() => vectorIndex.remove(target), `vector removal for '${target.id}'`);\n }\n}\n"]}
@@ -0,0 +1,44 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ import { succeed } from '@fgv/ts-utils';
6
+ /**
7
+ * Adapt a store's whole-vault scoped listing into the `IMemoryRecordSource` a
8
+ * vector-index rebuild reads: filtered to the kinds that participate in the
9
+ * record-granular index, and **counting what it drops**.
10
+ *
11
+ * @remarks
12
+ * The filter exists because this source drives `IVectorIndex` rebuilds, so a kind
13
+ * excluded from that index has no business being re-embedded on open — which is
14
+ * where the cost is worst, since a rebuild embeds the whole vault serially. With
15
+ * no `embedKinds` declaration every kind passes and this is the identity filter.
16
+ *
17
+ * The **tally** exists because this is the only layer that can produce it. A
18
+ * rebuild never sees an excluded record and so cannot count one; a coverage report
19
+ * assembled without this number leaves those records in none of `indexed` /
20
+ * `declined` / `skipped`, and a caller computing coverage undercounts — in the
21
+ * direction of looking healthier.
22
+ *
23
+ * The map is always present, empty when nothing was excluded: this source can
24
+ * always say, so it always does. An absent `excluded` means *"this source does not
25
+ * track exclusions"*, which is a different answer and not one this source gives.
26
+ */
27
+ export function vectorRecordSource(host) {
28
+ return {
29
+ list: async () => (await host.listScoped()).onSuccess((scoped) => {
30
+ const excluded = new Map();
31
+ const records = scoped.filter((s) => {
32
+ var _a;
33
+ const kind = s.record.envelope.kind;
34
+ if (host.embedsKind(kind)) {
35
+ return true;
36
+ }
37
+ excluded.set(kind, ((_a = excluded.get(kind)) !== null && _a !== void 0 ? _a : 0) + 1);
38
+ return false;
39
+ });
40
+ return succeed({ records, excluded });
41
+ })
42
+ };
43
+ }
44
+ //# sourceMappingURL=vectorRecordSource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vectorRecordSource.js","sourceRoot":"","sources":["../../../src/packlets/store/vectorRecordSource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAU,OAAO,EAAE,MAAM,eAAe,CAAC;AAehD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAA6B;IAC9D,OAAO;QACL,IAAI,EAAE,KAAK,IAA2C,EAAE,CACtD,CAAC,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,MAA0C,EAAE,EAAE;YACjF,MAAM,QAAQ,GAAsB,IAAI,GAAG,EAAgB,CAAC;YAC5D,MAAM,OAAO,GAAuC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;;gBACtE,MAAM,IAAI,GAAS,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAC1C,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC1B,OAAO,IAAI,CAAC;gBACd,CAAC;gBACD,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,MAAA,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,mCAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBAClD,OAAO,KAAK,CAAC;YACf,CAAC,CAAC,CAAC;YACH,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;QACxC,CAAC,CAAC;KACL,CAAC;AACJ,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, succeed } from '@fgv/ts-utils';\nimport { Kind } from '../types';\nimport { IMemoryRecordListing, IMemoryRecordSource, IScopedMemoryRecord } from '../vector';\n\n/**\n * The two store capabilities the vector record source needs, taken structurally\n * so this module does not import the store (which imports it).\n */\nexport interface IVectorRecordSourceHost {\n /** The whole-vault scoped listing, unfiltered. */\n listScoped(): Promise<Result<ReadonlyArray<IScopedMemoryRecord>>>;\n /** Whether records of `kind` participate in the record-granular vector index. */\n embedsKind(kind: Kind): boolean;\n}\n\n/**\n * Adapt a store's whole-vault scoped listing into the `IMemoryRecordSource` a\n * vector-index rebuild reads: filtered to the kinds that participate in the\n * record-granular index, and **counting what it drops**.\n *\n * @remarks\n * The filter exists because this source drives `IVectorIndex` rebuilds, so a kind\n * excluded from that index has no business being re-embedded on open — which is\n * where the cost is worst, since a rebuild embeds the whole vault serially. With\n * no `embedKinds` declaration every kind passes and this is the identity filter.\n *\n * The **tally** exists because this is the only layer that can produce it. A\n * rebuild never sees an excluded record and so cannot count one; a coverage report\n * assembled without this number leaves those records in none of `indexed` /\n * `declined` / `skipped`, and a caller computing coverage undercounts — in the\n * direction of looking healthier.\n *\n * The map is always present, empty when nothing was excluded: this source can\n * always say, so it always does. An absent `excluded` means *\"this source does not\n * track exclusions\"*, which is a different answer and not one this source gives.\n */\nexport function vectorRecordSource(host: IVectorRecordSourceHost): IMemoryRecordSource {\n return {\n list: async (): Promise<Result<IMemoryRecordListing>> =>\n (await host.listScoped()).onSuccess((scoped: ReadonlyArray<IScopedMemoryRecord>) => {\n const excluded: Map<Kind, number> = new Map<Kind, number>();\n const records: ReadonlyArray<IScopedMemoryRecord> = scoped.filter((s) => {\n const kind: Kind = s.record.envelope.kind;\n if (host.embedsKind(kind)) {\n return true;\n }\n excluded.set(kind, (excluded.get(kind) ?? 0) + 1);\n return false;\n });\n return succeed({ records, excluded });\n })\n };\n}\n"]}
@@ -66,7 +66,9 @@ const searchSchema = JsonSchema.object({
66
66
  kind: JsonSchema.optional(JsonSchema.string({ description: 'Restrict to this kind.' })),
67
67
  tag: JsonSchema.optional(JsonSchema.string({ description: 'Restrict to records carrying this tag.' })),
68
68
  semantic: JsonSchema.optional(JsonSchema.string({ description: 'Semantic query text (requires a semantic-capable retriever).' })),
69
- limit: JsonSchema.optional(JsonSchema.integer({ description: 'Maximum number of results to return.' })),
69
+ limit: JsonSchema.optional(JsonSchema.integer({
70
+ description: 'Maximum number of results to return. At least one of kind, tag, semantic or limit is required.'
71
+ })),
70
72
  offset: JsonSchema.optional(JsonSchema.integer({ description: 'Number of results to skip after ordering, before limit. Default 0.' })),
71
73
  detail: JsonSchema.optional(JsonSchema.enumOf(['gist', 'full'], { description: "'gist' (default) | 'full'." }))
72
74
  });
@@ -303,7 +305,8 @@ function buildSearchTool(ctx) {
303
305
  config: {
304
306
  type: 'client_tool',
305
307
  name: 'memory_search',
306
- description: 'Search memories by tag, kind, or semantic text. Returns ranked results.',
308
+ description: 'Search memories by tag, kind, or semantic text. Returns ranked results. ' +
309
+ 'At least one of kind, tag, semantic or limit must be supplied.',
307
310
  parametersSchema: searchSchema,
308
311
  annotations: READ_ONLY_ANNOTATIONS
309
312
  },
@@ -311,6 +314,26 @@ function buildSearchTool(ctx) {
311
314
  .convert(args)
312
315
  .withErrorFormat((msg) => `memory_search: invalid arguments: ${msg}`)
313
316
  .onSuccess((typed) => resolveOptionalKind(ctx, typed.kind).onSuccess((kind) => resolveOptionalTag(typed.tag).onSuccess((tag) => succeed({ typed, kind, tag }))))
317
+ .onSuccess(({ typed, kind, tag }) => {
318
+ // A search with no axis at all is a whole-vault read issued by a model,
319
+ // and since the index holds envelopes only it materializes every body
320
+ // to answer. An LLM asking for "everything" is nearly always an
321
+ // under-specified query rather than an intended full scan, so this
322
+ // refuses instead of serving it — the model can retry with an axis,
323
+ // which is the outcome we want anyway.
324
+ //
325
+ // `limit` counts: an ordered top-N materializes N records rather than
326
+ // the vault (see `resolveQuery`), so it bounds the read as genuinely as
327
+ // `kind` or `tag` does.
328
+ if (kind === undefined &&
329
+ tag === undefined &&
330
+ typed.semantic === undefined &&
331
+ typed.limit === undefined) {
332
+ return fail('memory_search: supply at least one of kind, tag, semantic or limit — ' +
333
+ 'an unrestricted search reads every record in the vault');
334
+ }
335
+ return succeed({ typed, kind, tag });
336
+ })
314
337
  .thenOnSuccess(async ({ typed, kind, tag }) => {
315
338
  const detail = resolveDetail(typed.detail);
316
339
  const query = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (kind !== undefined ? { kind } : {})), (tag !== undefined ? { tag } : {})), (typed.semantic !== undefined ? { semantic: typed.semantic } : {})), (typed.limit !== undefined ? { limit: typed.limit } : {})), (typed.offset !== undefined ? { offset: typed.offset } : {}));
@@ -1 +1 @@
1
- {"version":3,"file":"memoryTools.js","sourceRoot":"","sources":["../../../src/packlets/tools/memoryTools.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, captureResult, fail, succeed } from '@fgv/ts-utils';\nimport { JsonSchema } from '@fgv/ts-json-base';\nimport { AiAssist } from '@fgv/ts-extras';\nimport { Convert, EntityId, IEdgeTarget, IIdentityCodec, IMemoryRecord, Kind, MemoryId, Tag } from '../types';\nimport { IBodyConverterRegistry, envelopeConverter } from '../converters';\nimport { IMemoryStore } from '../store';\nimport { IMemoryQuery, IMemoryRetriever } from '../retrieve';\n\n/**\n * The names of the five proof-set memory tools. A caller selects a subset via\n * {@link ICreateMemoryToolsParams.tools | tools}.\n * @public\n */\nexport type MemoryToolName =\n | 'memory_write'\n | 'memory_read'\n | 'memory_search'\n | 'memory_context'\n | 'memory_delete';\n\n/**\n * The default tool subset when {@link ICreateMemoryToolsParams.tools | tools} is\n * omitted: the read-only set. Mutating tools (`memory_write` / `memory_delete`)\n * are **off by default** and must be named explicitly — writes stay\n * curation-mediated unless the host opts in.\n * @public\n */\nexport const DEFAULT_MEMORY_TOOLS: ReadonlyArray<MemoryToolName> = ['memory_search', 'memory_context'];\n\n/**\n * Discriminates the outcome of a {@link createMemoryTools | memory_write} call so\n * the agent can reason about what its write actually did.\n *\n * @remarks\n * - `written` — a new record was persisted, or an existing entity was updated.\n * - `deduped` — the content already existed (content-hash dedup no-op); the\n * store returned the existing record unchanged.\n *\n * The store's public `put` return does not surface cap-cull evictions, so a\n * `culled` outcome is not distinguishable at this layer without an L1 change or\n * observer wiring (both out of scope for L2). The writer's own record is always\n * `written` even when the write triggers a cull of older siblings.\n * @public\n */\nexport type MemoryWriteOutcome = 'written' | 'deduped';\n\n/**\n * The success value returned by `memory_write.execute`.\n * @public\n */\nexport interface IMemoryWriteResult {\n /** What the write did — see {@link MemoryWriteOutcome}. */\n readonly outcome: MemoryWriteOutcome;\n /** The stored record's {@link MemoryId}. */\n readonly id: MemoryId;\n /** The domain {@link EntityId} the write targeted. */\n readonly entityId: EntityId;\n /** The record's {@link Kind}. */\n readonly kind: Kind;\n}\n\n/**\n * A single agent-visible search / context result item. The agent-facing key is\n * {@link IMemoryToolResultItem.handle | handle}: the host mnemonic when a\n * {@link ICreateMemoryToolsParams.handleFor | handleFor} hook is supplied, else\n * the raw {@link MemoryId}.\n * @public\n */\nexport interface IMemoryToolResultItem {\n /** The agent-visible key (host handle when supplied, raw {@link MemoryId} otherwise). */\n readonly handle: string;\n /** The record's {@link Kind}. */\n readonly kind: Kind;\n /** The record's domain {@link EntityId}. */\n readonly entityId: EntityId;\n /** The record's tags. */\n readonly tags: ReadonlyArray<string>;\n /** The record body (a markdown string in B1). */\n readonly body: unknown;\n}\n\n/**\n * Parameters for {@link createMemoryTools}.\n *\n * @remarks\n * **Scope isolation is constructor-fixed.** The {@link\n * ICreateMemoryToolsParams.store | store} is the sole scope authority — it is the\n * actor's own, pre-scoped memory root. No tool's `parametersSchema` declares a\n * `scope` (or any scope-widening) property, so an LLM cannot steer a tool at\n * another actor's memory.\n * @public\n */\nexport interface ICreateMemoryToolsParams {\n /**\n * The pre-scoped memory store (the actor's own memory root). Sole scope\n * authority — backs `memory_write` / `memory_read` / `memory_delete`.\n */\n readonly store: IMemoryStore;\n /** Retriever backing `memory_search` (and `memory_context` via link traversal). */\n readonly retriever: IMemoryRetriever;\n /** Body converter registry — gates the toolable kinds via `has(kind)`. */\n readonly registry: IBodyConverterRegistry;\n /**\n * The per-tool enable subset. Defaults to {@link DEFAULT_MEMORY_TOOLS} (the\n * read-only set). Name `memory_write` / `memory_delete` here to opt into the\n * mutating tools.\n */\n readonly tools?: ReadonlyArray<MemoryToolName>;\n /**\n * Optional whitelist of toolable kinds. When present, a tool `kind` argument\n * outside this set is rejected. When absent, {@link\n * IBodyConverterRegistry.has | registry.has} is the sole kind gate.\n */\n readonly kinds?: ReadonlyArray<Kind>;\n /**\n * Per-kind identity codecs, used by `memory_write` to map the domain\n * {@link EntityId} to the record's storage id (the store resolves codecs\n * internally for `get` / `delete`, so read / delete do not need them). Absent\n * → `memory_write` uses {@link ICreateMemoryToolsParams.defaultCodec |\n * defaultCodec}, and fails loudly for a kind with no resolvable codec. The\n * read-only default tool set needs no codecs.\n */\n readonly codecs?: ReadonlyMap<Kind, IIdentityCodec>;\n /** Default identity codec for kinds without an explicit {@link ICreateMemoryToolsParams.codecs | codecs} entry. */\n readonly defaultCodec?: IIdentityCodec;\n /**\n * Optional host hook mapping a record to its agent-visible handle (an evocative\n * mnemonic tag). When supplied, `memory_search` / `memory_context` results use\n * the returned handle as the agent-visible key; when absent the raw\n * {@link MemoryId} is used.\n */\n readonly handleFor?: (record: IMemoryRecord<unknown>) => string;\n /**\n * Optional host projector mapping a record (and the requested detail tier) to\n * its agent-visible {@link IMemoryToolResultItem}. When supplied, every\n * `memory_search` / `memory_context` / `memory_read` result item is produced by\n * this callback — the host owns how much of the body a `'gist'` vs `'full'`\n * result carries, so it can bound the default (`'gist'`) path.\n *\n * When absent, the built-in default projection is used (full body plus the\n * {@link ICreateMemoryToolsParams.handleFor | handleFor} handle), which ignores\n * the detail tier — behavior is byte-identical to a build with no projector.\n *\n * The callback is guarded exactly like `handleFor`: a throw degrades to the\n * default full-body projection for that item rather than failing the whole\n * search.\n */\n readonly projectItem?: (record: IMemoryRecord<unknown>, detail: MemoryDetailTier) => IMemoryToolResultItem;\n}\n\n/**\n * The detail tier a `memory_search` / `memory_context` result is projected at.\n * `'gist'` is the default (bounded) path; `'full'` is opt-in. Only meaningful\n * when a host {@link ICreateMemoryToolsParams.projectItem | projectItem} is\n * supplied — the built-in default projection returns the full body regardless.\n * @public\n */\nexport type MemoryDetailTier = 'gist' | 'full';\n\n/** The resolved factory context threaded into each tool's `execute`. */\ninterface IToolContext {\n readonly store: IMemoryStore;\n readonly retriever: IMemoryRetriever;\n readonly registry: IBodyConverterRegistry;\n readonly kinds?: ReadonlyArray<Kind>;\n readonly codecs?: ReadonlyMap<Kind, IIdentityCodec>;\n readonly defaultCodec?: IIdentityCodec;\n readonly handleFor?: (record: IMemoryRecord<unknown>) => string;\n readonly projectItem?: (record: IMemoryRecord<unknown>, detail: MemoryDetailTier) => IMemoryToolResultItem;\n}\n\n// ---------------------------------------------------------------------------\n// Parameter schemas — authored once via JsonSchema.object(...); the schema IS\n// both the wire schema (.toJson()) and the runtime validator (.convert()).\n// NONE of these declare a `scope` (or scope-widening) property — the adoption\n// gate is enforced structurally and asserted in the tests.\n// ---------------------------------------------------------------------------\n\n/**\n * The scope-qualified target of a link edge authored by the agent on a write.\n * `scope` is optional: when omitted it defaults to the writing record's OWN\n * resolved scope (the common same-conversation case); supply it explicitly to\n * point an edge at a record in a different scope.\n */\n// eslint-disable-next-line @rushstack/typedef-var\nconst linkTargetSchema = JsonSchema.object({\n id: JsonSchema.string({ description: 'The MemoryId of the record this edge points at.' }),\n scope: JsonSchema.optional(\n JsonSchema.string({\n description: \"The target record's scope. Defaults to the writing record's own scope when omitted.\"\n })\n )\n});\n\n/** A single attributed link edge as authored by the agent on a write. */\n// eslint-disable-next-line @rushstack/typedef-var\nconst linkEdgeSchema = JsonSchema.object({\n type: JsonSchema.string({ description: 'The relation type of the link.' }),\n target: linkTargetSchema,\n confidence: JsonSchema.optional(JsonSchema.number({ description: 'Optional confidence in [0, 1].' }))\n});\n\n// eslint-disable-next-line @rushstack/typedef-var\nconst writeSchema = JsonSchema.object({\n kind: JsonSchema.string({ description: 'The record kind (must be an enabled, registered kind).' }),\n entityId: JsonSchema.string({\n description:\n 'The domain entity id. For composite (e.g. medium-term) kinds this is the full composite key.'\n }),\n body: JsonSchema.string({ description: \"The serialized record body; validated by the kind's converter.\" }),\n tags: JsonSchema.optional(JsonSchema.array(JsonSchema.string({ description: 'A tag label.' }))),\n links: JsonSchema.optional(JsonSchema.array(linkEdgeSchema))\n});\ntype WriteArgs = JsonSchema.Static<typeof writeSchema>;\n\n// eslint-disable-next-line @rushstack/typedef-var\nconst readSchema = JsonSchema.object({\n kind: JsonSchema.string({ description: 'The record kind.' }),\n entityId: JsonSchema.string({ description: 'The domain entity id to read.' }),\n detail: JsonSchema.optional(\n JsonSchema.enumOf(['gist', 'full'] as const, { description: \"'gist' | 'full' (default).\" })\n )\n});\n\n// eslint-disable-next-line @rushstack/typedef-var\nconst deleteSchema = JsonSchema.object({\n kind: JsonSchema.string({ description: 'The record kind.' }),\n entityId: JsonSchema.string({ description: 'The domain entity id to delete.' })\n});\n\n// eslint-disable-next-line @rushstack/typedef-var\nconst searchSchema = JsonSchema.object({\n kind: JsonSchema.optional(JsonSchema.string({ description: 'Restrict to this kind.' })),\n tag: JsonSchema.optional(JsonSchema.string({ description: 'Restrict to records carrying this tag.' })),\n semantic: JsonSchema.optional(\n JsonSchema.string({ description: 'Semantic query text (requires a semantic-capable retriever).' })\n ),\n limit: JsonSchema.optional(JsonSchema.integer({ description: 'Maximum number of results to return.' })),\n offset: JsonSchema.optional(\n JsonSchema.integer({ description: 'Number of results to skip after ordering, before limit. Default 0.' })\n ),\n detail: JsonSchema.optional(\n JsonSchema.enumOf(['gist', 'full'] as const, { description: \"'gist' (default) | 'full'.\" })\n )\n});\n\n/**\n * The scope-qualified seed a `memory_context` traversal starts from. Nested\n * `{ id, scope }` shape like a link target, but — unlike a write edge — there is\n * no writing record to default the scope from, so `scope` is REQUIRED to\n * disambiguate the seed across scopes (a bare stem like `turn-3` is otherwise\n * ambiguous). It is schema-required (not just runtime-required) so the wire\n * schema an LLM reads never advertises an optionality the tool does not honor.\n */\n// eslint-disable-next-line @rushstack/typedef-var\nconst contextSeedSchema = JsonSchema.object({\n id: JsonSchema.string({ description: 'The MemoryId of the seed record to traverse links from.' }),\n scope: JsonSchema.string({\n description: 'The scope of the seed record (required — a bare seed id is ambiguous across scopes).'\n })\n});\n\n// eslint-disable-next-line @rushstack/typedef-var\nconst contextSchema = JsonSchema.object({\n from: contextSeedSchema,\n kind: JsonSchema.optional(JsonSchema.string({ description: 'Restrict reached records to this kind.' })),\n tag: JsonSchema.optional(JsonSchema.string({ description: 'Restrict reached records carrying this tag.' })),\n hops: JsonSchema.optional(JsonSchema.integer({ description: 'BFS hop count (default 1).' })),\n limit: JsonSchema.optional(JsonSchema.integer({ description: 'Maximum number of results to return.' })),\n detail: JsonSchema.optional(\n JsonSchema.enumOf(['gist', 'full'] as const, { description: \"'gist' (default) | 'full'.\" })\n )\n});\n\n// ---------------------------------------------------------------------------\n// Behavior annotations (Component 4) — host-advisory hints; never serialized to\n// the model. openWorldHint is false throughout (a closed, local store).\n// ---------------------------------------------------------------------------\n\nconst READ_ONLY_ANNOTATIONS: AiAssist.IAiToolAnnotations = {\n readOnlyHint: true,\n openWorldHint: false\n};\n\nconst WRITE_ANNOTATIONS: AiAssist.IAiToolAnnotations = {\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false\n};\n\nconst DELETE_ANNOTATIONS: AiAssist.IAiToolAnnotations = {\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: false\n};\n\n// ---------------------------------------------------------------------------\n// Shared validation / projection helpers\n// ---------------------------------------------------------------------------\n\n/** Validate a `kind` string and assert it is an enabled, registered toolable kind. */\nfunction assertKindEnabled(ctx: IToolContext, kindStr: string): Result<Kind> {\n return Convert.kind.convert(kindStr).onSuccess((kind) => {\n if (!ctx.registry.has(kind)) {\n return fail(`memory tools: kind '${kind}' has no registered body converter`);\n }\n if (ctx.kinds !== undefined && !ctx.kinds.includes(kind)) {\n return fail(`memory tools: kind '${kind}' is not enabled for memory tools`);\n }\n return succeed(kind);\n });\n}\n\n/** Validate an optional `kind` string (enabled when present; `undefined` passes through). */\nfunction resolveOptionalKind(ctx: IToolContext, kindStr?: string): Result<Kind | undefined> {\n if (kindStr === undefined) {\n return succeed(undefined);\n }\n return assertKindEnabled(ctx, kindStr);\n}\n\n/**\n * Resolve the requested detail tier from the optional tool `detail` string.\n * `'full'` is the only opt-in value; every other input (absent, or an\n * unrecognized string) resolves safely to the bounded default `'gist'`.\n */\nfunction resolveDetail(detail?: string): MemoryDetailTier {\n return detail === 'full' ? 'full' : 'gist';\n}\n\n/** The built-in default projection: full body plus the guarded host handle. Ignores the detail tier. */\nfunction defaultProjectItem(ctx: IToolContext, record: IMemoryRecord<unknown>): IMemoryToolResultItem {\n // `handleFor` is a host callback; guard it so a throw degrades to the raw id rather than\n // escaping the Result chain (and crashing the whole search/context call).\n const handle =\n ctx.handleFor !== undefined\n ? captureResult(() => ctx.handleFor!(record)).orDefault(record.envelope.id)\n : record.envelope.id;\n return {\n handle,\n kind: record.envelope.kind,\n entityId: record.envelope.entityId,\n tags: record.envelope.tags,\n body: record.body\n };\n}\n\n/**\n * Project a record into an agent-visible result item at the requested detail\n * tier. When a host {@link ICreateMemoryToolsParams.projectItem | projectItem}\n * is supplied it owns the projection; otherwise the built-in\n * {@link defaultProjectItem} (full body) is used. The host callback is guarded\n * like `handleFor` — a throw degrades to the default full-body projection for\n * that item rather than failing the whole search/context call.\n */\nfunction projectItem(\n ctx: IToolContext,\n record: IMemoryRecord<unknown>,\n detail: MemoryDetailTier\n): IMemoryToolResultItem {\n if (ctx.projectItem === undefined) {\n return defaultProjectItem(ctx, record);\n }\n // Guard the host projector like `handleFor`: a throw degrades to the built-in\n // full-body projection (itself throw-safe) rather than escaping the chain.\n return captureResult(() => ctx.projectItem!(record, detail)).orDefault(defaultProjectItem(ctx, record));\n}\n\n/** Resolve the identity codec used by `memory_write` to derive the storage id. */\nfunction codecForWrite(ctx: IToolContext, kind: Kind): Result<IIdentityCodec> {\n const codec: IIdentityCodec | undefined = ctx.codecs?.get(kind) ?? ctx.defaultCodec;\n if (codec === undefined) {\n return fail(`memory_write: no identity codec available for kind '${kind}'`);\n }\n return succeed(codec);\n}\n\n/** Build the record to persist from validated write args (store stamps txn-time metadata). */\nfunction buildWriteRecord(\n typed: WriteArgs,\n kind: Kind,\n entityId: EntityId,\n idStem: string,\n sourceScope: string\n): Result<IMemoryRecord<unknown>> {\n // Plain shapes handed to `envelopeConverter`, which validates each field\n // (type → LinkType, target → { scope, id }) and produces the branded IEdge[].\n // An edge target with no explicit `scope` defaults to the writing record's own\n // resolved scope — the same-conversation case authors just an id.\n const links: ReadonlyArray<Record<string, unknown>> = (typed.links ?? []).map((link) => ({\n type: link.type,\n target: { scope: link.target.scope ?? sourceScope, id: link.target.id },\n ...(link.confidence !== undefined ? { confidence: link.confidence } : {})\n }));\n return envelopeConverter\n .convert({\n id: idStem,\n entityId,\n kind,\n tags: typed.tags ?? [],\n links,\n created: 0,\n updated: 0,\n seq: 0,\n contentHash: '',\n provenance: { source: 'agent' }\n })\n .withErrorFormat((msg) => `memory_write: invalid record: ${msg}`)\n .onSuccess((envelope) => succeed({ envelope, body: typed.body }));\n}\n\n/** Resolve the validated write args into the storage id + record (all synchronous). */\nfunction prepareWrite(\n ctx: IToolContext,\n typed: WriteArgs\n): Result<{ readonly kind: Kind; readonly entityId: EntityId; readonly record: IMemoryRecord<unknown> }> {\n return assertKindEnabled(ctx, typed.kind).onSuccess((kind) =>\n Convert.entityId.convert(typed.entityId).onSuccess((entityId) =>\n codecForWrite(ctx, kind).onSuccess((codec) =>\n codec.encode(entityId).onSuccess((addr) => {\n if (addr.isVersioned) {\n return fail(`memory_write: versioned/temporal kind '${kind}' is not supported`);\n }\n return buildWriteRecord(typed, kind, entityId, addr.idStem, addr.scope).onSuccess((record) =>\n succeed({ kind, entityId, record })\n );\n })\n )\n )\n );\n}\n\n/**\n * Discriminate the write outcome from a pre-put snapshot and the returned record.\n * A dedup no-op returns either a different entity (content-scope dedup) or the\n * same-id record with an unchanged `seq` (entity-scope re-put); a fresh write or\n * update advances `seq`.\n */\nfunction writeOutcome(\n before: IMemoryRecord<unknown> | undefined,\n written: IMemoryRecord<unknown>,\n entityId: EntityId\n): MemoryWriteOutcome {\n if (written.envelope.entityId !== entityId) {\n return 'deduped';\n }\n if (before !== undefined && written.envelope.seq === before.envelope.seq) {\n return 'deduped';\n }\n return 'written';\n}\n\n// ---------------------------------------------------------------------------\n// Tool builders — config with a JsonSchema.object(...) parametersSchema; execute\n// validates/narrows → delegates to store/retriever → returns the Result (never\n// swallowed), following the ts-extras-mcp adapter shape.\n//\n// Each `execute` re-runs its own `parametersSchema.convert(args)` even though the\n// `executeClientToolTurn` harness already validates against the same schema. This\n// is deliberate: the factory returns a heterogeneous `ReadonlyArray<IAiClientTool>`\n// (TParams erased to `unknown`, since the members carry different param shapes), so\n// `execute` receives `unknown` and must narrow it back to the typed args. The\n// re-validation is also the narrowing step exercised by the direct-call tests (which\n// invoke `execute` with raw args, bypassing the harness). Re-validating an\n// already-conforming shape is a cheap, side-effect-free identity.\n// ---------------------------------------------------------------------------\n\nfunction buildWriteTool(ctx: IToolContext): AiAssist.IAiClientTool {\n return {\n config: {\n type: 'client_tool',\n name: 'memory_write',\n description:\n 'Store a new memory record or update an existing one by (kind, entityId). ' +\n 'Identical content is a no-op that returns the existing record.',\n parametersSchema: writeSchema,\n annotations: WRITE_ANNOTATIONS\n },\n execute: async (args: unknown): Promise<Result<unknown>> =>\n writeSchema\n .convert(args)\n .withErrorFormat((msg) => `memory_write: invalid arguments: ${msg}`)\n .onSuccess((typed) => prepareWrite(ctx, typed))\n .thenOnSuccess(async ({ kind, entityId, record }) =>\n // Read the prior record to discriminate written-vs-deduped. A `Failure`\n // here (corrupt file, I/O error, codec round-trip failure) is a real\n // condition distinct from the \"not found\" success (`undefined`); propagate\n // it rather than defaulting it away, so a genuine store fault surfaces\n // instead of being masked as a normal write.\n (await ctx.store.get(kind, entityId)).thenOnSuccess(async (before) =>\n (await ctx.store.put(record)).onSuccess((persisted) =>\n succeed<IMemoryWriteResult>({\n outcome: writeOutcome(before, persisted, entityId),\n id: persisted.envelope.id,\n entityId,\n kind\n })\n )\n )\n )\n };\n}\n\nfunction buildReadTool(ctx: IToolContext): AiAssist.IAiClientTool {\n return {\n config: {\n type: 'client_tool',\n name: 'memory_read',\n description: 'Read a specific memory record by (kind, entityId).',\n parametersSchema: readSchema,\n annotations: READ_ONLY_ANNOTATIONS\n },\n execute: async (args: unknown): Promise<Result<unknown>> =>\n readSchema\n .convert(args)\n .withErrorFormat((msg) => `memory_read: invalid arguments: ${msg}`)\n .onSuccess((typed) =>\n assertKindEnabled(ctx, typed.kind).onSuccess((kind) =>\n Convert.entityId.convert(typed.entityId).onSuccess((entityId) => {\n // `memory_read` is the explicit drill-in path, so its detail default is\n // INVERTED vs search/context: `'full'` unless the caller opts down to `'gist'`.\n const detail: MemoryDetailTier = typed.detail === 'gist' ? 'gist' : 'full';\n return succeed({ kind, entityId, detail });\n })\n )\n )\n .thenOnSuccess(async ({ kind, entityId, detail }) =>\n (await ctx.store.get(kind, entityId)).onSuccess((record) =>\n record === undefined\n ? succeed({ found: false })\n : succeed({ found: true, item: projectItem(ctx, record, detail) })\n )\n )\n };\n}\n\nfunction buildSearchTool(ctx: IToolContext): AiAssist.IAiClientTool {\n return {\n config: {\n type: 'client_tool',\n name: 'memory_search',\n description: 'Search memories by tag, kind, or semantic text. Returns ranked results.',\n parametersSchema: searchSchema,\n annotations: READ_ONLY_ANNOTATIONS\n },\n execute: async (args: unknown): Promise<Result<unknown>> =>\n searchSchema\n .convert(args)\n .withErrorFormat((msg) => `memory_search: invalid arguments: ${msg}`)\n .onSuccess((typed) =>\n resolveOptionalKind(ctx, typed.kind).onSuccess((kind) =>\n resolveOptionalTag(typed.tag).onSuccess((tag) => succeed({ typed, kind, tag }))\n )\n )\n .thenOnSuccess(async ({ typed, kind, tag }) => {\n const detail: MemoryDetailTier = resolveDetail(typed.detail);\n const query: IMemoryQuery = {\n ...(kind !== undefined ? { kind } : {}),\n ...(tag !== undefined ? { tag } : {}),\n ...(typed.semantic !== undefined ? { semantic: typed.semantic } : {}),\n ...(typed.limit !== undefined ? { limit: typed.limit } : {}),\n ...(typed.offset !== undefined ? { offset: typed.offset } : {})\n };\n return (await ctx.retriever.retrieve(query)).onSuccess((records) =>\n succeed({ count: records.length, results: records.map((r) => projectItem(ctx, r, detail)) })\n );\n })\n };\n}\n\nfunction buildContextTool(ctx: IToolContext): AiAssist.IAiClientTool {\n return {\n config: {\n type: 'client_tool',\n name: 'memory_context',\n description:\n 'Build a context graph from a seed memory: returns the records linked from the seed, up to `hops` hops.',\n parametersSchema: contextSchema,\n annotations: READ_ONLY_ANNOTATIONS\n },\n execute: async (args: unknown): Promise<Result<unknown>> =>\n contextSchema\n .convert(args)\n .withErrorFormat((msg) => `memory_context: invalid arguments: ${msg}`)\n .onSuccess((typed) =>\n resolveContextSeed(typed.from).onSuccess((from) =>\n resolveOptionalKind(ctx, typed.kind).onSuccess((kind) =>\n resolveOptionalTag(typed.tag).onSuccess((tag) => succeed({ typed, from, kind, tag }))\n )\n )\n )\n .thenOnSuccess(async ({ typed, from, kind, tag }) => {\n const detail: MemoryDetailTier = resolveDetail(typed.detail);\n const query: IMemoryQuery = {\n linkedFrom: from,\n ...(kind !== undefined ? { kind } : {}),\n ...(tag !== undefined ? { tag } : {}),\n ...(typed.hops !== undefined ? { hops: typed.hops } : {}),\n ...(typed.limit !== undefined ? { limit: typed.limit } : {})\n };\n return (await ctx.retriever.retrieve(query)).onSuccess((records) =>\n succeed({\n seed: from,\n count: records.length,\n results: records.map((r) => projectItem(ctx, r, detail))\n })\n );\n })\n };\n}\n\nfunction buildDeleteTool(ctx: IToolContext): AiAssist.IAiClientTool {\n return {\n config: {\n type: 'client_tool',\n name: 'memory_delete',\n description: 'Delete a memory record by (kind, entityId). Destructive for non-temporal kinds.',\n parametersSchema: deleteSchema,\n annotations: DELETE_ANNOTATIONS\n },\n execute: async (args: unknown): Promise<Result<unknown>> =>\n deleteSchema\n .convert(args)\n .withErrorFormat((msg) => `memory_delete: invalid arguments: ${msg}`)\n .onSuccess((typed) =>\n assertKindEnabled(ctx, typed.kind).onSuccess((kind) =>\n Convert.entityId.convert(typed.entityId).onSuccess((entityId) => succeed({ kind, entityId }))\n )\n )\n .thenOnSuccess(async ({ kind, entityId }) =>\n (await ctx.store.delete(kind, entityId)).onSuccess((id) =>\n succeed({ deleted: true, id, entityId, kind })\n )\n )\n };\n}\n\n/**\n * Resolve a `memory_context` seed argument into a scope-qualified\n * {@link IEdgeTarget}. Both `id` and `scope` are present here — the tool's\n * `parametersSchema` ({@link contextSeedSchema}) makes `scope` schema-required —\n * so this only brands the two fields; a malformed value fails via the branded\n * converters (e.g. a path-unsafe seed id).\n */\nfunction resolveContextSeed(from: { readonly id: string; readonly scope: string }): Result<IEdgeTarget> {\n return Convert.memoryId\n .convert(from.id)\n .onSuccess((id) => Convert.scopeKey.convert(from.scope).onSuccess((scope) => succeed({ scope, id })));\n}\n\n/** Validate an optional `tag` string (`undefined` passes through). */\nfunction resolveOptionalTag(tagStr?: string): Result<Tag | undefined> {\n if (tagStr === undefined) {\n return succeed(undefined);\n }\n return Convert.tag.convert(tagStr);\n}\n\n/** Ordered registry of the five tool builders, keyed by name. */\nconst TOOL_BUILDERS: ReadonlyArray<{\n readonly name: MemoryToolName;\n readonly build: (ctx: IToolContext) => AiAssist.IAiClientTool;\n}> = [\n { name: 'memory_write', build: buildWriteTool },\n { name: 'memory_read', build: buildReadTool },\n { name: 'memory_search', build: buildSearchTool },\n { name: 'memory_context', build: buildContextTool },\n { name: 'memory_delete', build: buildDeleteTool }\n];\n\n/**\n * Build the selected suite of memory `AiAssist.IAiClientTool`s over a\n * pre-scoped store — ready to hand to `AiAssist.executeClientToolTurn` (and, via\n * the shared `JsonSchema.object(...)` schemas, `@fgv/ts-extras-mcp`).\n *\n * @remarks\n * **Scope isolation is make-or-break.** The returned tools close over the\n * pre-scoped {@link ICreateMemoryToolsParams.store | store}; no tool's\n * `parametersSchema` declares a `scope` (or any scope-widening) property, so an\n * LLM cannot steer a tool at another actor's memory. The store instance is the\n * sole scope authority.\n *\n * The default selection is {@link DEFAULT_MEMORY_TOOLS} (the read-only set) —\n * `memory_write` / `memory_delete` are included only when named in\n * {@link ICreateMemoryToolsParams.tools | tools}.\n * @public\n */\nexport function createMemoryTools(params: ICreateMemoryToolsParams): ReadonlyArray<AiAssist.IAiClientTool> {\n const ctx: IToolContext = {\n store: params.store,\n retriever: params.retriever,\n registry: params.registry,\n kinds: params.kinds,\n codecs: params.codecs,\n defaultCodec: params.defaultCodec,\n handleFor: params.handleFor,\n projectItem: params.projectItem\n };\n const selected: ReadonlySet<MemoryToolName> = new Set<MemoryToolName>(params.tools ?? DEFAULT_MEMORY_TOOLS);\n return TOOL_BUILDERS.filter((builder) => selected.has(builder.name)).map((builder) => builder.build(ctx));\n}\n"]}
1
+ {"version":3,"file":"memoryTools.js","sourceRoot":"","sources":["../../../src/packlets/tools/memoryTools.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, captureResult, fail, succeed } from '@fgv/ts-utils';\nimport { JsonSchema } from '@fgv/ts-json-base';\nimport { AiAssist } from '@fgv/ts-extras';\nimport { Convert, EntityId, IEdgeTarget, IIdentityCodec, IMemoryRecord, Kind, MemoryId, Tag } from '../types';\nimport { IBodyConverterRegistry, envelopeConverter } from '../converters';\nimport { IMemoryStore } from '../store';\nimport { IMemoryQuery, IMemoryRetriever } from '../retrieve';\n\n/**\n * The names of the five proof-set memory tools. A caller selects a subset via\n * {@link ICreateMemoryToolsParams.tools | tools}.\n * @public\n */\nexport type MemoryToolName =\n | 'memory_write'\n | 'memory_read'\n | 'memory_search'\n | 'memory_context'\n | 'memory_delete';\n\n/**\n * The default tool subset when {@link ICreateMemoryToolsParams.tools | tools} is\n * omitted: the read-only set. Mutating tools (`memory_write` / `memory_delete`)\n * are **off by default** and must be named explicitly — writes stay\n * curation-mediated unless the host opts in.\n * @public\n */\nexport const DEFAULT_MEMORY_TOOLS: ReadonlyArray<MemoryToolName> = ['memory_search', 'memory_context'];\n\n/**\n * Discriminates the outcome of a {@link createMemoryTools | memory_write} call so\n * the agent can reason about what its write actually did.\n *\n * @remarks\n * - `written` — a new record was persisted, or an existing entity was updated.\n * - `deduped` — the content already existed (content-hash dedup no-op); the\n * store returned the existing record unchanged.\n *\n * The store's public `put` return does not surface cap-cull evictions, so a\n * `culled` outcome is not distinguishable at this layer without an L1 change or\n * observer wiring (both out of scope for L2). The writer's own record is always\n * `written` even when the write triggers a cull of older siblings.\n * @public\n */\nexport type MemoryWriteOutcome = 'written' | 'deduped';\n\n/**\n * The success value returned by `memory_write.execute`.\n * @public\n */\nexport interface IMemoryWriteResult {\n /** What the write did — see {@link MemoryWriteOutcome}. */\n readonly outcome: MemoryWriteOutcome;\n /** The stored record's {@link MemoryId}. */\n readonly id: MemoryId;\n /** The domain {@link EntityId} the write targeted. */\n readonly entityId: EntityId;\n /** The record's {@link Kind}. */\n readonly kind: Kind;\n}\n\n/**\n * A single agent-visible search / context result item. The agent-facing key is\n * {@link IMemoryToolResultItem.handle | handle}: the host mnemonic when a\n * {@link ICreateMemoryToolsParams.handleFor | handleFor} hook is supplied, else\n * the raw {@link MemoryId}.\n * @public\n */\nexport interface IMemoryToolResultItem {\n /** The agent-visible key (host handle when supplied, raw {@link MemoryId} otherwise). */\n readonly handle: string;\n /** The record's {@link Kind}. */\n readonly kind: Kind;\n /** The record's domain {@link EntityId}. */\n readonly entityId: EntityId;\n /** The record's tags. */\n readonly tags: ReadonlyArray<string>;\n /** The record body (a markdown string in B1). */\n readonly body: unknown;\n}\n\n/**\n * Parameters for {@link createMemoryTools}.\n *\n * @remarks\n * **Scope isolation is constructor-fixed.** The {@link\n * ICreateMemoryToolsParams.store | store} is the sole scope authority — it is the\n * actor's own, pre-scoped memory root. No tool's `parametersSchema` declares a\n * `scope` (or any scope-widening) property, so an LLM cannot steer a tool at\n * another actor's memory.\n * @public\n */\nexport interface ICreateMemoryToolsParams {\n /**\n * The pre-scoped memory store (the actor's own memory root). Sole scope\n * authority — backs `memory_write` / `memory_read` / `memory_delete`.\n */\n readonly store: IMemoryStore;\n /** Retriever backing `memory_search` (and `memory_context` via link traversal). */\n readonly retriever: IMemoryRetriever;\n /** Body converter registry — gates the toolable kinds via `has(kind)`. */\n readonly registry: IBodyConverterRegistry;\n /**\n * The per-tool enable subset. Defaults to {@link DEFAULT_MEMORY_TOOLS} (the\n * read-only set). Name `memory_write` / `memory_delete` here to opt into the\n * mutating tools.\n */\n readonly tools?: ReadonlyArray<MemoryToolName>;\n /**\n * Optional whitelist of toolable kinds. When present, a tool `kind` argument\n * outside this set is rejected. When absent, {@link\n * IBodyConverterRegistry.has | registry.has} is the sole kind gate.\n */\n readonly kinds?: ReadonlyArray<Kind>;\n /**\n * Per-kind identity codecs, used by `memory_write` to map the domain\n * {@link EntityId} to the record's storage id (the store resolves codecs\n * internally for `get` / `delete`, so read / delete do not need them). Absent\n * → `memory_write` uses {@link ICreateMemoryToolsParams.defaultCodec |\n * defaultCodec}, and fails loudly for a kind with no resolvable codec. The\n * read-only default tool set needs no codecs.\n */\n readonly codecs?: ReadonlyMap<Kind, IIdentityCodec>;\n /** Default identity codec for kinds without an explicit {@link ICreateMemoryToolsParams.codecs | codecs} entry. */\n readonly defaultCodec?: IIdentityCodec;\n /**\n * Optional host hook mapping a record to its agent-visible handle (an evocative\n * mnemonic tag). When supplied, `memory_search` / `memory_context` results use\n * the returned handle as the agent-visible key; when absent the raw\n * {@link MemoryId} is used.\n */\n readonly handleFor?: (record: IMemoryRecord<unknown>) => string;\n /**\n * Optional host projector mapping a record (and the requested detail tier) to\n * its agent-visible {@link IMemoryToolResultItem}. When supplied, every\n * `memory_search` / `memory_context` / `memory_read` result item is produced by\n * this callback — the host owns how much of the body a `'gist'` vs `'full'`\n * result carries, so it can bound the default (`'gist'`) path.\n *\n * When absent, the built-in default projection is used (full body plus the\n * {@link ICreateMemoryToolsParams.handleFor | handleFor} handle), which ignores\n * the detail tier — behavior is byte-identical to a build with no projector.\n *\n * The callback is guarded exactly like `handleFor`: a throw degrades to the\n * default full-body projection for that item rather than failing the whole\n * search.\n */\n readonly projectItem?: (record: IMemoryRecord<unknown>, detail: MemoryDetailTier) => IMemoryToolResultItem;\n}\n\n/**\n * The detail tier a `memory_search` / `memory_context` result is projected at.\n * `'gist'` is the default (bounded) path; `'full'` is opt-in. Only meaningful\n * when a host {@link ICreateMemoryToolsParams.projectItem | projectItem} is\n * supplied — the built-in default projection returns the full body regardless.\n * @public\n */\nexport type MemoryDetailTier = 'gist' | 'full';\n\n/** The resolved factory context threaded into each tool's `execute`. */\ninterface IToolContext {\n readonly store: IMemoryStore;\n readonly retriever: IMemoryRetriever;\n readonly registry: IBodyConverterRegistry;\n readonly kinds?: ReadonlyArray<Kind>;\n readonly codecs?: ReadonlyMap<Kind, IIdentityCodec>;\n readonly defaultCodec?: IIdentityCodec;\n readonly handleFor?: (record: IMemoryRecord<unknown>) => string;\n readonly projectItem?: (record: IMemoryRecord<unknown>, detail: MemoryDetailTier) => IMemoryToolResultItem;\n}\n\n// ---------------------------------------------------------------------------\n// Parameter schemas — authored once via JsonSchema.object(...); the schema IS\n// both the wire schema (.toJson()) and the runtime validator (.convert()).\n// NONE of these declare a `scope` (or scope-widening) property — the adoption\n// gate is enforced structurally and asserted in the tests.\n// ---------------------------------------------------------------------------\n\n/**\n * The scope-qualified target of a link edge authored by the agent on a write.\n * `scope` is optional: when omitted it defaults to the writing record's OWN\n * resolved scope (the common same-conversation case); supply it explicitly to\n * point an edge at a record in a different scope.\n */\n// eslint-disable-next-line @rushstack/typedef-var\nconst linkTargetSchema = JsonSchema.object({\n id: JsonSchema.string({ description: 'The MemoryId of the record this edge points at.' }),\n scope: JsonSchema.optional(\n JsonSchema.string({\n description: \"The target record's scope. Defaults to the writing record's own scope when omitted.\"\n })\n )\n});\n\n/** A single attributed link edge as authored by the agent on a write. */\n// eslint-disable-next-line @rushstack/typedef-var\nconst linkEdgeSchema = JsonSchema.object({\n type: JsonSchema.string({ description: 'The relation type of the link.' }),\n target: linkTargetSchema,\n confidence: JsonSchema.optional(JsonSchema.number({ description: 'Optional confidence in [0, 1].' }))\n});\n\n// eslint-disable-next-line @rushstack/typedef-var\nconst writeSchema = JsonSchema.object({\n kind: JsonSchema.string({ description: 'The record kind (must be an enabled, registered kind).' }),\n entityId: JsonSchema.string({\n description:\n 'The domain entity id. For composite (e.g. medium-term) kinds this is the full composite key.'\n }),\n body: JsonSchema.string({ description: \"The serialized record body; validated by the kind's converter.\" }),\n tags: JsonSchema.optional(JsonSchema.array(JsonSchema.string({ description: 'A tag label.' }))),\n links: JsonSchema.optional(JsonSchema.array(linkEdgeSchema))\n});\ntype WriteArgs = JsonSchema.Static<typeof writeSchema>;\n\n// eslint-disable-next-line @rushstack/typedef-var\nconst readSchema = JsonSchema.object({\n kind: JsonSchema.string({ description: 'The record kind.' }),\n entityId: JsonSchema.string({ description: 'The domain entity id to read.' }),\n detail: JsonSchema.optional(\n JsonSchema.enumOf(['gist', 'full'] as const, { description: \"'gist' | 'full' (default).\" })\n )\n});\n\n// eslint-disable-next-line @rushstack/typedef-var\nconst deleteSchema = JsonSchema.object({\n kind: JsonSchema.string({ description: 'The record kind.' }),\n entityId: JsonSchema.string({ description: 'The domain entity id to delete.' })\n});\n\n// eslint-disable-next-line @rushstack/typedef-var\nconst searchSchema = JsonSchema.object({\n kind: JsonSchema.optional(JsonSchema.string({ description: 'Restrict to this kind.' })),\n tag: JsonSchema.optional(JsonSchema.string({ description: 'Restrict to records carrying this tag.' })),\n semantic: JsonSchema.optional(\n JsonSchema.string({ description: 'Semantic query text (requires a semantic-capable retriever).' })\n ),\n limit: JsonSchema.optional(\n JsonSchema.integer({\n description:\n 'Maximum number of results to return. At least one of kind, tag, semantic or limit is required.'\n })\n ),\n offset: JsonSchema.optional(\n JsonSchema.integer({ description: 'Number of results to skip after ordering, before limit. Default 0.' })\n ),\n detail: JsonSchema.optional(\n JsonSchema.enumOf(['gist', 'full'] as const, { description: \"'gist' (default) | 'full'.\" })\n )\n});\n\n/**\n * The scope-qualified seed a `memory_context` traversal starts from. Nested\n * `{ id, scope }` shape like a link target, but — unlike a write edge — there is\n * no writing record to default the scope from, so `scope` is REQUIRED to\n * disambiguate the seed across scopes (a bare stem like `turn-3` is otherwise\n * ambiguous). It is schema-required (not just runtime-required) so the wire\n * schema an LLM reads never advertises an optionality the tool does not honor.\n */\n// eslint-disable-next-line @rushstack/typedef-var\nconst contextSeedSchema = JsonSchema.object({\n id: JsonSchema.string({ description: 'The MemoryId of the seed record to traverse links from.' }),\n scope: JsonSchema.string({\n description: 'The scope of the seed record (required — a bare seed id is ambiguous across scopes).'\n })\n});\n\n// eslint-disable-next-line @rushstack/typedef-var\nconst contextSchema = JsonSchema.object({\n from: contextSeedSchema,\n kind: JsonSchema.optional(JsonSchema.string({ description: 'Restrict reached records to this kind.' })),\n tag: JsonSchema.optional(JsonSchema.string({ description: 'Restrict reached records carrying this tag.' })),\n hops: JsonSchema.optional(JsonSchema.integer({ description: 'BFS hop count (default 1).' })),\n limit: JsonSchema.optional(JsonSchema.integer({ description: 'Maximum number of results to return.' })),\n detail: JsonSchema.optional(\n JsonSchema.enumOf(['gist', 'full'] as const, { description: \"'gist' (default) | 'full'.\" })\n )\n});\n\n// ---------------------------------------------------------------------------\n// Behavior annotations (Component 4) — host-advisory hints; never serialized to\n// the model. openWorldHint is false throughout (a closed, local store).\n// ---------------------------------------------------------------------------\n\nconst READ_ONLY_ANNOTATIONS: AiAssist.IAiToolAnnotations = {\n readOnlyHint: true,\n openWorldHint: false\n};\n\nconst WRITE_ANNOTATIONS: AiAssist.IAiToolAnnotations = {\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false\n};\n\nconst DELETE_ANNOTATIONS: AiAssist.IAiToolAnnotations = {\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: false\n};\n\n// ---------------------------------------------------------------------------\n// Shared validation / projection helpers\n// ---------------------------------------------------------------------------\n\n/** Validate a `kind` string and assert it is an enabled, registered toolable kind. */\nfunction assertKindEnabled(ctx: IToolContext, kindStr: string): Result<Kind> {\n return Convert.kind.convert(kindStr).onSuccess((kind) => {\n if (!ctx.registry.has(kind)) {\n return fail(`memory tools: kind '${kind}' has no registered body converter`);\n }\n if (ctx.kinds !== undefined && !ctx.kinds.includes(kind)) {\n return fail(`memory tools: kind '${kind}' is not enabled for memory tools`);\n }\n return succeed(kind);\n });\n}\n\n/** Validate an optional `kind` string (enabled when present; `undefined` passes through). */\nfunction resolveOptionalKind(ctx: IToolContext, kindStr?: string): Result<Kind | undefined> {\n if (kindStr === undefined) {\n return succeed(undefined);\n }\n return assertKindEnabled(ctx, kindStr);\n}\n\n/**\n * Resolve the requested detail tier from the optional tool `detail` string.\n * `'full'` is the only opt-in value; every other input (absent, or an\n * unrecognized string) resolves safely to the bounded default `'gist'`.\n */\nfunction resolveDetail(detail?: string): MemoryDetailTier {\n return detail === 'full' ? 'full' : 'gist';\n}\n\n/** The built-in default projection: full body plus the guarded host handle. Ignores the detail tier. */\nfunction defaultProjectItem(ctx: IToolContext, record: IMemoryRecord<unknown>): IMemoryToolResultItem {\n // `handleFor` is a host callback; guard it so a throw degrades to the raw id rather than\n // escaping the Result chain (and crashing the whole search/context call).\n const handle =\n ctx.handleFor !== undefined\n ? captureResult(() => ctx.handleFor!(record)).orDefault(record.envelope.id)\n : record.envelope.id;\n return {\n handle,\n kind: record.envelope.kind,\n entityId: record.envelope.entityId,\n tags: record.envelope.tags,\n body: record.body\n };\n}\n\n/**\n * Project a record into an agent-visible result item at the requested detail\n * tier. When a host {@link ICreateMemoryToolsParams.projectItem | projectItem}\n * is supplied it owns the projection; otherwise the built-in\n * {@link defaultProjectItem} (full body) is used. The host callback is guarded\n * like `handleFor` — a throw degrades to the default full-body projection for\n * that item rather than failing the whole search/context call.\n */\nfunction projectItem(\n ctx: IToolContext,\n record: IMemoryRecord<unknown>,\n detail: MemoryDetailTier\n): IMemoryToolResultItem {\n if (ctx.projectItem === undefined) {\n return defaultProjectItem(ctx, record);\n }\n // Guard the host projector like `handleFor`: a throw degrades to the built-in\n // full-body projection (itself throw-safe) rather than escaping the chain.\n return captureResult(() => ctx.projectItem!(record, detail)).orDefault(defaultProjectItem(ctx, record));\n}\n\n/** Resolve the identity codec used by `memory_write` to derive the storage id. */\nfunction codecForWrite(ctx: IToolContext, kind: Kind): Result<IIdentityCodec> {\n const codec: IIdentityCodec | undefined = ctx.codecs?.get(kind) ?? ctx.defaultCodec;\n if (codec === undefined) {\n return fail(`memory_write: no identity codec available for kind '${kind}'`);\n }\n return succeed(codec);\n}\n\n/** Build the record to persist from validated write args (store stamps txn-time metadata). */\nfunction buildWriteRecord(\n typed: WriteArgs,\n kind: Kind,\n entityId: EntityId,\n idStem: string,\n sourceScope: string\n): Result<IMemoryRecord<unknown>> {\n // Plain shapes handed to `envelopeConverter`, which validates each field\n // (type → LinkType, target → { scope, id }) and produces the branded IEdge[].\n // An edge target with no explicit `scope` defaults to the writing record's own\n // resolved scope — the same-conversation case authors just an id.\n const links: ReadonlyArray<Record<string, unknown>> = (typed.links ?? []).map((link) => ({\n type: link.type,\n target: { scope: link.target.scope ?? sourceScope, id: link.target.id },\n ...(link.confidence !== undefined ? { confidence: link.confidence } : {})\n }));\n return envelopeConverter\n .convert({\n id: idStem,\n entityId,\n kind,\n tags: typed.tags ?? [],\n links,\n created: 0,\n updated: 0,\n seq: 0,\n contentHash: '',\n provenance: { source: 'agent' }\n })\n .withErrorFormat((msg) => `memory_write: invalid record: ${msg}`)\n .onSuccess((envelope) => succeed({ envelope, body: typed.body }));\n}\n\n/** Resolve the validated write args into the storage id + record (all synchronous). */\nfunction prepareWrite(\n ctx: IToolContext,\n typed: WriteArgs\n): Result<{ readonly kind: Kind; readonly entityId: EntityId; readonly record: IMemoryRecord<unknown> }> {\n return assertKindEnabled(ctx, typed.kind).onSuccess((kind) =>\n Convert.entityId.convert(typed.entityId).onSuccess((entityId) =>\n codecForWrite(ctx, kind).onSuccess((codec) =>\n codec.encode(entityId).onSuccess((addr) => {\n if (addr.isVersioned) {\n return fail(`memory_write: versioned/temporal kind '${kind}' is not supported`);\n }\n return buildWriteRecord(typed, kind, entityId, addr.idStem, addr.scope).onSuccess((record) =>\n succeed({ kind, entityId, record })\n );\n })\n )\n )\n );\n}\n\n/**\n * Discriminate the write outcome from a pre-put snapshot and the returned record.\n * A dedup no-op returns either a different entity (content-scope dedup) or the\n * same-id record with an unchanged `seq` (entity-scope re-put); a fresh write or\n * update advances `seq`.\n */\nfunction writeOutcome(\n before: IMemoryRecord<unknown> | undefined,\n written: IMemoryRecord<unknown>,\n entityId: EntityId\n): MemoryWriteOutcome {\n if (written.envelope.entityId !== entityId) {\n return 'deduped';\n }\n if (before !== undefined && written.envelope.seq === before.envelope.seq) {\n return 'deduped';\n }\n return 'written';\n}\n\n// ---------------------------------------------------------------------------\n// Tool builders — config with a JsonSchema.object(...) parametersSchema; execute\n// validates/narrows → delegates to store/retriever → returns the Result (never\n// swallowed), following the ts-extras-mcp adapter shape.\n//\n// Each `execute` re-runs its own `parametersSchema.convert(args)` even though the\n// `executeClientToolTurn` harness already validates against the same schema. This\n// is deliberate: the factory returns a heterogeneous `ReadonlyArray<IAiClientTool>`\n// (TParams erased to `unknown`, since the members carry different param shapes), so\n// `execute` receives `unknown` and must narrow it back to the typed args. The\n// re-validation is also the narrowing step exercised by the direct-call tests (which\n// invoke `execute` with raw args, bypassing the harness). Re-validating an\n// already-conforming shape is a cheap, side-effect-free identity.\n// ---------------------------------------------------------------------------\n\nfunction buildWriteTool(ctx: IToolContext): AiAssist.IAiClientTool {\n return {\n config: {\n type: 'client_tool',\n name: 'memory_write',\n description:\n 'Store a new memory record or update an existing one by (kind, entityId). ' +\n 'Identical content is a no-op that returns the existing record.',\n parametersSchema: writeSchema,\n annotations: WRITE_ANNOTATIONS\n },\n execute: async (args: unknown): Promise<Result<unknown>> =>\n writeSchema\n .convert(args)\n .withErrorFormat((msg) => `memory_write: invalid arguments: ${msg}`)\n .onSuccess((typed) => prepareWrite(ctx, typed))\n .thenOnSuccess(async ({ kind, entityId, record }) =>\n // Read the prior record to discriminate written-vs-deduped. A `Failure`\n // here (corrupt file, I/O error, codec round-trip failure) is a real\n // condition distinct from the \"not found\" success (`undefined`); propagate\n // it rather than defaulting it away, so a genuine store fault surfaces\n // instead of being masked as a normal write.\n (await ctx.store.get(kind, entityId)).thenOnSuccess(async (before) =>\n (await ctx.store.put(record)).onSuccess((persisted) =>\n succeed<IMemoryWriteResult>({\n outcome: writeOutcome(before, persisted, entityId),\n id: persisted.envelope.id,\n entityId,\n kind\n })\n )\n )\n )\n };\n}\n\nfunction buildReadTool(ctx: IToolContext): AiAssist.IAiClientTool {\n return {\n config: {\n type: 'client_tool',\n name: 'memory_read',\n description: 'Read a specific memory record by (kind, entityId).',\n parametersSchema: readSchema,\n annotations: READ_ONLY_ANNOTATIONS\n },\n execute: async (args: unknown): Promise<Result<unknown>> =>\n readSchema\n .convert(args)\n .withErrorFormat((msg) => `memory_read: invalid arguments: ${msg}`)\n .onSuccess((typed) =>\n assertKindEnabled(ctx, typed.kind).onSuccess((kind) =>\n Convert.entityId.convert(typed.entityId).onSuccess((entityId) => {\n // `memory_read` is the explicit drill-in path, so its detail default is\n // INVERTED vs search/context: `'full'` unless the caller opts down to `'gist'`.\n const detail: MemoryDetailTier = typed.detail === 'gist' ? 'gist' : 'full';\n return succeed({ kind, entityId, detail });\n })\n )\n )\n .thenOnSuccess(async ({ kind, entityId, detail }) =>\n (await ctx.store.get(kind, entityId)).onSuccess((record) =>\n record === undefined\n ? succeed({ found: false })\n : succeed({ found: true, item: projectItem(ctx, record, detail) })\n )\n )\n };\n}\n\nfunction buildSearchTool(ctx: IToolContext): AiAssist.IAiClientTool {\n return {\n config: {\n type: 'client_tool',\n name: 'memory_search',\n description:\n 'Search memories by tag, kind, or semantic text. Returns ranked results. ' +\n 'At least one of kind, tag, semantic or limit must be supplied.',\n parametersSchema: searchSchema,\n annotations: READ_ONLY_ANNOTATIONS\n },\n execute: async (args: unknown): Promise<Result<unknown>> =>\n searchSchema\n .convert(args)\n .withErrorFormat((msg) => `memory_search: invalid arguments: ${msg}`)\n .onSuccess((typed) =>\n resolveOptionalKind(ctx, typed.kind).onSuccess((kind) =>\n resolveOptionalTag(typed.tag).onSuccess((tag) => succeed({ typed, kind, tag }))\n )\n )\n .onSuccess(({ typed, kind, tag }) => {\n // A search with no axis at all is a whole-vault read issued by a model,\n // and since the index holds envelopes only it materializes every body\n // to answer. An LLM asking for \"everything\" is nearly always an\n // under-specified query rather than an intended full scan, so this\n // refuses instead of serving it — the model can retry with an axis,\n // which is the outcome we want anyway.\n //\n // `limit` counts: an ordered top-N materializes N records rather than\n // the vault (see `resolveQuery`), so it bounds the read as genuinely as\n // `kind` or `tag` does.\n if (\n kind === undefined &&\n tag === undefined &&\n typed.semantic === undefined &&\n typed.limit === undefined\n ) {\n return fail<{ typed: typeof typed; kind: typeof kind; tag: typeof tag }>(\n 'memory_search: supply at least one of kind, tag, semantic or limit — ' +\n 'an unrestricted search reads every record in the vault'\n );\n }\n return succeed({ typed, kind, tag });\n })\n .thenOnSuccess(async ({ typed, kind, tag }) => {\n const detail: MemoryDetailTier = resolveDetail(typed.detail);\n const query: IMemoryQuery = {\n ...(kind !== undefined ? { kind } : {}),\n ...(tag !== undefined ? { tag } : {}),\n ...(typed.semantic !== undefined ? { semantic: typed.semantic } : {}),\n ...(typed.limit !== undefined ? { limit: typed.limit } : {}),\n ...(typed.offset !== undefined ? { offset: typed.offset } : {})\n };\n return (await ctx.retriever.retrieve(query)).onSuccess((records) =>\n succeed({ count: records.length, results: records.map((r) => projectItem(ctx, r, detail)) })\n );\n })\n };\n}\n\nfunction buildContextTool(ctx: IToolContext): AiAssist.IAiClientTool {\n return {\n config: {\n type: 'client_tool',\n name: 'memory_context',\n description:\n 'Build a context graph from a seed memory: returns the records linked from the seed, up to `hops` hops.',\n parametersSchema: contextSchema,\n annotations: READ_ONLY_ANNOTATIONS\n },\n execute: async (args: unknown): Promise<Result<unknown>> =>\n contextSchema\n .convert(args)\n .withErrorFormat((msg) => `memory_context: invalid arguments: ${msg}`)\n .onSuccess((typed) =>\n resolveContextSeed(typed.from).onSuccess((from) =>\n resolveOptionalKind(ctx, typed.kind).onSuccess((kind) =>\n resolveOptionalTag(typed.tag).onSuccess((tag) => succeed({ typed, from, kind, tag }))\n )\n )\n )\n .thenOnSuccess(async ({ typed, from, kind, tag }) => {\n const detail: MemoryDetailTier = resolveDetail(typed.detail);\n const query: IMemoryQuery = {\n linkedFrom: from,\n ...(kind !== undefined ? { kind } : {}),\n ...(tag !== undefined ? { tag } : {}),\n ...(typed.hops !== undefined ? { hops: typed.hops } : {}),\n ...(typed.limit !== undefined ? { limit: typed.limit } : {})\n };\n return (await ctx.retriever.retrieve(query)).onSuccess((records) =>\n succeed({\n seed: from,\n count: records.length,\n results: records.map((r) => projectItem(ctx, r, detail))\n })\n );\n })\n };\n}\n\nfunction buildDeleteTool(ctx: IToolContext): AiAssist.IAiClientTool {\n return {\n config: {\n type: 'client_tool',\n name: 'memory_delete',\n description: 'Delete a memory record by (kind, entityId). Destructive for non-temporal kinds.',\n parametersSchema: deleteSchema,\n annotations: DELETE_ANNOTATIONS\n },\n execute: async (args: unknown): Promise<Result<unknown>> =>\n deleteSchema\n .convert(args)\n .withErrorFormat((msg) => `memory_delete: invalid arguments: ${msg}`)\n .onSuccess((typed) =>\n assertKindEnabled(ctx, typed.kind).onSuccess((kind) =>\n Convert.entityId.convert(typed.entityId).onSuccess((entityId) => succeed({ kind, entityId }))\n )\n )\n .thenOnSuccess(async ({ kind, entityId }) =>\n (await ctx.store.delete(kind, entityId)).onSuccess((id) =>\n succeed({ deleted: true, id, entityId, kind })\n )\n )\n };\n}\n\n/**\n * Resolve a `memory_context` seed argument into a scope-qualified\n * {@link IEdgeTarget}. Both `id` and `scope` are present here — the tool's\n * `parametersSchema` ({@link contextSeedSchema}) makes `scope` schema-required —\n * so this only brands the two fields; a malformed value fails via the branded\n * converters (e.g. a path-unsafe seed id).\n */\nfunction resolveContextSeed(from: { readonly id: string; readonly scope: string }): Result<IEdgeTarget> {\n return Convert.memoryId\n .convert(from.id)\n .onSuccess((id) => Convert.scopeKey.convert(from.scope).onSuccess((scope) => succeed({ scope, id })));\n}\n\n/** Validate an optional `tag` string (`undefined` passes through). */\nfunction resolveOptionalTag(tagStr?: string): Result<Tag | undefined> {\n if (tagStr === undefined) {\n return succeed(undefined);\n }\n return Convert.tag.convert(tagStr);\n}\n\n/** Ordered registry of the five tool builders, keyed by name. */\nconst TOOL_BUILDERS: ReadonlyArray<{\n readonly name: MemoryToolName;\n readonly build: (ctx: IToolContext) => AiAssist.IAiClientTool;\n}> = [\n { name: 'memory_write', build: buildWriteTool },\n { name: 'memory_read', build: buildReadTool },\n { name: 'memory_search', build: buildSearchTool },\n { name: 'memory_context', build: buildContextTool },\n { name: 'memory_delete', build: buildDeleteTool }\n];\n\n/**\n * Build the selected suite of memory `AiAssist.IAiClientTool`s over a\n * pre-scoped store — ready to hand to `AiAssist.executeClientToolTurn` (and, via\n * the shared `JsonSchema.object(...)` schemas, `@fgv/ts-extras-mcp`).\n *\n * @remarks\n * **Scope isolation is make-or-break.** The returned tools close over the\n * pre-scoped {@link ICreateMemoryToolsParams.store | store}; no tool's\n * `parametersSchema` declares a `scope` (or any scope-widening) property, so an\n * LLM cannot steer a tool at another actor's memory. The store instance is the\n * sole scope authority.\n *\n * The default selection is {@link DEFAULT_MEMORY_TOOLS} (the read-only set) —\n * `memory_write` / `memory_delete` are included only when named in\n * {@link ICreateMemoryToolsParams.tools | tools}.\n * @public\n */\nexport function createMemoryTools(params: ICreateMemoryToolsParams): ReadonlyArray<AiAssist.IAiClientTool> {\n const ctx: IToolContext = {\n store: params.store,\n retriever: params.retriever,\n registry: params.registry,\n kinds: params.kinds,\n codecs: params.codecs,\n defaultCodec: params.defaultCodec,\n handleFor: params.handleFor,\n projectItem: params.projectItem\n };\n const selected: ReadonlySet<MemoryToolName> = new Set<MemoryToolName>(params.tools ?? DEFAULT_MEMORY_TOOLS);\n return TOOL_BUILDERS.filter((builder) => selected.has(builder.name)).map((builder) => builder.build(ctx));\n}\n"]}
@@ -15,4 +15,29 @@
15
15
  export function edgeTargetKey(target) {
16
16
  return `${target.scope}\0${target.id}`;
17
17
  }
18
+ /**
19
+ * The envelope's embedding reference if it carries a usable one, `undefined`
20
+ * otherwise — **the one place `null`-vs-absent is collapsed.**
21
+ *
22
+ * @remarks
23
+ * {@link IMemoryEnvelope.embeddingRef} is `string | null | undefined`, where
24
+ * `null` and absent both mean *not embedded* (`null` is the explicit sentinel;
25
+ * absent is the backwards-compat seam). That makes the obvious presence check
26
+ * wrong in **both** directions, and both mistakes were shipped before this
27
+ * accessor existed: `!== undefined` counts a `null` as an embedding that is not
28
+ * there, and `=== undefined` misses a `null` when looking for one that is
29
+ * missing. Neither is a type error, and neither is visible to a coverage gate,
30
+ * because the sentinel is a *value* rather than a branch.
31
+ *
32
+ * Returning the reference rather than a boolean is deliberate: a caller that
33
+ * needs the string gets the check for free, so there is no second, weaker way
34
+ * to ask.
35
+ *
36
+ * **Do not test `embeddingRef` for presence directly — call this.**
37
+ * @public
38
+ */
39
+ export function embeddingRefOf(envelope) {
40
+ var _a;
41
+ return (_a = envelope.embeddingRef) !== null && _a !== void 0 ? _a : undefined;
42
+ }
18
43
  //# sourceMappingURL=envelope.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../../src/packlets/types/envelope.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAyDH;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,MAAmB;IAC/C,OAAO,GAAG,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC;AACzC,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { EntityId, Kind, LinkType, MemoryId, MemoryScopeKey, Tag } from './ids';\n\n/**\n * Origin of a provenance attribution. Open vocabulary: the three named\n * sources are conventional, but the `(string & {})` arm admits any other\n * source string without resignature.\n * @public\n */\nexport type ProvenanceSource = 'agent' | 'host-ingest' | 'human' | (string & {});\n\n/**\n * Structured provenance for a record or an edge. Never a flat enum — the\n * `[key: string]: unknown` index signature lets a consumer attach an opaque\n * domain payload (e.g. PersonAIlity's sentiment / epistemic blocks) without\n * changing this interface, while still satisfying the no-`any` rule.\n * @public\n */\nexport interface IProvenance {\n /** Where the attribution came from. */\n readonly source: ProvenanceSource;\n /** Optional human or agent identifier responsible for the write. */\n readonly by?: string;\n /** Optional model identifier, when a model produced the content. */\n readonly model?: string;\n /** Optional confidence in `[0, 1]`. */\n readonly confidence?: number;\n /**\n * Scope-qualified back-link to the source record. Enables the cross-kind\n * provenance spine. A scope-qualified {@link IEdgeTarget} (not a bare\n * {@link MemoryId}) because per-scope codecs (e.g. the MTM codec's `turn-<n>`\n * stems) legally reuse a stem across scopes, so a bare id would be ambiguous —\n * the same reason {@link IEdge.target} is scope-qualified.\n */\n readonly derivedFrom?: IEdgeTarget;\n /** Opaque extension payload — consumer-owned, never interpreted by the store. */\n readonly [key: string]: unknown;\n}\n\n/**\n * The physical address of a linked-to record: the `(scope, id)` pair that\n * uniquely identifies it. Both components are required because a bare\n * {@link MemoryId} is NOT unique across scopes — per-scope codecs (e.g. the\n * medium-term codec's `turn-<n>` stems) legally mint the same stem under\n * different scopes, so an edge that carried only the id would be ambiguous.\n * `(scope, id)` matches the store's `getById(scope, id)` addressing and the\n * index's composite primary key.\n * @public\n */\nexport interface IEdgeTarget {\n /** The scope the target record lives under. */\n readonly scope: MemoryScopeKey;\n /** The target record's stable file-stem id (unique WITHIN {@link IEdgeTarget.scope}). */\n readonly id: MemoryId;\n}\n\n/**\n * The canonical composite-key string for an {@link IEdgeTarget}: scope + id,\n * NUL-separated. NUL is excluded from both components (scope segments are\n * filename-safe; {@link MemoryId} is portable-filename-safe), so it is a\n * collision-proof separator. This is the ONE canonicalization every consumer\n * that keys on a scoped target uses — the backlink index, the cycle guard, and\n * the ingest edge-validation path all route through it so their notions of\n * \"same target\" cannot drift.\n * @public\n */\nexport function edgeTargetKey(target: IEdgeTarget): string {\n return `${target.scope}\\0${target.id}`;\n}\n\n/**\n * An attributed link between two records. Carries the relation type, the\n * scope-qualified {@link IEdgeTarget | target}, and optional confidence /\n * provenance / world-truth validity. Replaces bare string references (e.g.\n * PersonAIlity's `IMtmRef` becomes an `IEdge` with `type: LinkType('mtm-ref')`).\n * @public\n */\nexport interface IEdge {\n /** Open-vocabulary relation type. */\n readonly type: LinkType;\n /** The scope-qualified address of the linked-to record. */\n readonly target: IEdgeTarget;\n /** Optional confidence in `[0, 1]`. */\n readonly confidence?: number;\n /** Optional structured provenance for the link itself. */\n readonly provenance?: IProvenance;\n /** World-truth validity start (epoch ms). Present only on temporal edges. */\n readonly valid_at?: number;\n /**\n * World-truth validity end (epoch ms). `null` = still valid; absent = no\n * temporal extent.\n */\n // eslint-disable-next-line @rushstack/no-new-null -- null is a meaningful value here (still-valid) distinct from absent (no temporal extent); design-lock §2.3\n readonly invalid_at?: number | null;\n}\n\n/**\n * Optional bi-temporal validity block on an envelope. Present only on\n * temporal kinds; absent = zero cost for atemporal kinds.\n * @public\n */\nexport interface ITemporalBlock {\n /** World-truth validity start (epoch ms). */\n readonly valid_at?: number;\n /** World-truth validity end (epoch ms). `null` = still valid. */\n // eslint-disable-next-line @rushstack/no-new-null -- null is a meaningful value here (still-valid) distinct from absent; design-lock §2.4\n readonly invalid_at?: number | null;\n}\n\n/**\n * The invariant identity + transaction-time envelope carried by every memory\n * record, independent of the per-kind body.\n * @public\n */\nexport interface IMemoryEnvelope {\n // --- Core identity ---\n /** Stable file-stem identifier. MUST equal the on-disk filename stem. */\n readonly id: MemoryId;\n /** Consumer-supplied domain key. Equals {@link IMemoryEnvelope.id | id} for non-temporal kinds. */\n readonly entityId: EntityId;\n /** Consumer-registered kind; dispatches the body Converter. */\n readonly kind: Kind;\n /** Open-vocabulary tags. */\n readonly tags: ReadonlyArray<Tag>;\n /** Attributed outbound edges. */\n readonly links: ReadonlyArray<IEdge>;\n\n // --- Transaction-time metadata (always present) ---\n /** Epoch ms of the first write. Immutable after creation. */\n readonly created: number;\n /** Epoch ms of the most recent write. */\n readonly updated: number;\n /**\n * Monotonic write counter within the store instance, assigned by the store\n * on every successful put. Enables stable cursor paging over observation\n * records without a full walk.\n */\n readonly seq: number;\n /**\n * Content hash over the canonical `{ kind, body, links }`. The dedup key:\n * an exact match is a no-op upsert that returns the existing record.\n */\n readonly contentHash: string;\n /**\n * Store-computed host-defined ordering value, produced by the kind's\n * {@link RankProjector} on every put/update and stamped into the envelope in\n * the same pass that recomputes {@link IMemoryEnvelope.contentHash | contentHash}.\n * Absent when the kind has no registered projector (or the projector threw on\n * this record). Ordered retrieval (`orderBy: 'rank'`) and the index's rank view\n * sort by this value descending, placing records with an absent `rank` last.\n *\n * **The projector runs on the write path only — registering one does not rank\n * records already in the store.** Because absent sorts last, those records land\n * below every subsequently-written one regardless of what the projector would\n * have scored them, so the result is not a partial ordering but one inverted\n * with respect to the projector's intent, with nothing failing to say so. Call\n * `IMemoryStore.reconcileRank` after registering a projector against a\n * populated store.\n */\n readonly rank?: number;\n /** Structured provenance (never a flat enum). */\n readonly provenance: IProvenance;\n\n // --- Optional temporal block ---\n /** Bi-temporal validity. Present only on temporal kinds. */\n readonly temporal?: ITemporalBlock;\n\n /**\n * Vector-index entry reference, set by the vector index on write. `null` =\n * not embedded; absent = same as `null` (backwards-compat seam).\n */\n // eslint-disable-next-line @rushstack/no-new-null -- null is the explicit \"not embedded\" sentinel distinct from absent (backwards-compat seam); design-lock §2.5\n readonly embeddingRef?: string | null;\n}\n\n/**\n * A complete memory record: the invariant {@link IMemoryEnvelope} plus the\n * typed, per-kind body. The store's public surface uses\n * `IMemoryRecord<unknown>`; consumers narrow `TBody` by checking\n * `envelope.kind` and validating through the registered Converter.\n * @public\n */\nexport interface IMemoryRecord<TBody = unknown> {\n /** The invariant identity + transaction-time envelope. */\n readonly envelope: IMemoryEnvelope;\n /** The per-kind, Converter-validated body. */\n readonly body: TBody;\n}\n\n/**\n * A per-kind host projection from a fully-resolved (post-merge) memory record\n * to a numeric ordering value. Registered per kind at store construction (see\n * `rankProjectors`); the store runs it on every put/update over the same\n * resolved record whose `contentHash` it computes, stamping the result into\n * {@link IMemoryEnvelope.rank}. The store never interprets the body — the host\n * owns what the number means. A projector that throws is treated as \"no rank\n * for this record\" (logged at `warn`), never failing the write.\n *\n * Runs on writes only. To apply a newly-registered projector to records that\n * already exist, call `IMemoryStore.reconcileRank`.\n * @public\n */\nexport type RankProjector = (record: IMemoryRecord<unknown>) => number;\n"]}
1
+ {"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../../src/packlets/types/envelope.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAyDH;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,MAAmB;IAC/C,OAAO,GAAG,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC;AACzC,CAAC;AAyHD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,cAAc,CAAC,QAAyB;;IACtD,OAAO,MAAA,QAAQ,CAAC,YAAY,mCAAI,SAAS,CAAC;AAC5C,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { EntityId, Kind, LinkType, MemoryId, MemoryScopeKey, Tag } from './ids';\n\n/**\n * Origin of a provenance attribution. Open vocabulary: the three named\n * sources are conventional, but the `(string & {})` arm admits any other\n * source string without resignature.\n * @public\n */\nexport type ProvenanceSource = 'agent' | 'host-ingest' | 'human' | (string & {});\n\n/**\n * Structured provenance for a record or an edge. Never a flat enum — the\n * `[key: string]: unknown` index signature lets a consumer attach an opaque\n * domain payload (e.g. PersonAIlity's sentiment / epistemic blocks) without\n * changing this interface, while still satisfying the no-`any` rule.\n * @public\n */\nexport interface IProvenance {\n /** Where the attribution came from. */\n readonly source: ProvenanceSource;\n /** Optional human or agent identifier responsible for the write. */\n readonly by?: string;\n /** Optional model identifier, when a model produced the content. */\n readonly model?: string;\n /** Optional confidence in `[0, 1]`. */\n readonly confidence?: number;\n /**\n * Scope-qualified back-link to the source record. Enables the cross-kind\n * provenance spine. A scope-qualified {@link IEdgeTarget} (not a bare\n * {@link MemoryId}) because per-scope codecs (e.g. the MTM codec's `turn-<n>`\n * stems) legally reuse a stem across scopes, so a bare id would be ambiguous —\n * the same reason {@link IEdge.target} is scope-qualified.\n */\n readonly derivedFrom?: IEdgeTarget;\n /** Opaque extension payload — consumer-owned, never interpreted by the store. */\n readonly [key: string]: unknown;\n}\n\n/**\n * The physical address of a linked-to record: the `(scope, id)` pair that\n * uniquely identifies it. Both components are required because a bare\n * {@link MemoryId} is NOT unique across scopes — per-scope codecs (e.g. the\n * medium-term codec's `turn-<n>` stems) legally mint the same stem under\n * different scopes, so an edge that carried only the id would be ambiguous.\n * `(scope, id)` matches the store's `getById(scope, id)` addressing and the\n * index's composite primary key.\n * @public\n */\nexport interface IEdgeTarget {\n /** The scope the target record lives under. */\n readonly scope: MemoryScopeKey;\n /** The target record's stable file-stem id (unique WITHIN {@link IEdgeTarget.scope}). */\n readonly id: MemoryId;\n}\n\n/**\n * The canonical composite-key string for an {@link IEdgeTarget}: scope + id,\n * NUL-separated. NUL is excluded from both components (scope segments are\n * filename-safe; {@link MemoryId} is portable-filename-safe), so it is a\n * collision-proof separator. This is the ONE canonicalization every consumer\n * that keys on a scoped target uses — the backlink index, the cycle guard, and\n * the ingest edge-validation path all route through it so their notions of\n * \"same target\" cannot drift.\n * @public\n */\nexport function edgeTargetKey(target: IEdgeTarget): string {\n return `${target.scope}\\0${target.id}`;\n}\n\n/**\n * An attributed link between two records. Carries the relation type, the\n * scope-qualified {@link IEdgeTarget | target}, and optional confidence /\n * provenance / world-truth validity. Replaces bare string references (e.g.\n * PersonAIlity's `IMtmRef` becomes an `IEdge` with `type: LinkType('mtm-ref')`).\n * @public\n */\nexport interface IEdge {\n /** Open-vocabulary relation type. */\n readonly type: LinkType;\n /** The scope-qualified address of the linked-to record. */\n readonly target: IEdgeTarget;\n /** Optional confidence in `[0, 1]`. */\n readonly confidence?: number;\n /** Optional structured provenance for the link itself. */\n readonly provenance?: IProvenance;\n /** World-truth validity start (epoch ms). Present only on temporal edges. */\n readonly valid_at?: number;\n /**\n * World-truth validity end (epoch ms). `null` = still valid; absent = no\n * temporal extent.\n */\n // eslint-disable-next-line @rushstack/no-new-null -- null is a meaningful value here (still-valid) distinct from absent (no temporal extent); design-lock §2.3\n readonly invalid_at?: number | null;\n}\n\n/**\n * Optional bi-temporal validity block on an envelope. Present only on\n * temporal kinds; absent = zero cost for atemporal kinds.\n * @public\n */\nexport interface ITemporalBlock {\n /** World-truth validity start (epoch ms). */\n readonly valid_at?: number;\n /** World-truth validity end (epoch ms). `null` = still valid. */\n // eslint-disable-next-line @rushstack/no-new-null -- null is a meaningful value here (still-valid) distinct from absent; design-lock §2.4\n readonly invalid_at?: number | null;\n}\n\n/**\n * The invariant identity + transaction-time envelope carried by every memory\n * record, independent of the per-kind body.\n * @public\n */\nexport interface IMemoryEnvelope {\n // --- Core identity ---\n /** Stable file-stem identifier. MUST equal the on-disk filename stem. */\n readonly id: MemoryId;\n /** Consumer-supplied domain key. Equals {@link IMemoryEnvelope.id | id} for non-temporal kinds. */\n readonly entityId: EntityId;\n /** Consumer-registered kind; dispatches the body Converter. */\n readonly kind: Kind;\n /** Open-vocabulary tags. */\n readonly tags: ReadonlyArray<Tag>;\n /** Attributed outbound edges. */\n readonly links: ReadonlyArray<IEdge>;\n\n // --- Transaction-time metadata (always present) ---\n /** Epoch ms of the first write. Immutable after creation. */\n readonly created: number;\n /** Epoch ms of the most recent write. */\n readonly updated: number;\n /**\n * Monotonic write counter within the store instance, assigned by the store\n * on every successful put. Enables stable cursor paging over observation\n * records without a full walk.\n */\n readonly seq: number;\n /**\n * Content hash over the canonical `{ kind, body, links }`. The dedup key:\n * an exact match is a no-op upsert that returns the existing record.\n */\n readonly contentHash: string;\n /**\n * Store-computed host-defined ordering value, produced by the kind's\n * {@link RankProjector} on every put/update and stamped into the envelope in\n * the same pass that recomputes {@link IMemoryEnvelope.contentHash | contentHash}.\n * Absent when the kind has no registered projector (or the projector threw on\n * this record). Ordered retrieval (`orderBy: 'rank'`) and the index's rank view\n * sort by this value descending, placing records with an absent `rank` last.\n *\n * **The projector runs on the write path only — registering one does not rank\n * records already in the store.** Because absent sorts last, those records land\n * below every subsequently-written one regardless of what the projector would\n * have scored them, so the result is not a partial ordering but one inverted\n * with respect to the projector's intent, with nothing failing to say so. Call\n * `IMemoryStore.reconcile(kind, 'rank')` after registering a projector against a\n * populated store.\n */\n readonly rank?: number;\n /** Structured provenance (never a flat enum). */\n readonly provenance: IProvenance;\n\n // --- Optional temporal block ---\n /** Bi-temporal validity. Present only on temporal kinds. */\n readonly temporal?: ITemporalBlock;\n\n /**\n * Vector-index entry reference, set by the vector index on write. `null` =\n * not embedded; absent = same as `null` (backwards-compat seam).\n */\n // eslint-disable-next-line @rushstack/no-new-null -- null is the explicit \"not embedded\" sentinel distinct from absent (backwards-compat seam); design-lock §2.5\n readonly embeddingRef?: string | null;\n}\n\n/**\n * A complete memory record: the invariant {@link IMemoryEnvelope} plus the\n * typed, per-kind body. The store's public surface uses\n * `IMemoryRecord<unknown>`; consumers narrow `TBody` by checking\n * `envelope.kind` and validating through the registered Converter.\n * @public\n */\nexport interface IMemoryRecord<TBody = unknown> {\n /** The invariant identity + transaction-time envelope. */\n readonly envelope: IMemoryEnvelope;\n /** The per-kind, Converter-validated body. */\n readonly body: TBody;\n}\n\n/**\n * The envelope's embedding reference if it carries a usable one, `undefined`\n * otherwise — **the one place `null`-vs-absent is collapsed.**\n *\n * @remarks\n * {@link IMemoryEnvelope.embeddingRef} is `string | null | undefined`, where\n * `null` and absent both mean *not embedded* (`null` is the explicit sentinel;\n * absent is the backwards-compat seam). That makes the obvious presence check\n * wrong in **both** directions, and both mistakes were shipped before this\n * accessor existed: `!== undefined` counts a `null` as an embedding that is not\n * there, and `=== undefined` misses a `null` when looking for one that is\n * missing. Neither is a type error, and neither is visible to a coverage gate,\n * because the sentinel is a *value* rather than a branch.\n *\n * Returning the reference rather than a boolean is deliberate: a caller that\n * needs the string gets the check for free, so there is no second, weaker way\n * to ask.\n *\n * **Do not test `embeddingRef` for presence directly — call this.**\n * @public\n */\nexport function embeddingRefOf(envelope: IMemoryEnvelope): string | undefined {\n return envelope.embeddingRef ?? undefined;\n}\n\n/**\n * A per-kind host projection from a fully-resolved (post-merge) memory record\n * to a numeric ordering value. Registered per kind at store construction (see\n * `rankProjectors`); the store runs it on every put/update over the same\n * resolved record whose `contentHash` it computes, stamping the result into\n * {@link IMemoryEnvelope.rank}. The store never interprets the body — the host\n * owns what the number means. A projector that throws is treated as \"no rank\n * for this record\" (logged at `warn`), never failing the write.\n *\n * Runs on writes only. To apply a newly-registered projector to records that\n * already exist, call `IMemoryStore.reconcile(kind, 'rank')`.\n * @public\n */\nexport type RankProjector = (record: IMemoryRecord<unknown>) => number;\n"]}
@@ -6,6 +6,7 @@ export * from './ids';
6
6
  export * from './envelope';
7
7
  export * from './filenameSafety';
8
8
  export * from './identityCodec';
9
+ export * from './recordResolver';
9
10
  export * from './temporal';
10
11
  export * from './writePolicy';
11
12
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,OAAO,CAAC;AACtB,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nexport * from './ids';\nexport * from './envelope';\nexport * from './filenameSafety';\nexport * from './identityCodec';\nexport * from './temporal';\nexport * from './writePolicy';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,OAAO,CAAC;AACtB,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nexport * from './ids';\nexport * from './envelope';\nexport * from './filenameSafety';\nexport * from './identityCodec';\nexport * from './recordResolver';\nexport * from './temporal';\nexport * from './writePolicy';\n"]}
@@ -0,0 +1,6 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=recordResolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordResolver.js","sourceRoot":"","sources":["../../../src/packlets/types/recordResolver.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result } from '@fgv/ts-utils';\nimport { IMemoryRecord } from './envelope';\nimport { MemoryId, MemoryScopeKey } from './ids';\n\n/**\n * Materializes one record's body from its scope-qualified address — the other\n * half of the partial-read split, and the narrowest seam that can be.\n *\n * @remarks\n * The index holds `IIndexedMemoryEntry` (scope + envelope, no body), so anything\n * that *selects* works from envelopes and anything that must *return records*\n * resolves the survivors through this. `FileTreeMemoryStore` implements it over\n * the same read path `getById` uses, so a resolved record is byte-identical to a\n * keyed read, verification included.\n *\n * **Deliberately one method, and deliberately not the store.** A retriever needs\n * exactly this capability; handing it an `IMemoryStore` would hand it `put` and\n * `delete` as well, and would invert the construction direction (the store builds\n * retrievers today, not the reverse). One method also keeps a test double\n * trivial.\n *\n * Synchronous `Result` rather than `Promise<Result>` because every shipped\n * `FileTree` backend resolves without awaiting, and an async signature here would\n * have rippled through retrievers that are otherwise synchronous over the index.\n * A future backend that genuinely needs I/O should be adapted at its own boundary\n * rather than by making this contract async for everyone.\n *\n * Resolving an address the vault does not hold is `succeed(undefined)`, not a\n * failure — an entry can legitimately vanish between selection and\n * materialization (a concurrent delete), and that is a miss rather than a fault.\n * A failure means the record is there and could not be read.\n * @public\n */\nexport interface IMemoryRecordResolver {\n /**\n * The record at `(scope, id)`, or `undefined` if the vault does not hold one.\n */\n resolveRecord(scope: MemoryScopeKey, id: MemoryId): Result<IMemoryRecord<unknown> | undefined>;\n}\n"]}