agentflow-core 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5PRHVYYD.js → chunk-6X5HU5LB.js} +123 -52
- package/dist/cli.cjs +150 -75
- package/dist/cli.js +7 -3
- package/dist/index.cjs +1405 -119
- package/dist/index.d.cts +710 -37
- package/dist/index.d.ts +710 -37
- package/dist/index.js +1207 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,12 +23,655 @@ import {
|
|
|
23
23
|
stitchTrace,
|
|
24
24
|
toAsciiTree,
|
|
25
25
|
toTimeline
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-6X5HU5LB.js";
|
|
27
27
|
import {
|
|
28
28
|
graphToJson,
|
|
29
29
|
loadGraph
|
|
30
30
|
} from "./chunk-DY7YHFIB.js";
|
|
31
31
|
|
|
32
|
+
// src/process-mining.ts
|
|
33
|
+
function getPathSignature(graph) {
|
|
34
|
+
const root = graph.nodes.get(graph.rootNodeId);
|
|
35
|
+
if (!root) return "";
|
|
36
|
+
const parts = [];
|
|
37
|
+
function walk(node) {
|
|
38
|
+
parts.push(`${node.type}:${node.name}`);
|
|
39
|
+
const childNodes = [];
|
|
40
|
+
for (const childId of node.children) {
|
|
41
|
+
const child = graph.nodes.get(childId);
|
|
42
|
+
if (child) childNodes.push(child);
|
|
43
|
+
}
|
|
44
|
+
childNodes.sort((a, b) => {
|
|
45
|
+
const keyA = `${a.type}:${a.name}`;
|
|
46
|
+
const keyB = `${b.type}:${b.name}`;
|
|
47
|
+
return keyA.localeCompare(keyB);
|
|
48
|
+
});
|
|
49
|
+
for (const child of childNodes) {
|
|
50
|
+
walk(child);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
walk(root);
|
|
54
|
+
return parts.join("\u2192");
|
|
55
|
+
}
|
|
56
|
+
function stepKey(node) {
|
|
57
|
+
return `${node.type}:${node.name}`;
|
|
58
|
+
}
|
|
59
|
+
function discoverProcess(graphs) {
|
|
60
|
+
if (graphs.length === 0) {
|
|
61
|
+
throw new Error("discoverProcess requires at least one graph");
|
|
62
|
+
}
|
|
63
|
+
const steps = /* @__PURE__ */ new Set();
|
|
64
|
+
const transitionCounts = /* @__PURE__ */ new Map();
|
|
65
|
+
const outgoingCounts = /* @__PURE__ */ new Map();
|
|
66
|
+
for (const graph of graphs) {
|
|
67
|
+
for (const node of graph.nodes.values()) {
|
|
68
|
+
const parentKey = stepKey(node);
|
|
69
|
+
steps.add(parentKey);
|
|
70
|
+
for (const childId of node.children) {
|
|
71
|
+
const child = graph.nodes.get(childId);
|
|
72
|
+
if (!child) continue;
|
|
73
|
+
const childKey = stepKey(child);
|
|
74
|
+
const tKey = `${parentKey}\0${childKey}`;
|
|
75
|
+
transitionCounts.set(tKey, (transitionCounts.get(tKey) ?? 0) + 1);
|
|
76
|
+
outgoingCounts.set(parentKey, (outgoingCounts.get(parentKey) ?? 0) + 1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const transitions = [];
|
|
81
|
+
for (const [tKey, count] of transitionCounts) {
|
|
82
|
+
const [from, to] = tKey.split("\0");
|
|
83
|
+
const outgoing = outgoingCounts.get(from) ?? count;
|
|
84
|
+
transitions.push({
|
|
85
|
+
from,
|
|
86
|
+
to,
|
|
87
|
+
count,
|
|
88
|
+
probability: count / outgoing
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
transitions.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
|
|
92
|
+
return {
|
|
93
|
+
steps: [...steps].sort(),
|
|
94
|
+
transitions,
|
|
95
|
+
totalGraphs: graphs.length,
|
|
96
|
+
agentId: graphs[0].agentId
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function findVariants(graphs) {
|
|
100
|
+
if (graphs.length === 0) return [];
|
|
101
|
+
const groups = /* @__PURE__ */ new Map();
|
|
102
|
+
for (const graph of graphs) {
|
|
103
|
+
const sig = getPathSignature(graph);
|
|
104
|
+
const group = groups.get(sig) ?? [];
|
|
105
|
+
group.push(graph);
|
|
106
|
+
groups.set(sig, group);
|
|
107
|
+
}
|
|
108
|
+
const total = graphs.length;
|
|
109
|
+
const variants = [];
|
|
110
|
+
for (const [pathSignature, groupGraphs] of groups) {
|
|
111
|
+
variants.push({
|
|
112
|
+
pathSignature,
|
|
113
|
+
count: groupGraphs.length,
|
|
114
|
+
percentage: groupGraphs.length / total * 100,
|
|
115
|
+
graphIds: groupGraphs.map((g) => g.id),
|
|
116
|
+
exampleGraph: groupGraphs[0]
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
variants.sort((a, b) => {
|
|
120
|
+
const freqDiff = b.count - a.count;
|
|
121
|
+
if (freqDiff !== 0) return freqDiff;
|
|
122
|
+
return a.pathSignature.localeCompare(b.pathSignature);
|
|
123
|
+
});
|
|
124
|
+
return variants;
|
|
125
|
+
}
|
|
126
|
+
function percentile(sorted, p) {
|
|
127
|
+
if (sorted.length === 0) return 0;
|
|
128
|
+
if (sorted.length === 1) return sorted[0];
|
|
129
|
+
const index = p / 100 * (sorted.length - 1);
|
|
130
|
+
const lower = Math.floor(index);
|
|
131
|
+
const upper = Math.ceil(index);
|
|
132
|
+
if (lower === upper) return sorted[lower];
|
|
133
|
+
const weight = index - lower;
|
|
134
|
+
return sorted[lower] * (1 - weight) + sorted[upper] * weight;
|
|
135
|
+
}
|
|
136
|
+
function getBottlenecks(graphs) {
|
|
137
|
+
if (graphs.length === 0) return [];
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
const stats = /* @__PURE__ */ new Map();
|
|
140
|
+
for (const graph of graphs) {
|
|
141
|
+
for (const node of graph.nodes.values()) {
|
|
142
|
+
const key = `${node.type}:${node.name}`;
|
|
143
|
+
const entry = stats.get(key) ?? { durations: [], nodeType: node.type, nodeName: node.name };
|
|
144
|
+
const end = node.endTime ?? now;
|
|
145
|
+
entry.durations.push(end - node.startTime);
|
|
146
|
+
stats.set(key, entry);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const total = graphs.length;
|
|
150
|
+
const bottlenecks = [];
|
|
151
|
+
for (const [, entry] of stats) {
|
|
152
|
+
const sorted = [...entry.durations].sort((a, b) => a - b);
|
|
153
|
+
bottlenecks.push({
|
|
154
|
+
nodeName: entry.nodeName,
|
|
155
|
+
nodeType: entry.nodeType,
|
|
156
|
+
occurrences: sorted.length,
|
|
157
|
+
durations: {
|
|
158
|
+
median: percentile(sorted, 50),
|
|
159
|
+
p95: percentile(sorted, 95),
|
|
160
|
+
p99: percentile(sorted, 99),
|
|
161
|
+
min: sorted[0],
|
|
162
|
+
max: sorted[sorted.length - 1]
|
|
163
|
+
},
|
|
164
|
+
percentOfGraphs: sorted.length / total * 100
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
bottlenecks.sort((a, b) => b.durations.p95 - a.durations.p95);
|
|
168
|
+
return bottlenecks;
|
|
169
|
+
}
|
|
170
|
+
function extractGraphTransitions(graph) {
|
|
171
|
+
const transitions = /* @__PURE__ */ new Set();
|
|
172
|
+
for (const node of graph.nodes.values()) {
|
|
173
|
+
const parentKey = stepKey(node);
|
|
174
|
+
for (const childId of node.children) {
|
|
175
|
+
const child = graph.nodes.get(childId);
|
|
176
|
+
if (!child) continue;
|
|
177
|
+
transitions.add(`${parentKey}\0${stepKey(child)}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return transitions;
|
|
181
|
+
}
|
|
182
|
+
function checkConformance(graph, model) {
|
|
183
|
+
const graphTransitions = extractGraphTransitions(graph);
|
|
184
|
+
const deviations = [];
|
|
185
|
+
const modelLookup = /* @__PURE__ */ new Map();
|
|
186
|
+
for (const t of model.transitions) {
|
|
187
|
+
modelLookup.set(`${t.from}\0${t.to}`, t);
|
|
188
|
+
}
|
|
189
|
+
let totalChecks = 0;
|
|
190
|
+
let deviationCount = 0;
|
|
191
|
+
for (const tKey of graphTransitions) {
|
|
192
|
+
totalChecks++;
|
|
193
|
+
const [from, to] = tKey.split("\0");
|
|
194
|
+
const modelTransition = modelLookup.get(tKey);
|
|
195
|
+
if (!modelTransition) {
|
|
196
|
+
deviationCount++;
|
|
197
|
+
deviations.push({
|
|
198
|
+
type: "unexpected-transition",
|
|
199
|
+
from,
|
|
200
|
+
to,
|
|
201
|
+
message: `Unexpected transition ${from} \u2192 ${to} (not in process model)`
|
|
202
|
+
});
|
|
203
|
+
} else if (modelTransition.probability < 0.1) {
|
|
204
|
+
deviationCount++;
|
|
205
|
+
deviations.push({
|
|
206
|
+
type: "low-frequency-path",
|
|
207
|
+
from,
|
|
208
|
+
to,
|
|
209
|
+
message: `Low-frequency path ${from} \u2192 ${to} (model probability: ${(modelTransition.probability * 100).toFixed(1)}%)`,
|
|
210
|
+
modelProbability: modelTransition.probability
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const graphSteps = /* @__PURE__ */ new Set();
|
|
215
|
+
for (const node of graph.nodes.values()) {
|
|
216
|
+
graphSteps.add(stepKey(node));
|
|
217
|
+
}
|
|
218
|
+
for (const t of model.transitions) {
|
|
219
|
+
if (t.probability > 0.5) {
|
|
220
|
+
const tKey = `${t.from}\0${t.to}`;
|
|
221
|
+
if (graphSteps.has(t.from) && !graphTransitions.has(tKey)) {
|
|
222
|
+
totalChecks++;
|
|
223
|
+
deviationCount++;
|
|
224
|
+
deviations.push({
|
|
225
|
+
type: "missing-transition",
|
|
226
|
+
from: t.from,
|
|
227
|
+
to: t.to,
|
|
228
|
+
message: `Missing expected transition ${t.from} \u2192 ${t.to} (model probability: ${(t.probability * 100).toFixed(1)}%)`,
|
|
229
|
+
modelProbability: t.probability
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const conformanceScore = totalChecks === 0 ? 1 : (totalChecks - deviationCount) / totalChecks;
|
|
235
|
+
return {
|
|
236
|
+
conformanceScore,
|
|
237
|
+
isConforming: deviations.length === 0,
|
|
238
|
+
deviations
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/event-emitter.ts
|
|
243
|
+
var SCHEMA_VERSION = 1;
|
|
244
|
+
function createExecutionEvent(graph, options) {
|
|
245
|
+
const duration = graph.endTime !== null ? graph.endTime - graph.startTime : Date.now() - graph.startTime;
|
|
246
|
+
let failurePoint;
|
|
247
|
+
if (graph.status === "failed") {
|
|
248
|
+
let candidate;
|
|
249
|
+
for (const node of graph.nodes.values()) {
|
|
250
|
+
if (node.status === "failed" || node.status === "timeout") {
|
|
251
|
+
const errorMeta = node.metadata.error;
|
|
252
|
+
const fp = {
|
|
253
|
+
nodeId: node.id,
|
|
254
|
+
nodeName: node.name,
|
|
255
|
+
nodeType: node.type,
|
|
256
|
+
error: typeof errorMeta === "string" ? errorMeta : void 0
|
|
257
|
+
};
|
|
258
|
+
if (node.id !== graph.rootNodeId) {
|
|
259
|
+
failurePoint = fp;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
if (!candidate) candidate = fp;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (!failurePoint) failurePoint = candidate;
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
eventType: graph.status === "failed" ? "execution.failed" : "execution.completed",
|
|
269
|
+
graphId: graph.id,
|
|
270
|
+
agentId: graph.agentId,
|
|
271
|
+
timestamp: Date.now(),
|
|
272
|
+
schemaVersion: SCHEMA_VERSION,
|
|
273
|
+
status: graph.status,
|
|
274
|
+
duration,
|
|
275
|
+
nodeCount: graph.nodes.size,
|
|
276
|
+
pathSignature: getPathSignature(graph),
|
|
277
|
+
...failurePoint ? { failurePoint } : {},
|
|
278
|
+
...options?.processContext ? { processContext: options.processContext } : {},
|
|
279
|
+
...options?.semantic ? { semantic: options.semantic } : {},
|
|
280
|
+
violations: options?.violations ?? []
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function createPatternEvent(agentId, model, variants, bottlenecks) {
|
|
284
|
+
return {
|
|
285
|
+
eventType: "pattern.discovered",
|
|
286
|
+
agentId,
|
|
287
|
+
timestamp: Date.now(),
|
|
288
|
+
schemaVersion: SCHEMA_VERSION,
|
|
289
|
+
pattern: {
|
|
290
|
+
totalGraphs: model.totalGraphs,
|
|
291
|
+
variantCount: variants.length,
|
|
292
|
+
topVariants: variants.slice(0, 5).map((v) => ({
|
|
293
|
+
pathSignature: v.pathSignature,
|
|
294
|
+
count: v.count,
|
|
295
|
+
percentage: v.percentage
|
|
296
|
+
})),
|
|
297
|
+
topBottlenecks: bottlenecks.slice(0, 5).map((b) => ({
|
|
298
|
+
nodeName: b.nodeName,
|
|
299
|
+
nodeType: b.nodeType,
|
|
300
|
+
p95: b.durations.p95
|
|
301
|
+
})),
|
|
302
|
+
processModel: model
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function createEventEmitter(config) {
|
|
307
|
+
const writers = config?.writers ?? [];
|
|
308
|
+
const knowledgeStore = config?.knowledgeStore;
|
|
309
|
+
const onError = config?.onError ?? (() => {
|
|
310
|
+
});
|
|
311
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
312
|
+
return {
|
|
313
|
+
async emit(event) {
|
|
314
|
+
if (knowledgeStore) {
|
|
315
|
+
try {
|
|
316
|
+
knowledgeStore.append(event);
|
|
317
|
+
} catch (err) {
|
|
318
|
+
onError(err);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
for (const writer of writers) {
|
|
322
|
+
try {
|
|
323
|
+
await writer.writeEvent(event);
|
|
324
|
+
} catch (err) {
|
|
325
|
+
onError(err);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
for (const listener of subscribers) {
|
|
329
|
+
try {
|
|
330
|
+
listener(event);
|
|
331
|
+
} catch (err) {
|
|
332
|
+
onError(err);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
subscribe(listener) {
|
|
337
|
+
subscribers.add(listener);
|
|
338
|
+
return () => {
|
|
339
|
+
subscribers.delete(listener);
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/prompt-builder.ts
|
|
346
|
+
var ROLE = "You are analyzing execution data for an AI agent system. Provide clear, actionable analysis based on the data below.";
|
|
347
|
+
function fmtDuration(ms) {
|
|
348
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
349
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
350
|
+
}
|
|
351
|
+
function fmtTime(ts) {
|
|
352
|
+
return new Date(ts).toISOString();
|
|
353
|
+
}
|
|
354
|
+
function durationStats(durations) {
|
|
355
|
+
if (durations.length === 0) return { avg: 0, p50: 0, p95: 0, min: 0, max: 0 };
|
|
356
|
+
const sorted = [...durations].sort((a, b) => a - b);
|
|
357
|
+
const sum = sorted.reduce((a, b) => a + b, 0);
|
|
358
|
+
return {
|
|
359
|
+
avg: Math.round(sum / sorted.length),
|
|
360
|
+
p50: sorted[Math.floor(sorted.length * 0.5)] ?? 0,
|
|
361
|
+
p95: sorted[Math.floor(sorted.length * 0.95)] ?? 0,
|
|
362
|
+
min: sorted[0] ?? 0,
|
|
363
|
+
max: sorted[sorted.length - 1] ?? 0
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function buildFailureAnalysisPrompt(events, profile) {
|
|
367
|
+
const stats = durationStats(profile.recentDurations);
|
|
368
|
+
const failureDetails = events.map((e, i) => {
|
|
369
|
+
const lines = [
|
|
370
|
+
`Failure ${i + 1}:`,
|
|
371
|
+
` Time: ${fmtTime(e.timestamp)}`,
|
|
372
|
+
` Duration: ${fmtDuration(e.duration)}`,
|
|
373
|
+
` Path: ${e.pathSignature}`
|
|
374
|
+
];
|
|
375
|
+
if (e.failurePoint) {
|
|
376
|
+
lines.push(` Failed at: ${e.failurePoint.nodeName} (${e.failurePoint.nodeType})`);
|
|
377
|
+
if (e.failurePoint.error) lines.push(` Error: ${e.failurePoint.error}`);
|
|
378
|
+
}
|
|
379
|
+
if (e.violations.length > 0) {
|
|
380
|
+
lines.push(` Violations: ${e.violations.map((v) => v.message).join("; ")}`);
|
|
381
|
+
}
|
|
382
|
+
return lines.join("\n");
|
|
383
|
+
}).join("\n\n");
|
|
384
|
+
return `${ROLE}
|
|
385
|
+
|
|
386
|
+
## Agent Profile
|
|
387
|
+
- Agent: ${profile.agentId}
|
|
388
|
+
- Total runs: ${profile.totalRuns}
|
|
389
|
+
- Failure rate: ${(profile.failureRate * 100).toFixed(1)}% (${profile.failureCount} failures / ${profile.totalRuns} total)
|
|
390
|
+
- Avg duration: ${fmtDuration(stats.avg)} (p50: ${fmtDuration(stats.p50)}, p95: ${fmtDuration(stats.p95)})
|
|
391
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
392
|
+
- Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
|
|
393
|
+
|
|
394
|
+
## Recent Failures (${events.length})
|
|
395
|
+
|
|
396
|
+
${failureDetails}
|
|
397
|
+
|
|
398
|
+
## Question
|
|
399
|
+
Analyze these failures. What patterns do you see? What is the most likely root cause? Are these related or independent failures?`;
|
|
400
|
+
}
|
|
401
|
+
function buildAnomalyExplanationPrompt(event, profile) {
|
|
402
|
+
const stats = durationStats(profile.recentDurations);
|
|
403
|
+
const eventDetails = [
|
|
404
|
+
`Time: ${fmtTime(event.timestamp)}`,
|
|
405
|
+
`Status: ${event.status}`,
|
|
406
|
+
`Duration: ${fmtDuration(event.duration)}`,
|
|
407
|
+
`Path: ${event.pathSignature}`,
|
|
408
|
+
`Node count: ${event.nodeCount}`
|
|
409
|
+
];
|
|
410
|
+
if (event.processContext) {
|
|
411
|
+
eventDetails.push(`Conformance score: ${event.processContext.conformanceScore}`);
|
|
412
|
+
eventDetails.push(`Is anomaly: ${event.processContext.isAnomaly}`);
|
|
413
|
+
eventDetails.push(`Variant: ${event.processContext.variant}`);
|
|
414
|
+
}
|
|
415
|
+
if (event.failurePoint) {
|
|
416
|
+
eventDetails.push(`Failed at: ${event.failurePoint.nodeName} (${event.failurePoint.nodeType})`);
|
|
417
|
+
if (event.failurePoint.error) eventDetails.push(`Error: ${event.failurePoint.error}`);
|
|
418
|
+
}
|
|
419
|
+
if (event.violations.length > 0) {
|
|
420
|
+
eventDetails.push(`Violations: ${event.violations.map((v) => v.message).join("; ")}`);
|
|
421
|
+
}
|
|
422
|
+
return `${ROLE}
|
|
423
|
+
|
|
424
|
+
## Agent Baseline (from profile)
|
|
425
|
+
- Agent: ${profile.agentId}
|
|
426
|
+
- Total runs: ${profile.totalRuns}
|
|
427
|
+
- Typical failure rate: ${(profile.failureRate * 100).toFixed(1)}%
|
|
428
|
+
- Typical duration: avg ${fmtDuration(stats.avg)}, p50 ${fmtDuration(stats.p50)}, p95 ${fmtDuration(stats.p95)}
|
|
429
|
+
- Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
|
|
430
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
431
|
+
|
|
432
|
+
## Anomalous Execution
|
|
433
|
+
${eventDetails.join("\n")}
|
|
434
|
+
|
|
435
|
+
## Question
|
|
436
|
+
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?`;
|
|
437
|
+
}
|
|
438
|
+
function buildAgentSummaryPrompt(profile, recentEvents, patterns) {
|
|
439
|
+
const stats = durationStats(profile.recentDurations);
|
|
440
|
+
const recentOutcomes = recentEvents.slice(0, 10).map((e) => ` ${fmtTime(e.timestamp)} \u2014 ${e.eventType} (${fmtDuration(e.duration)})`).join("\n");
|
|
441
|
+
const patternSummary = patterns.length > 0 ? patterns.slice(0, 3).map((p) => {
|
|
442
|
+
const lines = [
|
|
443
|
+
` Variants: ${p.pattern.variantCount} across ${p.pattern.totalGraphs} executions`,
|
|
444
|
+
` Top variant: ${p.pattern.topVariants[0]?.pathSignature ?? "N/A"} (${p.pattern.topVariants[0]?.percentage.toFixed(0) ?? 0}%)`
|
|
445
|
+
];
|
|
446
|
+
if (p.pattern.topBottlenecks.length > 0) {
|
|
447
|
+
const topB = p.pattern.topBottlenecks[0];
|
|
448
|
+
if (topB)
|
|
449
|
+
lines.push(` Top bottleneck: ${topB.nodeName} (p95: ${fmtDuration(topB.p95)})`);
|
|
450
|
+
}
|
|
451
|
+
return lines.join("\n");
|
|
452
|
+
}).join("\n\n") : " No patterns discovered yet.";
|
|
453
|
+
const dataNote = recentEvents.length === 0 && patterns.length === 0 ? "\nNote: Limited data available. Summary is based only on the profile statistics.\n" : "";
|
|
454
|
+
return `${ROLE}
|
|
455
|
+
|
|
456
|
+
## Agent Profile
|
|
457
|
+
- Agent: ${profile.agentId}
|
|
458
|
+
- Total runs: ${profile.totalRuns}
|
|
459
|
+
- Success rate: ${((1 - profile.failureRate) * 100).toFixed(1)}% (${profile.successCount} successes, ${profile.failureCount} failures)
|
|
460
|
+
- Duration: avg ${fmtDuration(stats.avg)}, p50 ${fmtDuration(stats.p50)}, p95 ${fmtDuration(stats.p95)}, range ${fmtDuration(stats.min)}\u2013${fmtDuration(stats.max)}
|
|
461
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
462
|
+
- Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
|
|
463
|
+
- Last pattern analysis: ${profile.lastPatternTimestamp ? fmtTime(profile.lastPatternTimestamp) : "never"}
|
|
464
|
+
${dataNote}
|
|
465
|
+
## Recent Executions (last ${recentEvents.slice(0, 10).length})
|
|
466
|
+
${recentOutcomes || " No recent events."}
|
|
467
|
+
|
|
468
|
+
## Pattern Analysis
|
|
469
|
+
${patternSummary}
|
|
470
|
+
|
|
471
|
+
## Question
|
|
472
|
+
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?`;
|
|
473
|
+
}
|
|
474
|
+
function buildFixSuggestionPrompt(events, profile, patterns) {
|
|
475
|
+
const failureGroups = /* @__PURE__ */ new Map();
|
|
476
|
+
for (const e of events) {
|
|
477
|
+
const key = e.failurePoint?.error ?? e.pathSignature;
|
|
478
|
+
const group = failureGroups.get(key) ?? [];
|
|
479
|
+
group.push(e);
|
|
480
|
+
failureGroups.set(key, group);
|
|
481
|
+
}
|
|
482
|
+
const failureGroupSummary = [...failureGroups.entries()].map(([key, group]) => {
|
|
483
|
+
const latest = group[0];
|
|
484
|
+
return ` "${key}" \u2014 ${group.length} occurrence(s), latest at ${latest ? fmtTime(latest.timestamp) : "unknown"}`;
|
|
485
|
+
}).join("\n");
|
|
486
|
+
const bottleneckDetails = patterns.flatMap((p) => p.pattern.topBottlenecks).map((b) => ` ${b.nodeName} (${b.nodeType}) \u2014 p95: ${fmtDuration(b.p95)}`);
|
|
487
|
+
const uniqueBottlenecks = [...new Set(bottleneckDetails)].join("\n");
|
|
488
|
+
const conformanceIssues = events.filter((e) => e.processContext && e.processContext.conformanceScore < 0.8).map(
|
|
489
|
+
(e) => ` ${fmtTime(e.timestamp)}: conformance ${e.processContext?.conformanceScore}, variant "${e.processContext?.variant}"`
|
|
490
|
+
).join("\n");
|
|
491
|
+
return `${ROLE}
|
|
492
|
+
|
|
493
|
+
## Agent Profile
|
|
494
|
+
- Agent: ${profile.agentId}
|
|
495
|
+
- Failure rate: ${(profile.failureRate * 100).toFixed(1)}%
|
|
496
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
497
|
+
|
|
498
|
+
## Failure Patterns (${events.length} failures)
|
|
499
|
+
${failureGroupSummary || " No failures recorded."}
|
|
500
|
+
|
|
501
|
+
## Bottlenecks
|
|
502
|
+
${uniqueBottlenecks || " No bottlenecks detected."}
|
|
503
|
+
|
|
504
|
+
## Conformance Issues
|
|
505
|
+
${conformanceIssues || " No conformance issues."}
|
|
506
|
+
|
|
507
|
+
## Question
|
|
508
|
+
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.`;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// src/insight-engine.ts
|
|
512
|
+
var DEFAULT_CACHE_TTL_MS = 36e5;
|
|
513
|
+
var SCHEMA_VERSION2 = 1;
|
|
514
|
+
function simpleHash(input) {
|
|
515
|
+
let hash = 0;
|
|
516
|
+
for (let i = 0; i < input.length; i++) {
|
|
517
|
+
const char = input.charCodeAt(i);
|
|
518
|
+
hash = (hash << 5) - hash + char | 0;
|
|
519
|
+
}
|
|
520
|
+
return (hash >>> 0).toString(36);
|
|
521
|
+
}
|
|
522
|
+
function createInsightEngine(store, analysisFn, config) {
|
|
523
|
+
const cacheTtlMs = config?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
524
|
+
function checkCache(agentId, insightType, dataHash) {
|
|
525
|
+
const recent = store.getRecentInsights(agentId, { type: insightType, limit: 1 });
|
|
526
|
+
if (recent.length === 0) return null;
|
|
527
|
+
const cached = recent[0];
|
|
528
|
+
if (!cached || cached.dataHash !== dataHash) return null;
|
|
529
|
+
const age = Date.now() - cached.timestamp;
|
|
530
|
+
if (age >= cacheTtlMs) return null;
|
|
531
|
+
return cached;
|
|
532
|
+
}
|
|
533
|
+
function storeAndReturn(agentId, insightType, prompt, response, dataHash) {
|
|
534
|
+
const event = {
|
|
535
|
+
eventType: "insight.generated",
|
|
536
|
+
agentId,
|
|
537
|
+
timestamp: Date.now(),
|
|
538
|
+
schemaVersion: SCHEMA_VERSION2,
|
|
539
|
+
insightType,
|
|
540
|
+
prompt,
|
|
541
|
+
response,
|
|
542
|
+
dataHash
|
|
543
|
+
};
|
|
544
|
+
store.appendInsight(event);
|
|
545
|
+
return {
|
|
546
|
+
agentId,
|
|
547
|
+
insightType,
|
|
548
|
+
content: response,
|
|
549
|
+
cached: false,
|
|
550
|
+
timestamp: event.timestamp
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function shortCircuit(agentId, insightType, content, cached, timestamp) {
|
|
554
|
+
return { agentId, insightType, content, cached, timestamp: timestamp ?? Date.now() };
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
async explainFailures(agentId) {
|
|
558
|
+
const profile = store.getAgentProfile(agentId);
|
|
559
|
+
if (!profile) {
|
|
560
|
+
return shortCircuit(
|
|
561
|
+
agentId,
|
|
562
|
+
"failure-analysis",
|
|
563
|
+
"No data available for this agent.",
|
|
564
|
+
false
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
const events = store.getRecentEvents(agentId, { limit: 50 });
|
|
568
|
+
const failures = events.filter((e) => e.eventType === "execution.failed");
|
|
569
|
+
if (failures.length === 0) {
|
|
570
|
+
return shortCircuit(
|
|
571
|
+
agentId,
|
|
572
|
+
"failure-analysis",
|
|
573
|
+
"No recent failures found for this agent.",
|
|
574
|
+
false
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
const dataHash = simpleHash(JSON.stringify({ failures, profile }));
|
|
578
|
+
const cached = checkCache(agentId, "failure-analysis", dataHash);
|
|
579
|
+
if (cached) {
|
|
580
|
+
return shortCircuit(agentId, "failure-analysis", cached.response, true, cached.timestamp);
|
|
581
|
+
}
|
|
582
|
+
const prompt = buildFailureAnalysisPrompt(failures, profile);
|
|
583
|
+
try {
|
|
584
|
+
const response = await analysisFn(prompt);
|
|
585
|
+
return storeAndReturn(agentId, "failure-analysis", prompt, response, dataHash);
|
|
586
|
+
} catch (err) {
|
|
587
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
588
|
+
return shortCircuit(agentId, "failure-analysis", `Analysis failed: ${message}`, false);
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
async explainAnomaly(agentId, event) {
|
|
592
|
+
const profile = store.getAgentProfile(agentId);
|
|
593
|
+
if (!profile) {
|
|
594
|
+
return shortCircuit(
|
|
595
|
+
agentId,
|
|
596
|
+
"anomaly-explanation",
|
|
597
|
+
"No data available for this agent.",
|
|
598
|
+
false
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
const dataHash = simpleHash(JSON.stringify({ event, profile }));
|
|
602
|
+
const cached = checkCache(agentId, "anomaly-explanation", dataHash);
|
|
603
|
+
if (cached) {
|
|
604
|
+
return shortCircuit(
|
|
605
|
+
agentId,
|
|
606
|
+
"anomaly-explanation",
|
|
607
|
+
cached.response,
|
|
608
|
+
true,
|
|
609
|
+
cached.timestamp
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
const prompt = buildAnomalyExplanationPrompt(event, profile);
|
|
613
|
+
try {
|
|
614
|
+
const response = await analysisFn(prompt);
|
|
615
|
+
return storeAndReturn(agentId, "anomaly-explanation", prompt, response, dataHash);
|
|
616
|
+
} catch (err) {
|
|
617
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
618
|
+
return shortCircuit(agentId, "anomaly-explanation", `Analysis failed: ${message}`, false);
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
async summarizeAgent(agentId) {
|
|
622
|
+
const profile = store.getAgentProfile(agentId);
|
|
623
|
+
if (!profile) {
|
|
624
|
+
return shortCircuit(agentId, "agent-summary", "No data available for this agent.", false);
|
|
625
|
+
}
|
|
626
|
+
const recentEvents = store.getRecentEvents(agentId, { limit: 20 });
|
|
627
|
+
const patterns = store.getPatternHistory(agentId, { limit: 5 });
|
|
628
|
+
const dataHash = simpleHash(JSON.stringify({ profile, recentEvents, patterns }));
|
|
629
|
+
const cached = checkCache(agentId, "agent-summary", dataHash);
|
|
630
|
+
if (cached) {
|
|
631
|
+
return shortCircuit(agentId, "agent-summary", cached.response, true, cached.timestamp);
|
|
632
|
+
}
|
|
633
|
+
const prompt = buildAgentSummaryPrompt(profile, recentEvents, patterns);
|
|
634
|
+
try {
|
|
635
|
+
const response = await analysisFn(prompt);
|
|
636
|
+
return storeAndReturn(agentId, "agent-summary", prompt, response, dataHash);
|
|
637
|
+
} catch (err) {
|
|
638
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
639
|
+
return shortCircuit(agentId, "agent-summary", `Analysis failed: ${message}`, false);
|
|
640
|
+
}
|
|
641
|
+
},
|
|
642
|
+
async suggestFixes(agentId) {
|
|
643
|
+
const profile = store.getAgentProfile(agentId);
|
|
644
|
+
if (!profile) {
|
|
645
|
+
return shortCircuit(agentId, "fix-suggestion", "No data available for this agent.", false);
|
|
646
|
+
}
|
|
647
|
+
const events = store.getRecentEvents(agentId, { limit: 50 });
|
|
648
|
+
const failures = events.filter((e) => e.eventType === "execution.failed");
|
|
649
|
+
const patterns = store.getPatternHistory(agentId, { limit: 5 });
|
|
650
|
+
if (failures.length === 0 && profile.knownBottlenecks.length === 0) {
|
|
651
|
+
return shortCircuit(
|
|
652
|
+
agentId,
|
|
653
|
+
"fix-suggestion",
|
|
654
|
+
"Agent is healthy \u2014 no failures or bottlenecks detected.",
|
|
655
|
+
false
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
const dataHash = simpleHash(JSON.stringify({ failures, profile, patterns }));
|
|
659
|
+
const cached = checkCache(agentId, "fix-suggestion", dataHash);
|
|
660
|
+
if (cached) {
|
|
661
|
+
return shortCircuit(agentId, "fix-suggestion", cached.response, true, cached.timestamp);
|
|
662
|
+
}
|
|
663
|
+
const prompt = buildFixSuggestionPrompt(failures, profile, patterns);
|
|
664
|
+
try {
|
|
665
|
+
const response = await analysisFn(prompt);
|
|
666
|
+
return storeAndReturn(agentId, "fix-suggestion", prompt, response, dataHash);
|
|
667
|
+
} catch (err) {
|
|
668
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
669
|
+
return shortCircuit(agentId, "fix-suggestion", `Analysis failed: ${message}`, false);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
32
675
|
// src/guards.ts
|
|
33
676
|
var DEFAULT_TIMEOUTS = {
|
|
34
677
|
tool: 3e4,
|
|
@@ -89,6 +732,9 @@ function checkGuards(graph, config) {
|
|
|
89
732
|
});
|
|
90
733
|
}
|
|
91
734
|
violations.push(...detectReasoningLoops(graph, maxReasoningSteps, now));
|
|
735
|
+
if (config?.policySource) {
|
|
736
|
+
violations.push(...checkPolicyViolations(graph, config.policySource, config.policyThresholds, now));
|
|
737
|
+
}
|
|
92
738
|
return violations;
|
|
93
739
|
}
|
|
94
740
|
function detectReasoningLoops(graph, maxSteps, timestamp) {
|
|
@@ -123,6 +769,40 @@ function detectReasoningLoops(graph, maxSteps, timestamp) {
|
|
|
123
769
|
walk(graph.rootNodeId, 0, null);
|
|
124
770
|
return violations;
|
|
125
771
|
}
|
|
772
|
+
function checkPolicyViolations(graph, policySource, thresholds, timestamp) {
|
|
773
|
+
const violations = [];
|
|
774
|
+
const maxFailureRate = thresholds?.maxFailureRate ?? 0.5;
|
|
775
|
+
const minConformance = thresholds?.minConformance ?? 0.7;
|
|
776
|
+
const failureRate = policySource.recentFailureRate(graph.agentId);
|
|
777
|
+
if (failureRate > maxFailureRate) {
|
|
778
|
+
violations.push({
|
|
779
|
+
type: "high-failure-rate",
|
|
780
|
+
nodeId: graph.rootNodeId,
|
|
781
|
+
message: `Agent ${graph.agentId} has a recent failure rate of ${(failureRate * 100).toFixed(0)}% (threshold: ${(maxFailureRate * 100).toFixed(0)}%)`,
|
|
782
|
+
timestamp
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
const conformanceScore = policySource.lastConformanceScore(graph.agentId);
|
|
786
|
+
if (conformanceScore !== null && conformanceScore < minConformance) {
|
|
787
|
+
violations.push({
|
|
788
|
+
type: "conformance-drift",
|
|
789
|
+
nodeId: graph.rootNodeId,
|
|
790
|
+
message: `Agent ${graph.agentId} conformance score ${(conformanceScore * 100).toFixed(0)}% is below threshold ${(minConformance * 100).toFixed(0)}%`,
|
|
791
|
+
timestamp
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
for (const node of graph.nodes.values()) {
|
|
795
|
+
if (node.status === "running" && policySource.isKnownBottleneck(node.name)) {
|
|
796
|
+
violations.push({
|
|
797
|
+
type: "known-bottleneck",
|
|
798
|
+
nodeId: node.id,
|
|
799
|
+
message: `Node ${node.name} (${node.type}) is a known bottleneck`,
|
|
800
|
+
timestamp
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return violations;
|
|
805
|
+
}
|
|
126
806
|
function withGuards(builder, config) {
|
|
127
807
|
const logger = config?.logger ?? ((msg) => console.warn(`[AgentFlow Guard] ${msg}`));
|
|
128
808
|
const onViolation = config?.onViolation ?? "warn";
|
|
@@ -186,14 +866,539 @@ function withGuards(builder, config) {
|
|
|
186
866
|
}
|
|
187
867
|
};
|
|
188
868
|
}
|
|
869
|
+
|
|
870
|
+
// src/json-event-writer.ts
|
|
871
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
872
|
+
import { join } from "path";
|
|
873
|
+
function createJsonEventWriter(config) {
|
|
874
|
+
const { outputDir } = config;
|
|
875
|
+
function ensureDir() {
|
|
876
|
+
mkdirSync(outputDir, { recursive: true });
|
|
877
|
+
}
|
|
878
|
+
function eventFileName(event) {
|
|
879
|
+
const typePart = event.eventType.replace(/\./g, "-");
|
|
880
|
+
const agentId = "agentId" in event ? event.agentId : "unknown";
|
|
881
|
+
return `${typePart}-${agentId}-${event.timestamp}.json`;
|
|
882
|
+
}
|
|
883
|
+
return {
|
|
884
|
+
async write(_graph) {
|
|
885
|
+
},
|
|
886
|
+
async writeEvent(event) {
|
|
887
|
+
ensureDir();
|
|
888
|
+
const fileName = eventFileName(event);
|
|
889
|
+
const filePath = join(outputDir, fileName);
|
|
890
|
+
writeFileSync(filePath, JSON.stringify(event, null, 2), "utf-8");
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/knowledge-store.ts
|
|
896
|
+
import {
|
|
897
|
+
existsSync,
|
|
898
|
+
mkdirSync as mkdirSync2,
|
|
899
|
+
readdirSync,
|
|
900
|
+
readFileSync,
|
|
901
|
+
renameSync,
|
|
902
|
+
rmSync,
|
|
903
|
+
writeFileSync as writeFileSync2
|
|
904
|
+
} from "fs";
|
|
905
|
+
import { join as join2 } from "path";
|
|
906
|
+
var DEFAULT_BASE_DIR = ".agentflow/knowledge";
|
|
907
|
+
var MAX_RECENT_DURATIONS = 100;
|
|
908
|
+
var writeCounter = 0;
|
|
909
|
+
function toDateDir(epochMs) {
|
|
910
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
911
|
+
}
|
|
912
|
+
function readJson(filePath) {
|
|
913
|
+
try {
|
|
914
|
+
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
915
|
+
} catch {
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
function writeJsonAtomic(filePath, data) {
|
|
920
|
+
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
921
|
+
writeFileSync2(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
922
|
+
renameSync(tmpPath, filePath);
|
|
923
|
+
}
|
|
924
|
+
function emptyProfile(agentId) {
|
|
925
|
+
return {
|
|
926
|
+
agentId,
|
|
927
|
+
totalRuns: 0,
|
|
928
|
+
successCount: 0,
|
|
929
|
+
failureCount: 0,
|
|
930
|
+
failureRate: 0,
|
|
931
|
+
recentDurations: [],
|
|
932
|
+
lastConformanceScore: null,
|
|
933
|
+
knownBottlenecks: [],
|
|
934
|
+
lastPatternTimestamp: null,
|
|
935
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
function mergeExecutionEvent(profile, event) {
|
|
939
|
+
const totalRuns = profile.totalRuns + 1;
|
|
940
|
+
const isFailure = event.eventType === "execution.failed";
|
|
941
|
+
const successCount = profile.successCount + (isFailure ? 0 : 1);
|
|
942
|
+
const failureCount = profile.failureCount + (isFailure ? 1 : 0);
|
|
943
|
+
const durations = [...profile.recentDurations, event.duration];
|
|
944
|
+
if (durations.length > MAX_RECENT_DURATIONS) {
|
|
945
|
+
durations.shift();
|
|
946
|
+
}
|
|
947
|
+
const conformanceScore = event.processContext?.conformanceScore ?? profile.lastConformanceScore;
|
|
948
|
+
return {
|
|
949
|
+
agentId: profile.agentId,
|
|
950
|
+
totalRuns,
|
|
951
|
+
successCount,
|
|
952
|
+
failureCount,
|
|
953
|
+
failureRate: failureCount / totalRuns,
|
|
954
|
+
recentDurations: durations,
|
|
955
|
+
lastConformanceScore: conformanceScore,
|
|
956
|
+
knownBottlenecks: profile.knownBottlenecks,
|
|
957
|
+
lastPatternTimestamp: profile.lastPatternTimestamp,
|
|
958
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
function mergePatternEvent(profile, event) {
|
|
962
|
+
const existingBottlenecks = new Set(profile.knownBottlenecks);
|
|
963
|
+
for (const b of event.pattern.topBottlenecks) {
|
|
964
|
+
existingBottlenecks.add(b.nodeName);
|
|
965
|
+
}
|
|
966
|
+
return {
|
|
967
|
+
...profile,
|
|
968
|
+
knownBottlenecks: [...existingBottlenecks],
|
|
969
|
+
lastPatternTimestamp: event.timestamp,
|
|
970
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
function createKnowledgeStore(config) {
|
|
974
|
+
const baseDir = config?.baseDir ?? DEFAULT_BASE_DIR;
|
|
975
|
+
const eventsDir = join2(baseDir, "events");
|
|
976
|
+
const patternsDir = join2(baseDir, "patterns");
|
|
977
|
+
const profilesDir = join2(baseDir, "profiles");
|
|
978
|
+
const insightsDir = join2(baseDir, "insights");
|
|
979
|
+
function ensureDir(dir) {
|
|
980
|
+
mkdirSync2(dir, { recursive: true });
|
|
981
|
+
}
|
|
982
|
+
function profilePath(agentId) {
|
|
983
|
+
return join2(profilesDir, `${agentId}.json`);
|
|
984
|
+
}
|
|
985
|
+
function appendExecutionEvent(event) {
|
|
986
|
+
const dateDir = join2(eventsDir, event.agentId, toDateDir(event.timestamp));
|
|
987
|
+
ensureDir(dateDir);
|
|
988
|
+
const typePart = event.eventType.replace(/\./g, "-");
|
|
989
|
+
const seq = String(writeCounter++).padStart(4, "0");
|
|
990
|
+
const fileName = `${typePart}-${event.timestamp}-${seq}.json`;
|
|
991
|
+
writeFileSync2(join2(dateDir, fileName), JSON.stringify(event, null, 2), "utf-8");
|
|
992
|
+
ensureDir(profilesDir);
|
|
993
|
+
const existing = readJson(profilePath(event.agentId)) ?? emptyProfile(event.agentId);
|
|
994
|
+
const updated = mergeExecutionEvent(existing, event);
|
|
995
|
+
writeJsonAtomic(profilePath(event.agentId), updated);
|
|
996
|
+
}
|
|
997
|
+
function appendPatternEvent(event) {
|
|
998
|
+
const agentPatternDir = join2(patternsDir, event.agentId);
|
|
999
|
+
ensureDir(agentPatternDir);
|
|
1000
|
+
const seq = String(writeCounter++).padStart(4, "0");
|
|
1001
|
+
const fileName = `${event.timestamp}-${seq}.json`;
|
|
1002
|
+
writeFileSync2(join2(agentPatternDir, fileName), JSON.stringify(event, null, 2), "utf-8");
|
|
1003
|
+
ensureDir(profilesDir);
|
|
1004
|
+
const existing = readJson(profilePath(event.agentId)) ?? emptyProfile(event.agentId);
|
|
1005
|
+
const updated = mergePatternEvent(existing, event);
|
|
1006
|
+
writeJsonAtomic(profilePath(event.agentId), updated);
|
|
1007
|
+
}
|
|
1008
|
+
return {
|
|
1009
|
+
baseDir,
|
|
1010
|
+
append(event) {
|
|
1011
|
+
if (event.eventType === "pattern.discovered" || event.eventType === "pattern.updated") {
|
|
1012
|
+
appendPatternEvent(event);
|
|
1013
|
+
} else {
|
|
1014
|
+
appendExecutionEvent(event);
|
|
1015
|
+
}
|
|
1016
|
+
},
|
|
1017
|
+
getRecentEvents(agentId, options) {
|
|
1018
|
+
const limit = options?.limit ?? 50;
|
|
1019
|
+
const since = options?.since ?? 0;
|
|
1020
|
+
const agentDir = join2(eventsDir, agentId);
|
|
1021
|
+
if (!existsSync(agentDir)) return [];
|
|
1022
|
+
const events = [];
|
|
1023
|
+
const dateDirs = readdirSync(agentDir).sort().reverse();
|
|
1024
|
+
for (const dateDir of dateDirs) {
|
|
1025
|
+
const fullDateDir = join2(agentDir, dateDir);
|
|
1026
|
+
let files;
|
|
1027
|
+
try {
|
|
1028
|
+
files = readdirSync(fullDateDir).filter((f) => f.endsWith(".json"));
|
|
1029
|
+
} catch {
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
for (const file of files) {
|
|
1033
|
+
const event = readJson(join2(fullDateDir, file));
|
|
1034
|
+
if (event && event.timestamp > since) {
|
|
1035
|
+
events.push(event);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
if (events.length >= limit * 2) break;
|
|
1039
|
+
}
|
|
1040
|
+
events.sort((a, b) => b.timestamp - a.timestamp);
|
|
1041
|
+
return events.slice(0, limit);
|
|
1042
|
+
},
|
|
1043
|
+
getAgentProfile(agentId) {
|
|
1044
|
+
return readJson(profilePath(agentId));
|
|
1045
|
+
},
|
|
1046
|
+
getPatternHistory(agentId, options) {
|
|
1047
|
+
const limit = options?.limit ?? 20;
|
|
1048
|
+
const agentPatternDir = join2(patternsDir, agentId);
|
|
1049
|
+
if (!existsSync(agentPatternDir)) return [];
|
|
1050
|
+
const files = readdirSync(agentPatternDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
1051
|
+
const events = [];
|
|
1052
|
+
for (const file of files.slice(0, limit)) {
|
|
1053
|
+
const event = readJson(join2(agentPatternDir, file));
|
|
1054
|
+
if (event) events.push(event);
|
|
1055
|
+
}
|
|
1056
|
+
return events;
|
|
1057
|
+
},
|
|
1058
|
+
compact(options) {
|
|
1059
|
+
let removed = 0;
|
|
1060
|
+
if (existsSync(eventsDir)) {
|
|
1061
|
+
for (const agentId of readdirSync(eventsDir)) {
|
|
1062
|
+
const agentDir = join2(eventsDir, agentId);
|
|
1063
|
+
let dateDirs;
|
|
1064
|
+
try {
|
|
1065
|
+
dateDirs = readdirSync(agentDir);
|
|
1066
|
+
} catch {
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
for (const dateDir of dateDirs) {
|
|
1070
|
+
const fullDateDir = join2(agentDir, dateDir);
|
|
1071
|
+
let files;
|
|
1072
|
+
try {
|
|
1073
|
+
files = readdirSync(fullDateDir).filter((f) => f.endsWith(".json"));
|
|
1074
|
+
} catch {
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
for (const file of files) {
|
|
1078
|
+
const parts = file.replace(".json", "").split("-");
|
|
1079
|
+
const tsPart = parts[parts.length - 2];
|
|
1080
|
+
const ts = tsPart ? Number.parseInt(tsPart, 10) : 0;
|
|
1081
|
+
if (!Number.isNaN(ts) && ts < options.olderThan) {
|
|
1082
|
+
try {
|
|
1083
|
+
rmSync(join2(fullDateDir, file));
|
|
1084
|
+
removed++;
|
|
1085
|
+
} catch {
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
try {
|
|
1090
|
+
if (readdirSync(fullDateDir).length === 0) {
|
|
1091
|
+
rmSync(fullDateDir, { recursive: true });
|
|
1092
|
+
}
|
|
1093
|
+
} catch {
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
if (existsSync(patternsDir)) {
|
|
1099
|
+
for (const agentId of readdirSync(patternsDir)) {
|
|
1100
|
+
const agentPatternDir = join2(patternsDir, agentId);
|
|
1101
|
+
let files;
|
|
1102
|
+
try {
|
|
1103
|
+
files = readdirSync(agentPatternDir).filter((f) => f.endsWith(".json"));
|
|
1104
|
+
} catch {
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
for (const file of files) {
|
|
1108
|
+
const ts = Number.parseInt(file.split("-")[0], 10);
|
|
1109
|
+
if (!Number.isNaN(ts) && ts < options.olderThan) {
|
|
1110
|
+
try {
|
|
1111
|
+
rmSync(join2(agentPatternDir, file));
|
|
1112
|
+
removed++;
|
|
1113
|
+
} catch {
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
if (existsSync(insightsDir)) {
|
|
1120
|
+
for (const agentId of readdirSync(insightsDir)) {
|
|
1121
|
+
const agentInsightDir = join2(insightsDir, agentId);
|
|
1122
|
+
let files;
|
|
1123
|
+
try {
|
|
1124
|
+
files = readdirSync(agentInsightDir).filter((f) => f.endsWith(".json"));
|
|
1125
|
+
} catch {
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
for (const file of files) {
|
|
1129
|
+
const parts = file.replace(".json", "").split("-");
|
|
1130
|
+
const tsPart = parts[parts.length - 2];
|
|
1131
|
+
const ts = tsPart ? Number.parseInt(tsPart, 10) : 0;
|
|
1132
|
+
if (!Number.isNaN(ts) && ts < options.olderThan) {
|
|
1133
|
+
try {
|
|
1134
|
+
rmSync(join2(agentInsightDir, file));
|
|
1135
|
+
removed++;
|
|
1136
|
+
} catch {
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
return { removed };
|
|
1143
|
+
},
|
|
1144
|
+
appendInsight(event) {
|
|
1145
|
+
const agentInsightDir = join2(insightsDir, event.agentId);
|
|
1146
|
+
ensureDir(agentInsightDir);
|
|
1147
|
+
const seq = String(writeCounter++).padStart(4, "0");
|
|
1148
|
+
const fileName = `${event.insightType}-${event.timestamp}-${seq}.json`;
|
|
1149
|
+
writeFileSync2(join2(agentInsightDir, fileName), JSON.stringify(event, null, 2), "utf-8");
|
|
1150
|
+
},
|
|
1151
|
+
getRecentInsights(agentId, options) {
|
|
1152
|
+
const limit = options?.limit ?? 10;
|
|
1153
|
+
const typeFilter = options?.type;
|
|
1154
|
+
const agentInsightDir = join2(insightsDir, agentId);
|
|
1155
|
+
if (!existsSync(agentInsightDir)) return [];
|
|
1156
|
+
const files = readdirSync(agentInsightDir).filter((f) => f.endsWith(".json"));
|
|
1157
|
+
const events = [];
|
|
1158
|
+
for (const file of files) {
|
|
1159
|
+
const event = readJson(join2(agentInsightDir, file));
|
|
1160
|
+
if (event) {
|
|
1161
|
+
if (typeFilter && event.insightType !== typeFilter) continue;
|
|
1162
|
+
events.push(event);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
events.sort((a, b) => b.timestamp - a.timestamp);
|
|
1166
|
+
return events.slice(0, limit);
|
|
1167
|
+
},
|
|
1168
|
+
// EventWriter interface
|
|
1169
|
+
async write(_graph) {
|
|
1170
|
+
},
|
|
1171
|
+
async writeEvent(event) {
|
|
1172
|
+
this.append(event);
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// src/policy-source.ts
|
|
1178
|
+
import { readdirSync as readdirSync2 } from "fs";
|
|
1179
|
+
import { join as join3 } from "path";
|
|
1180
|
+
function createPolicySource(store) {
|
|
1181
|
+
return {
|
|
1182
|
+
recentFailureRate(agentId) {
|
|
1183
|
+
const profile = store.getAgentProfile(agentId);
|
|
1184
|
+
return profile?.failureRate ?? 0;
|
|
1185
|
+
},
|
|
1186
|
+
isKnownBottleneck(nodeName) {
|
|
1187
|
+
const profilesDir = join3(store.baseDir, "profiles");
|
|
1188
|
+
let agentIds;
|
|
1189
|
+
try {
|
|
1190
|
+
agentIds = readdirSync2(profilesDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
|
|
1191
|
+
} catch {
|
|
1192
|
+
return false;
|
|
1193
|
+
}
|
|
1194
|
+
for (const agentId of agentIds) {
|
|
1195
|
+
const profile = store.getAgentProfile(agentId);
|
|
1196
|
+
if (profile && profile.knownBottlenecks.includes(nodeName)) {
|
|
1197
|
+
return true;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return false;
|
|
1201
|
+
},
|
|
1202
|
+
lastConformanceScore(agentId) {
|
|
1203
|
+
const profile = store.getAgentProfile(agentId);
|
|
1204
|
+
return profile?.lastConformanceScore ?? null;
|
|
1205
|
+
},
|
|
1206
|
+
getAgentProfile(agentId) {
|
|
1207
|
+
return store.getAgentProfile(agentId);
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// src/soma-event-writer.ts
|
|
1213
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1214
|
+
import { join as join4 } from "path";
|
|
1215
|
+
function compactIso(epochMs) {
|
|
1216
|
+
return new Date(epochMs).toISOString().slice(0, 19).replace(/:/g, "");
|
|
1217
|
+
}
|
|
1218
|
+
function isoDate(epochMs) {
|
|
1219
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
1220
|
+
}
|
|
1221
|
+
function renderFrontmatter(fields) {
|
|
1222
|
+
const lines = ["---"];
|
|
1223
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
1224
|
+
if (value === void 0) continue;
|
|
1225
|
+
if (Array.isArray(value)) {
|
|
1226
|
+
lines.push(`${key}: [${value.map((v) => `'${v}'`).join(", ")}]`);
|
|
1227
|
+
} else if (typeof value === "string") {
|
|
1228
|
+
lines.push(`${key}: '${value}'`);
|
|
1229
|
+
} else {
|
|
1230
|
+
lines.push(`${key}: ${String(value)}`);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
lines.push("---");
|
|
1234
|
+
return lines.join("\n");
|
|
1235
|
+
}
|
|
1236
|
+
function formatDuration(ms) {
|
|
1237
|
+
if (ms < 1e3) return `${ms.toFixed(0)}ms`;
|
|
1238
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
1239
|
+
return `${(ms / 6e4).toFixed(1)}min`;
|
|
1240
|
+
}
|
|
1241
|
+
function executionEventToMarkdown(event) {
|
|
1242
|
+
const isCompleted = event.eventType === "execution.completed";
|
|
1243
|
+
const subtype = isCompleted ? "completed" : "failed";
|
|
1244
|
+
const tags = [
|
|
1245
|
+
"agentflow/execution",
|
|
1246
|
+
`agent/${event.agentId}`,
|
|
1247
|
+
`status/${subtype}`
|
|
1248
|
+
];
|
|
1249
|
+
if (event.processContext?.isAnomaly) {
|
|
1250
|
+
tags.push("agentflow/anomaly");
|
|
1251
|
+
}
|
|
1252
|
+
const frontmatter = {
|
|
1253
|
+
type: "execution",
|
|
1254
|
+
subtype,
|
|
1255
|
+
name: `Execution: ${event.agentId} \u2014 ${subtype}`,
|
|
1256
|
+
source: "agentflow",
|
|
1257
|
+
created: isoDate(event.timestamp),
|
|
1258
|
+
alfred_tags: tags,
|
|
1259
|
+
agentflow_graph_id: event.graphId,
|
|
1260
|
+
duration_ms: event.duration,
|
|
1261
|
+
node_count: event.nodeCount
|
|
1262
|
+
};
|
|
1263
|
+
if (event.processContext) {
|
|
1264
|
+
frontmatter.conformance_score = event.processContext.conformanceScore;
|
|
1265
|
+
frontmatter.is_anomaly = event.processContext.isAnomaly;
|
|
1266
|
+
}
|
|
1267
|
+
const body = [];
|
|
1268
|
+
body.push(`# Execution: ${event.agentId} \u2014 ${subtype}
|
|
1269
|
+
`);
|
|
1270
|
+
body.push(`**Duration:** ${formatDuration(event.duration)} `);
|
|
1271
|
+
body.push(`**Nodes:** ${event.nodeCount} `);
|
|
1272
|
+
body.push(`**Status:** ${event.status}
|
|
1273
|
+
`);
|
|
1274
|
+
if (event.pathSignature) {
|
|
1275
|
+
body.push(`## Path
|
|
1276
|
+
`);
|
|
1277
|
+
body.push(`\`${event.pathSignature}\`
|
|
1278
|
+
`);
|
|
1279
|
+
}
|
|
1280
|
+
if (!isCompleted && event.failurePoint) {
|
|
1281
|
+
const fp = event.failurePoint;
|
|
1282
|
+
body.push(`## Failure Point
|
|
1283
|
+
`);
|
|
1284
|
+
body.push(`**Node:** ${fp.nodeType}:${fp.nodeName} (\`${fp.nodeId}\`) `);
|
|
1285
|
+
if (fp.error) {
|
|
1286
|
+
body.push(`**Error:** ${fp.error}
|
|
1287
|
+
`);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
if (event.processContext) {
|
|
1291
|
+
body.push(`## Process Context
|
|
1292
|
+
`);
|
|
1293
|
+
body.push(`**Conformance:** ${(event.processContext.conformanceScore * 100).toFixed(0)}% `);
|
|
1294
|
+
body.push(`**Anomaly:** ${event.processContext.isAnomaly ? "yes" : "no"}
|
|
1295
|
+
`);
|
|
1296
|
+
}
|
|
1297
|
+
body.push(`## Related
|
|
1298
|
+
`);
|
|
1299
|
+
body.push(`- [[agent/${event.agentId}]]`);
|
|
1300
|
+
return `${renderFrontmatter(frontmatter)}
|
|
1301
|
+
|
|
1302
|
+
${body.join("\n")}`;
|
|
1303
|
+
}
|
|
1304
|
+
function patternEventToMarkdown(event) {
|
|
1305
|
+
const { pattern } = event;
|
|
1306
|
+
const tags = [
|
|
1307
|
+
"agentflow/pattern",
|
|
1308
|
+
`agent/${event.agentId}`
|
|
1309
|
+
];
|
|
1310
|
+
const frontmatter = {
|
|
1311
|
+
type: "synthesis",
|
|
1312
|
+
subtype: "pattern-discovery",
|
|
1313
|
+
name: `Pattern: ${event.agentId} \u2014 ${pattern.variantCount} variants across ${pattern.totalGraphs} runs`,
|
|
1314
|
+
source: "agentflow",
|
|
1315
|
+
created: isoDate(event.timestamp),
|
|
1316
|
+
alfred_tags: tags,
|
|
1317
|
+
variant_count: pattern.variantCount,
|
|
1318
|
+
total_graphs: pattern.totalGraphs
|
|
1319
|
+
};
|
|
1320
|
+
const body = [];
|
|
1321
|
+
body.push(`# Pattern: ${event.agentId}
|
|
1322
|
+
`);
|
|
1323
|
+
body.push(`**Variants:** ${pattern.variantCount} `);
|
|
1324
|
+
body.push(`**Total Runs:** ${pattern.totalGraphs}
|
|
1325
|
+
`);
|
|
1326
|
+
if (pattern.topVariants.length > 0) {
|
|
1327
|
+
body.push(`## Top Variants
|
|
1328
|
+
`);
|
|
1329
|
+
body.push(`| Path | Count | % |`);
|
|
1330
|
+
body.push(`|------|-------|---|`);
|
|
1331
|
+
for (const v of pattern.topVariants) {
|
|
1332
|
+
const sig = v.pathSignature.length > 60 ? `${v.pathSignature.slice(0, 57)}...` : v.pathSignature;
|
|
1333
|
+
body.push(`| \`${sig}\` | ${v.count} | ${v.percentage.toFixed(1)}% |`);
|
|
1334
|
+
}
|
|
1335
|
+
body.push("");
|
|
1336
|
+
}
|
|
1337
|
+
if (pattern.topBottlenecks.length > 0) {
|
|
1338
|
+
body.push(`## Top Bottlenecks
|
|
1339
|
+
`);
|
|
1340
|
+
body.push(`| Node | Type | p95 |`);
|
|
1341
|
+
body.push(`|------|------|-----|`);
|
|
1342
|
+
for (const b of pattern.topBottlenecks) {
|
|
1343
|
+
body.push(`| ${b.nodeName} | ${b.nodeType} | ${formatDuration(b.p95)} |`);
|
|
1344
|
+
}
|
|
1345
|
+
body.push("");
|
|
1346
|
+
}
|
|
1347
|
+
body.push(`## Related
|
|
1348
|
+
`);
|
|
1349
|
+
body.push(`- [[agent/${event.agentId}]]`);
|
|
1350
|
+
return `${renderFrontmatter(frontmatter)}
|
|
1351
|
+
|
|
1352
|
+
${body.join("\n")}`;
|
|
1353
|
+
}
|
|
1354
|
+
function createSomaEventWriter(config) {
|
|
1355
|
+
const { inboxDir } = config;
|
|
1356
|
+
function ensureDir() {
|
|
1357
|
+
mkdirSync3(inboxDir, { recursive: true });
|
|
1358
|
+
}
|
|
1359
|
+
function eventFileName(event) {
|
|
1360
|
+
const agentId = event.agentId;
|
|
1361
|
+
const ts = compactIso(event.timestamp);
|
|
1362
|
+
if (event.eventType === "pattern.discovered" || event.eventType === "pattern.updated") {
|
|
1363
|
+
return `synthesis-${agentId}-${ts}.md`;
|
|
1364
|
+
}
|
|
1365
|
+
return `execution-${agentId}-${ts}.md`;
|
|
1366
|
+
}
|
|
1367
|
+
return {
|
|
1368
|
+
async write(_graph) {
|
|
1369
|
+
},
|
|
1370
|
+
async writeEvent(event) {
|
|
1371
|
+
ensureDir();
|
|
1372
|
+
const markdown = event.eventType === "pattern.discovered" || event.eventType === "pattern.updated" ? patternEventToMarkdown(event) : executionEventToMarkdown(event);
|
|
1373
|
+
const fileName = eventFileName(event);
|
|
1374
|
+
writeFileSync3(join4(inboxDir, fileName), markdown, "utf-8");
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
189
1378
|
export {
|
|
190
1379
|
auditProcesses,
|
|
1380
|
+
buildAgentSummaryPrompt,
|
|
1381
|
+
buildAnomalyExplanationPrompt,
|
|
1382
|
+
buildFailureAnalysisPrompt,
|
|
1383
|
+
buildFixSuggestionPrompt,
|
|
1384
|
+
checkConformance,
|
|
191
1385
|
checkGuards,
|
|
1386
|
+
createEventEmitter,
|
|
1387
|
+
createExecutionEvent,
|
|
192
1388
|
createGraphBuilder,
|
|
1389
|
+
createInsightEngine,
|
|
1390
|
+
createJsonEventWriter,
|
|
1391
|
+
createKnowledgeStore,
|
|
1392
|
+
createPatternEvent,
|
|
1393
|
+
createPolicySource,
|
|
1394
|
+
createSomaEventWriter,
|
|
193
1395
|
createTraceStore,
|
|
1396
|
+
discoverProcess,
|
|
194
1397
|
discoverProcessConfig,
|
|
1398
|
+
findVariants,
|
|
195
1399
|
findWaitingOn,
|
|
196
1400
|
formatAuditReport,
|
|
1401
|
+
getBottlenecks,
|
|
197
1402
|
getChildren,
|
|
198
1403
|
getCriticalPath,
|
|
199
1404
|
getDepth,
|
|
@@ -202,6 +1407,7 @@ export {
|
|
|
202
1407
|
getHungNodes,
|
|
203
1408
|
getNode,
|
|
204
1409
|
getParent,
|
|
1410
|
+
getPathSignature,
|
|
205
1411
|
getStats,
|
|
206
1412
|
getSubtree,
|
|
207
1413
|
getTraceTree,
|