@mastra/memory 1.37.0 → 1.37.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-multi-user-threads.md +1 -1
- package/dist/docs/references/docs-memory-observational-memory.md +17 -15
- package/dist/docs/references/reference-memory-listThreads.md +1 -1
- package/dist/docs/references/reference-memory-observational-memory.md +3 -3
- package/dist/docs/references/reference-processors-token-limiter-processor.md +2 -2
- package/dist/docs/references/reference-vectors-libsql.md +1 -1
- package/dist/docs/references/reference-vectors-mongodb.md +2 -1
- package/dist/docs/references/reference-vectors-pg.md +16 -11
- package/dist/docs/references/reference-vectors-upstash.md +10 -4
- 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/extraction-runner.d.ts.map +1 -1
- 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/processors/observational-memory/working-memory-extractor.d.ts.map +1 -1
- package/dist/{src-D-jA6eRb.cjs → src-BcRCGtUY.cjs} +1062 -302
- package/dist/src-BcRCGtUY.cjs.map +1 -0
- package/dist/{src-B9qhQmNI.js → src-DjDnBIS0.js} +1063 -303
- package/dist/src-DjDnBIS0.js.map +1 -0
- package/package.json +3 -3
- package/dist/src-B9qhQmNI.js.map +0 -1
- package/dist/src-D-jA6eRb.cjs.map +0 -1
|
@@ -19251,8 +19251,12 @@ function formatOmError(error) {
|
|
|
19251
19251
|
function isAbortError(error, abortSignal) {
|
|
19252
19252
|
return abortSignal?.aborted === true || hasAbortInChain(error);
|
|
19253
19253
|
}
|
|
19254
|
+
function hasEmptyObjectValue(object, extractor) {
|
|
19255
|
+
const value = object[extractor.slug];
|
|
19256
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
19257
|
+
}
|
|
19254
19258
|
function shouldRetryEmptyStructuredObject(object, extractors) {
|
|
19255
|
-
return Object.keys(object).length === 0
|
|
19259
|
+
return extractors.some((extractor) => extractor.retryStructuredExtractionOnEmptyObject && (Object.keys(object).length === 0 || hasEmptyObjectValue(object, extractor)));
|
|
19256
19260
|
}
|
|
19257
19261
|
async function extractStructuredValues(opts) {
|
|
19258
19262
|
const structuredExtractors = (opts.extractors ?? []).filter((extractor) => extractor.mode === "structured");
|
|
@@ -19326,17 +19330,28 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
|
|
|
19326
19330
|
}
|
|
19327
19331
|
}
|
|
19328
19332
|
if (retryEmptyObject) try {
|
|
19329
|
-
|
|
19333
|
+
const fallbackJsonPromptInjection = _mastra_core_features.coreFeatures.has("json-prompt-injection:inline") ? "inline" : true;
|
|
19334
|
+
object = {
|
|
19335
|
+
...object,
|
|
19336
|
+
...await streamWithStructuredOutput(fallbackJsonPromptInjection)
|
|
19337
|
+
};
|
|
19330
19338
|
} catch (fallbackError) {
|
|
19331
19339
|
if (isAbortError(fallbackError, opts.abortSignal)) throw fallbackError;
|
|
19332
19340
|
const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
19333
|
-
return {
|
|
19341
|
+
if (Object.keys(object).length === 0) return {
|
|
19334
19342
|
values,
|
|
19335
19343
|
failures: structuredExtractors.map((extractor) => ({
|
|
19336
19344
|
slug: extractor.slug,
|
|
19337
19345
|
error: message
|
|
19338
19346
|
}))
|
|
19339
19347
|
};
|
|
19348
|
+
for (const extractor of structuredExtractors) if (extractor.retryStructuredExtractionOnEmptyObject && hasEmptyObjectValue(object, extractor)) {
|
|
19349
|
+
failures.push({
|
|
19350
|
+
slug: extractor.slug,
|
|
19351
|
+
error: message
|
|
19352
|
+
});
|
|
19353
|
+
delete object[extractor.slug];
|
|
19354
|
+
}
|
|
19340
19355
|
}
|
|
19341
19356
|
for (const extractor of structuredExtractors) {
|
|
19342
19357
|
const value = object[extractor.slug];
|
|
@@ -20578,7 +20593,7 @@ Group related observations (like tool sequences) by indenting:
|
|
|
20578
20593
|
* -> applied fix, tests now pass
|
|
20579
20594
|
* ✅ Tests passing, auth issue resolved
|
|
20580
20595
|
|
|
20581
|
-
Group observations by date,
|
|
20596
|
+
Group observations by date. Within a date, list observations in the order the events happened (not by importance), each with 24-hour time.
|
|
20582
20597
|
|
|
20583
20598
|
<observations>
|
|
20584
20599
|
Date: Dec 4, 2025
|
|
@@ -20951,8 +20966,8 @@ function extractToolResultAttachments(result, counter, attachmentFilter) {
|
|
|
20951
20966
|
attachments
|
|
20952
20967
|
};
|
|
20953
20968
|
}
|
|
20954
|
-
function formatObserverPartLine(title, body, time, previousTime) {
|
|
20955
|
-
const timeLabel = time && time !== previousTime ? `(${time})` : "";
|
|
20969
|
+
function formatObserverPartLine(title, body, time, previousTime, alwaysShowTime) {
|
|
20970
|
+
const timeLabel = time && (alwaysShowTime || time !== previousTime) ? `(${time})` : "";
|
|
20956
20971
|
if (!title) return timeLabel ? `${timeLabel}: ${body}` : body;
|
|
20957
20972
|
return `${title}${timeLabel ? ` ${timeLabel}` : ""}: ${body}`;
|
|
20958
20973
|
}
|
|
@@ -20977,7 +20992,7 @@ function formatObserverLines(lines, context = {}) {
|
|
|
20977
20992
|
previousDate = line.date;
|
|
20978
20993
|
previousTime = void 0;
|
|
20979
20994
|
}
|
|
20980
|
-
output.push(formatObserverPartLine(line.title, line.body, line.time, previousTime));
|
|
20995
|
+
output.push(formatObserverPartLine(line.title, line.body, line.time, previousTime, line.alwaysShowTime));
|
|
20981
20996
|
previousTime = line.time || previousTime;
|
|
20982
20997
|
}
|
|
20983
20998
|
return {
|
|
@@ -21078,14 +21093,15 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
21078
21093
|
const unavailableUrls = getUnavailableAttachmentUrls(msg);
|
|
21079
21094
|
let lines = [];
|
|
21080
21095
|
const temporalGapText = isTemporalGapMarker(msg) ? getTemporalGapMarkerText(msg) : void 0;
|
|
21081
|
-
const pushLine = (title, body, createdAt) => {
|
|
21096
|
+
const pushLine = (title, body, createdAt, opts) => {
|
|
21082
21097
|
if (!body) return;
|
|
21083
21098
|
const normalizedCreatedAt = normalizeObserverCreatedAt(createdAt) ?? messageCreatedAt;
|
|
21084
21099
|
lines.push({
|
|
21085
21100
|
date: formatObserverDate(normalizedCreatedAt, timeZone),
|
|
21086
21101
|
time: formatObserverTime(normalizedCreatedAt, timeZone),
|
|
21087
21102
|
title,
|
|
21088
|
-
body
|
|
21103
|
+
body,
|
|
21104
|
+
alwaysShowTime: opts?.tool === true
|
|
21089
21105
|
});
|
|
21090
21106
|
};
|
|
21091
21107
|
if (temporalGapText) pushLine("", temporalGapText, messageCreatedAt);
|
|
@@ -21102,7 +21118,7 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
21102
21118
|
const exchange = toolFormatting.exchanges.get(inv.toolCallId);
|
|
21103
21119
|
if (exchange && occurrence === exchange.firstOccurrence && exchange.signature && !toolFormatting.emittedCalls.has(inv.toolCallId)) {
|
|
21104
21120
|
toolFormatting.emittedCalls.add(inv.toolCallId);
|
|
21105
|
-
pushLine(`Tool Call ${exchange.signature.toolName}`, formatObserverToolArguments(exchange.signature.args, maxLen), partCreatedAt);
|
|
21121
|
+
pushLine(`Tool Call ${exchange.signature.toolName}`, formatObserverToolArguments(exchange.signature.args, maxLen), partCreatedAt, { tool: true });
|
|
21106
21122
|
}
|
|
21107
21123
|
if (inv.state === "result") {
|
|
21108
21124
|
const { value: resultForObserver } = resolveToolResultValue(part, inv.result);
|
|
@@ -21112,17 +21128,17 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
21112
21128
|
toolFormatting.emittedOutcomes.add(inv.toolCallId);
|
|
21113
21129
|
const hasResult = resultWithoutAttachments !== void 0 && (typeof resultWithoutAttachments !== "string" || resultWithoutAttachments.trim().length > 0);
|
|
21114
21130
|
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);
|
|
21131
|
+
pushLine(`${inv.isError ? "Tool Error" : "Tool Result"} ${inv.toolName}`, maybeTruncate(body, maxLen), partCreatedAt, { tool: true });
|
|
21116
21132
|
}
|
|
21117
21133
|
return;
|
|
21118
21134
|
}
|
|
21119
21135
|
if (exchange?.terminal?.occurrence === occurrence && !toolFormatting.emittedOutcomes.has(inv.toolCallId)) {
|
|
21120
21136
|
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);
|
|
21137
|
+
if (inv.state === "output-error") pushLine(`Tool Error ${inv.toolName}`, maybeTruncate(inv.errorText?.trim() ? inv.errorText : "Tool execution failed", maxLen), partCreatedAt, { tool: true });
|
|
21138
|
+
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
21139
|
return;
|
|
21124
21140
|
}
|
|
21125
|
-
if (!exchange) pushLine(`Tool Call ${inv.toolName}`, formatObserverToolArguments(inv.args, maxLen), partCreatedAt);
|
|
21141
|
+
if (!exchange) pushLine(`Tool Call ${inv.toolName}`, formatObserverToolArguments(inv.args, maxLen), partCreatedAt, { tool: true });
|
|
21126
21142
|
return;
|
|
21127
21143
|
}
|
|
21128
21144
|
const partType = part.type;
|
|
@@ -21277,13 +21293,17 @@ function surroundObserverHistory(history, contextPrompt, taskInstructions) {
|
|
|
21277
21293
|
function buildMultiThreadObserverTaskInstructions(includeThreadTitle) {
|
|
21278
21294
|
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
21295
|
}
|
|
21296
|
+
function buildTruncationNotice(wasTruncated) {
|
|
21297
|
+
if (!wasTruncated) return "";
|
|
21298
|
+
return "Previous observations were truncated for context budget reasons.\nThe main agent still has full memory context outside this observer window.\n\n";
|
|
21299
|
+
}
|
|
21280
21300
|
function buildMultiThreadObserverContextPrompt(existingObservations, threadOrder, priorMetadataByThread, wasTruncated, includeThreadTitle, extractors = []) {
|
|
21281
21301
|
let prompt = "";
|
|
21282
21302
|
if (existingObservations) {
|
|
21283
21303
|
prompt += `## Previous Observations\n\n${existingObservations}\n\n---\n\n`;
|
|
21284
21304
|
prompt += "Do not repeat these existing observations. Your new observations will be appended to the existing observations.\n\n";
|
|
21285
21305
|
}
|
|
21286
|
-
|
|
21306
|
+
prompt += buildTruncationNotice(wasTruncated);
|
|
21287
21307
|
const threadMetadataLines = threadOrder?.map((threadId) => {
|
|
21288
21308
|
const metadata = priorMetadataByThread?.get(threadId);
|
|
21289
21309
|
const extractorPriorLines = buildExtractorPriorLines(extractors, metadata?.extracted);
|
|
@@ -21297,10 +21317,6 @@ function buildMultiThreadObserverContextPrompt(existingObservations, threadOrder
|
|
|
21297
21317
|
}).filter(Boolean).join("\n");
|
|
21298
21318
|
if (threadMetadataLines) {
|
|
21299
21319
|
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
21320
|
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
21321
|
}
|
|
21306
21322
|
return prompt;
|
|
@@ -21343,7 +21359,7 @@ function parseMultiThreadObserverOutput(output, extractors = []) {
|
|
|
21343
21359
|
threadTitle = threadTitleMatch[1].trim();
|
|
21344
21360
|
observations = observations.replace(/<thread-title>[\s\S]*?<\/thread-title>/i, "");
|
|
21345
21361
|
}
|
|
21346
|
-
observations = sanitizeObservationLines(observations.trim());
|
|
21362
|
+
observations = sortObservationsByTime(sanitizeObservationLines(observations.trim()));
|
|
21347
21363
|
threads.set(threadId, {
|
|
21348
21364
|
observations,
|
|
21349
21365
|
currentTask: currentTask || getStringExtractedValue$1(parsedExtractedValues.values, "current-task"),
|
|
@@ -21383,7 +21399,7 @@ function buildObserverContextPrompt(existingObservations, options) {
|
|
|
21383
21399
|
prompt += `## Previous Observations\n\n${existingObservations}\n\n---\n\n`;
|
|
21384
21400
|
prompt += "Do not repeat these existing observations. Your new observations will be appended to the existing observations.\n\n";
|
|
21385
21401
|
}
|
|
21386
|
-
|
|
21402
|
+
prompt += buildTruncationNotice(options?.wasTruncated);
|
|
21387
21403
|
const priorMetadataLines = [];
|
|
21388
21404
|
if (options?.priorCurrentTask) priorMetadataLines.push(`- prior current-task: ${options.priorCurrentTask}`);
|
|
21389
21405
|
if (options?.priorSuggestedResponse) priorMetadataLines.push(`- prior suggested-response: ${options.priorSuggestedResponse}`);
|
|
@@ -21391,10 +21407,6 @@ function buildObserverContextPrompt(existingObservations, options) {
|
|
|
21391
21407
|
priorMetadataLines.push(...buildExtractorPriorLines(options?.extractors ?? [], options?.priorExtractedValues));
|
|
21392
21408
|
if (priorMetadataLines.length > 0) {
|
|
21393
21409
|
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
21410
|
const titleHint = options?.includeThreadTitle ? ", and thread-title" : "";
|
|
21399
21411
|
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
21412
|
}
|
|
@@ -21425,7 +21437,7 @@ function parseObserverOutput(output, extractors = []) {
|
|
|
21425
21437
|
const parsedExtractedValues = parseExtractedValues(output, inlineExtractors);
|
|
21426
21438
|
const parsed = parseMemorySectionXml(stripExtractorSections(output, inlineExtractors));
|
|
21427
21439
|
return {
|
|
21428
|
-
observations: sanitizeObservationLines(parsed.observations || ""),
|
|
21440
|
+
observations: sortObservationsByTime(sanitizeObservationLines(parsed.observations || "")),
|
|
21429
21441
|
currentTask: parsed.currentTask || getStringExtractedValue$1(parsedExtractedValues.values, "current-task"),
|
|
21430
21442
|
suggestedContinuation: parsed.suggestedResponse || getStringExtractedValue$1(parsedExtractedValues.values, "suggested-response"),
|
|
21431
21443
|
threadTitle: parsed.threadTitle || getStringExtractedValue$1(parsedExtractedValues.values, "thread-title"),
|
|
@@ -21519,6 +21531,68 @@ function sanitizeObservationLines(observations) {
|
|
|
21519
21531
|
}
|
|
21520
21532
|
return changed ? lines.join("\n") : observations;
|
|
21521
21533
|
}
|
|
21534
|
+
const OBSERVATION_TIME = /^[*-]\s+(?:\S+\s+)?\((\d{1,2}):(\d{2})\s*([AaPp])?\.?(?:[Mm]\.?)?\)/;
|
|
21535
|
+
/** Minutes since midnight for each item, or undefined when the group's times can't be ordered safely. */
|
|
21536
|
+
function observationMinutes(items) {
|
|
21537
|
+
const times = items.map((item) => OBSERVATION_TIME.exec(item[0]));
|
|
21538
|
+
if (times.some((t) => !t)) return void 0;
|
|
21539
|
+
const matches = times;
|
|
21540
|
+
const marked = matches.filter((m) => m[3]).length;
|
|
21541
|
+
if (marked > 0 && marked < matches.length) return void 0;
|
|
21542
|
+
if (marked === matches.length) return matches.map((m) => Number(m[1]) % 12 * 60 + (/p/i.test(m[3]) ? 720 : 0) + Number(m[2]));
|
|
21543
|
+
const hours = matches.map((m) => m[1]);
|
|
21544
|
+
const twelveHour = hours.some((h) => /^[1-9]$/.test(h));
|
|
21545
|
+
const twentyFourHour = hours.some((h) => /^(0\d|1[3-9]|2\d)$/.test(h));
|
|
21546
|
+
if (twelveHour && (twentyFourHour || new Set(hours).size > 1)) return void 0;
|
|
21547
|
+
return matches.map((m) => Number(m[1]) * 60 + Number(m[2]));
|
|
21548
|
+
}
|
|
21549
|
+
/**
|
|
21550
|
+
* Put the top-level observations of each "Date:" group in time order, keeping indented
|
|
21551
|
+
* sub-items with their parent. The Observer writes a time on every observation but tends
|
|
21552
|
+
* to list a user's message ahead of the earlier events it follows, which can make a
|
|
21553
|
+
* superseded state read as the latest one. Groups are left as written when any
|
|
21554
|
+
* observation has no time or the times are ambiguous.
|
|
21555
|
+
*/
|
|
21556
|
+
function sortObservationsByTime(observations) {
|
|
21557
|
+
if (!observations) return observations;
|
|
21558
|
+
const output = [];
|
|
21559
|
+
let group;
|
|
21560
|
+
const flush = () => {
|
|
21561
|
+
if (!group) return;
|
|
21562
|
+
const minutes = group.sortable && group.items.length > 1 ? observationMinutes(group.items) : void 0;
|
|
21563
|
+
const items = minutes ? group.items.map((item, i) => ({
|
|
21564
|
+
item,
|
|
21565
|
+
minute: minutes[i]
|
|
21566
|
+
})).sort((a, b) => a.minute - b.minute).map((x) => x.item) : group.items;
|
|
21567
|
+
output.push(...group.header, ...items.flat(), ...group.trailing);
|
|
21568
|
+
group = void 0;
|
|
21569
|
+
};
|
|
21570
|
+
for (const line of observations.split("\n")) if (/^\s*Date:\s/.test(line)) {
|
|
21571
|
+
flush();
|
|
21572
|
+
group = {
|
|
21573
|
+
header: [line],
|
|
21574
|
+
items: [],
|
|
21575
|
+
trailing: [],
|
|
21576
|
+
sortable: true
|
|
21577
|
+
};
|
|
21578
|
+
} else if (!group) output.push(line);
|
|
21579
|
+
else if (/^[*-]\s/.test(line)) {
|
|
21580
|
+
group.items.at(-1)?.push(...group.trailing);
|
|
21581
|
+
group.trailing = [];
|
|
21582
|
+
group.items.push([line]);
|
|
21583
|
+
} else if (group.items.length === 0) group.header.push(line);
|
|
21584
|
+
else if (line.trim() === "") group.trailing.push(line);
|
|
21585
|
+
else if (/^\s/.test(line)) {
|
|
21586
|
+
group.items.at(-1).push(...group.trailing, line);
|
|
21587
|
+
group.trailing = [];
|
|
21588
|
+
} else {
|
|
21589
|
+
group.sortable = false;
|
|
21590
|
+
group.items.at(-1).push(...group.trailing, line);
|
|
21591
|
+
group.trailing = [];
|
|
21592
|
+
}
|
|
21593
|
+
flush();
|
|
21594
|
+
return output.join("\n");
|
|
21595
|
+
}
|
|
21522
21596
|
function analyzeDegenerateRepetition(text) {
|
|
21523
21597
|
const lines = collapseBoundedLineRuns(text.split("\n"));
|
|
21524
21598
|
const windowText = lines.filter((line) => line.trim().length >= MIN_DUPLICATE_LINE_CHARS && line.length <= MAX_OBSERVATION_LINE_CHARS).join("\n");
|
|
@@ -22061,7 +22135,7 @@ var ObserverRunner = class {
|
|
|
22061
22135
|
/**
|
|
22062
22136
|
* Call the Observer agent for multiple threads in a single batched request.
|
|
22063
22137
|
*/
|
|
22064
|
-
async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext, timeZone, mainAgent, currentModel) {
|
|
22138
|
+
async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext, timeZone, mainAgent, currentModel, wasTruncated) {
|
|
22065
22139
|
const contextFor = (threadId) => ({
|
|
22066
22140
|
threadId,
|
|
22067
22141
|
...hookContext
|
|
@@ -22072,7 +22146,7 @@ var ObserverRunner = class {
|
|
|
22072
22146
|
if (filtered.length > 0) messagesByThread.set(threadId, filtered);
|
|
22073
22147
|
}
|
|
22074
22148
|
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);
|
|
22149
|
+
const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel, wasTruncated);
|
|
22076
22150
|
for (const threadId of allThreadOrder) {
|
|
22077
22151
|
const threadResult = output.results.get(threadId);
|
|
22078
22152
|
if (!threadResult) {
|
|
@@ -22083,7 +22157,7 @@ var ObserverRunner = class {
|
|
|
22083
22157
|
}
|
|
22084
22158
|
return output;
|
|
22085
22159
|
}
|
|
22086
|
-
async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel) {
|
|
22160
|
+
async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel, wasTruncated) {
|
|
22087
22161
|
if (threadOrder.length === 0) return { results: /* @__PURE__ */ new Map() };
|
|
22088
22162
|
const inputTokens = Array.from(messagesByThread.values()).reduce((total, messages) => total + this.tokenCounter.countMessages(messages), 0);
|
|
22089
22163
|
let resolvedModel;
|
|
@@ -22129,7 +22203,8 @@ var ObserverRunner = class {
|
|
|
22129
22203
|
priorThreadTitle: priorMetadataByThread?.get(threadId)?.threadTitle,
|
|
22130
22204
|
priorExtractedValues: priorMetadataByThread?.get(threadId)?.extracted,
|
|
22131
22205
|
model: resolvedModel.model,
|
|
22132
|
-
timeZone
|
|
22206
|
+
timeZone,
|
|
22207
|
+
wasTruncated
|
|
22133
22208
|
});
|
|
22134
22209
|
results.set(threadId, {
|
|
22135
22210
|
observations: threadResult.observations,
|
|
@@ -22155,7 +22230,7 @@ var ObserverRunner = class {
|
|
|
22155
22230
|
const agent = this.createAgent(resolvedModel.model, true, void 0, activeExtractors);
|
|
22156
22231
|
const internalRequestContext = withOmInternalThreadId(requestContext, agent.id);
|
|
22157
22232
|
const multiThreadAttachmentFilter = this.resolveAttachmentFilter(resolvedModel.model, requestContext);
|
|
22158
|
-
const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread,
|
|
22233
|
+
const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread, wasTruncated, this.observationConfig.threadTitle, activeExtractors, {
|
|
22159
22234
|
attachmentFilter: multiThreadAttachmentFilter,
|
|
22160
22235
|
timeZone
|
|
22161
22236
|
})];
|
|
@@ -22169,6 +22244,7 @@ var ObserverRunner = class {
|
|
|
22169
22244
|
metadata: {
|
|
22170
22245
|
omThreadCount: threadOrder.length,
|
|
22171
22246
|
omPreviousObserverTokens: this.observationConfig.previousObserverTokens,
|
|
22247
|
+
omWasTruncated: wasTruncated ?? false,
|
|
22172
22248
|
omThreadTitleEnabled: this.observationConfig.threadTitle,
|
|
22173
22249
|
...resolvedModel.selectedThreshold !== void 0 ? { omSelectedThreshold: resolvedModel.selectedThreshold } : {},
|
|
22174
22250
|
...resolvedModel.routingStrategy ? { omRoutingStrategy: resolvedModel.routingStrategy } : {},
|
|
@@ -24101,6 +24177,9 @@ async function validateAgainstConfiguredSchema(schema, value) {
|
|
|
24101
24177
|
}).join("; ");
|
|
24102
24178
|
throw new Error(`Working memory update does not match the configured schema, so it was not saved: ${details}`);
|
|
24103
24179
|
}
|
|
24180
|
+
function isEmptyPlainObject(value) {
|
|
24181
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
24182
|
+
}
|
|
24104
24183
|
async function getWorkingMemoryDetails(context) {
|
|
24105
24184
|
const memory = context.memory;
|
|
24106
24185
|
const memoryConfig = (0, _mastra_core_memory.parseMemoryRequestContext)(context.requestContext)?.memoryConfig;
|
|
@@ -24148,8 +24227,9 @@ var WorkingMemoryExtractor = class extends Extractor {
|
|
|
24148
24227
|
const configuredSchema = memory.getMergedThreadConfig(memoryConfig ?? {}).workingMemory?.schema;
|
|
24149
24228
|
let document = current;
|
|
24150
24229
|
if (configuredSchema) {
|
|
24151
|
-
if (current === null) return;
|
|
24230
|
+
if (current === null || isEmptyPlainObject(current)) return;
|
|
24152
24231
|
document = await validateAgainstConfiguredSchema(configuredSchema, current);
|
|
24232
|
+
if (document === null || isEmptyPlainObject(document)) return;
|
|
24153
24233
|
}
|
|
24154
24234
|
const workingMemory = typeof document === "string" ? document : JSON.stringify(document) ?? "";
|
|
24155
24235
|
if (!workingMemory.trim()) return;
|
|
@@ -26437,6 +26517,82 @@ function resolveAutoModelId(mainModelId, options) {
|
|
|
26437
26517
|
return pick ? `${prefix}${pick}` : mainModelId;
|
|
26438
26518
|
}
|
|
26439
26519
|
//#endregion
|
|
26520
|
+
//#region src/processors/observational-memory/commit-queue.ts
|
|
26521
|
+
/** How long an op may hold its key before later ops on that key stop waiting for it. */
|
|
26522
|
+
const OM_COMMIT_STALL_MS = 6e4;
|
|
26523
|
+
/** The per-thread / per-resource key shared by the commit queue and `BufferingCoordinator`. */
|
|
26524
|
+
function getOMLockKey(scope, threadId, resourceId) {
|
|
26525
|
+
if (scope === "resource" && resourceId) return `resource:${resourceId}`;
|
|
26526
|
+
return `thread:${threadId ?? "unknown"}`;
|
|
26527
|
+
}
|
|
26528
|
+
const REGISTRY = Symbol.for("@mastra/memory/om-commit-queue");
|
|
26529
|
+
const { queues, heldKeys } = globalThis[REGISTRY] ??= {
|
|
26530
|
+
queues: /* @__PURE__ */ new Map(),
|
|
26531
|
+
heldKeys: new async_hooks.AsyncLocalStorage()
|
|
26532
|
+
};
|
|
26533
|
+
function holdsKey(key) {
|
|
26534
|
+
const token = heldKeys.getStore()?.get(key);
|
|
26535
|
+
return token !== void 0 && queues.get(key)?.running === token;
|
|
26536
|
+
}
|
|
26537
|
+
function runNext(key, queue) {
|
|
26538
|
+
const next = queue.reflection.shift() ?? queue.normal.shift();
|
|
26539
|
+
if (!next) {
|
|
26540
|
+
queue.running = null;
|
|
26541
|
+
queues.delete(key);
|
|
26542
|
+
return;
|
|
26543
|
+
}
|
|
26544
|
+
next();
|
|
26545
|
+
}
|
|
26546
|
+
/**
|
|
26547
|
+
* Runs `op` once every earlier op on `key` has settled (reflection-priority ops first among those
|
|
26548
|
+
* still waiting) and resolves or rejects with its result. A rejection reaches only this caller.
|
|
26549
|
+
*
|
|
26550
|
+
* Enqueueing on a key from inside an op that holds the same key, directly or through nested ops
|
|
26551
|
+
* on other keys, would deadlock, so it rejects.
|
|
26552
|
+
*/
|
|
26553
|
+
function runOMCommit(key, op, { priority = "normal" } = {}) {
|
|
26554
|
+
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.`));
|
|
26555
|
+
return new Promise((resolve, reject) => {
|
|
26556
|
+
let queue = queues.get(key);
|
|
26557
|
+
if (!queue) {
|
|
26558
|
+
queue = {
|
|
26559
|
+
running: null,
|
|
26560
|
+
reflection: [],
|
|
26561
|
+
normal: []
|
|
26562
|
+
};
|
|
26563
|
+
queues.set(key, queue);
|
|
26564
|
+
}
|
|
26565
|
+
const owner = queue;
|
|
26566
|
+
const start = async_hooks.AsyncResource.bind(() => {
|
|
26567
|
+
const token = Symbol(key);
|
|
26568
|
+
owner.running = token;
|
|
26569
|
+
const held = new Map(heldKeys.getStore());
|
|
26570
|
+
held.set(key, token);
|
|
26571
|
+
let released = false;
|
|
26572
|
+
const release = () => {
|
|
26573
|
+
if (released) return;
|
|
26574
|
+
released = true;
|
|
26575
|
+
clearTimeout(stallTimer);
|
|
26576
|
+
runNext(key, owner);
|
|
26577
|
+
};
|
|
26578
|
+
const stallTimer = setTimeout(() => {
|
|
26579
|
+
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.`);
|
|
26580
|
+
release();
|
|
26581
|
+
}, OM_COMMIT_STALL_MS);
|
|
26582
|
+
stallTimer.unref?.();
|
|
26583
|
+
heldKeys.run(held, () => Promise.resolve().then(op)).then((value) => {
|
|
26584
|
+
release();
|
|
26585
|
+
resolve(value);
|
|
26586
|
+
}, (error) => {
|
|
26587
|
+
release();
|
|
26588
|
+
reject(error);
|
|
26589
|
+
});
|
|
26590
|
+
});
|
|
26591
|
+
(priority === "reflection" ? owner.reflection : owner.normal).push(start);
|
|
26592
|
+
if (owner.running === null) runNext(key, owner);
|
|
26593
|
+
});
|
|
26594
|
+
}
|
|
26595
|
+
//#endregion
|
|
26440
26596
|
//#region src/processors/observational-memory/operation-registry.ts
|
|
26441
26597
|
const activeOps = /* @__PURE__ */ new Map();
|
|
26442
26598
|
function opKey(recordId, op) {
|
|
@@ -26476,11 +26632,37 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26476
26632
|
*/
|
|
26477
26633
|
static asyncBufferingOps = /* @__PURE__ */ new Map();
|
|
26478
26634
|
/**
|
|
26635
|
+
* Observation buffer ops that may still append a buffered chunk.
|
|
26636
|
+
* Key format: "obs:{lockKey}"
|
|
26637
|
+
* Value: Promise that resolves once the op has persisted its chunk (or ended without one)
|
|
26638
|
+
*
|
|
26639
|
+
* Above the threshold→blockAfter band, activation waits (bounded) on this phase so the chunk
|
|
26640
|
+
* activates instead of being re-observed by the sync pass. It is not needed for safety: storage
|
|
26641
|
+
* keeps a chunk appended during activation. An op stays in asyncBufferingOps through its
|
|
26642
|
+
* post-persist work (indexing, thread title), which activation never waits for.
|
|
26643
|
+
*/
|
|
26644
|
+
static pendingChunkWrites = /* @__PURE__ */ new Map();
|
|
26645
|
+
/**
|
|
26479
26646
|
* Track the last token boundary at which we started buffering.
|
|
26480
26647
|
* Key format: "obs:{lockKey}" or "refl:{lockKey}"
|
|
26481
26648
|
*/
|
|
26482
26649
|
static lastBufferedBoundary = /* @__PURE__ */ new Map();
|
|
26483
26650
|
/**
|
|
26651
|
+
* The buffer op that set the observation boundary, if one did. Any other write (an activation
|
|
26652
|
+
* reset, another op) replaces or clears it, so an op that outlived it never writes its boundary
|
|
26653
|
+
* back. Key format: "obs:{lockKey}"
|
|
26654
|
+
*/
|
|
26655
|
+
static observationBoundaryOwners = /* @__PURE__ */ new Map();
|
|
26656
|
+
/** Set the observation boundary; `owner` is the buffer op setting it, if any. */
|
|
26657
|
+
static setObservationBoundary(bufferKey, tokens, owner) {
|
|
26658
|
+
BufferingCoordinator.lastBufferedBoundary.set(bufferKey, tokens);
|
|
26659
|
+
if (owner) BufferingCoordinator.observationBoundaryOwners.set(bufferKey, owner);
|
|
26660
|
+
else BufferingCoordinator.observationBoundaryOwners.delete(bufferKey);
|
|
26661
|
+
}
|
|
26662
|
+
static ownsObservationBoundary(bufferKey, owner) {
|
|
26663
|
+
return BufferingCoordinator.observationBoundaryOwners.get(bufferKey) === owner;
|
|
26664
|
+
}
|
|
26665
|
+
/**
|
|
26484
26666
|
* Track the timestamp cursor for buffered messages.
|
|
26485
26667
|
* Key format: "obs:{lockKey}"
|
|
26486
26668
|
*/
|
|
@@ -26496,8 +26678,7 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26496
26678
|
this.scope = opts.scope;
|
|
26497
26679
|
}
|
|
26498
26680
|
getLockKey(threadId, resourceId) {
|
|
26499
|
-
|
|
26500
|
-
return `thread:${threadId ?? "unknown"}`;
|
|
26681
|
+
return getOMLockKey(this.scope, threadId, resourceId);
|
|
26501
26682
|
}
|
|
26502
26683
|
isAsyncObservationEnabled() {
|
|
26503
26684
|
return this.observationConfig.bufferTokens !== void 0 && this.observationConfig.bufferTokens > 0;
|
|
@@ -26515,6 +26696,19 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26515
26696
|
return BufferingCoordinator.asyncBufferingOps.has(bufferKey);
|
|
26516
26697
|
}
|
|
26517
26698
|
/**
|
|
26699
|
+
* Mark an observation buffer op as able to append a chunk. Call the returned release
|
|
26700
|
+
* function once it no longer can; releasing more than once is a no-op.
|
|
26701
|
+
*/
|
|
26702
|
+
static trackChunkWrite(bufferKey) {
|
|
26703
|
+
let resolve;
|
|
26704
|
+
const pending = new Promise((r) => resolve = r);
|
|
26705
|
+
BufferingCoordinator.pendingChunkWrites.set(bufferKey, pending);
|
|
26706
|
+
return () => {
|
|
26707
|
+
if (BufferingCoordinator.pendingChunkWrites.get(bufferKey) === pending) BufferingCoordinator.pendingChunkWrites.delete(bufferKey);
|
|
26708
|
+
resolve();
|
|
26709
|
+
};
|
|
26710
|
+
}
|
|
26711
|
+
/**
|
|
26518
26712
|
* Clean up static maps for a thread/resource to prevent memory leaks.
|
|
26519
26713
|
*/
|
|
26520
26714
|
cleanupStaticMaps(threadId, resourceId, activatedMessageIds) {
|
|
@@ -26523,10 +26717,12 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26523
26717
|
const reflBufKey = this.getReflectionBufferKey(lockKey);
|
|
26524
26718
|
if (activatedMessageIds) {
|
|
26525
26719
|
BufferingCoordinator.lastBufferedBoundary.delete(obsBufKey);
|
|
26720
|
+
BufferingCoordinator.observationBoundaryOwners.delete(obsBufKey);
|
|
26526
26721
|
BufferingCoordinator.lastBufferedAtTime.delete(obsBufKey);
|
|
26527
26722
|
} else {
|
|
26528
26723
|
BufferingCoordinator.lastBufferedAtTime.delete(obsBufKey);
|
|
26529
26724
|
BufferingCoordinator.lastBufferedBoundary.delete(obsBufKey);
|
|
26725
|
+
BufferingCoordinator.observationBoundaryOwners.delete(obsBufKey);
|
|
26530
26726
|
BufferingCoordinator.lastBufferedBoundary.delete(reflBufKey);
|
|
26531
26727
|
BufferingCoordinator.asyncBufferingOps.delete(obsBufKey);
|
|
26532
26728
|
BufferingCoordinator.asyncBufferingOps.delete(reflBufKey);
|
|
@@ -26541,7 +26737,7 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26541
26737
|
if (record.isBufferingObservation) {
|
|
26542
26738
|
if (isOpActiveInProcess(record.id, "bufferingObservation")) return false;
|
|
26543
26739
|
omDebug(`[OM:shouldTriggerAsyncObs] isBufferingObservation=true but stale, clearing`);
|
|
26544
|
-
storage
|
|
26740
|
+
if (storage) runOMCommit(lockKey, () => storage.setBufferingObservationFlag(record.id, false)).catch(() => {});
|
|
26545
26741
|
}
|
|
26546
26742
|
const bufferKey = this.getObservationBufferKey(lockKey);
|
|
26547
26743
|
if (this.isAsyncBufferingInProgress(bufferKey)) return false;
|
|
@@ -26561,7 +26757,7 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26561
26757
|
* Await any in-flight async buffering operations for a given thread/resource.
|
|
26562
26758
|
*/
|
|
26563
26759
|
static async awaitBuffering(threadId, resourceId, scope, timeoutMs = 3e4) {
|
|
26564
|
-
const lockKey = scope
|
|
26760
|
+
const lockKey = getOMLockKey(scope, threadId, resourceId);
|
|
26565
26761
|
const obsKey = `obs:${lockKey}`;
|
|
26566
26762
|
const reflKey = `refl:${lockKey}`;
|
|
26567
26763
|
const promises = [];
|
|
@@ -26859,6 +27055,36 @@ function createThreadUpdateMarker(params) {
|
|
|
26859
27055
|
}
|
|
26860
27056
|
};
|
|
26861
27057
|
}
|
|
27058
|
+
//#endregion
|
|
27059
|
+
//#region src/processors/observational-memory/record-lineage.ts
|
|
27060
|
+
/**
|
|
27061
|
+
* The current head of `origin`'s lineage (the generations reflections have created from it),
|
|
27062
|
+
* or `null` when that lineage is gone.
|
|
27063
|
+
*
|
|
27064
|
+
* Use this instead of reading the head by thread/resource when committing work that started
|
|
27065
|
+
* from `origin`. Clearing a record (`om.clear`, `Memory.deleteThread`) deletes every generation
|
|
27066
|
+
* of the thread/resource, and the thread can then get a new, unrelated record. Work produced
|
|
27067
|
+
* from the cleared messages must never be committed to it.
|
|
27068
|
+
*
|
|
27069
|
+
* Generation ids are never reused and clearing deletes all of them, so the head belongs to
|
|
27070
|
+
* `origin`'s lineage iff `origin` still exists.
|
|
27071
|
+
*/
|
|
27072
|
+
async function getLineageHead(storage, origin) {
|
|
27073
|
+
const head = await storage.getObservationalMemory(origin.threadId, origin.resourceId);
|
|
27074
|
+
if (!head || head.id === origin.id) return head;
|
|
27075
|
+
if (head.generationCount <= origin.generationCount) return null;
|
|
27076
|
+
return (await storage.getObservationalMemoryHistory(origin.threadId, origin.resourceId, head.generationCount - origin.generationCount + 3)).some((record) => record.id === origin.id) ? head : null;
|
|
27077
|
+
}
|
|
27078
|
+
/**
|
|
27079
|
+
* The generation `id` of `origin`'s lineage, or `null` when that lineage is gone or `id` is not
|
|
27080
|
+
* part of it. Unlike {@link getLineageHead}, this keeps naming `id` after later reflections have
|
|
27081
|
+
* superseded it, e.g. to report which generation a write landed on.
|
|
27082
|
+
*/
|
|
27083
|
+
async function getLineageRecord(storage, origin, id) {
|
|
27084
|
+
const head = await getLineageHead(storage, origin);
|
|
27085
|
+
if (!head || head.id === id) return head;
|
|
27086
|
+
return (await storage.getObservationalMemoryHistory(origin.threadId, origin.resourceId, head.generationCount - origin.generationCount + 3)).find((record) => record.id === id) ?? null;
|
|
27087
|
+
}
|
|
26862
27088
|
function isRecord$1(value) {
|
|
26863
27089
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
26864
27090
|
}
|
|
@@ -27005,6 +27231,39 @@ function calculateProjectedMessageRemoval(chunks, bufferActivation, messageToken
|
|
|
27005
27231
|
//#region src/processors/observational-memory/observation-strategies/base.ts
|
|
27006
27232
|
/** Module-level xxhash singleton — loaded once, shared across all strategy instances. */
|
|
27007
27233
|
const hasherPromise = (0, xxhash_wasm.default)();
|
|
27234
|
+
/** Recompose-and-retry rounds for an observation commit that hit a retired or changed head. */
|
|
27235
|
+
const MAX_HEAD_COMMIT_RETRIES = 3;
|
|
27236
|
+
const OBSERVATION_LIFECYCLE_MARKERS = /* @__PURE__ */ new Set([
|
|
27237
|
+
"data-om-observation-start",
|
|
27238
|
+
"data-om-observation-end",
|
|
27239
|
+
"data-om-observation-failed"
|
|
27240
|
+
]);
|
|
27241
|
+
function isOmPart(part) {
|
|
27242
|
+
return String(part?.type ?? "").startsWith("data-om-");
|
|
27243
|
+
}
|
|
27244
|
+
/**
|
|
27245
|
+
* Index in `message` right after this cycle's start marker (and any OM parts directly after it),
|
|
27246
|
+
* or -1 when the message doesn't carry it.
|
|
27247
|
+
*/
|
|
27248
|
+
function afterOwnStartMarker(message, marker) {
|
|
27249
|
+
const cycleId = marker.data?.cycleId;
|
|
27250
|
+
const startType = marker.type.startsWith("data-om-buffering-") ? "data-om-buffering-start" : "data-om-observation-start";
|
|
27251
|
+
if (!cycleId || marker.type === startType) return -1;
|
|
27252
|
+
const parts = message.content?.parts ?? [];
|
|
27253
|
+
const start = parts.findIndex((part) => part?.type === startType && part?.data?.cycleId === cycleId);
|
|
27254
|
+
if (start === -1) return -1;
|
|
27255
|
+
let index = start + 1;
|
|
27256
|
+
while (index < parts.length && isOmPart(parts[index])) index++;
|
|
27257
|
+
return index;
|
|
27258
|
+
}
|
|
27259
|
+
/** True when the message has parts no completed observation boundary covers (OM markers aside). */
|
|
27260
|
+
function hasUnobservedContent(message) {
|
|
27261
|
+
return getUnobservedParts(message).some((part) => !isOmPart(part));
|
|
27262
|
+
}
|
|
27263
|
+
function isObservationLifecycleMarker(marker) {
|
|
27264
|
+
const operationType = marker.data?.operationType;
|
|
27265
|
+
return OBSERVATION_LIFECYCLE_MARKERS.has(marker.type) && operationType !== "reflection";
|
|
27266
|
+
}
|
|
27008
27267
|
/**
|
|
27009
27268
|
* Abstract base class for observation strategies.
|
|
27010
27269
|
*
|
|
@@ -27054,17 +27313,35 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
27054
27313
|
const fresh = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
27055
27314
|
if (fresh?.lastObservedAt && record.lastObservedAt && fresh.lastObservedAt > record.lastObservedAt) return { observed: false };
|
|
27056
27315
|
}
|
|
27057
|
-
const { messages, existingObservations } = await this.prepare();
|
|
27316
|
+
const { messages, existingObservations, contextRecord } = await this.prepare();
|
|
27317
|
+
if (messages.length === 0) return { observed: false };
|
|
27058
27318
|
const observationMessages = stripSubconsciousSignals(messages);
|
|
27059
27319
|
await this.emitStartMarkers(cycleId);
|
|
27060
|
-
const
|
|
27061
|
-
const
|
|
27062
|
-
|
|
27320
|
+
const observerContext = this.deps.prepareObserverContext(existingObservations, contextRecord ?? record);
|
|
27321
|
+
const output = await this.observe(observerContext.context ?? "", observationMessages, observerContext.wasTruncated);
|
|
27322
|
+
let processed = await this.process(output, existingObservations);
|
|
27323
|
+
let committedRecord = record;
|
|
27324
|
+
const outcome = await this.persist(processed);
|
|
27325
|
+
this.settleObservationCommit(outcome?.status === "committed" && !outcome.alreadyCovered);
|
|
27326
|
+
if (outcome?.status === "not-committed") {
|
|
27327
|
+
omDebug(`[OM:observe] cycle ${cycleId} not committed: ${outcome.reason}`);
|
|
27328
|
+
await this.emitFailedMarkers(cycleId, /* @__PURE__ */ new Error(`Observation not committed: ${outcome.reason}`));
|
|
27329
|
+
return {
|
|
27330
|
+
observed: false,
|
|
27331
|
+
usage: output.usage,
|
|
27332
|
+
providerMetadata: output.providerMetadata
|
|
27333
|
+
};
|
|
27334
|
+
}
|
|
27335
|
+
if (outcome?.status === "committed") {
|
|
27336
|
+
processed = outcome.processed;
|
|
27337
|
+
committedRecord = outcome.record;
|
|
27338
|
+
}
|
|
27063
27339
|
await this.emitEndMarkers(cycleId, processed);
|
|
27064
27340
|
if (this.needsReflection) await this.deps.reflector.maybeReflect({
|
|
27065
27341
|
record: {
|
|
27066
|
-
...
|
|
27067
|
-
activeObservations: processed.observations
|
|
27342
|
+
...committedRecord,
|
|
27343
|
+
activeObservations: processed.observations,
|
|
27344
|
+
observationTokenCount: processed.observationTokens
|
|
27068
27345
|
},
|
|
27069
27346
|
observationTokens: processed.observationTokens,
|
|
27070
27347
|
threadId,
|
|
@@ -27100,7 +27377,8 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
27100
27377
|
threadId
|
|
27101
27378
|
}
|
|
27102
27379
|
};
|
|
27103
|
-
|
|
27380
|
+
const observed = this.getObservedMessagesForThread(threadId);
|
|
27381
|
+
await (observed?.length ? this.persistMarkerToObservedMessage(failedMarkerForStorage, observed, threadId, this.opts.resourceId) : this.persistMarkerToStorage(failedMarkerForStorage, threadId, this.opts.resourceId)).catch(() => {});
|
|
27104
27382
|
if (abortSignal?.aborted) throw error;
|
|
27105
27383
|
omError("[OM] Observation failed", error);
|
|
27106
27384
|
return {
|
|
@@ -27127,6 +27405,11 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
27127
27405
|
transient: true
|
|
27128
27406
|
}).catch(() => {});
|
|
27129
27407
|
const markerThreadId = marker.data?.threadId ?? this.opts.threadId;
|
|
27408
|
+
const observed = isObservationLifecycleMarker(marker) ? this.getObservedMessagesForThread(markerThreadId) : void 0;
|
|
27409
|
+
if (observed?.length) {
|
|
27410
|
+
await this.persistMarkerToObservedMessage(marker, observed, markerThreadId, this.opts.resourceId);
|
|
27411
|
+
return;
|
|
27412
|
+
}
|
|
27130
27413
|
if (!await this.persistMarkerToMessage(marker, this.opts.messageList, markerThreadId, this.opts.resourceId)) await this.persistMarkerToStorage(marker, markerThreadId, this.opts.resourceId);
|
|
27131
27414
|
}
|
|
27132
27415
|
getObservationMarkerConfig() {
|
|
@@ -27210,6 +27493,76 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
27210
27493
|
}
|
|
27211
27494
|
return `${existingObservations}${lastObservedAt ? ObservationStrategy.createMessageBoundary(lastObservedAt) : "\n\n"}${newThreadSection}`;
|
|
27212
27495
|
}
|
|
27496
|
+
/**
|
|
27497
|
+
* Commit composed observations to the current head generation.
|
|
27498
|
+
*
|
|
27499
|
+
* The write is conditional on the head text the observations were composed from. When the
|
|
27500
|
+
* target generation was retired by a reflection, or another writer changed the text, the
|
|
27501
|
+
* observations are recomposed against the fresh head and the commit is retried (bounded).
|
|
27502
|
+
* When the fresh head's `observedMessageIds` already include every message this cycle observed
|
|
27503
|
+
* (another instance observed them first), nothing is written and `alreadyCovered` is set, so
|
|
27504
|
+
* callers advance cursors and markers without adding a second copy of the same observations.
|
|
27505
|
+
* The cursor alone is not proof: an activation can move it past a message it never covered.
|
|
27506
|
+
* Returns null when no commit landed — callers must then leave cursors, markers, and the live
|
|
27507
|
+
* context untouched.
|
|
27508
|
+
*/
|
|
27509
|
+
async commitActiveObservationsToHead(opts) {
|
|
27510
|
+
let { processed, composedFrom, target } = opts;
|
|
27511
|
+
for (let attempt = 0; attempt <= MAX_HEAD_COMMIT_RETRIES; attempt++) {
|
|
27512
|
+
const result = await this.runCommit(async () => {
|
|
27513
|
+
const head = await getLineageHead(this.storage, target);
|
|
27514
|
+
if (!head) return null;
|
|
27515
|
+
if (head.id !== target.id || (head.activeObservations ?? "") !== composedFrom) {
|
|
27516
|
+
if (this.headCoversCycle(head, opts.cycleMessageIds)) {
|
|
27517
|
+
omDebug(`[OM:observe] head ${head.id} already covers this cycle's messages; skipping the duplicate commit`);
|
|
27518
|
+
target = head;
|
|
27519
|
+
return {
|
|
27520
|
+
applied: true,
|
|
27521
|
+
alreadyCovered: true
|
|
27522
|
+
};
|
|
27523
|
+
}
|
|
27524
|
+
target = head;
|
|
27525
|
+
composedFrom = head.activeObservations ?? "";
|
|
27526
|
+
processed = await opts.recompose(head);
|
|
27527
|
+
}
|
|
27528
|
+
const input = {
|
|
27529
|
+
id: target.id,
|
|
27530
|
+
observations: processed.observations,
|
|
27531
|
+
tokenCount: processed.observationTokens,
|
|
27532
|
+
lastObservedAt: processed.lastObservedAt,
|
|
27533
|
+
observedMessageIds: processed.observedMessageIds,
|
|
27534
|
+
expectedActiveObservations: composedFrom
|
|
27535
|
+
};
|
|
27536
|
+
return typeof this.storage.commitActiveObservations === "function" ? await this.storage.commitActiveObservations(input) : (await this.storage.updateActiveObservations(input), { applied: true });
|
|
27537
|
+
});
|
|
27538
|
+
if (!result) return null;
|
|
27539
|
+
if ("alreadyCovered" in result) return {
|
|
27540
|
+
processed: {
|
|
27541
|
+
...processed,
|
|
27542
|
+
observations: target.activeObservations ?? "",
|
|
27543
|
+
observationTokens: target.observationTokenCount
|
|
27544
|
+
},
|
|
27545
|
+
record: target,
|
|
27546
|
+
alreadyCovered: true
|
|
27547
|
+
};
|
|
27548
|
+
if (result.applied) return {
|
|
27549
|
+
processed,
|
|
27550
|
+
record: target
|
|
27551
|
+
};
|
|
27552
|
+
omDebug(`[OM:observe] commit to ${target.id} not applied (${result.reason}); retrying against the head`);
|
|
27553
|
+
}
|
|
27554
|
+
return null;
|
|
27555
|
+
}
|
|
27556
|
+
headCoversCycle(head, cycleMessageIds) {
|
|
27557
|
+
if (cycleMessageIds.length === 0) return false;
|
|
27558
|
+
const headIds = new Set(Array.isArray(head.observedMessageIds) ? head.observedMessageIds : []);
|
|
27559
|
+
return cycleMessageIds.every((id) => headIds.has(id));
|
|
27560
|
+
}
|
|
27561
|
+
/** Runs one storage commit in this thread's (or resource's) slot of the process-wide commit queue. */
|
|
27562
|
+
runCommit(op) {
|
|
27563
|
+
const { threadId, resourceId, record } = this.opts;
|
|
27564
|
+
return runOMCommit(getOMLockKey(this.scope, record.threadId ?? threadId, record.resourceId ?? resourceId), op);
|
|
27565
|
+
}
|
|
27213
27566
|
async indexObservationGroups(observations, threadId, resourceId, observedAt, recordId) {
|
|
27214
27567
|
if (!resourceId || !this.deps.onIndexObservations) return;
|
|
27215
27568
|
const groups = parseObservationGroups(observations);
|
|
@@ -27228,30 +27581,133 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
27228
27581
|
})));
|
|
27229
27582
|
}
|
|
27230
27583
|
/**
|
|
27584
|
+
* Messages this cycle observed for `threadId`, or undefined when the strategy does not
|
|
27585
|
+
* place observation lifecycle markers.
|
|
27586
|
+
*/
|
|
27587
|
+
getObservedMessagesForThread(_threadId) {}
|
|
27588
|
+
/**
|
|
27589
|
+
* Persist an observation start/end/failed marker without hiding unobserved content. A completed
|
|
27590
|
+
* end marker tells `getUnobservedMessages` that everything before it in that message is observed
|
|
27591
|
+
* (whatever thread the marker names), so it must only follow content this cycle observed.
|
|
27592
|
+
* Placement, in order:
|
|
27593
|
+
* 1. the live MessageList's newest assistant message (this turn's response or its seed), when the
|
|
27594
|
+
* list belongs to the marker's thread and the cycle observed that message or it has no
|
|
27595
|
+
* unobserved content;
|
|
27596
|
+
* 2. the newest observed assistant message; a stored copy (no live list, or another instance's
|
|
27597
|
+
* message) gets the marker right after the parts this cycle observed (end/failed markers right
|
|
27598
|
+
* after this cycle's start marker), so parts added while the Observer ran stay unobserved;
|
|
27599
|
+
* 3. the newest stored assistant message that is not newer than the observed range, only when it
|
|
27600
|
+
* has nothing unobserved (or already carries this cycle's start marker); otherwise no marker,
|
|
27601
|
+
* and the thread cursor alone records the cycle.
|
|
27602
|
+
* Lifecycle markers never go on user messages.
|
|
27603
|
+
*/
|
|
27604
|
+
async persistMarkerToObservedMessage(marker, observed, threadId, resourceId) {
|
|
27605
|
+
const observedIds = new Set(observed.map((m) => m.id));
|
|
27606
|
+
const list = threadId === this.opts.threadId ? this.opts.messageList : void 0;
|
|
27607
|
+
const listMessages = list ? getObservableMessages(list) : [];
|
|
27608
|
+
const live = [...listMessages].reverse().find((m) => m.role === "assistant");
|
|
27609
|
+
if (live && (observedIds.has(live.id) || !hasUnobservedContent(live))) {
|
|
27610
|
+
await this.attachMarker(marker, live, live.content.parts.length, threadId, resourceId);
|
|
27611
|
+
return;
|
|
27612
|
+
}
|
|
27613
|
+
const target = [...observed].reverse().find((m) => m.role === "assistant");
|
|
27614
|
+
if (target) {
|
|
27615
|
+
const fromList = listMessages.find((m) => m.id === target.id);
|
|
27616
|
+
if (fromList) {
|
|
27617
|
+
await this.attachMarker(marker, fromList, fromList.content.parts.length, threadId, resourceId);
|
|
27618
|
+
return;
|
|
27619
|
+
}
|
|
27620
|
+
try {
|
|
27621
|
+
const stored = (await this.storage.listMessagesById({ messageIds: [target.id] })).messages[0];
|
|
27622
|
+
if (stored?.content?.parts && Array.isArray(stored.content.parts)) {
|
|
27623
|
+
const afterStart = afterOwnStartMarker(stored, marker);
|
|
27624
|
+
const index = afterStart !== -1 ? afterStart : this.insertionAfterObserved(stored, target);
|
|
27625
|
+
await this.attachMarker(marker, stored, index, threadId, resourceId);
|
|
27626
|
+
}
|
|
27627
|
+
} catch (e) {
|
|
27628
|
+
omDebug(`[OM:persistMarkerToObservedMessage] failed to load marker target: ${e}`);
|
|
27629
|
+
}
|
|
27630
|
+
return;
|
|
27631
|
+
}
|
|
27632
|
+
const observedTimes = observed.filter((m) => m.createdAt).map((m) => new Date(m.createdAt).getTime());
|
|
27633
|
+
if (observedTimes.length === 0) return;
|
|
27634
|
+
await this.persistMarkerToStorage(marker, threadId, resourceId, {
|
|
27635
|
+
notAfter: new Date(Math.max(...observedTimes)),
|
|
27636
|
+
onlyIfObserved: true
|
|
27637
|
+
});
|
|
27638
|
+
}
|
|
27639
|
+
/** What the cycle observed of each message, captured when it placed its first marker there. */
|
|
27640
|
+
observedParts = /* @__PURE__ */ new Map();
|
|
27641
|
+
/**
|
|
27642
|
+
* Index in `stored` right after the parts the cycle observed in `observed` (and any OM markers
|
|
27643
|
+
* directly after them). Parts are only ever appended, so the observed message's non-marker part
|
|
27644
|
+
* count locates them, counted from the start of the message or, when `observed` is the trimmed
|
|
27645
|
+
* copy `getUnobservedMessages` returns for an already marked message, from just after the stored
|
|
27646
|
+
* copy's last completed boundary. Captured at the cycle's first marker, before the Observer runs,
|
|
27647
|
+
* because `observed` may be a live reference that grows meanwhile. Falls back to the end when the
|
|
27648
|
+
* stored copy has fewer parts than were observed.
|
|
27649
|
+
*/
|
|
27650
|
+
insertionAfterObserved(stored, observed) {
|
|
27651
|
+
if (!this.observedParts.has(observed.id)) this.observedParts.set(observed.id, {
|
|
27652
|
+
count: (observed.content?.parts ?? []).filter((part) => !isOmPart(part)).length,
|
|
27653
|
+
observedIsTrimmed: findLastCompletedObservationBoundary(observed) === -1
|
|
27654
|
+
});
|
|
27655
|
+
const { count, observedIsTrimmed } = this.observedParts.get(observed.id);
|
|
27656
|
+
const parts = stored.content.parts;
|
|
27657
|
+
let index = observedIsTrimmed ? findLastCompletedObservationBoundary(stored) + 1 : 0;
|
|
27658
|
+
let seen = 0;
|
|
27659
|
+
while (index < parts.length && seen < count) {
|
|
27660
|
+
if (!isOmPart(parts[index])) seen++;
|
|
27661
|
+
index++;
|
|
27662
|
+
}
|
|
27663
|
+
if (seen < count) return parts.length;
|
|
27664
|
+
while (index < parts.length && isOmPart(parts[index])) index++;
|
|
27665
|
+
return index;
|
|
27666
|
+
}
|
|
27667
|
+
async attachMarker(marker, msg, index, threadId, resourceId) {
|
|
27668
|
+
const parts = msg.content?.parts;
|
|
27669
|
+
if (!parts || !Array.isArray(parts)) return;
|
|
27670
|
+
const markerData = marker.data;
|
|
27671
|
+
if (!(markerData?.cycleId && parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) parts.splice(index, 0, marker);
|
|
27672
|
+
try {
|
|
27673
|
+
await this.messageHistory.persistMessages({
|
|
27674
|
+
messages: [msg],
|
|
27675
|
+
threadId,
|
|
27676
|
+
resourceId
|
|
27677
|
+
});
|
|
27678
|
+
} catch (e) {
|
|
27679
|
+
omDebug(`[OM:persistMarkerToObservedMessage] failed to save marker to DB: ${e}`);
|
|
27680
|
+
}
|
|
27681
|
+
}
|
|
27682
|
+
/**
|
|
27231
27683
|
* Persist a marker to the last assistant message in storage.
|
|
27232
27684
|
* Fetches messages directly from the DB so it works even when
|
|
27233
27685
|
* no MessageList is available (e.g. async buffering ops).
|
|
27234
27686
|
*/
|
|
27235
|
-
async persistMarkerToStorage(marker, threadId, resourceId) {
|
|
27687
|
+
async persistMarkerToStorage(marker, threadId, resourceId, opts) {
|
|
27236
27688
|
try {
|
|
27237
|
-
const
|
|
27689
|
+
const assistants = ((await this.storage.listMessages({
|
|
27238
27690
|
threadId,
|
|
27239
27691
|
perPage: 20,
|
|
27240
27692
|
orderBy: {
|
|
27241
27693
|
field: "createdAt",
|
|
27242
27694
|
direction: "DESC"
|
|
27243
|
-
}
|
|
27244
|
-
|
|
27245
|
-
|
|
27246
|
-
|
|
27247
|
-
|
|
27248
|
-
|
|
27249
|
-
|
|
27250
|
-
|
|
27251
|
-
|
|
27252
|
-
|
|
27253
|
-
|
|
27254
|
-
|
|
27695
|
+
},
|
|
27696
|
+
...opts?.notAfter ? { filter: { dateRange: { end: opts.notAfter } } } : {}
|
|
27697
|
+
}))?.messages ?? []).filter((msg) => msg?.role === "assistant" && Array.isArray(msg.content?.parts));
|
|
27698
|
+
const startMessage = opts?.onlyIfObserved || opts?.onCycleStartMessage ? assistants.find((m) => afterOwnStartMarker(m, marker) !== -1) : void 0;
|
|
27699
|
+
const msg = opts?.onCycleStartMessage ? startMessage : startMessage ?? assistants[0];
|
|
27700
|
+
if (!msg) return;
|
|
27701
|
+
const markerData = marker.data;
|
|
27702
|
+
const alreadyPresent = markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId);
|
|
27703
|
+
const afterStart = afterOwnStartMarker(msg, marker);
|
|
27704
|
+
if (opts?.onlyIfObserved && afterStart === -1 && hasUnobservedContent(msg)) return;
|
|
27705
|
+
if (!alreadyPresent) msg.content.parts.splice(afterStart !== -1 ? afterStart : msg.content.parts.length, 0, marker);
|
|
27706
|
+
await this.messageHistory.persistMessages({
|
|
27707
|
+
messages: [msg],
|
|
27708
|
+
threadId,
|
|
27709
|
+
resourceId
|
|
27710
|
+
});
|
|
27255
27711
|
} catch (e) {
|
|
27256
27712
|
omDebug(`[OM:persistMarkerToStorage] failed to save marker to DB: ${e}`);
|
|
27257
27713
|
}
|
|
@@ -27313,10 +27769,15 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27313
27769
|
tokensToObserve = 0;
|
|
27314
27770
|
observerResult;
|
|
27315
27771
|
priorExtractedValues;
|
|
27772
|
+
/** Head text the processed observations were composed from (the commit's expected text). */
|
|
27773
|
+
composedFrom = "";
|
|
27316
27774
|
constructor(deps, opts) {
|
|
27317
27775
|
super(deps, opts);
|
|
27318
27776
|
this.lastMessage = opts.messages[opts.messages.length - 1];
|
|
27319
27777
|
}
|
|
27778
|
+
getObservedMessagesForThread(threadId) {
|
|
27779
|
+
return threadId === this.opts.threadId ? this.opts.messages : void 0;
|
|
27780
|
+
}
|
|
27320
27781
|
get needsLock() {
|
|
27321
27782
|
return true;
|
|
27322
27783
|
}
|
|
@@ -27351,9 +27812,11 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27351
27812
|
}
|
|
27352
27813
|
}
|
|
27353
27814
|
this.tokensToObserve = await this.tokenCounter.countMessagesAsync(messages);
|
|
27815
|
+
const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
27354
27816
|
return {
|
|
27355
27817
|
messages,
|
|
27356
|
-
existingObservations:
|
|
27818
|
+
existingObservations: freshRecord?.activeObservations ?? record.activeObservations ?? "",
|
|
27819
|
+
contextRecord: freshRecord
|
|
27357
27820
|
};
|
|
27358
27821
|
}
|
|
27359
27822
|
async emitStartMarkers(cycleId) {
|
|
@@ -27371,11 +27834,12 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27371
27834
|
await this.streamMarker(startMarker);
|
|
27372
27835
|
}
|
|
27373
27836
|
}
|
|
27374
|
-
async observe(
|
|
27837
|
+
async observe(observerContext, messages, wasTruncated) {
|
|
27375
27838
|
const thread = await this.storage.getThreadById({ threadId: this.opts.threadId });
|
|
27376
27839
|
const omMeta = thread ? (0, _mastra_core_memory.getThreadOMMetadata)(thread.metadata) : void 0;
|
|
27377
27840
|
this.priorExtractedValues = getPriorExtractedValues(omMeta, this.observationConfig.extractors);
|
|
27378
|
-
const result = await this.deps.observer.call(
|
|
27841
|
+
const result = await this.deps.observer.call(observerContext, messages, this.opts.abortSignal, {
|
|
27842
|
+
wasTruncated,
|
|
27379
27843
|
requestContext: this.opts.requestContext,
|
|
27380
27844
|
observabilityContext: this.opts.observabilityContext,
|
|
27381
27845
|
priorCurrentTask: omMeta?.currentTask,
|
|
@@ -27421,20 +27885,14 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27421
27885
|
}
|
|
27422
27886
|
async process(output, existingObservations) {
|
|
27423
27887
|
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])];
|
|
27888
|
+
this.composedFrom = existingObservations;
|
|
27889
|
+
const processed = await this.compose(output, existingObservations, record);
|
|
27432
27890
|
this.deps.emitDebugEvent({
|
|
27433
27891
|
type: "observation_complete",
|
|
27434
27892
|
timestamp: /* @__PURE__ */ new Date(),
|
|
27435
27893
|
threadId,
|
|
27436
27894
|
resourceId: record.resourceId ?? "",
|
|
27437
|
-
observations:
|
|
27895
|
+
observations: processed.observations,
|
|
27438
27896
|
rawObserverOutput: output.observations,
|
|
27439
27897
|
previousObservations: record.activeObservations,
|
|
27440
27898
|
messages: messages.map((m) => ({
|
|
@@ -27443,11 +27901,23 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27443
27901
|
})),
|
|
27444
27902
|
usage: output.usage
|
|
27445
27903
|
});
|
|
27904
|
+
return processed;
|
|
27905
|
+
}
|
|
27906
|
+
/** Build the observations to commit on top of `existingObservations` (the head text). */
|
|
27907
|
+
async compose(output, existingObservations, baseRecord) {
|
|
27908
|
+
const { threadId, messages } = this.opts;
|
|
27909
|
+
const lastObservedAt = this.getMaxMessageTimestamp(messages);
|
|
27910
|
+
const messageRange = this.retrieval ? buildMessageRange(messages) : void 0;
|
|
27911
|
+
const newObservations = await this.wrapObservations(output.observations, existingObservations, threadId, lastObservedAt, messageRange);
|
|
27912
|
+
const observationTokens = this.tokenCounter.countObservations(newObservations);
|
|
27913
|
+
const cycleObservationTokens = this.tokenCounter.countObservations(output.observations);
|
|
27914
|
+
const newMessageIds = messages.map((m) => m.id);
|
|
27915
|
+
const existingIds = baseRecord.observedMessageIds ?? [];
|
|
27446
27916
|
return {
|
|
27447
27917
|
observations: newObservations,
|
|
27448
27918
|
observationTokens,
|
|
27449
27919
|
cycleObservationTokens,
|
|
27450
|
-
observedMessageIds,
|
|
27920
|
+
observedMessageIds: [.../* @__PURE__ */ new Set([...Array.isArray(existingIds) ? existingIds : [], ...newMessageIds])],
|
|
27451
27921
|
lastObservedAt,
|
|
27452
27922
|
suggestedContinuation: output.suggestedContinuation,
|
|
27453
27923
|
currentTask: output.currentTask,
|
|
@@ -27459,12 +27929,27 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27459
27929
|
}
|
|
27460
27930
|
async persist(processed) {
|
|
27461
27931
|
const { record, threadId, resourceId, messages } = this.opts;
|
|
27462
|
-
const
|
|
27463
|
-
if (!
|
|
27932
|
+
const liveRecord = await getLineageHead(this.storage, record);
|
|
27933
|
+
if (!liveRecord) {
|
|
27464
27934
|
omDebug(`[OM:sync-obs] skipping persist for thread ${threadId}: observational memory record is gone`);
|
|
27465
|
-
return
|
|
27935
|
+
return {
|
|
27936
|
+
status: "not-committed",
|
|
27937
|
+
reason: "the observational memory record was cleared"
|
|
27938
|
+
};
|
|
27466
27939
|
}
|
|
27467
|
-
|
|
27940
|
+
const committed = await this.commitActiveObservationsToHead({
|
|
27941
|
+
processed,
|
|
27942
|
+
composedFrom: this.composedFrom,
|
|
27943
|
+
target: liveRecord,
|
|
27944
|
+
recompose: (head) => this.compose(this.observerResult, head.activeObservations ?? "", head),
|
|
27945
|
+
cycleMessageIds: messages.map((m) => m.id)
|
|
27946
|
+
});
|
|
27947
|
+
if (!committed) return {
|
|
27948
|
+
status: "not-committed",
|
|
27949
|
+
reason: "the observational memory head kept changing during the commit"
|
|
27950
|
+
};
|
|
27951
|
+
processed = committed.processed;
|
|
27952
|
+
const thread = committed.alreadyCovered ? null : await this.storage.getThreadById({ threadId });
|
|
27468
27953
|
if (thread) {
|
|
27469
27954
|
const oldTitle = thread.title?.trim();
|
|
27470
27955
|
const newTitle = resolveThreadTitleUpdate(thread, processed.threadTitle);
|
|
@@ -27486,23 +27971,20 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27486
27971
|
...shouldUpdateThreadTitle ? { title: newTitle } : {},
|
|
27487
27972
|
metadata: newMetadata
|
|
27488
27973
|
});
|
|
27489
|
-
if (shouldUpdateThreadTitle)
|
|
27974
|
+
if (shouldUpdateThreadTitle) await this.streamMarker(createThreadUpdateMarker({
|
|
27490
27975
|
cycleId: this.cycleId ?? crypto.randomUUID(),
|
|
27491
27976
|
threadId,
|
|
27492
27977
|
oldTitle,
|
|
27493
27978
|
newTitle
|
|
27494
|
-
});
|
|
27979
|
+
}));
|
|
27495
27980
|
}
|
|
27496
|
-
if (
|
|
27497
|
-
|
|
27498
|
-
|
|
27499
|
-
|
|
27500
|
-
|
|
27501
|
-
|
|
27502
|
-
|
|
27503
|
-
});
|
|
27504
|
-
await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, record.id);
|
|
27505
|
-
return true;
|
|
27981
|
+
if (!committed.alreadyCovered) await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, committed.record.id);
|
|
27982
|
+
return {
|
|
27983
|
+
status: "committed",
|
|
27984
|
+
processed,
|
|
27985
|
+
record: committed.record,
|
|
27986
|
+
alreadyCovered: committed.alreadyCovered
|
|
27987
|
+
};
|
|
27506
27988
|
}
|
|
27507
27989
|
async emitEndMarkers(cycleId, processed) {
|
|
27508
27990
|
const actualTokensObserved = await this.tokenCounter.countMessagesAsync(this.opts.messages);
|
|
@@ -27546,6 +28028,8 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27546
28028
|
startedAt;
|
|
27547
28029
|
cycleId;
|
|
27548
28030
|
priorExtractedValues;
|
|
28031
|
+
/** The generation the chunk was written to (the head when the target was retired). */
|
|
28032
|
+
persistedRecordId;
|
|
27549
28033
|
constructor(deps, opts) {
|
|
27550
28034
|
super(deps, opts);
|
|
27551
28035
|
this.cycleId = opts.cycleId;
|
|
@@ -27572,12 +28056,13 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27572
28056
|
};
|
|
27573
28057
|
}
|
|
27574
28058
|
async emitStartMarkers(_cycleId) {}
|
|
27575
|
-
async observe(
|
|
28059
|
+
async observe(observerContext, messages, wasTruncated) {
|
|
27576
28060
|
const thread = await this.storage.getThreadById({ threadId: this.opts.threadId });
|
|
27577
28061
|
const omMeta = thread ? (0, _mastra_core_memory.getThreadOMMetadata)(thread.metadata) : void 0;
|
|
27578
28062
|
this.priorExtractedValues = getPriorExtractedValues(omMeta, this.observationConfig.extractors);
|
|
27579
|
-
const result = await this.deps.observer.call(
|
|
28063
|
+
const result = await this.deps.observer.call(observerContext, messages, void 0, {
|
|
27580
28064
|
skipContinuationHints: true,
|
|
28065
|
+
wasTruncated,
|
|
27581
28066
|
requestContext: this.opts.requestContext,
|
|
27582
28067
|
observabilityContext: this.opts.observabilityContext,
|
|
27583
28068
|
priorExtractedValues: this.priorExtractedValues,
|
|
@@ -27651,71 +28136,124 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27651
28136
|
};
|
|
27652
28137
|
}
|
|
27653
28138
|
async persist(processed) {
|
|
27654
|
-
if (!processed.observations) return
|
|
28139
|
+
if (!processed.observations) return;
|
|
27655
28140
|
const { record, threadId, resourceId, messages } = this.opts;
|
|
27656
|
-
|
|
28141
|
+
const liveRecord = await getLineageHead(this.storage, record);
|
|
28142
|
+
if (!liveRecord) {
|
|
27657
28143
|
omDebug(`[OM:asyncBuffer] skipping persist for thread ${threadId}: observational memory record is gone`);
|
|
27658
|
-
return
|
|
28144
|
+
return {
|
|
28145
|
+
status: "not-committed",
|
|
28146
|
+
reason: "the observational memory record was cleared"
|
|
28147
|
+
};
|
|
27659
28148
|
}
|
|
27660
28149
|
const messageTokens = await this.tokenCounter.countMessagesAsync(messages);
|
|
27661
|
-
|
|
27662
|
-
|
|
27663
|
-
|
|
27664
|
-
|
|
27665
|
-
|
|
27666
|
-
|
|
27667
|
-
|
|
27668
|
-
|
|
27669
|
-
|
|
27670
|
-
|
|
27671
|
-
|
|
27672
|
-
|
|
27673
|
-
|
|
27674
|
-
|
|
27675
|
-
|
|
27676
|
-
|
|
27677
|
-
|
|
28150
|
+
let appendAttempts = 0;
|
|
28151
|
+
let targetId = record.id;
|
|
28152
|
+
const appendResult = await withRetry(() => {
|
|
28153
|
+
appendAttempts++;
|
|
28154
|
+
return this.runCommit(async () => {
|
|
28155
|
+
const head = await getLineageHead(this.storage, record);
|
|
28156
|
+
if (!head) return null;
|
|
28157
|
+
targetId = head.id;
|
|
28158
|
+
const input = {
|
|
28159
|
+
id: targetId,
|
|
28160
|
+
chunk: {
|
|
28161
|
+
cycleId: this.cycleId,
|
|
28162
|
+
observations: processed.observations,
|
|
28163
|
+
tokenCount: processed.observationTokens,
|
|
28164
|
+
messageIds: processed.observedMessageIds,
|
|
28165
|
+
messageTokens,
|
|
28166
|
+
lastObservedAt: processed.lastObservedAt,
|
|
28167
|
+
suggestedContinuation: processed.suggestedContinuation,
|
|
28168
|
+
currentTask: processed.currentTask,
|
|
28169
|
+
threadTitle: processed.threadTitle,
|
|
28170
|
+
extractedValues: processed.extractedValues,
|
|
28171
|
+
extractionFailures: processed.extractionFailures
|
|
28172
|
+
},
|
|
28173
|
+
lastBufferedAtTime: processed.lastObservedAt
|
|
28174
|
+
};
|
|
28175
|
+
return typeof this.storage.appendBufferedObservations === "function" ? this.storage.appendBufferedObservations(input) : this.storage.updateBufferedObservations(input).then(() => ({
|
|
28176
|
+
persisted: true,
|
|
28177
|
+
recordId: input.id
|
|
28178
|
+
}));
|
|
28179
|
+
});
|
|
28180
|
+
}, {
|
|
27678
28181
|
label: "persist-buffered-observations",
|
|
27679
28182
|
abortSignal: this.opts.abortSignal
|
|
27680
28183
|
});
|
|
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
|
-
|
|
28184
|
+
if (appendResult === null) {
|
|
28185
|
+
omDebug(`[OM:asyncBuffer] skipping persist for thread ${threadId}: observational memory record was cleared`);
|
|
28186
|
+
return {
|
|
28187
|
+
status: "not-committed",
|
|
28188
|
+
reason: "the observational memory record was cleared"
|
|
28189
|
+
};
|
|
28190
|
+
}
|
|
28191
|
+
let committedRecord = liveRecord;
|
|
28192
|
+
if (!appendResult.persisted) {
|
|
28193
|
+
const head = appendAttempts > 1 ? await getLineageHead(this.storage, record) : null;
|
|
28194
|
+
if (!(!!head && (getBufferedChunks(head).some((chunk) => chunk.cycleId === this.cycleId) || head.activeObservations.includes(processed.observations)))) return {
|
|
28195
|
+
status: "not-committed",
|
|
28196
|
+
reason: "the buffered chunk was already observed"
|
|
28197
|
+
};
|
|
28198
|
+
this.persistedRecordId = head.id;
|
|
28199
|
+
committedRecord = head;
|
|
28200
|
+
} else this.persistedRecordId = appendResult.recordId;
|
|
28201
|
+
await this.emitBufferingEndMarker(processed);
|
|
28202
|
+
await this.opts.onBufferedChunkPersisted?.();
|
|
28203
|
+
if (committedRecord.id !== this.persistedRecordId) {
|
|
28204
|
+
const landedOn = await getLineageRecord(this.storage, record, this.persistedRecordId);
|
|
28205
|
+
if (!landedOn) return {
|
|
28206
|
+
status: "not-committed",
|
|
28207
|
+
reason: "the observational memory record was cleared"
|
|
28208
|
+
};
|
|
28209
|
+
committedRecord = landedOn;
|
|
28210
|
+
}
|
|
28211
|
+
try {
|
|
28212
|
+
await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, this.persistedRecordId);
|
|
28213
|
+
const candidateTitle = processed.threadTitle?.trim();
|
|
28214
|
+
const hasValidThreadTitle = !!candidateTitle && candidateTitle.length >= 3;
|
|
28215
|
+
if (hasValidThreadTitle || processed.extractedValues) {
|
|
28216
|
+
const thread = await this.storage.getThreadById({ threadId });
|
|
28217
|
+
if (thread) {
|
|
28218
|
+
const oldTitle = thread.title?.trim();
|
|
28219
|
+
const newTitle = resolveThreadTitleUpdate(thread, candidateTitle);
|
|
28220
|
+
const shouldUpdateThreadTitle = newTitle !== void 0;
|
|
28221
|
+
const previousOmMetadata = (0, _mastra_core_memory.getThreadOMMetadata)(thread.metadata);
|
|
28222
|
+
const metadataUpdate = buildThreadMetadataFromExtractedValues(processed.extractors ?? this.observationConfig.extractors, processed.extractedValues);
|
|
28223
|
+
const newMetadata = (0, _mastra_core_memory.setThreadOMMetadata)(thread.metadata, {
|
|
28224
|
+
...hasValidThreadTitle || metadataUpdate.threadTitle ? { threadTitle: metadataUpdate.threadTitle ?? processed.threadTitle } : {},
|
|
28225
|
+
extracted: {
|
|
28226
|
+
...previousOmMetadata?.extracted ?? {},
|
|
28227
|
+
...metadataUpdate.extracted ?? {}
|
|
28228
|
+
}
|
|
27710
28229
|
});
|
|
27711
|
-
await this.
|
|
28230
|
+
await this.storage.patchThread({
|
|
28231
|
+
id: threadId,
|
|
28232
|
+
...shouldUpdateThreadTitle ? { title: newTitle } : {},
|
|
28233
|
+
metadata: newMetadata
|
|
28234
|
+
});
|
|
28235
|
+
if (shouldUpdateThreadTitle) {
|
|
28236
|
+
const marker = createThreadUpdateMarker({
|
|
28237
|
+
cycleId: this.cycleId,
|
|
28238
|
+
threadId,
|
|
28239
|
+
oldTitle,
|
|
28240
|
+
newTitle
|
|
28241
|
+
});
|
|
28242
|
+
await this.streamMarker(marker);
|
|
28243
|
+
}
|
|
27712
28244
|
}
|
|
27713
28245
|
}
|
|
28246
|
+
} catch (error) {
|
|
28247
|
+
omError("[OM] Post-buffer indexing or thread update failed; the buffered chunk is kept", error);
|
|
27714
28248
|
}
|
|
27715
|
-
return
|
|
28249
|
+
return {
|
|
28250
|
+
status: "committed",
|
|
28251
|
+
processed,
|
|
28252
|
+
record: committedRecord
|
|
28253
|
+
};
|
|
27716
28254
|
}
|
|
27717
|
-
async emitEndMarkers(
|
|
27718
|
-
|
|
28255
|
+
async emitEndMarkers() {}
|
|
28256
|
+
async emitBufferingEndMarker(processed) {
|
|
27719
28257
|
const { record, threadId, messages } = this.opts;
|
|
27720
28258
|
const tokensBuffered = await this.tokenCounter.countMessagesAsync(messages);
|
|
27721
28259
|
const totalBufferedTokens = getBufferedChunks(await this.storage.getObservationalMemory(record.threadId, record.resourceId)).reduce((sum, c) => sum + (c.tokenCount ?? 0), 0) || processed.observationTokens;
|
|
@@ -27725,7 +28263,7 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27725
28263
|
startedAt: this.startedAt,
|
|
27726
28264
|
tokensBuffered,
|
|
27727
28265
|
bufferedTokens: totalBufferedTokens,
|
|
27728
|
-
recordId: record.id,
|
|
28266
|
+
recordId: this.persistedRecordId ?? record.id,
|
|
27729
28267
|
threadId,
|
|
27730
28268
|
observations: processed.observations,
|
|
27731
28269
|
extractedValues: processed.extractedValues,
|
|
@@ -27735,7 +28273,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27735
28273
|
...endMarker,
|
|
27736
28274
|
transient: true
|
|
27737
28275
|
}).catch(() => {});
|
|
27738
|
-
await this.persistMarkerToStorage(endMarker, threadId, record.resourceId ?? void 0
|
|
28276
|
+
await this.persistMarkerToStorage(endMarker, threadId, record.resourceId ?? void 0, {
|
|
28277
|
+
notAfter: new Date(this.startedAt),
|
|
28278
|
+
onCycleStartMessage: true
|
|
28279
|
+
});
|
|
27739
28280
|
}
|
|
27740
28281
|
async emitFailedMarkers(_cycleId, error) {
|
|
27741
28282
|
const { record, threadId, messages } = this.opts;
|
|
@@ -27754,7 +28295,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27754
28295
|
...failedMarker,
|
|
27755
28296
|
transient: true
|
|
27756
28297
|
}).catch(() => {});
|
|
27757
|
-
await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0
|
|
28298
|
+
await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0, {
|
|
28299
|
+
notAfter: new Date(this.startedAt),
|
|
28300
|
+
onCycleStartMessage: true
|
|
28301
|
+
});
|
|
27758
28302
|
}
|
|
27759
28303
|
};
|
|
27760
28304
|
//#endregion
|
|
@@ -27777,10 +28321,17 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27777
28321
|
lastBatchProviderMetadata;
|
|
27778
28322
|
observationResults = [];
|
|
27779
28323
|
priorMetadataByThread = /* @__PURE__ */ new Map();
|
|
28324
|
+
/** Each observed thread's wrapped section from the last composition. */
|
|
28325
|
+
threadSections = /* @__PURE__ */ new Map();
|
|
28326
|
+
/** Head text the processed observations were composed from (the commit's expected text). */
|
|
28327
|
+
composedFrom = "";
|
|
27780
28328
|
constructor(deps, opts) {
|
|
27781
28329
|
super(deps, opts);
|
|
27782
28330
|
this.resourceId = opts.resourceId;
|
|
27783
28331
|
}
|
|
28332
|
+
getObservedMessagesForThread(threadId) {
|
|
28333
|
+
return this.threadsWithMessages.get(threadId);
|
|
28334
|
+
}
|
|
27784
28335
|
get needsLock() {
|
|
27785
28336
|
return true;
|
|
27786
28337
|
}
|
|
@@ -27867,10 +28418,12 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27867
28418
|
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
27868
28419
|
}))
|
|
27869
28420
|
});
|
|
27870
|
-
const
|
|
28421
|
+
const freshRecord = await this.storage.getObservationalMemory(null, this.resourceId);
|
|
28422
|
+
const existingObservations = freshRecord?.activeObservations ?? record.activeObservations ?? "";
|
|
27871
28423
|
return {
|
|
27872
28424
|
messages: Array.from(this.threadsWithMessages.values()).flat(),
|
|
27873
|
-
existingObservations
|
|
28425
|
+
existingObservations,
|
|
28426
|
+
contextRecord: freshRecord
|
|
27874
28427
|
};
|
|
27875
28428
|
}
|
|
27876
28429
|
async emitStartMarkers(cycleId) {
|
|
@@ -27894,7 +28447,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27894
28447
|
}
|
|
27895
28448
|
}
|
|
27896
28449
|
}
|
|
27897
|
-
async observe(
|
|
28450
|
+
async observe(observerContext, _messages, wasTruncated) {
|
|
27898
28451
|
const maxTokensPerBatch = this.observationConfig.maxTokensPerBatch ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.maxTokensPerBatch;
|
|
27899
28452
|
const orderedThreadIds = this.threadOrder.filter((tid) => this.threadsWithMessages.has(tid));
|
|
27900
28453
|
const batches = [];
|
|
@@ -27920,10 +28473,10 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27920
28473
|
}
|
|
27921
28474
|
if (currentBatch.threadIds.length > 0) batches.push(currentBatch);
|
|
27922
28475
|
const batchResults = await Promise.all(batches.map(async (batch) => {
|
|
27923
|
-
return this.deps.observer.callMultiThread(
|
|
28476
|
+
return this.deps.observer.callMultiThread(observerContext, batch.threadMap, batch.threadIds, this.opts.abortSignal, this.opts.requestContext, this.priorMetadataByThread, this.opts.observabilityContext, void 0, {
|
|
27924
28477
|
resourceId: this.opts.resourceId,
|
|
27925
28478
|
trigger: this.opts.trigger
|
|
27926
|
-
}, this.opts.record.observedTimezone, this.opts.agent, this.opts.currentModel);
|
|
28479
|
+
}, this.opts.record.observedTimezone, this.opts.agent, this.opts.currentModel, wasTruncated);
|
|
27927
28480
|
}));
|
|
27928
28481
|
for (const batchResult of batchResults) {
|
|
27929
28482
|
for (const [threadId, result] of batchResult.results) this.multiThreadResults.set(threadId, result);
|
|
@@ -27990,14 +28543,38 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27990
28543
|
}
|
|
27991
28544
|
});
|
|
27992
28545
|
}
|
|
28546
|
+
this.composedFrom = existingObservations;
|
|
28547
|
+
const processed = await this.compose(existingObservations, record);
|
|
28548
|
+
for (const [index, { threadId, threadMessages, result }] of this.observationResults.entries()) this.deps.emitDebugEvent({
|
|
28549
|
+
type: "observation_complete",
|
|
28550
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
28551
|
+
threadId,
|
|
28552
|
+
resourceId: this.resourceId,
|
|
28553
|
+
observations: this.threadSections.get(threadId) ?? "",
|
|
28554
|
+
rawObserverOutput: result.observations,
|
|
28555
|
+
previousObservations: record.activeObservations,
|
|
28556
|
+
messages: threadMessages.map((m) => ({
|
|
28557
|
+
role: m.role,
|
|
28558
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
28559
|
+
})),
|
|
28560
|
+
usage: index === 0 && this.totalBatchUsage.totalTokens > 0 ? this.totalBatchUsage : void 0
|
|
28561
|
+
});
|
|
28562
|
+
return processed;
|
|
28563
|
+
}
|
|
28564
|
+
/**
|
|
28565
|
+
* Merge every observed thread's section into `existingObservations` (the head text). Resource
|
|
28566
|
+
* scope rewrites an existing same-day `<thread>` section in place, so the result is not an
|
|
28567
|
+
* append of the head text.
|
|
28568
|
+
*/
|
|
28569
|
+
async compose(existingObservations, baseRecord) {
|
|
27993
28570
|
let currentObservations = existingObservations;
|
|
27994
28571
|
let cycleObservationTokens = 0;
|
|
27995
28572
|
const threadMetadataUpdates = [];
|
|
27996
|
-
for (const
|
|
27997
|
-
const { threadId, threadMessages, result } = obsResult;
|
|
28573
|
+
for (const { threadId, threadMessages, result } of this.observationResults) {
|
|
27998
28574
|
cycleObservationTokens += this.tokenCounter.countObservations(result.observations);
|
|
27999
28575
|
const messageRange = this.retrieval ? buildMessageRange(threadMessages) : void 0;
|
|
28000
28576
|
const threadSection = await this.wrapWithThreadTag(threadId, result.observations, messageRange);
|
|
28577
|
+
this.threadSections.set(threadId, threadSection);
|
|
28001
28578
|
const threadLastObservedAt = this.getMaxMessageTimestamp(threadMessages);
|
|
28002
28579
|
currentObservations = this.replaceOrAppendThreadSection(currentObservations, threadId, threadSection, threadLastObservedAt);
|
|
28003
28580
|
threadMetadataUpdates.push({
|
|
@@ -28011,26 +28588,11 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
28011
28588
|
extractors: result.extractors,
|
|
28012
28589
|
lastObservedMessageCursor: getLastObservedMessageCursor(threadMessages)
|
|
28013
28590
|
});
|
|
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
28591
|
}
|
|
28030
28592
|
const observedMessages = this.observationResults.flatMap((r) => r.threadMessages);
|
|
28031
28593
|
const lastObservedAt = this.getMaxMessageTimestamp(observedMessages);
|
|
28032
28594
|
const newMessageIds = observedMessages.map((m) => m.id);
|
|
28033
|
-
const existingIds =
|
|
28595
|
+
const existingIds = baseRecord.observedMessageIds ?? [];
|
|
28034
28596
|
const observedMessageIds = [.../* @__PURE__ */ new Set([...existingIds, ...newMessageIds])];
|
|
28035
28597
|
const observationTokens = this.tokenCounter.countObservations(currentObservations);
|
|
28036
28598
|
return {
|
|
@@ -28044,8 +28606,25 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
28044
28606
|
}
|
|
28045
28607
|
async persist(processed) {
|
|
28046
28608
|
const { record, resourceId } = this.opts;
|
|
28609
|
+
const head = await getLineageHead(this.storage, record);
|
|
28610
|
+
if (!head) return {
|
|
28611
|
+
status: "not-committed",
|
|
28612
|
+
reason: "the observational memory record was cleared"
|
|
28613
|
+
};
|
|
28614
|
+
const committed = await this.commitActiveObservationsToHead({
|
|
28615
|
+
processed,
|
|
28616
|
+
composedFrom: this.composedFrom,
|
|
28617
|
+
target: head,
|
|
28618
|
+
recompose: (fresh) => this.compose(fresh.activeObservations ?? "", fresh),
|
|
28619
|
+
cycleMessageIds: this.observationResults.flatMap((r) => r.threadMessages.map((m) => m.id))
|
|
28620
|
+
});
|
|
28621
|
+
if (!committed) return {
|
|
28622
|
+
status: "not-committed",
|
|
28623
|
+
reason: "the observational memory head kept changing during the commit"
|
|
28624
|
+
};
|
|
28625
|
+
processed = committed.processed;
|
|
28047
28626
|
const threadUpdateMarkers = [];
|
|
28048
|
-
if (processed.threadMetadataUpdates) for (const update of processed.threadMetadataUpdates) {
|
|
28627
|
+
if (processed.threadMetadataUpdates && !committed.alreadyCovered) for (const update of processed.threadMetadataUpdates) {
|
|
28049
28628
|
const thread = await this.storage.getThreadById({ threadId: update.threadId });
|
|
28050
28629
|
if (thread) {
|
|
28051
28630
|
const oldTitle = thread.title?.trim();
|
|
@@ -28078,15 +28657,13 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
28078
28657
|
}
|
|
28079
28658
|
}
|
|
28080
28659
|
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;
|
|
28660
|
+
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)));
|
|
28661
|
+
return {
|
|
28662
|
+
status: "committed",
|
|
28663
|
+
processed,
|
|
28664
|
+
record: committed.record,
|
|
28665
|
+
alreadyCovered: committed.alreadyCovered
|
|
28666
|
+
};
|
|
28090
28667
|
}
|
|
28091
28668
|
async emitEndMarkers(cycleId, processed) {
|
|
28092
28669
|
for (const obsResult of this.observationResults) {
|
|
@@ -28145,7 +28722,8 @@ ObservationStrategy.create = ((om, opts) => {
|
|
|
28145
28722
|
observedMessageIds: om.observedMessageIds,
|
|
28146
28723
|
obscureThreadIds: om.getObscureThreadIds(),
|
|
28147
28724
|
onIndexObservations: om.onIndexObservations,
|
|
28148
|
-
emitDebugEvent: (e) => om.emitDebugEvent(e)
|
|
28725
|
+
emitDebugEvent: (e) => om.emitDebugEvent(e),
|
|
28726
|
+
prepareObserverContext: (observations, record) => om.prepareObserverContext(observations, record)
|
|
28149
28727
|
};
|
|
28150
28728
|
if (opts.cycleId) return new AsyncBufferObservationStrategy(deps, opts);
|
|
28151
28729
|
if (deps.scope === "resource" && opts.resourceId) return new ResourceScopedObservationStrategy(deps, opts);
|
|
@@ -28277,8 +28855,8 @@ var ObservationStep = class {
|
|
|
28277
28855
|
let reflected = false;
|
|
28278
28856
|
let didThresholdCleanup = false;
|
|
28279
28857
|
let observerExchange;
|
|
28858
|
+
const step0Messages = this.stepNumber === 0 ? getObservableMessages(messageList) : [];
|
|
28280
28859
|
if (this.stepNumber === 0) {
|
|
28281
|
-
const step0Messages = getObservableMessages(messageList);
|
|
28282
28860
|
const activation = await om.activate({
|
|
28283
28861
|
threadId,
|
|
28284
28862
|
resourceId,
|
|
@@ -28335,7 +28913,17 @@ var ObservationStep = class {
|
|
|
28335
28913
|
record: this.turn.record,
|
|
28336
28914
|
messages: getObservableMessages(messageList)
|
|
28337
28915
|
});
|
|
28338
|
-
if (statusSnapshot.
|
|
28916
|
+
if (statusSnapshot.inAsyncObservationBand && !statusSnapshot.canActivate && !hasIncompleteToolCalls) {
|
|
28917
|
+
await this.turn.refreshRecord();
|
|
28918
|
+
statusSnapshot = await om.getStatus({
|
|
28919
|
+
threadId,
|
|
28920
|
+
resourceId,
|
|
28921
|
+
record: this.turn.record,
|
|
28922
|
+
messages: getObservableMessages(messageList)
|
|
28923
|
+
});
|
|
28924
|
+
}
|
|
28925
|
+
const activateBeforeBuffering = statusSnapshot.inAsyncObservationBand && statusSnapshot.canActivate && !hasIncompleteToolCalls;
|
|
28926
|
+
if (statusSnapshot.shouldBuffer && !activateBeforeBuffering) {
|
|
28339
28927
|
const allMessages = getObservableMessages(messageList);
|
|
28340
28928
|
const unobservedMessages = om.getUnobservedMessages(allMessages, statusSnapshot.record);
|
|
28341
28929
|
const candidates = om.getUnobservedMessages(unobservedMessages, statusSnapshot.record, { excludeBuffered: true });
|
|
@@ -28349,8 +28937,10 @@ var ObservationStep = class {
|
|
|
28349
28937
|
omDebug(`[OM:buffer] onBufferChunkSealed hook failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
28350
28938
|
}
|
|
28351
28939
|
if (this.turn.memory) await this.turn.memory.persistMessages(safeCandidates);
|
|
28352
|
-
messageList.
|
|
28353
|
-
|
|
28940
|
+
const inputIds = new Set(messageList.get.input.db().map((msg) => msg.id));
|
|
28941
|
+
const sealedOutput = safeCandidates.filter((msg) => !inputIds.has(msg.id));
|
|
28942
|
+
messageList.removeByIds(sealedOutput.map((msg) => msg.id));
|
|
28943
|
+
for (const msg of sealedOutput) messageList.add(msg, "memory");
|
|
28354
28944
|
}
|
|
28355
28945
|
if (!deferred) {
|
|
28356
28946
|
om.trackBackgroundWork(om.buffer({
|
|
@@ -28372,7 +28962,8 @@ var ObservationStep = class {
|
|
|
28372
28962
|
buffered = true;
|
|
28373
28963
|
}
|
|
28374
28964
|
}
|
|
28375
|
-
const
|
|
28965
|
+
const bandDefersObservation = statusSnapshot.inAsyncObservationBand && !statusSnapshot.canActivate;
|
|
28966
|
+
const willObserveNow = statusSnapshot.shouldObserve && !hasIncompleteToolCalls && !bandDefersObservation;
|
|
28376
28967
|
/** In-flight message ids the step-0 cleanup must never remove from live context. */
|
|
28377
28968
|
let step0PreserveIds;
|
|
28378
28969
|
if (this.stepNumber > 0 || willObserveNow) {
|
|
@@ -28414,7 +29005,7 @@ var ObservationStep = class {
|
|
|
28414
29005
|
}
|
|
28415
29006
|
if (willObserveNow) {
|
|
28416
29007
|
const preObsGeneration = this.turn.record.generationCount;
|
|
28417
|
-
const obsResult = await this.runThresholdObservation();
|
|
29008
|
+
const obsResult = await this.runThresholdObservation(statusSnapshot.inAsyncObservationBand);
|
|
28418
29009
|
observerExchange = obsResult.observerExchange;
|
|
28419
29010
|
if (obsResult.succeeded) {
|
|
28420
29011
|
observed = true;
|
|
@@ -28487,12 +29078,12 @@ var ObservationStep = class {
|
|
|
28487
29078
|
/**
|
|
28488
29079
|
* Run the full threshold observation pipeline:
|
|
28489
29080
|
* waitForBuffering → re-check → activate buffered chunks → reflect → observe
|
|
28490
|
-
* (sync fallback
|
|
29081
|
+
* (sync fallback at blockAfter when async buffering is enabled)
|
|
28491
29082
|
*/
|
|
28492
|
-
async runThresholdObservation() {
|
|
29083
|
+
async runThresholdObservation(inAsyncObservationBand) {
|
|
28493
29084
|
const { threadId, resourceId, messageList } = this.turn;
|
|
28494
29085
|
const om = this.turn.om;
|
|
28495
|
-
await om.waitForBuffering(threadId, resourceId);
|
|
29086
|
+
if (!inAsyncObservationBand) await om.waitForBuffering(threadId, resourceId);
|
|
28496
29087
|
await this.turn.refreshRecord();
|
|
28497
29088
|
const observableMessages = this.seededResponseMessage ? getObservableMessages(messageList).filter((msg) => msg.id !== this.turn.responseMessageId) : getObservableMessages(messageList);
|
|
28498
29089
|
let status = await om.getStatus({
|
|
@@ -28521,7 +29112,16 @@ var ObservationStep = class {
|
|
|
28521
29112
|
messageList
|
|
28522
29113
|
});
|
|
28523
29114
|
this.turn.setRecord(activation.record);
|
|
28524
|
-
if (!activation.activated)
|
|
29115
|
+
if (!activation.activated) {
|
|
29116
|
+
await this.turn.refreshRecord();
|
|
29117
|
+
status = await om.getStatus({
|
|
29118
|
+
threadId,
|
|
29119
|
+
resourceId,
|
|
29120
|
+
record: this.turn.record,
|
|
29121
|
+
messages: pendingMessages
|
|
29122
|
+
});
|
|
29123
|
+
break;
|
|
29124
|
+
}
|
|
28525
29125
|
activated = true;
|
|
28526
29126
|
const ids = new Set(activation.activatedMessageIds ?? []);
|
|
28527
29127
|
activatedMessageIds.push(...ids);
|
|
@@ -28533,11 +29133,17 @@ var ObservationStep = class {
|
|
|
28533
29133
|
messages: pendingMessages
|
|
28534
29134
|
});
|
|
28535
29135
|
}
|
|
29136
|
+
if (status.shouldObserve && status.canActivate) return {
|
|
29137
|
+
succeeded: activated,
|
|
29138
|
+
record: this.turn.record,
|
|
29139
|
+
activatedMessageIds: activated ? activatedMessageIds : void 0,
|
|
29140
|
+
cleanupMessageIds: activatedMessageIds
|
|
29141
|
+
};
|
|
28536
29142
|
if (activated) {
|
|
28537
|
-
const
|
|
29143
|
+
const preReflectionRecord = this.turn.record;
|
|
28538
29144
|
await om.reflector.maybeReflect({
|
|
28539
|
-
record:
|
|
28540
|
-
observationTokens:
|
|
29145
|
+
record: preReflectionRecord,
|
|
29146
|
+
observationTokens: preReflectionRecord.observationTokenCount ?? 0,
|
|
28541
29147
|
threadId,
|
|
28542
29148
|
writer: this.turn.writer,
|
|
28543
29149
|
messageList,
|
|
@@ -28552,6 +29158,14 @@ var ObservationStep = class {
|
|
|
28552
29158
|
}),
|
|
28553
29159
|
trigger: "turn-sync"
|
|
28554
29160
|
});
|
|
29161
|
+
await this.turn.refreshRecord();
|
|
29162
|
+
const postActivationRecord = this.turn.record;
|
|
29163
|
+
status = await om.getStatus({
|
|
29164
|
+
threadId,
|
|
29165
|
+
resourceId,
|
|
29166
|
+
record: postActivationRecord,
|
|
29167
|
+
messages: pendingMessages
|
|
29168
|
+
});
|
|
28555
29169
|
if (!status.shouldObserve) return {
|
|
28556
29170
|
succeeded: true,
|
|
28557
29171
|
record: postActivationRecord,
|
|
@@ -28559,6 +29173,12 @@ var ObservationStep = class {
|
|
|
28559
29173
|
cleanupMessageIds: activatedMessageIds
|
|
28560
29174
|
};
|
|
28561
29175
|
}
|
|
29176
|
+
if (status.inAsyncObservationBand || status.canActivate) return {
|
|
29177
|
+
succeeded: activated,
|
|
29178
|
+
record: this.turn.record,
|
|
29179
|
+
activatedMessageIds: activated ? activatedMessageIds : void 0,
|
|
29180
|
+
cleanupMessageIds: activatedMessageIds
|
|
29181
|
+
};
|
|
28562
29182
|
let obsResult;
|
|
28563
29183
|
try {
|
|
28564
29184
|
obsResult = await om.observe({
|
|
@@ -28910,13 +29530,18 @@ Take the existing observations and rewrite them to make it easier to continue in
|
|
|
28910
29530
|
|
|
28911
29531
|
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
29532
|
|
|
29533
|
+
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.
|
|
29534
|
+
|
|
28913
29535
|
When consolidating observations:
|
|
28914
|
-
-
|
|
28915
|
-
-
|
|
29536
|
+
- Keep every concrete fact: names, places, dates, numbers, amounts, durations, titles, versions, and who said what. Merge duplicates, but never drop specifics
|
|
29537
|
+
- Keep every dated statement with its date, and retain the timestamps that matter (start times, completion times, significant events)
|
|
29538
|
+
- 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
|
|
29539
|
+
- 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
|
|
29540
|
+
- Prefer many short factual lines over prose summaries. Do not replace facts with generalities like "discussed travel plans"
|
|
28916
29541
|
- Combine related items where it makes sense (e.g., "agent called view tool 5 times on file x")
|
|
28917
29542
|
- Preserve ✅ completion markers — they are memory signals that tell the assistant what is already resolved and help prevent repeated work
|
|
28918
29543
|
- Preserve the concrete resolved outcome captured by ✅ markers so the assistant knows what exactly is done
|
|
28919
|
-
- Condense older observations more
|
|
29544
|
+
- Condense the wording of older observations more than recent ones, but keep their facts
|
|
28920
29545
|
|
|
28921
29546
|
CRITICAL: USER ASSERTIONS vs QUESTIONS
|
|
28922
29547
|
- "User stated: X" = authoritative assertion (user told us something about themselves)
|
|
@@ -29198,6 +29823,25 @@ async function withAbortCheck(fn, abortSignal) {
|
|
|
29198
29823
|
* size while still letting them fire sooner than a threshold activation.
|
|
29199
29824
|
*/
|
|
29200
29825
|
const EARLY_ACTIVATION_SIZE_FLOOR_RATIO = .75;
|
|
29826
|
+
/** A reflection commit that created nothing because the reflected record is no longer the head. */
|
|
29827
|
+
var ReflectionNotAppliedError = class extends Error {
|
|
29828
|
+
constructor(recordId, headId) {
|
|
29829
|
+
super(`Reflection not applied: record ${recordId} is no longer the current generation (head is ${headId})`);
|
|
29830
|
+
this.name = "ReflectionNotAppliedError";
|
|
29831
|
+
}
|
|
29832
|
+
};
|
|
29833
|
+
/**
|
|
29834
|
+
* Whether a reflection commit created the generation the caller asked for. Storage creates
|
|
29835
|
+
* nothing (and returns the current head or the stored record) when the reflected record was
|
|
29836
|
+
* already retired or its observations were rewritten since the reflection read them.
|
|
29837
|
+
*
|
|
29838
|
+
* Adapters that predate `newRecordId` ignore it and do not report `supersededBy`; for those a
|
|
29839
|
+
* returned record other than the reflected one means a new generation was created.
|
|
29840
|
+
*/
|
|
29841
|
+
function isReflectionApplied(returned, newRecordId, reflectedRecordId) {
|
|
29842
|
+
if (returned.id === newRecordId) return true;
|
|
29843
|
+
return returned.supersededBy === void 0 && returned.id !== reflectedRecordId;
|
|
29844
|
+
}
|
|
29201
29845
|
/**
|
|
29202
29846
|
* Runs the Reflector agent for compressing observations.
|
|
29203
29847
|
* Handles synchronous reflection, async buffered reflection, and activation.
|
|
@@ -29561,14 +30205,14 @@ var ReflectorRunner = class {
|
|
|
29561
30205
|
* Perform async buffered reflection — reflects observations and stores to bufferedReflection.
|
|
29562
30206
|
* Does NOT create a new generation or update activeObservations.
|
|
29563
30207
|
*/
|
|
29564
|
-
async doAsyncBufferedReflection(record,
|
|
30208
|
+
async doAsyncBufferedReflection(record, bufferKey, writer, requestContext, observabilityContext, priorExtractedValues, mainAgent, sendSignal, trigger, currentModel) {
|
|
29565
30209
|
const currentRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record;
|
|
29566
30210
|
const observationTokens = currentRecord.observationTokenCount ?? 0;
|
|
29567
30211
|
const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(currentRecord));
|
|
29568
30212
|
const bufferActivation = this.reflectionConfig.bufferActivation ?? .5;
|
|
29569
30213
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
29570
30214
|
const cycleId = `reflect-buf-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
29571
|
-
BufferingCoordinator.reflectionBufferCycleIds.set(
|
|
30215
|
+
BufferingCoordinator.reflectionBufferCycleIds.set(bufferKey, cycleId);
|
|
29572
30216
|
const allLines = (currentRecord.activeObservations ?? "").split("\n");
|
|
29573
30217
|
const totalLines = allLines.length;
|
|
29574
30218
|
const avgTokensPerLine = totalLines > 0 ? observationTokens / totalLines : 0;
|
|
@@ -29607,13 +30251,24 @@ var ReflectorRunner = class {
|
|
|
29607
30251
|
await persistThreadExtractedValues(this.storage, this.reflectionConfig.extractors, currentRecord.threadId ?? void 0, reflectResult.extractedValues);
|
|
29608
30252
|
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
29609
30253
|
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
|
-
|
|
30254
|
+
if (!await runOMCommit(this.buffering.getLockKey(currentRecord.threadId, currentRecord.resourceId), async () => {
|
|
30255
|
+
if ((await this.storage.getObservationalMemory(currentRecord.threadId, currentRecord.resourceId))?.id !== currentRecord.id) return false;
|
|
30256
|
+
await this.storage.updateBufferedReflection({
|
|
30257
|
+
id: currentRecord.id,
|
|
30258
|
+
reflection: reflectResult.observations,
|
|
30259
|
+
tokenCount: reflectionTokenCount,
|
|
30260
|
+
inputTokenCount: sliceTokenEstimate,
|
|
30261
|
+
reflectedObservationLineCount
|
|
30262
|
+
});
|
|
30263
|
+
return true;
|
|
30264
|
+
})) {
|
|
30265
|
+
omDebug(`[OM:reflect] doAsyncBufferedReflection: record ${currentRecord.id} is no longer the head; discarding the buffered reflection`);
|
|
30266
|
+
BufferingCoordinator.lastBufferedBoundary.delete(bufferKey);
|
|
30267
|
+
return {
|
|
30268
|
+
usage: reflectResult.usage,
|
|
30269
|
+
providerMetadata: reflectResult.providerMetadata
|
|
30270
|
+
};
|
|
30271
|
+
}
|
|
29617
30272
|
omDebug(`[OM:reflect] doAsyncBufferedReflection: bufferedReflection saved with lineCount=${reflectedObservationLineCount}`);
|
|
29618
30273
|
if (writer) {
|
|
29619
30274
|
const endMarker = createBufferingEndMarker({
|
|
@@ -29692,10 +30347,16 @@ var ReflectorRunner = class {
|
|
|
29692
30347
|
}
|
|
29693
30348
|
}
|
|
29694
30349
|
omDebug(`[OM:reflect] tryActivateBufferedReflection: activating, beforeTokens=${beforeTokens}, combinedTokenCount=${combinedTokenCount}, reflectedLineCount=${reflectedLineCount}, unreflectedLines=${unreflectedLines.length}`);
|
|
29695
|
-
|
|
30350
|
+
const newRecordId = crypto.randomUUID();
|
|
30351
|
+
const swapped = await runOMCommit(lockKey, () => this.storage.swapBufferedReflectionToActive({
|
|
29696
30352
|
currentRecord: freshRecord,
|
|
29697
|
-
tokenCount: combinedTokenCount
|
|
29698
|
-
|
|
30353
|
+
tokenCount: combinedTokenCount,
|
|
30354
|
+
newRecordId
|
|
30355
|
+
}), { priority: "reflection" });
|
|
30356
|
+
if (!isReflectionApplied(swapped, newRecordId, freshRecord.id)) {
|
|
30357
|
+
omDebug(`[OM:reflect] tryActivateBufferedReflection: buffered reflection on ${freshRecord.id} not applied (head is ${swapped.id})`);
|
|
30358
|
+
return { status: "not-applied" };
|
|
30359
|
+
}
|
|
29699
30360
|
if (committedContext) await this.notifyReflectionCommitted({
|
|
29700
30361
|
...committedContext,
|
|
29701
30362
|
observations: allLines.slice(0, reflectedLineCount).join("\n").trim(),
|
|
@@ -29750,7 +30411,8 @@ var ReflectorRunner = class {
|
|
|
29750
30411
|
* @internal Used by observation strategies. Do not call directly.
|
|
29751
30412
|
*/
|
|
29752
30413
|
async maybeReflect(opts) {
|
|
29753
|
-
const { record, observationTokens, writer, abortSignal, mainAgent, sendSignal, sendStateSignal, messageList, currentModel, reflectionHooks, trigger, requestContext, observabilityContext, lastActivityAt, threadId: requestedThreadId } = opts;
|
|
30414
|
+
const { record: callerRecord, observationTokens, writer, abortSignal, mainAgent, sendSignal, sendStateSignal, messageList, currentModel, reflectionHooks, trigger, requestContext, observabilityContext, lastActivityAt, threadId: requestedThreadId } = opts;
|
|
30415
|
+
const record = { ...callerRecord };
|
|
29754
30416
|
const lockKey = this.buffering.getLockKey(record.threadId, record.resourceId);
|
|
29755
30417
|
const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(record));
|
|
29756
30418
|
const priorExtractedValues = await getThreadExtractedValues(this.storage, this.reflectionConfig.extractors, requestedThreadId ?? record.threadId);
|
|
@@ -29804,7 +30466,7 @@ var ReflectorRunner = class {
|
|
|
29804
30466
|
abortSignal,
|
|
29805
30467
|
observabilityContext
|
|
29806
30468
|
});
|
|
29807
|
-
if (activationResult.status === "activated") return;
|
|
30469
|
+
if (activationResult.status === "activated" || activationResult.status === "not-applied") return;
|
|
29808
30470
|
if (activationResult.status === "suppressed") {
|
|
29809
30471
|
omDebug(`[OM:reflect] skipping sync fallback / re-buffer after suppressed early ${activationMetadata.triggeredBy} activation (reason=${activationResult.reason})`);
|
|
29810
30472
|
return;
|
|
@@ -29887,13 +30549,19 @@ var ReflectorRunner = class {
|
|
|
29887
30549
|
reflectResult.observations = await applyTextTransform(this.hooks, "afterReflection", reflectResult.observations, hookContext);
|
|
29888
30550
|
reflectionUsage = reflectResult.usage;
|
|
29889
30551
|
reflectionProviderMetadata = reflectResult.providerMetadata;
|
|
29890
|
-
await persistThreadExtractedValues(this.storage, this.reflectionConfig.extractors, record.threadId ?? void 0, reflectResult.extractedValues);
|
|
29891
30552
|
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
29892
|
-
|
|
30553
|
+
const newRecordId = crypto.randomUUID();
|
|
30554
|
+
const committedRecord = await runOMCommit(this.buffering.getLockKey(record.threadId, record.resourceId), () => this.storage.createReflectionGeneration({
|
|
29893
30555
|
currentRecord: record,
|
|
29894
30556
|
reflection: reflectResult.observations,
|
|
29895
|
-
tokenCount: reflectionTokenCount
|
|
29896
|
-
|
|
30557
|
+
tokenCount: reflectionTokenCount,
|
|
30558
|
+
newRecordId
|
|
30559
|
+
}), { priority: "reflection" });
|
|
30560
|
+
if (!isReflectionApplied(committedRecord, newRecordId, record.id)) {
|
|
30561
|
+
omDebug(`[OM:reflect] reflection of ${record.id} not applied (head is ${committedRecord.id})`);
|
|
30562
|
+
throw new ReflectionNotAppliedError(record.id, committedRecord.id);
|
|
30563
|
+
}
|
|
30564
|
+
await persistThreadExtractedValues(this.storage, this.reflectionConfig.extractors, record.threadId ?? void 0, reflectResult.extractedValues);
|
|
29897
30565
|
if (reflectionTokenCount >= reflectThreshold) this.syncReflectionSuppression.set(lockKey, reflectionTokenCount);
|
|
29898
30566
|
else this.syncReflectionSuppression.delete(lockKey);
|
|
29899
30567
|
await this.notifyReflectionCommitted({
|
|
@@ -29973,6 +30641,7 @@ var ReflectorRunner = class {
|
|
|
29973
30641
|
failureKind: failedMarker.data.failureKind,
|
|
29974
30642
|
error: reflectionError.message
|
|
29975
30643
|
});
|
|
30644
|
+
if (error instanceof ReflectionNotAppliedError) return;
|
|
29976
30645
|
if (lifecycleError !== void 0 || abortSignal?.aborted || this.reflectionConfig.failurePolicy !== "continue" || !isOmModelExecutionError(error) || error.failureKind !== "reflector-model") throw error;
|
|
29977
30646
|
this.syncReflectionSuppression.set(lockKey, observationTokens);
|
|
29978
30647
|
omError("[OM] Reflection failed", error);
|
|
@@ -30316,6 +30985,8 @@ function writeProcessInputStepReproCapture(params) {
|
|
|
30316
30985
|
* Returns the parts from the latest step of a message (after the last step-start marker).
|
|
30317
30986
|
* If no step-start marker exists, returns all parts.
|
|
30318
30987
|
*/
|
|
30988
|
+
/** Re-reads of the head after an activation swap hit a generation retired by a reflection. */
|
|
30989
|
+
const MAX_ACTIVATION_RETIRED_RETRIES = 3;
|
|
30319
30990
|
function getLatestStepParts(parts) {
|
|
30320
30991
|
for (let i = parts.length - 1; i >= 0; i--) if (parts[i]?.type === "step-start") return parts.slice(i + 1);
|
|
30321
30992
|
return parts;
|
|
@@ -30509,6 +31180,8 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
30509
31180
|
reflector;
|
|
30510
31181
|
/** Buffering state coordinator — manages static maps and buffering lifecycle. */
|
|
30511
31182
|
buffering;
|
|
31183
|
+
/** Unresolved observation blockAfter (multiplier or absolute); resolved per record. */
|
|
31184
|
+
observationBlockAfterSetting;
|
|
30512
31185
|
shouldObscureThreadIds = false;
|
|
30513
31186
|
hasher = (0, xxhash_wasm.default)();
|
|
30514
31187
|
mastra;
|
|
@@ -30644,6 +31317,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
30644
31317
|
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
31318
|
const observationActivateAfterIdle = config.observation?.activateAfterIdle ?? config.activateAfterIdle;
|
|
30646
31319
|
const observationActivateAfterIdlePath = config.observation?.activateAfterIdle !== void 0 ? "observation.activateAfterIdle" : "activateAfterIdle";
|
|
31320
|
+
this.observationBlockAfterSetting = asyncBufferingDisabled ? void 0 : config.observation?.blockAfter ?? (config.observation?.bufferTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferTokens ? 1.2 : void 0);
|
|
30647
31321
|
this.observationConfig = {
|
|
30648
31322
|
model: observationModel,
|
|
30649
31323
|
maxRetries: config.observation?.maxRetries ?? RETRY_CONFIG.maxRetries,
|
|
@@ -30664,8 +31338,8 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
30664
31338
|
bufferActivation: asyncBufferingDisabled ? void 0 : config.observation?.bufferActivation ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferActivation,
|
|
30665
31339
|
activateAfterIdle: parseActivationTTLConfig(observationActivateAfterIdle, observationActivateAfterIdlePath),
|
|
30666
31340
|
activateOnProviderChange: config.observation?.activateOnProviderChange ?? config.activateOnProviderChange ?? false,
|
|
30667
|
-
blockAfter:
|
|
30668
|
-
previousObserverTokens: config.observation?.previousObserverTokens ??
|
|
31341
|
+
blockAfter: resolveBlockAfter(this.observationBlockAfterSetting, config.observation?.messageTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens),
|
|
31342
|
+
previousObserverTokens: config.observation?.previousObserverTokens ?? false,
|
|
30669
31343
|
instruction: config.observation?.instruction,
|
|
30670
31344
|
threadTitle: config.observation?.threadTitle ?? false,
|
|
30671
31345
|
observeAttachments: config.observation?.observeAttachments ?? [...OBSERVATIONAL_MEMORY_DEFAULTS.observation.observeAttachments],
|
|
@@ -30974,6 +31648,13 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
30974
31648
|
return this.observationConfig.messageTokens;
|
|
30975
31649
|
}
|
|
30976
31650
|
/**
|
|
31651
|
+
* Resolve the observation blockAfter for a record. A multiplier scales the record's
|
|
31652
|
+
* effective messageTokens (per-record overrides included); an absolute value is used as-is.
|
|
31653
|
+
*/
|
|
31654
|
+
getEffectiveObservationBlockAfter(record) {
|
|
31655
|
+
return resolveBlockAfter(this.observationBlockAfterSetting, this.getEffectiveMessageTokens(record));
|
|
31656
|
+
}
|
|
31657
|
+
/**
|
|
30977
31658
|
* Resolve the effective reflection observationTokens for a record.
|
|
30978
31659
|
* Only explicit per-record overrides (stored under `_overrides`) win;
|
|
30979
31660
|
* the initial config snapshot is ignored so instance-level changes
|
|
@@ -31087,18 +31768,21 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
31087
31768
|
* the latest user input that triggered it.
|
|
31088
31769
|
* @internal Used by observation strategies and the reflector. Do not call directly.
|
|
31089
31770
|
*/
|
|
31090
|
-
async persistMarkerToStorage(marker, threadId, resourceId) {
|
|
31771
|
+
async persistMarkerToStorage(marker, threadId, resourceId, opts) {
|
|
31091
31772
|
try {
|
|
31092
|
-
const
|
|
31773
|
+
const markerData = marker.data;
|
|
31774
|
+
const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
|
|
31775
|
+
const preferred = [...opts?.preferMessages ?? []].reverse().find((m) => m.role === targetRole && (!m.threadId || m.threadId === threadId));
|
|
31776
|
+
const preferredStored = preferred ? (await this.storage.listMessagesById({ messageIds: [preferred.id] })).messages : [];
|
|
31777
|
+
const messages = (preferredStored.length ? { messages: preferredStored } : await this.storage.listMessages({
|
|
31093
31778
|
threadId,
|
|
31094
31779
|
perPage: 20,
|
|
31095
31780
|
orderBy: {
|
|
31096
31781
|
field: "createdAt",
|
|
31097
31782
|
direction: "DESC"
|
|
31098
|
-
}
|
|
31783
|
+
},
|
|
31784
|
+
...opts?.notAfter ? { filter: { dateRange: { end: opts.notAfter } } } : {}
|
|
31099
31785
|
}))?.messages ?? [];
|
|
31100
|
-
const markerData = marker.data;
|
|
31101
|
-
const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
|
|
31102
31786
|
for (const msg of messages) if (msg?.role === targetRole && msg.content?.parts && Array.isArray(msg.content.parts)) {
|
|
31103
31787
|
if (!(markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) msg.content.parts.push(marker);
|
|
31104
31788
|
await this.messageHistory.persistMessages({
|
|
@@ -31185,7 +31869,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
31185
31869
|
*/
|
|
31186
31870
|
createUnobservedMessage(message) {
|
|
31187
31871
|
const unobservedParts = getUnobservedParts(message);
|
|
31188
|
-
if (unobservedParts.
|
|
31872
|
+
if (unobservedParts.every((part) => part?.type?.startsWith("data-om-"))) return null;
|
|
31189
31873
|
return {
|
|
31190
31874
|
...message,
|
|
31191
31875
|
content: {
|
|
@@ -31557,17 +32241,17 @@ ${formattedMessages}
|
|
|
31557
32241
|
async startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, contextWindowTokens, requestContext, observabilityContext) {
|
|
31558
32242
|
const bufferKey = this.buffering.getObservationBufferKey(lockKey);
|
|
31559
32243
|
const currentTokens = contextWindowTokens ?? await this.tokenCounter.countMessagesAsync(unobservedMessages) + (record.pendingMessageTokens ?? 0);
|
|
31560
|
-
BufferingCoordinator.
|
|
32244
|
+
BufferingCoordinator.setObservationBoundary(bufferKey, currentTokens);
|
|
31561
32245
|
registerOp(record.id, "bufferingObservation");
|
|
31562
|
-
this.storage.setBufferingObservationFlag(record.id, true, currentTokens).catch((err) => {
|
|
32246
|
+
runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, true, currentTokens)).catch((err) => {
|
|
31563
32247
|
omError("[OM] Failed to set buffering observation flag", err);
|
|
31564
32248
|
});
|
|
31565
32249
|
const asyncOp = this.runAsyncBufferedObservation(record, threadId, unobservedMessages, bufferKey, writer, requestContext, observabilityContext).catch((err) => {
|
|
31566
32250
|
omError("[OM] async buffering observation failed", err);
|
|
31567
32251
|
}).finally(() => {
|
|
31568
|
-
BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
|
|
32252
|
+
if (BufferingCoordinator.asyncBufferingOps.get(bufferKey) === asyncOp) BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
|
|
31569
32253
|
unregisterOp(record.id, "bufferingObservation");
|
|
31570
|
-
this.storage.setBufferingObservationFlag(record.id, false).catch((err) => {
|
|
32254
|
+
runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch((err) => {
|
|
31571
32255
|
omError("[OM] Failed to clear buffering observation flag", err);
|
|
31572
32256
|
});
|
|
31573
32257
|
});
|
|
@@ -31582,6 +32266,14 @@ ${formattedMessages}
|
|
|
31582
32266
|
if (existingOp) try {
|
|
31583
32267
|
await existingOp;
|
|
31584
32268
|
} catch {}
|
|
32269
|
+
const releaseChunkWrite = BufferingCoordinator.trackChunkWrite(bufferKey);
|
|
32270
|
+
try {
|
|
32271
|
+
await this.bufferObservationChunk(record, threadId, unobservedMessages, bufferKey, releaseChunkWrite, writer, requestContext, observabilityContext);
|
|
32272
|
+
} finally {
|
|
32273
|
+
releaseChunkWrite();
|
|
32274
|
+
}
|
|
32275
|
+
}
|
|
32276
|
+
async bufferObservationChunk(record, threadId, unobservedMessages, bufferKey, releaseChunkWrite, writer, requestContext, observabilityContext) {
|
|
31585
32277
|
const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
31586
32278
|
if (!freshRecord) return;
|
|
31587
32279
|
let bufferCursor = BufferingCoordinator.lastBufferedAtTime.get(bufferKey) ?? freshRecord.lastBufferedAtTime ?? null;
|
|
@@ -31622,13 +32314,16 @@ ${formattedMessages}
|
|
|
31622
32314
|
threadIds: [threadId],
|
|
31623
32315
|
config: this.getObservationMarkerConfig()
|
|
31624
32316
|
});
|
|
31625
|
-
await this.persistMarkerToStorage(startMarker, threadId, freshRecord.resourceId ?? void 0
|
|
32317
|
+
await this.persistMarkerToStorage(startMarker, threadId, freshRecord.resourceId ?? void 0, {
|
|
32318
|
+
notAfter: new Date(startedAt),
|
|
32319
|
+
preferMessages: messagesToBuffer
|
|
32320
|
+
});
|
|
31626
32321
|
if (writer) writer.custom({
|
|
31627
32322
|
...startMarker,
|
|
31628
32323
|
transient: true
|
|
31629
32324
|
}).catch(() => {});
|
|
31630
32325
|
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
|
-
|
|
32326
|
+
await this.runBufferedObservationCycle({
|
|
31632
32327
|
threadId,
|
|
31633
32328
|
resourceId: freshRecord.resourceId ?? void 0,
|
|
31634
32329
|
trigger: "async-buffer"
|
|
@@ -31642,12 +32337,13 @@ ${formattedMessages}
|
|
|
31642
32337
|
writer,
|
|
31643
32338
|
requestContext,
|
|
31644
32339
|
observabilityContext,
|
|
31645
|
-
trigger: "async-buffer"
|
|
31646
|
-
|
|
31647
|
-
|
|
31648
|
-
|
|
31649
|
-
|
|
31650
|
-
|
|
32340
|
+
trigger: "async-buffer",
|
|
32341
|
+
onBufferedChunkPersisted: async () => {
|
|
32342
|
+
const maxTs = this.getMaxMessageTimestamp(messagesToBuffer);
|
|
32343
|
+
BufferingCoordinator.lastBufferedAtTime.set(bufferKey, new Date(maxTs.getTime() + 1));
|
|
32344
|
+
releaseChunkWrite();
|
|
32345
|
+
}
|
|
32346
|
+
}).run());
|
|
31651
32347
|
}
|
|
31652
32348
|
/**
|
|
31653
32349
|
* Trigger async buffered observation if the token count has crossed a new interval.
|
|
@@ -31832,8 +32528,8 @@ ${formattedMessages}
|
|
|
31832
32528
|
const { threadId, resourceId, recordId, activatedMessageIds } = opts;
|
|
31833
32529
|
const lockKey = this.buffering.getLockKey(threadId, resourceId);
|
|
31834
32530
|
const bufKey = this.buffering.getObservationBufferKey(lockKey);
|
|
31835
|
-
BufferingCoordinator.
|
|
31836
|
-
await this.storage.setBufferingObservationFlag(recordId, false, 0).catch(() => {});
|
|
32531
|
+
BufferingCoordinator.setObservationBoundary(bufKey, 0);
|
|
32532
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(recordId, false, 0)).catch(() => {});
|
|
31837
32533
|
if (activatedMessageIds && activatedMessageIds.length > 0) this.buffering.cleanupStaticMaps(threadId, resourceId, activatedMessageIds);
|
|
31838
32534
|
}
|
|
31839
32535
|
/**
|
|
@@ -31992,9 +32688,13 @@ ${formattedMessages}
|
|
|
31992
32688
|
* @example
|
|
31993
32689
|
* ```ts
|
|
31994
32690
|
* const status = await om.getStatus({ threadId });
|
|
31995
|
-
* if (status.shouldObserve) {
|
|
32691
|
+
* if (status.shouldObserve && status.canActivate) {
|
|
32692
|
+
* await om.activate({ threadId });
|
|
32693
|
+
* } else if (status.shouldObserve && !status.inAsyncObservationBand) {
|
|
32694
|
+
* // At/above blockAfter (or async buffering disabled): observe synchronously
|
|
31996
32695
|
* await om.observe({ threadId });
|
|
31997
32696
|
* } else if (status.shouldBuffer) {
|
|
32697
|
+
* // Below the threshold, or in the threshold→blockAfter band: buffer in the background
|
|
31998
32698
|
* await om.buffer({ threadId });
|
|
31999
32699
|
* }
|
|
32000
32700
|
* if (status.shouldReflect) {
|
|
@@ -32024,8 +32724,10 @@ ${formattedMessages}
|
|
|
32024
32724
|
const bufferedChunkCount = bufferedChunks.length;
|
|
32025
32725
|
const bufferedChunkTokens = bufferedChunks.reduce((sum, chunk) => sum + (chunk.messageTokens ?? 0), 0);
|
|
32026
32726
|
const asyncObservationEnabled = this.buffering.isAsyncObservationEnabled();
|
|
32727
|
+
const observationBlockAfter = asyncObservationEnabled ? this.getEffectiveObservationBlockAfter(record) : void 0;
|
|
32728
|
+
const inAsyncObservationBand = observationBlockAfter !== void 0 && pendingTokens >= threshold && pendingTokens < observationBlockAfter;
|
|
32027
32729
|
let shouldBuffer = false;
|
|
32028
|
-
if (asyncObservationEnabled && pendingTokens < threshold) {
|
|
32730
|
+
if (asyncObservationEnabled && (pendingTokens < threshold || inAsyncObservationBand)) {
|
|
32029
32731
|
const lockKey = this.buffering.getLockKey(threadId, resourceId);
|
|
32030
32732
|
shouldBuffer = this.buffering.shouldTriggerAsyncObservation(pendingTokens, lockKey, record, this.storage, threshold);
|
|
32031
32733
|
}
|
|
@@ -32050,6 +32752,8 @@ ${formattedMessages}
|
|
|
32050
32752
|
canActivate,
|
|
32051
32753
|
asyncObservationEnabled,
|
|
32052
32754
|
asyncReflectionEnabled: this.buffering.isAsyncReflectionEnabled(),
|
|
32755
|
+
observationBlockAfter,
|
|
32756
|
+
inAsyncObservationBand,
|
|
32053
32757
|
scope: this.scope
|
|
32054
32758
|
};
|
|
32055
32759
|
}
|
|
@@ -32176,24 +32880,27 @@ ${formattedMessages}
|
|
|
32176
32880
|
const currentTokens = opts.pendingTokens ?? record.pendingMessageTokens ?? 0;
|
|
32177
32881
|
const lockKey = this.buffering.getLockKey(threadId, resourceId);
|
|
32178
32882
|
const bufferKey = this.buffering.getObservationBufferKey(lockKey);
|
|
32179
|
-
|
|
32180
|
-
|
|
32883
|
+
const boundaryOwner = Symbol("observation-boundary");
|
|
32884
|
+
BufferingCoordinator.setObservationBoundary(bufferKey, currentTokens, boundaryOwner);
|
|
32181
32885
|
const existingOp = BufferingCoordinator.asyncBufferingOps.get(bufferKey);
|
|
32886
|
+
let resolveOp;
|
|
32887
|
+
const opPromise = new Promise((resolve) => {
|
|
32888
|
+
resolveOp = resolve;
|
|
32889
|
+
});
|
|
32890
|
+
BufferingCoordinator.asyncBufferingOps.set(bufferKey, opPromise);
|
|
32891
|
+
if (record.isBufferingObservation) await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch(() => {});
|
|
32182
32892
|
if (existingOp) try {
|
|
32183
32893
|
await existingOp;
|
|
32184
32894
|
} catch {}
|
|
32185
|
-
|
|
32895
|
+
const opRecordId = record.id;
|
|
32896
|
+
registerOp(opRecordId, "bufferingObservation");
|
|
32897
|
+
const releaseChunkWrite = BufferingCoordinator.trackChunkWrite(bufferKey);
|
|
32898
|
+
const ownsStartBoundary = BufferingCoordinator.ownsObservationBoundary(bufferKey, boundaryOwner);
|
|
32186
32899
|
inMemoryRecord.isBufferingObservation = true;
|
|
32187
|
-
inMemoryRecord.lastBufferedAtTokens = currentTokens;
|
|
32188
|
-
this.storage.setBufferingObservationFlag(record.id, true, currentTokens).catch((err) => {
|
|
32900
|
+
if (ownsStartBoundary) inMemoryRecord.lastBufferedAtTokens = currentTokens;
|
|
32901
|
+
runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, true, ownsStartBoundary ? currentTokens : void 0)).catch((err) => {
|
|
32189
32902
|
omError("[OM] Failed to set buffering observation flag", err);
|
|
32190
32903
|
});
|
|
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
32904
|
const setBufferingState = (isBufferingObservation, lastBufferedAtTokens) => {
|
|
32198
32905
|
inMemoryRecord.isBufferingObservation = isBufferingObservation;
|
|
32199
32906
|
record.isBufferingObservation = isBufferingObservation;
|
|
@@ -32204,6 +32911,7 @@ ${formattedMessages}
|
|
|
32204
32911
|
};
|
|
32205
32912
|
let flagCleared = false;
|
|
32206
32913
|
try {
|
|
32914
|
+
record = await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record;
|
|
32207
32915
|
let candidateMessages;
|
|
32208
32916
|
if (opts.messages) candidateMessages = this.getUnobservedMessages(opts.messages, record, { excludeBuffered: true });
|
|
32209
32917
|
else {
|
|
@@ -32243,12 +32951,29 @@ ${formattedMessages}
|
|
|
32243
32951
|
threadIds: [threadId],
|
|
32244
32952
|
config: this.getObservationMarkerConfig()
|
|
32245
32953
|
});
|
|
32246
|
-
await this.persistMarkerToStorage(startMarker, threadId, record.resourceId ?? void 0
|
|
32954
|
+
await this.persistMarkerToStorage(startMarker, threadId, record.resourceId ?? void 0, {
|
|
32955
|
+
notAfter: new Date(startedAt),
|
|
32956
|
+
preferMessages: candidateMessages
|
|
32957
|
+
});
|
|
32247
32958
|
const writer = opts.writer;
|
|
32248
32959
|
if (writer) writer.custom({
|
|
32249
32960
|
...startMarker,
|
|
32250
32961
|
transient: true
|
|
32251
32962
|
}).catch(() => {});
|
|
32963
|
+
let chunkRecorded = false;
|
|
32964
|
+
const recordBufferedChunk = async () => {
|
|
32965
|
+
if (chunkRecorded) return;
|
|
32966
|
+
chunkRecorded = true;
|
|
32967
|
+
const ownsBoundary = BufferingCoordinator.ownsObservationBoundary(bufferKey, boundaryOwner);
|
|
32968
|
+
if (ownsBoundary) BufferingCoordinator.setObservationBoundary(bufferKey, newTokens, boundaryOwner);
|
|
32969
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false, ownsBoundary ? newTokens : void 0)).catch(() => {});
|
|
32970
|
+
flagCleared = true;
|
|
32971
|
+
const stillOwnsBoundary = ownsBoundary && BufferingCoordinator.ownsObservationBoundary(bufferKey, boundaryOwner);
|
|
32972
|
+
setBufferingState(false, stillOwnsBoundary ? newTokens : void 0);
|
|
32973
|
+
const maxTimestamp = this.getMaxMessageTimestamp(candidateMessages);
|
|
32974
|
+
BufferingCoordinator.lastBufferedAtTime.set(bufferKey, new Date(maxTimestamp.getTime() + 1));
|
|
32975
|
+
releaseChunkWrite();
|
|
32976
|
+
};
|
|
32252
32977
|
if (!(await this.runBufferedObservationCycle({
|
|
32253
32978
|
threadId,
|
|
32254
32979
|
resourceId: record.resourceId ?? resourceId,
|
|
@@ -32267,7 +32992,8 @@ ${formattedMessages}
|
|
|
32267
32992
|
requestContext,
|
|
32268
32993
|
currentModel: opts.currentModel,
|
|
32269
32994
|
observabilityContext,
|
|
32270
|
-
trigger: "async-buffer"
|
|
32995
|
+
trigger: "async-buffer",
|
|
32996
|
+
onBufferedChunkPersisted: recordBufferedChunk
|
|
32271
32997
|
}).run()))?.observed) return {
|
|
32272
32998
|
buffered: false,
|
|
32273
32999
|
record
|
|
@@ -32287,13 +33013,7 @@ ${formattedMessages}
|
|
|
32287
33013
|
newTokens
|
|
32288
33014
|
}
|
|
32289
33015
|
});
|
|
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);
|
|
33016
|
+
await recordBufferedChunk();
|
|
32297
33017
|
return {
|
|
32298
33018
|
buffered: true,
|
|
32299
33019
|
record: await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record
|
|
@@ -32305,12 +33025,13 @@ ${formattedMessages}
|
|
|
32305
33025
|
record
|
|
32306
33026
|
};
|
|
32307
33027
|
} finally {
|
|
32308
|
-
|
|
32309
|
-
|
|
33028
|
+
releaseChunkWrite();
|
|
33029
|
+
unregisterOp(opRecordId, "bufferingObservation");
|
|
33030
|
+
if (BufferingCoordinator.asyncBufferingOps.get(bufferKey) === opPromise) BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
|
|
32310
33031
|
resolveOp();
|
|
32311
33032
|
if (!flagCleared) {
|
|
32312
33033
|
setBufferingState(false);
|
|
32313
|
-
await this.storage.setBufferingObservationFlag(record.id, false).catch(() => {});
|
|
33034
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch(() => {});
|
|
32314
33035
|
}
|
|
32315
33036
|
}
|
|
32316
33037
|
}
|
|
@@ -32347,8 +33068,8 @@ ${formattedMessages}
|
|
|
32347
33068
|
const currentContextTokens = this.tokenCounter.countMessages(unobserved);
|
|
32348
33069
|
if (currentContextTokens < dbBoundary * .5) {
|
|
32349
33070
|
omDebug(`[OM:activate] resetting stale lastBufferedBoundary: dbBoundary=${dbBoundary}, currentContextTokens=${currentContextTokens}`);
|
|
32350
|
-
BufferingCoordinator.
|
|
32351
|
-
await this.storage.setBufferingObservationFlag(record.id, false, 0).catch(() => {});
|
|
33071
|
+
BufferingCoordinator.setObservationBoundary(bufKey, 0);
|
|
33072
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false, 0)).catch(() => {});
|
|
32352
33073
|
}
|
|
32353
33074
|
}
|
|
32354
33075
|
}
|
|
@@ -32394,39 +33115,63 @@ ${formattedMessages}
|
|
|
32394
33115
|
livePendingTokens ??= status.pendingTokens;
|
|
32395
33116
|
}
|
|
32396
33117
|
}
|
|
32397
|
-
|
|
32398
|
-
|
|
32399
|
-
|
|
32400
|
-
const
|
|
32401
|
-
|
|
32402
|
-
|
|
32403
|
-
|
|
33118
|
+
const obsBufferKey = this.buffering.getObservationBufferKey(this.buffering.getLockKey(threadId, resourceId));
|
|
33119
|
+
const pendingChunkWrite = BufferingCoordinator.pendingChunkWrites.get(obsBufferKey);
|
|
33120
|
+
if (pendingChunkWrite) {
|
|
33121
|
+
const blockAfter = this.buffering.isAsyncObservationEnabled() ? this.getEffectiveObservationBlockAfter(record) : void 0;
|
|
33122
|
+
const threshold = calculateDynamicThreshold(this.getEffectiveMessageTokens(record), record.observationTokenCount ?? 0);
|
|
33123
|
+
if (activationTriggeredBy === "threshold" && !(livePendingTokens !== void 0 && blockAfter !== void 0 && livePendingTokens >= threshold && livePendingTokens < blockAfter)) {
|
|
33124
|
+
let timeoutId;
|
|
33125
|
+
await Promise.race([pendingChunkWrite, new Promise((resolve) => timeoutId = setTimeout(resolve, 6e4))]);
|
|
33126
|
+
clearTimeout(timeoutId);
|
|
33127
|
+
}
|
|
33128
|
+
}
|
|
33129
|
+
const commitKey = this.buffering.getLockKey(threadId, resourceId);
|
|
33130
|
+
let freshRecord = null;
|
|
33131
|
+
let freshChunks = [];
|
|
33132
|
+
let activationResult;
|
|
33133
|
+
for (let attempt = 0; attempt <= MAX_ACTIVATION_RETIRED_RETRIES; attempt++) {
|
|
33134
|
+
const swapped = await runOMCommit(commitKey, async () => {
|
|
33135
|
+
const head = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
33136
|
+
if (!head) return null;
|
|
33137
|
+
const chunks = getBufferedChunks(head);
|
|
33138
|
+
if (!chunks.length) return null;
|
|
33139
|
+
const messageTokensThreshold = getMaxThreshold(this.getEffectiveMessageTokens(head));
|
|
33140
|
+
const activationRatio = resolveActivationRatio(this.observationConfig.bufferActivation ?? .7, messageTokensThreshold);
|
|
33141
|
+
const totalChunkMessageTokens = chunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
33142
|
+
const currentPendingTokens = livePendingTokens ?? (head.pendingMessageTokens || totalChunkMessageTokens);
|
|
33143
|
+
const blockAfter = this.getEffectiveObservationBlockAfter(head);
|
|
33144
|
+
const forceMaxActivation = !!(blockAfter && currentPendingTokens >= blockAfter);
|
|
33145
|
+
if (head.pendingMessageTokens !== currentPendingTokens) await this.storage.setPendingMessageTokens(head.id, currentPendingTokens);
|
|
33146
|
+
return {
|
|
33147
|
+
head,
|
|
33148
|
+
chunks,
|
|
33149
|
+
result: await this.storage.swapBufferedToActive({
|
|
33150
|
+
id: head.id,
|
|
33151
|
+
activationRatio,
|
|
33152
|
+
messageTokensThreshold,
|
|
33153
|
+
currentPendingTokens,
|
|
33154
|
+
forceMaxActivation,
|
|
33155
|
+
bufferedChunks: chunks
|
|
33156
|
+
})
|
|
33157
|
+
};
|
|
33158
|
+
});
|
|
33159
|
+
if (!swapped) return {
|
|
33160
|
+
activated: false,
|
|
33161
|
+
record
|
|
33162
|
+
};
|
|
33163
|
+
freshRecord = swapped.head;
|
|
33164
|
+
freshChunks = swapped.chunks;
|
|
33165
|
+
activationResult = swapped.result;
|
|
33166
|
+
if (!activationResult.retired) break;
|
|
33167
|
+
omDebug(`[OM:activate] record ${freshRecord.id} was retired by a reflection; retrying on the head`);
|
|
32404
33168
|
}
|
|
32405
|
-
|
|
32406
|
-
if (!freshRecord) return {
|
|
33169
|
+
if (!freshRecord || !activationResult || activationResult.retired) return {
|
|
32407
33170
|
activated: false,
|
|
32408
|
-
record
|
|
32409
|
-
};
|
|
32410
|
-
const freshChunks = getBufferedChunks(freshRecord);
|
|
32411
|
-
if (!freshChunks.length) return {
|
|
32412
|
-
activated: false,
|
|
32413
|
-
record
|
|
33171
|
+
record: await this.getRecord(threadId, resourceId) ?? record
|
|
32414
33172
|
};
|
|
32415
|
-
const
|
|
32416
|
-
|
|
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(() => {});
|
|
33173
|
+
const swappedRecordId = freshRecord.id;
|
|
33174
|
+
await runOMCommit(commitKey, () => this.storage.setBufferingObservationFlag(swappedRecordId, false)).catch(() => {});
|
|
32430
33175
|
unregisterOp(freshRecord.id, "bufferingObservation");
|
|
32431
33176
|
const postSwapRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
32432
33177
|
if (opts.writer && postSwapRecord && activationResult.activatedCycleIds.length > 0) {
|
|
@@ -32671,7 +33416,7 @@ ${formattedMessages}
|
|
|
32671
33416
|
* ```
|
|
32672
33417
|
*/
|
|
32673
33418
|
async reflect(threadId, resourceId, prompt, requestContext, observabilityContext) {
|
|
32674
|
-
const record = await this.getOrCreateRecord(threadId, resourceId);
|
|
33419
|
+
const record = { ...await this.getOrCreateRecord(threadId, resourceId) };
|
|
32675
33420
|
if (!record.activeObservations) return {
|
|
32676
33421
|
reflected: false,
|
|
32677
33422
|
record,
|
|
@@ -32716,11 +33461,23 @@ ${formattedMessages}
|
|
|
32716
33461
|
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
33462
|
reflectResult.observations = await applyTextTransform(this.hooks, "afterReflection", reflectResult.observations, hookContext);
|
|
32718
33463
|
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
32719
|
-
|
|
33464
|
+
const newRecordId = crypto.randomUUID();
|
|
33465
|
+
const committedRecord = await runOMCommit(this.buffering.getLockKey(threadId, resourceId), () => this.storage.createReflectionGeneration({
|
|
32720
33466
|
currentRecord: record,
|
|
32721
33467
|
reflection: reflectResult.observations,
|
|
32722
|
-
tokenCount: reflectionTokenCount
|
|
32723
|
-
|
|
33468
|
+
tokenCount: reflectionTokenCount,
|
|
33469
|
+
newRecordId
|
|
33470
|
+
}), { priority: "reflection" });
|
|
33471
|
+
reflectionUsage = reflectResult.usage;
|
|
33472
|
+
reflectionProviderMetadata = reflectResult.providerMetadata;
|
|
33473
|
+
if (!isReflectionApplied(committedRecord, newRecordId, record.id)) {
|
|
33474
|
+
omDebug(`[OM:reflect] manual reflection of ${record.id} not applied (head is ${committedRecord.id})`);
|
|
33475
|
+
return {
|
|
33476
|
+
reflected: false,
|
|
33477
|
+
record: await this.getOrCreateRecord(threadId, resourceId),
|
|
33478
|
+
usage: reflectResult.usage
|
|
33479
|
+
};
|
|
33480
|
+
}
|
|
32724
33481
|
if (thread && reflectResult.extractedValues) {
|
|
32725
33482
|
const metadataUpdate = buildThreadMetadataFromExtractedValues(this.reflectionConfig.extractors, reflectResult.extractedValues);
|
|
32726
33483
|
const newMetadata = (0, _mastra_core_memory.setThreadOMMetadata)(thread.metadata, {
|
|
@@ -32809,11 +33566,13 @@ ${formattedMessages}
|
|
|
32809
33566
|
*/
|
|
32810
33567
|
async updateRecordConfig(threadId, resourceId, config) {
|
|
32811
33568
|
const ids = this.getStorageIds(threadId, resourceId);
|
|
32812
|
-
|
|
32813
|
-
|
|
32814
|
-
|
|
32815
|
-
|
|
32816
|
-
|
|
33569
|
+
await runOMCommit(this.buffering.getLockKey(threadId, resourceId), async () => {
|
|
33570
|
+
const record = await this.storage.getObservationalMemory(ids.threadId, ids.resourceId);
|
|
33571
|
+
if (!record) throw new Error(`No observational memory record found for thread ${ids.threadId}`);
|
|
33572
|
+
await this.storage.updateObservationalMemoryConfig({
|
|
33573
|
+
id: record.id,
|
|
33574
|
+
config: { _overrides: config }
|
|
33575
|
+
});
|
|
32817
33576
|
});
|
|
32818
33577
|
}
|
|
32819
33578
|
/**
|
|
@@ -33207,7 +33966,7 @@ var ObservationalMemoryProcessor = class {
|
|
|
33207
33966
|
});
|
|
33208
33967
|
const finalTotalPending = ctx.status.pendingTokens;
|
|
33209
33968
|
try {
|
|
33210
|
-
await this.engine.getStorage().setPendingMessageTokens(turnRecord.id, finalTotalPending);
|
|
33969
|
+
await runOMCommit(this.engine.buffering.getLockKey(threadId, resourceId), () => this.engine.getStorage().setPendingMessageTokens(turnRecord.id, finalTotalPending));
|
|
33211
33970
|
this.turn.patchRecord({ pendingMessageTokens: finalTotalPending });
|
|
33212
33971
|
} catch {}
|
|
33213
33972
|
if (reproCaptureEnabled) writeProcessInputStepReproCapture({
|
|
@@ -35542,6 +36301,7 @@ Notes:
|
|
|
35542
36301
|
cloned.id = crypto.randomUUID();
|
|
35543
36302
|
cloned.createdAt = now;
|
|
35544
36303
|
cloned.updatedAt = now;
|
|
36304
|
+
cloned.supersededBy = null;
|
|
35545
36305
|
await memoryStore.insertObservationalMemoryRecord(cloned);
|
|
35546
36306
|
}
|
|
35547
36307
|
/**
|
|
@@ -36140,4 +36900,4 @@ Object.defineProperty(exports, "wrapInObservationGroup", {
|
|
|
36140
36900
|
}
|
|
36141
36901
|
});
|
|
36142
36902
|
|
|
36143
|
-
//# sourceMappingURL=src-
|
|
36903
|
+
//# sourceMappingURL=src-BcRCGtUY.cjs.map
|