@fgv/ts-agent-memory 5.1.0-49 → 5.1.0-51
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.
- package/dist/packlets/index/memoryIndex.js +23 -16
- package/dist/packlets/index/memoryIndex.js.map +1 -1
- package/dist/packlets/ingest/orchestrator.js +13 -1
- package/dist/packlets/ingest/orchestrator.js.map +1 -1
- package/dist/packlets/retrieve/fragmentSemanticRetriever.js +94 -5
- package/dist/packlets/retrieve/fragmentSemanticRetriever.js.map +1 -1
- package/dist/packlets/retrieve/linkTraversalRetriever.js +12 -26
- package/dist/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
- package/dist/packlets/retrieve/recencyRetriever.js +7 -7
- package/dist/packlets/retrieve/recencyRetriever.js.map +1 -1
- package/dist/packlets/retrieve/retriever.js +91 -10
- package/dist/packlets/retrieve/retriever.js.map +1 -1
- package/dist/packlets/retrieve/semanticRetriever.js +16 -16
- package/dist/packlets/retrieve/semanticRetriever.js.map +1 -1
- package/dist/packlets/retrieve/structuredFilterRetriever.js +7 -7
- package/dist/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
- package/dist/packlets/retrieve/tagRetriever.js +7 -7
- package/dist/packlets/retrieve/tagRetriever.js.map +1 -1
- package/dist/packlets/retrieve/temporalRetrievers.js +23 -20
- package/dist/packlets/retrieve/temporalRetrievers.js.map +1 -1
- package/dist/packlets/store/coverage.js +6 -0
- package/dist/packlets/store/coverage.js.map +1 -0
- package/dist/packlets/store/fileTreeMemoryStore.js +234 -103
- package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/dist/packlets/store/index.js +4 -0
- package/dist/packlets/store/index.js.map +1 -1
- package/dist/packlets/store/listSelection.js +36 -0
- package/dist/packlets/store/listSelection.js.map +1 -0
- package/dist/packlets/store/memoryStore.js +6 -0
- package/dist/packlets/store/memoryStore.js.map +1 -0
- package/dist/packlets/store/reconcile.js +6 -0
- package/dist/packlets/store/reconcile.js.map +1 -0
- package/dist/packlets/store/storeCoverage.js +102 -0
- package/dist/packlets/store/storeCoverage.js.map +1 -0
- package/dist/packlets/store/storeIdentity.js +62 -0
- package/dist/packlets/store/storeIdentity.js.map +1 -0
- package/dist/packlets/store/storeReconcile.js +122 -0
- package/dist/packlets/store/storeReconcile.js.map +1 -0
- package/dist/packlets/store/vectorMaintenance.js +116 -8
- package/dist/packlets/store/vectorMaintenance.js.map +1 -1
- package/dist/packlets/store/vectorRecordSource.js +44 -0
- package/dist/packlets/store/vectorRecordSource.js.map +1 -0
- package/dist/packlets/tools/memoryTools.js +25 -2
- package/dist/packlets/tools/memoryTools.js.map +1 -1
- package/dist/packlets/types/envelope.js +25 -0
- package/dist/packlets/types/envelope.js.map +1 -1
- package/dist/packlets/types/identityResolver.js +6 -0
- package/dist/packlets/types/identityResolver.js.map +1 -0
- package/dist/packlets/types/index.js +2 -0
- package/dist/packlets/types/index.js.map +1 -1
- package/dist/packlets/types/recordResolver.js +6 -0
- package/dist/packlets/types/recordResolver.js.map +1 -0
- package/dist/packlets/types/temporal.js.map +1 -1
- package/dist/packlets/vector/inMemoryCosineIndex.js +39 -18
- package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -1
- package/dist/packlets/vector/inMemoryFragmentCosineIndex.js +109 -14
- package/dist/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
- package/dist/packlets/vector/rebuildHelpers.js +38 -0
- package/dist/packlets/vector/rebuildHelpers.js.map +1 -0
- package/dist/packlets/vector/vectorIndex.js.map +1 -1
- package/dist/ts-agent-memory.d.ts +1226 -114
- package/lib/packlets/index/memoryIndex.d.ts +118 -27
- package/lib/packlets/index/memoryIndex.d.ts.map +1 -1
- package/lib/packlets/index/memoryIndex.js +23 -16
- package/lib/packlets/index/memoryIndex.js.map +1 -1
- package/lib/packlets/ingest/orchestrator.d.ts.map +1 -1
- package/lib/packlets/ingest/orchestrator.js +13 -1
- package/lib/packlets/ingest/orchestrator.js.map +1 -1
- package/lib/packlets/retrieve/fragmentSemanticRetriever.d.ts +79 -1
- package/lib/packlets/retrieve/fragmentSemanticRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/fragmentSemanticRetriever.js +94 -5
- package/lib/packlets/retrieve/fragmentSemanticRetriever.js.map +1 -1
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts +3 -10
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/linkTraversalRetriever.js +11 -25
- package/lib/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
- package/lib/packlets/retrieve/recencyRetriever.d.ts +3 -3
- package/lib/packlets/retrieve/recencyRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/recencyRetriever.js +6 -6
- package/lib/packlets/retrieve/recencyRetriever.js.map +1 -1
- package/lib/packlets/retrieve/retriever.d.ts +88 -7
- package/lib/packlets/retrieve/retriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/retriever.js +94 -9
- package/lib/packlets/retrieve/retriever.js.map +1 -1
- package/lib/packlets/retrieve/semanticRetriever.d.ts +3 -5
- package/lib/packlets/retrieve/semanticRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/semanticRetriever.js +15 -15
- package/lib/packlets/retrieve/semanticRetriever.js.map +1 -1
- package/lib/packlets/retrieve/structuredFilterRetriever.d.ts +3 -3
- package/lib/packlets/retrieve/structuredFilterRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/structuredFilterRetriever.js +6 -6
- package/lib/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
- package/lib/packlets/retrieve/tagRetriever.d.ts +3 -3
- package/lib/packlets/retrieve/tagRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/tagRetriever.js +6 -6
- package/lib/packlets/retrieve/tagRetriever.js.map +1 -1
- package/lib/packlets/retrieve/temporalRetrievers.d.ts +7 -5
- package/lib/packlets/retrieve/temporalRetrievers.d.ts.map +1 -1
- package/lib/packlets/retrieve/temporalRetrievers.js +22 -19
- package/lib/packlets/retrieve/temporalRetrievers.js.map +1 -1
- package/lib/packlets/store/coverage.d.ts +102 -0
- package/lib/packlets/store/coverage.d.ts.map +1 -0
- package/lib/packlets/store/coverage.js +7 -0
- package/lib/packlets/store/coverage.js.map +1 -0
- package/lib/packlets/store/fileTreeMemoryStore.d.ts +56 -167
- package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
- package/lib/packlets/store/fileTreeMemoryStore.js +234 -103
- package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/lib/packlets/store/index.d.ts +4 -0
- package/lib/packlets/store/index.d.ts.map +1 -1
- package/lib/packlets/store/index.js +4 -0
- package/lib/packlets/store/index.js.map +1 -1
- package/lib/packlets/store/listSelection.d.ts +101 -0
- package/lib/packlets/store/listSelection.d.ts.map +1 -0
- package/lib/packlets/store/listSelection.js +40 -0
- package/lib/packlets/store/listSelection.js.map +1 -0
- package/lib/packlets/store/memoryStore.d.ts +237 -0
- package/lib/packlets/store/memoryStore.d.ts.map +1 -0
- package/lib/packlets/store/memoryStore.js +7 -0
- package/lib/packlets/store/memoryStore.js.map +1 -0
- package/lib/packlets/store/reconcile.d.ts +82 -0
- package/lib/packlets/store/reconcile.d.ts.map +1 -0
- package/lib/packlets/store/reconcile.js +7 -0
- package/lib/packlets/store/reconcile.js.map +1 -0
- package/lib/packlets/store/storeCoverage.d.ts +45 -0
- package/lib/packlets/store/storeCoverage.d.ts.map +1 -0
- package/lib/packlets/store/storeCoverage.js +105 -0
- package/lib/packlets/store/storeCoverage.js.map +1 -0
- package/lib/packlets/store/storeIdentity.d.ts +38 -0
- package/lib/packlets/store/storeIdentity.d.ts.map +1 -0
- package/lib/packlets/store/storeIdentity.js +67 -0
- package/lib/packlets/store/storeIdentity.js.map +1 -0
- package/lib/packlets/store/storeReconcile.d.ts +41 -0
- package/lib/packlets/store/storeReconcile.d.ts.map +1 -0
- package/lib/packlets/store/storeReconcile.js +125 -0
- package/lib/packlets/store/storeReconcile.js.map +1 -0
- package/lib/packlets/store/vectorMaintenance.d.ts +74 -0
- package/lib/packlets/store/vectorMaintenance.d.ts.map +1 -1
- package/lib/packlets/store/vectorMaintenance.js +117 -8
- package/lib/packlets/store/vectorMaintenance.js.map +1 -1
- package/lib/packlets/store/vectorRecordSource.d.ts +36 -0
- package/lib/packlets/store/vectorRecordSource.d.ts.map +1 -0
- package/lib/packlets/store/vectorRecordSource.js +47 -0
- package/lib/packlets/store/vectorRecordSource.js.map +1 -0
- package/lib/packlets/tools/memoryTools.d.ts.map +1 -1
- package/lib/packlets/tools/memoryTools.js +25 -2
- package/lib/packlets/tools/memoryTools.js.map +1 -1
- package/lib/packlets/types/envelope.d.ts +24 -2
- package/lib/packlets/types/envelope.d.ts.map +1 -1
- package/lib/packlets/types/envelope.js +26 -0
- package/lib/packlets/types/envelope.js.map +1 -1
- package/lib/packlets/types/identityResolver.d.ts +42 -0
- package/lib/packlets/types/identityResolver.d.ts.map +1 -0
- package/lib/packlets/types/identityResolver.js +7 -0
- package/lib/packlets/types/identityResolver.js.map +1 -0
- package/lib/packlets/types/index.d.ts +2 -0
- package/lib/packlets/types/index.d.ts.map +1 -1
- package/lib/packlets/types/index.js +2 -0
- package/lib/packlets/types/index.js.map +1 -1
- package/lib/packlets/types/recordResolver.d.ts +39 -0
- package/lib/packlets/types/recordResolver.d.ts.map +1 -0
- package/lib/packlets/types/recordResolver.js +7 -0
- package/lib/packlets/types/recordResolver.js.map +1 -0
- package/lib/packlets/types/temporal.d.ts +26 -6
- package/lib/packlets/types/temporal.d.ts.map +1 -1
- package/lib/packlets/types/temporal.js.map +1 -1
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts +9 -2
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -1
- package/lib/packlets/vector/inMemoryCosineIndex.js +40 -19
- package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -1
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts +18 -4
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map +1 -1
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.js +108 -13
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
- package/lib/packlets/vector/rebuildHelpers.d.ts +30 -0
- package/lib/packlets/vector/rebuildHelpers.d.ts.map +1 -0
- package/lib/packlets/vector/rebuildHelpers.js +42 -0
- package/lib/packlets/vector/rebuildHelpers.js.map +1 -0
- package/lib/packlets/vector/vectorIndex.d.ts +326 -20
- package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
- package/lib/packlets/vector/vectorIndex.js.map +1 -1
- package/package.json +7 -7
|
@@ -16,7 +16,9 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
16
16
|
};
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
exports.VectorMaintenance = void 0;
|
|
19
|
+
exports.captureVectorHook = captureVectorHook;
|
|
19
20
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
21
|
+
const types_1 = require("../types");
|
|
20
22
|
/**
|
|
21
23
|
* Project a record the embedder **declined** into its written form: the same
|
|
22
24
|
* record with no `embeddingRef`, plus the vector (if any) that reference
|
|
@@ -43,7 +45,7 @@ const ts_utils_1 = require("@fgv/ts-utils");
|
|
|
43
45
|
* `_persist`.
|
|
44
46
|
*/
|
|
45
47
|
function declineEmbedding(built, index, target, embed) {
|
|
46
|
-
if (built.envelope
|
|
48
|
+
if ((0, types_1.embeddingRefOf)(built.envelope) === undefined) {
|
|
47
49
|
return { record: built, embed };
|
|
48
50
|
}
|
|
49
51
|
// Rest-spread rather than `embeddingRef: undefined`: the envelope is YAML-
|
|
@@ -53,6 +55,37 @@ function declineEmbedding(built, index, target, embed) {
|
|
|
53
55
|
const _a = built.envelope, { embeddingRef } = _a, envelope = __rest(_a, ["embeddingRef"]);
|
|
54
56
|
return { record: { envelope, body: built.body }, stale: { index, target }, embed };
|
|
55
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Run a consumer-supplied hook, normalizing a synchronous throw or a rejected
|
|
60
|
+
* promise into a `Failure`.
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* **Every vector-lane hook belongs to the consumer** — the embedder, the fragment
|
|
64
|
+
* embedder, and every member of both index seams — so any of them may throw
|
|
65
|
+
* rather than fail. Left unwrapped, that escapes as a rejected promise out of
|
|
66
|
+
* `IMemoryStore.reconcile`, which the repo's Result contract says cannot happen.
|
|
67
|
+
*
|
|
68
|
+
* **Module-level and exported rather than a private of {@link VectorMaintenance},
|
|
69
|
+
* because the repair loop in `storeReconcile.ts` needs it too.** It calls
|
|
70
|
+
* `index.has` per record, which is a consumer hook like any other; when the four
|
|
71
|
+
* embed/add hooks were wrapped, that fifth one was missed, and a second private
|
|
72
|
+
* copy would have made the next omission just as easy. One hook, one helper.
|
|
73
|
+
*
|
|
74
|
+
* Kept separate from {@link VectorMaintenance._tryVectorOp} (which delegates
|
|
75
|
+
* here and adds a warn) because that warning says "best-effort; derived index
|
|
76
|
+
* left for rebuild" — true of a write, false of a repair. The repair *is* the
|
|
77
|
+
* rebuild, and it returns its failures to the caller who asked for them rather
|
|
78
|
+
* than logging them past a success.
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
async function captureVectorHook(op, label) {
|
|
82
|
+
try {
|
|
83
|
+
return await op();
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
return (0, ts_utils_1.fail)(`${label} threw: ${String(err)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
56
89
|
/**
|
|
57
90
|
* The store's record- and fragment-vector maintenance, extracted from
|
|
58
91
|
* {@link FileTreeMemoryStore} as a collaborator.
|
|
@@ -88,6 +121,88 @@ class VectorMaintenance {
|
|
|
88
121
|
await this._removeVectorBestEffort(target);
|
|
89
122
|
await this._removeFragmentsBestEffort(target);
|
|
90
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* The wired record-vector index, or `undefined`. Read-only, and exposed solely
|
|
126
|
+
* so the store's {@link IMemoryStore.coverage} can report an index-side count
|
|
127
|
+
* without a second copy of the wiring — absent here IS the "lane not wired"
|
|
128
|
+
* answer that coverage reports as `undefined` rather than as zero.
|
|
129
|
+
*/
|
|
130
|
+
get vectorIndex() {
|
|
131
|
+
return this._vectorIndex;
|
|
132
|
+
}
|
|
133
|
+
/** The wired fragment index, or `undefined`. See {@link VectorMaintenance.vectorIndex}. */
|
|
134
|
+
get fragmentIndex() {
|
|
135
|
+
return this._fragmentIndex;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The wired record embedder, or `undefined`. Exposed alongside the index
|
|
139
|
+
* because a lane is only usable when BOTH halves are present — an index with
|
|
140
|
+
* no embedder is a legal store whose writes simply do not embed, and a repair
|
|
141
|
+
* has to say so rather than failing every record.
|
|
142
|
+
*/
|
|
143
|
+
get embedder() {
|
|
144
|
+
return this._embed;
|
|
145
|
+
}
|
|
146
|
+
/** The wired fragment embedder, or `undefined`. See {@link VectorMaintenance.embedder}. */
|
|
147
|
+
get fragmentEmbedder() {
|
|
148
|
+
return this._fragmentEmbedder;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Re-embed one record into the record-vector index — the repair path.
|
|
152
|
+
*
|
|
153
|
+
* @remarks
|
|
154
|
+
* Distinct from {@link VectorMaintenance.embedOnWrite} in the one way that
|
|
155
|
+
* matters: **this is not best-effort.** Embed-on-write swallows a failure
|
|
156
|
+
* because a vault record is the source of truth and a write must not be
|
|
157
|
+
* rejected over a derived artifact; a repair was *asked for* by a caller who
|
|
158
|
+
* wants to know whether it worked, so a failure is returned.
|
|
159
|
+
*
|
|
160
|
+
* `undefined` means the embedder declined — intentionally not embedded, which
|
|
161
|
+
* is neither a repair nor a fault.
|
|
162
|
+
*/
|
|
163
|
+
async reembedRecord(record, target) {
|
|
164
|
+
if (this._vectorIndex === undefined || this._embed === undefined) {
|
|
165
|
+
return (0, ts_utils_1.fail)('the record-vector lane is not wired');
|
|
166
|
+
}
|
|
167
|
+
const index = this._vectorIndex;
|
|
168
|
+
const embed = this._embed;
|
|
169
|
+
const embedded = await captureVectorHook(() => embed(record), `re-embedding '${record.envelope.id}'`);
|
|
170
|
+
if (embedded.isFailure()) {
|
|
171
|
+
return (0, ts_utils_1.fail)(embedded.message);
|
|
172
|
+
}
|
|
173
|
+
if (embedded.value === undefined) {
|
|
174
|
+
return (0, ts_utils_1.succeed)(undefined);
|
|
175
|
+
}
|
|
176
|
+
// Hoisted: the `undefined` check above does not narrow across the callback
|
|
177
|
+
// boundary below.
|
|
178
|
+
const vector = embedded.value;
|
|
179
|
+
// `add`'s return value IS the reference the store stamps — synthesizing one
|
|
180
|
+
// here would diverge from the write path for any index whose reference is
|
|
181
|
+
// not the scoped key.
|
|
182
|
+
return (await captureVectorHook(() => index.add(target, vector), `vector add for '${record.envelope.id}'`)).onSuccess((ref) => (0, ts_utils_1.succeed)({ count: 1, ref }));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Re-embed one record's fragments — the repair path, returning the fragment
|
|
186
|
+
* count written. See {@link VectorMaintenance.reembedRecord} for why this is
|
|
187
|
+
* not best-effort. An empty fragment array is this lane's decline and reports
|
|
188
|
+
* `undefined`, though the whole-record-replace still runs so stale fragments
|
|
189
|
+
* are cleared.
|
|
190
|
+
*/
|
|
191
|
+
async reembedFragments(record, target) {
|
|
192
|
+
if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {
|
|
193
|
+
return (0, ts_utils_1.fail)('the fragment lane is not wired');
|
|
194
|
+
}
|
|
195
|
+
const index = this._fragmentIndex;
|
|
196
|
+
const fragmentEmbedder = this._fragmentEmbedder;
|
|
197
|
+
const embedded = await captureVectorHook(() => fragmentEmbedder(record), `re-embedding fragments of '${record.envelope.id}'`);
|
|
198
|
+
if (embedded.isFailure()) {
|
|
199
|
+
return (0, ts_utils_1.fail)(embedded.message);
|
|
200
|
+
}
|
|
201
|
+
const fragments = embedded.value;
|
|
202
|
+
return (await captureVectorHook(() => index.addFragments(target, fragments), `fragment add for '${record.envelope.id}'`)).onSuccess((n) =>
|
|
203
|
+
// The fragment lane has no envelope reference, so `ref` is empty and unused.
|
|
204
|
+
(0, ts_utils_1.succeed)(n === 0 ? undefined : { count: n, ref: '' }));
|
|
205
|
+
}
|
|
91
206
|
/**
|
|
92
207
|
* Best-effort embed-on-write. When a vector index AND an embedder are wired,
|
|
93
208
|
* embeds the built record, `add`s the vector (replace semantics handle a same-id
|
|
@@ -225,13 +340,7 @@ class VectorMaintenance {
|
|
|
225
340
|
* regardless, since the index is rebuildable.
|
|
226
341
|
*/
|
|
227
342
|
async _tryVectorOp(op, label) {
|
|
228
|
-
|
|
229
|
-
try {
|
|
230
|
-
result = await op();
|
|
231
|
-
}
|
|
232
|
-
catch (err) {
|
|
233
|
-
result = (0, ts_utils_1.fail)(`${label} threw: ${String(err)}`);
|
|
234
|
-
}
|
|
343
|
+
const result = await captureVectorHook(op, label);
|
|
235
344
|
if (result.isFailure()) {
|
|
236
345
|
this._warn(`memory: ${label} failed (best-effort; derived index left for rebuild): ${result.message}`);
|
|
237
346
|
}
|
|
@@ -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,36 @@
|
|
|
1
|
+
import { Result } from '@fgv/ts-utils';
|
|
2
|
+
import { Kind } from '../types';
|
|
3
|
+
import { IMemoryRecordSource, IScopedMemoryRecord } from '../vector';
|
|
4
|
+
/**
|
|
5
|
+
* The two store capabilities the vector record source needs, taken structurally
|
|
6
|
+
* so this module does not import the store (which imports it).
|
|
7
|
+
*/
|
|
8
|
+
export interface IVectorRecordSourceHost {
|
|
9
|
+
/** The whole-vault scoped listing, unfiltered. */
|
|
10
|
+
listScoped(): Promise<Result<ReadonlyArray<IScopedMemoryRecord>>>;
|
|
11
|
+
/** Whether records of `kind` participate in the record-granular vector index. */
|
|
12
|
+
embedsKind(kind: Kind): boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Adapt a store's whole-vault scoped listing into the `IMemoryRecordSource` a
|
|
16
|
+
* vector-index rebuild reads: filtered to the kinds that participate in the
|
|
17
|
+
* record-granular index, and **counting what it drops**.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* The filter exists because this source drives `IVectorIndex` rebuilds, so a kind
|
|
21
|
+
* excluded from that index has no business being re-embedded on open — which is
|
|
22
|
+
* where the cost is worst, since a rebuild embeds the whole vault serially. With
|
|
23
|
+
* no `embedKinds` declaration every kind passes and this is the identity filter.
|
|
24
|
+
*
|
|
25
|
+
* The **tally** exists because this is the only layer that can produce it. A
|
|
26
|
+
* rebuild never sees an excluded record and so cannot count one; a coverage report
|
|
27
|
+
* assembled without this number leaves those records in none of `indexed` /
|
|
28
|
+
* `declined` / `skipped`, and a caller computing coverage undercounts — in the
|
|
29
|
+
* direction of looking healthier.
|
|
30
|
+
*
|
|
31
|
+
* The map is always present, empty when nothing was excluded: this source can
|
|
32
|
+
* always say, so it always does. An absent `excluded` means *"this source does not
|
|
33
|
+
* track exclusions"*, which is a different answer and not one this source gives.
|
|
34
|
+
*/
|
|
35
|
+
export declare function vectorRecordSource(host: IVectorRecordSourceHost): IMemoryRecordSource;
|
|
36
|
+
//# sourceMappingURL=vectorRecordSource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vectorRecordSource.d.ts","sourceRoot":"","sources":["../../../src/packlets/store/vectorRecordSource.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAW,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAwB,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAE3F;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,kDAAkD;IAClD,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAClE,iFAAiF;IACjF,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,uBAAuB,GAAG,mBAAmB,CAgBrF"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Erik Fortune
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.vectorRecordSource = vectorRecordSource;
|
|
8
|
+
const ts_utils_1 = require("@fgv/ts-utils");
|
|
9
|
+
/**
|
|
10
|
+
* Adapt a store's whole-vault scoped listing into the `IMemoryRecordSource` a
|
|
11
|
+
* vector-index rebuild reads: filtered to the kinds that participate in the
|
|
12
|
+
* record-granular index, and **counting what it drops**.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* The filter exists because this source drives `IVectorIndex` rebuilds, so a kind
|
|
16
|
+
* excluded from that index has no business being re-embedded on open — which is
|
|
17
|
+
* where the cost is worst, since a rebuild embeds the whole vault serially. With
|
|
18
|
+
* no `embedKinds` declaration every kind passes and this is the identity filter.
|
|
19
|
+
*
|
|
20
|
+
* The **tally** exists because this is the only layer that can produce it. A
|
|
21
|
+
* rebuild never sees an excluded record and so cannot count one; a coverage report
|
|
22
|
+
* assembled without this number leaves those records in none of `indexed` /
|
|
23
|
+
* `declined` / `skipped`, and a caller computing coverage undercounts — in the
|
|
24
|
+
* direction of looking healthier.
|
|
25
|
+
*
|
|
26
|
+
* The map is always present, empty when nothing was excluded: this source can
|
|
27
|
+
* always say, so it always does. An absent `excluded` means *"this source does not
|
|
28
|
+
* track exclusions"*, which is a different answer and not one this source gives.
|
|
29
|
+
*/
|
|
30
|
+
function vectorRecordSource(host) {
|
|
31
|
+
return {
|
|
32
|
+
list: async () => (await host.listScoped()).onSuccess((scoped) => {
|
|
33
|
+
const excluded = new Map();
|
|
34
|
+
const records = scoped.filter((s) => {
|
|
35
|
+
var _a;
|
|
36
|
+
const kind = s.record.envelope.kind;
|
|
37
|
+
if (host.embedsKind(kind)) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
excluded.set(kind, ((_a = excluded.get(kind)) !== null && _a !== void 0 ? _a : 0) + 1);
|
|
41
|
+
return false;
|
|
42
|
+
});
|
|
43
|
+
return (0, ts_utils_1.succeed)({ records, excluded });
|
|
44
|
+
})
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=vectorRecordSource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vectorRecordSource.js","sourceRoot":"","sources":["../../../src/packlets/store/vectorRecordSource.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAsCH,gDAgBC;AApDD,4CAAgD;AAehD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAgB,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,IAAA,kBAAO,EAAC,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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memoryTools.d.ts","sourceRoot":"","sources":["../../../src/packlets/tools/memoryTools.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAW,QAAQ,EAAe,cAAc,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAO,MAAM,UAAU,CAAC;AAC9G,OAAO,EAAE,sBAAsB,EAAqB,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAgB,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE7D;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB,cAAc,GACd,aAAa,GACb,eAAe,GACf,gBAAgB,GAChB,eAAe,CAAC;AAEpB;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE,aAAa,CAAC,cAAc,CAAuC,CAAC;AAEvG;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,SAAS,CAAC;AAEvD;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,iCAAiC;IACjC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,yFAAyF;IACzF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,iCAAiC;IACjC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,yBAAyB;IACzB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACrC,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,mFAAmF;IACnF,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;IACrC;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IACpD,mHAAmH;IACnH,QAAQ,CAAC,YAAY,CAAC,EAAE,cAAc,CAAC;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC;IAChE;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,gBAAgB,KAAK,qBAAqB,CAAC;CAC5G;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"memoryTools.d.ts","sourceRoot":"","sources":["../../../src/packlets/tools/memoryTools.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAW,QAAQ,EAAe,cAAc,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAO,MAAM,UAAU,CAAC;AAC9G,OAAO,EAAE,sBAAsB,EAAqB,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAgB,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE7D;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB,cAAc,GACd,aAAa,GACb,eAAe,GACf,gBAAgB,GAChB,eAAe,CAAC;AAEpB;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE,aAAa,CAAC,cAAc,CAAuC,CAAC;AAEvG;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,SAAS,CAAC;AAEvD;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,iCAAiC;IACjC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,yFAAyF;IACzF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,iCAAiC;IACjC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,yBAAyB;IACzB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACrC,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,mFAAmF;IACnF,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;IACrC;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IACpD,mHAAmH;IACnH,QAAQ,CAAC,YAAY,CAAC,EAAE,cAAc,CAAC;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC;IAChE;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,gBAAgB,KAAK,qBAAqB,CAAC;CAC5G;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,CAAC;AAgiB/C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,wBAAwB,GAAG,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,CAazG"}
|
|
@@ -70,7 +70,9 @@ const searchSchema = ts_json_base_1.JsonSchema.object({
|
|
|
70
70
|
kind: ts_json_base_1.JsonSchema.optional(ts_json_base_1.JsonSchema.string({ description: 'Restrict to this kind.' })),
|
|
71
71
|
tag: ts_json_base_1.JsonSchema.optional(ts_json_base_1.JsonSchema.string({ description: 'Restrict to records carrying this tag.' })),
|
|
72
72
|
semantic: ts_json_base_1.JsonSchema.optional(ts_json_base_1.JsonSchema.string({ description: 'Semantic query text (requires a semantic-capable retriever).' })),
|
|
73
|
-
limit: ts_json_base_1.JsonSchema.optional(ts_json_base_1.JsonSchema.integer({
|
|
73
|
+
limit: ts_json_base_1.JsonSchema.optional(ts_json_base_1.JsonSchema.integer({
|
|
74
|
+
description: 'Maximum number of results to return. At least one of kind, tag, semantic or limit is required.'
|
|
75
|
+
})),
|
|
74
76
|
offset: ts_json_base_1.JsonSchema.optional(ts_json_base_1.JsonSchema.integer({ description: 'Number of results to skip after ordering, before limit. Default 0.' })),
|
|
75
77
|
detail: ts_json_base_1.JsonSchema.optional(ts_json_base_1.JsonSchema.enumOf(['gist', 'full'], { description: "'gist' (default) | 'full'." }))
|
|
76
78
|
});
|
|
@@ -307,7 +309,8 @@ function buildSearchTool(ctx) {
|
|
|
307
309
|
config: {
|
|
308
310
|
type: 'client_tool',
|
|
309
311
|
name: 'memory_search',
|
|
310
|
-
description: 'Search memories by tag, kind, or semantic text. Returns ranked results.'
|
|
312
|
+
description: 'Search memories by tag, kind, or semantic text. Returns ranked results. ' +
|
|
313
|
+
'At least one of kind, tag, semantic or limit must be supplied.',
|
|
311
314
|
parametersSchema: searchSchema,
|
|
312
315
|
annotations: READ_ONLY_ANNOTATIONS
|
|
313
316
|
},
|
|
@@ -315,6 +318,26 @@ function buildSearchTool(ctx) {
|
|
|
315
318
|
.convert(args)
|
|
316
319
|
.withErrorFormat((msg) => `memory_search: invalid arguments: ${msg}`)
|
|
317
320
|
.onSuccess((typed) => resolveOptionalKind(ctx, typed.kind).onSuccess((kind) => resolveOptionalTag(typed.tag).onSuccess((tag) => (0, ts_utils_1.succeed)({ typed, kind, tag }))))
|
|
321
|
+
.onSuccess(({ typed, kind, tag }) => {
|
|
322
|
+
// A search with no axis at all is a whole-vault read issued by a model,
|
|
323
|
+
// and since the index holds envelopes only it materializes every body
|
|
324
|
+
// to answer. An LLM asking for "everything" is nearly always an
|
|
325
|
+
// under-specified query rather than an intended full scan, so this
|
|
326
|
+
// refuses instead of serving it — the model can retry with an axis,
|
|
327
|
+
// which is the outcome we want anyway.
|
|
328
|
+
//
|
|
329
|
+
// `limit` counts: an ordered top-N materializes N records rather than
|
|
330
|
+
// the vault (see `resolveQuery`), so it bounds the read as genuinely as
|
|
331
|
+
// `kind` or `tag` does.
|
|
332
|
+
if (kind === undefined &&
|
|
333
|
+
tag === undefined &&
|
|
334
|
+
typed.semantic === undefined &&
|
|
335
|
+
typed.limit === undefined) {
|
|
336
|
+
return (0, ts_utils_1.fail)('memory_search: supply at least one of kind, tag, semantic or limit — ' +
|
|
337
|
+
'an unrestricted search reads every record in the vault');
|
|
338
|
+
}
|
|
339
|
+
return (0, ts_utils_1.succeed)({ typed, kind, tag });
|
|
340
|
+
})
|
|
318
341
|
.thenOnSuccess(async ({ typed, kind, tag }) => {
|
|
319
342
|
const detail = resolveDetail(typed.detail);
|
|
320
343
|
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 } : {}));
|