{"slug": "blast-radius-for-agent-assisted-development-on-kubernetes", "title": "Blast Radius for Agent-Assisted Development on Kubernetes", "summary": "A developer introduced rgctl, a reachability graph control tool that indexes entire codebases to provide compact, deterministic blast-radius queries for AI coding agents. The tool precomputes reverse reachability on a condensed call graph, enabling O(1) lookups that help agents assess the impact of changes without loading the whole repository into their context window. Tested on the Kubernetes codebase, rgctl addresses the problem of agents missing transitive dependencies or burning context on irrelevant files.", "body_md": "Reachability Graph Control (rgctl) - AI coding agents default to reading files sequentially. That burns context, misses structure, and produces confident wrong answers about impact and dependencies. rgctl indexes the whole repository once into a rich graph with pre-computed reachability, then serves compact, deterministic query results — so agents (and humans) get the right slice of the codebase without loading it into the prompt.\n\nIf you refactor `RESTClientFor`\n\nin Kubernetes by hand, you probably do not open a\n\ncall-graph tool first. not that I am suggesting you should.. but lets take it as a good example when it comes to impact on the codebase, its dependencies etc. Usually you have an IDE find-references, a mental map of\n\n`client-go`\n\n, teammates who have been burned before, and CI that will catch what\n\nyou missed. For most edits, that workflow is fine.\n\nThe landscape changes when you decide to hand the same task to a coding agent. The agent\n\nhas a context window, grep, and no tenure on the team and importantly the LLM has its own limits, depending on which one you use.\n\nOn a tree with **181,000 functions**, asking it to \"refactor `newPodWorkers`\n\nsafely\" often\n\nproduces one of three outcomes: it reads the wrong files, stops at direct\n\ncallers and misses transitive impact, or burns most of the turn budget\n\nexploring `pkg/kubelet/`\n\nwithout ever reaching `cmd/kubelet/main`\n\n.\n\nThis post is for brave souls who work or plan to work **with** agents on large codebases. It\n\nexplains why [rgctl](https://github.com/sshaaf/rgctl) blast radius belongs in\n\nthat workflow, what you should expect from the output, and where your existing\n\ntools are still the right choice. The examples use the upstream\n\n[kubernetes/kubernetes](https://github.com/kubernetes/kubernetes) repository —\n\ncloned locally and indexed with rgctl, not a trimmed or synthetic corpus.\n\nBlast radius answers one question: **if I change this function, what upstream\ncode could be affected?** It walks the call graph\n\n`CALLS`\n\n| Output | Meaning |\n|---|---|\nDirect callers |\nFunctions that call the target directly |\nImpact zone |\nFull transitive closure of upstream callers |\nScore (0–100) |\nA capped impact rating derived from both counts |\n\nThe score is not PageRank (even though rgctl can also calculate it) and its not guestaming it either. rgctl combines two components:\n\n| Component | Formula | Cap |\n|---|---|---|\n| Direct callers | `direct_count × 25` |\n40 points |\n| Transitive impact | `impact_count × 0.05` |\n60 points |\n\n```\nscore = min(direct_component + transitive_component, 100)\n```\n\nA symbol with no upstream callers in the static graph scores **0**. A shared\n\nhelper with dozens of direct callers and a large impact zone lands in the\n\n**40–50** range; scores above **50** flag architectural hotspots where both\n\nfan-in dimensions are high.\n\nLets take a look at how this is made possible, or as my agent friends always say \"lets delve into it..\"\n\nAgents work in multi-turn sessions. A live breadth-first search over 1.7M\n\nedges on every \"who calls X?\" would make that unusable. rgctl does the expensive\n\nwork once, at `discover`\n\ntime, and serves lookups from pre-built snapshots.\n\n**Step 1 — Build the call graph:** `discover`\n\nparses source, extracts function\n\nnodes and `CALLS`\n\nedges, and writes `graph.snapshot.bin`\n\nplus a dedicated\n\n`blast_engine.snapshot.bin`\n\n.\n\n**Step 2 — Collapse cycles:** Mutual recursion is common in real codebases\n\n(7,097 circular call dependencies in Kubernetes). rgctl runs Kosaraju's\n\nalgorithm on the `CALLS`\n\nsubgraph and condenses each strongly connected\n\ncomponent (SCC) into a single node, producing a directed acyclic graph (DAG).\n\nCycles no longer break reachability analysis.\n\n**Step 3 — Precompute reverse reachability.** On the condensed DAG, rgctl\n\npropagates reachability in reverse topological order, storing the result as\n\ndense bitsets — one per SCC. A query becomes a bitset read: **O(1) lookup**\n\non the condensed graph, not a per-request graph walk.\n\n**Step 4 — Serve through tiers:** At query time, rgctl tries the fastest path\n\nfirst:\n\n`macro_call_index.db`\n\n)`--with-slices`\n\nor\n`--policy-file`\n\n)On Kubernetes that means a **~13 s** one-time index, then **~1 s** per symbol\n\nquery — no daemon, no remote service. Re-run `discover`\n\nafter large merges so\n\nthe graph stays current. The full breakdown is in the\n\n[design doc](https://shaaf.dev/rgctl/docs/design/blast-radius-design).\n\nA typical agent turn looks like this:\n\n```\nYou:   \"Change how newPodWorkers handles static pods — keep behavior, improve readability.\"\nAgent: [reads pod_workers.go] [greps \"newPodWorkers\"] [edits file] [maybe runs tests]\n```\n\nBefore any line changes, the agent has to answer a structural question: *who\ndepends on this symbol, transitively?* The usual approaches all fall short at\n\n| What the agent tries | What it gets | What it misses |\n|---|---|---|\n| Read one file | Local implementation | Callers in other packages |\n`grep newPodWorkers` |\nText matches | Upstream paths two or three hops away |\n| Rely on training data | Plausible Kubernetes architecture | This checkout's actual call graph |\n| Read 20 caller files | More context, fewer tokens left for the edit | No guaranteed closure; easy to stop early |\n\nHow many times we have seen an agent confidently edit\n\na helper and skip the integration test package that actually exercises it. Blast radius closes that gap with a single subprocess (point `rgctl -r`\n\nat your\n\nlocal clone of [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes)):\n\n```\nrgctl -r /path/to/kubernetes -f json blast-radius <Symbol>\n```\n\nThe response is a few hundred tokens of structured JSON instead of tens of\n\nthousands of lines of source the agent would otherwise have to read and still\n\nfail to synthesize correctly. You can run the same command yourself in a\n\nterminal — the point is not to replace your IDE, but to give the agent (and\n\nyou, when reviewing its plan) a shared, verifiable fact base.\n\nrgctl is built around a loop that matches how Cursor, Claude Code, and similar\n\ntools operate today:\n\n```\n1. Your prompt        →  natural language (\"what breaks if I change X?\")\n2. Subprocess         →  rgctl -f json blast-radius <Symbol>\n3. Structured facts   →  parse schema_version + payload\n4. Reasoning          →  risk summary, test plan, edit scope\n5. Edit / check       →  re-query if the graph may be stale\n```\n\nThe contract lives in [AGENTS.md](https://github.com/sshaaf/rgctl/blob/main/AGENTS.md)\n\nand installs as a project skill via `rgctl install --skill`\n\n. Agents parse\n\n**stdout JSON only** — not stderr, not the dashboard unless you ask for a UI.\n\nWhen you review an agent's plan, ask whether it grounded impact analysis in\n\nsomething like step 2, or whether it inferred callers from grep and memory.\n\nThat distinction is usually visible in the quality of the proposed test plan.\n\nAll numbers below come from indexing a fresh clone of\n\n[kubernetes/kubernetes](https://github.com/kubernetes/kubernetes) — the same\n\ntree you get from GitHub, not a fixture or subset. When working inside the\n\nrgctl repo, `./scripts/fetch-profile-repos.sh`\n\nclones it to `example/kubernetes`\n\n;\n\notherwise clone it anywhere and pass that path to `-r`\n\n.\n\n| Metric | Value |\n|---|---|\n| Source files indexed | 26,141 |\n| Functions analyzed | 181,680 |\n| Graph edges | 1,789,412 |\nFunctions named `Run`\n|\n513 |\nCold `discover` time |\n~13 s |\nTypical `blast-radius` query |\n~1 s |\n\nNo one should have to read 26k Go files to answer \"what is the upstream impact of this symbol?\".\n\nIMHO it just doesnt make sense, whether a programmer or the helper agent.\n\nIndex once:\n\n``` bash\n$ git clone https://github.com/kubernetes/kubernetes.git\n$ rgctl -r kubernetes discover .\n[✓] Loaded 26141 files -> 740986 nodes, 1789412 edges\n[✓] Analyzed 181680 functions\n[✓] Completed in 13.3s\n```\n\n`discover`\n\nwrites artifacts to `kubernetes/.rgctl/`\n\n(or wherever you cloned),\n\nincluding `blast_engine.snapshot.bin`\n\nand a macro call lookup cache. Queries\n\nare mmap lookups, not live graph walks over 1.7M edges.\n\n`RESTClientFor`\n\nSuppose you ask your agent to add a default timeout to `RESTClientFor`\n\nin\n\n`client-go/rest/config.go`\n\n.\n\nWithout blast radius, a competent agent will open the file, grep for\n\n`RESTClientFor(`\n\n, find a handful of call sites, edit, and suggest:\n\n```\ngo test ./staging/src/k8s.io/client-go/rest/...\n```\n\nThat is not wrong. It is incomplete.\n\nWith blast radius, one query returns the full upstream picture:\n\n``` bash\n$ rgctl -r kubernetes -f json blast-radius RESTClientFor\n{\n  \"schema_version\": 2,\n  \"target\": {\n    \"canonical_fqn\": \"RESTClientFor\",\n    \"file_path\": \"kubernetes/staging/src/k8s.io/client-go/rest/config.go\",\n    \"signature\": \"func RESTClientFor(config *Config) (*RESTClient, error) {\"\n  },\n  \"metrics\": {\n    \"direct_callers_count\": 38,\n    \"impact_zone_size\": 95,\n    \"score\": 44.75\n  },\n  \"topology\": {\n    \"direct_callers\": [\n      { \"fqn\": \"factoryImpl.RESTClient\" },\n      { \"fqn\": \"NewForConfig\" },\n      { \"fqn\": \"Framework.BeforeEach\" }\n    ],\n    \"impact_zone\": [\n      { \"fqn\": \"NewKubectlCommand\" },\n      { \"fqn\": \"main\", \"file_path\": \".../cmd/kubectl/kubectl.go\" }\n    ]\n  }\n}\n```\n\nFrom this you — and the agent — can derive a concrete plan:\n\n`cmd/kubectl/main`\n\n`rest/`\n\nare necessary\nbut not sufficient.You might reach the same conclusion with IDE find-references and ten minutes of\n\nclicking. The agent will not do that reliably unless you give it a tool that\n\nreturns the closure in one shot.\n\nThis matters if you care about agent turn quality, not just correctness:\n\n| Approach | Approx. context cost | Caller coverage |\n|---|---|---|\nRead `config.go` + five caller files |\n15k–40k tokens | Partial |\n`rgctl -f json blast-radius RESTClientFor` |\n0.5k–2k tokens | Full static closure |\n\nThe agent keeps context for the actual edit and for reasoning about risk, rather\n\nthan spending it reconstructing a call graph from grep output.\n\n`newPodWorkers`\n\nA subtler case. You ask the agent to extract pod worker configuration into a\n\nstruct in `pkg/kubelet/pod_workers.go`\n\n.\n\nGrep finds **four** direct call sites. That sounds low-risk.\n\n``` bash\n$ rgctl -r kubernetes blast-radius newPodWorkers \\\n    --file pkg/kubelet/pod_workers.go\nBlast radius for 'newPodWorkers'\n  Score: 41.5/100\n  Direct callers: 4\n  Impact zone: 30\n  Callers: TestFakePodWorkers, createPodWorkersWithLogger, NewMainKubelet,\n           TestVolumeAttachLimitExceededCleanup\n  Impact: ... NewMainKubelet, createAndInitKubelet, RunKubelet, startKubelet,\n          Run, run, NewKubeletCommand, main\n```\n\nFour direct callers, but **30** in the impact zone — the chain runs through\n\nkubelet construction all the way to `cmd/kubelet/main`\n\n.\n\nAn agent that stops at grep will under-scope the test plan. One that reads blast\n\nradius should propose `pkg/kubelet`\n\ntests **and** the `cmd/kubelet`\n\nstartup path,\n\nand mention `RunKubelet`\n\nin the change summary.\n\nYou might get there by tracing callers in your IDE or by running\n\n`make test WHAT=pkg/kubelet`\n\nand seeing what breaks. Blast radius is how you\n\nfront-load that knowledge before the agent edits, not after CI fails.\n\n`--depth`\n\nto control noise\nKubernetes impact zones include a lot of test harness symbols. When you want a\n\nlocal picture rather than the full release surface:\n\n``` bash\n$ rgctl -r kubernetes blast-radius newPodWorkers \\\n    --file pkg/kubelet/pod_workers.go --depth 2\n  Impact zone: 8   # down from 30\n```\n\nFull closure for \"what could this break in production?\" Bounded depth for\n\n\"what is immediately upstream?\" Same command, one flag.\n\nYou ask: \"Kubelet.Run looks unused — can we remove it?\"\n\n``` bash\n$ rgctl -r kubernetes blast-radius Kubelet::Run \\\n    --file pkg/kubelet/kubelet.go\n  Score: 0.0/100\n  Direct callers: 0\n  Impact zone: 0\n```\n\nA score of zero here does **not** mean safe to delete. `Kubelet.Run`\n\nis the\n\nkubelet's main loop; it is started from `cmd/kubelet`\n\n. For this symbol the gap\n\nis primarily **interface dispatch** — fast static analysis (without heavy\n\npointer analysis) struggles to link a caller that holds an interface type to the\n\nconcrete `Kubelet`\n\nimplementation. Goroutine spawns can produce the same blind\n\nspot: the graph may not record them as `CALLS`\n\nedges either.\n\nYou would not delete it. An agent might, if it treats the score as ground truth.\n\nThis is the most important caveat when relying on blast radius in agent\n\nworkflows: **the output is a risk signal for reasoning, not permission to\nmerge.** When score is zero on a symbol you know is hot, disambiguate with\n\n`--class`\n\nor `--file`\n\n, then grep for call sites the graph cannot see. TheWhether you or your agent runs the query, these are the fields worth paying\n\nattention to:\n\n| Field | Why it matters |\n|---|---|\n`target.canonical_fqn` |\nDisambiguated symbol — not just a name that matches 513 `Run` functions |\n`target.file_path` |\nAnchor for edits and citations |\n`metrics.score` |\nQuick risk tier: 0 / ~25–50 / 50+ |\n`metrics.direct_callers_count` |\nImmediate fan-in |\n`metrics.impact_zone_size` |\nTransitive fan-in |\n`topology.direct_callers[]` |\nFirst-hop checklist for tests and review |\n`topology.impact_zone[]` |\nFull upstream checklist |\n`topology.scc_component_id` |\nHint when the symbol sits in a cycle-heavy neighborhood |\n`gatekeeping.policy_status` |\n`VIOLATED` when a policy file rejects the change |\n\nText output is fine for a quick terminal check. Agent workflows and CI gates\n\nshould use `-f json`\n\nper the [JSON API](https://shaaf.dev/rgctl/docs/json-api).\n\nKubernetes has **513** functions named `Run`\n\n. A bare symbol query fails loudly:\n\n``` bash\n$ rgctl -r kubernetes blast-radius Run\nError: Symbol 'Run' is ambiguous. Found 513 matches.\nRemediation: rgctl blast-radius \"ClassName::Run\"\n              rgctl blast-radius \"path/to/file.go::Run\"\n```\n\nYou click the right reference in your IDE. An agent needs the remediation path\n\nin the error output — and a rule that says never pick a random row from the\n\ndisambiguation table. In practice: retry with `ClassName::symbol`\n\n, `--file`\n\n, or\n\n`--class`\n\nbefore editing.\n\nIf you are setting up agent rules or reviewing agent output, check whether\n\ndisambiguation happened before the blast-radius numbers were quoted.\n\nYou can wire blast radius into automated guardrails:\n\n```\n{ \"max_impact_nodes\": 50 }\nbash\n$ rgctl -r kubernetes -f json blast-radius RESTClientFor \\\n    --policy-file policy.json\n# gatekeeping.policy_status: \"VIOLATED\", exit code 1\n```\n\nFor an agent proposing a large refactor, `VIOLATED`\n\nshould mean stop and report\n\n— not silently proceed. The `check`\n\ncommand runs the same rules across all\n\ntouched symbols in one pass, which fits agent-opened PRs where you want a hard\n\nceiling on impact zone size.\n\nYou might override that judgment on a case-by-case basis. Agents should not\n\noverride it unless you explicitly say so.\n\nBlast radius is not the right first move for every task — even in agent-assisted\n\nwork:\n\n| Your question | Often better |\n|---|---|\n| What calls this in the file I have open? | IDE find references |\n| Will CI pass? | Run the tests |\n| Is this a public API? | Module boundaries, docs, review |\n| Who owns this package? | CODEOWNERS, team knowledge |\n| What changed in this branch? | `git diff` |\n\nBlast radius earns its place when you need **transitive upstream impact as a\nbounded fact** — before an agent edits a shared helper, before you approve its\n\n`SyncPod`\n\nmay show zero callers; the\ngraph does not always resolve interface-typed receivers`Test*`\n\nsymbols; use\n`--depth`\n\nor path filters when you care about production paths only`topology`\n\n, it was not\nin the query result; treat that as a hallucination risk`rgctl`\n\nfrom `rgctl discover . --export-migration-hints`\n\nto index your application.\n\n```\nexport REPO=\"$(pwd)/kubernetes\"\nrgctl -r \"$REPO\" discover .\nrgctl -r \"$REPO\" -f json blast-radius RESTClientFor\nrgctl -r \"$REPO\" -f json blast-radius newPodWorkers --file pkg/kubelet/pod_workers.go\n```\n\nIf you are already in the rgctl repo, `./scripts/fetch-profile-repos.sh`\n\nclones\n\nkubernetes/kubernetes into `example/kubernetes`\n\n— use that path for `-r`\n\ninstead.\n\nA useful sanity-check prompt for your agent:\n\nUse rgctl to find the blast radius of RESTClientFor, and tell me what tests\n\nwe should run before changing it based on the upstream callers.\n\nThe explicit tool invocation matters — agents sometimes skip subprocesses unless\n\nthe prompt names them. A good answer runs `rgctl -f json blast-radius`\n\nand cites\n\n`metrics`\n\nand `topology`\n\n. A weak one greps the tree and guesses.", "url": "https://wpnews.pro/news/blast-radius-for-agent-assisted-development-on-kubernetes", "canonical_source": "https://dev.to/sshaaf/blast-radius-for-agent-assisted-development-on-kubernetes-161", "published_at": "2026-09-02 20:52:38+00:00", "updated_at": "2026-09-02 21:23:26.355688+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "machine-learning"], "entities": ["rgctl", "Kubernetes", "Kosaraju's algorithm"], "alternates": {"html": "https://wpnews.pro/news/blast-radius-for-agent-assisted-development-on-kubernetes", "markdown": "https://wpnews.pro/news/blast-radius-for-agent-assisted-development-on-kubernetes.md", "text": "https://wpnews.pro/news/blast-radius-for-agent-assisted-development-on-kubernetes.txt", "jsonld": "https://wpnews.pro/news/blast-radius-for-agent-assisted-development-on-kubernetes.jsonld"}}