@mastra/memory 1.1.0-alpha.1 → 1.2.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +112 -0
- package/dist/_types/@internal_ai-sdk-v4/dist/index.d.ts +30 -17
- package/dist/{chunk-FQJWVCDF.cjs → chunk-AWE2QQPI.cjs} +1884 -312
- package/dist/chunk-AWE2QQPI.cjs.map +1 -0
- package/dist/chunk-EQ4M72KU.js +439 -0
- package/dist/chunk-EQ4M72KU.js.map +1 -0
- package/dist/{chunk-O3CS4UGX.cjs → chunk-IDRQZVB4.cjs} +4 -4
- package/dist/{chunk-O3CS4UGX.cjs.map → chunk-IDRQZVB4.cjs.map} +1 -1
- package/dist/{chunk-YF4R74L2.js → chunk-RC6RZVYE.js} +4 -4
- package/dist/{chunk-YF4R74L2.js.map → chunk-RC6RZVYE.js.map} +1 -1
- package/dist/{chunk-6TXUWFIU.js → chunk-TYVPTNCP.js} +1885 -313
- package/dist/chunk-TYVPTNCP.js.map +1 -0
- package/dist/chunk-ZD3BKU5O.cjs +441 -0
- package/dist/chunk-ZD3BKU5O.cjs.map +1 -0
- package/dist/docs/SKILL.md +51 -50
- package/dist/docs/{SOURCE_MAP.json → assets/SOURCE_MAP.json} +22 -22
- package/dist/docs/{agents/03-agent-approval.md → references/docs-agents-agent-approval.md} +19 -19
- package/dist/docs/references/docs-agents-agent-memory.md +212 -0
- package/dist/docs/{agents/04-network-approval.md → references/docs-agents-network-approval.md} +13 -12
- package/dist/docs/{agents/02-networks.md → references/docs-agents-networks.md} +10 -12
- package/dist/docs/{memory/06-memory-processors.md → references/docs-memory-memory-processors.md} +6 -8
- package/dist/docs/{memory/03-message-history.md → references/docs-memory-message-history.md} +31 -20
- package/dist/docs/references/docs-memory-observational-memory.md +238 -0
- package/dist/docs/{memory/01-overview.md → references/docs-memory-overview.md} +8 -8
- package/dist/docs/{memory/05-semantic-recall.md → references/docs-memory-semantic-recall.md} +33 -17
- package/dist/docs/{memory/02-storage.md → references/docs-memory-storage.md} +29 -39
- package/dist/docs/{memory/04-working-memory.md → references/docs-memory-working-memory.md} +16 -27
- package/dist/docs/references/reference-core-getMemory.md +50 -0
- package/dist/docs/references/reference-core-listMemory.md +56 -0
- package/dist/docs/references/reference-memory-clone-utilities.md +199 -0
- package/dist/docs/references/reference-memory-cloneThread.md +130 -0
- package/dist/docs/references/reference-memory-createThread.md +68 -0
- package/dist/docs/references/reference-memory-getThreadById.md +24 -0
- package/dist/docs/references/reference-memory-listThreads.md +145 -0
- package/dist/docs/references/reference-memory-memory-class.md +147 -0
- package/dist/docs/references/reference-memory-observational-memory.md +528 -0
- package/dist/docs/{processors/01-reference.md → references/reference-processors-token-limiter-processor.md} +25 -12
- package/dist/docs/references/reference-storage-dynamodb.md +282 -0
- package/dist/docs/references/reference-storage-libsql.md +135 -0
- package/dist/docs/references/reference-storage-mongodb.md +262 -0
- package/dist/docs/references/reference-storage-postgresql.md +529 -0
- package/dist/docs/references/reference-storage-upstash.md +160 -0
- package/dist/docs/references/reference-vectors-libsql.md +305 -0
- package/dist/docs/references/reference-vectors-mongodb.md +295 -0
- package/dist/docs/references/reference-vectors-pg.md +408 -0
- package/dist/docs/references/reference-vectors-upstash.md +294 -0
- package/dist/index.cjs +919 -507
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +914 -502
- package/dist/index.js.map +1 -1
- package/dist/{observational-memory-3Q42SITP.cjs → observational-memory-3UO64HYD.cjs} +14 -14
- package/dist/{observational-memory-3Q42SITP.cjs.map → observational-memory-3UO64HYD.cjs.map} +1 -1
- package/dist/{observational-memory-VXLHOSDZ.js → observational-memory-TVHT3HP4.js} +3 -3
- package/dist/{observational-memory-VXLHOSDZ.js.map → observational-memory-TVHT3HP4.js.map} +1 -1
- package/dist/processors/index.cjs +12 -12
- package/dist/processors/index.js +1 -1
- package/dist/processors/observational-memory/index.d.ts +1 -1
- package/dist/processors/observational-memory/index.d.ts.map +1 -1
- package/dist/processors/observational-memory/observational-memory.d.ts +267 -1
- package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
- package/dist/processors/observational-memory/observer-agent.d.ts +3 -1
- package/dist/processors/observational-memory/observer-agent.d.ts.map +1 -1
- package/dist/processors/observational-memory/reflector-agent.d.ts +10 -3
- package/dist/processors/observational-memory/reflector-agent.d.ts.map +1 -1
- package/dist/processors/observational-memory/types.d.ts +243 -19
- package/dist/processors/observational-memory/types.d.ts.map +1 -1
- package/dist/{token-6GSAFR2W-WGTMOPEU.js → token-APYSY3BW-2DN6RAUY.js} +11 -11
- package/dist/token-APYSY3BW-2DN6RAUY.js.map +1 -0
- package/dist/{token-6GSAFR2W-2B4WM6AQ.cjs → token-APYSY3BW-ZQ7TMBY7.cjs} +14 -14
- package/dist/token-APYSY3BW-ZQ7TMBY7.cjs.map +1 -0
- package/dist/token-util-RMHT2CPJ-6TGPE335.cjs +10 -0
- package/dist/token-util-RMHT2CPJ-6TGPE335.cjs.map +1 -0
- package/dist/token-util-RMHT2CPJ-RJEA3FAN.js +8 -0
- package/dist/token-util-RMHT2CPJ-RJEA3FAN.js.map +1 -0
- package/dist/tools/working-memory.d.ts.map +1 -1
- package/package.json +9 -10
- package/dist/chunk-6TXUWFIU.js.map +0 -1
- package/dist/chunk-FQJWVCDF.cjs.map +0 -1
- package/dist/chunk-WM6IIUQW.js +0 -250
- package/dist/chunk-WM6IIUQW.js.map +0 -1
- package/dist/chunk-ZSBBXHNM.cjs +0 -252
- package/dist/chunk-ZSBBXHNM.cjs.map +0 -1
- package/dist/docs/README.md +0 -36
- package/dist/docs/agents/01-agent-memory.md +0 -166
- package/dist/docs/core/01-reference.md +0 -114
- package/dist/docs/memory/07-reference.md +0 -687
- package/dist/docs/storage/01-reference.md +0 -1218
- package/dist/docs/vectors/01-reference.md +0 -942
- package/dist/token-6GSAFR2W-2B4WM6AQ.cjs.map +0 -1
- package/dist/token-6GSAFR2W-WGTMOPEU.js.map +0 -1
- package/dist/token-util-NEHG7TUY-TV2H7N56.js +0 -8
- package/dist/token-util-NEHG7TUY-TV2H7N56.js.map +0 -1
- package/dist/token-util-NEHG7TUY-WJZIPNNX.cjs +0 -10
- package/dist/token-util-NEHG7TUY-WJZIPNNX.cjs.map +0 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { appendFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
1
3
|
import { Agent } from '@mastra/core/agent';
|
|
2
4
|
import { resolveModelConfig } from '@mastra/core/llm';
|
|
3
|
-
import {
|
|
5
|
+
import { getThreadOMMetadata, parseMemoryRequestContext, setThreadOMMetadata } from '@mastra/core/memory';
|
|
4
6
|
import { MessageHistory } from '@mastra/core/processors';
|
|
5
7
|
import xxhash from 'xxhash-wasm';
|
|
6
8
|
import { Tiktoken } from 'js-tiktoken/lite';
|
|
@@ -641,7 +643,7 @@ function parseMultiThreadObserverOutput(output) {
|
|
|
641
643
|
rawOutput: output
|
|
642
644
|
};
|
|
643
645
|
}
|
|
644
|
-
function buildObserverPrompt(existingObservations, messagesToObserve) {
|
|
646
|
+
function buildObserverPrompt(existingObservations, messagesToObserve, options) {
|
|
645
647
|
const formattedMessages = formatMessagesForObserver(messagesToObserve);
|
|
646
648
|
let prompt = "";
|
|
647
649
|
if (existingObservations) {
|
|
@@ -665,6 +667,11 @@ ${formattedMessages}
|
|
|
665
667
|
|
|
666
668
|
`;
|
|
667
669
|
prompt += `Extract new observations from the message history above. Do not repeat observations that are already in the previous observations. Add your new observations in the format specified in your instructions.`;
|
|
670
|
+
if (options?.skipContinuationHints) {
|
|
671
|
+
prompt += `
|
|
672
|
+
|
|
673
|
+
IMPORTANT: Do NOT include <current-task> or <suggested-response> sections in your output. Only output <observations>.`;
|
|
674
|
+
}
|
|
668
675
|
return prompt;
|
|
669
676
|
}
|
|
670
677
|
function parseObserverOutput(output) {
|
|
@@ -840,7 +847,9 @@ Hint for the agent's immediate next message. Examples:
|
|
|
840
847
|
|
|
841
848
|
User messages are extremely important. If the user asks a question or gives a new task, make it clear in <current-task> that this is the priority. If the assistant needs to respond to the user, indicate in <suggested-response> that it should pause for user reply before continuing other tasks.`;
|
|
842
849
|
}
|
|
843
|
-
var
|
|
850
|
+
var COMPRESSION_GUIDANCE = {
|
|
851
|
+
0: "",
|
|
852
|
+
1: `
|
|
844
853
|
## COMPRESSION REQUIRED
|
|
845
854
|
|
|
846
855
|
Your previous reflection was the same size or larger than the original observations.
|
|
@@ -853,8 +862,25 @@ Please re-process with slightly more compression:
|
|
|
853
862
|
- For example if there is a long nested observation list about repeated tool calls, you can combine those into a single line and observe that the tool was called multiple times for x reason, and finally y outcome happened.
|
|
854
863
|
|
|
855
864
|
Your current detail level was a 10/10, lets aim for a 8/10 detail level.
|
|
856
|
-
|
|
857
|
-
|
|
865
|
+
`,
|
|
866
|
+
2: `
|
|
867
|
+
## AGGRESSIVE COMPRESSION REQUIRED
|
|
868
|
+
|
|
869
|
+
Your previous reflection was still too large after compression guidance.
|
|
870
|
+
|
|
871
|
+
Please re-process with much more aggressive compression:
|
|
872
|
+
- Towards the beginning, heavily condense observations into high-level summaries
|
|
873
|
+
- Closer to the end, retain fine details (recent context matters more)
|
|
874
|
+
- Memory is getting very long - use a significantly more condensed style throughout
|
|
875
|
+
- Combine related items aggressively but do not lose important specific details of names, places, events, and people
|
|
876
|
+
- For example if there is a long nested observation list about repeated tool calls, you can combine those into a single line and observe that the tool was called multiple times for x reason, and finally y outcome happened.
|
|
877
|
+
- Remove redundant information and merge overlapping observations
|
|
878
|
+
|
|
879
|
+
Your current detail level was a 10/10, lets aim for a 6/10 detail level.
|
|
880
|
+
`
|
|
881
|
+
};
|
|
882
|
+
function buildReflectorPrompt(observations, manualPrompt, compressionLevel, skipContinuationHints) {
|
|
883
|
+
const level = typeof compressionLevel === "number" ? compressionLevel : compressionLevel ? 1 : 0;
|
|
858
884
|
let prompt = `## OBSERVATIONS TO REFLECT ON
|
|
859
885
|
|
|
860
886
|
${observations}
|
|
@@ -869,10 +895,16 @@ Please analyze these observations and produce a refined, condensed version that
|
|
|
869
895
|
|
|
870
896
|
${manualPrompt}`;
|
|
871
897
|
}
|
|
872
|
-
|
|
898
|
+
const guidance = COMPRESSION_GUIDANCE[level];
|
|
899
|
+
if (guidance) {
|
|
900
|
+
prompt += `
|
|
901
|
+
|
|
902
|
+
${guidance}`;
|
|
903
|
+
}
|
|
904
|
+
if (skipContinuationHints) {
|
|
873
905
|
prompt += `
|
|
874
906
|
|
|
875
|
-
|
|
907
|
+
IMPORTANT: Do NOT include <current-task> or <suggested-response> sections in your output. Only output <observations>.`;
|
|
876
908
|
}
|
|
877
909
|
return prompt;
|
|
878
910
|
}
|
|
@@ -1016,6 +1048,43 @@ var TokenCounter = class _TokenCounter {
|
|
|
1016
1048
|
};
|
|
1017
1049
|
|
|
1018
1050
|
// src/processors/observational-memory/observational-memory.ts
|
|
1051
|
+
var OM_DEBUG_LOG = process.env.OM_DEBUG ? join(process.cwd(), "om-debug.log") : null;
|
|
1052
|
+
function omDebug(msg) {
|
|
1053
|
+
if (!OM_DEBUG_LOG) return;
|
|
1054
|
+
try {
|
|
1055
|
+
appendFileSync(OM_DEBUG_LOG, `[${(/* @__PURE__ */ new Date()).toLocaleString()}] ${msg}
|
|
1056
|
+
`);
|
|
1057
|
+
} catch {
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
function omError(msg, err) {
|
|
1061
|
+
const errStr = err instanceof Error ? err.stack ?? err.message : err !== void 0 ? String(err) : "";
|
|
1062
|
+
const full = errStr ? `${msg}: ${errStr}` : msg;
|
|
1063
|
+
omDebug(`[OM:ERROR] ${full}`);
|
|
1064
|
+
}
|
|
1065
|
+
omDebug(`[OM:process-start] OM module loaded, pid=${process.pid}`);
|
|
1066
|
+
var activeOps = /* @__PURE__ */ new Set();
|
|
1067
|
+
function opKey(recordId, op) {
|
|
1068
|
+
return `${recordId}:${op}`;
|
|
1069
|
+
}
|
|
1070
|
+
function registerOp(recordId, op) {
|
|
1071
|
+
activeOps.add(opKey(recordId, op));
|
|
1072
|
+
}
|
|
1073
|
+
function unregisterOp(recordId, op) {
|
|
1074
|
+
activeOps.delete(opKey(recordId, op));
|
|
1075
|
+
}
|
|
1076
|
+
function isOpActiveInProcess(recordId, op) {
|
|
1077
|
+
return activeOps.has(opKey(recordId, op));
|
|
1078
|
+
}
|
|
1079
|
+
if (OM_DEBUG_LOG) {
|
|
1080
|
+
const _origConsoleError = console.error;
|
|
1081
|
+
console.error = (...args) => {
|
|
1082
|
+
omDebug(
|
|
1083
|
+
`[console.error] ${args.map((a) => a instanceof Error ? a.stack ?? a.message : typeof a === "object" && a !== null ? JSON.stringify(a) : String(a)).join(" ")}`
|
|
1084
|
+
);
|
|
1085
|
+
_origConsoleError.apply(console, args);
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1019
1088
|
function formatRelativeTime(date, currentDate) {
|
|
1020
1089
|
const diffMs = currentDate.getTime() - date.getTime();
|
|
1021
1090
|
const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
|
|
@@ -1184,7 +1253,12 @@ var OBSERVATIONAL_MEMORY_DEFAULTS = {
|
|
|
1184
1253
|
}
|
|
1185
1254
|
}
|
|
1186
1255
|
},
|
|
1187
|
-
maxTokensPerBatch: 1e4
|
|
1256
|
+
maxTokensPerBatch: 1e4,
|
|
1257
|
+
// Async buffering defaults (enabled by default)
|
|
1258
|
+
bufferTokens: 0.2,
|
|
1259
|
+
// Buffer every 20% of messageTokens
|
|
1260
|
+
bufferActivation: 0.8
|
|
1261
|
+
// Activate to retain 20% of threshold
|
|
1188
1262
|
},
|
|
1189
1263
|
reflection: {
|
|
1190
1264
|
model: "google/gemini-2.5-flash",
|
|
@@ -1200,10 +1274,13 @@ var OBSERVATIONAL_MEMORY_DEFAULTS = {
|
|
|
1200
1274
|
thinkingBudget: 1024
|
|
1201
1275
|
}
|
|
1202
1276
|
}
|
|
1203
|
-
}
|
|
1277
|
+
},
|
|
1278
|
+
// Async reflection buffering (enabled by default)
|
|
1279
|
+
bufferActivation: 0.5
|
|
1280
|
+
// Start buffering at 50% of observationTokens
|
|
1204
1281
|
}
|
|
1205
1282
|
};
|
|
1206
|
-
var ObservationalMemory = class {
|
|
1283
|
+
var ObservationalMemory = class _ObservationalMemory {
|
|
1207
1284
|
id = "observational-memory";
|
|
1208
1285
|
name = "Observational Memory";
|
|
1209
1286
|
storage;
|
|
@@ -1240,6 +1317,210 @@ var ObservationalMemory = class {
|
|
|
1240
1317
|
* accept eventual consistency (acceptable for v1).
|
|
1241
1318
|
*/
|
|
1242
1319
|
locks = /* @__PURE__ */ new Map();
|
|
1320
|
+
/**
|
|
1321
|
+
* Track in-flight async buffering operations per resource/thread.
|
|
1322
|
+
* STATIC: Shared across all ObservationalMemory instances in this process.
|
|
1323
|
+
* This is critical because multiple OM instances are created per agent loop step,
|
|
1324
|
+
* and we need them to share knowledge of in-flight operations.
|
|
1325
|
+
* Key format: "obs:{lockKey}" or "refl:{lockKey}"
|
|
1326
|
+
* Value: Promise that resolves when buffering completes
|
|
1327
|
+
*/
|
|
1328
|
+
static asyncBufferingOps = /* @__PURE__ */ new Map();
|
|
1329
|
+
/**
|
|
1330
|
+
* Track the last token boundary at which we started buffering.
|
|
1331
|
+
* STATIC: Shared across all instances so boundary tracking persists across OM recreations.
|
|
1332
|
+
* Key format: "obs:{lockKey}" or "refl:{lockKey}"
|
|
1333
|
+
*/
|
|
1334
|
+
static lastBufferedBoundary = /* @__PURE__ */ new Map();
|
|
1335
|
+
/**
|
|
1336
|
+
* Track the timestamp cursor for buffered messages.
|
|
1337
|
+
* STATIC: Shared across all instances so each buffer only observes messages
|
|
1338
|
+
* newer than the previous buffer's boundary.
|
|
1339
|
+
* Key format: "obs:{lockKey}"
|
|
1340
|
+
*/
|
|
1341
|
+
static lastBufferedAtTime = /* @__PURE__ */ new Map();
|
|
1342
|
+
/**
|
|
1343
|
+
* Tracks cycleId for in-flight buffered reflections.
|
|
1344
|
+
* STATIC: Shared across instances so we can match cycleId at activation time.
|
|
1345
|
+
* Key format: "refl:{lockKey}"
|
|
1346
|
+
*/
|
|
1347
|
+
static reflectionBufferCycleIds = /* @__PURE__ */ new Map();
|
|
1348
|
+
/**
|
|
1349
|
+
* Track message IDs that have been sealed during async buffering.
|
|
1350
|
+
* STATIC: Shared across all instances so saveMessagesWithSealedIdTracking
|
|
1351
|
+
* generates new IDs when re-saving messages that were sealed in a previous step.
|
|
1352
|
+
* Key format: threadId
|
|
1353
|
+
* Value: Set of sealed message IDs
|
|
1354
|
+
*/
|
|
1355
|
+
static sealedMessageIds = /* @__PURE__ */ new Map();
|
|
1356
|
+
/**
|
|
1357
|
+
* Check if async buffering is enabled for observations.
|
|
1358
|
+
*/
|
|
1359
|
+
isAsyncObservationEnabled() {
|
|
1360
|
+
const enabled = this.observationConfig.bufferTokens !== void 0 && this.observationConfig.bufferTokens > 0;
|
|
1361
|
+
return enabled;
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Check if async buffering is enabled for reflections.
|
|
1365
|
+
* Reflection buffering is enabled when bufferActivation is set (triggers at threshold * bufferActivation).
|
|
1366
|
+
*/
|
|
1367
|
+
isAsyncReflectionEnabled() {
|
|
1368
|
+
return this.reflectionConfig.bufferActivation !== void 0 && this.reflectionConfig.bufferActivation > 0;
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Get the buffer interval boundary key for observations.
|
|
1372
|
+
*/
|
|
1373
|
+
getObservationBufferKey(lockKey) {
|
|
1374
|
+
return `obs:${lockKey}`;
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Get the buffer interval boundary key for reflections.
|
|
1378
|
+
*/
|
|
1379
|
+
getReflectionBufferKey(lockKey) {
|
|
1380
|
+
return `refl:${lockKey}`;
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Clean up static maps for a thread/resource to prevent memory leaks.
|
|
1384
|
+
* Called after activation (to remove activated message IDs from sealedMessageIds)
|
|
1385
|
+
* and from clear() (to fully remove all static state for a thread).
|
|
1386
|
+
*/
|
|
1387
|
+
cleanupStaticMaps(threadId, resourceId, activatedMessageIds) {
|
|
1388
|
+
const lockKey = this.getLockKey(threadId, resourceId);
|
|
1389
|
+
const obsBufKey = this.getObservationBufferKey(lockKey);
|
|
1390
|
+
const reflBufKey = this.getReflectionBufferKey(lockKey);
|
|
1391
|
+
if (activatedMessageIds) {
|
|
1392
|
+
const sealedSet = _ObservationalMemory.sealedMessageIds.get(threadId);
|
|
1393
|
+
if (sealedSet) {
|
|
1394
|
+
for (const id of activatedMessageIds) {
|
|
1395
|
+
sealedSet.delete(id);
|
|
1396
|
+
}
|
|
1397
|
+
if (sealedSet.size === 0) {
|
|
1398
|
+
_ObservationalMemory.sealedMessageIds.delete(threadId);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
} else {
|
|
1402
|
+
_ObservationalMemory.sealedMessageIds.delete(threadId);
|
|
1403
|
+
_ObservationalMemory.lastBufferedAtTime.delete(obsBufKey);
|
|
1404
|
+
_ObservationalMemory.lastBufferedBoundary.delete(obsBufKey);
|
|
1405
|
+
_ObservationalMemory.lastBufferedBoundary.delete(reflBufKey);
|
|
1406
|
+
_ObservationalMemory.asyncBufferingOps.delete(obsBufKey);
|
|
1407
|
+
_ObservationalMemory.asyncBufferingOps.delete(reflBufKey);
|
|
1408
|
+
_ObservationalMemory.reflectionBufferCycleIds.delete(obsBufKey);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Safely get bufferedObservationChunks as an array.
|
|
1413
|
+
* Handles cases where it might be a JSON string or undefined.
|
|
1414
|
+
*/
|
|
1415
|
+
getBufferedChunks(record) {
|
|
1416
|
+
if (!record?.bufferedObservationChunks) return [];
|
|
1417
|
+
if (Array.isArray(record.bufferedObservationChunks)) return record.bufferedObservationChunks;
|
|
1418
|
+
if (typeof record.bufferedObservationChunks === "string") {
|
|
1419
|
+
try {
|
|
1420
|
+
const parsed = JSON.parse(record.bufferedObservationChunks);
|
|
1421
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1422
|
+
} catch {
|
|
1423
|
+
return [];
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
return [];
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Calculate the projected message tokens that would be removed if activation happened now.
|
|
1430
|
+
* This replicates the chunk boundary logic in swapBufferedToActive without actually activating.
|
|
1431
|
+
*/
|
|
1432
|
+
calculateProjectedMessageRemoval(chunks, activationRatio, messageTokensThreshold, currentPendingTokens) {
|
|
1433
|
+
if (chunks.length === 0) return 0;
|
|
1434
|
+
const retentionFloor = messageTokensThreshold * (1 - activationRatio);
|
|
1435
|
+
const targetMessageTokens = Math.max(0, currentPendingTokens - retentionFloor);
|
|
1436
|
+
let cumulativeMessageTokens = 0;
|
|
1437
|
+
let bestBoundary = 0;
|
|
1438
|
+
let bestBoundaryMessageTokens = 0;
|
|
1439
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
1440
|
+
cumulativeMessageTokens += chunks[i].messageTokens ?? 0;
|
|
1441
|
+
const boundary = i + 1;
|
|
1442
|
+
const isUnder = cumulativeMessageTokens <= targetMessageTokens;
|
|
1443
|
+
const bestIsUnder = bestBoundaryMessageTokens <= targetMessageTokens;
|
|
1444
|
+
if (bestBoundary === 0) {
|
|
1445
|
+
bestBoundary = boundary;
|
|
1446
|
+
bestBoundaryMessageTokens = cumulativeMessageTokens;
|
|
1447
|
+
} else if (isUnder && !bestIsUnder) {
|
|
1448
|
+
bestBoundary = boundary;
|
|
1449
|
+
bestBoundaryMessageTokens = cumulativeMessageTokens;
|
|
1450
|
+
} else if (isUnder && bestIsUnder) {
|
|
1451
|
+
if (cumulativeMessageTokens > bestBoundaryMessageTokens) {
|
|
1452
|
+
bestBoundary = boundary;
|
|
1453
|
+
bestBoundaryMessageTokens = cumulativeMessageTokens;
|
|
1454
|
+
}
|
|
1455
|
+
} else if (!isUnder && !bestIsUnder) {
|
|
1456
|
+
if (cumulativeMessageTokens < bestBoundaryMessageTokens) {
|
|
1457
|
+
bestBoundary = boundary;
|
|
1458
|
+
bestBoundaryMessageTokens = cumulativeMessageTokens;
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
if (bestBoundary === 0) {
|
|
1463
|
+
return chunks[0]?.messageTokens ?? 0;
|
|
1464
|
+
}
|
|
1465
|
+
return bestBoundaryMessageTokens;
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Check if we've crossed a new bufferTokens interval boundary.
|
|
1469
|
+
* Returns true if async buffering should be triggered.
|
|
1470
|
+
*/
|
|
1471
|
+
shouldTriggerAsyncObservation(currentTokens, lockKey, record) {
|
|
1472
|
+
if (!this.isAsyncObservationEnabled()) return false;
|
|
1473
|
+
if (record.isBufferingObservation) {
|
|
1474
|
+
if (isOpActiveInProcess(record.id, "bufferingObservation")) return false;
|
|
1475
|
+
omDebug(`[OM:shouldTriggerAsyncObs] isBufferingObservation=true but stale, clearing`);
|
|
1476
|
+
this.storage.setBufferingObservationFlag(record.id, false).catch(() => {
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
const bufferKey = this.getObservationBufferKey(lockKey);
|
|
1480
|
+
if (this.isAsyncBufferingInProgress(bufferKey)) return false;
|
|
1481
|
+
const bufferTokens = this.observationConfig.bufferTokens;
|
|
1482
|
+
const dbBoundary = record.lastBufferedAtTokens ?? 0;
|
|
1483
|
+
const memBoundary = _ObservationalMemory.lastBufferedBoundary.get(bufferKey) ?? 0;
|
|
1484
|
+
const lastBoundary = Math.max(dbBoundary, memBoundary);
|
|
1485
|
+
const currentInterval = Math.floor(currentTokens / bufferTokens);
|
|
1486
|
+
const lastInterval = Math.floor(lastBoundary / bufferTokens);
|
|
1487
|
+
const shouldTrigger = currentInterval > lastInterval;
|
|
1488
|
+
omDebug(
|
|
1489
|
+
`[OM:shouldTriggerAsyncObs] tokens=${currentTokens}, bufferTokens=${bufferTokens}, currentInterval=${currentInterval}, lastInterval=${lastInterval}, lastBoundary=${lastBoundary} (db=${dbBoundary}, mem=${memBoundary}), shouldTrigger=${shouldTrigger}`
|
|
1490
|
+
);
|
|
1491
|
+
return shouldTrigger;
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Check if async reflection buffering should be triggered.
|
|
1495
|
+
* Triggers once when observation tokens reach `threshold * bufferActivation`.
|
|
1496
|
+
* Only allows one buffered reflection at a time.
|
|
1497
|
+
*/
|
|
1498
|
+
shouldTriggerAsyncReflection(currentObservationTokens, lockKey, record) {
|
|
1499
|
+
if (!this.isAsyncReflectionEnabled()) return false;
|
|
1500
|
+
if (record.isBufferingReflection) {
|
|
1501
|
+
if (isOpActiveInProcess(record.id, "bufferingReflection")) return false;
|
|
1502
|
+
omDebug(`[OM:shouldTriggerAsyncRefl] isBufferingReflection=true but stale, clearing`);
|
|
1503
|
+
this.storage.setBufferingReflectionFlag(record.id, false).catch(() => {
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
const bufferKey = this.getReflectionBufferKey(lockKey);
|
|
1507
|
+
if (this.isAsyncBufferingInProgress(bufferKey)) return false;
|
|
1508
|
+
if (_ObservationalMemory.lastBufferedBoundary.has(bufferKey)) return false;
|
|
1509
|
+
if (record.bufferedReflection) return false;
|
|
1510
|
+
const reflectThreshold = this.getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
1511
|
+
const activationPoint = reflectThreshold * this.reflectionConfig.bufferActivation;
|
|
1512
|
+
const shouldTrigger = currentObservationTokens >= activationPoint;
|
|
1513
|
+
omDebug(
|
|
1514
|
+
`[OM:shouldTriggerAsyncRefl] obsTokens=${currentObservationTokens}, reflThreshold=${reflectThreshold}, activationPoint=${activationPoint}, bufferActivation=${this.reflectionConfig.bufferActivation}, shouldTrigger=${shouldTrigger}, isBufferingRefl=${record.isBufferingReflection}, hasBufferedReflection=${!!record.bufferedReflection}`
|
|
1515
|
+
);
|
|
1516
|
+
return shouldTrigger;
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* Check if an async buffering operation is already in progress.
|
|
1520
|
+
*/
|
|
1521
|
+
isAsyncBufferingInProgress(bufferKey) {
|
|
1522
|
+
return _ObservationalMemory.asyncBufferingOps.has(bufferKey);
|
|
1523
|
+
}
|
|
1243
1524
|
/**
|
|
1244
1525
|
* Acquire a lock for the given key, execute the callback, then release.
|
|
1245
1526
|
* If a lock is already held, waits for it to be released before acquiring.
|
|
@@ -1286,12 +1567,46 @@ var ObservationalMemory = class {
|
|
|
1286
1567
|
this.shouldObscureThreadIds = config.obscureThreadIds || false;
|
|
1287
1568
|
this.storage = config.storage;
|
|
1288
1569
|
this.scope = config.scope ?? "thread";
|
|
1289
|
-
const
|
|
1290
|
-
const
|
|
1570
|
+
const resolveModel = (m) => m === "default" ? OBSERVATIONAL_MEMORY_DEFAULTS.observation.model : m;
|
|
1571
|
+
const observationModel = resolveModel(config.model) ?? resolveModel(config.observation?.model) ?? resolveModel(config.reflection?.model);
|
|
1572
|
+
const reflectionModel = resolveModel(config.model) ?? resolveModel(config.reflection?.model) ?? resolveModel(config.observation?.model);
|
|
1573
|
+
if (!observationModel || !reflectionModel) {
|
|
1574
|
+
throw new Error(
|
|
1575
|
+
`Observational Memory requires a model to be set. Use \`observationalMemory: true\` for the default (google/gemini-2.5-flash), or set a model explicitly:
|
|
1576
|
+
|
|
1577
|
+
observationalMemory: {
|
|
1578
|
+
model: "$provider/$model",
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
See https://mastra.ai/docs/memory/observational-memory#models for model recommendations and alternatives.`
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1291
1584
|
const messageTokens = config.observation?.messageTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens;
|
|
1292
1585
|
const observationTokens = config.reflection?.observationTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.observationTokens;
|
|
1293
1586
|
const isSharedBudget = config.shareTokenBudget ?? false;
|
|
1294
1587
|
const totalBudget = messageTokens + observationTokens;
|
|
1588
|
+
const userExplicitlyConfiguredAsync = config.observation?.bufferTokens !== void 0 || config.observation?.bufferActivation !== void 0 || config.reflection?.bufferActivation !== void 0;
|
|
1589
|
+
const asyncBufferingDisabled = config.observation?.bufferTokens === false || config.scope === "resource" && !userExplicitlyConfiguredAsync;
|
|
1590
|
+
if (isSharedBudget && !asyncBufferingDisabled) {
|
|
1591
|
+
const common = `shareTokenBudget requires async buffering to be disabled (this is a temporary limitation). Add observation: { bufferTokens: false } to your config:
|
|
1592
|
+
|
|
1593
|
+
observationalMemory: {
|
|
1594
|
+
shareTokenBudget: true,
|
|
1595
|
+
observation: { bufferTokens: false },
|
|
1596
|
+
}
|
|
1597
|
+
`;
|
|
1598
|
+
if (userExplicitlyConfiguredAsync) {
|
|
1599
|
+
throw new Error(
|
|
1600
|
+
common + `
|
|
1601
|
+
Remove any other async buffering settings (bufferTokens, bufferActivation, blockAfter).`
|
|
1602
|
+
);
|
|
1603
|
+
} else {
|
|
1604
|
+
throw new Error(
|
|
1605
|
+
common + `
|
|
1606
|
+
Async buffering is enabled by default \u2014 this opt-out is only needed when using shareTokenBudget.`
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1295
1610
|
this.observationConfig = {
|
|
1296
1611
|
model: observationModel,
|
|
1297
1612
|
// When shared budget, store as range: min = base threshold, max = total budget
|
|
@@ -1303,7 +1618,16 @@ var ObservationalMemory = class {
|
|
|
1303
1618
|
maxOutputTokens: config.observation?.modelSettings?.maxOutputTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.modelSettings.maxOutputTokens
|
|
1304
1619
|
},
|
|
1305
1620
|
providerOptions: config.observation?.providerOptions ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.providerOptions,
|
|
1306
|
-
maxTokensPerBatch: config.observation?.maxTokensPerBatch ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.maxTokensPerBatch
|
|
1621
|
+
maxTokensPerBatch: config.observation?.maxTokensPerBatch ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.maxTokensPerBatch,
|
|
1622
|
+
bufferTokens: asyncBufferingDisabled ? void 0 : this.resolveBufferTokens(
|
|
1623
|
+
config.observation?.bufferTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferTokens,
|
|
1624
|
+
config.observation?.messageTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens
|
|
1625
|
+
),
|
|
1626
|
+
bufferActivation: asyncBufferingDisabled ? void 0 : config.observation?.bufferActivation ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferActivation,
|
|
1627
|
+
blockAfter: asyncBufferingDisabled ? void 0 : this.resolveBlockAfter(
|
|
1628
|
+
config.observation?.blockAfter ?? (config.observation?.bufferTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferTokens ? 1.2 : void 0),
|
|
1629
|
+
config.observation?.messageTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens
|
|
1630
|
+
)
|
|
1307
1631
|
};
|
|
1308
1632
|
this.reflectionConfig = {
|
|
1309
1633
|
model: reflectionModel,
|
|
@@ -1313,11 +1637,20 @@ var ObservationalMemory = class {
|
|
|
1313
1637
|
temperature: config.reflection?.modelSettings?.temperature ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.modelSettings.temperature,
|
|
1314
1638
|
maxOutputTokens: config.reflection?.modelSettings?.maxOutputTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.modelSettings.maxOutputTokens
|
|
1315
1639
|
},
|
|
1316
|
-
providerOptions: config.reflection?.providerOptions ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.providerOptions
|
|
1640
|
+
providerOptions: config.reflection?.providerOptions ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.providerOptions,
|
|
1641
|
+
bufferActivation: asyncBufferingDisabled ? void 0 : config?.reflection?.bufferActivation ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.bufferActivation,
|
|
1642
|
+
blockAfter: asyncBufferingDisabled ? void 0 : this.resolveBlockAfter(
|
|
1643
|
+
config.reflection?.blockAfter ?? (config.reflection?.bufferActivation ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.bufferActivation ? 1.2 : void 0),
|
|
1644
|
+
config.reflection?.observationTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.observationTokens
|
|
1645
|
+
)
|
|
1317
1646
|
};
|
|
1318
1647
|
this.tokenCounter = new TokenCounter();
|
|
1319
1648
|
this.onDebugEvent = config.onDebugEvent;
|
|
1320
1649
|
this.messageHistory = new MessageHistory({ storage: this.storage });
|
|
1650
|
+
this.validateBufferConfig();
|
|
1651
|
+
omDebug(
|
|
1652
|
+
`[OM:init] new ObservationalMemory instance created \u2014 scope=${this.scope}, messageTokens=${JSON.stringify(this.observationConfig.messageTokens)}, obsAsyncEnabled=${this.isAsyncObservationEnabled()}, bufferTokens=${this.observationConfig.bufferTokens}, bufferActivation=${this.observationConfig.bufferActivation}, blockAfter=${this.observationConfig.blockAfter}, reflectionTokens=${this.reflectionConfig.observationTokens}, refAsyncEnabled=${this.isAsyncReflectionEnabled()}, refAsyncActivation=${this.reflectionConfig.bufferActivation}, refBlockAfter=${this.reflectionConfig.blockAfter}`
|
|
1653
|
+
);
|
|
1321
1654
|
}
|
|
1322
1655
|
/**
|
|
1323
1656
|
* Get the current configuration for this OM instance.
|
|
@@ -1341,7 +1674,7 @@ var ObservationalMemory = class {
|
|
|
1341
1674
|
async getResolvedConfig(requestContext) {
|
|
1342
1675
|
const getModelToResolve = (model) => {
|
|
1343
1676
|
if (Array.isArray(model)) {
|
|
1344
|
-
return model[0]?.model ??
|
|
1677
|
+
return model[0]?.model ?? "unknown";
|
|
1345
1678
|
}
|
|
1346
1679
|
return model;
|
|
1347
1680
|
};
|
|
@@ -1354,7 +1687,7 @@ var ObservationalMemory = class {
|
|
|
1354
1687
|
const resolved = await resolveModelConfig(modelToResolve, requestContext);
|
|
1355
1688
|
return formatModelName(resolved);
|
|
1356
1689
|
} catch (error) {
|
|
1357
|
-
|
|
1690
|
+
omError("[OM] Failed to resolve model config", error);
|
|
1358
1691
|
return "(unknown)";
|
|
1359
1692
|
}
|
|
1360
1693
|
};
|
|
@@ -1382,24 +1715,96 @@ var ObservationalMemory = class {
|
|
|
1382
1715
|
this.onDebugEvent(event);
|
|
1383
1716
|
}
|
|
1384
1717
|
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1718
|
+
/**
|
|
1719
|
+
* Validate buffer configuration on first use.
|
|
1720
|
+
* Ensures bufferTokens is less than the threshold and bufferActivation is valid.
|
|
1721
|
+
*/
|
|
1722
|
+
validateBufferConfig() {
|
|
1723
|
+
const hasAsyncBuffering = this.observationConfig.bufferTokens !== void 0 || this.observationConfig.bufferActivation !== void 0 || this.reflectionConfig.bufferActivation !== void 0;
|
|
1724
|
+
if (hasAsyncBuffering && this.scope === "resource") {
|
|
1725
|
+
throw new Error(
|
|
1726
|
+
`Async buffering is not yet supported with scope: 'resource'. Use scope: 'thread', or set observation: { bufferTokens: false } to disable async buffering.`
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
const observationThreshold = this.getMaxThreshold(this.observationConfig.messageTokens);
|
|
1730
|
+
if (this.observationConfig.bufferTokens !== void 0) {
|
|
1731
|
+
if (this.observationConfig.bufferTokens <= 0) {
|
|
1732
|
+
throw new Error(`observation.bufferTokens must be > 0, got ${this.observationConfig.bufferTokens}`);
|
|
1733
|
+
}
|
|
1734
|
+
if (this.observationConfig.bufferTokens >= observationThreshold) {
|
|
1735
|
+
throw new Error(
|
|
1736
|
+
`observation.bufferTokens (${this.observationConfig.bufferTokens}) must be less than messageTokens (${observationThreshold})`
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
if (this.observationConfig.bufferActivation !== void 0) {
|
|
1741
|
+
if (this.observationConfig.bufferActivation <= 0 || this.observationConfig.bufferActivation > 1) {
|
|
1742
|
+
throw new Error(
|
|
1743
|
+
`observation.bufferActivation must be in range (0, 1], got ${this.observationConfig.bufferActivation}`
|
|
1744
|
+
);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
if (this.observationConfig.blockAfter !== void 0) {
|
|
1748
|
+
if (this.observationConfig.blockAfter < observationThreshold) {
|
|
1749
|
+
throw new Error(
|
|
1750
|
+
`observation.blockAfter (${this.observationConfig.blockAfter}) must be >= messageTokens (${observationThreshold})`
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
if (!this.observationConfig.bufferTokens) {
|
|
1754
|
+
throw new Error(
|
|
1755
|
+
`observation.blockAfter requires observation.bufferTokens to be set (blockAfter only applies when async buffering is enabled)`
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
if (this.reflectionConfig.bufferActivation !== void 0) {
|
|
1760
|
+
if (this.reflectionConfig.bufferActivation <= 0 || this.reflectionConfig.bufferActivation > 1) {
|
|
1761
|
+
throw new Error(
|
|
1762
|
+
`reflection.bufferActivation must be in range (0, 1], got ${this.reflectionConfig.bufferActivation}`
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
if (this.reflectionConfig.blockAfter !== void 0) {
|
|
1767
|
+
const reflectionThreshold = this.getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
1768
|
+
if (this.reflectionConfig.blockAfter < reflectionThreshold) {
|
|
1769
|
+
throw new Error(
|
|
1770
|
+
`reflection.blockAfter (${this.reflectionConfig.blockAfter}) must be >= reflection.observationTokens (${reflectionThreshold})`
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
if (!this.reflectionConfig.bufferActivation) {
|
|
1774
|
+
throw new Error(
|
|
1775
|
+
`reflection.blockAfter requires reflection.bufferActivation to be set (blockAfter only applies when async reflection is enabled)`
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
/**
|
|
1781
|
+
* Resolve bufferTokens: if it's a fraction (0 < value < 1), multiply by messageTokens threshold.
|
|
1782
|
+
* Otherwise return the absolute token count.
|
|
1783
|
+
*/
|
|
1784
|
+
resolveBufferTokens(bufferTokens, messageTokens) {
|
|
1785
|
+
if (bufferTokens === false) return void 0;
|
|
1786
|
+
if (bufferTokens === void 0) return void 0;
|
|
1787
|
+
if (bufferTokens > 0 && bufferTokens < 1) {
|
|
1788
|
+
const threshold = typeof messageTokens === "number" ? messageTokens : messageTokens.max;
|
|
1789
|
+
return Math.round(threshold * bufferTokens);
|
|
1790
|
+
}
|
|
1791
|
+
return bufferTokens;
|
|
1792
|
+
}
|
|
1793
|
+
/**
|
|
1794
|
+
* Resolve blockAfter config value.
|
|
1795
|
+
* Values between 1 and 2 (exclusive) are treated as multipliers of the threshold.
|
|
1796
|
+
* e.g. blockAfter: 1.5 with messageTokens: 20_000 → 30_000
|
|
1797
|
+
* Values >= 2 are treated as absolute token counts.
|
|
1798
|
+
* Defaults to 1.2 (120% of threshold) when async buffering is enabled but blockAfter is omitted.
|
|
1799
|
+
*/
|
|
1800
|
+
resolveBlockAfter(blockAfter, messageTokens) {
|
|
1801
|
+
if (blockAfter === void 0) return void 0;
|
|
1802
|
+
if (blockAfter >= 1 && blockAfter < 2) {
|
|
1803
|
+
const threshold = typeof messageTokens === "number" ? messageTokens : messageTokens.max;
|
|
1804
|
+
return Math.round(threshold * blockAfter);
|
|
1805
|
+
}
|
|
1806
|
+
return blockAfter;
|
|
1807
|
+
}
|
|
1403
1808
|
/**
|
|
1404
1809
|
* Get the maximum value from a threshold (simple number or range)
|
|
1405
1810
|
*/
|
|
@@ -1589,6 +1994,112 @@ var ObservationalMemory = class {
|
|
|
1589
1994
|
}
|
|
1590
1995
|
};
|
|
1591
1996
|
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Create a start marker for when async buffering begins.
|
|
1999
|
+
*/
|
|
2000
|
+
createBufferingStartMarker(params) {
|
|
2001
|
+
return {
|
|
2002
|
+
type: "data-om-buffering-start",
|
|
2003
|
+
data: {
|
|
2004
|
+
cycleId: params.cycleId,
|
|
2005
|
+
operationType: params.operationType,
|
|
2006
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2007
|
+
tokensToBuffer: params.tokensToBuffer,
|
|
2008
|
+
recordId: params.recordId,
|
|
2009
|
+
threadId: params.threadId,
|
|
2010
|
+
threadIds: params.threadIds,
|
|
2011
|
+
config: this.getObservationMarkerConfig()
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
/**
|
|
2016
|
+
* Create an end marker for when async buffering completes successfully.
|
|
2017
|
+
*/
|
|
2018
|
+
createBufferingEndMarker(params) {
|
|
2019
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2020
|
+
const durationMs = new Date(completedAt).getTime() - new Date(params.startedAt).getTime();
|
|
2021
|
+
return {
|
|
2022
|
+
type: "data-om-buffering-end",
|
|
2023
|
+
data: {
|
|
2024
|
+
cycleId: params.cycleId,
|
|
2025
|
+
operationType: params.operationType,
|
|
2026
|
+
completedAt,
|
|
2027
|
+
durationMs,
|
|
2028
|
+
tokensBuffered: params.tokensBuffered,
|
|
2029
|
+
bufferedTokens: params.bufferedTokens,
|
|
2030
|
+
recordId: params.recordId,
|
|
2031
|
+
threadId: params.threadId,
|
|
2032
|
+
observations: params.observations
|
|
2033
|
+
}
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Create a failed marker for when async buffering fails.
|
|
2038
|
+
*/
|
|
2039
|
+
createBufferingFailedMarker(params) {
|
|
2040
|
+
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2041
|
+
const durationMs = new Date(failedAt).getTime() - new Date(params.startedAt).getTime();
|
|
2042
|
+
return {
|
|
2043
|
+
type: "data-om-buffering-failed",
|
|
2044
|
+
data: {
|
|
2045
|
+
cycleId: params.cycleId,
|
|
2046
|
+
operationType: params.operationType,
|
|
2047
|
+
failedAt,
|
|
2048
|
+
durationMs,
|
|
2049
|
+
tokensAttempted: params.tokensAttempted,
|
|
2050
|
+
error: params.error,
|
|
2051
|
+
recordId: params.recordId,
|
|
2052
|
+
threadId: params.threadId
|
|
2053
|
+
}
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
/**
|
|
2057
|
+
* Create an activation marker for when buffered observations are activated.
|
|
2058
|
+
*/
|
|
2059
|
+
createActivationMarker(params) {
|
|
2060
|
+
return {
|
|
2061
|
+
type: "data-om-activation",
|
|
2062
|
+
data: {
|
|
2063
|
+
cycleId: params.cycleId,
|
|
2064
|
+
operationType: params.operationType,
|
|
2065
|
+
activatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2066
|
+
chunksActivated: params.chunksActivated,
|
|
2067
|
+
tokensActivated: params.tokensActivated,
|
|
2068
|
+
observationTokens: params.observationTokens,
|
|
2069
|
+
messagesActivated: params.messagesActivated,
|
|
2070
|
+
recordId: params.recordId,
|
|
2071
|
+
threadId: params.threadId,
|
|
2072
|
+
generationCount: params.generationCount,
|
|
2073
|
+
config: this.getObservationMarkerConfig(),
|
|
2074
|
+
observations: params.observations
|
|
2075
|
+
}
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
/**
|
|
2079
|
+
* Persist a data-om-* marker part on the last assistant message in messageList
|
|
2080
|
+
* AND save the updated message to the DB so it survives page reload.
|
|
2081
|
+
* (data-* parts are filtered out before sending to the LLM, so they don't affect model calls.)
|
|
2082
|
+
*/
|
|
2083
|
+
async persistMarkerToMessage(marker, messageList, threadId, resourceId) {
|
|
2084
|
+
if (!messageList) return;
|
|
2085
|
+
const allMsgs = messageList.get.all.db();
|
|
2086
|
+
for (let i = allMsgs.length - 1; i >= 0; i--) {
|
|
2087
|
+
const msg = allMsgs[i];
|
|
2088
|
+
if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
|
|
2089
|
+
msg.content.parts.push(marker);
|
|
2090
|
+
try {
|
|
2091
|
+
await this.messageHistory.persistMessages({
|
|
2092
|
+
messages: [msg],
|
|
2093
|
+
threadId,
|
|
2094
|
+
resourceId
|
|
2095
|
+
});
|
|
2096
|
+
} catch (e) {
|
|
2097
|
+
omDebug(`[OM:persistMarker] failed to save marker to DB: ${e}`);
|
|
2098
|
+
}
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
1592
2103
|
/**
|
|
1593
2104
|
* Find the last completed observation boundary in a message's parts.
|
|
1594
2105
|
* A completed observation is a start marker followed by an end marker.
|
|
@@ -1626,6 +2137,44 @@ var ObservationalMemory = class {
|
|
|
1626
2137
|
}
|
|
1627
2138
|
return lastStartIndex !== -1 && lastStartIndex > lastEndOrFailedIndex;
|
|
1628
2139
|
}
|
|
2140
|
+
/**
|
|
2141
|
+
* Seal messages to prevent new parts from being merged into them.
|
|
2142
|
+
* This is used when starting buffering to capture the current content state.
|
|
2143
|
+
*
|
|
2144
|
+
* Sealing works by:
|
|
2145
|
+
* 1. Setting `message.content.metadata.mastra.sealed = true` (message-level flag)
|
|
2146
|
+
* 2. Adding `metadata.mastra.sealedAt` to the last part (boundary marker)
|
|
2147
|
+
*
|
|
2148
|
+
* When MessageList.add() receives a message with the same ID as a sealed message,
|
|
2149
|
+
* it creates a new message with only the parts beyond the seal boundary.
|
|
2150
|
+
*
|
|
2151
|
+
* The messages are mutated in place - since they're references to the same objects
|
|
2152
|
+
* in the MessageList, the seal will be recognized immediately.
|
|
2153
|
+
*
|
|
2154
|
+
* @param messages - Messages to seal (mutated in place)
|
|
2155
|
+
*/
|
|
2156
|
+
sealMessagesForBuffering(messages) {
|
|
2157
|
+
const sealedAt = Date.now();
|
|
2158
|
+
for (const msg of messages) {
|
|
2159
|
+
if (!msg.content?.parts?.length) continue;
|
|
2160
|
+
if (!msg.content.metadata) {
|
|
2161
|
+
msg.content.metadata = {};
|
|
2162
|
+
}
|
|
2163
|
+
const metadata = msg.content.metadata;
|
|
2164
|
+
if (!metadata.mastra) {
|
|
2165
|
+
metadata.mastra = {};
|
|
2166
|
+
}
|
|
2167
|
+
metadata.mastra.sealed = true;
|
|
2168
|
+
const lastPart = msg.content.parts[msg.content.parts.length - 1];
|
|
2169
|
+
if (!lastPart.metadata) {
|
|
2170
|
+
lastPart.metadata = {};
|
|
2171
|
+
}
|
|
2172
|
+
if (!lastPart.metadata.mastra) {
|
|
2173
|
+
lastPart.metadata.mastra = {};
|
|
2174
|
+
}
|
|
2175
|
+
lastPart.metadata.mastra.sealedAt = sealedAt;
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
1629
2178
|
/**
|
|
1630
2179
|
* Insert an observation marker into a message.
|
|
1631
2180
|
* The marker is appended directly to the message's parts array (mutating in place).
|
|
@@ -1692,10 +2241,22 @@ var ObservationalMemory = class {
|
|
|
1692
2241
|
* This handles the case where a single message accumulates many parts
|
|
1693
2242
|
* (like tool calls) during an agentic loop - we only observe the new parts.
|
|
1694
2243
|
*/
|
|
1695
|
-
getUnobservedMessages(allMessages, record) {
|
|
2244
|
+
getUnobservedMessages(allMessages, record, opts) {
|
|
1696
2245
|
const lastObservedAt = record.lastObservedAt;
|
|
1697
|
-
const observedMessageIds =
|
|
1698
|
-
|
|
2246
|
+
const observedMessageIds = new Set(
|
|
2247
|
+
Array.isArray(record.observedMessageIds) ? record.observedMessageIds : []
|
|
2248
|
+
);
|
|
2249
|
+
if (opts?.excludeBuffered) {
|
|
2250
|
+
const bufferedChunks = this.getBufferedChunks(record);
|
|
2251
|
+
for (const chunk of bufferedChunks) {
|
|
2252
|
+
if (Array.isArray(chunk.messageIds)) {
|
|
2253
|
+
for (const id of chunk.messageIds) {
|
|
2254
|
+
observedMessageIds.add(id);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
if (!lastObservedAt && observedMessageIds.size === 0) {
|
|
1699
2260
|
return allMessages;
|
|
1700
2261
|
}
|
|
1701
2262
|
const result = [];
|
|
@@ -1713,7 +2274,7 @@ var ObservationalMemory = class {
|
|
|
1713
2274
|
result.push(virtualMsg);
|
|
1714
2275
|
}
|
|
1715
2276
|
} else {
|
|
1716
|
-
if (!msg.createdAt) {
|
|
2277
|
+
if (!msg.createdAt || !lastObservedAt) {
|
|
1717
2278
|
result.push(msg);
|
|
1718
2279
|
} else {
|
|
1719
2280
|
const msgDate = new Date(msg.createdAt);
|
|
@@ -1744,16 +2305,16 @@ var ObservationalMemory = class {
|
|
|
1744
2305
|
/**
|
|
1745
2306
|
* Call the Observer agent to extract observations.
|
|
1746
2307
|
*/
|
|
1747
|
-
async callObserver(existingObservations, messagesToObserve, abortSignal) {
|
|
2308
|
+
async callObserver(existingObservations, messagesToObserve, abortSignal, options) {
|
|
1748
2309
|
const agent = this.getObserverAgent();
|
|
1749
|
-
const prompt = buildObserverPrompt(existingObservations, messagesToObserve);
|
|
2310
|
+
const prompt = buildObserverPrompt(existingObservations, messagesToObserve, options);
|
|
1750
2311
|
const result = await this.withAbortCheck(
|
|
1751
2312
|
() => agent.generate(prompt, {
|
|
1752
2313
|
modelSettings: {
|
|
1753
2314
|
...this.observationConfig.modelSettings
|
|
1754
2315
|
},
|
|
1755
2316
|
providerOptions: this.observationConfig.providerOptions,
|
|
1756
|
-
abortSignal
|
|
2317
|
+
...abortSignal ? { abortSignal } : {}
|
|
1757
2318
|
}),
|
|
1758
2319
|
abortSignal
|
|
1759
2320
|
);
|
|
@@ -1797,7 +2358,7 @@ var ObservationalMemory = class {
|
|
|
1797
2358
|
...this.observationConfig.modelSettings
|
|
1798
2359
|
},
|
|
1799
2360
|
providerOptions: this.observationConfig.providerOptions,
|
|
1800
|
-
abortSignal
|
|
2361
|
+
...abortSignal ? { abortSignal } : {}
|
|
1801
2362
|
}),
|
|
1802
2363
|
abortSignal
|
|
1803
2364
|
);
|
|
@@ -1829,21 +2390,48 @@ var ObservationalMemory = class {
|
|
|
1829
2390
|
* Call the Reflector agent to condense observations.
|
|
1830
2391
|
* Includes compression validation and retry logic.
|
|
1831
2392
|
*/
|
|
1832
|
-
async callReflector(observations, manualPrompt, streamContext, observationTokensThreshold, abortSignal) {
|
|
2393
|
+
async callReflector(observations, manualPrompt, streamContext, observationTokensThreshold, abortSignal, skipContinuationHints, compressionStartLevel) {
|
|
1833
2394
|
const agent = this.getReflectorAgent();
|
|
1834
2395
|
const originalTokens = this.tokenCounter.countObservations(observations);
|
|
1835
2396
|
const targetThreshold = observationTokensThreshold ?? this.getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
1836
2397
|
let totalUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
2398
|
+
const firstLevel = compressionStartLevel ?? 0;
|
|
2399
|
+
const retryLevel = Math.min(firstLevel + 1, 2);
|
|
2400
|
+
let prompt = buildReflectorPrompt(observations, manualPrompt, firstLevel, skipContinuationHints);
|
|
2401
|
+
omDebug(
|
|
2402
|
+
`[OM:callReflector] starting first attempt: originalTokens=${originalTokens}, targetThreshold=${targetThreshold}, promptLen=${prompt.length}, skipContinuationHints=${skipContinuationHints}`
|
|
2403
|
+
);
|
|
2404
|
+
let chunkCount = 0;
|
|
2405
|
+
const generatePromise = agent.generate(prompt, {
|
|
2406
|
+
modelSettings: {
|
|
2407
|
+
...this.reflectionConfig.modelSettings
|
|
2408
|
+
},
|
|
2409
|
+
providerOptions: this.reflectionConfig.providerOptions,
|
|
2410
|
+
...abortSignal ? { abortSignal } : {},
|
|
2411
|
+
onChunk(chunk) {
|
|
2412
|
+
chunkCount++;
|
|
2413
|
+
if (chunkCount === 1 || chunkCount % 50 === 0) {
|
|
2414
|
+
const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
|
|
2415
|
+
omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
|
|
2416
|
+
}
|
|
2417
|
+
},
|
|
2418
|
+
onFinish(event) {
|
|
2419
|
+
omDebug(
|
|
2420
|
+
`[OM:callReflector] onFinish: chunks=${chunkCount}, finishReason=${event.finishReason}, inputTokens=${event.usage?.inputTokens}, outputTokens=${event.usage?.outputTokens}, textLen=${event.text?.length}`
|
|
2421
|
+
);
|
|
2422
|
+
},
|
|
2423
|
+
onAbort(event) {
|
|
2424
|
+
omDebug(`[OM:callReflector] onAbort: chunks=${chunkCount}, reason=${event?.reason ?? "unknown"}`);
|
|
2425
|
+
},
|
|
2426
|
+
onError({ error }) {
|
|
2427
|
+
omError(`[OM:callReflector] onError after ${chunkCount} chunks`, error);
|
|
2428
|
+
}
|
|
2429
|
+
});
|
|
2430
|
+
let result = await this.withAbortCheck(async () => {
|
|
2431
|
+
return await generatePromise;
|
|
2432
|
+
}, abortSignal);
|
|
2433
|
+
omDebug(
|
|
2434
|
+
`[OM:callReflector] first attempt returned: textLen=${result.text?.length}, textPreview="${result.text?.slice(0, 120)}...", inputTokens=${result.usage?.inputTokens ?? result.totalUsage?.inputTokens}, outputTokens=${result.usage?.outputTokens ?? result.totalUsage?.outputTokens}, keys=${Object.keys(result).join(",")}`
|
|
1847
2435
|
);
|
|
1848
2436
|
const firstUsage = result.totalUsage ?? result.usage;
|
|
1849
2437
|
if (firstUsage) {
|
|
@@ -1853,6 +2441,9 @@ var ObservationalMemory = class {
|
|
|
1853
2441
|
}
|
|
1854
2442
|
let parsed = parseReflectorOutput(result.text);
|
|
1855
2443
|
let reflectedTokens = this.tokenCounter.countObservations(parsed.observations);
|
|
2444
|
+
omDebug(
|
|
2445
|
+
`[OM:callReflector] first attempt parsed: reflectedTokens=${reflectedTokens}, targetThreshold=${targetThreshold}, compressionValid=${validateCompression(reflectedTokens, targetThreshold)}, parsedObsLen=${parsed.observations?.length}`
|
|
2446
|
+
);
|
|
1856
2447
|
if (!validateCompression(reflectedTokens, targetThreshold)) {
|
|
1857
2448
|
if (streamContext?.writer) {
|
|
1858
2449
|
const failedMarker = this.createObservationFailedMarker({
|
|
@@ -1880,17 +2471,21 @@ var ObservationalMemory = class {
|
|
|
1880
2471
|
await streamContext.writer.custom(startMarker).catch(() => {
|
|
1881
2472
|
});
|
|
1882
2473
|
}
|
|
1883
|
-
prompt = buildReflectorPrompt(observations, manualPrompt,
|
|
2474
|
+
prompt = buildReflectorPrompt(observations, manualPrompt, retryLevel, skipContinuationHints);
|
|
2475
|
+
omDebug(`[OM:callReflector] starting retry: promptLen=${prompt.length}`);
|
|
1884
2476
|
result = await this.withAbortCheck(
|
|
1885
2477
|
() => agent.generate(prompt, {
|
|
1886
2478
|
modelSettings: {
|
|
1887
2479
|
...this.reflectionConfig.modelSettings
|
|
1888
2480
|
},
|
|
1889
2481
|
providerOptions: this.reflectionConfig.providerOptions,
|
|
1890
|
-
abortSignal
|
|
2482
|
+
...abortSignal ? { abortSignal } : {}
|
|
1891
2483
|
}),
|
|
1892
2484
|
abortSignal
|
|
1893
2485
|
);
|
|
2486
|
+
omDebug(
|
|
2487
|
+
`[OM:callReflector] retry returned: textLen=${result.text?.length}, inputTokens=${result.usage?.inputTokens ?? result.totalUsage?.inputTokens}, outputTokens=${result.usage?.outputTokens ?? result.totalUsage?.outputTokens}`
|
|
2488
|
+
);
|
|
1894
2489
|
const retryUsage = result.totalUsage ?? result.usage;
|
|
1895
2490
|
if (retryUsage) {
|
|
1896
2491
|
totalUsage.inputTokens += retryUsage.inputTokens ?? 0;
|
|
@@ -1899,6 +2494,9 @@ var ObservationalMemory = class {
|
|
|
1899
2494
|
}
|
|
1900
2495
|
parsed = parseReflectorOutput(result.text);
|
|
1901
2496
|
reflectedTokens = this.tokenCounter.countObservations(parsed.observations);
|
|
2497
|
+
omDebug(
|
|
2498
|
+
`[OM:callReflector] retry parsed: reflectedTokens=${reflectedTokens}, compressionValid=${validateCompression(reflectedTokens, targetThreshold)}`
|
|
2499
|
+
);
|
|
1902
2500
|
}
|
|
1903
2501
|
return {
|
|
1904
2502
|
observations: parsed.observations,
|
|
@@ -1981,34 +2579,34 @@ ${suggestedResponse}
|
|
|
1981
2579
|
}
|
|
1982
2580
|
return null;
|
|
1983
2581
|
}
|
|
2582
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
2583
|
+
// PROCESS INPUT STEP HELPERS
|
|
2584
|
+
// These helpers extract logical units from processInputStep for clarity.
|
|
2585
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
1984
2586
|
/**
|
|
1985
|
-
*
|
|
1986
|
-
*
|
|
1987
|
-
*
|
|
1988
|
-
* Flow:
|
|
1989
|
-
* 1. Load historical messages (step 0 only)
|
|
1990
|
-
* 2. Check if observation threshold is reached
|
|
1991
|
-
* 3. If threshold reached: observe, save messages with markers
|
|
1992
|
-
* 4. Inject observations into context
|
|
1993
|
-
* 5. Filter out already-observed messages
|
|
2587
|
+
* Load historical unobserved messages into the message list (step 0 only).
|
|
2588
|
+
* In resource scope, loads only current thread's messages.
|
|
2589
|
+
* In thread scope, loads all unobserved messages for the thread.
|
|
1994
2590
|
*/
|
|
1995
|
-
async
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
const context = this.getThreadContext(requestContext, messageList);
|
|
1999
|
-
if (!context) {
|
|
2000
|
-
return messageList;
|
|
2591
|
+
async loadHistoricalMessagesIfNeeded(messageList, state, threadId, resourceId, lastObservedAt) {
|
|
2592
|
+
if (state.initialSetupDone) {
|
|
2593
|
+
return;
|
|
2001
2594
|
}
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2595
|
+
state.initialSetupDone = true;
|
|
2596
|
+
if (this.scope === "resource" && resourceId) {
|
|
2597
|
+
const currentThreadMessages = await this.loadUnobservedMessages(threadId, void 0, lastObservedAt);
|
|
2598
|
+
for (const msg of currentThreadMessages) {
|
|
2599
|
+
if (msg.role !== "system") {
|
|
2600
|
+
if (!this.hasUnobservedParts(msg) && this.findLastCompletedObservationBoundary(msg) !== -1) {
|
|
2601
|
+
continue;
|
|
2602
|
+
}
|
|
2603
|
+
messageList.add(msg, "memory");
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
} else {
|
|
2607
|
+
const historicalMessages = await this.loadUnobservedMessages(threadId, resourceId, lastObservedAt);
|
|
2608
|
+
if (historicalMessages.length > 0) {
|
|
2609
|
+
for (const msg of historicalMessages) {
|
|
2012
2610
|
if (msg.role !== "system") {
|
|
2013
2611
|
if (!this.hasUnobservedParts(msg) && this.findLastCompletedObservationBoundary(msg) !== -1) {
|
|
2014
2612
|
continue;
|
|
@@ -2016,261 +2614,637 @@ ${suggestedResponse}
|
|
|
2016
2614
|
messageList.add(msg, "memory");
|
|
2017
2615
|
}
|
|
2018
2616
|
}
|
|
2019
|
-
} else {
|
|
2020
|
-
const historicalMessages = await this.loadUnobservedMessages(threadId, resourceId, lastObservedAt);
|
|
2021
|
-
if (historicalMessages.length > 0) {
|
|
2022
|
-
for (const msg of historicalMessages) {
|
|
2023
|
-
if (msg.role !== "system") {
|
|
2024
|
-
if (!this.hasUnobservedParts(msg) && this.findLastCompletedObservationBoundary(msg) !== -1) {
|
|
2025
|
-
continue;
|
|
2026
|
-
}
|
|
2027
|
-
messageList.add(msg, "memory");
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
2617
|
}
|
|
2032
2618
|
}
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2619
|
+
}
|
|
2620
|
+
/**
|
|
2621
|
+
* Calculate all threshold-related values for observation decision making.
|
|
2622
|
+
*/
|
|
2623
|
+
calculateObservationThresholds(allMessages, _unobservedMessages, _pendingTokens, otherThreadTokens, currentObservationTokens, _record) {
|
|
2624
|
+
const contextWindowTokens = this.tokenCounter.countMessages(allMessages);
|
|
2625
|
+
const totalPendingTokens = Math.max(0, contextWindowTokens + otherThreadTokens);
|
|
2626
|
+
const threshold = this.calculateDynamicThreshold(this.observationConfig.messageTokens, currentObservationTokens);
|
|
2627
|
+
const baseReflectionThreshold = this.getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
2628
|
+
const isSharedBudget = typeof this.observationConfig.messageTokens !== "number";
|
|
2629
|
+
const totalBudget = isSharedBudget ? this.observationConfig.messageTokens.max : 0;
|
|
2630
|
+
const effectiveObservationTokensThreshold = isSharedBudget ? Math.max(totalBudget - threshold, 1e3) : baseReflectionThreshold;
|
|
2631
|
+
return {
|
|
2632
|
+
totalPendingTokens,
|
|
2633
|
+
threshold,
|
|
2634
|
+
effectiveObservationTokensThreshold,
|
|
2635
|
+
isSharedBudget
|
|
2636
|
+
};
|
|
2637
|
+
}
|
|
2638
|
+
/**
|
|
2639
|
+
* Emit debug event and stream progress part for UI feedback.
|
|
2640
|
+
*/
|
|
2641
|
+
async emitStepProgress(writer, threadId, resourceId, stepNumber, record, thresholds, currentObservationTokens) {
|
|
2642
|
+
const { totalPendingTokens, threshold, effectiveObservationTokensThreshold } = thresholds;
|
|
2643
|
+
this.emitDebugEvent({
|
|
2644
|
+
type: "step_progress",
|
|
2645
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
2646
|
+
threadId,
|
|
2647
|
+
resourceId: resourceId ?? "",
|
|
2648
|
+
stepNumber,
|
|
2649
|
+
finishReason: "unknown",
|
|
2650
|
+
pendingTokens: totalPendingTokens,
|
|
2651
|
+
threshold,
|
|
2652
|
+
thresholdPercent: Math.round(totalPendingTokens / threshold * 100),
|
|
2653
|
+
willSave: totalPendingTokens >= threshold,
|
|
2654
|
+
willObserve: totalPendingTokens >= threshold
|
|
2655
|
+
});
|
|
2656
|
+
if (writer) {
|
|
2657
|
+
const bufferedChunks = this.getBufferedChunks(record);
|
|
2658
|
+
const bufferedObservationTokens = bufferedChunks.reduce((sum, chunk) => sum + (chunk.tokenCount ?? 0), 0);
|
|
2659
|
+
const rawBufferedMessageTokens = bufferedChunks.reduce((sum, chunk) => sum + (chunk.messageTokens ?? 0), 0);
|
|
2660
|
+
const bufferedMessageTokens = Math.min(rawBufferedMessageTokens, totalPendingTokens);
|
|
2661
|
+
const projectedMessageRemoval = this.calculateProjectedMessageRemoval(
|
|
2662
|
+
bufferedChunks,
|
|
2663
|
+
this.observationConfig.bufferActivation ?? 1,
|
|
2664
|
+
this.getMaxThreshold(this.observationConfig.messageTokens),
|
|
2665
|
+
totalPendingTokens
|
|
2052
2666
|
);
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
stepNumber,
|
|
2059
|
-
finishReason: "unknown",
|
|
2060
|
-
pendingTokens: totalPendingTokens,
|
|
2061
|
-
threshold,
|
|
2062
|
-
thresholdPercent: Math.round(totalPendingTokens / threshold * 100),
|
|
2063
|
-
willSave: totalPendingTokens >= threshold,
|
|
2064
|
-
willObserve: totalPendingTokens >= threshold
|
|
2065
|
-
});
|
|
2066
|
-
if (writer) {
|
|
2067
|
-
const progressPart = {
|
|
2068
|
-
type: "data-om-progress",
|
|
2069
|
-
data: {
|
|
2070
|
-
pendingTokens: totalPendingTokens,
|
|
2071
|
-
messageTokens: threshold,
|
|
2072
|
-
messageTokensPercent: Math.round(totalPendingTokens / threshold * 100),
|
|
2073
|
-
observationTokens: currentObservationTokens,
|
|
2074
|
-
observationTokensThreshold: effectiveObservationTokensThreshold,
|
|
2075
|
-
observationTokensPercent,
|
|
2076
|
-
willObserve: totalPendingTokens >= threshold,
|
|
2077
|
-
recordId: record.id,
|
|
2078
|
-
threadId,
|
|
2079
|
-
stepNumber
|
|
2080
|
-
}
|
|
2081
|
-
};
|
|
2082
|
-
await writer.custom(progressPart).catch(() => {
|
|
2083
|
-
});
|
|
2667
|
+
let obsBufferStatus = "idle";
|
|
2668
|
+
if (record.isBufferingObservation) {
|
|
2669
|
+
obsBufferStatus = "running";
|
|
2670
|
+
} else if (bufferedChunks.length > 0) {
|
|
2671
|
+
obsBufferStatus = "complete";
|
|
2084
2672
|
}
|
|
2085
|
-
|
|
2086
|
-
if (
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
}
|
|
2104
|
-
const preObservationTime = freshRecord.lastObservedAt?.getTime() ?? 0;
|
|
2105
|
-
if (freshUnobservedMessages.length > 0) {
|
|
2106
|
-
try {
|
|
2107
|
-
if (this.scope === "resource" && resourceId) {
|
|
2108
|
-
await this.doResourceScopedObservation(
|
|
2109
|
-
freshRecord,
|
|
2110
|
-
threadId,
|
|
2111
|
-
resourceId,
|
|
2112
|
-
freshUnobservedMessages,
|
|
2113
|
-
writer,
|
|
2114
|
-
abortSignal
|
|
2115
|
-
);
|
|
2116
|
-
} else {
|
|
2117
|
-
await this.doSynchronousObservation(
|
|
2118
|
-
freshRecord,
|
|
2119
|
-
threadId,
|
|
2120
|
-
freshUnobservedMessages,
|
|
2121
|
-
writer,
|
|
2122
|
-
abortSignal
|
|
2123
|
-
);
|
|
2673
|
+
let refBufferStatus = "idle";
|
|
2674
|
+
if (record.isBufferingReflection) {
|
|
2675
|
+
refBufferStatus = "running";
|
|
2676
|
+
} else if (record.bufferedReflection && record.bufferedReflection.length > 0) {
|
|
2677
|
+
refBufferStatus = "complete";
|
|
2678
|
+
}
|
|
2679
|
+
const statusPart = {
|
|
2680
|
+
type: "data-om-status",
|
|
2681
|
+
data: {
|
|
2682
|
+
windows: {
|
|
2683
|
+
active: {
|
|
2684
|
+
messages: {
|
|
2685
|
+
tokens: totalPendingTokens,
|
|
2686
|
+
threshold
|
|
2687
|
+
},
|
|
2688
|
+
observations: {
|
|
2689
|
+
tokens: currentObservationTokens,
|
|
2690
|
+
threshold: effectiveObservationTokensThreshold
|
|
2124
2691
|
}
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2692
|
+
},
|
|
2693
|
+
buffered: {
|
|
2694
|
+
observations: {
|
|
2695
|
+
chunks: bufferedChunks.length,
|
|
2696
|
+
messageTokens: bufferedMessageTokens,
|
|
2697
|
+
projectedMessageRemoval,
|
|
2698
|
+
observationTokens: bufferedObservationTokens,
|
|
2699
|
+
status: obsBufferStatus
|
|
2700
|
+
},
|
|
2701
|
+
reflection: {
|
|
2702
|
+
inputObservationTokens: record.bufferedReflectionInputTokens ?? 0,
|
|
2703
|
+
observationTokens: record.bufferedReflectionTokens ?? 0,
|
|
2704
|
+
status: refBufferStatus
|
|
2135
2705
|
}
|
|
2136
|
-
observationSucceeded = false;
|
|
2137
2706
|
}
|
|
2707
|
+
},
|
|
2708
|
+
recordId: record.id,
|
|
2709
|
+
threadId,
|
|
2710
|
+
stepNumber,
|
|
2711
|
+
generationCount: record.generationCount
|
|
2712
|
+
}
|
|
2713
|
+
};
|
|
2714
|
+
omDebug(
|
|
2715
|
+
`[OM:status] step=${stepNumber} msgs=${totalPendingTokens}/${threshold} obs=${currentObservationTokens}/${effectiveObservationTokensThreshold} bufObs={chunks=${bufferedChunks.length},msgTok=${bufferedMessageTokens},obsTok=${bufferedObservationTokens},status=${obsBufferStatus}} bufRef={inTok=${record.bufferedReflectionInputTokens ?? 0},outTok=${record.bufferedReflectionTokens ?? 0},status=${refBufferStatus}} gen=${record.generationCount}`
|
|
2716
|
+
);
|
|
2717
|
+
await writer.custom(statusPart).catch(() => {
|
|
2718
|
+
});
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
/**
|
|
2722
|
+
* Handle observation when threshold is reached.
|
|
2723
|
+
* Tries async activation first if enabled, then falls back to sync observation.
|
|
2724
|
+
* Returns whether observation succeeded.
|
|
2725
|
+
*/
|
|
2726
|
+
async handleThresholdReached(messageList, record, threadId, resourceId, threshold, lockKey, writer, abortSignal, abort) {
|
|
2727
|
+
let observationSucceeded = false;
|
|
2728
|
+
let updatedRecord = record;
|
|
2729
|
+
let activatedMessageIds;
|
|
2730
|
+
await this.withLock(lockKey, async () => {
|
|
2731
|
+
let freshRecord = await this.getOrCreateRecord(threadId, resourceId);
|
|
2732
|
+
const freshAllMessages = messageList.get.all.db();
|
|
2733
|
+
let freshUnobservedMessages = this.getUnobservedMessages(freshAllMessages, freshRecord);
|
|
2734
|
+
const freshContextTokens = this.tokenCounter.countMessages(freshAllMessages);
|
|
2735
|
+
let freshOtherThreadTokens = 0;
|
|
2736
|
+
if (this.scope === "resource" && resourceId) {
|
|
2737
|
+
const freshOtherContext = await this.loadOtherThreadsContext(resourceId, threadId);
|
|
2738
|
+
freshOtherThreadTokens = freshOtherContext ? this.tokenCounter.countString(freshOtherContext) : 0;
|
|
2739
|
+
}
|
|
2740
|
+
const freshTotal = freshContextTokens + freshOtherThreadTokens;
|
|
2741
|
+
omDebug(
|
|
2742
|
+
`[OM:threshold] handleThresholdReached (inside lock): freshTotal=${freshTotal}, threshold=${threshold}, freshUnobserved=${freshUnobservedMessages.length}, freshOtherThreadTokens=${freshOtherThreadTokens}, freshCurrentTokens=${freshContextTokens}`
|
|
2743
|
+
);
|
|
2744
|
+
if (freshTotal < threshold) {
|
|
2745
|
+
omDebug(`[OM:threshold] freshTotal < threshold, bailing out`);
|
|
2746
|
+
return;
|
|
2747
|
+
}
|
|
2748
|
+
const preObservationTime = freshRecord.lastObservedAt?.getTime() ?? 0;
|
|
2749
|
+
let activationResult = { success: false };
|
|
2750
|
+
if (this.isAsyncObservationEnabled()) {
|
|
2751
|
+
const bufferKey = this.getObservationBufferKey(lockKey);
|
|
2752
|
+
const asyncOp = _ObservationalMemory.asyncBufferingOps.get(bufferKey);
|
|
2753
|
+
if (asyncOp) {
|
|
2754
|
+
try {
|
|
2755
|
+
await Promise.race([
|
|
2756
|
+
asyncOp,
|
|
2757
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 3e4))
|
|
2758
|
+
]);
|
|
2759
|
+
} catch {
|
|
2138
2760
|
}
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2761
|
+
}
|
|
2762
|
+
const recordAfterWait = await this.getOrCreateRecord(threadId, resourceId);
|
|
2763
|
+
const chunksAfterWait = this.getBufferedChunks(recordAfterWait);
|
|
2764
|
+
omDebug(
|
|
2765
|
+
`[OM:threshold] tryActivation: chunksAvailable=${chunksAfterWait.length}, isBufferingObs=${recordAfterWait.isBufferingObservation}`
|
|
2766
|
+
);
|
|
2767
|
+
activationResult = await this.tryActivateBufferedObservations(
|
|
2768
|
+
recordAfterWait,
|
|
2769
|
+
lockKey,
|
|
2770
|
+
freshTotal,
|
|
2771
|
+
writer,
|
|
2772
|
+
messageList
|
|
2773
|
+
);
|
|
2774
|
+
omDebug(`[OM:threshold] activationResult: success=${activationResult.success}`);
|
|
2775
|
+
if (activationResult.success) {
|
|
2776
|
+
observationSucceeded = true;
|
|
2777
|
+
updatedRecord = activationResult.updatedRecord ?? recordAfterWait;
|
|
2778
|
+
activatedMessageIds = activationResult.activatedMessageIds;
|
|
2779
|
+
omDebug(
|
|
2780
|
+
`[OM:threshold] activation succeeded, obsTokens=${updatedRecord.observationTokenCount}, activeObsLen=${updatedRecord.activeObservations?.length}`
|
|
2781
|
+
);
|
|
2782
|
+
await this.maybeAsyncReflect(updatedRecord, updatedRecord.observationTokenCount ?? 0, writer, messageList);
|
|
2783
|
+
return;
|
|
2784
|
+
}
|
|
2785
|
+
if (this.observationConfig.blockAfter && freshTotal >= this.observationConfig.blockAfter) {
|
|
2786
|
+
omDebug(
|
|
2787
|
+
`[OM:threshold] blockAfter exceeded (${freshTotal} >= ${this.observationConfig.blockAfter}), falling through to sync observation`
|
|
2788
|
+
);
|
|
2789
|
+
freshRecord = await this.getOrCreateRecord(threadId, resourceId);
|
|
2790
|
+
const refreshedAll = messageList.get.all.db();
|
|
2791
|
+
freshUnobservedMessages = this.getUnobservedMessages(refreshedAll, freshRecord);
|
|
2792
|
+
} else {
|
|
2793
|
+
omDebug(`[OM:threshold] activation failed, no blockAfter or below it \u2014 letting async buffering catch up`);
|
|
2794
|
+
return;
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
if (freshUnobservedMessages.length > 0) {
|
|
2798
|
+
try {
|
|
2799
|
+
if (this.scope === "resource" && resourceId) {
|
|
2800
|
+
await this.doResourceScopedObservation(
|
|
2801
|
+
freshRecord,
|
|
2802
|
+
threadId,
|
|
2803
|
+
resourceId,
|
|
2804
|
+
freshUnobservedMessages,
|
|
2805
|
+
writer,
|
|
2806
|
+
abortSignal
|
|
2807
|
+
);
|
|
2808
|
+
} else {
|
|
2809
|
+
await this.doSynchronousObservation(freshRecord, threadId, freshUnobservedMessages, writer, abortSignal);
|
|
2152
2810
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
idsToRemove.push(msg.id);
|
|
2160
|
-
messagesToSave.push(msg);
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
messagesToSave.push(markerMsg);
|
|
2164
|
-
const unobservedParts = this.getUnobservedParts(markerMsg);
|
|
2165
|
-
if (unobservedParts.length === 0) {
|
|
2166
|
-
if (markerMsg.id) {
|
|
2167
|
-
idsToRemove.push(markerMsg.id);
|
|
2168
|
-
}
|
|
2169
|
-
} else if (unobservedParts.length < (markerMsg.content?.parts?.length ?? 0)) {
|
|
2170
|
-
markerMsg.content.parts = unobservedParts;
|
|
2171
|
-
}
|
|
2172
|
-
if (messagesToSave.length > 0) {
|
|
2173
|
-
await this.saveMessagesWithSealedIdTracking(messagesToSave, sealedIds, threadId, resourceId, state);
|
|
2174
|
-
}
|
|
2175
|
-
if (idsToRemove.length > 0) {
|
|
2176
|
-
messageList.removeByIds(idsToRemove);
|
|
2177
|
-
}
|
|
2811
|
+
updatedRecord = await this.getOrCreateRecord(threadId, resourceId);
|
|
2812
|
+
const updatedTime = updatedRecord.lastObservedAt?.getTime() ?? 0;
|
|
2813
|
+
observationSucceeded = updatedTime > preObservationTime;
|
|
2814
|
+
} catch (error) {
|
|
2815
|
+
if (abortSignal?.aborted) {
|
|
2816
|
+
abort("Agent execution was aborted");
|
|
2178
2817
|
} else {
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
if (messagesToSave.length > 0) {
|
|
2183
|
-
await this.saveMessagesWithSealedIdTracking(messagesToSave, sealedIds, threadId, resourceId, state);
|
|
2184
|
-
}
|
|
2818
|
+
abort(
|
|
2819
|
+
`Encountered error during memory observation ${error instanceof Error ? error.message : JSON.stringify(error, null, 2)}`
|
|
2820
|
+
);
|
|
2185
2821
|
}
|
|
2186
|
-
messageList.clear.input.db();
|
|
2187
|
-
messageList.clear.response.db();
|
|
2188
2822
|
}
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2823
|
+
}
|
|
2824
|
+
});
|
|
2825
|
+
return { observationSucceeded, updatedRecord, activatedMessageIds };
|
|
2826
|
+
}
|
|
2827
|
+
/**
|
|
2828
|
+
* Remove observed messages from message list after successful observation.
|
|
2829
|
+
* Accepts optional observedMessageIds for activation-based cleanup (when no markers are present).
|
|
2830
|
+
*/
|
|
2831
|
+
async cleanupAfterObservation(messageList, sealedIds, threadId, resourceId, state, observedMessageIds) {
|
|
2832
|
+
const allMsgs = messageList.get.all.db();
|
|
2833
|
+
let markerIdx = -1;
|
|
2834
|
+
let markerMsg = null;
|
|
2835
|
+
for (let i = allMsgs.length - 1; i >= 0; i--) {
|
|
2836
|
+
const msg = allMsgs[i];
|
|
2837
|
+
if (!msg) continue;
|
|
2838
|
+
if (this.findLastCompletedObservationBoundary(msg) !== -1) {
|
|
2839
|
+
markerIdx = i;
|
|
2840
|
+
markerMsg = msg;
|
|
2841
|
+
break;
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
omDebug(
|
|
2845
|
+
`[OM:cleanupBranch] allMsgs=${allMsgs.length}, markerFound=${markerIdx !== -1}, markerIdx=${markerIdx}, observedMessageIds=${observedMessageIds?.length ?? "undefined"}, allIds=${allMsgs.map((m) => m.id?.slice(0, 8)).join(",")}`
|
|
2846
|
+
);
|
|
2847
|
+
if (markerMsg && markerIdx !== -1) {
|
|
2848
|
+
const idsToRemove = [];
|
|
2849
|
+
const messagesToSave = [];
|
|
2850
|
+
for (let i = 0; i < markerIdx; i++) {
|
|
2851
|
+
const msg = allMsgs[i];
|
|
2852
|
+
if (msg?.id && msg.id !== "om-continuation") {
|
|
2853
|
+
idsToRemove.push(msg.id);
|
|
2854
|
+
messagesToSave.push(msg);
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
messagesToSave.push(markerMsg);
|
|
2858
|
+
const unobservedParts = this.getUnobservedParts(markerMsg);
|
|
2859
|
+
if (unobservedParts.length === 0) {
|
|
2860
|
+
if (markerMsg.id) {
|
|
2861
|
+
idsToRemove.push(markerMsg.id);
|
|
2199
2862
|
}
|
|
2863
|
+
} else if (unobservedParts.length < (markerMsg.content?.parts?.length ?? 0)) {
|
|
2864
|
+
markerMsg.content.parts = unobservedParts;
|
|
2865
|
+
}
|
|
2866
|
+
if (idsToRemove.length > 0) {
|
|
2867
|
+
messageList.removeByIds(idsToRemove);
|
|
2868
|
+
}
|
|
2869
|
+
if (messagesToSave.length > 0) {
|
|
2870
|
+
await this.saveMessagesWithSealedIdTracking(messagesToSave, sealedIds, threadId, resourceId, state);
|
|
2871
|
+
}
|
|
2872
|
+
} else if (observedMessageIds && observedMessageIds.length > 0) {
|
|
2873
|
+
const observedSet = new Set(observedMessageIds);
|
|
2874
|
+
const idsToRemove = [];
|
|
2875
|
+
for (const msg of allMsgs) {
|
|
2876
|
+
if (msg?.id && msg.id !== "om-continuation" && observedSet.has(msg.id)) {
|
|
2877
|
+
idsToRemove.push(msg.id);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
omDebug(
|
|
2881
|
+
`[OM:cleanupActivation] observedSet=${[...observedSet].map((id) => id.slice(0, 8)).join(",")}, matched=${idsToRemove.length}, idsToRemove=${idsToRemove.map((id) => id.slice(0, 8)).join(",")}`
|
|
2882
|
+
);
|
|
2883
|
+
if (idsToRemove.length > 0) {
|
|
2884
|
+
messageList.removeByIds(idsToRemove);
|
|
2885
|
+
omDebug(
|
|
2886
|
+
`[OM:cleanupActivation] removed ${idsToRemove.length} messages, remaining=${messageList.get.all.db().length}`
|
|
2887
|
+
);
|
|
2888
|
+
}
|
|
2889
|
+
} else {
|
|
2890
|
+
const newInput = messageList.clear.input.db();
|
|
2891
|
+
const newOutput = messageList.clear.response.db();
|
|
2892
|
+
const messagesToSave = [...newInput, ...newOutput];
|
|
2893
|
+
if (messagesToSave.length > 0) {
|
|
2894
|
+
await this.saveMessagesWithSealedIdTracking(messagesToSave, sealedIds, threadId, resourceId, state);
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
messageList.clear.input.db();
|
|
2898
|
+
messageList.clear.response.db();
|
|
2899
|
+
}
|
|
2900
|
+
/**
|
|
2901
|
+
* Handle per-step save when threshold is not reached.
|
|
2902
|
+
* Persists messages incrementally to prevent data loss on interruption.
|
|
2903
|
+
*/
|
|
2904
|
+
async handlePerStepSave(messageList, sealedIds, threadId, resourceId, state) {
|
|
2905
|
+
const newInput = messageList.clear.input.db();
|
|
2906
|
+
const newOutput = messageList.clear.response.db();
|
|
2907
|
+
const messagesToSave = [...newInput, ...newOutput];
|
|
2908
|
+
omDebug(
|
|
2909
|
+
`[OM:handlePerStepSave] cleared input=${newInput.length}, response=${newOutput.length}, toSave=${messagesToSave.length}, ids=${messagesToSave.map((m) => m.id?.slice(0, 8)).join(",")}`
|
|
2910
|
+
);
|
|
2911
|
+
if (messagesToSave.length > 0) {
|
|
2912
|
+
await this.saveMessagesWithSealedIdTracking(messagesToSave, sealedIds, threadId, resourceId, state);
|
|
2913
|
+
for (const msg of messagesToSave) {
|
|
2914
|
+
messageList.add(msg, "memory");
|
|
2200
2915
|
}
|
|
2201
2916
|
}
|
|
2917
|
+
}
|
|
2918
|
+
/**
|
|
2919
|
+
* Inject observations as system message and add continuation reminder.
|
|
2920
|
+
*/
|
|
2921
|
+
async injectObservationsIntoContext(messageList, record, threadId, resourceId, unobservedContextBlocks, requestContext) {
|
|
2202
2922
|
const thread = await this.storage.getThreadById({ threadId });
|
|
2203
2923
|
const threadOMMetadata = getThreadOMMetadata(thread?.metadata);
|
|
2204
2924
|
const currentTask = threadOMMetadata?.currentTask;
|
|
2205
2925
|
const suggestedResponse = threadOMMetadata?.suggestedResponse;
|
|
2206
|
-
const
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2926
|
+
const rawCurrentDate = requestContext?.get("currentDate");
|
|
2927
|
+
const currentDate = rawCurrentDate instanceof Date ? rawCurrentDate : typeof rawCurrentDate === "string" ? new Date(rawCurrentDate) : /* @__PURE__ */ new Date();
|
|
2928
|
+
if (!record.activeObservations) {
|
|
2929
|
+
return;
|
|
2930
|
+
}
|
|
2931
|
+
const observationSystemMessage = this.formatObservationsForContext(
|
|
2932
|
+
record.activeObservations,
|
|
2933
|
+
currentTask,
|
|
2934
|
+
suggestedResponse,
|
|
2935
|
+
unobservedContextBlocks,
|
|
2936
|
+
currentDate
|
|
2937
|
+
);
|
|
2938
|
+
messageList.clearSystemMessages("observational-memory");
|
|
2939
|
+
messageList.addSystem(observationSystemMessage, "observational-memory");
|
|
2940
|
+
const continuationMessage = {
|
|
2941
|
+
id: `om-continuation`,
|
|
2942
|
+
role: "user",
|
|
2943
|
+
createdAt: /* @__PURE__ */ new Date(0),
|
|
2944
|
+
content: {
|
|
2945
|
+
format: 2,
|
|
2946
|
+
parts: [
|
|
2947
|
+
{
|
|
2948
|
+
type: "text",
|
|
2949
|
+
text: `<system-reminder>This message is not from the user, the conversation history grew too long and wouldn't fit in context! Thankfully the entire conversation is stored in your memory observations. Please continue from where the observations left off. Do not refer to your "memory observations" directly, the user doesn't know about them, they are your memories! Just respond naturally as if you're remembering the conversation (you are!). Do not say "Hi there!" or "based on our previous conversation" as if the conversation is just starting, this is not a new conversation. This is an ongoing conversation, keep continuity by responding based on your memory. For example do not say "I understand. I've reviewed my memory observations", or "I remember [...]". Answer naturally following the suggestion from your memory. Note that your memory may contain a suggested first response, which you should follow.
|
|
2227
2950
|
|
|
2228
2951
|
IMPORTANT: this system reminder is NOT from the user. The system placed it here as part of your memory system. This message is part of you remembering your conversation with the user.
|
|
2229
2952
|
|
|
2230
2953
|
NOTE: Any messages following this system reminder are newer than your memories.
|
|
2231
2954
|
</system-reminder>`
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2955
|
+
}
|
|
2956
|
+
]
|
|
2957
|
+
},
|
|
2958
|
+
threadId,
|
|
2959
|
+
resourceId
|
|
2960
|
+
};
|
|
2961
|
+
messageList.add(continuationMessage, "memory");
|
|
2962
|
+
}
|
|
2963
|
+
/**
|
|
2964
|
+
* Filter out already-observed messages from message list (step 0 only).
|
|
2965
|
+
* Historical messages loaded from DB may contain observation markers from previous sessions.
|
|
2966
|
+
*/
|
|
2967
|
+
filterAlreadyObservedMessages(messageList, record) {
|
|
2968
|
+
const allMessages = messageList.get.all.db();
|
|
2969
|
+
let markerMessageIndex = -1;
|
|
2970
|
+
let markerMessage = null;
|
|
2971
|
+
for (let i = allMessages.length - 1; i >= 0; i--) {
|
|
2972
|
+
const msg = allMessages[i];
|
|
2973
|
+
if (!msg) continue;
|
|
2974
|
+
if (this.findLastCompletedObservationBoundary(msg) !== -1) {
|
|
2975
|
+
markerMessageIndex = i;
|
|
2976
|
+
markerMessage = msg;
|
|
2977
|
+
break;
|
|
2978
|
+
}
|
|
2239
2979
|
}
|
|
2240
|
-
if (
|
|
2241
|
-
const
|
|
2242
|
-
let
|
|
2243
|
-
let markerMessage = null;
|
|
2244
|
-
for (let i = allMessages.length - 1; i >= 0; i--) {
|
|
2980
|
+
if (markerMessage && markerMessageIndex !== -1) {
|
|
2981
|
+
const messagesToRemove = [];
|
|
2982
|
+
for (let i = 0; i < markerMessageIndex; i++) {
|
|
2245
2983
|
const msg = allMessages[i];
|
|
2246
|
-
if (
|
|
2247
|
-
|
|
2248
|
-
markerMessageIndex = i;
|
|
2249
|
-
markerMessage = msg;
|
|
2250
|
-
break;
|
|
2984
|
+
if (msg?.id && msg.id !== "om-continuation") {
|
|
2985
|
+
messagesToRemove.push(msg.id);
|
|
2251
2986
|
}
|
|
2252
2987
|
}
|
|
2253
|
-
if (
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2988
|
+
if (messagesToRemove.length > 0) {
|
|
2989
|
+
messageList.removeByIds(messagesToRemove);
|
|
2990
|
+
}
|
|
2991
|
+
const unobservedParts = this.getUnobservedParts(markerMessage);
|
|
2992
|
+
if (unobservedParts.length === 0) {
|
|
2993
|
+
if (markerMessage.id) {
|
|
2994
|
+
messageList.removeByIds([markerMessage.id]);
|
|
2995
|
+
}
|
|
2996
|
+
} else if (unobservedParts.length < (markerMessage.content?.parts?.length ?? 0)) {
|
|
2997
|
+
markerMessage.content.parts = unobservedParts;
|
|
2998
|
+
}
|
|
2999
|
+
} else if (record) {
|
|
3000
|
+
const observedIds = new Set(Array.isArray(record.observedMessageIds) ? record.observedMessageIds : []);
|
|
3001
|
+
const lastObservedAt = record.lastObservedAt;
|
|
3002
|
+
const messagesToRemove = [];
|
|
3003
|
+
for (const msg of allMessages) {
|
|
3004
|
+
if (!msg?.id || msg.id === "om-continuation") continue;
|
|
3005
|
+
if (observedIds.has(msg.id)) {
|
|
3006
|
+
messagesToRemove.push(msg.id);
|
|
3007
|
+
continue;
|
|
3008
|
+
}
|
|
3009
|
+
if (lastObservedAt && msg.createdAt) {
|
|
3010
|
+
const msgDate = new Date(msg.createdAt);
|
|
3011
|
+
if (msgDate <= lastObservedAt) {
|
|
2258
3012
|
messagesToRemove.push(msg.id);
|
|
2259
3013
|
}
|
|
2260
3014
|
}
|
|
2261
|
-
|
|
2262
|
-
|
|
3015
|
+
}
|
|
3016
|
+
if (messagesToRemove.length > 0) {
|
|
3017
|
+
messageList.removeByIds(messagesToRemove);
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
/**
|
|
3022
|
+
* Process input at each step - check threshold, observe if needed, save, inject observations.
|
|
3023
|
+
* This is the ONLY processor method - all OM logic happens here.
|
|
3024
|
+
*
|
|
3025
|
+
* Flow:
|
|
3026
|
+
* 1. Load historical messages (step 0 only)
|
|
3027
|
+
* 2. Check if observation threshold is reached
|
|
3028
|
+
* 3. If threshold reached: observe, save messages with markers
|
|
3029
|
+
* 4. Inject observations into context
|
|
3030
|
+
* 5. Filter out already-observed messages
|
|
3031
|
+
*/
|
|
3032
|
+
async processInputStep(args) {
|
|
3033
|
+
const { messageList, requestContext, stepNumber, state: _state, writer, abortSignal, abort } = args;
|
|
3034
|
+
const state = _state ?? {};
|
|
3035
|
+
const context = this.getThreadContext(requestContext, messageList);
|
|
3036
|
+
if (!context) {
|
|
3037
|
+
return messageList;
|
|
3038
|
+
}
|
|
3039
|
+
const { threadId, resourceId } = context;
|
|
3040
|
+
const memoryContext = parseMemoryRequestContext(requestContext);
|
|
3041
|
+
const readOnly = memoryContext?.memoryConfig?.readOnly;
|
|
3042
|
+
let record = await this.getOrCreateRecord(threadId, resourceId);
|
|
3043
|
+
omDebug(
|
|
3044
|
+
`[OM:step] processInputStep step=${stepNumber}: recordId=${record.id}, genCount=${record.generationCount}, obsTokens=${record.observationTokenCount}, bufferedReflection=${record.bufferedReflection ? "present (" + record.bufferedReflection.length + " chars)" : "empty"}, activeObsLen=${record.activeObservations?.length}`
|
|
3045
|
+
);
|
|
3046
|
+
await this.loadHistoricalMessagesIfNeeded(messageList, state, threadId, resourceId, record.lastObservedAt);
|
|
3047
|
+
let unobservedContextBlocks;
|
|
3048
|
+
if (this.scope === "resource" && resourceId) {
|
|
3049
|
+
unobservedContextBlocks = await this.loadOtherThreadsContext(resourceId, threadId);
|
|
3050
|
+
}
|
|
3051
|
+
if (stepNumber === 0 && !readOnly && this.isAsyncObservationEnabled()) {
|
|
3052
|
+
const lockKey = this.getLockKey(threadId, resourceId);
|
|
3053
|
+
const bufferedChunks = this.getBufferedChunks(record);
|
|
3054
|
+
omDebug(
|
|
3055
|
+
`[OM:step0-activation] asyncObsEnabled=true, bufferedChunks=${bufferedChunks.length}, isBufferingObs=${record.isBufferingObservation}`
|
|
3056
|
+
);
|
|
3057
|
+
{
|
|
3058
|
+
const bufKey = this.getObservationBufferKey(lockKey);
|
|
3059
|
+
const dbBoundary = record.lastBufferedAtTokens ?? 0;
|
|
3060
|
+
const currentContextTokens = this.tokenCounter.countMessages(messageList.get.all.db());
|
|
3061
|
+
if (dbBoundary > currentContextTokens) {
|
|
3062
|
+
omDebug(
|
|
3063
|
+
`[OM:step0-boundary-reset] dbBoundary=${dbBoundary} > currentContext=${currentContextTokens}, resetting to current`
|
|
3064
|
+
);
|
|
3065
|
+
_ObservationalMemory.lastBufferedBoundary.set(bufKey, currentContextTokens);
|
|
3066
|
+
this.storage.setBufferingObservationFlag(record.id, false, currentContextTokens).catch(() => {
|
|
3067
|
+
});
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
if (bufferedChunks.length > 0) {
|
|
3071
|
+
const allMsgsForCheck = messageList.get.all.db();
|
|
3072
|
+
const otherThreadTokensForCheck = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
|
|
3073
|
+
const currentObsTokensForCheck = record.observationTokenCount ?? 0;
|
|
3074
|
+
const { totalPendingTokens: step0PendingTokens, threshold: step0Threshold } = this.calculateObservationThresholds(
|
|
3075
|
+
allMsgsForCheck,
|
|
3076
|
+
[],
|
|
3077
|
+
// unobserved not needed for threshold calculation
|
|
3078
|
+
0,
|
|
3079
|
+
// pendingTokens not needed — allMessages covers context
|
|
3080
|
+
otherThreadTokensForCheck,
|
|
3081
|
+
currentObsTokensForCheck,
|
|
3082
|
+
record
|
|
3083
|
+
);
|
|
3084
|
+
omDebug(
|
|
3085
|
+
`[OM:step0-activation] pendingTokens=${step0PendingTokens}, threshold=${step0Threshold}, blockAfter=${this.observationConfig.blockAfter}, shouldActivate=${step0PendingTokens >= step0Threshold}, allMsgs=${allMsgsForCheck.length}`
|
|
3086
|
+
);
|
|
3087
|
+
if (step0PendingTokens >= step0Threshold) {
|
|
3088
|
+
const activationResult = await this.tryActivateBufferedObservations(
|
|
3089
|
+
record,
|
|
3090
|
+
lockKey,
|
|
3091
|
+
step0PendingTokens,
|
|
3092
|
+
writer,
|
|
3093
|
+
messageList
|
|
3094
|
+
);
|
|
3095
|
+
if (activationResult.success && activationResult.updatedRecord) {
|
|
3096
|
+
record = activationResult.updatedRecord;
|
|
3097
|
+
const activatedIds = activationResult.activatedMessageIds ?? [];
|
|
3098
|
+
if (activatedIds.length > 0) {
|
|
3099
|
+
const activatedSet = new Set(activatedIds);
|
|
3100
|
+
const allMsgs = messageList.get.all.db();
|
|
3101
|
+
const idsToRemove = allMsgs.filter((msg) => msg?.id && msg.id !== "om-continuation" && activatedSet.has(msg.id)).map((msg) => msg.id);
|
|
3102
|
+
if (idsToRemove.length > 0) {
|
|
3103
|
+
messageList.removeByIds(idsToRemove);
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
this.cleanupStaticMaps(threadId, resourceId, activatedIds);
|
|
3107
|
+
const bufKey = this.getObservationBufferKey(lockKey);
|
|
3108
|
+
_ObservationalMemory.lastBufferedBoundary.set(bufKey, 0);
|
|
3109
|
+
this.storage.setBufferingObservationFlag(record.id, false, 0).catch(() => {
|
|
3110
|
+
});
|
|
3111
|
+
await this.maybeReflect(
|
|
3112
|
+
record,
|
|
3113
|
+
record.observationTokenCount ?? 0,
|
|
3114
|
+
threadId,
|
|
3115
|
+
writer,
|
|
3116
|
+
void 0,
|
|
3117
|
+
messageList
|
|
3118
|
+
);
|
|
3119
|
+
record = await this.getOrCreateRecord(threadId, resourceId);
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
if (stepNumber === 0 && !readOnly) {
|
|
3125
|
+
const obsTokens = record.observationTokenCount ?? 0;
|
|
3126
|
+
if (this.shouldReflect(obsTokens)) {
|
|
3127
|
+
omDebug(`[OM:step0-reflect] obsTokens=${obsTokens} over reflectThreshold, triggering reflection`);
|
|
3128
|
+
await this.maybeReflect(record, obsTokens, threadId, writer, void 0, messageList);
|
|
3129
|
+
record = await this.getOrCreateRecord(threadId, resourceId);
|
|
3130
|
+
} else if (this.isAsyncReflectionEnabled()) {
|
|
3131
|
+
const lockKey = this.getLockKey(threadId, resourceId);
|
|
3132
|
+
if (this.shouldTriggerAsyncReflection(obsTokens, lockKey, record)) {
|
|
3133
|
+
omDebug(`[OM:step0-reflect] obsTokens=${obsTokens} above activation point, triggering async reflection`);
|
|
3134
|
+
await this.maybeAsyncReflect(record, obsTokens, writer, messageList);
|
|
3135
|
+
record = await this.getOrCreateRecord(threadId, resourceId);
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
if (!readOnly) {
|
|
3140
|
+
const allMessages = messageList.get.all.db();
|
|
3141
|
+
const unobservedMessages = this.getUnobservedMessages(allMessages, record);
|
|
3142
|
+
const otherThreadTokens = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
|
|
3143
|
+
const currentObservationTokens = record.observationTokenCount ?? 0;
|
|
3144
|
+
const thresholds = this.calculateObservationThresholds(
|
|
3145
|
+
allMessages,
|
|
3146
|
+
unobservedMessages,
|
|
3147
|
+
0,
|
|
3148
|
+
// pendingTokens not needed — allMessages covers context
|
|
3149
|
+
otherThreadTokens,
|
|
3150
|
+
currentObservationTokens,
|
|
3151
|
+
record
|
|
3152
|
+
);
|
|
3153
|
+
const { totalPendingTokens, threshold } = thresholds;
|
|
3154
|
+
const stateSealedIds = state.sealedIds ?? /* @__PURE__ */ new Set();
|
|
3155
|
+
const staticSealedIds = _ObservationalMemory.sealedMessageIds.get(threadId) ?? /* @__PURE__ */ new Set();
|
|
3156
|
+
const sealedIds = /* @__PURE__ */ new Set([...stateSealedIds, ...staticSealedIds]);
|
|
3157
|
+
state.sealedIds = sealedIds;
|
|
3158
|
+
const lockKey = this.getLockKey(threadId, resourceId);
|
|
3159
|
+
if (this.isAsyncObservationEnabled() && totalPendingTokens < threshold) {
|
|
3160
|
+
const shouldTrigger = this.shouldTriggerAsyncObservation(totalPendingTokens, lockKey, record);
|
|
3161
|
+
omDebug(
|
|
3162
|
+
`[OM:async-obs] belowThreshold: pending=${totalPendingTokens}, threshold=${threshold}, shouldTrigger=${shouldTrigger}, isBufferingObs=${record.isBufferingObservation}, lastBufferedAt=${record.lastBufferedAtTokens}`
|
|
3163
|
+
);
|
|
3164
|
+
if (shouldTrigger) {
|
|
3165
|
+
this.startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, totalPendingTokens);
|
|
3166
|
+
}
|
|
3167
|
+
} else if (this.isAsyncObservationEnabled()) {
|
|
3168
|
+
const shouldTrigger = this.shouldTriggerAsyncObservation(totalPendingTokens, lockKey, record);
|
|
3169
|
+
omDebug(
|
|
3170
|
+
`[OM:async-obs] atOrAboveThreshold: pending=${totalPendingTokens}, threshold=${threshold}, step=${stepNumber}, shouldTrigger=${shouldTrigger}`
|
|
3171
|
+
);
|
|
3172
|
+
if (shouldTrigger) {
|
|
3173
|
+
this.startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, totalPendingTokens);
|
|
2263
3174
|
}
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
3175
|
+
}
|
|
3176
|
+
if (stepNumber > 0) {
|
|
3177
|
+
await this.handlePerStepSave(messageList, sealedIds, threadId, resourceId, state);
|
|
3178
|
+
}
|
|
3179
|
+
if (stepNumber > 0 && totalPendingTokens >= threshold) {
|
|
3180
|
+
const { observationSucceeded, updatedRecord, activatedMessageIds } = await this.handleThresholdReached(
|
|
3181
|
+
messageList,
|
|
3182
|
+
record,
|
|
3183
|
+
threadId,
|
|
3184
|
+
resourceId,
|
|
3185
|
+
threshold,
|
|
3186
|
+
lockKey,
|
|
3187
|
+
writer,
|
|
3188
|
+
abortSignal,
|
|
3189
|
+
abort
|
|
3190
|
+
);
|
|
3191
|
+
if (observationSucceeded) {
|
|
3192
|
+
const observedIds = activatedMessageIds?.length ? activatedMessageIds : Array.isArray(updatedRecord.observedMessageIds) ? updatedRecord.observedMessageIds : void 0;
|
|
3193
|
+
omDebug(
|
|
3194
|
+
`[OM:cleanup] observedIds=${observedIds?.length ?? "undefined"}, ids=${observedIds?.join(",") ?? "none"}, updatedRecord.observedMessageIds=${JSON.stringify(updatedRecord.observedMessageIds)}`
|
|
3195
|
+
);
|
|
3196
|
+
await this.cleanupAfterObservation(messageList, sealedIds, threadId, resourceId, state, observedIds);
|
|
3197
|
+
if (activatedMessageIds?.length) {
|
|
3198
|
+
this.cleanupStaticMaps(threadId, resourceId, activatedMessageIds);
|
|
3199
|
+
}
|
|
3200
|
+
if (this.isAsyncObservationEnabled()) {
|
|
3201
|
+
const bufKey = this.getObservationBufferKey(lockKey);
|
|
3202
|
+
_ObservationalMemory.lastBufferedBoundary.set(bufKey, 0);
|
|
3203
|
+
this.storage.setBufferingObservationFlag(updatedRecord.id, false, 0).catch(() => {
|
|
3204
|
+
});
|
|
3205
|
+
omDebug(`[OM:threshold] post-activation boundary reset to 0`);
|
|
2268
3206
|
}
|
|
2269
|
-
} else if (unobservedParts.length < (markerMessage.content?.parts?.length ?? 0)) {
|
|
2270
|
-
markerMessage.content.parts = unobservedParts;
|
|
2271
3207
|
}
|
|
3208
|
+
record = updatedRecord;
|
|
2272
3209
|
}
|
|
2273
3210
|
}
|
|
3211
|
+
await this.injectObservationsIntoContext(
|
|
3212
|
+
messageList,
|
|
3213
|
+
record,
|
|
3214
|
+
threadId,
|
|
3215
|
+
resourceId,
|
|
3216
|
+
unobservedContextBlocks,
|
|
3217
|
+
requestContext
|
|
3218
|
+
);
|
|
3219
|
+
if (stepNumber === 0) {
|
|
3220
|
+
this.filterAlreadyObservedMessages(messageList, record);
|
|
3221
|
+
}
|
|
3222
|
+
{
|
|
3223
|
+
const freshRecord = await this.getOrCreateRecord(threadId, resourceId);
|
|
3224
|
+
const contextMessages = messageList.get.all.db();
|
|
3225
|
+
const freshUnobservedTokens = this.tokenCounter.countMessages(contextMessages);
|
|
3226
|
+
const otherThreadTokens = unobservedContextBlocks ? this.tokenCounter.countString(unobservedContextBlocks) : 0;
|
|
3227
|
+
const currentObservationTokens = freshRecord.observationTokenCount ?? 0;
|
|
3228
|
+
const threshold = this.calculateDynamicThreshold(this.observationConfig.messageTokens, currentObservationTokens);
|
|
3229
|
+
const baseReflectionThreshold = this.getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
3230
|
+
const isSharedBudget = typeof this.observationConfig.messageTokens !== "number";
|
|
3231
|
+
const totalBudget = isSharedBudget ? this.observationConfig.messageTokens.max : 0;
|
|
3232
|
+
const effectiveObservationTokensThreshold = isSharedBudget ? Math.max(totalBudget - threshold, 1e3) : baseReflectionThreshold;
|
|
3233
|
+
const totalPendingTokens = freshUnobservedTokens + otherThreadTokens;
|
|
3234
|
+
await this.emitStepProgress(
|
|
3235
|
+
writer,
|
|
3236
|
+
threadId,
|
|
3237
|
+
resourceId,
|
|
3238
|
+
stepNumber,
|
|
3239
|
+
freshRecord,
|
|
3240
|
+
{
|
|
3241
|
+
totalPendingTokens,
|
|
3242
|
+
threshold,
|
|
3243
|
+
effectiveObservationTokensThreshold
|
|
3244
|
+
},
|
|
3245
|
+
currentObservationTokens
|
|
3246
|
+
);
|
|
3247
|
+
}
|
|
2274
3248
|
return messageList;
|
|
2275
3249
|
}
|
|
2276
3250
|
/**
|
|
@@ -2296,11 +3270,21 @@ NOTE: Any messages following this system reminder are newer than your memories.
|
|
|
2296
3270
|
const newInput = messageList.get.input.db();
|
|
2297
3271
|
const newOutput = messageList.get.response.db();
|
|
2298
3272
|
const messagesToSave = [...newInput, ...newOutput];
|
|
3273
|
+
omDebug(
|
|
3274
|
+
`[OM:processOutputResult] threadId=${threadId}, inputMsgs=${newInput.length}, responseMsgs=${newOutput.length}, totalToSave=${messagesToSave.length}, allMsgsInList=${messageList.get.all.db().length}`
|
|
3275
|
+
);
|
|
2299
3276
|
if (messagesToSave.length === 0) {
|
|
3277
|
+
omDebug(`[OM:processOutputResult] nothing to save \u2014 all messages were already saved during per-step saves`);
|
|
2300
3278
|
return messageList;
|
|
2301
3279
|
}
|
|
2302
3280
|
const sealedIds = state.sealedIds ?? /* @__PURE__ */ new Set();
|
|
3281
|
+
omDebug(
|
|
3282
|
+
`[OM:processOutputResult] saving ${messagesToSave.length} messages, sealedIds=${sealedIds.size}, ids=${messagesToSave.map((m) => m.id?.slice(0, 8)).join(",")}`
|
|
3283
|
+
);
|
|
2303
3284
|
await this.saveMessagesWithSealedIdTracking(messagesToSave, sealedIds, threadId, resourceId, state);
|
|
3285
|
+
omDebug(
|
|
3286
|
+
`[OM:processOutputResult] saved successfully, finalIds=${messagesToSave.map((m) => m.id?.slice(0, 8)).join(",")}`
|
|
3287
|
+
);
|
|
2304
3288
|
return messageList;
|
|
2305
3289
|
}
|
|
2306
3290
|
/**
|
|
@@ -2530,6 +3514,7 @@ ${newThreadSection}`;
|
|
|
2530
3514
|
}))
|
|
2531
3515
|
});
|
|
2532
3516
|
await this.storage.setObservingFlag(record.id, true);
|
|
3517
|
+
registerOp(record.id, "observing");
|
|
2533
3518
|
const cycleId = crypto.randomUUID();
|
|
2534
3519
|
const tokensToObserve = this.tokenCounter.countMessages(unobservedMessages);
|
|
2535
3520
|
const lastMessage = unobservedMessages[unobservedMessages.length - 1];
|
|
@@ -2555,9 +3540,20 @@ ${newThreadSection}`;
|
|
|
2555
3540
|
return;
|
|
2556
3541
|
}
|
|
2557
3542
|
}
|
|
3543
|
+
let messagesToObserve = unobservedMessages;
|
|
3544
|
+
const bufferActivation = this.observationConfig.bufferActivation;
|
|
3545
|
+
if (bufferActivation && bufferActivation < 1 && unobservedMessages.length >= 1) {
|
|
3546
|
+
const newestMsg = unobservedMessages[unobservedMessages.length - 1];
|
|
3547
|
+
if (newestMsg?.content?.parts?.length) {
|
|
3548
|
+
this.sealMessagesForBuffering([newestMsg]);
|
|
3549
|
+
omDebug(
|
|
3550
|
+
`[OM:sync-obs] sealed newest message (${newestMsg.role}, ${newestMsg.content.parts.length} parts) for ratio-aware observation`
|
|
3551
|
+
);
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
2558
3554
|
const result = await this.callObserver(
|
|
2559
3555
|
freshRecord?.activeObservations ?? record.activeObservations,
|
|
2560
|
-
|
|
3556
|
+
messagesToObserve,
|
|
2561
3557
|
abortSignal
|
|
2562
3558
|
);
|
|
2563
3559
|
const existingObservations = freshRecord?.activeObservations ?? record.activeObservations ?? "";
|
|
@@ -2572,8 +3568,8 @@ ${result.observations}` : result.observations;
|
|
|
2572
3568
|
}
|
|
2573
3569
|
let totalTokenCount = this.tokenCounter.countObservations(newObservations);
|
|
2574
3570
|
const cycleObservationTokens = this.tokenCounter.countObservations(result.observations);
|
|
2575
|
-
const lastObservedAt = this.getMaxMessageTimestamp(
|
|
2576
|
-
const newMessageIds =
|
|
3571
|
+
const lastObservedAt = this.getMaxMessageTimestamp(messagesToObserve);
|
|
3572
|
+
const newMessageIds = messagesToObserve.map((m) => m.id);
|
|
2577
3573
|
const existingIds = freshRecord?.observedMessageIds ?? record.observedMessageIds ?? [];
|
|
2578
3574
|
const allObservedIds = [.../* @__PURE__ */ new Set([...Array.isArray(existingIds) ? existingIds : [], ...newMessageIds])];
|
|
2579
3575
|
await this.storage.updateActiveObservations({
|
|
@@ -2597,12 +3593,13 @@ ${result.observations}` : result.observations;
|
|
|
2597
3593
|
});
|
|
2598
3594
|
}
|
|
2599
3595
|
}
|
|
3596
|
+
const actualTokensObserved = this.tokenCounter.countMessages(messagesToObserve);
|
|
2600
3597
|
if (lastMessage?.id) {
|
|
2601
3598
|
const endMarker = this.createObservationEndMarker({
|
|
2602
3599
|
cycleId,
|
|
2603
3600
|
operationType: "observation",
|
|
2604
3601
|
startedAt,
|
|
2605
|
-
tokensObserved:
|
|
3602
|
+
tokensObserved: actualTokensObserved,
|
|
2606
3603
|
observationTokens: cycleObservationTokens,
|
|
2607
3604
|
observations: result.observations,
|
|
2608
3605
|
currentTask: result.currentTask,
|
|
@@ -2623,7 +3620,7 @@ ${result.observations}` : result.observations;
|
|
|
2623
3620
|
observations: newObservations,
|
|
2624
3621
|
rawObserverOutput: result.observations,
|
|
2625
3622
|
previousObservations: record.activeObservations,
|
|
2626
|
-
messages:
|
|
3623
|
+
messages: messagesToObserve.map((m) => ({
|
|
2627
3624
|
role: m.role,
|
|
2628
3625
|
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
2629
3626
|
})),
|
|
@@ -2655,11 +3652,514 @@ ${result.observations}` : result.observations;
|
|
|
2655
3652
|
if (abortSignal?.aborted) {
|
|
2656
3653
|
throw error;
|
|
2657
3654
|
}
|
|
2658
|
-
|
|
3655
|
+
omError("[OM] Observation failed", error);
|
|
2659
3656
|
} finally {
|
|
2660
3657
|
await this.storage.setObservingFlag(record.id, false);
|
|
3658
|
+
unregisterOp(record.id, "observing");
|
|
2661
3659
|
}
|
|
2662
3660
|
}
|
|
3661
|
+
/**
|
|
3662
|
+
* Start an async background observation that stores results to bufferedObservations.
|
|
3663
|
+
* This is a fire-and-forget operation that runs in the background.
|
|
3664
|
+
* The results will be swapped to active when the main threshold is reached.
|
|
3665
|
+
*
|
|
3666
|
+
* If another buffering operation is already in progress for this scope, this will
|
|
3667
|
+
* wait for it to complete before starting a new one (mutex behavior).
|
|
3668
|
+
*
|
|
3669
|
+
* @param record - Current OM record
|
|
3670
|
+
* @param threadId - Thread ID
|
|
3671
|
+
* @param unobservedMessages - All unobserved messages (will be filtered for already-buffered)
|
|
3672
|
+
* @param lockKey - Lock key for this scope
|
|
3673
|
+
* @param writer - Optional stream writer for emitting buffering markers
|
|
3674
|
+
*/
|
|
3675
|
+
startAsyncBufferedObservation(record, threadId, unobservedMessages, lockKey, writer, contextWindowTokens) {
|
|
3676
|
+
const bufferKey = this.getObservationBufferKey(lockKey);
|
|
3677
|
+
const currentTokens = contextWindowTokens ?? this.tokenCounter.countMessages(unobservedMessages) + (record.pendingMessageTokens ?? 0);
|
|
3678
|
+
_ObservationalMemory.lastBufferedBoundary.set(bufferKey, currentTokens);
|
|
3679
|
+
registerOp(record.id, "bufferingObservation");
|
|
3680
|
+
this.storage.setBufferingObservationFlag(record.id, true, currentTokens).catch((err) => {
|
|
3681
|
+
omError("[OM] Failed to set buffering observation flag", err);
|
|
3682
|
+
});
|
|
3683
|
+
const asyncOp = this.runAsyncBufferedObservation(record, threadId, unobservedMessages, bufferKey, writer).finally(
|
|
3684
|
+
() => {
|
|
3685
|
+
_ObservationalMemory.asyncBufferingOps.delete(bufferKey);
|
|
3686
|
+
unregisterOp(record.id, "bufferingObservation");
|
|
3687
|
+
this.storage.setBufferingObservationFlag(record.id, false).catch((err) => {
|
|
3688
|
+
omError("[OM] Failed to clear buffering observation flag", err);
|
|
3689
|
+
});
|
|
3690
|
+
}
|
|
3691
|
+
);
|
|
3692
|
+
_ObservationalMemory.asyncBufferingOps.set(bufferKey, asyncOp);
|
|
3693
|
+
}
|
|
3694
|
+
/**
|
|
3695
|
+
* Internal method that waits for existing buffering operation and then runs new buffering.
|
|
3696
|
+
* This implements the mutex-wait behavior.
|
|
3697
|
+
*/
|
|
3698
|
+
async runAsyncBufferedObservation(record, threadId, unobservedMessages, bufferKey, writer) {
|
|
3699
|
+
const existingOp = _ObservationalMemory.asyncBufferingOps.get(bufferKey);
|
|
3700
|
+
if (existingOp) {
|
|
3701
|
+
try {
|
|
3702
|
+
await existingOp;
|
|
3703
|
+
} catch {
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
3707
|
+
if (!freshRecord) {
|
|
3708
|
+
return;
|
|
3709
|
+
}
|
|
3710
|
+
let bufferCursor = _ObservationalMemory.lastBufferedAtTime.get(bufferKey) ?? freshRecord.lastBufferedAtTime ?? null;
|
|
3711
|
+
if (freshRecord.lastObservedAt) {
|
|
3712
|
+
const lastObserved = new Date(freshRecord.lastObservedAt);
|
|
3713
|
+
if (!bufferCursor || lastObserved > bufferCursor) {
|
|
3714
|
+
bufferCursor = lastObserved;
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
let candidateMessages = this.getUnobservedMessages(unobservedMessages, freshRecord, {
|
|
3718
|
+
excludeBuffered: true
|
|
3719
|
+
});
|
|
3720
|
+
const preFilterCount = candidateMessages.length;
|
|
3721
|
+
if (bufferCursor) {
|
|
3722
|
+
candidateMessages = candidateMessages.filter((msg) => {
|
|
3723
|
+
if (!msg.createdAt) return true;
|
|
3724
|
+
return new Date(msg.createdAt) > bufferCursor;
|
|
3725
|
+
});
|
|
3726
|
+
}
|
|
3727
|
+
omDebug(
|
|
3728
|
+
`[OM:bufferCursor] cursor=${bufferCursor?.toISOString() ?? "null"}, unobserved=${unobservedMessages.length}, afterExcludeBuffered=${preFilterCount}, afterCursorFilter=${candidateMessages.length}`
|
|
3729
|
+
);
|
|
3730
|
+
const bufferTokens = this.observationConfig.bufferTokens ?? 5e3;
|
|
3731
|
+
const minNewTokens = bufferTokens / 2;
|
|
3732
|
+
const newTokens = this.tokenCounter.countMessages(candidateMessages);
|
|
3733
|
+
if (newTokens < minNewTokens) {
|
|
3734
|
+
return;
|
|
3735
|
+
}
|
|
3736
|
+
const messagesToBuffer = candidateMessages;
|
|
3737
|
+
this.sealMessagesForBuffering(messagesToBuffer);
|
|
3738
|
+
await this.messageHistory.persistMessages({
|
|
3739
|
+
messages: messagesToBuffer,
|
|
3740
|
+
threadId,
|
|
3741
|
+
resourceId: freshRecord.resourceId ?? void 0
|
|
3742
|
+
});
|
|
3743
|
+
let staticSealedIds = _ObservationalMemory.sealedMessageIds.get(threadId);
|
|
3744
|
+
if (!staticSealedIds) {
|
|
3745
|
+
staticSealedIds = /* @__PURE__ */ new Set();
|
|
3746
|
+
_ObservationalMemory.sealedMessageIds.set(threadId, staticSealedIds);
|
|
3747
|
+
}
|
|
3748
|
+
for (const msg of messagesToBuffer) {
|
|
3749
|
+
staticSealedIds.add(msg.id);
|
|
3750
|
+
}
|
|
3751
|
+
const cycleId = `buffer-obs-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
3752
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3753
|
+
const tokensToBuffer = this.tokenCounter.countMessages(messagesToBuffer);
|
|
3754
|
+
if (writer) {
|
|
3755
|
+
const startMarker = this.createBufferingStartMarker({
|
|
3756
|
+
cycleId,
|
|
3757
|
+
operationType: "observation",
|
|
3758
|
+
tokensToBuffer,
|
|
3759
|
+
recordId: freshRecord.id,
|
|
3760
|
+
threadId,
|
|
3761
|
+
threadIds: [threadId]
|
|
3762
|
+
});
|
|
3763
|
+
void writer.custom(startMarker).catch(() => {
|
|
3764
|
+
});
|
|
3765
|
+
}
|
|
3766
|
+
try {
|
|
3767
|
+
omDebug(
|
|
3768
|
+
`[OM:bufferInput] cycleId=${cycleId}, msgCount=${messagesToBuffer.length}, msgTokens=${this.tokenCounter.countMessages(messagesToBuffer)}, ids=${messagesToBuffer.map((m) => `${m.id?.slice(0, 8)}@${m.createdAt ? new Date(m.createdAt).toISOString() : "none"}`).join(",")}`
|
|
3769
|
+
);
|
|
3770
|
+
await this.doAsyncBufferedObservation(freshRecord, threadId, messagesToBuffer, cycleId, startedAt, writer);
|
|
3771
|
+
const maxTs = this.getMaxMessageTimestamp(messagesToBuffer);
|
|
3772
|
+
const cursor = new Date(maxTs.getTime() + 1);
|
|
3773
|
+
_ObservationalMemory.lastBufferedAtTime.set(bufferKey, cursor);
|
|
3774
|
+
} catch (error) {
|
|
3775
|
+
if (writer) {
|
|
3776
|
+
const failedMarker = this.createBufferingFailedMarker({
|
|
3777
|
+
cycleId,
|
|
3778
|
+
operationType: "observation",
|
|
3779
|
+
startedAt,
|
|
3780
|
+
tokensAttempted: tokensToBuffer,
|
|
3781
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3782
|
+
recordId: freshRecord.id,
|
|
3783
|
+
threadId
|
|
3784
|
+
});
|
|
3785
|
+
void writer.custom(failedMarker).catch(() => {
|
|
3786
|
+
});
|
|
3787
|
+
}
|
|
3788
|
+
omError("[OM] Async buffered observation failed", error);
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
/**
|
|
3792
|
+
* Perform async buffered observation - observes messages and stores to bufferedObservations.
|
|
3793
|
+
* Does NOT update activeObservations or trigger reflection.
|
|
3794
|
+
*
|
|
3795
|
+
* The observer sees: active observations + existing buffered observations + message history
|
|
3796
|
+
* (excluding already-buffered messages).
|
|
3797
|
+
*/
|
|
3798
|
+
async doAsyncBufferedObservation(record, threadId, messagesToBuffer, cycleId, startedAt, writer) {
|
|
3799
|
+
const bufferedChunks = this.getBufferedChunks(record);
|
|
3800
|
+
const bufferedChunksText = bufferedChunks.map((c) => c.observations).join("\n\n");
|
|
3801
|
+
const combinedObservations = this.combineObservationsForBuffering(record.activeObservations, bufferedChunksText);
|
|
3802
|
+
const result = await this.callObserver(
|
|
3803
|
+
combinedObservations,
|
|
3804
|
+
messagesToBuffer,
|
|
3805
|
+
void 0,
|
|
3806
|
+
// No abort signal for background ops
|
|
3807
|
+
{ skipContinuationHints: true }
|
|
3808
|
+
);
|
|
3809
|
+
let newObservations;
|
|
3810
|
+
if (this.scope === "resource") {
|
|
3811
|
+
newObservations = await this.wrapWithThreadTag(threadId, result.observations);
|
|
3812
|
+
} else {
|
|
3813
|
+
newObservations = result.observations;
|
|
3814
|
+
}
|
|
3815
|
+
const newTokenCount = this.tokenCounter.countObservations(newObservations);
|
|
3816
|
+
const newMessageIds = messagesToBuffer.map((m) => m.id);
|
|
3817
|
+
const messageTokens = this.tokenCounter.countMessages(messagesToBuffer);
|
|
3818
|
+
const maxMessageTimestamp = this.getMaxMessageTimestamp(messagesToBuffer);
|
|
3819
|
+
const lastObservedAt = new Date(maxMessageTimestamp.getTime() + 1);
|
|
3820
|
+
await this.storage.updateBufferedObservations({
|
|
3821
|
+
id: record.id,
|
|
3822
|
+
chunk: {
|
|
3823
|
+
cycleId,
|
|
3824
|
+
observations: newObservations,
|
|
3825
|
+
tokenCount: newTokenCount,
|
|
3826
|
+
messageIds: newMessageIds,
|
|
3827
|
+
messageTokens,
|
|
3828
|
+
lastObservedAt
|
|
3829
|
+
},
|
|
3830
|
+
lastBufferedAtTime: lastObservedAt
|
|
3831
|
+
});
|
|
3832
|
+
if (writer) {
|
|
3833
|
+
const tokensBuffered = this.tokenCounter.countMessages(messagesToBuffer);
|
|
3834
|
+
const updatedRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
3835
|
+
const updatedChunks = this.getBufferedChunks(updatedRecord);
|
|
3836
|
+
const totalBufferedTokens = updatedChunks.reduce((sum, c) => sum + (c.tokenCount ?? 0), 0) || newTokenCount;
|
|
3837
|
+
const endMarker = this.createBufferingEndMarker({
|
|
3838
|
+
cycleId,
|
|
3839
|
+
operationType: "observation",
|
|
3840
|
+
startedAt,
|
|
3841
|
+
tokensBuffered,
|
|
3842
|
+
bufferedTokens: totalBufferedTokens,
|
|
3843
|
+
recordId: record.id,
|
|
3844
|
+
threadId,
|
|
3845
|
+
observations: newObservations
|
|
3846
|
+
});
|
|
3847
|
+
void writer.custom(endMarker).catch(() => {
|
|
3848
|
+
});
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
/**
|
|
3852
|
+
* Combine active and buffered observations for the buffering observer context.
|
|
3853
|
+
* The buffering observer needs to see both so it doesn't duplicate content.
|
|
3854
|
+
*/
|
|
3855
|
+
combineObservationsForBuffering(activeObservations, bufferedObservations) {
|
|
3856
|
+
if (!activeObservations && !bufferedObservations) {
|
|
3857
|
+
return void 0;
|
|
3858
|
+
}
|
|
3859
|
+
if (!activeObservations) {
|
|
3860
|
+
return bufferedObservations;
|
|
3861
|
+
}
|
|
3862
|
+
if (!bufferedObservations) {
|
|
3863
|
+
return activeObservations;
|
|
3864
|
+
}
|
|
3865
|
+
return `${activeObservations}
|
|
3866
|
+
|
|
3867
|
+
--- BUFFERED (pending activation) ---
|
|
3868
|
+
|
|
3869
|
+
${bufferedObservations}`;
|
|
3870
|
+
}
|
|
3871
|
+
/**
|
|
3872
|
+
* Try to activate buffered observations when threshold is reached.
|
|
3873
|
+
* Returns true if activation succeeded, false if no buffered content or activation failed.
|
|
3874
|
+
*
|
|
3875
|
+
* @param record - Current OM record
|
|
3876
|
+
* @param lockKey - Lock key for this scope
|
|
3877
|
+
* @param writer - Optional writer for emitting UI markers
|
|
3878
|
+
*/
|
|
3879
|
+
async tryActivateBufferedObservations(record, lockKey, currentPendingTokens, writer, messageList) {
|
|
3880
|
+
const chunks = this.getBufferedChunks(record);
|
|
3881
|
+
omDebug(`[OM:tryActivate] chunks=${chunks.length}, recordId=${record.id}`);
|
|
3882
|
+
if (!chunks.length) {
|
|
3883
|
+
omDebug(`[OM:tryActivate] no chunks, returning false`);
|
|
3884
|
+
return { success: false };
|
|
3885
|
+
}
|
|
3886
|
+
const bufferKey = this.getObservationBufferKey(lockKey);
|
|
3887
|
+
const asyncOp = _ObservationalMemory.asyncBufferingOps.get(bufferKey);
|
|
3888
|
+
if (asyncOp) {
|
|
3889
|
+
try {
|
|
3890
|
+
await Promise.race([
|
|
3891
|
+
asyncOp,
|
|
3892
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 6e4))
|
|
3893
|
+
]);
|
|
3894
|
+
} catch {
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
3898
|
+
if (!freshRecord) {
|
|
3899
|
+
return { success: false };
|
|
3900
|
+
}
|
|
3901
|
+
const freshChunks = this.getBufferedChunks(freshRecord);
|
|
3902
|
+
if (!freshChunks.length) {
|
|
3903
|
+
return { success: false };
|
|
3904
|
+
}
|
|
3905
|
+
const activationRatio = this.observationConfig.bufferActivation ?? 0.7;
|
|
3906
|
+
omDebug(
|
|
3907
|
+
`[OM:tryActivate] swapping: freshChunks=${freshChunks.length}, activationRatio=${activationRatio}, totalChunkTokens=${freshChunks.reduce((s, c) => s + (c.tokenCount ?? 0), 0)}`
|
|
3908
|
+
);
|
|
3909
|
+
const messageTokensThreshold = this.getMaxThreshold(this.observationConfig.messageTokens);
|
|
3910
|
+
const activationResult = await this.storage.swapBufferedToActive({
|
|
3911
|
+
id: freshRecord.id,
|
|
3912
|
+
activationRatio,
|
|
3913
|
+
messageTokensThreshold,
|
|
3914
|
+
currentPendingTokens
|
|
3915
|
+
});
|
|
3916
|
+
omDebug(
|
|
3917
|
+
`[OM:tryActivate] swapResult: chunksActivated=${activationResult.chunksActivated}, tokensActivated=${activationResult.messageTokensActivated}, obsTokensActivated=${activationResult.observationTokensActivated}, activatedCycleIds=${activationResult.activatedCycleIds.join(",")}`
|
|
3918
|
+
);
|
|
3919
|
+
await this.storage.setBufferingObservationFlag(freshRecord.id, false);
|
|
3920
|
+
unregisterOp(freshRecord.id, "bufferingObservation");
|
|
3921
|
+
const updatedRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
3922
|
+
if (writer && updatedRecord && activationResult.activatedCycleIds.length > 0) {
|
|
3923
|
+
const perChunkMap = new Map(activationResult.perChunk?.map((c) => [c.cycleId, c]));
|
|
3924
|
+
for (const cycleId of activationResult.activatedCycleIds) {
|
|
3925
|
+
const chunkData = perChunkMap.get(cycleId);
|
|
3926
|
+
const activationMarker = this.createActivationMarker({
|
|
3927
|
+
cycleId,
|
|
3928
|
+
// Use the original buffering cycleId so UI can link them
|
|
3929
|
+
operationType: "observation",
|
|
3930
|
+
chunksActivated: 1,
|
|
3931
|
+
tokensActivated: chunkData?.messageTokens ?? activationResult.messageTokensActivated,
|
|
3932
|
+
observationTokens: chunkData?.observationTokens ?? activationResult.observationTokensActivated,
|
|
3933
|
+
messagesActivated: chunkData?.messageCount ?? activationResult.messagesActivated,
|
|
3934
|
+
recordId: updatedRecord.id,
|
|
3935
|
+
threadId: updatedRecord.threadId ?? record.threadId ?? "",
|
|
3936
|
+
generationCount: updatedRecord.generationCount ?? 0,
|
|
3937
|
+
observations: chunkData?.observations ?? activationResult.observations
|
|
3938
|
+
});
|
|
3939
|
+
void writer.custom(activationMarker).catch(() => {
|
|
3940
|
+
});
|
|
3941
|
+
await this.persistMarkerToMessage(
|
|
3942
|
+
activationMarker,
|
|
3943
|
+
messageList,
|
|
3944
|
+
record.threadId ?? "",
|
|
3945
|
+
record.resourceId ?? void 0
|
|
3946
|
+
);
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
return {
|
|
3950
|
+
success: true,
|
|
3951
|
+
updatedRecord: updatedRecord ?? void 0,
|
|
3952
|
+
messageTokensActivated: activationResult.messageTokensActivated,
|
|
3953
|
+
activatedMessageIds: activationResult.activatedMessageIds
|
|
3954
|
+
};
|
|
3955
|
+
}
|
|
3956
|
+
/**
|
|
3957
|
+
* Start an async background reflection that stores results to bufferedReflection.
|
|
3958
|
+
* This is a fire-and-forget operation that runs in the background.
|
|
3959
|
+
* The results will be swapped to active when the main reflection threshold is reached.
|
|
3960
|
+
*
|
|
3961
|
+
* @param record - Current OM record
|
|
3962
|
+
* @param observationTokens - Current observation token count
|
|
3963
|
+
* @param lockKey - Lock key for this scope
|
|
3964
|
+
*/
|
|
3965
|
+
startAsyncBufferedReflection(record, observationTokens, lockKey, writer) {
|
|
3966
|
+
const bufferKey = this.getReflectionBufferKey(lockKey);
|
|
3967
|
+
if (this.isAsyncBufferingInProgress(bufferKey)) {
|
|
3968
|
+
return;
|
|
3969
|
+
}
|
|
3970
|
+
_ObservationalMemory.lastBufferedBoundary.set(bufferKey, observationTokens);
|
|
3971
|
+
registerOp(record.id, "bufferingReflection");
|
|
3972
|
+
this.storage.setBufferingReflectionFlag(record.id, true).catch((err) => {
|
|
3973
|
+
omError("[OM] Failed to set buffering reflection flag", err);
|
|
3974
|
+
});
|
|
3975
|
+
const asyncOp = this.doAsyncBufferedReflection(record, bufferKey, writer).catch((error) => {
|
|
3976
|
+
if (writer) {
|
|
3977
|
+
const failedMarker = this.createBufferingFailedMarker({
|
|
3978
|
+
cycleId: `reflect-buf-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
3979
|
+
operationType: "reflection",
|
|
3980
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3981
|
+
tokensAttempted: observationTokens,
|
|
3982
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3983
|
+
recordId: record.id,
|
|
3984
|
+
threadId: record.threadId ?? ""
|
|
3985
|
+
});
|
|
3986
|
+
void writer.custom(failedMarker).catch(() => {
|
|
3987
|
+
});
|
|
3988
|
+
}
|
|
3989
|
+
omError("[OM] Async buffered reflection failed", error);
|
|
3990
|
+
}).finally(() => {
|
|
3991
|
+
_ObservationalMemory.asyncBufferingOps.delete(bufferKey);
|
|
3992
|
+
unregisterOp(record.id, "bufferingReflection");
|
|
3993
|
+
this.storage.setBufferingReflectionFlag(record.id, false).catch((err) => {
|
|
3994
|
+
omError("[OM] Failed to clear buffering reflection flag", err);
|
|
3995
|
+
});
|
|
3996
|
+
});
|
|
3997
|
+
_ObservationalMemory.asyncBufferingOps.set(bufferKey, asyncOp);
|
|
3998
|
+
}
|
|
3999
|
+
/**
|
|
4000
|
+
* Perform async buffered reflection - reflects observations and stores to bufferedReflection.
|
|
4001
|
+
* Does NOT create a new generation or update activeObservations.
|
|
4002
|
+
*/
|
|
4003
|
+
async doAsyncBufferedReflection(record, _bufferKey, writer) {
|
|
4004
|
+
const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
4005
|
+
const currentRecord = freshRecord ?? record;
|
|
4006
|
+
const observationTokens = currentRecord.observationTokenCount ?? 0;
|
|
4007
|
+
const reflectThreshold = this.getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
4008
|
+
const bufferActivation = this.reflectionConfig.bufferActivation ?? 0.5;
|
|
4009
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4010
|
+
const cycleId = `reflect-buf-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
4011
|
+
_ObservationalMemory.reflectionBufferCycleIds.set(_bufferKey, cycleId);
|
|
4012
|
+
const fullObservations = currentRecord.activeObservations ?? "";
|
|
4013
|
+
const allLines = fullObservations.split("\n");
|
|
4014
|
+
const totalLines = allLines.length;
|
|
4015
|
+
const avgTokensPerLine = totalLines > 0 ? observationTokens / totalLines : 0;
|
|
4016
|
+
const activationPointTokens = reflectThreshold * bufferActivation;
|
|
4017
|
+
const linesToReflect = avgTokensPerLine > 0 ? Math.min(Math.floor(activationPointTokens / avgTokensPerLine), totalLines) : totalLines;
|
|
4018
|
+
const activeObservations = allLines.slice(0, linesToReflect).join("\n");
|
|
4019
|
+
const reflectedObservationLineCount = linesToReflect;
|
|
4020
|
+
const sliceTokenEstimate = Math.round(avgTokensPerLine * linesToReflect);
|
|
4021
|
+
const compressionTarget = Math.min(sliceTokenEstimate * bufferActivation, reflectThreshold);
|
|
4022
|
+
omDebug(
|
|
4023
|
+
`[OM:reflect] doAsyncBufferedReflection: slicing observations for reflection \u2014 totalLines=${totalLines}, avgTokPerLine=${avgTokensPerLine.toFixed(1)}, activationPointTokens=${activationPointTokens}, linesToReflect=${linesToReflect}/${totalLines}, sliceTokenEstimate=${sliceTokenEstimate}, compressionTarget=${compressionTarget}`
|
|
4024
|
+
);
|
|
4025
|
+
omDebug(
|
|
4026
|
+
`[OM:reflect] doAsyncBufferedReflection: starting reflector call, recordId=${currentRecord.id}, observationTokens=${sliceTokenEstimate}, compressionTarget=${compressionTarget} (inputTokens), activeObsLength=${activeObservations.length}, reflectedLineCount=${reflectedObservationLineCount}`
|
|
4027
|
+
);
|
|
4028
|
+
if (writer) {
|
|
4029
|
+
const startMarker = this.createBufferingStartMarker({
|
|
4030
|
+
cycleId,
|
|
4031
|
+
operationType: "reflection",
|
|
4032
|
+
tokensToBuffer: sliceTokenEstimate,
|
|
4033
|
+
recordId: record.id,
|
|
4034
|
+
threadId: record.threadId ?? "",
|
|
4035
|
+
threadIds: record.threadId ? [record.threadId] : []
|
|
4036
|
+
});
|
|
4037
|
+
void writer.custom(startMarker).catch(() => {
|
|
4038
|
+
});
|
|
4039
|
+
}
|
|
4040
|
+
const reflectResult = await this.callReflector(
|
|
4041
|
+
activeObservations,
|
|
4042
|
+
void 0,
|
|
4043
|
+
// No manual prompt
|
|
4044
|
+
void 0,
|
|
4045
|
+
// No stream context for background ops
|
|
4046
|
+
compressionTarget,
|
|
4047
|
+
void 0,
|
|
4048
|
+
// No abort signal for background ops
|
|
4049
|
+
true,
|
|
4050
|
+
// Skip continuation hints for async buffering
|
|
4051
|
+
1
|
|
4052
|
+
// Start at compression level 1 for buffered reflection
|
|
4053
|
+
);
|
|
4054
|
+
const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
|
|
4055
|
+
omDebug(
|
|
4056
|
+
`[OM:reflect] doAsyncBufferedReflection: reflector returned ${reflectionTokenCount} tokens (${reflectResult.observations?.length} chars), saving to recordId=${currentRecord.id}`
|
|
4057
|
+
);
|
|
4058
|
+
await this.storage.updateBufferedReflection({
|
|
4059
|
+
id: currentRecord.id,
|
|
4060
|
+
reflection: reflectResult.observations,
|
|
4061
|
+
tokenCount: reflectionTokenCount,
|
|
4062
|
+
inputTokenCount: sliceTokenEstimate,
|
|
4063
|
+
reflectedObservationLineCount
|
|
4064
|
+
});
|
|
4065
|
+
omDebug(
|
|
4066
|
+
`[OM:reflect] doAsyncBufferedReflection: bufferedReflection saved with lineCount=${reflectedObservationLineCount}`
|
|
4067
|
+
);
|
|
4068
|
+
if (writer) {
|
|
4069
|
+
const endMarker = this.createBufferingEndMarker({
|
|
4070
|
+
cycleId,
|
|
4071
|
+
operationType: "reflection",
|
|
4072
|
+
startedAt,
|
|
4073
|
+
tokensBuffered: observationTokens,
|
|
4074
|
+
bufferedTokens: reflectionTokenCount,
|
|
4075
|
+
recordId: currentRecord.id,
|
|
4076
|
+
threadId: currentRecord.threadId ?? "",
|
|
4077
|
+
observations: reflectResult.observations
|
|
4078
|
+
});
|
|
4079
|
+
void writer.custom(endMarker).catch(() => {
|
|
4080
|
+
});
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
/**
|
|
4084
|
+
* Try to activate buffered reflection when threshold is reached.
|
|
4085
|
+
* Returns true if activation succeeded, false if no buffered content or activation failed.
|
|
4086
|
+
*
|
|
4087
|
+
* @param record - Current OM record
|
|
4088
|
+
* @param lockKey - Lock key for this scope
|
|
4089
|
+
*/
|
|
4090
|
+
async tryActivateBufferedReflection(record, lockKey, writer, messageList) {
|
|
4091
|
+
const bufferKey = this.getReflectionBufferKey(lockKey);
|
|
4092
|
+
const asyncOp = _ObservationalMemory.asyncBufferingOps.get(bufferKey);
|
|
4093
|
+
if (asyncOp) {
|
|
4094
|
+
omDebug(`[OM:reflect] tryActivateBufferedReflection: waiting for in-progress op...`);
|
|
4095
|
+
try {
|
|
4096
|
+
await Promise.race([
|
|
4097
|
+
asyncOp,
|
|
4098
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 6e4))
|
|
4099
|
+
]);
|
|
4100
|
+
} catch {
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
4104
|
+
omDebug(
|
|
4105
|
+
`[OM:reflect] tryActivateBufferedReflection: recordId=${record.id}, hasBufferedReflection=${!!freshRecord?.bufferedReflection}, bufferedReflectionLen=${freshRecord?.bufferedReflection?.length ?? 0}`
|
|
4106
|
+
);
|
|
4107
|
+
omDebug(
|
|
4108
|
+
`[OM:reflect] tryActivateBufferedReflection: freshRecord.id=${freshRecord?.id}, freshBufferedReflection=${freshRecord?.bufferedReflection ? "present (" + freshRecord.bufferedReflection.length + " chars)" : "empty"}, freshObsTokens=${freshRecord?.observationTokenCount}`
|
|
4109
|
+
);
|
|
4110
|
+
if (!freshRecord?.bufferedReflection) {
|
|
4111
|
+
omDebug(`[OM:reflect] tryActivateBufferedReflection: no buffered reflection after re-fetch, returning false`);
|
|
4112
|
+
return false;
|
|
4113
|
+
}
|
|
4114
|
+
const beforeTokens = freshRecord.observationTokenCount ?? 0;
|
|
4115
|
+
const reflectedLineCount = freshRecord.reflectedObservationLineCount ?? 0;
|
|
4116
|
+
const currentObservations = freshRecord.activeObservations ?? "";
|
|
4117
|
+
const allLines = currentObservations.split("\n");
|
|
4118
|
+
const unreflectedLines = allLines.slice(reflectedLineCount);
|
|
4119
|
+
const unreflectedContent = unreflectedLines.join("\n").trim();
|
|
4120
|
+
const combinedObservations = unreflectedContent ? `${freshRecord.bufferedReflection}
|
|
4121
|
+
|
|
4122
|
+
${unreflectedContent}` : freshRecord.bufferedReflection;
|
|
4123
|
+
const combinedTokenCount = this.tokenCounter.countObservations(combinedObservations);
|
|
4124
|
+
omDebug(
|
|
4125
|
+
`[OM:reflect] tryActivateBufferedReflection: activating, beforeTokens=${beforeTokens}, combinedTokenCount=${combinedTokenCount}, reflectedLineCount=${reflectedLineCount}, unreflectedLines=${unreflectedLines.length}`
|
|
4126
|
+
);
|
|
4127
|
+
await this.storage.swapBufferedReflectionToActive({
|
|
4128
|
+
currentRecord: freshRecord,
|
|
4129
|
+
tokenCount: combinedTokenCount
|
|
4130
|
+
});
|
|
4131
|
+
_ObservationalMemory.lastBufferedBoundary.delete(bufferKey);
|
|
4132
|
+
const afterRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
4133
|
+
const afterTokens = afterRecord?.observationTokenCount ?? 0;
|
|
4134
|
+
omDebug(
|
|
4135
|
+
`[OM:reflect] tryActivateBufferedReflection: activation complete! beforeTokens=${beforeTokens}, afterTokens=${afterTokens}, newRecordId=${afterRecord?.id}, newGenCount=${afterRecord?.generationCount}`
|
|
4136
|
+
);
|
|
4137
|
+
if (writer) {
|
|
4138
|
+
const originalCycleId = _ObservationalMemory.reflectionBufferCycleIds.get(bufferKey);
|
|
4139
|
+
const activationMarker = this.createActivationMarker({
|
|
4140
|
+
cycleId: originalCycleId ?? `reflect-act-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
4141
|
+
operationType: "reflection",
|
|
4142
|
+
chunksActivated: 1,
|
|
4143
|
+
tokensActivated: beforeTokens,
|
|
4144
|
+
observationTokens: afterTokens,
|
|
4145
|
+
messagesActivated: 0,
|
|
4146
|
+
recordId: freshRecord.id,
|
|
4147
|
+
threadId: freshRecord.threadId ?? "",
|
|
4148
|
+
generationCount: afterRecord?.generationCount ?? freshRecord.generationCount ?? 0,
|
|
4149
|
+
observations: afterRecord?.activeObservations
|
|
4150
|
+
});
|
|
4151
|
+
void writer.custom(activationMarker).catch(() => {
|
|
4152
|
+
});
|
|
4153
|
+
await this.persistMarkerToMessage(
|
|
4154
|
+
activationMarker,
|
|
4155
|
+
messageList,
|
|
4156
|
+
freshRecord.threadId ?? "",
|
|
4157
|
+
freshRecord.resourceId ?? void 0
|
|
4158
|
+
);
|
|
4159
|
+
}
|
|
4160
|
+
_ObservationalMemory.reflectionBufferCycleIds.delete(bufferKey);
|
|
4161
|
+
return true;
|
|
4162
|
+
}
|
|
2663
4163
|
/**
|
|
2664
4164
|
* Resource-scoped observation: observe ALL threads with unobserved messages.
|
|
2665
4165
|
* Threads are observed in oldest-first order to ensure no thread's messages
|
|
@@ -2747,6 +4247,7 @@ ${result.observations}` : result.observations;
|
|
|
2747
4247
|
new Map(threadsToObserve.map((tid) => [tid, messagesByThread.get(tid) ?? []]))
|
|
2748
4248
|
);
|
|
2749
4249
|
await this.storage.setObservingFlag(record.id, true);
|
|
4250
|
+
registerOp(record.id, "observing");
|
|
2750
4251
|
const cycleId = crypto.randomUUID();
|
|
2751
4252
|
const threadsWithMessages = /* @__PURE__ */ new Map();
|
|
2752
4253
|
const threadTokensToObserve = /* @__PURE__ */ new Map();
|
|
@@ -2962,24 +4463,91 @@ ${result.observations}` : result.observations;
|
|
|
2962
4463
|
if (abortSignal?.aborted) {
|
|
2963
4464
|
throw error;
|
|
2964
4465
|
}
|
|
2965
|
-
|
|
4466
|
+
omError("[OM] Resource-scoped observation failed", error);
|
|
2966
4467
|
} finally {
|
|
2967
4468
|
await this.storage.setObservingFlag(record.id, false);
|
|
4469
|
+
unregisterOp(record.id, "observing");
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4472
|
+
/**
|
|
4473
|
+
* Check if async reflection should be triggered or activated.
|
|
4474
|
+
* Only handles the async path — will never do synchronous (blocking) reflection.
|
|
4475
|
+
* Safe to call after buffered observation activation.
|
|
4476
|
+
*/
|
|
4477
|
+
async maybeAsyncReflect(record, observationTokens, writer, messageList) {
|
|
4478
|
+
if (!this.isAsyncReflectionEnabled()) return;
|
|
4479
|
+
const lockKey = this.getLockKey(record.threadId, record.resourceId);
|
|
4480
|
+
const reflectThreshold = this.getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
4481
|
+
omDebug(
|
|
4482
|
+
`[OM:reflect] maybeAsyncReflect: observationTokens=${observationTokens}, reflectThreshold=${reflectThreshold}, isReflecting=${record.isReflecting}, bufferedReflection=${record.bufferedReflection ? "present (" + record.bufferedReflection.length + " chars)" : "empty"}, recordId=${record.id}, genCount=${record.generationCount}`
|
|
4483
|
+
);
|
|
4484
|
+
if (observationTokens < reflectThreshold) {
|
|
4485
|
+
const shouldTrigger = this.shouldTriggerAsyncReflection(observationTokens, lockKey, record);
|
|
4486
|
+
omDebug(`[OM:reflect] below threshold: shouldTrigger=${shouldTrigger}`);
|
|
4487
|
+
if (shouldTrigger) {
|
|
4488
|
+
this.startAsyncBufferedReflection(record, observationTokens, lockKey, writer);
|
|
4489
|
+
}
|
|
4490
|
+
return;
|
|
4491
|
+
}
|
|
4492
|
+
if (record.isReflecting) {
|
|
4493
|
+
if (isOpActiveInProcess(record.id, "reflecting")) {
|
|
4494
|
+
omDebug(`[OM:reflect] skipping - actively reflecting in this process`);
|
|
4495
|
+
return;
|
|
4496
|
+
}
|
|
4497
|
+
omDebug(`[OM:reflect] isReflecting=true but stale (not active in this process), clearing`);
|
|
4498
|
+
await this.storage.setReflectingFlag(record.id, false);
|
|
2968
4499
|
}
|
|
4500
|
+
omDebug(`[OM:reflect] at/above threshold, trying activation...`);
|
|
4501
|
+
const activationSuccess = await this.tryActivateBufferedReflection(record, lockKey, writer, messageList);
|
|
4502
|
+
omDebug(`[OM:reflect] activationSuccess=${activationSuccess}`);
|
|
4503
|
+
if (activationSuccess) return;
|
|
4504
|
+
omDebug(`[OM:reflect] no buffered reflection, starting background reflection...`);
|
|
4505
|
+
this.startAsyncBufferedReflection(record, observationTokens, lockKey, writer);
|
|
2969
4506
|
}
|
|
2970
4507
|
/**
|
|
2971
4508
|
* Check if reflection needed and trigger if so.
|
|
2972
|
-
*
|
|
4509
|
+
* Supports both synchronous reflection and async buffered reflection.
|
|
4510
|
+
* When async buffering is enabled via `bufferTokens`, reflection is triggered
|
|
4511
|
+
* in the background at intervals, and activated when the threshold is reached.
|
|
2973
4512
|
*/
|
|
2974
|
-
async maybeReflect(record, observationTokens, _threadId, writer, abortSignal) {
|
|
4513
|
+
async maybeReflect(record, observationTokens, _threadId, writer, abortSignal, messageList) {
|
|
4514
|
+
const lockKey = this.getLockKey(record.threadId, record.resourceId);
|
|
4515
|
+
const reflectThreshold = this.getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
4516
|
+
if (this.isAsyncReflectionEnabled() && observationTokens < reflectThreshold) {
|
|
4517
|
+
if (this.shouldTriggerAsyncReflection(observationTokens, lockKey, record)) {
|
|
4518
|
+
this.startAsyncBufferedReflection(record, observationTokens, lockKey, writer);
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
2975
4521
|
if (!this.shouldReflect(observationTokens)) {
|
|
2976
4522
|
return;
|
|
2977
4523
|
}
|
|
2978
4524
|
if (record.isReflecting) {
|
|
2979
|
-
|
|
4525
|
+
if (isOpActiveInProcess(record.id, "reflecting")) {
|
|
4526
|
+
omDebug(`[OM:reflect] isReflecting=true and active in this process, skipping`);
|
|
4527
|
+
return;
|
|
4528
|
+
}
|
|
4529
|
+
omDebug(`[OM:reflect] isReflecting=true but NOT active in this process \u2014 stale flag from dead process, clearing`);
|
|
4530
|
+
await this.storage.setReflectingFlag(record.id, false);
|
|
4531
|
+
}
|
|
4532
|
+
if (this.isAsyncReflectionEnabled()) {
|
|
4533
|
+
const activationSuccess = await this.tryActivateBufferedReflection(record, lockKey, writer, messageList);
|
|
4534
|
+
if (activationSuccess) {
|
|
4535
|
+
return;
|
|
4536
|
+
}
|
|
4537
|
+
if (this.reflectionConfig.blockAfter && observationTokens >= this.reflectionConfig.blockAfter) {
|
|
4538
|
+
omDebug(
|
|
4539
|
+
`[OM:reflect] blockAfter exceeded (${observationTokens} >= ${this.reflectionConfig.blockAfter}), falling through to sync reflection`
|
|
4540
|
+
);
|
|
4541
|
+
} else {
|
|
4542
|
+
omDebug(
|
|
4543
|
+
`[OM:reflect] async activation failed, no blockAfter or below it (obsTokens=${observationTokens}, blockAfter=${this.reflectionConfig.blockAfter}) \u2014 starting background reflection`
|
|
4544
|
+
);
|
|
4545
|
+
this.startAsyncBufferedReflection(record, observationTokens, lockKey, writer);
|
|
4546
|
+
return;
|
|
4547
|
+
}
|
|
2980
4548
|
}
|
|
2981
|
-
const reflectThreshold = this.getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
2982
4549
|
await this.storage.setReflectingFlag(record.id, true);
|
|
4550
|
+
registerOp(record.id, "reflecting");
|
|
2983
4551
|
const cycleId = crypto.randomUUID();
|
|
2984
4552
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2985
4553
|
const threadId = _threadId ?? "unknown";
|
|
@@ -3065,9 +4633,10 @@ ${result.observations}` : result.observations;
|
|
|
3065
4633
|
if (abortSignal?.aborted) {
|
|
3066
4634
|
throw error;
|
|
3067
4635
|
}
|
|
3068
|
-
|
|
4636
|
+
omError("[OM] Reflection failed", error);
|
|
3069
4637
|
} finally {
|
|
3070
4638
|
await this.storage.setReflectingFlag(record.id, false);
|
|
4639
|
+
unregisterOp(record.id, "reflecting");
|
|
3071
4640
|
}
|
|
3072
4641
|
}
|
|
3073
4642
|
/**
|
|
@@ -3115,6 +4684,7 @@ ${result.observations}` : result.observations;
|
|
|
3115
4684
|
return;
|
|
3116
4685
|
}
|
|
3117
4686
|
await this.storage.setReflectingFlag(record.id, true);
|
|
4687
|
+
registerOp(record.id, "reflecting");
|
|
3118
4688
|
try {
|
|
3119
4689
|
const reflectThreshold = this.getMaxThreshold(this.reflectionConfig.observationTokens);
|
|
3120
4690
|
const reflectResult = await this.callReflector(record.activeObservations, prompt, void 0, reflectThreshold);
|
|
@@ -3126,6 +4696,7 @@ ${result.observations}` : result.observations;
|
|
|
3126
4696
|
});
|
|
3127
4697
|
} finally {
|
|
3128
4698
|
await this.storage.setReflectingFlag(record.id, false);
|
|
4699
|
+
unregisterOp(record.id, "reflecting");
|
|
3129
4700
|
}
|
|
3130
4701
|
}
|
|
3131
4702
|
/**
|
|
@@ -3156,6 +4727,7 @@ ${result.observations}` : result.observations;
|
|
|
3156
4727
|
async clear(threadId, resourceId) {
|
|
3157
4728
|
const ids = this.getStorageIds(threadId, resourceId);
|
|
3158
4729
|
await this.storage.clearObservationalMemory(ids.threadId, ids.resourceId);
|
|
4730
|
+
this.cleanupStaticMaps(ids.threadId ?? ids.resourceId, ids.resourceId);
|
|
3159
4731
|
}
|
|
3160
4732
|
/**
|
|
3161
4733
|
* Get the underlying storage adapter
|
|
@@ -3184,5 +4756,5 @@ ${result.observations}` : result.observations;
|
|
|
3184
4756
|
};
|
|
3185
4757
|
|
|
3186
4758
|
export { OBSERVATIONAL_MEMORY_DEFAULTS, OBSERVER_SYSTEM_PROMPT, ObservationalMemory, TokenCounter, buildObserverPrompt, buildObserverSystemPrompt, extractCurrentTask, formatMessagesForObserver, hasCurrentTaskSection, optimizeObservationsForContext, parseObserverOutput };
|
|
3187
|
-
//# sourceMappingURL=chunk-
|
|
3188
|
-
//# sourceMappingURL=chunk-
|
|
4759
|
+
//# sourceMappingURL=chunk-TYVPTNCP.js.map
|
|
4760
|
+
//# sourceMappingURL=chunk-TYVPTNCP.js.map
|