agentflow-core 0.7.0 → 0.8.1
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 +99 -0
- package/dist/{chunk-DY7YHFIB.js → chunk-BYWLDTZK.js} +2 -1
- package/dist/{chunk-5PRHVYYD.js → chunk-NVFWBTAZ.js} +198 -89
- package/dist/cli.cjs +225 -112
- package/dist/cli.js +12 -8
- package/dist/index.cjs +1657 -166
- package/dist/index.d.cts +917 -37
- package/dist/index.d.ts +917 -37
- package/dist/index.js +1381 -10
- package/dist/{loader-LYRR6LMM.js → loader-JMFEFI3Q.js} +1 -1
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
auditProcesses,
|
|
3
3
|
createGraphBuilder,
|
|
4
4
|
createTraceStore,
|
|
5
|
+
discoverAllProcessConfigs,
|
|
5
6
|
discoverProcessConfig,
|
|
6
7
|
findWaitingOn,
|
|
7
8
|
formatAuditReport,
|
|
@@ -23,13 +24,330 @@ import {
|
|
|
23
24
|
stitchTrace,
|
|
24
25
|
toAsciiTree,
|
|
25
26
|
toTimeline
|
|
26
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-NVFWBTAZ.js";
|
|
27
28
|
import {
|
|
28
29
|
graphToJson,
|
|
29
30
|
loadGraph
|
|
30
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-BYWLDTZK.js";
|
|
32
|
+
|
|
33
|
+
// src/process-mining.ts
|
|
34
|
+
function getPathSignature(graph) {
|
|
35
|
+
const root = graph.nodes.get(graph.rootNodeId);
|
|
36
|
+
if (!root) return "";
|
|
37
|
+
const parts = [];
|
|
38
|
+
function walk(node) {
|
|
39
|
+
parts.push(`${node.type}:${node.name}`);
|
|
40
|
+
const childNodes = [];
|
|
41
|
+
for (const childId of node.children) {
|
|
42
|
+
const child = graph.nodes.get(childId);
|
|
43
|
+
if (child) childNodes.push(child);
|
|
44
|
+
}
|
|
45
|
+
childNodes.sort((a, b) => {
|
|
46
|
+
const keyA = `${a.type}:${a.name}`;
|
|
47
|
+
const keyB = `${b.type}:${b.name}`;
|
|
48
|
+
return keyA.localeCompare(keyB);
|
|
49
|
+
});
|
|
50
|
+
for (const child of childNodes) {
|
|
51
|
+
walk(child);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
walk(root);
|
|
55
|
+
return parts.join("\u2192");
|
|
56
|
+
}
|
|
57
|
+
function stepKey(node) {
|
|
58
|
+
return `${node.type}:${node.name}`;
|
|
59
|
+
}
|
|
60
|
+
function discoverProcess(graphs) {
|
|
61
|
+
if (graphs.length === 0) {
|
|
62
|
+
throw new Error("discoverProcess requires at least one graph");
|
|
63
|
+
}
|
|
64
|
+
const steps = /* @__PURE__ */ new Set();
|
|
65
|
+
const transitionCounts = /* @__PURE__ */ new Map();
|
|
66
|
+
const outgoingCounts = /* @__PURE__ */ new Map();
|
|
67
|
+
for (const graph of graphs) {
|
|
68
|
+
for (const node of graph.nodes.values()) {
|
|
69
|
+
const parentKey = stepKey(node);
|
|
70
|
+
steps.add(parentKey);
|
|
71
|
+
for (const childId of node.children) {
|
|
72
|
+
const child = graph.nodes.get(childId);
|
|
73
|
+
if (!child) continue;
|
|
74
|
+
const childKey = stepKey(child);
|
|
75
|
+
const tKey = `${parentKey}\0${childKey}`;
|
|
76
|
+
transitionCounts.set(tKey, (transitionCounts.get(tKey) ?? 0) + 1);
|
|
77
|
+
outgoingCounts.set(parentKey, (outgoingCounts.get(parentKey) ?? 0) + 1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const transitions = [];
|
|
82
|
+
for (const [tKey, count] of transitionCounts) {
|
|
83
|
+
const [from, to] = tKey.split("\0");
|
|
84
|
+
const outgoing = outgoingCounts.get(from) ?? count;
|
|
85
|
+
transitions.push({
|
|
86
|
+
from,
|
|
87
|
+
to,
|
|
88
|
+
count,
|
|
89
|
+
probability: count / outgoing
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
transitions.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
|
|
93
|
+
return {
|
|
94
|
+
steps: [...steps].sort(),
|
|
95
|
+
transitions,
|
|
96
|
+
totalGraphs: graphs.length,
|
|
97
|
+
agentId: graphs[0]?.agentId ?? ""
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function findVariants(graphs) {
|
|
101
|
+
if (graphs.length === 0) return [];
|
|
102
|
+
const groups = /* @__PURE__ */ new Map();
|
|
103
|
+
for (const graph of graphs) {
|
|
104
|
+
const sig = getPathSignature(graph);
|
|
105
|
+
const group = groups.get(sig) ?? [];
|
|
106
|
+
group.push(graph);
|
|
107
|
+
groups.set(sig, group);
|
|
108
|
+
}
|
|
109
|
+
const total = graphs.length;
|
|
110
|
+
const variants = [];
|
|
111
|
+
for (const [pathSignature, groupGraphs] of groups) {
|
|
112
|
+
variants.push({
|
|
113
|
+
pathSignature,
|
|
114
|
+
count: groupGraphs.length,
|
|
115
|
+
percentage: groupGraphs.length / total * 100,
|
|
116
|
+
graphIds: groupGraphs.map((g) => g.id),
|
|
117
|
+
exampleGraph: groupGraphs[0]
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
variants.sort((a, b) => {
|
|
121
|
+
const freqDiff = b.count - a.count;
|
|
122
|
+
if (freqDiff !== 0) return freqDiff;
|
|
123
|
+
return a.pathSignature.localeCompare(b.pathSignature);
|
|
124
|
+
});
|
|
125
|
+
return variants;
|
|
126
|
+
}
|
|
127
|
+
function percentile(sorted, p) {
|
|
128
|
+
if (sorted.length === 0) return 0;
|
|
129
|
+
if (sorted.length === 1) return sorted[0] ?? 0;
|
|
130
|
+
const index = p / 100 * (sorted.length - 1);
|
|
131
|
+
const lower = Math.floor(index);
|
|
132
|
+
const upper = Math.ceil(index);
|
|
133
|
+
if (lower === upper) return sorted[lower] ?? 0;
|
|
134
|
+
const weight = index - lower;
|
|
135
|
+
return (sorted[lower] ?? 0) * (1 - weight) + (sorted[upper] ?? 0) * weight;
|
|
136
|
+
}
|
|
137
|
+
function getBottlenecks(graphs) {
|
|
138
|
+
if (graphs.length === 0) return [];
|
|
139
|
+
const now = Date.now();
|
|
140
|
+
const stats = /* @__PURE__ */ new Map();
|
|
141
|
+
for (const graph of graphs) {
|
|
142
|
+
for (const node of graph.nodes.values()) {
|
|
143
|
+
const key = `${node.type}:${node.name}`;
|
|
144
|
+
const entry = stats.get(key) ?? { durations: [], nodeType: node.type, nodeName: node.name };
|
|
145
|
+
const end = node.endTime ?? now;
|
|
146
|
+
entry.durations.push(end - node.startTime);
|
|
147
|
+
stats.set(key, entry);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const total = graphs.length;
|
|
151
|
+
const bottlenecks = [];
|
|
152
|
+
for (const [, entry] of stats) {
|
|
153
|
+
const sorted = [...entry.durations].sort((a, b) => a - b);
|
|
154
|
+
bottlenecks.push({
|
|
155
|
+
nodeName: entry.nodeName,
|
|
156
|
+
nodeType: entry.nodeType,
|
|
157
|
+
occurrences: sorted.length,
|
|
158
|
+
durations: {
|
|
159
|
+
median: percentile(sorted, 50),
|
|
160
|
+
p95: percentile(sorted, 95),
|
|
161
|
+
p99: percentile(sorted, 99),
|
|
162
|
+
min: sorted[0] ?? 0,
|
|
163
|
+
max: sorted[sorted.length - 1] ?? 0
|
|
164
|
+
},
|
|
165
|
+
percentOfGraphs: sorted.length / total * 100
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
bottlenecks.sort((a, b) => b.durations.p95 - a.durations.p95);
|
|
169
|
+
return bottlenecks;
|
|
170
|
+
}
|
|
171
|
+
function extractGraphTransitions(graph) {
|
|
172
|
+
const transitions = /* @__PURE__ */ new Set();
|
|
173
|
+
for (const node of graph.nodes.values()) {
|
|
174
|
+
const parentKey = stepKey(node);
|
|
175
|
+
for (const childId of node.children) {
|
|
176
|
+
const child = graph.nodes.get(childId);
|
|
177
|
+
if (!child) continue;
|
|
178
|
+
transitions.add(`${parentKey}\0${stepKey(child)}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return transitions;
|
|
182
|
+
}
|
|
183
|
+
function checkConformance(graph, model) {
|
|
184
|
+
const graphTransitions = extractGraphTransitions(graph);
|
|
185
|
+
const deviations = [];
|
|
186
|
+
const modelLookup = /* @__PURE__ */ new Map();
|
|
187
|
+
for (const t of model.transitions) {
|
|
188
|
+
modelLookup.set(`${t.from}\0${t.to}`, t);
|
|
189
|
+
}
|
|
190
|
+
let totalChecks = 0;
|
|
191
|
+
let deviationCount = 0;
|
|
192
|
+
for (const tKey of graphTransitions) {
|
|
193
|
+
totalChecks++;
|
|
194
|
+
const [from, to] = tKey.split("\0");
|
|
195
|
+
const modelTransition = modelLookup.get(tKey);
|
|
196
|
+
if (!modelTransition) {
|
|
197
|
+
deviationCount++;
|
|
198
|
+
deviations.push({
|
|
199
|
+
type: "unexpected-transition",
|
|
200
|
+
from,
|
|
201
|
+
to,
|
|
202
|
+
message: `Unexpected transition ${from} \u2192 ${to} (not in process model)`
|
|
203
|
+
});
|
|
204
|
+
} else if (modelTransition.probability < 0.1) {
|
|
205
|
+
deviationCount++;
|
|
206
|
+
deviations.push({
|
|
207
|
+
type: "low-frequency-path",
|
|
208
|
+
from,
|
|
209
|
+
to,
|
|
210
|
+
message: `Low-frequency path ${from} \u2192 ${to} (model probability: ${(modelTransition.probability * 100).toFixed(1)}%)`,
|
|
211
|
+
modelProbability: modelTransition.probability
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const graphSteps = /* @__PURE__ */ new Set();
|
|
216
|
+
for (const node of graph.nodes.values()) {
|
|
217
|
+
graphSteps.add(stepKey(node));
|
|
218
|
+
}
|
|
219
|
+
for (const t of model.transitions) {
|
|
220
|
+
if (t.probability > 0.5) {
|
|
221
|
+
const tKey = `${t.from}\0${t.to}`;
|
|
222
|
+
if (graphSteps.has(t.from) && !graphTransitions.has(tKey)) {
|
|
223
|
+
totalChecks++;
|
|
224
|
+
deviationCount++;
|
|
225
|
+
deviations.push({
|
|
226
|
+
type: "missing-transition",
|
|
227
|
+
from: t.from,
|
|
228
|
+
to: t.to,
|
|
229
|
+
message: `Missing expected transition ${t.from} \u2192 ${t.to} (model probability: ${(t.probability * 100).toFixed(1)}%)`,
|
|
230
|
+
modelProbability: t.probability
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const conformanceScore = totalChecks === 0 ? 1 : (totalChecks - deviationCount) / totalChecks;
|
|
236
|
+
return {
|
|
237
|
+
conformanceScore,
|
|
238
|
+
isConforming: deviations.length === 0,
|
|
239
|
+
deviations
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/event-emitter.ts
|
|
244
|
+
var SCHEMA_VERSION = 1;
|
|
245
|
+
function createExecutionEvent(graph, options) {
|
|
246
|
+
const duration = graph.endTime !== null ? graph.endTime - graph.startTime : Date.now() - graph.startTime;
|
|
247
|
+
let failurePoint;
|
|
248
|
+
if (graph.status === "failed") {
|
|
249
|
+
let candidate;
|
|
250
|
+
for (const node of graph.nodes.values()) {
|
|
251
|
+
if (node.status === "failed" || node.status === "timeout") {
|
|
252
|
+
const errorMeta = node.metadata.error;
|
|
253
|
+
const fp = {
|
|
254
|
+
nodeId: node.id,
|
|
255
|
+
nodeName: node.name,
|
|
256
|
+
nodeType: node.type,
|
|
257
|
+
error: typeof errorMeta === "string" ? errorMeta : void 0
|
|
258
|
+
};
|
|
259
|
+
if (node.id !== graph.rootNodeId) {
|
|
260
|
+
failurePoint = fp;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
if (!candidate) candidate = fp;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (!failurePoint) failurePoint = candidate;
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
eventType: graph.status === "failed" ? "execution.failed" : "execution.completed",
|
|
270
|
+
graphId: graph.id,
|
|
271
|
+
agentId: graph.agentId,
|
|
272
|
+
timestamp: Date.now(),
|
|
273
|
+
schemaVersion: SCHEMA_VERSION,
|
|
274
|
+
status: graph.status,
|
|
275
|
+
duration,
|
|
276
|
+
nodeCount: graph.nodes.size,
|
|
277
|
+
pathSignature: getPathSignature(graph),
|
|
278
|
+
...failurePoint ? { failurePoint } : {},
|
|
279
|
+
...options?.processContext ? { processContext: options.processContext } : {},
|
|
280
|
+
...options?.semantic ? { semantic: options.semantic } : {},
|
|
281
|
+
violations: options?.violations ?? []
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function createPatternEvent(agentId, model, variants, bottlenecks) {
|
|
285
|
+
return {
|
|
286
|
+
eventType: "pattern.discovered",
|
|
287
|
+
agentId,
|
|
288
|
+
timestamp: Date.now(),
|
|
289
|
+
schemaVersion: SCHEMA_VERSION,
|
|
290
|
+
pattern: {
|
|
291
|
+
totalGraphs: model.totalGraphs,
|
|
292
|
+
variantCount: variants.length,
|
|
293
|
+
topVariants: variants.slice(0, 5).map((v) => ({
|
|
294
|
+
pathSignature: v.pathSignature,
|
|
295
|
+
count: v.count,
|
|
296
|
+
percentage: v.percentage
|
|
297
|
+
})),
|
|
298
|
+
topBottlenecks: bottlenecks.slice(0, 5).map((b) => ({
|
|
299
|
+
nodeName: b.nodeName,
|
|
300
|
+
nodeType: b.nodeType,
|
|
301
|
+
p95: b.durations.p95
|
|
302
|
+
})),
|
|
303
|
+
processModel: model
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function createEventEmitter(config) {
|
|
308
|
+
const writers = config?.writers ?? [];
|
|
309
|
+
const knowledgeStore = config?.knowledgeStore;
|
|
310
|
+
const onError = config?.onError ?? (() => {
|
|
311
|
+
});
|
|
312
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
313
|
+
return {
|
|
314
|
+
async emit(event) {
|
|
315
|
+
if (knowledgeStore) {
|
|
316
|
+
try {
|
|
317
|
+
knowledgeStore.append(event);
|
|
318
|
+
} catch (err) {
|
|
319
|
+
onError(err);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
for (const writer of writers) {
|
|
323
|
+
try {
|
|
324
|
+
await writer.writeEvent(event);
|
|
325
|
+
} catch (err) {
|
|
326
|
+
onError(err);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
for (const listener of subscribers) {
|
|
330
|
+
try {
|
|
331
|
+
listener(event);
|
|
332
|
+
} catch (err) {
|
|
333
|
+
onError(err);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
subscribe(listener) {
|
|
338
|
+
subscribers.add(listener);
|
|
339
|
+
return () => {
|
|
340
|
+
subscribers.delete(listener);
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
}
|
|
31
345
|
|
|
32
346
|
// src/guards.ts
|
|
347
|
+
function explainMessage(explanation) {
|
|
348
|
+
const sourceLabel = explanation.source === "static" ? "static config" : explanation.source === "soma-policy" ? `soma-policy${explanation.evidence ? ` (${explanation.evidence})` : ""}` : explanation.source === "assertion" ? "assertion" : "adaptive";
|
|
349
|
+
return `This run was stopped because ${explanation.rule} reached ${explanation.actual}, which exceeds the limit of ${explanation.threshold}. Source: ${sourceLabel}.`;
|
|
350
|
+
}
|
|
33
351
|
var DEFAULT_TIMEOUTS = {
|
|
34
352
|
tool: 3e4,
|
|
35
353
|
// 30s
|
|
@@ -56,22 +374,36 @@ function checkGuards(graph, config) {
|
|
|
56
374
|
const timeoutThreshold = timeouts[node.type];
|
|
57
375
|
const elapsed = now - node.startTime;
|
|
58
376
|
if (elapsed > timeoutThreshold) {
|
|
377
|
+
const explanation = {
|
|
378
|
+
rule: "timeout",
|
|
379
|
+
threshold: timeoutThreshold,
|
|
380
|
+
actual: elapsed,
|
|
381
|
+
source: "static"
|
|
382
|
+
};
|
|
59
383
|
violations.push({
|
|
60
384
|
type: "timeout",
|
|
61
385
|
nodeId: node.id,
|
|
62
|
-
message:
|
|
63
|
-
timestamp: now
|
|
386
|
+
message: explainMessage(explanation),
|
|
387
|
+
timestamp: now,
|
|
388
|
+
explanation
|
|
64
389
|
});
|
|
65
390
|
}
|
|
66
391
|
}
|
|
67
392
|
}
|
|
68
393
|
const depth = getDepth(graph);
|
|
69
394
|
if (depth > maxDepth) {
|
|
395
|
+
const explanation = {
|
|
396
|
+
rule: "max-depth",
|
|
397
|
+
threshold: maxDepth,
|
|
398
|
+
actual: depth,
|
|
399
|
+
source: "static"
|
|
400
|
+
};
|
|
70
401
|
violations.push({
|
|
71
402
|
type: "spawn-explosion",
|
|
72
403
|
nodeId: graph.rootNodeId,
|
|
73
|
-
message:
|
|
74
|
-
timestamp: now
|
|
404
|
+
message: explainMessage(explanation),
|
|
405
|
+
timestamp: now,
|
|
406
|
+
explanation
|
|
75
407
|
});
|
|
76
408
|
}
|
|
77
409
|
let agentCount = 0;
|
|
@@ -81,14 +413,26 @@ function checkGuards(graph, config) {
|
|
|
81
413
|
}
|
|
82
414
|
}
|
|
83
415
|
if (agentCount > maxAgentSpawns) {
|
|
416
|
+
const explanation = {
|
|
417
|
+
rule: "max-agent-spawns",
|
|
418
|
+
threshold: maxAgentSpawns,
|
|
419
|
+
actual: agentCount,
|
|
420
|
+
source: "static"
|
|
421
|
+
};
|
|
84
422
|
violations.push({
|
|
85
423
|
type: "spawn-explosion",
|
|
86
424
|
nodeId: graph.rootNodeId,
|
|
87
|
-
message:
|
|
88
|
-
timestamp: now
|
|
425
|
+
message: explainMessage(explanation),
|
|
426
|
+
timestamp: now,
|
|
427
|
+
explanation
|
|
89
428
|
});
|
|
90
429
|
}
|
|
91
430
|
violations.push(...detectReasoningLoops(graph, maxReasoningSteps, now));
|
|
431
|
+
if (config?.policySource) {
|
|
432
|
+
violations.push(
|
|
433
|
+
...checkPolicyViolations(graph, config.policySource, config.policyThresholds, now)
|
|
434
|
+
);
|
|
435
|
+
}
|
|
92
436
|
return violations;
|
|
93
437
|
}
|
|
94
438
|
function detectReasoningLoops(graph, maxSteps, timestamp) {
|
|
@@ -108,11 +452,18 @@ function detectReasoningLoops(graph, maxSteps, timestamp) {
|
|
|
108
452
|
}
|
|
109
453
|
if (newCount > maxSteps && !reported.has(newType)) {
|
|
110
454
|
reported.add(newType);
|
|
455
|
+
const explanation = {
|
|
456
|
+
rule: "max-reasoning-steps",
|
|
457
|
+
threshold: maxSteps,
|
|
458
|
+
actual: newCount,
|
|
459
|
+
source: "static"
|
|
460
|
+
};
|
|
111
461
|
violations.push({
|
|
112
462
|
type: "reasoning-loop",
|
|
113
463
|
nodeId: node.id,
|
|
114
|
-
message:
|
|
115
|
-
timestamp
|
|
464
|
+
message: explainMessage(explanation),
|
|
465
|
+
timestamp,
|
|
466
|
+
explanation
|
|
116
467
|
});
|
|
117
468
|
}
|
|
118
469
|
const children = getChildren(graph, nodeId);
|
|
@@ -123,6 +474,61 @@ function detectReasoningLoops(graph, maxSteps, timestamp) {
|
|
|
123
474
|
walk(graph.rootNodeId, 0, null);
|
|
124
475
|
return violations;
|
|
125
476
|
}
|
|
477
|
+
function checkPolicyViolations(graph, policySource, thresholds, timestamp) {
|
|
478
|
+
const violations = [];
|
|
479
|
+
const maxFailureRate = thresholds?.maxFailureRate ?? 0.5;
|
|
480
|
+
const minConformance = thresholds?.minConformance ?? 0.7;
|
|
481
|
+
const failureRate = policySource.recentFailureRate(graph.agentId);
|
|
482
|
+
if (failureRate > maxFailureRate) {
|
|
483
|
+
const explanation = {
|
|
484
|
+
rule: "max-failure-rate",
|
|
485
|
+
threshold: maxFailureRate,
|
|
486
|
+
actual: failureRate,
|
|
487
|
+
source: "soma-policy"
|
|
488
|
+
};
|
|
489
|
+
violations.push({
|
|
490
|
+
type: "high-failure-rate",
|
|
491
|
+
nodeId: graph.rootNodeId,
|
|
492
|
+
message: explainMessage(explanation),
|
|
493
|
+
timestamp,
|
|
494
|
+
explanation
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
const conformanceScore = policySource.lastConformanceScore(graph.agentId);
|
|
498
|
+
if (conformanceScore !== null && conformanceScore < minConformance) {
|
|
499
|
+
const explanation = {
|
|
500
|
+
rule: "min-conformance",
|
|
501
|
+
threshold: minConformance,
|
|
502
|
+
actual: conformanceScore,
|
|
503
|
+
source: "soma-policy"
|
|
504
|
+
};
|
|
505
|
+
violations.push({
|
|
506
|
+
type: "conformance-drift",
|
|
507
|
+
nodeId: graph.rootNodeId,
|
|
508
|
+
message: explainMessage(explanation),
|
|
509
|
+
timestamp,
|
|
510
|
+
explanation
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
for (const node of graph.nodes.values()) {
|
|
514
|
+
if (node.status === "running" && policySource.isKnownBottleneck(node.name)) {
|
|
515
|
+
const explanation = {
|
|
516
|
+
rule: "known-bottleneck",
|
|
517
|
+
threshold: "flagged",
|
|
518
|
+
actual: "running",
|
|
519
|
+
source: "soma-policy"
|
|
520
|
+
};
|
|
521
|
+
violations.push({
|
|
522
|
+
type: "known-bottleneck",
|
|
523
|
+
nodeId: node.id,
|
|
524
|
+
message: explainMessage(explanation),
|
|
525
|
+
timestamp,
|
|
526
|
+
explanation
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return violations;
|
|
531
|
+
}
|
|
126
532
|
function withGuards(builder, config) {
|
|
127
533
|
const logger = config?.logger ?? ((msg) => console.warn(`[AgentFlow Guard] ${msg}`));
|
|
128
534
|
const onViolation = config?.onViolation ?? "warn";
|
|
@@ -186,14 +592,977 @@ function withGuards(builder, config) {
|
|
|
186
592
|
}
|
|
187
593
|
};
|
|
188
594
|
}
|
|
595
|
+
|
|
596
|
+
// src/prompt-builder.ts
|
|
597
|
+
var ROLE = "You are analyzing execution data for an AI agent system. Provide clear, actionable analysis based on the data below.";
|
|
598
|
+
function fmtDuration(ms) {
|
|
599
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
600
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
601
|
+
}
|
|
602
|
+
function fmtTime(ts) {
|
|
603
|
+
return new Date(ts).toISOString();
|
|
604
|
+
}
|
|
605
|
+
function durationStats(durations) {
|
|
606
|
+
if (durations.length === 0) return { avg: 0, p50: 0, p95: 0, min: 0, max: 0 };
|
|
607
|
+
const sorted = [...durations].sort((a, b) => a - b);
|
|
608
|
+
const sum = sorted.reduce((a, b) => a + b, 0);
|
|
609
|
+
return {
|
|
610
|
+
avg: Math.round(sum / sorted.length),
|
|
611
|
+
p50: sorted[Math.floor(sorted.length * 0.5)] ?? 0,
|
|
612
|
+
p95: sorted[Math.floor(sorted.length * 0.95)] ?? 0,
|
|
613
|
+
min: sorted[0] ?? 0,
|
|
614
|
+
max: sorted[sorted.length - 1] ?? 0
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
function buildFailureAnalysisPrompt(events, profile) {
|
|
618
|
+
const stats = durationStats(profile.recentDurations);
|
|
619
|
+
const failureDetails = events.map((e, i) => {
|
|
620
|
+
const lines = [
|
|
621
|
+
`Failure ${i + 1}:`,
|
|
622
|
+
` Time: ${fmtTime(e.timestamp)}`,
|
|
623
|
+
` Duration: ${fmtDuration(e.duration)}`,
|
|
624
|
+
` Path: ${e.pathSignature}`
|
|
625
|
+
];
|
|
626
|
+
if (e.failurePoint) {
|
|
627
|
+
lines.push(` Failed at: ${e.failurePoint.nodeName} (${e.failurePoint.nodeType})`);
|
|
628
|
+
if (e.failurePoint.error) lines.push(` Error: ${e.failurePoint.error}`);
|
|
629
|
+
}
|
|
630
|
+
if (e.violations.length > 0) {
|
|
631
|
+
lines.push(` Violations: ${e.violations.map((v) => v.message).join("; ")}`);
|
|
632
|
+
}
|
|
633
|
+
return lines.join("\n");
|
|
634
|
+
}).join("\n\n");
|
|
635
|
+
return `${ROLE}
|
|
636
|
+
|
|
637
|
+
## Agent Profile
|
|
638
|
+
- Agent: ${profile.agentId}
|
|
639
|
+
- Total runs: ${profile.totalRuns}
|
|
640
|
+
- Failure rate: ${(profile.failureRate * 100).toFixed(1)}% (${profile.failureCount} failures / ${profile.totalRuns} total)
|
|
641
|
+
- Avg duration: ${fmtDuration(stats.avg)} (p50: ${fmtDuration(stats.p50)}, p95: ${fmtDuration(stats.p95)})
|
|
642
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
643
|
+
- Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
|
|
644
|
+
|
|
645
|
+
## Recent Failures (${events.length})
|
|
646
|
+
|
|
647
|
+
${failureDetails}
|
|
648
|
+
|
|
649
|
+
## Question
|
|
650
|
+
Analyze these failures. What patterns do you see? What is the most likely root cause? Are these related or independent failures?`;
|
|
651
|
+
}
|
|
652
|
+
function buildAnomalyExplanationPrompt(event, profile) {
|
|
653
|
+
const stats = durationStats(profile.recentDurations);
|
|
654
|
+
const eventDetails = [
|
|
655
|
+
`Time: ${fmtTime(event.timestamp)}`,
|
|
656
|
+
`Status: ${event.status}`,
|
|
657
|
+
`Duration: ${fmtDuration(event.duration)}`,
|
|
658
|
+
`Path: ${event.pathSignature}`,
|
|
659
|
+
`Node count: ${event.nodeCount}`
|
|
660
|
+
];
|
|
661
|
+
if (event.processContext) {
|
|
662
|
+
eventDetails.push(`Conformance score: ${event.processContext.conformanceScore}`);
|
|
663
|
+
eventDetails.push(`Is anomaly: ${event.processContext.isAnomaly}`);
|
|
664
|
+
eventDetails.push(`Variant: ${event.processContext.variant}`);
|
|
665
|
+
}
|
|
666
|
+
if (event.failurePoint) {
|
|
667
|
+
eventDetails.push(`Failed at: ${event.failurePoint.nodeName} (${event.failurePoint.nodeType})`);
|
|
668
|
+
if (event.failurePoint.error) eventDetails.push(`Error: ${event.failurePoint.error}`);
|
|
669
|
+
}
|
|
670
|
+
if (event.violations.length > 0) {
|
|
671
|
+
eventDetails.push(`Violations: ${event.violations.map((v) => v.message).join("; ")}`);
|
|
672
|
+
}
|
|
673
|
+
return `${ROLE}
|
|
674
|
+
|
|
675
|
+
## Agent Baseline (from profile)
|
|
676
|
+
- Agent: ${profile.agentId}
|
|
677
|
+
- Total runs: ${profile.totalRuns}
|
|
678
|
+
- Typical failure rate: ${(profile.failureRate * 100).toFixed(1)}%
|
|
679
|
+
- Typical duration: avg ${fmtDuration(stats.avg)}, p50 ${fmtDuration(stats.p50)}, p95 ${fmtDuration(stats.p95)}
|
|
680
|
+
- Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
|
|
681
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
682
|
+
|
|
683
|
+
## Anomalous Execution
|
|
684
|
+
${eventDetails.join("\n")}
|
|
685
|
+
|
|
686
|
+
## Question
|
|
687
|
+
This execution has been flagged as anomalous. Explain what is unusual about it compared to the agent's typical behavior. What might have caused this deviation?`;
|
|
688
|
+
}
|
|
689
|
+
function buildAgentSummaryPrompt(profile, recentEvents, patterns) {
|
|
690
|
+
const stats = durationStats(profile.recentDurations);
|
|
691
|
+
const recentOutcomes = recentEvents.slice(0, 10).map((e) => ` ${fmtTime(e.timestamp)} \u2014 ${e.eventType} (${fmtDuration(e.duration)})`).join("\n");
|
|
692
|
+
const patternSummary = patterns.length > 0 ? patterns.slice(0, 3).map((p) => {
|
|
693
|
+
const lines = [
|
|
694
|
+
` Variants: ${p.pattern.variantCount} across ${p.pattern.totalGraphs} executions`,
|
|
695
|
+
` Top variant: ${p.pattern.topVariants[0]?.pathSignature ?? "N/A"} (${p.pattern.topVariants[0]?.percentage.toFixed(0) ?? 0}%)`
|
|
696
|
+
];
|
|
697
|
+
if (p.pattern.topBottlenecks.length > 0) {
|
|
698
|
+
const topB = p.pattern.topBottlenecks[0];
|
|
699
|
+
if (topB)
|
|
700
|
+
lines.push(` Top bottleneck: ${topB.nodeName} (p95: ${fmtDuration(topB.p95)})`);
|
|
701
|
+
}
|
|
702
|
+
return lines.join("\n");
|
|
703
|
+
}).join("\n\n") : " No patterns discovered yet.";
|
|
704
|
+
const dataNote = recentEvents.length === 0 && patterns.length === 0 ? "\nNote: Limited data available. Summary is based only on the profile statistics.\n" : "";
|
|
705
|
+
return `${ROLE}
|
|
706
|
+
|
|
707
|
+
## Agent Profile
|
|
708
|
+
- Agent: ${profile.agentId}
|
|
709
|
+
- Total runs: ${profile.totalRuns}
|
|
710
|
+
- Success rate: ${((1 - profile.failureRate) * 100).toFixed(1)}% (${profile.successCount} successes, ${profile.failureCount} failures)
|
|
711
|
+
- Duration: avg ${fmtDuration(stats.avg)}, p50 ${fmtDuration(stats.p50)}, p95 ${fmtDuration(stats.p95)}, range ${fmtDuration(stats.min)}\u2013${fmtDuration(stats.max)}
|
|
712
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
713
|
+
- Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
|
|
714
|
+
- Last pattern analysis: ${profile.lastPatternTimestamp ? fmtTime(profile.lastPatternTimestamp) : "never"}
|
|
715
|
+
${dataNote}
|
|
716
|
+
## Recent Executions (last ${recentEvents.slice(0, 10).length})
|
|
717
|
+
${recentOutcomes || " No recent events."}
|
|
718
|
+
|
|
719
|
+
## Pattern Analysis
|
|
720
|
+
${patternSummary}
|
|
721
|
+
|
|
722
|
+
## Question
|
|
723
|
+
Provide a health summary for this agent. What are the key observations? Is the agent healthy, degrading, or in trouble? What should the operator pay attention to?`;
|
|
724
|
+
}
|
|
725
|
+
function buildFixSuggestionPrompt(events, profile, patterns) {
|
|
726
|
+
const failureGroups = /* @__PURE__ */ new Map();
|
|
727
|
+
for (const e of events) {
|
|
728
|
+
const key = e.failurePoint?.error ?? e.pathSignature;
|
|
729
|
+
const group = failureGroups.get(key) ?? [];
|
|
730
|
+
group.push(e);
|
|
731
|
+
failureGroups.set(key, group);
|
|
732
|
+
}
|
|
733
|
+
const failureGroupSummary = [...failureGroups.entries()].map(([key, group]) => {
|
|
734
|
+
const latest = group[0];
|
|
735
|
+
return ` "${key}" \u2014 ${group.length} occurrence(s), latest at ${latest ? fmtTime(latest.timestamp) : "unknown"}`;
|
|
736
|
+
}).join("\n");
|
|
737
|
+
const bottleneckDetails = patterns.flatMap((p) => p.pattern.topBottlenecks).map((b) => ` ${b.nodeName} (${b.nodeType}) \u2014 p95: ${fmtDuration(b.p95)}`);
|
|
738
|
+
const uniqueBottlenecks = [...new Set(bottleneckDetails)].join("\n");
|
|
739
|
+
const conformanceIssues = events.filter((e) => e.processContext && e.processContext.conformanceScore < 0.8).map(
|
|
740
|
+
(e) => ` ${fmtTime(e.timestamp)}: conformance ${e.processContext?.conformanceScore}, variant "${e.processContext?.variant}"`
|
|
741
|
+
).join("\n");
|
|
742
|
+
return `${ROLE}
|
|
743
|
+
|
|
744
|
+
## Agent Profile
|
|
745
|
+
- Agent: ${profile.agentId}
|
|
746
|
+
- Failure rate: ${(profile.failureRate * 100).toFixed(1)}%
|
|
747
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
748
|
+
|
|
749
|
+
## Failure Patterns (${events.length} failures)
|
|
750
|
+
${failureGroupSummary || " No failures recorded."}
|
|
751
|
+
|
|
752
|
+
## Bottlenecks
|
|
753
|
+
${uniqueBottlenecks || " No bottlenecks detected."}
|
|
754
|
+
|
|
755
|
+
## Conformance Issues
|
|
756
|
+
${conformanceIssues || " No conformance issues."}
|
|
757
|
+
|
|
758
|
+
## Question
|
|
759
|
+
Based on the failure patterns, bottlenecks, and conformance issues above, provide specific, actionable recommendations to improve this agent's reliability and performance. Prioritize by impact.`;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/insight-engine.ts
|
|
763
|
+
var DEFAULT_CACHE_TTL_MS = 36e5;
|
|
764
|
+
var SCHEMA_VERSION2 = 1;
|
|
765
|
+
function simpleHash(input) {
|
|
766
|
+
let hash = 0;
|
|
767
|
+
for (let i = 0; i < input.length; i++) {
|
|
768
|
+
const char = input.charCodeAt(i);
|
|
769
|
+
hash = (hash << 5) - hash + char | 0;
|
|
770
|
+
}
|
|
771
|
+
return (hash >>> 0).toString(36);
|
|
772
|
+
}
|
|
773
|
+
function createInsightEngine(store, analysisFn, config) {
|
|
774
|
+
const cacheTtlMs = config?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
775
|
+
function checkCache(agentId, insightType, dataHash) {
|
|
776
|
+
const recent = store.getRecentInsights(agentId, { type: insightType, limit: 1 });
|
|
777
|
+
if (recent.length === 0) return null;
|
|
778
|
+
const cached = recent[0];
|
|
779
|
+
if (!cached || cached.dataHash !== dataHash) return null;
|
|
780
|
+
const age = Date.now() - cached.timestamp;
|
|
781
|
+
if (age >= cacheTtlMs) return null;
|
|
782
|
+
return cached;
|
|
783
|
+
}
|
|
784
|
+
function storeAndReturn(agentId, insightType, prompt, response, dataHash) {
|
|
785
|
+
const event = {
|
|
786
|
+
eventType: "insight.generated",
|
|
787
|
+
agentId,
|
|
788
|
+
timestamp: Date.now(),
|
|
789
|
+
schemaVersion: SCHEMA_VERSION2,
|
|
790
|
+
insightType,
|
|
791
|
+
prompt,
|
|
792
|
+
response,
|
|
793
|
+
dataHash
|
|
794
|
+
};
|
|
795
|
+
store.appendInsight(event);
|
|
796
|
+
return {
|
|
797
|
+
agentId,
|
|
798
|
+
insightType,
|
|
799
|
+
content: response,
|
|
800
|
+
cached: false,
|
|
801
|
+
timestamp: event.timestamp
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
function shortCircuit(agentId, insightType, content, cached, timestamp) {
|
|
805
|
+
return { agentId, insightType, content, cached, timestamp: timestamp ?? Date.now() };
|
|
806
|
+
}
|
|
807
|
+
return {
|
|
808
|
+
async explainFailures(agentId) {
|
|
809
|
+
const profile = store.getAgentProfile(agentId);
|
|
810
|
+
if (!profile) {
|
|
811
|
+
return shortCircuit(
|
|
812
|
+
agentId,
|
|
813
|
+
"failure-analysis",
|
|
814
|
+
"No data available for this agent.",
|
|
815
|
+
false
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
const events = store.getRecentEvents(agentId, { limit: 50 });
|
|
819
|
+
const failures = events.filter((e) => e.eventType === "execution.failed");
|
|
820
|
+
if (failures.length === 0) {
|
|
821
|
+
return shortCircuit(
|
|
822
|
+
agentId,
|
|
823
|
+
"failure-analysis",
|
|
824
|
+
"No recent failures found for this agent.",
|
|
825
|
+
false
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
const dataHash = simpleHash(JSON.stringify({ failures, profile }));
|
|
829
|
+
const cached = checkCache(agentId, "failure-analysis", dataHash);
|
|
830
|
+
if (cached) {
|
|
831
|
+
return shortCircuit(agentId, "failure-analysis", cached.response, true, cached.timestamp);
|
|
832
|
+
}
|
|
833
|
+
const prompt = buildFailureAnalysisPrompt(failures, profile);
|
|
834
|
+
try {
|
|
835
|
+
const response = await analysisFn(prompt);
|
|
836
|
+
return storeAndReturn(agentId, "failure-analysis", prompt, response, dataHash);
|
|
837
|
+
} catch (err) {
|
|
838
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
839
|
+
return shortCircuit(agentId, "failure-analysis", `Analysis failed: ${message}`, false);
|
|
840
|
+
}
|
|
841
|
+
},
|
|
842
|
+
async explainAnomaly(agentId, event) {
|
|
843
|
+
const profile = store.getAgentProfile(agentId);
|
|
844
|
+
if (!profile) {
|
|
845
|
+
return shortCircuit(
|
|
846
|
+
agentId,
|
|
847
|
+
"anomaly-explanation",
|
|
848
|
+
"No data available for this agent.",
|
|
849
|
+
false
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
const dataHash = simpleHash(JSON.stringify({ event, profile }));
|
|
853
|
+
const cached = checkCache(agentId, "anomaly-explanation", dataHash);
|
|
854
|
+
if (cached) {
|
|
855
|
+
return shortCircuit(
|
|
856
|
+
agentId,
|
|
857
|
+
"anomaly-explanation",
|
|
858
|
+
cached.response,
|
|
859
|
+
true,
|
|
860
|
+
cached.timestamp
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
const prompt = buildAnomalyExplanationPrompt(event, profile);
|
|
864
|
+
try {
|
|
865
|
+
const response = await analysisFn(prompt);
|
|
866
|
+
return storeAndReturn(agentId, "anomaly-explanation", prompt, response, dataHash);
|
|
867
|
+
} catch (err) {
|
|
868
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
869
|
+
return shortCircuit(agentId, "anomaly-explanation", `Analysis failed: ${message}`, false);
|
|
870
|
+
}
|
|
871
|
+
},
|
|
872
|
+
async summarizeAgent(agentId) {
|
|
873
|
+
const profile = store.getAgentProfile(agentId);
|
|
874
|
+
if (!profile) {
|
|
875
|
+
return shortCircuit(agentId, "agent-summary", "No data available for this agent.", false);
|
|
876
|
+
}
|
|
877
|
+
const recentEvents = store.getRecentEvents(agentId, { limit: 20 });
|
|
878
|
+
const patterns = store.getPatternHistory(agentId, { limit: 5 });
|
|
879
|
+
const dataHash = simpleHash(JSON.stringify({ profile, recentEvents, patterns }));
|
|
880
|
+
const cached = checkCache(agentId, "agent-summary", dataHash);
|
|
881
|
+
if (cached) {
|
|
882
|
+
return shortCircuit(agentId, "agent-summary", cached.response, true, cached.timestamp);
|
|
883
|
+
}
|
|
884
|
+
const prompt = buildAgentSummaryPrompt(profile, recentEvents, patterns);
|
|
885
|
+
try {
|
|
886
|
+
const response = await analysisFn(prompt);
|
|
887
|
+
return storeAndReturn(agentId, "agent-summary", prompt, response, dataHash);
|
|
888
|
+
} catch (err) {
|
|
889
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
890
|
+
return shortCircuit(agentId, "agent-summary", `Analysis failed: ${message}`, false);
|
|
891
|
+
}
|
|
892
|
+
},
|
|
893
|
+
async suggestFixes(agentId) {
|
|
894
|
+
const profile = store.getAgentProfile(agentId);
|
|
895
|
+
if (!profile) {
|
|
896
|
+
return shortCircuit(agentId, "fix-suggestion", "No data available for this agent.", false);
|
|
897
|
+
}
|
|
898
|
+
const events = store.getRecentEvents(agentId, { limit: 50 });
|
|
899
|
+
const failures = events.filter((e) => e.eventType === "execution.failed");
|
|
900
|
+
const patterns = store.getPatternHistory(agentId, { limit: 5 });
|
|
901
|
+
if (failures.length === 0 && profile.knownBottlenecks.length === 0) {
|
|
902
|
+
return shortCircuit(
|
|
903
|
+
agentId,
|
|
904
|
+
"fix-suggestion",
|
|
905
|
+
"Agent is healthy \u2014 no failures or bottlenecks detected.",
|
|
906
|
+
false
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
const dataHash = simpleHash(JSON.stringify({ failures, profile, patterns }));
|
|
910
|
+
const cached = checkCache(agentId, "fix-suggestion", dataHash);
|
|
911
|
+
if (cached) {
|
|
912
|
+
return shortCircuit(agentId, "fix-suggestion", cached.response, true, cached.timestamp);
|
|
913
|
+
}
|
|
914
|
+
const prompt = buildFixSuggestionPrompt(failures, profile, patterns);
|
|
915
|
+
try {
|
|
916
|
+
const response = await analysisFn(prompt);
|
|
917
|
+
return storeAndReturn(agentId, "fix-suggestion", prompt, response, dataHash);
|
|
918
|
+
} catch (err) {
|
|
919
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
920
|
+
return shortCircuit(agentId, "fix-suggestion", `Analysis failed: ${message}`, false);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// src/json-event-writer.ts
|
|
927
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
928
|
+
import { join } from "path";
|
|
929
|
+
function createJsonEventWriter(config) {
|
|
930
|
+
const { outputDir } = config;
|
|
931
|
+
function ensureDir() {
|
|
932
|
+
mkdirSync(outputDir, { recursive: true });
|
|
933
|
+
}
|
|
934
|
+
function eventFileName(event) {
|
|
935
|
+
const typePart = event.eventType.replace(/\./g, "-");
|
|
936
|
+
const agentId = "agentId" in event ? event.agentId : "unknown";
|
|
937
|
+
return `${typePart}-${agentId}-${event.timestamp}.json`;
|
|
938
|
+
}
|
|
939
|
+
return {
|
|
940
|
+
async write(_graph) {
|
|
941
|
+
},
|
|
942
|
+
async writeEvent(event) {
|
|
943
|
+
ensureDir();
|
|
944
|
+
const fileName = eventFileName(event);
|
|
945
|
+
const filePath = join(outputDir, fileName);
|
|
946
|
+
writeFileSync(filePath, JSON.stringify(event, null, 2), "utf-8");
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// src/knowledge-store.ts
|
|
952
|
+
import {
|
|
953
|
+
existsSync,
|
|
954
|
+
mkdirSync as mkdirSync2,
|
|
955
|
+
readdirSync,
|
|
956
|
+
readFileSync,
|
|
957
|
+
renameSync,
|
|
958
|
+
rmSync,
|
|
959
|
+
writeFileSync as writeFileSync2
|
|
960
|
+
} from "fs";
|
|
961
|
+
import { join as join2 } from "path";
|
|
962
|
+
var DEFAULT_BASE_DIR = ".agentflow/knowledge";
|
|
963
|
+
var MAX_RECENT_DURATIONS = 100;
|
|
964
|
+
var writeCounter = 0;
|
|
965
|
+
function toDateDir(epochMs) {
|
|
966
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
967
|
+
}
|
|
968
|
+
function readJson(filePath) {
|
|
969
|
+
try {
|
|
970
|
+
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
971
|
+
} catch {
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
function writeJsonAtomic(filePath, data) {
|
|
976
|
+
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
977
|
+
writeFileSync2(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
978
|
+
renameSync(tmpPath, filePath);
|
|
979
|
+
}
|
|
980
|
+
function emptyProfile(agentId) {
|
|
981
|
+
return {
|
|
982
|
+
agentId,
|
|
983
|
+
totalRuns: 0,
|
|
984
|
+
successCount: 0,
|
|
985
|
+
failureCount: 0,
|
|
986
|
+
failureRate: 0,
|
|
987
|
+
recentDurations: [],
|
|
988
|
+
lastConformanceScore: null,
|
|
989
|
+
knownBottlenecks: [],
|
|
990
|
+
lastPatternTimestamp: null,
|
|
991
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
function mergeExecutionEvent(profile, event) {
|
|
995
|
+
const totalRuns = profile.totalRuns + 1;
|
|
996
|
+
const isFailure = event.eventType === "execution.failed";
|
|
997
|
+
const successCount = profile.successCount + (isFailure ? 0 : 1);
|
|
998
|
+
const failureCount = profile.failureCount + (isFailure ? 1 : 0);
|
|
999
|
+
const durations = [...profile.recentDurations, event.duration];
|
|
1000
|
+
if (durations.length > MAX_RECENT_DURATIONS) {
|
|
1001
|
+
durations.shift();
|
|
1002
|
+
}
|
|
1003
|
+
const conformanceScore = event.processContext?.conformanceScore ?? profile.lastConformanceScore;
|
|
1004
|
+
return {
|
|
1005
|
+
agentId: profile.agentId,
|
|
1006
|
+
totalRuns,
|
|
1007
|
+
successCount,
|
|
1008
|
+
failureCount,
|
|
1009
|
+
failureRate: failureCount / totalRuns,
|
|
1010
|
+
recentDurations: durations,
|
|
1011
|
+
lastConformanceScore: conformanceScore,
|
|
1012
|
+
knownBottlenecks: profile.knownBottlenecks,
|
|
1013
|
+
lastPatternTimestamp: profile.lastPatternTimestamp,
|
|
1014
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
function mergePatternEvent(profile, event) {
|
|
1018
|
+
const existingBottlenecks = new Set(profile.knownBottlenecks);
|
|
1019
|
+
for (const b of event.pattern.topBottlenecks) {
|
|
1020
|
+
existingBottlenecks.add(b.nodeName);
|
|
1021
|
+
}
|
|
1022
|
+
return {
|
|
1023
|
+
...profile,
|
|
1024
|
+
knownBottlenecks: [...existingBottlenecks],
|
|
1025
|
+
lastPatternTimestamp: event.timestamp,
|
|
1026
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
function createKnowledgeStore(config) {
|
|
1030
|
+
const baseDir = config?.baseDir ?? DEFAULT_BASE_DIR;
|
|
1031
|
+
const eventsDir = join2(baseDir, "events");
|
|
1032
|
+
const patternsDir = join2(baseDir, "patterns");
|
|
1033
|
+
const profilesDir = join2(baseDir, "profiles");
|
|
1034
|
+
const insightsDir = join2(baseDir, "insights");
|
|
1035
|
+
function ensureDir(dir) {
|
|
1036
|
+
mkdirSync2(dir, { recursive: true });
|
|
1037
|
+
}
|
|
1038
|
+
function profilePath(agentId) {
|
|
1039
|
+
const safe = agentId.replace(/[/\\]/g, "_").replace(/\.\./g, "_");
|
|
1040
|
+
return join2(profilesDir, `${safe}.json`);
|
|
1041
|
+
}
|
|
1042
|
+
function appendExecutionEvent(event) {
|
|
1043
|
+
const dateDir = join2(eventsDir, event.agentId, toDateDir(event.timestamp));
|
|
1044
|
+
ensureDir(dateDir);
|
|
1045
|
+
const typePart = event.eventType.replace(/\./g, "-");
|
|
1046
|
+
const seq = String(writeCounter++).padStart(4, "0");
|
|
1047
|
+
const fileName = `${typePart}-${event.timestamp}-${seq}.json`;
|
|
1048
|
+
writeFileSync2(join2(dateDir, fileName), JSON.stringify(event, null, 2), "utf-8");
|
|
1049
|
+
ensureDir(profilesDir);
|
|
1050
|
+
const existing = readJson(profilePath(event.agentId)) ?? emptyProfile(event.agentId);
|
|
1051
|
+
const updated = mergeExecutionEvent(existing, event);
|
|
1052
|
+
writeJsonAtomic(profilePath(event.agentId), updated);
|
|
1053
|
+
}
|
|
1054
|
+
function appendPatternEvent(event) {
|
|
1055
|
+
const agentPatternDir = join2(patternsDir, event.agentId);
|
|
1056
|
+
ensureDir(agentPatternDir);
|
|
1057
|
+
const seq = String(writeCounter++).padStart(4, "0");
|
|
1058
|
+
const fileName = `${event.timestamp}-${seq}.json`;
|
|
1059
|
+
writeFileSync2(join2(agentPatternDir, fileName), JSON.stringify(event, null, 2), "utf-8");
|
|
1060
|
+
ensureDir(profilesDir);
|
|
1061
|
+
const existing = readJson(profilePath(event.agentId)) ?? emptyProfile(event.agentId);
|
|
1062
|
+
const updated = mergePatternEvent(existing, event);
|
|
1063
|
+
writeJsonAtomic(profilePath(event.agentId), updated);
|
|
1064
|
+
}
|
|
1065
|
+
return {
|
|
1066
|
+
baseDir,
|
|
1067
|
+
append(event) {
|
|
1068
|
+
if (event.eventType === "pattern.discovered" || event.eventType === "pattern.updated") {
|
|
1069
|
+
appendPatternEvent(event);
|
|
1070
|
+
} else {
|
|
1071
|
+
appendExecutionEvent(event);
|
|
1072
|
+
}
|
|
1073
|
+
},
|
|
1074
|
+
getRecentEvents(agentId, options) {
|
|
1075
|
+
const limit = options?.limit ?? 50;
|
|
1076
|
+
const since = options?.since ?? 0;
|
|
1077
|
+
const agentDir = join2(eventsDir, agentId);
|
|
1078
|
+
if (!existsSync(agentDir)) return [];
|
|
1079
|
+
const events = [];
|
|
1080
|
+
const dateDirs = readdirSync(agentDir).sort().reverse();
|
|
1081
|
+
for (const dateDir of dateDirs) {
|
|
1082
|
+
const fullDateDir = join2(agentDir, dateDir);
|
|
1083
|
+
let files;
|
|
1084
|
+
try {
|
|
1085
|
+
files = readdirSync(fullDateDir).filter((f) => f.endsWith(".json"));
|
|
1086
|
+
} catch {
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
for (const file of files) {
|
|
1090
|
+
const event = readJson(join2(fullDateDir, file));
|
|
1091
|
+
if (event && event.timestamp > since) {
|
|
1092
|
+
events.push(event);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
if (events.length >= limit * 2) break;
|
|
1096
|
+
}
|
|
1097
|
+
events.sort((a, b) => b.timestamp - a.timestamp);
|
|
1098
|
+
return events.slice(0, limit);
|
|
1099
|
+
},
|
|
1100
|
+
getAgentProfile(agentId) {
|
|
1101
|
+
return readJson(profilePath(agentId));
|
|
1102
|
+
},
|
|
1103
|
+
getPatternHistory(agentId, options) {
|
|
1104
|
+
const limit = options?.limit ?? 20;
|
|
1105
|
+
const agentPatternDir = join2(patternsDir, agentId);
|
|
1106
|
+
if (!existsSync(agentPatternDir)) return [];
|
|
1107
|
+
const files = readdirSync(agentPatternDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
1108
|
+
const events = [];
|
|
1109
|
+
for (const file of files.slice(0, limit)) {
|
|
1110
|
+
const event = readJson(join2(agentPatternDir, file));
|
|
1111
|
+
if (event) events.push(event);
|
|
1112
|
+
}
|
|
1113
|
+
return events;
|
|
1114
|
+
},
|
|
1115
|
+
compact(options) {
|
|
1116
|
+
let removed = 0;
|
|
1117
|
+
if (existsSync(eventsDir)) {
|
|
1118
|
+
for (const agentId of readdirSync(eventsDir)) {
|
|
1119
|
+
const agentDir = join2(eventsDir, agentId);
|
|
1120
|
+
let dateDirs;
|
|
1121
|
+
try {
|
|
1122
|
+
dateDirs = readdirSync(agentDir);
|
|
1123
|
+
} catch {
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
for (const dateDir of dateDirs) {
|
|
1127
|
+
const fullDateDir = join2(agentDir, dateDir);
|
|
1128
|
+
let files;
|
|
1129
|
+
try {
|
|
1130
|
+
files = readdirSync(fullDateDir).filter((f) => f.endsWith(".json"));
|
|
1131
|
+
} catch {
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
for (const file of files) {
|
|
1135
|
+
const parts = file.replace(".json", "").split("-");
|
|
1136
|
+
const tsPart = parts[parts.length - 2];
|
|
1137
|
+
const ts = tsPart ? Number.parseInt(tsPart, 10) : 0;
|
|
1138
|
+
if (!Number.isNaN(ts) && ts < options.olderThan) {
|
|
1139
|
+
try {
|
|
1140
|
+
rmSync(join2(fullDateDir, file));
|
|
1141
|
+
removed++;
|
|
1142
|
+
} catch {
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
try {
|
|
1147
|
+
if (readdirSync(fullDateDir).length === 0) {
|
|
1148
|
+
rmSync(fullDateDir, { recursive: true });
|
|
1149
|
+
}
|
|
1150
|
+
} catch {
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
if (existsSync(patternsDir)) {
|
|
1156
|
+
for (const agentId of readdirSync(patternsDir)) {
|
|
1157
|
+
const agentPatternDir = join2(patternsDir, agentId);
|
|
1158
|
+
let files;
|
|
1159
|
+
try {
|
|
1160
|
+
files = readdirSync(agentPatternDir).filter((f) => f.endsWith(".json"));
|
|
1161
|
+
} catch {
|
|
1162
|
+
continue;
|
|
1163
|
+
}
|
|
1164
|
+
for (const file of files) {
|
|
1165
|
+
const ts = Number.parseInt(file.split("-")[0] ?? "", 10);
|
|
1166
|
+
if (!Number.isNaN(ts) && ts < options.olderThan) {
|
|
1167
|
+
try {
|
|
1168
|
+
rmSync(join2(agentPatternDir, file));
|
|
1169
|
+
removed++;
|
|
1170
|
+
} catch {
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (existsSync(insightsDir)) {
|
|
1177
|
+
for (const agentId of readdirSync(insightsDir)) {
|
|
1178
|
+
const agentInsightDir = join2(insightsDir, agentId);
|
|
1179
|
+
let files;
|
|
1180
|
+
try {
|
|
1181
|
+
files = readdirSync(agentInsightDir).filter((f) => f.endsWith(".json"));
|
|
1182
|
+
} catch {
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
for (const file of files) {
|
|
1186
|
+
const parts = file.replace(".json", "").split("-");
|
|
1187
|
+
const tsPart = parts[parts.length - 2];
|
|
1188
|
+
const ts = tsPart ? Number.parseInt(tsPart, 10) : 0;
|
|
1189
|
+
if (!Number.isNaN(ts) && ts < options.olderThan) {
|
|
1190
|
+
try {
|
|
1191
|
+
rmSync(join2(agentInsightDir, file));
|
|
1192
|
+
removed++;
|
|
1193
|
+
} catch {
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return { removed };
|
|
1200
|
+
},
|
|
1201
|
+
appendInsight(event) {
|
|
1202
|
+
const agentInsightDir = join2(insightsDir, event.agentId);
|
|
1203
|
+
ensureDir(agentInsightDir);
|
|
1204
|
+
const seq = String(writeCounter++).padStart(4, "0");
|
|
1205
|
+
const fileName = `${event.insightType}-${event.timestamp}-${seq}.json`;
|
|
1206
|
+
writeFileSync2(join2(agentInsightDir, fileName), JSON.stringify(event, null, 2), "utf-8");
|
|
1207
|
+
},
|
|
1208
|
+
getRecentInsights(agentId, options) {
|
|
1209
|
+
const limit = options?.limit ?? 10;
|
|
1210
|
+
const typeFilter = options?.type;
|
|
1211
|
+
const agentInsightDir = join2(insightsDir, agentId);
|
|
1212
|
+
if (!existsSync(agentInsightDir)) return [];
|
|
1213
|
+
const files = readdirSync(agentInsightDir).filter((f) => f.endsWith(".json"));
|
|
1214
|
+
const events = [];
|
|
1215
|
+
for (const file of files) {
|
|
1216
|
+
const event = readJson(join2(agentInsightDir, file));
|
|
1217
|
+
if (event) {
|
|
1218
|
+
if (typeFilter && event.insightType !== typeFilter) continue;
|
|
1219
|
+
events.push(event);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
events.sort((a, b) => b.timestamp - a.timestamp);
|
|
1223
|
+
return events.slice(0, limit);
|
|
1224
|
+
},
|
|
1225
|
+
// EventWriter interface
|
|
1226
|
+
async write(_graph) {
|
|
1227
|
+
},
|
|
1228
|
+
async writeEvent(event) {
|
|
1229
|
+
this.append(event);
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// src/policy-source.ts
|
|
1235
|
+
import { readdirSync as readdirSync2 } from "fs";
|
|
1236
|
+
import { join as join3 } from "path";
|
|
1237
|
+
function createPolicySource(store) {
|
|
1238
|
+
return {
|
|
1239
|
+
recentFailureRate(agentId) {
|
|
1240
|
+
const profile = store.getAgentProfile(agentId);
|
|
1241
|
+
return profile?.failureRate ?? 0;
|
|
1242
|
+
},
|
|
1243
|
+
isKnownBottleneck(nodeName) {
|
|
1244
|
+
const profilesDir = join3(store.baseDir, "profiles");
|
|
1245
|
+
let agentIds;
|
|
1246
|
+
try {
|
|
1247
|
+
agentIds = readdirSync2(profilesDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
|
|
1248
|
+
} catch {
|
|
1249
|
+
return false;
|
|
1250
|
+
}
|
|
1251
|
+
for (const agentId of agentIds) {
|
|
1252
|
+
const profile = store.getAgentProfile(agentId);
|
|
1253
|
+
if (profile?.knownBottlenecks.includes(nodeName)) {
|
|
1254
|
+
return true;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
return false;
|
|
1258
|
+
},
|
|
1259
|
+
lastConformanceScore(agentId) {
|
|
1260
|
+
const profile = store.getAgentProfile(agentId);
|
|
1261
|
+
return profile?.lastConformanceScore ?? null;
|
|
1262
|
+
},
|
|
1263
|
+
getAgentProfile(agentId) {
|
|
1264
|
+
return store.getAgentProfile(agentId);
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// src/receipts.ts
|
|
1270
|
+
function extractTokenCost(node) {
|
|
1271
|
+
const semantic = node.metadata?.semantic;
|
|
1272
|
+
if (semantic?.tokenCost !== void 0 && semantic.tokenCost !== null) {
|
|
1273
|
+
return semantic.tokenCost;
|
|
1274
|
+
}
|
|
1275
|
+
if (node.state?.tokenCost !== void 0 && node.state.tokenCost !== null) {
|
|
1276
|
+
return node.state.tokenCost;
|
|
1277
|
+
}
|
|
1278
|
+
return null;
|
|
1279
|
+
}
|
|
1280
|
+
function extractError(node) {
|
|
1281
|
+
if (node.state?.error !== void 0 && node.state.error !== null) {
|
|
1282
|
+
return String(node.state.error);
|
|
1283
|
+
}
|
|
1284
|
+
if (node.metadata?.error !== void 0 && node.metadata.error !== null) {
|
|
1285
|
+
return String(node.metadata.error);
|
|
1286
|
+
}
|
|
1287
|
+
return null;
|
|
1288
|
+
}
|
|
1289
|
+
function nodeDuration(node) {
|
|
1290
|
+
if (node.endTime === null) return null;
|
|
1291
|
+
return node.endTime - node.startTime;
|
|
1292
|
+
}
|
|
1293
|
+
function toReceipt(graph) {
|
|
1294
|
+
const nodes = [...graph.nodes.values()];
|
|
1295
|
+
nodes.sort((a, b) => a.startTime - b.startTime);
|
|
1296
|
+
const steps = nodes.map((node) => ({
|
|
1297
|
+
nodeId: node.id,
|
|
1298
|
+
name: node.name,
|
|
1299
|
+
type: node.type,
|
|
1300
|
+
status: node.status,
|
|
1301
|
+
durationMs: nodeDuration(node),
|
|
1302
|
+
tokenCost: extractTokenCost(node),
|
|
1303
|
+
error: extractError(node)
|
|
1304
|
+
}));
|
|
1305
|
+
let succeeded = 0;
|
|
1306
|
+
let failed = 0;
|
|
1307
|
+
const skipped = 0;
|
|
1308
|
+
for (const node of nodes) {
|
|
1309
|
+
if (node.status === "completed") {
|
|
1310
|
+
succeeded++;
|
|
1311
|
+
} else if (node.status === "failed" || node.status === "hung" || node.status === "timeout") {
|
|
1312
|
+
failed++;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
const attempted = nodes.length;
|
|
1316
|
+
let totalTokenCost = null;
|
|
1317
|
+
for (const step of steps) {
|
|
1318
|
+
if (step.tokenCost !== null) {
|
|
1319
|
+
totalTokenCost = (totalTokenCost ?? 0) + step.tokenCost;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
const totalDurationMs = graph.endTime !== null ? graph.endTime - graph.startTime : null;
|
|
1323
|
+
return {
|
|
1324
|
+
runId: graph.id,
|
|
1325
|
+
agentId: graph.agentId,
|
|
1326
|
+
status: graph.status,
|
|
1327
|
+
startTime: graph.startTime,
|
|
1328
|
+
endTime: graph.endTime,
|
|
1329
|
+
totalDurationMs,
|
|
1330
|
+
totalTokenCost,
|
|
1331
|
+
steps,
|
|
1332
|
+
summary: { attempted, succeeded, failed, skipped }
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
function formatReceipt(receipt) {
|
|
1336
|
+
const lines = [];
|
|
1337
|
+
lines.push("=== Run Receipt ===");
|
|
1338
|
+
lines.push(`Run: ${receipt.runId}`);
|
|
1339
|
+
lines.push(`Agent: ${receipt.agentId}`);
|
|
1340
|
+
lines.push(`Status: ${receipt.status}`);
|
|
1341
|
+
lines.push(
|
|
1342
|
+
`Duration: ${receipt.totalDurationMs !== null ? `${receipt.totalDurationMs}ms` : "\u2014"}`
|
|
1343
|
+
);
|
|
1344
|
+
lines.push("");
|
|
1345
|
+
const s = receipt.summary;
|
|
1346
|
+
lines.push(
|
|
1347
|
+
`Summary: ${s.attempted} attempted, ${s.succeeded} succeeded, ${s.failed} failed, ${s.skipped} skipped`
|
|
1348
|
+
);
|
|
1349
|
+
lines.push("");
|
|
1350
|
+
const nameWidth = Math.max(4, ...receipt.steps.map((st) => st.name.length));
|
|
1351
|
+
const typeWidth = Math.max(4, ...receipt.steps.map((st) => st.type.length));
|
|
1352
|
+
const pad = (str, width) => str.padEnd(width);
|
|
1353
|
+
const padNum = (str, width) => str.padStart(width);
|
|
1354
|
+
const idxWidth = Math.max(2, String(receipt.steps.length).length);
|
|
1355
|
+
const statusWidth = 9;
|
|
1356
|
+
const durWidth = 10;
|
|
1357
|
+
const tokWidth = 8;
|
|
1358
|
+
lines.push(
|
|
1359
|
+
` ${padNum("#", idxWidth)} | ${pad("Step", nameWidth)} | ${pad("Type", typeWidth)} | ${pad("Status", statusWidth)} | ${padNum("Duration", durWidth)} | ${padNum("Tokens", tokWidth)}`
|
|
1360
|
+
);
|
|
1361
|
+
lines.push(
|
|
1362
|
+
`${"-".repeat(idxWidth + 1)}|${"-".repeat(nameWidth + 2)}|${"-".repeat(typeWidth + 2)}|${"-".repeat(statusWidth + 2)}|${"-".repeat(durWidth + 2)}|${"-".repeat(tokWidth + 2)}`
|
|
1363
|
+
);
|
|
1364
|
+
for (let i = 0; i < receipt.steps.length; i++) {
|
|
1365
|
+
const step = receipt.steps[i];
|
|
1366
|
+
const durStr = step.durationMs !== null ? `${step.durationMs}ms` : "\u2014";
|
|
1367
|
+
const tokStr = step.tokenCost !== null ? String(step.tokenCost) : "\u2014";
|
|
1368
|
+
lines.push(
|
|
1369
|
+
` ${padNum(String(i + 1), idxWidth)} | ${pad(step.name, nameWidth)} | ${pad(step.type, typeWidth)} | ${pad(step.status, statusWidth)} | ${padNum(durStr, durWidth)} | ${padNum(tokStr, tokWidth)}`
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
lines.push("");
|
|
1373
|
+
if (receipt.totalTokenCost !== null) {
|
|
1374
|
+
lines.push(`Total token cost: ${receipt.totalTokenCost}`);
|
|
1375
|
+
} else {
|
|
1376
|
+
lines.push("Total token cost: no cost data");
|
|
1377
|
+
}
|
|
1378
|
+
return lines.join("\n");
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
// src/soma-event-writer.ts
|
|
1382
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1383
|
+
import { join as join4 } from "path";
|
|
1384
|
+
function compactIso(epochMs) {
|
|
1385
|
+
return new Date(epochMs).toISOString().slice(0, 19).replace(/:/g, "");
|
|
1386
|
+
}
|
|
1387
|
+
function isoDate(epochMs) {
|
|
1388
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
1389
|
+
}
|
|
1390
|
+
function renderFrontmatter(fields) {
|
|
1391
|
+
const lines = ["---"];
|
|
1392
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
1393
|
+
if (value === void 0) continue;
|
|
1394
|
+
if (Array.isArray(value)) {
|
|
1395
|
+
lines.push(`${key}: [${value.map((v) => `'${v}'`).join(", ")}]`);
|
|
1396
|
+
} else if (typeof value === "string") {
|
|
1397
|
+
lines.push(`${key}: '${value}'`);
|
|
1398
|
+
} else {
|
|
1399
|
+
lines.push(`${key}: ${String(value)}`);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
lines.push("---");
|
|
1403
|
+
return lines.join("\n");
|
|
1404
|
+
}
|
|
1405
|
+
function formatDuration(ms) {
|
|
1406
|
+
if (ms < 1e3) return `${ms.toFixed(0)}ms`;
|
|
1407
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
1408
|
+
return `${(ms / 6e4).toFixed(1)}min`;
|
|
1409
|
+
}
|
|
1410
|
+
function executionEventToMarkdown(event) {
|
|
1411
|
+
const isCompleted = event.eventType === "execution.completed";
|
|
1412
|
+
const subtype = isCompleted ? "completed" : "failed";
|
|
1413
|
+
const tags = ["agentflow/execution", `agent/${event.agentId}`, `status/${subtype}`];
|
|
1414
|
+
if (event.processContext?.isAnomaly) {
|
|
1415
|
+
tags.push("agentflow/anomaly");
|
|
1416
|
+
}
|
|
1417
|
+
const frontmatter = {
|
|
1418
|
+
type: "execution",
|
|
1419
|
+
subtype,
|
|
1420
|
+
name: `Execution: ${event.agentId} \u2014 ${subtype}`,
|
|
1421
|
+
source: "agentflow",
|
|
1422
|
+
created: isoDate(event.timestamp),
|
|
1423
|
+
alfred_tags: tags,
|
|
1424
|
+
agentflow_graph_id: event.graphId,
|
|
1425
|
+
duration_ms: event.duration,
|
|
1426
|
+
node_count: event.nodeCount
|
|
1427
|
+
};
|
|
1428
|
+
if (event.processContext) {
|
|
1429
|
+
frontmatter.conformance_score = event.processContext.conformanceScore;
|
|
1430
|
+
frontmatter.is_anomaly = event.processContext.isAnomaly;
|
|
1431
|
+
}
|
|
1432
|
+
const body = [];
|
|
1433
|
+
body.push(`# Execution: ${event.agentId} \u2014 ${subtype}
|
|
1434
|
+
`);
|
|
1435
|
+
body.push(`**Duration:** ${formatDuration(event.duration)} `);
|
|
1436
|
+
body.push(`**Nodes:** ${event.nodeCount} `);
|
|
1437
|
+
body.push(`**Status:** ${event.status}
|
|
1438
|
+
`);
|
|
1439
|
+
if (event.pathSignature) {
|
|
1440
|
+
body.push(`## Path
|
|
1441
|
+
`);
|
|
1442
|
+
body.push(`\`${event.pathSignature}\`
|
|
1443
|
+
`);
|
|
1444
|
+
}
|
|
1445
|
+
if (!isCompleted && event.failurePoint) {
|
|
1446
|
+
const fp = event.failurePoint;
|
|
1447
|
+
body.push(`## Failure Point
|
|
1448
|
+
`);
|
|
1449
|
+
body.push(`**Node:** ${fp.nodeType}:${fp.nodeName} (\`${fp.nodeId}\`) `);
|
|
1450
|
+
if (fp.error) {
|
|
1451
|
+
body.push(`**Error:** ${fp.error}
|
|
1452
|
+
`);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
if (event.processContext) {
|
|
1456
|
+
body.push(`## Process Context
|
|
1457
|
+
`);
|
|
1458
|
+
body.push(`**Conformance:** ${(event.processContext.conformanceScore * 100).toFixed(0)}% `);
|
|
1459
|
+
body.push(`**Anomaly:** ${event.processContext.isAnomaly ? "yes" : "no"}
|
|
1460
|
+
`);
|
|
1461
|
+
}
|
|
1462
|
+
body.push(`## Related
|
|
1463
|
+
`);
|
|
1464
|
+
body.push(`- [[agent/${event.agentId}]]`);
|
|
1465
|
+
return `${renderFrontmatter(frontmatter)}
|
|
1466
|
+
|
|
1467
|
+
${body.join("\n")}`;
|
|
1468
|
+
}
|
|
1469
|
+
function patternEventToMarkdown(event) {
|
|
1470
|
+
const { pattern } = event;
|
|
1471
|
+
const tags = ["agentflow/pattern", `agent/${event.agentId}`];
|
|
1472
|
+
const frontmatter = {
|
|
1473
|
+
type: "synthesis",
|
|
1474
|
+
subtype: "pattern-discovery",
|
|
1475
|
+
name: `Pattern: ${event.agentId} \u2014 ${pattern.variantCount} variants across ${pattern.totalGraphs} runs`,
|
|
1476
|
+
source: "agentflow",
|
|
1477
|
+
created: isoDate(event.timestamp),
|
|
1478
|
+
alfred_tags: tags,
|
|
1479
|
+
variant_count: pattern.variantCount,
|
|
1480
|
+
total_graphs: pattern.totalGraphs
|
|
1481
|
+
};
|
|
1482
|
+
const body = [];
|
|
1483
|
+
body.push(`# Pattern: ${event.agentId}
|
|
1484
|
+
`);
|
|
1485
|
+
body.push(`**Variants:** ${pattern.variantCount} `);
|
|
1486
|
+
body.push(`**Total Runs:** ${pattern.totalGraphs}
|
|
1487
|
+
`);
|
|
1488
|
+
if (pattern.topVariants.length > 0) {
|
|
1489
|
+
body.push(`## Top Variants
|
|
1490
|
+
`);
|
|
1491
|
+
body.push(`| Path | Count | % |`);
|
|
1492
|
+
body.push(`|------|-------|---|`);
|
|
1493
|
+
for (const v of pattern.topVariants) {
|
|
1494
|
+
const sig = v.pathSignature.length > 60 ? `${v.pathSignature.slice(0, 57)}...` : v.pathSignature;
|
|
1495
|
+
body.push(`| \`${sig}\` | ${v.count} | ${v.percentage.toFixed(1)}% |`);
|
|
1496
|
+
}
|
|
1497
|
+
body.push("");
|
|
1498
|
+
}
|
|
1499
|
+
if (pattern.topBottlenecks.length > 0) {
|
|
1500
|
+
body.push(`## Top Bottlenecks
|
|
1501
|
+
`);
|
|
1502
|
+
body.push(`| Node | Type | p95 |`);
|
|
1503
|
+
body.push(`|------|------|-----|`);
|
|
1504
|
+
for (const b of pattern.topBottlenecks) {
|
|
1505
|
+
body.push(`| ${b.nodeName} | ${b.nodeType} | ${formatDuration(b.p95)} |`);
|
|
1506
|
+
}
|
|
1507
|
+
body.push("");
|
|
1508
|
+
}
|
|
1509
|
+
body.push(`## Related
|
|
1510
|
+
`);
|
|
1511
|
+
body.push(`- [[agent/${event.agentId}]]`);
|
|
1512
|
+
return `${renderFrontmatter(frontmatter)}
|
|
1513
|
+
|
|
1514
|
+
${body.join("\n")}`;
|
|
1515
|
+
}
|
|
1516
|
+
function createSomaEventWriter(config) {
|
|
1517
|
+
const { inboxDir } = config;
|
|
1518
|
+
function ensureDir() {
|
|
1519
|
+
mkdirSync3(inboxDir, { recursive: true });
|
|
1520
|
+
}
|
|
1521
|
+
function eventFileName(event) {
|
|
1522
|
+
const agentId = event.agentId;
|
|
1523
|
+
const ts = compactIso(event.timestamp);
|
|
1524
|
+
if (event.eventType === "pattern.discovered" || event.eventType === "pattern.updated") {
|
|
1525
|
+
return `synthesis-${agentId}-${ts}.md`;
|
|
1526
|
+
}
|
|
1527
|
+
return `execution-${agentId}-${ts}.md`;
|
|
1528
|
+
}
|
|
1529
|
+
return {
|
|
1530
|
+
async write(_graph) {
|
|
1531
|
+
},
|
|
1532
|
+
async writeEvent(event) {
|
|
1533
|
+
ensureDir();
|
|
1534
|
+
const markdown = event.eventType === "pattern.discovered" || event.eventType === "pattern.updated" ? patternEventToMarkdown(event) : executionEventToMarkdown(event);
|
|
1535
|
+
const fileName = eventFileName(event);
|
|
1536
|
+
writeFileSync3(join4(inboxDir, fileName), markdown, "utf-8");
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
189
1540
|
export {
|
|
190
1541
|
auditProcesses,
|
|
1542
|
+
buildAgentSummaryPrompt,
|
|
1543
|
+
buildAnomalyExplanationPrompt,
|
|
1544
|
+
buildFailureAnalysisPrompt,
|
|
1545
|
+
buildFixSuggestionPrompt,
|
|
1546
|
+
checkConformance,
|
|
191
1547
|
checkGuards,
|
|
1548
|
+
createEventEmitter,
|
|
1549
|
+
createExecutionEvent,
|
|
192
1550
|
createGraphBuilder,
|
|
1551
|
+
createInsightEngine,
|
|
1552
|
+
createJsonEventWriter,
|
|
1553
|
+
createKnowledgeStore,
|
|
1554
|
+
createPatternEvent,
|
|
1555
|
+
createPolicySource,
|
|
1556
|
+
createSomaEventWriter,
|
|
193
1557
|
createTraceStore,
|
|
1558
|
+
discoverAllProcessConfigs,
|
|
1559
|
+
discoverProcess,
|
|
194
1560
|
discoverProcessConfig,
|
|
1561
|
+
findVariants,
|
|
195
1562
|
findWaitingOn,
|
|
196
1563
|
formatAuditReport,
|
|
1564
|
+
formatReceipt,
|
|
1565
|
+
getBottlenecks,
|
|
197
1566
|
getChildren,
|
|
198
1567
|
getCriticalPath,
|
|
199
1568
|
getDepth,
|
|
@@ -202,6 +1571,7 @@ export {
|
|
|
202
1571
|
getHungNodes,
|
|
203
1572
|
getNode,
|
|
204
1573
|
getParent,
|
|
1574
|
+
getPathSignature,
|
|
205
1575
|
getStats,
|
|
206
1576
|
getSubtree,
|
|
207
1577
|
getTraceTree,
|
|
@@ -213,6 +1583,7 @@ export {
|
|
|
213
1583
|
startWatch,
|
|
214
1584
|
stitchTrace,
|
|
215
1585
|
toAsciiTree,
|
|
1586
|
+
toReceipt,
|
|
216
1587
|
toTimeline,
|
|
217
1588
|
withGuards
|
|
218
1589
|
};
|