Engineering

Blast Radius Is a Graph Query

How we made a machine-built map of production trustworthy enough for agents to bet an incident response on it, from expiring stale facts to pointing the blast radius query in the right direction.

Tuncer Kaplankiran
Tuncer Kaplankiran
Senior Staff Software Engineer
Aug 27, 20266 minutes
blast-radius-cover

When a service starts degrading, the first question in the room is what else is about to hurt. That is a graph question, and it gets asked against a graph nobody at your company has ever actually drawn. We built that graph as a memory tier for our agents. We’ve written about what the map is and what your coding tools can do with it, and both posts make it sound pleasantly simple: connect a source, watch your system appear.

This post is about the part that was not simple. A machine-built map of production is only useful if an agent can bet an incident response on its answers, and betting on a graph means trusting that its facts are current, that a service appears as one entity rather than three, and that the blast radius query points the way you think it does. In this post, we’ll walk through how each of those is earned.

Facts expire

Statements about infrastructure go stale quickly: the dependency that was true before the migration, the service that was decommissioned in March, the runbook that moved to a new home. A map that remembers all of them forever ends up wrong about most of them.

So no fact in our graph is timeless. Every node and every edge records when it became true and when it stopped being true, and every query filters to what is currently valid. Retiring a dependency doesn’t delete history; it closes the fact’s validity, and from that moment the edge stops shaping answers while remaining on the record. Alongside validity, every fact counts how many discovery runs have confirmed it. A service observed by four hundred runs outranks a name that appeared once in a half-configured connector, and search results come back in exactly that order, most-confirmed first.

Both decisions come from the same concern: a stale or flimsy edge looks exactly like a fresh one to the agent reading it, so we would rather the graph admit a gap than serve bad data confidently.

The same service arrives under three names

Each connector describes the world in its own vocabulary. Your checkout service shows up as a repository from the code connector, a workload from the cluster, and a dashboard label from monitoring, three names for one thing, and a naive graph would happily store three things.

The fix is aliasing: when discovery meets a new name for an entity that already exists, the name attaches to the existing identity as an alias instead of becoming a second node, and the write path enforces that rather than politely requesting it in a prompt. Searches match against aliases too, so whichever name an engineer types, they land on the one canonical entity. For the duplicates that survive anyway, and some always do, there is a dedicated merge flow with guards on it, because the failure modes carry very different costs. A duplicate that lingers just costs an engineer a second lookup. A wrong merge combines two services’ dependency chains and corrupts every blast radius computed through them, which is why merging is the operation we made hardest to do by accident.

The topology view: one canonical entity per service, with its dependencies, owners, and documentation

Blast radius only works in one direction

The query everyone wants from this graph is blast radius: if this service dies, who suffers? The first thing to get right has nothing to do with graph databases. Our edges follow the convention “A depends on B”. Under that convention, the services endangered by B’s failure are the ones whose dependency chains reach B, which means the traversal walks incoming dependency edges only.

Get that backwards, or shrug and traverse in both directions, and the query still returns a tidy set of nodes that happens to be the wrong one: outgoing edges hand you the service’s own infrastructure, the databases and queues it depends on, which is precisely the opposite of who depends on it. A blast radius pointed the wrong way looks completely plausible in a demo and sends an incident response toward the victims’ suppliers instead of the victims.

Direction is not the only filter; the traversal also has to be picky about edge type. Failure propagates along dependency relationships, so the traversal walks those and nothing else. Ownership, documentation, and correlation edges answer different questions, and letting the traversal walk them inflates the result until nearly every service in the company lands within two hops of the incident.

Then the graph database gets a vote. Our managed graph store speaks openCypher but lacks some of the predicates a full Cypher engine would offer, so the traversal returns paths rather than aggregates and the expired-edge filtering happens in application code, with hop limits and a hard cap on enumerated paths so a dense dependency mesh can’t turn one question into an expedition. The standing version of the same question, a criticality score for every node computed from its transitive dependents, ships with the memory system, and we covered its scoring math when we introduced it. The detail worth repeating here is how it behaves before the data is ready: a young graph that hasn’t earned real dependency edges yet falls back to a plain connectivity count, and every response says which basis produced it, so neither the UI nor the agent mistakes a placeholder for structure.

Feeding a graph to a token budget

The consumer of all this is a language model in the middle of an investigation, one that is already spending its context window on logs and diffs. That shapes the interface more than the storage did.

Results come back ranked and capped, and a page that isn’t complete says explicitly that more entities exist. The investigation rules built around that marker have teeth. An agent may never conclude that an entity does not exist from a page that says more exist. When it needs the full set it pages forward, and when it’s after one specific thing the rule is to narrow the query rather than scroll. Pagination itself hides behind opaque cursor tokens, for the unglamorous reason that real entity ids contain every delimiter you might be tempted to split on.

And consulting the graph is not optional. Checking the map, dependencies, owners, blast radius, is a required step of every investigation rather than a tool the model might remember to reach for. When we made it mandatory, root-cause quality jumped more than any model upgrade had moved it. Left optional, the agents often skipped the map and guessed.

Most of the engineering described here went into expiring facts, refusing casual merges, and pointing the most important query in the right direction, because those are what make the map trustworthy enough for an agent to bet an incident response on it. Start a free trial, connect a source, and watch your own map assemble itself before the next incident asks it a question.

Automate Alert Triage with AI

Edge Delta's out-of-the-box AI agent for SRE automatically surfaces important signals from alert noise and analyzes real-time telemetry data to pinpoint the root cause.

Learn more

See Edge Delta in Action

Get hands-on in our interactive playground environment.