> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zespan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Blast Radius — what breaks if you change this

> A dependency graph across prompts, agents, models, guardrails, evaluators, and alerts, backing an impact check shown before you release a prompt and a delete-blocking check on evaluators.

Blast Radius answers "what actually depends on this?" for a single resource — a prompt, an agent, a guardrail policy, an evaluator, a dataset, a model, or an alert — by walking a dependency graph built from both your project's configuration and its real production traffic.

It isn't a standalone dashboard page today. It surfaces in two places where the answer changes what you'd do next:

* **Releasing a prompt** — the confirmation dialog for promoting a version to `production` shows an impact summary before you commit. Advisory only; it never blocks the release.
* **Deleting an evaluator** — the delete confirmation shows the same summary plus a full dependents table, and it *does* block: if the evaluator has any dependents (or the check fails to load), you have to explicitly acknowledge before **Delete** becomes clickable.

<Note>
  A full graph visualization and a standalone Blast Radius page are on the roadmap but not built yet. Today the graph is only consumed through the two integration points above, and directly through the API described below.
</Note>

## Declared vs. observed edges

Every dependency in the graph is one of two kinds, and the UI keeps them visually distinct rather than collapsing them into one number:

| Origin     | What it means                                                                                      | Source                                                                         |
| ---------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `declared` | Configured — someone wired this dependency in your project, whether or not it's ever actually used | PostgreSQL (prompt embeddings, guardrail agent filters, evaluator→alert links) |
| `observed` | Actually called — real production traffic passed through this dependency in the trace window       | ClickHouse trace data                                                          |

The distinction matters in both directions: a prompt configured as a dependency of six agents but only ever called by one is a much smaller real blast radius than the declared graph alone suggests. Conversely, a dependency nobody explicitly configured but that shows real call volume is exactly the kind of undocumented coupling this feature exists to surface.

A dependent can be reached through more than one edge (for example, a prompt embedded in two different parent prompts, both called by the same agent). When that happens, `hasObservedTraffic`, `totalCallVolume30d`, and `totalCostUsd30d` on that dependent are summed across **every** inbound edge feeding it from within the blast radius — not just the one edge that happened to be discovered first. A dependent reached only through a `declared` edge can still show real observed traffic, if another edge into it (from elsewhere in the same blast radius) is `observed`.

## Dependency direction

Every edge is `{ from, to }`, and **`to` depends on `from`** — read it as "changing `from` affects `to`":

| From             | To            | What it means                                                                                              |
| ---------------- | ------------- | ---------------------------------------------------------------------------------------------------------- |
| Child prompt     | Parent prompt | The parent embeds the child's content by reference; editing the child changes the parent's rendered output |
| Guardrail policy | Agent         | The policy is scoped to that agent via its agent filter                                                    |
| Model            | Agent         | The agent actually called that model (observed)                                                            |
| Prompt           | Agent         | The agent actually called that prompt (observed)                                                           |
| Evaluator        | Alert         | The alert rule watches that evaluator                                                                      |

So "what breaks if I change this prompt?" is answered by walking dependents *from* the prompt — which surfaces every agent that calls it, directly or transitively.

## Node ids

A node id is `<kind>:<key>` — for example `prompt:support-reply` or `evaluator:3f9c...`. Keys are **names** for prompts, agents, and models, and **UUIDs** for row-backed kinds (evaluators, guardrail policies, datasets, alerts). Because a key can itself contain a colon or a slash (a prompt legitimately named `checkout/v2` produces the id `prompt:checkout/v2`), the id always travels as the `node` query parameter — never a URL path segment.

## Depth, node, and time-window caps

A blast radius is a breadth-first walk from the root node, bounded on three axes so a hub resource with hundreds of dependents (or a genuine dependency cycle — agent-to-agent delegation produces real ones) can't turn one request into an unbounded scan:

| Parameter    | Default | Max  | What it bounds                                                 |
| ------------ | ------- | ---- | -------------------------------------------------------------- |
| `maxDepth`   | 3       | 5    | How many hops from the root to traverse                        |
| `maxNodes`   | 500     | 2000 | Total dependents returned                                      |
| `windowDays` | 30      | 90   | Lookback window for observed (ClickHouse) call volume and cost |

`truncated: true` in the response means the walk hit one of these caps **and** the graph genuinely continues beyond it — not merely that the graph happened to end exactly at the cap. If depth 3 reaches every dependent and stops naturally, `truncated` is `false` even though depth 3 was the limit checked.

## Response shape

```json theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
{
  "root": { "id": "prompt:support-reply", "kind": "prompt", "name": "support-reply", "href": "prompts/support-reply", "environments": ["prod"] },
  "dependents": [
    {
      "node": { "id": "agent:CustomerSupportAgent", "kind": "agent", "name": "CustomerSupportAgent", "href": "agents/CustomerSupportAgent", "environments": ["prod", "staging"] },
      "depth": 1,
      "via": [{ "from": "prompt:support-reply", "to": "agent:CustomerSupportAgent", "origin": "observed", "callVolume": 386, "costUsd": 41.2 }],
      "totalCallVolume30d": 386,
      "totalCostUsd30d": 41.2,
      "hasObservedTraffic": true
    }
  ],
  "impact": {
    "agents": 1,
    "environments": ["prod", "staging"],
    "productionAffected": true,
    "callVolume30d": 386,
    "cost30dUsd": 41.2,
    "slosAtRisk": [],
    "gates": []
  },
  "truncated": false,
  "computedAt": "2026-08-07T09:54:00.000Z"
}
```

`dependents[].totalCallVolume30d` and `totalCostUsd30d` sum to `impact.callVolume30d` / `impact.cost30dUsd` across the whole response — the per-node numbers and the headline number are the same aggregation at two grains, not two separately-computed figures that can drift apart.

`computedAt` marks the moment this response's graph was built. Every request computes fresh — there's no caching in front of this endpoint today — so `computedAt` is effectively "now," modulo the small delay before a trace becomes queryable. It's still shown on every card and table because a number with no timestamp reads as unexplainably stale the first time it lags even slightly behind a dashboard refresh.

<Warning>
  `impact.slosAtRisk` and `impact.gates` are always empty arrays today. They're reserved fields for an SLO model that hasn't shipped yet — present so the response shape won't need to change later, not because either is currently populated. Don't build against them expecting real data.
</Warning>

## Where it's not (yet)

* **No standalone Blast Radius page.** The graph is only reachable through the two integration points above and the API directly.
* **No graph visualization.** `GET /projects/:id/graph` returns the full raw graph (nodes + edges) for a project, sized for a future visualization — nothing in the dashboard renders it yet.
* **Tool and HTTP-target nodes aren't in the graph.** Only prompts, agents, models, guardrail policies, evaluators, datasets, and alerts are covered. MCP tool calls and HTTP targets are candidates for a later pass.

## API

Both endpoints require a dashboard session (browser cookie) with the `dashboard:read` permission — not an `x-api-key` request. See the **Blast Radius** group in the [API Reference](/api-reference/introduction) for full parameter and response documentation.

```bash Get the blast radius for one node theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
curl "$ZESPAN_API_URL/projects/$PROJECT_ID/blast-radius?node=prompt%3Asupport-reply" \
  -H "Cookie: <your dashboard session cookie>"
```

A malformed `node` (missing the `kind:key` separator) returns `400`. A well-formed id for a resource that doesn't exist in the project — or that belongs to a different project — returns `404`; the two cases (typo vs. wrong project) are deliberately indistinguishable in the response, so a cross-project id can't be used to probe for a resource's existence.

## Next steps

* [Prompts](/dashboard/prompts#blast-radius-before-releasing) — the release-confirmation impact card
* [Evaluations](/dashboard/evaluations#deleting-an-evaluator) — the delete-blocking check
* [Changes](/dashboard/changes) — the project-wide "what changed" timeline this feature complements: Changes tells you what happened, Blast Radius tells you what else would be affected before it happens
