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