@tangle-network/agent-knowledge 3.2.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4740 @@
1
+ import {
2
+ DEFAULT_MEMORY_RECOVERY_RETRIES_PER_ATTEMPT,
3
+ acquireAgentMemoryRunLease,
4
+ appendAttemptJournalEvent,
5
+ appendDurableJournalEvent,
6
+ assertNoInterruptedPaidCalls,
7
+ createMemoryExecutionPool,
8
+ hasSettledPaidCall,
9
+ memoryHitToSourceRecord,
10
+ memoryRecoveryDelayMs,
11
+ memoryWriteResultToSourceRecord,
12
+ readActiveAttemptJournal,
13
+ reconcileInterruptedMemoryPaidCalls,
14
+ reconcileInterruptedRunPaidCalls,
15
+ releaseMemoryAdapterCreatedAfterAbort,
16
+ reserveRecoveryAttempts,
17
+ resolveMemoryCleanupTimeoutMs,
18
+ runBoundedMemoryLifecycle,
19
+ scoreMemoryBenchmarkArtifact,
20
+ sleepForMemoryRecovery
21
+ } from "./chunk-DW6APRTX.js";
22
+ import {
23
+ sha256,
24
+ stableId
25
+ } from "./chunk-YMKHCTS2.js";
26
+
27
+ // src/memory/holdout.ts
28
+ import { randomUUID } from "crypto";
29
+ import { mulberry32 } from "@tangle-network/agent-eval";
30
+ function deterministicRng(key) {
31
+ return mulberry32(Number.parseInt(sha256(key).slice(0, 8), 16))();
32
+ }
33
+ function canonicalJson(value) {
34
+ if (typeof value === "bigint") {
35
+ throw new TypeError("canonicalJson: bigint values are not supported in holdout hashing");
36
+ }
37
+ if (value === void 0) return "null";
38
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
39
+ if (value !== null && typeof value === "object") {
40
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`);
41
+ return `{${entries.join(",")}}`;
42
+ }
43
+ return JSON.stringify(value);
44
+ }
45
+ function retrievalHoldoutConfigHash(config) {
46
+ return sha256(
47
+ canonicalJson({ epsilon: config.epsilon, watchlist: [...config.watchlist ?? []].sort() })
48
+ ).slice(0, 16);
49
+ }
50
+ function assertValidHoldoutConfig(config) {
51
+ const { epsilon, watchlist } = config;
52
+ if (typeof epsilon !== "number" || !Number.isFinite(epsilon) || epsilon < 0 || epsilon > 1) {
53
+ throw new Error(`retrieval holdout epsilon must be a number in [0, 1], got ${String(epsilon)}`);
54
+ }
55
+ if (watchlist !== void 0 && (!Array.isArray(watchlist) || watchlist.some((id) => typeof id !== "string"))) {
56
+ throw new Error("retrieval holdout watchlist must be an array of item-id strings");
57
+ }
58
+ }
59
+ function safeEmit(config, event) {
60
+ try {
61
+ config.onEvent(event);
62
+ } catch (error) {
63
+ console.error(
64
+ "[agent-knowledge] retrieval holdout onEvent sink threw; retrieval continues",
65
+ error
66
+ );
67
+ }
68
+ }
69
+ function applyRetrievalHoldout(hits, config, ctx = {}) {
70
+ assertValidHoldoutConfig(config);
71
+ const rng = config.rng ?? deterministicRng;
72
+ const sessionId = ctx.sessionId ?? ctx.scope?.sessionId;
73
+ const holdoutEligible = typeof sessionId === "string" && sessionId.length > 0;
74
+ const base = holdoutEventBase(hits, config, ctx);
75
+ let session;
76
+ if (holdoutEligible) {
77
+ const prior = ctx.session?.sessionId === sessionId ? ctx.session : void 0;
78
+ session = prior ?? {
79
+ sessionId,
80
+ callCount: 0,
81
+ // The epsilon coin depends only on sessionId, so assignment is independent of task
82
+ // features by construction and exactly replayable (design rules D1 + D5).
83
+ sessionHoldout: rng(`${sessionId}#holdout`) < config.epsilon,
84
+ targetId: null,
85
+ pickPropensity: null
86
+ };
87
+ session = { ...session, callCount: session.callCount + 1 };
88
+ }
89
+ const watchlistEligible = base.watchlistEligible;
90
+ if (session?.sessionHoldout && session.targetId === null && watchlistEligible.length > 0) {
91
+ const candidates = [...watchlistEligible].sort();
92
+ const index = Math.min(
93
+ Math.floor(rng(`${session.sessionId}#pick`) * candidates.length),
94
+ candidates.length - 1
95
+ );
96
+ const picked = candidates[index];
97
+ if (picked !== void 0) {
98
+ session = { ...session, targetId: picked, pickPropensity: 1 / candidates.length };
99
+ }
100
+ }
101
+ const targetId = session?.sessionHoldout ? session.targetId ?? null : null;
102
+ const droppedId = targetId !== null && hits.some((hit) => hit.id === targetId) ? targetId : null;
103
+ const delivered = droppedId === null ? hits : hits.filter((hit) => hit.id !== droppedId);
104
+ const pickPropensity = session?.sessionHoldout ? session.pickPropensity ?? null : null;
105
+ const event = {
106
+ ...base,
107
+ callIndex: session?.callCount ?? 0,
108
+ holdoutEligible,
109
+ sessionHoldout: session?.sessionHoldout ?? false,
110
+ sessionTargetId: targetId,
111
+ droppedId,
112
+ pickPropensity,
113
+ dropPropensity: pickPropensity !== null ? config.epsilon * pickPropensity : null,
114
+ deliveredIds: delivered.map((hit) => hit.id)
115
+ };
116
+ safeEmit(config, event);
117
+ return droppedId === null ? { delivered: hits, event, ...session !== void 0 ? { session } : {} } : { delivered, event, ...session !== void 0 ? { session } : {} };
118
+ }
119
+ function holdoutEventBase(hits, config, ctx) {
120
+ const sessionId = ctx.sessionId ?? ctx.scope?.sessionId;
121
+ const watchlist = config.watchlist ?? [];
122
+ const watchlistSet = new Set(watchlist);
123
+ const plaintext = config.includePlaintextIdentifiers === true;
124
+ return {
125
+ v: 1,
126
+ eventId: randomUUID(),
127
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
128
+ ...config.adapterId !== void 0 ? { adapterId: config.adapterId } : {},
129
+ ...plaintext && sessionId !== void 0 ? { sessionId } : {},
130
+ ...ctx.taskId !== void 0 ? { taskId: ctx.taskId } : {},
131
+ ...sessionId !== void 0 ? { sessionIdHash: sha256(sessionId).slice(0, 16) } : {},
132
+ ...ctx.query !== void 0 ? { queryHash: sha256(ctx.query).slice(0, 16) } : {},
133
+ ...plaintext && ctx.scope !== void 0 ? { scope: ctx.scope } : {},
134
+ ...ctx.scope !== void 0 ? { scopeHash: sha256(canonicalJson(ctx.scope)).slice(0, 16) } : {},
135
+ config: {
136
+ epsilon: config.epsilon,
137
+ watchlist: [...watchlist],
138
+ ...config.configVersion !== void 0 ? { configVersion: config.configVersion } : {}
139
+ },
140
+ configHash: retrievalHoldoutConfigHash(config),
141
+ eligible: hits.map((hit, index) => ({
142
+ id: hit.id,
143
+ rank: index + 1,
144
+ ...typeof (hit.normalizedScore ?? hit.score) === "number" ? { score: hit.normalizedScore ?? hit.score } : {},
145
+ kind: hit.kind,
146
+ contentHash: sha256(hit.text).slice(0, 16)
147
+ })),
148
+ watchlistEligible: hits.filter((hit) => watchlistSet.has(hit.id)).map((hit) => hit.id),
149
+ ...config.corpusVersion !== void 0 ? { corpusVersion: config.corpusVersion } : {}
150
+ };
151
+ }
152
+ function emitRetrievalHoldoutBypass(hits, config, ctx, bypassReason) {
153
+ assertValidHoldoutConfig(config);
154
+ const event = {
155
+ ...holdoutEventBase(hits, config, ctx),
156
+ callIndex: 0,
157
+ holdoutEligible: false,
158
+ sessionHoldout: false,
159
+ sessionTargetId: null,
160
+ droppedId: null,
161
+ pickPropensity: null,
162
+ dropPropensity: null,
163
+ deliveredIds: hits.map((hit) => hit.id),
164
+ bypassReason
165
+ };
166
+ safeEmit(config, event);
167
+ return event;
168
+ }
169
+ var DEFAULT_MAX_TRACKED_SESSIONS = 1e4;
170
+ var sessionRegistry = /* @__PURE__ */ new Map();
171
+ function resetRetrievalHoldoutRegistry() {
172
+ sessionRegistry.clear();
173
+ }
174
+ function applySessionStickyRetrievalHoldout(hits, config, ctx = {}) {
175
+ assertValidHoldoutConfig(config);
176
+ const sessionId = ctx.sessionId ?? ctx.scope?.sessionId;
177
+ const configHash = retrievalHoldoutConfigHash(config);
178
+ let sessions = sessionRegistry.get(configHash);
179
+ if (!sessions) {
180
+ sessions = /* @__PURE__ */ new Map();
181
+ sessionRegistry.set(configHash, sessions);
182
+ }
183
+ const prior = sessionId !== void 0 ? sessions.get(sessionId) : void 0;
184
+ const result = applyRetrievalHoldout(hits, config, {
185
+ ...ctx,
186
+ ...prior ? { session: prior } : {}
187
+ });
188
+ if (sessionId !== void 0 && result.session) {
189
+ const cap = config.maxTrackedSessions ?? DEFAULT_MAX_TRACKED_SESSIONS;
190
+ if (!sessions.has(sessionId) && sessions.size >= cap) {
191
+ const oldest = sessions.keys().next().value;
192
+ if (oldest !== void 0) sessions.delete(oldest);
193
+ }
194
+ sessions.set(sessionId, result.session);
195
+ }
196
+ return result;
197
+ }
198
+ function toOffPolicyTrajectory(events, options) {
199
+ const groups = /* @__PURE__ */ new Map();
200
+ let unattributableEvents = 0;
201
+ for (const event of events) {
202
+ if (event.sessionIdHash === void 0) {
203
+ unattributableEvents += 1;
204
+ continue;
205
+ }
206
+ const key = `${event.configHash}:${event.sessionIdHash}`;
207
+ const group = groups.get(key);
208
+ if (group) group.push(event);
209
+ else groups.set(key, [event]);
210
+ }
211
+ const trajectories = [];
212
+ const sessions = [];
213
+ const excluded = [];
214
+ for (const [runId, group] of groups) {
215
+ const ordered = [...group].sort((a, b) => a.callIndex - b.callIndex || a.ts.localeCompare(b.ts));
216
+ const randomized = ordered.filter((event) => event.holdoutEligible);
217
+ const firstEvent = ordered[0];
218
+ if (firstEvent?.sessionIdHash === void 0) continue;
219
+ const base = {
220
+ runId,
221
+ configHash: firstEvent.configHash,
222
+ sessionIdHash: firstEvent.sessionIdHash,
223
+ callCount: randomized.length,
224
+ bypassCallCount: ordered.length - randomized.length
225
+ };
226
+ const first = randomized[0];
227
+ if (first === void 0) {
228
+ excluded.push({
229
+ ...base,
230
+ sessionHoldout: false,
231
+ sessionTargetId: null,
232
+ droppedId: null,
233
+ firstCandidateCount: 0,
234
+ behaviorProb: 1,
235
+ mixedExposure: false,
236
+ exclusionReason: "no-randomized-calls"
237
+ });
238
+ continue;
239
+ }
240
+ const targets = new Set(
241
+ randomized.map((event) => event.sessionTargetId).filter((target) => target !== null)
242
+ );
243
+ const arms = new Set(randomized.map((event) => event.sessionHoldout));
244
+ const mixedExposure = targets.size > 1 || arms.size > 1;
245
+ const dropEvent = randomized.find((event) => event.droppedId !== null);
246
+ const firstIntersecting = randomized.find((event) => event.watchlistEligible.length > 0);
247
+ const sessionTargetId = dropEvent?.sessionTargetId ?? [...targets][0] ?? null;
248
+ let behaviorProb;
249
+ if (dropEvent !== void 0) {
250
+ if (dropEvent.dropPropensity === null) {
251
+ throw new Error(`holdout event ${dropEvent.eventId} recorded a drop without dropPropensity`);
252
+ }
253
+ behaviorProb = dropEvent.dropPropensity;
254
+ } else if (!mixedExposure && sessionTargetId !== null) {
255
+ throw new Error(
256
+ `holdout session ${base.sessionIdHash} has a drawn target but no drop event: the event batch is missing this session draw call`
257
+ );
258
+ } else if (firstIntersecting === void 0) {
259
+ behaviorProb = 1;
260
+ } else {
261
+ behaviorProb = 1 - first.config.epsilon;
262
+ }
263
+ const summary = {
264
+ ...base,
265
+ sessionHoldout: first.sessionHoldout,
266
+ sessionTargetId,
267
+ droppedId: dropEvent?.droppedId ?? null,
268
+ firstCandidateCount: firstIntersecting ? new Set(firstIntersecting.watchlistEligible).size : 0,
269
+ behaviorProb,
270
+ mixedExposure,
271
+ ...mixedExposure ? { exclusionReason: "mixed-exposure" } : {}
272
+ };
273
+ if (mixedExposure) {
274
+ excluded.push(summary);
275
+ continue;
276
+ }
277
+ const reward = options.rewards[summary.sessionIdHash];
278
+ if (reward === void 0) {
279
+ throw new Error(
280
+ `no reward for session ${summary.sessionIdHash}: filter events to scored sessions before converting`
281
+ );
282
+ }
283
+ trajectories.push({
284
+ runId,
285
+ reward,
286
+ behaviorProb,
287
+ targetProb: options.targetProb?.(summary) ?? (summary.droppedId === null ? 1 : 0),
288
+ qHat: options.qHat?.(summary) ?? null
289
+ });
290
+ sessions.push(summary);
291
+ }
292
+ return { trajectories, sessions, excluded, unattributableEvents };
293
+ }
294
+
295
+ // src/memory/adapter.ts
296
+ async function defaultGetMemoryContext(adapter, query, options = {}) {
297
+ const hits = await adapter.search(query, options);
298
+ const delivered = options.holdout ? applySessionStickyRetrievalHoldout(hits, options.holdout, {
299
+ query,
300
+ ...options.scope !== void 0 ? { scope: options.scope } : {},
301
+ ...options.scope?.tags?.taskId !== void 0 ? { taskId: options.scope.tags.taskId } : {}
302
+ }).delivered : hits;
303
+ return {
304
+ query,
305
+ hits: delivered,
306
+ sourceRecords: delivered.map((hit) => memoryHitToSourceRecord(hit, { scope: options.scope })),
307
+ text: renderMemoryContext(delivered)
308
+ };
309
+ }
310
+ function renderMemoryContext(hits) {
311
+ return hits.map((hit, index) => {
312
+ const label = hit.title ?? `${hit.kind}:${hit.id}`;
313
+ const score = typeof hit.normalizedScore === "number" ? ` score=${hit.normalizedScore.toFixed(3)}` : "";
314
+ return [`[${index + 1}] ${label}${score}`, hit.text].join("\n");
315
+ }).join("\n\n");
316
+ }
317
+
318
+ // src/memory/branch.ts
319
+ import { canonicalJson as canonicalJson2 } from "@tangle-network/agent-eval";
320
+
321
+ // src/memory/rank.ts
322
+ function mergeRankedMemoryHits(groups, limit) {
323
+ const byIdentity = /* @__PURE__ */ new Map();
324
+ let encounter = 0;
325
+ for (const [groupIndex, group] of groups.entries()) {
326
+ for (const [rank, hit] of group.entries()) {
327
+ const key = JSON.stringify([hit.uri, hit.text]);
328
+ const score = memoryHitScore(hit);
329
+ const prior = byIdentity.get(key);
330
+ if (!prior) {
331
+ byIdentity.set(key, {
332
+ hit,
333
+ score,
334
+ reciprocalRank: 1 / (60 + rank + 1),
335
+ bestRank: rank,
336
+ firstGroup: groupIndex,
337
+ encounter
338
+ });
339
+ } else {
340
+ prior.reciprocalRank += 1 / (60 + rank + 1);
341
+ prior.bestRank = Math.min(prior.bestRank, rank);
342
+ if (score !== void 0 && (prior.score === void 0 || score > prior.score)) {
343
+ prior.hit = hit;
344
+ prior.score = score;
345
+ }
346
+ }
347
+ encounter += 1;
348
+ }
349
+ }
350
+ return [...byIdentity.values()].sort(
351
+ (left, right) => right.reciprocalRank - left.reciprocalRank || Number(right.score !== void 0) - Number(left.score !== void 0) || (right.score ?? 0) - (left.score ?? 0) || left.bestRank - right.bestRank || left.firstGroup - right.firstGroup || left.encounter - right.encounter
352
+ ).map(({ hit }) => hit).slice(0, limit ?? Number.POSITIVE_INFINITY);
353
+ }
354
+ function memoryHitScore(hit) {
355
+ const score = hit.normalizedScore ?? hit.score;
356
+ return score !== void 0 && Number.isFinite(score) ? score : void 0;
357
+ }
358
+
359
+ // src/memory/branch.ts
360
+ var DEFAULT_POLICY = {
361
+ read: ["private"],
362
+ write: "private"
363
+ };
364
+ var adapterLeases = /* @__PURE__ */ new WeakMap();
365
+ function createAgentMemoryBranch(options) {
366
+ if (typeof options.branchId !== "string" || !options.branchId.trim()) {
367
+ throw new Error("memory branchId must be a non-empty string");
368
+ }
369
+ const snapshot = options.snapshot;
370
+ if (snapshot && snapshot.branchId !== options.branchId) {
371
+ throw new Error(
372
+ `memory branch snapshot belongs to ${snapshot.branchId}, not ${options.branchId}`
373
+ );
374
+ }
375
+ if (snapshot && snapshot.adapterId !== options.adapter.id) {
376
+ throw new Error(
377
+ `memory branch snapshot uses ${snapshot.adapterId}, not ${options.adapter.id}; fork it to replay into another adapter`
378
+ );
379
+ }
380
+ if (snapshot?.lifetime === "attempt") {
381
+ throw new Error(
382
+ "memory branch attempt snapshots cannot be resumed; replay them into a fresh branch instead"
383
+ );
384
+ }
385
+ if (options.lifetime !== void 0 && !["resumable", "attempt"].includes(options.lifetime)) {
386
+ throw new Error("memory branch lifetime must be resumable or attempt");
387
+ }
388
+ if (snapshot) {
389
+ assertDurableValue(snapshot, `${options.branchId}: branch snapshot`);
390
+ assertSnapshotDigest(snapshot);
391
+ }
392
+ if (snapshot && options.policy && canonicalJson2(stripUndefined(options.policy)) !== canonicalJson2(stripUndefined(snapshot.policy))) {
393
+ throw new Error("memory branch snapshot policy cannot change during resume; fork it instead");
394
+ }
395
+ if (snapshot && options.baseScope && canonicalJson2(stripUndefined(options.baseScope)) !== canonicalJson2(stripUndefined(snapshot.baseScope))) {
396
+ throw new Error("memory branch snapshot scope cannot change during resume; fork it instead");
397
+ }
398
+ if (snapshot && options.lifetime && options.lifetime !== snapshot.lifetime) {
399
+ throw new Error("memory branch snapshot lifetime cannot change during resume; fork it instead");
400
+ }
401
+ const branchId = options.branchId;
402
+ const parentBranchId = options.parentBranchId ?? snapshot?.parentBranchId;
403
+ const lifetime = options.lifetime ?? snapshot?.lifetime ?? "resumable";
404
+ const policy = normalizePolicy(options.policy ?? snapshot?.policy ?? DEFAULT_POLICY);
405
+ assertBranchIsolation(options.adapter, branchId, policy, lifetime);
406
+ const mergedBaseScope = mergeScopes(void 0, snapshot?.baseScope ?? options.baseScope);
407
+ assertDurableValue(mergedBaseScope, `${branchId}: base scope`);
408
+ const baseScope = cloneDurableValue(mergedBaseScope);
409
+ const allowedWriteScopeKeys = options.allowedWriteScopes ? new Set(
410
+ options.allowedWriteScopes.map(
411
+ (scope) => canonicalJson2(stripUndefined(mergeScopes(baseScope, scope)))
412
+ )
413
+ ) : void 0;
414
+ const journal = [...snapshot?.journal ?? []].map(cloneJournalEntry);
415
+ let nextSequence = journal.reduce((max, entry) => Math.max(max, entry.sequence + 1), 0);
416
+ const queues = /* @__PURE__ */ new Map();
417
+ const pending = /* @__PURE__ */ new Set();
418
+ const reads = /* @__PURE__ */ new Set();
419
+ const active = /* @__PURE__ */ new Set();
420
+ const touchedScopes = /* @__PURE__ */ new Map();
421
+ for (const entry of journal) {
422
+ const scope = mergeScopes(baseScope, entry.input.scope);
423
+ touchedScopes.set(canonicalJson2(stripUndefined(scope)), scope);
424
+ }
425
+ let mutationBarrier = Promise.resolve();
426
+ const releaseAdapter = retainAdapter(options.adapter, branchId);
427
+ let lifecycle = "open";
428
+ let closePromise;
429
+ const assertOpen = (operation) => {
430
+ if (lifecycle !== "open") throw new Error(`${branchId}: cannot ${operation} a closed branch`);
431
+ };
432
+ const track = (operation) => {
433
+ active.add(operation);
434
+ void operation.then(
435
+ () => active.delete(operation),
436
+ () => active.delete(operation)
437
+ );
438
+ return operation;
439
+ };
440
+ const trackRead = (operation) => {
441
+ reads.add(operation);
442
+ void operation.then(
443
+ () => reads.delete(operation),
444
+ () => reads.delete(operation)
445
+ );
446
+ return track(operation);
447
+ };
448
+ const flushWrites = async () => {
449
+ const results = await Promise.allSettled([...pending]);
450
+ const failures = results.flatMap(
451
+ (result) => result.status === "rejected" ? [result.reason] : []
452
+ );
453
+ if (failures.length === 1) throw failures[0];
454
+ if (failures.length > 1) {
455
+ throw new AggregateError(failures, `${branchId}: pending memory writes failed`);
456
+ }
457
+ };
458
+ const flushAll = async () => {
459
+ await flushWrites();
460
+ await options.adapter.flush?.();
461
+ };
462
+ const runExclusive = async (operation, exclusiveOptions = {}) => {
463
+ const precedingBarrier = mutationBarrier;
464
+ const precedingWrites = [...pending];
465
+ const precedingReads = [...reads];
466
+ let releaseBarrier;
467
+ const ownBarrier = new Promise((resolve) => {
468
+ releaseBarrier = resolve;
469
+ });
470
+ mutationBarrier = precedingBarrier.then(() => ownBarrier);
471
+ await precedingBarrier;
472
+ const settled = await Promise.allSettled([...precedingWrites, ...precedingReads]);
473
+ const failures = settled.flatMap(
474
+ (result) => result.status === "rejected" ? [result.reason] : []
475
+ );
476
+ try {
477
+ if ((exclusiveOptions.failOnOperationError ?? true) && failures.length > 0) {
478
+ if (failures.length === 1) throw failures[0];
479
+ throw new AggregateError(failures, `${branchId}: pending memory operations failed`);
480
+ }
481
+ if (exclusiveOptions.flushAdapter ?? true) await options.adapter.flush?.();
482
+ return await operation();
483
+ } finally {
484
+ releaseBarrier();
485
+ }
486
+ };
487
+ const branch = {
488
+ id: `${options.adapter.id}:branch:${stableId("branch", branchId)}`,
489
+ branchId,
490
+ lifetime,
491
+ ...parentBranchId !== void 0 ? { parentBranchId } : {},
492
+ policy,
493
+ async search(query, searchOptions = {}) {
494
+ assertOpen("search");
495
+ const precedingBarrier = mutationBarrier;
496
+ return trackRead(
497
+ (async () => {
498
+ await precedingBarrier;
499
+ const logicalScope = mergeScopes(baseScope, searchOptions.scope);
500
+ const scopes = policy.read.map(
501
+ (visibility) => physicalScope(branchId, logicalScope, visibility)
502
+ );
503
+ const groups = await Promise.all(
504
+ scopes.map(async (scope) => {
505
+ const hits = await options.adapter.search(query, {
506
+ ...searchOptions,
507
+ scope,
508
+ holdout: void 0
509
+ });
510
+ return hits.map((hit) => ({
511
+ ...hit,
512
+ metadata: {
513
+ ...hit.metadata,
514
+ memoryBranchId: branchId,
515
+ memoryVisibility: scope.tags?.memoryVisibility
516
+ }
517
+ }));
518
+ })
519
+ );
520
+ return mergeRankedMemoryHits(groups, searchOptions.limit);
521
+ })()
522
+ );
523
+ },
524
+ async getContext(query, searchOptions = {}) {
525
+ assertOpen("read context from");
526
+ return track(defaultGetMemoryContext(branch, query, searchOptions));
527
+ },
528
+ async write(input) {
529
+ assertOpen("write to");
530
+ const sequence = nextSequence;
531
+ nextSequence += 1;
532
+ assertDurableValue(input, `${branch.id}: write input`);
533
+ const logicalInput = cloneWriteInput(input);
534
+ logicalInput.id ??= stableId("memory_write", `${branchId}:${sequence}`);
535
+ const logicalScope = mergeScopes(baseScope, logicalInput.scope);
536
+ if (allowedWriteScopeKeys && !allowedWriteScopeKeys.has(canonicalJson2(stripUndefined(logicalScope)))) {
537
+ throw new Error(
538
+ `${branch.id}: write scope was not declared in the experiment sequence cleanupScopes`
539
+ );
540
+ }
541
+ const scope = physicalScope(branchId, logicalScope, policy.write);
542
+ const queueKey = writeQueueKey(branchId, logicalScope, policy.write);
543
+ touchedScopes.set(canonicalJson2(stripUndefined(logicalScope)), logicalScope);
544
+ const previous = queues.get(queueKey) ?? Promise.resolve();
545
+ const precedingBarrier = mutationBarrier;
546
+ let resultPromise;
547
+ const queued = Promise.all([previous.catch(() => void 0), precedingBarrier]).then(
548
+ async () => {
549
+ const result = await options.adapter.write({ ...logicalInput, scope });
550
+ if (result.accepted) {
551
+ assertDurableValue(result, `${branch.id}: write result`);
552
+ journal.push({
553
+ sequence,
554
+ input: logicalInput,
555
+ result: cloneWriteResult(result)
556
+ });
557
+ }
558
+ return result;
559
+ }
560
+ );
561
+ resultPromise = queued;
562
+ const tail = queued.then(
563
+ () => void 0,
564
+ () => void 0
565
+ );
566
+ queues.set(queueKey, tail);
567
+ pending.add(resultPromise);
568
+ void resultPromise.then(
569
+ () => {
570
+ pending.delete(resultPromise);
571
+ if (queues.get(queueKey) === tail) queues.delete(queueKey);
572
+ },
573
+ () => {
574
+ pending.delete(resultPromise);
575
+ if (queues.get(queueKey) === tail) queues.delete(queueKey);
576
+ }
577
+ );
578
+ return track(resultPromise);
579
+ },
580
+ async clear(scope) {
581
+ assertOpen("clear");
582
+ return track(
583
+ runExclusive(
584
+ async () => {
585
+ if (!options.adapter.clear) {
586
+ throw new Error(`${options.adapter.id}: adapter does not support scoped clear`);
587
+ }
588
+ const flushErrors = [];
589
+ try {
590
+ await options.adapter.flush?.();
591
+ } catch (error) {
592
+ flushErrors.push(error);
593
+ }
594
+ const requestedScope = scope ? mergeScopes(baseScope, scope) : void 0;
595
+ const logicalScopes = /* @__PURE__ */ new Map();
596
+ if (requestedScope) {
597
+ logicalScopes.set(canonicalJson2(stripUndefined(requestedScope)), requestedScope);
598
+ for (const touched of touchedScopes.values()) {
599
+ if (scopeMatches(touched, requestedScope)) {
600
+ logicalScopes.set(canonicalJson2(stripUndefined(touched)), touched);
601
+ }
602
+ }
603
+ } else if (touchedScopes.size > 0) {
604
+ for (const [key, touched] of touchedScopes) logicalScopes.set(key, touched);
605
+ } else {
606
+ logicalScopes.set(canonicalJson2(stripUndefined(baseScope)), baseScope);
607
+ }
608
+ const visibilities = /* @__PURE__ */ new Set([...policy.read, policy.write]);
609
+ const physicalScopes = /* @__PURE__ */ new Map();
610
+ for (const logicalScope of logicalScopes.values()) {
611
+ for (const visibility of visibilities) {
612
+ const physical = physicalScope(branchId, logicalScope, visibility);
613
+ physicalScopes.set(canonicalJson2(stripUndefined(physical)), physical);
614
+ }
615
+ }
616
+ const clearErrors = [];
617
+ const clearedPhysicalScopes = /* @__PURE__ */ new Set();
618
+ for (const [key, physical] of physicalScopes) {
619
+ try {
620
+ await options.adapter.clear(physical);
621
+ clearedPhysicalScopes.add(key);
622
+ } catch (error) {
623
+ clearErrors.push(error);
624
+ }
625
+ }
626
+ if (clearedPhysicalScopes.size > 0) {
627
+ for (let index = journal.length - 1; index >= 0; index -= 1) {
628
+ const logical = mergeScopes(baseScope, journal[index].input.scope);
629
+ const physical = physicalScope(branchId, logical, policy.write);
630
+ if (clearedPhysicalScopes.has(canonicalJson2(stripUndefined(physical)))) {
631
+ journal.splice(index, 1);
632
+ }
633
+ }
634
+ for (const [key, touched] of touchedScopes) {
635
+ const physical = physicalScope(branchId, touched, policy.write);
636
+ if (clearedPhysicalScopes.has(canonicalJson2(stripUndefined(physical)))) {
637
+ touchedScopes.delete(key);
638
+ }
639
+ }
640
+ }
641
+ const failures = [...flushErrors, ...clearErrors];
642
+ if (failures.length === 1) throw failures[0];
643
+ if (failures.length > 1) {
644
+ throw new AggregateError(failures, `${branchId}: memory clear failed`);
645
+ }
646
+ },
647
+ { failOnOperationError: false, flushAdapter: false }
648
+ )
649
+ );
650
+ },
651
+ async flush() {
652
+ assertOpen("flush");
653
+ return track(runExclusive(async () => void 0));
654
+ },
655
+ close() {
656
+ if (closePromise) return closePromise;
657
+ lifecycle = "closing";
658
+ closePromise = (async () => {
659
+ const errors = [];
660
+ const operations = await Promise.allSettled([...active]);
661
+ for (const result of operations) {
662
+ if (result.status === "rejected") errors.push(result.reason);
663
+ }
664
+ try {
665
+ await flushAll();
666
+ } catch (error) {
667
+ errors.push(error);
668
+ }
669
+ try {
670
+ await releaseAdapter();
671
+ } catch (error) {
672
+ errors.push(error);
673
+ }
674
+ lifecycle = "closed";
675
+ if (errors.length === 1) throw errors[0];
676
+ if (errors.length > 1) throw new AggregateError(errors, `${branch.id}: close failed`);
677
+ })();
678
+ return closePromise;
679
+ },
680
+ async snapshot() {
681
+ assertOpen("snapshot");
682
+ return track(
683
+ runExclusive(async () => {
684
+ return createSnapshot({
685
+ branchId,
686
+ lifetime,
687
+ adapterId: options.adapter.id,
688
+ policy,
689
+ baseScope,
690
+ journal,
691
+ ...parentBranchId !== void 0 ? { parentBranchId } : {}
692
+ });
693
+ })
694
+ );
695
+ },
696
+ async fork(forkOptions) {
697
+ assertOpen("fork");
698
+ if (forkOptions.branchId === branchId) {
699
+ throw new Error(`memory branch ${branchId}: child branchId must differ from its parent`);
700
+ }
701
+ return track(
702
+ (async () => forkAgentMemoryBranchSnapshot({
703
+ snapshot: await branch.snapshot(),
704
+ adapter: forkOptions.adapter ?? options.adapter,
705
+ branchId: forkOptions.branchId,
706
+ policy: forkOptions.policy,
707
+ baseScope: forkOptions.baseScope,
708
+ lifetime: forkOptions.lifetime
709
+ }))()
710
+ );
711
+ }
712
+ };
713
+ return branch;
714
+ }
715
+ async function forkAgentMemoryBranchSnapshot(options) {
716
+ assertDurableValue(options.snapshot, `${options.snapshot.branchId}: branch snapshot`);
717
+ assertSnapshotDigest(options.snapshot);
718
+ if (options.branchId === options.snapshot.branchId) {
719
+ throw new Error(`memory branch ${options.branchId}: child branchId must differ from its parent`);
720
+ }
721
+ const child = createAgentMemoryBranch({
722
+ adapter: options.adapter,
723
+ branchId: options.branchId,
724
+ parentBranchId: options.snapshot.branchId,
725
+ policy: options.policy ?? options.snapshot.policy,
726
+ baseScope: mergeScopes(options.snapshot.baseScope, options.baseScope),
727
+ lifetime: options.lifetime ?? options.snapshot.lifetime
728
+ });
729
+ try {
730
+ for (const entry of options.snapshot.journal) {
731
+ const result = await child.write(entry.input);
732
+ if (!result.accepted) {
733
+ throw new Error(
734
+ `memory branch ${options.branchId}: adapter rejected replay of journal entry ${entry.sequence}`
735
+ );
736
+ }
737
+ }
738
+ await child.flush?.();
739
+ return child;
740
+ } catch (error) {
741
+ const cleanupErrors = [];
742
+ try {
743
+ await child.clear?.();
744
+ } catch (cleanupError) {
745
+ cleanupErrors.push(cleanupError);
746
+ }
747
+ try {
748
+ await child.close?.();
749
+ } catch (cleanupError) {
750
+ cleanupErrors.push(cleanupError);
751
+ }
752
+ if (cleanupErrors.length > 0) {
753
+ throw new AggregateError(
754
+ [error, ...cleanupErrors],
755
+ `memory branch ${options.branchId}: replay and cleanup failed`
756
+ );
757
+ }
758
+ throw error;
759
+ }
760
+ }
761
+ function assertBranchIsolation(adapter, branchId, policy, lifetime) {
762
+ const isolation = adapter.branchIsolation;
763
+ if (!isolation) {
764
+ throw new Error(
765
+ `${adapter.id}: cannot isolate memory branch '${branchId}': adapter must declare branchIsolation`
766
+ );
767
+ }
768
+ if (isolation.mode === "unsupported") {
769
+ throw new Error(
770
+ `${adapter.id}: cannot isolate memory branch '${branchId}': ${isolation.reason}`
771
+ );
772
+ }
773
+ if (isolation.mode === "scoped" && isolation.processExitSafe === false && lifetime !== "attempt") {
774
+ throw new Error(
775
+ `${adapter.id}: memory branch '${branchId}' must use lifetime='attempt' because provider writes can outlive the worker process`
776
+ );
777
+ }
778
+ if (isolation.mode === "scoped" && isolation.processExitSafe === false && (!Number.isSafeInteger(isolation.recoveryDelayMs) || isolation.recoveryDelayMs <= 0)) {
779
+ throw new Error(`${adapter.id}: processExitSafe=false requires a positive recoveryDelayMs`);
780
+ }
781
+ if (isolation.mode === "instance" && isolation.branchId !== branchId) {
782
+ throw new Error(
783
+ `${adapter.id}: adapter instance belongs to branch '${isolation.branchId}', not '${branchId}'`
784
+ );
785
+ }
786
+ if (isolation.mode === "instance" && isolation.supportsLogicalScopes !== true && (policy.write !== "shared" || policy.read.length !== 1 || policy.read[0] !== "shared")) {
787
+ throw new Error(
788
+ `${adapter.id}: dedicated branch instances without logical scope isolation support only shared memory policy`
789
+ );
790
+ }
791
+ }
792
+ function retainAdapter(adapter, branchId) {
793
+ const existing = adapterLeases.get(adapter);
794
+ if (existing?.closed) throw new Error(`${adapter.id}: adapter is already closed`);
795
+ const state = existing ?? { references: 0, closed: false, activeBranchIds: /* @__PURE__ */ new Set() };
796
+ if (state.activeBranchIds.has(branchId)) {
797
+ throw new Error(`${adapter.id}: memory branch '${branchId}' already has an open handle`);
798
+ }
799
+ state.activeBranchIds.add(branchId);
800
+ state.references += 1;
801
+ adapterLeases.set(adapter, state);
802
+ let released = false;
803
+ return async () => {
804
+ if (released) return;
805
+ released = true;
806
+ state.activeBranchIds.delete(branchId);
807
+ state.references -= 1;
808
+ if (state.references > 0) return;
809
+ if (!adapter.close) {
810
+ adapterLeases.delete(adapter);
811
+ return;
812
+ }
813
+ state.closed = true;
814
+ state.closePromise ??= adapter.close();
815
+ await state.closePromise;
816
+ };
817
+ }
818
+ function physicalScope(branchId, logical, visibility) {
819
+ if (visibility === "private" && !logical.agentId) {
820
+ throw new Error("private memory requires scope.agentId");
821
+ }
822
+ if (visibility === "team" && !logical.teamId) {
823
+ throw new Error("team memory requires scope.teamId");
824
+ }
825
+ const principal = visibility === "private" ? logical.agentId : visibility === "team" ? logical.teamId : "all";
826
+ const namespace = [
827
+ logical.namespace ?? "default",
828
+ stableId("branch", branchId),
829
+ visibility,
830
+ stableId("principal", principal)
831
+ ].join("/");
832
+ const physical = {
833
+ ...logical,
834
+ namespace,
835
+ tags: {
836
+ ...logical.tags ?? {},
837
+ memoryBranchId: branchId,
838
+ memoryVisibility: visibility,
839
+ logicalNamespace: logical.namespace ?? "default"
840
+ }
841
+ };
842
+ if (visibility === "shared") {
843
+ delete physical.agentId;
844
+ delete physical.teamId;
845
+ } else if (visibility === "team") {
846
+ delete physical.agentId;
847
+ }
848
+ return physical;
849
+ }
850
+ function writeQueueKey(branchId, logical, visibility) {
851
+ return canonicalJson2(
852
+ stripUndefined({
853
+ branchId,
854
+ visibility,
855
+ tenantId: logical.tenantId,
856
+ userId: logical.userId,
857
+ agentId: logical.agentId,
858
+ fallbackTeamId: logical.agentId === void 0 ? logical.teamId : void 0,
859
+ fallbackPrincipal: logical.agentId === void 0 && logical.teamId === void 0 ? "unattributed" : void 0
860
+ })
861
+ );
862
+ }
863
+ function normalizePolicy(policy) {
864
+ const allowed = /* @__PURE__ */ new Set(["private", "team", "shared"]);
865
+ if (!allowed.has(policy.write) || policy.read.some((visibility) => !allowed.has(visibility))) {
866
+ throw new Error("memory sharing policy contains an unsupported visibility");
867
+ }
868
+ const read = [...new Set(policy.read)];
869
+ if (read.length === 0) throw new Error("memory sharing policy must read at least one visibility");
870
+ return { read, write: policy.write };
871
+ }
872
+ function createSnapshot(input) {
873
+ const payload = cloneDurableValue({
874
+ ...input,
875
+ journal: [...input.journal].sort((a, b) => a.sequence - b.sequence)
876
+ });
877
+ assertDurableValue(payload, `${input.branchId}: branch snapshot`);
878
+ return {
879
+ ...payload,
880
+ digest: `sha256:${sha256(canonicalJson2(stripUndefined(payload)))}`
881
+ };
882
+ }
883
+ function assertSnapshotDigest(snapshot) {
884
+ const { digest, ...payload } = snapshot;
885
+ const actual = `sha256:${sha256(canonicalJson2(stripUndefined(payload)))}`;
886
+ if (actual !== digest)
887
+ throw new Error(`memory branch snapshot digest mismatch: ${digest} != ${actual}`);
888
+ }
889
+ function mergeScopes(base, extra) {
890
+ return {
891
+ ...base ?? {},
892
+ ...extra ?? {},
893
+ tags: { ...base?.tags ?? {}, ...extra?.tags ?? {} }
894
+ };
895
+ }
896
+ function scopeMatches(actual, requested) {
897
+ for (const key of [
898
+ "tenantId",
899
+ "userId",
900
+ "agentId",
901
+ "teamId",
902
+ "runId",
903
+ "sessionId",
904
+ "namespace"
905
+ ]) {
906
+ if (requested[key] !== void 0 && actual[key] !== requested[key]) return false;
907
+ }
908
+ for (const [key, value] of Object.entries(requested.tags ?? {})) {
909
+ if (actual.tags?.[key] !== value) return false;
910
+ }
911
+ return true;
912
+ }
913
+ function stripUndefined(value) {
914
+ if (Array.isArray(value)) return value.map(stripUndefined);
915
+ if (typeof value !== "object" || value === null) return value;
916
+ return Object.fromEntries(
917
+ Object.entries(value).filter(([, child]) => child !== void 0).map(([key, child]) => [key, stripUndefined(child)])
918
+ );
919
+ }
920
+ function cloneWriteInput(input) {
921
+ return cloneDurableValue(input);
922
+ }
923
+ function cloneWriteResult(result) {
924
+ return cloneDurableValue(result);
925
+ }
926
+ function cloneJournalEntry(entry) {
927
+ return {
928
+ sequence: entry.sequence,
929
+ input: cloneWriteInput(entry.input),
930
+ result: cloneWriteResult(entry.result)
931
+ };
932
+ }
933
+ function assertDurableValue(value, label, seen = /* @__PURE__ */ new WeakSet()) {
934
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
935
+ return;
936
+ }
937
+ if (value === void 0) return;
938
+ if (typeof value !== "object") {
939
+ throw new Error(`${label} must contain only JSON-compatible values`);
940
+ }
941
+ if (seen.has(value)) throw new Error(`${label} must not contain cycles`);
942
+ seen.add(value);
943
+ if (Array.isArray(value)) {
944
+ for (const child of value) {
945
+ if (child === void 0) {
946
+ throw new Error(`${label} arrays must not contain undefined values`);
947
+ }
948
+ assertDurableValue(child, label, seen);
949
+ }
950
+ } else {
951
+ const prototype = Object.getPrototypeOf(value);
952
+ if (prototype !== Object.prototype && prototype !== null) {
953
+ throw new Error(`${label} must contain only plain objects`);
954
+ }
955
+ for (const child of Object.values(value)) assertDurableValue(child, label, seen);
956
+ }
957
+ seen.delete(value);
958
+ }
959
+ function cloneDurableValue(value) {
960
+ if (Array.isArray(value)) return value.map((child) => cloneDurableValue(child));
961
+ if (typeof value !== "object" || value === null) return value;
962
+ return Object.fromEntries(
963
+ Object.entries(value).map(([key, child]) => [key, cloneDurableValue(child)])
964
+ );
965
+ }
966
+
967
+ // src/memory/experiment.ts
968
+ import { randomUUID as randomUUID2 } from "crypto";
969
+ import { join } from "path";
970
+ import { canonicalJson as canonicalJson3 } from "@tangle-network/agent-eval";
971
+ import {
972
+ createRunCostLedger,
973
+ fsCampaignStorage,
974
+ resolveRunDir,
975
+ runCampaign
976
+ } from "@tangle-network/agent-eval/campaign";
977
+ var MEMORY_EXPERIMENT_IMPLEMENTATION_REF = "agent-knowledge:memory-experiment:v6";
978
+ var AgentMemoryCleanupError = class extends AggregateError {
979
+ constructor(errors, message) {
980
+ super(errors, message);
981
+ this.name = "AgentMemoryCleanupError";
982
+ }
983
+ };
984
+ function buildAgentMemorySequencesFromBenchmarkCases(cases, options = {}) {
985
+ const memoryAgentId = options.memoryAgentId ?? "benchmark-agent";
986
+ return cases.map((testCase) => ({
987
+ id: testCase.id,
988
+ family: testCase.family,
989
+ ...testCase.split !== void 0 ? { split: testCase.split } : {},
990
+ ...testCase.tags !== void 0 ? { tags: testCase.tags } : {},
991
+ metadata: compactRecord({
992
+ ...testCase.metadata ?? {},
993
+ taskKind: testCase.taskKind,
994
+ source: testCase.source
995
+ }),
996
+ steps: [
997
+ ...testCase.events.map((event, eventIndex) => ({
998
+ id: `event:${event.id}`,
999
+ scope: compactScope(
1000
+ options.eventScope?.({ event, case: testCase, eventIndex }) ?? {
1001
+ agentId: memoryAgentId,
1002
+ sessionId: testCase.id
1003
+ }
1004
+ ),
1005
+ writes: [
1006
+ {
1007
+ id: event.id,
1008
+ kind: "observation",
1009
+ text: event.text,
1010
+ metadata: compactRecord({
1011
+ ...event.metadata ?? {},
1012
+ eventId: event.id,
1013
+ actorId: event.actorId,
1014
+ sessionId: event.sessionId,
1015
+ timestamp: event.timestamp
1016
+ })
1017
+ }
1018
+ ],
1019
+ metadata: { eventIndex }
1020
+ })),
1021
+ {
1022
+ id: "probe",
1023
+ scope: compactScope(
1024
+ options.probeScope?.(testCase) ?? {
1025
+ agentId: memoryAgentId,
1026
+ sessionId: testCase.id
1027
+ }
1028
+ ),
1029
+ probes: [
1030
+ {
1031
+ id: "answer",
1032
+ query: testCase.prompt,
1033
+ taskKind: testCase.taskKind,
1034
+ ...testCase.requiredFacts !== void 0 ? { requiredFacts: testCase.requiredFacts } : {},
1035
+ ...testCase.forbiddenFacts !== void 0 ? { forbiddenFacts: testCase.forbiddenFacts } : {},
1036
+ ...testCase.expectedEventIds !== void 0 ? { expectedEventIds: testCase.expectedEventIds } : {},
1037
+ ...testCase.expectedActorIds !== void 0 ? { expectedActorIds: testCase.expectedActorIds } : {},
1038
+ ...testCase.referenceAnswer !== void 0 ? { referenceAnswer: testCase.referenceAnswer } : {}
1039
+ }
1040
+ ]
1041
+ }
1042
+ ]
1043
+ }));
1044
+ }
1045
+ async function runAgentMemoryExperiment(options) {
1046
+ assertNonEmptyString(options.experimentId, "memory experiment experimentId");
1047
+ assertNonEmptyString(options.runDir, "memory experiment runDir");
1048
+ if (options.experimentRunId !== void 0) {
1049
+ assertNonEmptyString(options.experimentRunId, "memory experiment experimentRunId");
1050
+ }
1051
+ if (options.sequences.length === 0) throw new Error("memory experiment requires sequences");
1052
+ if (options.candidates.length === 0) throw new Error("memory experiment requires candidates");
1053
+ if (options.executeStep && !options.executeStepRef) {
1054
+ throw new Error("memory experiment executeStepRef is required when executeStep is configured");
1055
+ }
1056
+ assertUnique(
1057
+ options.sequences.map((sequence) => sequence.id),
1058
+ "sequence"
1059
+ );
1060
+ assertUnique(
1061
+ [...options.candidates, ...options.recoveryCandidates ?? []].map((candidate) => candidate.id),
1062
+ "candidate"
1063
+ );
1064
+ assertMemorySequences(options.sequences);
1065
+ for (const candidate of [...options.candidates, ...options.recoveryCandidates ?? []]) {
1066
+ if (options.cleanupBranches === false && !candidate.disposeAdapter) {
1067
+ throw new Error(
1068
+ `${candidate.id}: cleanupBranches=false requires disposeAdapter to delete isolated external state`
1069
+ );
1070
+ }
1071
+ if (candidate.externalCostUsdPerSequence !== void 0 && (!Number.isFinite(candidate.externalCostUsdPerSequence) || candidate.externalCostUsdPerSequence < 0)) {
1072
+ throw new Error(
1073
+ `${candidate.id}: externalCostUsdPerSequence must be a non-negative finite number`
1074
+ );
1075
+ }
1076
+ if (candidate.externalRecoveryCostUsdPerAttempt !== void 0 && (!Number.isFinite(candidate.externalRecoveryCostUsdPerAttempt) || candidate.externalRecoveryCostUsdPerAttempt < 0)) {
1077
+ throw new Error(
1078
+ `${candidate.id}: externalRecoveryCostUsdPerAttempt must be a non-negative finite number`
1079
+ );
1080
+ }
1081
+ assertNonEmptyString(candidate.ref, `${candidate.id} ref`);
1082
+ }
1083
+ const storage = options.storage ?? fsCampaignStorage();
1084
+ const runDir = resolveRunDir(options.runDir, options.repo);
1085
+ if (!storage.append) {
1086
+ throw new Error("memory experiment requires CampaignStorage.append for durable attempt state");
1087
+ }
1088
+ resolveMemoryCleanupTimeoutMs(options.cleanupTimeoutMs, "memory experiment");
1089
+ const maxRecoveryAttempts = options.maxRecoveryAttempts ?? 1e3;
1090
+ if (!Number.isSafeInteger(maxRecoveryAttempts) || maxRecoveryAttempts <= 0) {
1091
+ throw new Error("memory experiment maxRecoveryAttempts must be a positive safe integer");
1092
+ }
1093
+ const maxRecoveryRetriesPerAttempt = options.maxRecoveryRetriesPerAttempt ?? DEFAULT_MEMORY_RECOVERY_RETRIES_PER_ATTEMPT;
1094
+ if (!Number.isSafeInteger(maxRecoveryRetriesPerAttempt) || maxRecoveryRetriesPerAttempt <= 0) {
1095
+ throw new Error(
1096
+ "memory experiment maxRecoveryRetriesPerAttempt must be a positive safe integer"
1097
+ );
1098
+ }
1099
+ storage.ensureDir(runDir);
1100
+ const lease = await acquireAgentMemoryRunLease({
1101
+ experimentId: options.experimentId,
1102
+ runDir,
1103
+ storage,
1104
+ customStorage: options.storage !== void 0,
1105
+ lockFileName: "memory-experiment.lock",
1106
+ label: "memory experiment",
1107
+ controllerMode: options.controllerMode,
1108
+ acquireRunLease: options.acquireRunLease
1109
+ });
1110
+ let result;
1111
+ let primaryError;
1112
+ try {
1113
+ await lease.assertOwned();
1114
+ result = await runOwnedAgentMemoryExperiment(options, storage, runDir, lease);
1115
+ } catch (error) {
1116
+ primaryError = error;
1117
+ }
1118
+ let releaseError;
1119
+ try {
1120
+ await lease.release();
1121
+ } catch (error) {
1122
+ releaseError = error;
1123
+ }
1124
+ if (primaryError && releaseError) {
1125
+ throw new AggregateError(
1126
+ [primaryError, releaseError],
1127
+ "memory experiment failed and its controller lease could not be released"
1128
+ );
1129
+ }
1130
+ if (primaryError) throw primaryError;
1131
+ if (releaseError) throw releaseError;
1132
+ if (!result) throw new Error("memory experiment produced no result");
1133
+ return result;
1134
+ }
1135
+ async function runOwnedAgentMemoryExperiment(options, storage, runDir, lease) {
1136
+ const runIdentity = stableId(
1137
+ "memory_run",
1138
+ canonicalJson3({
1139
+ experimentId: options.experimentId,
1140
+ experimentRunId: options.experimentRunId ?? runDir
1141
+ })
1142
+ );
1143
+ const maxConcurrency = options.maxConcurrency ?? 2;
1144
+ if (!Number.isSafeInteger(maxConcurrency) || maxConcurrency <= 0) {
1145
+ throw new Error("memory experiment maxConcurrency must be a positive safe integer");
1146
+ }
1147
+ const executionPool = createMemoryExecutionPool(maxConcurrency);
1148
+ const dispatchedExecutions = [];
1149
+ const candidateById = new Map(options.candidates.map((candidate) => [candidate.id, candidate]));
1150
+ const recoveryCandidateById = new Map(
1151
+ [...options.candidates, ...options.recoveryCandidates ?? []].map((candidate) => [
1152
+ candidate.id,
1153
+ candidate
1154
+ ])
1155
+ );
1156
+ const sequenceById = new Map(options.sequences.map((sequence) => [sequence.id, sequence]));
1157
+ const scenarios = buildAgentMemorySequenceScenarios(options.sequences, options.candidates);
1158
+ const attemptLogPath = join(runDir, "memory-attempts.jsonl");
1159
+ const recoveryLogPath = join(runDir, "memory-recovery-attempts.jsonl");
1160
+ const costCeiling = options.costCeiling ?? options.costLedger?.costCeilingUsd ?? 0;
1161
+ const costLedger = options.costLedger ?? createRunCostLedger({
1162
+ storage,
1163
+ runDir,
1164
+ costCeilingUsd: costCeiling
1165
+ });
1166
+ if (costLedger.costCeilingUsd !== costCeiling) {
1167
+ throw new Error("memory experiment costCeiling must match the shared cost ledger ceiling");
1168
+ }
1169
+ storage.ensureDir(runDir);
1170
+ await recoverAbandonedMemoryAttempts({
1171
+ options,
1172
+ storage,
1173
+ runDir,
1174
+ attemptLogPath,
1175
+ candidateById: recoveryCandidateById,
1176
+ sequenceById,
1177
+ lease,
1178
+ maxConcurrency,
1179
+ costLedger,
1180
+ maxRecoveryAttempts: options.maxRecoveryAttempts ?? 1e3,
1181
+ recoveryLogPath,
1182
+ maxRecoveryRetriesPerAttempt: options.maxRecoveryRetriesPerAttempt ?? DEFAULT_MEMORY_RECOVERY_RETRIES_PER_ATTEMPT
1183
+ });
1184
+ await lease.assertOwned();
1185
+ let campaign;
1186
+ let campaignError;
1187
+ let settledExecutions = [];
1188
+ try {
1189
+ campaign = await runCampaign({
1190
+ scenarios,
1191
+ dispatch: (scenario, context) => {
1192
+ const candidate = candidateById.get(scenario.candidateId);
1193
+ if (!candidate) throw new Error(`unknown memory candidate ${scenario.candidateId}`);
1194
+ const operation = executionPool.run(
1195
+ () => runSequenceCell({
1196
+ options,
1197
+ candidate,
1198
+ scenario,
1199
+ context,
1200
+ runIdentity,
1201
+ storage,
1202
+ attemptLogPath,
1203
+ lease
1204
+ })
1205
+ );
1206
+ dispatchedExecutions.push(operation);
1207
+ return operation;
1208
+ },
1209
+ dispatchRef: memoryExperimentDispatchRef(options),
1210
+ judges: [agentMemorySequenceJudge()],
1211
+ runDir,
1212
+ storage,
1213
+ seed: options.seed,
1214
+ reps: options.reps,
1215
+ resumable: options.resumable,
1216
+ costCeiling,
1217
+ costLedger,
1218
+ costPhase: options.costPhase,
1219
+ maxConcurrency,
1220
+ dispatchTimeoutMs: options.dispatchTimeoutMs,
1221
+ expectUsage: "off",
1222
+ now: options.now
1223
+ });
1224
+ } catch (error) {
1225
+ campaignError = error;
1226
+ } finally {
1227
+ settledExecutions = await Promise.allSettled(dispatchedExecutions);
1228
+ }
1229
+ const cleanupFailures = settledExecutions.flatMap(
1230
+ (settled) => settled.status === "rejected" && settled.reason instanceof AgentMemoryCleanupError ? [settled.reason] : []
1231
+ );
1232
+ if (campaignError && cleanupFailures.length > 0) {
1233
+ throw new AggregateError(
1234
+ [campaignError, ...cleanupFailures],
1235
+ "memory experiment failed and provider cleanup also failed"
1236
+ );
1237
+ }
1238
+ if (campaignError) throw campaignError;
1239
+ if (cleanupFailures.length > 0) {
1240
+ throw new AggregateError(cleanupFailures, "memory experiment cleanup failed after dispatch");
1241
+ }
1242
+ if (!campaign) throw new Error("memory experiment produced no campaign result");
1243
+ await lease.assertOwned();
1244
+ const costByCandidate = memoryExperimentCostByCandidate(
1245
+ costLedger,
1246
+ campaign.runDir,
1247
+ scenarios,
1248
+ [...options.candidates, ...options.recoveryCandidates ?? []].map((candidate) => candidate.id)
1249
+ );
1250
+ const unrankedRecoveryCostUsd = normalizeUsd(
1251
+ (options.recoveryCandidates ?? []).reduce(
1252
+ (sum, candidate) => sum + (costByCandidate.get(candidate.id) ?? 0),
1253
+ 0
1254
+ )
1255
+ );
1256
+ const totalCostUsd = normalizeUsd(
1257
+ [...costByCandidate.values()].reduce((sum, cost) => sum + cost, 0)
1258
+ );
1259
+ const rows = rankAgentMemoryExperiment(options.candidates, scenarios, campaign, costByCandidate);
1260
+ const rankingJsonPath = join(campaign.runDir, "memory-experiment-ranking.json");
1261
+ const rankingMarkdownPath = join(campaign.runDir, "memory-experiment-ranking.md");
1262
+ storage.write(
1263
+ rankingJsonPath,
1264
+ `${JSON.stringify(
1265
+ { experimentId: options.experimentId, totalCostUsd, unrankedRecoveryCostUsd, rows },
1266
+ null,
1267
+ 2
1268
+ )}
1269
+ `
1270
+ );
1271
+ storage.write(
1272
+ rankingMarkdownPath,
1273
+ renderAgentMemoryExperimentRanking(rows, totalCostUsd, unrankedRecoveryCostUsd)
1274
+ );
1275
+ return {
1276
+ campaign,
1277
+ rows,
1278
+ totalCostUsd,
1279
+ unrankedRecoveryCostUsd,
1280
+ leaderCandidateId: rows.find((row) => row.cellsFailed === 0)?.candidateId,
1281
+ rankingJsonPath,
1282
+ rankingMarkdownPath,
1283
+ attemptLogPath,
1284
+ recoveryLogPath
1285
+ };
1286
+ }
1287
+ function buildAgentMemorySequenceScenarios(sequences, candidates) {
1288
+ return candidates.flatMap(
1289
+ (candidate) => sequences.map((sequence) => ({
1290
+ id: `${stableId("candidate", candidate.id)}:${sequence.id}`,
1291
+ kind: "agent-memory-sequence",
1292
+ candidateId: candidate.id,
1293
+ sequenceId: sequence.id,
1294
+ sequence,
1295
+ seedGroup: sequence.id,
1296
+ tags: [.../* @__PURE__ */ new Set([sequence.split ?? "dev", ...sequence.tags ?? [], candidate.id])]
1297
+ }))
1298
+ );
1299
+ }
1300
+ function agentMemorySequenceJudge() {
1301
+ return {
1302
+ name: "agent-memory-sequence",
1303
+ judgeVersion: "agent-knowledge:memory-sequence:v2",
1304
+ dimensions: [
1305
+ { key: "score", description: "mean memory probe score" },
1306
+ { key: "passed", description: "1 when every memory probe passes" },
1307
+ { key: "memory_fact_recall", description: "current memory fact coverage" },
1308
+ { key: "memory_event_recall", description: "memory source event coverage" },
1309
+ { key: "memory_actor_recall", description: "memory actor attribution coverage" },
1310
+ { key: "memory_stale_safe", description: "1 when obsolete memory is not reused" }
1311
+ ],
1312
+ score({ artifact }) {
1313
+ return {
1314
+ composite: artifact.score,
1315
+ dimensions: {
1316
+ score: artifact.score,
1317
+ passed: artifact.passed ? 1 : 0,
1318
+ ...artifact.dimensions
1319
+ },
1320
+ notes: `${artifact.probes.filter((probe) => probe.passed).length}/${artifact.probes.length} probes passed`
1321
+ };
1322
+ }
1323
+ };
1324
+ }
1325
+ async function runSequenceCell(input) {
1326
+ const { options, candidate, scenario, context, runIdentity, storage, attemptLogPath, lease } = input;
1327
+ const cleanupBranches = options.cleanupBranches ?? true;
1328
+ const costUsd = candidate.externalCostUsdPerSequence ?? 0;
1329
+ if (!Number.isFinite(costUsd) || costUsd < 0) {
1330
+ throw new Error(
1331
+ `${candidate.id}: externalCostUsdPerSequence must be a non-negative finite number`
1332
+ );
1333
+ }
1334
+ if (!cleanupBranches && !candidate.disposeAdapter) {
1335
+ throw new Error(
1336
+ `${candidate.id}: cleanupBranches=false requires disposeAdapter to delete isolated external state`
1337
+ );
1338
+ }
1339
+ const cleanupTimeoutMs = resolveMemoryCleanupTimeoutMs(
1340
+ options.cleanupTimeoutMs,
1341
+ `${candidate.id}: memory sequence`
1342
+ );
1343
+ context.signal.throwIfAborted();
1344
+ await lease.assertOwned();
1345
+ const startedAt = Date.now();
1346
+ const branchId = stableId(
1347
+ "memory_branch",
1348
+ `${runIdentity}:${context.cellId}:${context.seed}:${randomUUID2()}`
1349
+ );
1350
+ const attempt = memoryAttemptEvent({
1351
+ status: "started",
1352
+ branchId,
1353
+ candidate,
1354
+ sequence: scenario.sequence,
1355
+ rep: context.rep,
1356
+ seed: context.seed,
1357
+ recovery: false,
1358
+ cleanupBranches,
1359
+ now: options.now
1360
+ });
1361
+ appendMemoryAttemptEvent(storage, attemptLogPath, attempt);
1362
+ let externalCallAttempted = false;
1363
+ const appendCleanedAttempt = (priorError) => {
1364
+ try {
1365
+ appendMemoryAttemptEvent(storage, attemptLogPath, {
1366
+ ...attempt,
1367
+ status: "cleaned",
1368
+ recordedAt: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
1369
+ });
1370
+ } catch (error) {
1371
+ throw new AgentMemoryCleanupError(
1372
+ [...priorError ? [priorError] : [], error],
1373
+ `${candidate.id}: memory branch cleanup could not be recorded`
1374
+ );
1375
+ }
1376
+ };
1377
+ const execute = async () => {
1378
+ context.signal.throwIfAborted();
1379
+ await lease.assertOwned();
1380
+ let rawAdapter;
1381
+ let adapter;
1382
+ let memory;
1383
+ let primaryError;
1384
+ let completedArtifact;
1385
+ let finalClearStarted = false;
1386
+ let finalClearCompleted = false;
1387
+ try {
1388
+ const created = await candidate.createAdapter({
1389
+ branchId,
1390
+ sequence: scenario.sequence,
1391
+ rep: context.rep,
1392
+ seed: context.seed,
1393
+ purpose: "execute",
1394
+ signal: context.signal,
1395
+ markExternalCall: () => {
1396
+ externalCallAttempted = true;
1397
+ }
1398
+ });
1399
+ if (!created) throw new Error(`${candidate.id}: createAdapter returned no execution adapter`);
1400
+ rawAdapter = created;
1401
+ adapter = trackExternalMemoryCalls(created, () => {
1402
+ externalCallAttempted = true;
1403
+ });
1404
+ await lease.assertOwned();
1405
+ context.signal.throwIfAborted();
1406
+ if (cleanupBranches && !adapter.clear) {
1407
+ throw new Error(
1408
+ `${candidate.id}: cleanupBranches requires an adapter with scoped clear support`
1409
+ );
1410
+ }
1411
+ memory = createAgentMemoryBranch({
1412
+ adapter,
1413
+ branchId,
1414
+ lifetime: "attempt",
1415
+ policy: candidate.policy,
1416
+ allowedWriteScopes: sequenceCleanupScopes(scenario.sequence),
1417
+ baseScope: memoryExperimentBaseScope(options, candidate, scenario.sequenceId)
1418
+ });
1419
+ const probes = [];
1420
+ for (const step of scenario.sequence.steps) {
1421
+ context.signal.throwIfAborted();
1422
+ await lease.assertOwned();
1423
+ await writeStep(memory, step);
1424
+ await lease.assertOwned();
1425
+ await options.executeStep?.({
1426
+ memory,
1427
+ candidateId: candidate.id,
1428
+ sequence: scenario.sequence,
1429
+ step,
1430
+ context
1431
+ });
1432
+ context.signal.throwIfAborted();
1433
+ await lease.assertOwned();
1434
+ const stepProbes = await probeStep(memory, scenario.sequence, step);
1435
+ await lease.assertOwned();
1436
+ probes.push(...stepProbes);
1437
+ }
1438
+ const snapshot = await memory.snapshot();
1439
+ await lease.assertOwned();
1440
+ await options.onBranchSnapshot?.({
1441
+ candidateId: candidate.id,
1442
+ sequenceId: scenario.sequenceId,
1443
+ cellId: context.cellId,
1444
+ snapshot
1445
+ });
1446
+ await lease.assertOwned();
1447
+ const dimensions = meanDimensions(probes.map((probe) => probe.dimensions));
1448
+ const dimensionSampleCounts = countDimensions(
1449
+ probes.map((probe) => probe.applicableDimensions)
1450
+ );
1451
+ const artifact = {
1452
+ candidateId: candidate.id,
1453
+ sequenceId: scenario.sequenceId,
1454
+ score: mean(probes.map((probe) => probe.score)),
1455
+ passed: probes.length > 0 && probes.every((probe) => probe.passed),
1456
+ dimensions,
1457
+ dimensionSampleCounts,
1458
+ probes,
1459
+ branchDigest: snapshot.digest,
1460
+ journalEntries: snapshot.journal.length,
1461
+ durationMs: Math.max(0, Date.now() - startedAt)
1462
+ };
1463
+ if (cleanupBranches) {
1464
+ finalClearStarted = true;
1465
+ await runBoundedMemoryLifecycle({
1466
+ operation: `${candidate.id}: final branch cleanup`,
1467
+ timeoutMs: cleanupTimeoutMs,
1468
+ resource: adapter,
1469
+ run: () => clearSequenceScopes(memory, scenario.sequence)
1470
+ });
1471
+ finalClearCompleted = true;
1472
+ await lease.assertOwned();
1473
+ }
1474
+ completedArtifact = artifact;
1475
+ } catch (error) {
1476
+ primaryError = error;
1477
+ }
1478
+ const cleanupErrors = [];
1479
+ let cleanupOwned = true;
1480
+ let ownershipError;
1481
+ try {
1482
+ await lease.assertOwned();
1483
+ } catch (error) {
1484
+ cleanupOwned = false;
1485
+ ownershipError = error;
1486
+ }
1487
+ if (finalClearStarted && !finalClearCompleted && primaryError) {
1488
+ cleanupErrors.push(primaryError);
1489
+ }
1490
+ if (primaryError && cleanupOwned && !finalClearStarted && memory && cleanupBranches && adapter?.clear) {
1491
+ try {
1492
+ await runBoundedMemoryLifecycle({
1493
+ operation: `${candidate.id}: failed branch cleanup`,
1494
+ timeoutMs: cleanupTimeoutMs,
1495
+ resource: adapter,
1496
+ run: () => clearSequenceScopes(memory, scenario.sequence)
1497
+ });
1498
+ } catch (error) {
1499
+ cleanupErrors.push(error);
1500
+ }
1501
+ }
1502
+ if (adapter) {
1503
+ try {
1504
+ await runBoundedMemoryLifecycle({
1505
+ operation: `${candidate.id}: adapter close`,
1506
+ timeoutMs: cleanupTimeoutMs,
1507
+ resource: adapter,
1508
+ run: async () => {
1509
+ if (memory && cleanupOwned) await memory.close?.();
1510
+ else {
1511
+ if (cleanupOwned) await adapter.flush?.();
1512
+ await adapter.close?.();
1513
+ }
1514
+ }
1515
+ });
1516
+ } catch (error) {
1517
+ cleanupErrors.push(error);
1518
+ }
1519
+ if (cleanupOwned) {
1520
+ try {
1521
+ await runBoundedMemoryLifecycle({
1522
+ operation: `${candidate.id}: adapter disposal`,
1523
+ timeoutMs: cleanupTimeoutMs,
1524
+ resource: adapter,
1525
+ run: () => {
1526
+ if (candidate.disposeAdapter) externalCallAttempted = true;
1527
+ return candidate.disposeAdapter?.(rawAdapter);
1528
+ }
1529
+ });
1530
+ } catch (error) {
1531
+ cleanupErrors.push(error);
1532
+ }
1533
+ }
1534
+ } else {
1535
+ cleanupErrors.push(
1536
+ new Error(`${candidate.id}: adapter creation failed before cleanup could be confirmed`)
1537
+ );
1538
+ }
1539
+ if (cleanupOwned && cleanupErrors.length === 0) appendCleanedAttempt(primaryError);
1540
+ if (!cleanupOwned && cleanupErrors.length === 0) {
1541
+ if (primaryError) throw primaryError;
1542
+ throw ownershipError;
1543
+ }
1544
+ if (cleanupErrors.length > 0) {
1545
+ const primaryMessage = primaryError instanceof Error ? primaryError.message : String(primaryError ?? "");
1546
+ throw new AgentMemoryCleanupError(
1547
+ [
1548
+ ...primaryError && !cleanupErrors.includes(primaryError) ? [primaryError] : [],
1549
+ ...ownershipError ? [ownershipError] : [],
1550
+ ...cleanupErrors
1551
+ ],
1552
+ `${candidate.id}: memory branch cleanup failed${primaryMessage ? ` after: ${primaryMessage}` : ""}`
1553
+ );
1554
+ }
1555
+ if (primaryError) throw primaryError;
1556
+ if (!completedArtifact) throw new Error(`${candidate.id}: memory sequence produced no result`);
1557
+ return completedArtifact;
1558
+ };
1559
+ if (costUsd === 0) {
1560
+ let artifact;
1561
+ let error;
1562
+ try {
1563
+ artifact = await execute();
1564
+ } catch (caught) {
1565
+ error = caught;
1566
+ }
1567
+ if (error) throw error;
1568
+ if (!artifact) throw new Error(`${candidate.id}: memory sequence produced no result`);
1569
+ return artifact;
1570
+ }
1571
+ const receipt = {
1572
+ model: candidate.id,
1573
+ inputTokens: 0,
1574
+ outputTokens: 0,
1575
+ actualCostUsd: costUsd
1576
+ };
1577
+ const paid = await context.cost.runPaidCall({
1578
+ callId: memoryAttemptCostCallId(attempt, "execute", 0),
1579
+ actor: `agent-knowledge:memory-experiment:${candidate.id}`,
1580
+ model: candidate.id,
1581
+ maximumCharge: { externallyEnforcedMaximumUsd: costUsd },
1582
+ execute,
1583
+ receipt: () => receipt,
1584
+ receiptFromError: () => ({
1585
+ ...receipt,
1586
+ actualCostUsd: externalCallAttempted ? costUsd : 0
1587
+ })
1588
+ });
1589
+ if (!paid.succeeded) throw paid.error;
1590
+ return paid.value;
1591
+ }
1592
+ function trackExternalMemoryCalls(adapter, onExternalCall) {
1593
+ return {
1594
+ id: adapter.id,
1595
+ branchIsolation: adapter.branchIsolation,
1596
+ search(query, options) {
1597
+ onExternalCall();
1598
+ return adapter.search.call(adapter, query, options);
1599
+ },
1600
+ getContext(query, options) {
1601
+ onExternalCall();
1602
+ return adapter.getContext.call(adapter, query, options);
1603
+ },
1604
+ write(input) {
1605
+ onExternalCall();
1606
+ return adapter.write.call(adapter, input);
1607
+ },
1608
+ ...adapter.clear ? {
1609
+ clear(scope) {
1610
+ onExternalCall();
1611
+ return adapter.clear.call(adapter, scope);
1612
+ }
1613
+ } : {},
1614
+ ...adapter.flush ? {
1615
+ flush() {
1616
+ onExternalCall();
1617
+ return adapter.flush.call(adapter);
1618
+ }
1619
+ } : {},
1620
+ ...adapter.close ? {
1621
+ close() {
1622
+ onExternalCall();
1623
+ return adapter.close.call(adapter);
1624
+ }
1625
+ } : {}
1626
+ };
1627
+ }
1628
+ async function recoverAbandonedMemoryAttempts(input) {
1629
+ let attempts = readActiveMemoryAttempts(input.storage, input.attemptLogPath);
1630
+ if (attempts.length > input.maxRecoveryAttempts) {
1631
+ throw new Error(
1632
+ `memory experiment has ${attempts.length} unfinished attempts; maxRecoveryAttempts is ${input.maxRecoveryAttempts}`
1633
+ );
1634
+ }
1635
+ for (const attempt of attempts) {
1636
+ const candidate = input.candidateById.get(attempt.candidateId);
1637
+ if (!candidate) {
1638
+ throw new Error(
1639
+ `cannot recover memory branch '${attempt.branchId}': candidate '${attempt.candidateId}' is missing; pass it in recoveryCandidates`
1640
+ );
1641
+ }
1642
+ assertMemoryAttemptCandidateMatches(attempt, candidate);
1643
+ if (!input.sequenceById.has(attempt.sequenceId)) {
1644
+ throw new Error(
1645
+ `cannot recover memory branch '${attempt.branchId}': sequence '${attempt.sequenceId}' is missing`
1646
+ );
1647
+ }
1648
+ if ((input.options.cleanupBranches ?? true) !== attempt.cleanupBranches) {
1649
+ throw new Error(`cannot recover memory branch '${attempt.branchId}': cleanupBranches changed`);
1650
+ }
1651
+ }
1652
+ reconcileInterruptedMemoryPaidCalls(input.costLedger);
1653
+ assertNoInterruptedPaidCalls(input.costLedger, "memory experiment recovery");
1654
+ for (const attempt of attempts) {
1655
+ const candidate = input.candidateById.get(attempt.candidateId);
1656
+ const executionCostUsd = candidate.externalCostUsdPerSequence ?? 0;
1657
+ if (executionCostUsd > 0 && !hasSettledPaidCall(input.costLedger, memoryAttemptCostCallId(attempt, "execute", 0))) {
1658
+ appendMemoryAttemptEvent(input.storage, input.attemptLogPath, {
1659
+ ...attempt,
1660
+ status: "cleaned",
1661
+ recovery: true,
1662
+ recordedAt: (input.options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
1663
+ });
1664
+ }
1665
+ }
1666
+ attempts = readActiveMemoryAttempts(input.storage, input.attemptLogPath);
1667
+ const recoveryGenerations = reserveRecoveryAttempts({
1668
+ storage: input.storage,
1669
+ path: input.recoveryLogPath,
1670
+ attemptIds: attempts.map((attempt) => attempt.branchId),
1671
+ maxRetriesPerAttempt: input.maxRecoveryRetriesPerAttempt,
1672
+ label: "memory recovery attempt log",
1673
+ now: input.options.now
1674
+ });
1675
+ const pool = createMemoryExecutionPool(input.maxConcurrency);
1676
+ const settled = await Promise.allSettled(
1677
+ attempts.sort((left, right) => left.branchId.localeCompare(right.branchId)).map(
1678
+ (attempt) => pool.run(async () => {
1679
+ await input.lease.assertOwned();
1680
+ const candidate = input.candidateById.get(attempt.candidateId);
1681
+ if (!candidate) {
1682
+ throw new Error(
1683
+ `cannot recover memory branch '${attempt.branchId}': candidate '${attempt.candidateId}' is missing; pass it in recoveryCandidates`
1684
+ );
1685
+ }
1686
+ assertMemoryAttemptCandidateMatches(attempt, candidate);
1687
+ const sequence = input.sequenceById.get(attempt.sequenceId);
1688
+ if (!sequence) {
1689
+ throw new Error(
1690
+ `cannot recover memory branch '${attempt.branchId}': sequence '${attempt.sequenceId}' is missing`
1691
+ );
1692
+ }
1693
+ if ((input.options.cleanupBranches ?? true) !== attempt.cleanupBranches) {
1694
+ throw new Error(
1695
+ `cannot recover memory branch '${attempt.branchId}': cleanupBranches changed`
1696
+ );
1697
+ }
1698
+ const recoveryCostUsd = candidate.externalRecoveryCostUsdPerAttempt ?? 0;
1699
+ const recoveryGeneration = recoveryGenerations.get(attempt.branchId);
1700
+ if (recoveryGeneration === void 0) {
1701
+ throw new Error(`missing recovery generation for memory branch '${attempt.branchId}'`);
1702
+ }
1703
+ let externalRecoveryAttempted = false;
1704
+ const recover = async () => {
1705
+ await recoverMemoryAttempt({
1706
+ options: input.options,
1707
+ candidate,
1708
+ sequence,
1709
+ attempt,
1710
+ lease: input.lease,
1711
+ onExternalCall: () => {
1712
+ externalRecoveryAttempted = true;
1713
+ }
1714
+ });
1715
+ appendMemoryAttemptEvent(input.storage, input.attemptLogPath, {
1716
+ ...attempt,
1717
+ status: "cleaned",
1718
+ recovery: true,
1719
+ recordedAt: (input.options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
1720
+ });
1721
+ };
1722
+ if (recoveryCostUsd === 0) {
1723
+ await recover();
1724
+ } else {
1725
+ const tags = memoryRecoveryCostTags(input.runDir, candidate.id, attempt.branchId);
1726
+ const receipt = {
1727
+ model: candidate.id,
1728
+ inputTokens: 0,
1729
+ outputTokens: 0,
1730
+ actualCostUsd: recoveryCostUsd
1731
+ };
1732
+ const paid = await input.costLedger.runPaidCall({
1733
+ callId: memoryAttemptCostCallId(attempt, "recovery", recoveryGeneration),
1734
+ channel: "driver",
1735
+ phase: `${input.options.costPhase ?? "memory.experiment"}.recovery`,
1736
+ actor: `agent-knowledge:memory-recovery:${candidate.id}`,
1737
+ model: candidate.id,
1738
+ tags,
1739
+ maximumCharge: { externallyEnforcedMaximumUsd: recoveryCostUsd },
1740
+ execute: recover,
1741
+ receipt: () => ({
1742
+ ...receipt,
1743
+ actualCostUsd: externalRecoveryAttempted ? recoveryCostUsd : 0
1744
+ }),
1745
+ receiptFromError: () => ({
1746
+ ...receipt,
1747
+ actualCostUsd: externalRecoveryAttempted ? recoveryCostUsd : 0
1748
+ })
1749
+ });
1750
+ if (!paid.succeeded) throw paid.error;
1751
+ }
1752
+ })
1753
+ )
1754
+ );
1755
+ const failures = settled.flatMap(
1756
+ (result) => result.status === "rejected" ? [result.reason] : []
1757
+ );
1758
+ if (failures.length === 1) throw failures[0];
1759
+ if (failures.length > 1) {
1760
+ throw new AggregateError(failures, "multiple abandoned memory branches failed recovery");
1761
+ }
1762
+ }
1763
+ async function recoverMemoryAttempt(input) {
1764
+ const { options, candidate, sequence, attempt, lease, onExternalCall } = input;
1765
+ const cleanupBranches = attempt.cleanupBranches;
1766
+ const cleanupTimeoutMs = resolveMemoryCleanupTimeoutMs(
1767
+ options.cleanupTimeoutMs,
1768
+ `${candidate.id}: abandoned branch recovery`
1769
+ );
1770
+ let rawAdapter;
1771
+ let adapter;
1772
+ let memory;
1773
+ let primaryError;
1774
+ try {
1775
+ const abortController = new AbortController();
1776
+ const creation = Promise.resolve().then(
1777
+ () => candidate.createAdapter({
1778
+ branchId: attempt.branchId,
1779
+ sequence,
1780
+ rep: attempt.rep,
1781
+ seed: attempt.seed,
1782
+ purpose: "recovery",
1783
+ signal: abortController.signal,
1784
+ markExternalCall: onExternalCall
1785
+ })
1786
+ );
1787
+ releaseMemoryAdapterCreatedAfterAbort({
1788
+ creation,
1789
+ signal: abortController.signal,
1790
+ dispose: candidate.disposeAdapter ? async (created) => {
1791
+ onExternalCall();
1792
+ await candidate.disposeAdapter?.(created);
1793
+ } : void 0
1794
+ });
1795
+ const recovered = await runBoundedMemoryLifecycle({
1796
+ operation: `${candidate.id}: recovery adapter creation`,
1797
+ timeoutMs: cleanupTimeoutMs,
1798
+ abortController,
1799
+ run: () => creation
1800
+ });
1801
+ await lease.assertOwned();
1802
+ if (recovered === null) return;
1803
+ rawAdapter = recovered;
1804
+ adapter = trackExternalMemoryCalls(recovered, onExternalCall);
1805
+ const recoveryDelayMs = memoryRecoveryDelayMs(adapter);
1806
+ await sleepForMemoryRecovery(
1807
+ recoveryDelayMs,
1808
+ () => lease.assertOwned(),
1809
+ cleanupTimeoutMs,
1810
+ `${candidate.id}: abandoned branch recovery visibility wait`
1811
+ );
1812
+ if (cleanupBranches) {
1813
+ if (!adapter.clear) {
1814
+ throw new Error(
1815
+ `${candidate.id}: abandoned branch recovery requires an adapter with scoped clear support`
1816
+ );
1817
+ }
1818
+ memory = createAgentMemoryBranch({
1819
+ adapter,
1820
+ branchId: attempt.branchId,
1821
+ lifetime: "attempt",
1822
+ policy: candidate.policy,
1823
+ allowedWriteScopes: sequenceCleanupScopes(sequence),
1824
+ baseScope: memoryExperimentBaseScope(options, candidate, sequence.id)
1825
+ });
1826
+ await runBoundedMemoryLifecycle({
1827
+ operation: `${candidate.id}: abandoned branch cleanup`,
1828
+ timeoutMs: cleanupTimeoutMs,
1829
+ resource: adapter,
1830
+ run: () => clearSequenceScopes(memory, sequence)
1831
+ });
1832
+ await lease.assertOwned();
1833
+ }
1834
+ } catch (error) {
1835
+ primaryError = error;
1836
+ }
1837
+ const cleanupErrors = [];
1838
+ let cleanupOwned = true;
1839
+ try {
1840
+ await lease.assertOwned();
1841
+ } catch (error) {
1842
+ cleanupOwned = false;
1843
+ cleanupErrors.push(error);
1844
+ }
1845
+ if (adapter) {
1846
+ try {
1847
+ await runBoundedMemoryLifecycle({
1848
+ operation: `${candidate.id}: recovery adapter close`,
1849
+ timeoutMs: cleanupTimeoutMs,
1850
+ resource: adapter,
1851
+ run: async () => {
1852
+ if (memory && cleanupOwned) {
1853
+ await memory.close?.();
1854
+ } else {
1855
+ if (cleanupOwned && adapter.flush) {
1856
+ await adapter.flush();
1857
+ }
1858
+ await adapter.close?.();
1859
+ }
1860
+ }
1861
+ });
1862
+ } catch (error) {
1863
+ cleanupErrors.push(error);
1864
+ }
1865
+ if (cleanupOwned) {
1866
+ try {
1867
+ if (candidate.disposeAdapter) onExternalCall();
1868
+ await runBoundedMemoryLifecycle({
1869
+ operation: `${candidate.id}: recovery adapter disposal`,
1870
+ timeoutMs: cleanupTimeoutMs,
1871
+ resource: adapter,
1872
+ run: () => candidate.disposeAdapter?.(rawAdapter)
1873
+ });
1874
+ } catch (error) {
1875
+ cleanupErrors.push(error);
1876
+ }
1877
+ }
1878
+ } else {
1879
+ cleanupErrors.push(
1880
+ new Error(
1881
+ `${candidate.id}: recovery adapter creation failed before cleanup could be confirmed`
1882
+ )
1883
+ );
1884
+ }
1885
+ if (!cleanupOwned || primaryError || cleanupErrors.length > 0) {
1886
+ throw new AgentMemoryCleanupError(
1887
+ [...primaryError ? [primaryError] : [], ...cleanupErrors],
1888
+ `${candidate.id}: abandoned memory branch '${attempt.branchId}' recovery failed`
1889
+ );
1890
+ }
1891
+ }
1892
+ function memoryAttemptCostCallId(attempt, purpose, generation) {
1893
+ return stableId(
1894
+ "memory_cost_call",
1895
+ canonicalJson3({
1896
+ purpose,
1897
+ generation,
1898
+ branchId: attempt.branchId,
1899
+ candidateId: attempt.candidateId,
1900
+ candidateRef: attempt.candidateRef,
1901
+ externalCostUsdPerSequence: attempt.externalCostUsdPerSequence,
1902
+ externalRecoveryCostUsdPerAttempt: attempt.externalRecoveryCostUsdPerAttempt,
1903
+ sequenceId: attempt.sequenceId,
1904
+ rep: attempt.rep,
1905
+ seed: attempt.seed
1906
+ })
1907
+ );
1908
+ }
1909
+ function memoryRecoveryCostTags(runDir, candidateId, branchId) {
1910
+ return {
1911
+ runDir,
1912
+ candidateId,
1913
+ branchId,
1914
+ memoryRecovery: "attempt"
1915
+ };
1916
+ }
1917
+ function memoryAttemptEvent(input) {
1918
+ return {
1919
+ schema: 2,
1920
+ status: input.status,
1921
+ branchId: input.branchId,
1922
+ candidateId: input.candidate.id,
1923
+ candidateRef: input.candidate.ref,
1924
+ sequenceId: input.sequence.id,
1925
+ rep: input.rep,
1926
+ seed: input.seed,
1927
+ cleanupBranches: input.cleanupBranches ?? true,
1928
+ externalCostUsdPerSequence: input.candidate.externalCostUsdPerSequence ?? 0,
1929
+ externalRecoveryCostUsdPerAttempt: input.candidate.externalRecoveryCostUsdPerAttempt ?? 0,
1930
+ recordedAt: (input.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
1931
+ recovery: input.recovery
1932
+ };
1933
+ }
1934
+ function appendMemoryAttemptEvent(storage, path, event) {
1935
+ appendAttemptJournalEvent({
1936
+ storage,
1937
+ path,
1938
+ event,
1939
+ label: "memory attempt log"
1940
+ });
1941
+ }
1942
+ function readActiveMemoryAttempts(storage, path) {
1943
+ return readActiveAttemptJournal({
1944
+ storage,
1945
+ path,
1946
+ label: "memory attempt log",
1947
+ parse: parseMemoryAttemptEvent,
1948
+ id: (event) => event.branchId,
1949
+ sameAttempt: sameMemoryAttempt
1950
+ });
1951
+ }
1952
+ function parseMemoryAttemptEvent(value, path, line) {
1953
+ const event = value;
1954
+ const valid = typeof event === "object" && event !== null && event.schema === 2 && (event.status === "started" || event.status === "cleaned") && typeof event.branchId === "string" && event.branchId.length > 0 && typeof event.candidateId === "string" && event.candidateId.length > 0 && typeof event.candidateRef === "string" && event.candidateRef.length > 0 && typeof event.sequenceId === "string" && event.sequenceId.length > 0 && Number.isSafeInteger(event.rep) && Number.isSafeInteger(event.seed) && typeof event.cleanupBranches === "boolean" && typeof event.externalCostUsdPerSequence === "number" && Number.isFinite(event.externalCostUsdPerSequence) && event.externalCostUsdPerSequence >= 0 && typeof event.externalRecoveryCostUsdPerAttempt === "number" && Number.isFinite(event.externalRecoveryCostUsdPerAttempt) && event.externalRecoveryCostUsdPerAttempt >= 0 && typeof event.recordedAt === "string" && !Number.isNaN(Date.parse(event.recordedAt)) && typeof event.recovery === "boolean";
1955
+ if (!valid) throw new Error(`invalid memory attempt event in '${path}' line ${line}`);
1956
+ return value;
1957
+ }
1958
+ function sameMemoryAttempt(left, right) {
1959
+ return left.branchId === right.branchId && left.candidateId === right.candidateId && left.candidateRef === right.candidateRef && left.sequenceId === right.sequenceId && left.rep === right.rep && left.seed === right.seed && left.cleanupBranches === right.cleanupBranches && left.externalCostUsdPerSequence === right.externalCostUsdPerSequence && left.externalRecoveryCostUsdPerAttempt === right.externalRecoveryCostUsdPerAttempt;
1960
+ }
1961
+ function assertMemoryAttemptCandidateMatches(attempt, candidate) {
1962
+ if (candidate.ref !== attempt.candidateRef) {
1963
+ throw new Error(
1964
+ `cannot recover memory branch '${attempt.branchId}': candidate ref changed from '${attempt.candidateRef}' to '${candidate.ref}'`
1965
+ );
1966
+ }
1967
+ const executionCost = candidate.externalCostUsdPerSequence ?? 0;
1968
+ const recoveryCost = candidate.externalRecoveryCostUsdPerAttempt ?? 0;
1969
+ if (executionCost !== attempt.externalCostUsdPerSequence || recoveryCost !== attempt.externalRecoveryCostUsdPerAttempt) {
1970
+ throw new Error(
1971
+ `cannot recover memory branch '${attempt.branchId}': candidate cost settings changed; start a new run or restore the recorded costs`
1972
+ );
1973
+ }
1974
+ }
1975
+ async function writeStep(memory, step) {
1976
+ const writes = (step.writes ?? []).map((write) => ({
1977
+ ...write,
1978
+ scope: mergeScopes2(step.scope, write.scope)
1979
+ }));
1980
+ if (step.parallelWrites) {
1981
+ await Promise.all(writes.map((write) => memory.write(write)));
1982
+ return;
1983
+ }
1984
+ for (const write of writes) await memory.write(write);
1985
+ }
1986
+ async function probeStep(memory, sequence, step) {
1987
+ const run = async (probe) => {
1988
+ const scope = mergeScopes2(step.scope, probe.scope);
1989
+ const context = await memory.getContext(probe.query, { scope, limit: probe.limit });
1990
+ const artifact = {
1991
+ answer: context.text,
1992
+ rememberedFacts: context.hits.map((hit) => hit.text),
1993
+ citedEventIds: uniqueStrings([
1994
+ ...context.hits.map((hit) => hit.metadata?.eventId),
1995
+ ...context.hits.flatMap((hit) => stringArray(hit.metadata?.eventIds))
1996
+ ]),
1997
+ usedMemoryIds: context.hits.map((hit) => hit.id),
1998
+ actorIds: uniqueStrings([
1999
+ ...context.hits.map((hit) => hit.metadata?.actorId),
2000
+ ...context.hits.flatMap((hit) => stringArray(hit.metadata?.actorIds))
2001
+ ])
2002
+ };
2003
+ const evaluation = scoreMemoryBenchmarkArtifact(
2004
+ {
2005
+ id: `${sequence.id}:${step.id}:${probe.id}`,
2006
+ family: sequence.family,
2007
+ taskKind: probe.taskKind ?? "memory-recall",
2008
+ split: sequence.split,
2009
+ events: [],
2010
+ prompt: probe.query,
2011
+ requiredFacts: probe.requiredFacts && probe.requiredFacts.length > 0 ? probe.requiredFacts : probe.referenceAnswer ? [{ id: `${probe.id}:reference`, anyOf: [probe.referenceAnswer] }] : void 0,
2012
+ forbiddenFacts: probe.forbiddenFacts,
2013
+ expectedEventIds: probe.expectedEventIds,
2014
+ expectedActorIds: probe.expectedActorIds,
2015
+ referenceAnswer: probe.referenceAnswer
2016
+ },
2017
+ artifact
2018
+ );
2019
+ return {
2020
+ id: probe.id,
2021
+ stepId: step.id,
2022
+ query: probe.query,
2023
+ score: evaluation.score,
2024
+ passed: evaluation.passed,
2025
+ dimensions: evaluation.dimensions,
2026
+ applicableDimensions: evaluation.applicableDimensions ?? Object.keys(evaluation.dimensions),
2027
+ notes: evaluation.notes,
2028
+ hitIds: context.hits.map((hit) => hit.id)
2029
+ };
2030
+ };
2031
+ if (step.parallelProbes === false) {
2032
+ const results = [];
2033
+ for (const probe of step.probes ?? []) results.push(await run(probe));
2034
+ return results;
2035
+ }
2036
+ return Promise.all((step.probes ?? []).map(run));
2037
+ }
2038
+ function rankAgentMemoryExperiment(candidates, scenarios, campaign, costByCandidate) {
2039
+ const scenarioById = new Map(scenarios.map((scenario) => [scenario.id, scenario]));
2040
+ const rows = candidates.map((candidate) => {
2041
+ const candidateScenarios = scenarios.filter((scenario) => scenario.candidateId === candidate.id);
2042
+ const cells = campaign.cells.filter(
2043
+ (cell) => scenarioById.get(cell.scenarioId)?.candidateId === candidate.id
2044
+ );
2045
+ const successful = cells.filter((cell) => !cell.error && cell.artifact);
2046
+ const dimensionRows = successful.map((cell) => cell.artifact.dimensions);
2047
+ return {
2048
+ rank: 0,
2049
+ candidateId: candidate.id,
2050
+ label: candidate.label ?? candidate.id,
2051
+ scoreMean: mean(
2052
+ cells.map((cell) => !cell.error && cell.artifact ? cell.artifact.score : 0)
2053
+ ),
2054
+ passRate: mean(cells.map((cell) => !cell.error && cell.artifact?.passed ? 1 : 0)),
2055
+ totalSequences: candidateScenarios.length,
2056
+ totalCells: cells.length,
2057
+ totalProbes: successful.reduce((sum, cell) => sum + cell.artifact.probes.length, 0),
2058
+ cellsFailed: cells.filter((cell) => Boolean(cell.error)).length,
2059
+ totalCostUsd: normalizeUsd(costByCandidate.get(candidate.id) ?? 0),
2060
+ durationMs: cells.reduce((sum, cell) => sum + cell.durationMs, 0),
2061
+ dimensions: meanDimensions(dimensionRows)
2062
+ };
2063
+ });
2064
+ return rows.sort(
2065
+ (a, b) => Number(a.cellsFailed > 0) - Number(b.cellsFailed > 0) || b.scoreMean - a.scoreMean || b.passRate - a.passRate || a.totalCostUsd - b.totalCostUsd || a.candidateId.localeCompare(b.candidateId)
2066
+ ).map((row, index) => ({ ...row, rank: index + 1 }));
2067
+ }
2068
+ function memoryExperimentCostByCandidate(costLedger, runDir, scenarios, candidateIdsInput) {
2069
+ const candidateByScenario = new Map(
2070
+ scenarios.map((scenario) => [scenario.id, scenario.candidateId])
2071
+ );
2072
+ const candidateIds = new Set(candidateIdsInput);
2073
+ const totals = /* @__PURE__ */ new Map();
2074
+ for (const receipt of costLedger.list()) {
2075
+ if (receipt.tags?.runDir !== runDir) continue;
2076
+ const scenarioCandidate = receipt.tags.scenarioId ? candidateByScenario.get(receipt.tags.scenarioId) : void 0;
2077
+ const recoveryCandidate = receipt.tags.memoryRecovery === "attempt" && receipt.tags.candidateId ? receipt.tags.candidateId : void 0;
2078
+ const candidateId = scenarioCandidate ?? recoveryCandidate;
2079
+ if (!candidateId || !candidateIds.has(candidateId)) continue;
2080
+ totals.set(candidateId, (totals.get(candidateId) ?? 0) + receipt.costUsd);
2081
+ }
2082
+ return totals;
2083
+ }
2084
+ function renderAgentMemoryExperimentRanking(rows, totalCostUsd, unrankedRecoveryCostUsd) {
2085
+ return [
2086
+ "# Agent Memory Experiment",
2087
+ "",
2088
+ `- total cost: $${format(totalCostUsd)}`,
2089
+ `- retired-candidate recovery cost: $${format(unrankedRecoveryCostUsd)}`,
2090
+ "",
2091
+ "| rank | candidate | sequences | cells | probes | failed | score | pass rate | cost | duration ms |",
2092
+ "| ---: | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
2093
+ ...rows.map(
2094
+ (row) => `| ${row.rank} | ${row.label} | ${row.totalSequences} | ${row.totalCells} | ${row.totalProbes} | ${row.cellsFailed} | ${format(row.scoreMean)} | ${format(row.passRate)} | $${format(row.totalCostUsd)} | ${format(row.durationMs)} |`
2095
+ ),
2096
+ ""
2097
+ ].join("\n");
2098
+ }
2099
+ function memoryExperimentDispatchRef(options) {
2100
+ return stableId(
2101
+ "memory_experiment",
2102
+ canonicalJson3({
2103
+ implementationRef: MEMORY_EXPERIMENT_IMPLEMENTATION_REF,
2104
+ experimentId: options.experimentId,
2105
+ experimentRunId: options.experimentRunId ?? null,
2106
+ executeStepRef: options.executeStepRef ?? "fixtures",
2107
+ cleanupBranches: options.cleanupBranches ?? true,
2108
+ candidates: options.candidates.map((candidate) => ({
2109
+ id: candidate.id,
2110
+ ref: candidate.ref,
2111
+ policy: candidate.policy ?? null,
2112
+ baseScope: candidate.baseScope ?? null,
2113
+ externalCostUsdPerSequence: candidate.externalCostUsdPerSequence ?? 0,
2114
+ externalRecoveryCostUsdPerAttempt: candidate.externalRecoveryCostUsdPerAttempt ?? 0
2115
+ })).sort((a, b) => a.id.localeCompare(b.id))
2116
+ })
2117
+ );
2118
+ }
2119
+ function meanDimensions(rows) {
2120
+ const values = /* @__PURE__ */ new Map();
2121
+ for (const row of rows) {
2122
+ for (const [key, value] of Object.entries(row)) {
2123
+ if (!Number.isFinite(value)) continue;
2124
+ const bucket = values.get(key) ?? [];
2125
+ bucket.push(value);
2126
+ values.set(key, bucket);
2127
+ }
2128
+ }
2129
+ return Object.fromEntries([...values].map(([key, bucket]) => [key, mean(bucket)]));
2130
+ }
2131
+ function countDimensions(rows) {
2132
+ const counts = /* @__PURE__ */ new Map();
2133
+ for (const row of rows) {
2134
+ for (const key of new Set(row)) counts.set(key, (counts.get(key) ?? 0) + 1);
2135
+ }
2136
+ return Object.fromEntries(counts);
2137
+ }
2138
+ function mean(values) {
2139
+ return values.length === 0 ? 0 : values.reduce((sum, value) => sum + value, 0) / values.length;
2140
+ }
2141
+ function mergeScopes2(base, extra) {
2142
+ return {
2143
+ ...base ?? {},
2144
+ ...extra ?? {},
2145
+ tags: { ...base?.tags ?? {}, ...extra?.tags ?? {} }
2146
+ };
2147
+ }
2148
+ function memoryExperimentBaseScope(options, candidate, sequenceId) {
2149
+ return mergeScopes2(candidate.baseScope, {
2150
+ tags: {
2151
+ memoryExperimentId: options.experimentId,
2152
+ memoryCandidateId: candidate.id,
2153
+ memorySequenceId: sequenceId
2154
+ }
2155
+ });
2156
+ }
2157
+ function sequenceCleanupScopes(sequence) {
2158
+ const scopes = /* @__PURE__ */ new Map();
2159
+ for (const scope of sequence.cleanupScopes ?? []) {
2160
+ const normalized = normalizeCleanupScope(scope);
2161
+ scopes.set(JSON.stringify(normalized), normalized);
2162
+ }
2163
+ for (const step of sequence.steps) {
2164
+ const candidates = [
2165
+ ...step.scope ? [step.scope] : [],
2166
+ ...(step.writes ?? []).map((write) => mergeScopes2(step.scope, write.scope)),
2167
+ ...(step.probes ?? []).map((probe) => mergeScopes2(step.scope, probe.scope))
2168
+ ];
2169
+ for (const scope of candidates) {
2170
+ const normalized = normalizeCleanupScope(scope);
2171
+ scopes.set(JSON.stringify(normalized), normalized);
2172
+ }
2173
+ }
2174
+ return [...scopes.values()];
2175
+ }
2176
+ async function clearSequenceScopes(memory, sequence) {
2177
+ for (const scope of sequenceCleanupScopes(sequence)) await memory.clear?.(scope);
2178
+ }
2179
+ function assertMemorySequences(sequences) {
2180
+ for (const sequence of sequences) {
2181
+ assertNonEmptyString(sequence.family, `memory experiment sequence ${sequence.id} family`);
2182
+ if (sequence.steps.length === 0) {
2183
+ throw new Error(`memory experiment sequence ${sequence.id} has no steps`);
2184
+ }
2185
+ assertUnique(
2186
+ sequence.steps.map((step) => step.id),
2187
+ `step in sequence ${sequence.id}`
2188
+ );
2189
+ let probeCount = 0;
2190
+ for (const step of sequence.steps) {
2191
+ for (const write of step.writes ?? []) {
2192
+ assertNonEmptyString(write.text, `memory experiment write in ${sequence.id}/${step.id}`);
2193
+ if (write.id !== void 0) {
2194
+ assertNonEmptyString(write.id, `memory experiment write id in ${sequence.id}/${step.id}`);
2195
+ }
2196
+ }
2197
+ assertUnique(
2198
+ (step.probes ?? []).map((probe) => probe.id),
2199
+ `probe in sequence ${sequence.id} step ${step.id}`
2200
+ );
2201
+ for (const probe of step.probes ?? []) {
2202
+ probeCount += 1;
2203
+ assertNonEmptyString(
2204
+ probe.query,
2205
+ `memory experiment probe query ${sequence.id}/${step.id}/${probe.id}`
2206
+ );
2207
+ if (probe.limit !== void 0 && (!Number.isSafeInteger(probe.limit) || probe.limit <= 0)) {
2208
+ throw new Error(
2209
+ `memory experiment probe limit ${sequence.id}/${step.id}/${probe.id} must be a positive safe integer`
2210
+ );
2211
+ }
2212
+ assertMemoryFactMatchers(
2213
+ probe.requiredFacts ?? [],
2214
+ `${sequence.id}/${step.id}/${probe.id} requiredFacts`
2215
+ );
2216
+ assertMemoryFactMatchers(
2217
+ probe.forbiddenFacts ?? [],
2218
+ `${sequence.id}/${step.id}/${probe.id} forbiddenFacts`
2219
+ );
2220
+ assertStringList(probe.expectedEventIds, `${sequence.id}/${step.id}/${probe.id} event ids`);
2221
+ assertStringList(probe.expectedActorIds, `${sequence.id}/${step.id}/${probe.id} actor ids`);
2222
+ if (probe.referenceAnswer !== void 0) {
2223
+ assertNonEmptyString(
2224
+ probe.referenceAnswer,
2225
+ `memory experiment reference answer ${sequence.id}/${step.id}/${probe.id}`
2226
+ );
2227
+ }
2228
+ const hasTarget = (probe.requiredFacts?.length ?? 0) > 0 || (probe.forbiddenFacts?.length ?? 0) > 0 || (probe.expectedEventIds?.length ?? 0) > 0 || (probe.expectedActorIds?.length ?? 0) > 0 || Boolean(probe.referenceAnswer?.trim());
2229
+ if (!hasTarget) {
2230
+ throw new Error(
2231
+ `memory experiment probe ${sequence.id}/${step.id}/${probe.id} has no measurable target`
2232
+ );
2233
+ }
2234
+ }
2235
+ }
2236
+ if (probeCount === 0) {
2237
+ throw new Error(`memory experiment sequence ${sequence.id} has no probes`);
2238
+ }
2239
+ }
2240
+ }
2241
+ function normalizeCleanupScope(scope) {
2242
+ const normalized = compactScope(scope);
2243
+ if (normalized.tags && Object.keys(normalized.tags).length === 0) {
2244
+ delete normalized.tags;
2245
+ }
2246
+ return normalized;
2247
+ }
2248
+ function compactScope(scope) {
2249
+ return Object.fromEntries(
2250
+ Object.entries(scope).filter(([, value]) => value !== void 0)
2251
+ );
2252
+ }
2253
+ function compactRecord(record2) {
2254
+ return Object.fromEntries(Object.entries(record2).filter(([, value]) => value !== void 0));
2255
+ }
2256
+ function uniqueStrings(values) {
2257
+ return [...new Set(values.filter((value) => typeof value === "string"))];
2258
+ }
2259
+ function stringArray(value) {
2260
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
2261
+ }
2262
+ function assertUnique(values, label) {
2263
+ const seen = /* @__PURE__ */ new Set();
2264
+ for (const value of values) {
2265
+ assertNonEmptyString(value, `memory experiment ${label} id`);
2266
+ if (seen.has(value)) throw new Error(`duplicate memory experiment ${label} id: ${value}`);
2267
+ seen.add(value);
2268
+ }
2269
+ }
2270
+ function assertMemoryFactMatchers(matchers, label) {
2271
+ assertUnique(
2272
+ matchers.map((matcher) => matcher.id),
2273
+ `${label} matcher`
2274
+ );
2275
+ for (const matcher of matchers) {
2276
+ if (matcher.anyOf.length === 0) {
2277
+ throw new Error(`memory experiment ${label} matcher ${matcher.id} requires anyOf`);
2278
+ }
2279
+ assertStringList(matcher.anyOf, `${label} matcher ${matcher.id} anyOf`);
2280
+ assertStringList(matcher.sourceEventIds, `${label} matcher ${matcher.id} source event ids`);
2281
+ if (matcher.weight !== void 0 && (!Number.isFinite(matcher.weight) || matcher.weight <= 0)) {
2282
+ throw new Error(
2283
+ `memory experiment ${label} matcher ${matcher.id} weight must be a positive finite number`
2284
+ );
2285
+ }
2286
+ }
2287
+ }
2288
+ function assertStringList(values, label) {
2289
+ if (values === void 0) return;
2290
+ assertUnique(values, label);
2291
+ }
2292
+ function assertNonEmptyString(value, label) {
2293
+ if (typeof value !== "string" || value.trim().length === 0) {
2294
+ throw new Error(`${label} must be a non-empty string`);
2295
+ }
2296
+ }
2297
+ function format(value) {
2298
+ return Number.isFinite(value) ? value.toFixed(4) : "0.0000";
2299
+ }
2300
+ function normalizeUsd(value) {
2301
+ return Number(value.toFixed(12));
2302
+ }
2303
+
2304
+ // src/memory/graphiti.ts
2305
+ import { randomUUID as randomUUID3 } from "crypto";
2306
+ import { canonicalJson as canonicalJson4 } from "@tangle-network/agent-eval";
2307
+ var DEFAULT_GRAPHITI_INGESTION_TIMEOUT_MS = 12e4;
2308
+ function createGraphitiMemoryAdapter(options) {
2309
+ assertGraphitiOptions(options);
2310
+ const id = options.id ?? "graphiti";
2311
+ const consistency = options.consistency ?? "visible";
2312
+ const tools = graphitiToolNames(options);
2313
+ const queued = /* @__PURE__ */ new Map();
2314
+ const adapter = {
2315
+ id,
2316
+ branchIsolation: consistency === "visible" ? {
2317
+ mode: "scoped",
2318
+ processExitSafe: false,
2319
+ recoveryDelayMs: options.ingestionTimeoutMs ?? DEFAULT_GRAPHITI_INGESTION_TIMEOUT_MS
2320
+ } : {
2321
+ mode: "unsupported",
2322
+ reason: 'queued Graphiti writes can become visible after a process restart; use consistency="visible" for branch experiments'
2323
+ },
2324
+ async search(query, searchOptions = {}) {
2325
+ if (searchOptions.minScore !== void 0 && !Number.isFinite(searchOptions.minScore)) {
2326
+ throw new Error(`${id}: minScore must be finite`);
2327
+ }
2328
+ const scope = mergeScopes3(options.defaultScope, searchOptions.scope);
2329
+ const groupId = graphitiGroupId(id, scope);
2330
+ const modes = new Set(options.search ?? ["facts", "nodes"]);
2331
+ const kinds = searchOptions.kinds && searchOptions.kinds.length > 0 ? new Set(searchOptions.kinds) : null;
2332
+ const searchFacts = modes.has("facts") && (!kinds || kinds.has("fact"));
2333
+ const searchNodes = modes.has("nodes") && (!kinds || kinds.has("entity"));
2334
+ const limit = searchOptions.limit ?? 10;
2335
+ if (!Number.isSafeInteger(limit) || limit < 0) {
2336
+ throw new Error(`${id}: search limit must be a non-negative safe integer`);
2337
+ }
2338
+ if (limit === 0) return [];
2339
+ const [factPayload, nodePayload] = await Promise.all([
2340
+ searchFacts ? callGraphiti(options.client, tools.searchFacts, {
2341
+ query,
2342
+ group_ids: [groupId],
2343
+ max_facts: limit
2344
+ }) : void 0,
2345
+ searchNodes ? callGraphiti(options.client, tools.searchNodes, {
2346
+ query,
2347
+ group_ids: [groupId],
2348
+ max_nodes: limit,
2349
+ ...searchOptions.metadata?.entityTypes ? { entity_types: searchOptions.metadata.entityTypes } : {}
2350
+ }) : void 0
2351
+ ]);
2352
+ const hits = mergeRankedMemoryHits(
2353
+ [normalizeGraphitiFacts(factPayload, id), normalizeGraphitiNodes(nodePayload, id)].map(
2354
+ (group) => group.filter((hit) => {
2355
+ if (searchOptions.minScore === void 0) return true;
2356
+ const score = hit.normalizedScore ?? hit.score;
2357
+ return score !== void 0 && score >= searchOptions.minScore;
2358
+ })
2359
+ ),
2360
+ limit
2361
+ );
2362
+ await enrichGraphitiProvenance(options, groupId, hits);
2363
+ return hits;
2364
+ },
2365
+ async getContext(query, searchOptions = {}) {
2366
+ return defaultGetMemoryContext(adapter, query, searchOptions);
2367
+ },
2368
+ async write(input) {
2369
+ const scope = mergeScopes3(options.defaultScope, input.scope);
2370
+ const groupId = graphitiGroupId(id, scope);
2371
+ const eventKey = canonicalJson4(
2372
+ stripUndefined2({
2373
+ id: input.id,
2374
+ kind: input.kind,
2375
+ text: input.text,
2376
+ title: input.title,
2377
+ role: input.role,
2378
+ metadata: input.metadata
2379
+ })
2380
+ );
2381
+ const episodeUuid = input.id === void 0 ? randomUUID3() : deterministicUuid(`${id}:${groupId}:${eventKey}`);
2382
+ const name = input.title ?? `${input.kind}:${input.id ?? stableId("event", eventKey)}`;
2383
+ const sourceDescription = canonicalJson4(
2384
+ stripUndefined2({
2385
+ adapter: "agent-knowledge",
2386
+ memoryKind: input.kind,
2387
+ eventId: input.id,
2388
+ actorId: input.metadata?.actorId,
2389
+ sessionId: scope.sessionId,
2390
+ role: input.role,
2391
+ scope,
2392
+ metadata: input.metadata ?? {}
2393
+ })
2394
+ );
2395
+ await callGraphiti(options.client, tools.addMemory, {
2396
+ name,
2397
+ episode_body: input.text,
2398
+ group_id: groupId,
2399
+ source: input.kind === "message" ? "message" : "text",
2400
+ source_description: sourceDescription,
2401
+ uuid: episodeUuid,
2402
+ ...typeof input.metadata?.timestamp === "string" ? { reference_time: input.metadata.timestamp } : {}
2403
+ });
2404
+ queued.set(episodeUuid, { groupId, name });
2405
+ if (consistency === "visible") {
2406
+ await waitForGraphitiEpisode(options, groupId, episodeUuid);
2407
+ queued.delete(episodeUuid);
2408
+ }
2409
+ const result = {
2410
+ accepted: true,
2411
+ id: episodeUuid,
2412
+ uri: `memory://${id}/${episodeUuid}`,
2413
+ kind: input.kind,
2414
+ metadata: {
2415
+ provider: "graphiti",
2416
+ groupId,
2417
+ consistency,
2418
+ queued: consistency === "queued"
2419
+ }
2420
+ };
2421
+ return {
2422
+ ...result,
2423
+ sourceRecord: memoryWriteResultToSourceRecord(result, input.text, { scope })
2424
+ };
2425
+ },
2426
+ async clear(scope) {
2427
+ const groupId = graphitiGroupId(id, mergeScopes3(options.defaultScope, scope));
2428
+ for (const [episodeUuid, pending] of queued) {
2429
+ if (pending.groupId !== groupId) continue;
2430
+ await waitForGraphitiEpisode(options, groupId, episodeUuid);
2431
+ queued.delete(episodeUuid);
2432
+ }
2433
+ await callGraphiti(options.client, tools.clearGraph, { group_ids: [groupId] });
2434
+ },
2435
+ async flush() {
2436
+ for (const [episodeUuid, pending] of queued) {
2437
+ await waitForGraphitiEpisode(options, pending.groupId, episodeUuid);
2438
+ queued.delete(episodeUuid);
2439
+ }
2440
+ }
2441
+ };
2442
+ return adapter;
2443
+ }
2444
+ async function callGraphiti(client, name, args) {
2445
+ const raw = await client.callTool({ name, arguments: args });
2446
+ const payload = graphitiPayload(raw);
2447
+ if (isRecord(payload) && typeof payload.error === "string") {
2448
+ throw new Error(`Graphiti ${name}: ${payload.error}`);
2449
+ }
2450
+ return payload;
2451
+ }
2452
+ function graphitiPayload(raw) {
2453
+ if (!isRecord(raw)) return raw;
2454
+ if (raw.isError === true) throw new Error(`Graphiti MCP: ${toolText(raw) || "tool call failed"}`);
2455
+ if (isRecord(raw.structuredContent)) return unwrapResult(raw.structuredContent);
2456
+ const text = toolText(raw);
2457
+ if (text) {
2458
+ try {
2459
+ return unwrapResult(JSON.parse(text));
2460
+ } catch {
2461
+ return { message: text };
2462
+ }
2463
+ }
2464
+ return unwrapResult(raw);
2465
+ }
2466
+ function unwrapResult(value) {
2467
+ if (isRecord(value) && Object.keys(value).length === 1 && "result" in value) {
2468
+ return value.result;
2469
+ }
2470
+ return value;
2471
+ }
2472
+ function toolText(raw) {
2473
+ if (!Array.isArray(raw.content)) return "";
2474
+ return raw.content.filter(isRecord).map((part) => typeof part.text === "string" ? part.text : "").filter(Boolean).join("\n");
2475
+ }
2476
+ function normalizeGraphitiFacts(raw, adapterId) {
2477
+ const facts = isRecord(raw) && Array.isArray(raw.facts) ? raw.facts.filter(isRecord) : [];
2478
+ return facts.flatMap((fact) => {
2479
+ const text = stringField(fact, ["fact", "text"]);
2480
+ if (!text) return [];
2481
+ const factId = stringField(fact, ["uuid", "id"]) ?? stableId("fact", canonicalJson4(stripUndefined2({ adapterId, text, fact })));
2482
+ const episodeUuids = stringArray2(fact.episodes ?? fact.episode_uuids);
2483
+ const score = numberField(fact, ["score", "relevance_score"]);
2484
+ const normalizedScore = normalizedScoreOf(score);
2485
+ return [
2486
+ {
2487
+ id: factId,
2488
+ uri: `memory://${adapterId}/fact/${factId}`,
2489
+ kind: "fact",
2490
+ text,
2491
+ ...stringField(fact, ["name"]) ? { title: stringField(fact, ["name"]) } : {},
2492
+ ...stringField(fact, ["created_at"]) ? { createdAt: stringField(fact, ["created_at"]) } : {},
2493
+ ...stringField(fact, ["invalid_at"]) ? { validUntil: stringField(fact, ["invalid_at"]) } : stringField(fact, ["expired_at"]) ? { validUntil: stringField(fact, ["expired_at"]) } : {},
2494
+ ...score !== void 0 ? { score } : {},
2495
+ ...normalizedScore !== void 0 ? { normalizedScore } : {},
2496
+ metadata: {
2497
+ provider: "graphiti",
2498
+ groupId: fact.group_id,
2499
+ sourceNodeUuid: fact.source_node_uuid,
2500
+ targetNodeUuid: fact.target_node_uuid,
2501
+ validAt: fact.valid_at,
2502
+ invalidAt: fact.invalid_at,
2503
+ episodeUuids,
2504
+ attributes: fact.attributes
2505
+ }
2506
+ }
2507
+ ];
2508
+ });
2509
+ }
2510
+ function normalizeGraphitiNodes(raw, adapterId) {
2511
+ const nodes = isRecord(raw) && Array.isArray(raw.nodes) ? raw.nodes.filter(isRecord) : [];
2512
+ return nodes.flatMap((node) => {
2513
+ const name = stringField(node, ["name"]);
2514
+ const summary = stringField(node, ["summary"]);
2515
+ const text = summary ?? name;
2516
+ if (!text) return [];
2517
+ const nodeId = stringField(node, ["uuid", "id"]) ?? stableId("node", canonicalJson4(stripUndefined2({ adapterId, text, node })));
2518
+ const score = numberField(node, ["score", "relevance_score"]);
2519
+ const normalizedScore = normalizedScoreOf(score);
2520
+ return [
2521
+ {
2522
+ id: nodeId,
2523
+ uri: `memory://${adapterId}/entity/${nodeId}`,
2524
+ kind: "entity",
2525
+ text,
2526
+ ...name ? { title: name } : {},
2527
+ ...stringField(node, ["created_at"]) ? { createdAt: stringField(node, ["created_at"]) } : {},
2528
+ ...score !== void 0 ? { score } : {},
2529
+ ...normalizedScore !== void 0 ? { normalizedScore } : {},
2530
+ metadata: {
2531
+ provider: "graphiti",
2532
+ groupId: node.group_id,
2533
+ labels: node.labels,
2534
+ attributes: node.attributes
2535
+ }
2536
+ }
2537
+ ];
2538
+ });
2539
+ }
2540
+ async function enrichGraphitiProvenance(options, groupId, hits) {
2541
+ const wanted = new Set(hits.flatMap((hit) => stringArray2(hit.metadata?.episodeUuids)));
2542
+ if (wanted.size === 0) return;
2543
+ const scan = await scanGraphitiEpisodes(
2544
+ options,
2545
+ groupId,
2546
+ wanted,
2547
+ options.provenanceEpisodeLimit ?? options.episodeScanLimit ?? 1e4
2548
+ );
2549
+ const episodes = scan.episodes;
2550
+ const byId = new Map(episodes.map((episode) => [episode.uuid, episode]));
2551
+ for (const hit of hits) {
2552
+ const episodeUuids = stringArray2(hit.metadata?.episodeUuids);
2553
+ const sourceEpisodes = episodeUuids.map((uuid) => byId.get(uuid)).filter((episode) => episode !== void 0);
2554
+ const provenanceIncomplete = episodeUuids.some((uuid) => !byId.has(uuid));
2555
+ if (sourceEpisodes.length === 0) {
2556
+ if (provenanceIncomplete) {
2557
+ hit.metadata = { ...hit.metadata, provenanceIncomplete: true };
2558
+ }
2559
+ continue;
2560
+ }
2561
+ const descriptions = sourceEpisodes.map((episode) => parseDescription(episode.source_description)).filter(isRecord);
2562
+ const first = descriptions[0];
2563
+ hit.metadata = {
2564
+ ...hit.metadata,
2565
+ sourceEpisodes,
2566
+ ...provenanceIncomplete || !scan.complete ? { provenanceIncomplete: true } : {},
2567
+ ...first ?? {},
2568
+ eventIds: uniqueStrings2(descriptions.map((value) => value.eventId)),
2569
+ actorIds: uniqueStrings2(descriptions.map((value) => value.actorId)),
2570
+ ...typeof first?.eventId === "string" ? { eventId: first.eventId } : {},
2571
+ ...typeof first?.actorId === "string" ? { actorId: first.actorId } : {}
2572
+ };
2573
+ }
2574
+ }
2575
+ async function getGraphitiEpisodes(options, groupId, limit) {
2576
+ const raw = await callGraphiti(options.client, graphitiToolNames(options).getEpisodes, {
2577
+ group_ids: [groupId],
2578
+ max_episodes: limit
2579
+ });
2580
+ if (!isRecord(raw) || !Array.isArray(raw.episodes)) return [];
2581
+ return raw.episodes.filter(isRecord).flatMap((episode) => {
2582
+ const uuid = stringField(episode, ["uuid", "id"]);
2583
+ return uuid ? [{ ...episode, uuid }] : [];
2584
+ });
2585
+ }
2586
+ async function scanGraphitiEpisodes(options, groupId, wanted, maximum) {
2587
+ let limit = Math.min(100, maximum);
2588
+ for (; ; ) {
2589
+ const episodes = await getGraphitiEpisodes(options, groupId, limit);
2590
+ const foundAll = [...wanted].every((uuid) => episodes.some((episode) => episode.uuid === uuid));
2591
+ if (foundAll || episodes.length < limit) return { episodes, complete: foundAll };
2592
+ if (limit === maximum) return { episodes, complete: false };
2593
+ limit = Math.min(maximum, limit * 2);
2594
+ }
2595
+ }
2596
+ async function waitForGraphitiEpisode(options, groupId, episodeUuid) {
2597
+ const timeoutMs = options.ingestionTimeoutMs ?? DEFAULT_GRAPHITI_INGESTION_TIMEOUT_MS;
2598
+ const pollIntervalMs = options.pollIntervalMs ?? 500;
2599
+ const maximum = options.episodeScanLimit ?? 1e4;
2600
+ const deadline = Date.now() + timeoutMs;
2601
+ let scanLimitReached = false;
2602
+ for (; ; ) {
2603
+ const scan = await scanGraphitiEpisodes(options, groupId, /* @__PURE__ */ new Set([episodeUuid]), maximum);
2604
+ if (scan.complete) return;
2605
+ scanLimitReached ||= scan.episodes.length >= maximum;
2606
+ const remainingMs = deadline - Date.now();
2607
+ if (remainingMs <= 0) break;
2608
+ await new Promise((resolve) => setTimeout(resolve, Math.min(pollIntervalMs, remainingMs)));
2609
+ }
2610
+ if (scanLimitReached) {
2611
+ throw new Error(
2612
+ `Graphiti episode ${episodeUuid} is outside episodeScanLimit=${maximum}; raise the limit or use a smaller group`
2613
+ );
2614
+ }
2615
+ throw new Error(`Graphiti episode ${episodeUuid} was not visible after ${timeoutMs}ms`);
2616
+ }
2617
+ function graphitiGroupId(adapterId, scope) {
2618
+ return `ak_${sha256(
2619
+ canonicalJson4(
2620
+ stripUndefined2({
2621
+ adapterId,
2622
+ tenantId: scope.tenantId,
2623
+ userId: scope.userId,
2624
+ agentId: scope.agentId,
2625
+ teamId: scope.teamId,
2626
+ runId: scope.runId,
2627
+ sessionId: scope.sessionId,
2628
+ namespace: scope.namespace,
2629
+ tags: scope.tags
2630
+ })
2631
+ )
2632
+ ).slice(0, 32)}`;
2633
+ }
2634
+ function graphitiToolNames(options) {
2635
+ return {
2636
+ addMemory: options.toolNames?.addMemory ?? "add_memory",
2637
+ searchFacts: options.toolNames?.searchFacts ?? "search_memory_facts",
2638
+ searchNodes: options.toolNames?.searchNodes ?? "search_nodes",
2639
+ getEpisodes: options.toolNames?.getEpisodes ?? "get_episodes",
2640
+ clearGraph: options.toolNames?.clearGraph ?? "clear_graph"
2641
+ };
2642
+ }
2643
+ function assertGraphitiOptions(options) {
2644
+ if (options.id !== void 0 && (typeof options.id !== "string" || !options.id.trim())) {
2645
+ throw new Error("Graphiti id must be a non-empty string");
2646
+ }
2647
+ if (options.backendRef !== void 0 && (typeof options.backendRef !== "string" || !options.backendRef.trim())) {
2648
+ throw new Error("Graphiti backendRef must be a non-empty string");
2649
+ }
2650
+ if (options.consistency !== void 0 && !["queued", "visible"].includes(options.consistency)) {
2651
+ throw new Error("Graphiti consistency must be queued or visible");
2652
+ }
2653
+ for (const [name, value] of [
2654
+ ["ingestionTimeoutMs", options.ingestionTimeoutMs],
2655
+ ["pollIntervalMs", options.pollIntervalMs],
2656
+ ["episodeScanLimit", options.episodeScanLimit],
2657
+ ["provenanceEpisodeLimit", options.provenanceEpisodeLimit]
2658
+ ]) {
2659
+ if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) {
2660
+ throw new Error(`Graphiti ${name} must be a positive safe integer`);
2661
+ }
2662
+ }
2663
+ for (const [name, value] of Object.entries(options.toolNames ?? {})) {
2664
+ if (typeof value !== "string" || value.trim().length === 0) {
2665
+ throw new Error(`Graphiti toolNames.${name} must be a non-empty string`);
2666
+ }
2667
+ }
2668
+ }
2669
+ function deterministicUuid(value) {
2670
+ const hex = sha256(value);
2671
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
2672
+ }
2673
+ function parseDescription(value) {
2674
+ if (!value) return void 0;
2675
+ try {
2676
+ return JSON.parse(value);
2677
+ } catch {
2678
+ return void 0;
2679
+ }
2680
+ }
2681
+ function mergeScopes3(base, extra) {
2682
+ return {
2683
+ ...base ?? {},
2684
+ ...extra ?? {},
2685
+ tags: { ...base?.tags ?? {}, ...extra?.tags ?? {} }
2686
+ };
2687
+ }
2688
+ function uniqueStrings2(values) {
2689
+ return [...new Set(values.filter((value) => typeof value === "string"))];
2690
+ }
2691
+ function stringArray2(value) {
2692
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
2693
+ }
2694
+ function stringField(value, keys) {
2695
+ for (const key of keys) {
2696
+ const field = value[key];
2697
+ if (typeof field === "string" && field.length > 0) return field;
2698
+ }
2699
+ return void 0;
2700
+ }
2701
+ function numberField(value, keys) {
2702
+ for (const key of keys) {
2703
+ const field = value[key];
2704
+ if (typeof field === "number" && Number.isFinite(field)) return field;
2705
+ }
2706
+ return void 0;
2707
+ }
2708
+ function normalizedScoreOf(score) {
2709
+ return score !== void 0 && score >= 0 && score <= 1 ? score : void 0;
2710
+ }
2711
+ function isRecord(value) {
2712
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2713
+ }
2714
+ function stripUndefined2(value) {
2715
+ if (Array.isArray(value)) return value.map(stripUndefined2);
2716
+ if (!isRecord(value)) return value;
2717
+ return Object.fromEntries(
2718
+ Object.entries(value).filter(([, child]) => child !== void 0).map(([key, child]) => [key, stripUndefined2(child)])
2719
+ );
2720
+ }
2721
+ function graphitiMemoryAdapterIdentity(options) {
2722
+ if (typeof options.backendRef !== "string" || !options.backendRef.trim()) {
2723
+ throw new Error("Graphiti backendRef must be a non-empty string");
2724
+ }
2725
+ return stableId("graphiti", canonicalJson4(stripUndefined2(options)));
2726
+ }
2727
+
2728
+ // src/memory/improvement.ts
2729
+ import { join as join2 } from "path";
2730
+ import { canonicalJson as canonicalJson5 } from "@tangle-network/agent-eval";
2731
+ import {
2732
+ campaignLineageStore,
2733
+ createRunCostLedger as createRunCostLedger2,
2734
+ fsCampaignStorage as fsCampaignStorage2,
2735
+ heldoutSignificance,
2736
+ memLineageStore,
2737
+ resolveRunDir as resolveRunDir2,
2738
+ runLineageLoop,
2739
+ surfaceHash
2740
+ } from "@tangle-network/agent-eval/campaign";
2741
+ var DEFAULT_CRITICAL_DIMENSIONS = [
2742
+ "memory_stale_safe",
2743
+ "memory_actor_recall",
2744
+ "memory_event_recall"
2745
+ ];
2746
+ var MEMORY_IMPROVEMENT_IMPLEMENTATION_REF = "agent-knowledge:memory-improvement:v2";
2747
+ async function runAgentMemoryImprovement(options) {
2748
+ if ("onPromote" in options) {
2749
+ throw new Error(
2750
+ "memory improvement onPromote was removed; use activation.readCurrent and activation.compareAndSet"
2751
+ );
2752
+ }
2753
+ if (options.seeds.length === 0) throw new Error("memory improvement requires seed configs");
2754
+ if (options.trainSequences.length === 0) {
2755
+ throw new Error("memory improvement requires training sequences");
2756
+ }
2757
+ if (options.holdoutSequences.length === 0) {
2758
+ throw new Error("memory improvement requires holdout sequences");
2759
+ }
2760
+ const trainIds = new Set(options.trainSequences.map((sequence) => sequence.id));
2761
+ const overlap = options.holdoutSequences.map((sequence) => sequence.id).filter((id) => trainIds.has(id));
2762
+ if (overlap.length > 0) {
2763
+ throw new Error(`memory improvement train/holdout overlap: ${overlap.join(", ")}`);
2764
+ }
2765
+ const trainFingerprints = new Map(
2766
+ options.trainSequences.map((sequence) => [memorySequenceFingerprint(sequence), sequence.id])
2767
+ );
2768
+ const duplicateHistories = options.holdoutSequences.flatMap((sequence) => {
2769
+ const trainId = trainFingerprints.get(memorySequenceFingerprint(sequence));
2770
+ return trainId ? [`${trainId}/${sequence.id}`] : [];
2771
+ });
2772
+ if (duplicateHistories.length > 0) {
2773
+ throw new Error(
2774
+ `memory improvement train/holdout histories duplicate content: ${duplicateHistories.join(", ")}`
2775
+ );
2776
+ }
2777
+ if (typeof options.improvementRef !== "string" || !options.improvementRef.trim()) {
2778
+ throw new Error("memory improvement improvementRef must be a non-empty string");
2779
+ }
2780
+ assertMemoryImprovementOptions(options);
2781
+ const storage = options.storage ?? fsCampaignStorage2();
2782
+ const runDir = resolveRunDir2(options.runDir, options.repo);
2783
+ storage.ensureDir(runDir);
2784
+ const lease = await acquireAgentMemoryRunLease({
2785
+ experimentId: options.experimentId,
2786
+ runDir,
2787
+ storage,
2788
+ customStorage: options.storage !== void 0,
2789
+ lockFileName: "memory-improvement.lock",
2790
+ label: "memory improvement",
2791
+ controllerMode: options.controllerMode,
2792
+ acquireRunLease: options.acquireRunLease
2793
+ });
2794
+ let result;
2795
+ let primaryError;
2796
+ try {
2797
+ result = await runAgentMemoryImprovementOwned(options, storage, runDir, lease);
2798
+ } catch (error) {
2799
+ primaryError = error;
2800
+ }
2801
+ let releaseError;
2802
+ try {
2803
+ await lease.release();
2804
+ } catch (error) {
2805
+ releaseError = error;
2806
+ }
2807
+ if (primaryError && releaseError) {
2808
+ throw new AggregateError(
2809
+ [primaryError, releaseError],
2810
+ "memory improvement failed and its controller lease could not be released"
2811
+ );
2812
+ }
2813
+ if (primaryError) throw primaryError;
2814
+ if (releaseError) throw releaseError;
2815
+ if (!result) throw new Error("memory improvement produced no result");
2816
+ return result;
2817
+ }
2818
+ async function runAgentMemoryImprovementOwned(options, storage, runDir, lease) {
2819
+ await lease.assertOwned();
2820
+ const serializeRaw = options.serializeConfig ?? ((config) => canonicalJson5(config));
2821
+ const parseRaw = options.parseConfig ?? ((surface) => JSON.parse(surface));
2822
+ const serialize = (config) => serializeMemoryConfig(serializeRaw, config);
2823
+ const parse = (surface) => parseMemoryConfig(parseRaw, surface);
2824
+ for (const seed of options.seeds) assertMemoryConfigRoundTrip(seed.config, serialize, parse);
2825
+ if (options.activation && !storage.append) {
2826
+ throw new Error("memory activation requires CampaignStorage.append");
2827
+ }
2828
+ if (options.resumable !== false && !options.lineageStore && !storage.append) {
2829
+ throw new Error("resumable memory improvement requires CampaignStorage.append");
2830
+ }
2831
+ assertMemoryImprovementIdentity(options, storage, runDir, serialize);
2832
+ const costLedger = createRunCostLedger2({
2833
+ storage,
2834
+ runDir,
2835
+ costCeilingUsd: options.maxTotalCostUsd ?? 0
2836
+ });
2837
+ reconcileInterruptedRunPaidCalls(costLedger, "memory improvement run");
2838
+ assertNoInterruptedPaidCalls(costLedger, "memory improvement recovery");
2839
+ const trainScenarios = options.trainSequences.map((sequence) => ({
2840
+ id: sequence.id,
2841
+ kind: "agent-memory-config-search",
2842
+ sequenceId: sequence.id
2843
+ }));
2844
+ const evaluations = /* @__PURE__ */ new Map();
2845
+ const evaluateSurface = async (surface) => {
2846
+ await lease.assertOwned();
2847
+ const text = requireStringSurface(surface);
2848
+ const config = parse(text);
2849
+ const canonicalSurface = serialize(config);
2850
+ const hash = surfaceHash(canonicalSurface);
2851
+ let pending = evaluations.get(hash);
2852
+ if (!pending) {
2853
+ pending = (async () => {
2854
+ const candidate = await buildCandidate(options, config, hash, `search-${hash}`);
2855
+ await lease.assertOwned();
2856
+ const experiment = await runAgentMemoryExperiment({
2857
+ ...experimentOptions(options, costLedger, storage, lease),
2858
+ experimentId: `${options.experimentId}:search:${hash}`,
2859
+ sequences: options.trainSequences,
2860
+ candidates: [candidate],
2861
+ runDir: join2(runDir, "search", hash),
2862
+ costPhase: `memory.search.${hash}`
2863
+ });
2864
+ await lease.assertOwned();
2865
+ return experiment;
2866
+ })();
2867
+ evaluations.set(hash, pending);
2868
+ void pending.catch(() => evaluations.delete(hash));
2869
+ }
2870
+ const result2 = await pending;
2871
+ await lease.assertOwned();
2872
+ const row = result2.rows[0];
2873
+ if (!row || row.cellsFailed > 0) {
2874
+ throw new Error(`${hash}: memory candidate did not complete every training cell`);
2875
+ }
2876
+ return {
2877
+ score: row.scoreMean,
2878
+ scoreVector: sequenceScores(result2, options.trainSequences, row.candidateId)
2879
+ };
2880
+ };
2881
+ const lineageStore = options.lineageStore ?? (options.resumable === false ? memLineageStore() : campaignLineageStore(storage, join2(runDir, "lineage.jsonl")));
2882
+ const lineageOptions = {
2883
+ seeds: options.seeds.map((seed) => ({
2884
+ surface: serialize(seed.config),
2885
+ track: seed.track,
2886
+ proposer: seed.proposer,
2887
+ ...seed.vision !== void 0 ? { vision: seed.vision } : {}
2888
+ })),
2889
+ scenarios: trainScenarios,
2890
+ proposer: withCostContext(options.proposer, costLedger, lease, "default"),
2891
+ proposers: options.proposers ? Object.fromEntries(
2892
+ Object.entries(options.proposers).map(([name, proposer]) => [
2893
+ name,
2894
+ withCostContext(proposer, costLedger, lease, name)
2895
+ ])
2896
+ ) : void 0,
2897
+ scoreSurface: evaluateSurface,
2898
+ governor: options.governor ? withGovernorCostContext(options.governor, costLedger, lease) : void 0,
2899
+ budget: {
2900
+ ...options.budget,
2901
+ maxNodes: options.seeds.length + options.budget.maxSteps
2902
+ },
2903
+ store: lineageStore,
2904
+ populationSize: options.populationSize,
2905
+ candidateConcurrency: options.candidateConcurrency
2906
+ };
2907
+ const search = await runLineageLoop(lineageOptions);
2908
+ await lease.assertOwned();
2909
+ const best = search.best;
2910
+ if (!best) throw new Error("memory improvement produced no measured config");
2911
+ const baselineSurface = serialize(options.seeds[0].config);
2912
+ const baselineHash = surfaceHash(baselineSurface);
2913
+ const winnerConfig = parse(requireStringSurface(best.surface));
2914
+ const winnerSurface = serialize(winnerConfig);
2915
+ const winnerHash = surfaceHash(winnerSurface);
2916
+ const baselineConfig = parse(baselineSurface);
2917
+ let holdout;
2918
+ let decision;
2919
+ if (winnerHash === baselineHash) {
2920
+ const baselineMeasurement = await evaluateSurface(baselineSurface);
2921
+ decision = {
2922
+ status: "no-change",
2923
+ reasons: ["search did not find a config better than the baseline"],
2924
+ baselineScore: baselineMeasurement.score,
2925
+ winnerScore: baselineMeasurement.score,
2926
+ lift: 0,
2927
+ criticalDimensions: []
2928
+ };
2929
+ } else {
2930
+ await lease.assertOwned();
2931
+ const [baselineCandidate, winnerCandidate] = await Promise.all([
2932
+ buildCandidate(options, baselineConfig, baselineHash, "baseline"),
2933
+ buildCandidate(options, winnerConfig, winnerHash, "winner")
2934
+ ]);
2935
+ await lease.assertOwned();
2936
+ holdout = await runAgentMemoryExperiment({
2937
+ ...experimentOptions(options, costLedger, storage, lease),
2938
+ experimentId: `${options.experimentId}:holdout`,
2939
+ sequences: options.holdoutSequences,
2940
+ candidates: [baselineCandidate, winnerCandidate],
2941
+ runDir: join2(runDir, "holdout"),
2942
+ costPhase: "memory.holdout"
2943
+ });
2944
+ decision = decidePromotion({
2945
+ options,
2946
+ result: holdout,
2947
+ baselineId: baselineCandidate.id,
2948
+ winnerId: winnerCandidate.id
2949
+ });
2950
+ }
2951
+ const resultJsonPath = join2(runDir, "memory-improvement-result.json");
2952
+ const activationRef = options.activation?.ref ?? "not-configured";
2953
+ const activationId = `memory-activation-${surfaceHash(
2954
+ canonicalJson5({
2955
+ experimentId: options.experimentId,
2956
+ improvementRef: options.improvementRef,
2957
+ activationRef,
2958
+ baselineSurfaceHash: baselineHash,
2959
+ winnerSurfaceHash: winnerHash,
2960
+ holdoutManifestHash: holdout?.campaign.manifestHash ?? null,
2961
+ promotionPolicy: normalizedPromotionPolicy(options)
2962
+ })
2963
+ )}`;
2964
+ const activationJournalDir = join2(runDir, "activations");
2965
+ const activationJournalPath = join2(activationJournalDir, `${activationId}.jsonl`);
2966
+ const activationEligible = decision.status === "promote" && holdout !== void 0;
2967
+ const activationEventIdentity = holdout ? {
2968
+ schema: 1,
2969
+ activationId,
2970
+ experimentId: options.experimentId,
2971
+ activationRef,
2972
+ baselineSurfaceHash: baselineHash,
2973
+ winnerSurfaceHash: winnerHash,
2974
+ holdoutManifestHash: holdout.campaign.manifestHash
2975
+ } : void 0;
2976
+ const activationJournal = activationEligible && activationEventIdentity ? readMemoryActivationJournal(storage, activationJournalPath, activationEventIdentity) : { prepared: false };
2977
+ const activation = {
2978
+ id: activationId,
2979
+ status: !activationEligible ? "not-eligible" : activationJournal.activated ? "already-activated" : options.activation ? "pending" : "not-configured",
2980
+ journalPath: activationJournalPath
2981
+ };
2982
+ const result = {
2983
+ lineage: search.lineage,
2984
+ baselineConfig,
2985
+ winnerConfig,
2986
+ baselineSurface,
2987
+ winnerSurface,
2988
+ baselineSurfaceHash: baselineHash,
2989
+ winnerSurfaceHash: winnerHash,
2990
+ decision,
2991
+ activation,
2992
+ ...holdout ? { holdout } : {},
2993
+ totalCostUsd: costLedger.summary().totalCostUsd,
2994
+ resultJsonPath
2995
+ };
2996
+ await lease.assertOwned();
2997
+ writeMemoryImprovementResult(storage, options.experimentId, result);
2998
+ if (activationEligible && !activationJournal.activated && activationEventIdentity && holdout && options.activation) {
2999
+ const activationDriver = options.activation;
3000
+ const activationTimeoutMs = options.activationTimeoutMs ?? 6e4;
3001
+ const hadPreparedEvent = activationJournal.prepared;
3002
+ if (!hadPreparedEvent) {
3003
+ await lease.assertOwned();
3004
+ storage.ensureDir(activationJournalDir);
3005
+ appendMemoryActivationEvent(storage, activationJournalPath, {
3006
+ ...activationEventIdentity,
3007
+ status: "prepared",
3008
+ recordedAt: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
3009
+ });
3010
+ }
3011
+ await lease.assertOwned();
3012
+ const currentConfig = await runBoundedMemoryLifecycle({
3013
+ operation: `${activationDriver.ref}: read current memory configuration`,
3014
+ timeoutMs: activationTimeoutMs,
3015
+ run: () => activationDriver.readCurrent()
3016
+ });
3017
+ await lease.assertOwned();
3018
+ const currentHash = surfaceHash(serialize(currentConfig));
3019
+ if (currentHash !== baselineHash && currentHash !== winnerHash) {
3020
+ throw new Error(
3021
+ `memory activation target '${activationDriver.ref}' changed concurrently; expected '${baselineHash}' or '${winnerHash}', found '${currentHash}'`
3022
+ );
3023
+ }
3024
+ let outcome;
3025
+ if (currentHash === winnerHash) {
3026
+ outcome = hadPreparedEvent ? "recovered" : "already-current";
3027
+ activation.status = "recovered";
3028
+ } else {
3029
+ let compareError;
3030
+ try {
3031
+ await runBoundedMemoryLifecycle({
3032
+ operation: `${activationDriver.ref}: activate memory configuration`,
3033
+ timeoutMs: activationTimeoutMs,
3034
+ run: () => activationDriver.compareAndSet({
3035
+ activationId,
3036
+ expectedConfig: baselineConfig,
3037
+ expectedSurfaceHash: baselineHash,
3038
+ config: winnerConfig,
3039
+ surfaceHash: winnerHash,
3040
+ decision,
3041
+ lineage: search.lineage,
3042
+ holdout
3043
+ })
3044
+ });
3045
+ } catch (error) {
3046
+ compareError = error;
3047
+ }
3048
+ await lease.assertOwned();
3049
+ let observedConfig;
3050
+ try {
3051
+ observedConfig = await runBoundedMemoryLifecycle({
3052
+ operation: `${activationDriver.ref}: confirm memory configuration`,
3053
+ timeoutMs: activationTimeoutMs,
3054
+ run: () => activationDriver.readCurrent()
3055
+ });
3056
+ } catch (error) {
3057
+ if (compareError) {
3058
+ throw new AggregateError(
3059
+ [compareError, error],
3060
+ `memory activation '${activationId}' failed and its live state could not be confirmed`
3061
+ );
3062
+ }
3063
+ throw error;
3064
+ }
3065
+ await lease.assertOwned();
3066
+ const observedHash = surfaceHash(serialize(observedConfig));
3067
+ if (observedHash !== winnerHash) {
3068
+ const mismatch = new Error(
3069
+ `memory activation '${activationId}' did not install the measured winner; found '${observedHash}'`
3070
+ );
3071
+ if (compareError) {
3072
+ throw new AggregateError(
3073
+ [compareError, mismatch],
3074
+ `memory activation '${activationId}' failed without applying the measured winner`
3075
+ );
3076
+ }
3077
+ throw mismatch;
3078
+ }
3079
+ outcome = compareError ? "recovered" : "applied";
3080
+ activation.status = compareError ? "recovered" : "activated";
3081
+ }
3082
+ await lease.assertOwned();
3083
+ appendMemoryActivationEvent(storage, activationJournalPath, {
3084
+ ...activationEventIdentity,
3085
+ status: "activated",
3086
+ outcome,
3087
+ recordedAt: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
3088
+ });
3089
+ writeMemoryImprovementResult(storage, options.experimentId, result);
3090
+ }
3091
+ return result;
3092
+ }
3093
+ function withCostContext(proposer, costLedger, lease, label) {
3094
+ return {
3095
+ kind: proposer.kind,
3096
+ async propose(context) {
3097
+ await lease.assertOwned();
3098
+ const proposal = await proposer.propose({
3099
+ ...context,
3100
+ costLedger,
3101
+ costPhase: `memory.proposal.${context.track?.id ?? label}`
3102
+ });
3103
+ await lease.assertOwned();
3104
+ return proposal;
3105
+ },
3106
+ ...proposer.decide ? { decide: (input) => proposer.decide(input) } : {}
3107
+ };
3108
+ }
3109
+ function withGovernorCostContext(governor, costLedger, lease) {
3110
+ return {
3111
+ async decide(context) {
3112
+ await lease.assertOwned();
3113
+ const decision = await governor.decide({
3114
+ ...context,
3115
+ costLedger,
3116
+ costPhase: "memory.governor"
3117
+ });
3118
+ await lease.assertOwned();
3119
+ return decision;
3120
+ }
3121
+ };
3122
+ }
3123
+ async function buildCandidate(options, config, hash, role) {
3124
+ const built = await options.createCandidate({
3125
+ config,
3126
+ candidateId: `${role}-${hash}`,
3127
+ surfaceHash: hash
3128
+ });
3129
+ return {
3130
+ ...built,
3131
+ id: `${role}-${hash}`,
3132
+ ref: built.ref
3133
+ };
3134
+ }
3135
+ function experimentOptions(options, costLedger, storage, lease) {
3136
+ return {
3137
+ storage,
3138
+ seed: options.seed,
3139
+ reps: options.reps,
3140
+ resumable: options.resumable,
3141
+ maxConcurrency: options.sequenceConcurrency,
3142
+ dispatchTimeoutMs: options.dispatchTimeoutMs,
3143
+ cleanupTimeoutMs: options.cleanupTimeoutMs,
3144
+ maxRecoveryAttempts: options.maxRecoveryAttempts,
3145
+ maxRecoveryRetriesPerAttempt: options.maxRecoveryRetriesPerAttempt,
3146
+ executeStep: options.executeStep,
3147
+ executeStepRef: options.executeStepRef,
3148
+ onBranchSnapshot: options.onBranchSnapshot,
3149
+ cleanupBranches: options.cleanupBranches ?? true,
3150
+ costLedger,
3151
+ acquireRunLease: async () => ({
3152
+ assertOwned: () => lease.assertOwned(),
3153
+ release() {
3154
+ }
3155
+ }),
3156
+ now: options.now
3157
+ };
3158
+ }
3159
+ function assertMemoryImprovementIdentity(options, storage, runDir, serialize) {
3160
+ const path = join2(runDir, "memory-improvement-manifest.json");
3161
+ const identity = {
3162
+ schema: 6,
3163
+ implementationRef: MEMORY_IMPROVEMENT_IMPLEMENTATION_REF,
3164
+ experimentId: options.experimentId,
3165
+ improvementRef: options.improvementRef,
3166
+ activationRef: options.activation?.ref ?? null,
3167
+ proposerKind: options.proposer.kind,
3168
+ proposerKinds: Object.fromEntries(
3169
+ Object.entries(options.proposers ?? {}).sort(([a], [b]) => a.localeCompare(b)).map(([name, proposer]) => [name, proposer.kind])
3170
+ ),
3171
+ populationSize: options.populationSize ?? 4,
3172
+ budget: { maxSteps: options.budget.maxSteps },
3173
+ executeStepRef: options.executeStepRef ?? null,
3174
+ cleanupBranches: options.cleanupBranches ?? true,
3175
+ promotionPolicy: normalizedPromotionPolicy(options),
3176
+ seed: options.seed ?? null,
3177
+ reps: options.reps ?? 1,
3178
+ seeds: options.seeds.map((entry) => ({
3179
+ config: serialize(entry.config),
3180
+ track: entry.track,
3181
+ vision: entry.vision ?? null,
3182
+ proposer: entry.proposer
3183
+ })),
3184
+ trainSequences: options.trainSequences,
3185
+ holdoutSequences: options.holdoutSequences
3186
+ };
3187
+ const identityHash = surfaceHash(canonicalJson5(identity));
3188
+ const stored = storage.read(path);
3189
+ if (stored === void 0) {
3190
+ if (storage.exists(path)) throw new Error(`cannot read memory improvement manifest '${path}'`);
3191
+ if (storage.exists(join2(runDir, "lineage.jsonl")) || storage.exists(join2(runDir, "memory-improvement-result.json"))) {
3192
+ throw new Error(`memory improvement run '${runDir}' has state without an identity manifest`);
3193
+ }
3194
+ storage.write(path, `${JSON.stringify({ identityHash, identity }, null, 2)}
3195
+ `);
3196
+ return;
3197
+ }
3198
+ let manifest;
3199
+ try {
3200
+ manifest = JSON.parse(stored);
3201
+ } catch (error) {
3202
+ throw new Error(`invalid memory improvement manifest '${path}'`, { cause: error });
3203
+ }
3204
+ if (!manifest || typeof manifest !== "object" || manifest.identityHash !== identityHash || canonicalJson5(manifest.identity) !== canonicalJson5(identity)) {
3205
+ throw new Error(
3206
+ `memory improvement run '${runDir}' does not match its persisted inputs or implementationRef`
3207
+ );
3208
+ }
3209
+ }
3210
+ function appendMemoryActivationEvent(storage, path, event) {
3211
+ appendDurableJournalEvent({
3212
+ storage,
3213
+ path,
3214
+ event,
3215
+ label: "memory activation journal"
3216
+ });
3217
+ }
3218
+ function readMemoryActivationJournal(storage, path, expected) {
3219
+ const stored = storage.read(path);
3220
+ if (stored === void 0) {
3221
+ if (storage.exists(path)) throw new Error(`cannot read memory activation journal '${path}'`);
3222
+ return { prepared: false };
3223
+ }
3224
+ let prepared = false;
3225
+ let activated;
3226
+ for (const [index, line] of stored.split("\n").entries()) {
3227
+ if (!line) continue;
3228
+ let raw;
3229
+ try {
3230
+ raw = JSON.parse(line);
3231
+ } catch (error) {
3232
+ throw new Error(`invalid memory activation journal '${path}' line ${index + 1}`, {
3233
+ cause: error
3234
+ });
3235
+ }
3236
+ const event = parseMemoryActivationEvent(raw, path, index + 1, expected);
3237
+ if (event.status === "prepared") {
3238
+ if (prepared || activated) {
3239
+ throw new Error(`memory activation journal '${path}' repeats its prepared event`);
3240
+ }
3241
+ prepared = true;
3242
+ continue;
3243
+ }
3244
+ if (!prepared || activated) {
3245
+ throw new Error(`memory activation journal '${path}' has an out-of-order activated event`);
3246
+ }
3247
+ activated = event;
3248
+ }
3249
+ return { prepared, ...activated ? { activated } : {} };
3250
+ }
3251
+ function parseMemoryActivationEvent(value, path, line, expected) {
3252
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3253
+ throw new Error(`invalid memory activation journal '${path}' line ${line}`);
3254
+ }
3255
+ const event = value;
3256
+ for (const [key, expectedValue] of Object.entries(expected)) {
3257
+ if (event[key] !== expectedValue) {
3258
+ throw new Error(
3259
+ `memory activation journal '${path}' line ${line} does not match the measured winner`
3260
+ );
3261
+ }
3262
+ }
3263
+ if (event.status !== "prepared" && event.status !== "activated") {
3264
+ throw new Error(`invalid memory activation journal '${path}' line ${line} status`);
3265
+ }
3266
+ if (typeof event.recordedAt !== "string" || !Number.isFinite(Date.parse(event.recordedAt))) {
3267
+ throw new Error(`invalid memory activation journal '${path}' line ${line} recordedAt`);
3268
+ }
3269
+ if (event.status === "activated" && event.outcome !== "applied" && event.outcome !== "recovered" && event.outcome !== "already-current") {
3270
+ throw new Error(`invalid memory activation journal '${path}' line ${line} outcome`);
3271
+ }
3272
+ if (event.status === "prepared" && event.outcome !== void 0) {
3273
+ throw new Error(`invalid memory activation journal '${path}' line ${line} prepared outcome`);
3274
+ }
3275
+ return value;
3276
+ }
3277
+ function writeMemoryImprovementResult(storage, experimentId, result) {
3278
+ storage.write(
3279
+ result.resultJsonPath,
3280
+ `${JSON.stringify(
3281
+ {
3282
+ experimentId,
3283
+ baselineSurfaceHash: result.baselineSurfaceHash,
3284
+ winnerSurfaceHash: result.winnerSurfaceHash,
3285
+ baselineSurface: result.baselineSurface,
3286
+ winnerSurface: result.winnerSurface,
3287
+ decision: result.decision,
3288
+ activation: result.activation,
3289
+ totalCostUsd: result.totalCostUsd,
3290
+ lineage: result.lineage.toGraph()
3291
+ },
3292
+ null,
3293
+ 2
3294
+ )}
3295
+ `
3296
+ );
3297
+ }
3298
+ function decidePromotion(input) {
3299
+ const { options, result, baselineId, winnerId } = input;
3300
+ const baselineRow = result.rows.find((row) => row.candidateId === baselineId);
3301
+ const winnerRow = result.rows.find((row) => row.candidateId === winnerId);
3302
+ if (!baselineRow || !winnerRow) throw new Error("holdout result is missing a comparison arm");
3303
+ const paired = pairedArtifacts(result, baselineId, winnerId, (artifact) => artifact.score);
3304
+ const significance = heldoutSignificance(paired, options.significance);
3305
+ const tolerance = options.criticalDimensionTolerance ?? 0.05;
3306
+ const criticalDimensions = (options.criticalDimensions ?? DEFAULT_CRITICAL_DIMENSIONS).map(
3307
+ (dimension) => {
3308
+ const expectedN = applicableSequenceCount(options.holdoutSequences, dimension) * (options.reps ?? 1);
3309
+ const dimensionPairs = pairedArtifacts(
3310
+ result,
3311
+ baselineId,
3312
+ winnerId,
3313
+ (artifact) => (artifact.dimensionSampleCounts?.[dimension] ?? 0) > 0 ? artifact.dimensions[dimension] : void 0
3314
+ );
3315
+ const comparison = heldoutSignificance(dimensionPairs, {
3316
+ ...options.significance,
3317
+ deltaThreshold: 0
3318
+ });
3319
+ return {
3320
+ dimension,
3321
+ n: comparison.n,
3322
+ expectedN,
3323
+ measured: expectedN > 0 && comparison.n === expectedN,
3324
+ meanDelta: comparison.bootstrap.mean,
3325
+ low: comparison.bootstrap.low,
3326
+ high: comparison.bootstrap.high,
3327
+ tolerance,
3328
+ regressed: expectedN > 0 && comparison.n === expectedN && comparison.bootstrap.low < -tolerance
3329
+ };
3330
+ }
3331
+ );
3332
+ const reasons = [];
3333
+ if (baselineRow.cellsFailed > 0 || winnerRow.cellsFailed > 0) {
3334
+ reasons.push("at least one holdout cell failed");
3335
+ }
3336
+ if (!significance.significant) {
3337
+ reasons.push(
3338
+ significance.fewRuns ? `only ${significance.n} paired holdout cells; more are required` : "holdout lift is not confidently above the promotion threshold"
3339
+ );
3340
+ }
3341
+ if (winnerRow.scoreMean < (options.minHoldoutScore ?? 0)) {
3342
+ reasons.push(`winner holdout score ${winnerRow.scoreMean} is below the required minimum`);
3343
+ }
3344
+ for (const dimension of criticalDimensions) {
3345
+ if (!dimension.measured) {
3346
+ reasons.push(
3347
+ dimension.expectedN === 0 ? `critical dimension ${dimension.dimension} has no applicable holdout histories` : `critical dimension ${dimension.dimension} was measured on ${dimension.n}/${dimension.expectedN} applicable paired holdout cells`
3348
+ );
3349
+ } else if (dimension.regressed) {
3350
+ reasons.push(`${dimension.dimension} may regress beyond ${tolerance}`);
3351
+ }
3352
+ }
3353
+ return {
3354
+ status: reasons.length === 0 ? "promote" : "hold",
3355
+ reasons,
3356
+ baselineScore: baselineRow.scoreMean,
3357
+ winnerScore: winnerRow.scoreMean,
3358
+ lift: winnerRow.scoreMean - baselineRow.scoreMean,
3359
+ significance,
3360
+ criticalDimensions
3361
+ };
3362
+ }
3363
+ function applicableSequenceCount(sequences, dimension) {
3364
+ return sequences.filter(
3365
+ (sequence) => sequence.steps.some(
3366
+ (step) => (step.probes ?? []).some((probe) => probeAppliesToDimension(probe, dimension))
3367
+ )
3368
+ ).length;
3369
+ }
3370
+ function probeAppliesToDimension(probe, dimension) {
3371
+ switch (dimension) {
3372
+ case "memory_fact_recall":
3373
+ case "memory_required_fact_count":
3374
+ case "memory_matched_fact_count":
3375
+ return Boolean(
3376
+ probe.requiredFacts && probe.requiredFacts.length > 0 || probe.referenceAnswer
3377
+ );
3378
+ case "memory_event_recall":
3379
+ return Boolean(probe.expectedEventIds && probe.expectedEventIds.length > 0);
3380
+ case "memory_actor_recall":
3381
+ return Boolean(probe.expectedActorIds && probe.expectedActorIds.length > 0);
3382
+ case "memory_stale_safe":
3383
+ case "memory_stale_rate":
3384
+ case "memory_forbidden_fact_count":
3385
+ case "memory_matched_forbidden_fact_count":
3386
+ return Boolean(probe.forbiddenFacts && probe.forbiddenFacts.length > 0);
3387
+ default:
3388
+ return true;
3389
+ }
3390
+ }
3391
+ function normalizedPromotionPolicy(options) {
3392
+ return {
3393
+ significance: {
3394
+ deltaThreshold: options.significance?.deltaThreshold ?? 0,
3395
+ minProductiveRuns: options.significance?.minProductiveRuns ?? 3,
3396
+ confidence: options.significance?.confidence ?? 0.95,
3397
+ resamples: options.significance?.resamples ?? 2e3,
3398
+ seed: options.significance?.seed ?? 1337,
3399
+ statistic: options.significance?.statistic ?? "mean"
3400
+ },
3401
+ criticalDimensions: [...options.criticalDimensions ?? DEFAULT_CRITICAL_DIMENSIONS],
3402
+ criticalDimensionTolerance: options.criticalDimensionTolerance ?? 0.05,
3403
+ minHoldoutScore: options.minHoldoutScore ?? 0
3404
+ };
3405
+ }
3406
+ function pairedArtifacts(result, baselineId, winnerId, select) {
3407
+ const baseline = artifactValues(result, baselineId, select);
3408
+ const winner = artifactValues(result, winnerId, select);
3409
+ const keys = [...baseline.keys()].filter((key) => winner.has(key)).sort();
3410
+ return {
3411
+ before: keys.map((key) => baseline.get(key)),
3412
+ after: keys.map((key) => winner.get(key)),
3413
+ cellIds: keys
3414
+ };
3415
+ }
3416
+ function artifactValues(result, candidateId, select) {
3417
+ const values = /* @__PURE__ */ new Map();
3418
+ for (const cell of result.campaign.cells) {
3419
+ if (cell.error || cell.artifact.candidateId !== candidateId) continue;
3420
+ const value = select(cell.artifact);
3421
+ if (value === void 0 || !Number.isFinite(value)) continue;
3422
+ values.set(`${cell.artifact.sequenceId}:${cell.rep}`, value);
3423
+ }
3424
+ return values;
3425
+ }
3426
+ function sequenceScores(result, sequences, candidateId) {
3427
+ const bySequence = /* @__PURE__ */ new Map();
3428
+ for (const cell of result.campaign.cells) {
3429
+ if (cell.error || cell.artifact.candidateId !== candidateId) continue;
3430
+ const bucket = bySequence.get(cell.artifact.sequenceId) ?? [];
3431
+ bucket.push(cell.artifact.score);
3432
+ bySequence.set(cell.artifact.sequenceId, bucket);
3433
+ }
3434
+ return sequences.map((sequence) => mean2(bySequence.get(sequence.id) ?? []));
3435
+ }
3436
+ function requireStringSurface(surface) {
3437
+ if (typeof surface !== "string" || surface.trim().length === 0) {
3438
+ throw new Error("memory config proposer must return a JSON string surface");
3439
+ }
3440
+ return surface;
3441
+ }
3442
+ function serializeMemoryConfig(serialize, config) {
3443
+ let surface;
3444
+ try {
3445
+ surface = serialize(config);
3446
+ } catch (error) {
3447
+ throw new Error("memory config serializer failed", { cause: error });
3448
+ }
3449
+ if (typeof surface !== "string" || surface.trim().length === 0) {
3450
+ throw new Error("memory config serializer must return a non-empty string");
3451
+ }
3452
+ return surface;
3453
+ }
3454
+ function parseMemoryConfig(parse, surface) {
3455
+ try {
3456
+ const config = parse(surface);
3457
+ if (config === void 0) throw new Error("parser returned undefined");
3458
+ return config;
3459
+ } catch (error) {
3460
+ throw new Error("memory config surface could not be parsed", { cause: error });
3461
+ }
3462
+ }
3463
+ function assertMemoryConfigRoundTrip(config, serialize, parse) {
3464
+ const first = serialize(config);
3465
+ const second = serialize(parse(first));
3466
+ if (first !== second) {
3467
+ throw new Error("memory config serializer and parser must round-trip seed configs exactly");
3468
+ }
3469
+ }
3470
+ function memorySequenceFingerprint(sequence) {
3471
+ const { id: _id, split: _split, tags: _tags, ...content } = sequence;
3472
+ return surfaceHash(canonicalJson5(content));
3473
+ }
3474
+ function assertMemoryImprovementOptions(options) {
3475
+ for (const [name, value] of [
3476
+ ["experimentId", options.experimentId],
3477
+ ["runDir", options.runDir]
3478
+ ]) {
3479
+ if (typeof value !== "string" || !value.trim()) {
3480
+ throw new Error(`memory improvement ${name} must be a non-empty string`);
3481
+ }
3482
+ }
3483
+ if (typeof options.proposer?.kind !== "string" || !options.proposer.kind.trim()) {
3484
+ throw new Error("memory improvement proposer.kind must be a non-empty string");
3485
+ }
3486
+ if (typeof options.proposer?.propose !== "function") {
3487
+ throw new Error("memory improvement proposer.propose must be a function");
3488
+ }
3489
+ if (options.governor !== void 0 && typeof options.governor.decide !== "function") {
3490
+ throw new Error("memory improvement governor.decide must be a function");
3491
+ }
3492
+ if (options.activation !== void 0) {
3493
+ if (typeof options.activation.ref !== "string" || !options.activation.ref.trim()) {
3494
+ throw new Error("memory improvement activation.ref must be a non-empty string");
3495
+ }
3496
+ if (typeof options.activation.readCurrent !== "function") {
3497
+ throw new Error("memory improvement activation.readCurrent must be a function");
3498
+ }
3499
+ if (typeof options.activation.compareAndSet !== "function") {
3500
+ throw new Error("memory improvement activation.compareAndSet must be a function");
3501
+ }
3502
+ }
3503
+ for (const [name, proposer] of Object.entries(options.proposers ?? {})) {
3504
+ if (!name.trim()) throw new Error("memory improvement proposer labels must be non-empty");
3505
+ if (!proposer || typeof proposer.kind !== "string" || !proposer.kind.trim() || typeof proposer.propose !== "function") {
3506
+ throw new Error(`memory improvement proposer '${name}' is invalid`);
3507
+ }
3508
+ }
3509
+ if (options.serializeConfig !== void 0 && typeof options.serializeConfig !== "function") {
3510
+ throw new Error("memory improvement serializeConfig must be a function");
3511
+ }
3512
+ if (options.parseConfig !== void 0 && typeof options.parseConfig !== "function") {
3513
+ throw new Error("memory improvement parseConfig must be a function");
3514
+ }
3515
+ if (!Number.isSafeInteger(options.budget.maxSteps) || options.budget.maxSteps < 0) {
3516
+ throw new Error("memory improvement budget.maxSteps must be a non-negative safe integer");
3517
+ }
3518
+ for (const [name, value] of [
3519
+ ["populationSize", options.populationSize],
3520
+ ["candidateConcurrency", options.candidateConcurrency],
3521
+ ["sequenceConcurrency", options.sequenceConcurrency],
3522
+ ["reps", options.reps],
3523
+ ["maxRecoveryAttempts", options.maxRecoveryAttempts],
3524
+ ["maxRecoveryRetriesPerAttempt", options.maxRecoveryRetriesPerAttempt]
3525
+ ]) {
3526
+ if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) {
3527
+ throw new Error(`memory improvement ${name} must be a positive safe integer`);
3528
+ }
3529
+ }
3530
+ if (options.maxTotalCostUsd !== void 0 && (!Number.isFinite(options.maxTotalCostUsd) || options.maxTotalCostUsd < 0)) {
3531
+ throw new Error("memory improvement maxTotalCostUsd must be a non-negative finite number");
3532
+ }
3533
+ if (options.activationTimeoutMs !== void 0 && (!Number.isSafeInteger(options.activationTimeoutMs) || options.activationTimeoutMs <= 0)) {
3534
+ throw new Error("memory improvement activationTimeoutMs must be a positive safe integer");
3535
+ }
3536
+ const tolerance = options.criticalDimensionTolerance;
3537
+ if (tolerance !== void 0 && (!Number.isFinite(tolerance) || tolerance < 0 || tolerance > 1)) {
3538
+ throw new Error("memory improvement criticalDimensionTolerance must be between 0 and 1");
3539
+ }
3540
+ const minimum = options.minHoldoutScore;
3541
+ if (minimum !== void 0 && (!Number.isFinite(minimum) || minimum < 0 || minimum > 1)) {
3542
+ throw new Error("memory improvement minHoldoutScore must be between 0 and 1");
3543
+ }
3544
+ for (const seed of options.seeds) {
3545
+ if (typeof seed.track !== "string" || !seed.track.trim() || typeof seed.proposer !== "string" || !seed.proposer.trim()) {
3546
+ throw new Error("memory improvement seeds require non-empty track and proposer values");
3547
+ }
3548
+ if (seed.vision !== void 0 && (typeof seed.vision !== "string" || !seed.vision.trim())) {
3549
+ throw new Error("memory improvement seed vision must be a non-empty string when provided");
3550
+ }
3551
+ }
3552
+ const dimensions = options.criticalDimensions ?? DEFAULT_CRITICAL_DIMENSIONS;
3553
+ if (dimensions.some((dimension) => typeof dimension !== "string" || !dimension.trim())) {
3554
+ throw new Error("memory improvement criticalDimensions must contain non-empty names");
3555
+ }
3556
+ if (new Set(dimensions).size !== dimensions.length) {
3557
+ throw new Error("memory improvement criticalDimensions must be unique");
3558
+ }
3559
+ assertDistinctSequenceContent(options.trainSequences, "train");
3560
+ assertDistinctSequenceContent(options.holdoutSequences, "holdout");
3561
+ }
3562
+ function assertDistinctSequenceContent(sequences, split) {
3563
+ const idsByFingerprint = /* @__PURE__ */ new Map();
3564
+ for (const sequence of sequences) {
3565
+ const fingerprint = memorySequenceFingerprint(sequence);
3566
+ const prior = idsByFingerprint.get(fingerprint);
3567
+ if (prior) {
3568
+ throw new Error(
3569
+ `memory improvement ${split} histories duplicate content: ${prior}/${sequence.id}`
3570
+ );
3571
+ }
3572
+ idsByFingerprint.set(fingerprint, sequence.id);
3573
+ }
3574
+ }
3575
+ function mean2(values) {
3576
+ return values.length === 0 ? 0 : values.reduce((sum, value) => sum + value, 0) / values.length;
3577
+ }
3578
+
3579
+ // src/memory/mem0.ts
3580
+ import { randomUUID as randomUUID4 } from "crypto";
3581
+ import { canonicalJson as canonicalJson6 } from "@tangle-network/agent-eval";
3582
+ function createMem0MemoryAdapter(options) {
3583
+ assertMem0Options(options);
3584
+ const id = options.id ?? `mem0-${options.mode}`;
3585
+ const pendingWrites = /* @__PURE__ */ new Set();
3586
+ const adapter = {
3587
+ id,
3588
+ branchIsolation: options.mode === "oss" ? { mode: "scoped" } : {
3589
+ mode: "scoped",
3590
+ processExitSafe: false,
3591
+ recoveryDelayMs: options.ingestionTimeoutMs ?? 3e4
3592
+ },
3593
+ async search(query, searchOptions = {}) {
3594
+ assertMem0SearchOptions(searchOptions, id);
3595
+ if (searchOptions.limit === 0) return [];
3596
+ const scope = mergeScopes4(options.defaultScope, searchOptions.scope);
3597
+ assertMem0ProviderScope(scope, options.mode, id, "search");
3598
+ const requestedKinds = [...new Set(searchOptions.kinds ?? [])];
3599
+ const kindQueries = requestedKinds.length > 0 ? requestedKinds : [void 0];
3600
+ const payloads = await Promise.all(
3601
+ kindQueries.map(
3602
+ (kind) => options.client.search(query, {
3603
+ filters: mem0Filters(scope, options.appId, kind),
3604
+ topK: searchOptions.limit,
3605
+ threshold: searchOptions.minScore,
3606
+ ...options.rerank !== void 0 ? { rerank: options.rerank } : {},
3607
+ ...options.mode === "hosted" && options.latestOnly !== void 0 ? { latestOnly: options.latestOnly } : {}
3608
+ })
3609
+ )
3610
+ );
3611
+ return mergeMem0Hits(
3612
+ payloads.flatMap((raw) => normalizeMem0Hits(raw, id, searchOptions)),
3613
+ searchOptions.limit
3614
+ );
3615
+ },
3616
+ async getContext(query, searchOptions = {}) {
3617
+ return defaultGetMemoryContext(adapter, query, searchOptions);
3618
+ },
3619
+ async write(input) {
3620
+ const scope = mergeScopes4(options.defaultScope, input.scope);
3621
+ assertMem0ProviderScope(scope, options.mode, id, "write");
3622
+ pruneExpiredMem0PendingWrites(pendingWrites);
3623
+ const writeId = input.id ?? randomUUID4();
3624
+ const writeFilters = mem0Filters(scope, options.appId);
3625
+ const pendingProbe = {
3626
+ text: input.text,
3627
+ providerIds: /* @__PURE__ */ new Set(),
3628
+ filters: writeFilters,
3629
+ expiresAt: Number.POSITIVE_INFINITY
3630
+ };
3631
+ pendingWrites.add(pendingProbe);
3632
+ const scopeMetadata = mem0ScopeMetadata(scope, options.appId);
3633
+ const metadata = compactRecord2({
3634
+ ...input.metadata,
3635
+ agentKnowledgeWriteId: writeId,
3636
+ memoryKind: input.kind,
3637
+ memoryTitle: input.title,
3638
+ entityName: input.entityName,
3639
+ entityType: input.entityType,
3640
+ category: input.category,
3641
+ predicate: input.predicate,
3642
+ subject: input.subject,
3643
+ object: input.object,
3644
+ confidence: input.confidence,
3645
+ originalRole: input.role,
3646
+ ...scopeMetadata
3647
+ });
3648
+ const entity = mem0Entity(scope, options.appId);
3649
+ let raw;
3650
+ try {
3651
+ raw = await options.client.add([{ role: mem0Role(input.role), content: input.text }], {
3652
+ ...entity,
3653
+ metadata,
3654
+ infer: options.infer ?? true,
3655
+ ...options.mode === "oss" ? { filters: writeFilters } : {}
3656
+ });
3657
+ } finally {
3658
+ pendingProbe.expiresAt = Date.now() + (options.ingestionTimeoutMs ?? 3e4);
3659
+ }
3660
+ if (options.mode === "hosted" && !Array.isArray(raw)) {
3661
+ throw new Error(
3662
+ `${id}: hosted Mem0 add returned an unsupported response; mem0ai 3.x must return a memory array`
3663
+ );
3664
+ }
3665
+ const items = extractMem0Items(raw);
3666
+ const mutations = items.filter((candidate) => mem0Event(candidate) !== "NOOP");
3667
+ const durableMutations = mutations.filter((candidate) => mem0Event(candidate) !== "DELETE");
3668
+ for (const candidate of durableMutations) {
3669
+ const providerId = stringField2(candidate, ["id"]);
3670
+ if (providerId) pendingProbe.providerIds.add(providerId);
3671
+ }
3672
+ if (durableMutations.length === 0) {
3673
+ pendingWrites.delete(pendingProbe);
3674
+ }
3675
+ const item = mutations[0] ?? items[0];
3676
+ const itemId = stringField2(item, ["id"]) ?? writeId;
3677
+ const event = mem0Event(item);
3678
+ const events = [...new Set(mutations.map(mem0Event).filter(Boolean))];
3679
+ const result = {
3680
+ accepted: mutations.length > 0,
3681
+ id: itemId,
3682
+ uri: `memory://${id}/${encodeURIComponent(itemId)}`,
3683
+ kind: input.kind,
3684
+ metadata: {
3685
+ provider: "mem0",
3686
+ mode: options.mode,
3687
+ ...event ? { event } : {},
3688
+ ...events.length > 1 ? { events } : {}
3689
+ }
3690
+ };
3691
+ return {
3692
+ ...result,
3693
+ sourceRecord: memoryWriteResultToSourceRecord(result, input.text, { scope })
3694
+ };
3695
+ },
3696
+ async clear(scope) {
3697
+ const mergedScope = mergeScopes4(options.defaultScope, scope);
3698
+ assertMem0ProviderScope(mergedScope, options.mode, id, "clear");
3699
+ pruneExpiredMem0PendingWrites(pendingWrites);
3700
+ const clearFilters = mem0Filters(mergedScope, options.appId);
3701
+ if (Object.keys(clearFilters).length === 0) {
3702
+ throw new Error(`${id}: refusing an unscoped Mem0 clear`);
3703
+ }
3704
+ if (options.client.getAll && options.client.delete) {
3705
+ const pendingForClear = [...pendingWrites].filter(
3706
+ (pending) => mem0FiltersInclude(pending.filters, clearFilters)
3707
+ );
3708
+ const deletedIds = /* @__PURE__ */ new Set();
3709
+ const searchProbes = /* @__PURE__ */ new Map();
3710
+ const observedPending = /* @__PURE__ */ new Set();
3711
+ for (const pending of pendingForClear) {
3712
+ const ids = searchProbes.get(pending.text) ?? /* @__PURE__ */ new Set();
3713
+ for (const providerId of pending.providerIds) ids.add(providerId);
3714
+ searchProbes.set(pending.text, ids);
3715
+ if (pending.providerIds.size > 0) observedPending.add(pending);
3716
+ }
3717
+ for (const pending of pendingForClear) {
3718
+ for (const providerId of pending.providerIds) {
3719
+ if (deletedIds.has(providerId)) continue;
3720
+ await options.client.delete(providerId);
3721
+ deletedIds.add(providerId);
3722
+ }
3723
+ }
3724
+ const visibilityTimeoutMs = options.ingestionTimeoutMs ?? 3e4;
3725
+ const pollIntervalMs = options.pollIntervalMs ?? 250;
3726
+ let visibilityDeadline = Date.now() + visibilityTimeoutMs;
3727
+ for (let batch = 0; batch < 100; ) {
3728
+ const raw = await options.client.getAll(
3729
+ options.mode === "hosted" ? {
3730
+ filters: mem0Filters(mergedScope, options.appId),
3731
+ page: 1,
3732
+ pageSize: 100,
3733
+ latestOnly: false,
3734
+ showExpired: true
3735
+ } : {
3736
+ filters: mem0Filters(mergedScope, options.appId),
3737
+ topK: 100,
3738
+ showExpired: true
3739
+ }
3740
+ );
3741
+ const items = extractMem0Items(raw);
3742
+ if (items.length === 0) {
3743
+ const searchableByQuery = await searchableMem0IdsForCleanup(
3744
+ options,
3745
+ clearFilters,
3746
+ searchProbes
3747
+ );
3748
+ const searchableIds = new Set(
3749
+ [...searchableByQuery.values()].flatMap((ids2) => [...ids2])
3750
+ );
3751
+ const unseenSearchIds = [...searchableIds].filter(
3752
+ (memoryId) => !deletedIds.has(memoryId)
3753
+ );
3754
+ for (const [query, ids2] of searchableByQuery) {
3755
+ const known = searchProbes.get(query) ?? /* @__PURE__ */ new Set();
3756
+ for (const memoryId of ids2) known.add(memoryId);
3757
+ searchProbes.set(query, known);
3758
+ if (ids2.size > 0) {
3759
+ for (const pending of pendingForClear) {
3760
+ if (pending.text === query) observedPending.add(pending);
3761
+ }
3762
+ }
3763
+ }
3764
+ if (unseenSearchIds.length > 0) {
3765
+ for (const memoryId of unseenSearchIds) {
3766
+ await options.client.delete(memoryId);
3767
+ deletedIds.add(memoryId);
3768
+ }
3769
+ batch += 1;
3770
+ visibilityDeadline = Date.now() + visibilityTimeoutMs;
3771
+ continue;
3772
+ }
3773
+ const unresolvedPending = pendingForClear.some(
3774
+ (pending) => !observedPending.has(pending)
3775
+ );
3776
+ const needsQuietWindow = pendingForClear.some(
3777
+ (pending) => pending.providerIds.size === 0
3778
+ );
3779
+ const remainingMs = visibilityDeadline - Date.now();
3780
+ if (remainingMs <= 0) {
3781
+ if (!unresolvedPending && searchableIds.size === 0) {
3782
+ removeMem0PendingWrites(pendingWrites, pendingForClear);
3783
+ return;
3784
+ }
3785
+ if (searchableIds.size > 0) {
3786
+ throw new Error(
3787
+ `${id}: deleted Mem0 memories remained searchable after ${visibilityTimeoutMs}ms`
3788
+ );
3789
+ }
3790
+ throw new Error(
3791
+ `${id}: a Mem0 write never became visible for exact cleanup within ${visibilityTimeoutMs}ms`
3792
+ );
3793
+ }
3794
+ if (!unresolvedPending && searchableIds.size === 0 && !needsQuietWindow) {
3795
+ removeMem0PendingWrites(pendingWrites, pendingForClear);
3796
+ return;
3797
+ }
3798
+ await sleep(Math.min(pollIntervalMs, remainingMs));
3799
+ continue;
3800
+ }
3801
+ const rawIds = items.map((item) => stringField2(item, ["id"]));
3802
+ if (!rawIds.every((memoryId) => memoryId !== void 0)) {
3803
+ throw new Error(`${id}: Mem0 returned memories without ids during scoped clear`);
3804
+ }
3805
+ const ids = [...new Set(rawIds)];
3806
+ for (const item of items) {
3807
+ const memoryId = stringField2(item, ["id"]);
3808
+ const text = stringField2(item, ["memory", "text", "content"]) ?? stringField2(recordField(item, "data"), ["memory", "text", "content"]);
3809
+ if (!text) continue;
3810
+ const idsForText = searchProbes.get(text) ?? /* @__PURE__ */ new Set();
3811
+ idsForText.add(memoryId);
3812
+ searchProbes.set(text, idsForText);
3813
+ for (const pending of pendingForClear) {
3814
+ if (pending.text === text || pending.providerIds.has(memoryId)) {
3815
+ observedPending.add(pending);
3816
+ }
3817
+ }
3818
+ }
3819
+ const unseenIds = ids.filter((memoryId) => !deletedIds.has(memoryId));
3820
+ if (unseenIds.length === 0) {
3821
+ const remainingMs = visibilityDeadline - Date.now();
3822
+ if (remainingMs <= 0) {
3823
+ throw new Error(
3824
+ `${id}: deleted Mem0 memories remained visible after ${visibilityTimeoutMs}ms`
3825
+ );
3826
+ }
3827
+ await sleep(Math.min(pollIntervalMs, remainingMs));
3828
+ continue;
3829
+ }
3830
+ for (const memoryId of unseenIds) {
3831
+ await options.client.delete(memoryId);
3832
+ deletedIds.add(memoryId);
3833
+ }
3834
+ batch += 1;
3835
+ visibilityDeadline = Date.now() + visibilityTimeoutMs;
3836
+ }
3837
+ throw new Error(
3838
+ `${id}: refused to clear more than ${deletedIds.size} Mem0 memories in one call`
3839
+ );
3840
+ }
3841
+ throw new Error(`${id}: exact scoped clear requires Mem0 getAll plus per-memory delete`);
3842
+ }
3843
+ };
3844
+ return adapter;
3845
+ }
3846
+ function assertMem0Options(options) {
3847
+ if (options.id !== void 0 && (typeof options.id !== "string" || !options.id.trim())) {
3848
+ throw new Error("Mem0 id must be a non-empty string");
3849
+ }
3850
+ if (options.appId !== void 0 && (typeof options.appId !== "string" || !options.appId.trim())) {
3851
+ throw new Error("Mem0 appId must be a non-empty string");
3852
+ }
3853
+ if (options.backendRef !== void 0 && (typeof options.backendRef !== "string" || !options.backendRef.trim())) {
3854
+ throw new Error("Mem0 backendRef must be a non-empty string");
3855
+ }
3856
+ for (const [name, value] of [
3857
+ ["ingestionTimeoutMs", options.ingestionTimeoutMs],
3858
+ ["pollIntervalMs", options.pollIntervalMs]
3859
+ ]) {
3860
+ if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) {
3861
+ throw new Error(`Mem0 ${name} must be a positive safe integer`);
3862
+ }
3863
+ }
3864
+ }
3865
+ function assertMem0SearchOptions(options, adapterId) {
3866
+ if (options.limit !== void 0 && (!Number.isSafeInteger(options.limit) || options.limit < 0)) {
3867
+ throw new Error(`${adapterId}: search limit must be a non-negative safe integer`);
3868
+ }
3869
+ if (options.minScore !== void 0 && !Number.isFinite(options.minScore)) {
3870
+ throw new Error(`${adapterId}: minScore must be finite`);
3871
+ }
3872
+ }
3873
+ function assertMem0ProviderScope(scope, mode, adapterId, operation) {
3874
+ if (scope.userId || scope.agentId || scope.runId || scope.namespace) return;
3875
+ if (operation === "clear") {
3876
+ throw new Error(
3877
+ `${adapterId}: refusing an unscoped Mem0 clear; provide scope.userId, scope.agentId, scope.runId, or scope.namespace`
3878
+ );
3879
+ }
3880
+ throw new Error(
3881
+ `${adapterId}: Mem0 ${mode} ${operation} requires scope.userId, scope.agentId, scope.runId, or scope.namespace`
3882
+ );
3883
+ }
3884
+ function normalizeMem0Hits(raw, adapterId, options) {
3885
+ const limit = options.limit ?? Number.POSITIVE_INFINITY;
3886
+ const kinds = options.kinds && options.kinds.length > 0 ? new Set(options.kinds) : null;
3887
+ return extractMem0Items(raw).map((item) => {
3888
+ const text = stringField2(item, ["memory", "text", "content"]) ?? stringField2(recordField(item, "data"), ["memory", "text", "content"]);
3889
+ if (!text) return void 0;
3890
+ const metadata = recordField(item, "metadata");
3891
+ const kind = mem0Kind(metadata?.memoryKind ?? item?.memoryType);
3892
+ if (kinds && !kinds.has(kind)) return void 0;
3893
+ const score = numberField2(item, ["rerankScore", "rerank_score", "score"]);
3894
+ if (options.minScore !== void 0 && (score === void 0 || score < options.minScore)) {
3895
+ return void 0;
3896
+ }
3897
+ const memoryId = stringField2(item, ["id"]) ?? stableId("mem", canonicalJson6(compactRecord2({ adapterId, text, metadata, item })));
3898
+ const normalizedScore = score !== void 0 && score >= 0 && score <= 1 ? score : void 0;
3899
+ return {
3900
+ id: memoryId,
3901
+ uri: `memory://${adapterId}/${encodeURIComponent(memoryId)}`,
3902
+ kind,
3903
+ text,
3904
+ ...stringField2(metadata, ["memoryTitle"]) ? { title: String(metadata.memoryTitle) } : {},
3905
+ ...score !== void 0 ? { score } : {},
3906
+ ...normalizedScore !== void 0 ? { normalizedScore } : {},
3907
+ ...dateField(item, ["createdAt", "created_at"]) ? { createdAt: dateField(item, ["createdAt", "created_at"]) } : {},
3908
+ ...dateField(item, ["expirationDate", "expiration_date"]) ? { validUntil: dateField(item, ["expirationDate", "expiration_date"]) } : {},
3909
+ metadata: compactRecord2({
3910
+ provider: "mem0",
3911
+ ...metadata,
3912
+ categories: item?.categories,
3913
+ owner: item?.owner,
3914
+ runId: item?.runId,
3915
+ agentId: item?.agentId,
3916
+ userId: item?.userId
3917
+ })
3918
+ };
3919
+ }).filter((hit) => hit !== void 0).slice(0, limit);
3920
+ }
3921
+ function mergeMem0Hits(hits, limit) {
3922
+ const unique = /* @__PURE__ */ new Map();
3923
+ for (const [index, hit] of hits.entries()) {
3924
+ const key = JSON.stringify([hit.uri, hit.text]);
3925
+ const score = hit.normalizedScore ?? hit.score;
3926
+ const finiteScore = score !== void 0 && Number.isFinite(score) ? score : void 0;
3927
+ const prior = unique.get(key);
3928
+ if (!prior) {
3929
+ unique.set(key, { hit, index, score: finiteScore });
3930
+ } else if (finiteScore !== void 0 && (prior.score === void 0 || finiteScore > prior.score)) {
3931
+ unique.set(key, { hit, index: prior.index, score: finiteScore });
3932
+ }
3933
+ }
3934
+ return [...unique.values()].sort(
3935
+ (left, right) => Number(right.score !== void 0) - Number(left.score !== void 0) || (right.score ?? 0) - (left.score ?? 0) || left.index - right.index
3936
+ ).map(({ hit }) => hit).slice(0, limit ?? Number.POSITIVE_INFINITY);
3937
+ }
3938
+ function extractMem0Items(raw) {
3939
+ if (Array.isArray(raw)) return raw.filter(isRecord2);
3940
+ if (!isRecord2(raw)) return [];
3941
+ for (const key of ["results", "memories", "data"]) {
3942
+ if (Array.isArray(raw[key])) return raw[key].filter(isRecord2);
3943
+ }
3944
+ return [raw];
3945
+ }
3946
+ function mem0Event(item) {
3947
+ return (stringField2(item, ["event"]) ?? stringField2(recordField(item, "metadata"), ["event"]))?.toUpperCase();
3948
+ }
3949
+ function mem0Entity(scope, appId) {
3950
+ return compactStringRecord({
3951
+ userId: scope.userId,
3952
+ agentId: scope.agentId,
3953
+ appId,
3954
+ runId: scope.namespace ?? scope.runId
3955
+ });
3956
+ }
3957
+ function mem0Filters(scope, appId, kind) {
3958
+ const entity = mem0EntityFilters(scope, appId);
3959
+ const customMetadata = compactRecord2({
3960
+ ...mem0CustomScopeMetadata(scope),
3961
+ memoryKind: kind
3962
+ });
3963
+ return compactRecord2({ ...entity, ...customMetadata });
3964
+ }
3965
+ function mem0ScopeMetadata(scope, appId) {
3966
+ return { ...mem0EntityFilters(scope, appId), ...mem0CustomScopeMetadata(scope) };
3967
+ }
3968
+ function mem0EntityFilters(scope, appId) {
3969
+ return compactStringRecord({
3970
+ user_id: scope.userId,
3971
+ agent_id: scope.agentId,
3972
+ run_id: scope.namespace ?? scope.runId,
3973
+ app_id: appId
3974
+ });
3975
+ }
3976
+ function mem0CustomScopeMetadata(scope) {
3977
+ const metadata = compactStringRecord({
3978
+ logical_run_id: scope.namespace ? scope.runId : void 0,
3979
+ tenant_id: scope.tenantId,
3980
+ team_id: scope.teamId,
3981
+ session_id: scope.sessionId
3982
+ });
3983
+ for (const [key, value] of Object.entries(scope.tags ?? {})) {
3984
+ metadata[`tag_${key}`] = value;
3985
+ }
3986
+ return metadata;
3987
+ }
3988
+ async function searchableMem0IdsForCleanup(options, filters, probes) {
3989
+ const searchable = /* @__PURE__ */ new Map();
3990
+ const entries = [...probes];
3991
+ for (let offset = 0; offset < entries.length; offset += 8) {
3992
+ const results = await Promise.all(
3993
+ entries.slice(offset, offset + 8).map(async ([query, expectedIds]) => {
3994
+ const raw = await options.client.search(query, {
3995
+ filters,
3996
+ topK: Math.min(100, Math.max(10, expectedIds.size)),
3997
+ ...options.mode === "hosted" ? { latestOnly: false, showExpired: true } : {}
3998
+ });
3999
+ return extractMem0Items(raw).map((item) => {
4000
+ const memoryId = stringField2(item, ["id"]);
4001
+ if (!memoryId) {
4002
+ throw new Error(
4003
+ `${options.id ?? `mem0-${options.mode}`}: Mem0 search returned a memory without an id during clear verification`
4004
+ );
4005
+ }
4006
+ return memoryId;
4007
+ });
4008
+ })
4009
+ );
4010
+ for (const [index, ids] of results.entries()) {
4011
+ const query = entries[offset + index][0];
4012
+ searchable.set(query, new Set(ids));
4013
+ }
4014
+ }
4015
+ return searchable;
4016
+ }
4017
+ function mem0FiltersInclude(candidate, requested) {
4018
+ return Object.entries(requested).every(([key, value]) => Object.is(candidate[key], value));
4019
+ }
4020
+ function removeMem0PendingWrites(pendingWrites, removed) {
4021
+ for (const pending of removed) pendingWrites.delete(pending);
4022
+ }
4023
+ function pruneExpiredMem0PendingWrites(pendingWrites) {
4024
+ const now = Date.now();
4025
+ for (const pending of pendingWrites) {
4026
+ if (pending.expiresAt <= now) pendingWrites.delete(pending);
4027
+ }
4028
+ }
4029
+ function mem0Role(role) {
4030
+ return role === "assistant" || role === "tool" ? "assistant" : "user";
4031
+ }
4032
+ function mem0Kind(value) {
4033
+ switch (value) {
4034
+ case "message":
4035
+ case "entity":
4036
+ case "fact":
4037
+ case "preference":
4038
+ case "observation":
4039
+ case "reasoning-trace":
4040
+ return value;
4041
+ default:
4042
+ return "fact";
4043
+ }
4044
+ }
4045
+ function mergeScopes4(base, extra) {
4046
+ return {
4047
+ ...base ?? {},
4048
+ ...extra ?? {},
4049
+ tags: { ...base?.tags ?? {}, ...extra?.tags ?? {} }
4050
+ };
4051
+ }
4052
+ function sleep(ms) {
4053
+ return new Promise((resolve) => setTimeout(resolve, ms));
4054
+ }
4055
+ function compactRecord2(input) {
4056
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
4057
+ }
4058
+ function compactStringRecord(input) {
4059
+ return Object.fromEntries(
4060
+ Object.entries(input).filter((entry) => entry[1] !== void 0)
4061
+ );
4062
+ }
4063
+ function isRecord2(value) {
4064
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4065
+ }
4066
+ function recordField(value, key) {
4067
+ const field = value?.[key];
4068
+ return isRecord2(field) ? field : void 0;
4069
+ }
4070
+ function stringField2(value, keys) {
4071
+ for (const key of keys) {
4072
+ const field = value?.[key];
4073
+ if (typeof field === "string" && field.length > 0) return field;
4074
+ }
4075
+ return void 0;
4076
+ }
4077
+ function numberField2(value, keys) {
4078
+ for (const key of keys) {
4079
+ const field = value?.[key];
4080
+ if (typeof field === "number" && Number.isFinite(field)) return field;
4081
+ }
4082
+ return void 0;
4083
+ }
4084
+ function dateField(value, keys) {
4085
+ for (const key of keys) {
4086
+ const field = value?.[key];
4087
+ if (typeof field === "string" && field.length > 0) return field;
4088
+ if (field instanceof Date && !Number.isNaN(field.valueOf())) return field.toISOString();
4089
+ }
4090
+ return void 0;
4091
+ }
4092
+ function mem0MemoryAdapterIdentity(options) {
4093
+ if (typeof options.backendRef !== "string" || !options.backendRef.trim()) {
4094
+ throw new Error("Mem0 backendRef must be a non-empty string");
4095
+ }
4096
+ return stableId("mem0", canonicalJson6(compactRecord2(options)));
4097
+ }
4098
+
4099
+ // src/memory/neo4j.ts
4100
+ import { canonicalJson as canonicalJson7 } from "@tangle-network/agent-eval";
4101
+ function createNeo4jAgentMemoryAdapter(options) {
4102
+ assertNeo4jOptions(options);
4103
+ const client = options.client;
4104
+ const id = options.id ?? "neo4j-agent-memory";
4105
+ const isolatedInstance = options.branchId !== void 0;
4106
+ const adapter = {
4107
+ id,
4108
+ branchIsolation: options.branchId ? { mode: "instance", branchId: options.branchId, supportsLogicalScopes: false } : {
4109
+ mode: "unsupported",
4110
+ reason: "create a separate MemoryClient namespace per branch and pass branchId"
4111
+ },
4112
+ async search(query, searchOptions = {}) {
4113
+ assertNeo4jSearchOptions(searchOptions, id);
4114
+ return searchNeo4jMemory(
4115
+ client,
4116
+ query,
4117
+ searchOptions,
4118
+ id,
4119
+ options.transport,
4120
+ isolatedInstance
4121
+ );
4122
+ },
4123
+ async getContext(query, searchOptions = {}) {
4124
+ assertNeo4jSearchOptions(searchOptions, id);
4125
+ assertNeo4jSearchKinds(searchOptions.kinds, options.transport, id);
4126
+ const shortTerm = nested(client, ["shortTerm", "short_term"], {});
4127
+ const sessionId = searchOptions.scope?.sessionId;
4128
+ if (options.contextMode === "native" && options.transport !== "rest") {
4129
+ throw new Error(`${id}: Neo4j native context requires transport="rest"`);
4130
+ }
4131
+ if (options.contextMode === "native" && !sessionId) {
4132
+ throw new Error(`${id}: Neo4j native context requires scope.sessionId`);
4133
+ }
4134
+ if (options.contextMode === "native" && searchOptions.kinds?.some((kind) => kind !== "message" && kind !== "observation")) {
4135
+ throw new Error(
4136
+ `${id}: Neo4j native context only returns message and observation memory kinds`
4137
+ );
4138
+ }
4139
+ if (options.contextMode === "native" && options.transport === "rest" && sessionId) {
4140
+ assertNeo4jScopeSupported(
4141
+ searchOptions.scope,
4142
+ ["sessionId"],
4143
+ id,
4144
+ "native context read",
4145
+ isolatedInstance
4146
+ );
4147
+ const conversationContext = await callRequired(shortTerm, ["getContext"], [sessionId]);
4148
+ const hits = normalizeConversationContextHits(conversationContext, searchOptions, id);
4149
+ const text = renderHits(hits);
4150
+ if (searchOptions.holdout) {
4151
+ emitRetrievalHoldoutBypass(
4152
+ hits,
4153
+ searchOptions.holdout,
4154
+ holdoutBypassContext(query, searchOptions),
4155
+ "short-term-context"
4156
+ );
4157
+ }
4158
+ return {
4159
+ query,
4160
+ text,
4161
+ hits,
4162
+ sourceRecords: hits.map(
4163
+ (hit) => memoryHitToSourceRecord(hit, { scope: searchOptions.scope })
4164
+ ),
4165
+ metadata: { adapter: id, rawContext: conversationContext }
4166
+ };
4167
+ }
4168
+ return defaultGetMemoryContext(adapter, query, searchOptions);
4169
+ },
4170
+ async write(input) {
4171
+ const result = await writeNeo4jMemory(client, input, id, options.transport, isolatedInstance);
4172
+ return {
4173
+ ...result,
4174
+ sourceRecord: memoryWriteResultToSourceRecord(result, input.text, { scope: input.scope })
4175
+ };
4176
+ },
4177
+ async close() {
4178
+ await callOptional(client, ["close"], []);
4179
+ }
4180
+ };
4181
+ return adapter;
4182
+ }
4183
+ function assertNeo4jOptions(options) {
4184
+ if (typeof options.client !== "object" || options.client === null) {
4185
+ throw new Error("Neo4j agent-memory client must be an object");
4186
+ }
4187
+ if (options.id !== void 0 && (typeof options.id !== "string" || !options.id.trim())) {
4188
+ throw new Error("Neo4j agent-memory id must be a non-empty string");
4189
+ }
4190
+ if (options.branchId !== void 0 && (typeof options.branchId !== "string" || !options.branchId.trim())) {
4191
+ throw new Error("Neo4j agent-memory branchId must be a non-empty string");
4192
+ }
4193
+ if (options.contextMode !== void 0 && !["search", "native"].includes(options.contextMode)) {
4194
+ throw new Error("Neo4j agent-memory contextMode must be search or native");
4195
+ }
4196
+ }
4197
+ function assertNeo4jSearchOptions(options, adapterId) {
4198
+ if (options.limit !== void 0 && (!Number.isSafeInteger(options.limit) || options.limit < 0)) {
4199
+ throw new Error(`${adapterId}: search limit must be a non-negative safe integer`);
4200
+ }
4201
+ if (options.minScore !== void 0 && !Number.isFinite(options.minScore)) {
4202
+ throw new Error(`${adapterId}: minScore must be finite`);
4203
+ }
4204
+ }
4205
+ function assertNeo4jSearchKinds(kinds, transport, adapterId) {
4206
+ if (!kinds?.length) return;
4207
+ const supported = new Set(
4208
+ transport === "rest" ? ["message", "observation", "entity"] : ["message", "observation", "entity", "preference", "reasoning-trace"]
4209
+ );
4210
+ const unsupported = [...new Set(kinds.filter((kind) => !supported.has(kind)))];
4211
+ if (unsupported.length > 0) {
4212
+ throw new Error(
4213
+ `${adapterId}: Neo4j ${transport} cannot search memory kinds: ${unsupported.join(", ")}`
4214
+ );
4215
+ }
4216
+ }
4217
+ function assertNeo4jBridgeCapability(transport, kind, adapterId) {
4218
+ if (transport === "bridge") return;
4219
+ throw new Error(
4220
+ `${adapterId}: Neo4j REST cannot write ${kind}; use transport="bridge" or map it to an entity/message`
4221
+ );
4222
+ }
4223
+ async function writeNeo4jMemory(client, input, adapterId, transport, isolatedInstance) {
4224
+ const scope = input.scope ?? {};
4225
+ const sessionId = scope.sessionId ?? input.metadata?.sessionId;
4226
+ assertNeo4jScopeSupported(
4227
+ input.scope,
4228
+ input.kind === "message" || input.kind === "observation" ? ["sessionId"] : input.kind === "reasoning-trace" ? ["sessionId", "runId"] : [],
4229
+ adapterId,
4230
+ `${input.kind} write`,
4231
+ isolatedInstance
4232
+ );
4233
+ if (!isolatedInstance && input.kind === "reasoning-trace" && scope.sessionId !== void 0 && scope.runId !== void 0) {
4234
+ throw new Error(
4235
+ `${adapterId}: Neo4j reasoning write cannot enforce both sessionId and runId; provide one`
4236
+ );
4237
+ }
4238
+ let result;
4239
+ if (input.kind === "message" || input.kind === "observation") {
4240
+ if (transport === "rest" && typeof sessionId !== "string") {
4241
+ throw new Error(
4242
+ `${adapterId}: Neo4j REST message writes require scope.sessionId as the conversation id`
4243
+ );
4244
+ }
4245
+ const shortTerm = nested(client, ["shortTerm", "short_term"], client);
4246
+ const metadata = { ...input.metadata, agentKnowledgeKind: input.kind };
4247
+ result = await callRequired(
4248
+ shortTerm,
4249
+ ["addMessage"],
4250
+ [
4251
+ sessionId,
4252
+ neo4jMessageRole(input.role),
4253
+ input.text,
4254
+ { metadata, conversationId: sessionId }
4255
+ ]
4256
+ );
4257
+ } else if (input.kind === "entity") {
4258
+ const longTerm = nested(client, ["longTerm", "long_term"], client);
4259
+ result = await callRequired(
4260
+ longTerm,
4261
+ ["addEntity"],
4262
+ [
4263
+ input.entityName ?? input.title ?? input.text,
4264
+ input.entityType ?? "custom",
4265
+ neo4jEntityOptions(input)
4266
+ ]
4267
+ );
4268
+ } else if (input.kind === "preference") {
4269
+ assertNeo4jBridgeCapability(transport, input.kind, adapterId);
4270
+ const longTerm = nested(client, ["longTerm", "long_term"], client);
4271
+ result = await callRequired(
4272
+ longTerm,
4273
+ ["addPreference"],
4274
+ [input.category ?? "general", input.text, neo4jPreferenceOptions(input)]
4275
+ );
4276
+ } else if (input.kind === "reasoning-trace") {
4277
+ const reasoning = nested(client, ["reasoning"], client);
4278
+ const conversationId = sessionId ?? scope.runId;
4279
+ if (transport === "rest") {
4280
+ if (typeof conversationId !== "string") {
4281
+ throw new Error(
4282
+ `${adapterId}: Neo4j REST reasoning writes require scope.sessionId or scope.runId`
4283
+ );
4284
+ }
4285
+ result = await callRequired(
4286
+ reasoning,
4287
+ ["recordStep"],
4288
+ [
4289
+ {
4290
+ conversationId,
4291
+ reasoning: input.text,
4292
+ actionTaken: stringMetadata(input.metadata, "action") ?? input.title ?? "memory",
4293
+ result: stringMetadata(input.metadata, "result") ?? stringMetadata(input.metadata, "observation") ?? stringMetadata(input.metadata, "outcome")
4294
+ }
4295
+ ]
4296
+ );
4297
+ } else {
4298
+ const trace = await callRequired(
4299
+ reasoning,
4300
+ ["startTrace"],
4301
+ [
4302
+ conversationId ?? stableId("session", input.text),
4303
+ stringMetadata(input.metadata, "task") ?? input.title ?? input.text
4304
+ ]
4305
+ );
4306
+ assertNeo4jWriteSucceeded(trace, adapterId, "startTrace");
4307
+ const traceId = idFromResult(trace);
4308
+ if (!traceId) throw new Error(`${adapterId}: Neo4j startTrace returned no trace id`);
4309
+ try {
4310
+ const step = await callRequired(
4311
+ reasoning,
4312
+ ["addStep"],
4313
+ [
4314
+ traceId,
4315
+ {
4316
+ thought: input.text,
4317
+ observation: stringMetadata(input.metadata, "observation"),
4318
+ action: stringMetadata(input.metadata, "action")
4319
+ }
4320
+ ]
4321
+ );
4322
+ assertNeo4jWriteSucceeded(step, adapterId, "addStep");
4323
+ } catch (error) {
4324
+ try {
4325
+ await callRequired(
4326
+ reasoning,
4327
+ ["completeTrace"],
4328
+ [traceId, { outcome: "agent-knowledge addStep failed", success: false }]
4329
+ );
4330
+ } catch (cleanupError) {
4331
+ throw new AggregateError(
4332
+ [error, cleanupError],
4333
+ `${adapterId}: Neo4j reasoning step and trace cleanup failed`
4334
+ );
4335
+ }
4336
+ throw error;
4337
+ }
4338
+ result = await callRequired(
4339
+ reasoning,
4340
+ ["completeTrace"],
4341
+ [
4342
+ traceId,
4343
+ {
4344
+ outcome: stringMetadata(input.metadata, "outcome"),
4345
+ success: typeof input.metadata?.success === "boolean" ? input.metadata.success : void 0
4346
+ }
4347
+ ]
4348
+ );
4349
+ }
4350
+ } else {
4351
+ assertNeo4jBridgeCapability(transport, input.kind, adapterId);
4352
+ result = await callRequired(
4353
+ nested(client, ["longTerm", "long_term"], client),
4354
+ ["addFact"],
4355
+ [
4356
+ input.subject ?? input.title ?? input.kind,
4357
+ input.predicate ?? "states",
4358
+ input.object ?? input.text
4359
+ ]
4360
+ );
4361
+ }
4362
+ assertNeo4jWriteSucceeded(result, adapterId, input.kind);
4363
+ const id = idFromResult(result) ?? input.id ?? stableId(
4364
+ "mem",
4365
+ canonicalJson7({ adapterId, kind: input.kind, text: input.text, scope: input.scope ?? {} })
4366
+ );
4367
+ return {
4368
+ accepted: true,
4369
+ id,
4370
+ uri: `memory://${adapterId}/${encodeURIComponent(id)}`,
4371
+ kind: input.kind,
4372
+ metadata: { provider: "neo4j-agent-memory", transport }
4373
+ };
4374
+ }
4375
+ function assertNeo4jWriteSucceeded(value, adapterId, operation) {
4376
+ const response = record(value);
4377
+ if (!response) return;
4378
+ const status = stringField3(response, ["status"])?.toLowerCase();
4379
+ if (response.success === false || response.ok === false || response.isError === true || status === "failed" || status === "error") {
4380
+ const nestedError = record(response.error);
4381
+ const detail = stringField3(response, ["error", "message", "detail"]) ?? (nestedError ? stringField3(nestedError, ["message", "detail"]) : void 0);
4382
+ throw new Error(`${adapterId}: Neo4j ${operation} failed${detail ? `: ${detail}` : ""}`);
4383
+ }
4384
+ }
4385
+ async function searchNeo4jMemory(client, query, options, adapterId, transport, isolatedInstance) {
4386
+ const limit = options.limit ?? 10;
4387
+ if (limit === 0) return [];
4388
+ assertNeo4jSearchKinds(options.kinds, transport, adapterId);
4389
+ const onlyConversationKinds = options.kinds !== void 0 && options.kinds.length > 0 && options.kinds.every((kind) => kind === "message" || kind === "observation");
4390
+ assertNeo4jScopeSupported(
4391
+ options.scope,
4392
+ onlyConversationKinds ? ["sessionId"] : [],
4393
+ adapterId,
4394
+ "search",
4395
+ isolatedInstance
4396
+ );
4397
+ const searches = [];
4398
+ const kinds = options.kinds;
4399
+ const includeKind = (kind) => !kinds?.length || kinds.includes(kind);
4400
+ const shortTerm = nested(client, ["shortTerm", "short_term"], {});
4401
+ const longTerm = nested(client, ["longTerm", "long_term"], {});
4402
+ const reasoning = nested(client, ["reasoning"], {});
4403
+ const wantsMessages = includeKind("message") || includeKind("observation");
4404
+ const hasConversation = typeof options.scope?.sessionId === "string";
4405
+ if (transport === "rest" && wantsMessages && !hasConversation && options.kinds?.some((kind) => kind === "message" || kind === "observation")) {
4406
+ throw new Error(
4407
+ `${adapterId}: Neo4j REST message search requires scope.sessionId as the conversation id`
4408
+ );
4409
+ }
4410
+ if (wantsMessages && (transport === "bridge" || hasConversation)) {
4411
+ const messageKinds = options.kinds?.filter(
4412
+ (kind) => kind === "message" || kind === "observation"
4413
+ ) ?? ["message", "observation"];
4414
+ searches.push(
4415
+ callRequired(shortTerm, ["searchMessages"], [query, searchMessagesOptions(options)]).then(
4416
+ (result) => normalizeHits(result, { ...options, kinds: messageKinds }, adapterId)
4417
+ )
4418
+ );
4419
+ }
4420
+ if (includeKind("entity")) {
4421
+ searches.push(
4422
+ callRequired(longTerm, ["searchEntities"], [query, searchEntitiesOptions(options)]).then(
4423
+ (result) => normalizeHits(result, { ...options, kinds: ["entity"] }, adapterId)
4424
+ )
4425
+ );
4426
+ }
4427
+ if (transport === "bridge" && includeKind("preference")) {
4428
+ searches.push(
4429
+ callRequired(
4430
+ longTerm,
4431
+ ["searchPreferences"],
4432
+ [query, searchPreferencesOptions(options)]
4433
+ ).then((result) => normalizeHits(result, { ...options, kinds: ["preference"] }, adapterId))
4434
+ );
4435
+ }
4436
+ if (transport === "bridge" && includeKind("reasoning-trace")) {
4437
+ searches.push(
4438
+ callRequired(reasoning, ["getSimilarTraces"], [query, similarTracesOptions(options)]).then(
4439
+ (result) => normalizeHits(result, { ...options, kinds: ["reasoning-trace"] }, adapterId)
4440
+ )
4441
+ );
4442
+ }
4443
+ return mergeRankedMemoryHits(await Promise.all(searches), limit);
4444
+ }
4445
+ function holdoutBypassContext(query, options) {
4446
+ return {
4447
+ query,
4448
+ ...options.scope !== void 0 ? { scope: options.scope } : {},
4449
+ ...options.scope?.tags?.taskId !== void 0 ? { taskId: options.scope.tags.taskId } : {}
4450
+ };
4451
+ }
4452
+ function searchMessagesOptions(options) {
4453
+ return {
4454
+ limit: options.limit,
4455
+ sessionId: options.scope?.sessionId,
4456
+ conversationId: options.scope?.sessionId,
4457
+ threshold: options.minScore
4458
+ };
4459
+ }
4460
+ function searchEntitiesOptions(options) {
4461
+ return {
4462
+ limit: options.limit,
4463
+ type: options.metadata?.type
4464
+ };
4465
+ }
4466
+ function searchPreferencesOptions(options) {
4467
+ return {
4468
+ limit: options.limit,
4469
+ category: options.metadata?.category
4470
+ };
4471
+ }
4472
+ function similarTracesOptions(options) {
4473
+ return {
4474
+ limit: options.limit,
4475
+ successOnly: options.metadata?.successOnly
4476
+ };
4477
+ }
4478
+ function assertNeo4jScopeSupported(scope, allowedKeys, adapterId, operation, isolatedInstance) {
4479
+ if (isolatedInstance || !scope) return;
4480
+ const allowed = new Set(allowedKeys);
4481
+ const scopeKeys = [
4482
+ "tenantId",
4483
+ "userId",
4484
+ "agentId",
4485
+ "teamId",
4486
+ "runId",
4487
+ "sessionId",
4488
+ "namespace"
4489
+ ];
4490
+ const supplied = scopeKeys.filter((key) => scope[key] !== void 0);
4491
+ if (scope.tags && Object.keys(scope.tags).length > 0) supplied.push("tags");
4492
+ const unsupported = supplied.filter((key) => !allowed.has(key));
4493
+ if (unsupported.length === 0) return;
4494
+ throw new Error(
4495
+ `${adapterId}: Neo4j ${operation} cannot enforce scope fields: ${unsupported.join(", ")}; create a dedicated client and pass branchId`
4496
+ );
4497
+ }
4498
+ function neo4jEntityOptions(input) {
4499
+ return {
4500
+ description: input.metadata?.description ?? input.title
4501
+ };
4502
+ }
4503
+ function neo4jPreferenceOptions(input) {
4504
+ return {
4505
+ context: input.metadata?.context ?? input.title
4506
+ };
4507
+ }
4508
+ async function callOptional(target, names, args) {
4509
+ for (const name of names) {
4510
+ const fn = target[name];
4511
+ if (typeof fn === "function") return await fn.apply(target, args);
4512
+ }
4513
+ return void 0;
4514
+ }
4515
+ async function callRequired(target, names, args) {
4516
+ for (const name of names) {
4517
+ const fn = target[name];
4518
+ if (typeof fn === "function") return await fn.apply(target, args);
4519
+ }
4520
+ throw new Error(`Neo4j agent-memory client is missing method ${names.join(" or ")}`);
4521
+ }
4522
+ function nested(target, names, fallback) {
4523
+ for (const name of names) {
4524
+ const value = target[name];
4525
+ if (value && typeof value === "object") return value;
4526
+ }
4527
+ return fallback;
4528
+ }
4529
+ function normalizeHits(value, options, adapterId) {
4530
+ const rawHits = rawHitsFrom(value);
4531
+ return rawHits.map((hit, index) => normalizeHit(hit, index, adapterId)).filter((hit) => hit !== null).filter(
4532
+ (hit) => options.minScore === void 0 ? true : (hit.normalizedScore ?? hit.score ?? 0) >= options.minScore
4533
+ ).filter((hit) => options.kinds?.length ? options.kinds.includes(hit.kind) : true).slice(0, options.limit ?? 10);
4534
+ }
4535
+ function normalizeHit(value, index, adapterId) {
4536
+ if (typeof value === "string") {
4537
+ return {
4538
+ id: stableId("mem", value),
4539
+ uri: `memory://${adapterId}/${stableId("mem", value)}`,
4540
+ kind: "fact",
4541
+ text: value,
4542
+ normalizedScore: index === 0 ? 1 : void 0
4543
+ };
4544
+ }
4545
+ const obj = record(value);
4546
+ if (!obj) return null;
4547
+ const metadata = record(obj.metadata) ?? {};
4548
+ const kind = memoryKind(
4549
+ stringField3(metadata, ["agentKnowledgeKind"]) ?? stringField3(obj, ["kind", "type", "label"]),
4550
+ obj
4551
+ );
4552
+ const text = textFromHitObject(obj, kind);
4553
+ if (!text) return null;
4554
+ const id = stringField3(obj, ["id", "memoryId", "uuid"]) ?? stableId("mem", text);
4555
+ return {
4556
+ id,
4557
+ uri: stringField3(obj, ["uri"]) ?? `memory://${adapterId}/${encodeURIComponent(id)}`,
4558
+ kind,
4559
+ text,
4560
+ title: stringField3(obj, ["title", "name", "task", "subject"]),
4561
+ score: numberField3(obj, ["score"]),
4562
+ normalizedScore: numberField3(obj, ["normalizedScore", "normalized_score"]),
4563
+ confidence: numberField3(obj, ["confidence"]),
4564
+ createdAt: stringField3(obj, ["createdAt", "created_at"]),
4565
+ validUntil: stringField3(obj, ["validUntil", "valid_until"]),
4566
+ lastVerifiedAt: stringField3(obj, ["lastVerifiedAt", "last_verified_at"]),
4567
+ metadata: obj
4568
+ };
4569
+ }
4570
+ function memoryKind(value, obj = {}) {
4571
+ if (value === "message" || value === "entity" || value === "fact" || value === "preference" || value === "observation" || value === "reasoning-trace") {
4572
+ return value;
4573
+ }
4574
+ if ("role" in obj && "content" in obj) return "message";
4575
+ if ("name" in obj && ("type" in obj || "entityType" in obj)) return "entity";
4576
+ if ("preference" in obj && "category" in obj) return "preference";
4577
+ if ("task" in obj && "steps" in obj) return "reasoning-trace";
4578
+ return "fact";
4579
+ }
4580
+ function rawHitsFrom(value) {
4581
+ if (Array.isArray(value)) return value;
4582
+ const obj = record(value);
4583
+ if (!obj) return typeof value === "string" ? [value] : [];
4584
+ if (Array.isArray(obj.hits)) return obj.hits;
4585
+ if (Array.isArray(obj.results)) return obj.results;
4586
+ if (Array.isArray(obj.messages)) return obj.messages;
4587
+ if (Array.isArray(obj.recentMessages)) return obj.recentMessages;
4588
+ if (Array.isArray(obj.observations)) return obj.observations;
4589
+ if (Array.isArray(obj.reflections)) return obj.reflections;
4590
+ if (typeof obj.content === "string" || typeof obj.text === "string") return [obj];
4591
+ return [];
4592
+ }
4593
+ function textFromHitObject(obj, kind) {
4594
+ const direct = stringField3(obj, [
4595
+ "text",
4596
+ "content",
4597
+ "body",
4598
+ "summary",
4599
+ "preference",
4600
+ "description"
4601
+ ]);
4602
+ if (direct) return direct;
4603
+ if (kind === "entity") return stringField3(obj, ["name"]);
4604
+ if (kind === "fact") {
4605
+ const subject = stringField3(obj, ["subject"]);
4606
+ const predicate = stringField3(obj, ["predicate"]);
4607
+ const object = stringField3(obj, ["object", "obj"]);
4608
+ if (subject && predicate && object) return `${subject} ${predicate} ${object}`;
4609
+ }
4610
+ if (kind === "reasoning-trace") {
4611
+ const task = stringField3(obj, ["task"]);
4612
+ const outcome = stringField3(obj, ["outcome"]);
4613
+ return [task, outcome].filter(Boolean).join("\n") || void 0;
4614
+ }
4615
+ return void 0;
4616
+ }
4617
+ function normalizeConversationContextHits(value, options, adapterId) {
4618
+ const obj = record(value);
4619
+ if (!obj) return normalizeHits(value, options, adapterId);
4620
+ const raw = [
4621
+ ...rawHitsFrom(obj.reflections).map((hit) => tagContextHit(hit, "observation", "Reflection")),
4622
+ ...rawHitsFrom(obj.observations).map((hit) => tagContextHit(hit, "observation", "Observation")),
4623
+ ...rawHitsFrom(obj.recentMessages).map((hit) => tagContextHit(hit, "message"))
4624
+ ];
4625
+ return raw.length > 0 ? normalizeHits(raw, options, adapterId) : normalizeHits(value, options, adapterId);
4626
+ }
4627
+ function tagContextHit(value, kind, title) {
4628
+ return value && typeof value === "object" && !Array.isArray(value) ? { kind, title, ...value } : { kind, title, content: value };
4629
+ }
4630
+ function renderHits(hits) {
4631
+ return hits.map((hit) => hit.text).join("\n\n");
4632
+ }
4633
+ function idFromResult(value) {
4634
+ const obj = record(value);
4635
+ return obj ? stringField3(obj, ["id", "memoryId", "uuid"]) : void 0;
4636
+ }
4637
+ function record(value) {
4638
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
4639
+ }
4640
+ function stringField3(obj, names) {
4641
+ for (const name of names) {
4642
+ const value = obj[name];
4643
+ if (typeof value === "string" && value.length > 0) return value;
4644
+ }
4645
+ return void 0;
4646
+ }
4647
+ function numberField3(obj, names) {
4648
+ for (const name of names) {
4649
+ const value = obj[name];
4650
+ if (typeof value === "number" && Number.isFinite(value)) return value;
4651
+ }
4652
+ return void 0;
4653
+ }
4654
+ function neo4jMessageRole(role) {
4655
+ return role === "tool" ? "assistant" : role ?? "user";
4656
+ }
4657
+ function stringMetadata(metadata, key) {
4658
+ const value = metadata?.[key];
4659
+ return typeof value === "string" && value.length > 0 ? value : void 0;
4660
+ }
4661
+
4662
+ // src/memory/schemas.ts
4663
+ import { z } from "zod";
4664
+ var AgentMemoryKindSchema = z.enum([
4665
+ "message",
4666
+ "entity",
4667
+ "fact",
4668
+ "preference",
4669
+ "observation",
4670
+ "reasoning-trace"
4671
+ ]);
4672
+ var AgentMemoryScopeSchema = z.object({
4673
+ tenantId: z.string().optional(),
4674
+ userId: z.string().optional(),
4675
+ agentId: z.string().optional(),
4676
+ teamId: z.string().optional(),
4677
+ runId: z.string().optional(),
4678
+ sessionId: z.string().optional(),
4679
+ namespace: z.string().optional(),
4680
+ tags: z.record(z.string(), z.string()).optional()
4681
+ });
4682
+ var AgentMemoryHitSchema = z.object({
4683
+ id: z.string().min(1),
4684
+ uri: z.string().min(1),
4685
+ kind: AgentMemoryKindSchema,
4686
+ text: z.string().min(1),
4687
+ title: z.string().optional(),
4688
+ score: z.number().optional(),
4689
+ normalizedScore: z.number().optional(),
4690
+ confidence: z.number().optional(),
4691
+ createdAt: z.string().optional(),
4692
+ validUntil: z.string().optional(),
4693
+ lastVerifiedAt: z.string().optional(),
4694
+ metadata: z.record(z.string(), z.unknown()).optional()
4695
+ });
4696
+ var AgentMemoryWriteInputSchema = z.object({
4697
+ kind: AgentMemoryKindSchema,
4698
+ text: z.string().min(1),
4699
+ id: z.string().optional(),
4700
+ title: z.string().optional(),
4701
+ role: z.enum(["system", "user", "assistant", "tool"]).optional(),
4702
+ entityName: z.string().optional(),
4703
+ entityType: z.string().optional(),
4704
+ category: z.string().optional(),
4705
+ predicate: z.string().optional(),
4706
+ subject: z.string().optional(),
4707
+ object: z.string().optional(),
4708
+ confidence: z.number().min(0).max(1).optional(),
4709
+ scope: AgentMemoryScopeSchema.optional(),
4710
+ metadata: z.record(z.string(), z.unknown()).optional()
4711
+ });
4712
+
4713
+ export {
4714
+ deterministicRng,
4715
+ retrievalHoldoutConfigHash,
4716
+ applyRetrievalHoldout,
4717
+ emitRetrievalHoldoutBypass,
4718
+ resetRetrievalHoldoutRegistry,
4719
+ applySessionStickyRetrievalHoldout,
4720
+ toOffPolicyTrajectory,
4721
+ defaultGetMemoryContext,
4722
+ renderMemoryContext,
4723
+ createAgentMemoryBranch,
4724
+ forkAgentMemoryBranchSnapshot,
4725
+ buildAgentMemorySequencesFromBenchmarkCases,
4726
+ runAgentMemoryExperiment,
4727
+ buildAgentMemorySequenceScenarios,
4728
+ agentMemorySequenceJudge,
4729
+ createGraphitiMemoryAdapter,
4730
+ graphitiMemoryAdapterIdentity,
4731
+ runAgentMemoryImprovement,
4732
+ createMem0MemoryAdapter,
4733
+ mem0MemoryAdapterIdentity,
4734
+ createNeo4jAgentMemoryAdapter,
4735
+ AgentMemoryKindSchema,
4736
+ AgentMemoryScopeSchema,
4737
+ AgentMemoryHitSchema,
4738
+ AgentMemoryWriteInputSchema
4739
+ };
4740
+ //# sourceMappingURL=chunk-UWOTQNBI.js.map