TL;DRIf you build with composable prompts, changing one shared component can leave you with a difficult question: what actually needs to be re-evaluated?I built a pure Python prompt dependency graph that answers that question with two numbers:Reachable: everything downstream of the changed component—the structural ceiling.Candidate: the smaller set that directly depends on the changed section, plus its downstream consumers.I tested the approach on a deterministic 55-node synthetic system. Depending on how selectively a component is shared, section-aware tracking narrowed the evaluation set by anywhere from 0% to 85% in my experiments.The important caveat: these numbers identify what should be evaluated, not what will actually fail. Behavioral impact still requires running the evaluation itself.The results, before the methodology: Here is the data before we get into the code. I ran a 55-node synthetic experiment across a few different change scenarios, tracking the total ceiling versus the actual evaluation set:Change TargetReachable (Ceiling)Candidate (Evaluation Set)Narrowingbase-policy / refunds452447%tone / professional55550%format / json553536%base-policy / privacy452447%safety / no-medical-advice151313%The real takeaway here isn't just that a specific change can slash your test burden by nearly half. The catch is how unpredictable the graph is.Sometimes it finds a massive amount of narrowing, and other times it finds nothing. You literally do not know which outcome you are getting until you actually run it.The Line I Changed That I Couldn't Reason AboutI compose most of my production prompts out of shared pieces. There is a base-policy block inherited by a support agent, a sales agent, and an internal analyst agent. There's a tone block that almost imports everything, and a format block controlling JSON versus Markdown output. It is a completely ordinary setup once you have more than a handful of agents.One afternoon I changed one sentence in base-policy, extending the refund window from 30 days to 14. The problem wasn't making the edit. It was knowing which agents needed re-evaluation before I shipped it.Running the entire suite on everything was expensive. I had been guessing which agents used the policy, but I couldn’t tell when I was wrong. A correct guess and a lucky one look identical until something breaks. Shipping and waiting for a support agent to quote the old refund window to a real customer was obviously worse than either.I wanted a structural answer to a simple question: what depends on the thing I just changed? That question already has a name in traditional software engineering, change impact analysis, which means tracing dependency relationships outward from a change to determine what else needs re-verification [1]. Prompt engineering still lacks many of the lifecycle practices that software engineering takes for granted, and a recent academic proposal for promptware engineering makes the same observation [2].So I built the smallest version of that I could, and tested it against a system designed specifically to break my assumptions.Complete code: https://github.com/Emmimal/prompt-dependency-graph/Why Composability Creates a Larger Evaluation SurfaceThe blast radius of composability: A single edit to the shared base-policy cascades through the dependency graph, carrying potential downstream impacts to the final JSON, email, and report outputs.This diagram visualizes how composability creates a "blast radius" within prompt or software architecture. At the root of the hierarchy sits base-policy, acting as a single, shared point of dependency. From this foundation, the system branches into three separate workflows: support, sales, and analyst, which ultimately generate a JSON payload, an email, and a report. Because these downstream branches all inherit from base-policy, any modification to that root node automatically propagates outward through the entire graph. Just as in chaos engineering or Bazel build systems, this structure illustrates the concept of blast radius—how far and wide the consequences of a single upstream change can travel. In prompt chaining, where there are no compilers to catch cascading errors, a minor tweak to the base-policy risks unpredictable failures at the final output layer.Every shared component is a single point that many other things depend on. Every edit to it carries a blast radius.The term comes from explosives, but it has a long second life in software engineering describing how far a change's consequences propagate outward from its source [3]. I am using it in that software-engineering sense here. It means potential downstream impact, not observed behavioral damage.This doesn’t measure whether the prompt’s output actually changed. It measures how far that change could spread if it did.Chaos engineering treats minimizing blast radius as a first-class design goal for a simple reason: not to prevent failure, but to keep its consequences bounded and legible [4].Build systems solve an adjacent problem the same way. Bazel maintains a dependency graph across a codebase specifically so a single file change only triggers rebuilds of what is actually downstream of it, rather than everything [5].Prompt chaining has its own version of this failure. Because prompts feed into each other, a small and unintended change to an upstream prompt can produce unpredictable results several steps downstream, with no compiler or type system to catch it first [6].That is exactly what I was worried about with base-policy. I just had no way to measure it.Why a Flat Dependency Lookup Fails Before building anything complex, I tried the simplest approach: check who directly imports the component I edited.I set up two test cases with the exact same total impact (4 downstream nodes), but different layouts:Flat: comp-a feeds straight into 4 different agents. (4 direct dependents)Deep: comp-b feeds into an agent, which feeds into a workflow, then another agent, and so on. (1 direct dependent)A one-hop dependency lookup misses 3 of 4 real consumers.This is the flat-vs-deep test from earlier in the article, drawn out. comp-b only has one direct dependent, agent-1, but agent-1 feeds a workflow, which feeds another agent, which feeds another workflow. All four of those are genuinely downstream of comp-b. A tool that stops at direct dependents reports 1. The graph traversal reports 4, because it actually follows the chain instead of stopping at the first hop.If you only check direct imports, the flat system reports 4 impacted nodes, but the deep system reports 1. It misses three-quarters of the actual impact.The reachable downstream set didn't change; both graphs contain 4 downstream nodes. What changed was what a one-hop lookup could see. In the deep case, it finds only the first dependent and misses the three nodes further downstream. That is the reason transitive analysis matters in traditional change impact analysis: dependencies propagate through chains, so looking only at direct dependents can systematically undercount the downstream evaluation surface [1].In prompt systems, this happens often because prompts are layered like organizational charts. A base policy goes to an agent, which goes to a workflow, which goes to a router. Every layer is another hop.If a dependency check only looks one level deep, it can miss risks in prompts further down the chain, where important logic often lives.Component 1: The Data ModelA PromptComponent isn't modeled as an opaque blob of text. It is a versioned collection of named sections.The section-level approach is the most important design choice because it helps us define “candidate” more narrowly than “reachable.” Agents declare which specific sections they use, and with what relationship (imports, inherits, references, formats-with):I didn’t use dependency type in the impact calculation for v1, but I still needed the model to keep track of the difference.This was a deliberate choice: I made the data model flexible enough for future needs, instead of adding a new field later and changing all the existing dependency declarations. Adding a type system later is harder than including one extra field that you don’t use yet.Component 2: The Dependency GraphThe graph needs to answer two simple questions: who uses this component directly, and who is reachable further down the line?This dependency walk fixes the blind spot we saw earlier. It treats agent-to-agent and agent-to-workflow connections as normal graph links and uses a simple breadth-first search to follow them outward:I called this structural_blast_radius in the code, but I just label it Reachable in the output. Saying structural feels a bit too confident since we do not know the full behavioral impact yet. Reachable is a cleaner description of what it actually is: everything downstream that could get touched, rather than everything that definitely will be.I considered limiting how far the search could go in larger systems, but the results didn’t show a need for it. With 55 nodes, the search takes less than a millisecond. Testing it on larger production systems is still future work.Component 3: Reachable versus CandidateA section is considered changed when its text is different between two versions. There are no embeddings or semantic checks, just a direct text comparison that ignores extra spaces:That is one half of the mechanism. The other half runs the same graph traversal, but starts from the specific nodes that declared the changed section instead of the whole component:That’s the whole process. We run the same breadth-first search twice: once for everything connected to the component, and once only for nodes connected to the section that changed.Defining the Two NumbersThis gives us two clear terms to work with:Reachable set: The maximum possible impact: every node connected to the changed component, no matter which section changed. It comes only from the graph structure.Candidate set: The proposed evaluation boundary: nodes that directly depend on the changed section, plus everything downstream from them. It is based on the section diff and graph traversal.Once a node is selected, everything downstream from it is included. For example, if a changed section affects an agent and that agent affects a workflow, the workflow becomes a candidate too. After the first step, we track nodes rather than individual sections.Important Caveats on the NumbersBecause the reachable ceiling is computed per component rather than per section, it treats every section the same. If a component has one widely used section and one rarely used section, the reachable number stays identical no matter which one you edit. The candidate set is the only metric that actually shifts based on the specific edit.To keep things precise, I stick to specific terms for these outputs so the tool is never misunderstood:AvoidUse InsteadWhyStructural blast radiusReachable blast radius"Structural" implies a level of precision the ceiling doesn't have.Semantic blast radiusCandidate blast radiusNothing semantic is happening; it is just a section-level diff.Safe promptsCandidates for evaluationSkipping the candidate list is not the same as proving safety.Unaffected promptsOutside the candidate setThe graph only shows declared dependencies, not actual behavior.Broken promptsPotentially affected promptsThe graph never confirms behavior changed; only evaluation does.Evaluation reductionEvaluation narrowing"Reduction" implies a target; "narrowing" just describes the mechanism.Component 4: The 55-Node Test SystemTo test this, I built a deterministic 55-node system consisting of 50 agents across five roles (support, sales, analyst, operations, and marketing, with ten teams each) plus 5 workflow nodes. Those workflow nodes depend on agents rather than components directly, which creates a real transitive and diamond-shaped dependency structure. There are also five shared components (base-policy, tone, format, domain, safety), each with two to four sections.RoleAgentsDepends on base-policy?Notessupport10Yes (refunds, privacy; plus escalation for enterprise/vip)Heaviest policy consumer.sales10Yes (privacy only; plus escalation for enterprise/partner)Narrower policy surface than support.analyst10Yes (escalation only, referenced rather than imported)Reads policy but doesn't own it.operations10Yes (refunds plus escalation)Also inherits both safety sections.marketing10NoDeliberately disconnected. Real systems always have agents that share nothing with a given policy.workflows5Reachable transitively through 3 constituent agents eachIncludes refund-workflow, escalation-workflow, renewal-workflow, onboarding-workflow, and compliance-workflow. Each depends on 3 agents across roles to form diamond shapes.The key rules behind this table are simple: support imports the refunds and privacy sections, sales only imports privacy, and marketing doesn't touch the base policy at all.That last rule is deliberate. If every node in a synthetic system depended on every shared component, it would hide the exact pattern I wanted to find. You need some agents with zero exposure to a given component to test whether section-aware tracking actually works.The full generator covering all five roles and the workflow layer lives in the repository. I left out the 50 near-identical configuration blocks here to save space.Every number in the results table at the top of this article comes from running compute_impact() against this exact graph. These figures describe one deliberately built, synthetic 55-node system rather than a universal law for every production suite. Whether a real system shows this same pattern depends entirely on its actual dependency structure, which is what this tool exists to measure rather than assume.Walking Through One Change, Start to FinishWith the system in place, it is worth tracing what happens during a real edit because the mechanism is easy to lose track of in the abstract.Say I edit the refunds section of base-policy:v1: "Customers may request refunds within 30 days of purchase."v2: "Customers may request refunds within 14 days of purchase."changed_sections() compares every section of v1 against v2. It sees that refunds differs, while privacy and escalation do not.Next, structural_blast_radius("base-policy") walks the graph outward through every node depending on it for any reason, plus everything downstream of those. That gives us 45 nodes.Meanwhile, section_dependents("base-policy", "refunds") narrows that list down to only the nodes that explicitly declared a dependency on refunds, extended downstream. That gives us 24 nodes.Why the Difference MattersThe gap between those two sets is concrete. Every sales-* agent is in the reachable set of 45 because they depend on base-policy through the privacy section. However, none of them are in the candidate set of 24 because they never declared a dependency on refunds.A sales agent uses the privacy section because it handles customer data, but it does not use the refund section. Without section-level declarations, we cannot tell the difference, so the sales agent is incorrectly included in the refund check.Splitting the full 55-node system three ways for this change clarifies the actual footprint:GroupCountWhat it MeansCandidate24Declared a dependency on refunds specifically.Reachable, not candidate21Downstream of base-policy through another section (like privacy or escalation), but not refunds.Not reachable at all10No dependency on base-policy, direct or transitive (all 10 marketing-* agents).Adding those up: 24 + 21 + 10 = 55, and every single node lands in exactly one bucket.Instead of checking all 55 agents or guessing, we get a clear first-pass set of 24 agents and can see why the other 31 were left out.base-policy/refunds changed — 45 reachable, 24 candidates.A sample of 8 of the 45 reachable agents, plus all 5 workflow nodes (there are only 5 total, so all of them fit). Orange means that node declared a dependency on the refunds section specifically. Gray means it's downstream of base-policy some other way, through privacy or escalation, so it shows up as reachable but isn't flagged for this particular edit.The Sharing-Level CurveThat first change happened to land on 47% narrowing. Is that number unique to base-policy, or would any component in this system land somewhere similar?To find out, I changed just one thing: how many agents shared a component. Everything else stayed the same. I tested one component shared by about 10%, 24%, 50%, and 76% of the agents, plus the existing tone component shared by all 100% of agents.Sharing level (% of agents)DirectReachableCandidateNarrowing10.0%554885%24.0%12541572%50.0%25542946%76.0%38544320%100.0% (tone)5055550%Each point is one sharing tier, run through the same measurement as everything else in the article. At 10% sharing, narrowing to just the candidates cuts 85% of the evaluation work. At 100% sharing, tone, which every agent uses, there's nothing left to cut. It's close to a straight line between those two ends. One small thing: the chart's y-axis literally says "reduction," which is fine on its own, but it's the exact word your article's vocabulary table tells readers to avoid in favor of "narrowing." Not a big deal either way, but if you want full consistency between the prose and the figure, it's a one-line change in make_visual2.py's ax.set_ylabel(...) call and a quick re-run.One detail is worth noting: the four sharing tiers reach 54 of the 55 nodes because one node is not assigned to any tier. The tone component reaches all 55 because every agent depends on it. These are just two different components with different real-world coverage.In this synthetic experiment, the relationship is monotonic: as component sharing increases, the opportunity for section-aware narrowing decreases. Put simply, the more universally a component is shared, the less there is for dependency-aware evaluation to narrow.This experiment demonstrates that relationship inside one controlled synthetic system. It doesn't prove that production systems will yield the exact same percentages.I built a separate component for this test instead of using the five results from earlier. Those earlier components had different sharing levels by chance, not by design. Using them would make it hard to know if the results were caused by sharing level or something else. So I tested one component with four sections at planned sharing levels to measure only the effect of sharing.Notice that Reachable stayed at 54 for every sharing level from 10% to 76%. This shows the limitation of measuring one component. Only the candidate set changed with the sharing level.The Result That Mattered More Than the Percentagestone/professional: Reachable 55, Candidate 55, Narrowing 0%.Every agent inherits tone. When it changed, every agent was a legitimate candidate. The graph did not fail to find a shortcut; there simply isn't one when a component is genuinely universal.This is the result that keeps the tool honest. It would be simple to build something like this and imply that dependency-aware evaluation always saves work. It doesn't.Sometimes running all 55 evaluations is the correct answer. A tool built to always report a smaller number would have quietly forced one here too. Instead, this one returns 55 without apology, because that is the honest answer for a component every agent genuinely depends on.There’s a practical takeaway here. If every consumer uses a shared component in the same way, section-level tracking does not help. For example, tone is used by everyone, so there is no smaller group to exclude.The tool is most useful for components that are used by different groups of agents. That is what the sharing curve measures. I would rather have a tool that sometimes says there is no shortcut than one that always pretends there is.How These Numbers Were VerifiedEvery number in this article comes from an actual run of the code in the repository rather than a hand-calculated estimate.The synthetic system is generated deterministically with random.seed(7), so the 50-agent configuration, 5 workflows, and their dependency relationships are reproduced consistently across runs. I also reproduced the full pipeline independently on a separate Windows machine in a clean virtual environment. The results matched the original run, including:The 45/24 split on base-policy and refunds.The 55/55 on tone.The 54-node ceiling across all four sharing tiers.The 4-node blast radius on both the flat and deep shape comparison.That reproducibility is not incidental. If a dependency graph's numbers can't be reproduced deterministically on a second machine, there's no reason to trust anything downstream of them.A Gap Caught During VerificationThe verification pass exposed a modeling issue in the first version of the workflow layer. Workflows were copying their constituent agents' component dependencies instead of declaring those agents as graph nodes. That meant the 55-node experiment was not actually testing the intended transitive traversal.I changed the model so workflows depend directly on their constituent agents and re-ran the experiments. The Reachable, Candidate, and Narrowing results did not change because the flattened and transitive representations happened to produce the same sets for this synthetic graph.The direct dependency counts did change. That’s why the sharing curve uses the 50 agents as the base, not all 55 nodes. The experiment measures how many agents share the component, while workflows are downstream nodes.Runtime CharacteristicsMeasured on Python 3.12, CPU only, standard library for the core mechanism (matplotlib and networkx are used only for the figures):OperationLatencyNotesBuild graph (55 nodes, 5 components)0.229 msAverage of 200 builds.compute_impact() (single change)0.0375 msAverage of 2,000 calls.compute_impact() × 1,000 calls31.91 msSingle timed block, no caching.For this 55-node experiment, graph construction and impact calculation were negligible compared with the cost of running the resulting evaluation set. The measured compute_impact() latency was 0.0375 ms on average, while the 1,000-call batch completed in 31.91 ms.I have not yet tested the implementation on graphs containing thousands of prompts, so these measurements should not be treated as production-scale performance results. The core operations, set unions and breadth-first searches, are lightweight, but we still need to test how well they scale to larger systems.Honest Design DecisionsMechanical section diffing: Section diffing is purely mechanical. A one-character fix and a full rewrite are treated the same because the check only compares the old and new text. This is a real limitation, not an oversight. Using an LLM to decide whether an edit is meaningful would add a judgment that is hard to verify. A dependency graph should show what changed and what could be affected, not decide whether the change is important.No section rename inference: Section renames aren't inferred, and I went back and forth on this. Renaming refund_policy to refunds is not treated as the same section under a new name. It is reported as a removed section and an added section, flagged as high-risk, with continuity explicitly marked as unresolved. Guessing that two differently named sections mean the same thing is unreliable. I would rather the tool say it cannot tell than give the wrong answer.Unused dependency kinds: Dependency kinds (imports, inherits, references, formats-with) are modeled but not load-bearing yet. The impact calculator treats all four identically, even though I do not think they should produce identical evaluation requirements. A formats-with dependency on a JSON schema section probably should not trigger the same re-evaluation as an imports dependency on a refund policy section. I left this out of v1 on purpose rather than building a distinction I had not actually tested. It is the most obvious next increment, not a gap I overlooked.Toy system scope: This is a 55-node toy system, not a production benchmark, and every number in this piece is scoped to it specifically. I built it to isolate one variable at a time (sharing level, dependency depth, section granularity) cleanly enough to see real patterns, not to simulate what a production prompt library looks like.Section-level structure requirement: Section-level structure is the main requirement for this to work. If a prompt is not split into named sections, there is nothing for the system to compare. In that case, it tracks the whole component and gives the full Reachable number without narrowing it down.In practice, this usually does not require a big migration. Most system prompts already have sections, such as policy, tone, and formatting instructions. You just need to make those sections explicit using simple Markdown headers or comment blocks. That is most of the work needed to adopt this approach.Trade-offs and What's MissingThe current implementation deliberately stops at structural, section-aware dependency analysis. Several useful capabilities remain outside v1:MissingWhy it's out of scope for v1Dependency-type-aware filteringKind is modeled but not yet used in impact calculation.Evaluation harness integrationThis tool says what to check, not how to check it.Circular dependency handlingSystem was built without cycles; real graphs with them need this.Production-scale validation55 nodes proves the mechanism, not throughput at scale.Make the Cost of Change VisibleThe useful output of a prompt dependency graph is not a prediction of failure. It is a defensible evaluation boundary.When base-policy/refunds changes, this system says: start with these 24 candidates. It does not say those 24 prompts will break. When tone changes, it says all 55 are candidates because every agent genuinely depends on that component.A dependency graph doesn't make a prompt safer. It makes the cost of changing it visible before you pay it.Complete code: https://github.com/Emmimal/prompt-dependency-graph/Two earlier pieces cover adjacent ground:"Prompt Engineering Is Solved—Prompt Management Isn't" looks at contract validation for individual prompt changes."Prompt Engineering Fails Quietly—Prompt Regression Is Why" covers runtime behavioral regression detection after a change ships.This piece sits upstream of both. It is about knowing what to check before you run either of those checks, rather than the checking process itself.References[1] Change impact analysis. Wikipedia. https://en.wikipedia.org/wiki/Change_impact_analysis[2] Chen, Z., Wang, C., Sun, W., Liu, X., Zhang, J. M., & Liu, Y. (2026). Promptware engineering: Software engineering for prompt-enabled systems. ACM Transactions on Software Engineering and Methodology. https://doi.org/10.1145/3796535[3] Blast radius. Wikipedia. https://en.wikipedia.org/wiki/Blast_radius[4] Rosenthal, C., Hochstein, L., Blohowiak, A., Jones, N., & Basiri, A. (2017). Minimize blast radius. In Chaos engineering. O’Reilly Media. https://www.oreilly.com/library/view/chaos-engineering/9781491988459/ch07.html[5] Bazel overview. https://bazel.build/[6] Greyling, C. These Are The Challenges When Creating A LLM Based Conversational Interface. https://cobusgreyling.medium.com/these-are-the-challenges-when-creating-a-llm-based-conversational-interface-4c8bdf018c24DisclosureAll code, experiments, and figures in this article are my own work, developed and tested on Python 3.12. The synthetic 55-node system, all five experiments, the sharing-curve test, and three of the four diagrams (the direct-vs-transitive dependency chain, the candidate-vs-reachable subgraph, and the sharing-level curve) were generated by the linked repository's scripts, run on my own machine, with numbers reproduced independently, including the runtime table above, calculated from timed local runs. The pipeline diagram is a hand-built schematic, not a script output. The featured image was generated with ChatGPT (DALL·E). No production data, real customer prompts, or proprietary systems were used; the entire system is synthetic and illustrative. I have no financial relationship with any tool, library, or company mentioned in this.
Changing One Prompt Can Affect 50 Others — I Built a Prompt Dependency Graph to Find What Needs Retesting
Full Article
Original Source
Read the full article at Towardsdatascience →KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.