@effect-agent/storage-memory 0.1.0-beta.41 → 0.1.0-beta.44
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/index.mjs.map +1 -1
- package/package.json +1 -48
- package/src/memory-ledger.ts +192 -0
- package/src/memory-schedule-store.ts +42 -0
- package/src/memory-storage.ts +48 -0
- package/src/memory-subscription-store.ts +149 -0
- package/src/semantic-memory-index.ts +37 -0
- package/src/testing.ts +1 -0
|
@@ -50,9 +50,12 @@ interface IndexData {
|
|
|
50
50
|
|
|
51
51
|
const sameProfile = Schema.toEquivalence(SemanticMemoryProfile);
|
|
52
52
|
const sameSource = Schema.toEquivalence(MemoryIndexSource.Wire);
|
|
53
|
+
|
|
53
54
|
const error = (operation: string, reason: MemoryIndexError["reason"]): MemoryIndexError =>
|
|
54
55
|
MemoryIndexError.make({ operation, reason });
|
|
56
|
+
|
|
55
57
|
const keyString = (key: MemoryKey): string => JSON.stringify([key.namespace.address, key.id]);
|
|
58
|
+
|
|
56
59
|
const sourceIdentityBytes = (source: MemoryIndexSource): number =>
|
|
57
60
|
Encoding.encodeHex(JSON.stringify(source)).length / 2;
|
|
58
61
|
|
|
@@ -91,10 +94,12 @@ const sourceIsFenced = (source: MemoryIndexSource, existing: StoredEntry): boole
|
|
|
91
94
|
|
|
92
95
|
const squaredNorm = (vector: ReadonlyArray<number>): number | null => {
|
|
93
96
|
let sum = 0;
|
|
97
|
+
|
|
94
98
|
for (const value of vector) {
|
|
95
99
|
sum += value * value;
|
|
96
100
|
if (!Number.isFinite(sum)) return null;
|
|
97
101
|
}
|
|
102
|
+
|
|
98
103
|
return sum > 0 ? sum : null;
|
|
99
104
|
};
|
|
100
105
|
|
|
@@ -108,9 +113,11 @@ const validateChunks = Effect.fn("InMemorySemanticIndex.validateChunks")(functio
|
|
|
108
113
|
): Effect.fn.Return<void, MemoryIndexError> {
|
|
109
114
|
let nextByte = 0;
|
|
110
115
|
const passageIds = new Set<string>();
|
|
116
|
+
|
|
111
117
|
for (let index = 0; index < chunks.length; index++) {
|
|
112
118
|
const chunk = chunks[index];
|
|
113
119
|
const byteLength = Encoding.encodeHex(chunk.text).length / 2;
|
|
120
|
+
|
|
114
121
|
if (
|
|
115
122
|
chunk.ordinal !== index ||
|
|
116
123
|
chunk.startByte !== nextByte ||
|
|
@@ -131,10 +138,12 @@ const cosine = (left: ReadonlyArray<number>, right: ReadonlyArray<number>): numb
|
|
|
131
138
|
const leftNorm = Math.sqrt(squaredNorm(left) ?? 1);
|
|
132
139
|
const rightNorm = Math.sqrt(squaredNorm(right) ?? 1);
|
|
133
140
|
let score = 0;
|
|
141
|
+
|
|
134
142
|
for (let index = 0; index < left.length; index++) {
|
|
135
143
|
score += (left[index] / leftNorm) * (right[index] / rightNorm);
|
|
136
144
|
}
|
|
137
145
|
const bounded = Math.max(-1, Math.min(1, score));
|
|
146
|
+
|
|
138
147
|
return bounded === 0 ? 0 : bounded;
|
|
139
148
|
};
|
|
140
149
|
|
|
@@ -154,16 +163,19 @@ const makeIndex = Effect.fn("InMemorySemanticIndex.make")(function* (
|
|
|
154
163
|
)),
|
|
155
164
|
}),
|
|
156
165
|
);
|
|
166
|
+
|
|
157
167
|
const capacity = yield* decodeBoundary(
|
|
158
168
|
InMemorySemanticIndexCapacity,
|
|
159
169
|
rawCapacity,
|
|
160
170
|
"configure semantic memory index",
|
|
161
171
|
);
|
|
172
|
+
|
|
162
173
|
if (capacity.maxChunks * profile.dimensions > MaxStoredVectorComponents) {
|
|
163
174
|
return yield* error("configure semantic memory index", "invalid-input");
|
|
164
175
|
}
|
|
165
176
|
const maxSourceBytes = capacity.maxSourceBytes ?? 16_777_216;
|
|
166
177
|
const data = yield* Ref.make<IndexData>({ closed: false, entries: new Map(), sourceBytes: 0 });
|
|
178
|
+
|
|
167
179
|
yield* Effect.addFinalizer(() =>
|
|
168
180
|
Ref.set(data, { closed: true, entries: new Map(), sourceBytes: 0 }),
|
|
169
181
|
);
|
|
@@ -176,21 +188,25 @@ const makeIndex = Effect.fn("InMemorySemanticIndex.make")(function* (
|
|
|
176
188
|
"InMemorySemanticIndex.replace",
|
|
177
189
|
)(function* (rawRequest) {
|
|
178
190
|
const operation = "replace semantic memory source";
|
|
191
|
+
|
|
179
192
|
yield* ensureOpen(operation);
|
|
180
193
|
const request = yield* decodeBoundary(MemoryIndexReplacement.Wire, rawRequest, operation);
|
|
181
194
|
const source = freezeSource(request.source);
|
|
182
195
|
const sourceBytes = sourceIdentityBytes(source);
|
|
183
196
|
const chunks = Object.freeze(request.chunks.map(freezeChunk));
|
|
197
|
+
|
|
184
198
|
if (source.source.id !== source.key.id) return yield* error(operation, "invalid-input");
|
|
185
199
|
if (!sameProfile(request.profile, profile)) return yield* error(operation, "incompatible");
|
|
186
200
|
yield* validateChunks(chunks, profile, operation);
|
|
187
201
|
const indexedAt = yield* Clock.currentTimeMillis;
|
|
202
|
+
|
|
188
203
|
const failure = yield* Ref.modify(
|
|
189
204
|
data,
|
|
190
205
|
(current): readonly [MemoryIndexError | undefined, IndexData] => {
|
|
191
206
|
if (current.closed) return [error(operation, "unavailable"), current];
|
|
192
207
|
const id = keyString(source.key);
|
|
193
208
|
const existing = current.entries.get(id);
|
|
209
|
+
|
|
194
210
|
if (
|
|
195
211
|
existing !== undefined &&
|
|
196
212
|
(existing._tag === "Withdrawn" || sourceIsFenced(source, existing))
|
|
@@ -201,17 +217,22 @@ const makeIndex = Effect.fn("InMemorySemanticIndex.make")(function* (
|
|
|
201
217
|
return [error(operation, "budget"), current];
|
|
202
218
|
}
|
|
203
219
|
const nextSourceBytes = current.sourceBytes - (existing?.sourceBytes ?? 0) + sourceBytes;
|
|
220
|
+
|
|
204
221
|
if (nextSourceBytes > maxSourceBytes) return [error(operation, "budget"), current];
|
|
205
222
|
let count = chunks.length;
|
|
223
|
+
|
|
206
224
|
for (const [entryId, entry] of current.entries) {
|
|
207
225
|
if (entryId !== id && entry._tag === "Indexed") count += entry.chunks.length;
|
|
208
226
|
}
|
|
209
227
|
if (count > capacity.maxChunks) return [error(operation, "budget"), current];
|
|
210
228
|
const entries = new Map(current.entries);
|
|
229
|
+
|
|
211
230
|
entries.set(id, { _tag: "Indexed", source, sourceBytes, chunks, indexedAt });
|
|
231
|
+
|
|
212
232
|
return [undefined, { ...current, entries, sourceBytes: nextSourceBytes }];
|
|
213
233
|
},
|
|
214
234
|
);
|
|
235
|
+
|
|
215
236
|
if (failure !== undefined) return yield* failure;
|
|
216
237
|
});
|
|
217
238
|
|
|
@@ -219,18 +240,24 @@ const makeIndex = Effect.fn("InMemorySemanticIndex.make")(function* (
|
|
|
219
240
|
"InMemorySemanticIndex.withdraw",
|
|
220
241
|
)(function* (rawSource) {
|
|
221
242
|
const operation = "withdraw semantic memory source";
|
|
243
|
+
|
|
222
244
|
yield* ensureOpen(operation);
|
|
245
|
+
|
|
223
246
|
const source = freezeSource(
|
|
224
247
|
yield* decodeBoundary(MemoryIndexSource.Wire, rawSource, operation),
|
|
225
248
|
);
|
|
249
|
+
|
|
226
250
|
const sourceBytes = sourceIdentityBytes(source);
|
|
251
|
+
|
|
227
252
|
if (source.source.id !== source.key.id) return yield* error(operation, "invalid-input");
|
|
253
|
+
|
|
228
254
|
const failure = yield* Ref.modify(
|
|
229
255
|
data,
|
|
230
256
|
(current): readonly [MemoryIndexError | undefined, IndexData] => {
|
|
231
257
|
if (current.closed) return [error(operation, "unavailable"), current];
|
|
232
258
|
const id = keyString(source.key);
|
|
233
259
|
const existing = current.entries.get(id);
|
|
260
|
+
|
|
234
261
|
if (existing !== undefined) {
|
|
235
262
|
if (existing._tag === "Withdrawn") {
|
|
236
263
|
return [
|
|
@@ -243,26 +270,34 @@ const makeIndex = Effect.fn("InMemorySemanticIndex.make")(function* (
|
|
|
243
270
|
return [error(operation, "budget"), current];
|
|
244
271
|
}
|
|
245
272
|
const nextSourceBytes = current.sourceBytes - (existing?.sourceBytes ?? 0) + sourceBytes;
|
|
273
|
+
|
|
246
274
|
if (nextSourceBytes > maxSourceBytes) return [error(operation, "budget"), current];
|
|
247
275
|
const entries = new Map(current.entries);
|
|
276
|
+
|
|
248
277
|
entries.set(id, { _tag: "Withdrawn", source, sourceBytes });
|
|
278
|
+
|
|
249
279
|
return [undefined, { ...current, entries, sourceBytes: nextSourceBytes }];
|
|
250
280
|
},
|
|
251
281
|
);
|
|
282
|
+
|
|
252
283
|
if (failure !== undefined) return yield* failure;
|
|
253
284
|
});
|
|
254
285
|
|
|
255
286
|
const search = Effect.fn("InMemorySemanticIndex.search")(function* (rawQuery: MemoryIndexQuery) {
|
|
256
287
|
const operation = "search semantic memory index";
|
|
288
|
+
|
|
257
289
|
yield* ensureOpen(operation);
|
|
258
290
|
const query = yield* decodeBoundary(MemoryIndexQuery.Wire, rawQuery, operation);
|
|
259
291
|
const vector = Object.freeze([...query.vector]);
|
|
292
|
+
|
|
260
293
|
if (!validVector(vector, profile)) return yield* error(operation, "invalid-input");
|
|
261
294
|
const current = yield* Ref.get(data);
|
|
295
|
+
|
|
262
296
|
if (current.closed) return yield* error(operation, "unavailable");
|
|
263
297
|
let scannedChunks = 0;
|
|
264
298
|
let inspectedSources = 0;
|
|
265
299
|
const candidates: Array<MemoryIndexCandidate> = [];
|
|
300
|
+
|
|
266
301
|
for (const entry of current.entries.values()) {
|
|
267
302
|
inspectedSources += 1;
|
|
268
303
|
if (inspectedSources % 128 === 0) yield* Effect.yieldNow;
|
|
@@ -283,6 +318,7 @@ const makeIndex = Effect.fn("InMemorySemanticIndex.make")(function* (
|
|
|
283
318
|
yield* Effect.yieldNow;
|
|
284
319
|
for (const chunk of entry.chunks) {
|
|
285
320
|
const score = cosine(vector, chunk.vector);
|
|
321
|
+
|
|
286
322
|
if (score < query.minScore) continue;
|
|
287
323
|
candidates.push(
|
|
288
324
|
MemoryIndexCandidate.make({
|
|
@@ -306,6 +342,7 @@ const makeIndex = Effect.fn("InMemorySemanticIndex.make")(function* (
|
|
|
306
342
|
left.ordinal - right.ordinal,
|
|
307
343
|
);
|
|
308
344
|
yield* ensureOpen(operation);
|
|
345
|
+
|
|
309
346
|
return MemoryIndexSearch.make({ candidates: candidates.slice(0, query.limit), scannedChunks });
|
|
310
347
|
});
|
|
311
348
|
|