@memberjunction/ai-agents 5.40.2 → 5.42.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/README.md +53 -0
- package/dist/AgentRunner.d.ts +5 -2
- package/dist/AgentRunner.d.ts.map +1 -1
- package/dist/AgentRunner.js +14 -4
- package/dist/AgentRunner.js.map +1 -1
- package/dist/MemoryWriteManager.d.ts +188 -0
- package/dist/MemoryWriteManager.d.ts.map +1 -0
- package/dist/MemoryWriteManager.js +299 -0
- package/dist/MemoryWriteManager.js.map +1 -0
- package/dist/agent-context-injector.d.ts +29 -0
- package/dist/agent-context-injector.d.ts.map +1 -1
- package/dist/agent-context-injector.js +90 -32
- package/dist/agent-context-injector.js.map +1 -1
- package/dist/agent-memory-context-builder.d.ts +100 -0
- package/dist/agent-memory-context-builder.d.ts.map +1 -0
- package/dist/agent-memory-context-builder.js +172 -0
- package/dist/agent-memory-context-builder.js.map +1 -0
- package/dist/agent-types/index.d.ts +1 -0
- package/dist/agent-types/index.d.ts.map +1 -1
- package/dist/agent-types/index.js +1 -0
- package/dist/agent-types/index.js.map +1 -1
- package/dist/agent-types/loop-agent-response-type.d.ts +12 -1
- package/dist/agent-types/loop-agent-response-type.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-response-type.js.map +1 -1
- package/dist/agent-types/loop-agent-type.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-type.js +4 -0
- package/dist/agent-types/loop-agent-type.js.map +1 -1
- package/dist/agent-types/realtime-agent-type.d.ts +146 -0
- package/dist/agent-types/realtime-agent-type.d.ts.map +1 -0
- package/dist/agent-types/realtime-agent-type.js +176 -0
- package/dist/agent-types/realtime-agent-type.js.map +1 -0
- package/dist/base-agent.d.ts +386 -39
- package/dist/base-agent.d.ts.map +1 -1
- package/dist/base-agent.js +1121 -261
- package/dist/base-agent.js.map +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -1
- package/dist/memory-manager-agent.d.ts +99 -4
- package/dist/memory-manager-agent.d.ts.map +1 -1
- package/dist/memory-manager-agent.js +349 -117
- package/dist/memory-manager-agent.js.map +1 -1
- package/dist/realtime/bridge-realtime-session-factory.d.ts +111 -0
- package/dist/realtime/bridge-realtime-session-factory.d.ts.map +1 -0
- package/dist/realtime/bridge-realtime-session-factory.js +163 -0
- package/dist/realtime/bridge-realtime-session-factory.js.map +1 -0
- package/dist/realtime/bridge-room-transcript-sink.d.ts +58 -0
- package/dist/realtime/bridge-room-transcript-sink.d.ts.map +1 -0
- package/dist/realtime/bridge-room-transcript-sink.js +127 -0
- package/dist/realtime/bridge-room-transcript-sink.js.map +1 -0
- package/dist/realtime/meeting-controls-channel-server.d.ts +198 -0
- package/dist/realtime/meeting-controls-channel-server.d.ts.map +1 -0
- package/dist/realtime/meeting-controls-channel-server.js +319 -0
- package/dist/realtime/meeting-controls-channel-server.js.map +1 -0
- package/dist/realtime/meeting-controls-state.d.ts +191 -0
- package/dist/realtime/meeting-controls-state.d.ts.map +1 -0
- package/dist/realtime/meeting-controls-state.js +219 -0
- package/dist/realtime/meeting-controls-state.js.map +1 -0
- package/dist/realtime/realtime-channel-server-host.d.ts +166 -0
- package/dist/realtime/realtime-channel-server-host.d.ts.map +1 -0
- package/dist/realtime/realtime-channel-server-host.js +378 -0
- package/dist/realtime/realtime-channel-server-host.js.map +1 -0
- package/dist/realtime/realtime-client-session-service.d.ts +1026 -0
- package/dist/realtime/realtime-client-session-service.d.ts.map +1 -0
- package/dist/realtime/realtime-client-session-service.js +1607 -0
- package/dist/realtime/realtime-client-session-service.js.map +1 -0
- package/dist/realtime/realtime-coagent-config.d.ts +258 -0
- package/dist/realtime/realtime-coagent-config.d.ts.map +1 -0
- package/dist/realtime/realtime-coagent-config.js +408 -0
- package/dist/realtime/realtime-coagent-config.js.map +1 -0
- package/dist/realtime/realtime-narration.d.ts +67 -0
- package/dist/realtime/realtime-narration.d.ts.map +1 -0
- package/dist/realtime/realtime-narration.js +127 -0
- package/dist/realtime/realtime-narration.js.map +1 -0
- package/dist/realtime/realtime-session-runner.d.ts +383 -0
- package/dist/realtime/realtime-session-runner.d.ts.map +1 -0
- package/dist/realtime/realtime-session-runner.js +532 -0
- package/dist/realtime/realtime-session-runner.js.map +1 -0
- package/dist/realtime/realtime-tool-broker.d.ts +294 -0
- package/dist/realtime/realtime-tool-broker.d.ts.map +1 -0
- package/dist/realtime/realtime-tool-broker.js +206 -0
- package/dist/realtime/realtime-tool-broker.js.map +1 -0
- package/dist/realtime/whiteboard-channel-server.d.ts +50 -0
- package/dist/realtime/whiteboard-channel-server.d.ts.map +1 -0
- package/dist/realtime/whiteboard-channel-server.js +85 -0
- package/dist/realtime/whiteboard-channel-server.js.map +1 -0
- package/package.json +17 -17
|
@@ -7,7 +7,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var MemoryManagerAgent_1;
|
|
8
8
|
import { BaseAgent } from './base-agent.js';
|
|
9
9
|
import { RegisterClass, CleanAndParseJSON } from '@memberjunction/global';
|
|
10
|
-
import { RunView, RunQuery, LogError, LogStatus } from '@memberjunction/core';
|
|
10
|
+
import { RunView, RunQuery, LogError, LogStatus, LogStatusEx } from '@memberjunction/core';
|
|
11
|
+
import { IsInjectableNoteStatus } from '@memberjunction/core-entities';
|
|
11
12
|
import { AIPromptRunner } from '@memberjunction/ai-prompts';
|
|
12
13
|
import { AIPromptParams } from '@memberjunction/ai-core-plus';
|
|
13
14
|
import { AIEngine } from '@memberjunction/aiengine';
|
|
@@ -27,6 +28,10 @@ const EXTRACTION_CONFIG = {
|
|
|
27
28
|
* Configuration for note consolidation.
|
|
28
29
|
* Consolidation finds clusters of similar notes and synthesizes them into single comprehensive notes.
|
|
29
30
|
*/
|
|
31
|
+
const HARDENING_CONFIG = {
|
|
32
|
+
maxNotesPerRun: 200, // Batch cap per MM run — remaining provisional notes picked up next cycle
|
|
33
|
+
dedupeSimilarity: 0.85, // Similarity threshold for the dedupe check against hardened notes
|
|
34
|
+
};
|
|
30
35
|
const CONSOLIDATION_CONFIG = {
|
|
31
36
|
/**
|
|
32
37
|
* How often to run consolidation:
|
|
@@ -117,6 +122,8 @@ const DETERMINISTIC_PROMPT_PARAMS = { temperature: 0 };
|
|
|
117
122
|
* passes the visited check (e.g., extremely deep legitimate chains from a bug).
|
|
118
123
|
*/
|
|
119
124
|
const MAX_SOURCE_RESOLUTION_DEPTH = 10;
|
|
125
|
+
/** Fields fetched for a SourceRunScope projection. */
|
|
126
|
+
const SOURCE_RUN_SCOPE_FIELDS = ['AgentID', 'CompanyID', 'UserID', 'PrimaryScopeEntityID', 'PrimaryScopeRecordID', 'SecondaryScopes'];
|
|
120
127
|
/**
|
|
121
128
|
* Memory Manager Agent - automatically extracts notes and examples from conversations.
|
|
122
129
|
* This agent runs on a schedule (every 15 minutes) and analyzes high-quality conversations
|
|
@@ -209,13 +216,19 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
209
216
|
* For first run, returns null to process all history (limited by MaxRows).
|
|
210
217
|
*/
|
|
211
218
|
async GetLastRunTime(agentId, contextUser) {
|
|
219
|
+
// Read-only: we only need StartedAt, so use a 'simple' projection with a narrow Fields
|
|
220
|
+
// list. Loading this as 'entity_object' would build a full MJAIAgentRunEntityExtended,
|
|
221
|
+
// whose InnerLoad fires a secondary "MJ: AI Agent Run Steps" RunView (LoadRelatedData) —
|
|
222
|
+
// a round-trip the redundancy telemetry flagged as a duplicate. A simple projection
|
|
223
|
+
// skips that sub-query entirely.
|
|
212
224
|
const rv = new RunView();
|
|
213
225
|
const result = await rv.RunView({
|
|
214
226
|
EntityName: 'MJ: AI Agent Runs',
|
|
215
227
|
ExtraFilter: `AgentID='${agentId}' AND Status='Completed'`,
|
|
216
228
|
OrderBy: 'StartedAt DESC',
|
|
217
229
|
MaxRows: 1,
|
|
218
|
-
|
|
230
|
+
Fields: ['StartedAt'],
|
|
231
|
+
ResultType: 'simple'
|
|
219
232
|
}, contextUser);
|
|
220
233
|
if (result.Success && result.Results && result.Results.length > 0) {
|
|
221
234
|
// Ensure we return a proper Date object
|
|
@@ -225,6 +238,25 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
225
238
|
// First run - return null to process all history (with MaxRows limit)
|
|
226
239
|
return null;
|
|
227
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Load a source agent run's scope fields (read-only) for scope inheritance, memoized in
|
|
243
|
+
* the supplied per-cycle cache. Uses a 'simple' projection so it does not trigger the
|
|
244
|
+
* agent-run entity's LoadRelatedData steps sub-query.
|
|
245
|
+
*/
|
|
246
|
+
async loadSourceRunScope(sourceAgentRunId, runCache, contextUser) {
|
|
247
|
+
if (!runCache.has(sourceAgentRunId)) {
|
|
248
|
+
const rv = new RunView();
|
|
249
|
+
const runResult = await rv.RunView({
|
|
250
|
+
EntityName: 'MJ: AI Agent Runs',
|
|
251
|
+
ExtraFilter: `ID='${sourceAgentRunId}'`,
|
|
252
|
+
MaxRows: 1,
|
|
253
|
+
Fields: [...SOURCE_RUN_SCOPE_FIELDS],
|
|
254
|
+
ResultType: 'simple'
|
|
255
|
+
}, contextUser);
|
|
256
|
+
runCache.set(sourceAgentRunId, runResult.Success && runResult.Results?.length > 0 ? runResult.Results[0] : null);
|
|
257
|
+
}
|
|
258
|
+
return runCache.get(sourceAgentRunId) || null;
|
|
259
|
+
}
|
|
228
260
|
/**
|
|
229
261
|
* Load agents that have note or example injection enabled.
|
|
230
262
|
* Only extract notes/examples for agents that actually use these features.
|
|
@@ -275,7 +307,9 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
275
307
|
MaxRows: 1000 // Limit to 1000 conversations per run
|
|
276
308
|
}, contextUser);
|
|
277
309
|
if (!result.Success || !result.Results || result.Results.length === 0) {
|
|
278
|
-
|
|
310
|
+
// Verbose-only: the common no-op outcome. The engine's "✅ Completed (Nms)" line already
|
|
311
|
+
// signals the run finished with no work; surfacing "why" is a verbose diagnostic detail.
|
|
312
|
+
LogStatusEx({ message: 'Memory Manager: No conversations with new activity found', verboseOnly: true });
|
|
279
313
|
return [];
|
|
280
314
|
}
|
|
281
315
|
// Parse the query results into ConversationWithRatings objects
|
|
@@ -319,6 +353,12 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
319
353
|
/**
|
|
320
354
|
* Load agent runs with high-usage artifacts since last run.
|
|
321
355
|
* Links through: ArtifactUse -> ArtifactVersion -> ConversationDetailArtifact -> ConversationDetail -> Conversation -> AIAgentRun
|
|
356
|
+
*
|
|
357
|
+
* The caller only consumes the row count (`.length`) of the returned set, so this reads a
|
|
358
|
+
* 'simple' ID-only projection rather than full entity objects. Loading these as
|
|
359
|
+
* 'entity_object' would build up to 50 MJAIAgentRunEntityExtended instances, each firing a
|
|
360
|
+
* secondary "MJ: AI Agent Run Steps" RunView via InnerLoad/LoadRelatedData — exactly the
|
|
361
|
+
* redundant round-trips the telemetry flagged. The simple projection skips all of them.
|
|
322
362
|
*/
|
|
323
363
|
async LoadHighValueAgentRuns(since, contextUser) {
|
|
324
364
|
const rv = new RunView();
|
|
@@ -350,7 +390,8 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
350
390
|
ExtraFilter: filter,
|
|
351
391
|
OrderBy: '__mj_CreatedAt DESC',
|
|
352
392
|
MaxRows: 50, // Limit to most recent 50 high-value runs
|
|
353
|
-
|
|
393
|
+
Fields: ['ID'],
|
|
394
|
+
ResultType: 'simple'
|
|
354
395
|
}, contextUser);
|
|
355
396
|
return result.Success ? (result.Results || []) : [];
|
|
356
397
|
}
|
|
@@ -848,8 +889,7 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
848
889
|
let merged = 0;
|
|
849
890
|
let failed = 0;
|
|
850
891
|
const md = this.ProviderToUse;
|
|
851
|
-
|
|
852
|
-
// Cache source agent runs to avoid repeated lookups
|
|
892
|
+
// Cache source agent runs (scope fields only) to avoid repeated lookups
|
|
853
893
|
const runCache = new Map();
|
|
854
894
|
// Get the "AI" note type ID for AI-generated notes
|
|
855
895
|
const aiNoteTypeId = AIEngine.Instance.AgenteNoteTypeIDByName('AI');
|
|
@@ -867,16 +907,7 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
867
907
|
// Load source agent run for scope inheritance (if available)
|
|
868
908
|
let sourceRun = null;
|
|
869
909
|
if (extracted.sourceAgentRunId) {
|
|
870
|
-
|
|
871
|
-
const runResult = await rv.RunView({
|
|
872
|
-
EntityName: 'MJ: AI Agent Runs',
|
|
873
|
-
ExtraFilter: `ID='${extracted.sourceAgentRunId}'`,
|
|
874
|
-
MaxRows: 1,
|
|
875
|
-
ResultType: 'entity_object'
|
|
876
|
-
}, contextUser);
|
|
877
|
-
runCache.set(extracted.sourceAgentRunId, runResult.Success && runResult.Results?.length > 0 ? runResult.Results[0] : null);
|
|
878
|
-
}
|
|
879
|
-
sourceRun = runCache.get(extracted.sourceAgentRunId) || null;
|
|
910
|
+
sourceRun = await this.loadSourceRunScope(extracted.sourceAgentRunId, runCache, contextUser);
|
|
880
911
|
}
|
|
881
912
|
// Check if we should merge with existing (revoke all targets, create one replacement)
|
|
882
913
|
let createNewNote = !extracted.mergeWithExistingIds?.length;
|
|
@@ -1202,7 +1233,7 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
1202
1233
|
* Returns both a boolean and the reason it fired so the observability layer can
|
|
1203
1234
|
* record the trigger type on the parent consolidation run step.
|
|
1204
1235
|
*/
|
|
1205
|
-
async shouldRunConsolidation(agentId, contextUser, forceMaintenance) {
|
|
1236
|
+
async shouldRunConsolidation(agentId, contextUser, forceMaintenance, lastRun) {
|
|
1206
1237
|
if (forceMaintenance)
|
|
1207
1238
|
return { shouldRun: true, triggerType: 'forced' };
|
|
1208
1239
|
const freq = CONSOLIDATION_CONFIG.frequency;
|
|
@@ -1211,7 +1242,7 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
1211
1242
|
if (freq === 'every-run')
|
|
1212
1243
|
return { shouldRun: true, triggerType: 'every-run' };
|
|
1213
1244
|
if (freq === 'hourly' || freq === 'daily') {
|
|
1214
|
-
return this.shouldRunConsolidationByTimeWindow(
|
|
1245
|
+
return this.shouldRunConsolidationByTimeWindow(contextUser, freq === 'hourly' ? 1 : 24, lastRun);
|
|
1215
1246
|
}
|
|
1216
1247
|
if (typeof freq === 'number') {
|
|
1217
1248
|
return this.shouldRunConsolidationByRunCount(agentId, contextUser, freq);
|
|
@@ -1224,8 +1255,11 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
1224
1255
|
* when the event-driven new-note threshold has been crossed. Falls back to
|
|
1225
1256
|
* triggerType='not-triggered' when neither condition fires.
|
|
1226
1257
|
*/
|
|
1227
|
-
async shouldRunConsolidationByTimeWindow(
|
|
1228
|
-
|
|
1258
|
+
async shouldRunConsolidationByTimeWindow(contextUser, thresholdHours, lastRun) {
|
|
1259
|
+
// `lastRun` is the same value computed once at the top of the maintenance cycle
|
|
1260
|
+
// (executeAgentInternal). Threading it through here removes a second identical
|
|
1261
|
+
// `MJ: AI Agent Runs` RunView (AgentID + Status='Completed', OrderBy StartedAt DESC)
|
|
1262
|
+
// that the redundancy telemetry flagged as a duplicate round-trip.
|
|
1229
1263
|
if (!lastRun)
|
|
1230
1264
|
return { shouldRun: true, triggerType: 'time' };
|
|
1231
1265
|
const hoursSinceLast = (Date.now() - lastRun.getTime()) / (1000 * 60 * 60);
|
|
@@ -1234,20 +1268,26 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
1234
1268
|
// Event-driven trigger: consolidate when at least `noteCountTrigger` new notes
|
|
1235
1269
|
// have been added system-wide since the last Memory Manager run.
|
|
1236
1270
|
//
|
|
1237
|
-
// NOTE: this
|
|
1271
|
+
// NOTE: this count intentionally does NOT filter by AgentID. The `agentId` parameter
|
|
1238
1272
|
// here is the Memory Manager's own ID (passed from `params.agent.ID` at the call
|
|
1239
1273
|
// site), not the agent that owns the notes. Filtering the count by MM's own agent
|
|
1240
1274
|
// ID would never match anything because MM doesn't own notes — it processes notes
|
|
1241
1275
|
// belonging to other (memory-enabled) agents.
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1276
|
+
//
|
|
1277
|
+
// AIEngineBase caches `MJ: AI Agent Notes` unfiltered as full entity objects
|
|
1278
|
+
// (`AIEngine.Instance.AgentNotes`) and keeps that cache current via BaseEntity
|
|
1279
|
+
// save/delete events, so we count in-memory instead of a `count_only` RunView. This
|
|
1280
|
+
// avoids the "Entity Already in Engine" redundancy telemetry warning and a DB
|
|
1281
|
+
// round-trip on every maintenance cycle. Mirrors the cached-note filtering used
|
|
1282
|
+
// elsewhere in this class (e.g. buildMaintenanceSummary).
|
|
1283
|
+
const lastRunMs = lastRun.getTime();
|
|
1284
|
+
const newNoteCount = AIEngine.Instance.AgentNotes.filter(n => n.IsAutoGenerated &&
|
|
1285
|
+
n.Status === 'Active' &&
|
|
1286
|
+
n.__mj_CreatedAt != null &&
|
|
1287
|
+
new Date(n.__mj_CreatedAt).getTime() > lastRunMs).length;
|
|
1288
|
+
if (newNoteCount >= CONSOLIDATION_CONFIG.noteCountTrigger) {
|
|
1249
1289
|
if (this._verbose) {
|
|
1250
|
-
LogStatus(`Memory Manager: Event-driven consolidation trigger fired (${
|
|
1290
|
+
LogStatus(`Memory Manager: Event-driven consolidation trigger fired (${newNoteCount} new notes >= ${CONSOLIDATION_CONFIG.noteCountTrigger} threshold since last MM run)`);
|
|
1251
1291
|
}
|
|
1252
1292
|
return { shouldRun: true, triggerType: 'event' };
|
|
1253
1293
|
}
|
|
@@ -1420,8 +1460,57 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
1420
1460
|
}
|
|
1421
1461
|
return originals;
|
|
1422
1462
|
}
|
|
1423
|
-
/**
|
|
1463
|
+
/**
|
|
1464
|
+
* Load a batch of notes by ID with the fields needed for source resolution.
|
|
1465
|
+
*
|
|
1466
|
+
* AIEngineBase caches `MJ: AI Agent Notes` unfiltered as full entity objects
|
|
1467
|
+
* (`AIEngine.Instance.AgentNotes`), so source-chain ancestors — which are pre-existing
|
|
1468
|
+
* notes, never ones created earlier in this same cycle — are already in memory. We serve
|
|
1469
|
+
* those from the cache and only issue a RunView for IDs the cache doesn't have, instead of
|
|
1470
|
+
* a DB round-trip per BFS depth level. This resolves the redundancy telemetry's
|
|
1471
|
+
* "Entity Already in Engine" warning for AIEngineBase while preserving exact behavior
|
|
1472
|
+
* (the projection shape and field set are unchanged).
|
|
1473
|
+
*/
|
|
1424
1474
|
async loadResolvedNotes(noteIds, contextUser) {
|
|
1475
|
+
const cachedById = new Map();
|
|
1476
|
+
for (const note of AIEngine.Instance.AgentNotes) {
|
|
1477
|
+
if (note.ID)
|
|
1478
|
+
cachedById.set(note.ID.toLowerCase(), note);
|
|
1479
|
+
}
|
|
1480
|
+
const resolved = [];
|
|
1481
|
+
const missingIds = [];
|
|
1482
|
+
for (const id of noteIds) {
|
|
1483
|
+
const cached = cachedById.get(id.toLowerCase());
|
|
1484
|
+
if (cached) {
|
|
1485
|
+
resolved.push(this.projectResolvedNote(cached));
|
|
1486
|
+
}
|
|
1487
|
+
else {
|
|
1488
|
+
missingIds.push(id);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
if (missingIds.length > 0) {
|
|
1492
|
+
resolved.push(...await this.loadResolvedNotesFromDatabase(missingIds, contextUser));
|
|
1493
|
+
}
|
|
1494
|
+
return resolved;
|
|
1495
|
+
}
|
|
1496
|
+
/** Project a cached note entity into the read-only shape used by source resolution. */
|
|
1497
|
+
projectResolvedNote(note) {
|
|
1498
|
+
return {
|
|
1499
|
+
ID: note.ID,
|
|
1500
|
+
ConsolidationCount: note.ConsolidationCount,
|
|
1501
|
+
DerivedFromNoteIDs: note.DerivedFromNoteIDs,
|
|
1502
|
+
Note: note.Note,
|
|
1503
|
+
Type: note.Type,
|
|
1504
|
+
AccessCount: note.AccessCount,
|
|
1505
|
+
ImportanceScore: note.ImportanceScore,
|
|
1506
|
+
AgentID: note.AgentID,
|
|
1507
|
+
UserID: note.UserID,
|
|
1508
|
+
CompanyID: note.CompanyID,
|
|
1509
|
+
__mj_CreatedAt: note.__mj_CreatedAt
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
/** RunView fallback for note IDs not present in the AIEngine cache. */
|
|
1513
|
+
async loadResolvedNotesFromDatabase(noteIds, contextUser) {
|
|
1425
1514
|
const resolvedFields = [
|
|
1426
1515
|
'ID', 'ConsolidationCount', 'DerivedFromNoteIDs', 'Note', 'Type',
|
|
1427
1516
|
'AccessCount', 'ImportanceScore', 'AgentID', 'UserID', 'CompanyID', '__mj_CreatedAt'
|
|
@@ -1907,7 +1996,7 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
1907
1996
|
async pruneStaleReferences(contextUser) {
|
|
1908
1997
|
const maxPerCycle = STALE_PRUNING_CONFIG.maxNotesPerRun;
|
|
1909
1998
|
const result = { notesArchived: 0, orphanedAgents: 0, orphanedUsers: 0, orphanedCompanies: 0, orphanedConversations: 0 };
|
|
1910
|
-
const notes =
|
|
1999
|
+
const notes = this.loadPruneCandidateNotes(maxPerCycle * 2);
|
|
1911
2000
|
if (notes.length === 0) {
|
|
1912
2001
|
return result;
|
|
1913
2002
|
}
|
|
@@ -1924,22 +2013,33 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
1924
2013
|
return result;
|
|
1925
2014
|
}
|
|
1926
2015
|
/**
|
|
1927
|
-
*
|
|
2016
|
+
* Collect auto-generated, non-immutable `Active` notes that are candidates for orphan
|
|
2017
|
+
* pruning, oldest first.
|
|
2018
|
+
*
|
|
2019
|
+
* Served from the `AIEngine` cache rather than a `RunView`: `AIEngine.Instance.AgentNotes`
|
|
2020
|
+
* holds the full note pool, loaded unfiltered as entity objects and kept current via
|
|
2021
|
+
* `BaseEntity` save/delete events, so an equivalent in-memory filter/sort/cap avoids both a
|
|
2022
|
+
* DB round-trip and the "Entity Already in Engine" redundancy-telemetry warning each cycle.
|
|
2023
|
+
* Note `.filter()` returns a fresh array, so the subsequent `.sort()` never reorders the
|
|
2024
|
+
* engine's cached array. The result is projected to plain {@link PruneCandidateNote} rows —
|
|
2025
|
+
* we never hand a cached `BaseEntity` instance to a mutating caller; `pruneStaleReferences`
|
|
2026
|
+
* re-`Load()`s a fresh, owned entity before archiving.
|
|
2027
|
+
*
|
|
2028
|
+
* @param maxRows Hard cap on candidates returned this cycle (oldest-`__mj_CreatedAt` first).
|
|
2029
|
+
* @returns Lightweight projection rows for orphan-reference evaluation.
|
|
1928
2030
|
*/
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
}
|
|
1942
|
-
return notesResult.Results;
|
|
2031
|
+
loadPruneCandidateNotes(maxRows) {
|
|
2032
|
+
return AIEngine.Instance.AgentNotes
|
|
2033
|
+
.filter(n => n.IsAutoGenerated && n.Status === 'Active' && (n.ProtectionTier || 'Standard') !== 'Immutable')
|
|
2034
|
+
.sort((a, b) => new Date(a.__mj_CreatedAt).getTime() - new Date(b.__mj_CreatedAt).getTime())
|
|
2035
|
+
.slice(0, maxRows)
|
|
2036
|
+
.map(n => ({
|
|
2037
|
+
ID: n.ID,
|
|
2038
|
+
AgentID: n.AgentID,
|
|
2039
|
+
UserID: n.UserID,
|
|
2040
|
+
CompanyID: n.CompanyID,
|
|
2041
|
+
SourceConversationID: n.SourceConversationID
|
|
2042
|
+
}));
|
|
1943
2043
|
}
|
|
1944
2044
|
/**
|
|
1945
2045
|
* Build in-memory lookup caches of which referenced users/companies/conversations still
|
|
@@ -2118,7 +2218,7 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2118
2218
|
const result = { notesExpired: 0, examplesExpired: 0 };
|
|
2119
2219
|
const md = this.ProviderToUse;
|
|
2120
2220
|
const nowISO = new Date().toISOString();
|
|
2121
|
-
const [expiredNotes, expiredExamples] =
|
|
2221
|
+
const [expiredNotes, expiredExamples] = this.loadExpiredItems(nowISO);
|
|
2122
2222
|
for (const note of expiredNotes) {
|
|
2123
2223
|
if ((note.ProtectionTier || 'Standard') === 'Immutable')
|
|
2124
2224
|
continue;
|
|
@@ -2131,26 +2231,36 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2131
2231
|
}
|
|
2132
2232
|
return result;
|
|
2133
2233
|
}
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2234
|
+
/**
|
|
2235
|
+
* Find notes and examples whose explicit `ExpiresAt` timestamp has passed, so the
|
|
2236
|
+
* archival pass can expire them regardless of decay score.
|
|
2237
|
+
*
|
|
2238
|
+
* Served from the `AIEngine` caches (`AgentNotes` / `AgentExamples`) instead of a batched
|
|
2239
|
+
* `RunViews`: both pools are loaded unfiltered as entity objects and kept current via
|
|
2240
|
+
* `BaseEntity` events, so an in-memory filter/cap reproduces the former queries while
|
|
2241
|
+
* removing two DB round-trips and the pair of "Entity Already in Engine" redundancy
|
|
2242
|
+
* warnings they emitted. Rows are projected to plain shapes; `archiveExpiredNote` /
|
|
2243
|
+
* `archiveExpiredExample` re-`Load()` a fresh, owned entity before mutating.
|
|
2244
|
+
*
|
|
2245
|
+
* Notes include `Provisional` (via {@link IsInjectableNoteStatus} — the same Active+Provisional
|
|
2246
|
+
* predicate used by the read-path injection queries) so the TTL safety net on in-flight
|
|
2247
|
+
* agent writes is enforced: a provisional note the hardening pass never reaches must still
|
|
2248
|
+
* expire.
|
|
2249
|
+
*
|
|
2250
|
+
* @param nowISO Current time as an ISO string; items with `ExpiresAt` strictly before this are expired.
|
|
2251
|
+
* @returns A `[expiredNotes, expiredExamples]` tuple of projection rows (each capped at 200).
|
|
2252
|
+
*/
|
|
2253
|
+
loadExpiredItems(nowISO) {
|
|
2254
|
+
const nowMs = new Date(nowISO).getTime();
|
|
2255
|
+
const expiredNotes = AIEngine.Instance.AgentNotes
|
|
2256
|
+
.filter(n => IsInjectableNoteStatus(n.Status) && n.ExpiresAt != null && new Date(n.ExpiresAt).getTime() < nowMs)
|
|
2257
|
+
.slice(0, 200)
|
|
2258
|
+
.map(n => ({ ID: n.ID, ExpiresAt: n.ExpiresAt, ProtectionTier: n.ProtectionTier || 'Standard' }));
|
|
2259
|
+
const expiredExamples = AIEngine.Instance.AgentExamples
|
|
2260
|
+
.filter(e => e.Status === 'Active' && e.ExpiresAt != null && new Date(e.ExpiresAt).getTime() < nowMs)
|
|
2261
|
+
.slice(0, 200)
|
|
2262
|
+
.map(e => ({ ID: e.ID, ExpiresAt: e.ExpiresAt }));
|
|
2263
|
+
return [expiredNotes, expiredExamples];
|
|
2154
2264
|
}
|
|
2155
2265
|
async archiveExpiredNote(note, md, contextUser) {
|
|
2156
2266
|
const noteToArchive = await md.GetEntityObject('MJ: AI Agent Notes', contextUser);
|
|
@@ -2181,7 +2291,7 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2181
2291
|
const result = { notesDecayed: 0, notesArchived: 0, examplesDecayed: 0, examplesArchived: 0 };
|
|
2182
2292
|
const stats = { decayFactors: [], protectedPreserved: 0, ephemeralAccelerated: 0 };
|
|
2183
2293
|
const md = this.ProviderToUse;
|
|
2184
|
-
const [activeNotes, activeExamples] =
|
|
2294
|
+
const [activeNotes, activeExamples] = this.loadDecayCandidates();
|
|
2185
2295
|
for (const note of activeNotes) {
|
|
2186
2296
|
await this.decayOneNote(note, archiveEnabledAgentIds, md, contextUser, result, stats);
|
|
2187
2297
|
}
|
|
@@ -2191,28 +2301,43 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2191
2301
|
return { ...result, stats };
|
|
2192
2302
|
}
|
|
2193
2303
|
/**
|
|
2194
|
-
* Fetch notes and examples
|
|
2304
|
+
* Fetch decay-candidate notes and examples from the AIEngine caches.
|
|
2305
|
+
*
|
|
2306
|
+
* Served from `AIEngine.Instance.AgentNotes` / `AgentExamples` (loaded unfiltered as
|
|
2307
|
+
* entity objects and kept current via BaseEntity events), filtered/capped in-memory to
|
|
2308
|
+
* mirror the former RunViews — avoids two "Entity Already in Engine" redundancy warnings
|
|
2309
|
+
* and a DB round-trip per maintenance cycle. We project to plain candidate rows; the
|
|
2310
|
+
* actual decay/archival re-Loads fresh entities.
|
|
2311
|
+
*
|
|
2312
|
+
* @returns A `[activeNotes, activeExamples]` tuple of decay-candidate projection rows
|
|
2313
|
+
* (auto-generated `Active` notes capped at 1000; auto-generated `Active` examples
|
|
2314
|
+
* scoring below 50, capped at 500).
|
|
2195
2315
|
*/
|
|
2196
|
-
|
|
2197
|
-
const
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2316
|
+
loadDecayCandidates() {
|
|
2317
|
+
const activeNotes = AIEngine.Instance.AgentNotes
|
|
2318
|
+
.filter(n => n.IsAutoGenerated && n.Status === 'Active')
|
|
2319
|
+
.slice(0, 1000)
|
|
2320
|
+
.map(n => ({
|
|
2321
|
+
ID: n.ID,
|
|
2322
|
+
ImportanceScore: n.ImportanceScore,
|
|
2323
|
+
ProtectionTier: n.ProtectionTier || 'Standard',
|
|
2324
|
+
LastAccessedAt: n.LastAccessedAt,
|
|
2325
|
+
__mj_CreatedAt: n.__mj_CreatedAt,
|
|
2326
|
+
AccessCount: n.AccessCount,
|
|
2327
|
+
AgentID: n.AgentID
|
|
2328
|
+
}));
|
|
2329
|
+
const activeExamples = AIEngine.Instance.AgentExamples
|
|
2330
|
+
.filter(e => e.IsAutoGenerated && e.Status === 'Active' && (e.SuccessScore ?? 0) < 50)
|
|
2331
|
+
.slice(0, 500)
|
|
2332
|
+
.map(e => ({
|
|
2333
|
+
ID: e.ID,
|
|
2334
|
+
SuccessScore: e.SuccessScore,
|
|
2335
|
+
LastAccessedAt: e.LastAccessedAt,
|
|
2336
|
+
__mj_CreatedAt: e.__mj_CreatedAt,
|
|
2337
|
+
AccessCount: e.AccessCount,
|
|
2338
|
+
AgentID: e.AgentID
|
|
2339
|
+
}));
|
|
2340
|
+
return [activeNotes, activeExamples];
|
|
2216
2341
|
}
|
|
2217
2342
|
/**
|
|
2218
2343
|
* Apply proportional decay to a single note. Archives if the decayed score drops below
|
|
@@ -2282,6 +2407,110 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2282
2407
|
if (await exampleToArchive.Save())
|
|
2283
2408
|
result.examplesArchived++;
|
|
2284
2409
|
}
|
|
2410
|
+
/**
|
|
2411
|
+
* Hardening pass over in-flight agent-written notes (Status='Provisional',
|
|
2412
|
+
* AuthorType='Agent'). Runs UNCONDITIONALLY at the start of every MM cycle —
|
|
2413
|
+
* before the consolidation-gated maintenance phases — so that:
|
|
2414
|
+
* 1. provisional notes never languish waiting for a consolidation trigger, and
|
|
2415
|
+
* 2. notes hardened here participate in importance scoring, consolidation,
|
|
2416
|
+
* contradiction detection, and decay in the SAME cycle via the existing
|
|
2417
|
+
* machinery (no duplicated reconciliation logic in this pass).
|
|
2418
|
+
*
|
|
2419
|
+
* Per note: dedupe against hardened (Active) notes using the same LLM dedupe
|
|
2420
|
+
* prompt as extraction — duplicates are Archived with ConsolidatedIntoNoteID
|
|
2421
|
+
* lineage; survivors are hardened (Status='Active', ExpiresAt=NULL so the
|
|
2422
|
+
* standard decay machinery owns their lifetime).
|
|
2423
|
+
*/
|
|
2424
|
+
async runHardeningPhase(contextUser) {
|
|
2425
|
+
const counters = { hardened: 0, deduped: 0, failed: 0 };
|
|
2426
|
+
const step = await this.CreateRunStep('Decision', 'Harden Provisional Notes', {
|
|
2427
|
+
maxNotesPerRun: HARDENING_CONFIG.maxNotesPerRun
|
|
2428
|
+
});
|
|
2429
|
+
try {
|
|
2430
|
+
// Served from AIEngine.Instance.AgentNotes (loaded unfiltered as entity
|
|
2431
|
+
// objects and kept current via BaseEntity + remote-invalidate events),
|
|
2432
|
+
// filtered/ordered/capped in-memory to mirror the former RunView. This
|
|
2433
|
+
// avoids the "Entity Already in Engine" redundancy warning and a DB
|
|
2434
|
+
// round-trip every MM cycle, matching loadDecayCandidates/loadExpiredItems.
|
|
2435
|
+
// Provisional agent notes are in-flight writes already reflected in the
|
|
2436
|
+
// cache, and hardenSingleNote mutates+saves these entities directly —
|
|
2437
|
+
// consistent with the dedupe path, which already saves cached note
|
|
2438
|
+
// instances returned by FindSimilarAgentNotes.
|
|
2439
|
+
const provisionalNotes = AIEngine.Instance.AgentNotes
|
|
2440
|
+
.filter(n => n.Status === 'Provisional' && n.AuthorType === 'Agent')
|
|
2441
|
+
.sort((a, b) => new Date(a.__mj_CreatedAt).getTime() - new Date(b.__mj_CreatedAt).getTime())
|
|
2442
|
+
.slice(0, HARDENING_CONFIG.maxNotesPerRun);
|
|
2443
|
+
if (provisionalNotes.length === 0) {
|
|
2444
|
+
await this.FinalizeRunStep(step, true, { ...counters, provisionalCount: 0 });
|
|
2445
|
+
return counters;
|
|
2446
|
+
}
|
|
2447
|
+
const runner = new AIPromptRunner();
|
|
2448
|
+
const dedupePrompt = AIEngine.Instance.Prompts.find(p => p.Name === 'Memory Manager - Deduplicate Note' && p.Category === 'MJ: System');
|
|
2449
|
+
for (const note of provisionalNotes) {
|
|
2450
|
+
const outcome = await this.hardenSingleNote(note, dedupePrompt, runner, contextUser);
|
|
2451
|
+
counters[outcome]++;
|
|
2452
|
+
}
|
|
2453
|
+
if (this._verbose) {
|
|
2454
|
+
LogStatus(`Memory Manager: Hardening pass — ${counters.hardened} hardened, ${counters.deduped} deduped, ${counters.failed} failed of ${provisionalNotes.length} provisional notes`);
|
|
2455
|
+
}
|
|
2456
|
+
await this.FinalizeRunStep(step, counters.failed === 0, { ...counters, provisionalCount: provisionalNotes.length });
|
|
2457
|
+
}
|
|
2458
|
+
catch (error) {
|
|
2459
|
+
LogError('Memory Manager: Hardening pass failed, continuing with run:', error);
|
|
2460
|
+
await this.FinalizeRunStep(step, false, counters, undefined, error instanceof Error ? error.message : String(error));
|
|
2461
|
+
}
|
|
2462
|
+
return counters;
|
|
2463
|
+
}
|
|
2464
|
+
/**
|
|
2465
|
+
* Harden one provisional note: archive it as a duplicate when the LLM dedupe
|
|
2466
|
+
* decision says an equivalent hardened note exists, otherwise promote it to
|
|
2467
|
+
* Active with decay-managed lifetime.
|
|
2468
|
+
*/
|
|
2469
|
+
async hardenSingleNote(note, dedupePrompt, runner, contextUser) {
|
|
2470
|
+
try {
|
|
2471
|
+
// Compare against HARDENED notes only — other provisional notes are themselves
|
|
2472
|
+
// unvetted, and same-run duplicates were already handled at write time.
|
|
2473
|
+
const similar = (await AIEngine.Instance.FindSimilarAgentNotes(note.Note || '', note.AgentID || undefined, note.UserID || undefined, note.CompanyID || undefined, 10, HARDENING_CONFIG.dedupeSimilarity)).filter(m => m.note.ID !== note.ID && m.note.Status === 'Active');
|
|
2474
|
+
if (similar.length > 0 && dedupePrompt) {
|
|
2475
|
+
const candidate = {
|
|
2476
|
+
type: note.Type,
|
|
2477
|
+
content: note.Note || '',
|
|
2478
|
+
confidence: 100,
|
|
2479
|
+
agentId: note.AgentID || undefined,
|
|
2480
|
+
userId: note.UserID || undefined,
|
|
2481
|
+
companyId: note.CompanyID || undefined,
|
|
2482
|
+
};
|
|
2483
|
+
const decision = await this.runDedupePromptForCandidate(runner, dedupePrompt, candidate, similar, contextUser);
|
|
2484
|
+
if (!decision.shouldAdd) {
|
|
2485
|
+
const target = similar[0].note;
|
|
2486
|
+
note.Status = 'Archived';
|
|
2487
|
+
note.ConsolidatedIntoNoteID = target.ID;
|
|
2488
|
+
note.Comments = `${note.Comments || ''} [Hardening: duplicate of ${target.ID} — ${decision.reason}]`.trim();
|
|
2489
|
+
if (!await note.Save()) {
|
|
2490
|
+
LogError(`Memory Manager: Failed to archive duplicate provisional note ${note.ID}: ${note.LatestResult?.CompleteMessage || 'unknown error'}`);
|
|
2491
|
+
return 'failed';
|
|
2492
|
+
}
|
|
2493
|
+
target.AccessCount = (target.AccessCount || 0) + 1;
|
|
2494
|
+
target.LastAccessedAt = new Date();
|
|
2495
|
+
if (!await target.Save()) {
|
|
2496
|
+
LogError(`Memory Manager: Failed to bump dedupe target ${target.ID}: ${target.LatestResult?.CompleteMessage || 'unknown error'}`);
|
|
2497
|
+
}
|
|
2498
|
+
return 'deduped';
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
note.Status = 'Active';
|
|
2502
|
+
note.ExpiresAt = null;
|
|
2503
|
+
if (!await note.Save()) {
|
|
2504
|
+
LogError(`Memory Manager: Failed to harden provisional note ${note.ID}: ${note.LatestResult?.CompleteMessage || 'unknown error'}`);
|
|
2505
|
+
return 'failed';
|
|
2506
|
+
}
|
|
2507
|
+
return 'hardened';
|
|
2508
|
+
}
|
|
2509
|
+
catch (error) {
|
|
2510
|
+
LogError(`Memory Manager: Exception hardening note ${note.ID}:`, error);
|
|
2511
|
+
return 'failed';
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2285
2514
|
/**
|
|
2286
2515
|
* Run all 5 maintenance phases in order: importance scoring, consolidation,
|
|
2287
2516
|
* contradiction detection, stale-reference pruning, and decay-based archival.
|
|
@@ -2483,24 +2712,14 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2483
2712
|
let skipped = 0;
|
|
2484
2713
|
let failed = 0;
|
|
2485
2714
|
const md = this.ProviderToUse;
|
|
2486
|
-
|
|
2487
|
-
// Cache source agent runs to avoid repeated lookups
|
|
2715
|
+
// Cache source agent runs (scope fields only) to avoid repeated lookups
|
|
2488
2716
|
const runCache = new Map();
|
|
2489
2717
|
for (const extracted of extractedExamples) {
|
|
2490
2718
|
try {
|
|
2491
2719
|
// Load source agent run for scope inheritance (if available)
|
|
2492
2720
|
let sourceRun = null;
|
|
2493
2721
|
if (extracted.sourceAgentRunId) {
|
|
2494
|
-
|
|
2495
|
-
const runResult = await rv.RunView({
|
|
2496
|
-
EntityName: 'MJ: AI Agent Runs',
|
|
2497
|
-
ExtraFilter: `ID='${extracted.sourceAgentRunId}'`,
|
|
2498
|
-
MaxRows: 1,
|
|
2499
|
-
ResultType: 'entity_object'
|
|
2500
|
-
}, contextUser);
|
|
2501
|
-
runCache.set(extracted.sourceAgentRunId, runResult.Success && runResult.Results?.length > 0 ? runResult.Results[0] : null);
|
|
2502
|
-
}
|
|
2503
|
-
sourceRun = runCache.get(extracted.sourceAgentRunId) || null;
|
|
2722
|
+
sourceRun = await this.loadSourceRunScope(extracted.sourceAgentRunId, runCache, contextUser);
|
|
2504
2723
|
}
|
|
2505
2724
|
const example = await md.GetEntityObject('MJ: AI Agent Examples', contextUser);
|
|
2506
2725
|
// AgentID must come from source run - LLM doesn't know real agent IDs
|
|
@@ -2578,10 +2797,15 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2578
2797
|
this._agentRunID = this.AgentRun?.ID || null;
|
|
2579
2798
|
this._stepCounter = 0;
|
|
2580
2799
|
this._contextUser = params.contextUser || null;
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2800
|
+
// Verbose-only: the scheduling engine's always-on "▶️ Starting / ✅ Completed" lines already
|
|
2801
|
+
// mark that this run began/ended. On routine no-op runs this internal trace is just noise.
|
|
2802
|
+
LogStatusEx({ message: 'Memory Manager: Starting analysis cycle', verboseOnly: true });
|
|
2803
|
+
// Phase 1: the last-run-time and the memory-enabled agent set are independent — load in
|
|
2804
|
+
// parallel (avoids two back-to-back sequential RunViews flagged by the parallelization telemetry).
|
|
2805
|
+
const [lastRunTime, agentsUsingMemory] = await Promise.all([
|
|
2806
|
+
this.GetLastRunTime(params.agent.ID, params.contextUser),
|
|
2807
|
+
this.LoadAgentsUsingMemory(params.contextUser)
|
|
2808
|
+
]);
|
|
2585
2809
|
if (this._verbose) {
|
|
2586
2810
|
const sinceMessage = lastRunTime ? `since ${lastRunTime.toISOString()}` : 'all history';
|
|
2587
2811
|
LogStatus(`Memory Manager: Processing ${sinceMessage}`);
|
|
@@ -2595,13 +2819,21 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2595
2819
|
};
|
|
2596
2820
|
return { finalStep, stepCount: 1 };
|
|
2597
2821
|
}
|
|
2598
|
-
//
|
|
2822
|
+
// Phase 2: conversations-with-new-activity and high-value agent runs both depend only on
|
|
2823
|
+
// (lastRunTime, agentsUsingMemory) — never on each other — so load them in parallel, each
|
|
2824
|
+
// recorded in its own run step.
|
|
2599
2825
|
const step1 = await this.CreateRunStep('Decision', 'Load Conversations With New Activity', {
|
|
2600
2826
|
since: lastRunTime?.toISOString() || null,
|
|
2601
2827
|
agentCount: agentsUsingMemory.length,
|
|
2602
2828
|
agentIds: agentsUsingMemory.map(a => a.ID)
|
|
2603
2829
|
});
|
|
2604
|
-
const
|
|
2830
|
+
const step2 = await this.CreateRunStep('Decision', 'Load High-Value Agent Runs', {
|
|
2831
|
+
since: lastRunTime?.toISOString() || null
|
|
2832
|
+
});
|
|
2833
|
+
const [conversations, agentRuns] = await Promise.all([
|
|
2834
|
+
this.LoadConversationsWithNewActivity(lastRunTime, agentsUsingMemory, params.contextUser),
|
|
2835
|
+
this.LoadHighValueAgentRuns(lastRunTime, params.contextUser)
|
|
2836
|
+
]);
|
|
2605
2837
|
const totalMessages = conversations.reduce((sum, c) => sum + c.messages.length, 0);
|
|
2606
2838
|
await this.FinalizeRunStep(step1, true, {
|
|
2607
2839
|
conversationCount: conversations.length,
|
|
@@ -2610,18 +2842,11 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2610
2842
|
negativeCount: conversations.filter(c => c.hasNegativeRating).length,
|
|
2611
2843
|
unratedCount: conversations.filter(c => c.isUnrated).length
|
|
2612
2844
|
});
|
|
2613
|
-
if (this._verbose) {
|
|
2614
|
-
LogStatus(`Memory Manager: Found ${conversations.length} conversations with new activity`);
|
|
2615
|
-
}
|
|
2616
|
-
// Step 2: Load high-value agent runs
|
|
2617
|
-
const step2 = await this.CreateRunStep('Decision', 'Load High-Value Agent Runs', {
|
|
2618
|
-
since: lastRunTime?.toISOString() || null
|
|
2619
|
-
});
|
|
2620
|
-
const agentRuns = await this.LoadHighValueAgentRuns(lastRunTime, params.contextUser);
|
|
2621
2845
|
await this.FinalizeRunStep(step2, true, {
|
|
2622
2846
|
runCount: agentRuns.length
|
|
2623
2847
|
});
|
|
2624
2848
|
if (this._verbose) {
|
|
2849
|
+
LogStatus(`Memory Manager: Found ${conversations.length} conversations with new activity`);
|
|
2625
2850
|
LogStatus(`Memory Manager: Found ${agentRuns.length} high-value agent runs`);
|
|
2626
2851
|
}
|
|
2627
2852
|
const hasNewData = conversations.length > 0 || agentRuns.length > 0;
|
|
@@ -2691,8 +2916,15 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2691
2916
|
if (this._verbose)
|
|
2692
2917
|
LogStatus(`Memory Manager: Created ${notesCreated} notes and ${examplesCreated} examples`);
|
|
2693
2918
|
}
|
|
2919
|
+
// Harden agent-written provisional notes FIRST and unconditionally — survivors
|
|
2920
|
+
// become Active and participate in this same cycle's maintenance phases
|
|
2921
|
+
// (importance, consolidation, contradiction, decay) when those fire.
|
|
2922
|
+
const hardening = await this.runHardeningPhase(params.contextUser);
|
|
2923
|
+
const hardeningSummary = hardening.hardened + hardening.deduped + hardening.failed > 0
|
|
2924
|
+
? ` Hardened ${hardening.hardened} provisional notes (${hardening.deduped} deduped, ${hardening.failed} failed).`
|
|
2925
|
+
: '';
|
|
2694
2926
|
const forceMaintenance = params.data?.forceMaintenance === true;
|
|
2695
|
-
const triggerDecision = await this.shouldRunConsolidation(params.agent.ID, params.contextUser, forceMaintenance);
|
|
2927
|
+
const triggerDecision = await this.shouldRunConsolidation(params.agent.ID, params.contextUser, forceMaintenance, lastRunTime);
|
|
2696
2928
|
const maintenance = triggerDecision.shouldRun
|
|
2697
2929
|
? await this.runMaintenancePhases(params.contextUser, forceMaintenance, triggerDecision.triggerType)
|
|
2698
2930
|
: { consolidatedCount: 0, consolidationArchived: 0, contradictionsFound: 0, contradictionsResolved: 0, contradictionsFlagged: 0, staleNotesArchived: 0, decayNotesArchived: 0, decayExamplesArchived: 0, importanceScored: 0, tierPromotions: 0 };
|
|
@@ -2700,7 +2932,7 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2700
2932
|
const finalStep = {
|
|
2701
2933
|
terminate: true,
|
|
2702
2934
|
step: 'Success',
|
|
2703
|
-
message: `Processed ${conversations.length} conversations (${totalMessages} messages) and ${agentRuns.length} agent runs. Created ${notesCreated} notes and ${examplesCreated} examples.${maintenanceSummary}`,
|
|
2935
|
+
message: `Processed ${conversations.length} conversations (${totalMessages} messages) and ${agentRuns.length} agent runs. Created ${notesCreated} notes and ${examplesCreated} examples.${hardeningSummary}${maintenanceSummary}`,
|
|
2704
2936
|
newPayload: {
|
|
2705
2937
|
notesCreated,
|
|
2706
2938
|
examplesCreated,
|