@mastra/memory 1.8.3-alpha.0 → 1.8.3-alpha.2

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 +33 -0
  2. package/dist/{chunk-SUU4IAZJ.js → chunk-4KPXPQX3.js} +212 -75
  3. package/dist/chunk-4KPXPQX3.js.map +1 -0
  4. package/dist/{chunk-YPFNHFT6.cjs → chunk-LGCREJMO.cjs} +212 -74
  5. package/dist/chunk-LGCREJMO.cjs.map +1 -0
  6. package/dist/docs/SKILL.md +1 -1
  7. package/dist/docs/assets/SOURCE_MAP.json +30 -25
  8. package/dist/docs/references/docs-agents-agent-approval.md +4 -4
  9. package/dist/docs/references/docs-agents-network-approval.md +1 -1
  10. package/dist/docs/references/docs-agents-networks.md +1 -1
  11. package/dist/docs/references/docs-agents-supervisor-agents.md +3 -3
  12. package/dist/docs/references/docs-memory-memory-processors.md +6 -6
  13. package/dist/docs/references/docs-memory-semantic-recall.md +1 -1
  14. package/dist/docs/references/docs-memory-storage.md +1 -1
  15. package/dist/docs/references/docs-memory-working-memory.md +2 -2
  16. package/dist/docs/references/reference-memory-memory-class.md +3 -3
  17. package/dist/docs/references/reference-memory-observational-memory.md +5 -5
  18. package/dist/docs/references/reference-processors-token-limiter-processor.md +3 -3
  19. package/dist/docs/references/reference-storage-mongodb.md +1 -1
  20. package/dist/docs/references/reference-storage-postgresql.md +1 -1
  21. package/dist/docs/references/reference-storage-upstash.md +1 -1
  22. package/dist/docs/references/reference-vectors-libsql.md +1 -1
  23. package/dist/docs/references/reference-vectors-mongodb.md +1 -1
  24. package/dist/docs/references/reference-vectors-pg.md +1 -1
  25. package/dist/docs/references/reference-vectors-upstash.md +1 -1
  26. package/dist/index.cjs +16 -10
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +12 -10
  31. package/dist/index.js.map +1 -1
  32. package/dist/{observational-memory-3HFM7PY2.cjs → observational-memory-4TV5KKFV.cjs} +21 -17
  33. package/dist/{observational-memory-3HFM7PY2.cjs.map → observational-memory-4TV5KKFV.cjs.map} +1 -1
  34. package/dist/observational-memory-UEDVTWS2.js +3 -0
  35. package/dist/{observational-memory-XXD6E2SO.js.map → observational-memory-UEDVTWS2.js.map} +1 -1
  36. package/dist/processors/index.cjs +19 -15
  37. package/dist/processors/index.js +1 -1
  38. package/dist/processors/observational-memory/index.d.ts +1 -0
  39. package/dist/processors/observational-memory/index.d.ts.map +1 -1
  40. package/dist/processors/observational-memory/observation-utils.d.ts +16 -0
  41. package/dist/processors/observational-memory/observation-utils.d.ts.map +1 -0
  42. package/dist/processors/observational-memory/observational-memory.d.ts +13 -4
  43. package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
  44. package/dist/processors/observational-memory/observer-agent.d.ts +9 -7
  45. package/dist/processors/observational-memory/observer-agent.d.ts.map +1 -1
  46. package/dist/processors/observational-memory/token-counter.d.ts.map +1 -1
  47. package/dist/processors/observational-memory/tool-result-helpers.d.ts +12 -0
  48. package/dist/processors/observational-memory/tool-result-helpers.d.ts.map +1 -0
  49. package/package.json +6 -6
  50. package/dist/chunk-SUU4IAZJ.js.map +0 -1
  51. package/dist/chunk-YPFNHFT6.cjs.map +0 -1
  52. package/dist/observational-memory-XXD6E2SO.js +0 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # @mastra/memory
2
2
 
3
+ ## 1.8.3-alpha.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixed working memory tool description to accurately reflect merge behavior. The previous description incorrectly stated "Set a field to null to remove it" but null values are stripped by validation before reaching the merge logic. The updated description clarifies: omit fields to preserve existing data, and pass complete arrays or omit them since arrays are replaced entirely. ([#14424](https://github.com/mastra-ai/mastra/pull/14424))
8
+
9
+ - Updated dependencies [[`8b4ce84`](https://github.com/mastra-ai/mastra/commit/8b4ce84aed0808b9805cc4fd7147c1f8a2ef7a36), [`8d4cfe6`](https://github.com/mastra-ai/mastra/commit/8d4cfe6b9a7157d3876206227ec9f04cde6dbc4a), [`68a019d`](https://github.com/mastra-ai/mastra/commit/68a019d30d22251ddd628a2947d60215c03c350a), [`68a019d`](https://github.com/mastra-ai/mastra/commit/68a019d30d22251ddd628a2947d60215c03c350a)]:
10
+ - @mastra/core@1.14.0-alpha.3
11
+
12
+ ## 1.8.3-alpha.1
13
+
14
+ ### Patch Changes
15
+
16
+ - Fixed observational memory triggering observation while provider-executed tool calls are still pending, which could split messages and cause errors on follow-up turns. ([#14282](https://github.com/mastra-ai/mastra/pull/14282))
17
+
18
+ - Limit oversized observational-memory tool results before they reach the observer. ([#14344](https://github.com/mastra-ai/mastra/pull/14344))
19
+
20
+ This strips large `encryptedContent` blobs and truncates remaining tool result payloads to keep observer prompts and token estimates aligned with what the model actually sees.
21
+
22
+ - Improved observational memory cache stability by splitting persisted observations into separate prompt chunks using dated message boundary delimiters. ([#14367](https://github.com/mastra-ai/mastra/pull/14367))
23
+
24
+ Added `getObservationsAsOf()` utility to retrieve the observations that were active at a specific point in time. This enables filtering observation history by message creation date.
25
+
26
+ ```ts
27
+ import { getObservationsAsOf } from '@mastra/memory';
28
+
29
+ // Get observations that existed when a specific message was created
30
+ const observations = getObservationsAsOf(record.activeObservations, message.createdAt);
31
+ ```
32
+
33
+ - Updated dependencies [[`4444280`](https://github.com/mastra-ai/mastra/commit/444428094253e916ec077e66284e685fde67021e), [`dbb879a`](https://github.com/mastra-ai/mastra/commit/dbb879af0b809c668e9b3a9d8bac97d806caa267), [`8de3555`](https://github.com/mastra-ai/mastra/commit/8de355572c6fd838f863a3e7e6fe24d0947b774f)]:
34
+ - @mastra/core@1.14.0-alpha.2
35
+
3
36
  ## 1.8.3-alpha.0
4
37
 
5
38
  ### Patch Changes
@@ -6,10 +6,10 @@ import { resolveModelConfig } from '@mastra/core/llm';
6
6
  import { setThreadOMMetadata, getThreadOMMetadata, parseMemoryRequestContext } from '@mastra/core/memory';
7
7
  import { MessageHistory } from '@mastra/core/processors';
8
8
  import xxhash from 'xxhash-wasm';
9
+ import { estimateTokenCount } from 'tokenx';
9
10
  import { createHash, randomUUID } from 'crypto';
10
11
  import { AsyncLocalStorage } from 'async_hooks';
11
12
  import imageSize from 'image-size';
12
- import { estimateTokenCount } from 'tokenx';
13
13
 
14
14
  // src/processors/observational-memory/observational-memory.ts
15
15
 
@@ -300,6 +300,97 @@ function createActivationMarker(params) {
300
300
  }
301
301
  };
302
302
  }
303
+ var ENCRYPTED_CONTENT_KEY = "encryptedContent";
304
+ var ENCRYPTED_CONTENT_REDACTION_THRESHOLD = 256;
305
+ var DEFAULT_OBSERVER_TOOL_RESULT_MAX_TOKENS = 1e4;
306
+ function isObjectLike(value) {
307
+ return typeof value === "object" && value !== null;
308
+ }
309
+ function sanitizeToolResultValue(value, seen = /* @__PURE__ */ new WeakMap()) {
310
+ if (!isObjectLike(value)) {
311
+ return value;
312
+ }
313
+ if (seen.has(value)) {
314
+ return seen.get(value);
315
+ }
316
+ if (Array.isArray(value)) {
317
+ const sanitizedArray = [];
318
+ seen.set(value, sanitizedArray);
319
+ for (const item of value) {
320
+ sanitizedArray.push(sanitizeToolResultValue(item, seen));
321
+ }
322
+ return sanitizedArray;
323
+ }
324
+ const sanitizedObject = {};
325
+ seen.set(value, sanitizedObject);
326
+ for (const [key, entry] of Object.entries(value)) {
327
+ if (key === ENCRYPTED_CONTENT_KEY && typeof entry === "string" && entry.length > ENCRYPTED_CONTENT_REDACTION_THRESHOLD) {
328
+ sanitizedObject[key] = `[stripped encryptedContent: ${entry.length} characters]`;
329
+ continue;
330
+ }
331
+ sanitizedObject[key] = sanitizeToolResultValue(entry, seen);
332
+ }
333
+ return sanitizedObject;
334
+ }
335
+ function stringifyToolResult(value) {
336
+ if (typeof value === "string") {
337
+ return value;
338
+ }
339
+ const sanitized = sanitizeToolResultValue(value);
340
+ try {
341
+ return JSON.stringify(sanitized, null, 2);
342
+ } catch {
343
+ return String(sanitized);
344
+ }
345
+ }
346
+ function resolveToolResultValue(part, invocationResult) {
347
+ const mastraMetadata = part?.providerMetadata?.mastra;
348
+ if (mastraMetadata && typeof mastraMetadata === "object" && "modelOutput" in mastraMetadata) {
349
+ return {
350
+ value: mastraMetadata.modelOutput,
351
+ usingStoredModelOutput: true
352
+ };
353
+ }
354
+ return {
355
+ value: invocationResult,
356
+ usingStoredModelOutput: false
357
+ };
358
+ }
359
+ function truncateStringByTokens(text, maxTokens) {
360
+ if (!text || maxTokens <= 0) {
361
+ return "";
362
+ }
363
+ const totalTokens = estimateTokenCount(text);
364
+ if (totalTokens <= maxTokens) {
365
+ return text;
366
+ }
367
+ const buildCandidate = (sliceEnd) => {
368
+ const visible = text.slice(0, sliceEnd);
369
+ const omittedChars = text.length - sliceEnd;
370
+ return `${visible}
371
+ ... [truncated ~${totalTokens - estimateTokenCount(visible)} tokens / ${omittedChars} characters]`;
372
+ };
373
+ let low = 0;
374
+ let high = text.length;
375
+ let best = buildCandidate(0);
376
+ while (low <= high) {
377
+ const mid = Math.floor((low + high) / 2);
378
+ const candidate = buildCandidate(mid);
379
+ const candidateTokens = estimateTokenCount(candidate);
380
+ if (candidateTokens <= maxTokens) {
381
+ best = candidate;
382
+ low = mid + 1;
383
+ } else {
384
+ high = mid - 1;
385
+ }
386
+ }
387
+ return best;
388
+ }
389
+ function formatToolResultForObserver(value, options) {
390
+ const serialized = stringifyToolResult(value);
391
+ const maxTokens = options?.maxTokens ?? DEFAULT_OBSERVER_TOOL_RESULT_MAX_TOKENS;
392
+ return truncateStringByTokens(serialized, maxTokens);
393
+ }
303
394
 
304
395
  // src/processors/observational-memory/observer-agent.ts
305
396
  var OBSERVER_EXTRACTION_INSTRUCTIONS = `CRITICAL: DISTINGUISH USER ASSERTIONS FROM QUESTIONS
@@ -774,6 +865,7 @@ function formatObserverAttachmentPlaceholder(part, counter) {
774
865
  }
775
866
  function formatObserverMessage(msg, counter, options) {
776
867
  const maxLen = options?.maxPartLength;
868
+ const maxToolResultTokens = options?.maxToolResultTokens ?? DEFAULT_OBSERVER_TOOL_RESULT_MAX_TOKENS;
777
869
  const timestamp = formatObserverTimestamp(msg.createdAt);
778
870
  const role = msg.role.charAt(0).toUpperCase() + msg.role.slice(1);
779
871
  const timestampStr = timestamp ? ` (${timestamp})` : "";
@@ -787,7 +879,11 @@ function formatObserverMessage(msg, counter, options) {
787
879
  if (part.type === "tool-invocation") {
788
880
  const inv = part.toolInvocation;
789
881
  if (inv.state === "result") {
790
- const resultStr = JSON.stringify(inv.result, null, 2);
882
+ const { value: resultForObserver } = resolveToolResultValue(
883
+ part,
884
+ inv.result
885
+ );
886
+ const resultStr = formatToolResultForObserver(resultForObserver, { maxTokens: maxToolResultTokens });
791
887
  return `[Tool Result: ${inv.toolName}]
792
888
  ${maybeTruncate(resultStr, maxLen)}`;
793
889
  }
@@ -828,12 +924,12 @@ function formatMessagesForObserver(messages, options) {
828
924
  const counter = { nextImageId: 1, nextFileId: 1 };
829
925
  return messages.map((msg) => formatObserverMessage(msg, counter, options).text).filter(Boolean).join("\n\n---\n\n");
830
926
  }
831
- function buildObserverHistoryMessage(messages) {
927
+ function buildObserverHistoryMessage(messages, options) {
832
928
  const counter = { nextImageId: 1, nextFileId: 1 };
833
929
  const content = [{ type: "text", text: "## New Message History to Observe\n\n" }];
834
930
  let visibleCount = 0;
835
931
  messages.forEach((message) => {
836
- const formatted = formatObserverMessage(message, counter);
932
+ const formatted = formatObserverMessage(message, counter, options);
837
933
  if (!formatted.text && formatted.attachments.length === 0) return;
838
934
  if (visibleCount > 0) {
839
935
  content.push({ type: "text", text: "\n\n---\n\n" });
@@ -854,7 +950,7 @@ function maybeTruncate(str, maxLen) {
854
950
  return `${truncated}
855
951
  ... [truncated ${remaining} characters]`;
856
952
  }
857
- function buildMultiThreadObserverHistoryMessage(messagesByThread, threadOrder) {
953
+ function buildMultiThreadObserverHistoryMessage(messagesByThread, threadOrder, options) {
858
954
  const counter = { nextImageId: 1, nextFileId: 1 };
859
955
  const content = [
860
956
  {
@@ -872,7 +968,7 @@ The following messages are from ${threadOrder.length} different conversation thr
872
968
  const threadContent = [];
873
969
  let visibleCount = 0;
874
970
  messages.forEach((message) => {
875
- const formatted = formatObserverMessage(message, counter);
971
+ const formatted = formatObserverMessage(message, counter, options);
876
972
  if (!formatted.text && formatted.attachments.length === 0) return;
877
973
  if (visibleCount > 0) {
878
974
  threadContent.push({ type: "text", text: "\n\n---\n\n" });
@@ -2516,17 +2612,7 @@ var TokenCounter = class _TokenCounter {
2516
2612
  return tokens;
2517
2613
  }
2518
2614
  resolveToolResultForTokenCounting(part, invocationResult) {
2519
- const mastraMetadata = part?.providerMetadata?.mastra;
2520
- if (mastraMetadata && typeof mastraMetadata === "object" && "modelOutput" in mastraMetadata) {
2521
- return {
2522
- value: mastraMetadata.modelOutput,
2523
- usingStoredModelOutput: true
2524
- };
2525
- }
2526
- return {
2527
- value: invocationResult,
2528
- usingStoredModelOutput: false
2529
- };
2615
+ return resolveToolResultValue(part, invocationResult);
2530
2616
  }
2531
2617
  estimateImageAssetTokens(part, asset, kind) {
2532
2618
  const modelContext = this.getModelContext();
@@ -2765,19 +2851,13 @@ var TokenCounter = class _TokenCounter {
2765
2851
  invocation.result
2766
2852
  );
2767
2853
  if (resultForCounting !== void 0) {
2768
- if (typeof resultForCounting === "string") {
2769
- tokens += this.readOrPersistPartEstimate(
2770
- part,
2771
- usingStoredModelOutput ? "tool-result-model-output" : "tool-result",
2772
- resultForCounting
2773
- );
2774
- } else {
2775
- const resultJson = JSON.stringify(resultForCounting);
2776
- tokens += this.readOrPersistPartEstimate(
2777
- part,
2778
- usingStoredModelOutput ? "tool-result-model-output-json" : "tool-result-json",
2779
- resultJson
2780
- );
2854
+ const formattedResult = formatToolResultForObserver(resultForCounting);
2855
+ tokens += this.readOrPersistPartEstimate(
2856
+ part,
2857
+ usingStoredModelOutput ? "tool-result-model-output-json" : "tool-result-json",
2858
+ formattedResult
2859
+ );
2860
+ if (typeof resultForCounting !== "string") {
2781
2861
  overheadDelta -= 12;
2782
2862
  }
2783
2863
  }
@@ -2893,6 +2973,14 @@ function omDebug(msg) {
2893
2973
  } catch {
2894
2974
  }
2895
2975
  }
2976
+ function getLatestStepParts(parts) {
2977
+ for (let i = parts.length - 1; i >= 0; i--) {
2978
+ if (parts[i]?.type === "step-start") {
2979
+ return parts.slice(i + 1);
2980
+ }
2981
+ }
2982
+ return parts;
2983
+ }
2896
2984
  function omError(msg, err) {
2897
2985
  const errStr = err instanceof Error ? err.stack ?? err.message : err !== void 0 ? String(err) : "";
2898
2986
  const full = errStr ? `${msg}: ${errStr}` : msg;
@@ -4339,38 +4427,51 @@ ${unreflectedContent}` : bufferedReflection;
4339
4427
  if (currentDate) {
4340
4428
  optimized = addRelativeTimeToObservations(optimized, currentDate);
4341
4429
  }
4342
- let content = `
4343
- ${OBSERVATION_CONTEXT_PROMPT}
4430
+ const messages = [`${OBSERVATION_CONTEXT_PROMPT}
4344
4431
 
4345
- <observations>
4346
- ${optimized}
4347
- </observations>
4348
-
4349
- ${OBSERVATION_CONTEXT_INSTRUCTIONS}`;
4432
+ ${OBSERVATION_CONTEXT_INSTRUCTIONS}`];
4350
4433
  if (unobservedContextBlocks) {
4351
- content += `
4352
-
4353
- The following content is from OTHER conversations different from the current conversation, they're here for reference, but they're not necessarily your focus:
4434
+ messages.push(
4435
+ `The following content is from OTHER conversations different from the current conversation, they're here for reference, but they're not necessarily your focus:
4354
4436
  START_OTHER_CONVERSATIONS_BLOCK
4355
4437
  ${unobservedContextBlocks}
4356
- END_OTHER_CONVERSATIONS_BLOCK`;
4438
+ END_OTHER_CONVERSATIONS_BLOCK`
4439
+ );
4440
+ }
4441
+ const observationChunks = this.splitObservationContextChunks(optimized);
4442
+ if (observationChunks.length > 0) {
4443
+ messages.push("<observations>", ...observationChunks);
4357
4444
  }
4358
4445
  if (currentTask) {
4359
- content += `
4360
-
4361
- <current-task>
4446
+ messages.push(`<current-task>
4362
4447
  ${currentTask}
4363
- </current-task>`;
4448
+ </current-task>`);
4364
4449
  }
4365
4450
  if (suggestedResponse) {
4366
- content += `
4367
-
4368
- <suggested-response>
4451
+ messages.push(`<suggested-response>
4369
4452
  ${suggestedResponse}
4370
- </suggested-response>
4371
- `;
4453
+ </suggested-response>`);
4372
4454
  }
4373
- return content;
4455
+ return messages;
4456
+ }
4457
+ splitObservationContextChunks(observations) {
4458
+ const trimmed = observations.trim();
4459
+ if (!trimmed) {
4460
+ return [];
4461
+ }
4462
+ return trimmed.split(/\n{2,}--- message boundary \(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z\) ---\n{2,}/).map((chunk) => chunk.trim()).filter(Boolean);
4463
+ }
4464
+ /**
4465
+ * Create a message boundary delimiter with an ISO 8601 date.
4466
+ * The date should be the lastObservedAt timestamp — the latest message
4467
+ * timestamp that was observed to produce the observations following this boundary.
4468
+ */
4469
+ static createMessageBoundary(date) {
4470
+ return `
4471
+
4472
+ --- message boundary (${date.toISOString()}) ---
4473
+
4474
+ `;
4374
4475
  }
4375
4476
  /**
4376
4477
  * Get threadId and resourceId from either RequestContext or MessageList
@@ -4808,7 +4909,7 @@ ${suggestedResponse}
4808
4909
  if (!record.activeObservations) {
4809
4910
  return;
4810
4911
  }
4811
- const observationSystemMessage = this.formatObservationsForContext(
4912
+ const observationSystemMessages = this.formatObservationsForContext(
4812
4913
  record.activeObservations,
4813
4914
  currentTask,
4814
4915
  suggestedResponse,
@@ -4816,7 +4917,7 @@ ${suggestedResponse}
4816
4917
  currentDate
4817
4918
  );
4818
4919
  messageList.clearSystemMessages("observational-memory");
4819
- messageList.addSystem(observationSystemMessage, "observational-memory");
4920
+ messageList.addSystem(observationSystemMessages, "observational-memory");
4820
4921
  const continuationMessage = {
4821
4922
  id: `om-continuation`,
4822
4923
  role: "user",
@@ -5092,12 +5193,20 @@ ${suggestedResponse}
5092
5193
  const sealedIds = /* @__PURE__ */ new Set([...stateSealedIds, ...staticSealedIds]);
5093
5194
  state.sealedIds = sealedIds;
5094
5195
  const lockKey = this.getLockKey(threadId, resourceId);
5196
+ const lastMessage = allMessages[allMessages.length - 1];
5197
+ const latestStepParts = getLatestStepParts(lastMessage?.content?.parts ?? []);
5198
+ const hasIncompleteToolCalls = latestStepParts.some(
5199
+ (part) => part?.type === "tool-invocation" && part.toolInvocation?.state === "call"
5200
+ );
5201
+ omDebug(
5202
+ `[OM:deferred-check] hasIncompleteToolCalls=${hasIncompleteToolCalls}, latestStepPartsCount=${latestStepParts.length}`
5203
+ );
5095
5204
  if (this.isAsyncObservationEnabled() && totalPendingTokens < threshold) {
5096
5205
  const shouldTrigger = this.shouldTriggerAsyncObservation(totalPendingTokens, lockKey, record, threshold);
5097
5206
  omDebug(
5098
- `[OM:async-obs] belowThreshold: pending=${totalPendingTokens}, unbuffered=${unbufferedPendingTokens}, threshold=${threshold}, shouldTrigger=${shouldTrigger}, isBufferingObs=${record.isBufferingObservation}, lastBufferedAt=${record.lastBufferedAtTokens}`
5207
+ `[OM:async-obs] belowThreshold: pending=${totalPendingTokens}, unbuffered=${unbufferedPendingTokens}, threshold=${threshold}, shouldTrigger=${shouldTrigger}, isBufferingObs=${record.isBufferingObservation}, lastBufferedAt=${record.lastBufferedAtTokens}, hasIncompleteToolCalls=${hasIncompleteToolCalls}`
5099
5208
  );
5100
- if (shouldTrigger) {
5209
+ if (shouldTrigger && !hasIncompleteToolCalls) {
5101
5210
  void this.startAsyncBufferedObservation(
5102
5211
  record,
5103
5212
  threadId,
@@ -5111,9 +5220,9 @@ ${suggestedResponse}
5111
5220
  } else if (this.isAsyncObservationEnabled()) {
5112
5221
  const shouldTrigger = this.shouldTriggerAsyncObservation(totalPendingTokens, lockKey, record, threshold);
5113
5222
  omDebug(
5114
- `[OM:async-obs] atOrAboveThreshold: pending=${totalPendingTokens}, unbuffered=${unbufferedPendingTokens}, threshold=${threshold}, step=${stepNumber}, shouldTrigger=${shouldTrigger}`
5223
+ `[OM:async-obs] atOrAboveThreshold: pending=${totalPendingTokens}, unbuffered=${unbufferedPendingTokens}, threshold=${threshold}, step=${stepNumber}, shouldTrigger=${shouldTrigger}, hasIncompleteToolCalls=${hasIncompleteToolCalls}`
5115
5224
  );
5116
- if (shouldTrigger) {
5225
+ if (shouldTrigger && !hasIncompleteToolCalls) {
5117
5226
  void this.startAsyncBufferedObservation(
5118
5227
  record,
5119
5228
  threadId,
@@ -5128,7 +5237,7 @@ ${suggestedResponse}
5128
5237
  if (stepNumber > 0) {
5129
5238
  await this.handlePerStepSave(messageList, sealedIds, threadId, resourceId, state);
5130
5239
  }
5131
- if (stepNumber > 0 && totalPendingTokens >= threshold) {
5240
+ if (stepNumber > 0 && !hasIncompleteToolCalls && totalPendingTokens >= threshold) {
5132
5241
  reproCaptureDetails.thresholdReached = true;
5133
5242
  const { observationSucceeded, updatedRecord, activatedMessageIds } = await this.handleThresholdReached(
5134
5243
  messageList,
@@ -5486,16 +5595,14 @@ ${cleanObservations}
5486
5595
  * merge the observations into that section to reduce token usage.
5487
5596
  * Otherwise, append as a new section.
5488
5597
  */
5489
- replaceOrAppendThreadSection(existingObservations, _threadId, newThreadSection) {
5598
+ replaceOrAppendThreadSection(existingObservations, _threadId, newThreadSection, lastObservedAt) {
5490
5599
  if (!existingObservations) {
5491
5600
  return newThreadSection;
5492
5601
  }
5493
5602
  const threadIdMatch = newThreadSection.match(/<thread id="([^"]+)">/);
5494
5603
  const dateMatch = newThreadSection.match(/Date:\s*([A-Za-z]+\s+\d+,\s+\d+)/);
5495
5604
  if (!threadIdMatch || !dateMatch) {
5496
- return `${existingObservations}
5497
-
5498
- ${newThreadSection}`;
5605
+ return `${existingObservations}${_ObservationalMemory.createMessageBoundary(lastObservedAt)}${newThreadSection}`;
5499
5606
  }
5500
5607
  const newThreadId = threadIdMatch[1];
5501
5608
  const newDate = dateMatch[1];
@@ -5530,9 +5637,7 @@ ${threadClose}`;
5530
5637
  }
5531
5638
  }
5532
5639
  }
5533
- return `${existingObservations}
5534
-
5535
- ${newThreadSection}`;
5640
+ return `${existingObservations}${_ObservationalMemory.createMessageBoundary(lastObservedAt)}${newThreadSection}`;
5536
5641
  }
5537
5642
  /**
5538
5643
  * Sort threads by their oldest unobserved message.
@@ -5615,19 +5720,22 @@ ${newThreadSection}`;
5615
5720
  priorSuggestedResponse: threadOMMetadata?.suggestedResponse,
5616
5721
  wasTruncated
5617
5722
  });
5723
+ const lastObservedAt = this.getMaxMessageTimestamp(messagesToObserve);
5618
5724
  const existingObservations = freshRecord?.activeObservations ?? record.activeObservations ?? "";
5619
5725
  let newObservations;
5620
5726
  if (this.scope === "resource") {
5621
5727
  const threadSection = await this.wrapWithThreadTag(threadId, result.observations);
5622
- newObservations = this.replaceOrAppendThreadSection(existingObservations, threadId, threadSection);
5728
+ newObservations = this.replaceOrAppendThreadSection(
5729
+ existingObservations,
5730
+ threadId,
5731
+ threadSection,
5732
+ lastObservedAt
5733
+ );
5623
5734
  } else {
5624
- newObservations = existingObservations ? `${existingObservations}
5625
-
5626
- ${result.observations}` : result.observations;
5735
+ newObservations = existingObservations ? `${existingObservations}${_ObservationalMemory.createMessageBoundary(lastObservedAt)}${result.observations}` : result.observations;
5627
5736
  }
5628
5737
  let totalTokenCount = this.tokenCounter.countObservations(newObservations);
5629
5738
  const cycleObservationTokens = this.tokenCounter.countObservations(result.observations);
5630
- const lastObservedAt = this.getMaxMessageTimestamp(messagesToObserve);
5631
5739
  const newMessageIds = messagesToObserve.map((m) => m.id);
5632
5740
  const existingIds = freshRecord?.observedMessageIds ?? record.observedMessageIds ?? [];
5633
5741
  const allObservedIds = [.../* @__PURE__ */ new Set([...Array.isArray(existingIds) ? existingIds : [], ...newMessageIds])];
@@ -6521,9 +6629,14 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
6521
6629
  if (!obsResult) continue;
6522
6630
  const { threadId, threadMessages, result } = obsResult;
6523
6631
  cycleObservationTokens += this.tokenCounter.countObservations(result.observations);
6524
- const threadSection = await this.wrapWithThreadTag(threadId, result.observations);
6525
- currentObservations = this.replaceOrAppendThreadSection(currentObservations, threadId, threadSection);
6526
6632
  const threadLastObservedAt = this.getMaxMessageTimestamp(threadMessages);
6633
+ const threadSection = await this.wrapWithThreadTag(threadId, result.observations);
6634
+ currentObservations = this.replaceOrAppendThreadSection(
6635
+ currentObservations,
6636
+ threadId,
6637
+ threadSection,
6638
+ threadLastObservedAt
6639
+ );
6527
6640
  const thread = await this.storage.getThreadById({ threadId });
6528
6641
  if (thread) {
6529
6642
  const newMetadata = setThreadOMMetadata(thread.metadata, {
@@ -6968,6 +7081,30 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
6968
7081
  }
6969
7082
  };
6970
7083
 
6971
- 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 };
6972
- //# sourceMappingURL=chunk-SUU4IAZJ.js.map
6973
- //# sourceMappingURL=chunk-SUU4IAZJ.js.map
7084
+ // src/processors/observational-memory/observation-utils.ts
7085
+ var BOUNDARY_WITH_DATE_RE = /\n{2,}--- message boundary \((\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z)\) ---\n{2,}/;
7086
+ function getObservationsAsOf(activeObservations, asOf) {
7087
+ const trimmed = activeObservations.trim();
7088
+ if (!trimmed) return "";
7089
+ const parts = trimmed.split(BOUNDARY_WITH_DATE_RE);
7090
+ const chunks = [];
7091
+ const firstChunk = parts[0]?.trim();
7092
+ if (firstChunk) {
7093
+ chunks.push(firstChunk);
7094
+ }
7095
+ for (let i = 1; i < parts.length; i += 2) {
7096
+ const dateStr = parts[i];
7097
+ const chunk = parts[i + 1]?.trim();
7098
+ if (!chunk) continue;
7099
+ const boundaryDate = new Date(dateStr);
7100
+ if (isNaN(boundaryDate.getTime())) continue;
7101
+ if (boundaryDate <= asOf) {
7102
+ chunks.push(chunk);
7103
+ }
7104
+ }
7105
+ return chunks.join("\n\n");
7106
+ }
7107
+
7108
+ export { OBSERVATIONAL_MEMORY_DEFAULTS, OBSERVATION_CONTEXT_INSTRUCTIONS, OBSERVATION_CONTEXT_PROMPT, OBSERVATION_CONTINUATION_HINT, OBSERVER_SYSTEM_PROMPT, ObservationalMemory, TokenCounter, buildObserverPrompt, buildObserverSystemPrompt, extractCurrentTask, formatMessagesForObserver, getObservationsAsOf, hasCurrentTaskSection, optimizeObservationsForContext, parseObserverOutput };
7109
+ //# sourceMappingURL=chunk-4KPXPQX3.js.map
7110
+ //# sourceMappingURL=chunk-4KPXPQX3.js.map