Skip to main content

Process Mining

Process mining analyses a collection of execution graphs to discover how your agent actually behaves across many runs — not just in a single trace. AgentFlow ships three pure functions for this: discoverProcess, findVariants, and getBottlenecks. Conformance checking is handled by checkConformance.

All functions are zero-dependency and require no LLM.

Build a set of graphs​

First, create multiple execution graphs representing different runs. In production these come from your running agent; here we build them manually to illustrate the concepts.

import {
createGraphBuilder,
discoverProcess,
findVariants,
getBottlenecks,
checkConformance,
} from 'agentflow-core';

function buildHappyPath(): ExecutionGraph {
const b = createGraphBuilder({ agentId: 'research-agent', trigger: 'cron' });
const root = b.startNode({ type: 'agent', name: 'main' });
const fetch = b.startNode({ type: 'tool', name: 'fetch-data', parentId: root });
b.endNode(fetch);
const llm = b.startNode({ type: 'agent', name: 'summarise', parentId: root });
b.endNode(llm);
b.endNode(root);
return b.build();
}

function buildFailurePath(): ExecutionGraph {
const b = createGraphBuilder({ agentId: 'research-agent', trigger: 'cron' });
const root = b.startNode({ type: 'agent', name: 'main' });
const fetch = b.startNode({ type: 'tool', name: 'fetch-data', parentId: root });
b.failNode(fetch, new Error('HTTP 503'));
const retry = b.startNode({ type: 'tool', name: 'fetch-data', parentId: root });
b.endNode(retry);
const llm = b.startNode({ type: 'agent', name: 'summarise', parentId: root });
b.endNode(llm);
b.endNode(root);
return b.build();
}

// Simulate 10 runs: 8 happy paths and 2 failures
const graphs = [
...Array.from({ length: 8 }, buildHappyPath),
...Array.from({ length: 2 }, buildFailurePath),
];

Discover a process model​

discoverProcess builds a directly-follows graph (DFG) — every parent→child transition observed across all runs, annotated with frequency and probability.

const model = discoverProcess(graphs);

console.log('Steps:', model.steps);
// ['agent:main', 'agent:summarise', 'tool:fetch-data']

for (const t of model.transitions) {
console.log(
`${t.from} → ${t.to} count=${t.count} probability=${(t.probability * 100).toFixed(0)}%`
);
}
// agent:main → tool:fetch-data count=10 probability=100%
// agent:main → agent:summarise count=10 probability=50%
// tool:fetch-data → agent:summarise count=0 ...

The ProcessModel shape:

interface ProcessModel {
steps: string[]; // all observed 'type:name' identifiers
transitions: ProcessTransition[];
totalGraphs: number;
agentId: string;
}

interface ProcessTransition {
from: string; // 'type:name'
to: string;
count: number; // absolute frequency
probability: number; // relative frequency from source (0.0–1.0)
}

Variant analysis​

findVariants groups graphs by their structural path signature and returns clusters sorted by frequency. Use this to identify your happy path vs failure paths.

const variants = findVariants(graphs);

for (const v of variants) {
console.log(
`${v.percentage.toFixed(0)}% (${v.count} runs): ${v.pathSignature}`
);
}
// 80% (8 runs): agent:main→agent:summarise→tool:fetch-data
// 20% (2 runs): agent:main→agent:summarise→tool:fetch-data→tool:fetch-data

The path signature is produced by a depth-first traversal of the node tree, emitting type:name for each node. Children at the same level are sorted alphabetically so the signature is deterministic regardless of execution order.

interface Variant {
pathSignature: string; // canonical path string
count: number;
percentage: number; // 0–100
graphIds: string[]; // IDs of graphs in this cluster
exampleGraph: ExecutionGraph;
}

The first variant in the array is your happy path (most frequent). Tail variants reveal error handling branches, retry loops, and edge cases.

Bottleneck detection​

getBottlenecks aggregates node durations across all graphs and computes percentile statistics. Results are sorted by p95 duration descending.

const bottlenecks = getBottlenecks(graphs);

for (const b of bottlenecks) {
console.log(
`${b.nodeType}:${b.nodeName} p50=${b.durations.median}ms p95=${b.durations.p95}ms p99=${b.durations.p99}ms`
);
}
// tool:fetch-data p50=120ms p95=890ms p99=2100ms
// agent:summarise p50=340ms p95=520ms p99=610ms

The Bottleneck shape:

interface Bottleneck {
nodeName: string;
nodeType: NodeType;
occurrences: number; // how many graphs contain this node
durations: {
median: number;
p95: number;
p99: number;
min: number;
max: number;
};
percentOfGraphs: number; // 0–100
}

A high p95 relative to the median indicates occasional severe slowdowns — a classic sign of an intermittent external dependency.

Conformance checking​

Once you have a process model you can check whether a new run conforms to it. This is how AgentFlow detects conformance drift — executions that deviate significantly from the established pattern.

const newRun = buildHappyPath(); // or any new graph
const report = checkConformance(newRun, model);

console.log('Score:', report.conformanceScore); // 0.0–1.0
console.log('Conforming:', report.isConforming);

for (const deviation of report.deviations) {
console.log(`[${deviation.type}] ${deviation.message}`);
}

Deviations fall into three categories:

TypeMeaning
unexpected-transitionThe graph contains a parent→child step not seen in the model
missing-transitionA transition with model probability > 50% is absent from the graph
low-frequency-pathA transition is present but has model probability < 10%

A conformanceScore of 1.0 means no deviations. Scores below 0.7 are a strong signal that the run is anomalous.


Next: Adding guards — detect problems in real time as graphs are being built.