@mastra/server 1.76.0 → 1.77.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/dist/{dist-C8tgsOw_.cjs → dist-BOW7V2e6.cjs} +1036 -299
- package/dist/dist-BOW7V2e6.cjs.map +1 -0
- package/dist/{dist-C68HDUAO.js → dist-BmwOhJTG.js} +1037 -300
- package/dist/dist-BmwOhJTG.js.map +1 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/server/handlers/agent-builder.cjs +1 -1
- package/dist/server/handlers/agent-builder.js +1 -1
- package/package.json +4 -4
- package/dist/dist-C68HDUAO.js.map +0 -1
- package/dist/dist-C8tgsOw_.cjs.map +0 -1
|
@@ -4637,7 +4637,7 @@ function splitLines(text) {
|
|
|
4637
4637
|
return result;
|
|
4638
4638
|
}
|
|
4639
4639
|
//#endregion
|
|
4640
|
-
//#region ../memory/dist/src-
|
|
4640
|
+
//#region ../memory/dist/src-ThciS3vu.js
|
|
4641
4641
|
var __defProp$3 = Object.defineProperty;
|
|
4642
4642
|
var __exportAll = (all, no_symbols) => {
|
|
4643
4643
|
let target = {};
|
|
@@ -25063,7 +25063,7 @@ Group related observations (like tool sequences) by indenting:
|
|
|
25063
25063
|
* -> applied fix, tests now pass
|
|
25064
25064
|
* ✅ Tests passing, auth issue resolved
|
|
25065
25065
|
|
|
25066
|
-
Group observations by date,
|
|
25066
|
+
Group observations by date. Within a date, list observations in the order the events happened (not by importance), each with 24-hour time.
|
|
25067
25067
|
|
|
25068
25068
|
<observations>
|
|
25069
25069
|
Date: Dec 4, 2025
|
|
@@ -25436,8 +25436,8 @@ function extractToolResultAttachments(result, counter, attachmentFilter) {
|
|
|
25436
25436
|
attachments
|
|
25437
25437
|
};
|
|
25438
25438
|
}
|
|
25439
|
-
function formatObserverPartLine(title, body, time, previousTime) {
|
|
25440
|
-
const timeLabel = time && time !== previousTime ? `(${time})` : "";
|
|
25439
|
+
function formatObserverPartLine(title, body, time, previousTime, alwaysShowTime) {
|
|
25440
|
+
const timeLabel = time && (alwaysShowTime || time !== previousTime) ? `(${time})` : "";
|
|
25441
25441
|
if (!title) return timeLabel ? `${timeLabel}: ${body}` : body;
|
|
25442
25442
|
return `${title}${timeLabel ? ` ${timeLabel}` : ""}: ${body}`;
|
|
25443
25443
|
}
|
|
@@ -25462,7 +25462,7 @@ function formatObserverLines(lines, context = {}) {
|
|
|
25462
25462
|
previousDate = line.date;
|
|
25463
25463
|
previousTime = void 0;
|
|
25464
25464
|
}
|
|
25465
|
-
output.push(formatObserverPartLine(line.title, line.body, line.time, previousTime));
|
|
25465
|
+
output.push(formatObserverPartLine(line.title, line.body, line.time, previousTime, line.alwaysShowTime));
|
|
25466
25466
|
previousTime = line.time || previousTime;
|
|
25467
25467
|
}
|
|
25468
25468
|
return {
|
|
@@ -25563,14 +25563,15 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
25563
25563
|
const unavailableUrls = getUnavailableAttachmentUrls(msg);
|
|
25564
25564
|
let lines = [];
|
|
25565
25565
|
const temporalGapText = isTemporalGapMarker(msg) ? getTemporalGapMarkerText(msg) : void 0;
|
|
25566
|
-
const pushLine = (title, body, createdAt) => {
|
|
25566
|
+
const pushLine = (title, body, createdAt, opts) => {
|
|
25567
25567
|
if (!body) return;
|
|
25568
25568
|
const normalizedCreatedAt = normalizeObserverCreatedAt(createdAt) ?? messageCreatedAt;
|
|
25569
25569
|
lines.push({
|
|
25570
25570
|
date: formatObserverDate(normalizedCreatedAt, timeZone),
|
|
25571
25571
|
time: formatObserverTime(normalizedCreatedAt, timeZone),
|
|
25572
25572
|
title,
|
|
25573
|
-
body
|
|
25573
|
+
body,
|
|
25574
|
+
alwaysShowTime: opts?.tool === true
|
|
25574
25575
|
});
|
|
25575
25576
|
};
|
|
25576
25577
|
if (temporalGapText) pushLine("", temporalGapText, messageCreatedAt);
|
|
@@ -25587,7 +25588,7 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
25587
25588
|
const exchange = toolFormatting.exchanges.get(inv.toolCallId);
|
|
25588
25589
|
if (exchange && occurrence === exchange.firstOccurrence && exchange.signature && !toolFormatting.emittedCalls.has(inv.toolCallId)) {
|
|
25589
25590
|
toolFormatting.emittedCalls.add(inv.toolCallId);
|
|
25590
|
-
pushLine(`Tool Call ${exchange.signature.toolName}`, formatObserverToolArguments(exchange.signature.args, maxLen), partCreatedAt);
|
|
25591
|
+
pushLine(`Tool Call ${exchange.signature.toolName}`, formatObserverToolArguments(exchange.signature.args, maxLen), partCreatedAt, { tool: true });
|
|
25591
25592
|
}
|
|
25592
25593
|
if (inv.state === "result") {
|
|
25593
25594
|
const { value: resultForObserver } = resolveToolResultValue(part, inv.result);
|
|
@@ -25597,17 +25598,17 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
25597
25598
|
toolFormatting.emittedOutcomes.add(inv.toolCallId);
|
|
25598
25599
|
const hasResult = resultWithoutAttachments !== void 0 && (typeof resultWithoutAttachments !== "string" || resultWithoutAttachments.trim().length > 0);
|
|
25599
25600
|
const body = inv.isError ? hasResult ? formatToolResultForObserver(resultWithoutAttachments, { maxTokens: maxToolResultTokens }) : inv.errorText?.trim() ? inv.errorText : "Tool execution failed" : hasResult ? formatToolResultForObserver(resultWithoutAttachments, { maxTokens: maxToolResultTokens }) : "[empty result]";
|
|
25600
|
-
pushLine(`${inv.isError ? "Tool Error" : "Tool Result"} ${inv.toolName}`, maybeTruncate(body, maxLen), partCreatedAt);
|
|
25601
|
+
pushLine(`${inv.isError ? "Tool Error" : "Tool Result"} ${inv.toolName}`, maybeTruncate(body, maxLen), partCreatedAt, { tool: true });
|
|
25601
25602
|
}
|
|
25602
25603
|
return;
|
|
25603
25604
|
}
|
|
25604
25605
|
if (exchange?.terminal?.occurrence === occurrence && !toolFormatting.emittedOutcomes.has(inv.toolCallId)) {
|
|
25605
25606
|
toolFormatting.emittedOutcomes.add(inv.toolCallId);
|
|
25606
|
-
if (inv.state === "output-error") pushLine(`Tool Error ${inv.toolName}`, maybeTruncate(inv.errorText?.trim() ? inv.errorText : "Tool execution failed", maxLen), partCreatedAt);
|
|
25607
|
-
else if (inv.state === "output-denied") pushLine(`Tool Denied ${inv.toolName}`, maybeTruncate(inv.approval?.reason?.trim() ? inv.approval.reason : "Tool call was not approved by the user", maxLen), partCreatedAt);
|
|
25607
|
+
if (inv.state === "output-error") pushLine(`Tool Error ${inv.toolName}`, maybeTruncate(inv.errorText?.trim() ? inv.errorText : "Tool execution failed", maxLen), partCreatedAt, { tool: true });
|
|
25608
|
+
else if (inv.state === "output-denied") pushLine(`Tool Denied ${inv.toolName}`, maybeTruncate(inv.approval?.reason?.trim() ? inv.approval.reason : "Tool call was not approved by the user", maxLen), partCreatedAt, { tool: true });
|
|
25608
25609
|
return;
|
|
25609
25610
|
}
|
|
25610
|
-
if (!exchange) pushLine(`Tool Call ${inv.toolName}`, formatObserverToolArguments(inv.args, maxLen), partCreatedAt);
|
|
25611
|
+
if (!exchange) pushLine(`Tool Call ${inv.toolName}`, formatObserverToolArguments(inv.args, maxLen), partCreatedAt, { tool: true });
|
|
25611
25612
|
return;
|
|
25612
25613
|
}
|
|
25613
25614
|
const partType = part.type;
|
|
@@ -25762,13 +25763,17 @@ function surroundObserverHistory(history, contextPrompt, taskInstructions) {
|
|
|
25762
25763
|
function buildMultiThreadObserverTaskInstructions(includeThreadTitle) {
|
|
25763
25764
|
return `## Your Task\n\nExtract new observations from each thread. Output your observations grouped by thread using <thread id="..."> tags inside your <observations> block. Each thread block should contain that thread's observations, current-task, suggested-response${includeThreadTitle ? ", and thread-title" : ""}, in the format specified in your instructions.`;
|
|
25764
25765
|
}
|
|
25766
|
+
function buildTruncationNotice(wasTruncated) {
|
|
25767
|
+
if (!wasTruncated) return "";
|
|
25768
|
+
return "Previous observations were truncated for context budget reasons.\nThe main agent still has full memory context outside this observer window.\n\n";
|
|
25769
|
+
}
|
|
25765
25770
|
function buildMultiThreadObserverContextPrompt(existingObservations, threadOrder, priorMetadataByThread, wasTruncated, includeThreadTitle, extractors = []) {
|
|
25766
25771
|
let prompt = "";
|
|
25767
25772
|
if (existingObservations) {
|
|
25768
25773
|
prompt += `## Previous Observations\n\n${existingObservations}\n\n---\n\n`;
|
|
25769
25774
|
prompt += "Do not repeat these existing observations. Your new observations will be appended to the existing observations.\n\n";
|
|
25770
25775
|
}
|
|
25771
|
-
|
|
25776
|
+
prompt += buildTruncationNotice(wasTruncated);
|
|
25772
25777
|
const threadMetadataLines = threadOrder?.map((threadId) => {
|
|
25773
25778
|
const metadata = priorMetadataByThread?.get(threadId);
|
|
25774
25779
|
const extractorPriorLines = buildExtractorPriorLines(extractors, metadata?.extracted);
|
|
@@ -25782,10 +25787,6 @@ function buildMultiThreadObserverContextPrompt(existingObservations, threadOrder
|
|
|
25782
25787
|
}).filter(Boolean).join("\n");
|
|
25783
25788
|
if (threadMetadataLines) {
|
|
25784
25789
|
prompt += `## Prior Thread Metadata\n\n${threadMetadataLines}\n\n`;
|
|
25785
|
-
if (hasTruncatedObservations) {
|
|
25786
|
-
prompt += `Previous observations were truncated for context budget reasons.\n`;
|
|
25787
|
-
prompt += `The main agent still has full memory context outside this observer window.\n`;
|
|
25788
|
-
}
|
|
25789
25790
|
prompt += `Use each thread's prior current-task, suggested-response${includeThreadTitle ? ", and thread-title" : ""} as continuity hints, then update them based on that thread's new messages.\n\n---\n\n`;
|
|
25790
25791
|
}
|
|
25791
25792
|
return prompt;
|
|
@@ -25828,7 +25829,7 @@ function parseMultiThreadObserverOutput(output, extractors = []) {
|
|
|
25828
25829
|
threadTitle = threadTitleMatch[1].trim();
|
|
25829
25830
|
observations = observations.replace(/<thread-title>[\s\S]*?<\/thread-title>/i, "");
|
|
25830
25831
|
}
|
|
25831
|
-
observations = sanitizeObservationLines(observations.trim());
|
|
25832
|
+
observations = sortObservationsByTime(sanitizeObservationLines(observations.trim()));
|
|
25832
25833
|
threads.set(threadId, {
|
|
25833
25834
|
observations,
|
|
25834
25835
|
currentTask: currentTask || getStringExtractedValue$1(parsedExtractedValues.values, "current-task"),
|
|
@@ -25868,7 +25869,7 @@ function buildObserverContextPrompt(existingObservations, options) {
|
|
|
25868
25869
|
prompt += `## Previous Observations\n\n${existingObservations}\n\n---\n\n`;
|
|
25869
25870
|
prompt += "Do not repeat these existing observations. Your new observations will be appended to the existing observations.\n\n";
|
|
25870
25871
|
}
|
|
25871
|
-
|
|
25872
|
+
prompt += buildTruncationNotice(options?.wasTruncated);
|
|
25872
25873
|
const priorMetadataLines = [];
|
|
25873
25874
|
if (options?.priorCurrentTask) priorMetadataLines.push(`- prior current-task: ${options.priorCurrentTask}`);
|
|
25874
25875
|
if (options?.priorSuggestedResponse) priorMetadataLines.push(`- prior suggested-response: ${options.priorSuggestedResponse}`);
|
|
@@ -25876,10 +25877,6 @@ function buildObserverContextPrompt(existingObservations, options) {
|
|
|
25876
25877
|
priorMetadataLines.push(...buildExtractorPriorLines(options?.extractors ?? [], options?.priorExtractedValues));
|
|
25877
25878
|
if (priorMetadataLines.length > 0) {
|
|
25878
25879
|
prompt += `## Prior Thread Metadata\n\n${priorMetadataLines.join("\n")}\n\n`;
|
|
25879
|
-
if (hasTruncatedObservations) {
|
|
25880
|
-
prompt += `Previous observations were truncated for context budget reasons.\n`;
|
|
25881
|
-
prompt += `The main agent still has full memory context outside this observer window.\n`;
|
|
25882
|
-
}
|
|
25883
25880
|
const titleHint = options?.includeThreadTitle ? ", and thread-title" : "";
|
|
25884
25881
|
prompt += `Use the prior current-task, suggested-response${titleHint} as continuity hints, then update them based on the new messages.\n\n---\n\n`;
|
|
25885
25882
|
}
|
|
@@ -25910,7 +25907,7 @@ function parseObserverOutput(output, extractors = []) {
|
|
|
25910
25907
|
const parsedExtractedValues = parseExtractedValues(output, inlineExtractors);
|
|
25911
25908
|
const parsed = parseMemorySectionXml(stripExtractorSections(output, inlineExtractors));
|
|
25912
25909
|
return {
|
|
25913
|
-
observations: sanitizeObservationLines(parsed.observations || ""),
|
|
25910
|
+
observations: sortObservationsByTime(sanitizeObservationLines(parsed.observations || "")),
|
|
25914
25911
|
currentTask: parsed.currentTask || getStringExtractedValue$1(parsedExtractedValues.values, "current-task"),
|
|
25915
25912
|
suggestedContinuation: parsed.suggestedResponse || getStringExtractedValue$1(parsedExtractedValues.values, "suggested-response"),
|
|
25916
25913
|
threadTitle: parsed.threadTitle || getStringExtractedValue$1(parsedExtractedValues.values, "thread-title"),
|
|
@@ -26004,6 +26001,68 @@ function sanitizeObservationLines(observations) {
|
|
|
26004
26001
|
}
|
|
26005
26002
|
return changed ? lines.join("\n") : observations;
|
|
26006
26003
|
}
|
|
26004
|
+
const OBSERVATION_TIME = /^[*-]\s+(?:\S+\s+)?\((\d{1,2}):(\d{2})\s*([AaPp])?\.?(?:[Mm]\.?)?\)/;
|
|
26005
|
+
/** Minutes since midnight for each item, or undefined when the group's times can't be ordered safely. */
|
|
26006
|
+
function observationMinutes(items) {
|
|
26007
|
+
const times = items.map((item) => OBSERVATION_TIME.exec(item[0]));
|
|
26008
|
+
if (times.some((t) => !t)) return void 0;
|
|
26009
|
+
const matches = times;
|
|
26010
|
+
const marked = matches.filter((m) => m[3]).length;
|
|
26011
|
+
if (marked > 0 && marked < matches.length) return void 0;
|
|
26012
|
+
if (marked === matches.length) return matches.map((m) => Number(m[1]) % 12 * 60 + (/p/i.test(m[3]) ? 720 : 0) + Number(m[2]));
|
|
26013
|
+
const hours = matches.map((m) => m[1]);
|
|
26014
|
+
const twelveHour = hours.some((h) => /^[1-9]$/.test(h));
|
|
26015
|
+
const twentyFourHour = hours.some((h) => /^(0\d|1[3-9]|2\d)$/.test(h));
|
|
26016
|
+
if (twelveHour && (twentyFourHour || new Set(hours).size > 1)) return void 0;
|
|
26017
|
+
return matches.map((m) => Number(m[1]) * 60 + Number(m[2]));
|
|
26018
|
+
}
|
|
26019
|
+
/**
|
|
26020
|
+
* Put the top-level observations of each "Date:" group in time order, keeping indented
|
|
26021
|
+
* sub-items with their parent. The Observer writes a time on every observation but tends
|
|
26022
|
+
* to list a user's message ahead of the earlier events it follows, which can make a
|
|
26023
|
+
* superseded state read as the latest one. Groups are left as written when any
|
|
26024
|
+
* observation has no time or the times are ambiguous.
|
|
26025
|
+
*/
|
|
26026
|
+
function sortObservationsByTime(observations) {
|
|
26027
|
+
if (!observations) return observations;
|
|
26028
|
+
const output = [];
|
|
26029
|
+
let group;
|
|
26030
|
+
const flush = () => {
|
|
26031
|
+
if (!group) return;
|
|
26032
|
+
const minutes = group.sortable && group.items.length > 1 ? observationMinutes(group.items) : void 0;
|
|
26033
|
+
const items = minutes ? group.items.map((item, i) => ({
|
|
26034
|
+
item,
|
|
26035
|
+
minute: minutes[i]
|
|
26036
|
+
})).sort((a, b) => a.minute - b.minute).map((x) => x.item) : group.items;
|
|
26037
|
+
output.push(...group.header, ...items.flat(), ...group.trailing);
|
|
26038
|
+
group = void 0;
|
|
26039
|
+
};
|
|
26040
|
+
for (const line of observations.split("\n")) if (/^\s*Date:\s/.test(line)) {
|
|
26041
|
+
flush();
|
|
26042
|
+
group = {
|
|
26043
|
+
header: [line],
|
|
26044
|
+
items: [],
|
|
26045
|
+
trailing: [],
|
|
26046
|
+
sortable: true
|
|
26047
|
+
};
|
|
26048
|
+
} else if (!group) output.push(line);
|
|
26049
|
+
else if (/^[*-]\s/.test(line)) {
|
|
26050
|
+
group.items.at(-1)?.push(...group.trailing);
|
|
26051
|
+
group.trailing = [];
|
|
26052
|
+
group.items.push([line]);
|
|
26053
|
+
} else if (group.items.length === 0) group.header.push(line);
|
|
26054
|
+
else if (line.trim() === "") group.trailing.push(line);
|
|
26055
|
+
else if (/^\s/.test(line)) {
|
|
26056
|
+
group.items.at(-1).push(...group.trailing, line);
|
|
26057
|
+
group.trailing = [];
|
|
26058
|
+
} else {
|
|
26059
|
+
group.sortable = false;
|
|
26060
|
+
group.items.at(-1).push(...group.trailing, line);
|
|
26061
|
+
group.trailing = [];
|
|
26062
|
+
}
|
|
26063
|
+
flush();
|
|
26064
|
+
return output.join("\n");
|
|
26065
|
+
}
|
|
26007
26066
|
function analyzeDegenerateRepetition(text) {
|
|
26008
26067
|
const lines = collapseBoundedLineRuns(text.split("\n"));
|
|
26009
26068
|
const windowText = lines.filter((line) => line.trim().length >= MIN_DUPLICATE_LINE_CHARS && line.length <= MAX_OBSERVATION_LINE_CHARS).join("\n");
|
|
@@ -26538,7 +26597,7 @@ var ObserverRunner = class {
|
|
|
26538
26597
|
/**
|
|
26539
26598
|
* Call the Observer agent for multiple threads in a single batched request.
|
|
26540
26599
|
*/
|
|
26541
|
-
async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext, timeZone, mainAgent, currentModel) {
|
|
26600
|
+
async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext, timeZone, mainAgent, currentModel, wasTruncated) {
|
|
26542
26601
|
const contextFor = (threadId) => ({
|
|
26543
26602
|
threadId,
|
|
26544
26603
|
...hookContext
|
|
@@ -26549,7 +26608,7 @@ var ObserverRunner = class {
|
|
|
26549
26608
|
if (filtered.length > 0) messagesByThread.set(threadId, filtered);
|
|
26550
26609
|
}
|
|
26551
26610
|
const threadOrder = allThreadOrder.filter((threadId) => messagesByThread.has(threadId));
|
|
26552
|
-
const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel);
|
|
26611
|
+
const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel, wasTruncated);
|
|
26553
26612
|
for (const threadId of allThreadOrder) {
|
|
26554
26613
|
const threadResult = output.results.get(threadId);
|
|
26555
26614
|
if (!threadResult) {
|
|
@@ -26560,7 +26619,7 @@ var ObserverRunner = class {
|
|
|
26560
26619
|
}
|
|
26561
26620
|
return output;
|
|
26562
26621
|
}
|
|
26563
|
-
async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel) {
|
|
26622
|
+
async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel, wasTruncated) {
|
|
26564
26623
|
if (threadOrder.length === 0) return { results: /* @__PURE__ */ new Map() };
|
|
26565
26624
|
const inputTokens = Array.from(messagesByThread.values()).reduce((total, messages) => total + this.tokenCounter.countMessages(messages), 0);
|
|
26566
26625
|
let resolvedModel;
|
|
@@ -26606,7 +26665,8 @@ var ObserverRunner = class {
|
|
|
26606
26665
|
priorThreadTitle: priorMetadataByThread?.get(threadId)?.threadTitle,
|
|
26607
26666
|
priorExtractedValues: priorMetadataByThread?.get(threadId)?.extracted,
|
|
26608
26667
|
model: resolvedModel.model,
|
|
26609
|
-
timeZone
|
|
26668
|
+
timeZone,
|
|
26669
|
+
wasTruncated
|
|
26610
26670
|
});
|
|
26611
26671
|
results.set(threadId, {
|
|
26612
26672
|
observations: threadResult.observations,
|
|
@@ -26632,7 +26692,7 @@ var ObserverRunner = class {
|
|
|
26632
26692
|
const agent = this.createAgent(resolvedModel.model, true, void 0, activeExtractors);
|
|
26633
26693
|
const internalRequestContext = withOmInternalThreadId(requestContext, agent.id);
|
|
26634
26694
|
const multiThreadAttachmentFilter = this.resolveAttachmentFilter(resolvedModel.model, requestContext);
|
|
26635
|
-
const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread,
|
|
26695
|
+
const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread, wasTruncated, this.observationConfig.threadTitle, activeExtractors, {
|
|
26636
26696
|
attachmentFilter: multiThreadAttachmentFilter,
|
|
26637
26697
|
timeZone
|
|
26638
26698
|
})];
|
|
@@ -26646,6 +26706,7 @@ var ObserverRunner = class {
|
|
|
26646
26706
|
metadata: {
|
|
26647
26707
|
omThreadCount: threadOrder.length,
|
|
26648
26708
|
omPreviousObserverTokens: this.observationConfig.previousObserverTokens,
|
|
26709
|
+
omWasTruncated: wasTruncated ?? false,
|
|
26649
26710
|
omThreadTitleEnabled: this.observationConfig.threadTitle,
|
|
26650
26711
|
...resolvedModel.selectedThreshold !== void 0 ? { omSelectedThreshold: resolvedModel.selectedThreshold } : {},
|
|
26651
26712
|
...resolvedModel.routingStrategy ? { omRoutingStrategy: resolvedModel.routingStrategy } : {},
|
|
@@ -30887,6 +30948,80 @@ function resolveAutoModelId(mainModelId, options) {
|
|
|
30887
30948
|
const pick = provider ? models[provider] : void 0;
|
|
30888
30949
|
return pick ? `${prefix}${pick}` : mainModelId;
|
|
30889
30950
|
}
|
|
30951
|
+
/** How long an op may hold its key before later ops on that key stop waiting for it. */
|
|
30952
|
+
const OM_COMMIT_STALL_MS = 6e4;
|
|
30953
|
+
/** The per-thread / per-resource key shared by the commit queue and `BufferingCoordinator`. */
|
|
30954
|
+
function getOMLockKey(scope, threadId, resourceId) {
|
|
30955
|
+
if (scope === "resource" && resourceId) return `resource:${resourceId}`;
|
|
30956
|
+
return `thread:${threadId ?? "unknown"}`;
|
|
30957
|
+
}
|
|
30958
|
+
const REGISTRY = Symbol.for("@mastra/memory/om-commit-queue");
|
|
30959
|
+
const { queues, heldKeys } = globalThis[REGISTRY] ??= {
|
|
30960
|
+
queues: /* @__PURE__ */ new Map(),
|
|
30961
|
+
heldKeys: new async_hooks.AsyncLocalStorage()
|
|
30962
|
+
};
|
|
30963
|
+
function holdsKey(key) {
|
|
30964
|
+
const token = heldKeys.getStore()?.get(key);
|
|
30965
|
+
return token !== void 0 && queues.get(key)?.running === token;
|
|
30966
|
+
}
|
|
30967
|
+
function runNext(key, queue) {
|
|
30968
|
+
const next = queue.reflection.shift() ?? queue.normal.shift();
|
|
30969
|
+
if (!next) {
|
|
30970
|
+
queue.running = null;
|
|
30971
|
+
queues.delete(key);
|
|
30972
|
+
return;
|
|
30973
|
+
}
|
|
30974
|
+
next();
|
|
30975
|
+
}
|
|
30976
|
+
/**
|
|
30977
|
+
* Runs `op` once every earlier op on `key` has settled (reflection-priority ops first among those
|
|
30978
|
+
* still waiting) and resolves or rejects with its result. A rejection reaches only this caller.
|
|
30979
|
+
*
|
|
30980
|
+
* Enqueueing on a key from inside an op that holds the same key, directly or through nested ops
|
|
30981
|
+
* on other keys, would deadlock, so it rejects.
|
|
30982
|
+
*/
|
|
30983
|
+
function runOMCommit(key, op, { priority = "normal" } = {}) {
|
|
30984
|
+
if (holdsKey(key)) return Promise.reject(/* @__PURE__ */ new Error(`Re-entrant observational memory commit on "${key}": an op cannot enqueue on the key it holds.`));
|
|
30985
|
+
return new Promise((resolve, reject) => {
|
|
30986
|
+
let queue = queues.get(key);
|
|
30987
|
+
if (!queue) {
|
|
30988
|
+
queue = {
|
|
30989
|
+
running: null,
|
|
30990
|
+
reflection: [],
|
|
30991
|
+
normal: []
|
|
30992
|
+
};
|
|
30993
|
+
queues.set(key, queue);
|
|
30994
|
+
}
|
|
30995
|
+
const owner = queue;
|
|
30996
|
+
const start = async_hooks.AsyncResource.bind(() => {
|
|
30997
|
+
const token = Symbol(key);
|
|
30998
|
+
owner.running = token;
|
|
30999
|
+
const held = new Map(heldKeys.getStore());
|
|
31000
|
+
held.set(key, token);
|
|
31001
|
+
let released = false;
|
|
31002
|
+
const release = () => {
|
|
31003
|
+
if (released) return;
|
|
31004
|
+
released = true;
|
|
31005
|
+
clearTimeout(stallTimer);
|
|
31006
|
+
runNext(key, owner);
|
|
31007
|
+
};
|
|
31008
|
+
const stallTimer = setTimeout(() => {
|
|
31009
|
+
console.warn(`[observational-memory] Commit on "${key}" has not settled after ${OM_COMMIT_STALL_MS}ms; releasing the key so later commits can run. The stalled storage call is still pending.`);
|
|
31010
|
+
release();
|
|
31011
|
+
}, OM_COMMIT_STALL_MS);
|
|
31012
|
+
stallTimer.unref?.();
|
|
31013
|
+
heldKeys.run(held, () => Promise.resolve().then(op)).then((value) => {
|
|
31014
|
+
release();
|
|
31015
|
+
resolve(value);
|
|
31016
|
+
}, (error) => {
|
|
31017
|
+
release();
|
|
31018
|
+
reject(error);
|
|
31019
|
+
});
|
|
31020
|
+
});
|
|
31021
|
+
(priority === "reflection" ? owner.reflection : owner.normal).push(start);
|
|
31022
|
+
if (owner.running === null) runNext(key, owner);
|
|
31023
|
+
});
|
|
31024
|
+
}
|
|
30890
31025
|
const activeOps = /* @__PURE__ */ new Map();
|
|
30891
31026
|
function opKey(recordId, op) {
|
|
30892
31027
|
return `${recordId}:${op}`;
|
|
@@ -30923,11 +31058,37 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
30923
31058
|
*/
|
|
30924
31059
|
static asyncBufferingOps = /* @__PURE__ */ new Map();
|
|
30925
31060
|
/**
|
|
31061
|
+
* Observation buffer ops that may still append a buffered chunk.
|
|
31062
|
+
* Key format: "obs:{lockKey}"
|
|
31063
|
+
* Value: Promise that resolves once the op has persisted its chunk (or ended without one)
|
|
31064
|
+
*
|
|
31065
|
+
* Above the threshold→blockAfter band, activation waits (bounded) on this phase so the chunk
|
|
31066
|
+
* activates instead of being re-observed by the sync pass. It is not needed for safety: storage
|
|
31067
|
+
* keeps a chunk appended during activation. An op stays in asyncBufferingOps through its
|
|
31068
|
+
* post-persist work (indexing, thread title), which activation never waits for.
|
|
31069
|
+
*/
|
|
31070
|
+
static pendingChunkWrites = /* @__PURE__ */ new Map();
|
|
31071
|
+
/**
|
|
30926
31072
|
* Track the last token boundary at which we started buffering.
|
|
30927
31073
|
* Key format: "obs:{lockKey}" or "refl:{lockKey}"
|
|
30928
31074
|
*/
|
|
30929
31075
|
static lastBufferedBoundary = /* @__PURE__ */ new Map();
|
|
30930
31076
|
/**
|
|
31077
|
+
* The buffer op that set the observation boundary, if one did. Any other write (an activation
|
|
31078
|
+
* reset, another op) replaces or clears it, so an op that outlived it never writes its boundary
|
|
31079
|
+
* back. Key format: "obs:{lockKey}"
|
|
31080
|
+
*/
|
|
31081
|
+
static observationBoundaryOwners = /* @__PURE__ */ new Map();
|
|
31082
|
+
/** Set the observation boundary; `owner` is the buffer op setting it, if any. */
|
|
31083
|
+
static setObservationBoundary(bufferKey, tokens, owner) {
|
|
31084
|
+
BufferingCoordinator.lastBufferedBoundary.set(bufferKey, tokens);
|
|
31085
|
+
if (owner) BufferingCoordinator.observationBoundaryOwners.set(bufferKey, owner);
|
|
31086
|
+
else BufferingCoordinator.observationBoundaryOwners.delete(bufferKey);
|
|
31087
|
+
}
|
|
31088
|
+
static ownsObservationBoundary(bufferKey, owner) {
|
|
31089
|
+
return BufferingCoordinator.observationBoundaryOwners.get(bufferKey) === owner;
|
|
31090
|
+
}
|
|
31091
|
+
/**
|
|
30931
31092
|
* Track the timestamp cursor for buffered messages.
|
|
30932
31093
|
* Key format: "obs:{lockKey}"
|
|
30933
31094
|
*/
|
|
@@ -30943,8 +31104,7 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
30943
31104
|
this.scope = opts.scope;
|
|
30944
31105
|
}
|
|
30945
31106
|
getLockKey(threadId, resourceId) {
|
|
30946
|
-
|
|
30947
|
-
return `thread:${threadId ?? "unknown"}`;
|
|
31107
|
+
return getOMLockKey(this.scope, threadId, resourceId);
|
|
30948
31108
|
}
|
|
30949
31109
|
isAsyncObservationEnabled() {
|
|
30950
31110
|
return this.observationConfig.bufferTokens !== void 0 && this.observationConfig.bufferTokens > 0;
|
|
@@ -30962,6 +31122,19 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
30962
31122
|
return BufferingCoordinator.asyncBufferingOps.has(bufferKey);
|
|
30963
31123
|
}
|
|
30964
31124
|
/**
|
|
31125
|
+
* Mark an observation buffer op as able to append a chunk. Call the returned release
|
|
31126
|
+
* function once it no longer can; releasing more than once is a no-op.
|
|
31127
|
+
*/
|
|
31128
|
+
static trackChunkWrite(bufferKey) {
|
|
31129
|
+
let resolve;
|
|
31130
|
+
const pending = new Promise((r) => resolve = r);
|
|
31131
|
+
BufferingCoordinator.pendingChunkWrites.set(bufferKey, pending);
|
|
31132
|
+
return () => {
|
|
31133
|
+
if (BufferingCoordinator.pendingChunkWrites.get(bufferKey) === pending) BufferingCoordinator.pendingChunkWrites.delete(bufferKey);
|
|
31134
|
+
resolve();
|
|
31135
|
+
};
|
|
31136
|
+
}
|
|
31137
|
+
/**
|
|
30965
31138
|
* Clean up static maps for a thread/resource to prevent memory leaks.
|
|
30966
31139
|
*/
|
|
30967
31140
|
cleanupStaticMaps(threadId, resourceId, activatedMessageIds) {
|
|
@@ -30970,10 +31143,12 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
30970
31143
|
const reflBufKey = this.getReflectionBufferKey(lockKey);
|
|
30971
31144
|
if (activatedMessageIds) {
|
|
30972
31145
|
BufferingCoordinator.lastBufferedBoundary.delete(obsBufKey);
|
|
31146
|
+
BufferingCoordinator.observationBoundaryOwners.delete(obsBufKey);
|
|
30973
31147
|
BufferingCoordinator.lastBufferedAtTime.delete(obsBufKey);
|
|
30974
31148
|
} else {
|
|
30975
31149
|
BufferingCoordinator.lastBufferedAtTime.delete(obsBufKey);
|
|
30976
31150
|
BufferingCoordinator.lastBufferedBoundary.delete(obsBufKey);
|
|
31151
|
+
BufferingCoordinator.observationBoundaryOwners.delete(obsBufKey);
|
|
30977
31152
|
BufferingCoordinator.lastBufferedBoundary.delete(reflBufKey);
|
|
30978
31153
|
BufferingCoordinator.asyncBufferingOps.delete(obsBufKey);
|
|
30979
31154
|
BufferingCoordinator.asyncBufferingOps.delete(reflBufKey);
|
|
@@ -30988,7 +31163,7 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
30988
31163
|
if (record.isBufferingObservation) {
|
|
30989
31164
|
if (isOpActiveInProcess(record.id, "bufferingObservation")) return false;
|
|
30990
31165
|
omDebug(`[OM:shouldTriggerAsyncObs] isBufferingObservation=true but stale, clearing`);
|
|
30991
|
-
storage
|
|
31166
|
+
if (storage) runOMCommit(lockKey, () => storage.setBufferingObservationFlag(record.id, false)).catch(() => {});
|
|
30992
31167
|
}
|
|
30993
31168
|
const bufferKey = this.getObservationBufferKey(lockKey);
|
|
30994
31169
|
if (this.isAsyncBufferingInProgress(bufferKey)) return false;
|
|
@@ -31008,7 +31183,7 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
31008
31183
|
* Await any in-flight async buffering operations for a given thread/resource.
|
|
31009
31184
|
*/
|
|
31010
31185
|
static async awaitBuffering(threadId, resourceId, scope, timeoutMs = 3e4) {
|
|
31011
|
-
const lockKey = scope
|
|
31186
|
+
const lockKey = getOMLockKey(scope, threadId, resourceId);
|
|
31012
31187
|
const obsKey = `obs:${lockKey}`;
|
|
31013
31188
|
const reflKey = `refl:${lockKey}`;
|
|
31014
31189
|
const promises = [];
|
|
@@ -31300,6 +31475,34 @@ function createThreadUpdateMarker(params) {
|
|
|
31300
31475
|
}
|
|
31301
31476
|
};
|
|
31302
31477
|
}
|
|
31478
|
+
/**
|
|
31479
|
+
* The current head of `origin`'s lineage (the generations reflections have created from it),
|
|
31480
|
+
* or `null` when that lineage is gone.
|
|
31481
|
+
*
|
|
31482
|
+
* Use this instead of reading the head by thread/resource when committing work that started
|
|
31483
|
+
* from `origin`. Clearing a record (`om.clear`, `Memory.deleteThread`) deletes every generation
|
|
31484
|
+
* of the thread/resource, and the thread can then get a new, unrelated record. Work produced
|
|
31485
|
+
* from the cleared messages must never be committed to it.
|
|
31486
|
+
*
|
|
31487
|
+
* Generation ids are never reused and clearing deletes all of them, so the head belongs to
|
|
31488
|
+
* `origin`'s lineage iff `origin` still exists.
|
|
31489
|
+
*/
|
|
31490
|
+
async function getLineageHead(storage, origin) {
|
|
31491
|
+
const head = await storage.getObservationalMemory(origin.threadId, origin.resourceId);
|
|
31492
|
+
if (!head || head.id === origin.id) return head;
|
|
31493
|
+
if (head.generationCount <= origin.generationCount) return null;
|
|
31494
|
+
return (await storage.getObservationalMemoryHistory(origin.threadId, origin.resourceId, head.generationCount - origin.generationCount + 3)).some((record) => record.id === origin.id) ? head : null;
|
|
31495
|
+
}
|
|
31496
|
+
/**
|
|
31497
|
+
* The generation `id` of `origin`'s lineage, or `null` when that lineage is gone or `id` is not
|
|
31498
|
+
* part of it. Unlike {@link getLineageHead}, this keeps naming `id` after later reflections have
|
|
31499
|
+
* superseded it, e.g. to report which generation a write landed on.
|
|
31500
|
+
*/
|
|
31501
|
+
async function getLineageRecord(storage, origin, id) {
|
|
31502
|
+
const head = await getLineageHead(storage, origin);
|
|
31503
|
+
if (!head || head.id === id) return head;
|
|
31504
|
+
return (await storage.getObservationalMemoryHistory(origin.threadId, origin.resourceId, head.generationCount - origin.generationCount + 3)).find((record) => record.id === id) ?? null;
|
|
31505
|
+
}
|
|
31303
31506
|
function isRecord$1(value) {
|
|
31304
31507
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
31305
31508
|
}
|
|
@@ -31442,6 +31645,39 @@ function calculateProjectedMessageRemoval(chunks, bufferActivation, messageToken
|
|
|
31442
31645
|
}
|
|
31443
31646
|
/** Module-level xxhash singleton — loaded once, shared across all strategy instances. */
|
|
31444
31647
|
const hasherPromise = e();
|
|
31648
|
+
/** Recompose-and-retry rounds for an observation commit that hit a retired or changed head. */
|
|
31649
|
+
const MAX_HEAD_COMMIT_RETRIES = 3;
|
|
31650
|
+
const OBSERVATION_LIFECYCLE_MARKERS = /* @__PURE__ */ new Set([
|
|
31651
|
+
"data-om-observation-start",
|
|
31652
|
+
"data-om-observation-end",
|
|
31653
|
+
"data-om-observation-failed"
|
|
31654
|
+
]);
|
|
31655
|
+
function isOmPart(part) {
|
|
31656
|
+
return String(part?.type ?? "").startsWith("data-om-");
|
|
31657
|
+
}
|
|
31658
|
+
/**
|
|
31659
|
+
* Index in `message` right after this cycle's start marker (and any OM parts directly after it),
|
|
31660
|
+
* or -1 when the message doesn't carry it.
|
|
31661
|
+
*/
|
|
31662
|
+
function afterOwnStartMarker(message, marker) {
|
|
31663
|
+
const cycleId = marker.data?.cycleId;
|
|
31664
|
+
const startType = marker.type.startsWith("data-om-buffering-") ? "data-om-buffering-start" : "data-om-observation-start";
|
|
31665
|
+
if (!cycleId || marker.type === startType) return -1;
|
|
31666
|
+
const parts = message.content?.parts ?? [];
|
|
31667
|
+
const start = parts.findIndex((part) => part?.type === startType && part?.data?.cycleId === cycleId);
|
|
31668
|
+
if (start === -1) return -1;
|
|
31669
|
+
let index = start + 1;
|
|
31670
|
+
while (index < parts.length && isOmPart(parts[index])) index++;
|
|
31671
|
+
return index;
|
|
31672
|
+
}
|
|
31673
|
+
/** True when the message has parts no completed observation boundary covers (OM markers aside). */
|
|
31674
|
+
function hasUnobservedContent(message) {
|
|
31675
|
+
return getUnobservedParts(message).some((part) => !isOmPart(part));
|
|
31676
|
+
}
|
|
31677
|
+
function isObservationLifecycleMarker(marker) {
|
|
31678
|
+
const operationType = marker.data?.operationType;
|
|
31679
|
+
return OBSERVATION_LIFECYCLE_MARKERS.has(marker.type) && operationType !== "reflection";
|
|
31680
|
+
}
|
|
31445
31681
|
/**
|
|
31446
31682
|
* Abstract base class for observation strategies.
|
|
31447
31683
|
*
|
|
@@ -31491,17 +31727,35 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
31491
31727
|
const fresh = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
31492
31728
|
if (fresh?.lastObservedAt && record.lastObservedAt && fresh.lastObservedAt > record.lastObservedAt) return { observed: false };
|
|
31493
31729
|
}
|
|
31494
|
-
const { messages, existingObservations } = await this.prepare();
|
|
31730
|
+
const { messages, existingObservations, contextRecord } = await this.prepare();
|
|
31731
|
+
if (messages.length === 0) return { observed: false };
|
|
31495
31732
|
const observationMessages = stripSubconsciousSignals(messages);
|
|
31496
31733
|
await this.emitStartMarkers(cycleId);
|
|
31497
|
-
const
|
|
31498
|
-
const
|
|
31499
|
-
|
|
31734
|
+
const observerContext = this.deps.prepareObserverContext(existingObservations, contextRecord ?? record);
|
|
31735
|
+
const output = await this.observe(observerContext.context ?? "", observationMessages, observerContext.wasTruncated);
|
|
31736
|
+
let processed = await this.process(output, existingObservations);
|
|
31737
|
+
let committedRecord = record;
|
|
31738
|
+
const outcome = await this.persist(processed);
|
|
31739
|
+
this.settleObservationCommit(outcome?.status === "committed" && !outcome.alreadyCovered);
|
|
31740
|
+
if (outcome?.status === "not-committed") {
|
|
31741
|
+
omDebug(`[OM:observe] cycle ${cycleId} not committed: ${outcome.reason}`);
|
|
31742
|
+
await this.emitFailedMarkers(cycleId, /* @__PURE__ */ new Error(`Observation not committed: ${outcome.reason}`));
|
|
31743
|
+
return {
|
|
31744
|
+
observed: false,
|
|
31745
|
+
usage: output.usage,
|
|
31746
|
+
providerMetadata: output.providerMetadata
|
|
31747
|
+
};
|
|
31748
|
+
}
|
|
31749
|
+
if (outcome?.status === "committed") {
|
|
31750
|
+
processed = outcome.processed;
|
|
31751
|
+
committedRecord = outcome.record;
|
|
31752
|
+
}
|
|
31500
31753
|
await this.emitEndMarkers(cycleId, processed);
|
|
31501
31754
|
if (this.needsReflection) await this.deps.reflector.maybeReflect({
|
|
31502
31755
|
record: {
|
|
31503
|
-
...
|
|
31504
|
-
activeObservations: processed.observations
|
|
31756
|
+
...committedRecord,
|
|
31757
|
+
activeObservations: processed.observations,
|
|
31758
|
+
observationTokenCount: processed.observationTokens
|
|
31505
31759
|
},
|
|
31506
31760
|
observationTokens: processed.observationTokens,
|
|
31507
31761
|
threadId,
|
|
@@ -31537,7 +31791,8 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
31537
31791
|
threadId
|
|
31538
31792
|
}
|
|
31539
31793
|
};
|
|
31540
|
-
|
|
31794
|
+
const observed = this.getObservedMessagesForThread(threadId);
|
|
31795
|
+
await (observed?.length ? this.persistMarkerToObservedMessage(failedMarkerForStorage, observed, threadId, this.opts.resourceId) : this.persistMarkerToStorage(failedMarkerForStorage, threadId, this.opts.resourceId)).catch(() => {});
|
|
31541
31796
|
if (abortSignal?.aborted) throw error;
|
|
31542
31797
|
omError("[OM] Observation failed", error);
|
|
31543
31798
|
return {
|
|
@@ -31564,6 +31819,11 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
31564
31819
|
transient: true
|
|
31565
31820
|
}).catch(() => {});
|
|
31566
31821
|
const markerThreadId = marker.data?.threadId ?? this.opts.threadId;
|
|
31822
|
+
const observed = isObservationLifecycleMarker(marker) ? this.getObservedMessagesForThread(markerThreadId) : void 0;
|
|
31823
|
+
if (observed?.length) {
|
|
31824
|
+
await this.persistMarkerToObservedMessage(marker, observed, markerThreadId, this.opts.resourceId);
|
|
31825
|
+
return;
|
|
31826
|
+
}
|
|
31567
31827
|
if (!await this.persistMarkerToMessage(marker, this.opts.messageList, markerThreadId, this.opts.resourceId)) await this.persistMarkerToStorage(marker, markerThreadId, this.opts.resourceId);
|
|
31568
31828
|
}
|
|
31569
31829
|
getObservationMarkerConfig() {
|
|
@@ -31647,6 +31907,76 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
31647
31907
|
}
|
|
31648
31908
|
return `${existingObservations}${lastObservedAt ? ObservationStrategy.createMessageBoundary(lastObservedAt) : "\n\n"}${newThreadSection}`;
|
|
31649
31909
|
}
|
|
31910
|
+
/**
|
|
31911
|
+
* Commit composed observations to the current head generation.
|
|
31912
|
+
*
|
|
31913
|
+
* The write is conditional on the head text the observations were composed from. When the
|
|
31914
|
+
* target generation was retired by a reflection, or another writer changed the text, the
|
|
31915
|
+
* observations are recomposed against the fresh head and the commit is retried (bounded).
|
|
31916
|
+
* When the fresh head's `observedMessageIds` already include every message this cycle observed
|
|
31917
|
+
* (another instance observed them first), nothing is written and `alreadyCovered` is set, so
|
|
31918
|
+
* callers advance cursors and markers without adding a second copy of the same observations.
|
|
31919
|
+
* The cursor alone is not proof: an activation can move it past a message it never covered.
|
|
31920
|
+
* Returns null when no commit landed — callers must then leave cursors, markers, and the live
|
|
31921
|
+
* context untouched.
|
|
31922
|
+
*/
|
|
31923
|
+
async commitActiveObservationsToHead(opts) {
|
|
31924
|
+
let { processed, composedFrom, target } = opts;
|
|
31925
|
+
for (let attempt = 0; attempt <= MAX_HEAD_COMMIT_RETRIES; attempt++) {
|
|
31926
|
+
const result = await this.runCommit(async () => {
|
|
31927
|
+
const head = await getLineageHead(this.storage, target);
|
|
31928
|
+
if (!head) return null;
|
|
31929
|
+
if (head.id !== target.id || (head.activeObservations ?? "") !== composedFrom) {
|
|
31930
|
+
if (this.headCoversCycle(head, opts.cycleMessageIds)) {
|
|
31931
|
+
omDebug(`[OM:observe] head ${head.id} already covers this cycle's messages; skipping the duplicate commit`);
|
|
31932
|
+
target = head;
|
|
31933
|
+
return {
|
|
31934
|
+
applied: true,
|
|
31935
|
+
alreadyCovered: true
|
|
31936
|
+
};
|
|
31937
|
+
}
|
|
31938
|
+
target = head;
|
|
31939
|
+
composedFrom = head.activeObservations ?? "";
|
|
31940
|
+
processed = await opts.recompose(head);
|
|
31941
|
+
}
|
|
31942
|
+
const input = {
|
|
31943
|
+
id: target.id,
|
|
31944
|
+
observations: processed.observations,
|
|
31945
|
+
tokenCount: processed.observationTokens,
|
|
31946
|
+
lastObservedAt: processed.lastObservedAt,
|
|
31947
|
+
observedMessageIds: processed.observedMessageIds,
|
|
31948
|
+
expectedActiveObservations: composedFrom
|
|
31949
|
+
};
|
|
31950
|
+
return typeof this.storage.commitActiveObservations === "function" ? await this.storage.commitActiveObservations(input) : (await this.storage.updateActiveObservations(input), { applied: true });
|
|
31951
|
+
});
|
|
31952
|
+
if (!result) return null;
|
|
31953
|
+
if ("alreadyCovered" in result) return {
|
|
31954
|
+
processed: {
|
|
31955
|
+
...processed,
|
|
31956
|
+
observations: target.activeObservations ?? "",
|
|
31957
|
+
observationTokens: target.observationTokenCount
|
|
31958
|
+
},
|
|
31959
|
+
record: target,
|
|
31960
|
+
alreadyCovered: true
|
|
31961
|
+
};
|
|
31962
|
+
if (result.applied) return {
|
|
31963
|
+
processed,
|
|
31964
|
+
record: target
|
|
31965
|
+
};
|
|
31966
|
+
omDebug(`[OM:observe] commit to ${target.id} not applied (${result.reason}); retrying against the head`);
|
|
31967
|
+
}
|
|
31968
|
+
return null;
|
|
31969
|
+
}
|
|
31970
|
+
headCoversCycle(head, cycleMessageIds) {
|
|
31971
|
+
if (cycleMessageIds.length === 0) return false;
|
|
31972
|
+
const headIds = new Set(Array.isArray(head.observedMessageIds) ? head.observedMessageIds : []);
|
|
31973
|
+
return cycleMessageIds.every((id) => headIds.has(id));
|
|
31974
|
+
}
|
|
31975
|
+
/** Runs one storage commit in this thread's (or resource's) slot of the process-wide commit queue. */
|
|
31976
|
+
runCommit(op) {
|
|
31977
|
+
const { threadId, resourceId, record } = this.opts;
|
|
31978
|
+
return runOMCommit(getOMLockKey(this.scope, record.threadId ?? threadId, record.resourceId ?? resourceId), op);
|
|
31979
|
+
}
|
|
31650
31980
|
async indexObservationGroups(observations, threadId, resourceId, observedAt, recordId) {
|
|
31651
31981
|
if (!resourceId || !this.deps.onIndexObservations) return;
|
|
31652
31982
|
const groups = parseObservationGroups(observations);
|
|
@@ -31665,30 +31995,133 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
31665
31995
|
})));
|
|
31666
31996
|
}
|
|
31667
31997
|
/**
|
|
31998
|
+
* Messages this cycle observed for `threadId`, or undefined when the strategy does not
|
|
31999
|
+
* place observation lifecycle markers.
|
|
32000
|
+
*/
|
|
32001
|
+
getObservedMessagesForThread(_threadId) {}
|
|
32002
|
+
/**
|
|
32003
|
+
* Persist an observation start/end/failed marker without hiding unobserved content. A completed
|
|
32004
|
+
* end marker tells `getUnobservedMessages` that everything before it in that message is observed
|
|
32005
|
+
* (whatever thread the marker names), so it must only follow content this cycle observed.
|
|
32006
|
+
* Placement, in order:
|
|
32007
|
+
* 1. the live MessageList's newest assistant message (this turn's response or its seed), when the
|
|
32008
|
+
* list belongs to the marker's thread and the cycle observed that message or it has no
|
|
32009
|
+
* unobserved content;
|
|
32010
|
+
* 2. the newest observed assistant message; a stored copy (no live list, or another instance's
|
|
32011
|
+
* message) gets the marker right after the parts this cycle observed (end/failed markers right
|
|
32012
|
+
* after this cycle's start marker), so parts added while the Observer ran stay unobserved;
|
|
32013
|
+
* 3. the newest stored assistant message that is not newer than the observed range, only when it
|
|
32014
|
+
* has nothing unobserved (or already carries this cycle's start marker); otherwise no marker,
|
|
32015
|
+
* and the thread cursor alone records the cycle.
|
|
32016
|
+
* Lifecycle markers never go on user messages.
|
|
32017
|
+
*/
|
|
32018
|
+
async persistMarkerToObservedMessage(marker, observed, threadId, resourceId) {
|
|
32019
|
+
const observedIds = new Set(observed.map((m) => m.id));
|
|
32020
|
+
const list = threadId === this.opts.threadId ? this.opts.messageList : void 0;
|
|
32021
|
+
const listMessages = list ? getObservableMessages(list) : [];
|
|
32022
|
+
const live = [...listMessages].reverse().find((m) => m.role === "assistant");
|
|
32023
|
+
if (live && (observedIds.has(live.id) || !hasUnobservedContent(live))) {
|
|
32024
|
+
await this.attachMarker(marker, live, live.content.parts.length, threadId, resourceId);
|
|
32025
|
+
return;
|
|
32026
|
+
}
|
|
32027
|
+
const target = [...observed].reverse().find((m) => m.role === "assistant");
|
|
32028
|
+
if (target) {
|
|
32029
|
+
const fromList = listMessages.find((m) => m.id === target.id);
|
|
32030
|
+
if (fromList) {
|
|
32031
|
+
await this.attachMarker(marker, fromList, fromList.content.parts.length, threadId, resourceId);
|
|
32032
|
+
return;
|
|
32033
|
+
}
|
|
32034
|
+
try {
|
|
32035
|
+
const stored = (await this.storage.listMessagesById({ messageIds: [target.id] })).messages[0];
|
|
32036
|
+
if (stored?.content?.parts && Array.isArray(stored.content.parts)) {
|
|
32037
|
+
const afterStart = afterOwnStartMarker(stored, marker);
|
|
32038
|
+
const index = afterStart !== -1 ? afterStart : this.insertionAfterObserved(stored, target);
|
|
32039
|
+
await this.attachMarker(marker, stored, index, threadId, resourceId);
|
|
32040
|
+
}
|
|
32041
|
+
} catch (e) {
|
|
32042
|
+
omDebug(`[OM:persistMarkerToObservedMessage] failed to load marker target: ${e}`);
|
|
32043
|
+
}
|
|
32044
|
+
return;
|
|
32045
|
+
}
|
|
32046
|
+
const observedTimes = observed.filter((m) => m.createdAt).map((m) => new Date(m.createdAt).getTime());
|
|
32047
|
+
if (observedTimes.length === 0) return;
|
|
32048
|
+
await this.persistMarkerToStorage(marker, threadId, resourceId, {
|
|
32049
|
+
notAfter: new Date(Math.max(...observedTimes)),
|
|
32050
|
+
onlyIfObserved: true
|
|
32051
|
+
});
|
|
32052
|
+
}
|
|
32053
|
+
/** What the cycle observed of each message, captured when it placed its first marker there. */
|
|
32054
|
+
observedParts = /* @__PURE__ */ new Map();
|
|
32055
|
+
/**
|
|
32056
|
+
* Index in `stored` right after the parts the cycle observed in `observed` (and any OM markers
|
|
32057
|
+
* directly after them). Parts are only ever appended, so the observed message's non-marker part
|
|
32058
|
+
* count locates them, counted from the start of the message or, when `observed` is the trimmed
|
|
32059
|
+
* copy `getUnobservedMessages` returns for an already marked message, from just after the stored
|
|
32060
|
+
* copy's last completed boundary. Captured at the cycle's first marker, before the Observer runs,
|
|
32061
|
+
* because `observed` may be a live reference that grows meanwhile. Falls back to the end when the
|
|
32062
|
+
* stored copy has fewer parts than were observed.
|
|
32063
|
+
*/
|
|
32064
|
+
insertionAfterObserved(stored, observed) {
|
|
32065
|
+
if (!this.observedParts.has(observed.id)) this.observedParts.set(observed.id, {
|
|
32066
|
+
count: (observed.content?.parts ?? []).filter((part) => !isOmPart(part)).length,
|
|
32067
|
+
observedIsTrimmed: findLastCompletedObservationBoundary(observed) === -1
|
|
32068
|
+
});
|
|
32069
|
+
const { count, observedIsTrimmed } = this.observedParts.get(observed.id);
|
|
32070
|
+
const parts = stored.content.parts;
|
|
32071
|
+
let index = observedIsTrimmed ? findLastCompletedObservationBoundary(stored) + 1 : 0;
|
|
32072
|
+
let seen = 0;
|
|
32073
|
+
while (index < parts.length && seen < count) {
|
|
32074
|
+
if (!isOmPart(parts[index])) seen++;
|
|
32075
|
+
index++;
|
|
32076
|
+
}
|
|
32077
|
+
if (seen < count) return parts.length;
|
|
32078
|
+
while (index < parts.length && isOmPart(parts[index])) index++;
|
|
32079
|
+
return index;
|
|
32080
|
+
}
|
|
32081
|
+
async attachMarker(marker, msg, index, threadId, resourceId) {
|
|
32082
|
+
const parts = msg.content?.parts;
|
|
32083
|
+
if (!parts || !Array.isArray(parts)) return;
|
|
32084
|
+
const markerData = marker.data;
|
|
32085
|
+
if (!(markerData?.cycleId && parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) parts.splice(index, 0, marker);
|
|
32086
|
+
try {
|
|
32087
|
+
await this.messageHistory.persistMessages({
|
|
32088
|
+
messages: [msg],
|
|
32089
|
+
threadId,
|
|
32090
|
+
resourceId
|
|
32091
|
+
});
|
|
32092
|
+
} catch (e) {
|
|
32093
|
+
omDebug(`[OM:persistMarkerToObservedMessage] failed to save marker to DB: ${e}`);
|
|
32094
|
+
}
|
|
32095
|
+
}
|
|
32096
|
+
/**
|
|
31668
32097
|
* Persist a marker to the last assistant message in storage.
|
|
31669
32098
|
* Fetches messages directly from the DB so it works even when
|
|
31670
32099
|
* no MessageList is available (e.g. async buffering ops).
|
|
31671
32100
|
*/
|
|
31672
|
-
async persistMarkerToStorage(marker, threadId, resourceId) {
|
|
32101
|
+
async persistMarkerToStorage(marker, threadId, resourceId, opts) {
|
|
31673
32102
|
try {
|
|
31674
|
-
const
|
|
32103
|
+
const assistants = ((await this.storage.listMessages({
|
|
31675
32104
|
threadId,
|
|
31676
32105
|
perPage: 20,
|
|
31677
32106
|
orderBy: {
|
|
31678
32107
|
field: "createdAt",
|
|
31679
32108
|
direction: "DESC"
|
|
31680
|
-
}
|
|
31681
|
-
|
|
31682
|
-
|
|
31683
|
-
|
|
31684
|
-
|
|
31685
|
-
|
|
31686
|
-
|
|
31687
|
-
|
|
31688
|
-
|
|
31689
|
-
|
|
31690
|
-
|
|
31691
|
-
|
|
32109
|
+
},
|
|
32110
|
+
...opts?.notAfter ? { filter: { dateRange: { end: opts.notAfter } } } : {}
|
|
32111
|
+
}))?.messages ?? []).filter((msg) => msg?.role === "assistant" && Array.isArray(msg.content?.parts));
|
|
32112
|
+
const startMessage = opts?.onlyIfObserved || opts?.onCycleStartMessage ? assistants.find((m) => afterOwnStartMarker(m, marker) !== -1) : void 0;
|
|
32113
|
+
const msg = opts?.onCycleStartMessage ? startMessage : startMessage ?? assistants[0];
|
|
32114
|
+
if (!msg) return;
|
|
32115
|
+
const markerData = marker.data;
|
|
32116
|
+
const alreadyPresent = markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId);
|
|
32117
|
+
const afterStart = afterOwnStartMarker(msg, marker);
|
|
32118
|
+
if (opts?.onlyIfObserved && afterStart === -1 && hasUnobservedContent(msg)) return;
|
|
32119
|
+
if (!alreadyPresent) msg.content.parts.splice(afterStart !== -1 ? afterStart : msg.content.parts.length, 0, marker);
|
|
32120
|
+
await this.messageHistory.persistMessages({
|
|
32121
|
+
messages: [msg],
|
|
32122
|
+
threadId,
|
|
32123
|
+
resourceId
|
|
32124
|
+
});
|
|
31692
32125
|
} catch (e) {
|
|
31693
32126
|
omDebug(`[OM:persistMarkerToStorage] failed to save marker to DB: ${e}`);
|
|
31694
32127
|
}
|
|
@@ -31746,10 +32179,15 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
31746
32179
|
tokensToObserve = 0;
|
|
31747
32180
|
observerResult;
|
|
31748
32181
|
priorExtractedValues;
|
|
32182
|
+
/** Head text the processed observations were composed from (the commit's expected text). */
|
|
32183
|
+
composedFrom = "";
|
|
31749
32184
|
constructor(deps, opts) {
|
|
31750
32185
|
super(deps, opts);
|
|
31751
32186
|
this.lastMessage = opts.messages[opts.messages.length - 1];
|
|
31752
32187
|
}
|
|
32188
|
+
getObservedMessagesForThread(threadId) {
|
|
32189
|
+
return threadId === this.opts.threadId ? this.opts.messages : void 0;
|
|
32190
|
+
}
|
|
31753
32191
|
get needsLock() {
|
|
31754
32192
|
return true;
|
|
31755
32193
|
}
|
|
@@ -31784,9 +32222,11 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
31784
32222
|
}
|
|
31785
32223
|
}
|
|
31786
32224
|
this.tokensToObserve = await this.tokenCounter.countMessagesAsync(messages);
|
|
32225
|
+
const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
31787
32226
|
return {
|
|
31788
32227
|
messages,
|
|
31789
|
-
existingObservations:
|
|
32228
|
+
existingObservations: freshRecord?.activeObservations ?? record.activeObservations ?? "",
|
|
32229
|
+
contextRecord: freshRecord
|
|
31790
32230
|
};
|
|
31791
32231
|
}
|
|
31792
32232
|
async emitStartMarkers(cycleId) {
|
|
@@ -31804,11 +32244,12 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
31804
32244
|
await this.streamMarker(startMarker);
|
|
31805
32245
|
}
|
|
31806
32246
|
}
|
|
31807
|
-
async observe(
|
|
32247
|
+
async observe(observerContext, messages, wasTruncated) {
|
|
31808
32248
|
const thread = await this.storage.getThreadById({ threadId: this.opts.threadId });
|
|
31809
32249
|
const omMeta = thread ? (0, _mastra_core_memory.getThreadOMMetadata)(thread.metadata) : void 0;
|
|
31810
32250
|
this.priorExtractedValues = getPriorExtractedValues(omMeta, this.observationConfig.extractors);
|
|
31811
|
-
const result = await this.deps.observer.call(
|
|
32251
|
+
const result = await this.deps.observer.call(observerContext, messages, this.opts.abortSignal, {
|
|
32252
|
+
wasTruncated,
|
|
31812
32253
|
requestContext: this.opts.requestContext,
|
|
31813
32254
|
observabilityContext: this.opts.observabilityContext,
|
|
31814
32255
|
priorCurrentTask: omMeta?.currentTask,
|
|
@@ -31854,20 +32295,14 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
31854
32295
|
}
|
|
31855
32296
|
async process(output, existingObservations) {
|
|
31856
32297
|
const { record, threadId, messages } = this.opts;
|
|
31857
|
-
|
|
31858
|
-
const
|
|
31859
|
-
const newObservations = await this.wrapObservations(output.observations, existingObservations, threadId, lastObservedAt, messageRange);
|
|
31860
|
-
const observationTokens = this.tokenCounter.countObservations(newObservations);
|
|
31861
|
-
const cycleObservationTokens = this.tokenCounter.countObservations(output.observations);
|
|
31862
|
-
const newMessageIds = messages.map((m) => m.id);
|
|
31863
|
-
const existingIds = record.observedMessageIds ?? [];
|
|
31864
|
-
const observedMessageIds = [.../* @__PURE__ */ new Set([...Array.isArray(existingIds) ? existingIds : [], ...newMessageIds])];
|
|
32298
|
+
this.composedFrom = existingObservations;
|
|
32299
|
+
const processed = await this.compose(output, existingObservations, record);
|
|
31865
32300
|
this.deps.emitDebugEvent({
|
|
31866
32301
|
type: "observation_complete",
|
|
31867
32302
|
timestamp: /* @__PURE__ */ new Date(),
|
|
31868
32303
|
threadId,
|
|
31869
32304
|
resourceId: record.resourceId ?? "",
|
|
31870
|
-
observations:
|
|
32305
|
+
observations: processed.observations,
|
|
31871
32306
|
rawObserverOutput: output.observations,
|
|
31872
32307
|
previousObservations: record.activeObservations,
|
|
31873
32308
|
messages: messages.map((m) => ({
|
|
@@ -31876,11 +32311,23 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
31876
32311
|
})),
|
|
31877
32312
|
usage: output.usage
|
|
31878
32313
|
});
|
|
32314
|
+
return processed;
|
|
32315
|
+
}
|
|
32316
|
+
/** Build the observations to commit on top of `existingObservations` (the head text). */
|
|
32317
|
+
async compose(output, existingObservations, baseRecord) {
|
|
32318
|
+
const { threadId, messages } = this.opts;
|
|
32319
|
+
const lastObservedAt = this.getMaxMessageTimestamp(messages);
|
|
32320
|
+
const messageRange = this.retrieval ? buildMessageRange(messages) : void 0;
|
|
32321
|
+
const newObservations = await this.wrapObservations(output.observations, existingObservations, threadId, lastObservedAt, messageRange);
|
|
32322
|
+
const observationTokens = this.tokenCounter.countObservations(newObservations);
|
|
32323
|
+
const cycleObservationTokens = this.tokenCounter.countObservations(output.observations);
|
|
32324
|
+
const newMessageIds = messages.map((m) => m.id);
|
|
32325
|
+
const existingIds = baseRecord.observedMessageIds ?? [];
|
|
31879
32326
|
return {
|
|
31880
32327
|
observations: newObservations,
|
|
31881
32328
|
observationTokens,
|
|
31882
32329
|
cycleObservationTokens,
|
|
31883
|
-
observedMessageIds,
|
|
32330
|
+
observedMessageIds: [.../* @__PURE__ */ new Set([...Array.isArray(existingIds) ? existingIds : [], ...newMessageIds])],
|
|
31884
32331
|
lastObservedAt,
|
|
31885
32332
|
suggestedContinuation: output.suggestedContinuation,
|
|
31886
32333
|
currentTask: output.currentTask,
|
|
@@ -31892,12 +32339,27 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
31892
32339
|
}
|
|
31893
32340
|
async persist(processed) {
|
|
31894
32341
|
const { record, threadId, resourceId, messages } = this.opts;
|
|
31895
|
-
const
|
|
31896
|
-
if (!
|
|
32342
|
+
const liveRecord = await getLineageHead(this.storage, record);
|
|
32343
|
+
if (!liveRecord) {
|
|
31897
32344
|
omDebug(`[OM:sync-obs] skipping persist for thread ${threadId}: observational memory record is gone`);
|
|
31898
|
-
return
|
|
32345
|
+
return {
|
|
32346
|
+
status: "not-committed",
|
|
32347
|
+
reason: "the observational memory record was cleared"
|
|
32348
|
+
};
|
|
31899
32349
|
}
|
|
31900
|
-
|
|
32350
|
+
const committed = await this.commitActiveObservationsToHead({
|
|
32351
|
+
processed,
|
|
32352
|
+
composedFrom: this.composedFrom,
|
|
32353
|
+
target: liveRecord,
|
|
32354
|
+
recompose: (head) => this.compose(this.observerResult, head.activeObservations ?? "", head),
|
|
32355
|
+
cycleMessageIds: messages.map((m) => m.id)
|
|
32356
|
+
});
|
|
32357
|
+
if (!committed) return {
|
|
32358
|
+
status: "not-committed",
|
|
32359
|
+
reason: "the observational memory head kept changing during the commit"
|
|
32360
|
+
};
|
|
32361
|
+
processed = committed.processed;
|
|
32362
|
+
const thread = committed.alreadyCovered ? null : await this.storage.getThreadById({ threadId });
|
|
31901
32363
|
if (thread) {
|
|
31902
32364
|
const oldTitle = thread.title?.trim();
|
|
31903
32365
|
const newTitle = resolveThreadTitleUpdate(thread, processed.threadTitle);
|
|
@@ -31919,23 +32381,20 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
31919
32381
|
...shouldUpdateThreadTitle ? { title: newTitle } : {},
|
|
31920
32382
|
metadata: newMetadata
|
|
31921
32383
|
});
|
|
31922
|
-
if (shouldUpdateThreadTitle)
|
|
32384
|
+
if (shouldUpdateThreadTitle) await this.streamMarker(createThreadUpdateMarker({
|
|
31923
32385
|
cycleId: this.cycleId ?? crypto.randomUUID(),
|
|
31924
32386
|
threadId,
|
|
31925
32387
|
oldTitle,
|
|
31926
32388
|
newTitle
|
|
31927
|
-
});
|
|
32389
|
+
}));
|
|
31928
32390
|
}
|
|
31929
|
-
if (
|
|
31930
|
-
|
|
31931
|
-
|
|
31932
|
-
|
|
31933
|
-
|
|
31934
|
-
|
|
31935
|
-
|
|
31936
|
-
});
|
|
31937
|
-
await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, record.id);
|
|
31938
|
-
return true;
|
|
32391
|
+
if (!committed.alreadyCovered) await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, committed.record.id);
|
|
32392
|
+
return {
|
|
32393
|
+
status: "committed",
|
|
32394
|
+
processed,
|
|
32395
|
+
record: committed.record,
|
|
32396
|
+
alreadyCovered: committed.alreadyCovered
|
|
32397
|
+
};
|
|
31939
32398
|
}
|
|
31940
32399
|
async emitEndMarkers(cycleId, processed) {
|
|
31941
32400
|
const actualTokensObserved = await this.tokenCounter.countMessagesAsync(this.opts.messages);
|
|
@@ -31977,6 +32436,8 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
31977
32436
|
startedAt;
|
|
31978
32437
|
cycleId;
|
|
31979
32438
|
priorExtractedValues;
|
|
32439
|
+
/** The generation the chunk was written to (the head when the target was retired). */
|
|
32440
|
+
persistedRecordId;
|
|
31980
32441
|
constructor(deps, opts) {
|
|
31981
32442
|
super(deps, opts);
|
|
31982
32443
|
this.cycleId = opts.cycleId;
|
|
@@ -32003,12 +32464,13 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
32003
32464
|
};
|
|
32004
32465
|
}
|
|
32005
32466
|
async emitStartMarkers(_cycleId) {}
|
|
32006
|
-
async observe(
|
|
32467
|
+
async observe(observerContext, messages, wasTruncated) {
|
|
32007
32468
|
const thread = await this.storage.getThreadById({ threadId: this.opts.threadId });
|
|
32008
32469
|
const omMeta = thread ? (0, _mastra_core_memory.getThreadOMMetadata)(thread.metadata) : void 0;
|
|
32009
32470
|
this.priorExtractedValues = getPriorExtractedValues(omMeta, this.observationConfig.extractors);
|
|
32010
|
-
const result = await this.deps.observer.call(
|
|
32471
|
+
const result = await this.deps.observer.call(observerContext, messages, void 0, {
|
|
32011
32472
|
skipContinuationHints: true,
|
|
32473
|
+
wasTruncated,
|
|
32012
32474
|
requestContext: this.opts.requestContext,
|
|
32013
32475
|
observabilityContext: this.opts.observabilityContext,
|
|
32014
32476
|
priorExtractedValues: this.priorExtractedValues,
|
|
@@ -32082,71 +32544,124 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
32082
32544
|
};
|
|
32083
32545
|
}
|
|
32084
32546
|
async persist(processed) {
|
|
32085
|
-
if (!processed.observations) return
|
|
32547
|
+
if (!processed.observations) return;
|
|
32086
32548
|
const { record, threadId, resourceId, messages } = this.opts;
|
|
32087
|
-
|
|
32549
|
+
const liveRecord = await getLineageHead(this.storage, record);
|
|
32550
|
+
if (!liveRecord) {
|
|
32088
32551
|
omDebug(`[OM:asyncBuffer] skipping persist for thread ${threadId}: observational memory record is gone`);
|
|
32089
|
-
return
|
|
32552
|
+
return {
|
|
32553
|
+
status: "not-committed",
|
|
32554
|
+
reason: "the observational memory record was cleared"
|
|
32555
|
+
};
|
|
32090
32556
|
}
|
|
32091
32557
|
const messageTokens = await this.tokenCounter.countMessagesAsync(messages);
|
|
32092
|
-
|
|
32093
|
-
|
|
32094
|
-
|
|
32095
|
-
|
|
32096
|
-
|
|
32097
|
-
|
|
32098
|
-
|
|
32099
|
-
|
|
32100
|
-
|
|
32101
|
-
|
|
32102
|
-
|
|
32103
|
-
|
|
32104
|
-
|
|
32105
|
-
|
|
32106
|
-
|
|
32107
|
-
|
|
32108
|
-
|
|
32558
|
+
let appendAttempts = 0;
|
|
32559
|
+
let targetId = record.id;
|
|
32560
|
+
const appendResult = await withRetry(() => {
|
|
32561
|
+
appendAttempts++;
|
|
32562
|
+
return this.runCommit(async () => {
|
|
32563
|
+
const head = await getLineageHead(this.storage, record);
|
|
32564
|
+
if (!head) return null;
|
|
32565
|
+
targetId = head.id;
|
|
32566
|
+
const input = {
|
|
32567
|
+
id: targetId,
|
|
32568
|
+
chunk: {
|
|
32569
|
+
cycleId: this.cycleId,
|
|
32570
|
+
observations: processed.observations,
|
|
32571
|
+
tokenCount: processed.observationTokens,
|
|
32572
|
+
messageIds: processed.observedMessageIds,
|
|
32573
|
+
messageTokens,
|
|
32574
|
+
lastObservedAt: processed.lastObservedAt,
|
|
32575
|
+
suggestedContinuation: processed.suggestedContinuation,
|
|
32576
|
+
currentTask: processed.currentTask,
|
|
32577
|
+
threadTitle: processed.threadTitle,
|
|
32578
|
+
extractedValues: processed.extractedValues,
|
|
32579
|
+
extractionFailures: processed.extractionFailures
|
|
32580
|
+
},
|
|
32581
|
+
lastBufferedAtTime: processed.lastObservedAt
|
|
32582
|
+
};
|
|
32583
|
+
return typeof this.storage.appendBufferedObservations === "function" ? this.storage.appendBufferedObservations(input) : this.storage.updateBufferedObservations(input).then(() => ({
|
|
32584
|
+
persisted: true,
|
|
32585
|
+
recordId: input.id
|
|
32586
|
+
}));
|
|
32587
|
+
});
|
|
32588
|
+
}, {
|
|
32109
32589
|
label: "persist-buffered-observations",
|
|
32110
32590
|
abortSignal: this.opts.abortSignal
|
|
32111
32591
|
});
|
|
32112
|
-
|
|
32113
|
-
|
|
32114
|
-
|
|
32115
|
-
|
|
32116
|
-
|
|
32117
|
-
|
|
32118
|
-
|
|
32119
|
-
|
|
32120
|
-
|
|
32121
|
-
|
|
32122
|
-
|
|
32123
|
-
|
|
32124
|
-
|
|
32125
|
-
|
|
32126
|
-
|
|
32127
|
-
|
|
32128
|
-
|
|
32129
|
-
|
|
32130
|
-
|
|
32131
|
-
|
|
32132
|
-
|
|
32133
|
-
|
|
32134
|
-
|
|
32135
|
-
|
|
32136
|
-
|
|
32137
|
-
|
|
32138
|
-
|
|
32139
|
-
|
|
32140
|
-
|
|
32592
|
+
if (appendResult === null) {
|
|
32593
|
+
omDebug(`[OM:asyncBuffer] skipping persist for thread ${threadId}: observational memory record was cleared`);
|
|
32594
|
+
return {
|
|
32595
|
+
status: "not-committed",
|
|
32596
|
+
reason: "the observational memory record was cleared"
|
|
32597
|
+
};
|
|
32598
|
+
}
|
|
32599
|
+
let committedRecord = liveRecord;
|
|
32600
|
+
if (!appendResult.persisted) {
|
|
32601
|
+
const head = appendAttempts > 1 ? await getLineageHead(this.storage, record) : null;
|
|
32602
|
+
if (!(!!head && (getBufferedChunks(head).some((chunk) => chunk.cycleId === this.cycleId) || head.activeObservations.includes(processed.observations)))) return {
|
|
32603
|
+
status: "not-committed",
|
|
32604
|
+
reason: "the buffered chunk was already observed"
|
|
32605
|
+
};
|
|
32606
|
+
this.persistedRecordId = head.id;
|
|
32607
|
+
committedRecord = head;
|
|
32608
|
+
} else this.persistedRecordId = appendResult.recordId;
|
|
32609
|
+
await this.emitBufferingEndMarker(processed);
|
|
32610
|
+
await this.opts.onBufferedChunkPersisted?.();
|
|
32611
|
+
if (committedRecord.id !== this.persistedRecordId) {
|
|
32612
|
+
const landedOn = await getLineageRecord(this.storage, record, this.persistedRecordId);
|
|
32613
|
+
if (!landedOn) return {
|
|
32614
|
+
status: "not-committed",
|
|
32615
|
+
reason: "the observational memory record was cleared"
|
|
32616
|
+
};
|
|
32617
|
+
committedRecord = landedOn;
|
|
32618
|
+
}
|
|
32619
|
+
try {
|
|
32620
|
+
await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, this.persistedRecordId);
|
|
32621
|
+
const candidateTitle = processed.threadTitle?.trim();
|
|
32622
|
+
const hasValidThreadTitle = !!candidateTitle && candidateTitle.length >= 3;
|
|
32623
|
+
if (hasValidThreadTitle || processed.extractedValues) {
|
|
32624
|
+
const thread = await this.storage.getThreadById({ threadId });
|
|
32625
|
+
if (thread) {
|
|
32626
|
+
const oldTitle = thread.title?.trim();
|
|
32627
|
+
const newTitle = resolveThreadTitleUpdate(thread, candidateTitle);
|
|
32628
|
+
const shouldUpdateThreadTitle = newTitle !== void 0;
|
|
32629
|
+
const previousOmMetadata = (0, _mastra_core_memory.getThreadOMMetadata)(thread.metadata);
|
|
32630
|
+
const metadataUpdate = buildThreadMetadataFromExtractedValues(processed.extractors ?? this.observationConfig.extractors, processed.extractedValues);
|
|
32631
|
+
const newMetadata = (0, _mastra_core_memory.setThreadOMMetadata)(thread.metadata, {
|
|
32632
|
+
...hasValidThreadTitle || metadataUpdate.threadTitle ? { threadTitle: metadataUpdate.threadTitle ?? processed.threadTitle } : {},
|
|
32633
|
+
extracted: {
|
|
32634
|
+
...previousOmMetadata?.extracted ?? {},
|
|
32635
|
+
...metadataUpdate.extracted ?? {}
|
|
32636
|
+
}
|
|
32637
|
+
});
|
|
32638
|
+
await this.storage.patchThread({
|
|
32639
|
+
id: threadId,
|
|
32640
|
+
...shouldUpdateThreadTitle ? { title: newTitle } : {},
|
|
32641
|
+
metadata: newMetadata
|
|
32141
32642
|
});
|
|
32142
|
-
|
|
32643
|
+
if (shouldUpdateThreadTitle) {
|
|
32644
|
+
const marker = createThreadUpdateMarker({
|
|
32645
|
+
cycleId: this.cycleId,
|
|
32646
|
+
threadId,
|
|
32647
|
+
oldTitle,
|
|
32648
|
+
newTitle
|
|
32649
|
+
});
|
|
32650
|
+
await this.streamMarker(marker);
|
|
32651
|
+
}
|
|
32143
32652
|
}
|
|
32144
32653
|
}
|
|
32654
|
+
} catch (error) {
|
|
32655
|
+
omError("[OM] Post-buffer indexing or thread update failed; the buffered chunk is kept", error);
|
|
32145
32656
|
}
|
|
32146
|
-
return
|
|
32657
|
+
return {
|
|
32658
|
+
status: "committed",
|
|
32659
|
+
processed,
|
|
32660
|
+
record: committedRecord
|
|
32661
|
+
};
|
|
32147
32662
|
}
|
|
32148
|
-
async emitEndMarkers(
|
|
32149
|
-
|
|
32663
|
+
async emitEndMarkers() {}
|
|
32664
|
+
async emitBufferingEndMarker(processed) {
|
|
32150
32665
|
const { record, threadId, messages } = this.opts;
|
|
32151
32666
|
const tokensBuffered = await this.tokenCounter.countMessagesAsync(messages);
|
|
32152
32667
|
const totalBufferedTokens = getBufferedChunks(await this.storage.getObservationalMemory(record.threadId, record.resourceId)).reduce((sum, c) => sum + (c.tokenCount ?? 0), 0) || processed.observationTokens;
|
|
@@ -32156,7 +32671,7 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
32156
32671
|
startedAt: this.startedAt,
|
|
32157
32672
|
tokensBuffered,
|
|
32158
32673
|
bufferedTokens: totalBufferedTokens,
|
|
32159
|
-
recordId: record.id,
|
|
32674
|
+
recordId: this.persistedRecordId ?? record.id,
|
|
32160
32675
|
threadId,
|
|
32161
32676
|
observations: processed.observations,
|
|
32162
32677
|
extractedValues: processed.extractedValues,
|
|
@@ -32166,7 +32681,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
32166
32681
|
...endMarker,
|
|
32167
32682
|
transient: true
|
|
32168
32683
|
}).catch(() => {});
|
|
32169
|
-
await this.persistMarkerToStorage(endMarker, threadId, record.resourceId ?? void 0
|
|
32684
|
+
await this.persistMarkerToStorage(endMarker, threadId, record.resourceId ?? void 0, {
|
|
32685
|
+
notAfter: new Date(this.startedAt),
|
|
32686
|
+
onCycleStartMessage: true
|
|
32687
|
+
});
|
|
32170
32688
|
}
|
|
32171
32689
|
async emitFailedMarkers(_cycleId, error) {
|
|
32172
32690
|
const { record, threadId, messages } = this.opts;
|
|
@@ -32185,7 +32703,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
32185
32703
|
...failedMarker,
|
|
32186
32704
|
transient: true
|
|
32187
32705
|
}).catch(() => {});
|
|
32188
|
-
await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0
|
|
32706
|
+
await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0, {
|
|
32707
|
+
notAfter: new Date(this.startedAt),
|
|
32708
|
+
onCycleStartMessage: true
|
|
32709
|
+
});
|
|
32189
32710
|
}
|
|
32190
32711
|
};
|
|
32191
32712
|
var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
@@ -32206,10 +32727,17 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32206
32727
|
lastBatchProviderMetadata;
|
|
32207
32728
|
observationResults = [];
|
|
32208
32729
|
priorMetadataByThread = /* @__PURE__ */ new Map();
|
|
32730
|
+
/** Each observed thread's wrapped section from the last composition. */
|
|
32731
|
+
threadSections = /* @__PURE__ */ new Map();
|
|
32732
|
+
/** Head text the processed observations were composed from (the commit's expected text). */
|
|
32733
|
+
composedFrom = "";
|
|
32209
32734
|
constructor(deps, opts) {
|
|
32210
32735
|
super(deps, opts);
|
|
32211
32736
|
this.resourceId = opts.resourceId;
|
|
32212
32737
|
}
|
|
32738
|
+
getObservedMessagesForThread(threadId) {
|
|
32739
|
+
return this.threadsWithMessages.get(threadId);
|
|
32740
|
+
}
|
|
32213
32741
|
get needsLock() {
|
|
32214
32742
|
return true;
|
|
32215
32743
|
}
|
|
@@ -32296,10 +32824,12 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32296
32824
|
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
32297
32825
|
}))
|
|
32298
32826
|
});
|
|
32299
|
-
const
|
|
32827
|
+
const freshRecord = await this.storage.getObservationalMemory(null, this.resourceId);
|
|
32828
|
+
const existingObservations = freshRecord?.activeObservations ?? record.activeObservations ?? "";
|
|
32300
32829
|
return {
|
|
32301
32830
|
messages: Array.from(this.threadsWithMessages.values()).flat(),
|
|
32302
|
-
existingObservations
|
|
32831
|
+
existingObservations,
|
|
32832
|
+
contextRecord: freshRecord
|
|
32303
32833
|
};
|
|
32304
32834
|
}
|
|
32305
32835
|
async emitStartMarkers(cycleId) {
|
|
@@ -32323,7 +32853,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32323
32853
|
}
|
|
32324
32854
|
}
|
|
32325
32855
|
}
|
|
32326
|
-
async observe(
|
|
32856
|
+
async observe(observerContext, _messages, wasTruncated) {
|
|
32327
32857
|
const maxTokensPerBatch = this.observationConfig.maxTokensPerBatch ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.maxTokensPerBatch;
|
|
32328
32858
|
const orderedThreadIds = this.threadOrder.filter((tid) => this.threadsWithMessages.has(tid));
|
|
32329
32859
|
const batches = [];
|
|
@@ -32349,10 +32879,10 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32349
32879
|
}
|
|
32350
32880
|
if (currentBatch.threadIds.length > 0) batches.push(currentBatch);
|
|
32351
32881
|
const batchResults = await Promise.all(batches.map(async (batch) => {
|
|
32352
|
-
return this.deps.observer.callMultiThread(
|
|
32882
|
+
return this.deps.observer.callMultiThread(observerContext, batch.threadMap, batch.threadIds, this.opts.abortSignal, this.opts.requestContext, this.priorMetadataByThread, this.opts.observabilityContext, void 0, {
|
|
32353
32883
|
resourceId: this.opts.resourceId,
|
|
32354
32884
|
trigger: this.opts.trigger
|
|
32355
|
-
}, this.opts.record.observedTimezone, this.opts.agent, this.opts.currentModel);
|
|
32885
|
+
}, this.opts.record.observedTimezone, this.opts.agent, this.opts.currentModel, wasTruncated);
|
|
32356
32886
|
}));
|
|
32357
32887
|
for (const batchResult of batchResults) {
|
|
32358
32888
|
for (const [threadId, result] of batchResult.results) this.multiThreadResults.set(threadId, result);
|
|
@@ -32419,14 +32949,38 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32419
32949
|
}
|
|
32420
32950
|
});
|
|
32421
32951
|
}
|
|
32952
|
+
this.composedFrom = existingObservations;
|
|
32953
|
+
const processed = await this.compose(existingObservations, record);
|
|
32954
|
+
for (const [index, { threadId, threadMessages, result }] of this.observationResults.entries()) this.deps.emitDebugEvent({
|
|
32955
|
+
type: "observation_complete",
|
|
32956
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
32957
|
+
threadId,
|
|
32958
|
+
resourceId: this.resourceId,
|
|
32959
|
+
observations: this.threadSections.get(threadId) ?? "",
|
|
32960
|
+
rawObserverOutput: result.observations,
|
|
32961
|
+
previousObservations: record.activeObservations,
|
|
32962
|
+
messages: threadMessages.map((m) => ({
|
|
32963
|
+
role: m.role,
|
|
32964
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
32965
|
+
})),
|
|
32966
|
+
usage: index === 0 && this.totalBatchUsage.totalTokens > 0 ? this.totalBatchUsage : void 0
|
|
32967
|
+
});
|
|
32968
|
+
return processed;
|
|
32969
|
+
}
|
|
32970
|
+
/**
|
|
32971
|
+
* Merge every observed thread's section into `existingObservations` (the head text). Resource
|
|
32972
|
+
* scope rewrites an existing same-day `<thread>` section in place, so the result is not an
|
|
32973
|
+
* append of the head text.
|
|
32974
|
+
*/
|
|
32975
|
+
async compose(existingObservations, baseRecord) {
|
|
32422
32976
|
let currentObservations = existingObservations;
|
|
32423
32977
|
let cycleObservationTokens = 0;
|
|
32424
32978
|
const threadMetadataUpdates = [];
|
|
32425
|
-
for (const
|
|
32426
|
-
const { threadId, threadMessages, result } = obsResult;
|
|
32979
|
+
for (const { threadId, threadMessages, result } of this.observationResults) {
|
|
32427
32980
|
cycleObservationTokens += this.tokenCounter.countObservations(result.observations);
|
|
32428
32981
|
const messageRange = this.retrieval ? buildMessageRange(threadMessages) : void 0;
|
|
32429
32982
|
const threadSection = await this.wrapWithThreadTag(threadId, result.observations, messageRange);
|
|
32983
|
+
this.threadSections.set(threadId, threadSection);
|
|
32430
32984
|
const threadLastObservedAt = this.getMaxMessageTimestamp(threadMessages);
|
|
32431
32985
|
currentObservations = this.replaceOrAppendThreadSection(currentObservations, threadId, threadSection, threadLastObservedAt);
|
|
32432
32986
|
threadMetadataUpdates.push({
|
|
@@ -32440,26 +32994,11 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32440
32994
|
extractors: result.extractors,
|
|
32441
32995
|
lastObservedMessageCursor: getLastObservedMessageCursor(threadMessages)
|
|
32442
32996
|
});
|
|
32443
|
-
const isFirstThread = this.observationResults.indexOf(obsResult) === 0;
|
|
32444
|
-
this.deps.emitDebugEvent({
|
|
32445
|
-
type: "observation_complete",
|
|
32446
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
32447
|
-
threadId,
|
|
32448
|
-
resourceId: this.resourceId,
|
|
32449
|
-
observations: threadSection,
|
|
32450
|
-
rawObserverOutput: result.observations,
|
|
32451
|
-
previousObservations: record.activeObservations,
|
|
32452
|
-
messages: threadMessages.map((m) => ({
|
|
32453
|
-
role: m.role,
|
|
32454
|
-
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
32455
|
-
})),
|
|
32456
|
-
usage: isFirstThread && this.totalBatchUsage.totalTokens > 0 ? this.totalBatchUsage : void 0
|
|
32457
|
-
});
|
|
32458
32997
|
}
|
|
32459
32998
|
const observedMessages = this.observationResults.flatMap((r) => r.threadMessages);
|
|
32460
32999
|
const lastObservedAt = this.getMaxMessageTimestamp(observedMessages);
|
|
32461
33000
|
const newMessageIds = observedMessages.map((m) => m.id);
|
|
32462
|
-
const existingIds =
|
|
33001
|
+
const existingIds = baseRecord.observedMessageIds ?? [];
|
|
32463
33002
|
const observedMessageIds = [.../* @__PURE__ */ new Set([...existingIds, ...newMessageIds])];
|
|
32464
33003
|
const observationTokens = this.tokenCounter.countObservations(currentObservations);
|
|
32465
33004
|
return {
|
|
@@ -32473,8 +33012,25 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32473
33012
|
}
|
|
32474
33013
|
async persist(processed) {
|
|
32475
33014
|
const { record, resourceId } = this.opts;
|
|
33015
|
+
const head = await getLineageHead(this.storage, record);
|
|
33016
|
+
if (!head) return {
|
|
33017
|
+
status: "not-committed",
|
|
33018
|
+
reason: "the observational memory record was cleared"
|
|
33019
|
+
};
|
|
33020
|
+
const committed = await this.commitActiveObservationsToHead({
|
|
33021
|
+
processed,
|
|
33022
|
+
composedFrom: this.composedFrom,
|
|
33023
|
+
target: head,
|
|
33024
|
+
recompose: (fresh) => this.compose(fresh.activeObservations ?? "", fresh),
|
|
33025
|
+
cycleMessageIds: this.observationResults.flatMap((r) => r.threadMessages.map((m) => m.id))
|
|
33026
|
+
});
|
|
33027
|
+
if (!committed) return {
|
|
33028
|
+
status: "not-committed",
|
|
33029
|
+
reason: "the observational memory head kept changing during the commit"
|
|
33030
|
+
};
|
|
33031
|
+
processed = committed.processed;
|
|
32476
33032
|
const threadUpdateMarkers = [];
|
|
32477
|
-
if (processed.threadMetadataUpdates) for (const update of processed.threadMetadataUpdates) {
|
|
33033
|
+
if (processed.threadMetadataUpdates && !committed.alreadyCovered) for (const update of processed.threadMetadataUpdates) {
|
|
32478
33034
|
const thread = await this.storage.getThreadById({ threadId: update.threadId });
|
|
32479
33035
|
if (thread) {
|
|
32480
33036
|
const oldTitle = thread.title?.trim();
|
|
@@ -32507,15 +33063,13 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32507
33063
|
}
|
|
32508
33064
|
}
|
|
32509
33065
|
for (const marker of threadUpdateMarkers) await this.streamMarker(marker);
|
|
32510
|
-
await this.
|
|
32511
|
-
|
|
32512
|
-
|
|
32513
|
-
|
|
32514
|
-
|
|
32515
|
-
|
|
32516
|
-
}
|
|
32517
|
-
if (resourceId) await Promise.all(this.observationResults.map(({ threadId, threadMessages, result }) => this.indexObservationGroups(result.observations, threadId, resourceId, this.getMaxMessageTimestamp(threadMessages), record.id)));
|
|
32518
|
-
return true;
|
|
33066
|
+
if (resourceId && !committed.alreadyCovered) await Promise.all(this.observationResults.map(({ threadId, threadMessages, result }) => this.indexObservationGroups(result.observations, threadId, resourceId, this.getMaxMessageTimestamp(threadMessages), committed.record.id)));
|
|
33067
|
+
return {
|
|
33068
|
+
status: "committed",
|
|
33069
|
+
processed,
|
|
33070
|
+
record: committed.record,
|
|
33071
|
+
alreadyCovered: committed.alreadyCovered
|
|
33072
|
+
};
|
|
32519
33073
|
}
|
|
32520
33074
|
async emitEndMarkers(cycleId, processed) {
|
|
32521
33075
|
for (const obsResult of this.observationResults) {
|
|
@@ -32572,7 +33126,8 @@ ObservationStrategy.create = ((om, opts) => {
|
|
|
32572
33126
|
observedMessageIds: om.observedMessageIds,
|
|
32573
33127
|
obscureThreadIds: om.getObscureThreadIds(),
|
|
32574
33128
|
onIndexObservations: om.onIndexObservations,
|
|
32575
|
-
emitDebugEvent: (e) => om.emitDebugEvent(e)
|
|
33129
|
+
emitDebugEvent: (e) => om.emitDebugEvent(e),
|
|
33130
|
+
prepareObserverContext: (observations, record) => om.prepareObserverContext(observations, record)
|
|
32576
33131
|
};
|
|
32577
33132
|
if (opts.cycleId) return new AsyncBufferObservationStrategy(deps, opts);
|
|
32578
33133
|
if (deps.scope === "resource" && opts.resourceId) return new ResourceScopedObservationStrategy(deps, opts);
|
|
@@ -32698,8 +33253,8 @@ var ObservationStep = class {
|
|
|
32698
33253
|
let reflected = false;
|
|
32699
33254
|
let didThresholdCleanup = false;
|
|
32700
33255
|
let observerExchange;
|
|
33256
|
+
const step0Messages = this.stepNumber === 0 ? getObservableMessages(messageList) : [];
|
|
32701
33257
|
if (this.stepNumber === 0) {
|
|
32702
|
-
const step0Messages = getObservableMessages(messageList);
|
|
32703
33258
|
const activation = await om.activate({
|
|
32704
33259
|
threadId,
|
|
32705
33260
|
resourceId,
|
|
@@ -32756,7 +33311,17 @@ var ObservationStep = class {
|
|
|
32756
33311
|
record: this.turn.record,
|
|
32757
33312
|
messages: getObservableMessages(messageList)
|
|
32758
33313
|
});
|
|
32759
|
-
if (statusSnapshot.
|
|
33314
|
+
if (statusSnapshot.inAsyncObservationBand && !statusSnapshot.canActivate && !hasIncompleteToolCalls) {
|
|
33315
|
+
await this.turn.refreshRecord();
|
|
33316
|
+
statusSnapshot = await om.getStatus({
|
|
33317
|
+
threadId,
|
|
33318
|
+
resourceId,
|
|
33319
|
+
record: this.turn.record,
|
|
33320
|
+
messages: getObservableMessages(messageList)
|
|
33321
|
+
});
|
|
33322
|
+
}
|
|
33323
|
+
const activateBeforeBuffering = statusSnapshot.inAsyncObservationBand && statusSnapshot.canActivate && !hasIncompleteToolCalls;
|
|
33324
|
+
if (statusSnapshot.shouldBuffer && !activateBeforeBuffering) {
|
|
32760
33325
|
const allMessages = getObservableMessages(messageList);
|
|
32761
33326
|
const unobservedMessages = om.getUnobservedMessages(allMessages, statusSnapshot.record);
|
|
32762
33327
|
const candidates = om.getUnobservedMessages(unobservedMessages, statusSnapshot.record, { excludeBuffered: true });
|
|
@@ -32770,8 +33335,10 @@ var ObservationStep = class {
|
|
|
32770
33335
|
omDebug(`[OM:buffer] onBufferChunkSealed hook failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
32771
33336
|
}
|
|
32772
33337
|
if (this.turn.memory) await this.turn.memory.persistMessages(safeCandidates);
|
|
32773
|
-
messageList.
|
|
32774
|
-
|
|
33338
|
+
const inputIds = new Set(messageList.get.input.db().map((msg) => msg.id));
|
|
33339
|
+
const sealedOutput = safeCandidates.filter((msg) => !inputIds.has(msg.id));
|
|
33340
|
+
messageList.removeByIds(sealedOutput.map((msg) => msg.id));
|
|
33341
|
+
for (const msg of sealedOutput) messageList.add(msg, "memory");
|
|
32775
33342
|
}
|
|
32776
33343
|
if (!deferred) {
|
|
32777
33344
|
om.trackBackgroundWork(om.buffer({
|
|
@@ -32793,7 +33360,8 @@ var ObservationStep = class {
|
|
|
32793
33360
|
buffered = true;
|
|
32794
33361
|
}
|
|
32795
33362
|
}
|
|
32796
|
-
const
|
|
33363
|
+
const bandDefersObservation = statusSnapshot.inAsyncObservationBand && !statusSnapshot.canActivate;
|
|
33364
|
+
const willObserveNow = statusSnapshot.shouldObserve && !hasIncompleteToolCalls && !bandDefersObservation;
|
|
32797
33365
|
/** In-flight message ids the step-0 cleanup must never remove from live context. */
|
|
32798
33366
|
let step0PreserveIds;
|
|
32799
33367
|
if (this.stepNumber > 0 || willObserveNow) {
|
|
@@ -32835,7 +33403,7 @@ var ObservationStep = class {
|
|
|
32835
33403
|
}
|
|
32836
33404
|
if (willObserveNow) {
|
|
32837
33405
|
const preObsGeneration = this.turn.record.generationCount;
|
|
32838
|
-
const obsResult = await this.runThresholdObservation();
|
|
33406
|
+
const obsResult = await this.runThresholdObservation(statusSnapshot.inAsyncObservationBand);
|
|
32839
33407
|
observerExchange = obsResult.observerExchange;
|
|
32840
33408
|
if (obsResult.succeeded) {
|
|
32841
33409
|
observed = true;
|
|
@@ -32908,12 +33476,12 @@ var ObservationStep = class {
|
|
|
32908
33476
|
/**
|
|
32909
33477
|
* Run the full threshold observation pipeline:
|
|
32910
33478
|
* waitForBuffering → re-check → activate buffered chunks → reflect → observe
|
|
32911
|
-
* (sync fallback
|
|
33479
|
+
* (sync fallback at blockAfter when async buffering is enabled)
|
|
32912
33480
|
*/
|
|
32913
|
-
async runThresholdObservation() {
|
|
33481
|
+
async runThresholdObservation(inAsyncObservationBand) {
|
|
32914
33482
|
const { threadId, resourceId, messageList } = this.turn;
|
|
32915
33483
|
const om = this.turn.om;
|
|
32916
|
-
await om.waitForBuffering(threadId, resourceId);
|
|
33484
|
+
if (!inAsyncObservationBand) await om.waitForBuffering(threadId, resourceId);
|
|
32917
33485
|
await this.turn.refreshRecord();
|
|
32918
33486
|
const observableMessages = this.seededResponseMessage ? getObservableMessages(messageList).filter((msg) => msg.id !== this.turn.responseMessageId) : getObservableMessages(messageList);
|
|
32919
33487
|
let status = await om.getStatus({
|
|
@@ -32942,7 +33510,16 @@ var ObservationStep = class {
|
|
|
32942
33510
|
messageList
|
|
32943
33511
|
});
|
|
32944
33512
|
this.turn.setRecord(activation.record);
|
|
32945
|
-
if (!activation.activated)
|
|
33513
|
+
if (!activation.activated) {
|
|
33514
|
+
await this.turn.refreshRecord();
|
|
33515
|
+
status = await om.getStatus({
|
|
33516
|
+
threadId,
|
|
33517
|
+
resourceId,
|
|
33518
|
+
record: this.turn.record,
|
|
33519
|
+
messages: pendingMessages
|
|
33520
|
+
});
|
|
33521
|
+
break;
|
|
33522
|
+
}
|
|
32946
33523
|
activated = true;
|
|
32947
33524
|
const ids = new Set(activation.activatedMessageIds ?? []);
|
|
32948
33525
|
activatedMessageIds.push(...ids);
|
|
@@ -32954,11 +33531,17 @@ var ObservationStep = class {
|
|
|
32954
33531
|
messages: pendingMessages
|
|
32955
33532
|
});
|
|
32956
33533
|
}
|
|
33534
|
+
if (status.shouldObserve && status.canActivate) return {
|
|
33535
|
+
succeeded: activated,
|
|
33536
|
+
record: this.turn.record,
|
|
33537
|
+
activatedMessageIds: activated ? activatedMessageIds : void 0,
|
|
33538
|
+
cleanupMessageIds: activatedMessageIds
|
|
33539
|
+
};
|
|
32957
33540
|
if (activated) {
|
|
32958
|
-
const
|
|
33541
|
+
const preReflectionRecord = this.turn.record;
|
|
32959
33542
|
await om.reflector.maybeReflect({
|
|
32960
|
-
record:
|
|
32961
|
-
observationTokens:
|
|
33543
|
+
record: preReflectionRecord,
|
|
33544
|
+
observationTokens: preReflectionRecord.observationTokenCount ?? 0,
|
|
32962
33545
|
threadId,
|
|
32963
33546
|
writer: this.turn.writer,
|
|
32964
33547
|
messageList,
|
|
@@ -32973,6 +33556,14 @@ var ObservationStep = class {
|
|
|
32973
33556
|
}),
|
|
32974
33557
|
trigger: "turn-sync"
|
|
32975
33558
|
});
|
|
33559
|
+
await this.turn.refreshRecord();
|
|
33560
|
+
const postActivationRecord = this.turn.record;
|
|
33561
|
+
status = await om.getStatus({
|
|
33562
|
+
threadId,
|
|
33563
|
+
resourceId,
|
|
33564
|
+
record: postActivationRecord,
|
|
33565
|
+
messages: pendingMessages
|
|
33566
|
+
});
|
|
32976
33567
|
if (!status.shouldObserve) return {
|
|
32977
33568
|
succeeded: true,
|
|
32978
33569
|
record: postActivationRecord,
|
|
@@ -32980,6 +33571,12 @@ var ObservationStep = class {
|
|
|
32980
33571
|
cleanupMessageIds: activatedMessageIds
|
|
32981
33572
|
};
|
|
32982
33573
|
}
|
|
33574
|
+
if (status.inAsyncObservationBand || status.canActivate) return {
|
|
33575
|
+
succeeded: activated,
|
|
33576
|
+
record: this.turn.record,
|
|
33577
|
+
activatedMessageIds: activated ? activatedMessageIds : void 0,
|
|
33578
|
+
cleanupMessageIds: activatedMessageIds
|
|
33579
|
+
};
|
|
32983
33580
|
let obsResult;
|
|
32984
33581
|
try {
|
|
32985
33582
|
obsResult = await om.observe({
|
|
@@ -33327,13 +33924,18 @@ Take the existing observations and rewrite them to make it easier to continue in
|
|
|
33327
33924
|
|
|
33328
33925
|
IMPORTANT: your reflections are THE ENTIRETY of the assistants memory. Any information you do not add to your reflections will be immediately forgotten. Make sure you do not leave out anything. Your reflections must assume the assistant knows nothing - your reflections are the ENTIRE memory system.
|
|
33329
33926
|
|
|
33927
|
+
The assistant relies on this memory to answer precise questions about what the user said, when they said it, and how things changed over time.
|
|
33928
|
+
|
|
33330
33929
|
When consolidating observations:
|
|
33331
|
-
-
|
|
33332
|
-
-
|
|
33930
|
+
- Keep every concrete fact: names, places, dates, numbers, amounts, durations, titles, versions, and who said what. Merge duplicates, but never drop specifics
|
|
33931
|
+
- Keep every dated statement with its date, and retain the timestamps that matter (start times, completion times, significant events)
|
|
33932
|
+
- When the user changed their mind or a fact was updated, keep both the old and new values with their dates and say which is current
|
|
33933
|
+
- Keep what the user explicitly said they have never done, do not know, or refused, and keep every stated preference, rule, and instruction with its meaning intact
|
|
33934
|
+
- Prefer many short factual lines over prose summaries. Do not replace facts with generalities like "discussed travel plans"
|
|
33333
33935
|
- Combine related items where it makes sense (e.g., "agent called view tool 5 times on file x")
|
|
33334
33936
|
- Preserve ✅ completion markers — they are memory signals that tell the assistant what is already resolved and help prevent repeated work
|
|
33335
33937
|
- Preserve the concrete resolved outcome captured by ✅ markers so the assistant knows what exactly is done
|
|
33336
|
-
- Condense older observations more
|
|
33938
|
+
- Condense the wording of older observations more than recent ones, but keep their facts
|
|
33337
33939
|
|
|
33338
33940
|
CRITICAL: USER ASSERTIONS vs QUESTIONS
|
|
33339
33941
|
- "User stated: X" = authoritative assertion (user told us something about themselves)
|
|
@@ -33613,6 +34215,25 @@ async function withAbortCheck(fn, abortSignal) {
|
|
|
33613
34215
|
* size while still letting them fire sooner than a threshold activation.
|
|
33614
34216
|
*/
|
|
33615
34217
|
const EARLY_ACTIVATION_SIZE_FLOOR_RATIO = .75;
|
|
34218
|
+
/** A reflection commit that created nothing because the reflected record is no longer the head. */
|
|
34219
|
+
var ReflectionNotAppliedError = class extends Error {
|
|
34220
|
+
constructor(recordId, headId) {
|
|
34221
|
+
super(`Reflection not applied: record ${recordId} is no longer the current generation (head is ${headId})`);
|
|
34222
|
+
this.name = "ReflectionNotAppliedError";
|
|
34223
|
+
}
|
|
34224
|
+
};
|
|
34225
|
+
/**
|
|
34226
|
+
* Whether a reflection commit created the generation the caller asked for. Storage creates
|
|
34227
|
+
* nothing (and returns the current head or the stored record) when the reflected record was
|
|
34228
|
+
* already retired or its observations were rewritten since the reflection read them.
|
|
34229
|
+
*
|
|
34230
|
+
* Adapters that predate `newRecordId` ignore it and do not report `supersededBy`; for those a
|
|
34231
|
+
* returned record other than the reflected one means a new generation was created.
|
|
34232
|
+
*/
|
|
34233
|
+
function isReflectionApplied(returned, newRecordId, reflectedRecordId) {
|
|
34234
|
+
if (returned.id === newRecordId) return true;
|
|
34235
|
+
return returned.supersededBy === void 0 && returned.id !== reflectedRecordId;
|
|
34236
|
+
}
|
|
33616
34237
|
/**
|
|
33617
34238
|
* Runs the Reflector agent for compressing observations.
|
|
33618
34239
|
* Handles synchronous reflection, async buffered reflection, and activation.
|
|
@@ -33976,14 +34597,14 @@ var ReflectorRunner = class {
|
|
|
33976
34597
|
* Perform async buffered reflection — reflects observations and stores to bufferedReflection.
|
|
33977
34598
|
* Does NOT create a new generation or update activeObservations.
|
|
33978
34599
|
*/
|
|
33979
|
-
async doAsyncBufferedReflection(record,
|
|
34600
|
+
async doAsyncBufferedReflection(record, bufferKey, writer, requestContext, observabilityContext, priorExtractedValues, mainAgent, sendSignal, trigger, currentModel) {
|
|
33980
34601
|
const currentRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record;
|
|
33981
34602
|
const observationTokens = currentRecord.observationTokenCount ?? 0;
|
|
33982
34603
|
const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(currentRecord));
|
|
33983
34604
|
const bufferActivation = this.reflectionConfig.bufferActivation ?? .5;
|
|
33984
34605
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
33985
34606
|
const cycleId = `reflect-buf-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
33986
|
-
BufferingCoordinator.reflectionBufferCycleIds.set(
|
|
34607
|
+
BufferingCoordinator.reflectionBufferCycleIds.set(bufferKey, cycleId);
|
|
33987
34608
|
const allLines = (currentRecord.activeObservations ?? "").split("\n");
|
|
33988
34609
|
const totalLines = allLines.length;
|
|
33989
34610
|
const avgTokensPerLine = totalLines > 0 ? observationTokens / totalLines : 0;
|
|
@@ -34022,13 +34643,24 @@ var ReflectorRunner = class {
|
|
|
34022
34643
|
await persistThreadExtractedValues(this.storage, this.reflectionConfig.extractors, currentRecord.threadId ?? void 0, reflectResult.extractedValues);
|
|
34023
34644
|
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
34024
34645
|
omDebug(`[OM:reflect] doAsyncBufferedReflection: reflector returned ${reflectionTokenCount} tokens (${reflectResult.observations?.length} chars), saving to recordId=${currentRecord.id}`);
|
|
34025
|
-
await this.
|
|
34026
|
-
id
|
|
34027
|
-
|
|
34028
|
-
|
|
34029
|
-
|
|
34030
|
-
|
|
34031
|
-
|
|
34646
|
+
if (!await runOMCommit(this.buffering.getLockKey(currentRecord.threadId, currentRecord.resourceId), async () => {
|
|
34647
|
+
if ((await this.storage.getObservationalMemory(currentRecord.threadId, currentRecord.resourceId))?.id !== currentRecord.id) return false;
|
|
34648
|
+
await this.storage.updateBufferedReflection({
|
|
34649
|
+
id: currentRecord.id,
|
|
34650
|
+
reflection: reflectResult.observations,
|
|
34651
|
+
tokenCount: reflectionTokenCount,
|
|
34652
|
+
inputTokenCount: sliceTokenEstimate,
|
|
34653
|
+
reflectedObservationLineCount
|
|
34654
|
+
});
|
|
34655
|
+
return true;
|
|
34656
|
+
})) {
|
|
34657
|
+
omDebug(`[OM:reflect] doAsyncBufferedReflection: record ${currentRecord.id} is no longer the head; discarding the buffered reflection`);
|
|
34658
|
+
BufferingCoordinator.lastBufferedBoundary.delete(bufferKey);
|
|
34659
|
+
return {
|
|
34660
|
+
usage: reflectResult.usage,
|
|
34661
|
+
providerMetadata: reflectResult.providerMetadata
|
|
34662
|
+
};
|
|
34663
|
+
}
|
|
34032
34664
|
omDebug(`[OM:reflect] doAsyncBufferedReflection: bufferedReflection saved with lineCount=${reflectedObservationLineCount}`);
|
|
34033
34665
|
if (writer) {
|
|
34034
34666
|
const endMarker = createBufferingEndMarker({
|
|
@@ -34107,10 +34739,16 @@ var ReflectorRunner = class {
|
|
|
34107
34739
|
}
|
|
34108
34740
|
}
|
|
34109
34741
|
omDebug(`[OM:reflect] tryActivateBufferedReflection: activating, beforeTokens=${beforeTokens}, combinedTokenCount=${combinedTokenCount}, reflectedLineCount=${reflectedLineCount}, unreflectedLines=${unreflectedLines.length}`);
|
|
34110
|
-
|
|
34742
|
+
const newRecordId = crypto.randomUUID();
|
|
34743
|
+
const swapped = await runOMCommit(lockKey, () => this.storage.swapBufferedReflectionToActive({
|
|
34111
34744
|
currentRecord: freshRecord,
|
|
34112
|
-
tokenCount: combinedTokenCount
|
|
34113
|
-
|
|
34745
|
+
tokenCount: combinedTokenCount,
|
|
34746
|
+
newRecordId
|
|
34747
|
+
}), { priority: "reflection" });
|
|
34748
|
+
if (!isReflectionApplied(swapped, newRecordId, freshRecord.id)) {
|
|
34749
|
+
omDebug(`[OM:reflect] tryActivateBufferedReflection: buffered reflection on ${freshRecord.id} not applied (head is ${swapped.id})`);
|
|
34750
|
+
return { status: "not-applied" };
|
|
34751
|
+
}
|
|
34114
34752
|
if (committedContext) await this.notifyReflectionCommitted({
|
|
34115
34753
|
...committedContext,
|
|
34116
34754
|
observations: allLines.slice(0, reflectedLineCount).join("\n").trim(),
|
|
@@ -34165,7 +34803,8 @@ var ReflectorRunner = class {
|
|
|
34165
34803
|
* @internal Used by observation strategies. Do not call directly.
|
|
34166
34804
|
*/
|
|
34167
34805
|
async maybeReflect(opts) {
|
|
34168
|
-
const { record, observationTokens, writer, abortSignal, mainAgent, sendSignal, sendStateSignal, messageList, currentModel, reflectionHooks, trigger, requestContext, observabilityContext, lastActivityAt, threadId: requestedThreadId } = opts;
|
|
34806
|
+
const { record: callerRecord, observationTokens, writer, abortSignal, mainAgent, sendSignal, sendStateSignal, messageList, currentModel, reflectionHooks, trigger, requestContext, observabilityContext, lastActivityAt, threadId: requestedThreadId } = opts;
|
|
34807
|
+
const record = { ...callerRecord };
|
|
34169
34808
|
const lockKey = this.buffering.getLockKey(record.threadId, record.resourceId);
|
|
34170
34809
|
const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(record));
|
|
34171
34810
|
const priorExtractedValues = await getThreadExtractedValues(this.storage, this.reflectionConfig.extractors, requestedThreadId ?? record.threadId);
|
|
@@ -34219,7 +34858,7 @@ var ReflectorRunner = class {
|
|
|
34219
34858
|
abortSignal,
|
|
34220
34859
|
observabilityContext
|
|
34221
34860
|
});
|
|
34222
|
-
if (activationResult.status === "activated") return;
|
|
34861
|
+
if (activationResult.status === "activated" || activationResult.status === "not-applied") return;
|
|
34223
34862
|
if (activationResult.status === "suppressed") {
|
|
34224
34863
|
omDebug(`[OM:reflect] skipping sync fallback / re-buffer after suppressed early ${activationMetadata.triggeredBy} activation (reason=${activationResult.reason})`);
|
|
34225
34864
|
return;
|
|
@@ -34302,13 +34941,19 @@ var ReflectorRunner = class {
|
|
|
34302
34941
|
reflectResult.observations = await applyTextTransform(this.hooks, "afterReflection", reflectResult.observations, hookContext);
|
|
34303
34942
|
reflectionUsage = reflectResult.usage;
|
|
34304
34943
|
reflectionProviderMetadata = reflectResult.providerMetadata;
|
|
34305
|
-
await persistThreadExtractedValues(this.storage, this.reflectionConfig.extractors, record.threadId ?? void 0, reflectResult.extractedValues);
|
|
34306
34944
|
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
34307
|
-
|
|
34945
|
+
const newRecordId = crypto.randomUUID();
|
|
34946
|
+
const committedRecord = await runOMCommit(this.buffering.getLockKey(record.threadId, record.resourceId), () => this.storage.createReflectionGeneration({
|
|
34308
34947
|
currentRecord: record,
|
|
34309
34948
|
reflection: reflectResult.observations,
|
|
34310
|
-
tokenCount: reflectionTokenCount
|
|
34311
|
-
|
|
34949
|
+
tokenCount: reflectionTokenCount,
|
|
34950
|
+
newRecordId
|
|
34951
|
+
}), { priority: "reflection" });
|
|
34952
|
+
if (!isReflectionApplied(committedRecord, newRecordId, record.id)) {
|
|
34953
|
+
omDebug(`[OM:reflect] reflection of ${record.id} not applied (head is ${committedRecord.id})`);
|
|
34954
|
+
throw new ReflectionNotAppliedError(record.id, committedRecord.id);
|
|
34955
|
+
}
|
|
34956
|
+
await persistThreadExtractedValues(this.storage, this.reflectionConfig.extractors, record.threadId ?? void 0, reflectResult.extractedValues);
|
|
34312
34957
|
if (reflectionTokenCount >= reflectThreshold) this.syncReflectionSuppression.set(lockKey, reflectionTokenCount);
|
|
34313
34958
|
else this.syncReflectionSuppression.delete(lockKey);
|
|
34314
34959
|
await this.notifyReflectionCommitted({
|
|
@@ -34388,6 +35033,7 @@ var ReflectorRunner = class {
|
|
|
34388
35033
|
failureKind: failedMarker.data.failureKind,
|
|
34389
35034
|
error: reflectionError.message
|
|
34390
35035
|
});
|
|
35036
|
+
if (error instanceof ReflectionNotAppliedError) return;
|
|
34391
35037
|
if (lifecycleError !== void 0 || abortSignal?.aborted || this.reflectionConfig.failurePolicy !== "continue" || !isOmModelExecutionError(error) || error.failureKind !== "reflector-model") throw error;
|
|
34392
35038
|
this.syncReflectionSuppression.set(lockKey, observationTokens);
|
|
34393
35039
|
omError("[OM] Reflection failed", error);
|
|
@@ -34727,6 +35373,8 @@ function writeProcessInputStepReproCapture(params) {
|
|
|
34727
35373
|
* Returns the parts from the latest step of a message (after the last step-start marker).
|
|
34728
35374
|
* If no step-start marker exists, returns all parts.
|
|
34729
35375
|
*/
|
|
35376
|
+
/** Re-reads of the head after an activation swap hit a generation retired by a reflection. */
|
|
35377
|
+
const MAX_ACTIVATION_RETIRED_RETRIES = 3;
|
|
34730
35378
|
function getLatestStepParts(parts) {
|
|
34731
35379
|
for (let i = parts.length - 1; i >= 0; i--) if (parts[i]?.type === "step-start") return parts.slice(i + 1);
|
|
34732
35380
|
return parts;
|
|
@@ -34920,6 +35568,8 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
34920
35568
|
reflector;
|
|
34921
35569
|
/** Buffering state coordinator — manages static maps and buffering lifecycle. */
|
|
34922
35570
|
buffering;
|
|
35571
|
+
/** Unresolved observation blockAfter (multiplier or absolute); resolved per record. */
|
|
35572
|
+
observationBlockAfterSetting;
|
|
34923
35573
|
shouldObscureThreadIds = false;
|
|
34924
35574
|
hasher = e();
|
|
34925
35575
|
mastra;
|
|
@@ -35055,6 +35705,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
35055
35705
|
else throw new Error("shareTokenBudget requires async buffering to be disabled (this is a temporary limitation). Add observation: { bufferTokens: false } to your config:\n\n observationalMemory: {\n shareTokenBudget: true,\n observation: { bufferTokens: false },\n }\n\nAsync buffering is enabled by default — this opt-out is only needed when using shareTokenBudget.");
|
|
35056
35706
|
const observationActivateAfterIdle = config.observation?.activateAfterIdle ?? config.activateAfterIdle;
|
|
35057
35707
|
const observationActivateAfterIdlePath = config.observation?.activateAfterIdle !== void 0 ? "observation.activateAfterIdle" : "activateAfterIdle";
|
|
35708
|
+
this.observationBlockAfterSetting = asyncBufferingDisabled ? void 0 : config.observation?.blockAfter ?? (config.observation?.bufferTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferTokens ? 1.2 : void 0);
|
|
35058
35709
|
this.observationConfig = {
|
|
35059
35710
|
model: observationModel,
|
|
35060
35711
|
maxRetries: config.observation?.maxRetries ?? RETRY_CONFIG.maxRetries,
|
|
@@ -35075,8 +35726,8 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
35075
35726
|
bufferActivation: asyncBufferingDisabled ? void 0 : config.observation?.bufferActivation ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferActivation,
|
|
35076
35727
|
activateAfterIdle: parseActivationTTLConfig(observationActivateAfterIdle, observationActivateAfterIdlePath),
|
|
35077
35728
|
activateOnProviderChange: config.observation?.activateOnProviderChange ?? config.activateOnProviderChange ?? false,
|
|
35078
|
-
blockAfter:
|
|
35079
|
-
previousObserverTokens: config.observation?.previousObserverTokens ??
|
|
35729
|
+
blockAfter: resolveBlockAfter(this.observationBlockAfterSetting, config.observation?.messageTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens),
|
|
35730
|
+
previousObserverTokens: config.observation?.previousObserverTokens ?? false,
|
|
35080
35731
|
instruction: config.observation?.instruction,
|
|
35081
35732
|
threadTitle: config.observation?.threadTitle ?? false,
|
|
35082
35733
|
observeAttachments: config.observation?.observeAttachments ?? [...OBSERVATIONAL_MEMORY_DEFAULTS.observation.observeAttachments],
|
|
@@ -35385,6 +36036,13 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
35385
36036
|
return this.observationConfig.messageTokens;
|
|
35386
36037
|
}
|
|
35387
36038
|
/**
|
|
36039
|
+
* Resolve the observation blockAfter for a record. A multiplier scales the record's
|
|
36040
|
+
* effective messageTokens (per-record overrides included); an absolute value is used as-is.
|
|
36041
|
+
*/
|
|
36042
|
+
getEffectiveObservationBlockAfter(record) {
|
|
36043
|
+
return resolveBlockAfter(this.observationBlockAfterSetting, this.getEffectiveMessageTokens(record));
|
|
36044
|
+
}
|
|
36045
|
+
/**
|
|
35388
36046
|
* Resolve the effective reflection observationTokens for a record.
|
|
35389
36047
|
* Only explicit per-record overrides (stored under `_overrides`) win;
|
|
35390
36048
|
* the initial config snapshot is ignored so instance-level changes
|
|
@@ -35498,18 +36156,21 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
35498
36156
|
* the latest user input that triggered it.
|
|
35499
36157
|
* @internal Used by observation strategies and the reflector. Do not call directly.
|
|
35500
36158
|
*/
|
|
35501
|
-
async persistMarkerToStorage(marker, threadId, resourceId) {
|
|
36159
|
+
async persistMarkerToStorage(marker, threadId, resourceId, opts) {
|
|
35502
36160
|
try {
|
|
35503
|
-
const
|
|
36161
|
+
const markerData = marker.data;
|
|
36162
|
+
const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
|
|
36163
|
+
const preferred = [...opts?.preferMessages ?? []].reverse().find((m) => m.role === targetRole && (!m.threadId || m.threadId === threadId));
|
|
36164
|
+
const preferredStored = preferred ? (await this.storage.listMessagesById({ messageIds: [preferred.id] })).messages : [];
|
|
36165
|
+
const messages = (preferredStored.length ? { messages: preferredStored } : await this.storage.listMessages({
|
|
35504
36166
|
threadId,
|
|
35505
36167
|
perPage: 20,
|
|
35506
36168
|
orderBy: {
|
|
35507
36169
|
field: "createdAt",
|
|
35508
36170
|
direction: "DESC"
|
|
35509
|
-
}
|
|
36171
|
+
},
|
|
36172
|
+
...opts?.notAfter ? { filter: { dateRange: { end: opts.notAfter } } } : {}
|
|
35510
36173
|
}))?.messages ?? [];
|
|
35511
|
-
const markerData = marker.data;
|
|
35512
|
-
const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
|
|
35513
36174
|
for (const msg of messages) if (msg?.role === targetRole && msg.content?.parts && Array.isArray(msg.content.parts)) {
|
|
35514
36175
|
if (!(markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) msg.content.parts.push(marker);
|
|
35515
36176
|
await this.messageHistory.persistMessages({
|
|
@@ -35596,7 +36257,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
35596
36257
|
*/
|
|
35597
36258
|
createUnobservedMessage(message) {
|
|
35598
36259
|
const unobservedParts = getUnobservedParts(message);
|
|
35599
|
-
if (unobservedParts.
|
|
36260
|
+
if (unobservedParts.every((part) => part?.type?.startsWith("data-om-"))) return null;
|
|
35600
36261
|
return {
|
|
35601
36262
|
...message,
|
|
35602
36263
|
content: {
|
|
@@ -35968,17 +36629,17 @@ ${formattedMessages}
|
|
|
35968
36629
|
async startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, contextWindowTokens, requestContext, observabilityContext) {
|
|
35969
36630
|
const bufferKey = this.buffering.getObservationBufferKey(lockKey);
|
|
35970
36631
|
const currentTokens = contextWindowTokens ?? await this.tokenCounter.countMessagesAsync(unobservedMessages) + (record.pendingMessageTokens ?? 0);
|
|
35971
|
-
BufferingCoordinator.
|
|
36632
|
+
BufferingCoordinator.setObservationBoundary(bufferKey, currentTokens);
|
|
35972
36633
|
registerOp(record.id, "bufferingObservation");
|
|
35973
|
-
this.storage.setBufferingObservationFlag(record.id, true, currentTokens).catch((err) => {
|
|
36634
|
+
runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, true, currentTokens)).catch((err) => {
|
|
35974
36635
|
omError("[OM] Failed to set buffering observation flag", err);
|
|
35975
36636
|
});
|
|
35976
36637
|
const asyncOp = this.runAsyncBufferedObservation(record, threadId, unobservedMessages, bufferKey, writer, requestContext, observabilityContext).catch((err) => {
|
|
35977
36638
|
omError("[OM] async buffering observation failed", err);
|
|
35978
36639
|
}).finally(() => {
|
|
35979
|
-
BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
|
|
36640
|
+
if (BufferingCoordinator.asyncBufferingOps.get(bufferKey) === asyncOp) BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
|
|
35980
36641
|
unregisterOp(record.id, "bufferingObservation");
|
|
35981
|
-
this.storage.setBufferingObservationFlag(record.id, false).catch((err) => {
|
|
36642
|
+
runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch((err) => {
|
|
35982
36643
|
omError("[OM] Failed to clear buffering observation flag", err);
|
|
35983
36644
|
});
|
|
35984
36645
|
});
|
|
@@ -35993,6 +36654,14 @@ ${formattedMessages}
|
|
|
35993
36654
|
if (existingOp) try {
|
|
35994
36655
|
await existingOp;
|
|
35995
36656
|
} catch {}
|
|
36657
|
+
const releaseChunkWrite = BufferingCoordinator.trackChunkWrite(bufferKey);
|
|
36658
|
+
try {
|
|
36659
|
+
await this.bufferObservationChunk(record, threadId, unobservedMessages, bufferKey, releaseChunkWrite, writer, requestContext, observabilityContext);
|
|
36660
|
+
} finally {
|
|
36661
|
+
releaseChunkWrite();
|
|
36662
|
+
}
|
|
36663
|
+
}
|
|
36664
|
+
async bufferObservationChunk(record, threadId, unobservedMessages, bufferKey, releaseChunkWrite, writer, requestContext, observabilityContext) {
|
|
35996
36665
|
const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
35997
36666
|
if (!freshRecord) return;
|
|
35998
36667
|
let bufferCursor = BufferingCoordinator.lastBufferedAtTime.get(bufferKey) ?? freshRecord.lastBufferedAtTime ?? null;
|
|
@@ -36033,13 +36702,16 @@ ${formattedMessages}
|
|
|
36033
36702
|
threadIds: [threadId],
|
|
36034
36703
|
config: this.getObservationMarkerConfig()
|
|
36035
36704
|
});
|
|
36036
|
-
await this.persistMarkerToStorage(startMarker, threadId, freshRecord.resourceId ?? void 0
|
|
36705
|
+
await this.persistMarkerToStorage(startMarker, threadId, freshRecord.resourceId ?? void 0, {
|
|
36706
|
+
notAfter: new Date(startedAt),
|
|
36707
|
+
preferMessages: messagesToBuffer
|
|
36708
|
+
});
|
|
36037
36709
|
if (writer) writer.custom({
|
|
36038
36710
|
...startMarker,
|
|
36039
36711
|
transient: true
|
|
36040
36712
|
}).catch(() => {});
|
|
36041
36713
|
omDebug(`[OM:bufferInput] cycleId=${cycleId}, msgCount=${messagesToBuffer.length}, msgTokens=${tokensToBuffer}, ids=${messagesToBuffer.map((m) => `${m.id?.slice(0, 8)}@${m.createdAt ? new Date(m.createdAt).toISOString() : "none"}`).join(",")}`);
|
|
36042
|
-
|
|
36714
|
+
await this.runBufferedObservationCycle({
|
|
36043
36715
|
threadId,
|
|
36044
36716
|
resourceId: freshRecord.resourceId ?? void 0,
|
|
36045
36717
|
trigger: "async-buffer"
|
|
@@ -36053,12 +36725,13 @@ ${formattedMessages}
|
|
|
36053
36725
|
writer,
|
|
36054
36726
|
requestContext,
|
|
36055
36727
|
observabilityContext,
|
|
36056
|
-
trigger: "async-buffer"
|
|
36057
|
-
|
|
36058
|
-
|
|
36059
|
-
|
|
36060
|
-
|
|
36061
|
-
|
|
36728
|
+
trigger: "async-buffer",
|
|
36729
|
+
onBufferedChunkPersisted: async () => {
|
|
36730
|
+
const maxTs = this.getMaxMessageTimestamp(messagesToBuffer);
|
|
36731
|
+
BufferingCoordinator.lastBufferedAtTime.set(bufferKey, new Date(maxTs.getTime() + 1));
|
|
36732
|
+
releaseChunkWrite();
|
|
36733
|
+
}
|
|
36734
|
+
}).run());
|
|
36062
36735
|
}
|
|
36063
36736
|
/**
|
|
36064
36737
|
* Trigger async buffered observation if the token count has crossed a new interval.
|
|
@@ -36243,8 +36916,8 @@ ${formattedMessages}
|
|
|
36243
36916
|
const { threadId, resourceId, recordId, activatedMessageIds } = opts;
|
|
36244
36917
|
const lockKey = this.buffering.getLockKey(threadId, resourceId);
|
|
36245
36918
|
const bufKey = this.buffering.getObservationBufferKey(lockKey);
|
|
36246
|
-
BufferingCoordinator.
|
|
36247
|
-
await this.storage.setBufferingObservationFlag(recordId, false, 0).catch(() => {});
|
|
36919
|
+
BufferingCoordinator.setObservationBoundary(bufKey, 0);
|
|
36920
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(recordId, false, 0)).catch(() => {});
|
|
36248
36921
|
if (activatedMessageIds && activatedMessageIds.length > 0) this.buffering.cleanupStaticMaps(threadId, resourceId, activatedMessageIds);
|
|
36249
36922
|
}
|
|
36250
36923
|
/**
|
|
@@ -36403,9 +37076,13 @@ ${formattedMessages}
|
|
|
36403
37076
|
* @example
|
|
36404
37077
|
* ```ts
|
|
36405
37078
|
* const status = await om.getStatus({ threadId });
|
|
36406
|
-
* if (status.shouldObserve) {
|
|
37079
|
+
* if (status.shouldObserve && status.canActivate) {
|
|
37080
|
+
* await om.activate({ threadId });
|
|
37081
|
+
* } else if (status.shouldObserve && !status.inAsyncObservationBand) {
|
|
37082
|
+
* // At/above blockAfter (or async buffering disabled): observe synchronously
|
|
36407
37083
|
* await om.observe({ threadId });
|
|
36408
37084
|
* } else if (status.shouldBuffer) {
|
|
37085
|
+
* // Below the threshold, or in the threshold→blockAfter band: buffer in the background
|
|
36409
37086
|
* await om.buffer({ threadId });
|
|
36410
37087
|
* }
|
|
36411
37088
|
* if (status.shouldReflect) {
|
|
@@ -36435,8 +37112,10 @@ ${formattedMessages}
|
|
|
36435
37112
|
const bufferedChunkCount = bufferedChunks.length;
|
|
36436
37113
|
const bufferedChunkTokens = bufferedChunks.reduce((sum, chunk) => sum + (chunk.messageTokens ?? 0), 0);
|
|
36437
37114
|
const asyncObservationEnabled = this.buffering.isAsyncObservationEnabled();
|
|
37115
|
+
const observationBlockAfter = asyncObservationEnabled ? this.getEffectiveObservationBlockAfter(record) : void 0;
|
|
37116
|
+
const inAsyncObservationBand = observationBlockAfter !== void 0 && pendingTokens >= threshold && pendingTokens < observationBlockAfter;
|
|
36438
37117
|
let shouldBuffer = false;
|
|
36439
|
-
if (asyncObservationEnabled && pendingTokens < threshold) {
|
|
37118
|
+
if (asyncObservationEnabled && (pendingTokens < threshold || inAsyncObservationBand)) {
|
|
36440
37119
|
const lockKey = this.buffering.getLockKey(threadId, resourceId);
|
|
36441
37120
|
shouldBuffer = this.buffering.shouldTriggerAsyncObservation(pendingTokens, lockKey, record, this.storage, threshold);
|
|
36442
37121
|
}
|
|
@@ -36461,6 +37140,8 @@ ${formattedMessages}
|
|
|
36461
37140
|
canActivate,
|
|
36462
37141
|
asyncObservationEnabled,
|
|
36463
37142
|
asyncReflectionEnabled: this.buffering.isAsyncReflectionEnabled(),
|
|
37143
|
+
observationBlockAfter,
|
|
37144
|
+
inAsyncObservationBand,
|
|
36464
37145
|
scope: this.scope
|
|
36465
37146
|
};
|
|
36466
37147
|
}
|
|
@@ -36587,24 +37268,27 @@ ${formattedMessages}
|
|
|
36587
37268
|
const currentTokens = opts.pendingTokens ?? record.pendingMessageTokens ?? 0;
|
|
36588
37269
|
const lockKey = this.buffering.getLockKey(threadId, resourceId);
|
|
36589
37270
|
const bufferKey = this.buffering.getObservationBufferKey(lockKey);
|
|
36590
|
-
|
|
36591
|
-
|
|
37271
|
+
const boundaryOwner = Symbol("observation-boundary");
|
|
37272
|
+
BufferingCoordinator.setObservationBoundary(bufferKey, currentTokens, boundaryOwner);
|
|
36592
37273
|
const existingOp = BufferingCoordinator.asyncBufferingOps.get(bufferKey);
|
|
37274
|
+
let resolveOp;
|
|
37275
|
+
const opPromise = new Promise((resolve) => {
|
|
37276
|
+
resolveOp = resolve;
|
|
37277
|
+
});
|
|
37278
|
+
BufferingCoordinator.asyncBufferingOps.set(bufferKey, opPromise);
|
|
37279
|
+
if (record.isBufferingObservation) await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch(() => {});
|
|
36593
37280
|
if (existingOp) try {
|
|
36594
37281
|
await existingOp;
|
|
36595
37282
|
} catch {}
|
|
36596
|
-
|
|
37283
|
+
const opRecordId = record.id;
|
|
37284
|
+
registerOp(opRecordId, "bufferingObservation");
|
|
37285
|
+
const releaseChunkWrite = BufferingCoordinator.trackChunkWrite(bufferKey);
|
|
37286
|
+
const ownsStartBoundary = BufferingCoordinator.ownsObservationBoundary(bufferKey, boundaryOwner);
|
|
36597
37287
|
inMemoryRecord.isBufferingObservation = true;
|
|
36598
|
-
inMemoryRecord.lastBufferedAtTokens = currentTokens;
|
|
36599
|
-
this.storage.setBufferingObservationFlag(record.id, true, currentTokens).catch((err) => {
|
|
37288
|
+
if (ownsStartBoundary) inMemoryRecord.lastBufferedAtTokens = currentTokens;
|
|
37289
|
+
runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, true, ownsStartBoundary ? currentTokens : void 0)).catch((err) => {
|
|
36600
37290
|
omError("[OM] Failed to set buffering observation flag", err);
|
|
36601
37291
|
});
|
|
36602
|
-
let resolveOp;
|
|
36603
|
-
const opPromise = new Promise((resolve) => {
|
|
36604
|
-
resolveOp = resolve;
|
|
36605
|
-
});
|
|
36606
|
-
BufferingCoordinator.asyncBufferingOps.set(bufferKey, opPromise);
|
|
36607
|
-
record = await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record;
|
|
36608
37292
|
const setBufferingState = (isBufferingObservation, lastBufferedAtTokens) => {
|
|
36609
37293
|
inMemoryRecord.isBufferingObservation = isBufferingObservation;
|
|
36610
37294
|
record.isBufferingObservation = isBufferingObservation;
|
|
@@ -36615,6 +37299,7 @@ ${formattedMessages}
|
|
|
36615
37299
|
};
|
|
36616
37300
|
let flagCleared = false;
|
|
36617
37301
|
try {
|
|
37302
|
+
record = await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record;
|
|
36618
37303
|
let candidateMessages;
|
|
36619
37304
|
if (opts.messages) candidateMessages = this.getUnobservedMessages(opts.messages, record, { excludeBuffered: true });
|
|
36620
37305
|
else {
|
|
@@ -36654,12 +37339,29 @@ ${formattedMessages}
|
|
|
36654
37339
|
threadIds: [threadId],
|
|
36655
37340
|
config: this.getObservationMarkerConfig()
|
|
36656
37341
|
});
|
|
36657
|
-
await this.persistMarkerToStorage(startMarker, threadId, record.resourceId ?? void 0
|
|
37342
|
+
await this.persistMarkerToStorage(startMarker, threadId, record.resourceId ?? void 0, {
|
|
37343
|
+
notAfter: new Date(startedAt),
|
|
37344
|
+
preferMessages: candidateMessages
|
|
37345
|
+
});
|
|
36658
37346
|
const writer = opts.writer;
|
|
36659
37347
|
if (writer) writer.custom({
|
|
36660
37348
|
...startMarker,
|
|
36661
37349
|
transient: true
|
|
36662
37350
|
}).catch(() => {});
|
|
37351
|
+
let chunkRecorded = false;
|
|
37352
|
+
const recordBufferedChunk = async () => {
|
|
37353
|
+
if (chunkRecorded) return;
|
|
37354
|
+
chunkRecorded = true;
|
|
37355
|
+
const ownsBoundary = BufferingCoordinator.ownsObservationBoundary(bufferKey, boundaryOwner);
|
|
37356
|
+
if (ownsBoundary) BufferingCoordinator.setObservationBoundary(bufferKey, newTokens, boundaryOwner);
|
|
37357
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false, ownsBoundary ? newTokens : void 0)).catch(() => {});
|
|
37358
|
+
flagCleared = true;
|
|
37359
|
+
const stillOwnsBoundary = ownsBoundary && BufferingCoordinator.ownsObservationBoundary(bufferKey, boundaryOwner);
|
|
37360
|
+
setBufferingState(false, stillOwnsBoundary ? newTokens : void 0);
|
|
37361
|
+
const maxTimestamp = this.getMaxMessageTimestamp(candidateMessages);
|
|
37362
|
+
BufferingCoordinator.lastBufferedAtTime.set(bufferKey, new Date(maxTimestamp.getTime() + 1));
|
|
37363
|
+
releaseChunkWrite();
|
|
37364
|
+
};
|
|
36663
37365
|
if (!(await this.runBufferedObservationCycle({
|
|
36664
37366
|
threadId,
|
|
36665
37367
|
resourceId: record.resourceId ?? resourceId,
|
|
@@ -36678,7 +37380,8 @@ ${formattedMessages}
|
|
|
36678
37380
|
requestContext,
|
|
36679
37381
|
currentModel: opts.currentModel,
|
|
36680
37382
|
observabilityContext,
|
|
36681
|
-
trigger: "async-buffer"
|
|
37383
|
+
trigger: "async-buffer",
|
|
37384
|
+
onBufferedChunkPersisted: recordBufferedChunk
|
|
36682
37385
|
}).run()))?.observed) return {
|
|
36683
37386
|
buffered: false,
|
|
36684
37387
|
record
|
|
@@ -36698,13 +37401,7 @@ ${formattedMessages}
|
|
|
36698
37401
|
newTokens
|
|
36699
37402
|
}
|
|
36700
37403
|
});
|
|
36701
|
-
await
|
|
36702
|
-
flagCleared = true;
|
|
36703
|
-
setBufferingState(false, newTokens);
|
|
36704
|
-
BufferingCoordinator.lastBufferedBoundary.set(bufferKey, newTokens);
|
|
36705
|
-
const maxTimestamp = this.getMaxMessageTimestamp(candidateMessages);
|
|
36706
|
-
const cursor = new Date(maxTimestamp.getTime() + 1);
|
|
36707
|
-
BufferingCoordinator.lastBufferedAtTime.set(bufferKey, cursor);
|
|
37404
|
+
await recordBufferedChunk();
|
|
36708
37405
|
return {
|
|
36709
37406
|
buffered: true,
|
|
36710
37407
|
record: await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record
|
|
@@ -36716,12 +37413,13 @@ ${formattedMessages}
|
|
|
36716
37413
|
record
|
|
36717
37414
|
};
|
|
36718
37415
|
} finally {
|
|
36719
|
-
|
|
36720
|
-
|
|
37416
|
+
releaseChunkWrite();
|
|
37417
|
+
unregisterOp(opRecordId, "bufferingObservation");
|
|
37418
|
+
if (BufferingCoordinator.asyncBufferingOps.get(bufferKey) === opPromise) BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
|
|
36721
37419
|
resolveOp();
|
|
36722
37420
|
if (!flagCleared) {
|
|
36723
37421
|
setBufferingState(false);
|
|
36724
|
-
await this.storage.setBufferingObservationFlag(record.id, false).catch(() => {});
|
|
37422
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch(() => {});
|
|
36725
37423
|
}
|
|
36726
37424
|
}
|
|
36727
37425
|
}
|
|
@@ -36758,8 +37456,8 @@ ${formattedMessages}
|
|
|
36758
37456
|
const currentContextTokens = this.tokenCounter.countMessages(unobserved);
|
|
36759
37457
|
if (currentContextTokens < dbBoundary * .5) {
|
|
36760
37458
|
omDebug(`[OM:activate] resetting stale lastBufferedBoundary: dbBoundary=${dbBoundary}, currentContextTokens=${currentContextTokens}`);
|
|
36761
|
-
BufferingCoordinator.
|
|
36762
|
-
await this.storage.setBufferingObservationFlag(record.id, false, 0).catch(() => {});
|
|
37459
|
+
BufferingCoordinator.setObservationBoundary(bufKey, 0);
|
|
37460
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false, 0)).catch(() => {});
|
|
36763
37461
|
}
|
|
36764
37462
|
}
|
|
36765
37463
|
}
|
|
@@ -36805,39 +37503,63 @@ ${formattedMessages}
|
|
|
36805
37503
|
livePendingTokens ??= status.pendingTokens;
|
|
36806
37504
|
}
|
|
36807
37505
|
}
|
|
36808
|
-
|
|
36809
|
-
|
|
36810
|
-
|
|
36811
|
-
const
|
|
36812
|
-
|
|
36813
|
-
|
|
36814
|
-
|
|
37506
|
+
const obsBufferKey = this.buffering.getObservationBufferKey(this.buffering.getLockKey(threadId, resourceId));
|
|
37507
|
+
const pendingChunkWrite = BufferingCoordinator.pendingChunkWrites.get(obsBufferKey);
|
|
37508
|
+
if (pendingChunkWrite) {
|
|
37509
|
+
const blockAfter = this.buffering.isAsyncObservationEnabled() ? this.getEffectiveObservationBlockAfter(record) : void 0;
|
|
37510
|
+
const threshold = calculateDynamicThreshold(this.getEffectiveMessageTokens(record), record.observationTokenCount ?? 0);
|
|
37511
|
+
if (activationTriggeredBy === "threshold" && !(livePendingTokens !== void 0 && blockAfter !== void 0 && livePendingTokens >= threshold && livePendingTokens < blockAfter)) {
|
|
37512
|
+
let timeoutId;
|
|
37513
|
+
await Promise.race([pendingChunkWrite, new Promise((resolve) => timeoutId = setTimeout(resolve, 6e4))]);
|
|
37514
|
+
clearTimeout(timeoutId);
|
|
37515
|
+
}
|
|
37516
|
+
}
|
|
37517
|
+
const commitKey = this.buffering.getLockKey(threadId, resourceId);
|
|
37518
|
+
let freshRecord = null;
|
|
37519
|
+
let freshChunks = [];
|
|
37520
|
+
let activationResult;
|
|
37521
|
+
for (let attempt = 0; attempt <= MAX_ACTIVATION_RETIRED_RETRIES; attempt++) {
|
|
37522
|
+
const swapped = await runOMCommit(commitKey, async () => {
|
|
37523
|
+
const head = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
37524
|
+
if (!head) return null;
|
|
37525
|
+
const chunks = getBufferedChunks(head);
|
|
37526
|
+
if (!chunks.length) return null;
|
|
37527
|
+
const messageTokensThreshold = getMaxThreshold(this.getEffectiveMessageTokens(head));
|
|
37528
|
+
const activationRatio = resolveActivationRatio(this.observationConfig.bufferActivation ?? .7, messageTokensThreshold);
|
|
37529
|
+
const totalChunkMessageTokens = chunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
37530
|
+
const currentPendingTokens = livePendingTokens ?? (head.pendingMessageTokens || totalChunkMessageTokens);
|
|
37531
|
+
const blockAfter = this.getEffectiveObservationBlockAfter(head);
|
|
37532
|
+
const forceMaxActivation = !!(blockAfter && currentPendingTokens >= blockAfter);
|
|
37533
|
+
if (head.pendingMessageTokens !== currentPendingTokens) await this.storage.setPendingMessageTokens(head.id, currentPendingTokens);
|
|
37534
|
+
return {
|
|
37535
|
+
head,
|
|
37536
|
+
chunks,
|
|
37537
|
+
result: await this.storage.swapBufferedToActive({
|
|
37538
|
+
id: head.id,
|
|
37539
|
+
activationRatio,
|
|
37540
|
+
messageTokensThreshold,
|
|
37541
|
+
currentPendingTokens,
|
|
37542
|
+
forceMaxActivation,
|
|
37543
|
+
bufferedChunks: chunks
|
|
37544
|
+
})
|
|
37545
|
+
};
|
|
37546
|
+
});
|
|
37547
|
+
if (!swapped) return {
|
|
37548
|
+
activated: false,
|
|
37549
|
+
record
|
|
37550
|
+
};
|
|
37551
|
+
freshRecord = swapped.head;
|
|
37552
|
+
freshChunks = swapped.chunks;
|
|
37553
|
+
activationResult = swapped.result;
|
|
37554
|
+
if (!activationResult.retired) break;
|
|
37555
|
+
omDebug(`[OM:activate] record ${freshRecord.id} was retired by a reflection; retrying on the head`);
|
|
36815
37556
|
}
|
|
36816
|
-
|
|
36817
|
-
if (!freshRecord) return {
|
|
37557
|
+
if (!freshRecord || !activationResult || activationResult.retired) return {
|
|
36818
37558
|
activated: false,
|
|
36819
|
-
record
|
|
37559
|
+
record: await this.getRecord(threadId, resourceId) ?? record
|
|
36820
37560
|
};
|
|
36821
|
-
const
|
|
36822
|
-
|
|
36823
|
-
activated: false,
|
|
36824
|
-
record
|
|
36825
|
-
};
|
|
36826
|
-
const messageTokensThreshold = getMaxThreshold(this.getEffectiveMessageTokens(freshRecord));
|
|
36827
|
-
const activationRatio = resolveActivationRatio(this.observationConfig.bufferActivation ?? .7, messageTokensThreshold);
|
|
36828
|
-
const totalChunkMessageTokens = freshChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
36829
|
-
const currentPendingTokens = livePendingTokens ?? (freshRecord.pendingMessageTokens || totalChunkMessageTokens);
|
|
36830
|
-
const forceMaxActivation = !!(this.observationConfig.blockAfter && currentPendingTokens >= this.observationConfig.blockAfter);
|
|
36831
|
-
if (freshRecord.pendingMessageTokens !== currentPendingTokens) await this.storage.setPendingMessageTokens(freshRecord.id, currentPendingTokens);
|
|
36832
|
-
const activationResult = await this.storage.swapBufferedToActive({
|
|
36833
|
-
id: freshRecord.id,
|
|
36834
|
-
activationRatio,
|
|
36835
|
-
messageTokensThreshold,
|
|
36836
|
-
currentPendingTokens,
|
|
36837
|
-
forceMaxActivation,
|
|
36838
|
-
bufferedChunks: freshChunks
|
|
36839
|
-
});
|
|
36840
|
-
await this.storage.setBufferingObservationFlag(freshRecord.id, false).catch(() => {});
|
|
37561
|
+
const swappedRecordId = freshRecord.id;
|
|
37562
|
+
await runOMCommit(commitKey, () => this.storage.setBufferingObservationFlag(swappedRecordId, false)).catch(() => {});
|
|
36841
37563
|
unregisterOp(freshRecord.id, "bufferingObservation");
|
|
36842
37564
|
const postSwapRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
36843
37565
|
if (opts.writer && postSwapRecord && activationResult.activatedCycleIds.length > 0) {
|
|
@@ -37082,7 +37804,7 @@ ${formattedMessages}
|
|
|
37082
37804
|
* ```
|
|
37083
37805
|
*/
|
|
37084
37806
|
async reflect(threadId, resourceId, prompt, requestContext, observabilityContext) {
|
|
37085
|
-
const record = await this.getOrCreateRecord(threadId, resourceId);
|
|
37807
|
+
const record = { ...await this.getOrCreateRecord(threadId, resourceId) };
|
|
37086
37808
|
if (!record.activeObservations) return {
|
|
37087
37809
|
reflected: false,
|
|
37088
37810
|
record,
|
|
@@ -37127,11 +37849,23 @@ ${formattedMessages}
|
|
|
37127
37849
|
const reflectResult = await this.reflector.call(await applyTextTransform(this.hooks, "beforeReflection", record.activeObservations, hookContext), prompt, void 0, reflectThreshold, void 0, void 0, void 0, requestContext, priorExtractedValues, observabilityContext, void 0);
|
|
37128
37850
|
reflectResult.observations = await applyTextTransform(this.hooks, "afterReflection", reflectResult.observations, hookContext);
|
|
37129
37851
|
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
37130
|
-
|
|
37852
|
+
const newRecordId = crypto.randomUUID();
|
|
37853
|
+
const committedRecord = await runOMCommit(this.buffering.getLockKey(threadId, resourceId), () => this.storage.createReflectionGeneration({
|
|
37131
37854
|
currentRecord: record,
|
|
37132
37855
|
reflection: reflectResult.observations,
|
|
37133
|
-
tokenCount: reflectionTokenCount
|
|
37134
|
-
|
|
37856
|
+
tokenCount: reflectionTokenCount,
|
|
37857
|
+
newRecordId
|
|
37858
|
+
}), { priority: "reflection" });
|
|
37859
|
+
reflectionUsage = reflectResult.usage;
|
|
37860
|
+
reflectionProviderMetadata = reflectResult.providerMetadata;
|
|
37861
|
+
if (!isReflectionApplied(committedRecord, newRecordId, record.id)) {
|
|
37862
|
+
omDebug(`[OM:reflect] manual reflection of ${record.id} not applied (head is ${committedRecord.id})`);
|
|
37863
|
+
return {
|
|
37864
|
+
reflected: false,
|
|
37865
|
+
record: await this.getOrCreateRecord(threadId, resourceId),
|
|
37866
|
+
usage: reflectResult.usage
|
|
37867
|
+
};
|
|
37868
|
+
}
|
|
37135
37869
|
if (thread && reflectResult.extractedValues) {
|
|
37136
37870
|
const metadataUpdate = buildThreadMetadataFromExtractedValues(this.reflectionConfig.extractors, reflectResult.extractedValues);
|
|
37137
37871
|
const newMetadata = (0, _mastra_core_memory.setThreadOMMetadata)(thread.metadata, {
|
|
@@ -37220,11 +37954,13 @@ ${formattedMessages}
|
|
|
37220
37954
|
*/
|
|
37221
37955
|
async updateRecordConfig(threadId, resourceId, config) {
|
|
37222
37956
|
const ids = this.getStorageIds(threadId, resourceId);
|
|
37223
|
-
|
|
37224
|
-
|
|
37225
|
-
|
|
37226
|
-
|
|
37227
|
-
|
|
37957
|
+
await runOMCommit(this.buffering.getLockKey(threadId, resourceId), async () => {
|
|
37958
|
+
const record = await this.storage.getObservationalMemory(ids.threadId, ids.resourceId);
|
|
37959
|
+
if (!record) throw new Error(`No observational memory record found for thread ${ids.threadId}`);
|
|
37960
|
+
await this.storage.updateObservationalMemoryConfig({
|
|
37961
|
+
id: record.id,
|
|
37962
|
+
config: { _overrides: config }
|
|
37963
|
+
});
|
|
37228
37964
|
});
|
|
37229
37965
|
}
|
|
37230
37966
|
/**
|
|
@@ -37614,7 +38350,7 @@ var ObservationalMemoryProcessor = class {
|
|
|
37614
38350
|
});
|
|
37615
38351
|
const finalTotalPending = ctx.status.pendingTokens;
|
|
37616
38352
|
try {
|
|
37617
|
-
await this.engine.getStorage().setPendingMessageTokens(turnRecord.id, finalTotalPending);
|
|
38353
|
+
await runOMCommit(this.engine.buffering.getLockKey(threadId, resourceId), () => this.engine.getStorage().setPendingMessageTokens(turnRecord.id, finalTotalPending));
|
|
37618
38354
|
this.turn.patchRecord({ pendingMessageTokens: finalTotalPending });
|
|
37619
38355
|
} catch {}
|
|
37620
38356
|
if (reproCaptureEnabled) writeProcessInputStepReproCapture({
|
|
@@ -39928,6 +40664,7 @@ Notes:
|
|
|
39928
40664
|
cloned.id = crypto.randomUUID();
|
|
39929
40665
|
cloned.createdAt = now;
|
|
39930
40666
|
cloned.updatedAt = now;
|
|
40667
|
+
cloned.supersededBy = null;
|
|
39931
40668
|
await memoryStore.insertObservationalMemoryRecord(cloned);
|
|
39932
40669
|
}
|
|
39933
40670
|
/**
|
|
@@ -51002,4 +51739,4 @@ const agentBuilderWorkflows = {
|
|
|
51002
51739
|
//#endregion
|
|
51003
51740
|
exports.agentBuilderWorkflows = agentBuilderWorkflows;
|
|
51004
51741
|
|
|
51005
|
-
//# sourceMappingURL=dist-
|
|
51742
|
+
//# sourceMappingURL=dist-BOW7V2e6.cjs.map
|