FIG 0.1
Path, or no claim
algo.SPpaths looks for the causal chain from a change to the symptom. If FalkorDB returns no path, the change is out, whatever the model thinks.
Faultline won't blame a deploy until FalkorDB proves it: a causal path from the change to the symptom, and enough capacity on that path to carry the impact. Otherwise it says “I don't know”.

Accuracylower is better
–confident wrong blames in – fresh test worlds, five kinds of them adversarial
Ablationzero by design
–confirmed with FalkorDB switched off: without the graph, the agent cannot claim anything
Rankinghigher is better
–where the context pack ranks the true cause (MRR), vs – for vector RAG and – for recency, with 30 unrelated deploys in the window
Measured and pre-registered · evals/results/latest.json
FIG 0.1
algo.SPpaths looks for the causal chain from a change to the symptom. If FalkorDB returns no path, the change is out, whatever the model thinks.
FIG 0.2
algo.maxFlow bounds how much of the impact a suspect can explain. A same-minute deploy on a best-effort edge carries 0.02 of it: rejected.
FIG 0.3
The containment is the min-cut, tried first on a GRAPH.COPY clean room with a predicted outcome; then one human-approved action, verified.
The loop. Evidence comes in from the systems you already run, every verdict is a FalkorDB query, and one approved action goes out and is written back.
algo.WCC
Checkout and frontend page at once. Weakly connected components over the failure edges group the storm into incidents, so two faults never blend into one story. The search then runs upstream from the symptom.
CALL algo.WCC({nodeLabels: ['Node'], relationshipTypes: ['PROPAGATES_TO']}) YIELD node, componentIdWITH node, componentId WHERE size(coalesce(node.alerts, [])) > 0G2 · algo.SPpaths
Three deploys landed in the same few minutes. D-rec-12 changed recommendation, and FalkorDB returns no path from it to checkout. No path, no claim: it is out before any model reasons about it.
MATCH (c:Node {id:$cause}), (s:Node {id:$symptom})CALL algo.SPpaths({sourceNode: c, targetNode: s, relTypes: ['CHANGED', 'GATES', 'PROPAGATES_TO'], weightProp: 'w', relDirection: 'outgoing', maxLen: 8, pathCount: 3}) YIELD path, pathWeightG5 · algo.maxFlow
A path is not enough. D-email-88 reaches checkout over a best-effort edge whose max flow is 0.02, far below the share of the impact it would need to carry. Rejected.
MATCH (t:Node {id:$symptom})CALL algo.maxFlow({sourceNodes: srcs, targetNodes: [t], relationshipTypes: ['PROPAGATES_TO'], capacityProperty: 'cap'}) YIELD maxFlowRETURN maxFlow AS max_flow, size(srcs) AS sourcesthe path is the proof
D-pay-417 → payment → checkout, and the flow carries all of it. The chain comes back hop by hop and is drawn as returned; a hop learned from an earlier incident names that incident.
RETURN [n IN nodes(path) | n.id] AS chain, [r IN relationships(path) | type(r)] AS rels, pathWeight AS weight, length(path) AS hops,[r IN relationships(path) | coalesce(r.from_incident, '')] AS learned_frommin-cut · GRAPH.COPY
The smallest containment is the min-cut out of payment, computed from max-flow's edge flows. Every option is tried on its own GRAPH.COPY of the tenant graph first, with a predicted outcome.
CALL algo.maxFlow({sourceNodes: [f], targetNodes: prot, relationshipTypes: ['PROPAGATES_TO'], capacityProperty: 'cut_cost'}) YIELD edges, edgeFlows, maxFlowRETURN [e IN edges | [startNode(e).id, endNode(e).id]] AS flow_edges, edgeFlows AS flows, maxFlow AS max_flowtwo-step approval
A human approves the exact plan: flag paymentGate off. A second channel verifies it as APPLIED, the decision is written back, and a separate read-only connection re-reads every id, so the next incident starts warm.
MATCH (inc:Incident {id:$inc})OPTIONAL MATCH (c:Node {id:$cause})-[:CAUSED]->(inc)OPTIONAL MATCH (inc)-[:RESOLVED_BY]->(a:Action {id:$aid})Product tour
01Incident room
The stage timeline, the failure path lit hop by hop, and every suspect the gate tried, each ruled in or out by a query you can open.
Open it live: Incident room
For agents
Plug the graph into any agent. Every tool answers with the Cypher it ran, its parameters and a digest of the rows.
{ "mcpServers": { "faultline": { "type": "stdio", "command": "uv", "args": ["run", "python", "-m", "faultline.mcp_server.server", "--stdio"], "env": { "PYTHONPATH": "backend", "FAULTLINE_TENANT": "acme" } } }}Getting started
No. The deterministic investigator and every check run keyless. A model may only propose; a read-only query decides. LLM runs are optional and capped by an hourly budget.
Docker, uv and Node. make setup && make judge-check runs every check keyless and prints PASS; make demo starts the API and the UI.
It says so. The UI shows the outage and a run cannot start: it fails loudly and never answers from stale or fake data.
No. It takes one human-approved action, bound to the exact plan by its hash, and verifies the result through a second channel. A result it cannot confirm goes back to a human and is never recorded as a success.
The engine and every query are real; the data is synthetic and public: an OpenTelemetry-Demo-shaped topology and a synthetic org with its history, decisions and runbooks.
From evals/results/latest.json, regenerated by the eval harness. make claims fails on any number that drifts from it.