@mastra/memory 1.37.0 → 1.37.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-observational-memory.md +17 -15
- package/dist/docs/references/reference-memory-observational-memory.md +3 -3
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/processors/index.cjs +1 -1
- package/dist/processors/index.js +1 -1
- package/dist/processors/observational-memory/buffering-coordinator.d.ts +25 -0
- package/dist/processors/observational-memory/buffering-coordinator.d.ts.map +1 -1
- package/dist/processors/observational-memory/commit-queue.d.ts +46 -0
- package/dist/processors/observational-memory/commit-queue.d.ts.map +1 -0
- package/dist/processors/observational-memory/observation-strategies/async-buffer.d.ts +7 -4
- package/dist/processors/observational-memory/observation-strategies/async-buffer.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/base.d.ts +93 -6
- package/dist/processors/observational-memory/observation-strategies/base.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/resource-scoped.d.ts +20 -3
- package/dist/processors/observational-memory/observation-strategies/resource-scoped.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/sync.d.ts +10 -3
- package/dist/processors/observational-memory/observation-strategies/sync.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/types.d.ts +30 -0
- package/dist/processors/observational-memory/observation-strategies/types.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-turn/step.d.ts +1 -1
- package/dist/processors/observational-memory/observation-turn/step.d.ts.map +1 -1
- package/dist/processors/observational-memory/observational-memory.d.ts +30 -6
- package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
- package/dist/processors/observational-memory/observer-agent.d.ts +8 -0
- package/dist/processors/observational-memory/observer-agent.d.ts.map +1 -1
- package/dist/processors/observational-memory/observer-runner.d.ts +1 -1
- package/dist/processors/observational-memory/observer-runner.d.ts.map +1 -1
- package/dist/processors/observational-memory/processor.d.ts.map +1 -1
- package/dist/processors/observational-memory/record-lineage.d.ts +21 -0
- package/dist/processors/observational-memory/record-lineage.d.ts.map +1 -0
- package/dist/processors/observational-memory/reflector-agent.d.ts.map +1 -1
- package/dist/processors/observational-memory/reflector-runner.d.ts +9 -0
- package/dist/processors/observational-memory/reflector-runner.d.ts.map +1 -1
- package/dist/processors/observational-memory/types.d.ts +10 -8
- package/dist/processors/observational-memory/types.d.ts.map +1 -1
- package/dist/{src-D-jA6eRb.cjs → src-BpxX0T2g.cjs} +1039 -298
- package/dist/src-BpxX0T2g.cjs.map +1 -0
- package/dist/{src-B9qhQmNI.js → src-ThciS3vu.js} +1040 -299
- package/dist/src-ThciS3vu.js.map +1 -0
- package/package.json +2 -2
- package/dist/src-B9qhQmNI.js.map +0 -1
- package/dist/src-D-jA6eRb.cjs.map +0 -1
|
@@ -19,7 +19,7 @@ import { join } from "path";
|
|
|
19
19
|
import { createTool } from "@mastra/core/tools";
|
|
20
20
|
import { MASTRA_THREAD_ID_KEY, RequestContext } from "@mastra/core/request-context";
|
|
21
21
|
import { modelSupportsAttachments, modelSupportsTemperature, resolveModelConfig } from "@mastra/core/llm";
|
|
22
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
22
|
+
import { AsyncLocalStorage, AsyncResource } from "async_hooks";
|
|
23
23
|
import { createHash } from "crypto";
|
|
24
24
|
import probeImageSizeSync from "probe-image-size/sync.js";
|
|
25
25
|
import { isStandardSchemaWithJSON as isStandardSchemaWithJSON$1, toStandardSchema as toStandardSchema$1 } from "@mastra/core/schema";
|
|
@@ -20557,7 +20557,7 @@ Group related observations (like tool sequences) by indenting:
|
|
|
20557
20557
|
* -> applied fix, tests now pass
|
|
20558
20558
|
* ✅ Tests passing, auth issue resolved
|
|
20559
20559
|
|
|
20560
|
-
Group observations by date,
|
|
20560
|
+
Group observations by date. Within a date, list observations in the order the events happened (not by importance), each with 24-hour time.
|
|
20561
20561
|
|
|
20562
20562
|
<observations>
|
|
20563
20563
|
Date: Dec 4, 2025
|
|
@@ -20930,8 +20930,8 @@ function extractToolResultAttachments(result, counter, attachmentFilter) {
|
|
|
20930
20930
|
attachments
|
|
20931
20931
|
};
|
|
20932
20932
|
}
|
|
20933
|
-
function formatObserverPartLine(title, body, time, previousTime) {
|
|
20934
|
-
const timeLabel = time && time !== previousTime ? `(${time})` : "";
|
|
20933
|
+
function formatObserverPartLine(title, body, time, previousTime, alwaysShowTime) {
|
|
20934
|
+
const timeLabel = time && (alwaysShowTime || time !== previousTime) ? `(${time})` : "";
|
|
20935
20935
|
if (!title) return timeLabel ? `${timeLabel}: ${body}` : body;
|
|
20936
20936
|
return `${title}${timeLabel ? ` ${timeLabel}` : ""}: ${body}`;
|
|
20937
20937
|
}
|
|
@@ -20956,7 +20956,7 @@ function formatObserverLines(lines, context = {}) {
|
|
|
20956
20956
|
previousDate = line.date;
|
|
20957
20957
|
previousTime = void 0;
|
|
20958
20958
|
}
|
|
20959
|
-
output.push(formatObserverPartLine(line.title, line.body, line.time, previousTime));
|
|
20959
|
+
output.push(formatObserverPartLine(line.title, line.body, line.time, previousTime, line.alwaysShowTime));
|
|
20960
20960
|
previousTime = line.time || previousTime;
|
|
20961
20961
|
}
|
|
20962
20962
|
return {
|
|
@@ -21057,14 +21057,15 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
21057
21057
|
const unavailableUrls = getUnavailableAttachmentUrls(msg);
|
|
21058
21058
|
let lines = [];
|
|
21059
21059
|
const temporalGapText = isTemporalGapMarker(msg) ? getTemporalGapMarkerText(msg) : void 0;
|
|
21060
|
-
const pushLine = (title, body, createdAt) => {
|
|
21060
|
+
const pushLine = (title, body, createdAt, opts) => {
|
|
21061
21061
|
if (!body) return;
|
|
21062
21062
|
const normalizedCreatedAt = normalizeObserverCreatedAt(createdAt) ?? messageCreatedAt;
|
|
21063
21063
|
lines.push({
|
|
21064
21064
|
date: formatObserverDate(normalizedCreatedAt, timeZone),
|
|
21065
21065
|
time: formatObserverTime(normalizedCreatedAt, timeZone),
|
|
21066
21066
|
title,
|
|
21067
|
-
body
|
|
21067
|
+
body,
|
|
21068
|
+
alwaysShowTime: opts?.tool === true
|
|
21068
21069
|
});
|
|
21069
21070
|
};
|
|
21070
21071
|
if (temporalGapText) pushLine("", temporalGapText, messageCreatedAt);
|
|
@@ -21081,7 +21082,7 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
21081
21082
|
const exchange = toolFormatting.exchanges.get(inv.toolCallId);
|
|
21082
21083
|
if (exchange && occurrence === exchange.firstOccurrence && exchange.signature && !toolFormatting.emittedCalls.has(inv.toolCallId)) {
|
|
21083
21084
|
toolFormatting.emittedCalls.add(inv.toolCallId);
|
|
21084
|
-
pushLine(`Tool Call ${exchange.signature.toolName}`, formatObserverToolArguments(exchange.signature.args, maxLen), partCreatedAt);
|
|
21085
|
+
pushLine(`Tool Call ${exchange.signature.toolName}`, formatObserverToolArguments(exchange.signature.args, maxLen), partCreatedAt, { tool: true });
|
|
21085
21086
|
}
|
|
21086
21087
|
if (inv.state === "result") {
|
|
21087
21088
|
const { value: resultForObserver } = resolveToolResultValue(part, inv.result);
|
|
@@ -21091,17 +21092,17 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
|
|
|
21091
21092
|
toolFormatting.emittedOutcomes.add(inv.toolCallId);
|
|
21092
21093
|
const hasResult = resultWithoutAttachments !== void 0 && (typeof resultWithoutAttachments !== "string" || resultWithoutAttachments.trim().length > 0);
|
|
21093
21094
|
const body = inv.isError ? hasResult ? formatToolResultForObserver(resultWithoutAttachments, { maxTokens: maxToolResultTokens }) : inv.errorText?.trim() ? inv.errorText : "Tool execution failed" : hasResult ? formatToolResultForObserver(resultWithoutAttachments, { maxTokens: maxToolResultTokens }) : "[empty result]";
|
|
21094
|
-
pushLine(`${inv.isError ? "Tool Error" : "Tool Result"} ${inv.toolName}`, maybeTruncate(body, maxLen), partCreatedAt);
|
|
21095
|
+
pushLine(`${inv.isError ? "Tool Error" : "Tool Result"} ${inv.toolName}`, maybeTruncate(body, maxLen), partCreatedAt, { tool: true });
|
|
21095
21096
|
}
|
|
21096
21097
|
return;
|
|
21097
21098
|
}
|
|
21098
21099
|
if (exchange?.terminal?.occurrence === occurrence && !toolFormatting.emittedOutcomes.has(inv.toolCallId)) {
|
|
21099
21100
|
toolFormatting.emittedOutcomes.add(inv.toolCallId);
|
|
21100
|
-
if (inv.state === "output-error") pushLine(`Tool Error ${inv.toolName}`, maybeTruncate(inv.errorText?.trim() ? inv.errorText : "Tool execution failed", maxLen), partCreatedAt);
|
|
21101
|
-
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);
|
|
21101
|
+
if (inv.state === "output-error") pushLine(`Tool Error ${inv.toolName}`, maybeTruncate(inv.errorText?.trim() ? inv.errorText : "Tool execution failed", maxLen), partCreatedAt, { tool: true });
|
|
21102
|
+
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 });
|
|
21102
21103
|
return;
|
|
21103
21104
|
}
|
|
21104
|
-
if (!exchange) pushLine(`Tool Call ${inv.toolName}`, formatObserverToolArguments(inv.args, maxLen), partCreatedAt);
|
|
21105
|
+
if (!exchange) pushLine(`Tool Call ${inv.toolName}`, formatObserverToolArguments(inv.args, maxLen), partCreatedAt, { tool: true });
|
|
21105
21106
|
return;
|
|
21106
21107
|
}
|
|
21107
21108
|
const partType = part.type;
|
|
@@ -21256,13 +21257,17 @@ function surroundObserverHistory(history, contextPrompt, taskInstructions) {
|
|
|
21256
21257
|
function buildMultiThreadObserverTaskInstructions(includeThreadTitle) {
|
|
21257
21258
|
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.`;
|
|
21258
21259
|
}
|
|
21260
|
+
function buildTruncationNotice(wasTruncated) {
|
|
21261
|
+
if (!wasTruncated) return "";
|
|
21262
|
+
return "Previous observations were truncated for context budget reasons.\nThe main agent still has full memory context outside this observer window.\n\n";
|
|
21263
|
+
}
|
|
21259
21264
|
function buildMultiThreadObserverContextPrompt(existingObservations, threadOrder, priorMetadataByThread, wasTruncated, includeThreadTitle, extractors = []) {
|
|
21260
21265
|
let prompt = "";
|
|
21261
21266
|
if (existingObservations) {
|
|
21262
21267
|
prompt += `## Previous Observations\n\n${existingObservations}\n\n---\n\n`;
|
|
21263
21268
|
prompt += "Do not repeat these existing observations. Your new observations will be appended to the existing observations.\n\n";
|
|
21264
21269
|
}
|
|
21265
|
-
|
|
21270
|
+
prompt += buildTruncationNotice(wasTruncated);
|
|
21266
21271
|
const threadMetadataLines = threadOrder?.map((threadId) => {
|
|
21267
21272
|
const metadata = priorMetadataByThread?.get(threadId);
|
|
21268
21273
|
const extractorPriorLines = buildExtractorPriorLines(extractors, metadata?.extracted);
|
|
@@ -21276,10 +21281,6 @@ function buildMultiThreadObserverContextPrompt(existingObservations, threadOrder
|
|
|
21276
21281
|
}).filter(Boolean).join("\n");
|
|
21277
21282
|
if (threadMetadataLines) {
|
|
21278
21283
|
prompt += `## Prior Thread Metadata\n\n${threadMetadataLines}\n\n`;
|
|
21279
|
-
if (hasTruncatedObservations) {
|
|
21280
|
-
prompt += `Previous observations were truncated for context budget reasons.\n`;
|
|
21281
|
-
prompt += `The main agent still has full memory context outside this observer window.\n`;
|
|
21282
|
-
}
|
|
21283
21284
|
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`;
|
|
21284
21285
|
}
|
|
21285
21286
|
return prompt;
|
|
@@ -21322,7 +21323,7 @@ function parseMultiThreadObserverOutput(output, extractors = []) {
|
|
|
21322
21323
|
threadTitle = threadTitleMatch[1].trim();
|
|
21323
21324
|
observations = observations.replace(/<thread-title>[\s\S]*?<\/thread-title>/i, "");
|
|
21324
21325
|
}
|
|
21325
|
-
observations = sanitizeObservationLines(observations.trim());
|
|
21326
|
+
observations = sortObservationsByTime(sanitizeObservationLines(observations.trim()));
|
|
21326
21327
|
threads.set(threadId, {
|
|
21327
21328
|
observations,
|
|
21328
21329
|
currentTask: currentTask || getStringExtractedValue$1(parsedExtractedValues.values, "current-task"),
|
|
@@ -21362,7 +21363,7 @@ function buildObserverContextPrompt(existingObservations, options) {
|
|
|
21362
21363
|
prompt += `## Previous Observations\n\n${existingObservations}\n\n---\n\n`;
|
|
21363
21364
|
prompt += "Do not repeat these existing observations. Your new observations will be appended to the existing observations.\n\n";
|
|
21364
21365
|
}
|
|
21365
|
-
|
|
21366
|
+
prompt += buildTruncationNotice(options?.wasTruncated);
|
|
21366
21367
|
const priorMetadataLines = [];
|
|
21367
21368
|
if (options?.priorCurrentTask) priorMetadataLines.push(`- prior current-task: ${options.priorCurrentTask}`);
|
|
21368
21369
|
if (options?.priorSuggestedResponse) priorMetadataLines.push(`- prior suggested-response: ${options.priorSuggestedResponse}`);
|
|
@@ -21370,10 +21371,6 @@ function buildObserverContextPrompt(existingObservations, options) {
|
|
|
21370
21371
|
priorMetadataLines.push(...buildExtractorPriorLines(options?.extractors ?? [], options?.priorExtractedValues));
|
|
21371
21372
|
if (priorMetadataLines.length > 0) {
|
|
21372
21373
|
prompt += `## Prior Thread Metadata\n\n${priorMetadataLines.join("\n")}\n\n`;
|
|
21373
|
-
if (hasTruncatedObservations) {
|
|
21374
|
-
prompt += `Previous observations were truncated for context budget reasons.\n`;
|
|
21375
|
-
prompt += `The main agent still has full memory context outside this observer window.\n`;
|
|
21376
|
-
}
|
|
21377
21374
|
const titleHint = options?.includeThreadTitle ? ", and thread-title" : "";
|
|
21378
21375
|
prompt += `Use the prior current-task, suggested-response${titleHint} as continuity hints, then update them based on the new messages.\n\n---\n\n`;
|
|
21379
21376
|
}
|
|
@@ -21404,7 +21401,7 @@ function parseObserverOutput(output, extractors = []) {
|
|
|
21404
21401
|
const parsedExtractedValues = parseExtractedValues(output, inlineExtractors);
|
|
21405
21402
|
const parsed = parseMemorySectionXml(stripExtractorSections(output, inlineExtractors));
|
|
21406
21403
|
return {
|
|
21407
|
-
observations: sanitizeObservationLines(parsed.observations || ""),
|
|
21404
|
+
observations: sortObservationsByTime(sanitizeObservationLines(parsed.observations || "")),
|
|
21408
21405
|
currentTask: parsed.currentTask || getStringExtractedValue$1(parsedExtractedValues.values, "current-task"),
|
|
21409
21406
|
suggestedContinuation: parsed.suggestedResponse || getStringExtractedValue$1(parsedExtractedValues.values, "suggested-response"),
|
|
21410
21407
|
threadTitle: parsed.threadTitle || getStringExtractedValue$1(parsedExtractedValues.values, "thread-title"),
|
|
@@ -21498,6 +21495,68 @@ function sanitizeObservationLines(observations) {
|
|
|
21498
21495
|
}
|
|
21499
21496
|
return changed ? lines.join("\n") : observations;
|
|
21500
21497
|
}
|
|
21498
|
+
const OBSERVATION_TIME = /^[*-]\s+(?:\S+\s+)?\((\d{1,2}):(\d{2})\s*([AaPp])?\.?(?:[Mm]\.?)?\)/;
|
|
21499
|
+
/** Minutes since midnight for each item, or undefined when the group's times can't be ordered safely. */
|
|
21500
|
+
function observationMinutes(items) {
|
|
21501
|
+
const times = items.map((item) => OBSERVATION_TIME.exec(item[0]));
|
|
21502
|
+
if (times.some((t) => !t)) return void 0;
|
|
21503
|
+
const matches = times;
|
|
21504
|
+
const marked = matches.filter((m) => m[3]).length;
|
|
21505
|
+
if (marked > 0 && marked < matches.length) return void 0;
|
|
21506
|
+
if (marked === matches.length) return matches.map((m) => Number(m[1]) % 12 * 60 + (/p/i.test(m[3]) ? 720 : 0) + Number(m[2]));
|
|
21507
|
+
const hours = matches.map((m) => m[1]);
|
|
21508
|
+
const twelveHour = hours.some((h) => /^[1-9]$/.test(h));
|
|
21509
|
+
const twentyFourHour = hours.some((h) => /^(0\d|1[3-9]|2\d)$/.test(h));
|
|
21510
|
+
if (twelveHour && (twentyFourHour || new Set(hours).size > 1)) return void 0;
|
|
21511
|
+
return matches.map((m) => Number(m[1]) * 60 + Number(m[2]));
|
|
21512
|
+
}
|
|
21513
|
+
/**
|
|
21514
|
+
* Put the top-level observations of each "Date:" group in time order, keeping indented
|
|
21515
|
+
* sub-items with their parent. The Observer writes a time on every observation but tends
|
|
21516
|
+
* to list a user's message ahead of the earlier events it follows, which can make a
|
|
21517
|
+
* superseded state read as the latest one. Groups are left as written when any
|
|
21518
|
+
* observation has no time or the times are ambiguous.
|
|
21519
|
+
*/
|
|
21520
|
+
function sortObservationsByTime(observations) {
|
|
21521
|
+
if (!observations) return observations;
|
|
21522
|
+
const output = [];
|
|
21523
|
+
let group;
|
|
21524
|
+
const flush = () => {
|
|
21525
|
+
if (!group) return;
|
|
21526
|
+
const minutes = group.sortable && group.items.length > 1 ? observationMinutes(group.items) : void 0;
|
|
21527
|
+
const items = minutes ? group.items.map((item, i) => ({
|
|
21528
|
+
item,
|
|
21529
|
+
minute: minutes[i]
|
|
21530
|
+
})).sort((a, b) => a.minute - b.minute).map((x) => x.item) : group.items;
|
|
21531
|
+
output.push(...group.header, ...items.flat(), ...group.trailing);
|
|
21532
|
+
group = void 0;
|
|
21533
|
+
};
|
|
21534
|
+
for (const line of observations.split("\n")) if (/^\s*Date:\s/.test(line)) {
|
|
21535
|
+
flush();
|
|
21536
|
+
group = {
|
|
21537
|
+
header: [line],
|
|
21538
|
+
items: [],
|
|
21539
|
+
trailing: [],
|
|
21540
|
+
sortable: true
|
|
21541
|
+
};
|
|
21542
|
+
} else if (!group) output.push(line);
|
|
21543
|
+
else if (/^[*-]\s/.test(line)) {
|
|
21544
|
+
group.items.at(-1)?.push(...group.trailing);
|
|
21545
|
+
group.trailing = [];
|
|
21546
|
+
group.items.push([line]);
|
|
21547
|
+
} else if (group.items.length === 0) group.header.push(line);
|
|
21548
|
+
else if (line.trim() === "") group.trailing.push(line);
|
|
21549
|
+
else if (/^\s/.test(line)) {
|
|
21550
|
+
group.items.at(-1).push(...group.trailing, line);
|
|
21551
|
+
group.trailing = [];
|
|
21552
|
+
} else {
|
|
21553
|
+
group.sortable = false;
|
|
21554
|
+
group.items.at(-1).push(...group.trailing, line);
|
|
21555
|
+
group.trailing = [];
|
|
21556
|
+
}
|
|
21557
|
+
flush();
|
|
21558
|
+
return output.join("\n");
|
|
21559
|
+
}
|
|
21501
21560
|
function analyzeDegenerateRepetition(text) {
|
|
21502
21561
|
const lines = collapseBoundedLineRuns(text.split("\n"));
|
|
21503
21562
|
const windowText = lines.filter((line) => line.trim().length >= MIN_DUPLICATE_LINE_CHARS && line.length <= MAX_OBSERVATION_LINE_CHARS).join("\n");
|
|
@@ -22040,7 +22099,7 @@ var ObserverRunner = class {
|
|
|
22040
22099
|
/**
|
|
22041
22100
|
* Call the Observer agent for multiple threads in a single batched request.
|
|
22042
22101
|
*/
|
|
22043
|
-
async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext, timeZone, mainAgent, currentModel) {
|
|
22102
|
+
async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext, timeZone, mainAgent, currentModel, wasTruncated) {
|
|
22044
22103
|
const contextFor = (threadId) => ({
|
|
22045
22104
|
threadId,
|
|
22046
22105
|
...hookContext
|
|
@@ -22051,7 +22110,7 @@ var ObserverRunner = class {
|
|
|
22051
22110
|
if (filtered.length > 0) messagesByThread.set(threadId, filtered);
|
|
22052
22111
|
}
|
|
22053
22112
|
const threadOrder = allThreadOrder.filter((threadId) => messagesByThread.has(threadId));
|
|
22054
|
-
const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel);
|
|
22113
|
+
const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel, wasTruncated);
|
|
22055
22114
|
for (const threadId of allThreadOrder) {
|
|
22056
22115
|
const threadResult = output.results.get(threadId);
|
|
22057
22116
|
if (!threadResult) {
|
|
@@ -22062,7 +22121,7 @@ var ObserverRunner = class {
|
|
|
22062
22121
|
}
|
|
22063
22122
|
return output;
|
|
22064
22123
|
}
|
|
22065
|
-
async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel) {
|
|
22124
|
+
async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone, mainAgent, currentModel, wasTruncated) {
|
|
22066
22125
|
if (threadOrder.length === 0) return { results: /* @__PURE__ */ new Map() };
|
|
22067
22126
|
const inputTokens = Array.from(messagesByThread.values()).reduce((total, messages) => total + this.tokenCounter.countMessages(messages), 0);
|
|
22068
22127
|
let resolvedModel;
|
|
@@ -22108,7 +22167,8 @@ var ObserverRunner = class {
|
|
|
22108
22167
|
priorThreadTitle: priorMetadataByThread?.get(threadId)?.threadTitle,
|
|
22109
22168
|
priorExtractedValues: priorMetadataByThread?.get(threadId)?.extracted,
|
|
22110
22169
|
model: resolvedModel.model,
|
|
22111
|
-
timeZone
|
|
22170
|
+
timeZone,
|
|
22171
|
+
wasTruncated
|
|
22112
22172
|
});
|
|
22113
22173
|
results.set(threadId, {
|
|
22114
22174
|
observations: threadResult.observations,
|
|
@@ -22134,7 +22194,7 @@ var ObserverRunner = class {
|
|
|
22134
22194
|
const agent = this.createAgent(resolvedModel.model, true, void 0, activeExtractors);
|
|
22135
22195
|
const internalRequestContext = withOmInternalThreadId(requestContext, agent.id);
|
|
22136
22196
|
const multiThreadAttachmentFilter = this.resolveAttachmentFilter(resolvedModel.model, requestContext);
|
|
22137
|
-
const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread,
|
|
22197
|
+
const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread, wasTruncated, this.observationConfig.threadTitle, activeExtractors, {
|
|
22138
22198
|
attachmentFilter: multiThreadAttachmentFilter,
|
|
22139
22199
|
timeZone
|
|
22140
22200
|
})];
|
|
@@ -22148,6 +22208,7 @@ var ObserverRunner = class {
|
|
|
22148
22208
|
metadata: {
|
|
22149
22209
|
omThreadCount: threadOrder.length,
|
|
22150
22210
|
omPreviousObserverTokens: this.observationConfig.previousObserverTokens,
|
|
22211
|
+
omWasTruncated: wasTruncated ?? false,
|
|
22151
22212
|
omThreadTitleEnabled: this.observationConfig.threadTitle,
|
|
22152
22213
|
...resolvedModel.selectedThreshold !== void 0 ? { omSelectedThreshold: resolvedModel.selectedThreshold } : {},
|
|
22153
22214
|
...resolvedModel.routingStrategy ? { omRoutingStrategy: resolvedModel.routingStrategy } : {},
|
|
@@ -26416,6 +26477,82 @@ function resolveAutoModelId(mainModelId, options) {
|
|
|
26416
26477
|
return pick ? `${prefix}${pick}` : mainModelId;
|
|
26417
26478
|
}
|
|
26418
26479
|
//#endregion
|
|
26480
|
+
//#region src/processors/observational-memory/commit-queue.ts
|
|
26481
|
+
/** How long an op may hold its key before later ops on that key stop waiting for it. */
|
|
26482
|
+
const OM_COMMIT_STALL_MS = 6e4;
|
|
26483
|
+
/** The per-thread / per-resource key shared by the commit queue and `BufferingCoordinator`. */
|
|
26484
|
+
function getOMLockKey(scope, threadId, resourceId) {
|
|
26485
|
+
if (scope === "resource" && resourceId) return `resource:${resourceId}`;
|
|
26486
|
+
return `thread:${threadId ?? "unknown"}`;
|
|
26487
|
+
}
|
|
26488
|
+
const REGISTRY = Symbol.for("@mastra/memory/om-commit-queue");
|
|
26489
|
+
const { queues, heldKeys } = globalThis[REGISTRY] ??= {
|
|
26490
|
+
queues: /* @__PURE__ */ new Map(),
|
|
26491
|
+
heldKeys: new AsyncLocalStorage()
|
|
26492
|
+
};
|
|
26493
|
+
function holdsKey(key) {
|
|
26494
|
+
const token = heldKeys.getStore()?.get(key);
|
|
26495
|
+
return token !== void 0 && queues.get(key)?.running === token;
|
|
26496
|
+
}
|
|
26497
|
+
function runNext(key, queue) {
|
|
26498
|
+
const next = queue.reflection.shift() ?? queue.normal.shift();
|
|
26499
|
+
if (!next) {
|
|
26500
|
+
queue.running = null;
|
|
26501
|
+
queues.delete(key);
|
|
26502
|
+
return;
|
|
26503
|
+
}
|
|
26504
|
+
next();
|
|
26505
|
+
}
|
|
26506
|
+
/**
|
|
26507
|
+
* Runs `op` once every earlier op on `key` has settled (reflection-priority ops first among those
|
|
26508
|
+
* still waiting) and resolves or rejects with its result. A rejection reaches only this caller.
|
|
26509
|
+
*
|
|
26510
|
+
* Enqueueing on a key from inside an op that holds the same key, directly or through nested ops
|
|
26511
|
+
* on other keys, would deadlock, so it rejects.
|
|
26512
|
+
*/
|
|
26513
|
+
function runOMCommit(key, op, { priority = "normal" } = {}) {
|
|
26514
|
+
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.`));
|
|
26515
|
+
return new Promise((resolve, reject) => {
|
|
26516
|
+
let queue = queues.get(key);
|
|
26517
|
+
if (!queue) {
|
|
26518
|
+
queue = {
|
|
26519
|
+
running: null,
|
|
26520
|
+
reflection: [],
|
|
26521
|
+
normal: []
|
|
26522
|
+
};
|
|
26523
|
+
queues.set(key, queue);
|
|
26524
|
+
}
|
|
26525
|
+
const owner = queue;
|
|
26526
|
+
const start = AsyncResource.bind(() => {
|
|
26527
|
+
const token = Symbol(key);
|
|
26528
|
+
owner.running = token;
|
|
26529
|
+
const held = new Map(heldKeys.getStore());
|
|
26530
|
+
held.set(key, token);
|
|
26531
|
+
let released = false;
|
|
26532
|
+
const release = () => {
|
|
26533
|
+
if (released) return;
|
|
26534
|
+
released = true;
|
|
26535
|
+
clearTimeout(stallTimer);
|
|
26536
|
+
runNext(key, owner);
|
|
26537
|
+
};
|
|
26538
|
+
const stallTimer = setTimeout(() => {
|
|
26539
|
+
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.`);
|
|
26540
|
+
release();
|
|
26541
|
+
}, OM_COMMIT_STALL_MS);
|
|
26542
|
+
stallTimer.unref?.();
|
|
26543
|
+
heldKeys.run(held, () => Promise.resolve().then(op)).then((value) => {
|
|
26544
|
+
release();
|
|
26545
|
+
resolve(value);
|
|
26546
|
+
}, (error) => {
|
|
26547
|
+
release();
|
|
26548
|
+
reject(error);
|
|
26549
|
+
});
|
|
26550
|
+
});
|
|
26551
|
+
(priority === "reflection" ? owner.reflection : owner.normal).push(start);
|
|
26552
|
+
if (owner.running === null) runNext(key, owner);
|
|
26553
|
+
});
|
|
26554
|
+
}
|
|
26555
|
+
//#endregion
|
|
26419
26556
|
//#region src/processors/observational-memory/operation-registry.ts
|
|
26420
26557
|
const activeOps = /* @__PURE__ */ new Map();
|
|
26421
26558
|
function opKey(recordId, op) {
|
|
@@ -26455,11 +26592,37 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26455
26592
|
*/
|
|
26456
26593
|
static asyncBufferingOps = /* @__PURE__ */ new Map();
|
|
26457
26594
|
/**
|
|
26595
|
+
* Observation buffer ops that may still append a buffered chunk.
|
|
26596
|
+
* Key format: "obs:{lockKey}"
|
|
26597
|
+
* Value: Promise that resolves once the op has persisted its chunk (or ended without one)
|
|
26598
|
+
*
|
|
26599
|
+
* Above the threshold→blockAfter band, activation waits (bounded) on this phase so the chunk
|
|
26600
|
+
* activates instead of being re-observed by the sync pass. It is not needed for safety: storage
|
|
26601
|
+
* keeps a chunk appended during activation. An op stays in asyncBufferingOps through its
|
|
26602
|
+
* post-persist work (indexing, thread title), which activation never waits for.
|
|
26603
|
+
*/
|
|
26604
|
+
static pendingChunkWrites = /* @__PURE__ */ new Map();
|
|
26605
|
+
/**
|
|
26458
26606
|
* Track the last token boundary at which we started buffering.
|
|
26459
26607
|
* Key format: "obs:{lockKey}" or "refl:{lockKey}"
|
|
26460
26608
|
*/
|
|
26461
26609
|
static lastBufferedBoundary = /* @__PURE__ */ new Map();
|
|
26462
26610
|
/**
|
|
26611
|
+
* The buffer op that set the observation boundary, if one did. Any other write (an activation
|
|
26612
|
+
* reset, another op) replaces or clears it, so an op that outlived it never writes its boundary
|
|
26613
|
+
* back. Key format: "obs:{lockKey}"
|
|
26614
|
+
*/
|
|
26615
|
+
static observationBoundaryOwners = /* @__PURE__ */ new Map();
|
|
26616
|
+
/** Set the observation boundary; `owner` is the buffer op setting it, if any. */
|
|
26617
|
+
static setObservationBoundary(bufferKey, tokens, owner) {
|
|
26618
|
+
BufferingCoordinator.lastBufferedBoundary.set(bufferKey, tokens);
|
|
26619
|
+
if (owner) BufferingCoordinator.observationBoundaryOwners.set(bufferKey, owner);
|
|
26620
|
+
else BufferingCoordinator.observationBoundaryOwners.delete(bufferKey);
|
|
26621
|
+
}
|
|
26622
|
+
static ownsObservationBoundary(bufferKey, owner) {
|
|
26623
|
+
return BufferingCoordinator.observationBoundaryOwners.get(bufferKey) === owner;
|
|
26624
|
+
}
|
|
26625
|
+
/**
|
|
26463
26626
|
* Track the timestamp cursor for buffered messages.
|
|
26464
26627
|
* Key format: "obs:{lockKey}"
|
|
26465
26628
|
*/
|
|
@@ -26475,8 +26638,7 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26475
26638
|
this.scope = opts.scope;
|
|
26476
26639
|
}
|
|
26477
26640
|
getLockKey(threadId, resourceId) {
|
|
26478
|
-
|
|
26479
|
-
return `thread:${threadId ?? "unknown"}`;
|
|
26641
|
+
return getOMLockKey(this.scope, threadId, resourceId);
|
|
26480
26642
|
}
|
|
26481
26643
|
isAsyncObservationEnabled() {
|
|
26482
26644
|
return this.observationConfig.bufferTokens !== void 0 && this.observationConfig.bufferTokens > 0;
|
|
@@ -26494,6 +26656,19 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26494
26656
|
return BufferingCoordinator.asyncBufferingOps.has(bufferKey);
|
|
26495
26657
|
}
|
|
26496
26658
|
/**
|
|
26659
|
+
* Mark an observation buffer op as able to append a chunk. Call the returned release
|
|
26660
|
+
* function once it no longer can; releasing more than once is a no-op.
|
|
26661
|
+
*/
|
|
26662
|
+
static trackChunkWrite(bufferKey) {
|
|
26663
|
+
let resolve;
|
|
26664
|
+
const pending = new Promise((r) => resolve = r);
|
|
26665
|
+
BufferingCoordinator.pendingChunkWrites.set(bufferKey, pending);
|
|
26666
|
+
return () => {
|
|
26667
|
+
if (BufferingCoordinator.pendingChunkWrites.get(bufferKey) === pending) BufferingCoordinator.pendingChunkWrites.delete(bufferKey);
|
|
26668
|
+
resolve();
|
|
26669
|
+
};
|
|
26670
|
+
}
|
|
26671
|
+
/**
|
|
26497
26672
|
* Clean up static maps for a thread/resource to prevent memory leaks.
|
|
26498
26673
|
*/
|
|
26499
26674
|
cleanupStaticMaps(threadId, resourceId, activatedMessageIds) {
|
|
@@ -26502,10 +26677,12 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26502
26677
|
const reflBufKey = this.getReflectionBufferKey(lockKey);
|
|
26503
26678
|
if (activatedMessageIds) {
|
|
26504
26679
|
BufferingCoordinator.lastBufferedBoundary.delete(obsBufKey);
|
|
26680
|
+
BufferingCoordinator.observationBoundaryOwners.delete(obsBufKey);
|
|
26505
26681
|
BufferingCoordinator.lastBufferedAtTime.delete(obsBufKey);
|
|
26506
26682
|
} else {
|
|
26507
26683
|
BufferingCoordinator.lastBufferedAtTime.delete(obsBufKey);
|
|
26508
26684
|
BufferingCoordinator.lastBufferedBoundary.delete(obsBufKey);
|
|
26685
|
+
BufferingCoordinator.observationBoundaryOwners.delete(obsBufKey);
|
|
26509
26686
|
BufferingCoordinator.lastBufferedBoundary.delete(reflBufKey);
|
|
26510
26687
|
BufferingCoordinator.asyncBufferingOps.delete(obsBufKey);
|
|
26511
26688
|
BufferingCoordinator.asyncBufferingOps.delete(reflBufKey);
|
|
@@ -26520,7 +26697,7 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26520
26697
|
if (record.isBufferingObservation) {
|
|
26521
26698
|
if (isOpActiveInProcess(record.id, "bufferingObservation")) return false;
|
|
26522
26699
|
omDebug(`[OM:shouldTriggerAsyncObs] isBufferingObservation=true but stale, clearing`);
|
|
26523
|
-
storage
|
|
26700
|
+
if (storage) runOMCommit(lockKey, () => storage.setBufferingObservationFlag(record.id, false)).catch(() => {});
|
|
26524
26701
|
}
|
|
26525
26702
|
const bufferKey = this.getObservationBufferKey(lockKey);
|
|
26526
26703
|
if (this.isAsyncBufferingInProgress(bufferKey)) return false;
|
|
@@ -26540,7 +26717,7 @@ var BufferingCoordinator = class BufferingCoordinator {
|
|
|
26540
26717
|
* Await any in-flight async buffering operations for a given thread/resource.
|
|
26541
26718
|
*/
|
|
26542
26719
|
static async awaitBuffering(threadId, resourceId, scope, timeoutMs = 3e4) {
|
|
26543
|
-
const lockKey = scope
|
|
26720
|
+
const lockKey = getOMLockKey(scope, threadId, resourceId);
|
|
26544
26721
|
const obsKey = `obs:${lockKey}`;
|
|
26545
26722
|
const reflKey = `refl:${lockKey}`;
|
|
26546
26723
|
const promises = [];
|
|
@@ -26838,6 +27015,36 @@ function createThreadUpdateMarker(params) {
|
|
|
26838
27015
|
}
|
|
26839
27016
|
};
|
|
26840
27017
|
}
|
|
27018
|
+
//#endregion
|
|
27019
|
+
//#region src/processors/observational-memory/record-lineage.ts
|
|
27020
|
+
/**
|
|
27021
|
+
* The current head of `origin`'s lineage (the generations reflections have created from it),
|
|
27022
|
+
* or `null` when that lineage is gone.
|
|
27023
|
+
*
|
|
27024
|
+
* Use this instead of reading the head by thread/resource when committing work that started
|
|
27025
|
+
* from `origin`. Clearing a record (`om.clear`, `Memory.deleteThread`) deletes every generation
|
|
27026
|
+
* of the thread/resource, and the thread can then get a new, unrelated record. Work produced
|
|
27027
|
+
* from the cleared messages must never be committed to it.
|
|
27028
|
+
*
|
|
27029
|
+
* Generation ids are never reused and clearing deletes all of them, so the head belongs to
|
|
27030
|
+
* `origin`'s lineage iff `origin` still exists.
|
|
27031
|
+
*/
|
|
27032
|
+
async function getLineageHead(storage, origin) {
|
|
27033
|
+
const head = await storage.getObservationalMemory(origin.threadId, origin.resourceId);
|
|
27034
|
+
if (!head || head.id === origin.id) return head;
|
|
27035
|
+
if (head.generationCount <= origin.generationCount) return null;
|
|
27036
|
+
return (await storage.getObservationalMemoryHistory(origin.threadId, origin.resourceId, head.generationCount - origin.generationCount + 3)).some((record) => record.id === origin.id) ? head : null;
|
|
27037
|
+
}
|
|
27038
|
+
/**
|
|
27039
|
+
* The generation `id` of `origin`'s lineage, or `null` when that lineage is gone or `id` is not
|
|
27040
|
+
* part of it. Unlike {@link getLineageHead}, this keeps naming `id` after later reflections have
|
|
27041
|
+
* superseded it, e.g. to report which generation a write landed on.
|
|
27042
|
+
*/
|
|
27043
|
+
async function getLineageRecord(storage, origin, id) {
|
|
27044
|
+
const head = await getLineageHead(storage, origin);
|
|
27045
|
+
if (!head || head.id === id) return head;
|
|
27046
|
+
return (await storage.getObservationalMemoryHistory(origin.threadId, origin.resourceId, head.generationCount - origin.generationCount + 3)).find((record) => record.id === id) ?? null;
|
|
27047
|
+
}
|
|
26841
27048
|
function isRecord$1(value) {
|
|
26842
27049
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
26843
27050
|
}
|
|
@@ -26984,6 +27191,39 @@ function calculateProjectedMessageRemoval(chunks, bufferActivation, messageToken
|
|
|
26984
27191
|
//#region src/processors/observational-memory/observation-strategies/base.ts
|
|
26985
27192
|
/** Module-level xxhash singleton — loaded once, shared across all strategy instances. */
|
|
26986
27193
|
const hasherPromise = xxhash();
|
|
27194
|
+
/** Recompose-and-retry rounds for an observation commit that hit a retired or changed head. */
|
|
27195
|
+
const MAX_HEAD_COMMIT_RETRIES = 3;
|
|
27196
|
+
const OBSERVATION_LIFECYCLE_MARKERS = /* @__PURE__ */ new Set([
|
|
27197
|
+
"data-om-observation-start",
|
|
27198
|
+
"data-om-observation-end",
|
|
27199
|
+
"data-om-observation-failed"
|
|
27200
|
+
]);
|
|
27201
|
+
function isOmPart(part) {
|
|
27202
|
+
return String(part?.type ?? "").startsWith("data-om-");
|
|
27203
|
+
}
|
|
27204
|
+
/**
|
|
27205
|
+
* Index in `message` right after this cycle's start marker (and any OM parts directly after it),
|
|
27206
|
+
* or -1 when the message doesn't carry it.
|
|
27207
|
+
*/
|
|
27208
|
+
function afterOwnStartMarker(message, marker) {
|
|
27209
|
+
const cycleId = marker.data?.cycleId;
|
|
27210
|
+
const startType = marker.type.startsWith("data-om-buffering-") ? "data-om-buffering-start" : "data-om-observation-start";
|
|
27211
|
+
if (!cycleId || marker.type === startType) return -1;
|
|
27212
|
+
const parts = message.content?.parts ?? [];
|
|
27213
|
+
const start = parts.findIndex((part) => part?.type === startType && part?.data?.cycleId === cycleId);
|
|
27214
|
+
if (start === -1) return -1;
|
|
27215
|
+
let index = start + 1;
|
|
27216
|
+
while (index < parts.length && isOmPart(parts[index])) index++;
|
|
27217
|
+
return index;
|
|
27218
|
+
}
|
|
27219
|
+
/** True when the message has parts no completed observation boundary covers (OM markers aside). */
|
|
27220
|
+
function hasUnobservedContent(message) {
|
|
27221
|
+
return getUnobservedParts(message).some((part) => !isOmPart(part));
|
|
27222
|
+
}
|
|
27223
|
+
function isObservationLifecycleMarker(marker) {
|
|
27224
|
+
const operationType = marker.data?.operationType;
|
|
27225
|
+
return OBSERVATION_LIFECYCLE_MARKERS.has(marker.type) && operationType !== "reflection";
|
|
27226
|
+
}
|
|
26987
27227
|
/**
|
|
26988
27228
|
* Abstract base class for observation strategies.
|
|
26989
27229
|
*
|
|
@@ -27033,17 +27273,35 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
27033
27273
|
const fresh = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
27034
27274
|
if (fresh?.lastObservedAt && record.lastObservedAt && fresh.lastObservedAt > record.lastObservedAt) return { observed: false };
|
|
27035
27275
|
}
|
|
27036
|
-
const { messages, existingObservations } = await this.prepare();
|
|
27276
|
+
const { messages, existingObservations, contextRecord } = await this.prepare();
|
|
27277
|
+
if (messages.length === 0) return { observed: false };
|
|
27037
27278
|
const observationMessages = stripSubconsciousSignals(messages);
|
|
27038
27279
|
await this.emitStartMarkers(cycleId);
|
|
27039
|
-
const
|
|
27040
|
-
const
|
|
27041
|
-
|
|
27280
|
+
const observerContext = this.deps.prepareObserverContext(existingObservations, contextRecord ?? record);
|
|
27281
|
+
const output = await this.observe(observerContext.context ?? "", observationMessages, observerContext.wasTruncated);
|
|
27282
|
+
let processed = await this.process(output, existingObservations);
|
|
27283
|
+
let committedRecord = record;
|
|
27284
|
+
const outcome = await this.persist(processed);
|
|
27285
|
+
this.settleObservationCommit(outcome?.status === "committed" && !outcome.alreadyCovered);
|
|
27286
|
+
if (outcome?.status === "not-committed") {
|
|
27287
|
+
omDebug(`[OM:observe] cycle ${cycleId} not committed: ${outcome.reason}`);
|
|
27288
|
+
await this.emitFailedMarkers(cycleId, /* @__PURE__ */ new Error(`Observation not committed: ${outcome.reason}`));
|
|
27289
|
+
return {
|
|
27290
|
+
observed: false,
|
|
27291
|
+
usage: output.usage,
|
|
27292
|
+
providerMetadata: output.providerMetadata
|
|
27293
|
+
};
|
|
27294
|
+
}
|
|
27295
|
+
if (outcome?.status === "committed") {
|
|
27296
|
+
processed = outcome.processed;
|
|
27297
|
+
committedRecord = outcome.record;
|
|
27298
|
+
}
|
|
27042
27299
|
await this.emitEndMarkers(cycleId, processed);
|
|
27043
27300
|
if (this.needsReflection) await this.deps.reflector.maybeReflect({
|
|
27044
27301
|
record: {
|
|
27045
|
-
...
|
|
27046
|
-
activeObservations: processed.observations
|
|
27302
|
+
...committedRecord,
|
|
27303
|
+
activeObservations: processed.observations,
|
|
27304
|
+
observationTokenCount: processed.observationTokens
|
|
27047
27305
|
},
|
|
27048
27306
|
observationTokens: processed.observationTokens,
|
|
27049
27307
|
threadId,
|
|
@@ -27079,7 +27337,8 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
27079
27337
|
threadId
|
|
27080
27338
|
}
|
|
27081
27339
|
};
|
|
27082
|
-
|
|
27340
|
+
const observed = this.getObservedMessagesForThread(threadId);
|
|
27341
|
+
await (observed?.length ? this.persistMarkerToObservedMessage(failedMarkerForStorage, observed, threadId, this.opts.resourceId) : this.persistMarkerToStorage(failedMarkerForStorage, threadId, this.opts.resourceId)).catch(() => {});
|
|
27083
27342
|
if (abortSignal?.aborted) throw error;
|
|
27084
27343
|
omError("[OM] Observation failed", error);
|
|
27085
27344
|
return {
|
|
@@ -27106,6 +27365,11 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
27106
27365
|
transient: true
|
|
27107
27366
|
}).catch(() => {});
|
|
27108
27367
|
const markerThreadId = marker.data?.threadId ?? this.opts.threadId;
|
|
27368
|
+
const observed = isObservationLifecycleMarker(marker) ? this.getObservedMessagesForThread(markerThreadId) : void 0;
|
|
27369
|
+
if (observed?.length) {
|
|
27370
|
+
await this.persistMarkerToObservedMessage(marker, observed, markerThreadId, this.opts.resourceId);
|
|
27371
|
+
return;
|
|
27372
|
+
}
|
|
27109
27373
|
if (!await this.persistMarkerToMessage(marker, this.opts.messageList, markerThreadId, this.opts.resourceId)) await this.persistMarkerToStorage(marker, markerThreadId, this.opts.resourceId);
|
|
27110
27374
|
}
|
|
27111
27375
|
getObservationMarkerConfig() {
|
|
@@ -27189,6 +27453,76 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
27189
27453
|
}
|
|
27190
27454
|
return `${existingObservations}${lastObservedAt ? ObservationStrategy.createMessageBoundary(lastObservedAt) : "\n\n"}${newThreadSection}`;
|
|
27191
27455
|
}
|
|
27456
|
+
/**
|
|
27457
|
+
* Commit composed observations to the current head generation.
|
|
27458
|
+
*
|
|
27459
|
+
* The write is conditional on the head text the observations were composed from. When the
|
|
27460
|
+
* target generation was retired by a reflection, or another writer changed the text, the
|
|
27461
|
+
* observations are recomposed against the fresh head and the commit is retried (bounded).
|
|
27462
|
+
* When the fresh head's `observedMessageIds` already include every message this cycle observed
|
|
27463
|
+
* (another instance observed them first), nothing is written and `alreadyCovered` is set, so
|
|
27464
|
+
* callers advance cursors and markers without adding a second copy of the same observations.
|
|
27465
|
+
* The cursor alone is not proof: an activation can move it past a message it never covered.
|
|
27466
|
+
* Returns null when no commit landed — callers must then leave cursors, markers, and the live
|
|
27467
|
+
* context untouched.
|
|
27468
|
+
*/
|
|
27469
|
+
async commitActiveObservationsToHead(opts) {
|
|
27470
|
+
let { processed, composedFrom, target } = opts;
|
|
27471
|
+
for (let attempt = 0; attempt <= MAX_HEAD_COMMIT_RETRIES; attempt++) {
|
|
27472
|
+
const result = await this.runCommit(async () => {
|
|
27473
|
+
const head = await getLineageHead(this.storage, target);
|
|
27474
|
+
if (!head) return null;
|
|
27475
|
+
if (head.id !== target.id || (head.activeObservations ?? "") !== composedFrom) {
|
|
27476
|
+
if (this.headCoversCycle(head, opts.cycleMessageIds)) {
|
|
27477
|
+
omDebug(`[OM:observe] head ${head.id} already covers this cycle's messages; skipping the duplicate commit`);
|
|
27478
|
+
target = head;
|
|
27479
|
+
return {
|
|
27480
|
+
applied: true,
|
|
27481
|
+
alreadyCovered: true
|
|
27482
|
+
};
|
|
27483
|
+
}
|
|
27484
|
+
target = head;
|
|
27485
|
+
composedFrom = head.activeObservations ?? "";
|
|
27486
|
+
processed = await opts.recompose(head);
|
|
27487
|
+
}
|
|
27488
|
+
const input = {
|
|
27489
|
+
id: target.id,
|
|
27490
|
+
observations: processed.observations,
|
|
27491
|
+
tokenCount: processed.observationTokens,
|
|
27492
|
+
lastObservedAt: processed.lastObservedAt,
|
|
27493
|
+
observedMessageIds: processed.observedMessageIds,
|
|
27494
|
+
expectedActiveObservations: composedFrom
|
|
27495
|
+
};
|
|
27496
|
+
return typeof this.storage.commitActiveObservations === "function" ? await this.storage.commitActiveObservations(input) : (await this.storage.updateActiveObservations(input), { applied: true });
|
|
27497
|
+
});
|
|
27498
|
+
if (!result) return null;
|
|
27499
|
+
if ("alreadyCovered" in result) return {
|
|
27500
|
+
processed: {
|
|
27501
|
+
...processed,
|
|
27502
|
+
observations: target.activeObservations ?? "",
|
|
27503
|
+
observationTokens: target.observationTokenCount
|
|
27504
|
+
},
|
|
27505
|
+
record: target,
|
|
27506
|
+
alreadyCovered: true
|
|
27507
|
+
};
|
|
27508
|
+
if (result.applied) return {
|
|
27509
|
+
processed,
|
|
27510
|
+
record: target
|
|
27511
|
+
};
|
|
27512
|
+
omDebug(`[OM:observe] commit to ${target.id} not applied (${result.reason}); retrying against the head`);
|
|
27513
|
+
}
|
|
27514
|
+
return null;
|
|
27515
|
+
}
|
|
27516
|
+
headCoversCycle(head, cycleMessageIds) {
|
|
27517
|
+
if (cycleMessageIds.length === 0) return false;
|
|
27518
|
+
const headIds = new Set(Array.isArray(head.observedMessageIds) ? head.observedMessageIds : []);
|
|
27519
|
+
return cycleMessageIds.every((id) => headIds.has(id));
|
|
27520
|
+
}
|
|
27521
|
+
/** Runs one storage commit in this thread's (or resource's) slot of the process-wide commit queue. */
|
|
27522
|
+
runCommit(op) {
|
|
27523
|
+
const { threadId, resourceId, record } = this.opts;
|
|
27524
|
+
return runOMCommit(getOMLockKey(this.scope, record.threadId ?? threadId, record.resourceId ?? resourceId), op);
|
|
27525
|
+
}
|
|
27192
27526
|
async indexObservationGroups(observations, threadId, resourceId, observedAt, recordId) {
|
|
27193
27527
|
if (!resourceId || !this.deps.onIndexObservations) return;
|
|
27194
27528
|
const groups = parseObservationGroups(observations);
|
|
@@ -27207,30 +27541,133 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
27207
27541
|
})));
|
|
27208
27542
|
}
|
|
27209
27543
|
/**
|
|
27544
|
+
* Messages this cycle observed for `threadId`, or undefined when the strategy does not
|
|
27545
|
+
* place observation lifecycle markers.
|
|
27546
|
+
*/
|
|
27547
|
+
getObservedMessagesForThread(_threadId) {}
|
|
27548
|
+
/**
|
|
27549
|
+
* Persist an observation start/end/failed marker without hiding unobserved content. A completed
|
|
27550
|
+
* end marker tells `getUnobservedMessages` that everything before it in that message is observed
|
|
27551
|
+
* (whatever thread the marker names), so it must only follow content this cycle observed.
|
|
27552
|
+
* Placement, in order:
|
|
27553
|
+
* 1. the live MessageList's newest assistant message (this turn's response or its seed), when the
|
|
27554
|
+
* list belongs to the marker's thread and the cycle observed that message or it has no
|
|
27555
|
+
* unobserved content;
|
|
27556
|
+
* 2. the newest observed assistant message; a stored copy (no live list, or another instance's
|
|
27557
|
+
* message) gets the marker right after the parts this cycle observed (end/failed markers right
|
|
27558
|
+
* after this cycle's start marker), so parts added while the Observer ran stay unobserved;
|
|
27559
|
+
* 3. the newest stored assistant message that is not newer than the observed range, only when it
|
|
27560
|
+
* has nothing unobserved (or already carries this cycle's start marker); otherwise no marker,
|
|
27561
|
+
* and the thread cursor alone records the cycle.
|
|
27562
|
+
* Lifecycle markers never go on user messages.
|
|
27563
|
+
*/
|
|
27564
|
+
async persistMarkerToObservedMessage(marker, observed, threadId, resourceId) {
|
|
27565
|
+
const observedIds = new Set(observed.map((m) => m.id));
|
|
27566
|
+
const list = threadId === this.opts.threadId ? this.opts.messageList : void 0;
|
|
27567
|
+
const listMessages = list ? getObservableMessages(list) : [];
|
|
27568
|
+
const live = [...listMessages].reverse().find((m) => m.role === "assistant");
|
|
27569
|
+
if (live && (observedIds.has(live.id) || !hasUnobservedContent(live))) {
|
|
27570
|
+
await this.attachMarker(marker, live, live.content.parts.length, threadId, resourceId);
|
|
27571
|
+
return;
|
|
27572
|
+
}
|
|
27573
|
+
const target = [...observed].reverse().find((m) => m.role === "assistant");
|
|
27574
|
+
if (target) {
|
|
27575
|
+
const fromList = listMessages.find((m) => m.id === target.id);
|
|
27576
|
+
if (fromList) {
|
|
27577
|
+
await this.attachMarker(marker, fromList, fromList.content.parts.length, threadId, resourceId);
|
|
27578
|
+
return;
|
|
27579
|
+
}
|
|
27580
|
+
try {
|
|
27581
|
+
const stored = (await this.storage.listMessagesById({ messageIds: [target.id] })).messages[0];
|
|
27582
|
+
if (stored?.content?.parts && Array.isArray(stored.content.parts)) {
|
|
27583
|
+
const afterStart = afterOwnStartMarker(stored, marker);
|
|
27584
|
+
const index = afterStart !== -1 ? afterStart : this.insertionAfterObserved(stored, target);
|
|
27585
|
+
await this.attachMarker(marker, stored, index, threadId, resourceId);
|
|
27586
|
+
}
|
|
27587
|
+
} catch (e) {
|
|
27588
|
+
omDebug(`[OM:persistMarkerToObservedMessage] failed to load marker target: ${e}`);
|
|
27589
|
+
}
|
|
27590
|
+
return;
|
|
27591
|
+
}
|
|
27592
|
+
const observedTimes = observed.filter((m) => m.createdAt).map((m) => new Date(m.createdAt).getTime());
|
|
27593
|
+
if (observedTimes.length === 0) return;
|
|
27594
|
+
await this.persistMarkerToStorage(marker, threadId, resourceId, {
|
|
27595
|
+
notAfter: new Date(Math.max(...observedTimes)),
|
|
27596
|
+
onlyIfObserved: true
|
|
27597
|
+
});
|
|
27598
|
+
}
|
|
27599
|
+
/** What the cycle observed of each message, captured when it placed its first marker there. */
|
|
27600
|
+
observedParts = /* @__PURE__ */ new Map();
|
|
27601
|
+
/**
|
|
27602
|
+
* Index in `stored` right after the parts the cycle observed in `observed` (and any OM markers
|
|
27603
|
+
* directly after them). Parts are only ever appended, so the observed message's non-marker part
|
|
27604
|
+
* count locates them, counted from the start of the message or, when `observed` is the trimmed
|
|
27605
|
+
* copy `getUnobservedMessages` returns for an already marked message, from just after the stored
|
|
27606
|
+
* copy's last completed boundary. Captured at the cycle's first marker, before the Observer runs,
|
|
27607
|
+
* because `observed` may be a live reference that grows meanwhile. Falls back to the end when the
|
|
27608
|
+
* stored copy has fewer parts than were observed.
|
|
27609
|
+
*/
|
|
27610
|
+
insertionAfterObserved(stored, observed) {
|
|
27611
|
+
if (!this.observedParts.has(observed.id)) this.observedParts.set(observed.id, {
|
|
27612
|
+
count: (observed.content?.parts ?? []).filter((part) => !isOmPart(part)).length,
|
|
27613
|
+
observedIsTrimmed: findLastCompletedObservationBoundary(observed) === -1
|
|
27614
|
+
});
|
|
27615
|
+
const { count, observedIsTrimmed } = this.observedParts.get(observed.id);
|
|
27616
|
+
const parts = stored.content.parts;
|
|
27617
|
+
let index = observedIsTrimmed ? findLastCompletedObservationBoundary(stored) + 1 : 0;
|
|
27618
|
+
let seen = 0;
|
|
27619
|
+
while (index < parts.length && seen < count) {
|
|
27620
|
+
if (!isOmPart(parts[index])) seen++;
|
|
27621
|
+
index++;
|
|
27622
|
+
}
|
|
27623
|
+
if (seen < count) return parts.length;
|
|
27624
|
+
while (index < parts.length && isOmPart(parts[index])) index++;
|
|
27625
|
+
return index;
|
|
27626
|
+
}
|
|
27627
|
+
async attachMarker(marker, msg, index, threadId, resourceId) {
|
|
27628
|
+
const parts = msg.content?.parts;
|
|
27629
|
+
if (!parts || !Array.isArray(parts)) return;
|
|
27630
|
+
const markerData = marker.data;
|
|
27631
|
+
if (!(markerData?.cycleId && parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) parts.splice(index, 0, marker);
|
|
27632
|
+
try {
|
|
27633
|
+
await this.messageHistory.persistMessages({
|
|
27634
|
+
messages: [msg],
|
|
27635
|
+
threadId,
|
|
27636
|
+
resourceId
|
|
27637
|
+
});
|
|
27638
|
+
} catch (e) {
|
|
27639
|
+
omDebug(`[OM:persistMarkerToObservedMessage] failed to save marker to DB: ${e}`);
|
|
27640
|
+
}
|
|
27641
|
+
}
|
|
27642
|
+
/**
|
|
27210
27643
|
* Persist a marker to the last assistant message in storage.
|
|
27211
27644
|
* Fetches messages directly from the DB so it works even when
|
|
27212
27645
|
* no MessageList is available (e.g. async buffering ops).
|
|
27213
27646
|
*/
|
|
27214
|
-
async persistMarkerToStorage(marker, threadId, resourceId) {
|
|
27647
|
+
async persistMarkerToStorage(marker, threadId, resourceId, opts) {
|
|
27215
27648
|
try {
|
|
27216
|
-
const
|
|
27649
|
+
const assistants = ((await this.storage.listMessages({
|
|
27217
27650
|
threadId,
|
|
27218
27651
|
perPage: 20,
|
|
27219
27652
|
orderBy: {
|
|
27220
27653
|
field: "createdAt",
|
|
27221
27654
|
direction: "DESC"
|
|
27222
|
-
}
|
|
27223
|
-
|
|
27224
|
-
|
|
27225
|
-
|
|
27226
|
-
|
|
27227
|
-
|
|
27228
|
-
|
|
27229
|
-
|
|
27230
|
-
|
|
27231
|
-
|
|
27232
|
-
|
|
27233
|
-
|
|
27655
|
+
},
|
|
27656
|
+
...opts?.notAfter ? { filter: { dateRange: { end: opts.notAfter } } } : {}
|
|
27657
|
+
}))?.messages ?? []).filter((msg) => msg?.role === "assistant" && Array.isArray(msg.content?.parts));
|
|
27658
|
+
const startMessage = opts?.onlyIfObserved || opts?.onCycleStartMessage ? assistants.find((m) => afterOwnStartMarker(m, marker) !== -1) : void 0;
|
|
27659
|
+
const msg = opts?.onCycleStartMessage ? startMessage : startMessage ?? assistants[0];
|
|
27660
|
+
if (!msg) return;
|
|
27661
|
+
const markerData = marker.data;
|
|
27662
|
+
const alreadyPresent = markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId);
|
|
27663
|
+
const afterStart = afterOwnStartMarker(msg, marker);
|
|
27664
|
+
if (opts?.onlyIfObserved && afterStart === -1 && hasUnobservedContent(msg)) return;
|
|
27665
|
+
if (!alreadyPresent) msg.content.parts.splice(afterStart !== -1 ? afterStart : msg.content.parts.length, 0, marker);
|
|
27666
|
+
await this.messageHistory.persistMessages({
|
|
27667
|
+
messages: [msg],
|
|
27668
|
+
threadId,
|
|
27669
|
+
resourceId
|
|
27670
|
+
});
|
|
27234
27671
|
} catch (e) {
|
|
27235
27672
|
omDebug(`[OM:persistMarkerToStorage] failed to save marker to DB: ${e}`);
|
|
27236
27673
|
}
|
|
@@ -27292,10 +27729,15 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27292
27729
|
tokensToObserve = 0;
|
|
27293
27730
|
observerResult;
|
|
27294
27731
|
priorExtractedValues;
|
|
27732
|
+
/** Head text the processed observations were composed from (the commit's expected text). */
|
|
27733
|
+
composedFrom = "";
|
|
27295
27734
|
constructor(deps, opts) {
|
|
27296
27735
|
super(deps, opts);
|
|
27297
27736
|
this.lastMessage = opts.messages[opts.messages.length - 1];
|
|
27298
27737
|
}
|
|
27738
|
+
getObservedMessagesForThread(threadId) {
|
|
27739
|
+
return threadId === this.opts.threadId ? this.opts.messages : void 0;
|
|
27740
|
+
}
|
|
27299
27741
|
get needsLock() {
|
|
27300
27742
|
return true;
|
|
27301
27743
|
}
|
|
@@ -27330,9 +27772,11 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27330
27772
|
}
|
|
27331
27773
|
}
|
|
27332
27774
|
this.tokensToObserve = await this.tokenCounter.countMessagesAsync(messages);
|
|
27775
|
+
const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
27333
27776
|
return {
|
|
27334
27777
|
messages,
|
|
27335
|
-
existingObservations:
|
|
27778
|
+
existingObservations: freshRecord?.activeObservations ?? record.activeObservations ?? "",
|
|
27779
|
+
contextRecord: freshRecord
|
|
27336
27780
|
};
|
|
27337
27781
|
}
|
|
27338
27782
|
async emitStartMarkers(cycleId) {
|
|
@@ -27350,11 +27794,12 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27350
27794
|
await this.streamMarker(startMarker);
|
|
27351
27795
|
}
|
|
27352
27796
|
}
|
|
27353
|
-
async observe(
|
|
27797
|
+
async observe(observerContext, messages, wasTruncated) {
|
|
27354
27798
|
const thread = await this.storage.getThreadById({ threadId: this.opts.threadId });
|
|
27355
27799
|
const omMeta = thread ? getThreadOMMetadata(thread.metadata) : void 0;
|
|
27356
27800
|
this.priorExtractedValues = getPriorExtractedValues(omMeta, this.observationConfig.extractors);
|
|
27357
|
-
const result = await this.deps.observer.call(
|
|
27801
|
+
const result = await this.deps.observer.call(observerContext, messages, this.opts.abortSignal, {
|
|
27802
|
+
wasTruncated,
|
|
27358
27803
|
requestContext: this.opts.requestContext,
|
|
27359
27804
|
observabilityContext: this.opts.observabilityContext,
|
|
27360
27805
|
priorCurrentTask: omMeta?.currentTask,
|
|
@@ -27400,20 +27845,14 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27400
27845
|
}
|
|
27401
27846
|
async process(output, existingObservations) {
|
|
27402
27847
|
const { record, threadId, messages } = this.opts;
|
|
27403
|
-
|
|
27404
|
-
const
|
|
27405
|
-
const newObservations = await this.wrapObservations(output.observations, existingObservations, threadId, lastObservedAt, messageRange);
|
|
27406
|
-
const observationTokens = this.tokenCounter.countObservations(newObservations);
|
|
27407
|
-
const cycleObservationTokens = this.tokenCounter.countObservations(output.observations);
|
|
27408
|
-
const newMessageIds = messages.map((m) => m.id);
|
|
27409
|
-
const existingIds = record.observedMessageIds ?? [];
|
|
27410
|
-
const observedMessageIds = [.../* @__PURE__ */ new Set([...Array.isArray(existingIds) ? existingIds : [], ...newMessageIds])];
|
|
27848
|
+
this.composedFrom = existingObservations;
|
|
27849
|
+
const processed = await this.compose(output, existingObservations, record);
|
|
27411
27850
|
this.deps.emitDebugEvent({
|
|
27412
27851
|
type: "observation_complete",
|
|
27413
27852
|
timestamp: /* @__PURE__ */ new Date(),
|
|
27414
27853
|
threadId,
|
|
27415
27854
|
resourceId: record.resourceId ?? "",
|
|
27416
|
-
observations:
|
|
27855
|
+
observations: processed.observations,
|
|
27417
27856
|
rawObserverOutput: output.observations,
|
|
27418
27857
|
previousObservations: record.activeObservations,
|
|
27419
27858
|
messages: messages.map((m) => ({
|
|
@@ -27422,11 +27861,23 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27422
27861
|
})),
|
|
27423
27862
|
usage: output.usage
|
|
27424
27863
|
});
|
|
27864
|
+
return processed;
|
|
27865
|
+
}
|
|
27866
|
+
/** Build the observations to commit on top of `existingObservations` (the head text). */
|
|
27867
|
+
async compose(output, existingObservations, baseRecord) {
|
|
27868
|
+
const { threadId, messages } = this.opts;
|
|
27869
|
+
const lastObservedAt = this.getMaxMessageTimestamp(messages);
|
|
27870
|
+
const messageRange = this.retrieval ? buildMessageRange(messages) : void 0;
|
|
27871
|
+
const newObservations = await this.wrapObservations(output.observations, existingObservations, threadId, lastObservedAt, messageRange);
|
|
27872
|
+
const observationTokens = this.tokenCounter.countObservations(newObservations);
|
|
27873
|
+
const cycleObservationTokens = this.tokenCounter.countObservations(output.observations);
|
|
27874
|
+
const newMessageIds = messages.map((m) => m.id);
|
|
27875
|
+
const existingIds = baseRecord.observedMessageIds ?? [];
|
|
27425
27876
|
return {
|
|
27426
27877
|
observations: newObservations,
|
|
27427
27878
|
observationTokens,
|
|
27428
27879
|
cycleObservationTokens,
|
|
27429
|
-
observedMessageIds,
|
|
27880
|
+
observedMessageIds: [.../* @__PURE__ */ new Set([...Array.isArray(existingIds) ? existingIds : [], ...newMessageIds])],
|
|
27430
27881
|
lastObservedAt,
|
|
27431
27882
|
suggestedContinuation: output.suggestedContinuation,
|
|
27432
27883
|
currentTask: output.currentTask,
|
|
@@ -27438,12 +27889,27 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27438
27889
|
}
|
|
27439
27890
|
async persist(processed) {
|
|
27440
27891
|
const { record, threadId, resourceId, messages } = this.opts;
|
|
27441
|
-
const
|
|
27442
|
-
if (!
|
|
27892
|
+
const liveRecord = await getLineageHead(this.storage, record);
|
|
27893
|
+
if (!liveRecord) {
|
|
27443
27894
|
omDebug(`[OM:sync-obs] skipping persist for thread ${threadId}: observational memory record is gone`);
|
|
27444
|
-
return
|
|
27895
|
+
return {
|
|
27896
|
+
status: "not-committed",
|
|
27897
|
+
reason: "the observational memory record was cleared"
|
|
27898
|
+
};
|
|
27445
27899
|
}
|
|
27446
|
-
|
|
27900
|
+
const committed = await this.commitActiveObservationsToHead({
|
|
27901
|
+
processed,
|
|
27902
|
+
composedFrom: this.composedFrom,
|
|
27903
|
+
target: liveRecord,
|
|
27904
|
+
recompose: (head) => this.compose(this.observerResult, head.activeObservations ?? "", head),
|
|
27905
|
+
cycleMessageIds: messages.map((m) => m.id)
|
|
27906
|
+
});
|
|
27907
|
+
if (!committed) return {
|
|
27908
|
+
status: "not-committed",
|
|
27909
|
+
reason: "the observational memory head kept changing during the commit"
|
|
27910
|
+
};
|
|
27911
|
+
processed = committed.processed;
|
|
27912
|
+
const thread = committed.alreadyCovered ? null : await this.storage.getThreadById({ threadId });
|
|
27447
27913
|
if (thread) {
|
|
27448
27914
|
const oldTitle = thread.title?.trim();
|
|
27449
27915
|
const newTitle = resolveThreadTitleUpdate(thread, processed.threadTitle);
|
|
@@ -27465,23 +27931,20 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
27465
27931
|
...shouldUpdateThreadTitle ? { title: newTitle } : {},
|
|
27466
27932
|
metadata: newMetadata
|
|
27467
27933
|
});
|
|
27468
|
-
if (shouldUpdateThreadTitle)
|
|
27934
|
+
if (shouldUpdateThreadTitle) await this.streamMarker(createThreadUpdateMarker({
|
|
27469
27935
|
cycleId: this.cycleId ?? crypto.randomUUID(),
|
|
27470
27936
|
threadId,
|
|
27471
27937
|
oldTitle,
|
|
27472
27938
|
newTitle
|
|
27473
|
-
});
|
|
27939
|
+
}));
|
|
27474
27940
|
}
|
|
27475
|
-
if (
|
|
27476
|
-
|
|
27477
|
-
|
|
27478
|
-
|
|
27479
|
-
|
|
27480
|
-
|
|
27481
|
-
|
|
27482
|
-
});
|
|
27483
|
-
await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, record.id);
|
|
27484
|
-
return true;
|
|
27941
|
+
if (!committed.alreadyCovered) await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, committed.record.id);
|
|
27942
|
+
return {
|
|
27943
|
+
status: "committed",
|
|
27944
|
+
processed,
|
|
27945
|
+
record: committed.record,
|
|
27946
|
+
alreadyCovered: committed.alreadyCovered
|
|
27947
|
+
};
|
|
27485
27948
|
}
|
|
27486
27949
|
async emitEndMarkers(cycleId, processed) {
|
|
27487
27950
|
const actualTokensObserved = await this.tokenCounter.countMessagesAsync(this.opts.messages);
|
|
@@ -27525,6 +27988,8 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27525
27988
|
startedAt;
|
|
27526
27989
|
cycleId;
|
|
27527
27990
|
priorExtractedValues;
|
|
27991
|
+
/** The generation the chunk was written to (the head when the target was retired). */
|
|
27992
|
+
persistedRecordId;
|
|
27528
27993
|
constructor(deps, opts) {
|
|
27529
27994
|
super(deps, opts);
|
|
27530
27995
|
this.cycleId = opts.cycleId;
|
|
@@ -27551,12 +28016,13 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27551
28016
|
};
|
|
27552
28017
|
}
|
|
27553
28018
|
async emitStartMarkers(_cycleId) {}
|
|
27554
|
-
async observe(
|
|
28019
|
+
async observe(observerContext, messages, wasTruncated) {
|
|
27555
28020
|
const thread = await this.storage.getThreadById({ threadId: this.opts.threadId });
|
|
27556
28021
|
const omMeta = thread ? getThreadOMMetadata(thread.metadata) : void 0;
|
|
27557
28022
|
this.priorExtractedValues = getPriorExtractedValues(omMeta, this.observationConfig.extractors);
|
|
27558
|
-
const result = await this.deps.observer.call(
|
|
28023
|
+
const result = await this.deps.observer.call(observerContext, messages, void 0, {
|
|
27559
28024
|
skipContinuationHints: true,
|
|
28025
|
+
wasTruncated,
|
|
27560
28026
|
requestContext: this.opts.requestContext,
|
|
27561
28027
|
observabilityContext: this.opts.observabilityContext,
|
|
27562
28028
|
priorExtractedValues: this.priorExtractedValues,
|
|
@@ -27630,71 +28096,124 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27630
28096
|
};
|
|
27631
28097
|
}
|
|
27632
28098
|
async persist(processed) {
|
|
27633
|
-
if (!processed.observations) return
|
|
28099
|
+
if (!processed.observations) return;
|
|
27634
28100
|
const { record, threadId, resourceId, messages } = this.opts;
|
|
27635
|
-
|
|
28101
|
+
const liveRecord = await getLineageHead(this.storage, record);
|
|
28102
|
+
if (!liveRecord) {
|
|
27636
28103
|
omDebug(`[OM:asyncBuffer] skipping persist for thread ${threadId}: observational memory record is gone`);
|
|
27637
|
-
return
|
|
28104
|
+
return {
|
|
28105
|
+
status: "not-committed",
|
|
28106
|
+
reason: "the observational memory record was cleared"
|
|
28107
|
+
};
|
|
27638
28108
|
}
|
|
27639
28109
|
const messageTokens = await this.tokenCounter.countMessagesAsync(messages);
|
|
27640
|
-
|
|
27641
|
-
|
|
27642
|
-
|
|
27643
|
-
|
|
27644
|
-
|
|
27645
|
-
|
|
27646
|
-
|
|
27647
|
-
|
|
27648
|
-
|
|
27649
|
-
|
|
27650
|
-
|
|
27651
|
-
|
|
27652
|
-
|
|
27653
|
-
|
|
27654
|
-
|
|
27655
|
-
|
|
27656
|
-
|
|
28110
|
+
let appendAttempts = 0;
|
|
28111
|
+
let targetId = record.id;
|
|
28112
|
+
const appendResult = await withRetry(() => {
|
|
28113
|
+
appendAttempts++;
|
|
28114
|
+
return this.runCommit(async () => {
|
|
28115
|
+
const head = await getLineageHead(this.storage, record);
|
|
28116
|
+
if (!head) return null;
|
|
28117
|
+
targetId = head.id;
|
|
28118
|
+
const input = {
|
|
28119
|
+
id: targetId,
|
|
28120
|
+
chunk: {
|
|
28121
|
+
cycleId: this.cycleId,
|
|
28122
|
+
observations: processed.observations,
|
|
28123
|
+
tokenCount: processed.observationTokens,
|
|
28124
|
+
messageIds: processed.observedMessageIds,
|
|
28125
|
+
messageTokens,
|
|
28126
|
+
lastObservedAt: processed.lastObservedAt,
|
|
28127
|
+
suggestedContinuation: processed.suggestedContinuation,
|
|
28128
|
+
currentTask: processed.currentTask,
|
|
28129
|
+
threadTitle: processed.threadTitle,
|
|
28130
|
+
extractedValues: processed.extractedValues,
|
|
28131
|
+
extractionFailures: processed.extractionFailures
|
|
28132
|
+
},
|
|
28133
|
+
lastBufferedAtTime: processed.lastObservedAt
|
|
28134
|
+
};
|
|
28135
|
+
return typeof this.storage.appendBufferedObservations === "function" ? this.storage.appendBufferedObservations(input) : this.storage.updateBufferedObservations(input).then(() => ({
|
|
28136
|
+
persisted: true,
|
|
28137
|
+
recordId: input.id
|
|
28138
|
+
}));
|
|
28139
|
+
});
|
|
28140
|
+
}, {
|
|
27657
28141
|
label: "persist-buffered-observations",
|
|
27658
28142
|
abortSignal: this.opts.abortSignal
|
|
27659
28143
|
});
|
|
27660
|
-
|
|
27661
|
-
|
|
27662
|
-
|
|
27663
|
-
|
|
27664
|
-
|
|
27665
|
-
|
|
27666
|
-
|
|
27667
|
-
|
|
27668
|
-
|
|
27669
|
-
|
|
27670
|
-
|
|
27671
|
-
|
|
27672
|
-
|
|
27673
|
-
|
|
27674
|
-
|
|
27675
|
-
|
|
27676
|
-
|
|
27677
|
-
|
|
27678
|
-
|
|
27679
|
-
|
|
27680
|
-
|
|
27681
|
-
|
|
27682
|
-
|
|
27683
|
-
|
|
27684
|
-
|
|
27685
|
-
|
|
27686
|
-
|
|
27687
|
-
|
|
27688
|
-
|
|
28144
|
+
if (appendResult === null) {
|
|
28145
|
+
omDebug(`[OM:asyncBuffer] skipping persist for thread ${threadId}: observational memory record was cleared`);
|
|
28146
|
+
return {
|
|
28147
|
+
status: "not-committed",
|
|
28148
|
+
reason: "the observational memory record was cleared"
|
|
28149
|
+
};
|
|
28150
|
+
}
|
|
28151
|
+
let committedRecord = liveRecord;
|
|
28152
|
+
if (!appendResult.persisted) {
|
|
28153
|
+
const head = appendAttempts > 1 ? await getLineageHead(this.storage, record) : null;
|
|
28154
|
+
if (!(!!head && (getBufferedChunks(head).some((chunk) => chunk.cycleId === this.cycleId) || head.activeObservations.includes(processed.observations)))) return {
|
|
28155
|
+
status: "not-committed",
|
|
28156
|
+
reason: "the buffered chunk was already observed"
|
|
28157
|
+
};
|
|
28158
|
+
this.persistedRecordId = head.id;
|
|
28159
|
+
committedRecord = head;
|
|
28160
|
+
} else this.persistedRecordId = appendResult.recordId;
|
|
28161
|
+
await this.emitBufferingEndMarker(processed);
|
|
28162
|
+
await this.opts.onBufferedChunkPersisted?.();
|
|
28163
|
+
if (committedRecord.id !== this.persistedRecordId) {
|
|
28164
|
+
const landedOn = await getLineageRecord(this.storage, record, this.persistedRecordId);
|
|
28165
|
+
if (!landedOn) return {
|
|
28166
|
+
status: "not-committed",
|
|
28167
|
+
reason: "the observational memory record was cleared"
|
|
28168
|
+
};
|
|
28169
|
+
committedRecord = landedOn;
|
|
28170
|
+
}
|
|
28171
|
+
try {
|
|
28172
|
+
await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, this.persistedRecordId);
|
|
28173
|
+
const candidateTitle = processed.threadTitle?.trim();
|
|
28174
|
+
const hasValidThreadTitle = !!candidateTitle && candidateTitle.length >= 3;
|
|
28175
|
+
if (hasValidThreadTitle || processed.extractedValues) {
|
|
28176
|
+
const thread = await this.storage.getThreadById({ threadId });
|
|
28177
|
+
if (thread) {
|
|
28178
|
+
const oldTitle = thread.title?.trim();
|
|
28179
|
+
const newTitle = resolveThreadTitleUpdate(thread, candidateTitle);
|
|
28180
|
+
const shouldUpdateThreadTitle = newTitle !== void 0;
|
|
28181
|
+
const previousOmMetadata = getThreadOMMetadata(thread.metadata);
|
|
28182
|
+
const metadataUpdate = buildThreadMetadataFromExtractedValues(processed.extractors ?? this.observationConfig.extractors, processed.extractedValues);
|
|
28183
|
+
const newMetadata = setThreadOMMetadata(thread.metadata, {
|
|
28184
|
+
...hasValidThreadTitle || metadataUpdate.threadTitle ? { threadTitle: metadataUpdate.threadTitle ?? processed.threadTitle } : {},
|
|
28185
|
+
extracted: {
|
|
28186
|
+
...previousOmMetadata?.extracted ?? {},
|
|
28187
|
+
...metadataUpdate.extracted ?? {}
|
|
28188
|
+
}
|
|
28189
|
+
});
|
|
28190
|
+
await this.storage.patchThread({
|
|
28191
|
+
id: threadId,
|
|
28192
|
+
...shouldUpdateThreadTitle ? { title: newTitle } : {},
|
|
28193
|
+
metadata: newMetadata
|
|
27689
28194
|
});
|
|
27690
|
-
|
|
28195
|
+
if (shouldUpdateThreadTitle) {
|
|
28196
|
+
const marker = createThreadUpdateMarker({
|
|
28197
|
+
cycleId: this.cycleId,
|
|
28198
|
+
threadId,
|
|
28199
|
+
oldTitle,
|
|
28200
|
+
newTitle
|
|
28201
|
+
});
|
|
28202
|
+
await this.streamMarker(marker);
|
|
28203
|
+
}
|
|
27691
28204
|
}
|
|
27692
28205
|
}
|
|
28206
|
+
} catch (error) {
|
|
28207
|
+
omError("[OM] Post-buffer indexing or thread update failed; the buffered chunk is kept", error);
|
|
27693
28208
|
}
|
|
27694
|
-
return
|
|
28209
|
+
return {
|
|
28210
|
+
status: "committed",
|
|
28211
|
+
processed,
|
|
28212
|
+
record: committedRecord
|
|
28213
|
+
};
|
|
27695
28214
|
}
|
|
27696
|
-
async emitEndMarkers(
|
|
27697
|
-
|
|
28215
|
+
async emitEndMarkers() {}
|
|
28216
|
+
async emitBufferingEndMarker(processed) {
|
|
27698
28217
|
const { record, threadId, messages } = this.opts;
|
|
27699
28218
|
const tokensBuffered = await this.tokenCounter.countMessagesAsync(messages);
|
|
27700
28219
|
const totalBufferedTokens = getBufferedChunks(await this.storage.getObservationalMemory(record.threadId, record.resourceId)).reduce((sum, c) => sum + (c.tokenCount ?? 0), 0) || processed.observationTokens;
|
|
@@ -27704,7 +28223,7 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27704
28223
|
startedAt: this.startedAt,
|
|
27705
28224
|
tokensBuffered,
|
|
27706
28225
|
bufferedTokens: totalBufferedTokens,
|
|
27707
|
-
recordId: record.id,
|
|
28226
|
+
recordId: this.persistedRecordId ?? record.id,
|
|
27708
28227
|
threadId,
|
|
27709
28228
|
observations: processed.observations,
|
|
27710
28229
|
extractedValues: processed.extractedValues,
|
|
@@ -27714,7 +28233,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27714
28233
|
...endMarker,
|
|
27715
28234
|
transient: true
|
|
27716
28235
|
}).catch(() => {});
|
|
27717
|
-
await this.persistMarkerToStorage(endMarker, threadId, record.resourceId ?? void 0
|
|
28236
|
+
await this.persistMarkerToStorage(endMarker, threadId, record.resourceId ?? void 0, {
|
|
28237
|
+
notAfter: new Date(this.startedAt),
|
|
28238
|
+
onCycleStartMessage: true
|
|
28239
|
+
});
|
|
27718
28240
|
}
|
|
27719
28241
|
async emitFailedMarkers(_cycleId, error) {
|
|
27720
28242
|
const { record, threadId, messages } = this.opts;
|
|
@@ -27733,7 +28255,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
27733
28255
|
...failedMarker,
|
|
27734
28256
|
transient: true
|
|
27735
28257
|
}).catch(() => {});
|
|
27736
|
-
await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0
|
|
28258
|
+
await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0, {
|
|
28259
|
+
notAfter: new Date(this.startedAt),
|
|
28260
|
+
onCycleStartMessage: true
|
|
28261
|
+
});
|
|
27737
28262
|
}
|
|
27738
28263
|
};
|
|
27739
28264
|
//#endregion
|
|
@@ -27756,10 +28281,17 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27756
28281
|
lastBatchProviderMetadata;
|
|
27757
28282
|
observationResults = [];
|
|
27758
28283
|
priorMetadataByThread = /* @__PURE__ */ new Map();
|
|
28284
|
+
/** Each observed thread's wrapped section from the last composition. */
|
|
28285
|
+
threadSections = /* @__PURE__ */ new Map();
|
|
28286
|
+
/** Head text the processed observations were composed from (the commit's expected text). */
|
|
28287
|
+
composedFrom = "";
|
|
27759
28288
|
constructor(deps, opts) {
|
|
27760
28289
|
super(deps, opts);
|
|
27761
28290
|
this.resourceId = opts.resourceId;
|
|
27762
28291
|
}
|
|
28292
|
+
getObservedMessagesForThread(threadId) {
|
|
28293
|
+
return this.threadsWithMessages.get(threadId);
|
|
28294
|
+
}
|
|
27763
28295
|
get needsLock() {
|
|
27764
28296
|
return true;
|
|
27765
28297
|
}
|
|
@@ -27846,10 +28378,12 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27846
28378
|
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
27847
28379
|
}))
|
|
27848
28380
|
});
|
|
27849
|
-
const
|
|
28381
|
+
const freshRecord = await this.storage.getObservationalMemory(null, this.resourceId);
|
|
28382
|
+
const existingObservations = freshRecord?.activeObservations ?? record.activeObservations ?? "";
|
|
27850
28383
|
return {
|
|
27851
28384
|
messages: Array.from(this.threadsWithMessages.values()).flat(),
|
|
27852
|
-
existingObservations
|
|
28385
|
+
existingObservations,
|
|
28386
|
+
contextRecord: freshRecord
|
|
27853
28387
|
};
|
|
27854
28388
|
}
|
|
27855
28389
|
async emitStartMarkers(cycleId) {
|
|
@@ -27873,7 +28407,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27873
28407
|
}
|
|
27874
28408
|
}
|
|
27875
28409
|
}
|
|
27876
|
-
async observe(
|
|
28410
|
+
async observe(observerContext, _messages, wasTruncated) {
|
|
27877
28411
|
const maxTokensPerBatch = this.observationConfig.maxTokensPerBatch ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.maxTokensPerBatch;
|
|
27878
28412
|
const orderedThreadIds = this.threadOrder.filter((tid) => this.threadsWithMessages.has(tid));
|
|
27879
28413
|
const batches = [];
|
|
@@ -27899,10 +28433,10 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27899
28433
|
}
|
|
27900
28434
|
if (currentBatch.threadIds.length > 0) batches.push(currentBatch);
|
|
27901
28435
|
const batchResults = await Promise.all(batches.map(async (batch) => {
|
|
27902
|
-
return this.deps.observer.callMultiThread(
|
|
28436
|
+
return this.deps.observer.callMultiThread(observerContext, batch.threadMap, batch.threadIds, this.opts.abortSignal, this.opts.requestContext, this.priorMetadataByThread, this.opts.observabilityContext, void 0, {
|
|
27903
28437
|
resourceId: this.opts.resourceId,
|
|
27904
28438
|
trigger: this.opts.trigger
|
|
27905
|
-
}, this.opts.record.observedTimezone, this.opts.agent, this.opts.currentModel);
|
|
28439
|
+
}, this.opts.record.observedTimezone, this.opts.agent, this.opts.currentModel, wasTruncated);
|
|
27906
28440
|
}));
|
|
27907
28441
|
for (const batchResult of batchResults) {
|
|
27908
28442
|
for (const [threadId, result] of batchResult.results) this.multiThreadResults.set(threadId, result);
|
|
@@ -27969,14 +28503,38 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27969
28503
|
}
|
|
27970
28504
|
});
|
|
27971
28505
|
}
|
|
28506
|
+
this.composedFrom = existingObservations;
|
|
28507
|
+
const processed = await this.compose(existingObservations, record);
|
|
28508
|
+
for (const [index, { threadId, threadMessages, result }] of this.observationResults.entries()) this.deps.emitDebugEvent({
|
|
28509
|
+
type: "observation_complete",
|
|
28510
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
28511
|
+
threadId,
|
|
28512
|
+
resourceId: this.resourceId,
|
|
28513
|
+
observations: this.threadSections.get(threadId) ?? "",
|
|
28514
|
+
rawObserverOutput: result.observations,
|
|
28515
|
+
previousObservations: record.activeObservations,
|
|
28516
|
+
messages: threadMessages.map((m) => ({
|
|
28517
|
+
role: m.role,
|
|
28518
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
28519
|
+
})),
|
|
28520
|
+
usage: index === 0 && this.totalBatchUsage.totalTokens > 0 ? this.totalBatchUsage : void 0
|
|
28521
|
+
});
|
|
28522
|
+
return processed;
|
|
28523
|
+
}
|
|
28524
|
+
/**
|
|
28525
|
+
* Merge every observed thread's section into `existingObservations` (the head text). Resource
|
|
28526
|
+
* scope rewrites an existing same-day `<thread>` section in place, so the result is not an
|
|
28527
|
+
* append of the head text.
|
|
28528
|
+
*/
|
|
28529
|
+
async compose(existingObservations, baseRecord) {
|
|
27972
28530
|
let currentObservations = existingObservations;
|
|
27973
28531
|
let cycleObservationTokens = 0;
|
|
27974
28532
|
const threadMetadataUpdates = [];
|
|
27975
|
-
for (const
|
|
27976
|
-
const { threadId, threadMessages, result } = obsResult;
|
|
28533
|
+
for (const { threadId, threadMessages, result } of this.observationResults) {
|
|
27977
28534
|
cycleObservationTokens += this.tokenCounter.countObservations(result.observations);
|
|
27978
28535
|
const messageRange = this.retrieval ? buildMessageRange(threadMessages) : void 0;
|
|
27979
28536
|
const threadSection = await this.wrapWithThreadTag(threadId, result.observations, messageRange);
|
|
28537
|
+
this.threadSections.set(threadId, threadSection);
|
|
27980
28538
|
const threadLastObservedAt = this.getMaxMessageTimestamp(threadMessages);
|
|
27981
28539
|
currentObservations = this.replaceOrAppendThreadSection(currentObservations, threadId, threadSection, threadLastObservedAt);
|
|
27982
28540
|
threadMetadataUpdates.push({
|
|
@@ -27990,26 +28548,11 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
27990
28548
|
extractors: result.extractors,
|
|
27991
28549
|
lastObservedMessageCursor: getLastObservedMessageCursor(threadMessages)
|
|
27992
28550
|
});
|
|
27993
|
-
const isFirstThread = this.observationResults.indexOf(obsResult) === 0;
|
|
27994
|
-
this.deps.emitDebugEvent({
|
|
27995
|
-
type: "observation_complete",
|
|
27996
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
27997
|
-
threadId,
|
|
27998
|
-
resourceId: this.resourceId,
|
|
27999
|
-
observations: threadSection,
|
|
28000
|
-
rawObserverOutput: result.observations,
|
|
28001
|
-
previousObservations: record.activeObservations,
|
|
28002
|
-
messages: threadMessages.map((m) => ({
|
|
28003
|
-
role: m.role,
|
|
28004
|
-
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
28005
|
-
})),
|
|
28006
|
-
usage: isFirstThread && this.totalBatchUsage.totalTokens > 0 ? this.totalBatchUsage : void 0
|
|
28007
|
-
});
|
|
28008
28551
|
}
|
|
28009
28552
|
const observedMessages = this.observationResults.flatMap((r) => r.threadMessages);
|
|
28010
28553
|
const lastObservedAt = this.getMaxMessageTimestamp(observedMessages);
|
|
28011
28554
|
const newMessageIds = observedMessages.map((m) => m.id);
|
|
28012
|
-
const existingIds =
|
|
28555
|
+
const existingIds = baseRecord.observedMessageIds ?? [];
|
|
28013
28556
|
const observedMessageIds = [.../* @__PURE__ */ new Set([...existingIds, ...newMessageIds])];
|
|
28014
28557
|
const observationTokens = this.tokenCounter.countObservations(currentObservations);
|
|
28015
28558
|
return {
|
|
@@ -28023,8 +28566,25 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
28023
28566
|
}
|
|
28024
28567
|
async persist(processed) {
|
|
28025
28568
|
const { record, resourceId } = this.opts;
|
|
28569
|
+
const head = await getLineageHead(this.storage, record);
|
|
28570
|
+
if (!head) return {
|
|
28571
|
+
status: "not-committed",
|
|
28572
|
+
reason: "the observational memory record was cleared"
|
|
28573
|
+
};
|
|
28574
|
+
const committed = await this.commitActiveObservationsToHead({
|
|
28575
|
+
processed,
|
|
28576
|
+
composedFrom: this.composedFrom,
|
|
28577
|
+
target: head,
|
|
28578
|
+
recompose: (fresh) => this.compose(fresh.activeObservations ?? "", fresh),
|
|
28579
|
+
cycleMessageIds: this.observationResults.flatMap((r) => r.threadMessages.map((m) => m.id))
|
|
28580
|
+
});
|
|
28581
|
+
if (!committed) return {
|
|
28582
|
+
status: "not-committed",
|
|
28583
|
+
reason: "the observational memory head kept changing during the commit"
|
|
28584
|
+
};
|
|
28585
|
+
processed = committed.processed;
|
|
28026
28586
|
const threadUpdateMarkers = [];
|
|
28027
|
-
if (processed.threadMetadataUpdates) for (const update of processed.threadMetadataUpdates) {
|
|
28587
|
+
if (processed.threadMetadataUpdates && !committed.alreadyCovered) for (const update of processed.threadMetadataUpdates) {
|
|
28028
28588
|
const thread = await this.storage.getThreadById({ threadId: update.threadId });
|
|
28029
28589
|
if (thread) {
|
|
28030
28590
|
const oldTitle = thread.title?.trim();
|
|
@@ -28057,15 +28617,13 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
28057
28617
|
}
|
|
28058
28618
|
}
|
|
28059
28619
|
for (const marker of threadUpdateMarkers) await this.streamMarker(marker);
|
|
28060
|
-
await this.
|
|
28061
|
-
|
|
28062
|
-
|
|
28063
|
-
|
|
28064
|
-
|
|
28065
|
-
|
|
28066
|
-
}
|
|
28067
|
-
if (resourceId) await Promise.all(this.observationResults.map(({ threadId, threadMessages, result }) => this.indexObservationGroups(result.observations, threadId, resourceId, this.getMaxMessageTimestamp(threadMessages), record.id)));
|
|
28068
|
-
return true;
|
|
28620
|
+
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)));
|
|
28621
|
+
return {
|
|
28622
|
+
status: "committed",
|
|
28623
|
+
processed,
|
|
28624
|
+
record: committed.record,
|
|
28625
|
+
alreadyCovered: committed.alreadyCovered
|
|
28626
|
+
};
|
|
28069
28627
|
}
|
|
28070
28628
|
async emitEndMarkers(cycleId, processed) {
|
|
28071
28629
|
for (const obsResult of this.observationResults) {
|
|
@@ -28124,7 +28682,8 @@ ObservationStrategy.create = ((om, opts) => {
|
|
|
28124
28682
|
observedMessageIds: om.observedMessageIds,
|
|
28125
28683
|
obscureThreadIds: om.getObscureThreadIds(),
|
|
28126
28684
|
onIndexObservations: om.onIndexObservations,
|
|
28127
|
-
emitDebugEvent: (e) => om.emitDebugEvent(e)
|
|
28685
|
+
emitDebugEvent: (e) => om.emitDebugEvent(e),
|
|
28686
|
+
prepareObserverContext: (observations, record) => om.prepareObserverContext(observations, record)
|
|
28128
28687
|
};
|
|
28129
28688
|
if (opts.cycleId) return new AsyncBufferObservationStrategy(deps, opts);
|
|
28130
28689
|
if (deps.scope === "resource" && opts.resourceId) return new ResourceScopedObservationStrategy(deps, opts);
|
|
@@ -28256,8 +28815,8 @@ var ObservationStep = class {
|
|
|
28256
28815
|
let reflected = false;
|
|
28257
28816
|
let didThresholdCleanup = false;
|
|
28258
28817
|
let observerExchange;
|
|
28818
|
+
const step0Messages = this.stepNumber === 0 ? getObservableMessages(messageList) : [];
|
|
28259
28819
|
if (this.stepNumber === 0) {
|
|
28260
|
-
const step0Messages = getObservableMessages(messageList);
|
|
28261
28820
|
const activation = await om.activate({
|
|
28262
28821
|
threadId,
|
|
28263
28822
|
resourceId,
|
|
@@ -28314,7 +28873,17 @@ var ObservationStep = class {
|
|
|
28314
28873
|
record: this.turn.record,
|
|
28315
28874
|
messages: getObservableMessages(messageList)
|
|
28316
28875
|
});
|
|
28317
|
-
if (statusSnapshot.
|
|
28876
|
+
if (statusSnapshot.inAsyncObservationBand && !statusSnapshot.canActivate && !hasIncompleteToolCalls) {
|
|
28877
|
+
await this.turn.refreshRecord();
|
|
28878
|
+
statusSnapshot = await om.getStatus({
|
|
28879
|
+
threadId,
|
|
28880
|
+
resourceId,
|
|
28881
|
+
record: this.turn.record,
|
|
28882
|
+
messages: getObservableMessages(messageList)
|
|
28883
|
+
});
|
|
28884
|
+
}
|
|
28885
|
+
const activateBeforeBuffering = statusSnapshot.inAsyncObservationBand && statusSnapshot.canActivate && !hasIncompleteToolCalls;
|
|
28886
|
+
if (statusSnapshot.shouldBuffer && !activateBeforeBuffering) {
|
|
28318
28887
|
const allMessages = getObservableMessages(messageList);
|
|
28319
28888
|
const unobservedMessages = om.getUnobservedMessages(allMessages, statusSnapshot.record);
|
|
28320
28889
|
const candidates = om.getUnobservedMessages(unobservedMessages, statusSnapshot.record, { excludeBuffered: true });
|
|
@@ -28328,8 +28897,10 @@ var ObservationStep = class {
|
|
|
28328
28897
|
omDebug(`[OM:buffer] onBufferChunkSealed hook failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
28329
28898
|
}
|
|
28330
28899
|
if (this.turn.memory) await this.turn.memory.persistMessages(safeCandidates);
|
|
28331
|
-
messageList.
|
|
28332
|
-
|
|
28900
|
+
const inputIds = new Set(messageList.get.input.db().map((msg) => msg.id));
|
|
28901
|
+
const sealedOutput = safeCandidates.filter((msg) => !inputIds.has(msg.id));
|
|
28902
|
+
messageList.removeByIds(sealedOutput.map((msg) => msg.id));
|
|
28903
|
+
for (const msg of sealedOutput) messageList.add(msg, "memory");
|
|
28333
28904
|
}
|
|
28334
28905
|
if (!deferred) {
|
|
28335
28906
|
om.trackBackgroundWork(om.buffer({
|
|
@@ -28351,7 +28922,8 @@ var ObservationStep = class {
|
|
|
28351
28922
|
buffered = true;
|
|
28352
28923
|
}
|
|
28353
28924
|
}
|
|
28354
|
-
const
|
|
28925
|
+
const bandDefersObservation = statusSnapshot.inAsyncObservationBand && !statusSnapshot.canActivate;
|
|
28926
|
+
const willObserveNow = statusSnapshot.shouldObserve && !hasIncompleteToolCalls && !bandDefersObservation;
|
|
28355
28927
|
/** In-flight message ids the step-0 cleanup must never remove from live context. */
|
|
28356
28928
|
let step0PreserveIds;
|
|
28357
28929
|
if (this.stepNumber > 0 || willObserveNow) {
|
|
@@ -28393,7 +28965,7 @@ var ObservationStep = class {
|
|
|
28393
28965
|
}
|
|
28394
28966
|
if (willObserveNow) {
|
|
28395
28967
|
const preObsGeneration = this.turn.record.generationCount;
|
|
28396
|
-
const obsResult = await this.runThresholdObservation();
|
|
28968
|
+
const obsResult = await this.runThresholdObservation(statusSnapshot.inAsyncObservationBand);
|
|
28397
28969
|
observerExchange = obsResult.observerExchange;
|
|
28398
28970
|
if (obsResult.succeeded) {
|
|
28399
28971
|
observed = true;
|
|
@@ -28466,12 +29038,12 @@ var ObservationStep = class {
|
|
|
28466
29038
|
/**
|
|
28467
29039
|
* Run the full threshold observation pipeline:
|
|
28468
29040
|
* waitForBuffering → re-check → activate buffered chunks → reflect → observe
|
|
28469
|
-
* (sync fallback
|
|
29041
|
+
* (sync fallback at blockAfter when async buffering is enabled)
|
|
28470
29042
|
*/
|
|
28471
|
-
async runThresholdObservation() {
|
|
29043
|
+
async runThresholdObservation(inAsyncObservationBand) {
|
|
28472
29044
|
const { threadId, resourceId, messageList } = this.turn;
|
|
28473
29045
|
const om = this.turn.om;
|
|
28474
|
-
await om.waitForBuffering(threadId, resourceId);
|
|
29046
|
+
if (!inAsyncObservationBand) await om.waitForBuffering(threadId, resourceId);
|
|
28475
29047
|
await this.turn.refreshRecord();
|
|
28476
29048
|
const observableMessages = this.seededResponseMessage ? getObservableMessages(messageList).filter((msg) => msg.id !== this.turn.responseMessageId) : getObservableMessages(messageList);
|
|
28477
29049
|
let status = await om.getStatus({
|
|
@@ -28500,7 +29072,16 @@ var ObservationStep = class {
|
|
|
28500
29072
|
messageList
|
|
28501
29073
|
});
|
|
28502
29074
|
this.turn.setRecord(activation.record);
|
|
28503
|
-
if (!activation.activated)
|
|
29075
|
+
if (!activation.activated) {
|
|
29076
|
+
await this.turn.refreshRecord();
|
|
29077
|
+
status = await om.getStatus({
|
|
29078
|
+
threadId,
|
|
29079
|
+
resourceId,
|
|
29080
|
+
record: this.turn.record,
|
|
29081
|
+
messages: pendingMessages
|
|
29082
|
+
});
|
|
29083
|
+
break;
|
|
29084
|
+
}
|
|
28504
29085
|
activated = true;
|
|
28505
29086
|
const ids = new Set(activation.activatedMessageIds ?? []);
|
|
28506
29087
|
activatedMessageIds.push(...ids);
|
|
@@ -28512,11 +29093,17 @@ var ObservationStep = class {
|
|
|
28512
29093
|
messages: pendingMessages
|
|
28513
29094
|
});
|
|
28514
29095
|
}
|
|
29096
|
+
if (status.shouldObserve && status.canActivate) return {
|
|
29097
|
+
succeeded: activated,
|
|
29098
|
+
record: this.turn.record,
|
|
29099
|
+
activatedMessageIds: activated ? activatedMessageIds : void 0,
|
|
29100
|
+
cleanupMessageIds: activatedMessageIds
|
|
29101
|
+
};
|
|
28515
29102
|
if (activated) {
|
|
28516
|
-
const
|
|
29103
|
+
const preReflectionRecord = this.turn.record;
|
|
28517
29104
|
await om.reflector.maybeReflect({
|
|
28518
|
-
record:
|
|
28519
|
-
observationTokens:
|
|
29105
|
+
record: preReflectionRecord,
|
|
29106
|
+
observationTokens: preReflectionRecord.observationTokenCount ?? 0,
|
|
28520
29107
|
threadId,
|
|
28521
29108
|
writer: this.turn.writer,
|
|
28522
29109
|
messageList,
|
|
@@ -28531,6 +29118,14 @@ var ObservationStep = class {
|
|
|
28531
29118
|
}),
|
|
28532
29119
|
trigger: "turn-sync"
|
|
28533
29120
|
});
|
|
29121
|
+
await this.turn.refreshRecord();
|
|
29122
|
+
const postActivationRecord = this.turn.record;
|
|
29123
|
+
status = await om.getStatus({
|
|
29124
|
+
threadId,
|
|
29125
|
+
resourceId,
|
|
29126
|
+
record: postActivationRecord,
|
|
29127
|
+
messages: pendingMessages
|
|
29128
|
+
});
|
|
28534
29129
|
if (!status.shouldObserve) return {
|
|
28535
29130
|
succeeded: true,
|
|
28536
29131
|
record: postActivationRecord,
|
|
@@ -28538,6 +29133,12 @@ var ObservationStep = class {
|
|
|
28538
29133
|
cleanupMessageIds: activatedMessageIds
|
|
28539
29134
|
};
|
|
28540
29135
|
}
|
|
29136
|
+
if (status.inAsyncObservationBand || status.canActivate) return {
|
|
29137
|
+
succeeded: activated,
|
|
29138
|
+
record: this.turn.record,
|
|
29139
|
+
activatedMessageIds: activated ? activatedMessageIds : void 0,
|
|
29140
|
+
cleanupMessageIds: activatedMessageIds
|
|
29141
|
+
};
|
|
28541
29142
|
let obsResult;
|
|
28542
29143
|
try {
|
|
28543
29144
|
obsResult = await om.observe({
|
|
@@ -28889,13 +29490,18 @@ Take the existing observations and rewrite them to make it easier to continue in
|
|
|
28889
29490
|
|
|
28890
29491
|
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.
|
|
28891
29492
|
|
|
29493
|
+
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.
|
|
29494
|
+
|
|
28892
29495
|
When consolidating observations:
|
|
28893
|
-
-
|
|
28894
|
-
-
|
|
29496
|
+
- Keep every concrete fact: names, places, dates, numbers, amounts, durations, titles, versions, and who said what. Merge duplicates, but never drop specifics
|
|
29497
|
+
- Keep every dated statement with its date, and retain the timestamps that matter (start times, completion times, significant events)
|
|
29498
|
+
- 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
|
|
29499
|
+
- 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
|
|
29500
|
+
- Prefer many short factual lines over prose summaries. Do not replace facts with generalities like "discussed travel plans"
|
|
28895
29501
|
- Combine related items where it makes sense (e.g., "agent called view tool 5 times on file x")
|
|
28896
29502
|
- Preserve ✅ completion markers — they are memory signals that tell the assistant what is already resolved and help prevent repeated work
|
|
28897
29503
|
- Preserve the concrete resolved outcome captured by ✅ markers so the assistant knows what exactly is done
|
|
28898
|
-
- Condense older observations more
|
|
29504
|
+
- Condense the wording of older observations more than recent ones, but keep their facts
|
|
28899
29505
|
|
|
28900
29506
|
CRITICAL: USER ASSERTIONS vs QUESTIONS
|
|
28901
29507
|
- "User stated: X" = authoritative assertion (user told us something about themselves)
|
|
@@ -29177,6 +29783,25 @@ async function withAbortCheck(fn, abortSignal) {
|
|
|
29177
29783
|
* size while still letting them fire sooner than a threshold activation.
|
|
29178
29784
|
*/
|
|
29179
29785
|
const EARLY_ACTIVATION_SIZE_FLOOR_RATIO = .75;
|
|
29786
|
+
/** A reflection commit that created nothing because the reflected record is no longer the head. */
|
|
29787
|
+
var ReflectionNotAppliedError = class extends Error {
|
|
29788
|
+
constructor(recordId, headId) {
|
|
29789
|
+
super(`Reflection not applied: record ${recordId} is no longer the current generation (head is ${headId})`);
|
|
29790
|
+
this.name = "ReflectionNotAppliedError";
|
|
29791
|
+
}
|
|
29792
|
+
};
|
|
29793
|
+
/**
|
|
29794
|
+
* Whether a reflection commit created the generation the caller asked for. Storage creates
|
|
29795
|
+
* nothing (and returns the current head or the stored record) when the reflected record was
|
|
29796
|
+
* already retired or its observations were rewritten since the reflection read them.
|
|
29797
|
+
*
|
|
29798
|
+
* Adapters that predate `newRecordId` ignore it and do not report `supersededBy`; for those a
|
|
29799
|
+
* returned record other than the reflected one means a new generation was created.
|
|
29800
|
+
*/
|
|
29801
|
+
function isReflectionApplied(returned, newRecordId, reflectedRecordId) {
|
|
29802
|
+
if (returned.id === newRecordId) return true;
|
|
29803
|
+
return returned.supersededBy === void 0 && returned.id !== reflectedRecordId;
|
|
29804
|
+
}
|
|
29180
29805
|
/**
|
|
29181
29806
|
* Runs the Reflector agent for compressing observations.
|
|
29182
29807
|
* Handles synchronous reflection, async buffered reflection, and activation.
|
|
@@ -29540,14 +30165,14 @@ var ReflectorRunner = class {
|
|
|
29540
30165
|
* Perform async buffered reflection — reflects observations and stores to bufferedReflection.
|
|
29541
30166
|
* Does NOT create a new generation or update activeObservations.
|
|
29542
30167
|
*/
|
|
29543
|
-
async doAsyncBufferedReflection(record,
|
|
30168
|
+
async doAsyncBufferedReflection(record, bufferKey, writer, requestContext, observabilityContext, priorExtractedValues, mainAgent, sendSignal, trigger, currentModel) {
|
|
29544
30169
|
const currentRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record;
|
|
29545
30170
|
const observationTokens = currentRecord.observationTokenCount ?? 0;
|
|
29546
30171
|
const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(currentRecord));
|
|
29547
30172
|
const bufferActivation = this.reflectionConfig.bufferActivation ?? .5;
|
|
29548
30173
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
29549
30174
|
const cycleId = `reflect-buf-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
29550
|
-
BufferingCoordinator.reflectionBufferCycleIds.set(
|
|
30175
|
+
BufferingCoordinator.reflectionBufferCycleIds.set(bufferKey, cycleId);
|
|
29551
30176
|
const allLines = (currentRecord.activeObservations ?? "").split("\n");
|
|
29552
30177
|
const totalLines = allLines.length;
|
|
29553
30178
|
const avgTokensPerLine = totalLines > 0 ? observationTokens / totalLines : 0;
|
|
@@ -29586,13 +30211,24 @@ var ReflectorRunner = class {
|
|
|
29586
30211
|
await persistThreadExtractedValues(this.storage, this.reflectionConfig.extractors, currentRecord.threadId ?? void 0, reflectResult.extractedValues);
|
|
29587
30212
|
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
29588
30213
|
omDebug(`[OM:reflect] doAsyncBufferedReflection: reflector returned ${reflectionTokenCount} tokens (${reflectResult.observations?.length} chars), saving to recordId=${currentRecord.id}`);
|
|
29589
|
-
await this.
|
|
29590
|
-
id
|
|
29591
|
-
|
|
29592
|
-
|
|
29593
|
-
|
|
29594
|
-
|
|
29595
|
-
|
|
30214
|
+
if (!await runOMCommit(this.buffering.getLockKey(currentRecord.threadId, currentRecord.resourceId), async () => {
|
|
30215
|
+
if ((await this.storage.getObservationalMemory(currentRecord.threadId, currentRecord.resourceId))?.id !== currentRecord.id) return false;
|
|
30216
|
+
await this.storage.updateBufferedReflection({
|
|
30217
|
+
id: currentRecord.id,
|
|
30218
|
+
reflection: reflectResult.observations,
|
|
30219
|
+
tokenCount: reflectionTokenCount,
|
|
30220
|
+
inputTokenCount: sliceTokenEstimate,
|
|
30221
|
+
reflectedObservationLineCount
|
|
30222
|
+
});
|
|
30223
|
+
return true;
|
|
30224
|
+
})) {
|
|
30225
|
+
omDebug(`[OM:reflect] doAsyncBufferedReflection: record ${currentRecord.id} is no longer the head; discarding the buffered reflection`);
|
|
30226
|
+
BufferingCoordinator.lastBufferedBoundary.delete(bufferKey);
|
|
30227
|
+
return {
|
|
30228
|
+
usage: reflectResult.usage,
|
|
30229
|
+
providerMetadata: reflectResult.providerMetadata
|
|
30230
|
+
};
|
|
30231
|
+
}
|
|
29596
30232
|
omDebug(`[OM:reflect] doAsyncBufferedReflection: bufferedReflection saved with lineCount=${reflectedObservationLineCount}`);
|
|
29597
30233
|
if (writer) {
|
|
29598
30234
|
const endMarker = createBufferingEndMarker({
|
|
@@ -29671,10 +30307,16 @@ var ReflectorRunner = class {
|
|
|
29671
30307
|
}
|
|
29672
30308
|
}
|
|
29673
30309
|
omDebug(`[OM:reflect] tryActivateBufferedReflection: activating, beforeTokens=${beforeTokens}, combinedTokenCount=${combinedTokenCount}, reflectedLineCount=${reflectedLineCount}, unreflectedLines=${unreflectedLines.length}`);
|
|
29674
|
-
|
|
30310
|
+
const newRecordId = crypto.randomUUID();
|
|
30311
|
+
const swapped = await runOMCommit(lockKey, () => this.storage.swapBufferedReflectionToActive({
|
|
29675
30312
|
currentRecord: freshRecord,
|
|
29676
|
-
tokenCount: combinedTokenCount
|
|
29677
|
-
|
|
30313
|
+
tokenCount: combinedTokenCount,
|
|
30314
|
+
newRecordId
|
|
30315
|
+
}), { priority: "reflection" });
|
|
30316
|
+
if (!isReflectionApplied(swapped, newRecordId, freshRecord.id)) {
|
|
30317
|
+
omDebug(`[OM:reflect] tryActivateBufferedReflection: buffered reflection on ${freshRecord.id} not applied (head is ${swapped.id})`);
|
|
30318
|
+
return { status: "not-applied" };
|
|
30319
|
+
}
|
|
29678
30320
|
if (committedContext) await this.notifyReflectionCommitted({
|
|
29679
30321
|
...committedContext,
|
|
29680
30322
|
observations: allLines.slice(0, reflectedLineCount).join("\n").trim(),
|
|
@@ -29729,7 +30371,8 @@ var ReflectorRunner = class {
|
|
|
29729
30371
|
* @internal Used by observation strategies. Do not call directly.
|
|
29730
30372
|
*/
|
|
29731
30373
|
async maybeReflect(opts) {
|
|
29732
|
-
const { record, observationTokens, writer, abortSignal, mainAgent, sendSignal, sendStateSignal, messageList, currentModel, reflectionHooks, trigger, requestContext, observabilityContext, lastActivityAt, threadId: requestedThreadId } = opts;
|
|
30374
|
+
const { record: callerRecord, observationTokens, writer, abortSignal, mainAgent, sendSignal, sendStateSignal, messageList, currentModel, reflectionHooks, trigger, requestContext, observabilityContext, lastActivityAt, threadId: requestedThreadId } = opts;
|
|
30375
|
+
const record = { ...callerRecord };
|
|
29733
30376
|
const lockKey = this.buffering.getLockKey(record.threadId, record.resourceId);
|
|
29734
30377
|
const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(record));
|
|
29735
30378
|
const priorExtractedValues = await getThreadExtractedValues(this.storage, this.reflectionConfig.extractors, requestedThreadId ?? record.threadId);
|
|
@@ -29783,7 +30426,7 @@ var ReflectorRunner = class {
|
|
|
29783
30426
|
abortSignal,
|
|
29784
30427
|
observabilityContext
|
|
29785
30428
|
});
|
|
29786
|
-
if (activationResult.status === "activated") return;
|
|
30429
|
+
if (activationResult.status === "activated" || activationResult.status === "not-applied") return;
|
|
29787
30430
|
if (activationResult.status === "suppressed") {
|
|
29788
30431
|
omDebug(`[OM:reflect] skipping sync fallback / re-buffer after suppressed early ${activationMetadata.triggeredBy} activation (reason=${activationResult.reason})`);
|
|
29789
30432
|
return;
|
|
@@ -29866,13 +30509,19 @@ var ReflectorRunner = class {
|
|
|
29866
30509
|
reflectResult.observations = await applyTextTransform(this.hooks, "afterReflection", reflectResult.observations, hookContext);
|
|
29867
30510
|
reflectionUsage = reflectResult.usage;
|
|
29868
30511
|
reflectionProviderMetadata = reflectResult.providerMetadata;
|
|
29869
|
-
await persistThreadExtractedValues(this.storage, this.reflectionConfig.extractors, record.threadId ?? void 0, reflectResult.extractedValues);
|
|
29870
30512
|
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
29871
|
-
|
|
30513
|
+
const newRecordId = crypto.randomUUID();
|
|
30514
|
+
const committedRecord = await runOMCommit(this.buffering.getLockKey(record.threadId, record.resourceId), () => this.storage.createReflectionGeneration({
|
|
29872
30515
|
currentRecord: record,
|
|
29873
30516
|
reflection: reflectResult.observations,
|
|
29874
|
-
tokenCount: reflectionTokenCount
|
|
29875
|
-
|
|
30517
|
+
tokenCount: reflectionTokenCount,
|
|
30518
|
+
newRecordId
|
|
30519
|
+
}), { priority: "reflection" });
|
|
30520
|
+
if (!isReflectionApplied(committedRecord, newRecordId, record.id)) {
|
|
30521
|
+
omDebug(`[OM:reflect] reflection of ${record.id} not applied (head is ${committedRecord.id})`);
|
|
30522
|
+
throw new ReflectionNotAppliedError(record.id, committedRecord.id);
|
|
30523
|
+
}
|
|
30524
|
+
await persistThreadExtractedValues(this.storage, this.reflectionConfig.extractors, record.threadId ?? void 0, reflectResult.extractedValues);
|
|
29876
30525
|
if (reflectionTokenCount >= reflectThreshold) this.syncReflectionSuppression.set(lockKey, reflectionTokenCount);
|
|
29877
30526
|
else this.syncReflectionSuppression.delete(lockKey);
|
|
29878
30527
|
await this.notifyReflectionCommitted({
|
|
@@ -29952,6 +30601,7 @@ var ReflectorRunner = class {
|
|
|
29952
30601
|
failureKind: failedMarker.data.failureKind,
|
|
29953
30602
|
error: reflectionError.message
|
|
29954
30603
|
});
|
|
30604
|
+
if (error instanceof ReflectionNotAppliedError) return;
|
|
29955
30605
|
if (lifecycleError !== void 0 || abortSignal?.aborted || this.reflectionConfig.failurePolicy !== "continue" || !isOmModelExecutionError(error) || error.failureKind !== "reflector-model") throw error;
|
|
29956
30606
|
this.syncReflectionSuppression.set(lockKey, observationTokens);
|
|
29957
30607
|
omError("[OM] Reflection failed", error);
|
|
@@ -30295,6 +30945,8 @@ function writeProcessInputStepReproCapture(params) {
|
|
|
30295
30945
|
* Returns the parts from the latest step of a message (after the last step-start marker).
|
|
30296
30946
|
* If no step-start marker exists, returns all parts.
|
|
30297
30947
|
*/
|
|
30948
|
+
/** Re-reads of the head after an activation swap hit a generation retired by a reflection. */
|
|
30949
|
+
const MAX_ACTIVATION_RETIRED_RETRIES = 3;
|
|
30298
30950
|
function getLatestStepParts(parts) {
|
|
30299
30951
|
for (let i = parts.length - 1; i >= 0; i--) if (parts[i]?.type === "step-start") return parts.slice(i + 1);
|
|
30300
30952
|
return parts;
|
|
@@ -30488,6 +31140,8 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
30488
31140
|
reflector;
|
|
30489
31141
|
/** Buffering state coordinator — manages static maps and buffering lifecycle. */
|
|
30490
31142
|
buffering;
|
|
31143
|
+
/** Unresolved observation blockAfter (multiplier or absolute); resolved per record. */
|
|
31144
|
+
observationBlockAfterSetting;
|
|
30491
31145
|
shouldObscureThreadIds = false;
|
|
30492
31146
|
hasher = xxhash();
|
|
30493
31147
|
mastra;
|
|
@@ -30623,6 +31277,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
30623
31277
|
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.");
|
|
30624
31278
|
const observationActivateAfterIdle = config.observation?.activateAfterIdle ?? config.activateAfterIdle;
|
|
30625
31279
|
const observationActivateAfterIdlePath = config.observation?.activateAfterIdle !== void 0 ? "observation.activateAfterIdle" : "activateAfterIdle";
|
|
31280
|
+
this.observationBlockAfterSetting = asyncBufferingDisabled ? void 0 : config.observation?.blockAfter ?? (config.observation?.bufferTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferTokens ? 1.2 : void 0);
|
|
30626
31281
|
this.observationConfig = {
|
|
30627
31282
|
model: observationModel,
|
|
30628
31283
|
maxRetries: config.observation?.maxRetries ?? RETRY_CONFIG.maxRetries,
|
|
@@ -30643,8 +31298,8 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
30643
31298
|
bufferActivation: asyncBufferingDisabled ? void 0 : config.observation?.bufferActivation ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferActivation,
|
|
30644
31299
|
activateAfterIdle: parseActivationTTLConfig(observationActivateAfterIdle, observationActivateAfterIdlePath),
|
|
30645
31300
|
activateOnProviderChange: config.observation?.activateOnProviderChange ?? config.activateOnProviderChange ?? false,
|
|
30646
|
-
blockAfter:
|
|
30647
|
-
previousObserverTokens: config.observation?.previousObserverTokens ??
|
|
31301
|
+
blockAfter: resolveBlockAfter(this.observationBlockAfterSetting, config.observation?.messageTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens),
|
|
31302
|
+
previousObserverTokens: config.observation?.previousObserverTokens ?? false,
|
|
30648
31303
|
instruction: config.observation?.instruction,
|
|
30649
31304
|
threadTitle: config.observation?.threadTitle ?? false,
|
|
30650
31305
|
observeAttachments: config.observation?.observeAttachments ?? [...OBSERVATIONAL_MEMORY_DEFAULTS.observation.observeAttachments],
|
|
@@ -30953,6 +31608,13 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
30953
31608
|
return this.observationConfig.messageTokens;
|
|
30954
31609
|
}
|
|
30955
31610
|
/**
|
|
31611
|
+
* Resolve the observation blockAfter for a record. A multiplier scales the record's
|
|
31612
|
+
* effective messageTokens (per-record overrides included); an absolute value is used as-is.
|
|
31613
|
+
*/
|
|
31614
|
+
getEffectiveObservationBlockAfter(record) {
|
|
31615
|
+
return resolveBlockAfter(this.observationBlockAfterSetting, this.getEffectiveMessageTokens(record));
|
|
31616
|
+
}
|
|
31617
|
+
/**
|
|
30956
31618
|
* Resolve the effective reflection observationTokens for a record.
|
|
30957
31619
|
* Only explicit per-record overrides (stored under `_overrides`) win;
|
|
30958
31620
|
* the initial config snapshot is ignored so instance-level changes
|
|
@@ -31066,18 +31728,21 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
31066
31728
|
* the latest user input that triggered it.
|
|
31067
31729
|
* @internal Used by observation strategies and the reflector. Do not call directly.
|
|
31068
31730
|
*/
|
|
31069
|
-
async persistMarkerToStorage(marker, threadId, resourceId) {
|
|
31731
|
+
async persistMarkerToStorage(marker, threadId, resourceId, opts) {
|
|
31070
31732
|
try {
|
|
31071
|
-
const
|
|
31733
|
+
const markerData = marker.data;
|
|
31734
|
+
const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
|
|
31735
|
+
const preferred = [...opts?.preferMessages ?? []].reverse().find((m) => m.role === targetRole && (!m.threadId || m.threadId === threadId));
|
|
31736
|
+
const preferredStored = preferred ? (await this.storage.listMessagesById({ messageIds: [preferred.id] })).messages : [];
|
|
31737
|
+
const messages = (preferredStored.length ? { messages: preferredStored } : await this.storage.listMessages({
|
|
31072
31738
|
threadId,
|
|
31073
31739
|
perPage: 20,
|
|
31074
31740
|
orderBy: {
|
|
31075
31741
|
field: "createdAt",
|
|
31076
31742
|
direction: "DESC"
|
|
31077
|
-
}
|
|
31743
|
+
},
|
|
31744
|
+
...opts?.notAfter ? { filter: { dateRange: { end: opts.notAfter } } } : {}
|
|
31078
31745
|
}))?.messages ?? [];
|
|
31079
|
-
const markerData = marker.data;
|
|
31080
|
-
const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
|
|
31081
31746
|
for (const msg of messages) if (msg?.role === targetRole && msg.content?.parts && Array.isArray(msg.content.parts)) {
|
|
31082
31747
|
if (!(markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) msg.content.parts.push(marker);
|
|
31083
31748
|
await this.messageHistory.persistMessages({
|
|
@@ -31164,7 +31829,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
31164
31829
|
*/
|
|
31165
31830
|
createUnobservedMessage(message) {
|
|
31166
31831
|
const unobservedParts = getUnobservedParts(message);
|
|
31167
|
-
if (unobservedParts.
|
|
31832
|
+
if (unobservedParts.every((part) => part?.type?.startsWith("data-om-"))) return null;
|
|
31168
31833
|
return {
|
|
31169
31834
|
...message,
|
|
31170
31835
|
content: {
|
|
@@ -31536,17 +32201,17 @@ ${formattedMessages}
|
|
|
31536
32201
|
async startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, contextWindowTokens, requestContext, observabilityContext) {
|
|
31537
32202
|
const bufferKey = this.buffering.getObservationBufferKey(lockKey);
|
|
31538
32203
|
const currentTokens = contextWindowTokens ?? await this.tokenCounter.countMessagesAsync(unobservedMessages) + (record.pendingMessageTokens ?? 0);
|
|
31539
|
-
BufferingCoordinator.
|
|
32204
|
+
BufferingCoordinator.setObservationBoundary(bufferKey, currentTokens);
|
|
31540
32205
|
registerOp(record.id, "bufferingObservation");
|
|
31541
|
-
this.storage.setBufferingObservationFlag(record.id, true, currentTokens).catch((err) => {
|
|
32206
|
+
runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, true, currentTokens)).catch((err) => {
|
|
31542
32207
|
omError("[OM] Failed to set buffering observation flag", err);
|
|
31543
32208
|
});
|
|
31544
32209
|
const asyncOp = this.runAsyncBufferedObservation(record, threadId, unobservedMessages, bufferKey, writer, requestContext, observabilityContext).catch((err) => {
|
|
31545
32210
|
omError("[OM] async buffering observation failed", err);
|
|
31546
32211
|
}).finally(() => {
|
|
31547
|
-
BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
|
|
32212
|
+
if (BufferingCoordinator.asyncBufferingOps.get(bufferKey) === asyncOp) BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
|
|
31548
32213
|
unregisterOp(record.id, "bufferingObservation");
|
|
31549
|
-
this.storage.setBufferingObservationFlag(record.id, false).catch((err) => {
|
|
32214
|
+
runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch((err) => {
|
|
31550
32215
|
omError("[OM] Failed to clear buffering observation flag", err);
|
|
31551
32216
|
});
|
|
31552
32217
|
});
|
|
@@ -31561,6 +32226,14 @@ ${formattedMessages}
|
|
|
31561
32226
|
if (existingOp) try {
|
|
31562
32227
|
await existingOp;
|
|
31563
32228
|
} catch {}
|
|
32229
|
+
const releaseChunkWrite = BufferingCoordinator.trackChunkWrite(bufferKey);
|
|
32230
|
+
try {
|
|
32231
|
+
await this.bufferObservationChunk(record, threadId, unobservedMessages, bufferKey, releaseChunkWrite, writer, requestContext, observabilityContext);
|
|
32232
|
+
} finally {
|
|
32233
|
+
releaseChunkWrite();
|
|
32234
|
+
}
|
|
32235
|
+
}
|
|
32236
|
+
async bufferObservationChunk(record, threadId, unobservedMessages, bufferKey, releaseChunkWrite, writer, requestContext, observabilityContext) {
|
|
31564
32237
|
const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
31565
32238
|
if (!freshRecord) return;
|
|
31566
32239
|
let bufferCursor = BufferingCoordinator.lastBufferedAtTime.get(bufferKey) ?? freshRecord.lastBufferedAtTime ?? null;
|
|
@@ -31601,13 +32274,16 @@ ${formattedMessages}
|
|
|
31601
32274
|
threadIds: [threadId],
|
|
31602
32275
|
config: this.getObservationMarkerConfig()
|
|
31603
32276
|
});
|
|
31604
|
-
await this.persistMarkerToStorage(startMarker, threadId, freshRecord.resourceId ?? void 0
|
|
32277
|
+
await this.persistMarkerToStorage(startMarker, threadId, freshRecord.resourceId ?? void 0, {
|
|
32278
|
+
notAfter: new Date(startedAt),
|
|
32279
|
+
preferMessages: messagesToBuffer
|
|
32280
|
+
});
|
|
31605
32281
|
if (writer) writer.custom({
|
|
31606
32282
|
...startMarker,
|
|
31607
32283
|
transient: true
|
|
31608
32284
|
}).catch(() => {});
|
|
31609
32285
|
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(",")}`);
|
|
31610
|
-
|
|
32286
|
+
await this.runBufferedObservationCycle({
|
|
31611
32287
|
threadId,
|
|
31612
32288
|
resourceId: freshRecord.resourceId ?? void 0,
|
|
31613
32289
|
trigger: "async-buffer"
|
|
@@ -31621,12 +32297,13 @@ ${formattedMessages}
|
|
|
31621
32297
|
writer,
|
|
31622
32298
|
requestContext,
|
|
31623
32299
|
observabilityContext,
|
|
31624
|
-
trigger: "async-buffer"
|
|
31625
|
-
|
|
31626
|
-
|
|
31627
|
-
|
|
31628
|
-
|
|
31629
|
-
|
|
32300
|
+
trigger: "async-buffer",
|
|
32301
|
+
onBufferedChunkPersisted: async () => {
|
|
32302
|
+
const maxTs = this.getMaxMessageTimestamp(messagesToBuffer);
|
|
32303
|
+
BufferingCoordinator.lastBufferedAtTime.set(bufferKey, new Date(maxTs.getTime() + 1));
|
|
32304
|
+
releaseChunkWrite();
|
|
32305
|
+
}
|
|
32306
|
+
}).run());
|
|
31630
32307
|
}
|
|
31631
32308
|
/**
|
|
31632
32309
|
* Trigger async buffered observation if the token count has crossed a new interval.
|
|
@@ -31811,8 +32488,8 @@ ${formattedMessages}
|
|
|
31811
32488
|
const { threadId, resourceId, recordId, activatedMessageIds } = opts;
|
|
31812
32489
|
const lockKey = this.buffering.getLockKey(threadId, resourceId);
|
|
31813
32490
|
const bufKey = this.buffering.getObservationBufferKey(lockKey);
|
|
31814
|
-
BufferingCoordinator.
|
|
31815
|
-
await this.storage.setBufferingObservationFlag(recordId, false, 0).catch(() => {});
|
|
32491
|
+
BufferingCoordinator.setObservationBoundary(bufKey, 0);
|
|
32492
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(recordId, false, 0)).catch(() => {});
|
|
31816
32493
|
if (activatedMessageIds && activatedMessageIds.length > 0) this.buffering.cleanupStaticMaps(threadId, resourceId, activatedMessageIds);
|
|
31817
32494
|
}
|
|
31818
32495
|
/**
|
|
@@ -31971,9 +32648,13 @@ ${formattedMessages}
|
|
|
31971
32648
|
* @example
|
|
31972
32649
|
* ```ts
|
|
31973
32650
|
* const status = await om.getStatus({ threadId });
|
|
31974
|
-
* if (status.shouldObserve) {
|
|
32651
|
+
* if (status.shouldObserve && status.canActivate) {
|
|
32652
|
+
* await om.activate({ threadId });
|
|
32653
|
+
* } else if (status.shouldObserve && !status.inAsyncObservationBand) {
|
|
32654
|
+
* // At/above blockAfter (or async buffering disabled): observe synchronously
|
|
31975
32655
|
* await om.observe({ threadId });
|
|
31976
32656
|
* } else if (status.shouldBuffer) {
|
|
32657
|
+
* // Below the threshold, or in the threshold→blockAfter band: buffer in the background
|
|
31977
32658
|
* await om.buffer({ threadId });
|
|
31978
32659
|
* }
|
|
31979
32660
|
* if (status.shouldReflect) {
|
|
@@ -32003,8 +32684,10 @@ ${formattedMessages}
|
|
|
32003
32684
|
const bufferedChunkCount = bufferedChunks.length;
|
|
32004
32685
|
const bufferedChunkTokens = bufferedChunks.reduce((sum, chunk) => sum + (chunk.messageTokens ?? 0), 0);
|
|
32005
32686
|
const asyncObservationEnabled = this.buffering.isAsyncObservationEnabled();
|
|
32687
|
+
const observationBlockAfter = asyncObservationEnabled ? this.getEffectiveObservationBlockAfter(record) : void 0;
|
|
32688
|
+
const inAsyncObservationBand = observationBlockAfter !== void 0 && pendingTokens >= threshold && pendingTokens < observationBlockAfter;
|
|
32006
32689
|
let shouldBuffer = false;
|
|
32007
|
-
if (asyncObservationEnabled && pendingTokens < threshold) {
|
|
32690
|
+
if (asyncObservationEnabled && (pendingTokens < threshold || inAsyncObservationBand)) {
|
|
32008
32691
|
const lockKey = this.buffering.getLockKey(threadId, resourceId);
|
|
32009
32692
|
shouldBuffer = this.buffering.shouldTriggerAsyncObservation(pendingTokens, lockKey, record, this.storage, threshold);
|
|
32010
32693
|
}
|
|
@@ -32029,6 +32712,8 @@ ${formattedMessages}
|
|
|
32029
32712
|
canActivate,
|
|
32030
32713
|
asyncObservationEnabled,
|
|
32031
32714
|
asyncReflectionEnabled: this.buffering.isAsyncReflectionEnabled(),
|
|
32715
|
+
observationBlockAfter,
|
|
32716
|
+
inAsyncObservationBand,
|
|
32032
32717
|
scope: this.scope
|
|
32033
32718
|
};
|
|
32034
32719
|
}
|
|
@@ -32155,24 +32840,27 @@ ${formattedMessages}
|
|
|
32155
32840
|
const currentTokens = opts.pendingTokens ?? record.pendingMessageTokens ?? 0;
|
|
32156
32841
|
const lockKey = this.buffering.getLockKey(threadId, resourceId);
|
|
32157
32842
|
const bufferKey = this.buffering.getObservationBufferKey(lockKey);
|
|
32158
|
-
|
|
32159
|
-
|
|
32843
|
+
const boundaryOwner = Symbol("observation-boundary");
|
|
32844
|
+
BufferingCoordinator.setObservationBoundary(bufferKey, currentTokens, boundaryOwner);
|
|
32160
32845
|
const existingOp = BufferingCoordinator.asyncBufferingOps.get(bufferKey);
|
|
32846
|
+
let resolveOp;
|
|
32847
|
+
const opPromise = new Promise((resolve) => {
|
|
32848
|
+
resolveOp = resolve;
|
|
32849
|
+
});
|
|
32850
|
+
BufferingCoordinator.asyncBufferingOps.set(bufferKey, opPromise);
|
|
32851
|
+
if (record.isBufferingObservation) await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch(() => {});
|
|
32161
32852
|
if (existingOp) try {
|
|
32162
32853
|
await existingOp;
|
|
32163
32854
|
} catch {}
|
|
32164
|
-
|
|
32855
|
+
const opRecordId = record.id;
|
|
32856
|
+
registerOp(opRecordId, "bufferingObservation");
|
|
32857
|
+
const releaseChunkWrite = BufferingCoordinator.trackChunkWrite(bufferKey);
|
|
32858
|
+
const ownsStartBoundary = BufferingCoordinator.ownsObservationBoundary(bufferKey, boundaryOwner);
|
|
32165
32859
|
inMemoryRecord.isBufferingObservation = true;
|
|
32166
|
-
inMemoryRecord.lastBufferedAtTokens = currentTokens;
|
|
32167
|
-
this.storage.setBufferingObservationFlag(record.id, true, currentTokens).catch((err) => {
|
|
32860
|
+
if (ownsStartBoundary) inMemoryRecord.lastBufferedAtTokens = currentTokens;
|
|
32861
|
+
runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, true, ownsStartBoundary ? currentTokens : void 0)).catch((err) => {
|
|
32168
32862
|
omError("[OM] Failed to set buffering observation flag", err);
|
|
32169
32863
|
});
|
|
32170
|
-
let resolveOp;
|
|
32171
|
-
const opPromise = new Promise((resolve) => {
|
|
32172
|
-
resolveOp = resolve;
|
|
32173
|
-
});
|
|
32174
|
-
BufferingCoordinator.asyncBufferingOps.set(bufferKey, opPromise);
|
|
32175
|
-
record = await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record;
|
|
32176
32864
|
const setBufferingState = (isBufferingObservation, lastBufferedAtTokens) => {
|
|
32177
32865
|
inMemoryRecord.isBufferingObservation = isBufferingObservation;
|
|
32178
32866
|
record.isBufferingObservation = isBufferingObservation;
|
|
@@ -32183,6 +32871,7 @@ ${formattedMessages}
|
|
|
32183
32871
|
};
|
|
32184
32872
|
let flagCleared = false;
|
|
32185
32873
|
try {
|
|
32874
|
+
record = await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record;
|
|
32186
32875
|
let candidateMessages;
|
|
32187
32876
|
if (opts.messages) candidateMessages = this.getUnobservedMessages(opts.messages, record, { excludeBuffered: true });
|
|
32188
32877
|
else {
|
|
@@ -32222,12 +32911,29 @@ ${formattedMessages}
|
|
|
32222
32911
|
threadIds: [threadId],
|
|
32223
32912
|
config: this.getObservationMarkerConfig()
|
|
32224
32913
|
});
|
|
32225
|
-
await this.persistMarkerToStorage(startMarker, threadId, record.resourceId ?? void 0
|
|
32914
|
+
await this.persistMarkerToStorage(startMarker, threadId, record.resourceId ?? void 0, {
|
|
32915
|
+
notAfter: new Date(startedAt),
|
|
32916
|
+
preferMessages: candidateMessages
|
|
32917
|
+
});
|
|
32226
32918
|
const writer = opts.writer;
|
|
32227
32919
|
if (writer) writer.custom({
|
|
32228
32920
|
...startMarker,
|
|
32229
32921
|
transient: true
|
|
32230
32922
|
}).catch(() => {});
|
|
32923
|
+
let chunkRecorded = false;
|
|
32924
|
+
const recordBufferedChunk = async () => {
|
|
32925
|
+
if (chunkRecorded) return;
|
|
32926
|
+
chunkRecorded = true;
|
|
32927
|
+
const ownsBoundary = BufferingCoordinator.ownsObservationBoundary(bufferKey, boundaryOwner);
|
|
32928
|
+
if (ownsBoundary) BufferingCoordinator.setObservationBoundary(bufferKey, newTokens, boundaryOwner);
|
|
32929
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false, ownsBoundary ? newTokens : void 0)).catch(() => {});
|
|
32930
|
+
flagCleared = true;
|
|
32931
|
+
const stillOwnsBoundary = ownsBoundary && BufferingCoordinator.ownsObservationBoundary(bufferKey, boundaryOwner);
|
|
32932
|
+
setBufferingState(false, stillOwnsBoundary ? newTokens : void 0);
|
|
32933
|
+
const maxTimestamp = this.getMaxMessageTimestamp(candidateMessages);
|
|
32934
|
+
BufferingCoordinator.lastBufferedAtTime.set(bufferKey, new Date(maxTimestamp.getTime() + 1));
|
|
32935
|
+
releaseChunkWrite();
|
|
32936
|
+
};
|
|
32231
32937
|
if (!(await this.runBufferedObservationCycle({
|
|
32232
32938
|
threadId,
|
|
32233
32939
|
resourceId: record.resourceId ?? resourceId,
|
|
@@ -32246,7 +32952,8 @@ ${formattedMessages}
|
|
|
32246
32952
|
requestContext,
|
|
32247
32953
|
currentModel: opts.currentModel,
|
|
32248
32954
|
observabilityContext,
|
|
32249
|
-
trigger: "async-buffer"
|
|
32955
|
+
trigger: "async-buffer",
|
|
32956
|
+
onBufferedChunkPersisted: recordBufferedChunk
|
|
32250
32957
|
}).run()))?.observed) return {
|
|
32251
32958
|
buffered: false,
|
|
32252
32959
|
record
|
|
@@ -32266,13 +32973,7 @@ ${formattedMessages}
|
|
|
32266
32973
|
newTokens
|
|
32267
32974
|
}
|
|
32268
32975
|
});
|
|
32269
|
-
await
|
|
32270
|
-
flagCleared = true;
|
|
32271
|
-
setBufferingState(false, newTokens);
|
|
32272
|
-
BufferingCoordinator.lastBufferedBoundary.set(bufferKey, newTokens);
|
|
32273
|
-
const maxTimestamp = this.getMaxMessageTimestamp(candidateMessages);
|
|
32274
|
-
const cursor = new Date(maxTimestamp.getTime() + 1);
|
|
32275
|
-
BufferingCoordinator.lastBufferedAtTime.set(bufferKey, cursor);
|
|
32976
|
+
await recordBufferedChunk();
|
|
32276
32977
|
return {
|
|
32277
32978
|
buffered: true,
|
|
32278
32979
|
record: await this.storage.getObservationalMemory(record.threadId, record.resourceId) ?? record
|
|
@@ -32284,12 +32985,13 @@ ${formattedMessages}
|
|
|
32284
32985
|
record
|
|
32285
32986
|
};
|
|
32286
32987
|
} finally {
|
|
32287
|
-
|
|
32288
|
-
|
|
32988
|
+
releaseChunkWrite();
|
|
32989
|
+
unregisterOp(opRecordId, "bufferingObservation");
|
|
32990
|
+
if (BufferingCoordinator.asyncBufferingOps.get(bufferKey) === opPromise) BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
|
|
32289
32991
|
resolveOp();
|
|
32290
32992
|
if (!flagCleared) {
|
|
32291
32993
|
setBufferingState(false);
|
|
32292
|
-
await this.storage.setBufferingObservationFlag(record.id, false).catch(() => {});
|
|
32994
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false)).catch(() => {});
|
|
32293
32995
|
}
|
|
32294
32996
|
}
|
|
32295
32997
|
}
|
|
@@ -32326,8 +33028,8 @@ ${formattedMessages}
|
|
|
32326
33028
|
const currentContextTokens = this.tokenCounter.countMessages(unobserved);
|
|
32327
33029
|
if (currentContextTokens < dbBoundary * .5) {
|
|
32328
33030
|
omDebug(`[OM:activate] resetting stale lastBufferedBoundary: dbBoundary=${dbBoundary}, currentContextTokens=${currentContextTokens}`);
|
|
32329
|
-
BufferingCoordinator.
|
|
32330
|
-
await this.storage.setBufferingObservationFlag(record.id, false, 0).catch(() => {});
|
|
33031
|
+
BufferingCoordinator.setObservationBoundary(bufKey, 0);
|
|
33032
|
+
await runOMCommit(lockKey, () => this.storage.setBufferingObservationFlag(record.id, false, 0)).catch(() => {});
|
|
32331
33033
|
}
|
|
32332
33034
|
}
|
|
32333
33035
|
}
|
|
@@ -32373,39 +33075,63 @@ ${formattedMessages}
|
|
|
32373
33075
|
livePendingTokens ??= status.pendingTokens;
|
|
32374
33076
|
}
|
|
32375
33077
|
}
|
|
32376
|
-
|
|
32377
|
-
|
|
32378
|
-
|
|
32379
|
-
const
|
|
32380
|
-
|
|
32381
|
-
|
|
32382
|
-
|
|
33078
|
+
const obsBufferKey = this.buffering.getObservationBufferKey(this.buffering.getLockKey(threadId, resourceId));
|
|
33079
|
+
const pendingChunkWrite = BufferingCoordinator.pendingChunkWrites.get(obsBufferKey);
|
|
33080
|
+
if (pendingChunkWrite) {
|
|
33081
|
+
const blockAfter = this.buffering.isAsyncObservationEnabled() ? this.getEffectiveObservationBlockAfter(record) : void 0;
|
|
33082
|
+
const threshold = calculateDynamicThreshold(this.getEffectiveMessageTokens(record), record.observationTokenCount ?? 0);
|
|
33083
|
+
if (activationTriggeredBy === "threshold" && !(livePendingTokens !== void 0 && blockAfter !== void 0 && livePendingTokens >= threshold && livePendingTokens < blockAfter)) {
|
|
33084
|
+
let timeoutId;
|
|
33085
|
+
await Promise.race([pendingChunkWrite, new Promise((resolve) => timeoutId = setTimeout(resolve, 6e4))]);
|
|
33086
|
+
clearTimeout(timeoutId);
|
|
33087
|
+
}
|
|
33088
|
+
}
|
|
33089
|
+
const commitKey = this.buffering.getLockKey(threadId, resourceId);
|
|
33090
|
+
let freshRecord = null;
|
|
33091
|
+
let freshChunks = [];
|
|
33092
|
+
let activationResult;
|
|
33093
|
+
for (let attempt = 0; attempt <= MAX_ACTIVATION_RETIRED_RETRIES; attempt++) {
|
|
33094
|
+
const swapped = await runOMCommit(commitKey, async () => {
|
|
33095
|
+
const head = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
33096
|
+
if (!head) return null;
|
|
33097
|
+
const chunks = getBufferedChunks(head);
|
|
33098
|
+
if (!chunks.length) return null;
|
|
33099
|
+
const messageTokensThreshold = getMaxThreshold(this.getEffectiveMessageTokens(head));
|
|
33100
|
+
const activationRatio = resolveActivationRatio(this.observationConfig.bufferActivation ?? .7, messageTokensThreshold);
|
|
33101
|
+
const totalChunkMessageTokens = chunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
33102
|
+
const currentPendingTokens = livePendingTokens ?? (head.pendingMessageTokens || totalChunkMessageTokens);
|
|
33103
|
+
const blockAfter = this.getEffectiveObservationBlockAfter(head);
|
|
33104
|
+
const forceMaxActivation = !!(blockAfter && currentPendingTokens >= blockAfter);
|
|
33105
|
+
if (head.pendingMessageTokens !== currentPendingTokens) await this.storage.setPendingMessageTokens(head.id, currentPendingTokens);
|
|
33106
|
+
return {
|
|
33107
|
+
head,
|
|
33108
|
+
chunks,
|
|
33109
|
+
result: await this.storage.swapBufferedToActive({
|
|
33110
|
+
id: head.id,
|
|
33111
|
+
activationRatio,
|
|
33112
|
+
messageTokensThreshold,
|
|
33113
|
+
currentPendingTokens,
|
|
33114
|
+
forceMaxActivation,
|
|
33115
|
+
bufferedChunks: chunks
|
|
33116
|
+
})
|
|
33117
|
+
};
|
|
33118
|
+
});
|
|
33119
|
+
if (!swapped) return {
|
|
33120
|
+
activated: false,
|
|
33121
|
+
record
|
|
33122
|
+
};
|
|
33123
|
+
freshRecord = swapped.head;
|
|
33124
|
+
freshChunks = swapped.chunks;
|
|
33125
|
+
activationResult = swapped.result;
|
|
33126
|
+
if (!activationResult.retired) break;
|
|
33127
|
+
omDebug(`[OM:activate] record ${freshRecord.id} was retired by a reflection; retrying on the head`);
|
|
32383
33128
|
}
|
|
32384
|
-
|
|
32385
|
-
if (!freshRecord) return {
|
|
33129
|
+
if (!freshRecord || !activationResult || activationResult.retired) return {
|
|
32386
33130
|
activated: false,
|
|
32387
|
-
record
|
|
33131
|
+
record: await this.getRecord(threadId, resourceId) ?? record
|
|
32388
33132
|
};
|
|
32389
|
-
const
|
|
32390
|
-
|
|
32391
|
-
activated: false,
|
|
32392
|
-
record
|
|
32393
|
-
};
|
|
32394
|
-
const messageTokensThreshold = getMaxThreshold(this.getEffectiveMessageTokens(freshRecord));
|
|
32395
|
-
const activationRatio = resolveActivationRatio(this.observationConfig.bufferActivation ?? .7, messageTokensThreshold);
|
|
32396
|
-
const totalChunkMessageTokens = freshChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
|
|
32397
|
-
const currentPendingTokens = livePendingTokens ?? (freshRecord.pendingMessageTokens || totalChunkMessageTokens);
|
|
32398
|
-
const forceMaxActivation = !!(this.observationConfig.blockAfter && currentPendingTokens >= this.observationConfig.blockAfter);
|
|
32399
|
-
if (freshRecord.pendingMessageTokens !== currentPendingTokens) await this.storage.setPendingMessageTokens(freshRecord.id, currentPendingTokens);
|
|
32400
|
-
const activationResult = await this.storage.swapBufferedToActive({
|
|
32401
|
-
id: freshRecord.id,
|
|
32402
|
-
activationRatio,
|
|
32403
|
-
messageTokensThreshold,
|
|
32404
|
-
currentPendingTokens,
|
|
32405
|
-
forceMaxActivation,
|
|
32406
|
-
bufferedChunks: freshChunks
|
|
32407
|
-
});
|
|
32408
|
-
await this.storage.setBufferingObservationFlag(freshRecord.id, false).catch(() => {});
|
|
33133
|
+
const swappedRecordId = freshRecord.id;
|
|
33134
|
+
await runOMCommit(commitKey, () => this.storage.setBufferingObservationFlag(swappedRecordId, false)).catch(() => {});
|
|
32409
33135
|
unregisterOp(freshRecord.id, "bufferingObservation");
|
|
32410
33136
|
const postSwapRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
32411
33137
|
if (opts.writer && postSwapRecord && activationResult.activatedCycleIds.length > 0) {
|
|
@@ -32650,7 +33376,7 @@ ${formattedMessages}
|
|
|
32650
33376
|
* ```
|
|
32651
33377
|
*/
|
|
32652
33378
|
async reflect(threadId, resourceId, prompt, requestContext, observabilityContext) {
|
|
32653
|
-
const record = await this.getOrCreateRecord(threadId, resourceId);
|
|
33379
|
+
const record = { ...await this.getOrCreateRecord(threadId, resourceId) };
|
|
32654
33380
|
if (!record.activeObservations) return {
|
|
32655
33381
|
reflected: false,
|
|
32656
33382
|
record,
|
|
@@ -32695,11 +33421,23 @@ ${formattedMessages}
|
|
|
32695
33421
|
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);
|
|
32696
33422
|
reflectResult.observations = await applyTextTransform(this.hooks, "afterReflection", reflectResult.observations, hookContext);
|
|
32697
33423
|
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
32698
|
-
|
|
33424
|
+
const newRecordId = crypto.randomUUID();
|
|
33425
|
+
const committedRecord = await runOMCommit(this.buffering.getLockKey(threadId, resourceId), () => this.storage.createReflectionGeneration({
|
|
32699
33426
|
currentRecord: record,
|
|
32700
33427
|
reflection: reflectResult.observations,
|
|
32701
|
-
tokenCount: reflectionTokenCount
|
|
32702
|
-
|
|
33428
|
+
tokenCount: reflectionTokenCount,
|
|
33429
|
+
newRecordId
|
|
33430
|
+
}), { priority: "reflection" });
|
|
33431
|
+
reflectionUsage = reflectResult.usage;
|
|
33432
|
+
reflectionProviderMetadata = reflectResult.providerMetadata;
|
|
33433
|
+
if (!isReflectionApplied(committedRecord, newRecordId, record.id)) {
|
|
33434
|
+
omDebug(`[OM:reflect] manual reflection of ${record.id} not applied (head is ${committedRecord.id})`);
|
|
33435
|
+
return {
|
|
33436
|
+
reflected: false,
|
|
33437
|
+
record: await this.getOrCreateRecord(threadId, resourceId),
|
|
33438
|
+
usage: reflectResult.usage
|
|
33439
|
+
};
|
|
33440
|
+
}
|
|
32703
33441
|
if (thread && reflectResult.extractedValues) {
|
|
32704
33442
|
const metadataUpdate = buildThreadMetadataFromExtractedValues(this.reflectionConfig.extractors, reflectResult.extractedValues);
|
|
32705
33443
|
const newMetadata = setThreadOMMetadata(thread.metadata, {
|
|
@@ -32788,11 +33526,13 @@ ${formattedMessages}
|
|
|
32788
33526
|
*/
|
|
32789
33527
|
async updateRecordConfig(threadId, resourceId, config) {
|
|
32790
33528
|
const ids = this.getStorageIds(threadId, resourceId);
|
|
32791
|
-
|
|
32792
|
-
|
|
32793
|
-
|
|
32794
|
-
|
|
32795
|
-
|
|
33529
|
+
await runOMCommit(this.buffering.getLockKey(threadId, resourceId), async () => {
|
|
33530
|
+
const record = await this.storage.getObservationalMemory(ids.threadId, ids.resourceId);
|
|
33531
|
+
if (!record) throw new Error(`No observational memory record found for thread ${ids.threadId}`);
|
|
33532
|
+
await this.storage.updateObservationalMemoryConfig({
|
|
33533
|
+
id: record.id,
|
|
33534
|
+
config: { _overrides: config }
|
|
33535
|
+
});
|
|
32796
33536
|
});
|
|
32797
33537
|
}
|
|
32798
33538
|
/**
|
|
@@ -33186,7 +33926,7 @@ var ObservationalMemoryProcessor = class {
|
|
|
33186
33926
|
});
|
|
33187
33927
|
const finalTotalPending = ctx.status.pendingTokens;
|
|
33188
33928
|
try {
|
|
33189
|
-
await this.engine.getStorage().setPendingMessageTokens(turnRecord.id, finalTotalPending);
|
|
33929
|
+
await runOMCommit(this.engine.buffering.getLockKey(threadId, resourceId), () => this.engine.getStorage().setPendingMessageTokens(turnRecord.id, finalTotalPending));
|
|
33190
33930
|
this.turn.patchRecord({ pendingMessageTokens: finalTotalPending });
|
|
33191
33931
|
} catch {}
|
|
33192
33932
|
if (reproCaptureEnabled) writeProcessInputStepReproCapture({
|
|
@@ -35521,6 +36261,7 @@ Notes:
|
|
|
35521
36261
|
cloned.id = crypto.randomUUID();
|
|
35522
36262
|
cloned.createdAt = now;
|
|
35523
36263
|
cloned.updatedAt = now;
|
|
36264
|
+
cloned.supersededBy = null;
|
|
35524
36265
|
await memoryStore.insertObservationalMemoryRecord(cloned);
|
|
35525
36266
|
}
|
|
35526
36267
|
/**
|
|
@@ -35814,4 +36555,4 @@ Notes:
|
|
|
35814
36555
|
//#endregion
|
|
35815
36556
|
export { renderSubconsciousActivity as $, buildObserverSystemPrompt as A, OBSERVATION_CONTEXT_INSTRUCTIONS as B, WorkingMemoryExtractor as C, TokenCounter as D, summarizeConversation as E, parseObserverOutput as F, Subconscious as G, OBSERVATION_CONTEXT_PROMPT_THREAD as H, injectAnchorIds as I, SubconsciousRemindExtractor as J, KnowledgeSemanticIndexCoordinator as K, parseAnchorId as L, formatMessagesForObserver as M, hasCurrentTaskSection as N, OBSERVER_SYSTEM_PROMPT as O, optimizeObservationsForContext as P, publishSubconsciousActivity as Q, stripEphemeralAnchorIds as R, wrapInObservationGroup as S, SUMMARIZE_THREAD_DEFAULTS as T, OBSERVATION_CONTINUATION_HINT as U, OBSERVATION_CONTEXT_PROMPT as V, getObservationContextPrompt as W, SUBCONSCIOUS_ACTIVITY_STATE_ID as X, ModelByInputTokens as Y, buildSubconsciousActivitySnapshot as Z, deriveObservationGroupProvenance as _, extractWorkingMemoryContent as a, renderObservationGroupsForReflection as b, getObservationsAsOf as c, AUTO_MODEL_BY_PROVIDER as d, Extractor as et, resolveAutoModelId as f, combineObservationGroupRanges as g, WorkingMemoryStateProcessor as h, WorkingMemory as i, extractCurrentTask as j, buildObserverPrompt as k, ObservationalMemoryProcessor as l, WORKING_MEMORY_STATE_PROCESSOR_ID as m, MessageHistory$1 as n, extractWorkingMemoryTags as o, WORKING_MEMORY_STATE_ID as p, StaleKnowledgeSemanticIndexError as q, SemanticRecall as r, removeWorkingMemoryTags as s, Memory as t, ObservationalMemory as u, parseObservationGroups as v, deepMergeWorkingMemory as w, stripObservationGroups as x, reconcileObservationGroupsFromReflection as y, OBSERVATIONAL_MEMORY_DEFAULTS as z };
|
|
35816
36557
|
|
|
35817
|
-
//# sourceMappingURL=src-
|
|
36558
|
+
//# sourceMappingURL=src-ThciS3vu.js.map
|