@mastra/memory 1.3.0 → 1.4.0-alpha.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 (31) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/{chunk-F5P5HTMC.js → chunk-D4AWAGLM.js} +270 -203
  3. package/dist/chunk-D4AWAGLM.js.map +1 -0
  4. package/dist/{chunk-LXATBJ2L.cjs → chunk-QRKB5I2S.cjs} +270 -203
  5. package/dist/chunk-QRKB5I2S.cjs.map +1 -0
  6. package/dist/docs/SKILL.md +1 -1
  7. package/dist/docs/assets/SOURCE_MAP.json +25 -25
  8. package/dist/docs/references/reference-memory-observational-memory.md +36 -0
  9. package/dist/index.cjs +7 -5
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +7 -5
  13. package/dist/index.js.map +1 -1
  14. package/dist/{observational-memory-3DA7KJIH.js → observational-memory-53AFLLSH.js} +3 -3
  15. package/dist/{observational-memory-3DA7KJIH.js.map → observational-memory-53AFLLSH.js.map} +1 -1
  16. package/dist/{observational-memory-SA5RITIG.cjs → observational-memory-UCOMAMSF.cjs} +17 -17
  17. package/dist/{observational-memory-SA5RITIG.cjs.map → observational-memory-UCOMAMSF.cjs.map} +1 -1
  18. package/dist/processors/index.cjs +15 -15
  19. package/dist/processors/index.js +1 -1
  20. package/dist/processors/observational-memory/observational-memory.d.ts +6 -1
  21. package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
  22. package/dist/processors/observational-memory/observer-agent.d.ts +2 -2
  23. package/dist/processors/observational-memory/observer-agent.d.ts.map +1 -1
  24. package/dist/processors/observational-memory/reflector-agent.d.ts +5 -3
  25. package/dist/processors/observational-memory/reflector-agent.d.ts.map +1 -1
  26. package/dist/processors/observational-memory/token-counter.d.ts.map +1 -1
  27. package/dist/processors/observational-memory/types.d.ts +10 -0
  28. package/dist/processors/observational-memory/types.d.ts.map +1 -1
  29. package/package.json +6 -6
  30. package/dist/chunk-F5P5HTMC.js.map +0 -1
  31. package/dist/chunk-LXATBJ2L.cjs.map +0 -1
@@ -11,54 +11,6 @@ import o200k_base from 'js-tiktoken/ranks/o200k_base';
11
11
  // src/processors/observational-memory/observational-memory.ts
12
12
 
13
13
  // src/processors/observational-memory/observer-agent.ts
14
- var LEGACY_OBSERVER_EXTRACTION_INSTRUCTIONS = `CRITICAL: DISTINGUISH USER ASSERTIONS FROM QUESTIONS
15
-
16
- When the user TELLS you something about themselves, mark it as an assertion:
17
- - "I have two kids" \u2192 \u{1F534} (14:30) User stated has two kids
18
- - "I work at Acme Corp" \u2192 \u{1F534} (14:31) User stated works at Acme Corp
19
- - "I graduated in 2019" \u2192 \u{1F534} (14:32) User stated graduated in 2019
20
-
21
- When the user ASKS about something, mark it as a question/request:
22
- - "Can you help me with X?" \u2192 \u{1F7E1} (15:00) User asked help with X
23
- - "What's the best way to do Y?" \u2192 \u{1F7E1} (15:01) User asked best way to do Y
24
-
25
- USER ASSERTIONS ARE AUTHORITATIVE. The user is the source of truth about their own life.
26
- If a user previously stated something and later asks a question about the same topic,
27
- the assertion is the answer - the question doesn't invalidate what they already told you.
28
-
29
- TEMPORAL ANCHORING:
30
- Convert relative times to estimated dates based on the message timestamp.
31
- Include the user's original phrasing in quotes, then add an estimated date or range.
32
- Ranges may span multiple months - e.g., "within the last month" on July 15th could mean anytime in June to early July.
33
-
34
- BAD: User was given X by their friend last month.
35
- GOOD: User was given X by their friend "last month" (estimated mid-June to early July 202X).
36
-
37
- PRESERVE UNUSUAL PHRASING:
38
- When the user uses unexpected or non-standard terminology, quote their exact words.
39
-
40
- BAD: User exercised.
41
- GOOD: User stated they did a "movement session" (their term for exercise).
42
-
43
- CONVERSATION CONTEXT:
44
- - What the user is working on or asking about
45
- - Previous topics and their outcomes
46
- - What user understands or needs clarification on
47
- - Specific requirements or constraints mentioned
48
- - Contents of assistant learnings and summaries
49
- - Answers to users questions including full context to remember detailed summaries and explanations
50
- - Assistant explanations, especially complex ones. observe the fine details so that the assistant does not forget what they explained
51
- - Relevant code snippets
52
- - User preferences (like favourites, dislikes, preferences, etc)
53
- - Any specifically formatted text or ascii that would need to be reproduced or referenced in later interactions (preserve these verbatim in memory)
54
- - Any blocks of any text which the user and assistant are iteratively collaborating back and forth on should be preserved verbatim
55
- - When who/what/where/when is mentioned, note that in the observation. Example: if the user received went on a trip with someone, observe who that someone was, where the trip was, when it happened, and what happened, not just that the user went on the trip.
56
-
57
- ACTIONABLE INSIGHTS:
58
- - What worked well in explanations
59
- - What needs follow-up or clarification
60
- - User's stated goals or next steps (note if the user tells you not to do a next step, or asks for something specific, other next steps besides the users request should be marked as "waiting for user", unless the user explicitly says to continue all next steps)`;
61
- var USE_LEGACY_PROMPT = process.env.OM_USE_LEGACY_PROMPT === "1" || process.env.OM_USE_LEGACY_PROMPT === "true";
62
14
  var USE_CONDENSED_PROMPT = process.env.OM_USE_CONDENSED_PROMPT === "1" || process.env.OM_USE_CONDENSED_PROMPT === "true";
63
15
  var CONDENSED_OBSERVER_EXTRACTION_INSTRUCTIONS = `You are the memory consciousness of an AI assistant. Your observations will be the ONLY information the assistant has about past interactions with this user.
64
16
 
@@ -301,7 +253,7 @@ ACTIONABLE INSIGHTS:
301
253
  - What worked well in explanations
302
254
  - What needs follow-up or clarification
303
255
  - User's stated goals or next steps (note if the user tells you not to do a next step, or asks for something specific, other next steps besides the users request should be marked as "waiting for user", unless the user explicitly says to continue all next steps)`;
304
- var OBSERVER_EXTRACTION_INSTRUCTIONS = USE_CONDENSED_PROMPT ? CONDENSED_OBSERVER_EXTRACTION_INSTRUCTIONS : USE_LEGACY_PROMPT ? LEGACY_OBSERVER_EXTRACTION_INSTRUCTIONS : CURRENT_OBSERVER_EXTRACTION_INSTRUCTIONS;
256
+ var OBSERVER_EXTRACTION_INSTRUCTIONS = USE_CONDENSED_PROMPT ? CONDENSED_OBSERVER_EXTRACTION_INSTRUCTIONS : CURRENT_OBSERVER_EXTRACTION_INSTRUCTIONS;
305
257
  var CONDENSED_OBSERVER_OUTPUT_FORMAT = `Use priority levels:
306
258
  - \u{1F534} High: explicit user facts, preferences, goals achieved, critical context
307
259
  - \u{1F7E1} Medium: project details, learned information, tool results
@@ -402,7 +354,7 @@ var OBSERVER_GUIDELINES = USE_CONDENSED_PROMPT ? CONDENSED_OBSERVER_GUIDELINES :
402
354
  - Make sure you start each observation with a priority emoji (\u{1F534}, \u{1F7E1}, \u{1F7E2})
403
355
  - Observe WHAT the agent did and WHAT it means, not HOW well it did it.
404
356
  - If the user provides detailed messages or code snippets, observe all important details.`;
405
- function buildObserverSystemPrompt(multiThread = false) {
357
+ function buildObserverSystemPrompt(multiThread = false, instruction) {
406
358
  const outputFormat = USE_CONDENSED_PROMPT ? CONDENSED_OBSERVER_OUTPUT_FORMAT : OBSERVER_OUTPUT_FORMAT_BASE;
407
359
  if (multiThread) {
408
360
  return `You are the memory consciousness of an AI assistant. Your observations will be the ONLY information the assistant has about past interactions with this user.
@@ -460,7 +412,11 @@ ${OBSERVER_GUIDELINES}
460
412
 
461
413
  Remember: These observations are the assistant's ONLY memory. Make them count.
462
414
 
463
- User messages are extremely important. If the user asks a question or gives a new task, make it clear in <current-task> that this is the priority.`;
415
+ User messages are extremely important. If the user asks a question or gives a new task, make it clear in <current-task> that this is the priority.${instruction ? `
416
+
417
+ === CUSTOM INSTRUCTIONS ===
418
+
419
+ ${instruction}` : ""}`;
464
420
  }
465
421
  return `You are the memory consciousness of an AI assistant. Your observations will be the ONLY information the assistant has about past interactions with this user.
466
422
 
@@ -486,7 +442,11 @@ Simply output your observations without any thread-related markup.
486
442
 
487
443
  Remember: These observations are the assistant's ONLY memory. Make them count.
488
444
 
489
- User messages are extremely important. If the user asks a question or gives a new task, make it clear in <current-task> that this is the priority. If the assistant needs to respond to the user, indicate in <suggested-response> that it should pause for user reply before continuing other tasks.`;
445
+ User messages are extremely important. If the user asks a question or gives a new task, make it clear in <current-task> that this is the priority. If the assistant needs to respond to the user, indicate in <suggested-response> that it should pause for user reply before continuing other tasks.${instruction ? `
446
+
447
+ === CUSTOM INSTRUCTIONS ===
448
+
449
+ ${instruction}` : ""}`;
490
450
  }
491
451
  var OBSERVER_SYSTEM_PROMPT = buildObserverSystemPrompt();
492
452
  function formatMessagesForObserver(messages, options) {
@@ -519,7 +479,7 @@ ${maybeTruncate(resultStr, maxLen)}`;
519
479
  return `[Tool Call: ${inv.toolName}]
520
480
  ${maybeTruncate(argsStr, maxLen)}`;
521
481
  }
522
- if (part.type?.startsWith("data-om-observation-")) return "";
482
+ if (part.type?.startsWith("data-")) return "";
523
483
  return "";
524
484
  }).filter(Boolean).join("\n");
525
485
  } else if (msg.content?.content) {
@@ -752,7 +712,7 @@ function optimizeObservationsForContext(observations) {
752
712
  }
753
713
 
754
714
  // src/processors/observational-memory/reflector-agent.ts
755
- function buildReflectorSystemPrompt() {
715
+ function buildReflectorSystemPrompt(instruction) {
756
716
  return `You are the memory consciousness of an AI assistant. Your memory observation reflections will be the ONLY information the assistant has about past interactions with this user.
757
717
 
758
718
  The following instructions were given to another part of your psyche (the observer) to create memories.
@@ -845,7 +805,11 @@ Hint for the agent's immediate next message. Examples:
845
805
  - Call the view tool on src/example.ts to continue debugging.
846
806
  </suggested-response>
847
807
 
848
- User messages are extremely important. If the user asks a question or gives a new task, make it clear in <current-task> that this is the priority. If the assistant needs to respond to the user, indicate in <suggested-response> that it should pause for user reply before continuing other tasks.`;
808
+ User messages are extremely important. If the user asks a question or gives a new task, make it clear in <current-task> that this is the priority. If the assistant needs to respond to the user, indicate in <suggested-response> that it should pause for user reply before continuing other tasks.${instruction ? `
809
+
810
+ === CUSTOM INSTRUCTIONS ===
811
+
812
+ ${instruction}` : ""}`;
849
813
  }
850
814
  var COMPRESSION_GUIDANCE = {
851
815
  0: "",
@@ -877,6 +841,21 @@ Please re-process with much more aggressive compression:
877
841
  - Remove redundant information and merge overlapping observations
878
842
 
879
843
  Your current detail level was a 10/10, lets aim for a 6/10 detail level.
844
+ `,
845
+ 3: `
846
+ ## CRITICAL COMPRESSION REQUIRED
847
+
848
+ Your previous reflections have failed to compress sufficiently after multiple attempts.
849
+
850
+ Please re-process with maximum compression:
851
+ - Summarize the oldest observations (first 50-70%) into brief high-level paragraphs \u2014 only key facts, decisions, and outcomes
852
+ - For the most recent observations (last 30-50%), retain important details but still use a condensed style
853
+ - Ruthlessly merge related observations \u2014 if 10 observations are about the same topic, combine into 1-2 lines
854
+ - Drop procedural details (tool calls, retries, intermediate steps) \u2014 keep only final outcomes
855
+ - Drop observations that are no longer relevant or have been superseded by newer information
856
+ - Preserve: names, dates, decisions, errors, user preferences, and architectural choices
857
+
858
+ Your current detail level was a 10/10, lets aim for a 4/10 detail level.
880
859
  `
881
860
  };
882
861
  function buildReflectorPrompt(observations, manualPrompt, compressionLevel, skipContinuationHints) {
@@ -1017,7 +996,7 @@ var TokenCounter = class _TokenCounter {
1017
996
  `Unhandled tool-invocation state '${part.toolInvocation?.state}' in token counting for part type '${part.type}'`
1018
997
  );
1019
998
  }
1020
- } else {
999
+ } else if (typeof part.type === "string" && part.type.startsWith("data-")) ; else {
1021
1000
  tokenString += JSON.stringify(part);
1022
1001
  }
1023
1002
  }
@@ -1662,7 +1641,8 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
1662
1641
  blockAfter: asyncBufferingDisabled ? void 0 : this.resolveBlockAfter(
1663
1642
  config.observation?.blockAfter ?? (config.observation?.bufferTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferTokens ? 1.2 : void 0),
1664
1643
  config.observation?.messageTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens
1665
- )
1644
+ ),
1645
+ instruction: config.observation?.instruction
1666
1646
  };
1667
1647
  this.reflectionConfig = {
1668
1648
  model: reflectionModel,
@@ -1677,7 +1657,8 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
1677
1657
  blockAfter: asyncBufferingDisabled ? void 0 : this.resolveBlockAfter(
1678
1658
  config.reflection?.blockAfter ?? (config.reflection?.bufferActivation ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.bufferActivation ? 1.2 : void 0),
1679
1659
  config.reflection?.observationTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.observationTokens
1680
- )
1660
+ ),
1661
+ instruction: config.reflection?.instruction
1681
1662
  };
1682
1663
  this.tokenCounter = new TokenCounter();
1683
1664
  this.onDebugEvent = config.onDebugEvent;
@@ -1893,7 +1874,7 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
1893
1874
  */
1894
1875
  getObserverAgent() {
1895
1876
  if (!this.observerAgent) {
1896
- const systemPrompt = buildObserverSystemPrompt();
1877
+ const systemPrompt = buildObserverSystemPrompt(false, this.observationConfig.instruction);
1897
1878
  this.observerAgent = new Agent({
1898
1879
  id: "observational-memory-observer",
1899
1880
  name: "Observer",
@@ -1908,7 +1889,7 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
1908
1889
  */
1909
1890
  getReflectorAgent() {
1910
1891
  if (!this.reflectorAgent) {
1911
- const systemPrompt = buildReflectorSystemPrompt();
1892
+ const systemPrompt = buildReflectorSystemPrompt(this.reflectionConfig.instruction);
1912
1893
  this.reflectorAgent = new Agent({
1913
1894
  id: "observational-memory-reflector",
1914
1895
  name: "Reflector",
@@ -2139,7 +2120,11 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
2139
2120
  for (let i = allMsgs.length - 1; i >= 0; i--) {
2140
2121
  const msg = allMsgs[i];
2141
2122
  if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
2142
- msg.content.parts.push(marker);
2123
+ const markerData = marker.data;
2124
+ const alreadyPresent = markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId);
2125
+ if (!alreadyPresent) {
2126
+ msg.content.parts.push(marker);
2127
+ }
2143
2128
  try {
2144
2129
  await this.messageHistory.persistMessages({
2145
2130
  messages: [msg],
@@ -2168,7 +2153,11 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
2168
2153
  const messages = result?.messages ?? [];
2169
2154
  for (const msg of messages) {
2170
2155
  if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
2171
- msg.content.parts.push(marker);
2156
+ const markerData = marker.data;
2157
+ const alreadyPresent = markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId);
2158
+ if (!alreadyPresent) {
2159
+ msg.content.parts.push(marker);
2160
+ }
2172
2161
  await this.messageHistory.persistMessages({
2173
2162
  messages: [msg],
2174
2163
  threadId,
@@ -2395,7 +2384,8 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
2395
2384
  ...this.observationConfig.modelSettings
2396
2385
  },
2397
2386
  providerOptions: this.observationConfig.providerOptions,
2398
- ...abortSignal ? { abortSignal } : {}
2387
+ ...abortSignal ? { abortSignal } : {},
2388
+ ...options?.requestContext ? { requestContext: options.requestContext } : {}
2399
2389
  }),
2400
2390
  abortSignal
2401
2391
  );
@@ -2418,12 +2408,12 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
2418
2408
  * Returns per-thread results with observations, currentTask, and suggestedContinuation,
2419
2409
  * plus the total usage for the batch.
2420
2410
  */
2421
- async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal) {
2411
+ async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext) {
2422
2412
  const agent = new Agent({
2423
2413
  id: "multi-thread-observer",
2424
2414
  name: "multi-thread-observer",
2425
2415
  model: this.observationConfig.model,
2426
- instructions: buildObserverSystemPrompt(true)
2416
+ instructions: buildObserverSystemPrompt(true, this.observationConfig.instruction)
2427
2417
  });
2428
2418
  const prompt = buildMultiThreadObserverPrompt(existingObservations, messagesByThread, threadOrder);
2429
2419
  const allMessages = [];
@@ -2439,7 +2429,8 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
2439
2429
  ...this.observationConfig.modelSettings
2440
2430
  },
2441
2431
  providerOptions: this.observationConfig.providerOptions,
2442
- ...abortSignal ? { abortSignal } : {}
2432
+ ...abortSignal ? { abortSignal } : {},
2433
+ ...requestContext ? { requestContext } : {}
2443
2434
  }),
2444
2435
  abortSignal
2445
2436
  );
@@ -2471,68 +2462,79 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
2471
2462
  * Call the Reflector agent to condense observations.
2472
2463
  * Includes compression validation and retry logic.
2473
2464
  */
2474
- async callReflector(observations, manualPrompt, streamContext, observationTokensThreshold, abortSignal, skipContinuationHints, compressionStartLevel) {
2465
+ async callReflector(observations, manualPrompt, streamContext, observationTokensThreshold, abortSignal, skipContinuationHints, compressionStartLevel, requestContext) {
2475
2466
  const agent = this.getReflectorAgent();
2476
2467
  const originalTokens = this.tokenCounter.countObservations(observations);
2477
2468
  const targetThreshold = observationTokensThreshold ?? this.getMaxThreshold(this.reflectionConfig.observationTokens);
2478
2469
  let totalUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
2479
- const firstLevel = compressionStartLevel ?? 0;
2480
- const retryLevel = Math.min(firstLevel + 1, 2);
2481
- let prompt = buildReflectorPrompt(observations, manualPrompt, firstLevel, skipContinuationHints);
2482
- omDebug(
2483
- `[OM:callReflector] starting first attempt: originalTokens=${originalTokens}, targetThreshold=${targetThreshold}, promptLen=${prompt.length}, skipContinuationHints=${skipContinuationHints}`
2484
- );
2485
- let chunkCount = 0;
2486
- const generatePromise = agent.generate(prompt, {
2487
- modelSettings: {
2488
- ...this.reflectionConfig.modelSettings
2489
- },
2490
- providerOptions: this.reflectionConfig.providerOptions,
2491
- ...abortSignal ? { abortSignal } : {},
2492
- onChunk(chunk) {
2493
- chunkCount++;
2494
- if (chunkCount === 1 || chunkCount % 50 === 0) {
2495
- const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
2496
- omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
2497
- }
2498
- },
2499
- onFinish(event) {
2500
- omDebug(
2501
- `[OM:callReflector] onFinish: chunks=${chunkCount}, finishReason=${event.finishReason}, inputTokens=${event.usage?.inputTokens}, outputTokens=${event.usage?.outputTokens}, textLen=${event.text?.length}`
2502
- );
2503
- },
2504
- onAbort(event) {
2505
- omDebug(`[OM:callReflector] onAbort: chunks=${chunkCount}, reason=${event?.reason ?? "unknown"}`);
2506
- },
2507
- onError({ error }) {
2508
- omError(`[OM:callReflector] onError after ${chunkCount} chunks`, error);
2470
+ let currentLevel = compressionStartLevel ?? 0;
2471
+ const maxLevel = 3;
2472
+ let parsed = { observations: "", suggestedContinuation: void 0 };
2473
+ let reflectedTokens = 0;
2474
+ let attemptNumber = 0;
2475
+ while (currentLevel <= maxLevel) {
2476
+ attemptNumber++;
2477
+ const isRetry = attemptNumber > 1;
2478
+ const prompt = buildReflectorPrompt(observations, manualPrompt, currentLevel, skipContinuationHints);
2479
+ omDebug(
2480
+ `[OM:callReflector] ${isRetry ? `retry #${attemptNumber - 1}` : "first attempt"}: level=${currentLevel}, originalTokens=${originalTokens}, targetThreshold=${targetThreshold}, promptLen=${prompt.length}, skipContinuationHints=${skipContinuationHints}`
2481
+ );
2482
+ let chunkCount = 0;
2483
+ const result = await this.withAbortCheck(
2484
+ () => agent.generate(prompt, {
2485
+ modelSettings: {
2486
+ ...this.reflectionConfig.modelSettings
2487
+ },
2488
+ providerOptions: this.reflectionConfig.providerOptions,
2489
+ ...abortSignal ? { abortSignal } : {},
2490
+ ...requestContext ? { requestContext } : {},
2491
+ ...attemptNumber === 1 ? {
2492
+ onChunk(chunk) {
2493
+ chunkCount++;
2494
+ if (chunkCount === 1 || chunkCount % 50 === 0) {
2495
+ const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
2496
+ omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
2497
+ }
2498
+ },
2499
+ onFinish(event) {
2500
+ omDebug(
2501
+ `[OM:callReflector] onFinish: chunks=${chunkCount}, finishReason=${event.finishReason}, inputTokens=${event.usage?.inputTokens}, outputTokens=${event.usage?.outputTokens}, textLen=${event.text?.length}`
2502
+ );
2503
+ },
2504
+ onAbort(event) {
2505
+ omDebug(`[OM:callReflector] onAbort: chunks=${chunkCount}, reason=${event?.reason ?? "unknown"}`);
2506
+ },
2507
+ onError({ error }) {
2508
+ omError(`[OM:callReflector] onError after ${chunkCount} chunks`, error);
2509
+ }
2510
+ } : {}
2511
+ }),
2512
+ abortSignal
2513
+ );
2514
+ omDebug(
2515
+ `[OM:callReflector] attempt #${attemptNumber} returned: textLen=${result.text?.length}, textPreview="${result.text?.slice(0, 120)}...", inputTokens=${result.usage?.inputTokens ?? result.totalUsage?.inputTokens}, outputTokens=${result.usage?.outputTokens ?? result.totalUsage?.outputTokens}`
2516
+ );
2517
+ const usage = result.totalUsage ?? result.usage;
2518
+ if (usage) {
2519
+ totalUsage.inputTokens += usage.inputTokens ?? 0;
2520
+ totalUsage.outputTokens += usage.outputTokens ?? 0;
2521
+ totalUsage.totalTokens += usage.totalTokens ?? 0;
2522
+ }
2523
+ parsed = parseReflectorOutput(result.text);
2524
+ reflectedTokens = this.tokenCounter.countObservations(parsed.observations);
2525
+ omDebug(
2526
+ `[OM:callReflector] attempt #${attemptNumber} parsed: reflectedTokens=${reflectedTokens}, targetThreshold=${targetThreshold}, compressionValid=${validateCompression(reflectedTokens, targetThreshold)}, parsedObsLen=${parsed.observations?.length}`
2527
+ );
2528
+ if (validateCompression(reflectedTokens, targetThreshold) || currentLevel >= maxLevel) {
2529
+ break;
2509
2530
  }
2510
- });
2511
- let result = await this.withAbortCheck(async () => {
2512
- return await generatePromise;
2513
- }, abortSignal);
2514
- omDebug(
2515
- `[OM:callReflector] first attempt returned: textLen=${result.text?.length}, textPreview="${result.text?.slice(0, 120)}...", inputTokens=${result.usage?.inputTokens ?? result.totalUsage?.inputTokens}, outputTokens=${result.usage?.outputTokens ?? result.totalUsage?.outputTokens}, keys=${Object.keys(result).join(",")}`
2516
- );
2517
- const firstUsage = result.totalUsage ?? result.usage;
2518
- if (firstUsage) {
2519
- totalUsage.inputTokens += firstUsage.inputTokens ?? 0;
2520
- totalUsage.outputTokens += firstUsage.outputTokens ?? 0;
2521
- totalUsage.totalTokens += firstUsage.totalTokens ?? 0;
2522
- }
2523
- let parsed = parseReflectorOutput(result.text);
2524
- let reflectedTokens = this.tokenCounter.countObservations(parsed.observations);
2525
- omDebug(
2526
- `[OM:callReflector] first attempt parsed: reflectedTokens=${reflectedTokens}, targetThreshold=${targetThreshold}, compressionValid=${validateCompression(reflectedTokens, targetThreshold)}, parsedObsLen=${parsed.observations?.length}`
2527
- );
2528
- if (!validateCompression(reflectedTokens, targetThreshold)) {
2529
2531
  if (streamContext?.writer) {
2530
2532
  const failedMarker = this.createObservationFailedMarker({
2531
2533
  cycleId: streamContext.cycleId,
2532
2534
  operationType: "reflection",
2533
2535
  startedAt: streamContext.startedAt,
2534
2536
  tokensAttempted: originalTokens,
2535
- error: `Did not compress below threshold (${originalTokens} \u2192 ${reflectedTokens}, target: ${targetThreshold}), retrying with compression guidance`,
2537
+ error: `Did not compress below threshold (${originalTokens} \u2192 ${reflectedTokens}, target: ${targetThreshold}), retrying at level ${currentLevel + 1}`,
2536
2538
  recordId: streamContext.recordId,
2537
2539
  threadId: streamContext.threadId
2538
2540
  });
@@ -2552,32 +2554,7 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
2552
2554
  await streamContext.writer.custom(startMarker).catch(() => {
2553
2555
  });
2554
2556
  }
2555
- prompt = buildReflectorPrompt(observations, manualPrompt, retryLevel, skipContinuationHints);
2556
- omDebug(`[OM:callReflector] starting retry: promptLen=${prompt.length}`);
2557
- result = await this.withAbortCheck(
2558
- () => agent.generate(prompt, {
2559
- modelSettings: {
2560
- ...this.reflectionConfig.modelSettings
2561
- },
2562
- providerOptions: this.reflectionConfig.providerOptions,
2563
- ...abortSignal ? { abortSignal } : {}
2564
- }),
2565
- abortSignal
2566
- );
2567
- omDebug(
2568
- `[OM:callReflector] retry returned: textLen=${result.text?.length}, inputTokens=${result.usage?.inputTokens ?? result.totalUsage?.inputTokens}, outputTokens=${result.usage?.outputTokens ?? result.totalUsage?.outputTokens}`
2569
- );
2570
- const retryUsage = result.totalUsage ?? result.usage;
2571
- if (retryUsage) {
2572
- totalUsage.inputTokens += retryUsage.inputTokens ?? 0;
2573
- totalUsage.outputTokens += retryUsage.outputTokens ?? 0;
2574
- totalUsage.totalTokens += retryUsage.totalTokens ?? 0;
2575
- }
2576
- parsed = parseReflectorOutput(result.text);
2577
- reflectedTokens = this.tokenCounter.countObservations(parsed.observations);
2578
- omDebug(
2579
- `[OM:callReflector] retry parsed: reflectedTokens=${reflectedTokens}, compressionValid=${validateCompression(reflectedTokens, targetThreshold)}`
2580
- );
2557
+ currentLevel = Math.min(currentLevel + 1, maxLevel);
2581
2558
  }
2582
2559
  return {
2583
2560
  observations: parsed.observations,
@@ -2697,8 +2674,8 @@ ${suggestedResponse}
2697
2674
  /**
2698
2675
  * Calculate all threshold-related values for observation decision making.
2699
2676
  */
2700
- calculateObservationThresholds(allMessages, _unobservedMessages, _pendingTokens, otherThreadTokens, currentObservationTokens, _record) {
2701
- const contextWindowTokens = this.tokenCounter.countMessages(allMessages);
2677
+ calculateObservationThresholds(_allMessages, unobservedMessages, _pendingTokens, otherThreadTokens, currentObservationTokens, _record) {
2678
+ const contextWindowTokens = this.tokenCounter.countMessages(unobservedMessages);
2702
2679
  const totalPendingTokens = Math.max(0, contextWindowTokens + otherThreadTokens);
2703
2680
  const threshold = this.calculateDynamicThreshold(this.observationConfig.messageTokens, currentObservationTokens);
2704
2681
  const baseReflectionThreshold = this.getMaxThreshold(this.reflectionConfig.observationTokens);
@@ -2800,7 +2777,7 @@ ${suggestedResponse}
2800
2777
  * Tries async activation first if enabled, then falls back to sync observation.
2801
2778
  * Returns whether observation succeeded.
2802
2779
  */
2803
- async handleThresholdReached(messageList, record, threadId, resourceId, threshold, lockKey, writer, abortSignal, abort) {
2780
+ async handleThresholdReached(messageList, record, threadId, resourceId, threshold, lockKey, writer, abortSignal, abort, requestContext) {
2804
2781
  let observationSucceeded = false;
2805
2782
  let updatedRecord = record;
2806
2783
  let activatedMessageIds;
@@ -2808,7 +2785,7 @@ ${suggestedResponse}
2808
2785
  let freshRecord = await this.getOrCreateRecord(threadId, resourceId);
2809
2786
  const freshAllMessages = messageList.get.all.db();
2810
2787
  let freshUnobservedMessages = this.getUnobservedMessages(freshAllMessages, freshRecord);
2811
- const freshContextTokens = this.tokenCounter.countMessages(freshAllMessages);
2788
+ const freshContextTokens = this.tokenCounter.countMessages(freshUnobservedMessages);
2812
2789
  let freshOtherThreadTokens = 0;
2813
2790
  if (this.scope === "resource" && resourceId) {
2814
2791
  const freshOtherContext = await this.loadOtherThreadsContext(resourceId, threadId);
@@ -2856,7 +2833,13 @@ ${suggestedResponse}
2856
2833
  omDebug(
2857
2834
  `[OM:threshold] activation succeeded, obsTokens=${updatedRecord.observationTokenCount}, activeObsLen=${updatedRecord.activeObservations?.length}`
2858
2835
  );
2859
- await this.maybeAsyncReflect(updatedRecord, updatedRecord.observationTokenCount ?? 0, writer, messageList);
2836
+ await this.maybeAsyncReflect(
2837
+ updatedRecord,
2838
+ updatedRecord.observationTokenCount ?? 0,
2839
+ writer,
2840
+ messageList,
2841
+ requestContext
2842
+ );
2860
2843
  return;
2861
2844
  }
2862
2845
  if (this.observationConfig.blockAfter && freshTotal >= this.observationConfig.blockAfter) {
@@ -2880,7 +2863,8 @@ ${suggestedResponse}
2880
2863
  resourceId,
2881
2864
  currentThreadMessages: freshUnobservedMessages,
2882
2865
  writer,
2883
- abortSignal
2866
+ abortSignal,
2867
+ requestContext
2884
2868
  });
2885
2869
  } else {
2886
2870
  await this.doSynchronousObservation({
@@ -2888,7 +2872,8 @@ ${suggestedResponse}
2888
2872
  threadId,
2889
2873
  unobservedMessages: freshUnobservedMessages,
2890
2874
  writer,
2891
- abortSignal
2875
+ abortSignal,
2876
+ requestContext
2892
2877
  });
2893
2878
  }
2894
2879
  updatedRecord = await this.getOrCreateRecord(threadId, resourceId);
@@ -3147,12 +3132,12 @@ ${suggestedResponse}
3147
3132
  }
3148
3133
  if (bufferedChunks.length > 0) {
3149
3134
  const allMsgsForCheck = messageList.get.all.db();
3135
+ const unobservedMsgsForCheck = this.getUnobservedMessages(allMsgsForCheck, record);
3150
3136
  const otherThreadTokensForCheck = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
3151
3137
  const currentObsTokensForCheck = record.observationTokenCount ?? 0;
3152
3138
  const { totalPendingTokens: step0PendingTokens, threshold: step0Threshold } = this.calculateObservationThresholds(
3153
3139
  allMsgsForCheck,
3154
- [],
3155
- // unobserved not needed for threshold calculation
3140
+ unobservedMsgsForCheck,
3156
3141
  0,
3157
3142
  // pendingTokens not needed — allMessages covers context
3158
3143
  otherThreadTokensForCheck,
@@ -3191,7 +3176,8 @@ ${suggestedResponse}
3191
3176
  observationTokens: record.observationTokenCount ?? 0,
3192
3177
  threadId,
3193
3178
  writer,
3194
- messageList
3179
+ messageList,
3180
+ requestContext
3195
3181
  });
3196
3182
  record = await this.getOrCreateRecord(threadId, resourceId);
3197
3183
  }
@@ -3202,13 +3188,20 @@ ${suggestedResponse}
3202
3188
  const obsTokens = record.observationTokenCount ?? 0;
3203
3189
  if (this.shouldReflect(obsTokens)) {
3204
3190
  omDebug(`[OM:step0-reflect] obsTokens=${obsTokens} over reflectThreshold, triggering reflection`);
3205
- await this.maybeReflect({ record, observationTokens: obsTokens, threadId, writer, messageList });
3191
+ await this.maybeReflect({
3192
+ record,
3193
+ observationTokens: obsTokens,
3194
+ threadId,
3195
+ writer,
3196
+ messageList,
3197
+ requestContext
3198
+ });
3206
3199
  record = await this.getOrCreateRecord(threadId, resourceId);
3207
3200
  } else if (this.isAsyncReflectionEnabled()) {
3208
3201
  const lockKey = this.getLockKey(threadId, resourceId);
3209
3202
  if (this.shouldTriggerAsyncReflection(obsTokens, lockKey, record)) {
3210
3203
  omDebug(`[OM:step0-reflect] obsTokens=${obsTokens} above activation point, triggering async reflection`);
3211
- await this.maybeAsyncReflect(record, obsTokens, writer, messageList);
3204
+ await this.maybeAsyncReflect(record, obsTokens, writer, messageList, requestContext);
3212
3205
  record = await this.getOrCreateRecord(threadId, resourceId);
3213
3206
  }
3214
3207
  }
@@ -3228,26 +3221,44 @@ ${suggestedResponse}
3228
3221
  record
3229
3222
  );
3230
3223
  const { totalPendingTokens, threshold } = thresholds;
3224
+ const bufferedChunkTokens = this.getBufferedChunks(record).reduce((sum, c) => sum + (c.tokenCount ?? 0), 0);
3225
+ const unbufferedPendingTokens = Math.max(0, totalPendingTokens - bufferedChunkTokens);
3231
3226
  const stateSealedIds = state.sealedIds ?? /* @__PURE__ */ new Set();
3232
3227
  const staticSealedIds = _ObservationalMemory.sealedMessageIds.get(threadId) ?? /* @__PURE__ */ new Set();
3233
3228
  const sealedIds = /* @__PURE__ */ new Set([...stateSealedIds, ...staticSealedIds]);
3234
3229
  state.sealedIds = sealedIds;
3235
3230
  const lockKey = this.getLockKey(threadId, resourceId);
3236
3231
  if (this.isAsyncObservationEnabled() && totalPendingTokens < threshold) {
3237
- const shouldTrigger = this.shouldTriggerAsyncObservation(totalPendingTokens, lockKey, record);
3232
+ const shouldTrigger = this.shouldTriggerAsyncObservation(unbufferedPendingTokens, lockKey, record);
3238
3233
  omDebug(
3239
- `[OM:async-obs] belowThreshold: pending=${totalPendingTokens}, threshold=${threshold}, shouldTrigger=${shouldTrigger}, isBufferingObs=${record.isBufferingObservation}, lastBufferedAt=${record.lastBufferedAtTokens}`
3234
+ `[OM:async-obs] belowThreshold: pending=${totalPendingTokens}, unbuffered=${unbufferedPendingTokens}, threshold=${threshold}, shouldTrigger=${shouldTrigger}, isBufferingObs=${record.isBufferingObservation}, lastBufferedAt=${record.lastBufferedAtTokens}`
3240
3235
  );
3241
3236
  if (shouldTrigger) {
3242
- this.startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, totalPendingTokens);
3237
+ this.startAsyncBufferedObservation(
3238
+ record,
3239
+ threadId,
3240
+ unobservedMessages,
3241
+ lockKey,
3242
+ writer,
3243
+ unbufferedPendingTokens,
3244
+ requestContext
3245
+ );
3243
3246
  }
3244
3247
  } else if (this.isAsyncObservationEnabled()) {
3245
- const shouldTrigger = this.shouldTriggerAsyncObservation(totalPendingTokens, lockKey, record);
3248
+ const shouldTrigger = this.shouldTriggerAsyncObservation(unbufferedPendingTokens, lockKey, record);
3246
3249
  omDebug(
3247
- `[OM:async-obs] atOrAboveThreshold: pending=${totalPendingTokens}, threshold=${threshold}, step=${stepNumber}, shouldTrigger=${shouldTrigger}`
3250
+ `[OM:async-obs] atOrAboveThreshold: pending=${totalPendingTokens}, unbuffered=${unbufferedPendingTokens}, threshold=${threshold}, step=${stepNumber}, shouldTrigger=${shouldTrigger}`
3248
3251
  );
3249
3252
  if (shouldTrigger) {
3250
- this.startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, totalPendingTokens);
3253
+ this.startAsyncBufferedObservation(
3254
+ record,
3255
+ threadId,
3256
+ unobservedMessages,
3257
+ lockKey,
3258
+ writer,
3259
+ unbufferedPendingTokens,
3260
+ requestContext
3261
+ );
3251
3262
  }
3252
3263
  }
3253
3264
  if (stepNumber > 0) {
@@ -3263,7 +3274,8 @@ ${suggestedResponse}
3263
3274
  lockKey,
3264
3275
  writer,
3265
3276
  abortSignal,
3266
- abort
3277
+ abort,
3278
+ requestContext
3267
3279
  );
3268
3280
  if (observationSucceeded) {
3269
3281
  const observedIds = activatedMessageIds?.length ? activatedMessageIds : Array.isArray(updatedRecord.observedMessageIds) ? updatedRecord.observedMessageIds : void 0;
@@ -3595,7 +3607,7 @@ ${newThreadSection}`;
3595
3607
  * Do synchronous observation (fallback when no buffering)
3596
3608
  */
3597
3609
  async doSynchronousObservation(opts) {
3598
- const { record, threadId, unobservedMessages, writer, abortSignal, reflectionHooks } = opts;
3610
+ const { record, threadId, unobservedMessages, writer, abortSignal, reflectionHooks, requestContext } = opts;
3599
3611
  this.emitDebugEvent({
3600
3612
  type: "observation_triggered",
3601
3613
  timestamp: /* @__PURE__ */ new Date(),
@@ -3648,7 +3660,8 @@ ${newThreadSection}`;
3648
3660
  const result = await this.callObserver(
3649
3661
  freshRecord?.activeObservations ?? record.activeObservations,
3650
3662
  messagesToObserve,
3651
- abortSignal
3663
+ abortSignal,
3664
+ { requestContext }
3652
3665
  );
3653
3666
  const existingObservations = freshRecord?.activeObservations ?? record.activeObservations ?? "";
3654
3667
  let newObservations;
@@ -3726,7 +3739,8 @@ ${result.observations}` : result.observations;
3726
3739
  threadId,
3727
3740
  writer,
3728
3741
  abortSignal,
3729
- reflectionHooks
3742
+ reflectionHooks,
3743
+ requestContext
3730
3744
  });
3731
3745
  } catch (error) {
3732
3746
  if (lastMessage?.id) {
@@ -3767,7 +3781,7 @@ ${result.observations}` : result.observations;
3767
3781
  * @param lockKey - Lock key for this scope
3768
3782
  * @param writer - Optional stream writer for emitting buffering markers
3769
3783
  */
3770
- startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, contextWindowTokens) {
3784
+ startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, contextWindowTokens, requestContext) {
3771
3785
  const bufferKey = this.getObservationBufferKey(lockKey);
3772
3786
  const currentTokens = contextWindowTokens ?? this.tokenCounter.countMessages(unobservedMessages) + (record.pendingMessageTokens ?? 0);
3773
3787
  _ObservationalMemory.lastBufferedBoundary.set(bufferKey, currentTokens);
@@ -3775,22 +3789,27 @@ ${result.observations}` : result.observations;
3775
3789
  this.storage.setBufferingObservationFlag(record.id, true, currentTokens).catch((err) => {
3776
3790
  omError("[OM] Failed to set buffering observation flag", err);
3777
3791
  });
3778
- const asyncOp = this.runAsyncBufferedObservation(record, threadId, unobservedMessages, bufferKey, writer).finally(
3779
- () => {
3780
- _ObservationalMemory.asyncBufferingOps.delete(bufferKey);
3781
- unregisterOp(record.id, "bufferingObservation");
3782
- this.storage.setBufferingObservationFlag(record.id, false).catch((err) => {
3783
- omError("[OM] Failed to clear buffering observation flag", err);
3784
- });
3785
- }
3786
- );
3792
+ const asyncOp = this.runAsyncBufferedObservation(
3793
+ record,
3794
+ threadId,
3795
+ unobservedMessages,
3796
+ bufferKey,
3797
+ writer,
3798
+ requestContext
3799
+ ).finally(() => {
3800
+ _ObservationalMemory.asyncBufferingOps.delete(bufferKey);
3801
+ unregisterOp(record.id, "bufferingObservation");
3802
+ this.storage.setBufferingObservationFlag(record.id, false).catch((err) => {
3803
+ omError("[OM] Failed to clear buffering observation flag", err);
3804
+ });
3805
+ });
3787
3806
  _ObservationalMemory.asyncBufferingOps.set(bufferKey, asyncOp);
3788
3807
  }
3789
3808
  /**
3790
3809
  * Internal method that waits for existing buffering operation and then runs new buffering.
3791
3810
  * This implements the mutex-wait behavior.
3792
3811
  */
3793
- async runAsyncBufferedObservation(record, threadId, unobservedMessages, bufferKey, writer) {
3812
+ async runAsyncBufferedObservation(record, threadId, unobservedMessages, bufferKey, writer, requestContext) {
3794
3813
  const existingOp = _ObservationalMemory.asyncBufferingOps.get(bufferKey);
3795
3814
  if (existingOp) {
3796
3815
  try {
@@ -3862,7 +3881,15 @@ ${result.observations}` : result.observations;
3862
3881
  omDebug(
3863
3882
  `[OM:bufferInput] cycleId=${cycleId}, msgCount=${messagesToBuffer.length}, msgTokens=${this.tokenCounter.countMessages(messagesToBuffer)}, ids=${messagesToBuffer.map((m) => `${m.id?.slice(0, 8)}@${m.createdAt ? new Date(m.createdAt).toISOString() : "none"}`).join(",")}`
3864
3883
  );
3865
- await this.doAsyncBufferedObservation(freshRecord, threadId, messagesToBuffer, cycleId, startedAt, writer);
3884
+ await this.doAsyncBufferedObservation(
3885
+ freshRecord,
3886
+ threadId,
3887
+ messagesToBuffer,
3888
+ cycleId,
3889
+ startedAt,
3890
+ writer,
3891
+ requestContext
3892
+ );
3866
3893
  const maxTs = this.getMaxMessageTimestamp(messagesToBuffer);
3867
3894
  const cursor = new Date(maxTs.getTime() + 1);
3868
3895
  _ObservationalMemory.lastBufferedAtTime.set(bufferKey, cursor);
@@ -3891,7 +3918,7 @@ ${result.observations}` : result.observations;
3891
3918
  * The observer sees: active observations + existing buffered observations + message history
3892
3919
  * (excluding already-buffered messages).
3893
3920
  */
3894
- async doAsyncBufferedObservation(record, threadId, messagesToBuffer, cycleId, startedAt, writer) {
3921
+ async doAsyncBufferedObservation(record, threadId, messagesToBuffer, cycleId, startedAt, writer, requestContext) {
3895
3922
  const bufferedChunks = this.getBufferedChunks(record);
3896
3923
  const bufferedChunksText = bufferedChunks.map((c) => c.observations).join("\n\n");
3897
3924
  const combinedObservations = this.combineObservationsForBuffering(record.activeObservations, bufferedChunksText);
@@ -3900,7 +3927,7 @@ ${result.observations}` : result.observations;
3900
3927
  messagesToBuffer,
3901
3928
  void 0,
3902
3929
  // No abort signal for background ops
3903
- { skipContinuationHints: true }
3930
+ { skipContinuationHints: true, requestContext }
3904
3931
  );
3905
3932
  let newObservations;
3906
3933
  if (this.scope === "resource") {
@@ -3999,11 +4026,20 @@ ${bufferedObservations}`;
3999
4026
  if (!freshChunks.length) {
4000
4027
  return { success: false };
4001
4028
  }
4029
+ const messageTokensThreshold = this.getMaxThreshold(this.observationConfig.messageTokens);
4030
+ if (messageList) {
4031
+ const freshPendingTokens = this.tokenCounter.countMessages(messageList.get.all.db());
4032
+ if (freshPendingTokens < messageTokensThreshold) {
4033
+ omDebug(
4034
+ `[OM:tryActivate] skipping activation: freshPendingTokens=${freshPendingTokens} < threshold=${messageTokensThreshold}`
4035
+ );
4036
+ return { success: false };
4037
+ }
4038
+ }
4002
4039
  const activationRatio = this.observationConfig.bufferActivation ?? 0.7;
4003
4040
  omDebug(
4004
4041
  `[OM:tryActivate] swapping: freshChunks=${freshChunks.length}, activationRatio=${activationRatio}, totalChunkTokens=${freshChunks.reduce((s, c) => s + (c.tokenCount ?? 0), 0)}`
4005
4042
  );
4006
- const messageTokensThreshold = this.getMaxThreshold(this.observationConfig.messageTokens);
4007
4043
  const activationResult = await this.storage.swapBufferedToActive({
4008
4044
  id: freshRecord.id,
4009
4045
  activationRatio,
@@ -4059,7 +4095,7 @@ ${bufferedObservations}`;
4059
4095
  * @param observationTokens - Current observation token count
4060
4096
  * @param lockKey - Lock key for this scope
4061
4097
  */
4062
- startAsyncBufferedReflection(record, observationTokens, lockKey, writer) {
4098
+ startAsyncBufferedReflection(record, observationTokens, lockKey, writer, requestContext) {
4063
4099
  const bufferKey = this.getReflectionBufferKey(lockKey);
4064
4100
  if (this.isAsyncBufferingInProgress(bufferKey)) {
4065
4101
  return;
@@ -4069,7 +4105,7 @@ ${bufferedObservations}`;
4069
4105
  this.storage.setBufferingReflectionFlag(record.id, true).catch((err) => {
4070
4106
  omError("[OM] Failed to set buffering reflection flag", err);
4071
4107
  });
4072
- const asyncOp = this.doAsyncBufferedReflection(record, bufferKey, writer).catch(async (error) => {
4108
+ const asyncOp = this.doAsyncBufferedReflection(record, bufferKey, writer, requestContext).catch(async (error) => {
4073
4109
  if (writer) {
4074
4110
  const failedMarker = this.createBufferingFailedMarker({
4075
4111
  cycleId: `reflect-buf-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
@@ -4098,7 +4134,7 @@ ${bufferedObservations}`;
4098
4134
  * Perform async buffered reflection - reflects observations and stores to bufferedReflection.
4099
4135
  * Does NOT create a new generation or update activeObservations.
4100
4136
  */
4101
- async doAsyncBufferedReflection(record, _bufferKey, writer) {
4137
+ async doAsyncBufferedReflection(record, _bufferKey, writer, requestContext) {
4102
4138
  const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
4103
4139
  const currentRecord = freshRecord ?? record;
4104
4140
  const observationTokens = currentRecord.observationTokenCount ?? 0;
@@ -4116,7 +4152,7 @@ ${bufferedObservations}`;
4116
4152
  const activeObservations = allLines.slice(0, linesToReflect).join("\n");
4117
4153
  const reflectedObservationLineCount = linesToReflect;
4118
4154
  const sliceTokenEstimate = Math.round(avgTokensPerLine * linesToReflect);
4119
- const compressionTarget = Math.min(sliceTokenEstimate * bufferActivation, reflectThreshold);
4155
+ const compressionTarget = Math.round(sliceTokenEstimate * 0.75);
4120
4156
  omDebug(
4121
4157
  `[OM:reflect] doAsyncBufferedReflection: slicing observations for reflection \u2014 totalLines=${totalLines}, avgTokPerLine=${avgTokensPerLine.toFixed(1)}, activationPointTokens=${activationPointTokens}, linesToReflect=${linesToReflect}/${totalLines}, sliceTokenEstimate=${sliceTokenEstimate}, compressionTarget=${compressionTarget}`
4122
4158
  );
@@ -4146,8 +4182,9 @@ ${bufferedObservations}`;
4146
4182
  // No abort signal for background ops
4147
4183
  true,
4148
4184
  // Skip continuation hints for async buffering
4149
- 1
4185
+ 1,
4150
4186
  // Start at compression level 1 for buffered reflection
4187
+ requestContext
4151
4188
  );
4152
4189
  const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
4153
4190
  omDebug(
@@ -4168,7 +4205,7 @@ ${bufferedObservations}`;
4168
4205
  cycleId,
4169
4206
  operationType: "reflection",
4170
4207
  startedAt,
4171
- tokensBuffered: observationTokens,
4208
+ tokensBuffered: sliceTokenEstimate,
4172
4209
  bufferedTokens: reflectionTokenCount,
4173
4210
  recordId: currentRecord.id,
4174
4211
  threadId: currentRecord.threadId ?? "",
@@ -4271,7 +4308,16 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4271
4308
  * 4. Only triggers reflection AFTER all threads are observed
4272
4309
  */
4273
4310
  async doResourceScopedObservation(opts) {
4274
- const { record, currentThreadId, resourceId, currentThreadMessages, writer, abortSignal, reflectionHooks } = opts;
4311
+ const {
4312
+ record,
4313
+ currentThreadId,
4314
+ resourceId,
4315
+ currentThreadMessages,
4316
+ writer,
4317
+ abortSignal,
4318
+ reflectionHooks,
4319
+ requestContext
4320
+ } = opts;
4275
4321
  const { threads: allThreads } = await this.storage.listThreads({ filter: { resourceId } });
4276
4322
  const threadMetadataMap = /* @__PURE__ */ new Map();
4277
4323
  for (const thread of allThreads) {
@@ -4426,7 +4472,8 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4426
4472
  existingObservations,
4427
4473
  batch.threadMap,
4428
4474
  batch.threadIds,
4429
- abortSignal
4475
+ abortSignal,
4476
+ requestContext
4430
4477
  );
4431
4478
  return batchResult;
4432
4479
  });
@@ -4539,7 +4586,8 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4539
4586
  threadId: currentThreadId,
4540
4587
  writer,
4541
4588
  abortSignal,
4542
- reflectionHooks
4589
+ reflectionHooks,
4590
+ requestContext
4543
4591
  });
4544
4592
  } catch (error) {
4545
4593
  for (const [threadId, msgs] of threadsWithMessages) {
@@ -4575,7 +4623,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4575
4623
  * Only handles the async path — will never do synchronous (blocking) reflection.
4576
4624
  * Safe to call after buffered observation activation.
4577
4625
  */
4578
- async maybeAsyncReflect(record, observationTokens, writer, messageList) {
4626
+ async maybeAsyncReflect(record, observationTokens, writer, messageList, requestContext) {
4579
4627
  if (!this.isAsyncReflectionEnabled()) return;
4580
4628
  const lockKey = this.getLockKey(record.threadId, record.resourceId);
4581
4629
  const reflectThreshold = this.getMaxThreshold(this.reflectionConfig.observationTokens);
@@ -4586,7 +4634,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4586
4634
  const shouldTrigger = this.shouldTriggerAsyncReflection(observationTokens, lockKey, record);
4587
4635
  omDebug(`[OM:reflect] below threshold: shouldTrigger=${shouldTrigger}`);
4588
4636
  if (shouldTrigger) {
4589
- this.startAsyncBufferedReflection(record, observationTokens, lockKey, writer);
4637
+ this.startAsyncBufferedReflection(record, observationTokens, lockKey, writer, requestContext);
4590
4638
  }
4591
4639
  return;
4592
4640
  }
@@ -4603,7 +4651,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4603
4651
  omDebug(`[OM:reflect] activationSuccess=${activationSuccess}`);
4604
4652
  if (activationSuccess) return;
4605
4653
  omDebug(`[OM:reflect] no buffered reflection, starting background reflection...`);
4606
- this.startAsyncBufferedReflection(record, observationTokens, lockKey, writer);
4654
+ this.startAsyncBufferedReflection(record, observationTokens, lockKey, writer, requestContext);
4607
4655
  }
4608
4656
  /**
4609
4657
  * Check if reflection needed and trigger if so.
@@ -4612,12 +4660,12 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4612
4660
  * in the background at intervals, and activated when the threshold is reached.
4613
4661
  */
4614
4662
  async maybeReflect(opts) {
4615
- const { record, observationTokens, writer, abortSignal, messageList, reflectionHooks } = opts;
4663
+ const { record, observationTokens, writer, abortSignal, messageList, reflectionHooks, requestContext } = opts;
4616
4664
  const lockKey = this.getLockKey(record.threadId, record.resourceId);
4617
4665
  const reflectThreshold = this.getMaxThreshold(this.reflectionConfig.observationTokens);
4618
4666
  if (this.isAsyncReflectionEnabled() && observationTokens < reflectThreshold) {
4619
4667
  if (this.shouldTriggerAsyncReflection(observationTokens, lockKey, record)) {
4620
- this.startAsyncBufferedReflection(record, observationTokens, lockKey, writer);
4668
+ this.startAsyncBufferedReflection(record, observationTokens, lockKey, writer, requestContext);
4621
4669
  }
4622
4670
  }
4623
4671
  if (!this.shouldReflect(observationTokens)) {
@@ -4644,7 +4692,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4644
4692
  omDebug(
4645
4693
  `[OM:reflect] async activation failed, no blockAfter or below it (obsTokens=${observationTokens}, blockAfter=${this.reflectionConfig.blockAfter}) \u2014 starting background reflection`
4646
4694
  );
4647
- this.startAsyncBufferedReflection(record, observationTokens, lockKey, writer);
4695
+ this.startAsyncBufferedReflection(record, observationTokens, lockKey, writer, requestContext);
4648
4696
  return;
4649
4697
  }
4650
4698
  }
@@ -4687,7 +4735,10 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4687
4735
  void 0,
4688
4736
  streamContext,
4689
4737
  reflectThreshold,
4690
- abortSignal
4738
+ abortSignal,
4739
+ void 0,
4740
+ void 0,
4741
+ requestContext
4691
4742
  );
4692
4743
  const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
4693
4744
  await this.storage.createReflectionGeneration({
@@ -4751,7 +4802,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4751
4802
  * to pass conversation messages without duplicating them into Mastra's DB.
4752
4803
  */
4753
4804
  async observe(opts) {
4754
- const { threadId, resourceId, messages, hooks } = opts;
4805
+ const { threadId, resourceId, messages, hooks, requestContext } = opts;
4755
4806
  const lockKey = this.getLockKey(threadId, resourceId);
4756
4807
  const reflectionHooks = hooks ? { onReflectionStart: hooks.onReflectionStart, onReflectionEnd: hooks.onReflectionEnd } : void 0;
4757
4808
  await this.withLock(lockKey, async () => {
@@ -4771,7 +4822,8 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4771
4822
  currentThreadId: threadId,
4772
4823
  resourceId,
4773
4824
  currentThreadMessages: currentMessages,
4774
- reflectionHooks
4825
+ reflectionHooks,
4826
+ requestContext
4775
4827
  });
4776
4828
  } finally {
4777
4829
  hooks?.onObservationEnd?.();
@@ -4793,7 +4845,13 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4793
4845
  }
4794
4846
  hooks?.onObservationStart?.();
4795
4847
  try {
4796
- await this.doSynchronousObservation({ record: freshRecord, threadId, unobservedMessages, reflectionHooks });
4848
+ await this.doSynchronousObservation({
4849
+ record: freshRecord,
4850
+ threadId,
4851
+ unobservedMessages,
4852
+ reflectionHooks,
4853
+ requestContext
4854
+ });
4797
4855
  } finally {
4798
4856
  hooks?.onObservationEnd?.();
4799
4857
  }
@@ -4811,7 +4869,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4811
4869
  * );
4812
4870
  * ```
4813
4871
  */
4814
- async reflect(threadId, resourceId, prompt) {
4872
+ async reflect(threadId, resourceId, prompt, requestContext) {
4815
4873
  const record = await this.getOrCreateRecord(threadId, resourceId);
4816
4874
  if (!record.activeObservations) {
4817
4875
  return;
@@ -4820,7 +4878,16 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4820
4878
  registerOp(record.id, "reflecting");
4821
4879
  try {
4822
4880
  const reflectThreshold = this.getMaxThreshold(this.reflectionConfig.observationTokens);
4823
- const reflectResult = await this.callReflector(record.activeObservations, prompt, void 0, reflectThreshold);
4881
+ const reflectResult = await this.callReflector(
4882
+ record.activeObservations,
4883
+ prompt,
4884
+ void 0,
4885
+ reflectThreshold,
4886
+ void 0,
4887
+ void 0,
4888
+ void 0,
4889
+ requestContext
4890
+ );
4824
4891
  const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
4825
4892
  await this.storage.createReflectionGeneration({
4826
4893
  currentRecord: record,
@@ -4889,5 +4956,5 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4889
4956
  };
4890
4957
 
4891
4958
  export { OBSERVATIONAL_MEMORY_DEFAULTS, OBSERVATION_CONTEXT_INSTRUCTIONS, OBSERVATION_CONTEXT_PROMPT, OBSERVATION_CONTINUATION_HINT, OBSERVER_SYSTEM_PROMPT, ObservationalMemory, TokenCounter, buildObserverPrompt, buildObserverSystemPrompt, extractCurrentTask, formatMessagesForObserver, hasCurrentTaskSection, optimizeObservationsForContext, parseObserverOutput };
4892
- //# sourceMappingURL=chunk-F5P5HTMC.js.map
4893
- //# sourceMappingURL=chunk-F5P5HTMC.js.map
4959
+ //# sourceMappingURL=chunk-D4AWAGLM.js.map
4960
+ //# sourceMappingURL=chunk-D4AWAGLM.js.map