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