@tangle-network/agent-knowledge 19.1.2 → 20.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +32 -2
  2. package/README.md +17 -91
  3. package/dist/benchmarks/index.d.ts +2 -2
  4. package/dist/benchmarks/index.js +2 -2
  5. package/dist/benchmarks-jir3ZP-M.js +162 -0
  6. package/dist/benchmarks-jir3ZP-M.js.map +1 -0
  7. package/dist/cli.js +2 -3
  8. package/dist/cli.js.map +1 -1
  9. package/dist/index-BhvSH4E4.d.ts +512 -0
  10. package/dist/index-BhvSH4E4.d.ts.map +1 -0
  11. package/dist/index-OAxtm6is.d.ts +9 -0
  12. package/dist/index-OAxtm6is.d.ts.map +1 -0
  13. package/dist/index.d.ts +197 -1332
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +561 -3653
  16. package/dist/index.js.map +1 -1
  17. package/dist/{inspect-C6c4zuKp.js → inspect-CMigdsD5.js} +282 -24
  18. package/dist/inspect-CMigdsD5.js.map +1 -0
  19. package/dist/memory/index.d.ts +3 -3
  20. package/dist/memory/index.js +3 -3
  21. package/dist/memory-BH6oT1Qe.js +2728 -0
  22. package/dist/memory-BH6oT1Qe.js.map +1 -0
  23. package/dist/source-record-CdWvzvO6.js +39 -0
  24. package/dist/source-record-CdWvzvO6.js.map +1 -0
  25. package/dist/types-BWV_Fvp7.d.ts +224 -0
  26. package/dist/types-BWV_Fvp7.d.ts.map +1 -0
  27. package/dist/{types-m2QB86fF.d.ts → types-BekdNwGD.d.ts} +4 -19
  28. package/dist/types-BekdNwGD.d.ts.map +1 -0
  29. package/dist/viz/index.d.ts +1 -1
  30. package/docs/architecture.md +2 -30
  31. package/docs/eval/investment-material-facts.md +2 -0
  32. package/docs/results/research-driving.md +8 -8
  33. package/docs/verified-research-ab.md +12 -10
  34. package/package.json +6 -12
  35. package/skills/build-with-agent-knowledge/SKILL.md +2 -15
  36. package/dist/benchmarks-BjXxjAke.js +0 -2824
  37. package/dist/benchmarks-BjXxjAke.js.map +0 -1
  38. package/dist/index-Bh9wLd0_.d.ts +0 -1097
  39. package/dist/index-Bh9wLd0_.d.ts.map +0 -1
  40. package/dist/index-DZeFm-BP.d.ts +0 -680
  41. package/dist/index-DZeFm-BP.d.ts.map +0 -1
  42. package/dist/inspect-C6c4zuKp.js.map +0 -1
  43. package/dist/memory-CUk1mgPY.js +0 -5778
  44. package/dist/memory-CUk1mgPY.js.map +0 -1
  45. package/dist/search-BBr5eUyT.js +0 -275
  46. package/dist/search-BBr5eUyT.js.map +0 -1
  47. package/dist/sources/index.d.ts +0 -376
  48. package/dist/sources/index.d.ts.map +0 -1
  49. package/dist/sources/index.js +0 -615
  50. package/dist/sources/index.js.map +0 -1
  51. package/dist/types-m2QB86fF.d.ts.map +0 -1
  52. package/docs/eval/rag-eval-roadmap.md +0 -123
@@ -0,0 +1,2728 @@
1
+ import { r as stableId, t as sha256 } from "./ids-Bevz_pXV.js";
2
+ import { n as memoryWriteResultToSourceRecord, t as memoryHitToSourceRecord } from "./source-record-CdWvzvO6.js";
3
+ import { canonicalJson, mulberry32 } from "@tangle-network/agent-eval";
4
+ import { z } from "zod";
5
+ import { randomUUID } from "node:crypto";
6
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
7
+ //#region src/memory/holdout.ts
8
+ /**
9
+ * Deterministic uniform [0,1) for assignment draws. The sha256 key derivation is ours — it makes
10
+ * every draw replayable from the logged keys alone (design rule D5) — while the generator core is
11
+ * the substrate's `mulberry32` (@tangle-network/agent-eval statistics vocabulary), seeded with the
12
+ * top 32 bits of the digest, so the statistical machinery is reused rather than forked.
13
+ */
14
+ function deterministicRng(key) {
15
+ return mulberry32(Number.parseInt(sha256(key).slice(0, 8), 16))();
16
+ }
17
+ function canonicalJson$1(value) {
18
+ if (typeof value === "bigint") throw new TypeError("canonicalJson: bigint values are not supported in holdout hashing");
19
+ if (value === void 0) return "null";
20
+ if (Array.isArray(value)) return `[${value.map(canonicalJson$1).join(",")}]`;
21
+ 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(",")}}`;
22
+ return JSON.stringify(value);
23
+ }
24
+ /**
25
+ * Value-hash of the knobs that DEFINE the experiment (epsilon + watchlist, order-independent).
26
+ * Everything else on the config (callbacks, attribution stamps, privacy flags) does not change
27
+ * which assignments are drawn, so it stays out of the hash.
28
+ */
29
+ function retrievalHoldoutConfigHash(config) {
30
+ return sha256(canonicalJson$1({
31
+ epsilon: config.epsilon,
32
+ watchlist: [...config.watchlist ?? []].sort()
33
+ })).slice(0, 16);
34
+ }
35
+ function assertValidHoldoutConfig(config) {
36
+ const { epsilon, watchlist } = config;
37
+ 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)}`);
38
+ 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");
39
+ }
40
+ function safeEmit(config, event) {
41
+ try {
42
+ config.onEvent(event);
43
+ } catch (error) {
44
+ console.error("[agent-knowledge] retrieval holdout onEvent sink threw; retrieval continues", error);
45
+ }
46
+ }
47
+ /**
48
+ * Pure per-call holdout: takes post-filter hits, returns delivered hits plus the log event
49
+ * and the next session state. Suppression only removes items (no backfill), and it happens
50
+ * before rendering so a drop session's context is byte-identical to a natural smaller retrieval.
51
+ */
52
+ function applyRetrievalHoldout(hits, config, ctx = {}) {
53
+ assertValidHoldoutConfig(config);
54
+ const rng = config.rng ?? deterministicRng;
55
+ const sessionId = ctx.sessionId ?? ctx.scope?.sessionId;
56
+ const holdoutEligible = typeof sessionId === "string" && sessionId.length > 0;
57
+ const base = holdoutEventBase(hits, config, ctx);
58
+ let session;
59
+ if (holdoutEligible) {
60
+ session = (ctx.session?.sessionId === sessionId ? ctx.session : void 0) ?? {
61
+ sessionId,
62
+ callCount: 0,
63
+ sessionHoldout: rng(`${sessionId}#holdout`) < config.epsilon,
64
+ targetId: null,
65
+ pickPropensity: null
66
+ };
67
+ session = {
68
+ ...session,
69
+ callCount: session.callCount + 1
70
+ };
71
+ }
72
+ const watchlistEligible = base.watchlistEligible;
73
+ if (session?.sessionHoldout && session.targetId === null && watchlistEligible.length > 0) {
74
+ const candidates = [...watchlistEligible].sort();
75
+ const picked = candidates[Math.min(Math.floor(rng(`${session.sessionId}#pick`) * candidates.length), candidates.length - 1)];
76
+ if (picked !== void 0) session = {
77
+ ...session,
78
+ targetId: picked,
79
+ pickPropensity: 1 / candidates.length
80
+ };
81
+ }
82
+ const targetId = session?.sessionHoldout ? session.targetId ?? null : null;
83
+ const droppedId = targetId !== null && hits.some((hit) => hit.id === targetId) ? targetId : null;
84
+ const delivered = droppedId === null ? hits : hits.filter((hit) => hit.id !== droppedId);
85
+ const pickPropensity = session?.sessionHoldout ? session.pickPropensity ?? null : null;
86
+ const event = {
87
+ ...base,
88
+ callIndex: session?.callCount ?? 0,
89
+ holdoutEligible,
90
+ sessionHoldout: session?.sessionHoldout ?? false,
91
+ sessionTargetId: targetId,
92
+ droppedId,
93
+ pickPropensity,
94
+ dropPropensity: pickPropensity !== null ? config.epsilon * pickPropensity : null,
95
+ deliveredIds: delivered.map((hit) => hit.id)
96
+ };
97
+ safeEmit(config, event);
98
+ return droppedId === null ? {
99
+ delivered: hits,
100
+ event,
101
+ ...session !== void 0 ? { session } : {}
102
+ } : {
103
+ delivered,
104
+ event,
105
+ ...session !== void 0 ? { session } : {}
106
+ };
107
+ }
108
+ /** Fields shared by randomized and bypass events: identity, attribution, config echo, eligibility. */
109
+ function holdoutEventBase(hits, config, ctx) {
110
+ const sessionId = ctx.sessionId ?? ctx.scope?.sessionId;
111
+ const watchlist = config.watchlist ?? [];
112
+ const watchlistSet = new Set(watchlist);
113
+ const plaintext = config.includePlaintextIdentifiers === true;
114
+ return {
115
+ v: 1,
116
+ eventId: randomUUID(),
117
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
118
+ ...config.adapterId !== void 0 ? { adapterId: config.adapterId } : {},
119
+ ...plaintext && sessionId !== void 0 ? { sessionId } : {},
120
+ ...ctx.taskId !== void 0 ? { taskId: ctx.taskId } : {},
121
+ ...sessionId !== void 0 ? { sessionIdHash: sha256(sessionId).slice(0, 16) } : {},
122
+ ...ctx.query !== void 0 ? { queryHash: sha256(ctx.query).slice(0, 16) } : {},
123
+ ...plaintext && ctx.scope !== void 0 ? { scope: ctx.scope } : {},
124
+ ...ctx.scope !== void 0 ? { scopeHash: sha256(canonicalJson$1(ctx.scope)).slice(0, 16) } : {},
125
+ config: {
126
+ epsilon: config.epsilon,
127
+ watchlist: [...watchlist],
128
+ ...config.configVersion !== void 0 ? { configVersion: config.configVersion } : {}
129
+ },
130
+ configHash: retrievalHoldoutConfigHash(config),
131
+ eligible: hits.map((hit, index) => ({
132
+ id: hit.id,
133
+ rank: index + 1,
134
+ ...typeof (hit.normalizedScore ?? hit.score) === "number" ? { score: hit.normalizedScore ?? hit.score } : {},
135
+ kind: hit.kind,
136
+ contentHash: sha256(hit.text).slice(0, 16)
137
+ })),
138
+ watchlistEligible: hits.filter((hit) => watchlistSet.has(hit.id)).map((hit) => hit.id),
139
+ ...config.corpusVersion !== void 0 ? { corpusVersion: config.corpusVersion } : {}
140
+ };
141
+ }
142
+ /**
143
+ * Logs a holdout event for adapter context paths that answer WITHOUT going through the
144
+ * search→render seam (Neo4j short-term conversation context, raw-string getContext results),
145
+ * so a consumer with a holdout configured still sees every call and the fraction-under-experiment
146
+ * denominator stays honest instead of silently losing these calls. No suppression is applied:
147
+ * dropping is only meaningful for retrieved memory hits, never for conversation context.
148
+ */
149
+ function emitRetrievalHoldoutBypass(hits, config, ctx, bypassReason) {
150
+ assertValidHoldoutConfig(config);
151
+ const event = {
152
+ ...holdoutEventBase(hits, config, ctx),
153
+ callIndex: 0,
154
+ holdoutEligible: false,
155
+ sessionHoldout: false,
156
+ sessionTargetId: null,
157
+ droppedId: null,
158
+ pickPropensity: null,
159
+ dropPropensity: null,
160
+ deliveredIds: hits.map((hit) => hit.id),
161
+ bypassReason
162
+ };
163
+ safeEmit(config, event);
164
+ return event;
165
+ }
166
+ const DEFAULT_MAX_TRACKED_SESSIONS = 1e4;
167
+ const sessionRegistry = /* @__PURE__ */ new Map();
168
+ /**
169
+ * Clears all sticky-session state. For test isolation and experiment-epoch boundaries;
170
+ * sessions in flight afterwards re-draw deterministically (same rng keys), so with the default
171
+ * rng a reset is invisible in the logs unless eligibility sets changed in between.
172
+ */
173
+ function resetRetrievalHoldoutRegistry() {
174
+ sessionRegistry.clear();
175
+ }
176
+ /**
177
+ * Convenience wrapper that threads session state internally, keyed by the VALUE of the
178
+ * experiment-defining knobs (configHash = epsilon + sorted watchlist) plus sessionId, so a fresh
179
+ * config object per call — the natural pattern when options are built inline — keeps full
180
+ * stickiness. Distinct experiments never share session state; two config objects with the same
181
+ * epsilon/watchlist are the same experiment by definition.
182
+ */
183
+ function applySessionStickyRetrievalHoldout(hits, config, ctx = {}) {
184
+ assertValidHoldoutConfig(config);
185
+ const sessionId = ctx.sessionId ?? ctx.scope?.sessionId;
186
+ const configHash = retrievalHoldoutConfigHash(config);
187
+ let sessions = sessionRegistry.get(configHash);
188
+ if (!sessions) {
189
+ sessions = /* @__PURE__ */ new Map();
190
+ sessionRegistry.set(configHash, sessions);
191
+ }
192
+ const prior = sessionId !== void 0 ? sessions.get(sessionId) : void 0;
193
+ const result = applyRetrievalHoldout(hits, config, {
194
+ ...ctx,
195
+ ...prior ? { session: prior } : {}
196
+ });
197
+ if (sessionId !== void 0 && result.session) {
198
+ const cap = config.maxTrackedSessions ?? DEFAULT_MAX_TRACKED_SESSIONS;
199
+ if (!sessions.has(sessionId) && sessions.size >= cap) {
200
+ const oldest = sessions.keys().next().value;
201
+ if (oldest !== void 0) sessions.delete(oldest);
202
+ }
203
+ sessions.set(sessionId, result.session);
204
+ }
205
+ return result;
206
+ }
207
+ /**
208
+ * Maps retrieval-dropout events onto one `OffPolicyTrajectory` per session for use with
209
+ * `inverseProbabilityWeighting`, `selfNormalizedImportanceWeighting`, or `doublyRobust`.
210
+ * Randomization occurs once per session: the arm is selected with P(dropout) = epsilon and
211
+ * the sticky target is drawn once,
212
+ * uniformly over watchlist ∩ E at the session's first intersecting call.
213
+ *
214
+ * Session-level action space and behavior probabilities (they sum to 1 by construction):
215
+ * - full delivery: `1 - epsilon`;
216
+ * - drop candidate i of k = |watchlist ∩ E_first|: `epsilon / k` each (the draw event's logged
217
+ * `dropPropensity`);
218
+ * - sessions whose eligibility sets never intersect the watchlist: probability 1, because full
219
+ * delivery was certain in either arm.
220
+ *
221
+ * Per-call events are repeated observations WITHIN one session-level randomization; per-call
222
+ * IPW is statistically invalid for this design (per-call "propensities" imply an action
223
+ * distribution summing to more than 1 and bias IPS downward). Calls where the sticky target is
224
+ * absent from E, and adapter bypass calls, fold into the session summary (callCount /
225
+ * bypassCallCount) instead of generating independent propensities.
226
+ *
227
+ * Join contract: `rewards` is keyed by `sessionIdHash` (sha256(sessionId) first 16 hex; compute
228
+ * the same prefix over the outcome table's session ids, or log with
229
+ * `includePlaintextIdentifiers: true` and join on raw ids upstream). Events are grouped per
230
+ * (configHash, sessionIdHash) so different experiment configs are never pooled. Mixed-exposure
231
+ * sessions (arm or target disagreement, e.g. after registry eviction) are excluded from the
232
+ * trajectories and surfaced in `excluded` for analysis.
233
+ */
234
+ function toOffPolicyTrajectory(events, options) {
235
+ if (options.qHatChosen === void 0 !== (options.vHatTarget === void 0)) throw new Error("qHatChosen and vHatTarget must be supplied together");
236
+ const groups = /* @__PURE__ */ new Map();
237
+ let unattributableEvents = 0;
238
+ for (const event of events) {
239
+ if (event.sessionIdHash === void 0) {
240
+ unattributableEvents += 1;
241
+ continue;
242
+ }
243
+ const key = `${event.configHash}:${event.sessionIdHash}`;
244
+ const group = groups.get(key);
245
+ if (group) group.push(event);
246
+ else groups.set(key, [event]);
247
+ }
248
+ const trajectories = [];
249
+ const sessions = [];
250
+ const excluded = [];
251
+ for (const [runId, group] of groups) {
252
+ const ordered = [...group].sort((a, b) => a.callIndex - b.callIndex || a.ts.localeCompare(b.ts));
253
+ const randomized = ordered.filter((event) => event.holdoutEligible);
254
+ const firstEvent = ordered[0];
255
+ if (firstEvent?.sessionIdHash === void 0) continue;
256
+ const base = {
257
+ runId,
258
+ configHash: firstEvent.configHash,
259
+ sessionIdHash: firstEvent.sessionIdHash,
260
+ callCount: randomized.length,
261
+ bypassCallCount: ordered.length - randomized.length
262
+ };
263
+ const first = randomized[0];
264
+ if (first === void 0) {
265
+ excluded.push({
266
+ ...base,
267
+ sessionHoldout: false,
268
+ sessionTargetId: null,
269
+ droppedId: null,
270
+ firstCandidateCount: 0,
271
+ behaviorProb: 1,
272
+ mixedExposure: false,
273
+ exclusionReason: "no-randomized-calls"
274
+ });
275
+ continue;
276
+ }
277
+ const targets = new Set(randomized.map((event) => event.sessionTargetId).filter((target) => target !== null));
278
+ const arms = new Set(randomized.map((event) => event.sessionHoldout));
279
+ const mixedExposure = targets.size > 1 || arms.size > 1;
280
+ const dropEvent = randomized.find((event) => event.droppedId !== null);
281
+ const firstIntersecting = randomized.find((event) => event.watchlistEligible.length > 0);
282
+ const sessionTargetId = dropEvent?.sessionTargetId ?? [...targets][0] ?? null;
283
+ let behaviorProb;
284
+ if (dropEvent !== void 0) {
285
+ if (dropEvent.dropPropensity === null) throw new Error(`holdout event ${dropEvent.eventId} recorded a drop without dropPropensity`);
286
+ behaviorProb = dropEvent.dropPropensity;
287
+ } 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`);
288
+ else if (firstIntersecting === void 0) behaviorProb = 1;
289
+ else behaviorProb = 1 - first.config.epsilon;
290
+ const summary = {
291
+ ...base,
292
+ sessionHoldout: first.sessionHoldout,
293
+ sessionTargetId,
294
+ droppedId: dropEvent?.droppedId ?? null,
295
+ firstCandidateCount: firstIntersecting ? new Set(firstIntersecting.watchlistEligible).size : 0,
296
+ behaviorProb,
297
+ mixedExposure,
298
+ ...mixedExposure ? { exclusionReason: "mixed-exposure" } : {}
299
+ };
300
+ if (mixedExposure) {
301
+ excluded.push(summary);
302
+ continue;
303
+ }
304
+ const reward = options.rewards[summary.sessionIdHash];
305
+ if (reward === void 0) throw new Error(`no reward for session ${summary.sessionIdHash}: filter events to scored sessions before converting`);
306
+ const qHatChosen = options.qHatChosen?.(summary);
307
+ const vHatTarget = options.vHatTarget?.(summary);
308
+ trajectories.push({
309
+ runId,
310
+ reward,
311
+ behaviorProb,
312
+ targetProb: options.targetProb?.(summary) ?? (summary.droppedId === null ? 1 : 0),
313
+ ...options.qHatChosen === void 0 ? {} : { qHatChosen: qHatChosen ?? null },
314
+ ...options.vHatTarget === void 0 ? {} : { vHatTarget: vHatTarget ?? null }
315
+ });
316
+ sessions.push(summary);
317
+ }
318
+ return {
319
+ trajectories,
320
+ sessions,
321
+ excluded,
322
+ unattributableEvents
323
+ };
324
+ }
325
+ //#endregion
326
+ //#region src/memory/adapter.ts
327
+ async function defaultGetMemoryContext(adapter, query, options = {}) {
328
+ const hits = await adapter.search(query, options);
329
+ const delivered = options.holdout ? applySessionStickyRetrievalHoldout(hits, options.holdout, {
330
+ query,
331
+ ...options.scope !== void 0 ? { scope: options.scope } : {},
332
+ ...options.scope?.tags?.taskId !== void 0 ? { taskId: options.scope.tags.taskId } : {}
333
+ }).delivered : hits;
334
+ return {
335
+ query,
336
+ hits: delivered,
337
+ sourceRecords: delivered.map((hit) => memoryHitToSourceRecord(hit, { scope: options.scope })),
338
+ text: renderMemoryContext(delivered)
339
+ };
340
+ }
341
+ function renderMemoryContext(hits) {
342
+ return hits.map((hit, index) => {
343
+ const label = hit.title ?? `${hit.kind}:${hit.id}`;
344
+ const score = typeof hit.normalizedScore === "number" ? ` score=${hit.normalizedScore.toFixed(3)}` : "";
345
+ return [`[${index + 1}] ${label}${score}`, hit.text].join("\n");
346
+ }).join("\n\n");
347
+ }
348
+ //#endregion
349
+ //#region src/memory/rank.ts
350
+ /** Merges independently ranked memory lists without letting the first unscored list dominate. */
351
+ function mergeRankedMemoryHits(groups, limit) {
352
+ const byIdentity = /* @__PURE__ */ new Map();
353
+ let encounter = 0;
354
+ for (const [groupIndex, group] of groups.entries()) for (const [rank, hit] of group.entries()) {
355
+ const key = JSON.stringify([hit.uri, hit.text]);
356
+ const score = memoryHitScore(hit);
357
+ const prior = byIdentity.get(key);
358
+ if (!prior) byIdentity.set(key, {
359
+ hit,
360
+ score,
361
+ reciprocalRank: 1 / (60 + rank + 1),
362
+ bestRank: rank,
363
+ firstGroup: groupIndex,
364
+ encounter
365
+ });
366
+ else {
367
+ prior.reciprocalRank += 1 / (60 + rank + 1);
368
+ prior.bestRank = Math.min(prior.bestRank, rank);
369
+ if (score !== void 0 && (prior.score === void 0 || score > prior.score)) {
370
+ prior.hit = hit;
371
+ prior.score = score;
372
+ }
373
+ }
374
+ encounter += 1;
375
+ }
376
+ 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);
377
+ }
378
+ function memoryHitScore(hit) {
379
+ const score = hit.normalizedScore ?? hit.score;
380
+ return score !== void 0 && Number.isFinite(score) ? score : void 0;
381
+ }
382
+ //#endregion
383
+ //#region src/memory/branch.ts
384
+ const DEFAULT_POLICY = {
385
+ read: ["private"],
386
+ write: "private"
387
+ };
388
+ const adapterLeases = /* @__PURE__ */ new WeakMap();
389
+ /**
390
+ * Isolates one candidate branch and records accepted writes for durable resume
391
+ * or replay into a child branch, including a child backed by another provider.
392
+ */
393
+ function createAgentMemoryBranch(options) {
394
+ if (typeof options.branchId !== "string" || !options.branchId.trim()) throw new Error("memory branchId must be a non-empty string");
395
+ const snapshot = options.snapshot;
396
+ if (snapshot && snapshot.branchId !== options.branchId) throw new Error(`memory branch snapshot belongs to ${snapshot.branchId}, not ${options.branchId}`);
397
+ 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`);
398
+ if (snapshot?.lifetime === "attempt") throw new Error("memory branch attempt snapshots cannot be resumed; replay them into a fresh branch instead");
399
+ if (options.lifetime !== void 0 && !["resumable", "attempt"].includes(options.lifetime)) throw new Error("memory branch lifetime must be resumable or attempt");
400
+ if (snapshot) {
401
+ assertDurableValue(snapshot, `${options.branchId}: branch snapshot`);
402
+ assertSnapshotDigest(snapshot);
403
+ }
404
+ 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");
405
+ 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");
406
+ if (snapshot && options.lifetime && options.lifetime !== snapshot.lifetime) throw new Error("memory branch snapshot lifetime cannot change during resume; fork it instead");
407
+ const branchId = options.branchId;
408
+ const parentBranchId = options.parentBranchId ?? snapshot?.parentBranchId;
409
+ const lifetime = options.lifetime ?? snapshot?.lifetime ?? "resumable";
410
+ const policy = normalizePolicy(options.policy ?? snapshot?.policy ?? DEFAULT_POLICY);
411
+ assertBranchIsolation(options.adapter, branchId, policy, lifetime);
412
+ const mergedBaseScope = mergeScopes$2(void 0, snapshot?.baseScope ?? options.baseScope);
413
+ assertDurableValue(mergedBaseScope, `${branchId}: base scope`);
414
+ const baseScope = cloneDurableValue(mergedBaseScope);
415
+ const allowedWriteScopeKeys = options.allowedWriteScopes ? new Set(options.allowedWriteScopes.map((scope) => canonicalJson(stripUndefined$1(mergeScopes$2(baseScope, scope))))) : void 0;
416
+ const journal = [...snapshot?.journal ?? []].map(cloneJournalEntry);
417
+ let nextSequence = journal.reduce((max, entry) => Math.max(max, entry.sequence + 1), 0);
418
+ const queues = /* @__PURE__ */ new Map();
419
+ const pending = /* @__PURE__ */ new Set();
420
+ const reads = /* @__PURE__ */ new Set();
421
+ const active = /* @__PURE__ */ new Set();
422
+ const touchedScopes = /* @__PURE__ */ new Map();
423
+ for (const entry of journal) {
424
+ const scope = mergeScopes$2(baseScope, entry.input.scope);
425
+ touchedScopes.set(canonicalJson(stripUndefined$1(scope)), scope);
426
+ }
427
+ let mutationBarrier = Promise.resolve();
428
+ const releaseAdapter = retainAdapter(options.adapter, branchId);
429
+ let lifecycle = "open";
430
+ let closePromise;
431
+ const assertOpen = (operation) => {
432
+ if (lifecycle !== "open") throw new Error(`${branchId}: cannot ${operation} a closed branch`);
433
+ };
434
+ const track = (operation) => {
435
+ active.add(operation);
436
+ operation.then(() => active.delete(operation), () => active.delete(operation));
437
+ return operation;
438
+ };
439
+ const trackRead = (operation) => {
440
+ reads.add(operation);
441
+ operation.then(() => reads.delete(operation), () => reads.delete(operation));
442
+ return track(operation);
443
+ };
444
+ const flushWrites = async () => {
445
+ const failures = (await Promise.allSettled([...pending])).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
446
+ if (failures.length === 1) throw failures[0];
447
+ if (failures.length > 1) throw new AggregateError(failures, `${branchId}: pending memory writes failed`);
448
+ };
449
+ const flushAll = async () => {
450
+ await flushWrites();
451
+ await options.adapter.flush?.();
452
+ };
453
+ const runExclusive = async (operation, exclusiveOptions = {}) => {
454
+ const precedingBarrier = mutationBarrier;
455
+ const precedingWrites = [...pending];
456
+ const precedingReads = [...reads];
457
+ let releaseBarrier;
458
+ const ownBarrier = new Promise((resolve) => {
459
+ releaseBarrier = resolve;
460
+ });
461
+ mutationBarrier = precedingBarrier.then(() => ownBarrier);
462
+ await precedingBarrier;
463
+ const failures = (await Promise.allSettled([...precedingWrites, ...precedingReads])).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
464
+ try {
465
+ if ((exclusiveOptions.failOnOperationError ?? true) && failures.length > 0) {
466
+ if (failures.length === 1) throw failures[0];
467
+ throw new AggregateError(failures, `${branchId}: pending memory operations failed`);
468
+ }
469
+ if (exclusiveOptions.flushAdapter ?? true) await options.adapter.flush?.();
470
+ return await operation();
471
+ } finally {
472
+ releaseBarrier();
473
+ }
474
+ };
475
+ const branch = {
476
+ id: `${options.adapter.id}:branch:${stableId("branch", branchId)}`,
477
+ branchId,
478
+ lifetime,
479
+ ...parentBranchId !== void 0 ? { parentBranchId } : {},
480
+ policy,
481
+ async search(query, searchOptions = {}) {
482
+ assertOpen("search");
483
+ const precedingBarrier = mutationBarrier;
484
+ return trackRead((async () => {
485
+ await precedingBarrier;
486
+ const logicalScope = mergeScopes$2(baseScope, searchOptions.scope);
487
+ const scopes = policy.read.map((visibility) => physicalScope(branchId, logicalScope, visibility));
488
+ return mergeRankedMemoryHits(await Promise.all(scopes.map(async (scope) => {
489
+ return (await options.adapter.search(query, {
490
+ ...searchOptions,
491
+ scope,
492
+ holdout: void 0
493
+ })).map((hit) => ({
494
+ ...hit,
495
+ metadata: {
496
+ ...hit.metadata,
497
+ memoryBranchId: branchId,
498
+ memoryVisibility: scope.tags?.memoryVisibility
499
+ }
500
+ }));
501
+ })), searchOptions.limit);
502
+ })());
503
+ },
504
+ async getContext(query, searchOptions = {}) {
505
+ assertOpen("read context from");
506
+ return track(defaultGetMemoryContext(branch, query, searchOptions));
507
+ },
508
+ async write(input) {
509
+ assertOpen("write to");
510
+ const sequence = nextSequence;
511
+ nextSequence += 1;
512
+ assertDurableValue(input, `${branch.id}: write input`);
513
+ const logicalInput = cloneWriteInput(input);
514
+ logicalInput.id ??= stableId("memory_write", `${branchId}:${sequence}`);
515
+ const logicalScope = mergeScopes$2(baseScope, logicalInput.scope);
516
+ if (allowedWriteScopeKeys && !allowedWriteScopeKeys.has(canonicalJson(stripUndefined$1(logicalScope)))) throw new Error(`${branch.id}: write scope was not declared in the experiment sequence cleanupScopes`);
517
+ const scope = physicalScope(branchId, logicalScope, policy.write);
518
+ const queueKey = writeQueueKey(branchId, logicalScope, policy.write);
519
+ touchedScopes.set(canonicalJson(stripUndefined$1(logicalScope)), logicalScope);
520
+ const previous = queues.get(queueKey) ?? Promise.resolve();
521
+ const precedingBarrier = mutationBarrier;
522
+ let resultPromise;
523
+ const queued = Promise.all([previous.catch(() => void 0), precedingBarrier]).then(async () => {
524
+ const result = await options.adapter.write({
525
+ ...logicalInput,
526
+ scope
527
+ });
528
+ if (result.accepted) {
529
+ assertDurableValue(result, `${branch.id}: write result`);
530
+ journal.push({
531
+ sequence,
532
+ input: logicalInput,
533
+ result: cloneWriteResult(result)
534
+ });
535
+ }
536
+ return result;
537
+ });
538
+ resultPromise = queued;
539
+ const tail = queued.then(() => void 0, () => void 0);
540
+ queues.set(queueKey, tail);
541
+ pending.add(resultPromise);
542
+ resultPromise.then(() => {
543
+ pending.delete(resultPromise);
544
+ if (queues.get(queueKey) === tail) queues.delete(queueKey);
545
+ }, () => {
546
+ pending.delete(resultPromise);
547
+ if (queues.get(queueKey) === tail) queues.delete(queueKey);
548
+ });
549
+ return track(resultPromise);
550
+ },
551
+ async clear(scope) {
552
+ assertOpen("clear");
553
+ return track(runExclusive(async () => {
554
+ if (!options.adapter.clear) throw new Error(`${options.adapter.id}: adapter does not support scoped clear`);
555
+ const flushErrors = [];
556
+ try {
557
+ await options.adapter.flush?.();
558
+ } catch (error) {
559
+ flushErrors.push(error);
560
+ }
561
+ const requestedScope = scope ? mergeScopes$2(baseScope, scope) : void 0;
562
+ const logicalScopes = /* @__PURE__ */ new Map();
563
+ if (requestedScope) {
564
+ logicalScopes.set(canonicalJson(stripUndefined$1(requestedScope)), requestedScope);
565
+ for (const touched of touchedScopes.values()) if (scopeMatches(touched, requestedScope)) logicalScopes.set(canonicalJson(stripUndefined$1(touched)), touched);
566
+ } else if (touchedScopes.size > 0) for (const [key, touched] of touchedScopes) logicalScopes.set(key, touched);
567
+ else logicalScopes.set(canonicalJson(stripUndefined$1(baseScope)), baseScope);
568
+ const visibilities = /* @__PURE__ */ new Set([...policy.read, policy.write]);
569
+ const physicalScopes = /* @__PURE__ */ new Map();
570
+ for (const logicalScope of logicalScopes.values()) for (const visibility of visibilities) {
571
+ const physical = physicalScope(branchId, logicalScope, visibility);
572
+ physicalScopes.set(canonicalJson(stripUndefined$1(physical)), physical);
573
+ }
574
+ const clearErrors = [];
575
+ const clearedPhysicalScopes = /* @__PURE__ */ new Set();
576
+ for (const [key, physical] of physicalScopes) try {
577
+ await options.adapter.clear(physical);
578
+ clearedPhysicalScopes.add(key);
579
+ } catch (error) {
580
+ clearErrors.push(error);
581
+ }
582
+ if (clearedPhysicalScopes.size > 0) {
583
+ for (let index = journal.length - 1; index >= 0; index -= 1) {
584
+ const logical = mergeScopes$2(baseScope, journal[index].input.scope);
585
+ const physical = physicalScope(branchId, logical, policy.write);
586
+ if (clearedPhysicalScopes.has(canonicalJson(stripUndefined$1(physical)))) journal.splice(index, 1);
587
+ }
588
+ for (const [key, touched] of touchedScopes) {
589
+ const physical = physicalScope(branchId, touched, policy.write);
590
+ if (clearedPhysicalScopes.has(canonicalJson(stripUndefined$1(physical)))) touchedScopes.delete(key);
591
+ }
592
+ }
593
+ const failures = [...flushErrors, ...clearErrors];
594
+ if (failures.length === 1) throw failures[0];
595
+ if (failures.length > 1) throw new AggregateError(failures, `${branchId}: memory clear failed`);
596
+ }, {
597
+ failOnOperationError: false,
598
+ flushAdapter: false
599
+ }));
600
+ },
601
+ async flush() {
602
+ assertOpen("flush");
603
+ return track(runExclusive(async () => void 0));
604
+ },
605
+ close() {
606
+ if (closePromise) return closePromise;
607
+ lifecycle = "closing";
608
+ closePromise = (async () => {
609
+ const errors = [];
610
+ const operations = await Promise.allSettled([...active]);
611
+ for (const result of operations) if (result.status === "rejected") errors.push(result.reason);
612
+ try {
613
+ await flushAll();
614
+ } catch (error) {
615
+ errors.push(error);
616
+ }
617
+ try {
618
+ await releaseAdapter();
619
+ } catch (error) {
620
+ errors.push(error);
621
+ }
622
+ lifecycle = "closed";
623
+ if (errors.length === 1) throw errors[0];
624
+ if (errors.length > 1) throw new AggregateError(errors, `${branch.id}: close failed`);
625
+ })();
626
+ return closePromise;
627
+ },
628
+ async snapshot() {
629
+ assertOpen("snapshot");
630
+ return track(runExclusive(async () => {
631
+ return createSnapshot({
632
+ branchId,
633
+ lifetime,
634
+ adapterId: options.adapter.id,
635
+ policy,
636
+ baseScope,
637
+ journal,
638
+ ...parentBranchId !== void 0 ? { parentBranchId } : {}
639
+ });
640
+ }));
641
+ },
642
+ async fork(forkOptions) {
643
+ assertOpen("fork");
644
+ if (forkOptions.branchId === branchId) throw new Error(`memory branch ${branchId}: child branchId must differ from its parent`);
645
+ return track((async () => forkAgentMemoryBranchSnapshot({
646
+ snapshot: await branch.snapshot(),
647
+ adapter: forkOptions.adapter ?? options.adapter,
648
+ branchId: forkOptions.branchId,
649
+ policy: forkOptions.policy,
650
+ baseScope: forkOptions.baseScope,
651
+ lifetime: forkOptions.lifetime
652
+ }))());
653
+ }
654
+ };
655
+ return branch;
656
+ }
657
+ /** Replays a durable journal into a fresh provider branch. */
658
+ async function forkAgentMemoryBranchSnapshot(options) {
659
+ assertDurableValue(options.snapshot, `${options.snapshot.branchId}: branch snapshot`);
660
+ assertSnapshotDigest(options.snapshot);
661
+ if (options.branchId === options.snapshot.branchId) throw new Error(`memory branch ${options.branchId}: child branchId must differ from its parent`);
662
+ const child = createAgentMemoryBranch({
663
+ adapter: options.adapter,
664
+ branchId: options.branchId,
665
+ parentBranchId: options.snapshot.branchId,
666
+ policy: options.policy ?? options.snapshot.policy,
667
+ baseScope: mergeScopes$2(options.snapshot.baseScope, options.baseScope),
668
+ lifetime: options.lifetime ?? options.snapshot.lifetime
669
+ });
670
+ try {
671
+ 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}`);
672
+ await child.flush?.();
673
+ return child;
674
+ } catch (error) {
675
+ const cleanupErrors = [];
676
+ try {
677
+ await child.clear?.();
678
+ } catch (cleanupError) {
679
+ cleanupErrors.push(cleanupError);
680
+ }
681
+ try {
682
+ await child.close?.();
683
+ } catch (cleanupError) {
684
+ cleanupErrors.push(cleanupError);
685
+ }
686
+ if (cleanupErrors.length > 0) throw new AggregateError([error, ...cleanupErrors], `memory branch ${options.branchId}: replay and cleanup failed`);
687
+ throw error;
688
+ }
689
+ }
690
+ function assertBranchIsolation(adapter, branchId, policy, lifetime) {
691
+ const isolation = adapter.branchIsolation;
692
+ if (!isolation) throw new Error(`${adapter.id}: cannot isolate memory branch '${branchId}': adapter must declare branchIsolation`);
693
+ if (isolation.mode === "unsupported") throw new Error(`${adapter.id}: cannot isolate memory branch '${branchId}': ${isolation.reason}`);
694
+ 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`);
695
+ 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`);
696
+ if (isolation.mode === "instance" && isolation.branchId !== branchId) throw new Error(`${adapter.id}: adapter instance belongs to branch '${isolation.branchId}', not '${branchId}'`);
697
+ 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`);
698
+ }
699
+ function retainAdapter(adapter, branchId) {
700
+ const existing = adapterLeases.get(adapter);
701
+ if (existing?.closed) throw new Error(`${adapter.id}: adapter is already closed`);
702
+ const state = existing ?? {
703
+ references: 0,
704
+ closed: false,
705
+ activeBranchIds: /* @__PURE__ */ new Set()
706
+ };
707
+ if (state.activeBranchIds.has(branchId)) throw new Error(`${adapter.id}: memory branch '${branchId}' already has an open handle`);
708
+ state.activeBranchIds.add(branchId);
709
+ state.references += 1;
710
+ adapterLeases.set(adapter, state);
711
+ let released = false;
712
+ return async () => {
713
+ if (released) return;
714
+ released = true;
715
+ state.activeBranchIds.delete(branchId);
716
+ state.references -= 1;
717
+ if (state.references > 0) return;
718
+ if (!adapter.close) {
719
+ adapterLeases.delete(adapter);
720
+ return;
721
+ }
722
+ state.closed = true;
723
+ state.closePromise ??= adapter.close();
724
+ await state.closePromise;
725
+ };
726
+ }
727
+ function physicalScope(branchId, logical, visibility) {
728
+ if (visibility === "private" && !logical.agentId) throw new Error("private memory requires scope.agentId");
729
+ if (visibility === "team" && !logical.teamId) throw new Error("team memory requires scope.teamId");
730
+ const principal = visibility === "private" ? logical.agentId : visibility === "team" ? logical.teamId : "all";
731
+ const namespace = [
732
+ logical.namespace ?? "default",
733
+ stableId("branch", branchId),
734
+ visibility,
735
+ stableId("principal", principal)
736
+ ].join("/");
737
+ const physical = {
738
+ ...logical,
739
+ namespace,
740
+ tags: {
741
+ ...logical.tags ?? {},
742
+ memoryBranchId: branchId,
743
+ memoryVisibility: visibility,
744
+ logicalNamespace: logical.namespace ?? "default"
745
+ }
746
+ };
747
+ if (visibility === "shared") {
748
+ delete physical.agentId;
749
+ delete physical.teamId;
750
+ } else if (visibility === "team") delete physical.agentId;
751
+ return physical;
752
+ }
753
+ function writeQueueKey(branchId, logical, visibility) {
754
+ return canonicalJson(stripUndefined$1({
755
+ branchId,
756
+ visibility,
757
+ tenantId: logical.tenantId,
758
+ userId: logical.userId,
759
+ agentId: logical.agentId,
760
+ fallbackTeamId: logical.agentId === void 0 ? logical.teamId : void 0,
761
+ fallbackPrincipal: logical.agentId === void 0 && logical.teamId === void 0 ? "unattributed" : void 0
762
+ }));
763
+ }
764
+ function normalizePolicy(policy) {
765
+ const allowed = /* @__PURE__ */ new Set([
766
+ "private",
767
+ "team",
768
+ "shared"
769
+ ]);
770
+ if (!allowed.has(policy.write) || policy.read.some((visibility) => !allowed.has(visibility))) throw new Error("memory sharing policy contains an unsupported visibility");
771
+ const read = [...new Set(policy.read)];
772
+ if (read.length === 0) throw new Error("memory sharing policy must read at least one visibility");
773
+ return {
774
+ read,
775
+ write: policy.write
776
+ };
777
+ }
778
+ function createSnapshot(input) {
779
+ const payload = cloneDurableValue({
780
+ ...input,
781
+ journal: [...input.journal].sort((a, b) => a.sequence - b.sequence)
782
+ });
783
+ assertDurableValue(payload, `${input.branchId}: branch snapshot`);
784
+ return {
785
+ ...payload,
786
+ digest: `sha256:${sha256(canonicalJson(stripUndefined$1(payload)))}`
787
+ };
788
+ }
789
+ function assertSnapshotDigest(snapshot) {
790
+ const { digest, ...payload } = snapshot;
791
+ const actual = `sha256:${sha256(canonicalJson(stripUndefined$1(payload)))}`;
792
+ if (actual !== digest) throw new Error(`memory branch snapshot digest mismatch: ${digest} != ${actual}`);
793
+ }
794
+ function mergeScopes$2(base, extra) {
795
+ return {
796
+ ...base ?? {},
797
+ ...extra ?? {},
798
+ tags: {
799
+ ...base?.tags ?? {},
800
+ ...extra?.tags ?? {}
801
+ }
802
+ };
803
+ }
804
+ function scopeMatches(actual, requested) {
805
+ for (const key of [
806
+ "tenantId",
807
+ "userId",
808
+ "agentId",
809
+ "teamId",
810
+ "runId",
811
+ "sessionId",
812
+ "namespace"
813
+ ]) if (requested[key] !== void 0 && actual[key] !== requested[key]) return false;
814
+ for (const [key, value] of Object.entries(requested.tags ?? {})) if (actual.tags?.[key] !== value) return false;
815
+ return true;
816
+ }
817
+ function stripUndefined$1(value) {
818
+ if (Array.isArray(value)) return value.map(stripUndefined$1);
819
+ if (typeof value !== "object" || value === null) return value;
820
+ return Object.fromEntries(Object.entries(value).filter(([, child]) => child !== void 0).map(([key, child]) => [key, stripUndefined$1(child)]));
821
+ }
822
+ function cloneWriteInput(input) {
823
+ return cloneDurableValue(input);
824
+ }
825
+ function cloneWriteResult(result) {
826
+ return cloneDurableValue(result);
827
+ }
828
+ function cloneJournalEntry(entry) {
829
+ return {
830
+ sequence: entry.sequence,
831
+ input: cloneWriteInput(entry.input),
832
+ result: cloneWriteResult(entry.result)
833
+ };
834
+ }
835
+ function assertDurableValue(value, label, seen = /* @__PURE__ */ new WeakSet()) {
836
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return;
837
+ if (value === void 0) return;
838
+ if (typeof value !== "object") throw new Error(`${label} must contain only JSON-compatible values`);
839
+ if (seen.has(value)) throw new Error(`${label} must not contain cycles`);
840
+ seen.add(value);
841
+ if (Array.isArray(value)) for (const child of value) {
842
+ if (child === void 0) throw new Error(`${label} arrays must not contain undefined values`);
843
+ assertDurableValue(child, label, seen);
844
+ }
845
+ else {
846
+ const prototype = Object.getPrototypeOf(value);
847
+ if (prototype !== Object.prototype && prototype !== null) throw new Error(`${label} must contain only plain objects`);
848
+ for (const child of Object.values(value)) assertDurableValue(child, label, seen);
849
+ }
850
+ seen.delete(value);
851
+ }
852
+ function cloneDurableValue(value) {
853
+ if (Array.isArray(value)) return value.map((child) => cloneDurableValue(child));
854
+ if (typeof value !== "object" || value === null) return value;
855
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, cloneDurableValue(child)]));
856
+ }
857
+ //#endregion
858
+ //#region src/memory/graphiti.ts
859
+ const DEFAULT_GRAPHITI_INGESTION_TIMEOUT_MS = 12e4;
860
+ /** Connects the official Graphiti MCP server without coupling to its Python internals. */
861
+ function createGraphitiMemoryAdapter(options) {
862
+ assertGraphitiOptions(options);
863
+ const id = options.id ?? "graphiti";
864
+ const consistency = options.consistency ?? "visible";
865
+ const tools = graphitiToolNames(options);
866
+ const queued = /* @__PURE__ */ new Map();
867
+ const adapter = {
868
+ id,
869
+ branchIsolation: consistency === "visible" ? {
870
+ mode: "scoped",
871
+ processExitSafe: false,
872
+ recoveryDelayMs: options.ingestionTimeoutMs ?? DEFAULT_GRAPHITI_INGESTION_TIMEOUT_MS
873
+ } : {
874
+ mode: "unsupported",
875
+ reason: "queued Graphiti writes can become visible after a process restart; use consistency=\"visible\" for branch experiments"
876
+ },
877
+ async search(query, searchOptions = {}) {
878
+ if (searchOptions.minScore !== void 0 && !Number.isFinite(searchOptions.minScore)) throw new Error(`${id}: minScore must be finite`);
879
+ const scope = mergeScopes$1(options.defaultScope, searchOptions.scope);
880
+ const groupId = graphitiGroupId(id, scope);
881
+ const modes = new Set(options.search ?? ["facts", "nodes"]);
882
+ const kinds = searchOptions.kinds && searchOptions.kinds.length > 0 ? new Set(searchOptions.kinds) : null;
883
+ const searchFacts = modes.has("facts") && (!kinds || kinds.has("fact"));
884
+ const searchNodes = modes.has("nodes") && (!kinds || kinds.has("entity"));
885
+ const limit = searchOptions.limit ?? 10;
886
+ if (!Number.isSafeInteger(limit) || limit < 0) throw new Error(`${id}: search limit must be a non-negative safe integer`);
887
+ if (limit === 0) return [];
888
+ const [factPayload, nodePayload] = await Promise.all([searchFacts ? callGraphiti(options.client, tools.searchFacts, {
889
+ query,
890
+ group_ids: [groupId],
891
+ max_facts: limit
892
+ }) : void 0, searchNodes ? callGraphiti(options.client, tools.searchNodes, {
893
+ query,
894
+ group_ids: [groupId],
895
+ max_nodes: limit,
896
+ ...searchOptions.metadata?.entityTypes ? { entity_types: searchOptions.metadata.entityTypes } : {}
897
+ }) : void 0]);
898
+ const hits = mergeRankedMemoryHits([normalizeGraphitiFacts(factPayload, id), normalizeGraphitiNodes(nodePayload, id)].map((group) => group.filter((hit) => {
899
+ if (searchOptions.minScore === void 0) return true;
900
+ const score = hit.normalizedScore ?? hit.score;
901
+ return score !== void 0 && score >= searchOptions.minScore;
902
+ })), limit);
903
+ await enrichGraphitiProvenance(options, groupId, hits);
904
+ return hits;
905
+ },
906
+ async getContext(query, searchOptions = {}) {
907
+ return defaultGetMemoryContext(adapter, query, searchOptions);
908
+ },
909
+ async write(input) {
910
+ const scope = mergeScopes$1(options.defaultScope, input.scope);
911
+ const groupId = graphitiGroupId(id, scope);
912
+ const eventKey = canonicalJson(stripUndefined({
913
+ id: input.id,
914
+ kind: input.kind,
915
+ text: input.text,
916
+ title: input.title,
917
+ role: input.role,
918
+ metadata: input.metadata
919
+ }));
920
+ const episodeUuid = input.id === void 0 ? randomUUID() : deterministicUuid(`${id}:${groupId}:${eventKey}`);
921
+ const name = input.title ?? `${input.kind}:${input.id ?? stableId("event", eventKey)}`;
922
+ const sourceDescription = canonicalJson(stripUndefined({
923
+ adapter: "agent-knowledge",
924
+ memoryKind: input.kind,
925
+ eventId: input.id,
926
+ actorId: input.metadata?.actorId,
927
+ sessionId: scope.sessionId,
928
+ role: input.role,
929
+ scope,
930
+ metadata: input.metadata ?? {}
931
+ }));
932
+ await callGraphiti(options.client, tools.addMemory, {
933
+ name,
934
+ episode_body: input.text,
935
+ group_id: groupId,
936
+ source: input.kind === "message" ? "message" : "text",
937
+ source_description: sourceDescription,
938
+ uuid: episodeUuid,
939
+ ...typeof input.metadata?.timestamp === "string" ? { reference_time: input.metadata.timestamp } : {}
940
+ });
941
+ queued.set(episodeUuid, {
942
+ groupId,
943
+ name
944
+ });
945
+ if (consistency === "visible") {
946
+ await waitForGraphitiEpisode(options, groupId, episodeUuid);
947
+ queued.delete(episodeUuid);
948
+ }
949
+ const result = {
950
+ accepted: true,
951
+ id: episodeUuid,
952
+ uri: `memory://${id}/${episodeUuid}`,
953
+ kind: input.kind,
954
+ metadata: {
955
+ provider: "graphiti",
956
+ groupId,
957
+ consistency,
958
+ queued: consistency === "queued"
959
+ }
960
+ };
961
+ return {
962
+ ...result,
963
+ sourceRecord: memoryWriteResultToSourceRecord(result, input.text, { scope })
964
+ };
965
+ },
966
+ async clear(scope) {
967
+ const groupId = graphitiGroupId(id, mergeScopes$1(options.defaultScope, scope));
968
+ for (const [episodeUuid, pending] of queued) {
969
+ if (pending.groupId !== groupId) continue;
970
+ await waitForGraphitiEpisode(options, groupId, episodeUuid);
971
+ queued.delete(episodeUuid);
972
+ }
973
+ await callGraphiti(options.client, tools.clearGraph, { group_ids: [groupId] });
974
+ },
975
+ async flush() {
976
+ for (const [episodeUuid, pending] of queued) {
977
+ await waitForGraphitiEpisode(options, pending.groupId, episodeUuid);
978
+ queued.delete(episodeUuid);
979
+ }
980
+ }
981
+ };
982
+ return adapter;
983
+ }
984
+ async function callGraphiti(client, name, args) {
985
+ const payload = graphitiPayload(await client.callTool({
986
+ name,
987
+ arguments: args
988
+ }));
989
+ if (isRecord$1(payload) && typeof payload.error === "string") throw new Error(`Graphiti ${name}: ${payload.error}`);
990
+ return payload;
991
+ }
992
+ function graphitiPayload(raw) {
993
+ if (!isRecord$1(raw)) return raw;
994
+ if (raw.isError === true) throw new Error(`Graphiti MCP: ${toolText(raw) || "tool call failed"}`);
995
+ if (isRecord$1(raw.structuredContent)) return unwrapResult(raw.structuredContent);
996
+ const text = toolText(raw);
997
+ if (text) try {
998
+ return unwrapResult(JSON.parse(text));
999
+ } catch {
1000
+ return { message: text };
1001
+ }
1002
+ return unwrapResult(raw);
1003
+ }
1004
+ function unwrapResult(value) {
1005
+ if (isRecord$1(value) && Object.keys(value).length === 1 && "result" in value) return value.result;
1006
+ return value;
1007
+ }
1008
+ function toolText(raw) {
1009
+ if (!Array.isArray(raw.content)) return "";
1010
+ return raw.content.filter(isRecord$1).map((part) => typeof part.text === "string" ? part.text : "").filter(Boolean).join("\n");
1011
+ }
1012
+ function normalizeGraphitiFacts(raw, adapterId) {
1013
+ return (isRecord$1(raw) && Array.isArray(raw.facts) ? raw.facts.filter(isRecord$1) : []).flatMap((fact) => {
1014
+ const text = stringField$2(fact, ["fact", "text"]);
1015
+ if (!text) return [];
1016
+ const factId = stringField$2(fact, ["uuid", "id"]) ?? stableId("fact", canonicalJson(stripUndefined({
1017
+ adapterId,
1018
+ text,
1019
+ fact
1020
+ })));
1021
+ const episodeUuids = stringArray(fact.episodes ?? fact.episode_uuids);
1022
+ const score = numberField$2(fact, ["score", "relevance_score"]);
1023
+ const normalizedScore = normalizedScoreOf(score);
1024
+ return [{
1025
+ id: factId,
1026
+ uri: `memory://${adapterId}/fact/${factId}`,
1027
+ kind: "fact",
1028
+ text,
1029
+ ...stringField$2(fact, ["name"]) ? { title: stringField$2(fact, ["name"]) } : {},
1030
+ ...stringField$2(fact, ["created_at"]) ? { createdAt: stringField$2(fact, ["created_at"]) } : {},
1031
+ ...stringField$2(fact, ["invalid_at"]) ? { validUntil: stringField$2(fact, ["invalid_at"]) } : stringField$2(fact, ["expired_at"]) ? { validUntil: stringField$2(fact, ["expired_at"]) } : {},
1032
+ ...score !== void 0 ? { score } : {},
1033
+ ...normalizedScore !== void 0 ? { normalizedScore } : {},
1034
+ metadata: {
1035
+ provider: "graphiti",
1036
+ groupId: fact.group_id,
1037
+ sourceNodeUuid: fact.source_node_uuid,
1038
+ targetNodeUuid: fact.target_node_uuid,
1039
+ validAt: fact.valid_at,
1040
+ invalidAt: fact.invalid_at,
1041
+ episodeUuids,
1042
+ attributes: fact.attributes
1043
+ }
1044
+ }];
1045
+ });
1046
+ }
1047
+ function normalizeGraphitiNodes(raw, adapterId) {
1048
+ return (isRecord$1(raw) && Array.isArray(raw.nodes) ? raw.nodes.filter(isRecord$1) : []).flatMap((node) => {
1049
+ const name = stringField$2(node, ["name"]);
1050
+ const text = stringField$2(node, ["summary"]) ?? name;
1051
+ if (!text) return [];
1052
+ const nodeId = stringField$2(node, ["uuid", "id"]) ?? stableId("node", canonicalJson(stripUndefined({
1053
+ adapterId,
1054
+ text,
1055
+ node
1056
+ })));
1057
+ const score = numberField$2(node, ["score", "relevance_score"]);
1058
+ const normalizedScore = normalizedScoreOf(score);
1059
+ return [{
1060
+ id: nodeId,
1061
+ uri: `memory://${adapterId}/entity/${nodeId}`,
1062
+ kind: "entity",
1063
+ text,
1064
+ ...name ? { title: name } : {},
1065
+ ...stringField$2(node, ["created_at"]) ? { createdAt: stringField$2(node, ["created_at"]) } : {},
1066
+ ...score !== void 0 ? { score } : {},
1067
+ ...normalizedScore !== void 0 ? { normalizedScore } : {},
1068
+ metadata: {
1069
+ provider: "graphiti",
1070
+ groupId: node.group_id,
1071
+ labels: node.labels,
1072
+ attributes: node.attributes
1073
+ }
1074
+ }];
1075
+ });
1076
+ }
1077
+ async function enrichGraphitiProvenance(options, groupId, hits) {
1078
+ const wanted = new Set(hits.flatMap((hit) => stringArray(hit.metadata?.episodeUuids)));
1079
+ if (wanted.size === 0) return;
1080
+ const scan = await scanGraphitiEpisodes(options, groupId, wanted, options.provenanceEpisodeLimit ?? options.episodeScanLimit ?? 1e4);
1081
+ const episodes = scan.episodes;
1082
+ const byId = new Map(episodes.map((episode) => [episode.uuid, episode]));
1083
+ for (const hit of hits) {
1084
+ const episodeUuids = stringArray(hit.metadata?.episodeUuids);
1085
+ const sourceEpisodes = episodeUuids.map((uuid) => byId.get(uuid)).filter((episode) => episode !== void 0);
1086
+ const provenanceIncomplete = episodeUuids.some((uuid) => !byId.has(uuid));
1087
+ if (sourceEpisodes.length === 0) {
1088
+ if (provenanceIncomplete) hit.metadata = {
1089
+ ...hit.metadata,
1090
+ provenanceIncomplete: true
1091
+ };
1092
+ continue;
1093
+ }
1094
+ const descriptions = sourceEpisodes.map((episode) => parseDescription(episode.source_description)).filter(isRecord$1);
1095
+ const first = descriptions[0];
1096
+ hit.metadata = {
1097
+ ...hit.metadata,
1098
+ sourceEpisodes,
1099
+ ...provenanceIncomplete || !scan.complete ? { provenanceIncomplete: true } : {},
1100
+ ...first ?? {},
1101
+ eventIds: uniqueStrings(descriptions.map((value) => value.eventId)),
1102
+ actorIds: uniqueStrings(descriptions.map((value) => value.actorId)),
1103
+ ...typeof first?.eventId === "string" ? { eventId: first.eventId } : {},
1104
+ ...typeof first?.actorId === "string" ? { actorId: first.actorId } : {}
1105
+ };
1106
+ }
1107
+ }
1108
+ async function getGraphitiEpisodes(options, groupId, limit) {
1109
+ const raw = await callGraphiti(options.client, graphitiToolNames(options).getEpisodes, {
1110
+ group_ids: [groupId],
1111
+ max_episodes: limit
1112
+ });
1113
+ if (!isRecord$1(raw) || !Array.isArray(raw.episodes)) return [];
1114
+ return raw.episodes.filter(isRecord$1).flatMap((episode) => {
1115
+ const uuid = stringField$2(episode, ["uuid", "id"]);
1116
+ return uuid ? [{
1117
+ ...episode,
1118
+ uuid
1119
+ }] : [];
1120
+ });
1121
+ }
1122
+ async function scanGraphitiEpisodes(options, groupId, wanted, maximum) {
1123
+ let limit = Math.min(100, maximum);
1124
+ for (;;) {
1125
+ const episodes = await getGraphitiEpisodes(options, groupId, limit);
1126
+ const foundAll = [...wanted].every((uuid) => episodes.some((episode) => episode.uuid === uuid));
1127
+ if (foundAll || episodes.length < limit) return {
1128
+ episodes,
1129
+ complete: foundAll
1130
+ };
1131
+ if (limit === maximum) return {
1132
+ episodes,
1133
+ complete: false
1134
+ };
1135
+ limit = Math.min(maximum, limit * 2);
1136
+ }
1137
+ }
1138
+ async function waitForGraphitiEpisode(options, groupId, episodeUuid) {
1139
+ const timeoutMs = options.ingestionTimeoutMs ?? DEFAULT_GRAPHITI_INGESTION_TIMEOUT_MS;
1140
+ const pollIntervalMs = options.pollIntervalMs ?? 500;
1141
+ const maximum = options.episodeScanLimit ?? 1e4;
1142
+ const deadline = Date.now() + timeoutMs;
1143
+ let scanLimitReached = false;
1144
+ for (;;) {
1145
+ const scan = await scanGraphitiEpisodes(options, groupId, /* @__PURE__ */ new Set([episodeUuid]), maximum);
1146
+ if (scan.complete) return;
1147
+ scanLimitReached ||= scan.episodes.length >= maximum;
1148
+ const remainingMs = deadline - Date.now();
1149
+ if (remainingMs <= 0) break;
1150
+ await new Promise((resolve) => setTimeout(resolve, Math.min(pollIntervalMs, remainingMs)));
1151
+ }
1152
+ if (scanLimitReached) throw new Error(`Graphiti episode ${episodeUuid} is outside episodeScanLimit=${maximum}; raise the limit or use a smaller group`);
1153
+ throw new Error(`Graphiti episode ${episodeUuid} was not visible after ${timeoutMs}ms`);
1154
+ }
1155
+ function graphitiGroupId(adapterId, scope) {
1156
+ return `ak_${sha256(canonicalJson(stripUndefined({
1157
+ adapterId,
1158
+ tenantId: scope.tenantId,
1159
+ userId: scope.userId,
1160
+ agentId: scope.agentId,
1161
+ teamId: scope.teamId,
1162
+ runId: scope.runId,
1163
+ sessionId: scope.sessionId,
1164
+ namespace: scope.namespace,
1165
+ tags: scope.tags
1166
+ }))).slice(0, 32)}`;
1167
+ }
1168
+ function graphitiToolNames(options) {
1169
+ return {
1170
+ addMemory: options.toolNames?.addMemory ?? "add_memory",
1171
+ searchFacts: options.toolNames?.searchFacts ?? "search_memory_facts",
1172
+ searchNodes: options.toolNames?.searchNodes ?? "search_nodes",
1173
+ getEpisodes: options.toolNames?.getEpisodes ?? "get_episodes",
1174
+ clearGraph: options.toolNames?.clearGraph ?? "clear_graph"
1175
+ };
1176
+ }
1177
+ function assertGraphitiOptions(options) {
1178
+ if (options.id !== void 0 && (typeof options.id !== "string" || !options.id.trim())) throw new Error("Graphiti id must be a non-empty string");
1179
+ if (options.backendRef !== void 0 && (typeof options.backendRef !== "string" || !options.backendRef.trim())) throw new Error("Graphiti backendRef must be a non-empty string");
1180
+ if (options.consistency !== void 0 && !["queued", "visible"].includes(options.consistency)) throw new Error("Graphiti consistency must be queued or visible");
1181
+ for (const [name, value] of [
1182
+ ["ingestionTimeoutMs", options.ingestionTimeoutMs],
1183
+ ["pollIntervalMs", options.pollIntervalMs],
1184
+ ["episodeScanLimit", options.episodeScanLimit],
1185
+ ["provenanceEpisodeLimit", options.provenanceEpisodeLimit]
1186
+ ]) if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) throw new Error(`Graphiti ${name} must be a positive safe integer`);
1187
+ 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`);
1188
+ }
1189
+ function deterministicUuid(value) {
1190
+ const hex = sha256(value);
1191
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
1192
+ }
1193
+ function parseDescription(value) {
1194
+ if (!value) return void 0;
1195
+ try {
1196
+ return JSON.parse(value);
1197
+ } catch {
1198
+ return;
1199
+ }
1200
+ }
1201
+ function mergeScopes$1(base, extra) {
1202
+ return {
1203
+ ...base ?? {},
1204
+ ...extra ?? {},
1205
+ tags: {
1206
+ ...base?.tags ?? {},
1207
+ ...extra?.tags ?? {}
1208
+ }
1209
+ };
1210
+ }
1211
+ function uniqueStrings(values) {
1212
+ return [...new Set(values.filter((value) => typeof value === "string"))];
1213
+ }
1214
+ function stringArray(value) {
1215
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
1216
+ }
1217
+ function stringField$2(value, keys) {
1218
+ for (const key of keys) {
1219
+ const field = value[key];
1220
+ if (typeof field === "string" && field.length > 0) return field;
1221
+ }
1222
+ }
1223
+ function numberField$2(value, keys) {
1224
+ for (const key of keys) {
1225
+ const field = value[key];
1226
+ if (typeof field === "number" && Number.isFinite(field)) return field;
1227
+ }
1228
+ }
1229
+ function normalizedScoreOf(score) {
1230
+ return score !== void 0 && score >= 0 && score <= 1 ? score : void 0;
1231
+ }
1232
+ function isRecord$1(value) {
1233
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1234
+ }
1235
+ function stripUndefined(value) {
1236
+ if (Array.isArray(value)) return value.map(stripUndefined);
1237
+ if (!isRecord$1(value)) return value;
1238
+ return Object.fromEntries(Object.entries(value).filter(([, child]) => child !== void 0).map(([key, child]) => [key, stripUndefined(child)]));
1239
+ }
1240
+ function graphitiMemoryAdapterIdentity(options) {
1241
+ if (typeof options.backendRef !== "string" || !options.backendRef.trim()) throw new Error("Graphiti backendRef must be a non-empty string");
1242
+ return `sha256:${sha256(canonicalJson(stripUndefined(options)))}`;
1243
+ }
1244
+ //#endregion
1245
+ //#region src/memory/json.ts
1246
+ /** Optional object properties are absent on the wire; other unsupported values still fail. */
1247
+ function canonicalMemoryJson(value) {
1248
+ return canonicalJson(omitUndefinedProperties(value));
1249
+ }
1250
+ function omitUndefinedProperties(value) {
1251
+ if (Array.isArray(value)) return value.map(omitUndefinedProperties);
1252
+ if (value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)) return Object.fromEntries(Object.entries(value).filter(([, child]) => child !== void 0).map(([key, child]) => [key, omitUndefinedProperties(child)]));
1253
+ return value;
1254
+ }
1255
+ //#endregion
1256
+ //#region src/memory/lifecycle.ts
1257
+ const DEFAULT_MEMORY_CLEANUP_TIMEOUT_MS = 18e4;
1258
+ var AgentMemoryLifecycleTimeoutError = class extends Error {
1259
+ operation;
1260
+ timeoutMs;
1261
+ constructor(operation, timeoutMs) {
1262
+ super(`${operation} did not finish within ${timeoutMs}ms`);
1263
+ this.operation = operation;
1264
+ this.timeoutMs = timeoutMs;
1265
+ this.name = "AgentMemoryLifecycleTimeoutError";
1266
+ }
1267
+ };
1268
+ var AgentMemoryLifecycleUnsafeError = class extends Error {
1269
+ operation;
1270
+ priorTimeout;
1271
+ constructor(operation, priorTimeout) {
1272
+ super(`${operation} cannot start because '${priorTimeout.operation}' is still running after its ${priorTimeout.timeoutMs}ms timeout`, { cause: priorTimeout });
1273
+ this.operation = operation;
1274
+ this.priorTimeout = priorTimeout;
1275
+ this.name = "AgentMemoryLifecycleUnsafeError";
1276
+ }
1277
+ };
1278
+ const timedOutResources = /* @__PURE__ */ new WeakMap();
1279
+ function resolveMemoryCleanupTimeoutMs(value, label) {
1280
+ const timeoutMs = value ?? 18e4;
1281
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new Error(`${label} cleanupTimeoutMs must be a positive safe integer`);
1282
+ return timeoutMs;
1283
+ }
1284
+ async function runBoundedMemoryLifecycle(input) {
1285
+ input.signal?.throwIfAborted();
1286
+ if (input.cancellationTimeoutMs !== void 0 && (!Number.isSafeInteger(input.cancellationTimeoutMs) || input.cancellationTimeoutMs <= 0)) throw new Error(`${input.operation} cancellationTimeoutMs must be a positive safe integer`);
1287
+ const priorTimeout = input.resource ? timedOutResources.get(input.resource) : void 0;
1288
+ if (priorTimeout) throw new AgentMemoryLifecycleUnsafeError(input.operation, priorTimeout);
1289
+ let timeout;
1290
+ let cancellationTimeout;
1291
+ let timeoutError;
1292
+ let relayAbort;
1293
+ const work = Promise.resolve().then(input.run);
1294
+ if (input.resource) {
1295
+ const resource = input.resource;
1296
+ work.then(() => {
1297
+ if (timeoutError && timedOutResources.get(resource) === timeoutError) timedOutResources.delete(resource);
1298
+ }, () => {
1299
+ if (timeoutError && timedOutResources.get(resource) === timeoutError) timedOutResources.delete(resource);
1300
+ });
1301
+ }
1302
+ const deadline = new Promise((_, reject) => {
1303
+ timeout = setTimeout(() => {
1304
+ timeoutError = new AgentMemoryLifecycleTimeoutError(input.operation, input.timeoutMs);
1305
+ if (input.resource) timedOutResources.set(input.resource, timeoutError);
1306
+ input.abortController?.abort(timeoutError);
1307
+ reject(timeoutError);
1308
+ }, input.timeoutMs);
1309
+ });
1310
+ const signal = input.signal;
1311
+ const cancellation = signal ? new Promise((_, reject) => {
1312
+ relayAbort = () => {
1313
+ const error = memoryLifecycleAbortError(input.operation);
1314
+ input.abortController?.abort(error);
1315
+ if (input.cancellationTimeoutMs === void 0) {
1316
+ reject(signal.reason ?? error);
1317
+ return;
1318
+ }
1319
+ cancellationTimeout = setTimeout(() => {
1320
+ timeoutError = new AgentMemoryLifecycleTimeoutError(`${input.operation} cancellation`, input.cancellationTimeoutMs);
1321
+ if (input.resource) timedOutResources.set(input.resource, timeoutError);
1322
+ reject(timeoutError);
1323
+ }, input.cancellationTimeoutMs);
1324
+ };
1325
+ if (signal.aborted) relayAbort();
1326
+ else signal.addEventListener("abort", relayAbort, { once: true });
1327
+ }) : void 0;
1328
+ try {
1329
+ return await Promise.race([
1330
+ work,
1331
+ deadline,
1332
+ ...cancellation ? [cancellation] : []
1333
+ ]);
1334
+ } finally {
1335
+ if (timeout) clearTimeout(timeout);
1336
+ if (cancellationTimeout) clearTimeout(cancellationTimeout);
1337
+ if (relayAbort) signal?.removeEventListener("abort", relayAbort);
1338
+ }
1339
+ }
1340
+ function memoryLifecycleAbortError(operation) {
1341
+ const error = /* @__PURE__ */ new Error(`${operation} aborted`);
1342
+ error.name = "AbortError";
1343
+ return error;
1344
+ }
1345
+ function memoryRecoveryDelayMs(adapter) {
1346
+ const isolation = adapter.branchIsolation;
1347
+ if (isolation?.mode !== "scoped" || isolation.processExitSafe !== false) return 0;
1348
+ const delayMs = isolation.recoveryDelayMs;
1349
+ if (typeof delayMs !== "number" || !Number.isSafeInteger(delayMs) || delayMs <= 0) throw new Error(`${adapter.id}: processExitSafe=false requires a positive recoveryDelayMs for abandoned writes`);
1350
+ return delayMs;
1351
+ }
1352
+ async function sleepForMemoryRecovery(delayMs, assertOwned, timeoutMs = delayMs, operation = "memory recovery visibility wait", signal) {
1353
+ if (delayMs <= 0) return;
1354
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new Error(`${operation} timeout must be a positive safe integer`);
1355
+ signal?.throwIfAborted();
1356
+ await waitForMemoryRecoveryDelay(Math.min(delayMs, timeoutMs), signal, operation);
1357
+ await assertOwned();
1358
+ if (delayMs > timeoutMs) throw new AgentMemoryLifecycleTimeoutError(operation, timeoutMs);
1359
+ }
1360
+ async function waitForMemoryRecoveryDelay(delayMs, signal, operation) {
1361
+ await new Promise((resolve, reject) => {
1362
+ let timeout;
1363
+ const abort = () => {
1364
+ if (timeout) clearTimeout(timeout);
1365
+ reject(signal?.reason ?? memoryLifecycleAbortError(operation));
1366
+ };
1367
+ timeout = setTimeout(() => {
1368
+ signal?.removeEventListener("abort", abort);
1369
+ resolve();
1370
+ }, delayMs);
1371
+ if (signal?.aborted) abort();
1372
+ else signal?.addEventListener("abort", abort, { once: true });
1373
+ });
1374
+ }
1375
+ function createMemoryExecutionPool(limit) {
1376
+ let active = 0;
1377
+ const waiters = [];
1378
+ return { async run(operation) {
1379
+ if (active >= limit) await new Promise((resolve) => waiters.push(resolve));
1380
+ active += 1;
1381
+ try {
1382
+ return await operation();
1383
+ } finally {
1384
+ active -= 1;
1385
+ waiters.shift()?.();
1386
+ }
1387
+ } };
1388
+ }
1389
+ //#endregion
1390
+ //#region src/memory/schemas.ts
1391
+ const AgentMemoryKindSchema = z.enum([
1392
+ "message",
1393
+ "entity",
1394
+ "fact",
1395
+ "preference",
1396
+ "observation",
1397
+ "reasoning-trace"
1398
+ ]);
1399
+ const AgentMemoryScopeSchema = z.object({
1400
+ tenantId: z.string().optional(),
1401
+ userId: z.string().optional(),
1402
+ agentId: z.string().optional(),
1403
+ teamId: z.string().optional(),
1404
+ runId: z.string().optional(),
1405
+ sessionId: z.string().optional(),
1406
+ namespace: z.string().optional(),
1407
+ tags: z.record(z.string(), z.string()).optional()
1408
+ });
1409
+ const AgentMemoryHitSchema = z.object({
1410
+ id: z.string().min(1),
1411
+ uri: z.string().min(1),
1412
+ kind: AgentMemoryKindSchema,
1413
+ text: z.string().min(1),
1414
+ title: z.string().optional(),
1415
+ score: z.number().optional(),
1416
+ normalizedScore: z.number().optional(),
1417
+ confidence: z.number().optional(),
1418
+ createdAt: z.string().optional(),
1419
+ validUntil: z.string().optional(),
1420
+ lastVerifiedAt: z.string().optional(),
1421
+ metadata: z.record(z.string(), z.unknown()).optional()
1422
+ });
1423
+ const AgentMemoryWriteInputSchema = z.object({
1424
+ kind: AgentMemoryKindSchema,
1425
+ text: z.string().min(1),
1426
+ id: z.string().optional(),
1427
+ title: z.string().optional(),
1428
+ role: z.enum([
1429
+ "system",
1430
+ "user",
1431
+ "assistant",
1432
+ "tool"
1433
+ ]).optional(),
1434
+ entityName: z.string().optional(),
1435
+ entityType: z.string().optional(),
1436
+ category: z.string().optional(),
1437
+ predicate: z.string().optional(),
1438
+ subject: z.string().optional(),
1439
+ object: z.string().optional(),
1440
+ confidence: z.number().min(0).max(1).optional(),
1441
+ scope: AgentMemoryScopeSchema.optional(),
1442
+ metadata: z.record(z.string(), z.unknown()).optional()
1443
+ });
1444
+ //#endregion
1445
+ //#region src/memory/hindsight.ts
1446
+ var HindsightOperationUnknownError = class extends Error {
1447
+ bankId;
1448
+ operationId;
1449
+ documentId;
1450
+ constructor(bankId, operationId, documentId, options) {
1451
+ super("Hindsight retain is unresolved; retry the same write id and bytes to reconcile it before checkpointing", options);
1452
+ this.bankId = bankId;
1453
+ this.operationId = operationId;
1454
+ this.documentId = documentId;
1455
+ this.name = "HindsightOperationUnknownError";
1456
+ }
1457
+ };
1458
+ const operationSchema = z.object({
1459
+ operation_id: z.string(),
1460
+ status: z.enum([
1461
+ "pending",
1462
+ "processing",
1463
+ "completed",
1464
+ "failed",
1465
+ "cancelled",
1466
+ "not_found"
1467
+ ])
1468
+ });
1469
+ const documentSchema = z.object({
1470
+ id: z.string(),
1471
+ bank_id: z.string(),
1472
+ original_text: z.string().nullable(),
1473
+ document_metadata: z.record(z.string(), z.unknown()).nullable().optional()
1474
+ });
1475
+ const recallSchema = z.object({
1476
+ results: z.array(z.object({
1477
+ id: z.string().min(1),
1478
+ text: z.string(),
1479
+ type: z.string().nullable().optional(),
1480
+ document_id: z.string().nullable().optional(),
1481
+ chunk_id: z.string().nullable().optional(),
1482
+ metadata: z.record(z.string(), z.string()).nullable().optional(),
1483
+ source_fact_ids: z.array(z.string()).nullable().optional(),
1484
+ scores: z.object({ final: z.number().nullable().optional() }).passthrough().nullable().optional()
1485
+ })),
1486
+ source_facts: z.record(z.string(), z.unknown()).nullable().optional(),
1487
+ source_facts_truncated: z.boolean().nullable().optional()
1488
+ });
1489
+ /** Dedicated bank identity includes the entire logical scope, including the existing branch partition. */
1490
+ function hindsightMemoryBankId(input) {
1491
+ if (!input.backendRef.trim() || !input.branchId.trim()) throw new Error("Hindsight requires backendRef and branchId");
1492
+ if (!input.scope.namespace?.trim()) throw new Error("Hindsight memory requires an explicit stable play scope.namespace");
1493
+ if (input.scope.tags?.memoryBranchId !== input.branchId) throw new Error("Hindsight memory scope must belong to its bound AgentMemoryBranch");
1494
+ return `ak-${sha256(canonicalMemoryJson(input))}`;
1495
+ }
1496
+ /** Hindsight 0.10.2 adapter. Snapshots remain accepted-input journals, never provider database snapshots. */
1497
+ function createHindsightMemoryAdapter(options) {
1498
+ if (!options.backendRef?.trim() || !options.branchId?.trim()) throw new Error("Hindsight requires backendRef and branchId");
1499
+ const requestTimeoutMs = bounded(options.requestTimeoutMs ?? 15e3, 3e5, "requestTimeoutMs");
1500
+ const ingestionTimeoutMs = bounded(options.ingestionTimeoutMs ?? 12e4, 6e5, "ingestionTimeoutMs");
1501
+ const pollIntervalMs = bounded(options.pollIntervalMs ?? 1e3, 3e4, "pollIntervalMs");
1502
+ const maxTextChars = bounded(options.maxTextChars ?? 8e3, 1e5, "maxTextChars");
1503
+ const maxTokens = bounded(options.recallMaxTokens ?? 1e3, 16e3, "recallMaxTokens");
1504
+ const id = `hindsight-0.10.2:${sha256(options.backendRef)}`;
1505
+ const unresolved = /* @__PURE__ */ new Map();
1506
+ let version;
1507
+ async function request(method, path, body, timeoutMs = requestTimeoutMs) {
1508
+ const abortController = new AbortController();
1509
+ const response = await runBoundedMemoryLifecycle({
1510
+ operation: `Hindsight ${method} ${path}`,
1511
+ timeoutMs,
1512
+ resource: options.client,
1513
+ abortController,
1514
+ run: () => options.client.request({
1515
+ method,
1516
+ path,
1517
+ ...body === void 0 ? {} : { body },
1518
+ signal: abortController.signal
1519
+ })
1520
+ });
1521
+ if (response.status !== 404 && (response.status < 200 || response.status >= 300)) throw new Error(`Hindsight ${method} failed with HTTP ${response.status}`);
1522
+ return response;
1523
+ }
1524
+ async function checkVersion() {
1525
+ version ??= (async () => {
1526
+ const response = await request("GET", "/version");
1527
+ const parsed = z.object({ api_version: z.literal("0.10.2") }).safeParse(response.body);
1528
+ if (response.status !== 200 || !parsed.success) throw new Error("Hindsight adapter requires the qualified 0.10.2 API");
1529
+ })();
1530
+ await version;
1531
+ }
1532
+ function bank(scope) {
1533
+ return hindsightMemoryBankId({
1534
+ backendRef: options.backendRef,
1535
+ branchId: options.branchId,
1536
+ scope: scope ?? {}
1537
+ });
1538
+ }
1539
+ function path(bankId, suffix) {
1540
+ return `/v1/default/banks/${encodeURIComponent(bankId)}${suffix}`;
1541
+ }
1542
+ async function readDocument(bankId, documentId) {
1543
+ const response = await request("GET", path(bankId, `/documents/${encodeURIComponent(documentId)}`));
1544
+ return response.status === 404 ? null : documentSchema.parse(response.body);
1545
+ }
1546
+ async function readOperation(bankId, operationId, timeoutMs = requestTimeoutMs) {
1547
+ const response = await request("GET", path(bankId, `/operations/${operationId}`), void 0, timeoutMs);
1548
+ if (response.status === 404) return {
1549
+ operation_id: operationId,
1550
+ status: "not_found"
1551
+ };
1552
+ const operation = operationSchema.parse(response.body);
1553
+ if (operation.operation_id !== operationId) throw new Error("Hindsight returned another operation identity");
1554
+ return operation;
1555
+ }
1556
+ const adapter = {
1557
+ id,
1558
+ branchIsolation: {
1559
+ mode: "instance",
1560
+ branchId: options.branchId,
1561
+ supportsLogicalScopes: true
1562
+ },
1563
+ async search(query, searchOptions = {}) {
1564
+ const bankId = bank(searchOptions.scope);
1565
+ if (!query.trim()) throw new Error("Hindsight recall requires a nonempty query");
1566
+ const limit = searchOptions.limit ?? 10;
1567
+ if (!Number.isSafeInteger(limit) || limit < 0 || limit > 100) throw new Error("Hindsight recall limit must be between 0 and 100");
1568
+ if (searchOptions.minScore !== void 0 && !Number.isFinite(searchOptions.minScore)) throw new Error("Hindsight minScore must be finite");
1569
+ if (limit === 0) return [];
1570
+ await checkVersion();
1571
+ const response = await request("POST", path(bankId, "/memories/recall"), {
1572
+ query,
1573
+ budget: "low",
1574
+ max_tokens: maxTokens,
1575
+ include: {
1576
+ entities: null,
1577
+ source_facts: { max_tokens: maxTokens }
1578
+ }
1579
+ });
1580
+ if (response.status === 404) return [];
1581
+ const result = recallSchema.parse(response.body);
1582
+ return result.results.map((hit) => ({
1583
+ id: hit.id,
1584
+ uri: `hindsight://${bankId}/memories/${encodeURIComponent(hit.id)}`,
1585
+ kind: hit.type === "world" ? "fact" : "observation",
1586
+ text: hit.text,
1587
+ ...hit.scores?.final == null ? {} : { score: hit.scores.final },
1588
+ metadata: {
1589
+ provider: "hindsight",
1590
+ backendRef: options.backendRef,
1591
+ bankId,
1592
+ branchId: options.branchId,
1593
+ providerFactType: hit.type ?? null,
1594
+ documentId: hit.document_id ?? null,
1595
+ chunkId: hit.chunk_id ?? null,
1596
+ provenance: hit.metadata ?? null,
1597
+ sourceFactIds: hit.source_fact_ids ?? [],
1598
+ sourceFacts: Object.fromEntries((hit.source_fact_ids ?? []).flatMap((key) => result.source_facts?.[key] === void 0 ? [] : [[key, result.source_facts[key]]])),
1599
+ sourceFactsTruncated: result.source_facts_truncated ?? null,
1600
+ evidenceStatus: "provider-derived; source attribution is not independent validation"
1601
+ }
1602
+ })).filter((hit) => (!searchOptions.kinds?.length || searchOptions.kinds.includes(hit.kind)) && (searchOptions.minScore === void 0 || hit.score !== void 0 && hit.score >= searchOptions.minScore)).slice(0, limit);
1603
+ },
1604
+ async getContext(query, searchOptions) {
1605
+ return defaultGetMemoryContext(adapter, query, searchOptions);
1606
+ },
1607
+ async write(raw) {
1608
+ const input = AgentMemoryWriteInputSchema.parse(raw);
1609
+ if (!input.id?.trim()) throw new Error("Hindsight writes require a stable caller id");
1610
+ if (input.text.length > maxTextChars) throw new Error(`Hindsight write exceeds ${maxTextChars} characters`);
1611
+ const bankId = bank(input.scope);
1612
+ const documentId = `ak-${sha256(input.id)}`;
1613
+ const operationId = operationUuid(`${bankId}:${documentId}`);
1614
+ const identity = `${bankId}:${operationId}`;
1615
+ const { scope, ...sourceInput } = input;
1616
+ const inputDigest = sha256(canonicalMemoryJson(sourceInput));
1617
+ const expectedMetadata = {
1618
+ ak_input_sha256: inputDigest,
1619
+ ak_scope_sha256: sha256(canonicalMemoryJson(scope)),
1620
+ ak_source: canonicalMemoryJson(sourceInput)
1621
+ };
1622
+ const verifyDocument = (document) => {
1623
+ if (!document || document.id !== documentId || document.bank_id !== bankId || document.original_text !== input.text || document.document_metadata?.ak_input_sha256 !== inputDigest || document.document_metadata?.ak_scope_sha256 !== expectedMetadata.ak_scope_sha256) throw new Error("Hindsight document identity or bytes differ from this stable write id");
1624
+ };
1625
+ await checkVersion();
1626
+ const prior = await readDocument(bankId, documentId);
1627
+ if (prior) verifyDocument(prior);
1628
+ let operation = await readOperation(bankId, operationId);
1629
+ if (prior && operation.status === "not_found") {
1630
+ const unknown = new HindsightOperationUnknownError(bankId, operationId, documentId);
1631
+ unresolved.set(identity, unknown);
1632
+ throw unknown;
1633
+ }
1634
+ const unknown = new HindsightOperationUnknownError(bankId, operationId, documentId);
1635
+ unresolved.set(identity, unknown);
1636
+ const deadline = Date.now() + ingestionTimeoutMs;
1637
+ try {
1638
+ if (operation.status === "not_found") {
1639
+ const submitted = await request("POST", path(bankId, "/memories"), {
1640
+ async: true,
1641
+ operation_id: operationId,
1642
+ items: [{
1643
+ content: input.text,
1644
+ document_id: documentId,
1645
+ metadata: expectedMetadata,
1646
+ tags: ["source:agent-knowledge"],
1647
+ observation_scopes: "shared",
1648
+ context: "Retained agent input. Claims of success or correctness remain attributed claims, not verified findings."
1649
+ }]
1650
+ });
1651
+ if (!z.object({
1652
+ success: z.literal(true),
1653
+ bank_id: z.literal(bankId),
1654
+ async: z.literal(true),
1655
+ operation_id: z.literal(operationId)
1656
+ }).parse(submitted.body).success) throw new Error("Hindsight rejected retain");
1657
+ }
1658
+ while (operation.status !== "completed") {
1659
+ const remaining = deadline - Date.now();
1660
+ if (remaining <= 0) throw unknown;
1661
+ operation = await readOperation(bankId, operationId, Math.min(requestTimeoutMs, remaining));
1662
+ if (operation.status === "failed" || operation.status === "cancelled") throw new Error(`Hindsight retain is ${operation.status}; possible partial provider state requires inspection`);
1663
+ if (operation.status !== "completed") await setTimeout$1(Math.min(pollIntervalMs, Math.max(0, deadline - Date.now())));
1664
+ }
1665
+ verifyDocument(await readDocument(bankId, documentId));
1666
+ unresolved.delete(identity);
1667
+ return accepted("completed");
1668
+ } catch (cause) {
1669
+ throw new HindsightOperationUnknownError(bankId, operationId, documentId, { cause });
1670
+ }
1671
+ function accepted(operationStatus) {
1672
+ unresolved.delete(identity);
1673
+ const result = {
1674
+ accepted: true,
1675
+ id: input.id,
1676
+ uri: `hindsight://${bankId}/documents/${documentId}`,
1677
+ kind: input.kind,
1678
+ metadata: {
1679
+ provider: "hindsight",
1680
+ bankId,
1681
+ branchId: options.branchId,
1682
+ operationId,
1683
+ documentId,
1684
+ inputDigest,
1685
+ operationStatus,
1686
+ cost: "unmeasured"
1687
+ }
1688
+ };
1689
+ return {
1690
+ ...result,
1691
+ sourceRecord: memoryWriteResultToSourceRecord(result, input.text, { scope })
1692
+ };
1693
+ }
1694
+ },
1695
+ async flush() {
1696
+ if (unresolved.size) throw unresolved.values().next().value;
1697
+ }
1698
+ };
1699
+ return adapter;
1700
+ }
1701
+ function operationUuid(value) {
1702
+ const hex = sha256(value);
1703
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
1704
+ }
1705
+ function bounded(value, max, name) {
1706
+ if (!Number.isSafeInteger(value) || value < 1 || value > max) throw new Error(`Hindsight ${name} must be an integer between 1 and ${max}`);
1707
+ return value;
1708
+ }
1709
+ //#endregion
1710
+ //#region src/memory/mem0.ts
1711
+ /** Connects both the hosted Mem0 client and the open-source `Memory` class. */
1712
+ function createMem0MemoryAdapter(options) {
1713
+ assertMem0Options(options);
1714
+ const id = options.id ?? `mem0-${options.mode}`;
1715
+ const pendingWrites = /* @__PURE__ */ new Set();
1716
+ const adapter = {
1717
+ id,
1718
+ branchIsolation: options.mode === "oss" ? { mode: "scoped" } : {
1719
+ mode: "scoped",
1720
+ processExitSafe: false,
1721
+ recoveryDelayMs: options.ingestionTimeoutMs ?? 3e4
1722
+ },
1723
+ async search(query, searchOptions = {}) {
1724
+ assertMem0SearchOptions(searchOptions, id);
1725
+ if (searchOptions.limit === 0) return [];
1726
+ const scope = mergeScopes(options.defaultScope, searchOptions.scope);
1727
+ assertMem0ProviderScope(scope, options.mode, id, "search");
1728
+ const requestedKinds = [...new Set(searchOptions.kinds ?? [])];
1729
+ const kindQueries = requestedKinds.length > 0 ? requestedKinds : [void 0];
1730
+ return mergeMem0Hits((await Promise.all(kindQueries.map((kind) => options.client.search(query, {
1731
+ filters: mem0Filters(scope, options.appId, kind),
1732
+ topK: searchOptions.limit,
1733
+ threshold: searchOptions.minScore,
1734
+ ...options.rerank !== void 0 ? { rerank: options.rerank } : {},
1735
+ ...options.mode === "hosted" && options.latestOnly !== void 0 ? { latestOnly: options.latestOnly } : {}
1736
+ })))).flatMap((raw) => normalizeMem0Hits(raw, id, searchOptions)), searchOptions.limit);
1737
+ },
1738
+ async getContext(query, searchOptions = {}) {
1739
+ return defaultGetMemoryContext(adapter, query, searchOptions);
1740
+ },
1741
+ async write(input) {
1742
+ const scope = mergeScopes(options.defaultScope, input.scope);
1743
+ assertMem0ProviderScope(scope, options.mode, id, "write");
1744
+ pruneExpiredMem0PendingWrites(pendingWrites);
1745
+ const writeId = input.id ?? randomUUID();
1746
+ const writeFilters = mem0Filters(scope, options.appId);
1747
+ const pendingProbe = {
1748
+ text: input.text,
1749
+ providerIds: /* @__PURE__ */ new Set(),
1750
+ filters: writeFilters,
1751
+ expiresAt: Number.POSITIVE_INFINITY
1752
+ };
1753
+ pendingWrites.add(pendingProbe);
1754
+ const scopeMetadata = mem0ScopeMetadata(scope, options.appId);
1755
+ const metadata = compactRecord({
1756
+ ...input.metadata,
1757
+ agentKnowledgeWriteId: writeId,
1758
+ memoryKind: input.kind,
1759
+ memoryTitle: input.title,
1760
+ entityName: input.entityName,
1761
+ entityType: input.entityType,
1762
+ category: input.category,
1763
+ predicate: input.predicate,
1764
+ subject: input.subject,
1765
+ object: input.object,
1766
+ confidence: input.confidence,
1767
+ originalRole: input.role,
1768
+ ...scopeMetadata
1769
+ });
1770
+ const entity = mem0Entity(scope, options.appId);
1771
+ let raw;
1772
+ try {
1773
+ raw = await options.client.add([{
1774
+ role: mem0Role(input.role),
1775
+ content: input.text
1776
+ }], {
1777
+ ...entity,
1778
+ metadata,
1779
+ infer: options.infer ?? true,
1780
+ ...options.mode === "oss" ? { filters: writeFilters } : {}
1781
+ });
1782
+ } finally {
1783
+ pendingProbe.expiresAt = Date.now() + (options.ingestionTimeoutMs ?? 3e4);
1784
+ }
1785
+ 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`);
1786
+ const items = extractMem0Items(raw);
1787
+ const mutations = items.filter((candidate) => mem0Event(candidate) !== "NOOP");
1788
+ const durableMutations = mutations.filter((candidate) => mem0Event(candidate) !== "DELETE");
1789
+ for (const candidate of durableMutations) {
1790
+ const providerId = stringField$1(candidate, ["id"]);
1791
+ if (providerId) pendingProbe.providerIds.add(providerId);
1792
+ }
1793
+ if (durableMutations.length === 0) pendingWrites.delete(pendingProbe);
1794
+ const item = mutations[0] ?? items[0];
1795
+ const itemId = stringField$1(item, ["id"]) ?? writeId;
1796
+ const event = mem0Event(item);
1797
+ const events = [...new Set(mutations.map(mem0Event).filter(Boolean))];
1798
+ const result = {
1799
+ accepted: mutations.length > 0,
1800
+ id: itemId,
1801
+ uri: `memory://${id}/${encodeURIComponent(itemId)}`,
1802
+ kind: input.kind,
1803
+ metadata: {
1804
+ provider: "mem0",
1805
+ mode: options.mode,
1806
+ ...event ? { event } : {},
1807
+ ...events.length > 1 ? { events } : {}
1808
+ }
1809
+ };
1810
+ return {
1811
+ ...result,
1812
+ sourceRecord: memoryWriteResultToSourceRecord(result, input.text, { scope })
1813
+ };
1814
+ },
1815
+ async clear(scope) {
1816
+ const mergedScope = mergeScopes(options.defaultScope, scope);
1817
+ assertMem0ProviderScope(mergedScope, options.mode, id, "clear");
1818
+ pruneExpiredMem0PendingWrites(pendingWrites);
1819
+ const clearFilters = mem0Filters(mergedScope, options.appId);
1820
+ if (Object.keys(clearFilters).length === 0) throw new Error(`${id}: refusing an unscoped Mem0 clear`);
1821
+ if (options.client.getAll && options.client.delete) {
1822
+ const pendingForClear = [...pendingWrites].filter((pending) => mem0FiltersInclude(pending.filters, clearFilters));
1823
+ const deletedIds = /* @__PURE__ */ new Set();
1824
+ const searchProbes = /* @__PURE__ */ new Map();
1825
+ const observedPending = /* @__PURE__ */ new Set();
1826
+ for (const pending of pendingForClear) {
1827
+ const ids = searchProbes.get(pending.text) ?? /* @__PURE__ */ new Set();
1828
+ for (const providerId of pending.providerIds) ids.add(providerId);
1829
+ searchProbes.set(pending.text, ids);
1830
+ if (pending.providerIds.size > 0) observedPending.add(pending);
1831
+ }
1832
+ for (const pending of pendingForClear) for (const providerId of pending.providerIds) {
1833
+ if (deletedIds.has(providerId)) continue;
1834
+ await options.client.delete(providerId);
1835
+ deletedIds.add(providerId);
1836
+ }
1837
+ const visibilityTimeoutMs = options.ingestionTimeoutMs ?? 3e4;
1838
+ const pollIntervalMs = options.pollIntervalMs ?? 250;
1839
+ let visibilityDeadline = Date.now() + visibilityTimeoutMs;
1840
+ for (let batch = 0; batch < 100;) {
1841
+ const items = extractMem0Items(await options.client.getAll(options.mode === "hosted" ? {
1842
+ filters: mem0Filters(mergedScope, options.appId),
1843
+ page: 1,
1844
+ pageSize: 100,
1845
+ latestOnly: false,
1846
+ showExpired: true
1847
+ } : {
1848
+ filters: mem0Filters(mergedScope, options.appId),
1849
+ topK: 100,
1850
+ showExpired: true
1851
+ }));
1852
+ if (items.length === 0) {
1853
+ const searchableByQuery = await searchableMem0IdsForCleanup(options, clearFilters, searchProbes);
1854
+ const searchableIds = new Set([...searchableByQuery.values()].flatMap((ids) => [...ids]));
1855
+ const unseenSearchIds = [...searchableIds].filter((memoryId) => !deletedIds.has(memoryId));
1856
+ for (const [query, ids] of searchableByQuery) {
1857
+ const known = searchProbes.get(query) ?? /* @__PURE__ */ new Set();
1858
+ for (const memoryId of ids) known.add(memoryId);
1859
+ searchProbes.set(query, known);
1860
+ if (ids.size > 0) {
1861
+ for (const pending of pendingForClear) if (pending.text === query) observedPending.add(pending);
1862
+ }
1863
+ }
1864
+ if (unseenSearchIds.length > 0) {
1865
+ for (const memoryId of unseenSearchIds) {
1866
+ await options.client.delete(memoryId);
1867
+ deletedIds.add(memoryId);
1868
+ }
1869
+ batch += 1;
1870
+ visibilityDeadline = Date.now() + visibilityTimeoutMs;
1871
+ continue;
1872
+ }
1873
+ const unresolvedPending = pendingForClear.some((pending) => !observedPending.has(pending));
1874
+ const needsQuietWindow = pendingForClear.some((pending) => pending.providerIds.size === 0);
1875
+ const remainingMs = visibilityDeadline - Date.now();
1876
+ if (remainingMs <= 0) {
1877
+ if (!unresolvedPending && searchableIds.size === 0) {
1878
+ removeMem0PendingWrites(pendingWrites, pendingForClear);
1879
+ return;
1880
+ }
1881
+ if (searchableIds.size > 0) throw new Error(`${id}: deleted Mem0 memories remained searchable after ${visibilityTimeoutMs}ms`);
1882
+ throw new Error(`${id}: a Mem0 write never became visible for exact cleanup within ${visibilityTimeoutMs}ms`);
1883
+ }
1884
+ if (!unresolvedPending && searchableIds.size === 0 && !needsQuietWindow) {
1885
+ removeMem0PendingWrites(pendingWrites, pendingForClear);
1886
+ return;
1887
+ }
1888
+ await sleep(Math.min(pollIntervalMs, remainingMs));
1889
+ continue;
1890
+ }
1891
+ const rawIds = items.map((item) => stringField$1(item, ["id"]));
1892
+ if (!rawIds.every((memoryId) => memoryId !== void 0)) throw new Error(`${id}: Mem0 returned memories without ids during scoped clear`);
1893
+ const ids = [...new Set(rawIds)];
1894
+ for (const item of items) {
1895
+ const memoryId = stringField$1(item, ["id"]);
1896
+ const text = stringField$1(item, [
1897
+ "memory",
1898
+ "text",
1899
+ "content"
1900
+ ]) ?? stringField$1(recordField(item, "data"), [
1901
+ "memory",
1902
+ "text",
1903
+ "content"
1904
+ ]);
1905
+ if (!text) continue;
1906
+ const idsForText = searchProbes.get(text) ?? /* @__PURE__ */ new Set();
1907
+ idsForText.add(memoryId);
1908
+ searchProbes.set(text, idsForText);
1909
+ for (const pending of pendingForClear) if (pending.text === text || pending.providerIds.has(memoryId)) observedPending.add(pending);
1910
+ }
1911
+ const unseenIds = ids.filter((memoryId) => !deletedIds.has(memoryId));
1912
+ if (unseenIds.length === 0) {
1913
+ const remainingMs = visibilityDeadline - Date.now();
1914
+ if (remainingMs <= 0) throw new Error(`${id}: deleted Mem0 memories remained visible after ${visibilityTimeoutMs}ms`);
1915
+ await sleep(Math.min(pollIntervalMs, remainingMs));
1916
+ continue;
1917
+ }
1918
+ for (const memoryId of unseenIds) {
1919
+ await options.client.delete(memoryId);
1920
+ deletedIds.add(memoryId);
1921
+ }
1922
+ batch += 1;
1923
+ visibilityDeadline = Date.now() + visibilityTimeoutMs;
1924
+ }
1925
+ throw new Error(`${id}: refused to clear more than ${deletedIds.size} Mem0 memories in one call`);
1926
+ }
1927
+ throw new Error(`${id}: exact scoped clear requires Mem0 getAll plus per-memory delete`);
1928
+ }
1929
+ };
1930
+ return adapter;
1931
+ }
1932
+ function assertMem0Options(options) {
1933
+ if (options.id !== void 0 && (typeof options.id !== "string" || !options.id.trim())) throw new Error("Mem0 id must be a non-empty string");
1934
+ if (options.appId !== void 0 && (typeof options.appId !== "string" || !options.appId.trim())) throw new Error("Mem0 appId must be a non-empty string");
1935
+ if (options.backendRef !== void 0 && (typeof options.backendRef !== "string" || !options.backendRef.trim())) throw new Error("Mem0 backendRef must be a non-empty string");
1936
+ 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`);
1937
+ }
1938
+ function assertMem0SearchOptions(options, adapterId) {
1939
+ 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`);
1940
+ if (options.minScore !== void 0 && !Number.isFinite(options.minScore)) throw new Error(`${adapterId}: minScore must be finite`);
1941
+ }
1942
+ function assertMem0ProviderScope(scope, mode, adapterId, operation) {
1943
+ if (scope.userId || scope.agentId || scope.runId || scope.namespace) return;
1944
+ if (operation === "clear") throw new Error(`${adapterId}: refusing an unscoped Mem0 clear; provide scope.userId, scope.agentId, scope.runId, or scope.namespace`);
1945
+ throw new Error(`${adapterId}: Mem0 ${mode} ${operation} requires scope.userId, scope.agentId, scope.runId, or scope.namespace`);
1946
+ }
1947
+ function normalizeMem0Hits(raw, adapterId, options) {
1948
+ const limit = options.limit ?? Number.POSITIVE_INFINITY;
1949
+ const kinds = options.kinds && options.kinds.length > 0 ? new Set(options.kinds) : null;
1950
+ return extractMem0Items(raw).map((item) => {
1951
+ const text = stringField$1(item, [
1952
+ "memory",
1953
+ "text",
1954
+ "content"
1955
+ ]) ?? stringField$1(recordField(item, "data"), [
1956
+ "memory",
1957
+ "text",
1958
+ "content"
1959
+ ]);
1960
+ if (!text) return void 0;
1961
+ const metadata = recordField(item, "metadata");
1962
+ const kind = mem0Kind(metadata?.memoryKind ?? item?.memoryType);
1963
+ if (kinds && !kinds.has(kind)) return void 0;
1964
+ const score = numberField$1(item, [
1965
+ "rerankScore",
1966
+ "rerank_score",
1967
+ "score"
1968
+ ]);
1969
+ if (options.minScore !== void 0 && (score === void 0 || score < options.minScore)) return;
1970
+ const memoryId = stringField$1(item, ["id"]) ?? stableId("mem", canonicalJson(compactRecord({
1971
+ adapterId,
1972
+ text,
1973
+ metadata,
1974
+ item
1975
+ })));
1976
+ const normalizedScore = score !== void 0 && score >= 0 && score <= 1 ? score : void 0;
1977
+ return {
1978
+ id: memoryId,
1979
+ uri: `memory://${adapterId}/${encodeURIComponent(memoryId)}`,
1980
+ kind,
1981
+ text,
1982
+ ...stringField$1(metadata, ["memoryTitle"]) ? { title: String(metadata.memoryTitle) } : {},
1983
+ ...score !== void 0 ? { score } : {},
1984
+ ...normalizedScore !== void 0 ? { normalizedScore } : {},
1985
+ ...dateField(item, ["createdAt", "created_at"]) ? { createdAt: dateField(item, ["createdAt", "created_at"]) } : {},
1986
+ ...dateField(item, ["expirationDate", "expiration_date"]) ? { validUntil: dateField(item, ["expirationDate", "expiration_date"]) } : {},
1987
+ metadata: compactRecord({
1988
+ provider: "mem0",
1989
+ ...metadata,
1990
+ categories: item?.categories,
1991
+ owner: item?.owner,
1992
+ runId: item?.runId,
1993
+ agentId: item?.agentId,
1994
+ userId: item?.userId
1995
+ })
1996
+ };
1997
+ }).filter((hit) => hit !== void 0).slice(0, limit);
1998
+ }
1999
+ function mergeMem0Hits(hits, limit) {
2000
+ const unique = /* @__PURE__ */ new Map();
2001
+ for (const [index, hit] of hits.entries()) {
2002
+ const key = JSON.stringify([hit.uri, hit.text]);
2003
+ const score = hit.normalizedScore ?? hit.score;
2004
+ const finiteScore = score !== void 0 && Number.isFinite(score) ? score : void 0;
2005
+ const prior = unique.get(key);
2006
+ if (!prior) unique.set(key, {
2007
+ hit,
2008
+ index,
2009
+ score: finiteScore
2010
+ });
2011
+ else if (finiteScore !== void 0 && (prior.score === void 0 || finiteScore > prior.score)) unique.set(key, {
2012
+ hit,
2013
+ index: prior.index,
2014
+ score: finiteScore
2015
+ });
2016
+ }
2017
+ 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);
2018
+ }
2019
+ function extractMem0Items(raw) {
2020
+ if (Array.isArray(raw)) return raw.filter(isRecord);
2021
+ if (!isRecord(raw)) return [];
2022
+ for (const key of [
2023
+ "results",
2024
+ "memories",
2025
+ "data"
2026
+ ]) if (Array.isArray(raw[key])) return raw[key].filter(isRecord);
2027
+ return [raw];
2028
+ }
2029
+ function mem0Event(item) {
2030
+ return (stringField$1(item, ["event"]) ?? stringField$1(recordField(item, "metadata"), ["event"]))?.toUpperCase();
2031
+ }
2032
+ function mem0Entity(scope, appId) {
2033
+ return compactStringRecord({
2034
+ userId: scope.userId,
2035
+ agentId: scope.agentId,
2036
+ appId,
2037
+ runId: scope.namespace ?? scope.runId
2038
+ });
2039
+ }
2040
+ function mem0Filters(scope, appId, kind) {
2041
+ const entity = mem0EntityFilters(scope, appId);
2042
+ const customMetadata = compactRecord({
2043
+ ...mem0CustomScopeMetadata(scope),
2044
+ memoryKind: kind
2045
+ });
2046
+ return compactRecord({
2047
+ ...entity,
2048
+ ...customMetadata
2049
+ });
2050
+ }
2051
+ function mem0ScopeMetadata(scope, appId) {
2052
+ return {
2053
+ ...mem0EntityFilters(scope, appId),
2054
+ ...mem0CustomScopeMetadata(scope)
2055
+ };
2056
+ }
2057
+ function mem0EntityFilters(scope, appId) {
2058
+ return compactStringRecord({
2059
+ user_id: scope.userId,
2060
+ agent_id: scope.agentId,
2061
+ run_id: scope.namespace ?? scope.runId,
2062
+ app_id: appId
2063
+ });
2064
+ }
2065
+ function mem0CustomScopeMetadata(scope) {
2066
+ const metadata = compactStringRecord({
2067
+ logical_run_id: scope.namespace ? scope.runId : void 0,
2068
+ tenant_id: scope.tenantId,
2069
+ team_id: scope.teamId,
2070
+ session_id: scope.sessionId
2071
+ });
2072
+ for (const [key, value] of Object.entries(scope.tags ?? {})) metadata[`tag_${key}`] = value;
2073
+ return metadata;
2074
+ }
2075
+ async function searchableMem0IdsForCleanup(options, filters, probes) {
2076
+ const searchable = /* @__PURE__ */ new Map();
2077
+ const entries = [...probes];
2078
+ for (let offset = 0; offset < entries.length; offset += 8) {
2079
+ const results = await Promise.all(entries.slice(offset, offset + 8).map(async ([query, expectedIds]) => {
2080
+ return extractMem0Items(await options.client.search(query, {
2081
+ filters,
2082
+ topK: Math.min(100, Math.max(10, expectedIds.size)),
2083
+ ...options.mode === "hosted" ? {
2084
+ latestOnly: false,
2085
+ showExpired: true
2086
+ } : {}
2087
+ })).map((item) => {
2088
+ const memoryId = stringField$1(item, ["id"]);
2089
+ if (!memoryId) throw new Error(`${options.id ?? `mem0-${options.mode}`}: Mem0 search returned a memory without an id during clear verification`);
2090
+ return memoryId;
2091
+ });
2092
+ }));
2093
+ for (const [index, ids] of results.entries()) {
2094
+ const query = entries[offset + index][0];
2095
+ searchable.set(query, new Set(ids));
2096
+ }
2097
+ }
2098
+ return searchable;
2099
+ }
2100
+ function mem0FiltersInclude(candidate, requested) {
2101
+ return Object.entries(requested).every(([key, value]) => Object.is(candidate[key], value));
2102
+ }
2103
+ function removeMem0PendingWrites(pendingWrites, removed) {
2104
+ for (const pending of removed) pendingWrites.delete(pending);
2105
+ }
2106
+ function pruneExpiredMem0PendingWrites(pendingWrites) {
2107
+ const now = Date.now();
2108
+ for (const pending of pendingWrites) if (pending.expiresAt <= now) pendingWrites.delete(pending);
2109
+ }
2110
+ function mem0Role(role) {
2111
+ return role === "assistant" || role === "tool" ? "assistant" : "user";
2112
+ }
2113
+ function mem0Kind(value) {
2114
+ switch (value) {
2115
+ case "message":
2116
+ case "entity":
2117
+ case "fact":
2118
+ case "preference":
2119
+ case "observation":
2120
+ case "reasoning-trace": return value;
2121
+ default: return "fact";
2122
+ }
2123
+ }
2124
+ function mergeScopes(base, extra) {
2125
+ return {
2126
+ ...base ?? {},
2127
+ ...extra ?? {},
2128
+ tags: {
2129
+ ...base?.tags ?? {},
2130
+ ...extra?.tags ?? {}
2131
+ }
2132
+ };
2133
+ }
2134
+ function sleep(ms) {
2135
+ return new Promise((resolve) => setTimeout(resolve, ms));
2136
+ }
2137
+ function compactRecord(input) {
2138
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
2139
+ }
2140
+ function compactStringRecord(input) {
2141
+ return Object.fromEntries(Object.entries(input).filter((entry) => entry[1] !== void 0));
2142
+ }
2143
+ function isRecord(value) {
2144
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2145
+ }
2146
+ function recordField(value, key) {
2147
+ const field = value?.[key];
2148
+ return isRecord(field) ? field : void 0;
2149
+ }
2150
+ function stringField$1(value, keys) {
2151
+ for (const key of keys) {
2152
+ const field = value?.[key];
2153
+ if (typeof field === "string" && field.length > 0) return field;
2154
+ }
2155
+ }
2156
+ function numberField$1(value, keys) {
2157
+ for (const key of keys) {
2158
+ const field = value?.[key];
2159
+ if (typeof field === "number" && Number.isFinite(field)) return field;
2160
+ }
2161
+ }
2162
+ function dateField(value, keys) {
2163
+ for (const key of keys) {
2164
+ const field = value?.[key];
2165
+ if (typeof field === "string" && field.length > 0) return field;
2166
+ if (field instanceof Date && !Number.isNaN(field.valueOf())) return field.toISOString();
2167
+ }
2168
+ }
2169
+ /** Stable identity useful for dispatch/cache keys without exposing client credentials. */
2170
+ function mem0MemoryAdapterIdentity(options) {
2171
+ if (typeof options.backendRef !== "string" || !options.backendRef.trim()) throw new Error("Mem0 backendRef must be a non-empty string");
2172
+ return `sha256:${sha256(canonicalJson(compactRecord(options)))}`;
2173
+ }
2174
+ //#endregion
2175
+ //#region src/memory/neo4j.ts
2176
+ function createNeo4jAgentMemoryAdapter(options) {
2177
+ assertNeo4jOptions(options);
2178
+ const client = options.client;
2179
+ const id = options.id ?? "neo4j-agent-memory";
2180
+ const isolatedInstance = options.branchId !== void 0;
2181
+ const adapter = {
2182
+ id,
2183
+ branchIsolation: options.branchId ? {
2184
+ mode: "instance",
2185
+ branchId: options.branchId,
2186
+ supportsLogicalScopes: false
2187
+ } : {
2188
+ mode: "unsupported",
2189
+ reason: "create a separate MemoryClient namespace per branch and pass branchId"
2190
+ },
2191
+ async search(query, searchOptions = {}) {
2192
+ assertNeo4jSearchOptions(searchOptions, id);
2193
+ return searchNeo4jMemory(client, query, searchOptions, id, options.transport, isolatedInstance);
2194
+ },
2195
+ async getContext(query, searchOptions = {}) {
2196
+ assertNeo4jSearchOptions(searchOptions, id);
2197
+ assertNeo4jSearchKinds(searchOptions.kinds, options.transport, id);
2198
+ const shortTerm = nested(client, ["shortTerm", "short_term"], {});
2199
+ const sessionId = searchOptions.scope?.sessionId;
2200
+ if (options.contextMode === "native" && options.transport !== "rest") throw new Error(`${id}: Neo4j native context requires transport="rest"`);
2201
+ if (options.contextMode === "native" && !sessionId) throw new Error(`${id}: Neo4j native context requires scope.sessionId`);
2202
+ 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`);
2203
+ if (options.contextMode === "native" && options.transport === "rest" && sessionId) {
2204
+ assertNeo4jScopeSupported(searchOptions.scope, ["sessionId"], id, "native context read", isolatedInstance);
2205
+ const conversationContext = await callRequired(shortTerm, ["getContext"], [sessionId]);
2206
+ const hits = normalizeConversationContextHits(conversationContext, searchOptions, id);
2207
+ const text = renderHits(hits);
2208
+ if (searchOptions.holdout) emitRetrievalHoldoutBypass(hits, searchOptions.holdout, holdoutBypassContext(query, searchOptions), "short-term-context");
2209
+ return {
2210
+ query,
2211
+ text,
2212
+ hits,
2213
+ sourceRecords: hits.map((hit) => memoryHitToSourceRecord(hit, { scope: searchOptions.scope })),
2214
+ metadata: {
2215
+ adapter: id,
2216
+ rawContext: conversationContext
2217
+ }
2218
+ };
2219
+ }
2220
+ return defaultGetMemoryContext(adapter, query, searchOptions);
2221
+ },
2222
+ async write(input) {
2223
+ const result = await writeNeo4jMemory(client, input, id, options.transport, isolatedInstance);
2224
+ return {
2225
+ ...result,
2226
+ sourceRecord: memoryWriteResultToSourceRecord(result, input.text, { scope: input.scope })
2227
+ };
2228
+ },
2229
+ async close() {
2230
+ await callOptional(client, ["close"], []);
2231
+ }
2232
+ };
2233
+ return adapter;
2234
+ }
2235
+ function assertNeo4jOptions(options) {
2236
+ if (typeof options.client !== "object" || options.client === null) throw new Error("Neo4j agent-memory client must be an object");
2237
+ 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");
2238
+ 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");
2239
+ if (options.contextMode !== void 0 && !["search", "native"].includes(options.contextMode)) throw new Error("Neo4j agent-memory contextMode must be search or native");
2240
+ }
2241
+ function assertNeo4jSearchOptions(options, adapterId) {
2242
+ 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`);
2243
+ if (options.minScore !== void 0 && !Number.isFinite(options.minScore)) throw new Error(`${adapterId}: minScore must be finite`);
2244
+ }
2245
+ function assertNeo4jSearchKinds(kinds, transport, adapterId) {
2246
+ if (!kinds?.length) return;
2247
+ const supported = new Set(transport === "rest" ? [
2248
+ "message",
2249
+ "observation",
2250
+ "entity"
2251
+ ] : [
2252
+ "message",
2253
+ "observation",
2254
+ "entity",
2255
+ "preference",
2256
+ "reasoning-trace"
2257
+ ]);
2258
+ const unsupported = [...new Set(kinds.filter((kind) => !supported.has(kind)))];
2259
+ if (unsupported.length > 0) throw new Error(`${adapterId}: Neo4j ${transport} cannot search memory kinds: ${unsupported.join(", ")}`);
2260
+ }
2261
+ function assertNeo4jBridgeCapability(transport, kind, adapterId) {
2262
+ if (transport === "bridge") return;
2263
+ throw new Error(`${adapterId}: Neo4j REST cannot write ${kind}; use transport="bridge" or map it to an entity/message`);
2264
+ }
2265
+ async function writeNeo4jMemory(client, input, adapterId, transport, isolatedInstance) {
2266
+ const scope = input.scope ?? {};
2267
+ const sessionId = scope.sessionId ?? input.metadata?.sessionId;
2268
+ assertNeo4jScopeSupported(input.scope, input.kind === "message" || input.kind === "observation" ? ["sessionId"] : input.kind === "reasoning-trace" ? ["sessionId", "runId"] : [], adapterId, `${input.kind} write`, isolatedInstance);
2269
+ 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`);
2270
+ let result;
2271
+ if (input.kind === "message" || input.kind === "observation") {
2272
+ if (transport === "rest" && typeof sessionId !== "string") throw new Error(`${adapterId}: Neo4j REST message writes require scope.sessionId as the conversation id`);
2273
+ const shortTerm = nested(client, ["shortTerm", "short_term"], client);
2274
+ const metadata = {
2275
+ ...input.metadata,
2276
+ agentKnowledgeKind: input.kind
2277
+ };
2278
+ result = await callRequired(shortTerm, ["addMessage"], [
2279
+ sessionId,
2280
+ neo4jMessageRole(input.role),
2281
+ input.text,
2282
+ {
2283
+ metadata,
2284
+ conversationId: sessionId
2285
+ }
2286
+ ]);
2287
+ } else if (input.kind === "entity") result = await callRequired(nested(client, ["longTerm", "long_term"], client), ["addEntity"], [
2288
+ input.entityName ?? input.title ?? input.text,
2289
+ input.entityType ?? "custom",
2290
+ neo4jEntityOptions(input)
2291
+ ]);
2292
+ else if (input.kind === "preference") {
2293
+ assertNeo4jBridgeCapability(transport, input.kind, adapterId);
2294
+ result = await callRequired(nested(client, ["longTerm", "long_term"], client), ["addPreference"], [
2295
+ input.category ?? "general",
2296
+ input.text,
2297
+ neo4jPreferenceOptions(input)
2298
+ ]);
2299
+ } else if (input.kind === "reasoning-trace") {
2300
+ const reasoning = nested(client, ["reasoning"], client);
2301
+ const conversationId = sessionId ?? scope.runId;
2302
+ if (transport === "rest") {
2303
+ if (typeof conversationId !== "string") throw new Error(`${adapterId}: Neo4j REST reasoning writes require scope.sessionId or scope.runId`);
2304
+ result = await callRequired(reasoning, ["recordStep"], [{
2305
+ conversationId,
2306
+ reasoning: input.text,
2307
+ actionTaken: stringMetadata(input.metadata, "action") ?? input.title ?? "memory",
2308
+ result: stringMetadata(input.metadata, "result") ?? stringMetadata(input.metadata, "observation") ?? stringMetadata(input.metadata, "outcome")
2309
+ }]);
2310
+ } else {
2311
+ const trace = await callRequired(reasoning, ["startTrace"], [conversationId ?? stableId("session", input.text), stringMetadata(input.metadata, "task") ?? input.title ?? input.text]);
2312
+ assertNeo4jWriteSucceeded(trace, adapterId, "startTrace");
2313
+ const traceId = idFromResult(trace);
2314
+ if (!traceId) throw new Error(`${adapterId}: Neo4j startTrace returned no trace id`);
2315
+ try {
2316
+ assertNeo4jWriteSucceeded(await callRequired(reasoning, ["addStep"], [traceId, {
2317
+ thought: input.text,
2318
+ observation: stringMetadata(input.metadata, "observation"),
2319
+ action: stringMetadata(input.metadata, "action")
2320
+ }]), adapterId, "addStep");
2321
+ } catch (error) {
2322
+ try {
2323
+ await callRequired(reasoning, ["completeTrace"], [traceId, {
2324
+ outcome: "agent-knowledge addStep failed",
2325
+ success: false
2326
+ }]);
2327
+ } catch (cleanupError) {
2328
+ throw new AggregateError([error, cleanupError], `${adapterId}: Neo4j reasoning step and trace cleanup failed`);
2329
+ }
2330
+ throw error;
2331
+ }
2332
+ result = await callRequired(reasoning, ["completeTrace"], [traceId, {
2333
+ outcome: stringMetadata(input.metadata, "outcome"),
2334
+ success: typeof input.metadata?.success === "boolean" ? input.metadata.success : void 0
2335
+ }]);
2336
+ }
2337
+ } else {
2338
+ assertNeo4jBridgeCapability(transport, input.kind, adapterId);
2339
+ result = await callRequired(nested(client, ["longTerm", "long_term"], client), ["addFact"], [
2340
+ input.subject ?? input.title ?? input.kind,
2341
+ input.predicate ?? "states",
2342
+ input.object ?? input.text
2343
+ ]);
2344
+ }
2345
+ assertNeo4jWriteSucceeded(result, adapterId, input.kind);
2346
+ const id = idFromResult(result) ?? input.id ?? stableId("mem", canonicalJson({
2347
+ adapterId,
2348
+ kind: input.kind,
2349
+ text: input.text,
2350
+ scope: input.scope ?? {}
2351
+ }));
2352
+ return {
2353
+ accepted: true,
2354
+ id,
2355
+ uri: `memory://${adapterId}/${encodeURIComponent(id)}`,
2356
+ kind: input.kind,
2357
+ metadata: {
2358
+ provider: "neo4j-agent-memory",
2359
+ transport
2360
+ }
2361
+ };
2362
+ }
2363
+ function assertNeo4jWriteSucceeded(value, adapterId, operation) {
2364
+ const response = record(value);
2365
+ if (!response) return;
2366
+ const status = stringField(response, ["status"])?.toLowerCase();
2367
+ if (response.success === false || response.ok === false || response.isError === true || status === "failed" || status === "error") {
2368
+ const nestedError = record(response.error);
2369
+ const detail = stringField(response, [
2370
+ "error",
2371
+ "message",
2372
+ "detail"
2373
+ ]) ?? (nestedError ? stringField(nestedError, ["message", "detail"]) : void 0);
2374
+ throw new Error(`${adapterId}: Neo4j ${operation} failed${detail ? `: ${detail}` : ""}`);
2375
+ }
2376
+ }
2377
+ async function searchNeo4jMemory(client, query, options, adapterId, transport, isolatedInstance) {
2378
+ const limit = options.limit ?? 10;
2379
+ if (limit === 0) return [];
2380
+ assertNeo4jSearchKinds(options.kinds, transport, adapterId);
2381
+ const onlyConversationKinds = options.kinds !== void 0 && options.kinds.length > 0 && options.kinds.every((kind) => kind === "message" || kind === "observation");
2382
+ assertNeo4jScopeSupported(options.scope, onlyConversationKinds ? ["sessionId"] : [], adapterId, "search", isolatedInstance);
2383
+ const searches = [];
2384
+ const kinds = options.kinds;
2385
+ const includeKind = (kind) => !kinds?.length || kinds.includes(kind);
2386
+ const shortTerm = nested(client, ["shortTerm", "short_term"], {});
2387
+ const longTerm = nested(client, ["longTerm", "long_term"], {});
2388
+ const reasoning = nested(client, ["reasoning"], {});
2389
+ const wantsMessages = includeKind("message") || includeKind("observation");
2390
+ const hasConversation = typeof options.scope?.sessionId === "string";
2391
+ 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`);
2392
+ if (wantsMessages && (transport === "bridge" || hasConversation)) {
2393
+ const messageKinds = options.kinds?.filter((kind) => kind === "message" || kind === "observation") ?? ["message", "observation"];
2394
+ searches.push(callRequired(shortTerm, ["searchMessages"], [query, searchMessagesOptions(options)]).then((result) => normalizeHits(result, {
2395
+ ...options,
2396
+ kinds: messageKinds
2397
+ }, adapterId)));
2398
+ }
2399
+ if (includeKind("entity")) searches.push(callRequired(longTerm, ["searchEntities"], [query, searchEntitiesOptions(options)]).then((result) => normalizeHits(result, {
2400
+ ...options,
2401
+ kinds: ["entity"]
2402
+ }, adapterId)));
2403
+ if (transport === "bridge" && includeKind("preference")) searches.push(callRequired(longTerm, ["searchPreferences"], [query, searchPreferencesOptions(options)]).then((result) => normalizeHits(result, {
2404
+ ...options,
2405
+ kinds: ["preference"]
2406
+ }, adapterId)));
2407
+ if (transport === "bridge" && includeKind("reasoning-trace")) searches.push(callRequired(reasoning, ["getSimilarTraces"], [query, similarTracesOptions(options)]).then((result) => normalizeHits(result, {
2408
+ ...options,
2409
+ kinds: ["reasoning-trace"]
2410
+ }, adapterId)));
2411
+ return mergeRankedMemoryHits(await Promise.all(searches), limit);
2412
+ }
2413
+ function holdoutBypassContext(query, options) {
2414
+ return {
2415
+ query,
2416
+ ...options.scope !== void 0 ? { scope: options.scope } : {},
2417
+ ...options.scope?.tags?.taskId !== void 0 ? { taskId: options.scope.tags.taskId } : {}
2418
+ };
2419
+ }
2420
+ function searchMessagesOptions(options) {
2421
+ return {
2422
+ limit: options.limit,
2423
+ sessionId: options.scope?.sessionId,
2424
+ conversationId: options.scope?.sessionId,
2425
+ threshold: options.minScore
2426
+ };
2427
+ }
2428
+ function searchEntitiesOptions(options) {
2429
+ return {
2430
+ limit: options.limit,
2431
+ type: options.metadata?.type
2432
+ };
2433
+ }
2434
+ function searchPreferencesOptions(options) {
2435
+ return {
2436
+ limit: options.limit,
2437
+ category: options.metadata?.category
2438
+ };
2439
+ }
2440
+ function similarTracesOptions(options) {
2441
+ return {
2442
+ limit: options.limit,
2443
+ successOnly: options.metadata?.successOnly
2444
+ };
2445
+ }
2446
+ function assertNeo4jScopeSupported(scope, allowedKeys, adapterId, operation, isolatedInstance) {
2447
+ if (isolatedInstance || !scope) return;
2448
+ const allowed = new Set(allowedKeys);
2449
+ const supplied = [
2450
+ "tenantId",
2451
+ "userId",
2452
+ "agentId",
2453
+ "teamId",
2454
+ "runId",
2455
+ "sessionId",
2456
+ "namespace"
2457
+ ].filter((key) => scope[key] !== void 0);
2458
+ if (scope.tags && Object.keys(scope.tags).length > 0) supplied.push("tags");
2459
+ const unsupported = supplied.filter((key) => !allowed.has(key));
2460
+ if (unsupported.length === 0) return;
2461
+ throw new Error(`${adapterId}: Neo4j ${operation} cannot enforce scope fields: ${unsupported.join(", ")}; create a dedicated client and pass branchId`);
2462
+ }
2463
+ function neo4jEntityOptions(input) {
2464
+ return { description: input.metadata?.description ?? input.title };
2465
+ }
2466
+ function neo4jPreferenceOptions(input) {
2467
+ return { context: input.metadata?.context ?? input.title };
2468
+ }
2469
+ async function callOptional(target, names, args) {
2470
+ for (const name of names) {
2471
+ const fn = target[name];
2472
+ if (typeof fn === "function") return await fn.apply(target, args);
2473
+ }
2474
+ }
2475
+ async function callRequired(target, names, args) {
2476
+ for (const name of names) {
2477
+ const fn = target[name];
2478
+ if (typeof fn === "function") return await fn.apply(target, args);
2479
+ }
2480
+ throw new Error(`Neo4j agent-memory client is missing method ${names.join(" or ")}`);
2481
+ }
2482
+ function nested(target, names, fallback) {
2483
+ for (const name of names) {
2484
+ const value = target[name];
2485
+ if (value && typeof value === "object") return value;
2486
+ }
2487
+ return fallback;
2488
+ }
2489
+ function normalizeHits(value, options, adapterId) {
2490
+ 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);
2491
+ }
2492
+ function normalizeHit(value, index, adapterId) {
2493
+ if (typeof value === "string") return {
2494
+ id: stableId("mem", value),
2495
+ uri: `memory://${adapterId}/${stableId("mem", value)}`,
2496
+ kind: "fact",
2497
+ text: value,
2498
+ normalizedScore: index === 0 ? 1 : void 0
2499
+ };
2500
+ const obj = record(value);
2501
+ if (!obj) return null;
2502
+ const kind = memoryKind(stringField(record(obj.metadata) ?? {}, ["agentKnowledgeKind"]) ?? stringField(obj, [
2503
+ "kind",
2504
+ "type",
2505
+ "label"
2506
+ ]), obj);
2507
+ const text = textFromHitObject(obj, kind);
2508
+ if (!text) return null;
2509
+ const id = stringField(obj, [
2510
+ "id",
2511
+ "memoryId",
2512
+ "uuid"
2513
+ ]) ?? stableId("mem", text);
2514
+ return {
2515
+ id,
2516
+ uri: stringField(obj, ["uri"]) ?? `memory://${adapterId}/${encodeURIComponent(id)}`,
2517
+ kind,
2518
+ text,
2519
+ title: stringField(obj, [
2520
+ "title",
2521
+ "name",
2522
+ "task",
2523
+ "subject"
2524
+ ]),
2525
+ score: numberField(obj, ["score"]),
2526
+ normalizedScore: numberField(obj, ["normalizedScore", "normalized_score"]),
2527
+ confidence: numberField(obj, ["confidence"]),
2528
+ createdAt: stringField(obj, ["createdAt", "created_at"]),
2529
+ validUntil: stringField(obj, ["validUntil", "valid_until"]),
2530
+ lastVerifiedAt: stringField(obj, ["lastVerifiedAt", "last_verified_at"]),
2531
+ metadata: obj
2532
+ };
2533
+ }
2534
+ function memoryKind(value, obj = {}) {
2535
+ if (value === "message" || value === "entity" || value === "fact" || value === "preference" || value === "observation" || value === "reasoning-trace") return value;
2536
+ if ("role" in obj && "content" in obj) return "message";
2537
+ if ("name" in obj && ("type" in obj || "entityType" in obj)) return "entity";
2538
+ if ("preference" in obj && "category" in obj) return "preference";
2539
+ if ("task" in obj && "steps" in obj) return "reasoning-trace";
2540
+ return "fact";
2541
+ }
2542
+ function rawHitsFrom(value) {
2543
+ if (Array.isArray(value)) return value;
2544
+ const obj = record(value);
2545
+ if (!obj) return typeof value === "string" ? [value] : [];
2546
+ if (Array.isArray(obj.hits)) return obj.hits;
2547
+ if (Array.isArray(obj.results)) return obj.results;
2548
+ if (Array.isArray(obj.messages)) return obj.messages;
2549
+ if (Array.isArray(obj.recentMessages)) return obj.recentMessages;
2550
+ if (Array.isArray(obj.observations)) return obj.observations;
2551
+ if (Array.isArray(obj.reflections)) return obj.reflections;
2552
+ if (typeof obj.content === "string" || typeof obj.text === "string") return [obj];
2553
+ return [];
2554
+ }
2555
+ function textFromHitObject(obj, kind) {
2556
+ const direct = stringField(obj, [
2557
+ "text",
2558
+ "content",
2559
+ "body",
2560
+ "summary",
2561
+ "preference",
2562
+ "description"
2563
+ ]);
2564
+ if (direct) return direct;
2565
+ if (kind === "entity") return stringField(obj, ["name"]);
2566
+ if (kind === "fact") {
2567
+ const subject = stringField(obj, ["subject"]);
2568
+ const predicate = stringField(obj, ["predicate"]);
2569
+ const object = stringField(obj, ["object", "obj"]);
2570
+ if (subject && predicate && object) return `${subject} ${predicate} ${object}`;
2571
+ }
2572
+ if (kind === "reasoning-trace") return [stringField(obj, ["task"]), stringField(obj, ["outcome"])].filter(Boolean).join("\n") || void 0;
2573
+ }
2574
+ function normalizeConversationContextHits(value, options, adapterId) {
2575
+ const obj = record(value);
2576
+ if (!obj) return normalizeHits(value, options, adapterId);
2577
+ const raw = [
2578
+ ...rawHitsFrom(obj.reflections).map((hit) => tagContextHit(hit, "observation", "Reflection")),
2579
+ ...rawHitsFrom(obj.observations).map((hit) => tagContextHit(hit, "observation", "Observation")),
2580
+ ...rawHitsFrom(obj.recentMessages).map((hit) => tagContextHit(hit, "message"))
2581
+ ];
2582
+ return raw.length > 0 ? normalizeHits(raw, options, adapterId) : normalizeHits(value, options, adapterId);
2583
+ }
2584
+ function tagContextHit(value, kind, title) {
2585
+ return value && typeof value === "object" && !Array.isArray(value) ? {
2586
+ kind,
2587
+ title,
2588
+ ...value
2589
+ } : {
2590
+ kind,
2591
+ title,
2592
+ content: value
2593
+ };
2594
+ }
2595
+ function renderHits(hits) {
2596
+ return hits.map((hit) => hit.text).join("\n\n");
2597
+ }
2598
+ function idFromResult(value) {
2599
+ const obj = record(value);
2600
+ return obj ? stringField(obj, [
2601
+ "id",
2602
+ "memoryId",
2603
+ "uuid"
2604
+ ]) : void 0;
2605
+ }
2606
+ function record(value) {
2607
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
2608
+ }
2609
+ function stringField(obj, names) {
2610
+ for (const name of names) {
2611
+ const value = obj[name];
2612
+ if (typeof value === "string" && value.length > 0) return value;
2613
+ }
2614
+ }
2615
+ function numberField(obj, names) {
2616
+ for (const name of names) {
2617
+ const value = obj[name];
2618
+ if (typeof value === "number" && Number.isFinite(value)) return value;
2619
+ }
2620
+ }
2621
+ function neo4jMessageRole(role) {
2622
+ return role === "tool" ? "assistant" : role ?? "user";
2623
+ }
2624
+ function stringMetadata(metadata, key) {
2625
+ const value = metadata?.[key];
2626
+ return typeof value === "string" && value.length > 0 ? value : void 0;
2627
+ }
2628
+ //#endregion
2629
+ //#region src/memory/tools.ts
2630
+ const publications = /* @__PURE__ */ new WeakMap();
2631
+ function createPlayMemoryTools(options) {
2632
+ if (!options.actorId?.trim()) throw new Error("play memory tools require a trusted actorId");
2633
+ if (typeof options.onCheckpoint !== "function") throw new Error("play memory tools require durable checkpoint publication");
2634
+ const maxTextChars = options.maxTextChars ?? 8e3;
2635
+ const maxResults = options.maxResults ?? 20;
2636
+ if (!Number.isSafeInteger(maxTextChars) || maxTextChars < 1 || maxTextChars > 1e5 || !Number.isSafeInteger(maxResults) || maxResults < 1 || maxResults > 100) throw new Error("invalid play memory tool bounds");
2637
+ const prefix = options.namePrefix ?? "";
2638
+ if (!/^[a-zA-Z0-9_-]*$/.test(prefix)) throw new Error("invalid memory tool namePrefix");
2639
+ const recall = z.strictObject({
2640
+ question: z.string().trim().min(1).max(8e3),
2641
+ limit: z.int().min(1).max(maxResults).optional()
2642
+ });
2643
+ const record = z.strictObject({
2644
+ id: z.string().trim().min(1).max(256),
2645
+ kind: AgentMemoryKindSchema,
2646
+ text: z.string().min(1).max(maxTextChars),
2647
+ title: z.string().max(512).optional(),
2648
+ sourceRefs: z.array(z.string().min(1).max(2048)).max(50).optional()
2649
+ });
2650
+ return [tool(`${prefix}memory_recall`, "Recall memory visible to this play. Returned memories are attributed provider evidence, not independently verified findings.", recall, async (input) => {
2651
+ const context = await options.branch.getContext(input.question, { limit: input.limit ?? maxResults });
2652
+ const content = {
2653
+ branchId: options.branch.branchId,
2654
+ adapterId: options.branch.id,
2655
+ actorId: options.actorId,
2656
+ query: input.question,
2657
+ context
2658
+ };
2659
+ const receipt = {
2660
+ ...content,
2661
+ receiptDigest: `sha256:${sha256(canonicalMemoryJson(content))}`
2662
+ };
2663
+ await options.recordRetrieval?.(receipt);
2664
+ return {
2665
+ ...context,
2666
+ receipt
2667
+ };
2668
+ }), tool(`${prefix}memory_record`, "Retain a sourced observation in this play and checkpoint its accepted input. Reuse the same id and bytes after an ambiguous response.", record, async (input) => {
2669
+ const write = {
2670
+ id: input.id,
2671
+ kind: input.kind,
2672
+ text: input.text,
2673
+ ...input.title === void 0 ? {} : { title: input.title },
2674
+ metadata: {
2675
+ actorId: options.actorId,
2676
+ sourceRefs: input.sourceRefs ?? []
2677
+ }
2678
+ };
2679
+ let state = publications.get(options.branch);
2680
+ if (!state) {
2681
+ state = { tail: Promise.resolve() };
2682
+ publications.set(options.branch, state);
2683
+ }
2684
+ const shared = state;
2685
+ const publication = shared.tail.catch(() => void 0).then(async () => {
2686
+ if (shared.pending && canonicalMemoryJson(shared.pending.input) !== canonicalMemoryJson(write)) throw new Error("reconcile the prior memory write with its same id, bytes, and actor before another write");
2687
+ if (!shared.pending) {
2688
+ const existing = (await options.branch.snapshot()).journal.find((entry) => entry.input.id === input.id);
2689
+ if (existing && canonicalMemoryJson(existing.input) !== canonicalMemoryJson(write)) throw new Error("memory write id already names different content or attribution");
2690
+ shared.pending = {
2691
+ input: write,
2692
+ ...existing ? { result: existing.result } : {}
2693
+ };
2694
+ }
2695
+ const result = shared.pending.result ?? await options.branch.write(write);
2696
+ shared.pending.result = result;
2697
+ if (!result.accepted) {
2698
+ shared.pending = void 0;
2699
+ return {
2700
+ ...result,
2701
+ checkpointDigest: null
2702
+ };
2703
+ }
2704
+ const snapshot = await options.branch.snapshot();
2705
+ await options.onCheckpoint(snapshot);
2706
+ shared.pending = void 0;
2707
+ return {
2708
+ ...result,
2709
+ checkpointDigest: snapshot.digest
2710
+ };
2711
+ });
2712
+ shared.tail = publication;
2713
+ return publication;
2714
+ })];
2715
+ }
2716
+ function tool(name, description, inputSchema, handler) {
2717
+ return {
2718
+ name,
2719
+ description,
2720
+ inputSchema,
2721
+ inputSchemaJson: z.toJSONSchema(inputSchema),
2722
+ handler: async (input) => handler(inputSchema.parse(input))
2723
+ };
2724
+ }
2725
+ //#endregion
2726
+ export { resetRetrievalHoldoutRegistry as A, forkAgentMemoryBranchSnapshot as C, applySessionStickyRetrievalHoldout as D, applyRetrievalHoldout as E, toOffPolicyTrajectory as M, deterministicRng as O, createAgentMemoryBranch as S, renderMemoryContext as T, resolveMemoryCleanupTimeoutMs as _, HindsightOperationUnknownError as a, createGraphitiMemoryAdapter as b, AgentMemoryHitSchema as c, AgentMemoryWriteInputSchema as d, AgentMemoryLifecycleTimeoutError as f, memoryRecoveryDelayMs as g, createMemoryExecutionPool as h, mem0MemoryAdapterIdentity as i, retrievalHoldoutConfigHash as j, emitRetrievalHoldoutBypass as k, AgentMemoryKindSchema as l, DEFAULT_MEMORY_CLEANUP_TIMEOUT_MS as m, createNeo4jAgentMemoryAdapter as n, createHindsightMemoryAdapter as o, AgentMemoryLifecycleUnsafeError as p, createMem0MemoryAdapter as r, hindsightMemoryBankId as s, createPlayMemoryTools as t, AgentMemoryScopeSchema as u, runBoundedMemoryLifecycle as v, defaultGetMemoryContext as w, graphitiMemoryAdapterIdentity as x, sleepForMemoryRecovery as y };
2727
+
2728
+ //# sourceMappingURL=memory-BH6oT1Qe.js.map