@framers/agentos 0.1.32 → 0.1.34
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 +5 -2
- package/dist/api/AgentOS.d.ts +62 -1
- package/dist/api/AgentOS.d.ts.map +1 -1
- package/dist/api/AgentOS.js +177 -2
- package/dist/api/AgentOS.js.map +1 -1
- package/dist/api/AgentOSOrchestrator.d.ts +187 -0
- package/dist/api/AgentOSOrchestrator.d.ts.map +1 -1
- package/dist/api/AgentOSOrchestrator.js +709 -16
- package/dist/api/AgentOSOrchestrator.js.map +1 -1
- package/dist/cognitive_substrate/GMI.d.ts.map +1 -1
- package/dist/cognitive_substrate/GMI.js +36 -1
- package/dist/cognitive_substrate/GMI.js.map +1 -1
- package/dist/cognitive_substrate/IGMI.d.ts +21 -0
- package/dist/cognitive_substrate/IGMI.d.ts.map +1 -1
- package/dist/cognitive_substrate/IGMI.js.map +1 -1
- package/dist/config/AgentOSConfig.d.ts.map +1 -1
- package/dist/config/AgentOSConfig.js +17 -0
- package/dist/config/AgentOSConfig.js.map +1 -1
- package/dist/config/VectorStoreConfiguration.d.ts +2 -1
- package/dist/config/VectorStoreConfiguration.d.ts.map +1 -1
- package/dist/config/VectorStoreConfiguration.js.map +1 -1
- package/dist/core/knowledge/Neo4jKnowledgeGraph.d.ts +89 -0
- package/dist/core/knowledge/Neo4jKnowledgeGraph.d.ts.map +1 -0
- package/dist/core/knowledge/Neo4jKnowledgeGraph.js +683 -0
- package/dist/core/knowledge/Neo4jKnowledgeGraph.js.map +1 -0
- package/dist/core/llm/providers/implementations/OllamaProvider.d.ts +14 -1
- package/dist/core/llm/providers/implementations/OllamaProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/OllamaProvider.js +142 -37
- package/dist/core/llm/providers/implementations/OllamaProvider.js.map +1 -1
- package/dist/core/llm/providers/implementations/OpenAIProvider.js +3 -3
- package/dist/core/llm/providers/implementations/OpenAIProvider.js.map +1 -1
- package/dist/core/observability/otel.d.ts +2 -0
- package/dist/core/observability/otel.d.ts.map +1 -1
- package/dist/core/observability/otel.js +14 -0
- package/dist/core/observability/otel.js.map +1 -1
- package/dist/core/orchestration/SqlTaskOutcomeTelemetryStore.d.ts +30 -0
- package/dist/core/orchestration/SqlTaskOutcomeTelemetryStore.d.ts.map +1 -0
- package/dist/core/orchestration/SqlTaskOutcomeTelemetryStore.js +123 -0
- package/dist/core/orchestration/SqlTaskOutcomeTelemetryStore.js.map +1 -0
- package/dist/core/orchestration/TurnPlanner.d.ts +89 -0
- package/dist/core/orchestration/TurnPlanner.d.ts.map +1 -0
- package/dist/core/orchestration/TurnPlanner.js +242 -0
- package/dist/core/orchestration/TurnPlanner.js.map +1 -0
- package/dist/discovery/CapabilityDiscoveryEngine.js +4 -4
- package/dist/discovery/CapabilityDiscoveryEngine.js.map +1 -1
- package/dist/discovery/CapabilityGraph.d.ts +2 -2
- package/dist/discovery/CapabilityGraph.d.ts.map +1 -1
- package/dist/discovery/CapabilityGraph.js +46 -17
- package/dist/discovery/CapabilityGraph.js.map +1 -1
- package/dist/discovery/Neo4jCapabilityGraph.d.ts +58 -0
- package/dist/discovery/Neo4jCapabilityGraph.d.ts.map +1 -0
- package/dist/discovery/Neo4jCapabilityGraph.js +226 -0
- package/dist/discovery/Neo4jCapabilityGraph.js.map +1 -0
- package/dist/discovery/index.d.ts +1 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/index.js +1 -0
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/types.d.ts +1 -1
- package/dist/discovery/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/neo4j/Neo4jConnectionManager.d.ts +59 -0
- package/dist/neo4j/Neo4jConnectionManager.d.ts.map +1 -0
- package/dist/neo4j/Neo4jConnectionManager.js +115 -0
- package/dist/neo4j/Neo4jConnectionManager.js.map +1 -0
- package/dist/neo4j/Neo4jCypherRunner.d.ts +39 -0
- package/dist/neo4j/Neo4jCypherRunner.d.ts.map +1 -0
- package/dist/neo4j/Neo4jCypherRunner.js +74 -0
- package/dist/neo4j/Neo4jCypherRunner.js.map +1 -0
- package/dist/neo4j/index.d.ts +12 -0
- package/dist/neo4j/index.d.ts.map +1 -0
- package/dist/neo4j/index.js +11 -0
- package/dist/neo4j/index.js.map +1 -0
- package/dist/neo4j/types.d.ts +27 -0
- package/dist/neo4j/types.d.ts.map +1 -0
- package/dist/neo4j/types.js +6 -0
- package/dist/neo4j/types.js.map +1 -0
- package/dist/rag/VectorStoreManager.d.ts.map +1 -1
- package/dist/rag/VectorStoreManager.js +6 -7
- package/dist/rag/VectorStoreManager.js.map +1 -1
- package/dist/rag/graphrag/GraphRAGEngine.d.ts.map +1 -1
- package/dist/rag/graphrag/GraphRAGEngine.js +42 -10
- package/dist/rag/graphrag/GraphRAGEngine.js.map +1 -1
- package/dist/rag/graphrag/Neo4jGraphRAGEngine.d.ts +95 -0
- package/dist/rag/graphrag/Neo4jGraphRAGEngine.d.ts.map +1 -0
- package/dist/rag/graphrag/Neo4jGraphRAGEngine.js +748 -0
- package/dist/rag/graphrag/Neo4jGraphRAGEngine.js.map +1 -0
- package/dist/rag/graphrag/index.d.ts +1 -0
- package/dist/rag/graphrag/index.d.ts.map +1 -1
- package/dist/rag/graphrag/index.js +1 -0
- package/dist/rag/graphrag/index.js.map +1 -1
- package/dist/rag/implementations/vector_stores/Neo4jVectorStore.d.ts +55 -0
- package/dist/rag/implementations/vector_stores/Neo4jVectorStore.d.ts.map +1 -0
- package/dist/rag/implementations/vector_stores/Neo4jVectorStore.js +369 -0
- package/dist/rag/implementations/vector_stores/Neo4jVectorStore.js.map +1 -0
- package/dist/rag/implementations/vector_stores/index.d.ts +1 -0
- package/dist/rag/implementations/vector_stores/index.d.ts.map +1 -1
- package/dist/rag/implementations/vector_stores/index.js +2 -0
- package/dist/rag/implementations/vector_stores/index.js.map +1 -1
- package/package.json +5 -1
|
@@ -19,6 +19,26 @@ import { DEFAULT_PROMPT_PROFILE_CONFIG, selectPromptProfile, } from '../core/pro
|
|
|
19
19
|
import { DEFAULT_ROLLING_SUMMARY_COMPACTION_CONFIG, maybeCompactConversationMessages, } from '../core/conversation/RollingSummaryCompactor.js';
|
|
20
20
|
import { DEFAULT_LONG_TERM_MEMORY_POLICY, hasAnyLongTermMemoryScope, LONG_TERM_MEMORY_POLICY_METADATA_KEY, resolveLongTermMemoryPolicy, } from '../core/conversation/LongTermMemoryPolicy.js';
|
|
21
21
|
import { getActiveTraceMetadata, recordAgentOSToolResultMetrics, recordAgentOSTurnMetrics, recordExceptionOnActiveSpan, runWithSpanContext, shouldIncludeTraceInAgentOSResponses, startAgentOSSpan, withAgentOSSpan, } from '../core/observability/otel.js';
|
|
22
|
+
const RECALL_PROFILE_DEFAULTS = {
|
|
23
|
+
aggressive: {
|
|
24
|
+
cadenceTurns: 2,
|
|
25
|
+
forceOnCompaction: true,
|
|
26
|
+
maxContextChars: 4200,
|
|
27
|
+
topKByScope: { user: 8, persona: 8, organization: 8 },
|
|
28
|
+
},
|
|
29
|
+
balanced: {
|
|
30
|
+
cadenceTurns: 4,
|
|
31
|
+
forceOnCompaction: true,
|
|
32
|
+
maxContextChars: 3200,
|
|
33
|
+
topKByScope: { user: 6, persona: 6, organization: 6 },
|
|
34
|
+
},
|
|
35
|
+
conservative: {
|
|
36
|
+
cadenceTurns: 8,
|
|
37
|
+
forceOnCompaction: false,
|
|
38
|
+
maxContextChars: 2200,
|
|
39
|
+
topKByScope: { user: 4, persona: 4, organization: 4 },
|
|
40
|
+
},
|
|
41
|
+
};
|
|
22
42
|
function normalizeMode(value) {
|
|
23
43
|
return (value || '').trim().toLowerCase();
|
|
24
44
|
}
|
|
@@ -62,6 +82,208 @@ function renderPlainText(markdown) {
|
|
|
62
82
|
text = text.replace(/\n{3,}/g, '\n\n');
|
|
63
83
|
return text.trim();
|
|
64
84
|
}
|
|
85
|
+
function normalizeTaskOutcomeOverride(customFlags) {
|
|
86
|
+
if (!customFlags)
|
|
87
|
+
return null;
|
|
88
|
+
const read = (keys) => {
|
|
89
|
+
for (const key of keys) {
|
|
90
|
+
if (Object.prototype.hasOwnProperty.call(customFlags, key))
|
|
91
|
+
return customFlags[key];
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
};
|
|
95
|
+
const raw = read(['taskOutcome', 'task_outcome', 'taskSuccess', 'task_success']);
|
|
96
|
+
if (typeof raw === 'boolean') {
|
|
97
|
+
return {
|
|
98
|
+
status: raw ? 'success' : 'failed',
|
|
99
|
+
score: raw ? 1 : 0,
|
|
100
|
+
reason: raw ? 'Caller marked task successful.' : 'Caller marked task failed.',
|
|
101
|
+
source: 'request_override',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (typeof raw === 'number' && Number.isFinite(raw)) {
|
|
105
|
+
const score = Math.max(0, Math.min(1, raw));
|
|
106
|
+
return {
|
|
107
|
+
status: score >= 0.8 ? 'success' : score >= 0.4 ? 'partial' : 'failed',
|
|
108
|
+
score,
|
|
109
|
+
reason: 'Caller supplied numeric task outcome score.',
|
|
110
|
+
source: 'request_override',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (typeof raw !== 'string' || !raw.trim())
|
|
114
|
+
return null;
|
|
115
|
+
const normalized = raw.trim().toLowerCase().replace(/\s+/g, '_').replace(/-/g, '_');
|
|
116
|
+
if (normalized === 'success' ||
|
|
117
|
+
normalized === 'succeeded' ||
|
|
118
|
+
normalized === 'done' ||
|
|
119
|
+
normalized === 'completed' ||
|
|
120
|
+
normalized === 'true') {
|
|
121
|
+
return {
|
|
122
|
+
status: 'success',
|
|
123
|
+
score: 1,
|
|
124
|
+
reason: 'Caller marked task successful.',
|
|
125
|
+
source: 'request_override',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (normalized === 'partial' ||
|
|
129
|
+
normalized === 'incomplete' ||
|
|
130
|
+
normalized === 'needs_followup') {
|
|
131
|
+
return {
|
|
132
|
+
status: 'partial',
|
|
133
|
+
score: 0.5,
|
|
134
|
+
reason: 'Caller marked task partially completed.',
|
|
135
|
+
source: 'request_override',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (normalized === 'failed' ||
|
|
139
|
+
normalized === 'failure' ||
|
|
140
|
+
normalized === 'error' ||
|
|
141
|
+
normalized === 'false') {
|
|
142
|
+
return {
|
|
143
|
+
status: 'failed',
|
|
144
|
+
score: 0,
|
|
145
|
+
reason: 'Caller marked task failed.',
|
|
146
|
+
source: 'request_override',
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
function normalizeRequestedToolFailureMode(customFlags) {
|
|
152
|
+
if (!customFlags)
|
|
153
|
+
return null;
|
|
154
|
+
const read = (keys) => {
|
|
155
|
+
for (const key of keys) {
|
|
156
|
+
if (Object.prototype.hasOwnProperty.call(customFlags, key))
|
|
157
|
+
return customFlags[key];
|
|
158
|
+
}
|
|
159
|
+
return undefined;
|
|
160
|
+
};
|
|
161
|
+
const raw = read(['toolFailureMode', 'tool_failure_mode', 'failureMode', 'failMode']);
|
|
162
|
+
if (typeof raw !== 'string')
|
|
163
|
+
return null;
|
|
164
|
+
const normalized = raw.trim().toLowerCase().replace(/\s+/g, '_').replace(/-/g, '_');
|
|
165
|
+
if (normalized === 'fail_open' || normalized === 'open')
|
|
166
|
+
return 'fail_open';
|
|
167
|
+
if (normalized === 'fail_closed' || normalized === 'closed')
|
|
168
|
+
return 'fail_closed';
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
function clampInteger(value, fallback, min, max) {
|
|
172
|
+
const num = Number(value);
|
|
173
|
+
if (!Number.isFinite(num))
|
|
174
|
+
return fallback;
|
|
175
|
+
return Math.max(min, Math.min(max, Math.trunc(num)));
|
|
176
|
+
}
|
|
177
|
+
function normalizeOrganizationId(value) {
|
|
178
|
+
if (typeof value !== 'string')
|
|
179
|
+
return undefined;
|
|
180
|
+
const trimmed = value.trim();
|
|
181
|
+
return trimmed || undefined;
|
|
182
|
+
}
|
|
183
|
+
function resolveLongTermMemoryRecallConfig(config) {
|
|
184
|
+
const profile = config?.profile === 'balanced' || config?.profile === 'conservative'
|
|
185
|
+
? config.profile
|
|
186
|
+
: 'aggressive';
|
|
187
|
+
const defaults = RECALL_PROFILE_DEFAULTS[profile];
|
|
188
|
+
return {
|
|
189
|
+
profile,
|
|
190
|
+
cadenceTurns: clampInteger(config?.cadenceTurns, defaults.cadenceTurns, 1, 100),
|
|
191
|
+
forceOnCompaction: typeof config?.forceOnCompaction === 'boolean'
|
|
192
|
+
? config.forceOnCompaction
|
|
193
|
+
: defaults.forceOnCompaction,
|
|
194
|
+
maxContextChars: clampInteger(config?.maxContextChars, defaults.maxContextChars, 300, 12000),
|
|
195
|
+
topKByScope: {
|
|
196
|
+
user: clampInteger(config?.topKByScope?.user, defaults.topKByScope.user, 1, 50),
|
|
197
|
+
persona: clampInteger(config?.topKByScope?.persona, defaults.topKByScope.persona, 1, 50),
|
|
198
|
+
organization: clampInteger(config?.topKByScope?.organization, defaults.topKByScope.organization, 1, 50),
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function resolveTenantRoutingConfig(config) {
|
|
203
|
+
return {
|
|
204
|
+
mode: config?.mode === 'single_tenant' ? 'single_tenant' : 'multi_tenant',
|
|
205
|
+
defaultOrganizationId: normalizeOrganizationId(config?.defaultOrganizationId),
|
|
206
|
+
strictOrganizationIsolation: Boolean(config?.strictOrganizationIsolation),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function resolveTaskOutcomeTelemetryConfig(config) {
|
|
210
|
+
const scope = config?.scope === 'global' || config?.scope === 'organization'
|
|
211
|
+
? config.scope
|
|
212
|
+
: 'organization_persona';
|
|
213
|
+
return {
|
|
214
|
+
enabled: config?.enabled !== false,
|
|
215
|
+
rollingWindowSize: clampInteger(config?.rollingWindowSize, 100, 5, 5000),
|
|
216
|
+
scope,
|
|
217
|
+
emitAlerts: config?.emitAlerts !== false,
|
|
218
|
+
alertBelowWeightedSuccessRate: Math.max(0, Math.min(1, Number(config?.alertBelowWeightedSuccessRate ?? 0.55))),
|
|
219
|
+
alertMinSamples: clampInteger(config?.alertMinSamples, 8, 1, 10000),
|
|
220
|
+
alertCooldownMs: clampInteger(config?.alertCooldownMs, 60000, 0, 86400000),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function resolveAdaptiveExecutionConfig(config) {
|
|
224
|
+
return {
|
|
225
|
+
enabled: config?.enabled !== false,
|
|
226
|
+
minSamples: clampInteger(config?.minSamples, 5, 1, 1000),
|
|
227
|
+
minWeightedSuccessRate: Math.max(0, Math.min(1, Number(config?.minWeightedSuccessRate ?? 0.7))),
|
|
228
|
+
forceAllToolsWhenDegraded: config?.forceAllToolsWhenDegraded !== false,
|
|
229
|
+
forceFailOpenWhenDegraded: config?.forceFailOpenWhenDegraded !== false,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function sanitizeKpiEntry(raw) {
|
|
233
|
+
const status = raw?.status;
|
|
234
|
+
const validStatus = status === 'success' || status === 'partial' || status === 'failed' ? status : null;
|
|
235
|
+
if (!validStatus)
|
|
236
|
+
return null;
|
|
237
|
+
const scoreNum = Number(raw?.score);
|
|
238
|
+
const timestampNum = Number(raw?.timestamp);
|
|
239
|
+
if (!Number.isFinite(scoreNum) || !Number.isFinite(timestampNum))
|
|
240
|
+
return null;
|
|
241
|
+
return {
|
|
242
|
+
status: validStatus,
|
|
243
|
+
score: Math.max(0, Math.min(1, scoreNum)),
|
|
244
|
+
timestamp: Math.max(0, Math.trunc(timestampNum)),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function evaluateTaskOutcome(args) {
|
|
248
|
+
const override = normalizeTaskOutcomeOverride(args.customFlags);
|
|
249
|
+
if (override)
|
|
250
|
+
return override;
|
|
251
|
+
if (args.didForceTerminate || args.finalOutput.error) {
|
|
252
|
+
return {
|
|
253
|
+
status: 'failed',
|
|
254
|
+
score: 0,
|
|
255
|
+
reason: args.didForceTerminate
|
|
256
|
+
? 'Turn force-terminated due to iteration cap.'
|
|
257
|
+
: 'Final response contains an error payload.',
|
|
258
|
+
source: 'heuristic',
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const text = typeof args.finalOutput.responseText === 'string'
|
|
262
|
+
? args.finalOutput.responseText.trim()
|
|
263
|
+
: '';
|
|
264
|
+
if (text.length >= 48) {
|
|
265
|
+
return {
|
|
266
|
+
status: 'success',
|
|
267
|
+
score: args.degraded ? 0.85 : 0.95,
|
|
268
|
+
reason: 'Final response was produced without terminal errors.',
|
|
269
|
+
source: 'heuristic',
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
if (text.length > 0 || (args.finalOutput.toolCalls?.length ?? 0) > 0) {
|
|
273
|
+
return {
|
|
274
|
+
status: 'partial',
|
|
275
|
+
score: args.degraded ? 0.5 : 0.6,
|
|
276
|
+
reason: 'Turn completed but produced a limited final response.',
|
|
277
|
+
source: 'heuristic',
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
status: 'failed',
|
|
282
|
+
score: 0.1,
|
|
283
|
+
reason: 'No usable final response was produced.',
|
|
284
|
+
source: 'heuristic',
|
|
285
|
+
};
|
|
286
|
+
}
|
|
65
287
|
/**
|
|
66
288
|
* @class AgentOSOrchestrator
|
|
67
289
|
* @description
|
|
@@ -74,6 +296,8 @@ function renderPlainText(markdown) {
|
|
|
74
296
|
export class AgentOSOrchestrator {
|
|
75
297
|
constructor() {
|
|
76
298
|
this.initialized = false;
|
|
299
|
+
this.taskOutcomeKpiWindows = new Map();
|
|
300
|
+
this.taskOutcomeAlertState = new Map();
|
|
77
301
|
/**
|
|
78
302
|
* A map to hold ongoing stream contexts.
|
|
79
303
|
* Key: streamId (generated by orchestrator for this interaction flow).
|
|
@@ -114,8 +338,35 @@ export class AgentOSOrchestrator {
|
|
|
114
338
|
rollingSummaryCompactionProfilesConfig: config.rollingSummaryCompactionProfilesConfig ?? null,
|
|
115
339
|
rollingSummarySystemPrompt: config.rollingSummarySystemPrompt ?? '',
|
|
116
340
|
rollingSummaryStateKey: config.rollingSummaryStateKey ?? 'rollingSummaryState',
|
|
341
|
+
longTermMemoryRecall: resolveLongTermMemoryRecallConfig(config.longTermMemoryRecall),
|
|
342
|
+
tenantRouting: resolveTenantRoutingConfig(config.tenantRouting),
|
|
343
|
+
taskOutcomeTelemetry: resolveTaskOutcomeTelemetryConfig(config.taskOutcomeTelemetry),
|
|
344
|
+
adaptiveExecution: resolveAdaptiveExecutionConfig(config.adaptiveExecution),
|
|
117
345
|
};
|
|
346
|
+
this.taskOutcomeKpiWindows.clear();
|
|
347
|
+
this.taskOutcomeAlertState.clear();
|
|
118
348
|
this.dependencies = dependencies;
|
|
349
|
+
if (dependencies.taskOutcomeTelemetryStore && this.config.taskOutcomeTelemetry.enabled) {
|
|
350
|
+
try {
|
|
351
|
+
const persisted = await dependencies.taskOutcomeTelemetryStore.loadWindows();
|
|
352
|
+
const cap = this.config.taskOutcomeTelemetry.rollingWindowSize;
|
|
353
|
+
for (const [scopeKey, rawEntries] of Object.entries(persisted ?? {})) {
|
|
354
|
+
if (!Array.isArray(rawEntries))
|
|
355
|
+
continue;
|
|
356
|
+
const normalized = rawEntries
|
|
357
|
+
.map((entry) => sanitizeKpiEntry(entry))
|
|
358
|
+
.filter((entry) => Boolean(entry))
|
|
359
|
+
.sort((a, b) => a.timestamp - b.timestamp);
|
|
360
|
+
if (normalized.length === 0)
|
|
361
|
+
continue;
|
|
362
|
+
const trimmed = normalized.slice(Math.max(0, normalized.length - cap));
|
|
363
|
+
this.taskOutcomeKpiWindows.set(scopeKey, trimmed);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
console.warn('AgentOSOrchestrator: Failed to load persisted task outcome telemetry windows; continuing with empty windows.', error);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
119
370
|
this.initialized = true;
|
|
120
371
|
console.log('AgentOSOrchestrator initialized.');
|
|
121
372
|
}
|
|
@@ -129,6 +380,209 @@ export class AgentOSOrchestrator {
|
|
|
129
380
|
throw new GMIError('AgentOSOrchestrator is not initialized. Call initialize() first.', GMIErrorCode.NOT_INITIALIZED);
|
|
130
381
|
}
|
|
131
382
|
}
|
|
383
|
+
resolveOrganizationContext(inputOrganizationId) {
|
|
384
|
+
const inbound = normalizeOrganizationId(inputOrganizationId);
|
|
385
|
+
const tenantConfig = this.config.tenantRouting;
|
|
386
|
+
if (tenantConfig.mode === 'single_tenant') {
|
|
387
|
+
const fallback = tenantConfig.defaultOrganizationId;
|
|
388
|
+
if (tenantConfig.strictOrganizationIsolation &&
|
|
389
|
+
inbound &&
|
|
390
|
+
fallback &&
|
|
391
|
+
inbound !== fallback) {
|
|
392
|
+
throw new GMIError(`organizationId '${inbound}' does not match configured single-tenant org '${fallback}'.`, GMIErrorCode.VALIDATION_ERROR, {
|
|
393
|
+
mode: tenantConfig.mode,
|
|
394
|
+
inboundOrganizationId: inbound,
|
|
395
|
+
configuredOrganizationId: fallback,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
const resolved = inbound ?? fallback;
|
|
399
|
+
if (tenantConfig.strictOrganizationIsolation && !resolved) {
|
|
400
|
+
throw new GMIError('Single-tenant mode requires an organizationId or tenantRouting.defaultOrganizationId.', GMIErrorCode.VALIDATION_ERROR, { mode: tenantConfig.mode });
|
|
401
|
+
}
|
|
402
|
+
return resolved;
|
|
403
|
+
}
|
|
404
|
+
return inbound;
|
|
405
|
+
}
|
|
406
|
+
resolveTaskOutcomeScopeKey(args) {
|
|
407
|
+
const scope = this.config.taskOutcomeTelemetry.scope;
|
|
408
|
+
const org = normalizeOrganizationId(args.organizationId) ?? 'none';
|
|
409
|
+
const persona = normalizeOrganizationId(args.personaId) ?? 'unknown';
|
|
410
|
+
if (scope === 'global')
|
|
411
|
+
return 'global';
|
|
412
|
+
if (scope === 'organization')
|
|
413
|
+
return `org:${org}`;
|
|
414
|
+
return `org:${org}|persona:${persona}`;
|
|
415
|
+
}
|
|
416
|
+
updateTaskOutcomeKpi(args) {
|
|
417
|
+
const telemetry = this.config.taskOutcomeTelemetry;
|
|
418
|
+
if (!telemetry.enabled)
|
|
419
|
+
return null;
|
|
420
|
+
const scopeKey = this.resolveTaskOutcomeScopeKey({
|
|
421
|
+
organizationId: args.organizationId,
|
|
422
|
+
personaId: args.personaId,
|
|
423
|
+
});
|
|
424
|
+
const now = Date.now();
|
|
425
|
+
const window = this.taskOutcomeKpiWindows.get(scopeKey) ?? [];
|
|
426
|
+
window.push({
|
|
427
|
+
status: args.outcome.status,
|
|
428
|
+
score: Math.max(0, Math.min(1, Number(args.outcome.score) || 0)),
|
|
429
|
+
timestamp: now,
|
|
430
|
+
});
|
|
431
|
+
const cap = telemetry.rollingWindowSize;
|
|
432
|
+
if (window.length > cap) {
|
|
433
|
+
window.splice(0, window.length - cap);
|
|
434
|
+
}
|
|
435
|
+
this.taskOutcomeKpiWindows.set(scopeKey, window);
|
|
436
|
+
if (this.dependencies.taskOutcomeTelemetryStore) {
|
|
437
|
+
const snapshot = window.map((entry) => ({ ...entry }));
|
|
438
|
+
void this.dependencies.taskOutcomeTelemetryStore
|
|
439
|
+
.saveWindow(scopeKey, snapshot)
|
|
440
|
+
.catch((error) => {
|
|
441
|
+
console.warn(`AgentOSOrchestrator: Failed to persist task outcome telemetry window for scope '${scopeKey}'.`, error);
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
return this.summarizeTaskOutcomeWindow(scopeKey);
|
|
445
|
+
}
|
|
446
|
+
getCurrentTaskOutcomeKpi(args) {
|
|
447
|
+
if (!this.config.taskOutcomeTelemetry.enabled)
|
|
448
|
+
return null;
|
|
449
|
+
const scopeKey = this.resolveTaskOutcomeScopeKey({
|
|
450
|
+
organizationId: args.organizationId,
|
|
451
|
+
personaId: args.personaId,
|
|
452
|
+
});
|
|
453
|
+
return this.summarizeTaskOutcomeWindow(scopeKey);
|
|
454
|
+
}
|
|
455
|
+
summarizeTaskOutcomeWindow(scopeKey) {
|
|
456
|
+
const telemetry = this.config.taskOutcomeTelemetry;
|
|
457
|
+
const window = this.taskOutcomeKpiWindows.get(scopeKey) ?? [];
|
|
458
|
+
if (window.length === 0)
|
|
459
|
+
return null;
|
|
460
|
+
const now = Date.now();
|
|
461
|
+
let successCount = 0;
|
|
462
|
+
let partialCount = 0;
|
|
463
|
+
let failedCount = 0;
|
|
464
|
+
let scoreSum = 0;
|
|
465
|
+
for (const entry of window) {
|
|
466
|
+
if (entry.status === 'success')
|
|
467
|
+
successCount += 1;
|
|
468
|
+
else if (entry.status === 'partial')
|
|
469
|
+
partialCount += 1;
|
|
470
|
+
else
|
|
471
|
+
failedCount += 1;
|
|
472
|
+
scoreSum += entry.score;
|
|
473
|
+
}
|
|
474
|
+
const sampleCount = window.length;
|
|
475
|
+
const successRate = sampleCount > 0 ? successCount / sampleCount : 0;
|
|
476
|
+
const averageScore = sampleCount > 0 ? scoreSum / sampleCount : 0;
|
|
477
|
+
return {
|
|
478
|
+
scopeKey,
|
|
479
|
+
scopeMode: telemetry.scope,
|
|
480
|
+
windowSize: telemetry.rollingWindowSize,
|
|
481
|
+
sampleCount,
|
|
482
|
+
successCount,
|
|
483
|
+
partialCount,
|
|
484
|
+
failedCount,
|
|
485
|
+
successRate,
|
|
486
|
+
averageScore,
|
|
487
|
+
weightedSuccessRate: averageScore,
|
|
488
|
+
timestamp: new Date(now).toISOString(),
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
maybeApplyAdaptiveExecutionPolicy(args) {
|
|
492
|
+
const adaptive = this.config.adaptiveExecution;
|
|
493
|
+
if (!adaptive.enabled || !args.turnPlan)
|
|
494
|
+
return { applied: false };
|
|
495
|
+
const kpi = this.getCurrentTaskOutcomeKpi({
|
|
496
|
+
organizationId: args.organizationId,
|
|
497
|
+
personaId: args.personaId,
|
|
498
|
+
});
|
|
499
|
+
if (!kpi)
|
|
500
|
+
return { applied: false, kpi };
|
|
501
|
+
if (kpi.sampleCount < adaptive.minSamples)
|
|
502
|
+
return { applied: false, kpi };
|
|
503
|
+
if (kpi.weightedSuccessRate >= adaptive.minWeightedSuccessRate)
|
|
504
|
+
return { applied: false, kpi };
|
|
505
|
+
const reasons = [
|
|
506
|
+
`weightedSuccessRate=${kpi.weightedSuccessRate.toFixed(3)} below threshold=${adaptive.minWeightedSuccessRate.toFixed(3)}`,
|
|
507
|
+
];
|
|
508
|
+
let forcedToolSelectionMode = false;
|
|
509
|
+
let forcedToolFailureMode = false;
|
|
510
|
+
let preservedRequestedFailClosed = false;
|
|
511
|
+
if (adaptive.forceAllToolsWhenDegraded &&
|
|
512
|
+
args.turnPlan.policy.toolSelectionMode === 'discovered') {
|
|
513
|
+
args.turnPlan.policy.toolSelectionMode = 'all';
|
|
514
|
+
forcedToolSelectionMode = true;
|
|
515
|
+
reasons.push('toolSelectionMode switched discovered -> all');
|
|
516
|
+
}
|
|
517
|
+
if (adaptive.forceFailOpenWhenDegraded && args.turnPlan.policy.toolFailureMode !== 'fail_open') {
|
|
518
|
+
const requestedFailureMode = normalizeRequestedToolFailureMode(args.requestCustomFlags);
|
|
519
|
+
if (requestedFailureMode === 'fail_closed') {
|
|
520
|
+
preservedRequestedFailClosed = true;
|
|
521
|
+
reasons.push('preserved explicit request override toolFailureMode=fail_closed');
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
const before = args.turnPlan.policy.toolFailureMode;
|
|
525
|
+
args.turnPlan.policy.toolFailureMode = 'fail_open';
|
|
526
|
+
forcedToolFailureMode = true;
|
|
527
|
+
reasons.push(`toolFailureMode switched ${before} -> fail_open`);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (!forcedToolSelectionMode && !forcedToolFailureMode) {
|
|
531
|
+
return {
|
|
532
|
+
applied: false,
|
|
533
|
+
reason: preservedRequestedFailClosed
|
|
534
|
+
? 'Adaptive execution detected degraded KPI but preserved explicit fail-closed request override.'
|
|
535
|
+
: undefined,
|
|
536
|
+
kpi,
|
|
537
|
+
actions: preservedRequestedFailClosed
|
|
538
|
+
? {
|
|
539
|
+
preservedRequestedFailClosed: true,
|
|
540
|
+
}
|
|
541
|
+
: undefined,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
args.turnPlan.capability.fallbackApplied = true;
|
|
545
|
+
args.turnPlan.capability.fallbackReason = `Adaptive fallback applied: ${reasons.join('; ')}.`;
|
|
546
|
+
args.turnPlan.diagnostics.usedFallback = true;
|
|
547
|
+
return {
|
|
548
|
+
applied: true,
|
|
549
|
+
reason: args.turnPlan.capability.fallbackReason,
|
|
550
|
+
kpi,
|
|
551
|
+
actions: {
|
|
552
|
+
forcedToolSelectionMode,
|
|
553
|
+
forcedToolFailureMode,
|
|
554
|
+
preservedRequestedFailClosed: preservedRequestedFailClosed || undefined,
|
|
555
|
+
},
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
maybeBuildTaskOutcomeAlert(kpi) {
|
|
559
|
+
const telemetry = this.config.taskOutcomeTelemetry;
|
|
560
|
+
if (!telemetry.enabled || !telemetry.emitAlerts || !kpi)
|
|
561
|
+
return null;
|
|
562
|
+
if (kpi.sampleCount < telemetry.alertMinSamples)
|
|
563
|
+
return null;
|
|
564
|
+
if (kpi.weightedSuccessRate >= telemetry.alertBelowWeightedSuccessRate)
|
|
565
|
+
return null;
|
|
566
|
+
const now = Date.now();
|
|
567
|
+
const lastAlertAt = this.taskOutcomeAlertState.get(kpi.scopeKey) ?? 0;
|
|
568
|
+
if (telemetry.alertCooldownMs > 0 && now - lastAlertAt < telemetry.alertCooldownMs) {
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
this.taskOutcomeAlertState.set(kpi.scopeKey, now);
|
|
572
|
+
const severity = kpi.weightedSuccessRate < telemetry.alertBelowWeightedSuccessRate * 0.6
|
|
573
|
+
? 'critical'
|
|
574
|
+
: 'warning';
|
|
575
|
+
return {
|
|
576
|
+
scopeKey: kpi.scopeKey,
|
|
577
|
+
severity,
|
|
578
|
+
reason: `Weighted success rate ${kpi.weightedSuccessRate.toFixed(3)} below alert threshold ` +
|
|
579
|
+
`${telemetry.alertBelowWeightedSuccessRate.toFixed(3)}.`,
|
|
580
|
+
threshold: telemetry.alertBelowWeightedSuccessRate,
|
|
581
|
+
value: kpi.weightedSuccessRate,
|
|
582
|
+
sampleCount: kpi.sampleCount,
|
|
583
|
+
timestamp: new Date(now).toISOString(),
|
|
584
|
+
};
|
|
585
|
+
}
|
|
132
586
|
/**
|
|
133
587
|
* Helper method to create and push response chunks via StreamingManager.
|
|
134
588
|
* @private
|
|
@@ -286,6 +740,18 @@ export class AgentOSOrchestrator {
|
|
|
286
740
|
await this.pushChunkToStream(streamId, AgentOSResponseChunkType.ERROR, gmiInstanceId, personaId, true, // Errors are usually final for the current operation
|
|
287
741
|
{ code: code.toString(), message, details });
|
|
288
742
|
}
|
|
743
|
+
async emitExecutionLifecycleUpdate(args) {
|
|
744
|
+
await this.pushChunkToStream(args.streamId, AgentOSResponseChunkType.METADATA_UPDATE, args.gmiInstanceId, args.personaId, false, {
|
|
745
|
+
updates: {
|
|
746
|
+
executionLifecycle: {
|
|
747
|
+
phase: args.phase,
|
|
748
|
+
status: args.status,
|
|
749
|
+
timestamp: new Date().toISOString(),
|
|
750
|
+
...(args.details ? { details: args.details } : null),
|
|
751
|
+
},
|
|
752
|
+
},
|
|
753
|
+
});
|
|
754
|
+
}
|
|
289
755
|
/**
|
|
290
756
|
* Orchestrates a full logical turn for a user request.
|
|
291
757
|
* This involves managing GMI interaction, tool calls, and streaming responses.
|
|
@@ -352,6 +818,7 @@ export class AgentOSOrchestrator {
|
|
|
352
818
|
const turnStartedAt = Date.now();
|
|
353
819
|
let turnMetricsStatus = 'ok';
|
|
354
820
|
let turnMetricsPersonaId = input.selectedPersonaId;
|
|
821
|
+
let turnMetricsTaskOutcome;
|
|
355
822
|
let turnMetricsUsage;
|
|
356
823
|
const selectedPersonaId = input.selectedPersonaId;
|
|
357
824
|
let gmi;
|
|
@@ -361,6 +828,7 @@ export class AgentOSOrchestrator {
|
|
|
361
828
|
let organizationIdForMemory;
|
|
362
829
|
let longTermMemoryPolicy = null;
|
|
363
830
|
let didForceTerminate = false;
|
|
831
|
+
let lifecycleDegraded = false;
|
|
364
832
|
try {
|
|
365
833
|
if (!selectedPersonaId) {
|
|
366
834
|
throw new GMIError('AgentOSOrchestrator requires a selectedPersonaId on AgentOSInput.', GMIErrorCode.VALIDATION_ERROR);
|
|
@@ -389,12 +857,78 @@ export class AgentOSOrchestrator {
|
|
|
389
857
|
this.activeStreamContexts.set(agentOSStreamId, streamContext);
|
|
390
858
|
await this.pushChunkToStream(agentOSStreamId, AgentOSResponseChunkType.SYSTEM_PROGRESS, gmiInstanceIdForChunks, currentPersonaId, false, { message: `Initializing persona ${currentPersonaId}... GMI: ${gmiInstanceIdForChunks}`, progressPercentage: 10 });
|
|
391
859
|
const gmiInput = this.constructGMITurnInput(agentOSStreamId, input, streamContext);
|
|
860
|
+
let turnPlan = null;
|
|
861
|
+
const resolvedOrganizationId = this.resolveOrganizationContext(input.organizationId);
|
|
862
|
+
if (this.dependencies.turnPlanner) {
|
|
863
|
+
const planningMessage = gmiInput.type === GMIInteractionType.TEXT && typeof gmiInput.content === 'string'
|
|
864
|
+
? gmiInput.content
|
|
865
|
+
: gmiInput.type === GMIInteractionType.MULTIMODAL_CONTENT
|
|
866
|
+
? JSON.stringify(gmiInput.content)
|
|
867
|
+
: '';
|
|
868
|
+
try {
|
|
869
|
+
turnPlan = await this.dependencies.turnPlanner.planTurn({
|
|
870
|
+
userId: input.userId,
|
|
871
|
+
organizationId: resolvedOrganizationId,
|
|
872
|
+
sessionId: input.sessionId,
|
|
873
|
+
conversationId: input.conversationId,
|
|
874
|
+
persona: gmi.getPersona(),
|
|
875
|
+
userMessage: planningMessage,
|
|
876
|
+
options: input.options,
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
catch (planningError) {
|
|
880
|
+
throw new GMIError(`Turn planning failed before execution: ${planningError?.message || String(planningError)}`, GMIErrorCode.PROCESSING_ERROR, { streamId: agentOSStreamId, planningError });
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
const adaptiveExecution = this.maybeApplyAdaptiveExecutionPolicy({
|
|
884
|
+
turnPlan,
|
|
885
|
+
organizationId: resolvedOrganizationId,
|
|
886
|
+
personaId: currentPersonaId,
|
|
887
|
+
requestCustomFlags: input.options?.customFlags,
|
|
888
|
+
});
|
|
889
|
+
const adaptiveExecutionPayload = adaptiveExecution.applied || adaptiveExecution.kpi || adaptiveExecution.actions
|
|
890
|
+
? {
|
|
891
|
+
applied: adaptiveExecution.applied,
|
|
892
|
+
reason: adaptiveExecution.reason,
|
|
893
|
+
kpi: adaptiveExecution.kpi,
|
|
894
|
+
actions: adaptiveExecution.actions,
|
|
895
|
+
}
|
|
896
|
+
: undefined;
|
|
897
|
+
await this.emitExecutionLifecycleUpdate({
|
|
898
|
+
streamId: agentOSStreamId,
|
|
899
|
+
gmiInstanceId: gmiInstanceIdForChunks,
|
|
900
|
+
personaId: currentPersonaId,
|
|
901
|
+
phase: 'planned',
|
|
902
|
+
status: 'ok',
|
|
903
|
+
details: turnPlan
|
|
904
|
+
? {
|
|
905
|
+
plannerVersion: turnPlan.policy.plannerVersion,
|
|
906
|
+
toolFailureMode: turnPlan.policy.toolFailureMode,
|
|
907
|
+
toolSelectionMode: turnPlan.policy.toolSelectionMode,
|
|
908
|
+
adaptiveExecution: adaptiveExecutionPayload,
|
|
909
|
+
}
|
|
910
|
+
: { plannerVersion: 'none' },
|
|
911
|
+
});
|
|
912
|
+
if (turnPlan?.capability.fallbackApplied || adaptiveExecution.applied) {
|
|
913
|
+
lifecycleDegraded = true;
|
|
914
|
+
await this.emitExecutionLifecycleUpdate({
|
|
915
|
+
streamId: agentOSStreamId,
|
|
916
|
+
gmiInstanceId: gmiInstanceIdForChunks,
|
|
917
|
+
personaId: currentPersonaId,
|
|
918
|
+
phase: 'degraded',
|
|
919
|
+
status: 'degraded',
|
|
920
|
+
details: {
|
|
921
|
+
reason: turnPlan?.capability.fallbackReason || adaptiveExecution.reason || 'fallback applied',
|
|
922
|
+
discoveryAttempts: turnPlan?.diagnostics.discoveryAttempts,
|
|
923
|
+
adaptiveExecution: adaptiveExecutionPayload,
|
|
924
|
+
},
|
|
925
|
+
});
|
|
926
|
+
}
|
|
392
927
|
// --- Org context + long-term memory policy (persisted per conversation) ---
|
|
928
|
+
organizationIdForMemory = resolvedOrganizationId;
|
|
393
929
|
if (conversationContext) {
|
|
394
|
-
const inboundOrg = typeof input.organizationId === 'string' ? input.organizationId.trim() : '';
|
|
395
930
|
// SECURITY NOTE: do not persist organizationId in conversation metadata. The org context
|
|
396
931
|
// should be asserted by the trusted caller each request (after membership checks).
|
|
397
|
-
organizationIdForMemory = inboundOrg || undefined;
|
|
398
932
|
const rawPrevPolicy = conversationContext.getMetadata(LONG_TERM_MEMORY_POLICY_METADATA_KEY);
|
|
399
933
|
const prevPolicy = rawPrevPolicy && typeof rawPrevPolicy === 'object'
|
|
400
934
|
? rawPrevPolicy
|
|
@@ -411,12 +945,14 @@ export class AgentOSOrchestrator {
|
|
|
411
945
|
}
|
|
412
946
|
}
|
|
413
947
|
else {
|
|
414
|
-
organizationIdForMemory =
|
|
415
|
-
typeof input.organizationId === 'string' ? input.organizationId.trim() : undefined;
|
|
416
948
|
longTermMemoryPolicy = resolveLongTermMemoryPolicy({
|
|
417
949
|
defaults: DEFAULT_LONG_TERM_MEMORY_POLICY,
|
|
418
950
|
});
|
|
419
951
|
}
|
|
952
|
+
if (turnPlan) {
|
|
953
|
+
(gmiInput.metadata ?? (gmiInput.metadata = {})).executionPolicy = turnPlan.policy;
|
|
954
|
+
gmiInput.metadata.capabilityDiscovery = turnPlan.capability;
|
|
955
|
+
}
|
|
420
956
|
(gmiInput.metadata ?? (gmiInput.metadata = {})).organizationId = organizationIdForMemory ?? null;
|
|
421
957
|
gmiInput.metadata.longTermMemoryPolicy = longTermMemoryPolicy;
|
|
422
958
|
// Persist inbound user/system message to ConversationContext BEFORE any LLM call so persona switches
|
|
@@ -571,6 +1107,8 @@ export class AgentOSOrchestrator {
|
|
|
571
1107
|
// --- Long-term memory retrieval (user/persona/org) ---
|
|
572
1108
|
let longTermMemoryContextText = null;
|
|
573
1109
|
let longTermMemoryRetrievalDiagnostics;
|
|
1110
|
+
let longTermMemoryShouldReview = false;
|
|
1111
|
+
let longTermMemoryReviewReason = null;
|
|
574
1112
|
if (conversationContext &&
|
|
575
1113
|
this.dependencies.longTermMemoryRetriever &&
|
|
576
1114
|
Boolean(longTermMemoryPolicy?.enabled) &&
|
|
@@ -584,21 +1122,32 @@ export class AgentOSOrchestrator {
|
|
|
584
1122
|
? JSON.stringify(gmiInput.content).trim()
|
|
585
1123
|
: '';
|
|
586
1124
|
const userTurnCount = conversationContext.getAllMessages().filter((m) => m?.role === MessageRole.USER).length;
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const forceOnCompaction = typeof this.config.promptProfileConfig?.routing?.forceReviewOnCompaction === 'boolean'
|
|
591
|
-
? Boolean(this.config.promptProfileConfig.routing.forceReviewOnCompaction)
|
|
592
|
-
: true;
|
|
1125
|
+
const recallConfig = this.config.longTermMemoryRecall;
|
|
1126
|
+
const cadenceTurns = recallConfig.cadenceTurns;
|
|
1127
|
+
const forceOnCompaction = recallConfig.forceOnCompaction;
|
|
593
1128
|
const rawState = conversationContext.getMetadata('longTermMemoryRetrievalState');
|
|
594
1129
|
const prevState = rawState &&
|
|
595
1130
|
typeof rawState === 'object' &&
|
|
596
1131
|
typeof rawState.lastReviewedUserTurn === 'number'
|
|
597
1132
|
? rawState
|
|
598
1133
|
: null;
|
|
599
|
-
const
|
|
600
|
-
(
|
|
601
|
-
|
|
1134
|
+
const turnsSinceReview = prevState
|
|
1135
|
+
? Math.max(0, userTurnCount - prevState.lastReviewedUserTurn)
|
|
1136
|
+
: Number.POSITIVE_INFINITY;
|
|
1137
|
+
const dueToCadence = !prevState || turnsSinceReview >= cadenceTurns;
|
|
1138
|
+
const dueToCompaction = forceOnCompaction && Boolean(rollingSummaryResult?.didCompact);
|
|
1139
|
+
const shouldReview = dueToCadence || dueToCompaction;
|
|
1140
|
+
longTermMemoryShouldReview = shouldReview;
|
|
1141
|
+
if (shouldReview) {
|
|
1142
|
+
longTermMemoryReviewReason = !prevState
|
|
1143
|
+
? 'initial_review'
|
|
1144
|
+
: dueToCompaction
|
|
1145
|
+
? 'forced_on_compaction'
|
|
1146
|
+
: 'cadence_due';
|
|
1147
|
+
}
|
|
1148
|
+
else {
|
|
1149
|
+
longTermMemoryReviewReason = 'cadence_not_due';
|
|
1150
|
+
}
|
|
602
1151
|
if (shouldReview && queryText.length > 0) {
|
|
603
1152
|
const retrievalResult = await this.dependencies.longTermMemoryRetriever.retrieveLongTermMemory({
|
|
604
1153
|
userId: streamContext.userId,
|
|
@@ -608,8 +1157,8 @@ export class AgentOSOrchestrator {
|
|
|
608
1157
|
mode: modeForRouting,
|
|
609
1158
|
queryText,
|
|
610
1159
|
memoryPolicy: longTermMemoryPolicy ?? DEFAULT_LONG_TERM_MEMORY_POLICY,
|
|
611
|
-
maxContextChars:
|
|
612
|
-
topKByScope:
|
|
1160
|
+
maxContextChars: recallConfig.maxContextChars,
|
|
1161
|
+
topKByScope: recallConfig.topKByScope,
|
|
613
1162
|
});
|
|
614
1163
|
if (retrievalResult?.contextText && retrievalResult.contextText.trim()) {
|
|
615
1164
|
longTermMemoryContextText = retrievalResult.contextText.trim();
|
|
@@ -620,11 +1169,18 @@ export class AgentOSOrchestrator {
|
|
|
620
1169
|
lastReviewedAt: Date.now(),
|
|
621
1170
|
});
|
|
622
1171
|
}
|
|
1172
|
+
else if (shouldReview && queryText.length === 0) {
|
|
1173
|
+
longTermMemoryReviewReason = 'empty_query';
|
|
1174
|
+
}
|
|
623
1175
|
}
|
|
624
1176
|
catch (retrievalError) {
|
|
625
1177
|
console.warn(`AgentOSOrchestrator: Long-term memory retrieval failed for stream ${agentOSStreamId} (continuing without it).`, retrievalError);
|
|
1178
|
+
longTermMemoryReviewReason = 'retrieval_error';
|
|
626
1179
|
}
|
|
627
1180
|
}
|
|
1181
|
+
else {
|
|
1182
|
+
longTermMemoryReviewReason = 'retriever_not_applicable';
|
|
1183
|
+
}
|
|
628
1184
|
gmiInput.metadata.longTermMemoryContext =
|
|
629
1185
|
typeof longTermMemoryContextText === 'string' && longTermMemoryContextText.length > 0
|
|
630
1186
|
? longTermMemoryContextText
|
|
@@ -717,14 +1273,44 @@ export class AgentOSOrchestrator {
|
|
|
717
1273
|
updates: {
|
|
718
1274
|
promptProfile: promptProfileSelection,
|
|
719
1275
|
organizationId: organizationIdForMemory ?? null,
|
|
1276
|
+
tenantRouting: {
|
|
1277
|
+
mode: this.config.tenantRouting.mode,
|
|
1278
|
+
strictOrganizationIsolation: this.config.tenantRouting.strictOrganizationIsolation,
|
|
1279
|
+
defaultOrganizationId: this.config.tenantRouting.defaultOrganizationId ?? null,
|
|
1280
|
+
},
|
|
720
1281
|
longTermMemoryPolicy,
|
|
1282
|
+
longTermMemoryRecall: this.config.longTermMemoryRecall,
|
|
1283
|
+
taskOutcomeTelemetry: this.config.taskOutcomeTelemetry,
|
|
1284
|
+
adaptiveExecution: this.config.adaptiveExecution,
|
|
1285
|
+
turnPlanning: turnPlan
|
|
1286
|
+
? {
|
|
1287
|
+
policy: turnPlan.policy,
|
|
1288
|
+
diagnostics: turnPlan.diagnostics,
|
|
1289
|
+
adaptiveExecution: adaptiveExecutionPayload ?? null,
|
|
1290
|
+
discovery: {
|
|
1291
|
+
enabled: turnPlan.capability.enabled,
|
|
1292
|
+
kind: turnPlan.capability.kind,
|
|
1293
|
+
selectedToolNames: turnPlan.capability.selectedToolNames,
|
|
1294
|
+
fallbackApplied: turnPlan.capability.fallbackApplied,
|
|
1295
|
+
fallbackReason: turnPlan.capability.fallbackReason,
|
|
1296
|
+
tokenEstimate: turnPlan.capability.result?.tokenEstimate,
|
|
1297
|
+
diagnostics: turnPlan.capability.result?.diagnostics,
|
|
1298
|
+
},
|
|
1299
|
+
}
|
|
1300
|
+
: null,
|
|
721
1301
|
longTermMemoryRetrieval: longTermMemoryContextText
|
|
722
1302
|
? {
|
|
1303
|
+
shouldReview: longTermMemoryShouldReview,
|
|
1304
|
+
reviewReason: longTermMemoryReviewReason,
|
|
723
1305
|
didRetrieve: true,
|
|
724
1306
|
contextChars: longTermMemoryContextText.length,
|
|
725
1307
|
diagnostics: longTermMemoryRetrievalDiagnostics,
|
|
726
1308
|
}
|
|
727
|
-
: {
|
|
1309
|
+
: {
|
|
1310
|
+
shouldReview: longTermMemoryShouldReview,
|
|
1311
|
+
reviewReason: longTermMemoryReviewReason,
|
|
1312
|
+
didRetrieve: false,
|
|
1313
|
+
},
|
|
728
1314
|
rollingSummary: rollingSummaryResult
|
|
729
1315
|
? {
|
|
730
1316
|
profileId: rollingSummaryProfileId,
|
|
@@ -742,6 +1328,16 @@ export class AgentOSOrchestrator {
|
|
|
742
1328
|
let currentToolCallIteration = 0;
|
|
743
1329
|
let continueProcessing = true;
|
|
744
1330
|
let lastGMIOutput; // To store the result from handleToolResult or final processTurnStream result
|
|
1331
|
+
await this.emitExecutionLifecycleUpdate({
|
|
1332
|
+
streamId: agentOSStreamId,
|
|
1333
|
+
gmiInstanceId: gmiInstanceIdForChunks,
|
|
1334
|
+
personaId: currentPersonaId,
|
|
1335
|
+
phase: 'executing',
|
|
1336
|
+
status: lifecycleDegraded ? 'degraded' : 'ok',
|
|
1337
|
+
details: {
|
|
1338
|
+
maxToolCallIterations: this.config.maxToolCallIterations,
|
|
1339
|
+
},
|
|
1340
|
+
});
|
|
745
1341
|
while (continueProcessing && currentToolCallIteration < this.config.maxToolCallIterations) {
|
|
746
1342
|
currentToolCallIteration++;
|
|
747
1343
|
if (lastGMIOutput?.toolCalls && lastGMIOutput.toolCalls.length > 0) {
|
|
@@ -832,6 +1428,67 @@ export class AgentOSOrchestrator {
|
|
|
832
1428
|
if (didForceTerminate || Boolean(finalGMIStateForResponse.error)) {
|
|
833
1429
|
turnMetricsStatus = 'error';
|
|
834
1430
|
}
|
|
1431
|
+
const taskOutcome = evaluateTaskOutcome({
|
|
1432
|
+
finalOutput: finalGMIStateForResponse,
|
|
1433
|
+
didForceTerminate,
|
|
1434
|
+
degraded: lifecycleDegraded,
|
|
1435
|
+
customFlags: input.options?.customFlags,
|
|
1436
|
+
});
|
|
1437
|
+
turnMetricsTaskOutcome = taskOutcome;
|
|
1438
|
+
const taskOutcomeKpi = this.updateTaskOutcomeKpi({
|
|
1439
|
+
outcome: taskOutcome,
|
|
1440
|
+
organizationId: organizationIdForMemory,
|
|
1441
|
+
personaId: currentPersonaId,
|
|
1442
|
+
});
|
|
1443
|
+
const taskOutcomeAlert = this.maybeBuildTaskOutcomeAlert(taskOutcomeKpi);
|
|
1444
|
+
await this.pushChunkToStream(agentOSStreamId, AgentOSResponseChunkType.METADATA_UPDATE, gmiInstanceIdForChunks, currentPersonaId, false, {
|
|
1445
|
+
updates: {
|
|
1446
|
+
taskOutcome,
|
|
1447
|
+
taskOutcomeKpi,
|
|
1448
|
+
taskOutcomeAlert,
|
|
1449
|
+
},
|
|
1450
|
+
});
|
|
1451
|
+
if (turnMetricsStatus === 'error') {
|
|
1452
|
+
await this.emitExecutionLifecycleUpdate({
|
|
1453
|
+
streamId: agentOSStreamId,
|
|
1454
|
+
gmiInstanceId: gmiInstanceIdForChunks,
|
|
1455
|
+
personaId: currentPersonaId,
|
|
1456
|
+
phase: 'errored',
|
|
1457
|
+
status: 'error',
|
|
1458
|
+
details: {
|
|
1459
|
+
didForceTerminate,
|
|
1460
|
+
hasFinalError: Boolean(finalGMIStateForResponse.error),
|
|
1461
|
+
taskOutcomeStatus: taskOutcome.status,
|
|
1462
|
+
taskOutcomeScore: taskOutcome.score,
|
|
1463
|
+
},
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
else {
|
|
1467
|
+
if (lifecycleDegraded) {
|
|
1468
|
+
await this.emitExecutionLifecycleUpdate({
|
|
1469
|
+
streamId: agentOSStreamId,
|
|
1470
|
+
gmiInstanceId: gmiInstanceIdForChunks,
|
|
1471
|
+
personaId: currentPersonaId,
|
|
1472
|
+
phase: 'recovered',
|
|
1473
|
+
status: 'ok',
|
|
1474
|
+
details: {
|
|
1475
|
+
recovery: 'Turn completed with fallback path.',
|
|
1476
|
+
},
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
await this.emitExecutionLifecycleUpdate({
|
|
1480
|
+
streamId: agentOSStreamId,
|
|
1481
|
+
gmiInstanceId: gmiInstanceIdForChunks,
|
|
1482
|
+
personaId: currentPersonaId,
|
|
1483
|
+
phase: 'completed',
|
|
1484
|
+
status: 'ok',
|
|
1485
|
+
details: {
|
|
1486
|
+
toolIterations: currentToolCallIteration,
|
|
1487
|
+
taskOutcomeStatus: taskOutcome.status,
|
|
1488
|
+
taskOutcomeScore: taskOutcome.score,
|
|
1489
|
+
},
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
835
1492
|
// Persist assistant output into ConversationContext for durable memory / prompt reconstruction.
|
|
836
1493
|
if (this.config.enableConversationalPersistence && conversationContext) {
|
|
837
1494
|
const persistContext = conversationContext;
|
|
@@ -883,7 +1540,39 @@ export class AgentOSOrchestrator {
|
|
|
883
1540
|
recordExceptionOnActiveSpan(error, `Error in orchestrateTurn for stream ${agentOSStreamId}`);
|
|
884
1541
|
const gmiErr = GMIError.wrap?.(error, GMIErrorCode.GMI_PROCESSING_ERROR, `Error in orchestrateTurn for stream ${agentOSStreamId}`) ||
|
|
885
1542
|
new GMIError(`Error in orchestrateTurn for stream ${agentOSStreamId}: ${error.message}`, GMIErrorCode.GMI_PROCESSING_ERROR, error);
|
|
1543
|
+
turnMetricsTaskOutcome = {
|
|
1544
|
+
status: 'failed',
|
|
1545
|
+
score: 0,
|
|
1546
|
+
reason: `Exception before completion: ${gmiErr.code}`,
|
|
1547
|
+
source: 'heuristic',
|
|
1548
|
+
};
|
|
1549
|
+
const taskOutcomeKpi = this.updateTaskOutcomeKpi({
|
|
1550
|
+
outcome: turnMetricsTaskOutcome,
|
|
1551
|
+
organizationId: organizationIdForMemory,
|
|
1552
|
+
personaId: currentPersonaId,
|
|
1553
|
+
});
|
|
1554
|
+
const taskOutcomeAlert = this.maybeBuildTaskOutcomeAlert(taskOutcomeKpi);
|
|
886
1555
|
console.error(`AgentOSOrchestrator: Error during _processTurnInternal for stream ${agentOSStreamId}:`, gmiErr);
|
|
1556
|
+
await this.pushChunkToStream(agentOSStreamId, AgentOSResponseChunkType.METADATA_UPDATE, gmiInstanceIdForChunks, currentPersonaId ?? 'unknown_persona', false, {
|
|
1557
|
+
updates: {
|
|
1558
|
+
taskOutcome: turnMetricsTaskOutcome,
|
|
1559
|
+
taskOutcomeKpi,
|
|
1560
|
+
taskOutcomeAlert,
|
|
1561
|
+
},
|
|
1562
|
+
});
|
|
1563
|
+
await this.emitExecutionLifecycleUpdate({
|
|
1564
|
+
streamId: agentOSStreamId,
|
|
1565
|
+
gmiInstanceId: gmiInstanceIdForChunks,
|
|
1566
|
+
personaId: currentPersonaId ?? 'unknown_persona',
|
|
1567
|
+
phase: 'errored',
|
|
1568
|
+
status: 'error',
|
|
1569
|
+
details: {
|
|
1570
|
+
code: gmiErr.code,
|
|
1571
|
+
message: gmiErr.message,
|
|
1572
|
+
taskOutcomeStatus: turnMetricsTaskOutcome.status,
|
|
1573
|
+
taskOutcomeScore: turnMetricsTaskOutcome.score,
|
|
1574
|
+
},
|
|
1575
|
+
});
|
|
887
1576
|
await this.pushErrorChunk(agentOSStreamId, currentPersonaId ?? 'unknown_persona', gmiInstanceIdForChunks, gmiErr.code, gmiErr.message, gmiErr.details);
|
|
888
1577
|
await this.dependencies.streamingManager.closeStream(agentOSStreamId, "Error during turn processing.");
|
|
889
1578
|
}
|
|
@@ -893,6 +1582,8 @@ export class AgentOSOrchestrator {
|
|
|
893
1582
|
status: turnMetricsStatus,
|
|
894
1583
|
personaId: turnMetricsPersonaId,
|
|
895
1584
|
usage: turnMetricsUsage,
|
|
1585
|
+
taskOutcomeStatus: turnMetricsTaskOutcome?.status,
|
|
1586
|
+
taskOutcomeScore: turnMetricsTaskOutcome?.score,
|
|
896
1587
|
});
|
|
897
1588
|
// Stream is closed explicitly in the success/error paths; this finally block always
|
|
898
1589
|
// clears internal state to avoid leaks.
|
|
@@ -1244,6 +1935,8 @@ export class AgentOSOrchestrator {
|
|
|
1244
1935
|
}
|
|
1245
1936
|
}
|
|
1246
1937
|
this.activeStreamContexts.clear();
|
|
1938
|
+
this.taskOutcomeKpiWindows.clear();
|
|
1939
|
+
this.taskOutcomeAlertState.clear();
|
|
1247
1940
|
this.initialized = false;
|
|
1248
1941
|
console.log('AgentOSOrchestrator: Shutdown complete.');
|
|
1249
1942
|
}
|