@memberjunction/ai-agents 5.40.1 → 5.41.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 +45 -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 +365 -24
- package/dist/base-agent.d.ts.map +1 -1
- package/dist/base-agent.js +995 -175
- package/dist/base-agent.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/memory-manager-agent.d.ts +55 -2
- package/dist/memory-manager-agent.d.ts.map +1 -1
- package/dist/memory-manager-agent.js +261 -62
- package/dist/memory-manager-agent.js.map +1 -1
- 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 +884 -0
- package/dist/realtime/realtime-client-session-service.d.ts.map +1 -0
- package/dist/realtime/realtime-client-session-service.js +1401 -0
- package/dist/realtime/realtime-client-session-service.js.map +1 -0
- package/dist/realtime/realtime-coagent-config.d.ts +202 -0
- package/dist/realtime/realtime-coagent-config.d.ts.map +1 -0
- package/dist/realtime/realtime-coagent-config.js +334 -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 +279 -0
- package/dist/realtime/realtime-tool-broker.d.ts.map +1 -0
- package/dist/realtime/realtime-tool-broker.js +184 -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
|
@@ -8,6 +8,7 @@ var MemoryManagerAgent_1;
|
|
|
8
8
|
import { BaseAgent } from './base-agent.js';
|
|
9
9
|
import { RegisterClass, CleanAndParseJSON } from '@memberjunction/global';
|
|
10
10
|
import { RunView, RunQuery, LogError, LogStatus } from '@memberjunction/core';
|
|
11
|
+
import { InjectableNoteStatusSQLList } 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.
|
|
@@ -319,6 +351,12 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
319
351
|
/**
|
|
320
352
|
* Load agent runs with high-usage artifacts since last run.
|
|
321
353
|
* Links through: ArtifactUse -> ArtifactVersion -> ConversationDetailArtifact -> ConversationDetail -> Conversation -> AIAgentRun
|
|
354
|
+
*
|
|
355
|
+
* The caller only consumes the row count (`.length`) of the returned set, so this reads a
|
|
356
|
+
* 'simple' ID-only projection rather than full entity objects. Loading these as
|
|
357
|
+
* 'entity_object' would build up to 50 MJAIAgentRunEntityExtended instances, each firing a
|
|
358
|
+
* secondary "MJ: AI Agent Run Steps" RunView via InnerLoad/LoadRelatedData — exactly the
|
|
359
|
+
* redundant round-trips the telemetry flagged. The simple projection skips all of them.
|
|
322
360
|
*/
|
|
323
361
|
async LoadHighValueAgentRuns(since, contextUser) {
|
|
324
362
|
const rv = new RunView();
|
|
@@ -350,7 +388,8 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
350
388
|
ExtraFilter: filter,
|
|
351
389
|
OrderBy: '__mj_CreatedAt DESC',
|
|
352
390
|
MaxRows: 50, // Limit to most recent 50 high-value runs
|
|
353
|
-
|
|
391
|
+
Fields: ['ID'],
|
|
392
|
+
ResultType: 'simple'
|
|
354
393
|
}, contextUser);
|
|
355
394
|
return result.Success ? (result.Results || []) : [];
|
|
356
395
|
}
|
|
@@ -848,8 +887,7 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
848
887
|
let merged = 0;
|
|
849
888
|
let failed = 0;
|
|
850
889
|
const md = this.ProviderToUse;
|
|
851
|
-
|
|
852
|
-
// Cache source agent runs to avoid repeated lookups
|
|
890
|
+
// Cache source agent runs (scope fields only) to avoid repeated lookups
|
|
853
891
|
const runCache = new Map();
|
|
854
892
|
// Get the "AI" note type ID for AI-generated notes
|
|
855
893
|
const aiNoteTypeId = AIEngine.Instance.AgenteNoteTypeIDByName('AI');
|
|
@@ -867,16 +905,7 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
867
905
|
// Load source agent run for scope inheritance (if available)
|
|
868
906
|
let sourceRun = null;
|
|
869
907
|
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;
|
|
908
|
+
sourceRun = await this.loadSourceRunScope(extracted.sourceAgentRunId, runCache, contextUser);
|
|
880
909
|
}
|
|
881
910
|
// Check if we should merge with existing (revoke all targets, create one replacement)
|
|
882
911
|
let createNewNote = !extracted.mergeWithExistingIds?.length;
|
|
@@ -1202,7 +1231,7 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
1202
1231
|
* Returns both a boolean and the reason it fired so the observability layer can
|
|
1203
1232
|
* record the trigger type on the parent consolidation run step.
|
|
1204
1233
|
*/
|
|
1205
|
-
async shouldRunConsolidation(agentId, contextUser, forceMaintenance) {
|
|
1234
|
+
async shouldRunConsolidation(agentId, contextUser, forceMaintenance, lastRun) {
|
|
1206
1235
|
if (forceMaintenance)
|
|
1207
1236
|
return { shouldRun: true, triggerType: 'forced' };
|
|
1208
1237
|
const freq = CONSOLIDATION_CONFIG.frequency;
|
|
@@ -1211,7 +1240,7 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
1211
1240
|
if (freq === 'every-run')
|
|
1212
1241
|
return { shouldRun: true, triggerType: 'every-run' };
|
|
1213
1242
|
if (freq === 'hourly' || freq === 'daily') {
|
|
1214
|
-
return this.shouldRunConsolidationByTimeWindow(
|
|
1243
|
+
return this.shouldRunConsolidationByTimeWindow(contextUser, freq === 'hourly' ? 1 : 24, lastRun);
|
|
1215
1244
|
}
|
|
1216
1245
|
if (typeof freq === 'number') {
|
|
1217
1246
|
return this.shouldRunConsolidationByRunCount(agentId, contextUser, freq);
|
|
@@ -1224,8 +1253,11 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
1224
1253
|
* when the event-driven new-note threshold has been crossed. Falls back to
|
|
1225
1254
|
* triggerType='not-triggered' when neither condition fires.
|
|
1226
1255
|
*/
|
|
1227
|
-
async shouldRunConsolidationByTimeWindow(
|
|
1228
|
-
|
|
1256
|
+
async shouldRunConsolidationByTimeWindow(contextUser, thresholdHours, lastRun) {
|
|
1257
|
+
// `lastRun` is the same value computed once at the top of the maintenance cycle
|
|
1258
|
+
// (executeAgentInternal). Threading it through here removes a second identical
|
|
1259
|
+
// `MJ: AI Agent Runs` RunView (AgentID + Status='Completed', OrderBy StartedAt DESC)
|
|
1260
|
+
// that the redundancy telemetry flagged as a duplicate round-trip.
|
|
1229
1261
|
if (!lastRun)
|
|
1230
1262
|
return { shouldRun: true, triggerType: 'time' };
|
|
1231
1263
|
const hoursSinceLast = (Date.now() - lastRun.getTime()) / (1000 * 60 * 60);
|
|
@@ -1420,8 +1452,57 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
1420
1452
|
}
|
|
1421
1453
|
return originals;
|
|
1422
1454
|
}
|
|
1423
|
-
/**
|
|
1455
|
+
/**
|
|
1456
|
+
* Load a batch of notes by ID with the fields needed for source resolution.
|
|
1457
|
+
*
|
|
1458
|
+
* AIEngineBase caches `MJ: AI Agent Notes` unfiltered as full entity objects
|
|
1459
|
+
* (`AIEngine.Instance.AgentNotes`), so source-chain ancestors — which are pre-existing
|
|
1460
|
+
* notes, never ones created earlier in this same cycle — are already in memory. We serve
|
|
1461
|
+
* those from the cache and only issue a RunView for IDs the cache doesn't have, instead of
|
|
1462
|
+
* a DB round-trip per BFS depth level. This resolves the redundancy telemetry's
|
|
1463
|
+
* "Entity Already in Engine" warning for AIEngineBase while preserving exact behavior
|
|
1464
|
+
* (the projection shape and field set are unchanged).
|
|
1465
|
+
*/
|
|
1424
1466
|
async loadResolvedNotes(noteIds, contextUser) {
|
|
1467
|
+
const cachedById = new Map();
|
|
1468
|
+
for (const note of AIEngine.Instance.AgentNotes) {
|
|
1469
|
+
if (note.ID)
|
|
1470
|
+
cachedById.set(note.ID.toLowerCase(), note);
|
|
1471
|
+
}
|
|
1472
|
+
const resolved = [];
|
|
1473
|
+
const missingIds = [];
|
|
1474
|
+
for (const id of noteIds) {
|
|
1475
|
+
const cached = cachedById.get(id.toLowerCase());
|
|
1476
|
+
if (cached) {
|
|
1477
|
+
resolved.push(this.projectResolvedNote(cached));
|
|
1478
|
+
}
|
|
1479
|
+
else {
|
|
1480
|
+
missingIds.push(id);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
if (missingIds.length > 0) {
|
|
1484
|
+
resolved.push(...await this.loadResolvedNotesFromDatabase(missingIds, contextUser));
|
|
1485
|
+
}
|
|
1486
|
+
return resolved;
|
|
1487
|
+
}
|
|
1488
|
+
/** Project a cached note entity into the read-only shape used by source resolution. */
|
|
1489
|
+
projectResolvedNote(note) {
|
|
1490
|
+
return {
|
|
1491
|
+
ID: note.ID,
|
|
1492
|
+
ConsolidationCount: note.ConsolidationCount,
|
|
1493
|
+
DerivedFromNoteIDs: note.DerivedFromNoteIDs,
|
|
1494
|
+
Note: note.Note,
|
|
1495
|
+
Type: note.Type,
|
|
1496
|
+
AccessCount: note.AccessCount,
|
|
1497
|
+
ImportanceScore: note.ImportanceScore,
|
|
1498
|
+
AgentID: note.AgentID,
|
|
1499
|
+
UserID: note.UserID,
|
|
1500
|
+
CompanyID: note.CompanyID,
|
|
1501
|
+
__mj_CreatedAt: note.__mj_CreatedAt
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
/** RunView fallback for note IDs not present in the AIEngine cache. */
|
|
1505
|
+
async loadResolvedNotesFromDatabase(noteIds, contextUser) {
|
|
1425
1506
|
const resolvedFields = [
|
|
1426
1507
|
'ID', 'ConsolidationCount', 'DerivedFromNoteIDs', 'Note', 'Type',
|
|
1427
1508
|
'AccessCount', 'ImportanceScore', 'AgentID', 'UserID', 'CompanyID', '__mj_CreatedAt'
|
|
@@ -2133,22 +2214,27 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2133
2214
|
}
|
|
2134
2215
|
async loadExpiredItems(contextUser, nowISO) {
|
|
2135
2216
|
const rv = new RunView();
|
|
2136
|
-
const [notesResult, examplesResult] = await
|
|
2137
|
-
|
|
2217
|
+
const [notesResult, examplesResult] = await rv.RunViews([
|
|
2218
|
+
{
|
|
2138
2219
|
EntityName: 'MJ: AI Agent Notes',
|
|
2139
|
-
|
|
2220
|
+
// Includes Provisional so the TTL safety net on in-flight agent writes is
|
|
2221
|
+
// actually enforced — a provisional note the hardening pass never reaches
|
|
2222
|
+
// must still expire.
|
|
2223
|
+
ExtraFilter: `Status IN (${InjectableNoteStatusSQLList()}) AND ExpiresAt IS NOT NULL AND ExpiresAt < '${nowISO}'`,
|
|
2140
2224
|
Fields: ['ID', 'ExpiresAt', 'ProtectionTier'],
|
|
2141
2225
|
MaxRows: 200,
|
|
2142
|
-
},
|
|
2143
|
-
|
|
2226
|
+
},
|
|
2227
|
+
{
|
|
2144
2228
|
EntityName: 'MJ: AI Agent Examples',
|
|
2145
2229
|
ExtraFilter: `Status = 'Active' AND ExpiresAt IS NOT NULL AND ExpiresAt < '${nowISO}'`,
|
|
2146
2230
|
Fields: ['ID', 'ExpiresAt'],
|
|
2147
2231
|
MaxRows: 200,
|
|
2148
|
-
}
|
|
2149
|
-
]);
|
|
2232
|
+
}
|
|
2233
|
+
], contextUser);
|
|
2150
2234
|
return [
|
|
2151
|
-
notesResult.Success && notesResult.Results
|
|
2235
|
+
notesResult.Success && notesResult.Results
|
|
2236
|
+
? notesResult.Results.filter((r) => 'ProtectionTier' in r)
|
|
2237
|
+
: [],
|
|
2152
2238
|
examplesResult.Success && examplesResult.Results ? examplesResult.Results : []
|
|
2153
2239
|
];
|
|
2154
2240
|
}
|
|
@@ -2194,24 +2280,32 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2194
2280
|
* Fetch notes and examples in parallel — they're independent.
|
|
2195
2281
|
*/
|
|
2196
2282
|
async loadDecayCandidates(contextUser) {
|
|
2283
|
+
// Batch into one RunViews round-trip — see loadExpiredItems for the rationale on why
|
|
2284
|
+
// two parallel RunView calls are flagged sequential by the redundancy telemetry. The
|
|
2285
|
+
// two projections differ, so the batch is typed with their union and narrowed by
|
|
2286
|
+
// position (notes carry ProtectionTier, examples carry SuccessScore).
|
|
2197
2287
|
const rv = new RunView();
|
|
2198
|
-
const [activeNotesResult, activeExamplesResult] = await
|
|
2199
|
-
|
|
2288
|
+
const [activeNotesResult, activeExamplesResult] = await rv.RunViews([
|
|
2289
|
+
{
|
|
2200
2290
|
EntityName: 'MJ: AI Agent Notes',
|
|
2201
2291
|
ExtraFilter: `IsAutoGenerated = 1 AND Status = 'Active'`,
|
|
2202
2292
|
Fields: ['ID', 'ImportanceScore', 'ProtectionTier', 'LastAccessedAt', '__mj_CreatedAt', 'AccessCount', 'AgentID'],
|
|
2203
2293
|
MaxRows: 1000,
|
|
2204
|
-
},
|
|
2205
|
-
|
|
2294
|
+
},
|
|
2295
|
+
{
|
|
2206
2296
|
EntityName: 'MJ: AI Agent Examples',
|
|
2207
2297
|
ExtraFilter: `IsAutoGenerated = 1 AND Status = 'Active' AND SuccessScore < 50`,
|
|
2208
2298
|
Fields: ['ID', 'SuccessScore', 'LastAccessedAt', '__mj_CreatedAt', 'AccessCount', 'AgentID'],
|
|
2209
2299
|
MaxRows: 500,
|
|
2210
|
-
}
|
|
2211
|
-
]);
|
|
2300
|
+
}
|
|
2301
|
+
], contextUser);
|
|
2212
2302
|
return [
|
|
2213
|
-
activeNotesResult.Success && activeNotesResult.Results
|
|
2214
|
-
|
|
2303
|
+
activeNotesResult.Success && activeNotesResult.Results
|
|
2304
|
+
? activeNotesResult.Results.filter((r) => 'ProtectionTier' in r)
|
|
2305
|
+
: [],
|
|
2306
|
+
activeExamplesResult.Success && activeExamplesResult.Results
|
|
2307
|
+
? activeExamplesResult.Results.filter((r) => 'SuccessScore' in r)
|
|
2308
|
+
: []
|
|
2215
2309
|
];
|
|
2216
2310
|
}
|
|
2217
2311
|
/**
|
|
@@ -2282,6 +2376,110 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2282
2376
|
if (await exampleToArchive.Save())
|
|
2283
2377
|
result.examplesArchived++;
|
|
2284
2378
|
}
|
|
2379
|
+
/**
|
|
2380
|
+
* Hardening pass over in-flight agent-written notes (Status='Provisional',
|
|
2381
|
+
* AuthorType='Agent'). Runs UNCONDITIONALLY at the start of every MM cycle —
|
|
2382
|
+
* before the consolidation-gated maintenance phases — so that:
|
|
2383
|
+
* 1. provisional notes never languish waiting for a consolidation trigger, and
|
|
2384
|
+
* 2. notes hardened here participate in importance scoring, consolidation,
|
|
2385
|
+
* contradiction detection, and decay in the SAME cycle via the existing
|
|
2386
|
+
* machinery (no duplicated reconciliation logic in this pass).
|
|
2387
|
+
*
|
|
2388
|
+
* Per note: dedupe against hardened (Active) notes using the same LLM dedupe
|
|
2389
|
+
* prompt as extraction — duplicates are Archived with ConsolidatedIntoNoteID
|
|
2390
|
+
* lineage; survivors are hardened (Status='Active', ExpiresAt=NULL so the
|
|
2391
|
+
* standard decay machinery owns their lifetime).
|
|
2392
|
+
*/
|
|
2393
|
+
async runHardeningPhase(contextUser) {
|
|
2394
|
+
const counters = { hardened: 0, deduped: 0, failed: 0 };
|
|
2395
|
+
const step = await this.CreateRunStep('Decision', 'Harden Provisional Notes', {
|
|
2396
|
+
maxNotesPerRun: HARDENING_CONFIG.maxNotesPerRun
|
|
2397
|
+
});
|
|
2398
|
+
try {
|
|
2399
|
+
const rv = new RunView();
|
|
2400
|
+
const result = await rv.RunView({
|
|
2401
|
+
EntityName: 'MJ: AI Agent Notes',
|
|
2402
|
+
ExtraFilter: `Status = 'Provisional' AND AuthorType = 'Agent'`,
|
|
2403
|
+
OrderBy: '__mj_CreatedAt ASC',
|
|
2404
|
+
MaxRows: HARDENING_CONFIG.maxNotesPerRun,
|
|
2405
|
+
ResultType: 'entity_object'
|
|
2406
|
+
}, contextUser);
|
|
2407
|
+
if (!result.Success) {
|
|
2408
|
+
await this.FinalizeRunStep(step, false, counters, undefined, `Failed to load provisional notes: ${result.ErrorMessage}`);
|
|
2409
|
+
return counters;
|
|
2410
|
+
}
|
|
2411
|
+
const provisionalNotes = result.Results || [];
|
|
2412
|
+
if (provisionalNotes.length === 0) {
|
|
2413
|
+
await this.FinalizeRunStep(step, true, { ...counters, provisionalCount: 0 });
|
|
2414
|
+
return counters;
|
|
2415
|
+
}
|
|
2416
|
+
const runner = new AIPromptRunner();
|
|
2417
|
+
const dedupePrompt = AIEngine.Instance.Prompts.find(p => p.Name === 'Memory Manager - Deduplicate Note' && p.Category === 'MJ: System');
|
|
2418
|
+
for (const note of provisionalNotes) {
|
|
2419
|
+
const outcome = await this.hardenSingleNote(note, dedupePrompt, runner, contextUser);
|
|
2420
|
+
counters[outcome]++;
|
|
2421
|
+
}
|
|
2422
|
+
if (this._verbose) {
|
|
2423
|
+
LogStatus(`Memory Manager: Hardening pass — ${counters.hardened} hardened, ${counters.deduped} deduped, ${counters.failed} failed of ${provisionalNotes.length} provisional notes`);
|
|
2424
|
+
}
|
|
2425
|
+
await this.FinalizeRunStep(step, counters.failed === 0, { ...counters, provisionalCount: provisionalNotes.length });
|
|
2426
|
+
}
|
|
2427
|
+
catch (error) {
|
|
2428
|
+
LogError('Memory Manager: Hardening pass failed, continuing with run:', error);
|
|
2429
|
+
await this.FinalizeRunStep(step, false, counters, undefined, error instanceof Error ? error.message : String(error));
|
|
2430
|
+
}
|
|
2431
|
+
return counters;
|
|
2432
|
+
}
|
|
2433
|
+
/**
|
|
2434
|
+
* Harden one provisional note: archive it as a duplicate when the LLM dedupe
|
|
2435
|
+
* decision says an equivalent hardened note exists, otherwise promote it to
|
|
2436
|
+
* Active with decay-managed lifetime.
|
|
2437
|
+
*/
|
|
2438
|
+
async hardenSingleNote(note, dedupePrompt, runner, contextUser) {
|
|
2439
|
+
try {
|
|
2440
|
+
// Compare against HARDENED notes only — other provisional notes are themselves
|
|
2441
|
+
// unvetted, and same-run duplicates were already handled at write time.
|
|
2442
|
+
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');
|
|
2443
|
+
if (similar.length > 0 && dedupePrompt) {
|
|
2444
|
+
const candidate = {
|
|
2445
|
+
type: note.Type,
|
|
2446
|
+
content: note.Note || '',
|
|
2447
|
+
confidence: 100,
|
|
2448
|
+
agentId: note.AgentID || undefined,
|
|
2449
|
+
userId: note.UserID || undefined,
|
|
2450
|
+
companyId: note.CompanyID || undefined,
|
|
2451
|
+
};
|
|
2452
|
+
const decision = await this.runDedupePromptForCandidate(runner, dedupePrompt, candidate, similar, contextUser);
|
|
2453
|
+
if (!decision.shouldAdd) {
|
|
2454
|
+
const target = similar[0].note;
|
|
2455
|
+
note.Status = 'Archived';
|
|
2456
|
+
note.ConsolidatedIntoNoteID = target.ID;
|
|
2457
|
+
note.Comments = `${note.Comments || ''} [Hardening: duplicate of ${target.ID} — ${decision.reason}]`.trim();
|
|
2458
|
+
if (!await note.Save()) {
|
|
2459
|
+
LogError(`Memory Manager: Failed to archive duplicate provisional note ${note.ID}: ${note.LatestResult?.CompleteMessage || 'unknown error'}`);
|
|
2460
|
+
return 'failed';
|
|
2461
|
+
}
|
|
2462
|
+
target.AccessCount = (target.AccessCount || 0) + 1;
|
|
2463
|
+
target.LastAccessedAt = new Date();
|
|
2464
|
+
if (!await target.Save()) {
|
|
2465
|
+
LogError(`Memory Manager: Failed to bump dedupe target ${target.ID}: ${target.LatestResult?.CompleteMessage || 'unknown error'}`);
|
|
2466
|
+
}
|
|
2467
|
+
return 'deduped';
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
note.Status = 'Active';
|
|
2471
|
+
note.ExpiresAt = null;
|
|
2472
|
+
if (!await note.Save()) {
|
|
2473
|
+
LogError(`Memory Manager: Failed to harden provisional note ${note.ID}: ${note.LatestResult?.CompleteMessage || 'unknown error'}`);
|
|
2474
|
+
return 'failed';
|
|
2475
|
+
}
|
|
2476
|
+
return 'hardened';
|
|
2477
|
+
}
|
|
2478
|
+
catch (error) {
|
|
2479
|
+
LogError(`Memory Manager: Exception hardening note ${note.ID}:`, error);
|
|
2480
|
+
return 'failed';
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2285
2483
|
/**
|
|
2286
2484
|
* Run all 5 maintenance phases in order: importance scoring, consolidation,
|
|
2287
2485
|
* contradiction detection, stale-reference pruning, and decay-based archival.
|
|
@@ -2483,24 +2681,14 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2483
2681
|
let skipped = 0;
|
|
2484
2682
|
let failed = 0;
|
|
2485
2683
|
const md = this.ProviderToUse;
|
|
2486
|
-
|
|
2487
|
-
// Cache source agent runs to avoid repeated lookups
|
|
2684
|
+
// Cache source agent runs (scope fields only) to avoid repeated lookups
|
|
2488
2685
|
const runCache = new Map();
|
|
2489
2686
|
for (const extracted of extractedExamples) {
|
|
2490
2687
|
try {
|
|
2491
2688
|
// Load source agent run for scope inheritance (if available)
|
|
2492
2689
|
let sourceRun = null;
|
|
2493
2690
|
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;
|
|
2691
|
+
sourceRun = await this.loadSourceRunScope(extracted.sourceAgentRunId, runCache, contextUser);
|
|
2504
2692
|
}
|
|
2505
2693
|
const example = await md.GetEntityObject('MJ: AI Agent Examples', contextUser);
|
|
2506
2694
|
// AgentID must come from source run - LLM doesn't know real agent IDs
|
|
@@ -2579,9 +2767,12 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2579
2767
|
this._stepCounter = 0;
|
|
2580
2768
|
this._contextUser = params.contextUser || null;
|
|
2581
2769
|
LogStatus('Memory Manager: Starting analysis cycle');
|
|
2582
|
-
|
|
2583
|
-
//
|
|
2584
|
-
const agentsUsingMemory = await
|
|
2770
|
+
// Phase 1: the last-run-time and the memory-enabled agent set are independent — load in
|
|
2771
|
+
// parallel (avoids two back-to-back sequential RunViews flagged by the parallelization telemetry).
|
|
2772
|
+
const [lastRunTime, agentsUsingMemory] = await Promise.all([
|
|
2773
|
+
this.GetLastRunTime(params.agent.ID, params.contextUser),
|
|
2774
|
+
this.LoadAgentsUsingMemory(params.contextUser)
|
|
2775
|
+
]);
|
|
2585
2776
|
if (this._verbose) {
|
|
2586
2777
|
const sinceMessage = lastRunTime ? `since ${lastRunTime.toISOString()}` : 'all history';
|
|
2587
2778
|
LogStatus(`Memory Manager: Processing ${sinceMessage}`);
|
|
@@ -2595,13 +2786,21 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2595
2786
|
};
|
|
2596
2787
|
return { finalStep, stepCount: 1 };
|
|
2597
2788
|
}
|
|
2598
|
-
//
|
|
2789
|
+
// Phase 2: conversations-with-new-activity and high-value agent runs both depend only on
|
|
2790
|
+
// (lastRunTime, agentsUsingMemory) — never on each other — so load them in parallel, each
|
|
2791
|
+
// recorded in its own run step.
|
|
2599
2792
|
const step1 = await this.CreateRunStep('Decision', 'Load Conversations With New Activity', {
|
|
2600
2793
|
since: lastRunTime?.toISOString() || null,
|
|
2601
2794
|
agentCount: agentsUsingMemory.length,
|
|
2602
2795
|
agentIds: agentsUsingMemory.map(a => a.ID)
|
|
2603
2796
|
});
|
|
2604
|
-
const
|
|
2797
|
+
const step2 = await this.CreateRunStep('Decision', 'Load High-Value Agent Runs', {
|
|
2798
|
+
since: lastRunTime?.toISOString() || null
|
|
2799
|
+
});
|
|
2800
|
+
const [conversations, agentRuns] = await Promise.all([
|
|
2801
|
+
this.LoadConversationsWithNewActivity(lastRunTime, agentsUsingMemory, params.contextUser),
|
|
2802
|
+
this.LoadHighValueAgentRuns(lastRunTime, params.contextUser)
|
|
2803
|
+
]);
|
|
2605
2804
|
const totalMessages = conversations.reduce((sum, c) => sum + c.messages.length, 0);
|
|
2606
2805
|
await this.FinalizeRunStep(step1, true, {
|
|
2607
2806
|
conversationCount: conversations.length,
|
|
@@ -2610,18 +2809,11 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2610
2809
|
negativeCount: conversations.filter(c => c.hasNegativeRating).length,
|
|
2611
2810
|
unratedCount: conversations.filter(c => c.isUnrated).length
|
|
2612
2811
|
});
|
|
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
2812
|
await this.FinalizeRunStep(step2, true, {
|
|
2622
2813
|
runCount: agentRuns.length
|
|
2623
2814
|
});
|
|
2624
2815
|
if (this._verbose) {
|
|
2816
|
+
LogStatus(`Memory Manager: Found ${conversations.length} conversations with new activity`);
|
|
2625
2817
|
LogStatus(`Memory Manager: Found ${agentRuns.length} high-value agent runs`);
|
|
2626
2818
|
}
|
|
2627
2819
|
const hasNewData = conversations.length > 0 || agentRuns.length > 0;
|
|
@@ -2691,8 +2883,15 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2691
2883
|
if (this._verbose)
|
|
2692
2884
|
LogStatus(`Memory Manager: Created ${notesCreated} notes and ${examplesCreated} examples`);
|
|
2693
2885
|
}
|
|
2886
|
+
// Harden agent-written provisional notes FIRST and unconditionally — survivors
|
|
2887
|
+
// become Active and participate in this same cycle's maintenance phases
|
|
2888
|
+
// (importance, consolidation, contradiction, decay) when those fire.
|
|
2889
|
+
const hardening = await this.runHardeningPhase(params.contextUser);
|
|
2890
|
+
const hardeningSummary = hardening.hardened + hardening.deduped + hardening.failed > 0
|
|
2891
|
+
? ` Hardened ${hardening.hardened} provisional notes (${hardening.deduped} deduped, ${hardening.failed} failed).`
|
|
2892
|
+
: '';
|
|
2694
2893
|
const forceMaintenance = params.data?.forceMaintenance === true;
|
|
2695
|
-
const triggerDecision = await this.shouldRunConsolidation(params.agent.ID, params.contextUser, forceMaintenance);
|
|
2894
|
+
const triggerDecision = await this.shouldRunConsolidation(params.agent.ID, params.contextUser, forceMaintenance, lastRunTime);
|
|
2696
2895
|
const maintenance = triggerDecision.shouldRun
|
|
2697
2896
|
? await this.runMaintenancePhases(params.contextUser, forceMaintenance, triggerDecision.triggerType)
|
|
2698
2897
|
: { consolidatedCount: 0, consolidationArchived: 0, contradictionsFound: 0, contradictionsResolved: 0, contradictionsFlagged: 0, staleNotesArchived: 0, decayNotesArchived: 0, decayExamplesArchived: 0, importanceScored: 0, tierPromotions: 0 };
|
|
@@ -2700,7 +2899,7 @@ let MemoryManagerAgent = MemoryManagerAgent_1 = class MemoryManagerAgent extends
|
|
|
2700
2899
|
const finalStep = {
|
|
2701
2900
|
terminate: true,
|
|
2702
2901
|
step: 'Success',
|
|
2703
|
-
message: `Processed ${conversations.length} conversations (${totalMessages} messages) and ${agentRuns.length} agent runs. Created ${notesCreated} notes and ${examplesCreated} examples.${maintenanceSummary}`,
|
|
2902
|
+
message: `Processed ${conversations.length} conversations (${totalMessages} messages) and ${agentRuns.length} agent runs. Created ${notesCreated} notes and ${examplesCreated} examples.${hardeningSummary}${maintenanceSummary}`,
|
|
2704
2903
|
newPayload: {
|
|
2705
2904
|
notesCreated,
|
|
2706
2905
|
examplesCreated,
|