@tangle-network/agent-knowledge 5.0.4 → 6.1.0

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