@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.
- package/CHANGELOG.md +61 -0
- package/dist/{chunk-5SMKVGJP.js → chunk-JJBSFPC5.js} +315 -25
- package/dist/chunk-JJBSFPC5.js.map +1 -0
- package/dist/{chunk-AR52LM55.cjs → chunk-LVV2RT42.cjs} +327 -24
- package/dist/chunk-LVV2RT42.cjs.map +1 -0
- package/dist/docs/SKILL.md +5 -7
- package/dist/docs/assets/SOURCE_MAP.json +77 -27
- package/dist/docs/references/docs-agents-agent-approval.md +114 -193
- package/dist/docs/references/docs-agents-networks.md +88 -205
- package/dist/docs/references/docs-agents-supervisor-agents.md +24 -18
- package/dist/docs/references/docs-memory-observational-memory.md +30 -2
- package/dist/docs/references/docs-memory-overview.md +219 -24
- package/dist/docs/references/docs-memory-semantic-recall.md +1 -1
- package/dist/docs/references/docs-memory-storage.md +4 -4
- package/dist/docs/references/docs-memory-working-memory.md +1 -1
- package/dist/docs/references/reference-core-getMemory.md +1 -2
- package/dist/docs/references/reference-core-listMemory.md +1 -2
- package/dist/docs/references/reference-memory-cloneThread.md +1 -1
- package/dist/docs/references/reference-memory-observational-memory.md +39 -1
- package/dist/index.cjs +432 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +432 -10
- package/dist/index.js.map +1 -1
- package/dist/observational-memory-3XFCO6MX.js +3 -0
- package/dist/{observational-memory-5NFPG6M3.js.map → observational-memory-3XFCO6MX.js.map} +1 -1
- package/dist/observational-memory-MJJFU26W.cjs +108 -0
- package/dist/{observational-memory-NH7VDTXM.cjs.map → observational-memory-MJJFU26W.cjs.map} +1 -1
- package/dist/processors/index.cjs +56 -16
- package/dist/processors/index.js +1 -1
- package/dist/processors/observational-memory/anchor-ids.d.ts +4 -0
- package/dist/processors/observational-memory/anchor-ids.d.ts.map +1 -0
- package/dist/processors/observational-memory/index.d.ts +2 -0
- package/dist/processors/observational-memory/index.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-groups.d.ts +15 -0
- package/dist/processors/observational-memory/observation-groups.d.ts.map +1 -0
- package/dist/processors/observational-memory/observational-memory.d.ts +14 -0
- package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
- package/dist/processors/observational-memory/observer-agent.d.ts.map +1 -1
- package/dist/processors/observational-memory/reflector-agent.d.ts +1 -1
- package/dist/processors/observational-memory/reflector-agent.d.ts.map +1 -1
- package/dist/processors/observational-memory/tool-result-helpers.d.ts.map +1 -1
- package/dist/tools/om-tools.d.ts +77 -0
- package/dist/tools/om-tools.d.ts.map +1 -0
- package/package.json +8 -8
- package/dist/chunk-5SMKVGJP.js.map +0 -1
- package/dist/chunk-AR52LM55.cjs.map +0 -1
- package/dist/docs/references/docs-agents-agent-memory.md +0 -209
- package/dist/docs/references/docs-agents-network-approval.md +0 -278
- package/dist/observational-memory-5NFPG6M3.js +0 -3
- 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
|
|
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
|
-
${
|
|
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
|
|
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 = [
|
|
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
|
-
${
|
|
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
|
-
|
|
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
|
|
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-
|
|
7278
|
-
//# sourceMappingURL=chunk-
|
|
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
|