@mastra/memory 1.9.0-alpha.1 → 1.9.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 (51) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/{chunk-5SMKVGJP.js → chunk-JJBSFPC5.js} +315 -25
  3. package/dist/chunk-JJBSFPC5.js.map +1 -0
  4. package/dist/{chunk-AR52LM55.cjs → chunk-LVV2RT42.cjs} +327 -24
  5. package/dist/chunk-LVV2RT42.cjs.map +1 -0
  6. package/dist/docs/SKILL.md +5 -7
  7. package/dist/docs/assets/SOURCE_MAP.json +77 -27
  8. package/dist/docs/references/docs-agents-agent-approval.md +114 -193
  9. package/dist/docs/references/docs-agents-networks.md +88 -205
  10. package/dist/docs/references/docs-agents-supervisor-agents.md +24 -18
  11. package/dist/docs/references/docs-memory-observational-memory.md +30 -2
  12. package/dist/docs/references/docs-memory-overview.md +219 -24
  13. package/dist/docs/references/docs-memory-semantic-recall.md +1 -1
  14. package/dist/docs/references/docs-memory-storage.md +4 -4
  15. package/dist/docs/references/docs-memory-working-memory.md +1 -1
  16. package/dist/docs/references/reference-core-getMemory.md +1 -2
  17. package/dist/docs/references/reference-core-listMemory.md +1 -2
  18. package/dist/docs/references/reference-memory-cloneThread.md +1 -1
  19. package/dist/docs/references/reference-memory-observational-memory.md +39 -1
  20. package/dist/index.cjs +432 -11
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +432 -10
  24. package/dist/index.js.map +1 -1
  25. package/dist/observational-memory-3XFCO6MX.js +3 -0
  26. package/dist/{observational-memory-5NFPG6M3.js.map → observational-memory-3XFCO6MX.js.map} +1 -1
  27. package/dist/observational-memory-MJJFU26W.cjs +108 -0
  28. package/dist/{observational-memory-NH7VDTXM.cjs.map → observational-memory-MJJFU26W.cjs.map} +1 -1
  29. package/dist/processors/index.cjs +56 -16
  30. package/dist/processors/index.js +1 -1
  31. package/dist/processors/observational-memory/anchor-ids.d.ts +4 -0
  32. package/dist/processors/observational-memory/anchor-ids.d.ts.map +1 -0
  33. package/dist/processors/observational-memory/index.d.ts +2 -0
  34. package/dist/processors/observational-memory/index.d.ts.map +1 -1
  35. package/dist/processors/observational-memory/observation-groups.d.ts +15 -0
  36. package/dist/processors/observational-memory/observation-groups.d.ts.map +1 -0
  37. package/dist/processors/observational-memory/observational-memory.d.ts +14 -0
  38. package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
  39. package/dist/processors/observational-memory/observer-agent.d.ts.map +1 -1
  40. package/dist/processors/observational-memory/reflector-agent.d.ts +1 -1
  41. package/dist/processors/observational-memory/reflector-agent.d.ts.map +1 -1
  42. package/dist/processors/observational-memory/tool-result-helpers.d.ts.map +1 -1
  43. package/dist/tools/om-tools.d.ts +77 -0
  44. package/dist/tools/om-tools.d.ts.map +1 -0
  45. package/package.json +8 -8
  46. package/dist/chunk-5SMKVGJP.js.map +0 -1
  47. package/dist/chunk-AR52LM55.cjs.map +0 -1
  48. package/dist/docs/references/docs-agents-agent-memory.md +0 -209
  49. package/dist/docs/references/docs-agents-network-approval.md +0 -278
  50. package/dist/observational-memory-5NFPG6M3.js +0 -3
  51. package/dist/observational-memory-NH7VDTXM.cjs +0 -68
package/CHANGELOG.md CHANGED
@@ -1,5 +1,66 @@
1
1
  # @mastra/memory
2
2
 
3
+ ## 1.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Added experimental retrieval-mode recall tooling for observational memory. ([#14437](https://github.com/mastra-ai/mastra/pull/14437))
8
+
9
+ When `observationalMemory.retrieval` is enabled with `scope: 'thread'`, observation groups store colon-delimited message ranges (`startId:endId`) pointing back to the raw messages they were derived from. A `recall` tool is registered that lets agents retrieve those source messages via cursor-based pagination.
10
+
11
+ The recall tool supports:
12
+ - **Detail levels**: `detail: 'low'` (default) returns truncated text with part indices; `detail: 'high'` returns full content clamped to one part per call with continuation hints
13
+ - **Part-level fetch**: `partIndex` targets a single message part at full detail
14
+ - **Pagination flags**: `hasNextPage` and `hasPrevPage` in results
15
+ - **Token limiting**: results are capped at a token budget with `truncated` and `tokenOffset` reporting
16
+ - **Smart range detection**: passing a range as a cursor returns a helpful hint explaining how to extract individual IDs
17
+
18
+ - Added opt-in Observational Memory thread titles. ([#14436](https://github.com/mastra-ai/mastra/pull/14436))
19
+
20
+ When enabled, the Observer suggests a short thread title and updates it as the conversation topic changes. Harness consumers can detect these updates via the new `om_thread_title_updated` event.
21
+
22
+ **Example**
23
+
24
+ ```ts
25
+ const memory = new Memory({
26
+ options: {
27
+ observationalMemory: {
28
+ observation: {
29
+ threadTitle: true,
30
+ },
31
+ },
32
+ },
33
+ });
34
+ ```
35
+
36
+ ### Patch Changes
37
+
38
+ - Improved observational memory so completed tasks and answered questions are explicitly tracked and retained, reducing repeated follow-up on resolved topics. ([#14419](https://github.com/mastra-ai/mastra/pull/14419))
39
+
40
+ - Updated dependencies [[`cb611a1`](https://github.com/mastra-ai/mastra/commit/cb611a1e89a4f4cf74c97b57e0c27bb56f2eceb5), [`da93115`](https://github.com/mastra-ai/mastra/commit/da931155c1a9bc63d455d3d86b4ec984db5991fe), [`b71bce1`](https://github.com/mastra-ai/mastra/commit/b71bce144912ed33f76c52a94e594988a649c3e1), [`62d1d3c`](https://github.com/mastra-ai/mastra/commit/62d1d3cc08fe8182e7080237fd975de862ec8c91), [`9e1a3ed`](https://github.com/mastra-ai/mastra/commit/9e1a3ed07cfafb5e8e19a796ce0bee817002d7c0), [`8681ecb`](https://github.com/mastra-ai/mastra/commit/8681ecb86184d5907267000e4576cc442a9a83fc), [`28d0249`](https://github.com/mastra-ai/mastra/commit/28d0249295782277040ad1e0d243e695b7ab1ce4), [`cd7b568`](https://github.com/mastra-ai/mastra/commit/cd7b568fe427b1b4838abe744fa5367a47539db3), [`681ee1c`](https://github.com/mastra-ai/mastra/commit/681ee1c811359efd1b8bebc4bce35b9bb7b14bec), [`bb0f09d`](https://github.com/mastra-ai/mastra/commit/bb0f09dbac58401b36069f483acf5673202db5b5), [`a579f7a`](https://github.com/mastra-ai/mastra/commit/a579f7a31e582674862b5679bc79af7ccf7429b8), [`5f7e9d0`](https://github.com/mastra-ai/mastra/commit/5f7e9d0db664020e1f3d97d7d18c6b0b9d4843d0), [`d7f14c3`](https://github.com/mastra-ai/mastra/commit/d7f14c3285cd253ecdd5f58139b7b6cbdf3678b5), [`0efe12a`](https://github.com/mastra-ai/mastra/commit/0efe12a5f008a939a1aac71699486ba40138054e)]:
41
+ - @mastra/core@1.15.0
42
+ - @mastra/schema-compat@1.2.6
43
+
44
+ ## 1.9.0-alpha.2
45
+
46
+ ### Minor Changes
47
+
48
+ - Added experimental retrieval-mode recall tooling for observational memory. ([#14437](https://github.com/mastra-ai/mastra/pull/14437))
49
+
50
+ When `observationalMemory.retrieval` is enabled with `scope: 'thread'`, observation groups store colon-delimited message ranges (`startId:endId`) pointing back to the raw messages they were derived from. A `recall` tool is registered that lets agents retrieve those source messages via cursor-based pagination.
51
+
52
+ The recall tool supports:
53
+ - **Detail levels**: `detail: 'low'` (default) returns truncated text with part indices; `detail: 'high'` returns full content clamped to one part per call with continuation hints
54
+ - **Part-level fetch**: `partIndex` targets a single message part at full detail
55
+ - **Pagination flags**: `hasNextPage` and `hasPrevPage` in results
56
+ - **Token limiting**: results are capped at a token budget with `truncated` and `tokenOffset` reporting
57
+ - **Smart range detection**: passing a range as a cursor returns a helpful hint explaining how to extract individual IDs
58
+
59
+ ### Patch Changes
60
+
61
+ - Updated dependencies [[`da93115`](https://github.com/mastra-ai/mastra/commit/da931155c1a9bc63d455d3d86b4ec984db5991fe), [`0efe12a`](https://github.com/mastra-ai/mastra/commit/0efe12a5f008a939a1aac71699486ba40138054e)]:
62
+ - @mastra/core@1.15.0-alpha.4
63
+
3
64
  ## 1.9.0-alpha.1
4
65
 
5
66
  ### Minor Changes
@@ -6,8 +6,8 @@ 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 { randomBytes, createHash, randomUUID } from 'crypto';
9
10
  import { estimateTokenCount } from 'tokenx';
10
- import { createHash, randomUUID } from 'crypto';
11
11
  import { AsyncLocalStorage } from 'async_hooks';
12
12
  import imageSize from 'image-size';
13
13
 
@@ -312,6 +312,222 @@ function createThreadUpdateMarker(params) {
312
312
  }
313
313
  };
314
314
  }
315
+ var OBSERVATION_GROUP_PATTERN = /<observation-group\s([^>]*)>([\s\S]*?)<\/observation-group>/g;
316
+ var ATTRIBUTE_PATTERN = /([\w][\w-]*)="([^"]*)"/g;
317
+ var REFLECTION_GROUP_SPLIT_PATTERN = /^##\s+Group\s+/m;
318
+ function parseObservationGroupAttributes(attributeString) {
319
+ const attributes = {};
320
+ for (const match of attributeString.matchAll(ATTRIBUTE_PATTERN)) {
321
+ const [, key, value] = match;
322
+ if (key && value !== void 0) {
323
+ attributes[key] = value;
324
+ }
325
+ }
326
+ return attributes;
327
+ }
328
+ function parseReflectionObservationGroupSections(content) {
329
+ const normalizedContent = content.trim();
330
+ if (!normalizedContent || !REFLECTION_GROUP_SPLIT_PATTERN.test(normalizedContent)) {
331
+ return [];
332
+ }
333
+ return normalizedContent.split(REFLECTION_GROUP_SPLIT_PATTERN).map((section) => section.trim()).filter(Boolean).map((section) => {
334
+ const newlineIndex = section.indexOf("\n");
335
+ const heading = (newlineIndex >= 0 ? section.slice(0, newlineIndex) : section).trim();
336
+ const body = (newlineIndex >= 0 ? section.slice(newlineIndex + 1) : "").trim();
337
+ return {
338
+ heading,
339
+ body: stripReflectionGroupMetadata(body)
340
+ };
341
+ });
342
+ }
343
+ function stripReflectionGroupMetadata(body) {
344
+ return body.replace(/^_range:\s*`[^`]*`_\s*\n?/m, "").trim();
345
+ }
346
+ function generateAnchorId() {
347
+ return randomBytes(8).toString("hex");
348
+ }
349
+ function wrapInObservationGroup(observations, range, id = generateAnchorId(), sourceGroupIds) {
350
+ const content = observations.trim();
351
+ const sourceGroupIdsAttr = sourceGroupIds?.length ? ` source-group-ids="${sourceGroupIds.join(",")}"` : "";
352
+ return `<observation-group id="${id}" range="${range}"${sourceGroupIdsAttr}>
353
+ ${content}
354
+ </observation-group>`;
355
+ }
356
+ function parseObservationGroups(observations) {
357
+ if (!observations) {
358
+ return [];
359
+ }
360
+ const groups = [];
361
+ let match;
362
+ while ((match = OBSERVATION_GROUP_PATTERN.exec(observations)) !== null) {
363
+ const attributes = parseObservationGroupAttributes(match[1] ?? "");
364
+ const id = attributes.id;
365
+ const range = attributes.range;
366
+ if (!id || !range) {
367
+ continue;
368
+ }
369
+ groups.push({
370
+ id,
371
+ range,
372
+ content: match[2].trim(),
373
+ sourceGroupIds: attributes["source-group-ids"]?.split(",").map((part) => part.trim()).filter(Boolean)
374
+ });
375
+ }
376
+ return groups;
377
+ }
378
+ function stripObservationGroups(observations) {
379
+ if (!observations) {
380
+ return observations;
381
+ }
382
+ return observations.replace(OBSERVATION_GROUP_PATTERN, (_match, _attributes, content) => content.trim()).replace(/\n{3,}/g, "\n\n").trim();
383
+ }
384
+ function combineObservationGroupRanges(groups) {
385
+ return Array.from(
386
+ new Set(
387
+ groups.flatMap((group) => group.range.split(",")).map((range) => range.trim()).filter(Boolean)
388
+ )
389
+ ).join(",");
390
+ }
391
+ function renderObservationGroupsForReflection(observations) {
392
+ const groups = parseObservationGroups(observations);
393
+ if (groups.length === 0) {
394
+ return null;
395
+ }
396
+ const groupsByContent = new Map(groups.map((g) => [g.content.trim(), g]));
397
+ const result = observations.replace(OBSERVATION_GROUP_PATTERN, (_match, _attrs, content) => {
398
+ const group = groupsByContent.get(content.trim());
399
+ if (!group) return content.trim();
400
+ return `## Group \`${group.id}\`
401
+ _range: \`${group.range}\`_
402
+
403
+ ${group.content}`;
404
+ });
405
+ return result.replace(/\n{3,}/g, "\n\n").trim();
406
+ }
407
+ function getCanonicalGroupId(sectionHeading, fallbackIndex) {
408
+ const match = sectionHeading.match(/`([^`]+)`/);
409
+ return match?.[1]?.trim() || `derived-group-${fallbackIndex + 1}`;
410
+ }
411
+ function deriveObservationGroupProvenance(content, groups) {
412
+ const sections = parseReflectionObservationGroupSections(content);
413
+ if (sections.length === 0 || groups.length === 0) {
414
+ return [];
415
+ }
416
+ return sections.map((section, index) => {
417
+ const bodyLines = new Set(
418
+ section.body.split("\n").map((line) => line.trim()).filter(Boolean)
419
+ );
420
+ const matchingGroups = groups.filter((group) => {
421
+ const groupLines = group.content.split("\n").map((line) => line.trim()).filter(Boolean);
422
+ return groupLines.some((line) => bodyLines.has(line));
423
+ });
424
+ const fallbackGroup = groups[Math.min(index, groups.length - 1)];
425
+ const resolvedGroups = matchingGroups.length > 0 ? matchingGroups : fallbackGroup ? [fallbackGroup] : [];
426
+ const sourceGroupIds = Array.from(
427
+ new Set(resolvedGroups.flatMap((group) => [group.id, ...group.sourceGroupIds ?? []]))
428
+ );
429
+ const canonicalGroupId = getCanonicalGroupId(section.heading, index);
430
+ return {
431
+ id: canonicalGroupId,
432
+ range: combineObservationGroupRanges(resolvedGroups),
433
+ content: section.body,
434
+ sourceGroupIds
435
+ };
436
+ });
437
+ }
438
+ function reconcileObservationGroupsFromReflection(content, sourceObservations) {
439
+ const sourceGroups = parseObservationGroups(sourceObservations);
440
+ if (sourceGroups.length === 0) {
441
+ return null;
442
+ }
443
+ const normalizedContent = content.trim();
444
+ if (!normalizedContent) {
445
+ return "";
446
+ }
447
+ const derivedGroups = deriveObservationGroupProvenance(normalizedContent, sourceGroups);
448
+ if (derivedGroups.length > 0) {
449
+ return derivedGroups.map((group) => wrapInObservationGroup(group.content, group.range, group.id, group.sourceGroupIds)).join("\n\n");
450
+ }
451
+ return wrapInObservationGroup(
452
+ normalizedContent,
453
+ combineObservationGroupRanges(sourceGroups),
454
+ generateAnchorId(),
455
+ Array.from(new Set(sourceGroups.flatMap((group) => [group.id, ...group.sourceGroupIds ?? []])))
456
+ );
457
+ }
458
+
459
+ // src/processors/observational-memory/anchor-ids.ts
460
+ var ANCHOR_ID_PATTERN = /^\[(O\d+(?:-N\d+)?)\]\s*/;
461
+ var OBSERVATION_DATE_HEADER_PATTERN = /^\s*Date:\s+/;
462
+ var XML_TAG_PATTERN = /^\s*<\/?[a-z][^>]*>\s*$/i;
463
+ var MARKDOWN_GROUP_HEADING_PATTERN = /^\s*##\s+Group\s+`[^`]+`\s*$/;
464
+ var MARKDOWN_GROUP_METADATA_PATTERN = /^\s*_range:\s*`[^`]*`_\s*$/;
465
+ function buildEphemeralAnchorId(topLevelCounter, nestedCounter) {
466
+ return nestedCounter === 0 ? `O${topLevelCounter}` : `O${topLevelCounter}-N${nestedCounter}`;
467
+ }
468
+ function parseAnchorId(line) {
469
+ const match = line.match(ANCHOR_ID_PATTERN);
470
+ return match?.[1] ?? null;
471
+ }
472
+ function shouldAnchorLine(line) {
473
+ const trimmed = line.trim();
474
+ if (!trimmed) {
475
+ return false;
476
+ }
477
+ if (parseAnchorId(trimmed)) {
478
+ return false;
479
+ }
480
+ if (OBSERVATION_DATE_HEADER_PATTERN.test(trimmed)) {
481
+ return false;
482
+ }
483
+ if (XML_TAG_PATTERN.test(trimmed)) {
484
+ return false;
485
+ }
486
+ if (MARKDOWN_GROUP_HEADING_PATTERN.test(trimmed) || MARKDOWN_GROUP_METADATA_PATTERN.test(trimmed)) {
487
+ return false;
488
+ }
489
+ return true;
490
+ }
491
+ function getIndentationDepth(line) {
492
+ const leadingWhitespace = line.match(/^\s*/)?.[0] ?? "";
493
+ return Math.floor(leadingWhitespace.replace(/\t/g, " ").length / 2);
494
+ }
495
+ function injectAnchorIds(observations) {
496
+ if (!observations) {
497
+ return observations;
498
+ }
499
+ const lines = observations.split("\n");
500
+ let topLevelCounter = 0;
501
+ let nestedCounter = 0;
502
+ let changed = false;
503
+ for (let i = 0; i < lines.length; i++) {
504
+ const line = lines[i];
505
+ if (!shouldAnchorLine(line)) {
506
+ continue;
507
+ }
508
+ const indentationDepth = getIndentationDepth(line);
509
+ if (indentationDepth === 0) {
510
+ topLevelCounter += 1;
511
+ nestedCounter = 0;
512
+ } else {
513
+ if (topLevelCounter === 0) {
514
+ topLevelCounter = 1;
515
+ }
516
+ nestedCounter += 1;
517
+ }
518
+ const anchorId = buildEphemeralAnchorId(topLevelCounter, nestedCounter);
519
+ const leadingWhitespace = line.match(/^\s*/)?.[0] ?? "";
520
+ lines[i] = `${leadingWhitespace}[${anchorId}] ${line.slice(leadingWhitespace.length)}`;
521
+ changed = true;
522
+ }
523
+ return changed ? lines.join("\n") : observations;
524
+ }
525
+ function stripEphemeralAnchorIds(observations) {
526
+ if (!observations) {
527
+ return observations;
528
+ }
529
+ return observations.replace(/(^|\n)([^\S\n]*)\[(O\d+(?:-N\d+)?)\][^\S\n]*/g, "$1$2");
530
+ }
315
531
  var ENCRYPTED_CONTENT_KEY = "encryptedContent";
316
532
  var ENCRYPTED_CONTENT_REDACTION_THRESHOLD = 256;
317
533
  var DEFAULT_OBSERVER_TOOL_RESULT_MAX_TOKENS = 1e4;
@@ -378,9 +594,8 @@ function truncateStringByTokens(text, maxTokens) {
378
594
  }
379
595
  const buildCandidate = (sliceEnd) => {
380
596
  const visible = text.slice(0, sliceEnd);
381
- const omittedChars = text.length - sliceEnd;
382
597
  return `${visible}
383
- ... [truncated ~${totalTokens - estimateTokenCount(visible)} tokens / ${omittedChars} characters]`;
598
+ ... [truncated ~${totalTokens - estimateTokenCount(visible)} tokens]`;
384
599
  };
385
600
  let low = 0;
386
601
  let high = text.length;
@@ -1379,7 +1594,7 @@ function extractCurrentTask(observations) {
1379
1594
  return content || null;
1380
1595
  }
1381
1596
  function optimizeObservationsForContext(observations) {
1382
- let optimized = observations;
1597
+ let optimized = stripEphemeralAnchorIds(observations);
1383
1598
  optimized = optimized.replace(/🟡\s*/g, "");
1384
1599
  optimized = optimized.replace(/🟢\s*/g, "");
1385
1600
  optimized = optimized.replace(/\[(?![\d\s]*items collapsed)[^\]]+\]/g, "");
@@ -1569,9 +1784,11 @@ Your current detail level was a 10/10, lets aim for a 4/10 detail level.
1569
1784
  };
1570
1785
  function buildReflectorPrompt(observations, manualPrompt, compressionLevel, skipContinuationHints) {
1571
1786
  const level = typeof compressionLevel === "number" ? compressionLevel : compressionLevel ? 1 : 0;
1787
+ const reflectionView = renderObservationGroupsForReflection(observations) ?? observations;
1788
+ const anchoredObservations = injectAnchorIds(reflectionView);
1572
1789
  let prompt = `## OBSERVATIONS TO REFLECT ON
1573
1790
 
1574
- ${observations}
1791
+ ${anchoredObservations}
1575
1792
 
1576
1793
  ---
1577
1794
 
@@ -1596,7 +1813,7 @@ IMPORTANT: Do NOT include <current-task> or <suggested-response> sections in you
1596
1813
  }
1597
1814
  return prompt;
1598
1815
  }
1599
- function parseReflectorOutput(output) {
1816
+ function parseReflectorOutput(output, sourceObservations) {
1600
1817
  if (detectDegenerateRepetition(output)) {
1601
1818
  return {
1602
1819
  observations: "",
@@ -1604,9 +1821,10 @@ function parseReflectorOutput(output) {
1604
1821
  };
1605
1822
  }
1606
1823
  const parsed = parseReflectorSectionXml(output);
1607
- const observations = sanitizeObservationLines(parsed.observations || "");
1824
+ const sanitizedObservations = sanitizeObservationLines(stripEphemeralAnchorIds(parsed.observations || ""));
1825
+ const reconciledObservations = sourceObservations ? reconcileObservationGroupsFromReflection(sanitizedObservations, sourceObservations) : null;
1608
1826
  return {
1609
- observations,
1827
+ observations: reconciledObservations ?? sanitizedObservations,
1610
1828
  suggestedContinuation: parsed.suggestedResponse || void 0
1611
1829
  // Note: Reflector's currentTask is not used - thread metadata preserves per-thread tasks
1612
1830
  };
@@ -3102,7 +3320,24 @@ if (OM_DEBUG_LOG) {
3102
3320
  _origConsoleError.apply(console, args);
3103
3321
  };
3104
3322
  }
3323
+ function messageHasVisibleContent(msg) {
3324
+ const content = msg.content;
3325
+ if (content?.parts && Array.isArray(content.parts)) {
3326
+ return content.parts.some((p) => {
3327
+ const t = p?.type;
3328
+ return t && !t.startsWith("data-") && t !== "step-start";
3329
+ });
3330
+ }
3331
+ if (content?.content) return true;
3332
+ return false;
3333
+ }
3334
+ function buildMessageRange(messages) {
3335
+ const first = messages.find(messageHasVisibleContent) ?? messages[0];
3336
+ const last = [...messages].reverse().find(messageHasVisibleContent) ?? messages[messages.length - 1];
3337
+ return `${first.id}:${last.id}`;
3338
+ }
3105
3339
  var OBSERVATIONAL_MEMORY_DEFAULTS = {
3340
+ retrieval: false,
3106
3341
  observation: {
3107
3342
  model: "google/gemini-2.5-flash",
3108
3343
  messageTokens: 3e4,
@@ -3157,12 +3392,54 @@ KNOWLEDGE UPDATES: When asked about current state (e.g., "where do I currently..
3157
3392
  PLANNED ACTIONS: If the user stated they planned to do something (e.g., "I'm going to...", "I'm looking forward to...", "I will...") and the date they planned to do it is now in the past (check the relative time like "3 weeks ago"), assume they completed the action unless there's evidence they didn't. For example, if someone said "I'll start my new diet on Monday" and that was 2 weeks ago, assume they started the diet.
3158
3393
 
3159
3394
  MOST RECENT USER INPUT: Treat the most recent user message as the highest-priority signal for what to do next. Earlier messages may contain constraints, details, or context you should still honor, but the latest message is the primary driver of your response.`;
3395
+ var OBSERVATION_RETRIEVAL_INSTRUCTIONS = `## Recall \u2014 looking up source messages
3396
+
3397
+ Your memory is comprised of observations which are sometimes wrapped in <observation-group> xml tags containing ranges like <observation-group range="startId:endId">. These ranges point back to the raw messages that each observation group was derived from. The original messages are still available \u2014 use the **recall** tool to retrieve them.
3398
+
3399
+ ### When to use recall
3400
+ - The user asks you to **repeat, show, or reproduce** something from a past conversation
3401
+ - The user asks for **exact content** \u2014 code, text, quotes, error messages, URLs, file paths, specific numbers
3402
+ - Your observations mention something but your memory lacks the detail needed to fully answer (e.g. you know a blog post was shared but only have a summary of it)
3403
+ - You want to **verify or expand on** an observation before responding
3404
+
3405
+ **Default to using recall when the user references specific past content.** Your observations capture the gist, not the details. If there's any doubt whether your memory is complete enough, use recall.
3406
+
3407
+ ### How to use recall
3408
+ Each range has the format \`startId:endId\` where both are message IDs separated by a colon.
3409
+
3410
+ 1. Find the observation group relevant to the user's question and extract the start or end ID from its range.
3411
+ 2. Call \`recall\` with that ID as the \`cursor\`.
3412
+ 3. Use \`page: 1\` (or omit) to read forward from the cursor, \`page: -1\` to read backward.
3413
+ 4. If the first page doesn't have what you need, increment the page number to keep paginating.
3414
+ 5. Check \`hasNextPage\`/\`hasPrevPage\` in the result to know if more pages exist in each direction.
3415
+
3416
+ ### Detail levels
3417
+ By default recall returns **low** detail: truncated text and tool names only. Each message shows its ID and each part has a positional index like \`[p0]\`, \`[p1]\`, etc.
3418
+
3419
+ - Use \`detail: "high"\` to get full message content including tool arguments and results. This will only return the high detail version of a single message part at a time.
3420
+ - Use \`partIndex\` with a cursor to fetch a single part at full detail \u2014 for example, to read one specific tool result or code block without loading every part.
3421
+
3422
+ If the result says \`truncated: true\`, the output was cut to fit the token budget. You can paginate or use \`partIndex\` to target specific content.
3423
+
3424
+ ### Following up on truncated parts
3425
+ Low-detail results may include truncation hints like:
3426
+ \`[truncated \u2014 call recall cursor="..." partIndex=N detail="high" for full content]\`
3427
+
3428
+ **When you see these hints and need the full content, make the exact call described in the hint.** This is the normal workflow: first recall at low detail to scan, then drill into specific parts at high detail. Do not stop at the low-detail result if the user asked for exact content.
3429
+
3430
+ ### When recall is NOT needed
3431
+ - The user is asking for a high-level summary and your observations already cover it
3432
+ - The question is about general preferences or facts that don't require source text
3433
+ - There is no relevant range in your observations for the topic
3434
+
3435
+ Observation groups with range IDs and your recall tool allows you to think back and remember details you're fuzzy on.`;
3160
3436
  var ObservationalMemory = class _ObservationalMemory {
3161
3437
  id = "observational-memory";
3162
3438
  name = "Observational Memory";
3163
3439
  storage;
3164
3440
  tokenCounter;
3165
3441
  scope;
3442
+ retrieval = false;
3166
3443
  observationConfig;
3167
3444
  reflectionConfig;
3168
3445
  onDebugEvent;
@@ -3475,6 +3752,7 @@ var ObservationalMemory = class _ObservationalMemory {
3475
3752
  this.shouldObscureThreadIds = config.obscureThreadIds || false;
3476
3753
  this.storage = config.storage;
3477
3754
  this.scope = config.scope ?? "thread";
3755
+ this.retrieval = this.scope === "thread" && (config.retrieval ?? OBSERVATIONAL_MEMORY_DEFAULTS.retrieval);
3478
3756
  const resolveModel = (m) => m === "default" ? OBSERVATIONAL_MEMORY_DEFAULTS.observation.model : m;
3479
3757
  const observationModel = resolveModel(config.model) ?? resolveModel(config.observation?.model) ?? resolveModel(config.reflection?.model);
3480
3758
  const reflectionModel = resolveModel(config.model) ?? resolveModel(config.reflection?.model) ?? resolveModel(config.observation?.model);
@@ -3578,6 +3856,7 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
3578
3856
  get config() {
3579
3857
  return {
3580
3858
  scope: this.scope,
3859
+ retrieval: this.retrieval,
3581
3860
  observation: {
3582
3861
  messageTokens: this.observationConfig.messageTokens,
3583
3862
  previousObserverTokens: this.observationConfig.previousObserverTokens
@@ -3834,7 +4113,8 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
3834
4113
  config: {
3835
4114
  observation: this.observationConfig,
3836
4115
  reflection: this.reflectionConfig,
3837
- scope: this.scope
4116
+ scope: this.scope,
4117
+ retrieval: this.retrieval
3838
4118
  },
3839
4119
  observedTimezone
3840
4120
  });
@@ -4475,7 +4755,7 @@ ${unreflectedContent}` : bufferedReflection;
4475
4755
  totalUsage.outputTokens += usage.outputTokens ?? 0;
4476
4756
  totalUsage.totalTokens += usage.totalTokens ?? 0;
4477
4757
  }
4478
- parsed = parseReflectorOutput(result.text);
4758
+ parsed = parseReflectorOutput(result.text, observations);
4479
4759
  if (parsed.degenerate) {
4480
4760
  omDebug(
4481
4761
  `[OM:callReflector] attempt #${attemptNumber}: degenerate repetition detected, treating as compression failure`
@@ -4542,14 +4822,18 @@ ${unreflectedContent}` : bufferedReflection;
4542
4822
  * @param suggestedResponse - Thread-specific suggested response (from thread metadata)
4543
4823
  * @param unobservedContextBlocks - Formatted <unobserved-context> blocks from other threads
4544
4824
  */
4545
- formatObservationsForContext(observations, currentTask, suggestedResponse, unobservedContextBlocks, currentDate) {
4546
- let optimized = optimizeObservationsForContext(observations);
4825
+ formatObservationsForContext(observations, currentTask, suggestedResponse, unobservedContextBlocks, currentDate, retrieval = false) {
4826
+ let optimized = retrieval ? renderObservationGroupsForReflection(observations) ?? observations : optimizeObservationsForContext(observations);
4547
4827
  if (currentDate) {
4548
4828
  optimized = addRelativeTimeToObservations(optimized, currentDate);
4549
4829
  }
4550
- const messages = [`${OBSERVATION_CONTEXT_PROMPT}
4830
+ const messages = [
4831
+ `${OBSERVATION_CONTEXT_PROMPT}
4551
4832
 
4552
- ${OBSERVATION_CONTEXT_INSTRUCTIONS}`];
4833
+ ${OBSERVATION_CONTEXT_INSTRUCTIONS}${retrieval ? `
4834
+
4835
+ ${OBSERVATION_RETRIEVAL_INSTRUCTIONS}` : ""}`
4836
+ ];
4553
4837
  if (unobservedContextBlocks) {
4554
4838
  messages.push(
4555
4839
  `The following content is from OTHER conversations different from the current conversation, they're here for reference, but they're not necessarily your focus:
@@ -5034,7 +5318,8 @@ ${suggestedResponse}
5034
5318
  currentTask,
5035
5319
  suggestedResponse,
5036
5320
  unobservedContextBlocks,
5037
- currentDate
5321
+ currentDate,
5322
+ this.retrieval
5038
5323
  );
5039
5324
  messageList.clearSystemMessages("observational-memory");
5040
5325
  messageList.addSystem(observationSystemMessages, "observational-memory");
@@ -5702,11 +5987,12 @@ ${formattedMessages}
5702
5987
  * Wrap observations in a thread attribution tag.
5703
5988
  * Used in resource scope to track which thread observations came from.
5704
5989
  */
5705
- async wrapWithThreadTag(threadId, observations) {
5990
+ async wrapWithThreadTag(threadId, observations, messageRange) {
5706
5991
  const cleanObservations = this.stripThreadTags(observations);
5992
+ const groupedObservations = this.retrieval && messageRange ? wrapInObservationGroup(cleanObservations, messageRange) : cleanObservations;
5707
5993
  const obscuredId = await this.representThreadIDInContext(threadId);
5708
5994
  return `<thread id="${obscuredId}">
5709
- ${cleanObservations}
5995
+ ${groupedObservations}
5710
5996
  </thread>`;
5711
5997
  }
5712
5998
  /**
@@ -5843,9 +6129,10 @@ ${threadClose}`;
5843
6129
  });
5844
6130
  const lastObservedAt = this.getMaxMessageTimestamp(messagesToObserve);
5845
6131
  const existingObservations = freshRecord?.activeObservations ?? record.activeObservations ?? "";
6132
+ const messageRange = this.retrieval ? buildMessageRange(messagesToObserve) : void 0;
5846
6133
  let newObservations;
5847
6134
  if (this.scope === "resource") {
5848
- const threadSection = await this.wrapWithThreadTag(threadId, result.observations);
6135
+ const threadSection = await this.wrapWithThreadTag(threadId, result.observations, messageRange);
5849
6136
  newObservations = this.replaceOrAppendThreadSection(
5850
6137
  existingObservations,
5851
6138
  threadId,
@@ -5853,7 +6140,8 @@ ${threadClose}`;
5853
6140
  lastObservedAt
5854
6141
  );
5855
6142
  } else {
5856
- newObservations = existingObservations ? `${existingObservations}${_ObservationalMemory.createMessageBoundary(lastObservedAt)}${result.observations}` : result.observations;
6143
+ const groupedObservations = this.retrieval && messageRange ? wrapInObservationGroup(result.observations, messageRange) : result.observations;
6144
+ newObservations = existingObservations ? `${existingObservations}${_ObservationalMemory.createMessageBoundary(lastObservedAt)}${groupedObservations}` : groupedObservations;
5857
6145
  }
5858
6146
  let totalTokenCount = this.tokenCounter.countObservations(newObservations);
5859
6147
  const cycleObservationTokens = this.tokenCounter.countObservations(result.observations);
@@ -6161,11 +6449,12 @@ ${threadClose}`;
6161
6449
  }
6162
6450
  }
6163
6451
  }
6452
+ const messageRange = this.retrieval ? buildMessageRange(messagesToBuffer) : void 0;
6164
6453
  let newObservations;
6165
6454
  if (this.scope === "resource") {
6166
- newObservations = await this.wrapWithThreadTag(threadId, result.observations);
6455
+ newObservations = await this.wrapWithThreadTag(threadId, result.observations, messageRange);
6167
6456
  } else {
6168
- newObservations = result.observations;
6457
+ newObservations = this.retrieval && messageRange ? wrapInObservationGroup(result.observations, messageRange) : result.observations;
6169
6458
  }
6170
6459
  const newTokenCount = this.tokenCounter.countObservations(newObservations);
6171
6460
  const newMessageIds = messagesToBuffer.map((m) => m.id);
@@ -6791,7 +7080,8 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
6791
7080
  const { threadId, threadMessages, result } = obsResult;
6792
7081
  cycleObservationTokens += this.tokenCounter.countObservations(result.observations);
6793
7082
  const threadLastObservedAt = this.getMaxMessageTimestamp(threadMessages);
6794
- const threadSection = await this.wrapWithThreadTag(threadId, result.observations);
7083
+ const messageRange = this.retrieval ? buildMessageRange(threadMessages) : void 0;
7084
+ const threadSection = await this.wrapWithThreadTag(threadId, result.observations, messageRange);
6795
7085
  currentObservations = this.replaceOrAppendThreadSection(
6796
7086
  currentObservations,
6797
7087
  threadId,
@@ -7273,6 +7563,6 @@ function getObservationsAsOf(activeObservations, asOf) {
7273
7563
  return chunks.join("\n\n");
7274
7564
  }
7275
7565
 
7276
- 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 };
7277
- //# sourceMappingURL=chunk-5SMKVGJP.js.map
7278
- //# sourceMappingURL=chunk-5SMKVGJP.js.map
7566
+ export { OBSERVATIONAL_MEMORY_DEFAULTS, OBSERVATION_CONTEXT_INSTRUCTIONS, OBSERVATION_CONTEXT_PROMPT, OBSERVATION_CONTINUATION_HINT, OBSERVER_SYSTEM_PROMPT, ObservationalMemory, TokenCounter, buildObserverPrompt, buildObserverSystemPrompt, combineObservationGroupRanges, deriveObservationGroupProvenance, extractCurrentTask, formatMessagesForObserver, formatToolResultForObserver, getObservationsAsOf, hasCurrentTaskSection, injectAnchorIds, optimizeObservationsForContext, parseAnchorId, parseObservationGroups, parseObserverOutput, reconcileObservationGroupsFromReflection, renderObservationGroupsForReflection, resolveToolResultValue, stripEphemeralAnchorIds, stripObservationGroups, truncateStringByTokens, wrapInObservationGroup };
7567
+ //# sourceMappingURL=chunk-JJBSFPC5.js.map
7568
+ //# sourceMappingURL=chunk-JJBSFPC5.js.map