@mastra/server 1.76.0 → 1.77.0-alpha.1
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/{datasets-C2aXB8At.cjs → datasets-BdD8JWhx.cjs} +64 -7
- package/dist/datasets-BdD8JWhx.cjs.map +1 -0
- package/dist/{datasets-CDQDQxos.js → datasets-CKwh-k4I.js} +53 -8
- package/dist/datasets-CKwh-k4I.js.map +1 -0
- package/dist/{dist-C68HDUAO.js → dist-B6B4Cxda.js} +1060 -304
- package/dist/dist-B6B4Cxda.js.map +1 -0
- package/dist/{dist-C8tgsOw_.cjs → dist-BGfXR4Xj.cjs} +1059 -303
- package/dist/dist-BGfXR4Xj.cjs.map +1 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-server-custom-adapters.md +1 -1
- package/dist/docs/references/reference-server-create-route.md +1 -1
- package/dist/docs/references/reference-server-routes.md +1 -1
- package/dist/server/handlers/agent-builder.cjs +1 -1
- package/dist/server/handlers/agent-builder.js +1 -1
- package/dist/server/handlers/datasets.cjs +27 -15
- package/dist/server/handlers/datasets.cjs.map +1 -1
- package/dist/server/handlers/datasets.d.ts +1032 -38
- package/dist/server/handlers/datasets.d.ts.map +1 -1
- package/dist/server/handlers/datasets.js +27 -15
- package/dist/server/handlers/datasets.js.map +1 -1
- package/dist/server/schemas/datasets.d.ts +430 -11
- package/dist/server/schemas/datasets.d.ts.map +1 -1
- package/dist/server/schemas/index.cjs +3 -1
- package/dist/server/schemas/index.js +2 -2
- package/dist/server/server-adapter/routes/datasets.d.ts +1032 -38
- package/dist/server/server-adapter/routes/datasets.d.ts.map +1 -1
- package/package.json +6 -6
- package/dist/datasets-C2aXB8At.cjs.map +0 -1
- package/dist/datasets-CDQDQxos.js.map +0 -1
- 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-DjDnBIS0.js
|
|
4641
4641
|
var __defProp$3 = Object.defineProperty;
|
|
4642
4642
|
var __exportAll = (all, no_symbols) => {
|
|
4643
4643
|
let target = {};
|
|
@@ -23752,8 +23752,12 @@ function formatOmError(error) {
|
|
|
23752
23752
|
function isAbortError$5(error, abortSignal) {
|
|
23753
23753
|
return abortSignal?.aborted === true || hasAbortInChain(error);
|
|
23754
23754
|
}
|
|
23755
|
+
function hasEmptyObjectValue(object, extractor) {
|
|
23756
|
+
const value = object[extractor.slug];
|
|
23757
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
23758
|
+
}
|
|
23755
23759
|
function shouldRetryEmptyStructuredObject(object, extractors) {
|
|
23756
|
-
return Object.keys(object).length === 0
|
|
23760
|
+
return extractors.some((extractor) => extractor.retryStructuredExtractionOnEmptyObject && (Object.keys(object).length === 0 || hasEmptyObjectValue(object, extractor)));
|
|
23757
23761
|
}
|
|
23758
23762
|
async function extractStructuredValues(opts) {
|
|
23759
23763
|
const structuredExtractors = (opts.extractors ?? []).filter((extractor) => extractor.mode === "structured");
|
|
@@ -23827,17 +23831,28 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
|
|
|
23827
23831
|
}
|
|
23828
23832
|
}
|
|
23829
23833
|
if (retryEmptyObject) try {
|
|
23830
|
-
|
|
23834
|
+
const fallbackJsonPromptInjection = _mastra_core_features.coreFeatures.has("json-prompt-injection:inline") ? "inline" : true;
|
|
23835
|
+
object = {
|
|
23836
|
+
...object,
|
|
23837
|
+
...await streamWithStructuredOutput(fallbackJsonPromptInjection)
|
|
23838
|
+
};
|
|
23831
23839
|
} catch (fallbackError) {
|
|
23832
23840
|
if (isAbortError$5(fallbackError, opts.abortSignal)) throw fallbackError;
|
|
23833
23841
|
const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
23834
|
-
return {
|
|
23842
|
+
if (Object.keys(object).length === 0) return {
|
|
23835
23843
|
values,
|
|
23836
23844
|
failures: structuredExtractors.map((extractor) => ({
|
|
23837
23845
|
slug: extractor.slug,
|
|
23838
23846
|
error: message
|
|
23839
23847
|
}))
|
|
23840
23848
|
};
|
|
23849
|
+
for (const extractor of structuredExtractors) if (extractor.retryStructuredExtractionOnEmptyObject && hasEmptyObjectValue(object, extractor)) {
|
|
23850
|
+
failures.push({
|
|
23851
|
+
slug: extractor.slug,
|
|
23852
|
+
error: message
|
|
23853
|
+
});
|
|
23854
|
+
delete object[extractor.slug];
|
|
23855
|
+
}
|
|
23841
23856
|
}
|
|
23842
23857
|
for (const extractor of structuredExtractors) {
|
|
23843
23858
|
const value = object[extractor.slug];
|
|
@@ -25063,7 +25078,7 @@ Group related observations (like tool sequences) by indenting:
|
|
|
25063
25078
|
* -> applied fix, tests now pass
|
|
25064
25079
|
* ✅ Tests passing, auth issue resolved
|
|
25065
25080
|
|
|
25066
|
-
Group observations by date,
|
|
25081
|
+
Group observations by date. Within a date, list observations in the order the events happened (not by importance), each with 24-hour time.
|
|
25067
25082
|
|
|
25068
25083
|
<observations>
|
|
25069
25084
|
Date: Dec 4, 2025
|
|
@@ -25436,8 +25451,8 @@ function extractToolResultAttachments(result, counter, attachmentFilter) {
|
|
|
25436
25451
|
attachments
|
|
25437
25452
|
};
|
|
25438
25453
|
}
|
|
25439
|
-
function formatObserverPartLine(title, body, time, previousTime) {
|
|
25440
|
-
const timeLabel = time && time !== previousTime ? `(${time})` : "";
|
|
25454
|
+
function formatObserverPartLine(title, body, time, previousTime, alwaysShowTime) {
|
|
25455
|
+
const timeLabel = time && (alwaysShowTime || time !== previousTime) ? `(${time})` : "";
|
|
25441
25456
|
if (!title) return timeLabel ? `${timeLabel}: ${body}` : body;
|
|
25442
25457
|
return `${title}${timeLabel ? ` ${timeLabel}` : ""}: ${body}`;
|
|
25443
25458
|
}
|
|
@@ -25462,7 +25477,7 @@ function formatObserverLines(lines, context = {}) {
|
|
|
25462
25477
|
previousDate = line.date;
|
|
25463
25478
|
previousTime = void 0;
|
|
25464
25479
|
}
|
|
25465
|
-
output.push(formatObserverPartLine(line.title, line.body, line.time, previousTime));
|
|
25480
|
+
output.push(formatObserverPartLine(line.title, line.body, line.time, previousTime, line.alwaysShowTime));
|
|
25466
25481
|
previousTime = line.time || previousTime;
|
|
25467
25482
|
}
|
|
25468
25483
|
return {
|
|
@@ -25563,14 +25578,15 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
25563
25578
|
const unavailableUrls = getUnavailableAttachmentUrls(msg);
|
|
25564
25579
|
let lines = [];
|
|
25565
25580
|
const temporalGapText = isTemporalGapMarker(msg) ? getTemporalGapMarkerText(msg) : void 0;
|
|
25566
|
-
const pushLine = (title, body, createdAt) => {
|
|
25581
|
+
const pushLine = (title, body, createdAt, opts) => {
|
|
25567
25582
|
if (!body) return;
|
|
25568
25583
|
const normalizedCreatedAt = normalizeObserverCreatedAt(createdAt) ?? messageCreatedAt;
|
|
25569
25584
|
lines.push({
|
|
25570
25585
|
date: formatObserverDate(normalizedCreatedAt, timeZone),
|
|
25571
25586
|
time: formatObserverTime(normalizedCreatedAt, timeZone),
|
|
25572
25587
|
title,
|
|
25573
|
-
body
|
|
25588
|
+
body,
|
|
25589
|
+
alwaysShowTime: opts?.tool === true
|
|
25574
25590
|
});
|
|
25575
25591
|
};
|
|
25576
25592
|
if (temporalGapText) pushLine("", temporalGapText, messageCreatedAt);
|
|
@@ -25587,7 +25603,7 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
25587
25603
|
const exchange = toolFormatting.exchanges.get(inv.toolCallId);
|
|
25588
25604
|
if (exchange && occurrence === exchange.firstOccurrence && exchange.signature && !toolFormatting.emittedCalls.has(inv.toolCallId)) {
|
|
25589
25605
|
toolFormatting.emittedCalls.add(inv.toolCallId);
|
|
25590
|
-
pushLine(`Tool Call ${exchange.signature.toolName}`, formatObserverToolArguments(exchange.signature.args, maxLen), partCreatedAt);
|
|
25606
|
+
pushLine(`Tool Call ${exchange.signature.toolName}`, formatObserverToolArguments(exchange.signature.args, maxLen), partCreatedAt, { tool: true });
|
|
25591
25607
|
}
|
|
25592
25608
|
if (inv.state === "result") {
|
|
25593
25609
|
const { value: resultForObserver } = resolveToolResultValue(part, inv.result);
|
|
@@ -25597,17 +25613,17 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
25597
25613
|
toolFormatting.emittedOutcomes.add(inv.toolCallId);
|
|
25598
25614
|
const hasResult = resultWithoutAttachments !== void 0 && (typeof resultWithoutAttachments !== "string" || resultWithoutAttachments.trim().length > 0);
|
|
25599
25615
|
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);
|
|
25616
|
+
pushLine(`${inv.isError ? "Tool Error" : "Tool Result"} ${inv.toolName}`, maybeTruncate(body, maxLen), partCreatedAt, { tool: true });
|
|
25601
25617
|
}
|
|
25602
25618
|
return;
|
|
25603
25619
|
}
|
|
25604
25620
|
if (exchange?.terminal?.occurrence === occurrence && !toolFormatting.emittedOutcomes.has(inv.toolCallId)) {
|
|
25605
25621
|
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);
|
|
25622
|
+
if (inv.state === "output-error") pushLine(`Tool Error ${inv.toolName}`, maybeTruncate(inv.errorText?.trim() ? inv.errorText : "Tool execution failed", maxLen), partCreatedAt, { tool: true });
|
|
25623
|
+
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
25624
|
return;
|
|
25609
25625
|
}
|
|
25610
|
-
if (!exchange) pushLine(`Tool Call ${inv.toolName}`, formatObserverToolArguments(inv.args, maxLen), partCreatedAt);
|
|
25626
|
+
if (!exchange) pushLine(`Tool Call ${inv.toolName}`, formatObserverToolArguments(inv.args, maxLen), partCreatedAt, { tool: true });
|
|
25611
25627
|
return;
|
|
25612
25628
|
}
|
|
25613
25629
|
const partType = part.type;
|
|
@@ -25762,13 +25778,17 @@ function surroundObserverHistory(history, contextPrompt, taskInstructions) {
|
|
|
25762
25778
|
function buildMultiThreadObserverTaskInstructions(includeThreadTitle) {
|
|
25763
25779
|
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
25780
|
}
|
|
25781
|
+
function buildTruncationNotice(wasTruncated) {
|
|
25782
|
+
if (!wasTruncated) return "";
|
|
25783
|
+
return "Previous observations were truncated for context budget reasons.\nThe main agent still has full memory context outside this observer window.\n\n";
|
|
25784
|
+
}
|
|
25765
25785
|
function buildMultiThreadObserverContextPrompt(existingObservations, threadOrder, priorMetadataByThread, wasTruncated, includeThreadTitle, extractors = []) {
|
|
25766
25786
|
let prompt = "";
|
|
25767
25787
|
if (existingObservations) {
|
|
25768
25788
|
prompt += `## Previous Observations\n\n${existingObservations}\n\n---\n\n`;
|
|
25769
25789
|
prompt += "Do not repeat these existing observations. Your new observations will be appended to the existing observations.\n\n";
|
|
25770
25790
|
}
|
|
25771
|
-
|
|
25791
|
+
prompt += buildTruncationNotice(wasTruncated);
|
|
25772
25792
|
const threadMetadataLines = threadOrder?.map((threadId) => {
|
|
25773
25793
|
const metadata = priorMetadataByThread?.get(threadId);
|
|
25774
25794
|
const extractorPriorLines = buildExtractorPriorLines(extractors, metadata?.extracted);
|
|
@@ -25782,10 +25802,6 @@ function buildMultiThreadObserverContextPrompt(existingObservations, threadOrder
|
|
|
25782
25802
|
}).filter(Boolean).join("\n");
|
|
25783
25803
|
if (threadMetadataLines) {
|
|
25784
25804
|
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
25805
|
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
25806
|
}
|
|
25791
25807
|
return prompt;
|
|
@@ -25828,7 +25844,7 @@ function parseMultiThreadObserverOutput(output, extractors = []) {
|
|
|
25828
25844
|
threadTitle = threadTitleMatch[1].trim();
|
|
25829
25845
|
observations = observations.replace(/<thread-title>[\s\S]*?<\/thread-title>/i, "");
|
|
25830
25846
|
}
|
|
25831
|
-
observations = sanitizeObservationLines(observations.trim());
|
|
25847
|
+
observations = sortObservationsByTime(sanitizeObservationLines(observations.trim()));
|
|
25832
25848
|
threads.set(threadId, {
|
|
25833
25849
|
observations,
|
|
25834
25850
|
currentTask: currentTask || getStringExtractedValue$1(parsedExtractedValues.values, "current-task"),
|
|
@@ -25868,7 +25884,7 @@ function buildObserverContextPrompt(existingObservations, options) {
|
|
|
25868
25884
|
prompt += `## Previous Observations\n\n${existingObservations}\n\n---\n\n`;
|
|
25869
25885
|
prompt += "Do not repeat these existing observations. Your new observations will be appended to the existing observations.\n\n";
|
|
25870
25886
|
}
|
|
25871
|
-
|
|
25887
|
+
prompt += buildTruncationNotice(options?.wasTruncated);
|
|
25872
25888
|
const priorMetadataLines = [];
|
|
25873
25889
|
if (options?.priorCurrentTask) priorMetadataLines.push(`- prior current-task: ${options.priorCurrentTask}`);
|
|
25874
25890
|
if (options?.priorSuggestedResponse) priorMetadataLines.push(`- prior suggested-response: ${options.priorSuggestedResponse}`);
|
|
@@ -25876,10 +25892,6 @@ function buildObserverContextPrompt(existingObservations, options) {
|
|
|
25876
25892
|
priorMetadataLines.push(...buildExtractorPriorLines(options?.extractors ?? [], options?.priorExtractedValues));
|
|
25877
25893
|
if (priorMetadataLines.length > 0) {
|
|
25878
25894
|
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
25895
|
const titleHint = options?.includeThreadTitle ? ", and thread-title" : "";
|
|
25884
25896
|
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
25897
|
}
|
|
@@ -25910,7 +25922,7 @@ function parseObserverOutput(output, extractors = []) {
|
|
|
25910
25922
|
const parsedExtractedValues = parseExtractedValues(output, inlineExtractors);
|
|
25911
25923
|
const parsed = parseMemorySectionXml(stripExtractorSections(output, inlineExtractors));
|
|
25912
25924
|
return {
|
|
25913
|
-
observations: sanitizeObservationLines(parsed.observations || ""),
|
|
25925
|
+
observations: sortObservationsByTime(sanitizeObservationLines(parsed.observations || "")),
|
|
25914
25926
|
currentTask: parsed.currentTask || getStringExtractedValue$1(parsedExtractedValues.values, "current-task"),
|
|
25915
25927
|
suggestedContinuation: parsed.suggestedResponse || getStringExtractedValue$1(parsedExtractedValues.values, "suggested-response"),
|
|
25916
25928
|
threadTitle: parsed.threadTitle || getStringExtractedValue$1(parsedExtractedValues.values, "thread-title"),
|
|
@@ -26004,6 +26016,68 @@ function sanitizeObservationLines(observations) {
|
|
|
26004
26016
|
}
|
|
26005
26017
|
return changed ? lines.join("\n") : observations;
|
|
26006
26018
|
}
|
|
26019
|
+
const OBSERVATION_TIME = /^[*-]\s+(?:\S+\s+)?\((\d{1,2}):(\d{2})\s*([AaPp])?\.?(?:[Mm]\.?)?\)/;
|
|
26020
|
+
/** Minutes since midnight for each item, or undefined when the group's times can't be ordered safely. */
|
|
26021
|
+
function observationMinutes(items) {
|
|
26022
|
+
const times = items.map((item) => OBSERVATION_TIME.exec(item[0]));
|
|
26023
|
+
if (times.some((t) => !t)) return void 0;
|
|
26024
|
+
const matches = times;
|
|
26025
|
+
const marked = matches.filter((m) => m[3]).length;
|
|
26026
|
+
if (marked > 0 && marked < matches.length) return void 0;
|
|
26027
|
+
if (marked === matches.length) return matches.map((m) => Number(m[1]) % 12 * 60 + (/p/i.test(m[3]) ? 720 : 0) + Number(m[2]));
|
|
26028
|
+
const hours = matches.map((m) => m[1]);
|
|
26029
|
+
const twelveHour = hours.some((h) => /^[1-9]$/.test(h));
|
|
26030
|
+
const twentyFourHour = hours.some((h) => /^(0\d|1[3-9]|2\d)$/.test(h));
|
|
26031
|
+
if (twelveHour && (twentyFourHour || new Set(hours).size > 1)) return void 0;
|
|
26032
|
+
return matches.map((m) => Number(m[1]) * 60 + Number(m[2]));
|
|
26033
|
+
}
|
|
26034
|
+
/**
|
|
26035
|
+
* Put the top-level observations of each "Date:" group in time order, keeping indented
|
|
26036
|
+
* sub-items with their parent. The Observer writes a time on every observation but tends
|
|
26037
|
+
* to list a user's message ahead of the earlier events it follows, which can make a
|
|
26038
|
+
* superseded state read as the latest one. Groups are left as written when any
|
|
26039
|
+
* observation has no time or the times are ambiguous.
|
|
26040
|
+
*/
|
|
26041
|
+
function sortObservationsByTime(observations) {
|
|
26042
|
+
if (!observations) return observations;
|
|
26043
|
+
const output = [];
|
|
26044
|
+
let group;
|
|
26045
|
+
const flush = () => {
|
|
26046
|
+
if (!group) return;
|
|
26047
|
+
const minutes = group.sortable && group.items.length > 1 ? observationMinutes(group.items) : void 0;
|
|
26048
|
+
const items = minutes ? group.items.map((item, i) => ({
|
|
26049
|
+
item,
|
|
26050
|
+
minute: minutes[i]
|
|
26051
|
+
})).sort((a, b) => a.minute - b.minute).map((x) => x.item) : group.items;
|
|
26052
|
+
output.push(...group.header, ...items.flat(), ...group.trailing);
|
|
26053
|
+
group = void 0;
|
|
26054
|
+
};
|
|
26055
|
+
for (const line of observations.split("\n")) if (/^\s*Date:\s/.test(line)) {
|
|
26056
|
+
flush();
|
|
26057
|
+
group = {
|
|
26058
|
+
header: [line],
|
|
26059
|
+
items: [],
|
|
26060
|
+
trailing: [],
|
|
26061
|
+
sortable: true
|
|
26062
|
+
};
|
|
26063
|
+
} else if (!group) output.push(line);
|
|
26064
|
+
else if (/^[*-]\s/.test(line)) {
|
|
26065
|
+
group.items.at(-1)?.push(...group.trailing);
|
|
26066
|
+
group.trailing = [];
|
|
26067
|
+
group.items.push([line]);
|
|
26068
|
+
} else if (group.items.length === 0) group.header.push(line);
|
|
26069
|
+
else if (line.trim() === "") group.trailing.push(line);
|
|
26070
|
+
else if (/^\s/.test(line)) {
|
|
26071
|
+
group.items.at(-1).push(...group.trailing, line);
|
|
26072
|
+
group.trailing = [];
|
|
26073
|
+
} else {
|
|
26074
|
+
group.sortable = false;
|
|
26075
|
+
group.items.at(-1).push(...group.trailing, line);
|
|
26076
|
+
group.trailing = [];
|
|
26077
|
+
}
|
|
26078
|
+
flush();
|
|
26079
|
+
return output.join("\n");
|
|
26080
|
+
}
|
|
26007
26081
|
function analyzeDegenerateRepetition(text) {
|
|
26008
26082
|
const lines = collapseBoundedLineRuns(text.split("\n"));
|
|
26009
26083
|
const windowText = lines.filter((line) => line.trim().length >= MIN_DUPLICATE_LINE_CHARS && line.length <= MAX_OBSERVATION_LINE_CHARS).join("\n");
|
|
@@ -26538,7 +26612,7 @@ var ObserverRunner = class {
|
|
|
26538
26612
|
/**
|
|
26539
26613
|
* Call the Observer agent for multiple threads in a single batched request.
|
|
26540
26614
|
*/
|
|
26541
|
-
async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext, timeZone, mainAgent, currentModel) {
|
|
26615
|
+
async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext, timeZone, mainAgent, currentModel, wasTruncated) {
|
|
26542
26616
|
const contextFor = (threadId) => ({
|
|
26543
26617
|
threadId,
|
|
26544
26618
|
...hookContext
|
|
@@ -26549,7 +26623,7 @@ var ObserverRunner = class {
|
|
|
26549
26623
|
if (filtered.length > 0) messagesByThread.set(threadId, filtered);
|
|
26550
26624
|
}
|
|
26551
26625
|
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);
|
|
26626
|
+
const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel, wasTruncated);
|
|
26553
26627
|
for (const threadId of allThreadOrder) {
|
|
26554
26628
|
const threadResult = output.results.get(threadId);
|
|
26555
26629
|
if (!threadResult) {
|
|
@@ -26560,7 +26634,7 @@ var ObserverRunner = class {
|
|
|
26560
26634
|
}
|
|
26561
26635
|
return output;
|
|
26562
26636
|
}
|
|
26563
|
-
async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel) {
|
|
26637
|
+
async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel, wasTruncated) {
|
|
26564
26638
|
if (threadOrder.length === 0) return { results: /* @__PURE__ */ new Map() };
|
|
26565
26639
|
const inputTokens = Array.from(messagesByThread.values()).reduce((total, messages) => total + this.tokenCounter.countMessages(messages), 0);
|
|
26566
26640
|
let resolvedModel;
|
|
@@ -26606,7 +26680,8 @@ var ObserverRunner = class {
|
|
|
26606
26680
|
priorThreadTitle: priorMetadataByThread?.get(threadId)?.threadTitle,
|
|
26607
26681
|
priorExtractedValues: priorMetadataByThread?.get(threadId)?.extracted,
|
|
26608
26682
|
model: resolvedModel.model,
|
|
26609
|
-
timeZone
|
|
26683
|
+
timeZone,
|
|
26684
|
+
wasTruncated
|
|
26610
26685
|
});
|
|
26611
26686
|
results.set(threadId, {
|
|
26612
26687
|
observations: threadResult.observations,
|
|
@@ -26632,7 +26707,7 @@ var ObserverRunner = class {
|
|
|
26632
26707
|
const agent = this.createAgent(resolvedModel.model, true, void 0, activeExtractors);
|
|
26633
26708
|
const internalRequestContext = withOmInternalThreadId(requestContext, agent.id);
|
|
26634
26709
|
const multiThreadAttachmentFilter = this.resolveAttachmentFilter(resolvedModel.model, requestContext);
|
|
26635
|
-
const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread,
|
|
26710
|
+
const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread, wasTruncated, this.observationConfig.threadTitle, activeExtractors, {
|
|
26636
26711
|
attachmentFilter: multiThreadAttachmentFilter,
|
|
26637
26712
|
timeZone
|
|
26638
26713
|
})];
|
|
@@ -26646,6 +26721,7 @@ var ObserverRunner = class {
|
|
|
26646
26721
|
metadata: {
|
|
26647
26722
|
omThreadCount: threadOrder.length,
|
|
26648
26723
|
omPreviousObserverTokens: this.observationConfig.previousObserverTokens,
|
|
26724
|
+
omWasTruncated: wasTruncated ?? false,
|
|
26649
26725
|
omThreadTitleEnabled: this.observationConfig.threadTitle,
|
|
26650
26726
|
...resolvedModel.selectedThreshold !== void 0 ? { omSelectedThreshold: resolvedModel.selectedThreshold } : {},
|
|
26651
26727
|
...resolvedModel.routingStrategy ? { omRoutingStrategy: resolvedModel.routingStrategy } : {},
|
|
@@ -28568,6 +28644,9 @@ async function validateAgainstConfiguredSchema(schema, value) {
|
|
|
28568
28644
|
}).join("; ");
|
|
28569
28645
|
throw new Error(`Working memory update does not match the configured schema, so it was not saved: ${details}`);
|
|
28570
28646
|
}
|
|
28647
|
+
function isEmptyPlainObject(value) {
|
|
28648
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
28649
|
+
}
|
|
28571
28650
|
async function getWorkingMemoryDetails(context) {
|
|
28572
28651
|
const memory = context.memory;
|
|
28573
28652
|
const memoryConfig = (0, _mastra_core_memory.parseMemoryRequestContext)(context.requestContext)?.memoryConfig;
|
|
@@ -28615,8 +28694,9 @@ var WorkingMemoryExtractor = class extends Extractor {
|
|
|
28615
28694
|
const configuredSchema = memory.getMergedThreadConfig(memoryConfig ?? {}).workingMemory?.schema;
|
|
28616
28695
|
let document = current;
|
|
28617
28696
|
if (configuredSchema) {
|
|
28618
|
-
if (current === null) return;
|
|
28697
|
+
if (current === null || isEmptyPlainObject(current)) return;
|
|
28619
28698
|
document = await validateAgainstConfiguredSchema(configuredSchema, current);
|
|
28699
|
+
if (document === null || isEmptyPlainObject(document)) return;
|
|
28620
28700
|
}
|
|
28621
28701
|
const workingMemory = typeof document === "string" ? document : JSON.stringify(document) ?? "";
|
|
28622
28702
|
if (!workingMemory.trim()) return;
|
|
@@ -30887,6 +30967,80 @@ function resolveAutoModelId(mainModelId, options) {
|
|
|
30887
30967
|
const pick = provider ? models[provider] : void 0;
|
|
30888
30968
|
return pick ? `${prefix}${pick}` : mainModelId;
|
|
30889
30969
|
}
|
|
30970
|
+
/** How long an op may hold its key before later ops on that key stop waiting for it. */
|
|
30971
|
+
const OM_COMMIT_STALL_MS = 6e4;
|
|
30972
|
+
/** The per-thread / per-resource key shared by the commit queue and `BufferingCoordinator`. */
|
|
30973
|
+
function getOMLockKey(scope, threadId, resourceId) {
|
|
30974
|
+
if (scope === "resource" && resourceId) return `resource:${resourceId}`;
|
|
30975
|
+
return `thread:${threadId ?? "unknown"}`;
|
|
30976
|
+
}
|
|
30977
|
+
const REGISTRY = Symbol.for("@mastra/memory/om-commit-queue");
|
|
30978
|
+
const { queues, heldKeys } = globalThis[REGISTRY] ??= {
|
|
30979
|
+
queues: /* @__PURE__ */ new Map(),
|
|
30980
|
+
heldKeys: new async_hooks.AsyncLocalStorage()
|
|
30981
|
+
};
|
|
30982
|
+
function holdsKey(key) {
|
|
30983
|
+
const token = heldKeys.getStore()?.get(key);
|
|
30984
|
+
return token !== void 0 && queues.get(key)?.running === token;
|
|
30985
|
+
}
|
|
30986
|
+
function runNext(key, queue) {
|
|
30987
|
+
const next = queue.reflection.shift() ?? queue.normal.shift();
|
|
30988
|
+
if (!next) {
|
|
30989
|
+
queue.running = null;
|
|
30990
|
+
queues.delete(key);
|
|
30991
|
+
return;
|
|
30992
|
+
}
|
|
30993
|
+
next();
|
|
30994
|
+
}
|
|
30995
|
+
/**
|
|
30996
|
+
* Runs `op` once every earlier op on `key` has settled (reflection-priority ops first among those
|
|
30997
|
+
* still waiting) and resolves or rejects with its result. A rejection reaches only this caller.
|
|
30998
|
+
*
|
|
30999
|
+
* Enqueueing on a key from inside an op that holds the same key, directly or through nested ops
|
|
31000
|
+
* on other keys, would deadlock, so it rejects.
|
|
31001
|
+
*/
|
|
31002
|
+
function runOMCommit(key, op, { priority = "normal" } = {}) {
|
|
31003
|
+
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.`));
|
|
31004
|
+
return new Promise((resolve, reject) => {
|
|
31005
|
+
let queue = queues.get(key);
|
|
31006
|
+
if (!queue) {
|
|
31007
|
+
queue = {
|
|
31008
|
+
running: null,
|
|
31009
|
+
reflection: [],
|
|
31010
|
+
normal: []
|
|
31011
|
+
};
|
|
31012
|
+
queues.set(key, queue);
|
|
31013
|
+
}
|
|
31014
|
+
const owner = queue;
|
|
31015
|
+
const start = async_hooks.AsyncResource.bind(() => {
|
|
31016
|
+
const token = Symbol(key);
|
|
31017
|
+
owner.running = token;
|
|
31018
|
+
const held = new Map(heldKeys.getStore());
|
|
31019
|
+
held.set(key, token);
|
|
31020
|
+
let released = false;
|
|
31021
|
+
const release = () => {
|
|
31022
|
+
if (released) return;
|
|
31023
|
+
released = true;
|
|
31024
|
+
clearTimeout(stallTimer);
|
|
31025
|
+
runNext(key, owner);
|
|
31026
|
+
};
|
|
31027
|
+
const stallTimer = setTimeout(() => {
|
|
31028
|
+
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.`);
|
|
31029
|
+
release();
|
|
31030
|
+
}, OM_COMMIT_STALL_MS);
|
|
31031
|
+
stallTimer.unref?.();
|
|
31032
|
+
heldKeys.run(held, () => Promise.resolve().then(op)).then((value) => {
|
|
31033
|
+
release();
|
|
31034
|
+
resolve(value);
|
|
31035
|
+
}, (error) => {
|
|
31036
|
+
release();
|
|
31037
|
+
reject(error);
|
|
31038
|
+
});
|
|
31039
|
+
});
|
|
31040
|
+
(priority === "reflection" ? owner.reflection : owner.normal).push(start);
|
|
31041
|
+
if (owner.running === null) runNext(key, owner);
|
|
31042
|
+
});
|
|
31043
|
+
}
|
|
30890
31044
|
const activeOps = /* @__PURE__ */ new Map();
|
|
30891
31045
|
function opKey(recordId, op) {
|
|
30892
31046
|
return `${recordId}:${op}`;
|
|
@@ -30923,11 +31077,37 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
30923
31077
|
*/
|
|
30924
31078
|
static asyncBufferingOps = /* @__PURE__ */ new Map();
|
|
30925
31079
|
/**
|
|
31080
|
+
* Observation buffer ops that may still append a buffered chunk.
|
|
31081
|
+
* Key format: "obs:{lockKey}"
|
|
31082
|
+
* Value: Promise that resolves once the op has persisted its chunk (or ended without one)
|
|
31083
|
+
*
|
|
31084
|
+
* Above the threshold→blockAfter band, activation waits (bounded) on this phase so the chunk
|
|
31085
|
+
* activates instead of being re-observed by the sync pass. It is not needed for safety: storage
|
|
31086
|
+
* keeps a chunk appended during activation. An op stays in asyncBufferingOps through its
|
|
31087
|
+
* post-persist work (indexing, thread title), which activation never waits for.
|
|
31088
|
+
*/
|
|
31089
|
+
static pendingChunkWrites = /* @__PURE__ */ new Map();
|
|
31090
|
+
/**
|
|
30926
31091
|
* Track the last token boundary at which we started buffering.
|
|
30927
31092
|
* Key format: "obs:{lockKey}" or "refl:{lockKey}"
|
|
30928
31093
|
*/
|
|
30929
31094
|
static lastBufferedBoundary = /* @__PURE__ */ new Map();
|
|
30930
31095
|
/**
|
|
31096
|
+
* The buffer op that set the observation boundary, if one did. Any other write (an activation
|
|
31097
|
+
* reset, another op) replaces or clears it, so an op that outlived it never writes its boundary
|
|
31098
|
+
* back. Key format: "obs:{lockKey}"
|
|
31099
|
+
*/
|
|
31100
|
+
static observationBoundaryOwners = /* @__PURE__ */ new Map();
|
|
31101
|
+
/** Set the observation boundary; `owner` is the buffer op setting it, if any. */
|
|
31102
|
+
static setObservationBoundary(bufferKey, tokens, owner) {
|
|
31103
|
+
BufferingCoordinator.lastBufferedBoundary.set(bufferKey, tokens);
|
|
31104
|
+
if (owner) BufferingCoordinator.observationBoundaryOwners.set(bufferKey, owner);
|
|
31105
|
+
else BufferingCoordinator.observationBoundaryOwners.delete(bufferKey);
|
|
31106
|
+
}
|
|
31107
|
+
static ownsObservationBoundary(bufferKey, owner) {
|
|
31108
|
+
return BufferingCoordinator.observationBoundaryOwners.get(bufferKey) === owner;
|
|
31109
|
+
}
|
|
31110
|
+
/**
|
|
30931
31111
|
* Track the timestamp cursor for buffered messages.
|
|
30932
31112
|
* Key format: "obs:{lockKey}"
|
|
30933
31113
|
*/
|
|
@@ -30943,8 +31123,7 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
30943
31123
|
this.scope = opts.scope;
|
|
30944
31124
|
}
|
|
30945
31125
|
getLockKey(threadId, resourceId) {
|
|
30946
|
-
|
|
30947
|
-
return `thread:${threadId ?? "unknown"}`;
|
|
31126
|
+
return getOMLockKey(this.scope, threadId, resourceId);
|
|
30948
31127
|
}
|
|
30949
31128
|
isAsyncObservationEnabled() {
|
|
30950
31129
|
return this.observationConfig.bufferTokens !== void 0 && this.observationConfig.bufferTokens > 0;
|
|
@@ -30962,6 +31141,19 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
30962
31141
|
return BufferingCoordinator.asyncBufferingOps.has(bufferKey);
|
|
30963
31142
|
}
|
|
30964
31143
|
/**
|
|
31144
|
+
* Mark an observation buffer op as able to append a chunk. Call the returned release
|
|
31145
|
+
* function once it no longer can; releasing more than once is a no-op.
|
|
31146
|
+
*/
|
|
31147
|
+
static trackChunkWrite(bufferKey) {
|
|
31148
|
+
let resolve;
|
|
31149
|
+
const pending = new Promise((r) => resolve = r);
|
|
31150
|
+
BufferingCoordinator.pendingChunkWrites.set(bufferKey, pending);
|
|
31151
|
+
return () => {
|
|
31152
|
+
if (BufferingCoordinator.pendingChunkWrites.get(bufferKey) === pending) BufferingCoordinator.pendingChunkWrites.delete(bufferKey);
|
|
31153
|
+
resolve();
|
|
31154
|
+
};
|
|
31155
|
+
}
|
|
31156
|
+
/**
|
|
30965
31157
|
* Clean up static maps for a thread/resource to prevent memory leaks.
|
|
30966
31158
|
*/
|
|
30967
31159
|
cleanupStaticMaps(threadId, resourceId, activatedMessageIds) {
|
|
@@ -30970,10 +31162,12 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
30970
31162
|
const reflBufKey = this.getReflectionBufferKey(lockKey);
|
|
30971
31163
|
if (activatedMessageIds) {
|
|
30972
31164
|
BufferingCoordinator.lastBufferedBoundary.delete(obsBufKey);
|
|
31165
|
+
BufferingCoordinator.observationBoundaryOwners.delete(obsBufKey);
|
|
30973
31166
|
BufferingCoordinator.lastBufferedAtTime.delete(obsBufKey);
|
|
30974
31167
|
} else {
|
|
30975
31168
|
BufferingCoordinator.lastBufferedAtTime.delete(obsBufKey);
|
|
30976
31169
|
BufferingCoordinator.lastBufferedBoundary.delete(obsBufKey);
|
|
31170
|
+
BufferingCoordinator.observationBoundaryOwners.delete(obsBufKey);
|
|
30977
31171
|
BufferingCoordinator.lastBufferedBoundary.delete(reflBufKey);
|
|
30978
31172
|
BufferingCoordinator.asyncBufferingOps.delete(obsBufKey);
|
|
30979
31173
|
BufferingCoordinator.asyncBufferingOps.delete(reflBufKey);
|
|
@@ -30988,7 +31182,7 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
30988
31182
|
if (record.isBufferingObservation) {
|
|
30989
31183
|
if (isOpActiveInProcess(record.id, "bufferingObservation")) return false;
|
|
30990
31184
|
omDebug(`[OM:shouldTriggerAsyncObs] isBufferingObservation=true but stale, clearing`);
|
|
30991
|
-
storage
|
|
31185
|
+
if (storage) runOMCommit(lockKey, () => storage.setBufferingObservationFlag(record.id, false)).catch(() => {});
|
|
30992
31186
|
}
|
|
30993
31187
|
const bufferKey = this.getObservationBufferKey(lockKey);
|
|
30994
31188
|
if (this.isAsyncBufferingInProgress(bufferKey)) return false;
|
|
@@ -31008,7 +31202,7 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
31008
31202
|
* Await any in-flight async buffering operations for a given thread/resource.
|
|
31009
31203
|
*/
|
|
31010
31204
|
static async awaitBuffering(threadId, resourceId, scope, timeoutMs = 3e4) {
|
|
31011
|
-
const lockKey = scope
|
|
31205
|
+
const lockKey = getOMLockKey(scope, threadId, resourceId);
|
|
31012
31206
|
const obsKey = `obs:${lockKey}`;
|
|
31013
31207
|
const reflKey = `refl:${lockKey}`;
|
|
31014
31208
|
const promises = [];
|
|
@@ -31300,6 +31494,34 @@ function createThreadUpdateMarker(params) {
|
|
|
31300
31494
|
}
|
|
31301
31495
|
};
|
|
31302
31496
|
}
|
|
31497
|
+
/**
|
|
31498
|
+
* The current head of `origin`'s lineage (the generations reflections have created from it),
|
|
31499
|
+
* or `null` when that lineage is gone.
|
|
31500
|
+
*
|
|
31501
|
+
* Use this instead of reading the head by thread/resource when committing work that started
|
|
31502
|
+
* from `origin`. Clearing a record (`om.clear`, `Memory.deleteThread`) deletes every generation
|
|
31503
|
+
* of the thread/resource, and the thread can then get a new, unrelated record. Work produced
|
|
31504
|
+
* from the cleared messages must never be committed to it.
|
|
31505
|
+
*
|
|
31506
|
+
* Generation ids are never reused and clearing deletes all of them, so the head belongs to
|
|
31507
|
+
* `origin`'s lineage iff `origin` still exists.
|
|
31508
|
+
*/
|
|
31509
|
+
async function getLineageHead(storage, origin) {
|
|
31510
|
+
const head = await storage.getObservationalMemory(origin.threadId, origin.resourceId);
|
|
31511
|
+
if (!head || head.id === origin.id) return head;
|
|
31512
|
+
if (head.generationCount <= origin.generationCount) return null;
|
|
31513
|
+
return (await storage.getObservationalMemoryHistory(origin.threadId, origin.resourceId, head.generationCount - origin.generationCount + 3)).some((record) => record.id === origin.id) ? head : null;
|
|
31514
|
+
}
|
|
31515
|
+
/**
|
|
31516
|
+
* The generation `id` of `origin`'s lineage, or `null` when that lineage is gone or `id` is not
|
|
31517
|
+
* part of it. Unlike {@link getLineageHead}, this keeps naming `id` after later reflections have
|
|
31518
|
+
* superseded it, e.g. to report which generation a write landed on.
|
|
31519
|
+
*/
|
|
31520
|
+
async function getLineageRecord(storage, origin, id) {
|
|
31521
|
+
const head = await getLineageHead(storage, origin);
|
|
31522
|
+
if (!head || head.id === id) return head;
|
|
31523
|
+
return (await storage.getObservationalMemoryHistory(origin.threadId, origin.resourceId, head.generationCount - origin.generationCount + 3)).find((record) => record.id === id) ?? null;
|
|
31524
|
+
}
|
|
31303
31525
|
function isRecord$1(value) {
|
|
31304
31526
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
31305
31527
|
}
|
|
@@ -31442,6 +31664,39 @@ function calculateProjectedMessageRemoval(chunks, bufferActivation, messageToken
|
|
|
31442
31664
|
}
|
|
31443
31665
|
/** Module-level xxhash singleton — loaded once, shared across all strategy instances. */
|
|
31444
31666
|
const hasherPromise = e();
|
|
31667
|
+
/** Recompose-and-retry rounds for an observation commit that hit a retired or changed head. */
|
|
31668
|
+
const MAX_HEAD_COMMIT_RETRIES = 3;
|
|
31669
|
+
const OBSERVATION_LIFECYCLE_MARKERS = /* @__PURE__ */ new Set([
|
|
31670
|
+
"data-om-observation-start",
|
|
31671
|
+
"data-om-observation-end",
|
|
31672
|
+
"data-om-observation-failed"
|
|
31673
|
+
]);
|
|
31674
|
+
function isOmPart(part) {
|
|
31675
|
+
return String(part?.type ?? "").startsWith("data-om-");
|
|
31676
|
+
}
|
|
31677
|
+
/**
|
|
31678
|
+
* Index in `message` right after this cycle's start marker (and any OM parts directly after it),
|
|
31679
|
+
* or -1 when the message doesn't carry it.
|
|
31680
|
+
*/
|
|
31681
|
+
function afterOwnStartMarker(message, marker) {
|
|
31682
|
+
const cycleId = marker.data?.cycleId;
|
|
31683
|
+
const startType = marker.type.startsWith("data-om-buffering-") ? "data-om-buffering-start" : "data-om-observation-start";
|
|
31684
|
+
if (!cycleId || marker.type === startType) return -1;
|
|
31685
|
+
const parts = message.content?.parts ?? [];
|
|
31686
|
+
const start = parts.findIndex((part) => part?.type === startType && part?.data?.cycleId === cycleId);
|
|
31687
|
+
if (start === -1) return -1;
|
|
31688
|
+
let index = start + 1;
|
|
31689
|
+
while (index < parts.length && isOmPart(parts[index])) index++;
|
|
31690
|
+
return index;
|
|
31691
|
+
}
|
|
31692
|
+
/** True when the message has parts no completed observation boundary covers (OM markers aside). */
|
|
31693
|
+
function hasUnobservedContent(message) {
|
|
31694
|
+
return getUnobservedParts(message).some((part) => !isOmPart(part));
|
|
31695
|
+
}
|
|
31696
|
+
function isObservationLifecycleMarker(marker) {
|
|
31697
|
+
const operationType = marker.data?.operationType;
|
|
31698
|
+
return OBSERVATION_LIFECYCLE_MARKERS.has(marker.type) && operationType !== "reflection";
|
|
31699
|
+
}
|
|
31445
31700
|
/**
|
|
31446
31701
|
* Abstract base class for observation strategies.
|
|
31447
31702
|
*
|
|
@@ -31491,17 +31746,35 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
31491
31746
|
const fresh = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
31492
31747
|
if (fresh?.lastObservedAt && record.lastObservedAt && fresh.lastObservedAt > record.lastObservedAt) return { observed: false };
|
|
31493
31748
|
}
|
|
31494
|
-
const { messages, existingObservations } = await this.prepare();
|
|
31749
|
+
const { messages, existingObservations, contextRecord } = await this.prepare();
|
|
31750
|
+
if (messages.length === 0) return { observed: false };
|
|
31495
31751
|
const observationMessages = stripSubconsciousSignals(messages);
|
|
31496
31752
|
await this.emitStartMarkers(cycleId);
|
|
31497
|
-
const
|
|
31498
|
-
const
|
|
31499
|
-
|
|
31753
|
+
const observerContext = this.deps.prepareObserverContext(existingObservations, contextRecord ?? record);
|
|
31754
|
+
const output = await this.observe(observerContext.context ?? "", observationMessages, observerContext.wasTruncated);
|
|
31755
|
+
let processed = await this.process(output, existingObservations);
|
|
31756
|
+
let committedRecord = record;
|
|
31757
|
+
const outcome = await this.persist(processed);
|
|
31758
|
+
this.settleObservationCommit(outcome?.status === "committed" && !outcome.alreadyCovered);
|
|
31759
|
+
if (outcome?.status === "not-committed") {
|
|
31760
|
+
omDebug(`[OM:observe] cycle ${cycleId} not committed: ${outcome.reason}`);
|
|
31761
|
+
await this.emitFailedMarkers(cycleId, /* @__PURE__ */ new Error(`Observation not committed: ${outcome.reason}`));
|
|
31762
|
+
return {
|
|
31763
|
+
observed: false,
|
|
31764
|
+
usage: output.usage,
|
|
31765
|
+
providerMetadata: output.providerMetadata
|
|
31766
|
+
};
|
|
31767
|
+
}
|
|
31768
|
+
if (outcome?.status === "committed") {
|
|
31769
|
+
processed = outcome.processed;
|
|
31770
|
+
committedRecord = outcome.record;
|
|
31771
|
+
}
|
|
31500
31772
|
await this.emitEndMarkers(cycleId, processed);
|
|
31501
31773
|
if (this.needsReflection) await this.deps.reflector.maybeReflect({
|
|
31502
31774
|
record: {
|
|
31503
|
-
...
|
|
31504
|
-
activeObservations: processed.observations
|
|
31775
|
+
...committedRecord,
|
|
31776
|
+
activeObservations: processed.observations,
|
|
31777
|
+
observationTokenCount: processed.observationTokens
|
|
31505
31778
|
},
|
|
31506
31779
|
observationTokens: processed.observationTokens,
|
|
31507
31780
|
threadId,
|
|
@@ -31537,7 +31810,8 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
31537
31810
|
threadId
|
|
31538
31811
|
}
|
|
31539
31812
|
};
|
|
31540
|
-
|
|
31813
|
+
const observed = this.getObservedMessagesForThread(threadId);
|
|
31814
|
+
await (observed?.length ? this.persistMarkerToObservedMessage(failedMarkerForStorage, observed, threadId, this.opts.resourceId) : this.persistMarkerToStorage(failedMarkerForStorage, threadId, this.opts.resourceId)).catch(() => {});
|
|
31541
31815
|
if (abortSignal?.aborted) throw error;
|
|
31542
31816
|
omError("[OM] Observation failed", error);
|
|
31543
31817
|
return {
|
|
@@ -31564,6 +31838,11 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
31564
31838
|
transient: true
|
|
31565
31839
|
}).catch(() => {});
|
|
31566
31840
|
const markerThreadId = marker.data?.threadId ?? this.opts.threadId;
|
|
31841
|
+
const observed = isObservationLifecycleMarker(marker) ? this.getObservedMessagesForThread(markerThreadId) : void 0;
|
|
31842
|
+
if (observed?.length) {
|
|
31843
|
+
await this.persistMarkerToObservedMessage(marker, observed, markerThreadId, this.opts.resourceId);
|
|
31844
|
+
return;
|
|
31845
|
+
}
|
|
31567
31846
|
if (!await this.persistMarkerToMessage(marker, this.opts.messageList, markerThreadId, this.opts.resourceId)) await this.persistMarkerToStorage(marker, markerThreadId, this.opts.resourceId);
|
|
31568
31847
|
}
|
|
31569
31848
|
getObservationMarkerConfig() {
|
|
@@ -31647,6 +31926,76 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
31647
31926
|
}
|
|
31648
31927
|
return `${existingObservations}${lastObservedAt ? ObservationStrategy.createMessageBoundary(lastObservedAt) : "\n\n"}${newThreadSection}`;
|
|
31649
31928
|
}
|
|
31929
|
+
/**
|
|
31930
|
+
* Commit composed observations to the current head generation.
|
|
31931
|
+
*
|
|
31932
|
+
* The write is conditional on the head text the observations were composed from. When the
|
|
31933
|
+
* target generation was retired by a reflection, or another writer changed the text, the
|
|
31934
|
+
* observations are recomposed against the fresh head and the commit is retried (bounded).
|
|
31935
|
+
* When the fresh head's `observedMessageIds` already include every message this cycle observed
|
|
31936
|
+
* (another instance observed them first), nothing is written and `alreadyCovered` is set, so
|
|
31937
|
+
* callers advance cursors and markers without adding a second copy of the same observations.
|
|
31938
|
+
* The cursor alone is not proof: an activation can move it past a message it never covered.
|
|
31939
|
+
* Returns null when no commit landed — callers must then leave cursors, markers, and the live
|
|
31940
|
+
* context untouched.
|
|
31941
|
+
*/
|
|
31942
|
+
async commitActiveObservationsToHead(opts) {
|
|
31943
|
+
let { processed, composedFrom, target } = opts;
|
|
31944
|
+
for (let attempt = 0; attempt <= MAX_HEAD_COMMIT_RETRIES; attempt++) {
|
|
31945
|
+
const result = await this.runCommit(async () => {
|
|
31946
|
+
const head = await getLineageHead(this.storage, target);
|
|
31947
|
+
if (!head) return null;
|
|
31948
|
+
if (head.id !== target.id || (head.activeObservations ?? "") !== composedFrom) {
|
|
31949
|
+
if (this.headCoversCycle(head, opts.cycleMessageIds)) {
|
|
31950
|
+
omDebug(`[OM:observe] head ${head.id} already covers this cycle's messages; skipping the duplicate commit`);
|
|
31951
|
+
target = head;
|
|
31952
|
+
return {
|
|
31953
|
+
applied: true,
|
|
31954
|
+
alreadyCovered: true
|
|
31955
|
+
};
|
|
31956
|
+
}
|
|
31957
|
+
target = head;
|
|
31958
|
+
composedFrom = head.activeObservations ?? "";
|
|
31959
|
+
processed = await opts.recompose(head);
|
|
31960
|
+
}
|
|
31961
|
+
const input = {
|
|
31962
|
+
id: target.id,
|
|
31963
|
+
observations: processed.observations,
|
|
31964
|
+
tokenCount: processed.observationTokens,
|
|
31965
|
+
lastObservedAt: processed.lastObservedAt,
|
|
31966
|
+
observedMessageIds: processed.observedMessageIds,
|
|
31967
|
+
expectedActiveObservations: composedFrom
|
|
31968
|
+
};
|
|
31969
|
+
return typeof this.storage.commitActiveObservations === "function" ? await this.storage.commitActiveObservations(input) : (await this.storage.updateActiveObservations(input), { applied: true });
|
|
31970
|
+
});
|
|
31971
|
+
if (!result) return null;
|
|
31972
|
+
if ("alreadyCovered" in result) return {
|
|
31973
|
+
processed: {
|
|
31974
|
+
...processed,
|
|
31975
|
+
observations: target.activeObservations ?? "",
|
|
31976
|
+
observationTokens: target.observationTokenCount
|
|
31977
|
+
},
|
|
31978
|
+
record: target,
|
|
31979
|
+
alreadyCovered: true
|
|
31980
|
+
};
|
|
31981
|
+
if (result.applied) return {
|
|
31982
|
+
processed,
|
|
31983
|
+
record: target
|
|
31984
|
+
};
|
|
31985
|
+
omDebug(`[OM:observe] commit to ${target.id} not applied (${result.reason}); retrying against the head`);
|
|
31986
|
+
}
|
|
31987
|
+
return null;
|
|
31988
|
+
}
|
|
31989
|
+
headCoversCycle(head, cycleMessageIds) {
|
|
31990
|
+
if (cycleMessageIds.length === 0) return false;
|
|
31991
|
+
const headIds = new Set(Array.isArray(head.observedMessageIds) ? head.observedMessageIds : []);
|
|
31992
|
+
return cycleMessageIds.every((id) => headIds.has(id));
|
|
31993
|
+
}
|
|
31994
|
+
/** Runs one storage commit in this thread's (or resource's) slot of the process-wide commit queue. */
|
|
31995
|
+
runCommit(op) {
|
|
31996
|
+
const { threadId, resourceId, record } = this.opts;
|
|
31997
|
+
return runOMCommit(getOMLockKey(this.scope, record.threadId ?? threadId, record.resourceId ?? resourceId), op);
|
|
31998
|
+
}
|
|
31650
31999
|
async indexObservationGroups(observations, threadId, resourceId, observedAt, recordId) {
|
|
31651
32000
|
if (!resourceId || !this.deps.onIndexObservations) return;
|
|
31652
32001
|
const groups = parseObservationGroups(observations);
|
|
@@ -31665,30 +32014,133 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
31665
32014
|
})));
|
|
31666
32015
|
}
|
|
31667
32016
|
/**
|
|
32017
|
+
* Messages this cycle observed for `threadId`, or undefined when the strategy does not
|
|
32018
|
+
* place observation lifecycle markers.
|
|
32019
|
+
*/
|
|
32020
|
+
getObservedMessagesForThread(_threadId) {}
|
|
32021
|
+
/**
|
|
32022
|
+
* Persist an observation start/end/failed marker without hiding unobserved content. A completed
|
|
32023
|
+
* end marker tells `getUnobservedMessages` that everything before it in that message is observed
|
|
32024
|
+
* (whatever thread the marker names), so it must only follow content this cycle observed.
|
|
32025
|
+
* Placement, in order:
|
|
32026
|
+
* 1. the live MessageList's newest assistant message (this turn's response or its seed), when the
|
|
32027
|
+
* list belongs to the marker's thread and the cycle observed that message or it has no
|
|
32028
|
+
* unobserved content;
|
|
32029
|
+
* 2. the newest observed assistant message; a stored copy (no live list, or another instance's
|
|
32030
|
+
* message) gets the marker right after the parts this cycle observed (end/failed markers right
|
|
32031
|
+
* after this cycle's start marker), so parts added while the Observer ran stay unobserved;
|
|
32032
|
+
* 3. the newest stored assistant message that is not newer than the observed range, only when it
|
|
32033
|
+
* has nothing unobserved (or already carries this cycle's start marker); otherwise no marker,
|
|
32034
|
+
* and the thread cursor alone records the cycle.
|
|
32035
|
+
* Lifecycle markers never go on user messages.
|
|
32036
|
+
*/
|
|
32037
|
+
async persistMarkerToObservedMessage(marker, observed, threadId, resourceId) {
|
|
32038
|
+
const observedIds = new Set(observed.map((m) => m.id));
|
|
32039
|
+
const list = threadId === this.opts.threadId ? this.opts.messageList : void 0;
|
|
32040
|
+
const listMessages = list ? getObservableMessages(list) : [];
|
|
32041
|
+
const live = [...listMessages].reverse().find((m) => m.role === "assistant");
|
|
32042
|
+
if (live && (observedIds.has(live.id) || !hasUnobservedContent(live))) {
|
|
32043
|
+
await this.attachMarker(marker, live, live.content.parts.length, threadId, resourceId);
|
|
32044
|
+
return;
|
|
32045
|
+
}
|
|
32046
|
+
const target = [...observed].reverse().find((m) => m.role === "assistant");
|
|
32047
|
+
if (target) {
|
|
32048
|
+
const fromList = listMessages.find((m) => m.id === target.id);
|
|
32049
|
+
if (fromList) {
|
|
32050
|
+
await this.attachMarker(marker, fromList, fromList.content.parts.length, threadId, resourceId);
|
|
32051
|
+
return;
|
|
32052
|
+
}
|
|
32053
|
+
try {
|
|
32054
|
+
const stored = (await this.storage.listMessagesById({ messageIds: [target.id] })).messages[0];
|
|
32055
|
+
if (stored?.content?.parts && Array.isArray(stored.content.parts)) {
|
|
32056
|
+
const afterStart = afterOwnStartMarker(stored, marker);
|
|
32057
|
+
const index = afterStart !== -1 ? afterStart : this.insertionAfterObserved(stored, target);
|
|
32058
|
+
await this.attachMarker(marker, stored, index, threadId, resourceId);
|
|
32059
|
+
}
|
|
32060
|
+
} catch (e) {
|
|
32061
|
+
omDebug(`[OM:persistMarkerToObservedMessage] failed to load marker target: ${e}`);
|
|
32062
|
+
}
|
|
32063
|
+
return;
|
|
32064
|
+
}
|
|
32065
|
+
const observedTimes = observed.filter((m) => m.createdAt).map((m) => new Date(m.createdAt).getTime());
|
|
32066
|
+
if (observedTimes.length === 0) return;
|
|
32067
|
+
await this.persistMarkerToStorage(marker, threadId, resourceId, {
|
|
32068
|
+
notAfter: new Date(Math.max(...observedTimes)),
|
|
32069
|
+
onlyIfObserved: true
|
|
32070
|
+
});
|
|
32071
|
+
}
|
|
32072
|
+
/** What the cycle observed of each message, captured when it placed its first marker there. */
|
|
32073
|
+
observedParts = /* @__PURE__ */ new Map();
|
|
32074
|
+
/**
|
|
32075
|
+
* Index in `stored` right after the parts the cycle observed in `observed` (and any OM markers
|
|
32076
|
+
* directly after them). Parts are only ever appended, so the observed message's non-marker part
|
|
32077
|
+
* count locates them, counted from the start of the message or, when `observed` is the trimmed
|
|
32078
|
+
* copy `getUnobservedMessages` returns for an already marked message, from just after the stored
|
|
32079
|
+
* copy's last completed boundary. Captured at the cycle's first marker, before the Observer runs,
|
|
32080
|
+
* because `observed` may be a live reference that grows meanwhile. Falls back to the end when the
|
|
32081
|
+
* stored copy has fewer parts than were observed.
|
|
32082
|
+
*/
|
|
32083
|
+
insertionAfterObserved(stored, observed) {
|
|
32084
|
+
if (!this.observedParts.has(observed.id)) this.observedParts.set(observed.id, {
|
|
32085
|
+
count: (observed.content?.parts ?? []).filter((part) => !isOmPart(part)).length,
|
|
32086
|
+
observedIsTrimmed: findLastCompletedObservationBoundary(observed) === -1
|
|
32087
|
+
});
|
|
32088
|
+
const { count, observedIsTrimmed } = this.observedParts.get(observed.id);
|
|
32089
|
+
const parts = stored.content.parts;
|
|
32090
|
+
let index = observedIsTrimmed ? findLastCompletedObservationBoundary(stored) + 1 : 0;
|
|
32091
|
+
let seen = 0;
|
|
32092
|
+
while (index < parts.length && seen < count) {
|
|
32093
|
+
if (!isOmPart(parts[index])) seen++;
|
|
32094
|
+
index++;
|
|
32095
|
+
}
|
|
32096
|
+
if (seen < count) return parts.length;
|
|
32097
|
+
while (index < parts.length && isOmPart(parts[index])) index++;
|
|
32098
|
+
return index;
|
|
32099
|
+
}
|
|
32100
|
+
async attachMarker(marker, msg, index, threadId, resourceId) {
|
|
32101
|
+
const parts = msg.content?.parts;
|
|
32102
|
+
if (!parts || !Array.isArray(parts)) return;
|
|
32103
|
+
const markerData = marker.data;
|
|
32104
|
+
if (!(markerData?.cycleId && parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) parts.splice(index, 0, marker);
|
|
32105
|
+
try {
|
|
32106
|
+
await this.messageHistory.persistMessages({
|
|
32107
|
+
messages: [msg],
|
|
32108
|
+
threadId,
|
|
32109
|
+
resourceId
|
|
32110
|
+
});
|
|
32111
|
+
} catch (e) {
|
|
32112
|
+
omDebug(`[OM:persistMarkerToObservedMessage] failed to save marker to DB: ${e}`);
|
|
32113
|
+
}
|
|
32114
|
+
}
|
|
32115
|
+
/**
|
|
31668
32116
|
* Persist a marker to the last assistant message in storage.
|
|
31669
32117
|
* Fetches messages directly from the DB so it works even when
|
|
31670
32118
|
* no MessageList is available (e.g. async buffering ops).
|
|
31671
32119
|
*/
|
|
31672
|
-
async persistMarkerToStorage(marker, threadId, resourceId) {
|
|
32120
|
+
async persistMarkerToStorage(marker, threadId, resourceId, opts) {
|
|
31673
32121
|
try {
|
|
31674
|
-
const
|
|
32122
|
+
const assistants = ((await this.storage.listMessages({
|
|
31675
32123
|
threadId,
|
|
31676
32124
|
perPage: 20,
|
|
31677
32125
|
orderBy: {
|
|
31678
32126
|
field: "createdAt",
|
|
31679
32127
|
direction: "DESC"
|
|
31680
|
-
}
|
|
31681
|
-
|
|
31682
|
-
|
|
31683
|
-
|
|
31684
|
-
|
|
31685
|
-
|
|
31686
|
-
|
|
31687
|
-
|
|
31688
|
-
|
|
31689
|
-
|
|
31690
|
-
|
|
31691
|
-
|
|
32128
|
+
},
|
|
32129
|
+
...opts?.notAfter ? { filter: { dateRange: { end: opts.notAfter } } } : {}
|
|
32130
|
+
}))?.messages ?? []).filter((msg) => msg?.role === "assistant" && Array.isArray(msg.content?.parts));
|
|
32131
|
+
const startMessage = opts?.onlyIfObserved || opts?.onCycleStartMessage ? assistants.find((m) => afterOwnStartMarker(m, marker) !== -1) : void 0;
|
|
32132
|
+
const msg = opts?.onCycleStartMessage ? startMessage : startMessage ?? assistants[0];
|
|
32133
|
+
if (!msg) return;
|
|
32134
|
+
const markerData = marker.data;
|
|
32135
|
+
const alreadyPresent = markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId);
|
|
32136
|
+
const afterStart = afterOwnStartMarker(msg, marker);
|
|
32137
|
+
if (opts?.onlyIfObserved && afterStart === -1 && hasUnobservedContent(msg)) return;
|
|
32138
|
+
if (!alreadyPresent) msg.content.parts.splice(afterStart !== -1 ? afterStart : msg.content.parts.length, 0, marker);
|
|
32139
|
+
await this.messageHistory.persistMessages({
|
|
32140
|
+
messages: [msg],
|
|
32141
|
+
threadId,
|
|
32142
|
+
resourceId
|
|
32143
|
+
});
|
|
31692
32144
|
} catch (e) {
|
|
31693
32145
|
omDebug(`[OM:persistMarkerToStorage] failed to save marker to DB: ${e}`);
|
|
31694
32146
|
}
|
|
@@ -31746,10 +32198,15 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
31746
32198
|
tokensToObserve = 0;
|
|
31747
32199
|
observerResult;
|
|
31748
32200
|
priorExtractedValues;
|
|
32201
|
+
/** Head text the processed observations were composed from (the commit's expected text). */
|
|
32202
|
+
composedFrom = "";
|
|
31749
32203
|
constructor(deps, opts) {
|
|
31750
32204
|
super(deps, opts);
|
|
31751
32205
|
this.lastMessage = opts.messages[opts.messages.length - 1];
|
|
31752
32206
|
}
|
|
32207
|
+
getObservedMessagesForThread(threadId) {
|
|
32208
|
+
return threadId === this.opts.threadId ? this.opts.messages : void 0;
|
|
32209
|
+
}
|
|
31753
32210
|
get needsLock() {
|
|
31754
32211
|
return true;
|
|
31755
32212
|
}
|
|
@@ -31784,9 +32241,11 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
31784
32241
|
}
|
|
31785
32242
|
}
|
|
31786
32243
|
this.tokensToObserve = await this.tokenCounter.countMessagesAsync(messages);
|
|
32244
|
+
const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
31787
32245
|
return {
|
|
31788
32246
|
messages,
|
|
31789
|
-
existingObservations:
|
|
32247
|
+
existingObservations: freshRecord?.activeObservations ?? record.activeObservations ?? "",
|
|
32248
|
+
contextRecord: freshRecord
|
|
31790
32249
|
};
|
|
31791
32250
|
}
|
|
31792
32251
|
async emitStartMarkers(cycleId) {
|
|
@@ -31804,11 +32263,12 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
31804
32263
|
await this.streamMarker(startMarker);
|
|
31805
32264
|
}
|
|
31806
32265
|
}
|
|
31807
|
-
async observe(
|
|
32266
|
+
async observe(observerContext, messages, wasTruncated) {
|
|
31808
32267
|
const thread = await this.storage.getThreadById({ threadId: this.opts.threadId });
|
|
31809
32268
|
const omMeta = thread ? (0, _mastra_core_memory.getThreadOMMetadata)(thread.metadata) : void 0;
|
|
31810
32269
|
this.priorExtractedValues = getPriorExtractedValues(omMeta, this.observationConfig.extractors);
|
|
31811
|
-
const result = await this.deps.observer.call(
|
|
32270
|
+
const result = await this.deps.observer.call(observerContext, messages, this.opts.abortSignal, {
|
|
32271
|
+
wasTruncated,
|
|
31812
32272
|
requestContext: this.opts.requestContext,
|
|
31813
32273
|
observabilityContext: this.opts.observabilityContext,
|
|
31814
32274
|
priorCurrentTask: omMeta?.currentTask,
|
|
@@ -31854,20 +32314,14 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
31854
32314
|
}
|
|
31855
32315
|
async process(output, existingObservations) {
|
|
31856
32316
|
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])];
|
|
32317
|
+
this.composedFrom = existingObservations;
|
|
32318
|
+
const processed = await this.compose(output, existingObservations, record);
|
|
31865
32319
|
this.deps.emitDebugEvent({
|
|
31866
32320
|
type: "observation_complete",
|
|
31867
32321
|
timestamp: /* @__PURE__ */ new Date(),
|
|
31868
32322
|
threadId,
|
|
31869
32323
|
resourceId: record.resourceId ?? "",
|
|
31870
|
-
observations:
|
|
32324
|
+
observations: processed.observations,
|
|
31871
32325
|
rawObserverOutput: output.observations,
|
|
31872
32326
|
previousObservations: record.activeObservations,
|
|
31873
32327
|
messages: messages.map((m) => ({
|
|
@@ -31876,11 +32330,23 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
31876
32330
|
})),
|
|
31877
32331
|
usage: output.usage
|
|
31878
32332
|
});
|
|
32333
|
+
return processed;
|
|
32334
|
+
}
|
|
32335
|
+
/** Build the observations to commit on top of `existingObservations` (the head text). */
|
|
32336
|
+
async compose(output, existingObservations, baseRecord) {
|
|
32337
|
+
const { threadId, messages } = this.opts;
|
|
32338
|
+
const lastObservedAt = this.getMaxMessageTimestamp(messages);
|
|
32339
|
+
const messageRange = this.retrieval ? buildMessageRange(messages) : void 0;
|
|
32340
|
+
const newObservations = await this.wrapObservations(output.observations, existingObservations, threadId, lastObservedAt, messageRange);
|
|
32341
|
+
const observationTokens = this.tokenCounter.countObservations(newObservations);
|
|
32342
|
+
const cycleObservationTokens = this.tokenCounter.countObservations(output.observations);
|
|
32343
|
+
const newMessageIds = messages.map((m) => m.id);
|
|
32344
|
+
const existingIds = baseRecord.observedMessageIds ?? [];
|
|
31879
32345
|
return {
|
|
31880
32346
|
observations: newObservations,
|
|
31881
32347
|
observationTokens,
|
|
31882
32348
|
cycleObservationTokens,
|
|
31883
|
-
observedMessageIds,
|
|
32349
|
+
observedMessageIds: [.../* @__PURE__ */ new Set([...Array.isArray(existingIds) ? existingIds : [], ...newMessageIds])],
|
|
31884
32350
|
lastObservedAt,
|
|
31885
32351
|
suggestedContinuation: output.suggestedContinuation,
|
|
31886
32352
|
currentTask: output.currentTask,
|
|
@@ -31892,12 +32358,27 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
31892
32358
|
}
|
|
31893
32359
|
async persist(processed) {
|
|
31894
32360
|
const { record, threadId, resourceId, messages } = this.opts;
|
|
31895
|
-
const
|
|
31896
|
-
if (!
|
|
32361
|
+
const liveRecord = await getLineageHead(this.storage, record);
|
|
32362
|
+
if (!liveRecord) {
|
|
31897
32363
|
omDebug(`[OM:sync-obs] skipping persist for thread ${threadId}: observational memory record is gone`);
|
|
31898
|
-
return
|
|
32364
|
+
return {
|
|
32365
|
+
status: "not-committed",
|
|
32366
|
+
reason: "the observational memory record was cleared"
|
|
32367
|
+
};
|
|
31899
32368
|
}
|
|
31900
|
-
|
|
32369
|
+
const committed = await this.commitActiveObservationsToHead({
|
|
32370
|
+
processed,
|
|
32371
|
+
composedFrom: this.composedFrom,
|
|
32372
|
+
target: liveRecord,
|
|
32373
|
+
recompose: (head) => this.compose(this.observerResult, head.activeObservations ?? "", head),
|
|
32374
|
+
cycleMessageIds: messages.map((m) => m.id)
|
|
32375
|
+
});
|
|
32376
|
+
if (!committed) return {
|
|
32377
|
+
status: "not-committed",
|
|
32378
|
+
reason: "the observational memory head kept changing during the commit"
|
|
32379
|
+
};
|
|
32380
|
+
processed = committed.processed;
|
|
32381
|
+
const thread = committed.alreadyCovered ? null : await this.storage.getThreadById({ threadId });
|
|
31901
32382
|
if (thread) {
|
|
31902
32383
|
const oldTitle = thread.title?.trim();
|
|
31903
32384
|
const newTitle = resolveThreadTitleUpdate(thread, processed.threadTitle);
|
|
@@ -31919,23 +32400,20 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
31919
32400
|
...shouldUpdateThreadTitle ? { title: newTitle } : {},
|
|
31920
32401
|
metadata: newMetadata
|
|
31921
32402
|
});
|
|
31922
|
-
if (shouldUpdateThreadTitle)
|
|
32403
|
+
if (shouldUpdateThreadTitle) await this.streamMarker(createThreadUpdateMarker({
|
|
31923
32404
|
cycleId: this.cycleId ?? crypto.randomUUID(),
|
|
31924
32405
|
threadId,
|
|
31925
32406
|
oldTitle,
|
|
31926
32407
|
newTitle
|
|
31927
|
-
});
|
|
32408
|
+
}));
|
|
31928
32409
|
}
|
|
31929
|
-
if (
|
|
31930
|
-
|
|
31931
|
-
|
|
31932
|
-
|
|
31933
|
-
|
|
31934
|
-
|
|
31935
|
-
|
|
31936
|
-
});
|
|
31937
|
-
await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, record.id);
|
|
31938
|
-
return true;
|
|
32410
|
+
if (!committed.alreadyCovered) await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, committed.record.id);
|
|
32411
|
+
return {
|
|
32412
|
+
status: "committed",
|
|
32413
|
+
processed,
|
|
32414
|
+
record: committed.record,
|
|
32415
|
+
alreadyCovered: committed.alreadyCovered
|
|
32416
|
+
};
|
|
31939
32417
|
}
|
|
31940
32418
|
async emitEndMarkers(cycleId, processed) {
|
|
31941
32419
|
const actualTokensObserved = await this.tokenCounter.countMessagesAsync(this.opts.messages);
|
|
@@ -31977,6 +32455,8 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
31977
32455
|
startedAt;
|
|
31978
32456
|
cycleId;
|
|
31979
32457
|
priorExtractedValues;
|
|
32458
|
+
/** The generation the chunk was written to (the head when the target was retired). */
|
|
32459
|
+
persistedRecordId;
|
|
31980
32460
|
constructor(deps, opts) {
|
|
31981
32461
|
super(deps, opts);
|
|
31982
32462
|
this.cycleId = opts.cycleId;
|
|
@@ -32003,12 +32483,13 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
32003
32483
|
};
|
|
32004
32484
|
}
|
|
32005
32485
|
async emitStartMarkers(_cycleId) {}
|
|
32006
|
-
async observe(
|
|
32486
|
+
async observe(observerContext, messages, wasTruncated) {
|
|
32007
32487
|
const thread = await this.storage.getThreadById({ threadId: this.opts.threadId });
|
|
32008
32488
|
const omMeta = thread ? (0, _mastra_core_memory.getThreadOMMetadata)(thread.metadata) : void 0;
|
|
32009
32489
|
this.priorExtractedValues = getPriorExtractedValues(omMeta, this.observationConfig.extractors);
|
|
32010
|
-
const result = await this.deps.observer.call(
|
|
32490
|
+
const result = await this.deps.observer.call(observerContext, messages, void 0, {
|
|
32011
32491
|
skipContinuationHints: true,
|
|
32492
|
+
wasTruncated,
|
|
32012
32493
|
requestContext: this.opts.requestContext,
|
|
32013
32494
|
observabilityContext: this.opts.observabilityContext,
|
|
32014
32495
|
priorExtractedValues: this.priorExtractedValues,
|
|
@@ -32082,71 +32563,124 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
32082
32563
|
};
|
|
32083
32564
|
}
|
|
32084
32565
|
async persist(processed) {
|
|
32085
|
-
if (!processed.observations) return
|
|
32566
|
+
if (!processed.observations) return;
|
|
32086
32567
|
const { record, threadId, resourceId, messages } = this.opts;
|
|
32087
|
-
|
|
32568
|
+
const liveRecord = await getLineageHead(this.storage, record);
|
|
32569
|
+
if (!liveRecord) {
|
|
32088
32570
|
omDebug(`[OM:asyncBuffer] skipping persist for thread ${threadId}: observational memory record is gone`);
|
|
32089
|
-
return
|
|
32571
|
+
return {
|
|
32572
|
+
status: "not-committed",
|
|
32573
|
+
reason: "the observational memory record was cleared"
|
|
32574
|
+
};
|
|
32090
32575
|
}
|
|
32091
32576
|
const messageTokens = await this.tokenCounter.countMessagesAsync(messages);
|
|
32092
|
-
|
|
32093
|
-
|
|
32094
|
-
|
|
32095
|
-
|
|
32096
|
-
|
|
32097
|
-
|
|
32098
|
-
|
|
32099
|
-
|
|
32100
|
-
|
|
32101
|
-
|
|
32102
|
-
|
|
32103
|
-
|
|
32104
|
-
|
|
32105
|
-
|
|
32106
|
-
|
|
32107
|
-
|
|
32108
|
-
|
|
32577
|
+
let appendAttempts = 0;
|
|
32578
|
+
let targetId = record.id;
|
|
32579
|
+
const appendResult = await withRetry(() => {
|
|
32580
|
+
appendAttempts++;
|
|
32581
|
+
return this.runCommit(async () => {
|
|
32582
|
+
const head = await getLineageHead(this.storage, record);
|
|
32583
|
+
if (!head) return null;
|
|
32584
|
+
targetId = head.id;
|
|
32585
|
+
const input = {
|
|
32586
|
+
id: targetId,
|
|
32587
|
+
chunk: {
|
|
32588
|
+
cycleId: this.cycleId,
|
|
32589
|
+
observations: processed.observations,
|
|
32590
|
+
tokenCount: processed.observationTokens,
|
|
32591
|
+
messageIds: processed.observedMessageIds,
|
|
32592
|
+
messageTokens,
|
|
32593
|
+
lastObservedAt: processed.lastObservedAt,
|
|
32594
|
+
suggestedContinuation: processed.suggestedContinuation,
|
|
32595
|
+
currentTask: processed.currentTask,
|
|
32596
|
+
threadTitle: processed.threadTitle,
|
|
32597
|
+
extractedValues: processed.extractedValues,
|
|
32598
|
+
extractionFailures: processed.extractionFailures
|
|
32599
|
+
},
|
|
32600
|
+
lastBufferedAtTime: processed.lastObservedAt
|
|
32601
|
+
};
|
|
32602
|
+
return typeof this.storage.appendBufferedObservations === "function" ? this.storage.appendBufferedObservations(input) : this.storage.updateBufferedObservations(input).then(() => ({
|
|
32603
|
+
persisted: true,
|
|
32604
|
+
recordId: input.id
|
|
32605
|
+
}));
|
|
32606
|
+
});
|
|
32607
|
+
}, {
|
|
32109
32608
|
label: "persist-buffered-observations",
|
|
32110
32609
|
abortSignal: this.opts.abortSignal
|
|
32111
32610
|
});
|
|
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
|
-
|
|
32611
|
+
if (appendResult === null) {
|
|
32612
|
+
omDebug(`[OM:asyncBuffer] skipping persist for thread ${threadId}: observational memory record was cleared`);
|
|
32613
|
+
return {
|
|
32614
|
+
status: "not-committed",
|
|
32615
|
+
reason: "the observational memory record was cleared"
|
|
32616
|
+
};
|
|
32617
|
+
}
|
|
32618
|
+
let committedRecord = liveRecord;
|
|
32619
|
+
if (!appendResult.persisted) {
|
|
32620
|
+
const head = appendAttempts > 1 ? await getLineageHead(this.storage, record) : null;
|
|
32621
|
+
if (!(!!head && (getBufferedChunks(head).some((chunk) => chunk.cycleId === this.cycleId) || head.activeObservations.includes(processed.observations)))) return {
|
|
32622
|
+
status: "not-committed",
|
|
32623
|
+
reason: "the buffered chunk was already observed"
|
|
32624
|
+
};
|
|
32625
|
+
this.persistedRecordId = head.id;
|
|
32626
|
+
committedRecord = head;
|
|
32627
|
+
} else this.persistedRecordId = appendResult.recordId;
|
|
32628
|
+
await this.emitBufferingEndMarker(processed);
|
|
32629
|
+
await this.opts.onBufferedChunkPersisted?.();
|
|
32630
|
+
if (committedRecord.id !== this.persistedRecordId) {
|
|
32631
|
+
const landedOn = await getLineageRecord(this.storage, record, this.persistedRecordId);
|
|
32632
|
+
if (!landedOn) return {
|
|
32633
|
+
status: "not-committed",
|
|
32634
|
+
reason: "the observational memory record was cleared"
|
|
32635
|
+
};
|
|
32636
|
+
committedRecord = landedOn;
|
|
32637
|
+
}
|
|
32638
|
+
try {
|
|
32639
|
+
await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, this.persistedRecordId);
|
|
32640
|
+
const candidateTitle = processed.threadTitle?.trim();
|
|
32641
|
+
const hasValidThreadTitle = !!candidateTitle && candidateTitle.length >= 3;
|
|
32642
|
+
if (hasValidThreadTitle || processed.extractedValues) {
|
|
32643
|
+
const thread = await this.storage.getThreadById({ threadId });
|
|
32644
|
+
if (thread) {
|
|
32645
|
+
const oldTitle = thread.title?.trim();
|
|
32646
|
+
const newTitle = resolveThreadTitleUpdate(thread, candidateTitle);
|
|
32647
|
+
const shouldUpdateThreadTitle = newTitle !== void 0;
|
|
32648
|
+
const previousOmMetadata = (0, _mastra_core_memory.getThreadOMMetadata)(thread.metadata);
|
|
32649
|
+
const metadataUpdate = buildThreadMetadataFromExtractedValues(processed.extractors ?? this.observationConfig.extractors, processed.extractedValues);
|
|
32650
|
+
const newMetadata = (0, _mastra_core_memory.setThreadOMMetadata)(thread.metadata, {
|
|
32651
|
+
...hasValidThreadTitle || metadataUpdate.threadTitle ? { threadTitle: metadataUpdate.threadTitle ?? processed.threadTitle } : {},
|
|
32652
|
+
extracted: {
|
|
32653
|
+
...previousOmMetadata?.extracted ?? {},
|
|
32654
|
+
...metadataUpdate.extracted ?? {}
|
|
32655
|
+
}
|
|
32656
|
+
});
|
|
32657
|
+
await this.storage.patchThread({
|
|
32658
|
+
id: threadId,
|
|
32659
|
+
...shouldUpdateThreadTitle ? { title: newTitle } : {},
|
|
32660
|
+
metadata: newMetadata
|
|
32141
32661
|
});
|
|
32142
|
-
|
|
32662
|
+
if (shouldUpdateThreadTitle) {
|
|
32663
|
+
const marker = createThreadUpdateMarker({
|
|
32664
|
+
cycleId: this.cycleId,
|
|
32665
|
+
threadId,
|
|
32666
|
+
oldTitle,
|
|
32667
|
+
newTitle
|
|
32668
|
+
});
|
|
32669
|
+
await this.streamMarker(marker);
|
|
32670
|
+
}
|
|
32143
32671
|
}
|
|
32144
32672
|
}
|
|
32673
|
+
} catch (error) {
|
|
32674
|
+
omError("[OM] Post-buffer indexing or thread update failed; the buffered chunk is kept", error);
|
|
32145
32675
|
}
|
|
32146
|
-
return
|
|
32676
|
+
return {
|
|
32677
|
+
status: "committed",
|
|
32678
|
+
processed,
|
|
32679
|
+
record: committedRecord
|
|
32680
|
+
};
|
|
32147
32681
|
}
|
|
32148
|
-
async emitEndMarkers(
|
|
32149
|
-
|
|
32682
|
+
async emitEndMarkers() {}
|
|
32683
|
+
async emitBufferingEndMarker(processed) {
|
|
32150
32684
|
const { record, threadId, messages } = this.opts;
|
|
32151
32685
|
const tokensBuffered = await this.tokenCounter.countMessagesAsync(messages);
|
|
32152
32686
|
const totalBufferedTokens = getBufferedChunks(await this.storage.getObservationalMemory(record.threadId, record.resourceId)).reduce((sum, c) => sum + (c.tokenCount ?? 0), 0) || processed.observationTokens;
|
|
@@ -32156,7 +32690,7 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
32156
32690
|
startedAt: this.startedAt,
|
|
32157
32691
|
tokensBuffered,
|
|
32158
32692
|
bufferedTokens: totalBufferedTokens,
|
|
32159
|
-
recordId: record.id,
|
|
32693
|
+
recordId: this.persistedRecordId ?? record.id,
|
|
32160
32694
|
threadId,
|
|
32161
32695
|
observations: processed.observations,
|
|
32162
32696
|
extractedValues: processed.extractedValues,
|
|
@@ -32166,7 +32700,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
32166
32700
|
...endMarker,
|
|
32167
32701
|
transient: true
|
|
32168
32702
|
}).catch(() => {});
|
|
32169
|
-
await this.persistMarkerToStorage(endMarker, threadId, record.resourceId ?? void 0
|
|
32703
|
+
await this.persistMarkerToStorage(endMarker, threadId, record.resourceId ?? void 0, {
|
|
32704
|
+
notAfter: new Date(this.startedAt),
|
|
32705
|
+
onCycleStartMessage: true
|
|
32706
|
+
});
|
|
32170
32707
|
}
|
|
32171
32708
|
async emitFailedMarkers(_cycleId, error) {
|
|
32172
32709
|
const { record, threadId, messages } = this.opts;
|
|
@@ -32185,7 +32722,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
32185
32722
|
...failedMarker,
|
|
32186
32723
|
transient: true
|
|
32187
32724
|
}).catch(() => {});
|
|
32188
|
-
await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0
|
|
32725
|
+
await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0, {
|
|
32726
|
+
notAfter: new Date(this.startedAt),
|
|
32727
|
+
onCycleStartMessage: true
|
|
32728
|
+
});
|
|
32189
32729
|
}
|
|
32190
32730
|
};
|
|
32191
32731
|
var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
@@ -32206,10 +32746,17 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32206
32746
|
lastBatchProviderMetadata;
|
|
32207
32747
|
observationResults = [];
|
|
32208
32748
|
priorMetadataByThread = /* @__PURE__ */ new Map();
|
|
32749
|
+
/** Each observed thread's wrapped section from the last composition. */
|
|
32750
|
+
threadSections = /* @__PURE__ */ new Map();
|
|
32751
|
+
/** Head text the processed observations were composed from (the commit's expected text). */
|
|
32752
|
+
composedFrom = "";
|
|
32209
32753
|
constructor(deps, opts) {
|
|
32210
32754
|
super(deps, opts);
|
|
32211
32755
|
this.resourceId = opts.resourceId;
|
|
32212
32756
|
}
|
|
32757
|
+
getObservedMessagesForThread(threadId) {
|
|
32758
|
+
return this.threadsWithMessages.get(threadId);
|
|
32759
|
+
}
|
|
32213
32760
|
get needsLock() {
|
|
32214
32761
|
return true;
|
|
32215
32762
|
}
|
|
@@ -32296,10 +32843,12 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32296
32843
|
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
32297
32844
|
}))
|
|
32298
32845
|
});
|
|
32299
|
-
const
|
|
32846
|
+
const freshRecord = await this.storage.getObservationalMemory(null, this.resourceId);
|
|
32847
|
+
const existingObservations = freshRecord?.activeObservations ?? record.activeObservations ?? "";
|
|
32300
32848
|
return {
|
|
32301
32849
|
messages: Array.from(this.threadsWithMessages.values()).flat(),
|
|
32302
|
-
existingObservations
|
|
32850
|
+
existingObservations,
|
|
32851
|
+
contextRecord: freshRecord
|
|
32303
32852
|
};
|
|
32304
32853
|
}
|
|
32305
32854
|
async emitStartMarkers(cycleId) {
|
|
@@ -32323,7 +32872,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32323
32872
|
}
|
|
32324
32873
|
}
|
|
32325
32874
|
}
|
|
32326
|
-
async observe(
|
|
32875
|
+
async observe(observerContext, _messages, wasTruncated) {
|
|
32327
32876
|
const maxTokensPerBatch = this.observationConfig.maxTokensPerBatch ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.maxTokensPerBatch;
|
|
32328
32877
|
const orderedThreadIds = this.threadOrder.filter((tid) => this.threadsWithMessages.has(tid));
|
|
32329
32878
|
const batches = [];
|
|
@@ -32349,10 +32898,10 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32349
32898
|
}
|
|
32350
32899
|
if (currentBatch.threadIds.length > 0) batches.push(currentBatch);
|
|
32351
32900
|
const batchResults = await Promise.all(batches.map(async (batch) => {
|
|
32352
|
-
return this.deps.observer.callMultiThread(
|
|
32901
|
+
return this.deps.observer.callMultiThread(observerContext, batch.threadMap, batch.threadIds, this.opts.abortSignal, this.opts.requestContext, this.priorMetadataByThread, this.opts.observabilityContext, void 0, {
|
|
32353
32902
|
resourceId: this.opts.resourceId,
|
|
32354
32903
|
trigger: this.opts.trigger
|
|
32355
|
-
}, this.opts.record.observedTimezone, this.opts.agent, this.opts.currentModel);
|
|
32904
|
+
}, this.opts.record.observedTimezone, this.opts.agent, this.opts.currentModel, wasTruncated);
|
|
32356
32905
|
}));
|
|
32357
32906
|
for (const batchResult of batchResults) {
|
|
32358
32907
|
for (const [threadId, result] of batchResult.results) this.multiThreadResults.set(threadId, result);
|
|
@@ -32419,14 +32968,38 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32419
32968
|
}
|
|
32420
32969
|
});
|
|
32421
32970
|
}
|
|
32971
|
+
this.composedFrom = existingObservations;
|
|
32972
|
+
const processed = await this.compose(existingObservations, record);
|
|
32973
|
+
for (const [index, { threadId, threadMessages, result }] of this.observationResults.entries()) this.deps.emitDebugEvent({
|
|
32974
|
+
type: "observation_complete",
|
|
32975
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
32976
|
+
threadId,
|
|
32977
|
+
resourceId: this.resourceId,
|
|
32978
|
+
observations: this.threadSections.get(threadId) ?? "",
|
|
32979
|
+
rawObserverOutput: result.observations,
|
|
32980
|
+
previousObservations: record.activeObservations,
|
|
32981
|
+
messages: threadMessages.map((m) => ({
|
|
32982
|
+
role: m.role,
|
|
32983
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
32984
|
+
})),
|
|
32985
|
+
usage: index === 0 && this.totalBatchUsage.totalTokens > 0 ? this.totalBatchUsage : void 0
|
|
32986
|
+
});
|
|
32987
|
+
return processed;
|
|
32988
|
+
}
|
|
32989
|
+
/**
|
|
32990
|
+
* Merge every observed thread's section into `existingObservations` (the head text). Resource
|
|
32991
|
+
* scope rewrites an existing same-day `<thread>` section in place, so the result is not an
|
|
32992
|
+
* append of the head text.
|
|
32993
|
+
*/
|
|
32994
|
+
async compose(existingObservations, baseRecord) {
|
|
32422
32995
|
let currentObservations = existingObservations;
|
|
32423
32996
|
let cycleObservationTokens = 0;
|
|
32424
32997
|
const threadMetadataUpdates = [];
|
|
32425
|
-
for (const
|
|
32426
|
-
const { threadId, threadMessages, result } = obsResult;
|
|
32998
|
+
for (const { threadId, threadMessages, result } of this.observationResults) {
|
|
32427
32999
|
cycleObservationTokens += this.tokenCounter.countObservations(result.observations);
|
|
32428
33000
|
const messageRange = this.retrieval ? buildMessageRange(threadMessages) : void 0;
|
|
32429
33001
|
const threadSection = await this.wrapWithThreadTag(threadId, result.observations, messageRange);
|
|
33002
|
+
this.threadSections.set(threadId, threadSection);
|
|
32430
33003
|
const threadLastObservedAt = this.getMaxMessageTimestamp(threadMessages);
|
|
32431
33004
|
currentObservations = this.replaceOrAppendThreadSection(currentObservations, threadId, threadSection, threadLastObservedAt);
|
|
32432
33005
|
threadMetadataUpdates.push({
|
|
@@ -32440,26 +33013,11 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32440
33013
|
extractors: result.extractors,
|
|
32441
33014
|
lastObservedMessageCursor: getLastObservedMessageCursor(threadMessages)
|
|
32442
33015
|
});
|
|
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
33016
|
}
|
|
32459
33017
|
const observedMessages = this.observationResults.flatMap((r) => r.threadMessages);
|
|
32460
33018
|
const lastObservedAt = this.getMaxMessageTimestamp(observedMessages);
|
|
32461
33019
|
const newMessageIds = observedMessages.map((m) => m.id);
|
|
32462
|
-
const existingIds =
|
|
33020
|
+
const existingIds = baseRecord.observedMessageIds ?? [];
|
|
32463
33021
|
const observedMessageIds = [.../* @__PURE__ */ new Set([...existingIds, ...newMessageIds])];
|
|
32464
33022
|
const observationTokens = this.tokenCounter.countObservations(currentObservations);
|
|
32465
33023
|
return {
|
|
@@ -32473,8 +33031,25 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32473
33031
|
}
|
|
32474
33032
|
async persist(processed) {
|
|
32475
33033
|
const { record, resourceId } = this.opts;
|
|
33034
|
+
const head = await getLineageHead(this.storage, record);
|
|
33035
|
+
if (!head) return {
|
|
33036
|
+
status: "not-committed",
|
|
33037
|
+
reason: "the observational memory record was cleared"
|
|
33038
|
+
};
|
|
33039
|
+
const committed = await this.commitActiveObservationsToHead({
|
|
33040
|
+
processed,
|
|
33041
|
+
composedFrom: this.composedFrom,
|
|
33042
|
+
target: head,
|
|
33043
|
+
recompose: (fresh) => this.compose(fresh.activeObservations ?? "", fresh),
|
|
33044
|
+
cycleMessageIds: this.observationResults.flatMap((r) => r.threadMessages.map((m) => m.id))
|
|
33045
|
+
});
|
|
33046
|
+
if (!committed) return {
|
|
33047
|
+
status: "not-committed",
|
|
33048
|
+
reason: "the observational memory head kept changing during the commit"
|
|
33049
|
+
};
|
|
33050
|
+
processed = committed.processed;
|
|
32476
33051
|
const threadUpdateMarkers = [];
|
|
32477
|
-
if (processed.threadMetadataUpdates) for (const update of processed.threadMetadataUpdates) {
|
|
33052
|
+
if (processed.threadMetadataUpdates && !committed.alreadyCovered) for (const update of processed.threadMetadataUpdates) {
|
|
32478
33053
|
const thread = await this.storage.getThreadById({ threadId: update.threadId });
|
|
32479
33054
|
if (thread) {
|
|
32480
33055
|
const oldTitle = thread.title?.trim();
|
|
@@ -32507,15 +33082,13 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
32507
33082
|
}
|
|
32508
33083
|
}
|
|
32509
33084
|
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;
|
|
33085
|
+
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)));
|
|
33086
|
+
return {
|
|
33087
|
+
status: "committed",
|
|
33088
|
+
processed,
|
|
33089
|
+
record: committed.record,
|
|
33090
|
+
alreadyCovered: committed.alreadyCovered
|
|
33091
|
+
};
|
|
32519
33092
|
}
|
|
32520
33093
|
async emitEndMarkers(cycleId, processed) {
|
|
32521
33094
|
for (const obsResult of this.observationResults) {
|
|
@@ -32572,7 +33145,8 @@ ObservationStrategy.create = ((om, opts) => {
|
|
|
32572
33145
|
observedMessageIds: om.observedMessageIds,
|
|
32573
33146
|
obscureThreadIds: om.getObscureThreadIds(),
|
|
32574
33147
|
onIndexObservations: om.onIndexObservations,
|
|
32575
|
-
emitDebugEvent: (e) => om.emitDebugEvent(e)
|
|
33148
|
+
emitDebugEvent: (e) => om.emitDebugEvent(e),
|
|
33149
|
+
prepareObserverContext: (observations, record) => om.prepareObserverContext(observations, record)
|
|
32576
33150
|
};
|
|
32577
33151
|
if (opts.cycleId) return new AsyncBufferObservationStrategy(deps, opts);
|
|
32578
33152
|
if (deps.scope === "resource" && opts.resourceId) return new ResourceScopedObservationStrategy(deps, opts);
|
|
@@ -32698,8 +33272,8 @@ var ObservationStep = class {
|
|
|
32698
33272
|
let reflected = false;
|
|
32699
33273
|
let didThresholdCleanup = false;
|
|
32700
33274
|
let observerExchange;
|
|
33275
|
+
const step0Messages = this.stepNumber === 0 ? getObservableMessages(messageList) : [];
|
|
32701
33276
|
if (this.stepNumber === 0) {
|
|
32702
|
-
const step0Messages = getObservableMessages(messageList);
|
|
32703
33277
|
const activation = await om.activate({
|
|
32704
33278
|
threadId,
|
|
32705
33279
|
resourceId,
|
|
@@ -32756,7 +33330,17 @@ var ObservationStep = class {
|
|
|
32756
33330
|
record: this.turn.record,
|
|
32757
33331
|
messages: getObservableMessages(messageList)
|
|
32758
33332
|
});
|
|
32759
|
-
if (statusSnapshot.
|
|
33333
|
+
if (statusSnapshot.inAsyncObservationBand && !statusSnapshot.canActivate && !hasIncompleteToolCalls) {
|
|
33334
|
+
await this.turn.refreshRecord();
|
|
33335
|
+
statusSnapshot = await om.getStatus({
|
|
33336
|
+
threadId,
|
|
33337
|
+
resourceId,
|
|
33338
|
+
record: this.turn.record,
|
|
33339
|
+
messages: getObservableMessages(messageList)
|
|
33340
|
+
});
|
|
33341
|
+
}
|
|
33342
|
+
const activateBeforeBuffering = statusSnapshot.inAsyncObservationBand && statusSnapshot.canActivate && !hasIncompleteToolCalls;
|
|
33343
|
+
if (statusSnapshot.shouldBuffer && !activateBeforeBuffering) {
|
|
32760
33344
|
const allMessages = getObservableMessages(messageList);
|
|
32761
33345
|
const unobservedMessages = om.getUnobservedMessages(allMessages, statusSnapshot.record);
|
|
32762
33346
|
const candidates = om.getUnobservedMessages(unobservedMessages, statusSnapshot.record, { excludeBuffered: true });
|
|
@@ -32770,8 +33354,10 @@ var ObservationStep = class {
|
|
|
32770
33354
|
omDebug(`[OM:buffer] onBufferChunkSealed hook failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
32771
33355
|
}
|
|
32772
33356
|
if (this.turn.memory) await this.turn.memory.persistMessages(safeCandidates);
|
|
32773
|
-
messageList.
|
|
32774
|
-
|
|
33357
|
+
const inputIds = new Set(messageList.get.input.db().map((msg) => msg.id));
|
|
33358
|
+
const sealedOutput = safeCandidates.filter((msg) => !inputIds.has(msg.id));
|
|
33359
|
+
messageList.removeByIds(sealedOutput.map((msg) => msg.id));
|
|
33360
|
+
for (const msg of sealedOutput) messageList.add(msg, "memory");
|
|
32775
33361
|
}
|
|
32776
33362
|
if (!deferred) {
|
|
32777
33363
|
om.trackBackgroundWork(om.buffer({
|
|
@@ -32793,7 +33379,8 @@ var ObservationStep = class {
|
|
|
32793
33379
|
buffered = true;
|
|
32794
33380
|
}
|
|
32795
33381
|
}
|
|
32796
|
-
const
|
|
33382
|
+
const bandDefersObservation = statusSnapshot.inAsyncObservationBand && !statusSnapshot.canActivate;
|
|
33383
|
+
const willObserveNow = statusSnapshot.shouldObserve && !hasIncompleteToolCalls && !bandDefersObservation;
|
|
32797
33384
|
/** In-flight message ids the step-0 cleanup must never remove from live context. */
|
|
32798
33385
|
let step0PreserveIds;
|
|
32799
33386
|
if (this.stepNumber > 0 || willObserveNow) {
|
|
@@ -32835,7 +33422,7 @@ var ObservationStep = class {
|
|
|
32835
33422
|
}
|
|
32836
33423
|
if (willObserveNow) {
|
|
32837
33424
|
const preObsGeneration = this.turn.record.generationCount;
|
|
32838
|
-
const obsResult = await this.runThresholdObservation();
|
|
33425
|
+
const obsResult = await this.runThresholdObservation(statusSnapshot.inAsyncObservationBand);
|
|
32839
33426
|
observerExchange = obsResult.observerExchange;
|
|
32840
33427
|
if (obsResult.succeeded) {
|
|
32841
33428
|
observed = true;
|
|
@@ -32908,12 +33495,12 @@ var ObservationStep = class {
|
|
|
32908
33495
|
/**
|
|
32909
33496
|
* Run the full threshold observation pipeline:
|
|
32910
33497
|
* waitForBuffering → re-check → activate buffered chunks → reflect → observe
|
|
32911
|
-
* (sync fallback
|
|
33498
|
+
* (sync fallback at blockAfter when async buffering is enabled)
|
|
32912
33499
|
*/
|
|
32913
|
-
async runThresholdObservation() {
|
|
33500
|
+
async runThresholdObservation(inAsyncObservationBand) {
|
|
32914
33501
|
const { threadId, resourceId, messageList } = this.turn;
|
|
32915
33502
|
const om = this.turn.om;
|
|
32916
|
-
await om.waitForBuffering(threadId, resourceId);
|
|
33503
|
+
if (!inAsyncObservationBand) await om.waitForBuffering(threadId, resourceId);
|
|
32917
33504
|
await this.turn.refreshRecord();
|
|
32918
33505
|
const observableMessages = this.seededResponseMessage ? getObservableMessages(messageList).filter((msg) => msg.id !== this.turn.responseMessageId) : getObservableMessages(messageList);
|
|
32919
33506
|
let status = await om.getStatus({
|
|
@@ -32942,7 +33529,16 @@ var ObservationStep = class {
|
|
|
32942
33529
|
messageList
|
|
32943
33530
|
});
|
|
32944
33531
|
this.turn.setRecord(activation.record);
|
|
32945
|
-
if (!activation.activated)
|
|
33532
|
+
if (!activation.activated) {
|
|
33533
|
+
await this.turn.refreshRecord();
|
|
33534
|
+
status = await om.getStatus({
|
|
33535
|
+
threadId,
|
|
33536
|
+
resourceId,
|
|
33537
|
+
record: this.turn.record,
|
|
33538
|
+
messages: pendingMessages
|
|
33539
|
+
});
|
|
33540
|
+
break;
|
|
33541
|
+
}
|
|
32946
33542
|
activated = true;
|
|
32947
33543
|
const ids = new Set(activation.activatedMessageIds ?? []);
|
|
32948
33544
|
activatedMessageIds.push(...ids);
|
|
@@ -32954,11 +33550,17 @@ var ObservationStep = class {
|
|
|
32954
33550
|
messages: pendingMessages
|
|
32955
33551
|
});
|
|
32956
33552
|
}
|
|
33553
|
+
if (status.shouldObserve && status.canActivate) return {
|
|
33554
|
+
succeeded: activated,
|
|
33555
|
+
record: this.turn.record,
|
|
33556
|
+
activatedMessageIds: activated ? activatedMessageIds : void 0,
|
|
33557
|
+
cleanupMessageIds: activatedMessageIds
|
|
33558
|
+
};
|
|
32957
33559
|
if (activated) {
|
|
32958
|
-
const
|
|
33560
|
+
const preReflectionRecord = this.turn.record;
|
|
32959
33561
|
await om.reflector.maybeReflect({
|
|
32960
|
-
record:
|
|
32961
|
-
observationTokens:
|
|
33562
|
+
record: preReflectionRecord,
|
|
33563
|
+
observationTokens: preReflectionRecord.observationTokenCount ?? 0,
|
|
32962
33564
|
threadId,
|
|
32963
33565
|
writer: this.turn.writer,
|
|
32964
33566
|
messageList,
|
|
@@ -32973,6 +33575,14 @@ var ObservationStep = class {
|
|
|
32973
33575
|
}),
|
|
32974
33576
|
trigger: "turn-sync"
|
|
32975
33577
|
});
|
|
33578
|
+
await this.turn.refreshRecord();
|
|
33579
|
+
const postActivationRecord = this.turn.record;
|
|
33580
|
+
status = await om.getStatus({
|
|
33581
|
+
threadId,
|
|
33582
|
+
resourceId,
|
|
33583
|
+
record: postActivationRecord,
|
|
33584
|
+
messages: pendingMessages
|
|
33585
|
+
});
|
|
32976
33586
|
if (!status.shouldObserve) return {
|
|
32977
33587
|
succeeded: true,
|
|
32978
33588
|
record: postActivationRecord,
|
|
@@ -32980,6 +33590,12 @@ var ObservationStep = class {
|
|
|
32980
33590
|
cleanupMessageIds: activatedMessageIds
|
|
32981
33591
|
};
|
|
32982
33592
|
}
|
|
33593
|
+
if (status.inAsyncObservationBand || status.canActivate) return {
|
|
33594
|
+
succeeded: activated,
|
|
33595
|
+
record: this.turn.record,
|
|
33596
|
+
activatedMessageIds: activated ? activatedMessageIds : void 0,
|
|
33597
|
+
cleanupMessageIds: activatedMessageIds
|
|
33598
|
+
};
|
|
32983
33599
|
let obsResult;
|
|
32984
33600
|
try {
|
|
32985
33601
|
obsResult = await om.observe({
|
|
@@ -33327,13 +33943,18 @@ Take the existing observations and rewrite them to make it easier to continue in
|
|
|
33327
33943
|
|
|
33328
33944
|
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
33945
|
|
|
33946
|
+
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.
|
|
33947
|
+
|
|
33330
33948
|
When consolidating observations:
|
|
33331
|
-
-
|
|
33332
|
-
-
|
|
33949
|
+
- Keep every concrete fact: names, places, dates, numbers, amounts, durations, titles, versions, and who said what. Merge duplicates, but never drop specifics
|
|
33950
|
+
- Keep every dated statement with its date, and retain the timestamps that matter (start times, completion times, significant events)
|
|
33951
|
+
- 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
|
|
33952
|
+
- 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
|
|
33953
|
+
- Prefer many short factual lines over prose summaries. Do not replace facts with generalities like "discussed travel plans"
|
|
33333
33954
|
- Combine related items where it makes sense (e.g., "agent called view tool 5 times on file x")
|
|
33334
33955
|
- Preserve ✅ completion markers — they are memory signals that tell the assistant what is already resolved and help prevent repeated work
|
|
33335
33956
|
- Preserve the concrete resolved outcome captured by ✅ markers so the assistant knows what exactly is done
|
|
33336
|
-
- Condense older observations more
|
|
33957
|
+
- Condense the wording of older observations more than recent ones, but keep their facts
|
|
33337
33958
|
|
|
33338
33959
|
CRITICAL: USER ASSERTIONS vs QUESTIONS
|
|
33339
33960
|
- "User stated: X" = authoritative assertion (user told us something about themselves)
|
|
@@ -33613,6 +34234,25 @@ async function withAbortCheck(fn, abortSignal) {
|
|
|
33613
34234
|
* size while still letting them fire sooner than a threshold activation.
|
|
33614
34235
|
*/
|
|
33615
34236
|
const EARLY_ACTIVATION_SIZE_FLOOR_RATIO = .75;
|
|
34237
|
+
/** A reflection commit that created nothing because the reflected record is no longer the head. */
|
|
34238
|
+
var ReflectionNotAppliedError = class extends Error {
|
|
34239
|
+
constructor(recordId, headId) {
|
|
34240
|
+
super(`Reflection not applied: record ${recordId} is no longer the current generation (head is ${headId})`);
|
|
34241
|
+
this.name = "ReflectionNotAppliedError";
|
|
34242
|
+
}
|
|
34243
|
+
};
|
|
34244
|
+
/**
|
|
34245
|
+
* Whether a reflection commit created the generation the caller asked for. Storage creates
|
|
34246
|
+
* nothing (and returns the current head or the stored record) when the reflected record was
|
|
34247
|
+
* already retired or its observations were rewritten since the reflection read them.
|
|
34248
|
+
*
|
|
34249
|
+
* Adapters that predate `newRecordId` ignore it and do not report `supersededBy`; for those a
|
|
34250
|
+
* returned record other than the reflected one means a new generation was created.
|
|
34251
|
+
*/
|
|
34252
|
+
function isReflectionApplied(returned, newRecordId, reflectedRecordId) {
|
|
34253
|
+
if (returned.id === newRecordId) return true;
|
|
34254
|
+
return returned.supersededBy === void 0 && returned.id !== reflectedRecordId;
|
|
34255
|
+
}
|
|
33616
34256
|
/**
|
|
33617
34257
|
* Runs the Reflector agent for compressing observations.
|
|
33618
34258
|
* Handles synchronous reflection, async buffered reflection, and activation.
|
|
@@ -33976,14 +34616,14 @@ var ReflectorRunner = class {
|
|
|
33976
34616
|
* Perform async buffered reflection — reflects observations and stores to bufferedReflection.
|
|
33977
34617
|
* Does NOT create a new generation or update activeObservations.
|
|
33978
34618
|
*/
|
|
33979
|
-
async doAsyncBufferedReflection(record,
|
|
34619
|
+
async doAsyncBufferedReflection(record, bufferKey, writer, requestContext, observabilityContext, priorExtractedValues, mainAgent, sendSignal, trigger, currentModel) {
|
|
33980
34620
|
const currentRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record;
|
|
33981
34621
|
const observationTokens = currentRecord.observationTokenCount ?? 0;
|
|
33982
34622
|
const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(currentRecord));
|
|
33983
34623
|
const bufferActivation = this.reflectionConfig.bufferActivation ?? .5;
|
|
33984
34624
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
33985
34625
|
const cycleId = `reflect-buf-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
33986
|
-
BufferingCoordinator.reflectionBufferCycleIds.set(
|
|
34626
|
+
BufferingCoordinator.reflectionBufferCycleIds.set(bufferKey, cycleId);
|
|
33987
34627
|
const allLines = (currentRecord.activeObservations ?? "").split("\n");
|
|
33988
34628
|
const totalLines = allLines.length;
|
|
33989
34629
|
const avgTokensPerLine = totalLines > 0 ? observationTokens / totalLines : 0;
|
|
@@ -34022,13 +34662,24 @@ var ReflectorRunner = class {
|
|
|
34022
34662
|
await persistThreadExtractedValues(this.storage, this.reflectionConfig.extractors, currentRecord.threadId ?? void 0, reflectResult.extractedValues);
|
|
34023
34663
|
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
34024
34664
|
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
|
-
|
|
34665
|
+
if (!await runOMCommit(this.buffering.getLockKey(currentRecord.threadId, currentRecord.resourceId), async () => {
|
|
34666
|
+
if ((await this.storage.getObservationalMemory(currentRecord.threadId, currentRecord.resourceId))?.id !== currentRecord.id) return false;
|
|
34667
|
+
await this.storage.updateBufferedReflection({
|
|
34668
|
+
id: currentRecord.id,
|
|
34669
|
+
reflection: reflectResult.observations,
|
|
34670
|
+
tokenCount: reflectionTokenCount,
|
|
34671
|
+
inputTokenCount: sliceTokenEstimate,
|
|
34672
|
+
reflectedObservationLineCount
|
|
34673
|
+
});
|
|
34674
|
+
return true;
|
|
34675
|
+
})) {
|
|
34676
|
+
omDebug(`[OM:reflect] doAsyncBufferedReflection: record ${currentRecord.id} is no longer the head; discarding the buffered reflection`);
|
|
34677
|
+
BufferingCoordinator.lastBufferedBoundary.delete(bufferKey);
|
|
34678
|
+
return {
|
|
34679
|
+
usage: reflectResult.usage,
|
|
34680
|
+
providerMetadata: reflectResult.providerMetadata
|
|
34681
|
+
};
|
|
34682
|
+
}
|
|
34032
34683
|
omDebug(`[OM:reflect] doAsyncBufferedReflection: bufferedReflection saved with lineCount=${reflectedObservationLineCount}`);
|
|
34033
34684
|
if (writer) {
|
|
34034
34685
|
const endMarker = createBufferingEndMarker({
|
|
@@ -34107,10 +34758,16 @@ var ReflectorRunner = class {
|
|
|
34107
34758
|
}
|
|
34108
34759
|
}
|
|
34109
34760
|
omDebug(`[OM:reflect] tryActivateBufferedReflection: activating, beforeTokens=${beforeTokens}, combinedTokenCount=${combinedTokenCount}, reflectedLineCount=${reflectedLineCount}, unreflectedLines=${unreflectedLines.length}`);
|
|
34110
|
-
|
|
34761
|
+
const newRecordId = crypto.randomUUID();
|
|
34762
|
+
const swapped = await runOMCommit(lockKey, () => this.storage.swapBufferedReflectionToActive({
|
|
34111
34763
|
currentRecord: freshRecord,
|
|
34112
|
-
tokenCount: combinedTokenCount
|
|
34113
|
-
|
|
34764
|
+
tokenCount: combinedTokenCount,
|
|
34765
|
+
newRecordId
|
|
34766
|
+
}), { priority: "reflection" });
|
|
34767
|
+
if (!isReflectionApplied(swapped, newRecordId, freshRecord.id)) {
|
|
34768
|
+
omDebug(`[OM:reflect] tryActivateBufferedReflection: buffered reflection on ${freshRecord.id} not applied (head is ${swapped.id})`);
|
|
34769
|
+
return { status: "not-applied" };
|
|
34770
|
+
}
|
|
34114
34771
|
if (committedContext) await this.notifyReflectionCommitted({
|
|
34115
34772
|
...committedContext,
|
|
34116
34773
|
observations: allLines.slice(0, reflectedLineCount).join("\n").trim(),
|
|
@@ -34165,7 +34822,8 @@ var ReflectorRunner = class {
|
|
|
34165
34822
|
* @internal Used by observation strategies. Do not call directly.
|
|
34166
34823
|
*/
|
|
34167
34824
|
async maybeReflect(opts) {
|
|
34168
|
-
const { record, observationTokens, writer, abortSignal, mainAgent, sendSignal, sendStateSignal, messageList, currentModel, reflectionHooks, trigger, requestContext, observabilityContext, lastActivityAt, threadId: requestedThreadId } = opts;
|
|
34825
|
+
const { record: callerRecord, observationTokens, writer, abortSignal, mainAgent, sendSignal, sendStateSignal, messageList, currentModel, reflectionHooks, trigger, requestContext, observabilityContext, lastActivityAt, threadId: requestedThreadId } = opts;
|
|
34826
|
+
const record = { ...callerRecord };
|
|
34169
34827
|
const lockKey = this.buffering.getLockKey(record.threadId, record.resourceId);
|
|
34170
34828
|
const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(record));
|
|
34171
34829
|
const priorExtractedValues = await getThreadExtractedValues(this.storage, this.reflectionConfig.extractors, requestedThreadId ?? record.threadId);
|
|
@@ -34219,7 +34877,7 @@ var ReflectorRunner = class {
|
|
|
34219
34877
|
abortSignal,
|
|
34220
34878
|
observabilityContext
|
|
34221
34879
|
});
|
|
34222
|
-
if (activationResult.status === "activated") return;
|
|
34880
|
+
if (activationResult.status === "activated" || activationResult.status === "not-applied") return;
|
|
34223
34881
|
if (activationResult.status === "suppressed") {
|
|
34224
34882
|
omDebug(`[OM:reflect] skipping sync fallback / re-buffer after suppressed early ${activationMetadata.triggeredBy} activation (reason=${activationResult.reason})`);
|
|
34225
34883
|
return;
|
|
@@ -34302,13 +34960,19 @@ var ReflectorRunner = class {
|
|
|
34302
34960
|
reflectResult.observations = await applyTextTransform(this.hooks, "afterReflection", reflectResult.observations, hookContext);
|
|
34303
34961
|
reflectionUsage = reflectResult.usage;
|
|
34304
34962
|
reflectionProviderMetadata = reflectResult.providerMetadata;
|
|
34305
|
-
await persistThreadExtractedValues(this.storage, this.reflectionConfig.extractors, record.threadId ?? void 0, reflectResult.extractedValues);
|
|
34306
34963
|
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
34307
|
-
|
|
34964
|
+
const newRecordId = crypto.randomUUID();
|
|
34965
|
+
const committedRecord = await runOMCommit(this.buffering.getLockKey(record.threadId, record.resourceId), () => this.storage.createReflectionGeneration({
|
|
34308
34966
|
currentRecord: record,
|
|
34309
34967
|
reflection: reflectResult.observations,
|
|
34310
|
-
tokenCount: reflectionTokenCount
|
|
34311
|
-
|
|
34968
|
+
tokenCount: reflectionTokenCount,
|
|
34969
|
+
newRecordId
|
|
34970
|
+
}), { priority: "reflection" });
|
|
34971
|
+
if (!isReflectionApplied(committedRecord, newRecordId, record.id)) {
|
|
34972
|
+
omDebug(`[OM:reflect] reflection of ${record.id} not applied (head is ${committedRecord.id})`);
|
|
34973
|
+
throw new ReflectionNotAppliedError(record.id, committedRecord.id);
|
|
34974
|
+
}
|
|
34975
|
+
await persistThreadExtractedValues(this.storage, this.reflectionConfig.extractors, record.threadId ?? void 0, reflectResult.extractedValues);
|
|
34312
34976
|
if (reflectionTokenCount >= reflectThreshold) this.syncReflectionSuppression.set(lockKey, reflectionTokenCount);
|
|
34313
34977
|
else this.syncReflectionSuppression.delete(lockKey);
|
|
34314
34978
|
await this.notifyReflectionCommitted({
|
|
@@ -34388,6 +35052,7 @@ var ReflectorRunner = class {
|
|
|
34388
35052
|
failureKind: failedMarker.data.failureKind,
|
|
34389
35053
|
error: reflectionError.message
|
|
34390
35054
|
});
|
|
35055
|
+
if (error instanceof ReflectionNotAppliedError) return;
|
|
34391
35056
|
if (lifecycleError !== void 0 || abortSignal?.aborted || this.reflectionConfig.failurePolicy !== "continue" || !isOmModelExecutionError(error) || error.failureKind !== "reflector-model") throw error;
|
|
34392
35057
|
this.syncReflectionSuppression.set(lockKey, observationTokens);
|
|
34393
35058
|
omError("[OM] Reflection failed", error);
|
|
@@ -34727,6 +35392,8 @@ function writeProcessInputStepReproCapture(params) {
|
|
|
34727
35392
|
* Returns the parts from the latest step of a message (after the last step-start marker).
|
|
34728
35393
|
* If no step-start marker exists, returns all parts.
|
|
34729
35394
|
*/
|
|
35395
|
+
/** Re-reads of the head after an activation swap hit a generation retired by a reflection. */
|
|
35396
|
+
const MAX_ACTIVATION_RETIRED_RETRIES = 3;
|
|
34730
35397
|
function getLatestStepParts(parts) {
|
|
34731
35398
|
for (let i = parts.length - 1; i >= 0; i--) if (parts[i]?.type === "step-start") return parts.slice(i + 1);
|
|
34732
35399
|
return parts;
|
|
@@ -34920,6 +35587,8 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
34920
35587
|
reflector;
|
|
34921
35588
|
/** Buffering state coordinator — manages static maps and buffering lifecycle. */
|
|
34922
35589
|
buffering;
|
|
35590
|
+
/** Unresolved observation blockAfter (multiplier or absolute); resolved per record. */
|
|
35591
|
+
observationBlockAfterSetting;
|
|
34923
35592
|
shouldObscureThreadIds = false;
|
|
34924
35593
|
hasher = e();
|
|
34925
35594
|
mastra;
|
|
@@ -35055,6 +35724,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
35055
35724
|
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
35725
|
const observationActivateAfterIdle = config.observation?.activateAfterIdle ?? config.activateAfterIdle;
|
|
35057
35726
|
const observationActivateAfterIdlePath = config.observation?.activateAfterIdle !== void 0 ? "observation.activateAfterIdle" : "activateAfterIdle";
|
|
35727
|
+
this.observationBlockAfterSetting = asyncBufferingDisabled ? void 0 : config.observation?.blockAfter ?? (config.observation?.bufferTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferTokens ? 1.2 : void 0);
|
|
35058
35728
|
this.observationConfig = {
|
|
35059
35729
|
model: observationModel,
|
|
35060
35730
|
maxRetries: config.observation?.maxRetries ?? RETRY_CONFIG.maxRetries,
|
|
@@ -35075,8 +35745,8 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
35075
35745
|
bufferActivation: asyncBufferingDisabled ? void 0 : config.observation?.bufferActivation ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferActivation,
|
|
35076
35746
|
activateAfterIdle: parseActivationTTLConfig(observationActivateAfterIdle, observationActivateAfterIdlePath),
|
|
35077
35747
|
activateOnProviderChange: config.observation?.activateOnProviderChange ?? config.activateOnProviderChange ?? false,
|
|
35078
|
-
blockAfter:
|
|
35079
|
-
previousObserverTokens: config.observation?.previousObserverTokens ??
|
|
35748
|
+
blockAfter: resolveBlockAfter(this.observationBlockAfterSetting, config.observation?.messageTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens),
|
|
35749
|
+
previousObserverTokens: config.observation?.previousObserverTokens ?? false,
|
|
35080
35750
|
instruction: config.observation?.instruction,
|
|
35081
35751
|
threadTitle: config.observation?.threadTitle ?? false,
|
|
35082
35752
|
observeAttachments: config.observation?.observeAttachments ?? [...OBSERVATIONAL_MEMORY_DEFAULTS.observation.observeAttachments],
|
|
@@ -35385,6 +36055,13 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
35385
36055
|
return this.observationConfig.messageTokens;
|
|
35386
36056
|
}
|
|
35387
36057
|
/**
|
|
36058
|
+
* Resolve the observation blockAfter for a record. A multiplier scales the record's
|
|
36059
|
+
* effective messageTokens (per-record overrides included); an absolute value is used as-is.
|
|
36060
|
+
*/
|
|
36061
|
+
getEffectiveObservationBlockAfter(record) {
|
|
36062
|
+
return resolveBlockAfter(this.observationBlockAfterSetting, this.getEffectiveMessageTokens(record));
|
|
36063
|
+
}
|
|
36064
|
+
/**
|
|
35388
36065
|
* Resolve the effective reflection observationTokens for a record.
|
|
35389
36066
|
* Only explicit per-record overrides (stored under `_overrides`) win;
|
|
35390
36067
|
* the initial config snapshot is ignored so instance-level changes
|
|
@@ -35498,18 +36175,21 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
35498
36175
|
* the latest user input that triggered it.
|
|
35499
36176
|
* @internal Used by observation strategies and the reflector. Do not call directly.
|
|
35500
36177
|
*/
|
|
35501
|
-
async persistMarkerToStorage(marker, threadId, resourceId) {
|
|
36178
|
+
async persistMarkerToStorage(marker, threadId, resourceId, opts) {
|
|
35502
36179
|
try {
|
|
35503
|
-
const
|
|
36180
|
+
const markerData = marker.data;
|
|
36181
|
+
const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
|
|
36182
|
+
const preferred = [...opts?.preferMessages ?? []].reverse().find((m) => m.role === targetRole && (!m.threadId || m.threadId === threadId));
|
|
36183
|
+
const preferredStored = preferred ? (await this.storage.listMessagesById({ messageIds: [preferred.id] })).messages : [];
|
|
36184
|
+
const messages = (preferredStored.length ? { messages: preferredStored } : await this.storage.listMessages({
|
|
35504
36185
|
threadId,
|
|
35505
36186
|
perPage: 20,
|
|
35506
36187
|
orderBy: {
|
|
35507
36188
|
field: "createdAt",
|
|
35508
36189
|
direction: "DESC"
|
|
35509
|
-
}
|
|
36190
|
+
},
|
|
36191
|
+
...opts?.notAfter ? { filter: { dateRange: { end: opts.notAfter } } } : {}
|
|
35510
36192
|
}))?.messages ?? [];
|
|
35511
|
-
const markerData = marker.data;
|
|
35512
|
-
const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
|
|
35513
36193
|
for (const msg of messages) if (msg?.role === targetRole && msg.content?.parts && Array.isArray(msg.content.parts)) {
|
|
35514
36194
|
if (!(markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) msg.content.parts.push(marker);
|
|
35515
36195
|
await this.messageHistory.persistMessages({
|
|
@@ -35596,7 +36276,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
35596
36276
|
*/
|
|
35597
36277
|
createUnobservedMessage(message) {
|
|
35598
36278
|
const unobservedParts = getUnobservedParts(message);
|
|
35599
|
-
if (unobservedParts.
|
|
36279
|
+
if (unobservedParts.every((part) => part?.type?.startsWith("data-om-"))) return null;
|
|
35600
36280
|
return {
|
|
35601
36281
|
...message,
|
|
35602
36282
|
content: {
|
|
@@ -35968,17 +36648,17 @@ ${formattedMessages}
|
|
|
35968
36648
|
async startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, contextWindowTokens, requestContext, observabilityContext) {
|
|
35969
36649
|
const bufferKey = this.buffering.getObservationBufferKey(lockKey);
|
|
35970
36650
|
const currentTokens = contextWindowTokens ?? await this.tokenCounter.countMessagesAsync(unobservedMessages) + (record.pendingMessageTokens ?? 0);
|
|
35971
|
-
BufferingCoordinator.
|
|
36651
|
+
BufferingCoordinator.setObservationBoundary(bufferKey, currentTokens);
|
|
35972
36652
|
registerOp(record.id, "bufferingObservation");
|
|
35973
|
-
this.storage.setBufferingObservationFlag(record.id, true, currentTokens).catch((err) => {
|
|
36653
|
+
runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, true, currentTokens)).catch((err) => {
|
|
35974
36654
|
omError("[OM] Failed to set buffering observation flag", err);
|
|
35975
36655
|
});
|
|
35976
36656
|
const asyncOp = this.runAsyncBufferedObservation(record, threadId, unobservedMessages, bufferKey, writer, requestContext, observabilityContext).catch((err) => {
|
|
35977
36657
|
omError("[OM] async buffering observation failed", err);
|
|
35978
36658
|
}).finally(() => {
|
|
35979
|
-
BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
|
|
36659
|
+
if (BufferingCoordinator.asyncBufferingOps.get(bufferKey) === asyncOp) BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
|
|
35980
36660
|
unregisterOp(record.id, "bufferingObservation");
|
|
35981
|
-
this.storage.setBufferingObservationFlag(record.id, false).catch((err) => {
|
|
36661
|
+
runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch((err) => {
|
|
35982
36662
|
omError("[OM] Failed to clear buffering observation flag", err);
|
|
35983
36663
|
});
|
|
35984
36664
|
});
|
|
@@ -35993,6 +36673,14 @@ ${formattedMessages}
|
|
|
35993
36673
|
if (existingOp) try {
|
|
35994
36674
|
await existingOp;
|
|
35995
36675
|
} catch {}
|
|
36676
|
+
const releaseChunkWrite = BufferingCoordinator.trackChunkWrite(bufferKey);
|
|
36677
|
+
try {
|
|
36678
|
+
await this.bufferObservationChunk(record, threadId, unobservedMessages, bufferKey, releaseChunkWrite, writer, requestContext, observabilityContext);
|
|
36679
|
+
} finally {
|
|
36680
|
+
releaseChunkWrite();
|
|
36681
|
+
}
|
|
36682
|
+
}
|
|
36683
|
+
async bufferObservationChunk(record, threadId, unobservedMessages, bufferKey, releaseChunkWrite, writer, requestContext, observabilityContext) {
|
|
35996
36684
|
const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
35997
36685
|
if (!freshRecord) return;
|
|
35998
36686
|
let bufferCursor = BufferingCoordinator.lastBufferedAtTime.get(bufferKey) ?? freshRecord.lastBufferedAtTime ?? null;
|
|
@@ -36033,13 +36721,16 @@ ${formattedMessages}
|
|
|
36033
36721
|
threadIds: [threadId],
|
|
36034
36722
|
config: this.getObservationMarkerConfig()
|
|
36035
36723
|
});
|
|
36036
|
-
await this.persistMarkerToStorage(startMarker, threadId, freshRecord.resourceId ?? void 0
|
|
36724
|
+
await this.persistMarkerToStorage(startMarker, threadId, freshRecord.resourceId ?? void 0, {
|
|
36725
|
+
notAfter: new Date(startedAt),
|
|
36726
|
+
preferMessages: messagesToBuffer
|
|
36727
|
+
});
|
|
36037
36728
|
if (writer) writer.custom({
|
|
36038
36729
|
...startMarker,
|
|
36039
36730
|
transient: true
|
|
36040
36731
|
}).catch(() => {});
|
|
36041
36732
|
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
|
-
|
|
36733
|
+
await this.runBufferedObservationCycle({
|
|
36043
36734
|
threadId,
|
|
36044
36735
|
resourceId: freshRecord.resourceId ?? void 0,
|
|
36045
36736
|
trigger: "async-buffer"
|
|
@@ -36053,12 +36744,13 @@ ${formattedMessages}
|
|
|
36053
36744
|
writer,
|
|
36054
36745
|
requestContext,
|
|
36055
36746
|
observabilityContext,
|
|
36056
|
-
trigger: "async-buffer"
|
|
36057
|
-
|
|
36058
|
-
|
|
36059
|
-
|
|
36060
|
-
|
|
36061
|
-
|
|
36747
|
+
trigger: "async-buffer",
|
|
36748
|
+
onBufferedChunkPersisted: async () => {
|
|
36749
|
+
const maxTs = this.getMaxMessageTimestamp(messagesToBuffer);
|
|
36750
|
+
BufferingCoordinator.lastBufferedAtTime.set(bufferKey, new Date(maxTs.getTime() + 1));
|
|
36751
|
+
releaseChunkWrite();
|
|
36752
|
+
}
|
|
36753
|
+
}).run());
|
|
36062
36754
|
}
|
|
36063
36755
|
/**
|
|
36064
36756
|
* Trigger async buffered observation if the token count has crossed a new interval.
|
|
@@ -36243,8 +36935,8 @@ ${formattedMessages}
|
|
|
36243
36935
|
const { threadId, resourceId, recordId, activatedMessageIds } = opts;
|
|
36244
36936
|
const lockKey = this.buffering.getLockKey(threadId, resourceId);
|
|
36245
36937
|
const bufKey = this.buffering.getObservationBufferKey(lockKey);
|
|
36246
|
-
BufferingCoordinator.
|
|
36247
|
-
await this.storage.setBufferingObservationFlag(recordId, false, 0).catch(() => {});
|
|
36938
|
+
BufferingCoordinator.setObservationBoundary(bufKey, 0);
|
|
36939
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(recordId, false, 0)).catch(() => {});
|
|
36248
36940
|
if (activatedMessageIds && activatedMessageIds.length > 0) this.buffering.cleanupStaticMaps(threadId, resourceId, activatedMessageIds);
|
|
36249
36941
|
}
|
|
36250
36942
|
/**
|
|
@@ -36403,9 +37095,13 @@ ${formattedMessages}
|
|
|
36403
37095
|
* @example
|
|
36404
37096
|
* ```ts
|
|
36405
37097
|
* const status = await om.getStatus({ threadId });
|
|
36406
|
-
* if (status.shouldObserve) {
|
|
37098
|
+
* if (status.shouldObserve && status.canActivate) {
|
|
37099
|
+
* await om.activate({ threadId });
|
|
37100
|
+
* } else if (status.shouldObserve && !status.inAsyncObservationBand) {
|
|
37101
|
+
* // At/above blockAfter (or async buffering disabled): observe synchronously
|
|
36407
37102
|
* await om.observe({ threadId });
|
|
36408
37103
|
* } else if (status.shouldBuffer) {
|
|
37104
|
+
* // Below the threshold, or in the threshold→blockAfter band: buffer in the background
|
|
36409
37105
|
* await om.buffer({ threadId });
|
|
36410
37106
|
* }
|
|
36411
37107
|
* if (status.shouldReflect) {
|
|
@@ -36435,8 +37131,10 @@ ${formattedMessages}
|
|
|
36435
37131
|
const bufferedChunkCount = bufferedChunks.length;
|
|
36436
37132
|
const bufferedChunkTokens = bufferedChunks.reduce((sum, chunk) => sum + (chunk.messageTokens ?? 0), 0);
|
|
36437
37133
|
const asyncObservationEnabled = this.buffering.isAsyncObservationEnabled();
|
|
37134
|
+
const observationBlockAfter = asyncObservationEnabled ? this.getEffectiveObservationBlockAfter(record) : void 0;
|
|
37135
|
+
const inAsyncObservationBand = observationBlockAfter !== void 0 && pendingTokens >= threshold && pendingTokens < observationBlockAfter;
|
|
36438
37136
|
let shouldBuffer = false;
|
|
36439
|
-
if (asyncObservationEnabled && pendingTokens < threshold) {
|
|
37137
|
+
if (asyncObservationEnabled && (pendingTokens < threshold || inAsyncObservationBand)) {
|
|
36440
37138
|
const lockKey = this.buffering.getLockKey(threadId, resourceId);
|
|
36441
37139
|
shouldBuffer = this.buffering.shouldTriggerAsyncObservation(pendingTokens, lockKey, record, this.storage, threshold);
|
|
36442
37140
|
}
|
|
@@ -36461,6 +37159,8 @@ ${formattedMessages}
|
|
|
36461
37159
|
canActivate,
|
|
36462
37160
|
asyncObservationEnabled,
|
|
36463
37161
|
asyncReflectionEnabled: this.buffering.isAsyncReflectionEnabled(),
|
|
37162
|
+
observationBlockAfter,
|
|
37163
|
+
inAsyncObservationBand,
|
|
36464
37164
|
scope: this.scope
|
|
36465
37165
|
};
|
|
36466
37166
|
}
|
|
@@ -36587,24 +37287,27 @@ ${formattedMessages}
|
|
|
36587
37287
|
const currentTokens = opts.pendingTokens ?? record.pendingMessageTokens ?? 0;
|
|
36588
37288
|
const lockKey = this.buffering.getLockKey(threadId, resourceId);
|
|
36589
37289
|
const bufferKey = this.buffering.getObservationBufferKey(lockKey);
|
|
36590
|
-
|
|
36591
|
-
|
|
37290
|
+
const boundaryOwner = Symbol("observation-boundary");
|
|
37291
|
+
BufferingCoordinator.setObservationBoundary(bufferKey, currentTokens, boundaryOwner);
|
|
36592
37292
|
const existingOp = BufferingCoordinator.asyncBufferingOps.get(bufferKey);
|
|
37293
|
+
let resolveOp;
|
|
37294
|
+
const opPromise = new Promise((resolve) => {
|
|
37295
|
+
resolveOp = resolve;
|
|
37296
|
+
});
|
|
37297
|
+
BufferingCoordinator.asyncBufferingOps.set(bufferKey, opPromise);
|
|
37298
|
+
if (record.isBufferingObservation) await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch(() => {});
|
|
36593
37299
|
if (existingOp) try {
|
|
36594
37300
|
await existingOp;
|
|
36595
37301
|
} catch {}
|
|
36596
|
-
|
|
37302
|
+
const opRecordId = record.id;
|
|
37303
|
+
registerOp(opRecordId, "bufferingObservation");
|
|
37304
|
+
const releaseChunkWrite = BufferingCoordinator.trackChunkWrite(bufferKey);
|
|
37305
|
+
const ownsStartBoundary = BufferingCoordinator.ownsObservationBoundary(bufferKey, boundaryOwner);
|
|
36597
37306
|
inMemoryRecord.isBufferingObservation = true;
|
|
36598
|
-
inMemoryRecord.lastBufferedAtTokens = currentTokens;
|
|
36599
|
-
this.storage.setBufferingObservationFlag(record.id, true, currentTokens).catch((err) => {
|
|
37307
|
+
if (ownsStartBoundary) inMemoryRecord.lastBufferedAtTokens = currentTokens;
|
|
37308
|
+
runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, true, ownsStartBoundary ? currentTokens : void 0)).catch((err) => {
|
|
36600
37309
|
omError("[OM] Failed to set buffering observation flag", err);
|
|
36601
37310
|
});
|
|
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
37311
|
const setBufferingState = (isBufferingObservation, lastBufferedAtTokens) => {
|
|
36609
37312
|
inMemoryRecord.isBufferingObservation = isBufferingObservation;
|
|
36610
37313
|
record.isBufferingObservation = isBufferingObservation;
|
|
@@ -36615,6 +37318,7 @@ ${formattedMessages}
|
|
|
36615
37318
|
};
|
|
36616
37319
|
let flagCleared = false;
|
|
36617
37320
|
try {
|
|
37321
|
+
record = await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record;
|
|
36618
37322
|
let candidateMessages;
|
|
36619
37323
|
if (opts.messages) candidateMessages = this.getUnobservedMessages(opts.messages, record, { excludeBuffered: true });
|
|
36620
37324
|
else {
|
|
@@ -36654,12 +37358,29 @@ ${formattedMessages}
|
|
|
36654
37358
|
threadIds: [threadId],
|
|
36655
37359
|
config: this.getObservationMarkerConfig()
|
|
36656
37360
|
});
|
|
36657
|
-
await this.persistMarkerToStorage(startMarker, threadId, record.resourceId ?? void 0
|
|
37361
|
+
await this.persistMarkerToStorage(startMarker, threadId, record.resourceId ?? void 0, {
|
|
37362
|
+
notAfter: new Date(startedAt),
|
|
37363
|
+
preferMessages: candidateMessages
|
|
37364
|
+
});
|
|
36658
37365
|
const writer = opts.writer;
|
|
36659
37366
|
if (writer) writer.custom({
|
|
36660
37367
|
...startMarker,
|
|
36661
37368
|
transient: true
|
|
36662
37369
|
}).catch(() => {});
|
|
37370
|
+
let chunkRecorded = false;
|
|
37371
|
+
const recordBufferedChunk = async () => {
|
|
37372
|
+
if (chunkRecorded) return;
|
|
37373
|
+
chunkRecorded = true;
|
|
37374
|
+
const ownsBoundary = BufferingCoordinator.ownsObservationBoundary(bufferKey, boundaryOwner);
|
|
37375
|
+
if (ownsBoundary) BufferingCoordinator.setObservationBoundary(bufferKey, newTokens, boundaryOwner);
|
|
37376
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false, ownsBoundary ? newTokens : void 0)).catch(() => {});
|
|
37377
|
+
flagCleared = true;
|
|
37378
|
+
const stillOwnsBoundary = ownsBoundary && BufferingCoordinator.ownsObservationBoundary(bufferKey, boundaryOwner);
|
|
37379
|
+
setBufferingState(false, stillOwnsBoundary ? newTokens : void 0);
|
|
37380
|
+
const maxTimestamp = this.getMaxMessageTimestamp(candidateMessages);
|
|
37381
|
+
BufferingCoordinator.lastBufferedAtTime.set(bufferKey, new Date(maxTimestamp.getTime() + 1));
|
|
37382
|
+
releaseChunkWrite();
|
|
37383
|
+
};
|
|
36663
37384
|
if (!(await this.runBufferedObservationCycle({
|
|
36664
37385
|
threadId,
|
|
36665
37386
|
resourceId: record.resourceId ?? resourceId,
|
|
@@ -36678,7 +37399,8 @@ ${formattedMessages}
|
|
|
36678
37399
|
requestContext,
|
|
36679
37400
|
currentModel: opts.currentModel,
|
|
36680
37401
|
observabilityContext,
|
|
36681
|
-
trigger: "async-buffer"
|
|
37402
|
+
trigger: "async-buffer",
|
|
37403
|
+
onBufferedChunkPersisted: recordBufferedChunk
|
|
36682
37404
|
}).run()))?.observed) return {
|
|
36683
37405
|
buffered: false,
|
|
36684
37406
|
record
|
|
@@ -36698,13 +37420,7 @@ ${formattedMessages}
|
|
|
36698
37420
|
newTokens
|
|
36699
37421
|
}
|
|
36700
37422
|
});
|
|
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);
|
|
37423
|
+
await recordBufferedChunk();
|
|
36708
37424
|
return {
|
|
36709
37425
|
buffered: true,
|
|
36710
37426
|
record: await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record
|
|
@@ -36716,12 +37432,13 @@ ${formattedMessages}
|
|
|
36716
37432
|
record
|
|
36717
37433
|
};
|
|
36718
37434
|
} finally {
|
|
36719
|
-
|
|
36720
|
-
|
|
37435
|
+
releaseChunkWrite();
|
|
37436
|
+
unregisterOp(opRecordId, "bufferingObservation");
|
|
37437
|
+
if (BufferingCoordinator.asyncBufferingOps.get(bufferKey) === opPromise) BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
|
|
36721
37438
|
resolveOp();
|
|
36722
37439
|
if (!flagCleared) {
|
|
36723
37440
|
setBufferingState(false);
|
|
36724
|
-
await this.storage.setBufferingObservationFlag(record.id, false).catch(() => {});
|
|
37441
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch(() => {});
|
|
36725
37442
|
}
|
|
36726
37443
|
}
|
|
36727
37444
|
}
|
|
@@ -36758,8 +37475,8 @@ ${formattedMessages}
|
|
|
36758
37475
|
const currentContextTokens = this.tokenCounter.countMessages(unobserved);
|
|
36759
37476
|
if (currentContextTokens < dbBoundary * .5) {
|
|
36760
37477
|
omDebug(`[OM:activate] resetting stale lastBufferedBoundary: dbBoundary=${dbBoundary}, currentContextTokens=${currentContextTokens}`);
|
|
36761
|
-
BufferingCoordinator.
|
|
36762
|
-
await this.storage.setBufferingObservationFlag(record.id, false, 0).catch(() => {});
|
|
37478
|
+
BufferingCoordinator.setObservationBoundary(bufKey, 0);
|
|
37479
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false, 0)).catch(() => {});
|
|
36763
37480
|
}
|
|
36764
37481
|
}
|
|
36765
37482
|
}
|
|
@@ -36805,39 +37522,63 @@ ${formattedMessages}
|
|
|
36805
37522
|
livePendingTokens ??= status.pendingTokens;
|
|
36806
37523
|
}
|
|
36807
37524
|
}
|
|
36808
|
-
|
|
36809
|
-
|
|
36810
|
-
|
|
36811
|
-
const
|
|
36812
|
-
|
|
36813
|
-
|
|
36814
|
-
|
|
37525
|
+
const obsBufferKey = this.buffering.getObservationBufferKey(this.buffering.getLockKey(threadId, resourceId));
|
|
37526
|
+
const pendingChunkWrite = BufferingCoordinator.pendingChunkWrites.get(obsBufferKey);
|
|
37527
|
+
if (pendingChunkWrite) {
|
|
37528
|
+
const blockAfter = this.buffering.isAsyncObservationEnabled() ? this.getEffectiveObservationBlockAfter(record) : void 0;
|
|
37529
|
+
const threshold = calculateDynamicThreshold(this.getEffectiveMessageTokens(record), record.observationTokenCount ?? 0);
|
|
37530
|
+
if (activationTriggeredBy === "threshold" && !(livePendingTokens !== void 0 && blockAfter !== void 0 && livePendingTokens >= threshold && livePendingTokens < blockAfter)) {
|
|
37531
|
+
let timeoutId;
|
|
37532
|
+
await Promise.race([pendingChunkWrite, new Promise((resolve) => timeoutId = setTimeout(resolve, 6e4))]);
|
|
37533
|
+
clearTimeout(timeoutId);
|
|
37534
|
+
}
|
|
37535
|
+
}
|
|
37536
|
+
const commitKey = this.buffering.getLockKey(threadId, resourceId);
|
|
37537
|
+
let freshRecord = null;
|
|
37538
|
+
let freshChunks = [];
|
|
37539
|
+
let activationResult;
|
|
37540
|
+
for (let attempt = 0; attempt <= MAX_ACTIVATION_RETIRED_RETRIES; attempt++) {
|
|
37541
|
+
const swapped = await runOMCommit(commitKey, async () => {
|
|
37542
|
+
const head = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
37543
|
+
if (!head) return null;
|
|
37544
|
+
const chunks = getBufferedChunks(head);
|
|
37545
|
+
if (!chunks.length) return null;
|
|
37546
|
+
const messageTokensThreshold = getMaxThreshold(this.getEffectiveMessageTokens(head));
|
|
37547
|
+
const activationRatio = resolveActivationRatio(this.observationConfig.bufferActivation ?? .7, messageTokensThreshold);
|
|
37548
|
+
const totalChunkMessageTokens = chunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
37549
|
+
const currentPendingTokens = livePendingTokens ?? (head.pendingMessageTokens || totalChunkMessageTokens);
|
|
37550
|
+
const blockAfter = this.getEffectiveObservationBlockAfter(head);
|
|
37551
|
+
const forceMaxActivation = !!(blockAfter && currentPendingTokens >= blockAfter);
|
|
37552
|
+
if (head.pendingMessageTokens !== currentPendingTokens) await this.storage.setPendingMessageTokens(head.id, currentPendingTokens);
|
|
37553
|
+
return {
|
|
37554
|
+
head,
|
|
37555
|
+
chunks,
|
|
37556
|
+
result: await this.storage.swapBufferedToActive({
|
|
37557
|
+
id: head.id,
|
|
37558
|
+
activationRatio,
|
|
37559
|
+
messageTokensThreshold,
|
|
37560
|
+
currentPendingTokens,
|
|
37561
|
+
forceMaxActivation,
|
|
37562
|
+
bufferedChunks: chunks
|
|
37563
|
+
})
|
|
37564
|
+
};
|
|
37565
|
+
});
|
|
37566
|
+
if (!swapped) return {
|
|
37567
|
+
activated: false,
|
|
37568
|
+
record
|
|
37569
|
+
};
|
|
37570
|
+
freshRecord = swapped.head;
|
|
37571
|
+
freshChunks = swapped.chunks;
|
|
37572
|
+
activationResult = swapped.result;
|
|
37573
|
+
if (!activationResult.retired) break;
|
|
37574
|
+
omDebug(`[OM:activate] record ${freshRecord.id} was retired by a reflection; retrying on the head`);
|
|
36815
37575
|
}
|
|
36816
|
-
|
|
36817
|
-
if (!freshRecord) return {
|
|
37576
|
+
if (!freshRecord || !activationResult || activationResult.retired) return {
|
|
36818
37577
|
activated: false,
|
|
36819
|
-
record
|
|
37578
|
+
record: await this.getRecord(threadId, resourceId) ?? record
|
|
36820
37579
|
};
|
|
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(() => {});
|
|
37580
|
+
const swappedRecordId = freshRecord.id;
|
|
37581
|
+
await runOMCommit(commitKey, () => this.storage.setBufferingObservationFlag(swappedRecordId, false)).catch(() => {});
|
|
36841
37582
|
unregisterOp(freshRecord.id, "bufferingObservation");
|
|
36842
37583
|
const postSwapRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
36843
37584
|
if (opts.writer && postSwapRecord && activationResult.activatedCycleIds.length > 0) {
|
|
@@ -37082,7 +37823,7 @@ ${formattedMessages}
|
|
|
37082
37823
|
* ```
|
|
37083
37824
|
*/
|
|
37084
37825
|
async reflect(threadId, resourceId, prompt, requestContext, observabilityContext) {
|
|
37085
|
-
const record = await this.getOrCreateRecord(threadId, resourceId);
|
|
37826
|
+
const record = { ...await this.getOrCreateRecord(threadId, resourceId) };
|
|
37086
37827
|
if (!record.activeObservations) return {
|
|
37087
37828
|
reflected: false,
|
|
37088
37829
|
record,
|
|
@@ -37127,11 +37868,23 @@ ${formattedMessages}
|
|
|
37127
37868
|
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
37869
|
reflectResult.observations = await applyTextTransform(this.hooks, "afterReflection", reflectResult.observations, hookContext);
|
|
37129
37870
|
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
37130
|
-
|
|
37871
|
+
const newRecordId = crypto.randomUUID();
|
|
37872
|
+
const committedRecord = await runOMCommit(this.buffering.getLockKey(threadId, resourceId), () => this.storage.createReflectionGeneration({
|
|
37131
37873
|
currentRecord: record,
|
|
37132
37874
|
reflection: reflectResult.observations,
|
|
37133
|
-
tokenCount: reflectionTokenCount
|
|
37134
|
-
|
|
37875
|
+
tokenCount: reflectionTokenCount,
|
|
37876
|
+
newRecordId
|
|
37877
|
+
}), { priority: "reflection" });
|
|
37878
|
+
reflectionUsage = reflectResult.usage;
|
|
37879
|
+
reflectionProviderMetadata = reflectResult.providerMetadata;
|
|
37880
|
+
if (!isReflectionApplied(committedRecord, newRecordId, record.id)) {
|
|
37881
|
+
omDebug(`[OM:reflect] manual reflection of ${record.id} not applied (head is ${committedRecord.id})`);
|
|
37882
|
+
return {
|
|
37883
|
+
reflected: false,
|
|
37884
|
+
record: await this.getOrCreateRecord(threadId, resourceId),
|
|
37885
|
+
usage: reflectResult.usage
|
|
37886
|
+
};
|
|
37887
|
+
}
|
|
37135
37888
|
if (thread && reflectResult.extractedValues) {
|
|
37136
37889
|
const metadataUpdate = buildThreadMetadataFromExtractedValues(this.reflectionConfig.extractors, reflectResult.extractedValues);
|
|
37137
37890
|
const newMetadata = (0, _mastra_core_memory.setThreadOMMetadata)(thread.metadata, {
|
|
@@ -37220,11 +37973,13 @@ ${formattedMessages}
|
|
|
37220
37973
|
*/
|
|
37221
37974
|
async updateRecordConfig(threadId, resourceId, config) {
|
|
37222
37975
|
const ids = this.getStorageIds(threadId, resourceId);
|
|
37223
|
-
|
|
37224
|
-
|
|
37225
|
-
|
|
37226
|
-
|
|
37227
|
-
|
|
37976
|
+
await runOMCommit(this.buffering.getLockKey(threadId, resourceId), async () => {
|
|
37977
|
+
const record = await this.storage.getObservationalMemory(ids.threadId, ids.resourceId);
|
|
37978
|
+
if (!record) throw new Error(`No observational memory record found for thread ${ids.threadId}`);
|
|
37979
|
+
await this.storage.updateObservationalMemoryConfig({
|
|
37980
|
+
id: record.id,
|
|
37981
|
+
config: { _overrides: config }
|
|
37982
|
+
});
|
|
37228
37983
|
});
|
|
37229
37984
|
}
|
|
37230
37985
|
/**
|
|
@@ -37614,7 +38369,7 @@ var ObservationalMemoryProcessor = class {
|
|
|
37614
38369
|
});
|
|
37615
38370
|
const finalTotalPending = ctx.status.pendingTokens;
|
|
37616
38371
|
try {
|
|
37617
|
-
await this.engine.getStorage().setPendingMessageTokens(turnRecord.id, finalTotalPending);
|
|
38372
|
+
await runOMCommit(this.engine.buffering.getLockKey(threadId, resourceId), () => this.engine.getStorage().setPendingMessageTokens(turnRecord.id, finalTotalPending));
|
|
37618
38373
|
this.turn.patchRecord({ pendingMessageTokens: finalTotalPending });
|
|
37619
38374
|
} catch {}
|
|
37620
38375
|
if (reproCaptureEnabled) writeProcessInputStepReproCapture({
|
|
@@ -39928,6 +40683,7 @@ Notes:
|
|
|
39928
40683
|
cloned.id = crypto.randomUUID();
|
|
39929
40684
|
cloned.createdAt = now;
|
|
39930
40685
|
cloned.updatedAt = now;
|
|
40686
|
+
cloned.supersededBy = null;
|
|
39931
40687
|
await memoryStore.insertObservationalMemoryRecord(cloned);
|
|
39932
40688
|
}
|
|
39933
40689
|
/**
|
|
@@ -51002,4 +51758,4 @@ const agentBuilderWorkflows = {
|
|
|
51002
51758
|
//#endregion
|
|
51003
51759
|
exports.agentBuilderWorkflows = agentBuilderWorkflows;
|
|
51004
51760
|
|
|
51005
|
-
//# sourceMappingURL=dist-
|
|
51761
|
+
//# sourceMappingURL=dist-BGfXR4Xj.cjs.map
|