@mastra/memory 1.37.0-alpha.2 → 1.37.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-observational-memory.md +17 -15
- package/dist/docs/references/docs-subagents.md +1 -0
- package/dist/docs/references/reference-memory-observational-memory.md +3 -3
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/processors/index.cjs +1 -1
- package/dist/processors/index.js +1 -1
- package/dist/processors/observational-memory/buffering-coordinator.d.ts +25 -0
- package/dist/processors/observational-memory/buffering-coordinator.d.ts.map +1 -1
- package/dist/processors/observational-memory/commit-queue.d.ts +46 -0
- package/dist/processors/observational-memory/commit-queue.d.ts.map +1 -0
- package/dist/processors/observational-memory/observation-strategies/async-buffer.d.ts +7 -4
- package/dist/processors/observational-memory/observation-strategies/async-buffer.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/base.d.ts +93 -6
- package/dist/processors/observational-memory/observation-strategies/base.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/resource-scoped.d.ts +20 -3
- package/dist/processors/observational-memory/observation-strategies/resource-scoped.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/sync.d.ts +10 -3
- package/dist/processors/observational-memory/observation-strategies/sync.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/types.d.ts +30 -0
- package/dist/processors/observational-memory/observation-strategies/types.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-turn/step.d.ts +1 -1
- package/dist/processors/observational-memory/observation-turn/step.d.ts.map +1 -1
- package/dist/processors/observational-memory/observational-memory.d.ts +30 -6
- package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
- package/dist/processors/observational-memory/observer-agent.d.ts +8 -0
- package/dist/processors/observational-memory/observer-agent.d.ts.map +1 -1
- package/dist/processors/observational-memory/observer-runner.d.ts +1 -1
- package/dist/processors/observational-memory/observer-runner.d.ts.map +1 -1
- package/dist/processors/observational-memory/processor.d.ts.map +1 -1
- package/dist/processors/observational-memory/record-lineage.d.ts +21 -0
- package/dist/processors/observational-memory/record-lineage.d.ts.map +1 -0
- package/dist/processors/observational-memory/reflector-agent.d.ts.map +1 -1
- package/dist/processors/observational-memory/reflector-runner.d.ts +9 -0
- package/dist/processors/observational-memory/reflector-runner.d.ts.map +1 -1
- package/dist/processors/observational-memory/types.d.ts +10 -8
- package/dist/processors/observational-memory/types.d.ts.map +1 -1
- package/dist/{src-D-jA6eRb.cjs → src-BpxX0T2g.cjs} +1039 -298
- package/dist/src-BpxX0T2g.cjs.map +1 -0
- package/dist/{src-B9qhQmNI.js → src-ThciS3vu.js} +1040 -299
- package/dist/src-ThciS3vu.js.map +1 -0
- package/package.json +7 -7
- package/dist/src-B9qhQmNI.js.map +0 -1
- package/dist/src-D-jA6eRb.cjs.map +0 -1
|
@@ -20578,7 +20578,7 @@ Group related observations (like tool sequences) by indenting:
|
|
|
20578
20578
|
* -> applied fix, tests now pass
|
|
20579
20579
|
* ✅ Tests passing, auth issue resolved
|
|
20580
20580
|
|
|
20581
|
-
Group observations by date,
|
|
20581
|
+
Group observations by date. Within a date, list observations in the order the events happened (not by importance), each with 24-hour time.
|
|
20582
20582
|
|
|
20583
20583
|
<observations>
|
|
20584
20584
|
Date: Dec 4, 2025
|
|
@@ -20951,8 +20951,8 @@ function extractToolResultAttachments(result, counter, attachmentFilter) {
|
|
|
20951
20951
|
attachments
|
|
20952
20952
|
};
|
|
20953
20953
|
}
|
|
20954
|
-
function formatObserverPartLine(title, body, time, previousTime) {
|
|
20955
|
-
const timeLabel = time && time !== previousTime ? `(${time})` : "";
|
|
20954
|
+
function formatObserverPartLine(title, body, time, previousTime, alwaysShowTime) {
|
|
20955
|
+
const timeLabel = time && (alwaysShowTime || time !== previousTime) ? `(${time})` : "";
|
|
20956
20956
|
if (!title) return timeLabel ? `${timeLabel}: ${body}` : body;
|
|
20957
20957
|
return `${title}${timeLabel ? ` ${timeLabel}` : ""}: ${body}`;
|
|
20958
20958
|
}
|
|
@@ -20977,7 +20977,7 @@ function formatObserverLines(lines, context = {}) {
|
|
|
20977
20977
|
previousDate = line.date;
|
|
20978
20978
|
previousTime = void 0;
|
|
20979
20979
|
}
|
|
20980
|
-
output.push(formatObserverPartLine(line.title, line.body, line.time, previousTime));
|
|
20980
|
+
output.push(formatObserverPartLine(line.title, line.body, line.time, previousTime, line.alwaysShowTime));
|
|
20981
20981
|
previousTime = line.time || previousTime;
|
|
20982
20982
|
}
|
|
20983
20983
|
return {
|
|
@@ -21078,14 +21078,15 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
21078
21078
|
const unavailableUrls = getUnavailableAttachmentUrls(msg);
|
|
21079
21079
|
let lines = [];
|
|
21080
21080
|
const temporalGapText = isTemporalGapMarker(msg) ? getTemporalGapMarkerText(msg) : void 0;
|
|
21081
|
-
const pushLine = (title, body, createdAt) => {
|
|
21081
|
+
const pushLine = (title, body, createdAt, opts) => {
|
|
21082
21082
|
if (!body) return;
|
|
21083
21083
|
const normalizedCreatedAt = normalizeObserverCreatedAt(createdAt) ?? messageCreatedAt;
|
|
21084
21084
|
lines.push({
|
|
21085
21085
|
date: formatObserverDate(normalizedCreatedAt, timeZone),
|
|
21086
21086
|
time: formatObserverTime(normalizedCreatedAt, timeZone),
|
|
21087
21087
|
title,
|
|
21088
|
-
body
|
|
21088
|
+
body,
|
|
21089
|
+
alwaysShowTime: opts?.tool === true
|
|
21089
21090
|
});
|
|
21090
21091
|
};
|
|
21091
21092
|
if (temporalGapText) pushLine("", temporalGapText, messageCreatedAt);
|
|
@@ -21102,7 +21103,7 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
21102
21103
|
const exchange = toolFormatting.exchanges.get(inv.toolCallId);
|
|
21103
21104
|
if (exchange && occurrence === exchange.firstOccurrence && exchange.signature && !toolFormatting.emittedCalls.has(inv.toolCallId)) {
|
|
21104
21105
|
toolFormatting.emittedCalls.add(inv.toolCallId);
|
|
21105
|
-
pushLine(`Tool Call ${exchange.signature.toolName}`, formatObserverToolArguments(exchange.signature.args, maxLen), partCreatedAt);
|
|
21106
|
+
pushLine(`Tool Call ${exchange.signature.toolName}`, formatObserverToolArguments(exchange.signature.args, maxLen), partCreatedAt, { tool: true });
|
|
21106
21107
|
}
|
|
21107
21108
|
if (inv.state === "result") {
|
|
21108
21109
|
const { value: resultForObserver } = resolveToolResultValue(part, inv.result);
|
|
@@ -21112,17 +21113,17 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
21112
21113
|
toolFormatting.emittedOutcomes.add(inv.toolCallId);
|
|
21113
21114
|
const hasResult = resultWithoutAttachments !== void 0 && (typeof resultWithoutAttachments !== "string" || resultWithoutAttachments.trim().length > 0);
|
|
21114
21115
|
const body = inv.isError ? hasResult ? formatToolResultForObserver(resultWithoutAttachments, { maxTokens: maxToolResultTokens }) : inv.errorText?.trim() ? inv.errorText : "Tool execution failed" : hasResult ? formatToolResultForObserver(resultWithoutAttachments, { maxTokens: maxToolResultTokens }) : "[empty result]";
|
|
21115
|
-
pushLine(`${inv.isError ? "Tool Error" : "Tool Result"} ${inv.toolName}`, maybeTruncate(body, maxLen), partCreatedAt);
|
|
21116
|
+
pushLine(`${inv.isError ? "Tool Error" : "Tool Result"} ${inv.toolName}`, maybeTruncate(body, maxLen), partCreatedAt, { tool: true });
|
|
21116
21117
|
}
|
|
21117
21118
|
return;
|
|
21118
21119
|
}
|
|
21119
21120
|
if (exchange?.terminal?.occurrence === occurrence && !toolFormatting.emittedOutcomes.has(inv.toolCallId)) {
|
|
21120
21121
|
toolFormatting.emittedOutcomes.add(inv.toolCallId);
|
|
21121
|
-
if (inv.state === "output-error") pushLine(`Tool Error ${inv.toolName}`, maybeTruncate(inv.errorText?.trim() ? inv.errorText : "Tool execution failed", maxLen), partCreatedAt);
|
|
21122
|
-
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);
|
|
21122
|
+
if (inv.state === "output-error") pushLine(`Tool Error ${inv.toolName}`, maybeTruncate(inv.errorText?.trim() ? inv.errorText : "Tool execution failed", maxLen), partCreatedAt, { tool: true });
|
|
21123
|
+
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 });
|
|
21123
21124
|
return;
|
|
21124
21125
|
}
|
|
21125
|
-
if (!exchange) pushLine(`Tool Call ${inv.toolName}`, formatObserverToolArguments(inv.args, maxLen), partCreatedAt);
|
|
21126
|
+
if (!exchange) pushLine(`Tool Call ${inv.toolName}`, formatObserverToolArguments(inv.args, maxLen), partCreatedAt, { tool: true });
|
|
21126
21127
|
return;
|
|
21127
21128
|
}
|
|
21128
21129
|
const partType = part.type;
|
|
@@ -21277,13 +21278,17 @@ function surroundObserverHistory(history, contextPrompt, taskInstructions) {
|
|
|
21277
21278
|
function buildMultiThreadObserverTaskInstructions(includeThreadTitle) {
|
|
21278
21279
|
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.`;
|
|
21279
21280
|
}
|
|
21281
|
+
function buildTruncationNotice(wasTruncated) {
|
|
21282
|
+
if (!wasTruncated) return "";
|
|
21283
|
+
return "Previous observations were truncated for context budget reasons.\nThe main agent still has full memory context outside this observer window.\n\n";
|
|
21284
|
+
}
|
|
21280
21285
|
function buildMultiThreadObserverContextPrompt(existingObservations, threadOrder, priorMetadataByThread, wasTruncated, includeThreadTitle, extractors = []) {
|
|
21281
21286
|
let prompt = "";
|
|
21282
21287
|
if (existingObservations) {
|
|
21283
21288
|
prompt += `## Previous Observations\n\n${existingObservations}\n\n---\n\n`;
|
|
21284
21289
|
prompt += "Do not repeat these existing observations. Your new observations will be appended to the existing observations.\n\n";
|
|
21285
21290
|
}
|
|
21286
|
-
|
|
21291
|
+
prompt += buildTruncationNotice(wasTruncated);
|
|
21287
21292
|
const threadMetadataLines = threadOrder?.map((threadId) => {
|
|
21288
21293
|
const metadata = priorMetadataByThread?.get(threadId);
|
|
21289
21294
|
const extractorPriorLines = buildExtractorPriorLines(extractors, metadata?.extracted);
|
|
@@ -21297,10 +21302,6 @@ function buildMultiThreadObserverContextPrompt(existingObservations, threadOrder
|
|
|
21297
21302
|
}).filter(Boolean).join("\n");
|
|
21298
21303
|
if (threadMetadataLines) {
|
|
21299
21304
|
prompt += `## Prior Thread Metadata\n\n${threadMetadataLines}\n\n`;
|
|
21300
|
-
if (hasTruncatedObservations) {
|
|
21301
|
-
prompt += `Previous observations were truncated for context budget reasons.\n`;
|
|
21302
|
-
prompt += `The main agent still has full memory context outside this observer window.\n`;
|
|
21303
|
-
}
|
|
21304
21305
|
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`;
|
|
21305
21306
|
}
|
|
21306
21307
|
return prompt;
|
|
@@ -21343,7 +21344,7 @@ function parseMultiThreadObserverOutput(output, extractors = []) {
|
|
|
21343
21344
|
threadTitle = threadTitleMatch[1].trim();
|
|
21344
21345
|
observations = observations.replace(/<thread-title>[\s\S]*?<\/thread-title>/i, "");
|
|
21345
21346
|
}
|
|
21346
|
-
observations = sanitizeObservationLines(observations.trim());
|
|
21347
|
+
observations = sortObservationsByTime(sanitizeObservationLines(observations.trim()));
|
|
21347
21348
|
threads.set(threadId, {
|
|
21348
21349
|
observations,
|
|
21349
21350
|
currentTask: currentTask || getStringExtractedValue$1(parsedExtractedValues.values, "current-task"),
|
|
@@ -21383,7 +21384,7 @@ function buildObserverContextPrompt(existingObservations, options) {
|
|
|
21383
21384
|
prompt += `## Previous Observations\n\n${existingObservations}\n\n---\n\n`;
|
|
21384
21385
|
prompt += "Do not repeat these existing observations. Your new observations will be appended to the existing observations.\n\n";
|
|
21385
21386
|
}
|
|
21386
|
-
|
|
21387
|
+
prompt += buildTruncationNotice(options?.wasTruncated);
|
|
21387
21388
|
const priorMetadataLines = [];
|
|
21388
21389
|
if (options?.priorCurrentTask) priorMetadataLines.push(`- prior current-task: ${options.priorCurrentTask}`);
|
|
21389
21390
|
if (options?.priorSuggestedResponse) priorMetadataLines.push(`- prior suggested-response: ${options.priorSuggestedResponse}`);
|
|
@@ -21391,10 +21392,6 @@ function buildObserverContextPrompt(existingObservations, options) {
|
|
|
21391
21392
|
priorMetadataLines.push(...buildExtractorPriorLines(options?.extractors ?? [], options?.priorExtractedValues));
|
|
21392
21393
|
if (priorMetadataLines.length > 0) {
|
|
21393
21394
|
prompt += `## Prior Thread Metadata\n\n${priorMetadataLines.join("\n")}\n\n`;
|
|
21394
|
-
if (hasTruncatedObservations) {
|
|
21395
|
-
prompt += `Previous observations were truncated for context budget reasons.\n`;
|
|
21396
|
-
prompt += `The main agent still has full memory context outside this observer window.\n`;
|
|
21397
|
-
}
|
|
21398
21395
|
const titleHint = options?.includeThreadTitle ? ", and thread-title" : "";
|
|
21399
21396
|
prompt += `Use the prior current-task, suggested-response${titleHint} as continuity hints, then update them based on the new messages.\n\n---\n\n`;
|
|
21400
21397
|
}
|
|
@@ -21425,7 +21422,7 @@ function parseObserverOutput(output, extractors = []) {
|
|
|
21425
21422
|
const parsedExtractedValues = parseExtractedValues(output, inlineExtractors);
|
|
21426
21423
|
const parsed = parseMemorySectionXml(stripExtractorSections(output, inlineExtractors));
|
|
21427
21424
|
return {
|
|
21428
|
-
observations: sanitizeObservationLines(parsed.observations || ""),
|
|
21425
|
+
observations: sortObservationsByTime(sanitizeObservationLines(parsed.observations || "")),
|
|
21429
21426
|
currentTask: parsed.currentTask || getStringExtractedValue$1(parsedExtractedValues.values, "current-task"),
|
|
21430
21427
|
suggestedContinuation: parsed.suggestedResponse || getStringExtractedValue$1(parsedExtractedValues.values, "suggested-response"),
|
|
21431
21428
|
threadTitle: parsed.threadTitle || getStringExtractedValue$1(parsedExtractedValues.values, "thread-title"),
|
|
@@ -21519,6 +21516,68 @@ function sanitizeObservationLines(observations) {
|
|
|
21519
21516
|
}
|
|
21520
21517
|
return changed ? lines.join("\n") : observations;
|
|
21521
21518
|
}
|
|
21519
|
+
const OBSERVATION_TIME = /^[*-]\s+(?:\S+\s+)?\((\d{1,2}):(\d{2})\s*([AaPp])?\.?(?:[Mm]\.?)?\)/;
|
|
21520
|
+
/** Minutes since midnight for each item, or undefined when the group's times can't be ordered safely. */
|
|
21521
|
+
function observationMinutes(items) {
|
|
21522
|
+
const times = items.map((item) => OBSERVATION_TIME.exec(item[0]));
|
|
21523
|
+
if (times.some((t) => !t)) return void 0;
|
|
21524
|
+
const matches = times;
|
|
21525
|
+
const marked = matches.filter((m) => m[3]).length;
|
|
21526
|
+
if (marked > 0 && marked < matches.length) return void 0;
|
|
21527
|
+
if (marked === matches.length) return matches.map((m) => Number(m[1]) % 12 * 60 + (/p/i.test(m[3]) ? 720 : 0) + Number(m[2]));
|
|
21528
|
+
const hours = matches.map((m) => m[1]);
|
|
21529
|
+
const twelveHour = hours.some((h) => /^[1-9]$/.test(h));
|
|
21530
|
+
const twentyFourHour = hours.some((h) => /^(0\d|1[3-9]|2\d)$/.test(h));
|
|
21531
|
+
if (twelveHour && (twentyFourHour || new Set(hours).size > 1)) return void 0;
|
|
21532
|
+
return matches.map((m) => Number(m[1]) * 60 + Number(m[2]));
|
|
21533
|
+
}
|
|
21534
|
+
/**
|
|
21535
|
+
* Put the top-level observations of each "Date:" group in time order, keeping indented
|
|
21536
|
+
* sub-items with their parent. The Observer writes a time on every observation but tends
|
|
21537
|
+
* to list a user's message ahead of the earlier events it follows, which can make a
|
|
21538
|
+
* superseded state read as the latest one. Groups are left as written when any
|
|
21539
|
+
* observation has no time or the times are ambiguous.
|
|
21540
|
+
*/
|
|
21541
|
+
function sortObservationsByTime(observations) {
|
|
21542
|
+
if (!observations) return observations;
|
|
21543
|
+
const output = [];
|
|
21544
|
+
let group;
|
|
21545
|
+
const flush = () => {
|
|
21546
|
+
if (!group) return;
|
|
21547
|
+
const minutes = group.sortable && group.items.length > 1 ? observationMinutes(group.items) : void 0;
|
|
21548
|
+
const items = minutes ? group.items.map((item, i) => ({
|
|
21549
|
+
item,
|
|
21550
|
+
minute: minutes[i]
|
|
21551
|
+
})).sort((a, b) => a.minute - b.minute).map((x) => x.item) : group.items;
|
|
21552
|
+
output.push(...group.header, ...items.flat(), ...group.trailing);
|
|
21553
|
+
group = void 0;
|
|
21554
|
+
};
|
|
21555
|
+
for (const line of observations.split("\n")) if (/^\s*Date:\s/.test(line)) {
|
|
21556
|
+
flush();
|
|
21557
|
+
group = {
|
|
21558
|
+
header: [line],
|
|
21559
|
+
items: [],
|
|
21560
|
+
trailing: [],
|
|
21561
|
+
sortable: true
|
|
21562
|
+
};
|
|
21563
|
+
} else if (!group) output.push(line);
|
|
21564
|
+
else if (/^[*-]\s/.test(line)) {
|
|
21565
|
+
group.items.at(-1)?.push(...group.trailing);
|
|
21566
|
+
group.trailing = [];
|
|
21567
|
+
group.items.push([line]);
|
|
21568
|
+
} else if (group.items.length === 0) group.header.push(line);
|
|
21569
|
+
else if (line.trim() === "") group.trailing.push(line);
|
|
21570
|
+
else if (/^\s/.test(line)) {
|
|
21571
|
+
group.items.at(-1).push(...group.trailing, line);
|
|
21572
|
+
group.trailing = [];
|
|
21573
|
+
} else {
|
|
21574
|
+
group.sortable = false;
|
|
21575
|
+
group.items.at(-1).push(...group.trailing, line);
|
|
21576
|
+
group.trailing = [];
|
|
21577
|
+
}
|
|
21578
|
+
flush();
|
|
21579
|
+
return output.join("\n");
|
|
21580
|
+
}
|
|
21522
21581
|
function analyzeDegenerateRepetition(text) {
|
|
21523
21582
|
const lines = collapseBoundedLineRuns(text.split("\n"));
|
|
21524
21583
|
const windowText = lines.filter((line) => line.trim().length >= MIN_DUPLICATE_LINE_CHARS && line.length <= MAX_OBSERVATION_LINE_CHARS).join("\n");
|
|
@@ -22061,7 +22120,7 @@ var ObserverRunner = class {
|
|
|
22061
22120
|
/**
|
|
22062
22121
|
* Call the Observer agent for multiple threads in a single batched request.
|
|
22063
22122
|
*/
|
|
22064
|
-
async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext, timeZone, mainAgent, currentModel) {
|
|
22123
|
+
async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext, timeZone, mainAgent, currentModel, wasTruncated) {
|
|
22065
22124
|
const contextFor = (threadId) => ({
|
|
22066
22125
|
threadId,
|
|
22067
22126
|
...hookContext
|
|
@@ -22072,7 +22131,7 @@ var ObserverRunner = class {
|
|
|
22072
22131
|
if (filtered.length > 0) messagesByThread.set(threadId, filtered);
|
|
22073
22132
|
}
|
|
22074
22133
|
const threadOrder = allThreadOrder.filter((threadId) => messagesByThread.has(threadId));
|
|
22075
|
-
const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel);
|
|
22134
|
+
const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel, wasTruncated);
|
|
22076
22135
|
for (const threadId of allThreadOrder) {
|
|
22077
22136
|
const threadResult = output.results.get(threadId);
|
|
22078
22137
|
if (!threadResult) {
|
|
@@ -22083,7 +22142,7 @@ var ObserverRunner = class {
|
|
|
22083
22142
|
}
|
|
22084
22143
|
return output;
|
|
22085
22144
|
}
|
|
22086
|
-
async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel) {
|
|
22145
|
+
async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel, wasTruncated) {
|
|
22087
22146
|
if (threadOrder.length === 0) return { results: /* @__PURE__ */ new Map() };
|
|
22088
22147
|
const inputTokens = Array.from(messagesByThread.values()).reduce((total, messages) => total + this.tokenCounter.countMessages(messages), 0);
|
|
22089
22148
|
let resolvedModel;
|
|
@@ -22129,7 +22188,8 @@ var ObserverRunner = class {
|
|
|
22129
22188
|
priorThreadTitle: priorMetadataByThread?.get(threadId)?.threadTitle,
|
|
22130
22189
|
priorExtractedValues: priorMetadataByThread?.get(threadId)?.extracted,
|
|
22131
22190
|
model: resolvedModel.model,
|
|
22132
|
-
timeZone
|
|
22191
|
+
timeZone,
|
|
22192
|
+
wasTruncated
|
|
22133
22193
|
});
|
|
22134
22194
|
results.set(threadId, {
|
|
22135
22195
|
observations: threadResult.observations,
|
|
@@ -22155,7 +22215,7 @@ var ObserverRunner = class {
|
|
|
22155
22215
|
const agent = this.createAgent(resolvedModel.model, true, void 0, activeExtractors);
|
|
22156
22216
|
const internalRequestContext = withOmInternalThreadId(requestContext, agent.id);
|
|
22157
22217
|
const multiThreadAttachmentFilter = this.resolveAttachmentFilter(resolvedModel.model, requestContext);
|
|
22158
|
-
const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread,
|
|
22218
|
+
const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread, wasTruncated, this.observationConfig.threadTitle, activeExtractors, {
|
|
22159
22219
|
attachmentFilter: multiThreadAttachmentFilter,
|
|
22160
22220
|
timeZone
|
|
22161
22221
|
})];
|
|
@@ -22169,6 +22229,7 @@ var ObserverRunner = class {
|
|
|
22169
22229
|
metadata: {
|
|
22170
22230
|
omThreadCount: threadOrder.length,
|
|
22171
22231
|
omPreviousObserverTokens: this.observationConfig.previousObserverTokens,
|
|
22232
|
+
omWasTruncated: wasTruncated ?? false,
|
|
22172
22233
|
omThreadTitleEnabled: this.observationConfig.threadTitle,
|
|
22173
22234
|
...resolvedModel.selectedThreshold !== void 0 ? { omSelectedThreshold: resolvedModel.selectedThreshold } : {},
|
|
22174
22235
|
...resolvedModel.routingStrategy ? { omRoutingStrategy: resolvedModel.routingStrategy } : {},
|
|
@@ -26437,6 +26498,82 @@ function resolveAutoModelId(mainModelId, options) {
|
|
|
26437
26498
|
return pick ? `${prefix}${pick}` : mainModelId;
|
|
26438
26499
|
}
|
|
26439
26500
|
//#endregion
|
|
26501
|
+
//#region src/processors/observational-memory/commit-queue.ts
|
|
26502
|
+
/** How long an op may hold its key before later ops on that key stop waiting for it. */
|
|
26503
|
+
const OM_COMMIT_STALL_MS = 6e4;
|
|
26504
|
+
/** The per-thread / per-resource key shared by the commit queue and `BufferingCoordinator`. */
|
|
26505
|
+
function getOMLockKey(scope, threadId, resourceId) {
|
|
26506
|
+
if (scope === "resource" && resourceId) return `resource:${resourceId}`;
|
|
26507
|
+
return `thread:${threadId ?? "unknown"}`;
|
|
26508
|
+
}
|
|
26509
|
+
const REGISTRY = Symbol.for("@mastra/memory/om-commit-queue");
|
|
26510
|
+
const { queues, heldKeys } = globalThis[REGISTRY] ??= {
|
|
26511
|
+
queues: /* @__PURE__ */ new Map(),
|
|
26512
|
+
heldKeys: new async_hooks.AsyncLocalStorage()
|
|
26513
|
+
};
|
|
26514
|
+
function holdsKey(key) {
|
|
26515
|
+
const token = heldKeys.getStore()?.get(key);
|
|
26516
|
+
return token !== void 0 && queues.get(key)?.running === token;
|
|
26517
|
+
}
|
|
26518
|
+
function runNext(key, queue) {
|
|
26519
|
+
const next = queue.reflection.shift() ?? queue.normal.shift();
|
|
26520
|
+
if (!next) {
|
|
26521
|
+
queue.running = null;
|
|
26522
|
+
queues.delete(key);
|
|
26523
|
+
return;
|
|
26524
|
+
}
|
|
26525
|
+
next();
|
|
26526
|
+
}
|
|
26527
|
+
/**
|
|
26528
|
+
* Runs `op` once every earlier op on `key` has settled (reflection-priority ops first among those
|
|
26529
|
+
* still waiting) and resolves or rejects with its result. A rejection reaches only this caller.
|
|
26530
|
+
*
|
|
26531
|
+
* Enqueueing on a key from inside an op that holds the same key, directly or through nested ops
|
|
26532
|
+
* on other keys, would deadlock, so it rejects.
|
|
26533
|
+
*/
|
|
26534
|
+
function runOMCommit(key, op, { priority = "normal" } = {}) {
|
|
26535
|
+
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.`));
|
|
26536
|
+
return new Promise((resolve, reject) => {
|
|
26537
|
+
let queue = queues.get(key);
|
|
26538
|
+
if (!queue) {
|
|
26539
|
+
queue = {
|
|
26540
|
+
running: null,
|
|
26541
|
+
reflection: [],
|
|
26542
|
+
normal: []
|
|
26543
|
+
};
|
|
26544
|
+
queues.set(key, queue);
|
|
26545
|
+
}
|
|
26546
|
+
const owner = queue;
|
|
26547
|
+
const start = async_hooks.AsyncResource.bind(() => {
|
|
26548
|
+
const token = Symbol(key);
|
|
26549
|
+
owner.running = token;
|
|
26550
|
+
const held = new Map(heldKeys.getStore());
|
|
26551
|
+
held.set(key, token);
|
|
26552
|
+
let released = false;
|
|
26553
|
+
const release = () => {
|
|
26554
|
+
if (released) return;
|
|
26555
|
+
released = true;
|
|
26556
|
+
clearTimeout(stallTimer);
|
|
26557
|
+
runNext(key, owner);
|
|
26558
|
+
};
|
|
26559
|
+
const stallTimer = setTimeout(() => {
|
|
26560
|
+
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.`);
|
|
26561
|
+
release();
|
|
26562
|
+
}, OM_COMMIT_STALL_MS);
|
|
26563
|
+
stallTimer.unref?.();
|
|
26564
|
+
heldKeys.run(held, () => Promise.resolve().then(op)).then((value) => {
|
|
26565
|
+
release();
|
|
26566
|
+
resolve(value);
|
|
26567
|
+
}, (error) => {
|
|
26568
|
+
release();
|
|
26569
|
+
reject(error);
|
|
26570
|
+
});
|
|
26571
|
+
});
|
|
26572
|
+
(priority === "reflection" ? owner.reflection : owner.normal).push(start);
|
|
26573
|
+
if (owner.running === null) runNext(key, owner);
|
|
26574
|
+
});
|
|
26575
|
+
}
|
|
26576
|
+
//#endregion
|
|
26440
26577
|
//#region src/processors/observational-memory/operation-registry.ts
|
|
26441
26578
|
const activeOps = /* @__PURE__ */ new Map();
|
|
26442
26579
|
function opKey(recordId, op) {
|
|
@@ -26476,11 +26613,37 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26476
26613
|
*/
|
|
26477
26614
|
static asyncBufferingOps = /* @__PURE__ */ new Map();
|
|
26478
26615
|
/**
|
|
26616
|
+
* Observation buffer ops that may still append a buffered chunk.
|
|
26617
|
+
* Key format: "obs:{lockKey}"
|
|
26618
|
+
* Value: Promise that resolves once the op has persisted its chunk (or ended without one)
|
|
26619
|
+
*
|
|
26620
|
+
* Above the threshold→blockAfter band, activation waits (bounded) on this phase so the chunk
|
|
26621
|
+
* activates instead of being re-observed by the sync pass. It is not needed for safety: storage
|
|
26622
|
+
* keeps a chunk appended during activation. An op stays in asyncBufferingOps through its
|
|
26623
|
+
* post-persist work (indexing, thread title), which activation never waits for.
|
|
26624
|
+
*/
|
|
26625
|
+
static pendingChunkWrites = /* @__PURE__ */ new Map();
|
|
26626
|
+
/**
|
|
26479
26627
|
* Track the last token boundary at which we started buffering.
|
|
26480
26628
|
* Key format: "obs:{lockKey}" or "refl:{lockKey}"
|
|
26481
26629
|
*/
|
|
26482
26630
|
static lastBufferedBoundary = /* @__PURE__ */ new Map();
|
|
26483
26631
|
/**
|
|
26632
|
+
* The buffer op that set the observation boundary, if one did. Any other write (an activation
|
|
26633
|
+
* reset, another op) replaces or clears it, so an op that outlived it never writes its boundary
|
|
26634
|
+
* back. Key format: "obs:{lockKey}"
|
|
26635
|
+
*/
|
|
26636
|
+
static observationBoundaryOwners = /* @__PURE__ */ new Map();
|
|
26637
|
+
/** Set the observation boundary; `owner` is the buffer op setting it, if any. */
|
|
26638
|
+
static setObservationBoundary(bufferKey, tokens, owner) {
|
|
26639
|
+
BufferingCoordinator.lastBufferedBoundary.set(bufferKey, tokens);
|
|
26640
|
+
if (owner) BufferingCoordinator.observationBoundaryOwners.set(bufferKey, owner);
|
|
26641
|
+
else BufferingCoordinator.observationBoundaryOwners.delete(bufferKey);
|
|
26642
|
+
}
|
|
26643
|
+
static ownsObservationBoundary(bufferKey, owner) {
|
|
26644
|
+
return BufferingCoordinator.observationBoundaryOwners.get(bufferKey) === owner;
|
|
26645
|
+
}
|
|
26646
|
+
/**
|
|
26484
26647
|
* Track the timestamp cursor for buffered messages.
|
|
26485
26648
|
* Key format: "obs:{lockKey}"
|
|
26486
26649
|
*/
|
|
@@ -26496,8 +26659,7 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26496
26659
|
this.scope = opts.scope;
|
|
26497
26660
|
}
|
|
26498
26661
|
getLockKey(threadId, resourceId) {
|
|
26499
|
-
|
|
26500
|
-
return `thread:${threadId ?? "unknown"}`;
|
|
26662
|
+
return getOMLockKey(this.scope, threadId, resourceId);
|
|
26501
26663
|
}
|
|
26502
26664
|
isAsyncObservationEnabled() {
|
|
26503
26665
|
return this.observationConfig.bufferTokens !== void 0 && this.observationConfig.bufferTokens > 0;
|
|
@@ -26515,6 +26677,19 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26515
26677
|
return BufferingCoordinator.asyncBufferingOps.has(bufferKey);
|
|
26516
26678
|
}
|
|
26517
26679
|
/**
|
|
26680
|
+
* Mark an observation buffer op as able to append a chunk. Call the returned release
|
|
26681
|
+
* function once it no longer can; releasing more than once is a no-op.
|
|
26682
|
+
*/
|
|
26683
|
+
static trackChunkWrite(bufferKey) {
|
|
26684
|
+
let resolve;
|
|
26685
|
+
const pending = new Promise((r) => resolve = r);
|
|
26686
|
+
BufferingCoordinator.pendingChunkWrites.set(bufferKey, pending);
|
|
26687
|
+
return () => {
|
|
26688
|
+
if (BufferingCoordinator.pendingChunkWrites.get(bufferKey) === pending) BufferingCoordinator.pendingChunkWrites.delete(bufferKey);
|
|
26689
|
+
resolve();
|
|
26690
|
+
};
|
|
26691
|
+
}
|
|
26692
|
+
/**
|
|
26518
26693
|
* Clean up static maps for a thread/resource to prevent memory leaks.
|
|
26519
26694
|
*/
|
|
26520
26695
|
cleanupStaticMaps(threadId, resourceId, activatedMessageIds) {
|
|
@@ -26523,10 +26698,12 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26523
26698
|
const reflBufKey = this.getReflectionBufferKey(lockKey);
|
|
26524
26699
|
if (activatedMessageIds) {
|
|
26525
26700
|
BufferingCoordinator.lastBufferedBoundary.delete(obsBufKey);
|
|
26701
|
+
BufferingCoordinator.observationBoundaryOwners.delete(obsBufKey);
|
|
26526
26702
|
BufferingCoordinator.lastBufferedAtTime.delete(obsBufKey);
|
|
26527
26703
|
} else {
|
|
26528
26704
|
BufferingCoordinator.lastBufferedAtTime.delete(obsBufKey);
|
|
26529
26705
|
BufferingCoordinator.lastBufferedBoundary.delete(obsBufKey);
|
|
26706
|
+
BufferingCoordinator.observationBoundaryOwners.delete(obsBufKey);
|
|
26530
26707
|
BufferingCoordinator.lastBufferedBoundary.delete(reflBufKey);
|
|
26531
26708
|
BufferingCoordinator.asyncBufferingOps.delete(obsBufKey);
|
|
26532
26709
|
BufferingCoordinator.asyncBufferingOps.delete(reflBufKey);
|
|
@@ -26541,7 +26718,7 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26541
26718
|
if (record.isBufferingObservation) {
|
|
26542
26719
|
if (isOpActiveInProcess(record.id, "bufferingObservation")) return false;
|
|
26543
26720
|
omDebug(`[OM:shouldTriggerAsyncObs] isBufferingObservation=true but stale, clearing`);
|
|
26544
|
-
storage
|
|
26721
|
+
if (storage) runOMCommit(lockKey, () => storage.setBufferingObservationFlag(record.id, false)).catch(() => {});
|
|
26545
26722
|
}
|
|
26546
26723
|
const bufferKey = this.getObservationBufferKey(lockKey);
|
|
26547
26724
|
if (this.isAsyncBufferingInProgress(bufferKey)) return false;
|
|
@@ -26561,7 +26738,7 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26561
26738
|
* Await any in-flight async buffering operations for a given thread/resource.
|
|
26562
26739
|
*/
|
|
26563
26740
|
static async awaitBuffering(threadId, resourceId, scope, timeoutMs = 3e4) {
|
|
26564
|
-
const lockKey = scope
|
|
26741
|
+
const lockKey = getOMLockKey(scope, threadId, resourceId);
|
|
26565
26742
|
const obsKey = `obs:${lockKey}`;
|
|
26566
26743
|
const reflKey = `refl:${lockKey}`;
|
|
26567
26744
|
const promises = [];
|
|
@@ -26859,6 +27036,36 @@ function createThreadUpdateMarker(params) {
|
|
|
26859
27036
|
}
|
|
26860
27037
|
};
|
|
26861
27038
|
}
|
|
27039
|
+
//#endregion
|
|
27040
|
+
//#region src/processors/observational-memory/record-lineage.ts
|
|
27041
|
+
/**
|
|
27042
|
+
* The current head of `origin`'s lineage (the generations reflections have created from it),
|
|
27043
|
+
* or `null` when that lineage is gone.
|
|
27044
|
+
*
|
|
27045
|
+
* Use this instead of reading the head by thread/resource when committing work that started
|
|
27046
|
+
* from `origin`. Clearing a record (`om.clear`, `Memory.deleteThread`) deletes every generation
|
|
27047
|
+
* of the thread/resource, and the thread can then get a new, unrelated record. Work produced
|
|
27048
|
+
* from the cleared messages must never be committed to it.
|
|
27049
|
+
*
|
|
27050
|
+
* Generation ids are never reused and clearing deletes all of them, so the head belongs to
|
|
27051
|
+
* `origin`'s lineage iff `origin` still exists.
|
|
27052
|
+
*/
|
|
27053
|
+
async function getLineageHead(storage, origin) {
|
|
27054
|
+
const head = await storage.getObservationalMemory(origin.threadId, origin.resourceId);
|
|
27055
|
+
if (!head || head.id === origin.id) return head;
|
|
27056
|
+
if (head.generationCount <= origin.generationCount) return null;
|
|
27057
|
+
return (await storage.getObservationalMemoryHistory(origin.threadId, origin.resourceId, head.generationCount - origin.generationCount + 3)).some((record) => record.id === origin.id) ? head : null;
|
|
27058
|
+
}
|
|
27059
|
+
/**
|
|
27060
|
+
* The generation `id` of `origin`'s lineage, or `null` when that lineage is gone or `id` is not
|
|
27061
|
+
* part of it. Unlike {@link getLineageHead}, this keeps naming `id` after later reflections have
|
|
27062
|
+
* superseded it, e.g. to report which generation a write landed on.
|
|
27063
|
+
*/
|
|
27064
|
+
async function getLineageRecord(storage, origin, id) {
|
|
27065
|
+
const head = await getLineageHead(storage, origin);
|
|
27066
|
+
if (!head || head.id === id) return head;
|
|
27067
|
+
return (await storage.getObservationalMemoryHistory(origin.threadId, origin.resourceId, head.generationCount - origin.generationCount + 3)).find((record) => record.id === id) ?? null;
|
|
27068
|
+
}
|
|
26862
27069
|
function isRecord$1(value) {
|
|
26863
27070
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
26864
27071
|
}
|
|
@@ -27005,6 +27212,39 @@ function calculateProjectedMessageRemoval(chunks, bufferActivation, messageToken
|
|
|
27005
27212
|
//#region src/processors/observational-memory/observation-strategies/base.ts
|
|
27006
27213
|
/** Module-level xxhash singleton — loaded once, shared across all strategy instances. */
|
|
27007
27214
|
const hasherPromise = (0, xxhash_wasm.default)();
|
|
27215
|
+
/** Recompose-and-retry rounds for an observation commit that hit a retired or changed head. */
|
|
27216
|
+
const MAX_HEAD_COMMIT_RETRIES = 3;
|
|
27217
|
+
const OBSERVATION_LIFECYCLE_MARKERS = /* @__PURE__ */ new Set([
|
|
27218
|
+
"data-om-observation-start",
|
|
27219
|
+
"data-om-observation-end",
|
|
27220
|
+
"data-om-observation-failed"
|
|
27221
|
+
]);
|
|
27222
|
+
function isOmPart(part) {
|
|
27223
|
+
return String(part?.type ?? "").startsWith("data-om-");
|
|
27224
|
+
}
|
|
27225
|
+
/**
|
|
27226
|
+
* Index in `message` right after this cycle's start marker (and any OM parts directly after it),
|
|
27227
|
+
* or -1 when the message doesn't carry it.
|
|
27228
|
+
*/
|
|
27229
|
+
function afterOwnStartMarker(message, marker) {
|
|
27230
|
+
const cycleId = marker.data?.cycleId;
|
|
27231
|
+
const startType = marker.type.startsWith("data-om-buffering-") ? "data-om-buffering-start" : "data-om-observation-start";
|
|
27232
|
+
if (!cycleId || marker.type === startType) return -1;
|
|
27233
|
+
const parts = message.content?.parts ?? [];
|
|
27234
|
+
const start = parts.findIndex((part) => part?.type === startType && part?.data?.cycleId === cycleId);
|
|
27235
|
+
if (start === -1) return -1;
|
|
27236
|
+
let index = start + 1;
|
|
27237
|
+
while (index < parts.length && isOmPart(parts[index])) index++;
|
|
27238
|
+
return index;
|
|
27239
|
+
}
|
|
27240
|
+
/** True when the message has parts no completed observation boundary covers (OM markers aside). */
|
|
27241
|
+
function hasUnobservedContent(message) {
|
|
27242
|
+
return getUnobservedParts(message).some((part) => !isOmPart(part));
|
|
27243
|
+
}
|
|
27244
|
+
function isObservationLifecycleMarker(marker) {
|
|
27245
|
+
const operationType = marker.data?.operationType;
|
|
27246
|
+
return OBSERVATION_LIFECYCLE_MARKERS.has(marker.type) && operationType !== "reflection";
|
|
27247
|
+
}
|
|
27008
27248
|
/**
|
|
27009
27249
|
* Abstract base class for observation strategies.
|
|
27010
27250
|
*
|
|
@@ -27054,17 +27294,35 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
27054
27294
|
const fresh = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
27055
27295
|
if (fresh?.lastObservedAt && record.lastObservedAt && fresh.lastObservedAt > record.lastObservedAt) return { observed: false };
|
|
27056
27296
|
}
|
|
27057
|
-
const { messages, existingObservations } = await this.prepare();
|
|
27297
|
+
const { messages, existingObservations, contextRecord } = await this.prepare();
|
|
27298
|
+
if (messages.length === 0) return { observed: false };
|
|
27058
27299
|
const observationMessages = stripSubconsciousSignals(messages);
|
|
27059
27300
|
await this.emitStartMarkers(cycleId);
|
|
27060
|
-
const
|
|
27061
|
-
const
|
|
27062
|
-
|
|
27301
|
+
const observerContext = this.deps.prepareObserverContext(existingObservations, contextRecord ?? record);
|
|
27302
|
+
const output = await this.observe(observerContext.context ?? "", observationMessages, observerContext.wasTruncated);
|
|
27303
|
+
let processed = await this.process(output, existingObservations);
|
|
27304
|
+
let committedRecord = record;
|
|
27305
|
+
const outcome = await this.persist(processed);
|
|
27306
|
+
this.settleObservationCommit(outcome?.status === "committed" && !outcome.alreadyCovered);
|
|
27307
|
+
if (outcome?.status === "not-committed") {
|
|
27308
|
+
omDebug(`[OM:observe] cycle ${cycleId} not committed: ${outcome.reason}`);
|
|
27309
|
+
await this.emitFailedMarkers(cycleId, /* @__PURE__ */ new Error(`Observation not committed: ${outcome.reason}`));
|
|
27310
|
+
return {
|
|
27311
|
+
observed: false,
|
|
27312
|
+
usage: output.usage,
|
|
27313
|
+
providerMetadata: output.providerMetadata
|
|
27314
|
+
};
|
|
27315
|
+
}
|
|
27316
|
+
if (outcome?.status === "committed") {
|
|
27317
|
+
processed = outcome.processed;
|
|
27318
|
+
committedRecord = outcome.record;
|
|
27319
|
+
}
|
|
27063
27320
|
await this.emitEndMarkers(cycleId, processed);
|
|
27064
27321
|
if (this.needsReflection) await this.deps.reflector.maybeReflect({
|
|
27065
27322
|
record: {
|
|
27066
|
-
...
|
|
27067
|
-
activeObservations: processed.observations
|
|
27323
|
+
...committedRecord,
|
|
27324
|
+
activeObservations: processed.observations,
|
|
27325
|
+
observationTokenCount: processed.observationTokens
|
|
27068
27326
|
},
|
|
27069
27327
|
observationTokens: processed.observationTokens,
|
|
27070
27328
|
threadId,
|
|
@@ -27100,7 +27358,8 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
27100
27358
|
threadId
|
|
27101
27359
|
}
|
|
27102
27360
|
};
|
|
27103
|
-
|
|
27361
|
+
const observed = this.getObservedMessagesForThread(threadId);
|
|
27362
|
+
await (observed?.length ? this.persistMarkerToObservedMessage(failedMarkerForStorage, observed, threadId, this.opts.resourceId) : this.persistMarkerToStorage(failedMarkerForStorage, threadId, this.opts.resourceId)).catch(() => {});
|
|
27104
27363
|
if (abortSignal?.aborted) throw error;
|
|
27105
27364
|
omError("[OM] Observation failed", error);
|
|
27106
27365
|
return {
|
|
@@ -27127,6 +27386,11 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
27127
27386
|
transient: true
|
|
27128
27387
|
}).catch(() => {});
|
|
27129
27388
|
const markerThreadId = marker.data?.threadId ?? this.opts.threadId;
|
|
27389
|
+
const observed = isObservationLifecycleMarker(marker) ? this.getObservedMessagesForThread(markerThreadId) : void 0;
|
|
27390
|
+
if (observed?.length) {
|
|
27391
|
+
await this.persistMarkerToObservedMessage(marker, observed, markerThreadId, this.opts.resourceId);
|
|
27392
|
+
return;
|
|
27393
|
+
}
|
|
27130
27394
|
if (!await this.persistMarkerToMessage(marker, this.opts.messageList, markerThreadId, this.opts.resourceId)) await this.persistMarkerToStorage(marker, markerThreadId, this.opts.resourceId);
|
|
27131
27395
|
}
|
|
27132
27396
|
getObservationMarkerConfig() {
|
|
@@ -27210,6 +27474,76 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
27210
27474
|
}
|
|
27211
27475
|
return `${existingObservations}${lastObservedAt ? ObservationStrategy.createMessageBoundary(lastObservedAt) : "\n\n"}${newThreadSection}`;
|
|
27212
27476
|
}
|
|
27477
|
+
/**
|
|
27478
|
+
* Commit composed observations to the current head generation.
|
|
27479
|
+
*
|
|
27480
|
+
* The write is conditional on the head text the observations were composed from. When the
|
|
27481
|
+
* target generation was retired by a reflection, or another writer changed the text, the
|
|
27482
|
+
* observations are recomposed against the fresh head and the commit is retried (bounded).
|
|
27483
|
+
* When the fresh head's `observedMessageIds` already include every message this cycle observed
|
|
27484
|
+
* (another instance observed them first), nothing is written and `alreadyCovered` is set, so
|
|
27485
|
+
* callers advance cursors and markers without adding a second copy of the same observations.
|
|
27486
|
+
* The cursor alone is not proof: an activation can move it past a message it never covered.
|
|
27487
|
+
* Returns null when no commit landed — callers must then leave cursors, markers, and the live
|
|
27488
|
+
* context untouched.
|
|
27489
|
+
*/
|
|
27490
|
+
async commitActiveObservationsToHead(opts) {
|
|
27491
|
+
let { processed, composedFrom, target } = opts;
|
|
27492
|
+
for (let attempt = 0; attempt <= MAX_HEAD_COMMIT_RETRIES; attempt++) {
|
|
27493
|
+
const result = await this.runCommit(async () => {
|
|
27494
|
+
const head = await getLineageHead(this.storage, target);
|
|
27495
|
+
if (!head) return null;
|
|
27496
|
+
if (head.id !== target.id || (head.activeObservations ?? "") !== composedFrom) {
|
|
27497
|
+
if (this.headCoversCycle(head, opts.cycleMessageIds)) {
|
|
27498
|
+
omDebug(`[OM:observe] head ${head.id} already covers this cycle's messages; skipping the duplicate commit`);
|
|
27499
|
+
target = head;
|
|
27500
|
+
return {
|
|
27501
|
+
applied: true,
|
|
27502
|
+
alreadyCovered: true
|
|
27503
|
+
};
|
|
27504
|
+
}
|
|
27505
|
+
target = head;
|
|
27506
|
+
composedFrom = head.activeObservations ?? "";
|
|
27507
|
+
processed = await opts.recompose(head);
|
|
27508
|
+
}
|
|
27509
|
+
const input = {
|
|
27510
|
+
id: target.id,
|
|
27511
|
+
observations: processed.observations,
|
|
27512
|
+
tokenCount: processed.observationTokens,
|
|
27513
|
+
lastObservedAt: processed.lastObservedAt,
|
|
27514
|
+
observedMessageIds: processed.observedMessageIds,
|
|
27515
|
+
expectedActiveObservations: composedFrom
|
|
27516
|
+
};
|
|
27517
|
+
return typeof this.storage.commitActiveObservations === "function" ? await this.storage.commitActiveObservations(input) : (await this.storage.updateActiveObservations(input), { applied: true });
|
|
27518
|
+
});
|
|
27519
|
+
if (!result) return null;
|
|
27520
|
+
if ("alreadyCovered" in result) return {
|
|
27521
|
+
processed: {
|
|
27522
|
+
...processed,
|
|
27523
|
+
observations: target.activeObservations ?? "",
|
|
27524
|
+
observationTokens: target.observationTokenCount
|
|
27525
|
+
},
|
|
27526
|
+
record: target,
|
|
27527
|
+
alreadyCovered: true
|
|
27528
|
+
};
|
|
27529
|
+
if (result.applied) return {
|
|
27530
|
+
processed,
|
|
27531
|
+
record: target
|
|
27532
|
+
};
|
|
27533
|
+
omDebug(`[OM:observe] commit to ${target.id} not applied (${result.reason}); retrying against the head`);
|
|
27534
|
+
}
|
|
27535
|
+
return null;
|
|
27536
|
+
}
|
|
27537
|
+
headCoversCycle(head, cycleMessageIds) {
|
|
27538
|
+
if (cycleMessageIds.length === 0) return false;
|
|
27539
|
+
const headIds = new Set(Array.isArray(head.observedMessageIds) ? head.observedMessageIds : []);
|
|
27540
|
+
return cycleMessageIds.every((id) => headIds.has(id));
|
|
27541
|
+
}
|
|
27542
|
+
/** Runs one storage commit in this thread's (or resource's) slot of the process-wide commit queue. */
|
|
27543
|
+
runCommit(op) {
|
|
27544
|
+
const { threadId, resourceId, record } = this.opts;
|
|
27545
|
+
return runOMCommit(getOMLockKey(this.scope, record.threadId ?? threadId, record.resourceId ?? resourceId), op);
|
|
27546
|
+
}
|
|
27213
27547
|
async indexObservationGroups(observations, threadId, resourceId, observedAt, recordId) {
|
|
27214
27548
|
if (!resourceId || !this.deps.onIndexObservations) return;
|
|
27215
27549
|
const groups = parseObservationGroups(observations);
|
|
@@ -27228,30 +27562,133 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
27228
27562
|
})));
|
|
27229
27563
|
}
|
|
27230
27564
|
/**
|
|
27565
|
+
* Messages this cycle observed for `threadId`, or undefined when the strategy does not
|
|
27566
|
+
* place observation lifecycle markers.
|
|
27567
|
+
*/
|
|
27568
|
+
getObservedMessagesForThread(_threadId) {}
|
|
27569
|
+
/**
|
|
27570
|
+
* Persist an observation start/end/failed marker without hiding unobserved content. A completed
|
|
27571
|
+
* end marker tells `getUnobservedMessages` that everything before it in that message is observed
|
|
27572
|
+
* (whatever thread the marker names), so it must only follow content this cycle observed.
|
|
27573
|
+
* Placement, in order:
|
|
27574
|
+
* 1. the live MessageList's newest assistant message (this turn's response or its seed), when the
|
|
27575
|
+
* list belongs to the marker's thread and the cycle observed that message or it has no
|
|
27576
|
+
* unobserved content;
|
|
27577
|
+
* 2. the newest observed assistant message; a stored copy (no live list, or another instance's
|
|
27578
|
+
* message) gets the marker right after the parts this cycle observed (end/failed markers right
|
|
27579
|
+
* after this cycle's start marker), so parts added while the Observer ran stay unobserved;
|
|
27580
|
+
* 3. the newest stored assistant message that is not newer than the observed range, only when it
|
|
27581
|
+
* has nothing unobserved (or already carries this cycle's start marker); otherwise no marker,
|
|
27582
|
+
* and the thread cursor alone records the cycle.
|
|
27583
|
+
* Lifecycle markers never go on user messages.
|
|
27584
|
+
*/
|
|
27585
|
+
async persistMarkerToObservedMessage(marker, observed, threadId, resourceId) {
|
|
27586
|
+
const observedIds = new Set(observed.map((m) => m.id));
|
|
27587
|
+
const list = threadId === this.opts.threadId ? this.opts.messageList : void 0;
|
|
27588
|
+
const listMessages = list ? getObservableMessages(list) : [];
|
|
27589
|
+
const live = [...listMessages].reverse().find((m) => m.role === "assistant");
|
|
27590
|
+
if (live && (observedIds.has(live.id) || !hasUnobservedContent(live))) {
|
|
27591
|
+
await this.attachMarker(marker, live, live.content.parts.length, threadId, resourceId);
|
|
27592
|
+
return;
|
|
27593
|
+
}
|
|
27594
|
+
const target = [...observed].reverse().find((m) => m.role === "assistant");
|
|
27595
|
+
if (target) {
|
|
27596
|
+
const fromList = listMessages.find((m) => m.id === target.id);
|
|
27597
|
+
if (fromList) {
|
|
27598
|
+
await this.attachMarker(marker, fromList, fromList.content.parts.length, threadId, resourceId);
|
|
27599
|
+
return;
|
|
27600
|
+
}
|
|
27601
|
+
try {
|
|
27602
|
+
const stored = (await this.storage.listMessagesById({ messageIds: [target.id] })).messages[0];
|
|
27603
|
+
if (stored?.content?.parts && Array.isArray(stored.content.parts)) {
|
|
27604
|
+
const afterStart = afterOwnStartMarker(stored, marker);
|
|
27605
|
+
const index = afterStart !== -1 ? afterStart : this.insertionAfterObserved(stored, target);
|
|
27606
|
+
await this.attachMarker(marker, stored, index, threadId, resourceId);
|
|
27607
|
+
}
|
|
27608
|
+
} catch (e) {
|
|
27609
|
+
omDebug(`[OM:persistMarkerToObservedMessage] failed to load marker target: ${e}`);
|
|
27610
|
+
}
|
|
27611
|
+
return;
|
|
27612
|
+
}
|
|
27613
|
+
const observedTimes = observed.filter((m) => m.createdAt).map((m) => new Date(m.createdAt).getTime());
|
|
27614
|
+
if (observedTimes.length === 0) return;
|
|
27615
|
+
await this.persistMarkerToStorage(marker, threadId, resourceId, {
|
|
27616
|
+
notAfter: new Date(Math.max(...observedTimes)),
|
|
27617
|
+
onlyIfObserved: true
|
|
27618
|
+
});
|
|
27619
|
+
}
|
|
27620
|
+
/** What the cycle observed of each message, captured when it placed its first marker there. */
|
|
27621
|
+
observedParts = /* @__PURE__ */ new Map();
|
|
27622
|
+
/**
|
|
27623
|
+
* Index in `stored` right after the parts the cycle observed in `observed` (and any OM markers
|
|
27624
|
+
* directly after them). Parts are only ever appended, so the observed message's non-marker part
|
|
27625
|
+
* count locates them, counted from the start of the message or, when `observed` is the trimmed
|
|
27626
|
+
* copy `getUnobservedMessages` returns for an already marked message, from just after the stored
|
|
27627
|
+
* copy's last completed boundary. Captured at the cycle's first marker, before the Observer runs,
|
|
27628
|
+
* because `observed` may be a live reference that grows meanwhile. Falls back to the end when the
|
|
27629
|
+
* stored copy has fewer parts than were observed.
|
|
27630
|
+
*/
|
|
27631
|
+
insertionAfterObserved(stored, observed) {
|
|
27632
|
+
if (!this.observedParts.has(observed.id)) this.observedParts.set(observed.id, {
|
|
27633
|
+
count: (observed.content?.parts ?? []).filter((part) => !isOmPart(part)).length,
|
|
27634
|
+
observedIsTrimmed: findLastCompletedObservationBoundary(observed) === -1
|
|
27635
|
+
});
|
|
27636
|
+
const { count, observedIsTrimmed } = this.observedParts.get(observed.id);
|
|
27637
|
+
const parts = stored.content.parts;
|
|
27638
|
+
let index = observedIsTrimmed ? findLastCompletedObservationBoundary(stored) + 1 : 0;
|
|
27639
|
+
let seen = 0;
|
|
27640
|
+
while (index < parts.length && seen < count) {
|
|
27641
|
+
if (!isOmPart(parts[index])) seen++;
|
|
27642
|
+
index++;
|
|
27643
|
+
}
|
|
27644
|
+
if (seen < count) return parts.length;
|
|
27645
|
+
while (index < parts.length && isOmPart(parts[index])) index++;
|
|
27646
|
+
return index;
|
|
27647
|
+
}
|
|
27648
|
+
async attachMarker(marker, msg, index, threadId, resourceId) {
|
|
27649
|
+
const parts = msg.content?.parts;
|
|
27650
|
+
if (!parts || !Array.isArray(parts)) return;
|
|
27651
|
+
const markerData = marker.data;
|
|
27652
|
+
if (!(markerData?.cycleId && parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) parts.splice(index, 0, marker);
|
|
27653
|
+
try {
|
|
27654
|
+
await this.messageHistory.persistMessages({
|
|
27655
|
+
messages: [msg],
|
|
27656
|
+
threadId,
|
|
27657
|
+
resourceId
|
|
27658
|
+
});
|
|
27659
|
+
} catch (e) {
|
|
27660
|
+
omDebug(`[OM:persistMarkerToObservedMessage] failed to save marker to DB: ${e}`);
|
|
27661
|
+
}
|
|
27662
|
+
}
|
|
27663
|
+
/**
|
|
27231
27664
|
* Persist a marker to the last assistant message in storage.
|
|
27232
27665
|
* Fetches messages directly from the DB so it works even when
|
|
27233
27666
|
* no MessageList is available (e.g. async buffering ops).
|
|
27234
27667
|
*/
|
|
27235
|
-
async persistMarkerToStorage(marker, threadId, resourceId) {
|
|
27668
|
+
async persistMarkerToStorage(marker, threadId, resourceId, opts) {
|
|
27236
27669
|
try {
|
|
27237
|
-
const
|
|
27670
|
+
const assistants = ((await this.storage.listMessages({
|
|
27238
27671
|
threadId,
|
|
27239
27672
|
perPage: 20,
|
|
27240
27673
|
orderBy: {
|
|
27241
27674
|
field: "createdAt",
|
|
27242
27675
|
direction: "DESC"
|
|
27243
|
-
}
|
|
27244
|
-
|
|
27245
|
-
|
|
27246
|
-
|
|
27247
|
-
|
|
27248
|
-
|
|
27249
|
-
|
|
27250
|
-
|
|
27251
|
-
|
|
27252
|
-
|
|
27253
|
-
|
|
27254
|
-
|
|
27676
|
+
},
|
|
27677
|
+
...opts?.notAfter ? { filter: { dateRange: { end: opts.notAfter } } } : {}
|
|
27678
|
+
}))?.messages ?? []).filter((msg) => msg?.role === "assistant" && Array.isArray(msg.content?.parts));
|
|
27679
|
+
const startMessage = opts?.onlyIfObserved || opts?.onCycleStartMessage ? assistants.find((m) => afterOwnStartMarker(m, marker) !== -1) : void 0;
|
|
27680
|
+
const msg = opts?.onCycleStartMessage ? startMessage : startMessage ?? assistants[0];
|
|
27681
|
+
if (!msg) return;
|
|
27682
|
+
const markerData = marker.data;
|
|
27683
|
+
const alreadyPresent = markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId);
|
|
27684
|
+
const afterStart = afterOwnStartMarker(msg, marker);
|
|
27685
|
+
if (opts?.onlyIfObserved && afterStart === -1 && hasUnobservedContent(msg)) return;
|
|
27686
|
+
if (!alreadyPresent) msg.content.parts.splice(afterStart !== -1 ? afterStart : msg.content.parts.length, 0, marker);
|
|
27687
|
+
await this.messageHistory.persistMessages({
|
|
27688
|
+
messages: [msg],
|
|
27689
|
+
threadId,
|
|
27690
|
+
resourceId
|
|
27691
|
+
});
|
|
27255
27692
|
} catch (e) {
|
|
27256
27693
|
omDebug(`[OM:persistMarkerToStorage] failed to save marker to DB: ${e}`);
|
|
27257
27694
|
}
|
|
@@ -27313,10 +27750,15 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27313
27750
|
tokensToObserve = 0;
|
|
27314
27751
|
observerResult;
|
|
27315
27752
|
priorExtractedValues;
|
|
27753
|
+
/** Head text the processed observations were composed from (the commit's expected text). */
|
|
27754
|
+
composedFrom = "";
|
|
27316
27755
|
constructor(deps, opts) {
|
|
27317
27756
|
super(deps, opts);
|
|
27318
27757
|
this.lastMessage = opts.messages[opts.messages.length - 1];
|
|
27319
27758
|
}
|
|
27759
|
+
getObservedMessagesForThread(threadId) {
|
|
27760
|
+
return threadId === this.opts.threadId ? this.opts.messages : void 0;
|
|
27761
|
+
}
|
|
27320
27762
|
get needsLock() {
|
|
27321
27763
|
return true;
|
|
27322
27764
|
}
|
|
@@ -27351,9 +27793,11 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27351
27793
|
}
|
|
27352
27794
|
}
|
|
27353
27795
|
this.tokensToObserve = await this.tokenCounter.countMessagesAsync(messages);
|
|
27796
|
+
const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
27354
27797
|
return {
|
|
27355
27798
|
messages,
|
|
27356
|
-
existingObservations:
|
|
27799
|
+
existingObservations: freshRecord?.activeObservations ?? record.activeObservations ?? "",
|
|
27800
|
+
contextRecord: freshRecord
|
|
27357
27801
|
};
|
|
27358
27802
|
}
|
|
27359
27803
|
async emitStartMarkers(cycleId) {
|
|
@@ -27371,11 +27815,12 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27371
27815
|
await this.streamMarker(startMarker);
|
|
27372
27816
|
}
|
|
27373
27817
|
}
|
|
27374
|
-
async observe(
|
|
27818
|
+
async observe(observerContext, messages, wasTruncated) {
|
|
27375
27819
|
const thread = await this.storage.getThreadById({ threadId: this.opts.threadId });
|
|
27376
27820
|
const omMeta = thread ? (0, _mastra_core_memory.getThreadOMMetadata)(thread.metadata) : void 0;
|
|
27377
27821
|
this.priorExtractedValues = getPriorExtractedValues(omMeta, this.observationConfig.extractors);
|
|
27378
|
-
const result = await this.deps.observer.call(
|
|
27822
|
+
const result = await this.deps.observer.call(observerContext, messages, this.opts.abortSignal, {
|
|
27823
|
+
wasTruncated,
|
|
27379
27824
|
requestContext: this.opts.requestContext,
|
|
27380
27825
|
observabilityContext: this.opts.observabilityContext,
|
|
27381
27826
|
priorCurrentTask: omMeta?.currentTask,
|
|
@@ -27421,20 +27866,14 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27421
27866
|
}
|
|
27422
27867
|
async process(output, existingObservations) {
|
|
27423
27868
|
const { record, threadId, messages } = this.opts;
|
|
27424
|
-
|
|
27425
|
-
const
|
|
27426
|
-
const newObservations = await this.wrapObservations(output.observations, existingObservations, threadId, lastObservedAt, messageRange);
|
|
27427
|
-
const observationTokens = this.tokenCounter.countObservations(newObservations);
|
|
27428
|
-
const cycleObservationTokens = this.tokenCounter.countObservations(output.observations);
|
|
27429
|
-
const newMessageIds = messages.map((m) => m.id);
|
|
27430
|
-
const existingIds = record.observedMessageIds ?? [];
|
|
27431
|
-
const observedMessageIds = [.../* @__PURE__ */ new Set([...Array.isArray(existingIds) ? existingIds : [], ...newMessageIds])];
|
|
27869
|
+
this.composedFrom = existingObservations;
|
|
27870
|
+
const processed = await this.compose(output, existingObservations, record);
|
|
27432
27871
|
this.deps.emitDebugEvent({
|
|
27433
27872
|
type: "observation_complete",
|
|
27434
27873
|
timestamp: /* @__PURE__ */ new Date(),
|
|
27435
27874
|
threadId,
|
|
27436
27875
|
resourceId: record.resourceId ?? "",
|
|
27437
|
-
observations:
|
|
27876
|
+
observations: processed.observations,
|
|
27438
27877
|
rawObserverOutput: output.observations,
|
|
27439
27878
|
previousObservations: record.activeObservations,
|
|
27440
27879
|
messages: messages.map((m) => ({
|
|
@@ -27443,11 +27882,23 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27443
27882
|
})),
|
|
27444
27883
|
usage: output.usage
|
|
27445
27884
|
});
|
|
27885
|
+
return processed;
|
|
27886
|
+
}
|
|
27887
|
+
/** Build the observations to commit on top of `existingObservations` (the head text). */
|
|
27888
|
+
async compose(output, existingObservations, baseRecord) {
|
|
27889
|
+
const { threadId, messages } = this.opts;
|
|
27890
|
+
const lastObservedAt = this.getMaxMessageTimestamp(messages);
|
|
27891
|
+
const messageRange = this.retrieval ? buildMessageRange(messages) : void 0;
|
|
27892
|
+
const newObservations = await this.wrapObservations(output.observations, existingObservations, threadId, lastObservedAt, messageRange);
|
|
27893
|
+
const observationTokens = this.tokenCounter.countObservations(newObservations);
|
|
27894
|
+
const cycleObservationTokens = this.tokenCounter.countObservations(output.observations);
|
|
27895
|
+
const newMessageIds = messages.map((m) => m.id);
|
|
27896
|
+
const existingIds = baseRecord.observedMessageIds ?? [];
|
|
27446
27897
|
return {
|
|
27447
27898
|
observations: newObservations,
|
|
27448
27899
|
observationTokens,
|
|
27449
27900
|
cycleObservationTokens,
|
|
27450
|
-
observedMessageIds,
|
|
27901
|
+
observedMessageIds: [.../* @__PURE__ */ new Set([...Array.isArray(existingIds) ? existingIds : [], ...newMessageIds])],
|
|
27451
27902
|
lastObservedAt,
|
|
27452
27903
|
suggestedContinuation: output.suggestedContinuation,
|
|
27453
27904
|
currentTask: output.currentTask,
|
|
@@ -27459,12 +27910,27 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27459
27910
|
}
|
|
27460
27911
|
async persist(processed) {
|
|
27461
27912
|
const { record, threadId, resourceId, messages } = this.opts;
|
|
27462
|
-
const
|
|
27463
|
-
if (!
|
|
27913
|
+
const liveRecord = await getLineageHead(this.storage, record);
|
|
27914
|
+
if (!liveRecord) {
|
|
27464
27915
|
omDebug(`[OM:sync-obs] skipping persist for thread ${threadId}: observational memory record is gone`);
|
|
27465
|
-
return
|
|
27916
|
+
return {
|
|
27917
|
+
status: "not-committed",
|
|
27918
|
+
reason: "the observational memory record was cleared"
|
|
27919
|
+
};
|
|
27466
27920
|
}
|
|
27467
|
-
|
|
27921
|
+
const committed = await this.commitActiveObservationsToHead({
|
|
27922
|
+
processed,
|
|
27923
|
+
composedFrom: this.composedFrom,
|
|
27924
|
+
target: liveRecord,
|
|
27925
|
+
recompose: (head) => this.compose(this.observerResult, head.activeObservations ?? "", head),
|
|
27926
|
+
cycleMessageIds: messages.map((m) => m.id)
|
|
27927
|
+
});
|
|
27928
|
+
if (!committed) return {
|
|
27929
|
+
status: "not-committed",
|
|
27930
|
+
reason: "the observational memory head kept changing during the commit"
|
|
27931
|
+
};
|
|
27932
|
+
processed = committed.processed;
|
|
27933
|
+
const thread = committed.alreadyCovered ? null : await this.storage.getThreadById({ threadId });
|
|
27468
27934
|
if (thread) {
|
|
27469
27935
|
const oldTitle = thread.title?.trim();
|
|
27470
27936
|
const newTitle = resolveThreadTitleUpdate(thread, processed.threadTitle);
|
|
@@ -27486,23 +27952,20 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27486
27952
|
...shouldUpdateThreadTitle ? { title: newTitle } : {},
|
|
27487
27953
|
metadata: newMetadata
|
|
27488
27954
|
});
|
|
27489
|
-
if (shouldUpdateThreadTitle)
|
|
27955
|
+
if (shouldUpdateThreadTitle) await this.streamMarker(createThreadUpdateMarker({
|
|
27490
27956
|
cycleId: this.cycleId ?? crypto.randomUUID(),
|
|
27491
27957
|
threadId,
|
|
27492
27958
|
oldTitle,
|
|
27493
27959
|
newTitle
|
|
27494
|
-
});
|
|
27960
|
+
}));
|
|
27495
27961
|
}
|
|
27496
|
-
if (
|
|
27497
|
-
|
|
27498
|
-
|
|
27499
|
-
|
|
27500
|
-
|
|
27501
|
-
|
|
27502
|
-
|
|
27503
|
-
});
|
|
27504
|
-
await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, record.id);
|
|
27505
|
-
return true;
|
|
27962
|
+
if (!committed.alreadyCovered) await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, committed.record.id);
|
|
27963
|
+
return {
|
|
27964
|
+
status: "committed",
|
|
27965
|
+
processed,
|
|
27966
|
+
record: committed.record,
|
|
27967
|
+
alreadyCovered: committed.alreadyCovered
|
|
27968
|
+
};
|
|
27506
27969
|
}
|
|
27507
27970
|
async emitEndMarkers(cycleId, processed) {
|
|
27508
27971
|
const actualTokensObserved = await this.tokenCounter.countMessagesAsync(this.opts.messages);
|
|
@@ -27546,6 +28009,8 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27546
28009
|
startedAt;
|
|
27547
28010
|
cycleId;
|
|
27548
28011
|
priorExtractedValues;
|
|
28012
|
+
/** The generation the chunk was written to (the head when the target was retired). */
|
|
28013
|
+
persistedRecordId;
|
|
27549
28014
|
constructor(deps, opts) {
|
|
27550
28015
|
super(deps, opts);
|
|
27551
28016
|
this.cycleId = opts.cycleId;
|
|
@@ -27572,12 +28037,13 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27572
28037
|
};
|
|
27573
28038
|
}
|
|
27574
28039
|
async emitStartMarkers(_cycleId) {}
|
|
27575
|
-
async observe(
|
|
28040
|
+
async observe(observerContext, messages, wasTruncated) {
|
|
27576
28041
|
const thread = await this.storage.getThreadById({ threadId: this.opts.threadId });
|
|
27577
28042
|
const omMeta = thread ? (0, _mastra_core_memory.getThreadOMMetadata)(thread.metadata) : void 0;
|
|
27578
28043
|
this.priorExtractedValues = getPriorExtractedValues(omMeta, this.observationConfig.extractors);
|
|
27579
|
-
const result = await this.deps.observer.call(
|
|
28044
|
+
const result = await this.deps.observer.call(observerContext, messages, void 0, {
|
|
27580
28045
|
skipContinuationHints: true,
|
|
28046
|
+
wasTruncated,
|
|
27581
28047
|
requestContext: this.opts.requestContext,
|
|
27582
28048
|
observabilityContext: this.opts.observabilityContext,
|
|
27583
28049
|
priorExtractedValues: this.priorExtractedValues,
|
|
@@ -27651,71 +28117,124 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27651
28117
|
};
|
|
27652
28118
|
}
|
|
27653
28119
|
async persist(processed) {
|
|
27654
|
-
if (!processed.observations) return
|
|
28120
|
+
if (!processed.observations) return;
|
|
27655
28121
|
const { record, threadId, resourceId, messages } = this.opts;
|
|
27656
|
-
|
|
28122
|
+
const liveRecord = await getLineageHead(this.storage, record);
|
|
28123
|
+
if (!liveRecord) {
|
|
27657
28124
|
omDebug(`[OM:asyncBuffer] skipping persist for thread ${threadId}: observational memory record is gone`);
|
|
27658
|
-
return
|
|
28125
|
+
return {
|
|
28126
|
+
status: "not-committed",
|
|
28127
|
+
reason: "the observational memory record was cleared"
|
|
28128
|
+
};
|
|
27659
28129
|
}
|
|
27660
28130
|
const messageTokens = await this.tokenCounter.countMessagesAsync(messages);
|
|
27661
|
-
|
|
27662
|
-
|
|
27663
|
-
|
|
27664
|
-
|
|
27665
|
-
|
|
27666
|
-
|
|
27667
|
-
|
|
27668
|
-
|
|
27669
|
-
|
|
27670
|
-
|
|
27671
|
-
|
|
27672
|
-
|
|
27673
|
-
|
|
27674
|
-
|
|
27675
|
-
|
|
27676
|
-
|
|
27677
|
-
|
|
28131
|
+
let appendAttempts = 0;
|
|
28132
|
+
let targetId = record.id;
|
|
28133
|
+
const appendResult = await withRetry(() => {
|
|
28134
|
+
appendAttempts++;
|
|
28135
|
+
return this.runCommit(async () => {
|
|
28136
|
+
const head = await getLineageHead(this.storage, record);
|
|
28137
|
+
if (!head) return null;
|
|
28138
|
+
targetId = head.id;
|
|
28139
|
+
const input = {
|
|
28140
|
+
id: targetId,
|
|
28141
|
+
chunk: {
|
|
28142
|
+
cycleId: this.cycleId,
|
|
28143
|
+
observations: processed.observations,
|
|
28144
|
+
tokenCount: processed.observationTokens,
|
|
28145
|
+
messageIds: processed.observedMessageIds,
|
|
28146
|
+
messageTokens,
|
|
28147
|
+
lastObservedAt: processed.lastObservedAt,
|
|
28148
|
+
suggestedContinuation: processed.suggestedContinuation,
|
|
28149
|
+
currentTask: processed.currentTask,
|
|
28150
|
+
threadTitle: processed.threadTitle,
|
|
28151
|
+
extractedValues: processed.extractedValues,
|
|
28152
|
+
extractionFailures: processed.extractionFailures
|
|
28153
|
+
},
|
|
28154
|
+
lastBufferedAtTime: processed.lastObservedAt
|
|
28155
|
+
};
|
|
28156
|
+
return typeof this.storage.appendBufferedObservations === "function" ? this.storage.appendBufferedObservations(input) : this.storage.updateBufferedObservations(input).then(() => ({
|
|
28157
|
+
persisted: true,
|
|
28158
|
+
recordId: input.id
|
|
28159
|
+
}));
|
|
28160
|
+
});
|
|
28161
|
+
}, {
|
|
27678
28162
|
label: "persist-buffered-observations",
|
|
27679
28163
|
abortSignal: this.opts.abortSignal
|
|
27680
28164
|
});
|
|
27681
|
-
|
|
27682
|
-
|
|
27683
|
-
|
|
27684
|
-
|
|
27685
|
-
|
|
27686
|
-
|
|
27687
|
-
|
|
27688
|
-
|
|
27689
|
-
|
|
27690
|
-
|
|
27691
|
-
|
|
27692
|
-
|
|
27693
|
-
|
|
27694
|
-
|
|
27695
|
-
|
|
27696
|
-
|
|
27697
|
-
|
|
27698
|
-
|
|
27699
|
-
|
|
27700
|
-
|
|
27701
|
-
|
|
27702
|
-
|
|
27703
|
-
|
|
27704
|
-
|
|
27705
|
-
|
|
27706
|
-
|
|
27707
|
-
|
|
27708
|
-
|
|
27709
|
-
|
|
28165
|
+
if (appendResult === null) {
|
|
28166
|
+
omDebug(`[OM:asyncBuffer] skipping persist for thread ${threadId}: observational memory record was cleared`);
|
|
28167
|
+
return {
|
|
28168
|
+
status: "not-committed",
|
|
28169
|
+
reason: "the observational memory record was cleared"
|
|
28170
|
+
};
|
|
28171
|
+
}
|
|
28172
|
+
let committedRecord = liveRecord;
|
|
28173
|
+
if (!appendResult.persisted) {
|
|
28174
|
+
const head = appendAttempts > 1 ? await getLineageHead(this.storage, record) : null;
|
|
28175
|
+
if (!(!!head && (getBufferedChunks(head).some((chunk) => chunk.cycleId === this.cycleId) || head.activeObservations.includes(processed.observations)))) return {
|
|
28176
|
+
status: "not-committed",
|
|
28177
|
+
reason: "the buffered chunk was already observed"
|
|
28178
|
+
};
|
|
28179
|
+
this.persistedRecordId = head.id;
|
|
28180
|
+
committedRecord = head;
|
|
28181
|
+
} else this.persistedRecordId = appendResult.recordId;
|
|
28182
|
+
await this.emitBufferingEndMarker(processed);
|
|
28183
|
+
await this.opts.onBufferedChunkPersisted?.();
|
|
28184
|
+
if (committedRecord.id !== this.persistedRecordId) {
|
|
28185
|
+
const landedOn = await getLineageRecord(this.storage, record, this.persistedRecordId);
|
|
28186
|
+
if (!landedOn) return {
|
|
28187
|
+
status: "not-committed",
|
|
28188
|
+
reason: "the observational memory record was cleared"
|
|
28189
|
+
};
|
|
28190
|
+
committedRecord = landedOn;
|
|
28191
|
+
}
|
|
28192
|
+
try {
|
|
28193
|
+
await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, this.persistedRecordId);
|
|
28194
|
+
const candidateTitle = processed.threadTitle?.trim();
|
|
28195
|
+
const hasValidThreadTitle = !!candidateTitle && candidateTitle.length >= 3;
|
|
28196
|
+
if (hasValidThreadTitle || processed.extractedValues) {
|
|
28197
|
+
const thread = await this.storage.getThreadById({ threadId });
|
|
28198
|
+
if (thread) {
|
|
28199
|
+
const oldTitle = thread.title?.trim();
|
|
28200
|
+
const newTitle = resolveThreadTitleUpdate(thread, candidateTitle);
|
|
28201
|
+
const shouldUpdateThreadTitle = newTitle !== void 0;
|
|
28202
|
+
const previousOmMetadata = (0, _mastra_core_memory.getThreadOMMetadata)(thread.metadata);
|
|
28203
|
+
const metadataUpdate = buildThreadMetadataFromExtractedValues(processed.extractors ?? this.observationConfig.extractors, processed.extractedValues);
|
|
28204
|
+
const newMetadata = (0, _mastra_core_memory.setThreadOMMetadata)(thread.metadata, {
|
|
28205
|
+
...hasValidThreadTitle || metadataUpdate.threadTitle ? { threadTitle: metadataUpdate.threadTitle ?? processed.threadTitle } : {},
|
|
28206
|
+
extracted: {
|
|
28207
|
+
...previousOmMetadata?.extracted ?? {},
|
|
28208
|
+
...metadataUpdate.extracted ?? {}
|
|
28209
|
+
}
|
|
28210
|
+
});
|
|
28211
|
+
await this.storage.patchThread({
|
|
28212
|
+
id: threadId,
|
|
28213
|
+
...shouldUpdateThreadTitle ? { title: newTitle } : {},
|
|
28214
|
+
metadata: newMetadata
|
|
27710
28215
|
});
|
|
27711
|
-
|
|
28216
|
+
if (shouldUpdateThreadTitle) {
|
|
28217
|
+
const marker = createThreadUpdateMarker({
|
|
28218
|
+
cycleId: this.cycleId,
|
|
28219
|
+
threadId,
|
|
28220
|
+
oldTitle,
|
|
28221
|
+
newTitle
|
|
28222
|
+
});
|
|
28223
|
+
await this.streamMarker(marker);
|
|
28224
|
+
}
|
|
27712
28225
|
}
|
|
27713
28226
|
}
|
|
28227
|
+
} catch (error) {
|
|
28228
|
+
omError("[OM] Post-buffer indexing or thread update failed; the buffered chunk is kept", error);
|
|
27714
28229
|
}
|
|
27715
|
-
return
|
|
28230
|
+
return {
|
|
28231
|
+
status: "committed",
|
|
28232
|
+
processed,
|
|
28233
|
+
record: committedRecord
|
|
28234
|
+
};
|
|
27716
28235
|
}
|
|
27717
|
-
async emitEndMarkers(
|
|
27718
|
-
|
|
28236
|
+
async emitEndMarkers() {}
|
|
28237
|
+
async emitBufferingEndMarker(processed) {
|
|
27719
28238
|
const { record, threadId, messages } = this.opts;
|
|
27720
28239
|
const tokensBuffered = await this.tokenCounter.countMessagesAsync(messages);
|
|
27721
28240
|
const totalBufferedTokens = getBufferedChunks(await this.storage.getObservationalMemory(record.threadId, record.resourceId)).reduce((sum, c) => sum + (c.tokenCount ?? 0), 0) || processed.observationTokens;
|
|
@@ -27725,7 +28244,7 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27725
28244
|
startedAt: this.startedAt,
|
|
27726
28245
|
tokensBuffered,
|
|
27727
28246
|
bufferedTokens: totalBufferedTokens,
|
|
27728
|
-
recordId: record.id,
|
|
28247
|
+
recordId: this.persistedRecordId ?? record.id,
|
|
27729
28248
|
threadId,
|
|
27730
28249
|
observations: processed.observations,
|
|
27731
28250
|
extractedValues: processed.extractedValues,
|
|
@@ -27735,7 +28254,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27735
28254
|
...endMarker,
|
|
27736
28255
|
transient: true
|
|
27737
28256
|
}).catch(() => {});
|
|
27738
|
-
await this.persistMarkerToStorage(endMarker, threadId, record.resourceId ?? void 0
|
|
28257
|
+
await this.persistMarkerToStorage(endMarker, threadId, record.resourceId ?? void 0, {
|
|
28258
|
+
notAfter: new Date(this.startedAt),
|
|
28259
|
+
onCycleStartMessage: true
|
|
28260
|
+
});
|
|
27739
28261
|
}
|
|
27740
28262
|
async emitFailedMarkers(_cycleId, error) {
|
|
27741
28263
|
const { record, threadId, messages } = this.opts;
|
|
@@ -27754,7 +28276,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27754
28276
|
...failedMarker,
|
|
27755
28277
|
transient: true
|
|
27756
28278
|
}).catch(() => {});
|
|
27757
|
-
await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0
|
|
28279
|
+
await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0, {
|
|
28280
|
+
notAfter: new Date(this.startedAt),
|
|
28281
|
+
onCycleStartMessage: true
|
|
28282
|
+
});
|
|
27758
28283
|
}
|
|
27759
28284
|
};
|
|
27760
28285
|
//#endregion
|
|
@@ -27777,10 +28302,17 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27777
28302
|
lastBatchProviderMetadata;
|
|
27778
28303
|
observationResults = [];
|
|
27779
28304
|
priorMetadataByThread = /* @__PURE__ */ new Map();
|
|
28305
|
+
/** Each observed thread's wrapped section from the last composition. */
|
|
28306
|
+
threadSections = /* @__PURE__ */ new Map();
|
|
28307
|
+
/** Head text the processed observations were composed from (the commit's expected text). */
|
|
28308
|
+
composedFrom = "";
|
|
27780
28309
|
constructor(deps, opts) {
|
|
27781
28310
|
super(deps, opts);
|
|
27782
28311
|
this.resourceId = opts.resourceId;
|
|
27783
28312
|
}
|
|
28313
|
+
getObservedMessagesForThread(threadId) {
|
|
28314
|
+
return this.threadsWithMessages.get(threadId);
|
|
28315
|
+
}
|
|
27784
28316
|
get needsLock() {
|
|
27785
28317
|
return true;
|
|
27786
28318
|
}
|
|
@@ -27867,10 +28399,12 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27867
28399
|
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
27868
28400
|
}))
|
|
27869
28401
|
});
|
|
27870
|
-
const
|
|
28402
|
+
const freshRecord = await this.storage.getObservationalMemory(null, this.resourceId);
|
|
28403
|
+
const existingObservations = freshRecord?.activeObservations ?? record.activeObservations ?? "";
|
|
27871
28404
|
return {
|
|
27872
28405
|
messages: Array.from(this.threadsWithMessages.values()).flat(),
|
|
27873
|
-
existingObservations
|
|
28406
|
+
existingObservations,
|
|
28407
|
+
contextRecord: freshRecord
|
|
27874
28408
|
};
|
|
27875
28409
|
}
|
|
27876
28410
|
async emitStartMarkers(cycleId) {
|
|
@@ -27894,7 +28428,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27894
28428
|
}
|
|
27895
28429
|
}
|
|
27896
28430
|
}
|
|
27897
|
-
async observe(
|
|
28431
|
+
async observe(observerContext, _messages, wasTruncated) {
|
|
27898
28432
|
const maxTokensPerBatch = this.observationConfig.maxTokensPerBatch ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.maxTokensPerBatch;
|
|
27899
28433
|
const orderedThreadIds = this.threadOrder.filter((tid) => this.threadsWithMessages.has(tid));
|
|
27900
28434
|
const batches = [];
|
|
@@ -27920,10 +28454,10 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27920
28454
|
}
|
|
27921
28455
|
if (currentBatch.threadIds.length > 0) batches.push(currentBatch);
|
|
27922
28456
|
const batchResults = await Promise.all(batches.map(async (batch) => {
|
|
27923
|
-
return this.deps.observer.callMultiThread(
|
|
28457
|
+
return this.deps.observer.callMultiThread(observerContext, batch.threadMap, batch.threadIds, this.opts.abortSignal, this.opts.requestContext, this.priorMetadataByThread, this.opts.observabilityContext, void 0, {
|
|
27924
28458
|
resourceId: this.opts.resourceId,
|
|
27925
28459
|
trigger: this.opts.trigger
|
|
27926
|
-
}, this.opts.record.observedTimezone, this.opts.agent, this.opts.currentModel);
|
|
28460
|
+
}, this.opts.record.observedTimezone, this.opts.agent, this.opts.currentModel, wasTruncated);
|
|
27927
28461
|
}));
|
|
27928
28462
|
for (const batchResult of batchResults) {
|
|
27929
28463
|
for (const [threadId, result] of batchResult.results) this.multiThreadResults.set(threadId, result);
|
|
@@ -27990,14 +28524,38 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27990
28524
|
}
|
|
27991
28525
|
});
|
|
27992
28526
|
}
|
|
28527
|
+
this.composedFrom = existingObservations;
|
|
28528
|
+
const processed = await this.compose(existingObservations, record);
|
|
28529
|
+
for (const [index, { threadId, threadMessages, result }] of this.observationResults.entries()) this.deps.emitDebugEvent({
|
|
28530
|
+
type: "observation_complete",
|
|
28531
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
28532
|
+
threadId,
|
|
28533
|
+
resourceId: this.resourceId,
|
|
28534
|
+
observations: this.threadSections.get(threadId) ?? "",
|
|
28535
|
+
rawObserverOutput: result.observations,
|
|
28536
|
+
previousObservations: record.activeObservations,
|
|
28537
|
+
messages: threadMessages.map((m) => ({
|
|
28538
|
+
role: m.role,
|
|
28539
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
28540
|
+
})),
|
|
28541
|
+
usage: index === 0 && this.totalBatchUsage.totalTokens > 0 ? this.totalBatchUsage : void 0
|
|
28542
|
+
});
|
|
28543
|
+
return processed;
|
|
28544
|
+
}
|
|
28545
|
+
/**
|
|
28546
|
+
* Merge every observed thread's section into `existingObservations` (the head text). Resource
|
|
28547
|
+
* scope rewrites an existing same-day `<thread>` section in place, so the result is not an
|
|
28548
|
+
* append of the head text.
|
|
28549
|
+
*/
|
|
28550
|
+
async compose(existingObservations, baseRecord) {
|
|
27993
28551
|
let currentObservations = existingObservations;
|
|
27994
28552
|
let cycleObservationTokens = 0;
|
|
27995
28553
|
const threadMetadataUpdates = [];
|
|
27996
|
-
for (const
|
|
27997
|
-
const { threadId, threadMessages, result } = obsResult;
|
|
28554
|
+
for (const { threadId, threadMessages, result } of this.observationResults) {
|
|
27998
28555
|
cycleObservationTokens += this.tokenCounter.countObservations(result.observations);
|
|
27999
28556
|
const messageRange = this.retrieval ? buildMessageRange(threadMessages) : void 0;
|
|
28000
28557
|
const threadSection = await this.wrapWithThreadTag(threadId, result.observations, messageRange);
|
|
28558
|
+
this.threadSections.set(threadId, threadSection);
|
|
28001
28559
|
const threadLastObservedAt = this.getMaxMessageTimestamp(threadMessages);
|
|
28002
28560
|
currentObservations = this.replaceOrAppendThreadSection(currentObservations, threadId, threadSection, threadLastObservedAt);
|
|
28003
28561
|
threadMetadataUpdates.push({
|
|
@@ -28011,26 +28569,11 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
28011
28569
|
extractors: result.extractors,
|
|
28012
28570
|
lastObservedMessageCursor: getLastObservedMessageCursor(threadMessages)
|
|
28013
28571
|
});
|
|
28014
|
-
const isFirstThread = this.observationResults.indexOf(obsResult) === 0;
|
|
28015
|
-
this.deps.emitDebugEvent({
|
|
28016
|
-
type: "observation_complete",
|
|
28017
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
28018
|
-
threadId,
|
|
28019
|
-
resourceId: this.resourceId,
|
|
28020
|
-
observations: threadSection,
|
|
28021
|
-
rawObserverOutput: result.observations,
|
|
28022
|
-
previousObservations: record.activeObservations,
|
|
28023
|
-
messages: threadMessages.map((m) => ({
|
|
28024
|
-
role: m.role,
|
|
28025
|
-
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
28026
|
-
})),
|
|
28027
|
-
usage: isFirstThread && this.totalBatchUsage.totalTokens > 0 ? this.totalBatchUsage : void 0
|
|
28028
|
-
});
|
|
28029
28572
|
}
|
|
28030
28573
|
const observedMessages = this.observationResults.flatMap((r) => r.threadMessages);
|
|
28031
28574
|
const lastObservedAt = this.getMaxMessageTimestamp(observedMessages);
|
|
28032
28575
|
const newMessageIds = observedMessages.map((m) => m.id);
|
|
28033
|
-
const existingIds =
|
|
28576
|
+
const existingIds = baseRecord.observedMessageIds ?? [];
|
|
28034
28577
|
const observedMessageIds = [.../* @__PURE__ */ new Set([...existingIds, ...newMessageIds])];
|
|
28035
28578
|
const observationTokens = this.tokenCounter.countObservations(currentObservations);
|
|
28036
28579
|
return {
|
|
@@ -28044,8 +28587,25 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
28044
28587
|
}
|
|
28045
28588
|
async persist(processed) {
|
|
28046
28589
|
const { record, resourceId } = this.opts;
|
|
28590
|
+
const head = await getLineageHead(this.storage, record);
|
|
28591
|
+
if (!head) return {
|
|
28592
|
+
status: "not-committed",
|
|
28593
|
+
reason: "the observational memory record was cleared"
|
|
28594
|
+
};
|
|
28595
|
+
const committed = await this.commitActiveObservationsToHead({
|
|
28596
|
+
processed,
|
|
28597
|
+
composedFrom: this.composedFrom,
|
|
28598
|
+
target: head,
|
|
28599
|
+
recompose: (fresh) => this.compose(fresh.activeObservations ?? "", fresh),
|
|
28600
|
+
cycleMessageIds: this.observationResults.flatMap((r) => r.threadMessages.map((m) => m.id))
|
|
28601
|
+
});
|
|
28602
|
+
if (!committed) return {
|
|
28603
|
+
status: "not-committed",
|
|
28604
|
+
reason: "the observational memory head kept changing during the commit"
|
|
28605
|
+
};
|
|
28606
|
+
processed = committed.processed;
|
|
28047
28607
|
const threadUpdateMarkers = [];
|
|
28048
|
-
if (processed.threadMetadataUpdates) for (const update of processed.threadMetadataUpdates) {
|
|
28608
|
+
if (processed.threadMetadataUpdates && !committed.alreadyCovered) for (const update of processed.threadMetadataUpdates) {
|
|
28049
28609
|
const thread = await this.storage.getThreadById({ threadId: update.threadId });
|
|
28050
28610
|
if (thread) {
|
|
28051
28611
|
const oldTitle = thread.title?.trim();
|
|
@@ -28078,15 +28638,13 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
28078
28638
|
}
|
|
28079
28639
|
}
|
|
28080
28640
|
for (const marker of threadUpdateMarkers) await this.streamMarker(marker);
|
|
28081
|
-
await this.
|
|
28082
|
-
|
|
28083
|
-
|
|
28084
|
-
|
|
28085
|
-
|
|
28086
|
-
|
|
28087
|
-
}
|
|
28088
|
-
if (resourceId) await Promise.all(this.observationResults.map(({ threadId, threadMessages, result }) => this.indexObservationGroups(result.observations, threadId, resourceId, this.getMaxMessageTimestamp(threadMessages), record.id)));
|
|
28089
|
-
return true;
|
|
28641
|
+
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)));
|
|
28642
|
+
return {
|
|
28643
|
+
status: "committed",
|
|
28644
|
+
processed,
|
|
28645
|
+
record: committed.record,
|
|
28646
|
+
alreadyCovered: committed.alreadyCovered
|
|
28647
|
+
};
|
|
28090
28648
|
}
|
|
28091
28649
|
async emitEndMarkers(cycleId, processed) {
|
|
28092
28650
|
for (const obsResult of this.observationResults) {
|
|
@@ -28145,7 +28703,8 @@ ObservationStrategy.create = ((om, opts) => {
|
|
|
28145
28703
|
observedMessageIds: om.observedMessageIds,
|
|
28146
28704
|
obscureThreadIds: om.getObscureThreadIds(),
|
|
28147
28705
|
onIndexObservations: om.onIndexObservations,
|
|
28148
|
-
emitDebugEvent: (e) => om.emitDebugEvent(e)
|
|
28706
|
+
emitDebugEvent: (e) => om.emitDebugEvent(e),
|
|
28707
|
+
prepareObserverContext: (observations, record) => om.prepareObserverContext(observations, record)
|
|
28149
28708
|
};
|
|
28150
28709
|
if (opts.cycleId) return new AsyncBufferObservationStrategy(deps, opts);
|
|
28151
28710
|
if (deps.scope === "resource" && opts.resourceId) return new ResourceScopedObservationStrategy(deps, opts);
|
|
@@ -28277,8 +28836,8 @@ var ObservationStep = class {
|
|
|
28277
28836
|
let reflected = false;
|
|
28278
28837
|
let didThresholdCleanup = false;
|
|
28279
28838
|
let observerExchange;
|
|
28839
|
+
const step0Messages = this.stepNumber === 0 ? getObservableMessages(messageList) : [];
|
|
28280
28840
|
if (this.stepNumber === 0) {
|
|
28281
|
-
const step0Messages = getObservableMessages(messageList);
|
|
28282
28841
|
const activation = await om.activate({
|
|
28283
28842
|
threadId,
|
|
28284
28843
|
resourceId,
|
|
@@ -28335,7 +28894,17 @@ var ObservationStep = class {
|
|
|
28335
28894
|
record: this.turn.record,
|
|
28336
28895
|
messages: getObservableMessages(messageList)
|
|
28337
28896
|
});
|
|
28338
|
-
if (statusSnapshot.
|
|
28897
|
+
if (statusSnapshot.inAsyncObservationBand && !statusSnapshot.canActivate && !hasIncompleteToolCalls) {
|
|
28898
|
+
await this.turn.refreshRecord();
|
|
28899
|
+
statusSnapshot = await om.getStatus({
|
|
28900
|
+
threadId,
|
|
28901
|
+
resourceId,
|
|
28902
|
+
record: this.turn.record,
|
|
28903
|
+
messages: getObservableMessages(messageList)
|
|
28904
|
+
});
|
|
28905
|
+
}
|
|
28906
|
+
const activateBeforeBuffering = statusSnapshot.inAsyncObservationBand && statusSnapshot.canActivate && !hasIncompleteToolCalls;
|
|
28907
|
+
if (statusSnapshot.shouldBuffer && !activateBeforeBuffering) {
|
|
28339
28908
|
const allMessages = getObservableMessages(messageList);
|
|
28340
28909
|
const unobservedMessages = om.getUnobservedMessages(allMessages, statusSnapshot.record);
|
|
28341
28910
|
const candidates = om.getUnobservedMessages(unobservedMessages, statusSnapshot.record, { excludeBuffered: true });
|
|
@@ -28349,8 +28918,10 @@ var ObservationStep = class {
|
|
|
28349
28918
|
omDebug(`[OM:buffer] onBufferChunkSealed hook failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
28350
28919
|
}
|
|
28351
28920
|
if (this.turn.memory) await this.turn.memory.persistMessages(safeCandidates);
|
|
28352
|
-
messageList.
|
|
28353
|
-
|
|
28921
|
+
const inputIds = new Set(messageList.get.input.db().map((msg) => msg.id));
|
|
28922
|
+
const sealedOutput = safeCandidates.filter((msg) => !inputIds.has(msg.id));
|
|
28923
|
+
messageList.removeByIds(sealedOutput.map((msg) => msg.id));
|
|
28924
|
+
for (const msg of sealedOutput) messageList.add(msg, "memory");
|
|
28354
28925
|
}
|
|
28355
28926
|
if (!deferred) {
|
|
28356
28927
|
om.trackBackgroundWork(om.buffer({
|
|
@@ -28372,7 +28943,8 @@ var ObservationStep = class {
|
|
|
28372
28943
|
buffered = true;
|
|
28373
28944
|
}
|
|
28374
28945
|
}
|
|
28375
|
-
const
|
|
28946
|
+
const bandDefersObservation = statusSnapshot.inAsyncObservationBand && !statusSnapshot.canActivate;
|
|
28947
|
+
const willObserveNow = statusSnapshot.shouldObserve && !hasIncompleteToolCalls && !bandDefersObservation;
|
|
28376
28948
|
/** In-flight message ids the step-0 cleanup must never remove from live context. */
|
|
28377
28949
|
let step0PreserveIds;
|
|
28378
28950
|
if (this.stepNumber > 0 || willObserveNow) {
|
|
@@ -28414,7 +28986,7 @@ var ObservationStep = class {
|
|
|
28414
28986
|
}
|
|
28415
28987
|
if (willObserveNow) {
|
|
28416
28988
|
const preObsGeneration = this.turn.record.generationCount;
|
|
28417
|
-
const obsResult = await this.runThresholdObservation();
|
|
28989
|
+
const obsResult = await this.runThresholdObservation(statusSnapshot.inAsyncObservationBand);
|
|
28418
28990
|
observerExchange = obsResult.observerExchange;
|
|
28419
28991
|
if (obsResult.succeeded) {
|
|
28420
28992
|
observed = true;
|
|
@@ -28487,12 +29059,12 @@ var ObservationStep = class {
|
|
|
28487
29059
|
/**
|
|
28488
29060
|
* Run the full threshold observation pipeline:
|
|
28489
29061
|
* waitForBuffering → re-check → activate buffered chunks → reflect → observe
|
|
28490
|
-
* (sync fallback
|
|
29062
|
+
* (sync fallback at blockAfter when async buffering is enabled)
|
|
28491
29063
|
*/
|
|
28492
|
-
async runThresholdObservation() {
|
|
29064
|
+
async runThresholdObservation(inAsyncObservationBand) {
|
|
28493
29065
|
const { threadId, resourceId, messageList } = this.turn;
|
|
28494
29066
|
const om = this.turn.om;
|
|
28495
|
-
await om.waitForBuffering(threadId, resourceId);
|
|
29067
|
+
if (!inAsyncObservationBand) await om.waitForBuffering(threadId, resourceId);
|
|
28496
29068
|
await this.turn.refreshRecord();
|
|
28497
29069
|
const observableMessages = this.seededResponseMessage ? getObservableMessages(messageList).filter((msg) => msg.id !== this.turn.responseMessageId) : getObservableMessages(messageList);
|
|
28498
29070
|
let status = await om.getStatus({
|
|
@@ -28521,7 +29093,16 @@ var ObservationStep = class {
|
|
|
28521
29093
|
messageList
|
|
28522
29094
|
});
|
|
28523
29095
|
this.turn.setRecord(activation.record);
|
|
28524
|
-
if (!activation.activated)
|
|
29096
|
+
if (!activation.activated) {
|
|
29097
|
+
await this.turn.refreshRecord();
|
|
29098
|
+
status = await om.getStatus({
|
|
29099
|
+
threadId,
|
|
29100
|
+
resourceId,
|
|
29101
|
+
record: this.turn.record,
|
|
29102
|
+
messages: pendingMessages
|
|
29103
|
+
});
|
|
29104
|
+
break;
|
|
29105
|
+
}
|
|
28525
29106
|
activated = true;
|
|
28526
29107
|
const ids = new Set(activation.activatedMessageIds ?? []);
|
|
28527
29108
|
activatedMessageIds.push(...ids);
|
|
@@ -28533,11 +29114,17 @@ var ObservationStep = class {
|
|
|
28533
29114
|
messages: pendingMessages
|
|
28534
29115
|
});
|
|
28535
29116
|
}
|
|
29117
|
+
if (status.shouldObserve && status.canActivate) return {
|
|
29118
|
+
succeeded: activated,
|
|
29119
|
+
record: this.turn.record,
|
|
29120
|
+
activatedMessageIds: activated ? activatedMessageIds : void 0,
|
|
29121
|
+
cleanupMessageIds: activatedMessageIds
|
|
29122
|
+
};
|
|
28536
29123
|
if (activated) {
|
|
28537
|
-
const
|
|
29124
|
+
const preReflectionRecord = this.turn.record;
|
|
28538
29125
|
await om.reflector.maybeReflect({
|
|
28539
|
-
record:
|
|
28540
|
-
observationTokens:
|
|
29126
|
+
record: preReflectionRecord,
|
|
29127
|
+
observationTokens: preReflectionRecord.observationTokenCount ?? 0,
|
|
28541
29128
|
threadId,
|
|
28542
29129
|
writer: this.turn.writer,
|
|
28543
29130
|
messageList,
|
|
@@ -28552,6 +29139,14 @@ var ObservationStep = class {
|
|
|
28552
29139
|
}),
|
|
28553
29140
|
trigger: "turn-sync"
|
|
28554
29141
|
});
|
|
29142
|
+
await this.turn.refreshRecord();
|
|
29143
|
+
const postActivationRecord = this.turn.record;
|
|
29144
|
+
status = await om.getStatus({
|
|
29145
|
+
threadId,
|
|
29146
|
+
resourceId,
|
|
29147
|
+
record: postActivationRecord,
|
|
29148
|
+
messages: pendingMessages
|
|
29149
|
+
});
|
|
28555
29150
|
if (!status.shouldObserve) return {
|
|
28556
29151
|
succeeded: true,
|
|
28557
29152
|
record: postActivationRecord,
|
|
@@ -28559,6 +29154,12 @@ var ObservationStep = class {
|
|
|
28559
29154
|
cleanupMessageIds: activatedMessageIds
|
|
28560
29155
|
};
|
|
28561
29156
|
}
|
|
29157
|
+
if (status.inAsyncObservationBand || status.canActivate) return {
|
|
29158
|
+
succeeded: activated,
|
|
29159
|
+
record: this.turn.record,
|
|
29160
|
+
activatedMessageIds: activated ? activatedMessageIds : void 0,
|
|
29161
|
+
cleanupMessageIds: activatedMessageIds
|
|
29162
|
+
};
|
|
28562
29163
|
let obsResult;
|
|
28563
29164
|
try {
|
|
28564
29165
|
obsResult = await om.observe({
|
|
@@ -28910,13 +29511,18 @@ Take the existing observations and rewrite them to make it easier to continue in
|
|
|
28910
29511
|
|
|
28911
29512
|
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.
|
|
28912
29513
|
|
|
29514
|
+
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.
|
|
29515
|
+
|
|
28913
29516
|
When consolidating observations:
|
|
28914
|
-
-
|
|
28915
|
-
-
|
|
29517
|
+
- Keep every concrete fact: names, places, dates, numbers, amounts, durations, titles, versions, and who said what. Merge duplicates, but never drop specifics
|
|
29518
|
+
- Keep every dated statement with its date, and retain the timestamps that matter (start times, completion times, significant events)
|
|
29519
|
+
- 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
|
|
29520
|
+
- 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
|
|
29521
|
+
- Prefer many short factual lines over prose summaries. Do not replace facts with generalities like "discussed travel plans"
|
|
28916
29522
|
- Combine related items where it makes sense (e.g., "agent called view tool 5 times on file x")
|
|
28917
29523
|
- Preserve ✅ completion markers — they are memory signals that tell the assistant what is already resolved and help prevent repeated work
|
|
28918
29524
|
- Preserve the concrete resolved outcome captured by ✅ markers so the assistant knows what exactly is done
|
|
28919
|
-
- Condense older observations more
|
|
29525
|
+
- Condense the wording of older observations more than recent ones, but keep their facts
|
|
28920
29526
|
|
|
28921
29527
|
CRITICAL: USER ASSERTIONS vs QUESTIONS
|
|
28922
29528
|
- "User stated: X" = authoritative assertion (user told us something about themselves)
|
|
@@ -29198,6 +29804,25 @@ async function withAbortCheck(fn, abortSignal) {
|
|
|
29198
29804
|
* size while still letting them fire sooner than a threshold activation.
|
|
29199
29805
|
*/
|
|
29200
29806
|
const EARLY_ACTIVATION_SIZE_FLOOR_RATIO = .75;
|
|
29807
|
+
/** A reflection commit that created nothing because the reflected record is no longer the head. */
|
|
29808
|
+
var ReflectionNotAppliedError = class extends Error {
|
|
29809
|
+
constructor(recordId, headId) {
|
|
29810
|
+
super(`Reflection not applied: record ${recordId} is no longer the current generation (head is ${headId})`);
|
|
29811
|
+
this.name = "ReflectionNotAppliedError";
|
|
29812
|
+
}
|
|
29813
|
+
};
|
|
29814
|
+
/**
|
|
29815
|
+
* Whether a reflection commit created the generation the caller asked for. Storage creates
|
|
29816
|
+
* nothing (and returns the current head or the stored record) when the reflected record was
|
|
29817
|
+
* already retired or its observations were rewritten since the reflection read them.
|
|
29818
|
+
*
|
|
29819
|
+
* Adapters that predate `newRecordId` ignore it and do not report `supersededBy`; for those a
|
|
29820
|
+
* returned record other than the reflected one means a new generation was created.
|
|
29821
|
+
*/
|
|
29822
|
+
function isReflectionApplied(returned, newRecordId, reflectedRecordId) {
|
|
29823
|
+
if (returned.id === newRecordId) return true;
|
|
29824
|
+
return returned.supersededBy === void 0 && returned.id !== reflectedRecordId;
|
|
29825
|
+
}
|
|
29201
29826
|
/**
|
|
29202
29827
|
* Runs the Reflector agent for compressing observations.
|
|
29203
29828
|
* Handles synchronous reflection, async buffered reflection, and activation.
|
|
@@ -29561,14 +30186,14 @@ var ReflectorRunner = class {
|
|
|
29561
30186
|
* Perform async buffered reflection — reflects observations and stores to bufferedReflection.
|
|
29562
30187
|
* Does NOT create a new generation or update activeObservations.
|
|
29563
30188
|
*/
|
|
29564
|
-
async doAsyncBufferedReflection(record,
|
|
30189
|
+
async doAsyncBufferedReflection(record, bufferKey, writer, requestContext, observabilityContext, priorExtractedValues, mainAgent, sendSignal, trigger, currentModel) {
|
|
29565
30190
|
const currentRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record;
|
|
29566
30191
|
const observationTokens = currentRecord.observationTokenCount ?? 0;
|
|
29567
30192
|
const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(currentRecord));
|
|
29568
30193
|
const bufferActivation = this.reflectionConfig.bufferActivation ?? .5;
|
|
29569
30194
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
29570
30195
|
const cycleId = `reflect-buf-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
29571
|
-
BufferingCoordinator.reflectionBufferCycleIds.set(
|
|
30196
|
+
BufferingCoordinator.reflectionBufferCycleIds.set(bufferKey, cycleId);
|
|
29572
30197
|
const allLines = (currentRecord.activeObservations ?? "").split("\n");
|
|
29573
30198
|
const totalLines = allLines.length;
|
|
29574
30199
|
const avgTokensPerLine = totalLines > 0 ? observationTokens / totalLines : 0;
|
|
@@ -29607,13 +30232,24 @@ var ReflectorRunner = class {
|
|
|
29607
30232
|
await persistThreadExtractedValues(this.storage, this.reflectionConfig.extractors, currentRecord.threadId ?? void 0, reflectResult.extractedValues);
|
|
29608
30233
|
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
29609
30234
|
omDebug(`[OM:reflect] doAsyncBufferedReflection: reflector returned ${reflectionTokenCount} tokens (${reflectResult.observations?.length} chars), saving to recordId=${currentRecord.id}`);
|
|
29610
|
-
await this.
|
|
29611
|
-
id
|
|
29612
|
-
|
|
29613
|
-
|
|
29614
|
-
|
|
29615
|
-
|
|
29616
|
-
|
|
30235
|
+
if (!await runOMCommit(this.buffering.getLockKey(currentRecord.threadId, currentRecord.resourceId), async () => {
|
|
30236
|
+
if ((await this.storage.getObservationalMemory(currentRecord.threadId, currentRecord.resourceId))?.id !== currentRecord.id) return false;
|
|
30237
|
+
await this.storage.updateBufferedReflection({
|
|
30238
|
+
id: currentRecord.id,
|
|
30239
|
+
reflection: reflectResult.observations,
|
|
30240
|
+
tokenCount: reflectionTokenCount,
|
|
30241
|
+
inputTokenCount: sliceTokenEstimate,
|
|
30242
|
+
reflectedObservationLineCount
|
|
30243
|
+
});
|
|
30244
|
+
return true;
|
|
30245
|
+
})) {
|
|
30246
|
+
omDebug(`[OM:reflect] doAsyncBufferedReflection: record ${currentRecord.id} is no longer the head; discarding the buffered reflection`);
|
|
30247
|
+
BufferingCoordinator.lastBufferedBoundary.delete(bufferKey);
|
|
30248
|
+
return {
|
|
30249
|
+
usage: reflectResult.usage,
|
|
30250
|
+
providerMetadata: reflectResult.providerMetadata
|
|
30251
|
+
};
|
|
30252
|
+
}
|
|
29617
30253
|
omDebug(`[OM:reflect] doAsyncBufferedReflection: bufferedReflection saved with lineCount=${reflectedObservationLineCount}`);
|
|
29618
30254
|
if (writer) {
|
|
29619
30255
|
const endMarker = createBufferingEndMarker({
|
|
@@ -29692,10 +30328,16 @@ var ReflectorRunner = class {
|
|
|
29692
30328
|
}
|
|
29693
30329
|
}
|
|
29694
30330
|
omDebug(`[OM:reflect] tryActivateBufferedReflection: activating, beforeTokens=${beforeTokens}, combinedTokenCount=${combinedTokenCount}, reflectedLineCount=${reflectedLineCount}, unreflectedLines=${unreflectedLines.length}`);
|
|
29695
|
-
|
|
30331
|
+
const newRecordId = crypto.randomUUID();
|
|
30332
|
+
const swapped = await runOMCommit(lockKey, () => this.storage.swapBufferedReflectionToActive({
|
|
29696
30333
|
currentRecord: freshRecord,
|
|
29697
|
-
tokenCount: combinedTokenCount
|
|
29698
|
-
|
|
30334
|
+
tokenCount: combinedTokenCount,
|
|
30335
|
+
newRecordId
|
|
30336
|
+
}), { priority: "reflection" });
|
|
30337
|
+
if (!isReflectionApplied(swapped, newRecordId, freshRecord.id)) {
|
|
30338
|
+
omDebug(`[OM:reflect] tryActivateBufferedReflection: buffered reflection on ${freshRecord.id} not applied (head is ${swapped.id})`);
|
|
30339
|
+
return { status: "not-applied" };
|
|
30340
|
+
}
|
|
29699
30341
|
if (committedContext) await this.notifyReflectionCommitted({
|
|
29700
30342
|
...committedContext,
|
|
29701
30343
|
observations: allLines.slice(0, reflectedLineCount).join("\n").trim(),
|
|
@@ -29750,7 +30392,8 @@ var ReflectorRunner = class {
|
|
|
29750
30392
|
* @internal Used by observation strategies. Do not call directly.
|
|
29751
30393
|
*/
|
|
29752
30394
|
async maybeReflect(opts) {
|
|
29753
|
-
const { record, observationTokens, writer, abortSignal, mainAgent, sendSignal, sendStateSignal, messageList, currentModel, reflectionHooks, trigger, requestContext, observabilityContext, lastActivityAt, threadId: requestedThreadId } = opts;
|
|
30395
|
+
const { record: callerRecord, observationTokens, writer, abortSignal, mainAgent, sendSignal, sendStateSignal, messageList, currentModel, reflectionHooks, trigger, requestContext, observabilityContext, lastActivityAt, threadId: requestedThreadId } = opts;
|
|
30396
|
+
const record = { ...callerRecord };
|
|
29754
30397
|
const lockKey = this.buffering.getLockKey(record.threadId, record.resourceId);
|
|
29755
30398
|
const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(record));
|
|
29756
30399
|
const priorExtractedValues = await getThreadExtractedValues(this.storage, this.reflectionConfig.extractors, requestedThreadId ?? record.threadId);
|
|
@@ -29804,7 +30447,7 @@ var ReflectorRunner = class {
|
|
|
29804
30447
|
abortSignal,
|
|
29805
30448
|
observabilityContext
|
|
29806
30449
|
});
|
|
29807
|
-
if (activationResult.status === "activated") return;
|
|
30450
|
+
if (activationResult.status === "activated" || activationResult.status === "not-applied") return;
|
|
29808
30451
|
if (activationResult.status === "suppressed") {
|
|
29809
30452
|
omDebug(`[OM:reflect] skipping sync fallback / re-buffer after suppressed early ${activationMetadata.triggeredBy} activation (reason=${activationResult.reason})`);
|
|
29810
30453
|
return;
|
|
@@ -29887,13 +30530,19 @@ var ReflectorRunner = class {
|
|
|
29887
30530
|
reflectResult.observations = await applyTextTransform(this.hooks, "afterReflection", reflectResult.observations, hookContext);
|
|
29888
30531
|
reflectionUsage = reflectResult.usage;
|
|
29889
30532
|
reflectionProviderMetadata = reflectResult.providerMetadata;
|
|
29890
|
-
await persistThreadExtractedValues(this.storage, this.reflectionConfig.extractors, record.threadId ?? void 0, reflectResult.extractedValues);
|
|
29891
30533
|
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
29892
|
-
|
|
30534
|
+
const newRecordId = crypto.randomUUID();
|
|
30535
|
+
const committedRecord = await runOMCommit(this.buffering.getLockKey(record.threadId, record.resourceId), () => this.storage.createReflectionGeneration({
|
|
29893
30536
|
currentRecord: record,
|
|
29894
30537
|
reflection: reflectResult.observations,
|
|
29895
|
-
tokenCount: reflectionTokenCount
|
|
29896
|
-
|
|
30538
|
+
tokenCount: reflectionTokenCount,
|
|
30539
|
+
newRecordId
|
|
30540
|
+
}), { priority: "reflection" });
|
|
30541
|
+
if (!isReflectionApplied(committedRecord, newRecordId, record.id)) {
|
|
30542
|
+
omDebug(`[OM:reflect] reflection of ${record.id} not applied (head is ${committedRecord.id})`);
|
|
30543
|
+
throw new ReflectionNotAppliedError(record.id, committedRecord.id);
|
|
30544
|
+
}
|
|
30545
|
+
await persistThreadExtractedValues(this.storage, this.reflectionConfig.extractors, record.threadId ?? void 0, reflectResult.extractedValues);
|
|
29897
30546
|
if (reflectionTokenCount >= reflectThreshold) this.syncReflectionSuppression.set(lockKey, reflectionTokenCount);
|
|
29898
30547
|
else this.syncReflectionSuppression.delete(lockKey);
|
|
29899
30548
|
await this.notifyReflectionCommitted({
|
|
@@ -29973,6 +30622,7 @@ var ReflectorRunner = class {
|
|
|
29973
30622
|
failureKind: failedMarker.data.failureKind,
|
|
29974
30623
|
error: reflectionError.message
|
|
29975
30624
|
});
|
|
30625
|
+
if (error instanceof ReflectionNotAppliedError) return;
|
|
29976
30626
|
if (lifecycleError !== void 0 || abortSignal?.aborted || this.reflectionConfig.failurePolicy !== "continue" || !isOmModelExecutionError(error) || error.failureKind !== "reflector-model") throw error;
|
|
29977
30627
|
this.syncReflectionSuppression.set(lockKey, observationTokens);
|
|
29978
30628
|
omError("[OM] Reflection failed", error);
|
|
@@ -30316,6 +30966,8 @@ function writeProcessInputStepReproCapture(params) {
|
|
|
30316
30966
|
* Returns the parts from the latest step of a message (after the last step-start marker).
|
|
30317
30967
|
* If no step-start marker exists, returns all parts.
|
|
30318
30968
|
*/
|
|
30969
|
+
/** Re-reads of the head after an activation swap hit a generation retired by a reflection. */
|
|
30970
|
+
const MAX_ACTIVATION_RETIRED_RETRIES = 3;
|
|
30319
30971
|
function getLatestStepParts(parts) {
|
|
30320
30972
|
for (let i = parts.length - 1; i >= 0; i--) if (parts[i]?.type === "step-start") return parts.slice(i + 1);
|
|
30321
30973
|
return parts;
|
|
@@ -30509,6 +31161,8 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
30509
31161
|
reflector;
|
|
30510
31162
|
/** Buffering state coordinator — manages static maps and buffering lifecycle. */
|
|
30511
31163
|
buffering;
|
|
31164
|
+
/** Unresolved observation blockAfter (multiplier or absolute); resolved per record. */
|
|
31165
|
+
observationBlockAfterSetting;
|
|
30512
31166
|
shouldObscureThreadIds = false;
|
|
30513
31167
|
hasher = (0, xxhash_wasm.default)();
|
|
30514
31168
|
mastra;
|
|
@@ -30644,6 +31298,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
30644
31298
|
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.");
|
|
30645
31299
|
const observationActivateAfterIdle = config.observation?.activateAfterIdle ?? config.activateAfterIdle;
|
|
30646
31300
|
const observationActivateAfterIdlePath = config.observation?.activateAfterIdle !== void 0 ? "observation.activateAfterIdle" : "activateAfterIdle";
|
|
31301
|
+
this.observationBlockAfterSetting = asyncBufferingDisabled ? void 0 : config.observation?.blockAfter ?? (config.observation?.bufferTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferTokens ? 1.2 : void 0);
|
|
30647
31302
|
this.observationConfig = {
|
|
30648
31303
|
model: observationModel,
|
|
30649
31304
|
maxRetries: config.observation?.maxRetries ?? RETRY_CONFIG.maxRetries,
|
|
@@ -30664,8 +31319,8 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
30664
31319
|
bufferActivation: asyncBufferingDisabled ? void 0 : config.observation?.bufferActivation ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferActivation,
|
|
30665
31320
|
activateAfterIdle: parseActivationTTLConfig(observationActivateAfterIdle, observationActivateAfterIdlePath),
|
|
30666
31321
|
activateOnProviderChange: config.observation?.activateOnProviderChange ?? config.activateOnProviderChange ?? false,
|
|
30667
|
-
blockAfter:
|
|
30668
|
-
previousObserverTokens: config.observation?.previousObserverTokens ??
|
|
31322
|
+
blockAfter: resolveBlockAfter(this.observationBlockAfterSetting, config.observation?.messageTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens),
|
|
31323
|
+
previousObserverTokens: config.observation?.previousObserverTokens ?? false,
|
|
30669
31324
|
instruction: config.observation?.instruction,
|
|
30670
31325
|
threadTitle: config.observation?.threadTitle ?? false,
|
|
30671
31326
|
observeAttachments: config.observation?.observeAttachments ?? [...OBSERVATIONAL_MEMORY_DEFAULTS.observation.observeAttachments],
|
|
@@ -30974,6 +31629,13 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
30974
31629
|
return this.observationConfig.messageTokens;
|
|
30975
31630
|
}
|
|
30976
31631
|
/**
|
|
31632
|
+
* Resolve the observation blockAfter for a record. A multiplier scales the record's
|
|
31633
|
+
* effective messageTokens (per-record overrides included); an absolute value is used as-is.
|
|
31634
|
+
*/
|
|
31635
|
+
getEffectiveObservationBlockAfter(record) {
|
|
31636
|
+
return resolveBlockAfter(this.observationBlockAfterSetting, this.getEffectiveMessageTokens(record));
|
|
31637
|
+
}
|
|
31638
|
+
/**
|
|
30977
31639
|
* Resolve the effective reflection observationTokens for a record.
|
|
30978
31640
|
* Only explicit per-record overrides (stored under `_overrides`) win;
|
|
30979
31641
|
* the initial config snapshot is ignored so instance-level changes
|
|
@@ -31087,18 +31749,21 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
31087
31749
|
* the latest user input that triggered it.
|
|
31088
31750
|
* @internal Used by observation strategies and the reflector. Do not call directly.
|
|
31089
31751
|
*/
|
|
31090
|
-
async persistMarkerToStorage(marker, threadId, resourceId) {
|
|
31752
|
+
async persistMarkerToStorage(marker, threadId, resourceId, opts) {
|
|
31091
31753
|
try {
|
|
31092
|
-
const
|
|
31754
|
+
const markerData = marker.data;
|
|
31755
|
+
const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
|
|
31756
|
+
const preferred = [...opts?.preferMessages ?? []].reverse().find((m) => m.role === targetRole && (!m.threadId || m.threadId === threadId));
|
|
31757
|
+
const preferredStored = preferred ? (await this.storage.listMessagesById({ messageIds: [preferred.id] })).messages : [];
|
|
31758
|
+
const messages = (preferredStored.length ? { messages: preferredStored } : await this.storage.listMessages({
|
|
31093
31759
|
threadId,
|
|
31094
31760
|
perPage: 20,
|
|
31095
31761
|
orderBy: {
|
|
31096
31762
|
field: "createdAt",
|
|
31097
31763
|
direction: "DESC"
|
|
31098
|
-
}
|
|
31764
|
+
},
|
|
31765
|
+
...opts?.notAfter ? { filter: { dateRange: { end: opts.notAfter } } } : {}
|
|
31099
31766
|
}))?.messages ?? [];
|
|
31100
|
-
const markerData = marker.data;
|
|
31101
|
-
const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
|
|
31102
31767
|
for (const msg of messages) if (msg?.role === targetRole && msg.content?.parts && Array.isArray(msg.content.parts)) {
|
|
31103
31768
|
if (!(markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) msg.content.parts.push(marker);
|
|
31104
31769
|
await this.messageHistory.persistMessages({
|
|
@@ -31185,7 +31850,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
31185
31850
|
*/
|
|
31186
31851
|
createUnobservedMessage(message) {
|
|
31187
31852
|
const unobservedParts = getUnobservedParts(message);
|
|
31188
|
-
if (unobservedParts.
|
|
31853
|
+
if (unobservedParts.every((part) => part?.type?.startsWith("data-om-"))) return null;
|
|
31189
31854
|
return {
|
|
31190
31855
|
...message,
|
|
31191
31856
|
content: {
|
|
@@ -31557,17 +32222,17 @@ ${formattedMessages}
|
|
|
31557
32222
|
async startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, contextWindowTokens, requestContext, observabilityContext) {
|
|
31558
32223
|
const bufferKey = this.buffering.getObservationBufferKey(lockKey);
|
|
31559
32224
|
const currentTokens = contextWindowTokens ?? await this.tokenCounter.countMessagesAsync(unobservedMessages) + (record.pendingMessageTokens ?? 0);
|
|
31560
|
-
BufferingCoordinator.
|
|
32225
|
+
BufferingCoordinator.setObservationBoundary(bufferKey, currentTokens);
|
|
31561
32226
|
registerOp(record.id, "bufferingObservation");
|
|
31562
|
-
this.storage.setBufferingObservationFlag(record.id, true, currentTokens).catch((err) => {
|
|
32227
|
+
runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, true, currentTokens)).catch((err) => {
|
|
31563
32228
|
omError("[OM] Failed to set buffering observation flag", err);
|
|
31564
32229
|
});
|
|
31565
32230
|
const asyncOp = this.runAsyncBufferedObservation(record, threadId, unobservedMessages, bufferKey, writer, requestContext, observabilityContext).catch((err) => {
|
|
31566
32231
|
omError("[OM] async buffering observation failed", err);
|
|
31567
32232
|
}).finally(() => {
|
|
31568
|
-
BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
|
|
32233
|
+
if (BufferingCoordinator.asyncBufferingOps.get(bufferKey) === asyncOp) BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
|
|
31569
32234
|
unregisterOp(record.id, "bufferingObservation");
|
|
31570
|
-
this.storage.setBufferingObservationFlag(record.id, false).catch((err) => {
|
|
32235
|
+
runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch((err) => {
|
|
31571
32236
|
omError("[OM] Failed to clear buffering observation flag", err);
|
|
31572
32237
|
});
|
|
31573
32238
|
});
|
|
@@ -31582,6 +32247,14 @@ ${formattedMessages}
|
|
|
31582
32247
|
if (existingOp) try {
|
|
31583
32248
|
await existingOp;
|
|
31584
32249
|
} catch {}
|
|
32250
|
+
const releaseChunkWrite = BufferingCoordinator.trackChunkWrite(bufferKey);
|
|
32251
|
+
try {
|
|
32252
|
+
await this.bufferObservationChunk(record, threadId, unobservedMessages, bufferKey, releaseChunkWrite, writer, requestContext, observabilityContext);
|
|
32253
|
+
} finally {
|
|
32254
|
+
releaseChunkWrite();
|
|
32255
|
+
}
|
|
32256
|
+
}
|
|
32257
|
+
async bufferObservationChunk(record, threadId, unobservedMessages, bufferKey, releaseChunkWrite, writer, requestContext, observabilityContext) {
|
|
31585
32258
|
const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
31586
32259
|
if (!freshRecord) return;
|
|
31587
32260
|
let bufferCursor = BufferingCoordinator.lastBufferedAtTime.get(bufferKey) ?? freshRecord.lastBufferedAtTime ?? null;
|
|
@@ -31622,13 +32295,16 @@ ${formattedMessages}
|
|
|
31622
32295
|
threadIds: [threadId],
|
|
31623
32296
|
config: this.getObservationMarkerConfig()
|
|
31624
32297
|
});
|
|
31625
|
-
await this.persistMarkerToStorage(startMarker, threadId, freshRecord.resourceId ?? void 0
|
|
32298
|
+
await this.persistMarkerToStorage(startMarker, threadId, freshRecord.resourceId ?? void 0, {
|
|
32299
|
+
notAfter: new Date(startedAt),
|
|
32300
|
+
preferMessages: messagesToBuffer
|
|
32301
|
+
});
|
|
31626
32302
|
if (writer) writer.custom({
|
|
31627
32303
|
...startMarker,
|
|
31628
32304
|
transient: true
|
|
31629
32305
|
}).catch(() => {});
|
|
31630
32306
|
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(",")}`);
|
|
31631
|
-
|
|
32307
|
+
await this.runBufferedObservationCycle({
|
|
31632
32308
|
threadId,
|
|
31633
32309
|
resourceId: freshRecord.resourceId ?? void 0,
|
|
31634
32310
|
trigger: "async-buffer"
|
|
@@ -31642,12 +32318,13 @@ ${formattedMessages}
|
|
|
31642
32318
|
writer,
|
|
31643
32319
|
requestContext,
|
|
31644
32320
|
observabilityContext,
|
|
31645
|
-
trigger: "async-buffer"
|
|
31646
|
-
|
|
31647
|
-
|
|
31648
|
-
|
|
31649
|
-
|
|
31650
|
-
|
|
32321
|
+
trigger: "async-buffer",
|
|
32322
|
+
onBufferedChunkPersisted: async () => {
|
|
32323
|
+
const maxTs = this.getMaxMessageTimestamp(messagesToBuffer);
|
|
32324
|
+
BufferingCoordinator.lastBufferedAtTime.set(bufferKey, new Date(maxTs.getTime() + 1));
|
|
32325
|
+
releaseChunkWrite();
|
|
32326
|
+
}
|
|
32327
|
+
}).run());
|
|
31651
32328
|
}
|
|
31652
32329
|
/**
|
|
31653
32330
|
* Trigger async buffered observation if the token count has crossed a new interval.
|
|
@@ -31832,8 +32509,8 @@ ${formattedMessages}
|
|
|
31832
32509
|
const { threadId, resourceId, recordId, activatedMessageIds } = opts;
|
|
31833
32510
|
const lockKey = this.buffering.getLockKey(threadId, resourceId);
|
|
31834
32511
|
const bufKey = this.buffering.getObservationBufferKey(lockKey);
|
|
31835
|
-
BufferingCoordinator.
|
|
31836
|
-
await this.storage.setBufferingObservationFlag(recordId, false, 0).catch(() => {});
|
|
32512
|
+
BufferingCoordinator.setObservationBoundary(bufKey, 0);
|
|
32513
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(recordId, false, 0)).catch(() => {});
|
|
31837
32514
|
if (activatedMessageIds && activatedMessageIds.length > 0) this.buffering.cleanupStaticMaps(threadId, resourceId, activatedMessageIds);
|
|
31838
32515
|
}
|
|
31839
32516
|
/**
|
|
@@ -31992,9 +32669,13 @@ ${formattedMessages}
|
|
|
31992
32669
|
* @example
|
|
31993
32670
|
* ```ts
|
|
31994
32671
|
* const status = await om.getStatus({ threadId });
|
|
31995
|
-
* if (status.shouldObserve) {
|
|
32672
|
+
* if (status.shouldObserve && status.canActivate) {
|
|
32673
|
+
* await om.activate({ threadId });
|
|
32674
|
+
* } else if (status.shouldObserve && !status.inAsyncObservationBand) {
|
|
32675
|
+
* // At/above blockAfter (or async buffering disabled): observe synchronously
|
|
31996
32676
|
* await om.observe({ threadId });
|
|
31997
32677
|
* } else if (status.shouldBuffer) {
|
|
32678
|
+
* // Below the threshold, or in the threshold→blockAfter band: buffer in the background
|
|
31998
32679
|
* await om.buffer({ threadId });
|
|
31999
32680
|
* }
|
|
32000
32681
|
* if (status.shouldReflect) {
|
|
@@ -32024,8 +32705,10 @@ ${formattedMessages}
|
|
|
32024
32705
|
const bufferedChunkCount = bufferedChunks.length;
|
|
32025
32706
|
const bufferedChunkTokens = bufferedChunks.reduce((sum, chunk) => sum + (chunk.messageTokens ?? 0), 0);
|
|
32026
32707
|
const asyncObservationEnabled = this.buffering.isAsyncObservationEnabled();
|
|
32708
|
+
const observationBlockAfter = asyncObservationEnabled ? this.getEffectiveObservationBlockAfter(record) : void 0;
|
|
32709
|
+
const inAsyncObservationBand = observationBlockAfter !== void 0 && pendingTokens >= threshold && pendingTokens < observationBlockAfter;
|
|
32027
32710
|
let shouldBuffer = false;
|
|
32028
|
-
if (asyncObservationEnabled && pendingTokens < threshold) {
|
|
32711
|
+
if (asyncObservationEnabled && (pendingTokens < threshold || inAsyncObservationBand)) {
|
|
32029
32712
|
const lockKey = this.buffering.getLockKey(threadId, resourceId);
|
|
32030
32713
|
shouldBuffer = this.buffering.shouldTriggerAsyncObservation(pendingTokens, lockKey, record, this.storage, threshold);
|
|
32031
32714
|
}
|
|
@@ -32050,6 +32733,8 @@ ${formattedMessages}
|
|
|
32050
32733
|
canActivate,
|
|
32051
32734
|
asyncObservationEnabled,
|
|
32052
32735
|
asyncReflectionEnabled: this.buffering.isAsyncReflectionEnabled(),
|
|
32736
|
+
observationBlockAfter,
|
|
32737
|
+
inAsyncObservationBand,
|
|
32053
32738
|
scope: this.scope
|
|
32054
32739
|
};
|
|
32055
32740
|
}
|
|
@@ -32176,24 +32861,27 @@ ${formattedMessages}
|
|
|
32176
32861
|
const currentTokens = opts.pendingTokens ?? record.pendingMessageTokens ?? 0;
|
|
32177
32862
|
const lockKey = this.buffering.getLockKey(threadId, resourceId);
|
|
32178
32863
|
const bufferKey = this.buffering.getObservationBufferKey(lockKey);
|
|
32179
|
-
|
|
32180
|
-
|
|
32864
|
+
const boundaryOwner = Symbol("observation-boundary");
|
|
32865
|
+
BufferingCoordinator.setObservationBoundary(bufferKey, currentTokens, boundaryOwner);
|
|
32181
32866
|
const existingOp = BufferingCoordinator.asyncBufferingOps.get(bufferKey);
|
|
32867
|
+
let resolveOp;
|
|
32868
|
+
const opPromise = new Promise((resolve) => {
|
|
32869
|
+
resolveOp = resolve;
|
|
32870
|
+
});
|
|
32871
|
+
BufferingCoordinator.asyncBufferingOps.set(bufferKey, opPromise);
|
|
32872
|
+
if (record.isBufferingObservation) await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch(() => {});
|
|
32182
32873
|
if (existingOp) try {
|
|
32183
32874
|
await existingOp;
|
|
32184
32875
|
} catch {}
|
|
32185
|
-
|
|
32876
|
+
const opRecordId = record.id;
|
|
32877
|
+
registerOp(opRecordId, "bufferingObservation");
|
|
32878
|
+
const releaseChunkWrite = BufferingCoordinator.trackChunkWrite(bufferKey);
|
|
32879
|
+
const ownsStartBoundary = BufferingCoordinator.ownsObservationBoundary(bufferKey, boundaryOwner);
|
|
32186
32880
|
inMemoryRecord.isBufferingObservation = true;
|
|
32187
|
-
inMemoryRecord.lastBufferedAtTokens = currentTokens;
|
|
32188
|
-
this.storage.setBufferingObservationFlag(record.id, true, currentTokens).catch((err) => {
|
|
32881
|
+
if (ownsStartBoundary) inMemoryRecord.lastBufferedAtTokens = currentTokens;
|
|
32882
|
+
runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, true, ownsStartBoundary ? currentTokens : void 0)).catch((err) => {
|
|
32189
32883
|
omError("[OM] Failed to set buffering observation flag", err);
|
|
32190
32884
|
});
|
|
32191
|
-
let resolveOp;
|
|
32192
|
-
const opPromise = new Promise((resolve) => {
|
|
32193
|
-
resolveOp = resolve;
|
|
32194
|
-
});
|
|
32195
|
-
BufferingCoordinator.asyncBufferingOps.set(bufferKey, opPromise);
|
|
32196
|
-
record = await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record;
|
|
32197
32885
|
const setBufferingState = (isBufferingObservation, lastBufferedAtTokens) => {
|
|
32198
32886
|
inMemoryRecord.isBufferingObservation = isBufferingObservation;
|
|
32199
32887
|
record.isBufferingObservation = isBufferingObservation;
|
|
@@ -32204,6 +32892,7 @@ ${formattedMessages}
|
|
|
32204
32892
|
};
|
|
32205
32893
|
let flagCleared = false;
|
|
32206
32894
|
try {
|
|
32895
|
+
record = await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record;
|
|
32207
32896
|
let candidateMessages;
|
|
32208
32897
|
if (opts.messages) candidateMessages = this.getUnobservedMessages(opts.messages, record, { excludeBuffered: true });
|
|
32209
32898
|
else {
|
|
@@ -32243,12 +32932,29 @@ ${formattedMessages}
|
|
|
32243
32932
|
threadIds: [threadId],
|
|
32244
32933
|
config: this.getObservationMarkerConfig()
|
|
32245
32934
|
});
|
|
32246
|
-
await this.persistMarkerToStorage(startMarker, threadId, record.resourceId ?? void 0
|
|
32935
|
+
await this.persistMarkerToStorage(startMarker, threadId, record.resourceId ?? void 0, {
|
|
32936
|
+
notAfter: new Date(startedAt),
|
|
32937
|
+
preferMessages: candidateMessages
|
|
32938
|
+
});
|
|
32247
32939
|
const writer = opts.writer;
|
|
32248
32940
|
if (writer) writer.custom({
|
|
32249
32941
|
...startMarker,
|
|
32250
32942
|
transient: true
|
|
32251
32943
|
}).catch(() => {});
|
|
32944
|
+
let chunkRecorded = false;
|
|
32945
|
+
const recordBufferedChunk = async () => {
|
|
32946
|
+
if (chunkRecorded) return;
|
|
32947
|
+
chunkRecorded = true;
|
|
32948
|
+
const ownsBoundary = BufferingCoordinator.ownsObservationBoundary(bufferKey, boundaryOwner);
|
|
32949
|
+
if (ownsBoundary) BufferingCoordinator.setObservationBoundary(bufferKey, newTokens, boundaryOwner);
|
|
32950
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false, ownsBoundary ? newTokens : void 0)).catch(() => {});
|
|
32951
|
+
flagCleared = true;
|
|
32952
|
+
const stillOwnsBoundary = ownsBoundary && BufferingCoordinator.ownsObservationBoundary(bufferKey, boundaryOwner);
|
|
32953
|
+
setBufferingState(false, stillOwnsBoundary ? newTokens : void 0);
|
|
32954
|
+
const maxTimestamp = this.getMaxMessageTimestamp(candidateMessages);
|
|
32955
|
+
BufferingCoordinator.lastBufferedAtTime.set(bufferKey, new Date(maxTimestamp.getTime() + 1));
|
|
32956
|
+
releaseChunkWrite();
|
|
32957
|
+
};
|
|
32252
32958
|
if (!(await this.runBufferedObservationCycle({
|
|
32253
32959
|
threadId,
|
|
32254
32960
|
resourceId: record.resourceId ?? resourceId,
|
|
@@ -32267,7 +32973,8 @@ ${formattedMessages}
|
|
|
32267
32973
|
requestContext,
|
|
32268
32974
|
currentModel: opts.currentModel,
|
|
32269
32975
|
observabilityContext,
|
|
32270
|
-
trigger: "async-buffer"
|
|
32976
|
+
trigger: "async-buffer",
|
|
32977
|
+
onBufferedChunkPersisted: recordBufferedChunk
|
|
32271
32978
|
}).run()))?.observed) return {
|
|
32272
32979
|
buffered: false,
|
|
32273
32980
|
record
|
|
@@ -32287,13 +32994,7 @@ ${formattedMessages}
|
|
|
32287
32994
|
newTokens
|
|
32288
32995
|
}
|
|
32289
32996
|
});
|
|
32290
|
-
await
|
|
32291
|
-
flagCleared = true;
|
|
32292
|
-
setBufferingState(false, newTokens);
|
|
32293
|
-
BufferingCoordinator.lastBufferedBoundary.set(bufferKey, newTokens);
|
|
32294
|
-
const maxTimestamp = this.getMaxMessageTimestamp(candidateMessages);
|
|
32295
|
-
const cursor = new Date(maxTimestamp.getTime() + 1);
|
|
32296
|
-
BufferingCoordinator.lastBufferedAtTime.set(bufferKey, cursor);
|
|
32997
|
+
await recordBufferedChunk();
|
|
32297
32998
|
return {
|
|
32298
32999
|
buffered: true,
|
|
32299
33000
|
record: await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record
|
|
@@ -32305,12 +33006,13 @@ ${formattedMessages}
|
|
|
32305
33006
|
record
|
|
32306
33007
|
};
|
|
32307
33008
|
} finally {
|
|
32308
|
-
|
|
32309
|
-
|
|
33009
|
+
releaseChunkWrite();
|
|
33010
|
+
unregisterOp(opRecordId, "bufferingObservation");
|
|
33011
|
+
if (BufferingCoordinator.asyncBufferingOps.get(bufferKey) === opPromise) BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
|
|
32310
33012
|
resolveOp();
|
|
32311
33013
|
if (!flagCleared) {
|
|
32312
33014
|
setBufferingState(false);
|
|
32313
|
-
await this.storage.setBufferingObservationFlag(record.id, false).catch(() => {});
|
|
33015
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch(() => {});
|
|
32314
33016
|
}
|
|
32315
33017
|
}
|
|
32316
33018
|
}
|
|
@@ -32347,8 +33049,8 @@ ${formattedMessages}
|
|
|
32347
33049
|
const currentContextTokens = this.tokenCounter.countMessages(unobserved);
|
|
32348
33050
|
if (currentContextTokens < dbBoundary * .5) {
|
|
32349
33051
|
omDebug(`[OM:activate] resetting stale lastBufferedBoundary: dbBoundary=${dbBoundary}, currentContextTokens=${currentContextTokens}`);
|
|
32350
|
-
BufferingCoordinator.
|
|
32351
|
-
await this.storage.setBufferingObservationFlag(record.id, false, 0).catch(() => {});
|
|
33052
|
+
BufferingCoordinator.setObservationBoundary(bufKey, 0);
|
|
33053
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false, 0)).catch(() => {});
|
|
32352
33054
|
}
|
|
32353
33055
|
}
|
|
32354
33056
|
}
|
|
@@ -32394,39 +33096,63 @@ ${formattedMessages}
|
|
|
32394
33096
|
livePendingTokens ??= status.pendingTokens;
|
|
32395
33097
|
}
|
|
32396
33098
|
}
|
|
32397
|
-
|
|
32398
|
-
|
|
32399
|
-
|
|
32400
|
-
const
|
|
32401
|
-
|
|
32402
|
-
|
|
32403
|
-
|
|
33099
|
+
const obsBufferKey = this.buffering.getObservationBufferKey(this.buffering.getLockKey(threadId, resourceId));
|
|
33100
|
+
const pendingChunkWrite = BufferingCoordinator.pendingChunkWrites.get(obsBufferKey);
|
|
33101
|
+
if (pendingChunkWrite) {
|
|
33102
|
+
const blockAfter = this.buffering.isAsyncObservationEnabled() ? this.getEffectiveObservationBlockAfter(record) : void 0;
|
|
33103
|
+
const threshold = calculateDynamicThreshold(this.getEffectiveMessageTokens(record), record.observationTokenCount ?? 0);
|
|
33104
|
+
if (activationTriggeredBy === "threshold" && !(livePendingTokens !== void 0 && blockAfter !== void 0 && livePendingTokens >= threshold && livePendingTokens < blockAfter)) {
|
|
33105
|
+
let timeoutId;
|
|
33106
|
+
await Promise.race([pendingChunkWrite, new Promise((resolve) => timeoutId = setTimeout(resolve, 6e4))]);
|
|
33107
|
+
clearTimeout(timeoutId);
|
|
33108
|
+
}
|
|
33109
|
+
}
|
|
33110
|
+
const commitKey = this.buffering.getLockKey(threadId, resourceId);
|
|
33111
|
+
let freshRecord = null;
|
|
33112
|
+
let freshChunks = [];
|
|
33113
|
+
let activationResult;
|
|
33114
|
+
for (let attempt = 0; attempt <= MAX_ACTIVATION_RETIRED_RETRIES; attempt++) {
|
|
33115
|
+
const swapped = await runOMCommit(commitKey, async () => {
|
|
33116
|
+
const head = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
33117
|
+
if (!head) return null;
|
|
33118
|
+
const chunks = getBufferedChunks(head);
|
|
33119
|
+
if (!chunks.length) return null;
|
|
33120
|
+
const messageTokensThreshold = getMaxThreshold(this.getEffectiveMessageTokens(head));
|
|
33121
|
+
const activationRatio = resolveActivationRatio(this.observationConfig.bufferActivation ?? .7, messageTokensThreshold);
|
|
33122
|
+
const totalChunkMessageTokens = chunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
33123
|
+
const currentPendingTokens = livePendingTokens ?? (head.pendingMessageTokens || totalChunkMessageTokens);
|
|
33124
|
+
const blockAfter = this.getEffectiveObservationBlockAfter(head);
|
|
33125
|
+
const forceMaxActivation = !!(blockAfter && currentPendingTokens >= blockAfter);
|
|
33126
|
+
if (head.pendingMessageTokens !== currentPendingTokens) await this.storage.setPendingMessageTokens(head.id, currentPendingTokens);
|
|
33127
|
+
return {
|
|
33128
|
+
head,
|
|
33129
|
+
chunks,
|
|
33130
|
+
result: await this.storage.swapBufferedToActive({
|
|
33131
|
+
id: head.id,
|
|
33132
|
+
activationRatio,
|
|
33133
|
+
messageTokensThreshold,
|
|
33134
|
+
currentPendingTokens,
|
|
33135
|
+
forceMaxActivation,
|
|
33136
|
+
bufferedChunks: chunks
|
|
33137
|
+
})
|
|
33138
|
+
};
|
|
33139
|
+
});
|
|
33140
|
+
if (!swapped) return {
|
|
33141
|
+
activated: false,
|
|
33142
|
+
record
|
|
33143
|
+
};
|
|
33144
|
+
freshRecord = swapped.head;
|
|
33145
|
+
freshChunks = swapped.chunks;
|
|
33146
|
+
activationResult = swapped.result;
|
|
33147
|
+
if (!activationResult.retired) break;
|
|
33148
|
+
omDebug(`[OM:activate] record ${freshRecord.id} was retired by a reflection; retrying on the head`);
|
|
32404
33149
|
}
|
|
32405
|
-
|
|
32406
|
-
if (!freshRecord) return {
|
|
33150
|
+
if (!freshRecord || !activationResult || activationResult.retired) return {
|
|
32407
33151
|
activated: false,
|
|
32408
|
-
record
|
|
33152
|
+
record: await this.getRecord(threadId, resourceId) ?? record
|
|
32409
33153
|
};
|
|
32410
|
-
const
|
|
32411
|
-
|
|
32412
|
-
activated: false,
|
|
32413
|
-
record
|
|
32414
|
-
};
|
|
32415
|
-
const messageTokensThreshold = getMaxThreshold(this.getEffectiveMessageTokens(freshRecord));
|
|
32416
|
-
const activationRatio = resolveActivationRatio(this.observationConfig.bufferActivation ?? .7, messageTokensThreshold);
|
|
32417
|
-
const totalChunkMessageTokens = freshChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
32418
|
-
const currentPendingTokens = livePendingTokens ?? (freshRecord.pendingMessageTokens || totalChunkMessageTokens);
|
|
32419
|
-
const forceMaxActivation = !!(this.observationConfig.blockAfter && currentPendingTokens >= this.observationConfig.blockAfter);
|
|
32420
|
-
if (freshRecord.pendingMessageTokens !== currentPendingTokens) await this.storage.setPendingMessageTokens(freshRecord.id, currentPendingTokens);
|
|
32421
|
-
const activationResult = await this.storage.swapBufferedToActive({
|
|
32422
|
-
id: freshRecord.id,
|
|
32423
|
-
activationRatio,
|
|
32424
|
-
messageTokensThreshold,
|
|
32425
|
-
currentPendingTokens,
|
|
32426
|
-
forceMaxActivation,
|
|
32427
|
-
bufferedChunks: freshChunks
|
|
32428
|
-
});
|
|
32429
|
-
await this.storage.setBufferingObservationFlag(freshRecord.id, false).catch(() => {});
|
|
33154
|
+
const swappedRecordId = freshRecord.id;
|
|
33155
|
+
await runOMCommit(commitKey, () => this.storage.setBufferingObservationFlag(swappedRecordId, false)).catch(() => {});
|
|
32430
33156
|
unregisterOp(freshRecord.id, "bufferingObservation");
|
|
32431
33157
|
const postSwapRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
32432
33158
|
if (opts.writer && postSwapRecord && activationResult.activatedCycleIds.length > 0) {
|
|
@@ -32671,7 +33397,7 @@ ${formattedMessages}
|
|
|
32671
33397
|
* ```
|
|
32672
33398
|
*/
|
|
32673
33399
|
async reflect(threadId, resourceId, prompt, requestContext, observabilityContext) {
|
|
32674
|
-
const record = await this.getOrCreateRecord(threadId, resourceId);
|
|
33400
|
+
const record = { ...await this.getOrCreateRecord(threadId, resourceId) };
|
|
32675
33401
|
if (!record.activeObservations) return {
|
|
32676
33402
|
reflected: false,
|
|
32677
33403
|
record,
|
|
@@ -32716,11 +33442,23 @@ ${formattedMessages}
|
|
|
32716
33442
|
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);
|
|
32717
33443
|
reflectResult.observations = await applyTextTransform(this.hooks, "afterReflection", reflectResult.observations, hookContext);
|
|
32718
33444
|
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
32719
|
-
|
|
33445
|
+
const newRecordId = crypto.randomUUID();
|
|
33446
|
+
const committedRecord = await runOMCommit(this.buffering.getLockKey(threadId, resourceId), () => this.storage.createReflectionGeneration({
|
|
32720
33447
|
currentRecord: record,
|
|
32721
33448
|
reflection: reflectResult.observations,
|
|
32722
|
-
tokenCount: reflectionTokenCount
|
|
32723
|
-
|
|
33449
|
+
tokenCount: reflectionTokenCount,
|
|
33450
|
+
newRecordId
|
|
33451
|
+
}), { priority: "reflection" });
|
|
33452
|
+
reflectionUsage = reflectResult.usage;
|
|
33453
|
+
reflectionProviderMetadata = reflectResult.providerMetadata;
|
|
33454
|
+
if (!isReflectionApplied(committedRecord, newRecordId, record.id)) {
|
|
33455
|
+
omDebug(`[OM:reflect] manual reflection of ${record.id} not applied (head is ${committedRecord.id})`);
|
|
33456
|
+
return {
|
|
33457
|
+
reflected: false,
|
|
33458
|
+
record: await this.getOrCreateRecord(threadId, resourceId),
|
|
33459
|
+
usage: reflectResult.usage
|
|
33460
|
+
};
|
|
33461
|
+
}
|
|
32724
33462
|
if (thread && reflectResult.extractedValues) {
|
|
32725
33463
|
const metadataUpdate = buildThreadMetadataFromExtractedValues(this.reflectionConfig.extractors, reflectResult.extractedValues);
|
|
32726
33464
|
const newMetadata = (0, _mastra_core_memory.setThreadOMMetadata)(thread.metadata, {
|
|
@@ -32809,11 +33547,13 @@ ${formattedMessages}
|
|
|
32809
33547
|
*/
|
|
32810
33548
|
async updateRecordConfig(threadId, resourceId, config) {
|
|
32811
33549
|
const ids = this.getStorageIds(threadId, resourceId);
|
|
32812
|
-
|
|
32813
|
-
|
|
32814
|
-
|
|
32815
|
-
|
|
32816
|
-
|
|
33550
|
+
await runOMCommit(this.buffering.getLockKey(threadId, resourceId), async () => {
|
|
33551
|
+
const record = await this.storage.getObservationalMemory(ids.threadId, ids.resourceId);
|
|
33552
|
+
if (!record) throw new Error(`No observational memory record found for thread ${ids.threadId}`);
|
|
33553
|
+
await this.storage.updateObservationalMemoryConfig({
|
|
33554
|
+
id: record.id,
|
|
33555
|
+
config: { _overrides: config }
|
|
33556
|
+
});
|
|
32817
33557
|
});
|
|
32818
33558
|
}
|
|
32819
33559
|
/**
|
|
@@ -33207,7 +33947,7 @@ var ObservationalMemoryProcessor = class {
|
|
|
33207
33947
|
});
|
|
33208
33948
|
const finalTotalPending = ctx.status.pendingTokens;
|
|
33209
33949
|
try {
|
|
33210
|
-
await this.engine.getStorage().setPendingMessageTokens(turnRecord.id, finalTotalPending);
|
|
33950
|
+
await runOMCommit(this.engine.buffering.getLockKey(threadId, resourceId), () => this.engine.getStorage().setPendingMessageTokens(turnRecord.id, finalTotalPending));
|
|
33211
33951
|
this.turn.patchRecord({ pendingMessageTokens: finalTotalPending });
|
|
33212
33952
|
} catch {}
|
|
33213
33953
|
if (reproCaptureEnabled) writeProcessInputStepReproCapture({
|
|
@@ -35542,6 +36282,7 @@ Notes:
|
|
|
35542
36282
|
cloned.id = crypto.randomUUID();
|
|
35543
36283
|
cloned.createdAt = now;
|
|
35544
36284
|
cloned.updatedAt = now;
|
|
36285
|
+
cloned.supersededBy = null;
|
|
35545
36286
|
await memoryStore.insertObservationalMemoryRecord(cloned);
|
|
35546
36287
|
}
|
|
35547
36288
|
/**
|
|
@@ -36140,4 +36881,4 @@ Object.defineProperty(exports, "wrapInObservationGroup", {
|
|
|
36140
36881
|
}
|
|
36141
36882
|
});
|
|
36142
36883
|
|
|
36143
|
-
//# sourceMappingURL=src-
|
|
36884
|
+
//# sourceMappingURL=src-BpxX0T2g.cjs.map
|