One reason complex systems are hard to reason about is that catastrophic failures are often not caused by a single broken component. Instead, they emerge from the way seemingly healthy components interact.
A retry mechanism can amplify load instead of helping recovery. An autoscaler can make a correct decision based on stale metrics. Two automated systems can optimize for different local goals and quietly fight each other in production.
In all of these cases, there may be no obviously broken component. No single service is necessarily down, and no engineer made a clearly wrong decision. The loss appears in the gaps between the parts.
Most engineering teams are pretty good at identifying known risks. We conduct architecture and incident reviews, run threat-modeling exercises, write postmortems, and build dashboards and alerts. All of that is useful.
But most of these practices have the same limitation: they usually start from risks we already know how to describe. What about the risks we cannot name yet? What about the expensive surprises hiding inside system behavior and assumptions nobody wrote down?
This is where Systems-Theoretic Process Analysis, or STPA, becomes interesting.
Limitations of Traditional Risk Analysis
The classic reliability workflow usually starts after something has already gone wrong.
An incident happens, then we investigate. We ask what failed, why it failed, what the root cause was, and what mitigation could prevent it from happening again.
That approach works reasonably well when component failure is the main problem.
A disk dies. A dependency returns errors. A service crashes after a bad deploy. A database runs out of connections. In cases like these, you can usually trace the failure, fix the component, improve an alert, add a test, write a runbook, and move on.
But modern distributed systems are not always that clean. The problem is not always inside one component. Sometimes the risk is in the interaction between components.
This is where failure-oriented thinking becomes weak. If the failure has not happened before, what exactly are you supposed to analyze? If there is no known incident, no obvious bug, and no clearly broken component, the usual process has very little to grab onto.
Another limitation of traditional risk analysis is its causal model.
One of the first things we do in incident analysis is reconstruct the chain of events and map it to a timeline. Then we try to identify the source event, pinpoint the root cause, and build a story that sounds roughly like this: “Event A happened, which led to event B, which then caused event C, and eventually resulted in a catastrophic failure.”

This works well enough for simple systems. But in systems built around complex interactions, searching for a single root cause often leads nowhere. In many cases, there is no single root cause at all.
Look at the Control Structure, Not Just the Chain of Events
This is where STPA comes in.
STPA, or Systems-Theoretic Process Analysis, is a risk analysis method that looks at systems as networks of control loops, not just collections of components that can fail. Instead of asking only “what could break and how?”, it asks what control actions could lead to losses if they are missing, wrong, mistimed, or applied for too long.
STPA has a strong track record in safety-critical industries such as aviation, nuclear energy, defense, and other fields where system failures can have severe consequences. More recently, people in the technology sector have started exploring how this method can be applied to software engineering.
A more detailed explanation of the terms used below and the method itself is available in the STPA handbook.
So, how does this apply to real-world distributed systems?
The core idea of STPA is the control loop. And once you start looking for control loops in software systems, you start seeing them everywhere.
For example, a deployment pipeline takes a code change, validates it, deploys it, watches production signals, and decides whether to continue, stop, or roll back. Incident response works in a similar way: an alert fires, an engineer interprets the signal, applies a mitigation, checks the system reaction, and decides what to do next.
Even a human operator looking at dashboards and clicking a rollback button is part of a control loop.
Now, let’s use a deployment pipeline to clarify the terminology.
Controller – a system or person that initiates control actions, such as an autoscaler, CI/CD system, or human operator.
Controlled process – the system being modified, such as microservices, infrastructure, or traffic.
Control action – an action that changes the state of the controlled process, such as deploying, scaling, rolling back, or routing traffic.
Sensors and feedback – information flowing from the controlled process back to the controller, such as metrics, logs, and traces.

Once you start seeing systems this way, you notice control loops everywhere: autoscaling, load balancing, failover, rate limiting, canary analysis, traffic shifting, queue processing, workflow orchestration, AI agents, automated remediation, and even management processes.
It usually takes some time to learn to identify control loops in application code. Here is a schematic example of what an autoscaler control loop might look like:
def determine_autoscaler_action(state):
metric = state.cpu_usage
if metric > 0.75:
return "SCALE_UP"
elif metric < 0.30:
return "SCALE_DOWN"
return "NO_OP"
current_state = fetch_current_state()
action = determine_autoscaler_action(current_state)
result = apply_scaling_action_to_deployment(action)
STPA asks a deceptively simple question: “What control actions could cause a loss if they are missing, wrong, mistimed, or applied for too long?”
Traditional risk analysis usually asks, “What component could fail?” STPA changes the analysis by asking when an otherwise valid control action could become unsafe.
Four Ways a Control Action Can Become Unsafe
For every important control action, STPA examines four ways in which it could become unsafe.
A control action may be unsafe if:
- it is not provided when needed;
- it is provided when it should not be;
- it is provided too early, too late, or in the wrong sequence;
- it is continued for too long or stopped too soon.
Let’s apply this to something familiar: autoscaling.
Most teams would start by asking whether the autoscaler can fail. What if it crashes? What if metrics are unavailable? What if the Kubernetes API is unreachable? What if nodes become unhealthy?
Those are valid questions, but they are not enough. STPA pushes us into more interesting territory.
What if the autoscaler scales down a healthy service too aggressively? What if scale-up happens too late? What if it acts on stale metrics? What if dependent services scale in the wrong order? What if the autoscaler keeps adding replicas while the real bottleneck is a downstream dependency? What if another controller is shifting traffic at the same time based on a different signal?
None of these scenarios requires the autoscaler to be broken.
It can collect metrics, compute a decision, call the API, and change replicas exactly as designed. The loss appears because the control action is unsafe in the current system state.
Here are a few ways unsafe control actions can lead to real failures:
- A scaling decision based on stale CPU metrics -> cascading overprovisioning or underprovisioning
- Two controllers acting independently -> resource thrashing
- Dependent services scaling in the wrong order -> overload in one service in the dependency chain
This is the kind of failure that is easy to miss in design reviews and painful to discover in production.
I have seen this pattern many times in my SRE career: the dashboards show each component as healthy, while the user-facing system is already drifting toward failure.
That is why interaction-driven incidents are so unpleasant. The components are not lying. They are just telling you a very narrow truth.
The Best Time to Use STPA Is Before the Code Exists
One of the most useful features of STPA is that it does not require production data to start.
You can apply it before you have an outage history, mature dashboards, or even a working implementation. A proposed architecture is enough to start asking useful questions.
What losses are unacceptable? What are the main control loops? Who or what makes decisions? What feedback do those decisions depend on? What happens if that feedback is missing, delayed, stale, noisy, or misunderstood?
This is especially useful when designing platforms, automation, and infrastructure services.
In fact, it is better to start asking these questions before writing code. The principle is simple: flaws found in requirements, design, or architecture are much cheaper to fix than flaws discovered after deployment.
Imagine a deployment platform that automatically promotes builds after validation. Let’s apply STPA to it.
Control action: Deploy an artifact to production
|
Unsafe behavior |
Outcome |
|---|---|
|
Control action not provided when needed |
A critical patch is not deployed |
|
Control action provided when unsafe |
Deployment begins while the target service is unhealthy |
|
Control action provided too early, too late, or out of sequence |
The build is promoted before canary validation completes |
|
Control action continued too long or stopped too soon |
The rollout remains stuck midway or continues after a safety threshold is exceeded |
One unsafe control action might be:
The deployment system promotes a build before validation signals are complete.
That immediately turns into a design constraint:
The deployment system must not promote a build until required validation signals have arrived and are still fresh.
deployment_policy:
require_canary_success: true
max_stale_metrics_age: 120s
This is not just a test case. It is an architectural requirement.
You discovered the requirement before writing the platform, onboarding teams, or experiencing the first production incident.
STPA Fits Naturally Into SRE Work
STPA fits surprisingly well with SRE. Much of SRE work already involves designing and improving control mechanisms.
SLOs influence product and engineering decisions. Error budgets control release velocity. Progressive rollouts limit blast radius. Rate limits manage traffic pressure, circuit breakers limit dependency failure propagation, and automated remediation controls recovery behavior. Even incident response procedures act as control mechanisms by shaping human coordination during stress.
The problem is that we often introduce them one by one, with each mechanism solving a local problem. Over time, they start interacting. Every control mechanism also carries assumptions. Some are documented; many are not. STPA helps make those hidden assumptions visible.
It may reveal that your canary system assumes metrics arrive within three minutes. Or that your autoscaler assumes CPU is a reliable proxy for demand. Or that your incident automation assumes the dependency graph is accurate. Or that your rollback procedure assumes the previous version is always safe.
These assumptions are dangerous precisely because they hold most of the time.
Why This Matters More Now
Software systems are becoming more autonomous. The number of control loops inside software systems is growing, and so is the number of possible interaction failures.
This is no longer only about classic infrastructure automation. The same pattern is now appearing in AI-native systems, where agents observe state, choose actions, call tools, and update the environment. That does not make AI systems impossible to reason about. But it does mean we need better ways to analyze unsafe actions, missing feedback, stale context, and poorly bounded automation – exactly the kinds of problems STPA gives us a vocabulary to describe.
It does not magically predict every failure. No method does. But it forces the right conversation earlier: before the migration, before the rollout, and before the automation has enough power to hurt you.
A typical AI agent loop looks like this:

Here are a few ways failures at different stages can break an agentic loop:
- Missing or incomplete observation -> an incorrect (hallucinated) internal model of the system
- Incorrect tool call -> an irreversible action
- Delayed feedback -> cascading failures
- Persistent memory drift -> compounding unsafe actions
How to Start Without Turning It Into a Bureaucracy
The good news is that you do not need special tooling to start. You do not need a formal workshop, a certification, or a giant spreadsheet.
Start with a whiteboard.
Pick one important system or one upcoming design. Draw the controller, the thing being controlled, the actions sent by the controller, and the feedback signals the controller depends on. It takes some time to get this right, and that is expected.

Then walk through five questions:
- What losses are unacceptable?
- What control loops exist in this system?
- What control actions can the system or people take?
- What feedback do those actions depend on?
- How could those actions become unsafe?
Keep the scope small at first. Analyze one deployment pipeline, one failover mechanism, one automated remediation, or one AI agent workflow.
Your first useful output may not be a perfect model; often, it is simply a list of assumptions you did not know you were making.
For example, you may discover that the rollback system assumes the previous version is always deployable. The canary analyzer may treat low traffic as success when it actually means low confidence. The alerting system may assume that the on-call engineer understands the entire dependency chain.
Once you see these assumptions, you can turn them into constraints, tests, dashboards, guardrails, runbooks, or architectural changes.
That is how STPA becomes practical: not as a theoretical safety exercise, but as a way to make hidden system behavior visible.
A lightweight workflow for applying these ideas to a software system can be summarized in six steps:
- Define the system boundary, unacceptable losses, and hazards.
- Map the control structure and its control loops.
- List the important control actions.
- Identify unsafe control actions.
- Develop scenarios that could produce them.
- Turn the findings into constraints and validate them through incident history, tests, or simulations.
Common Pitfalls When Applying STPA
Like any methodology, STPA can be applied incorrectly. Here are a few common mistakes to avoid when analyzing your own system.
One of the most common mistakes is identifying the controller and the controlled process incorrectly. In the autoscaler example, it may be tempting to treat the autoscaler itself as the controlled process. In this control loop, however, the autoscaler is the controller. The deployed service is the controlled process that responds to actions such as “scale up” and “scale down.”
It is also important to remember that STPA focuses on control loops, not data flows. Modern software engineers are used to thinking in terms of requests, events, messages, and data pipelines. That perspective is useful, but STPA requires a different mindset. When drawing the control structure, ask which connections represent control actions and which carry feedback to the controller. A data connection does not need to appear merely because it exists; include it when it plays a meaningful role in the control loop.
Another common mistake is trying to create the most detailed possible model on the first attempt. The resulting diagram quickly becomes difficult to understand and analyze. Start with a simple model containing only a handful of major blocks. Four or five may be enough for the first iteration. Once you have identified unsafe control actions and the scenarios that could lead to them, you can zoom in on the relevant part of the system and model it in more detail.
Like software development itself, STPA is an iterative process.
Conclusion
Most organizations discover unknown unknowns in production. The lesson usually arrives as an outage, gets documented in a postmortem, and eventually becomes a roadmap item. That process works, but it is an expensive way to learn. STPA offers a different path by helping engineers identify unsafe interactions before they turn into production incidents.
It shifts attention from broken components to unsafe control actions. It makes us ask how correct actions can become dangerous in the wrong context.
For modern software systems, this matters a lot. Our systems are no longer passive collections of services. They observe, decide, react, retry, scale, roll back, remediate, and sometimes even reason.
In that world, reliability is not only about making components stronger. It is about understanding the control loops that connect them.
Recovering quickly after a failure is one of the core reliability skills. But the higher-leverage skill is discovering the failure mode before production reveals it for you.