Contingency planning: how to keep the important thing working

What would you do if your most important dependency disappeared for six hours? Contingency planning is the engineering discipline of deciding, before the incident, which functions must survive, how degraded they may become, and what people and systems will do next. You will leave with a compact method for turning vague fears into tested recovery choices.

1. The failure that exposes the real problem

Imagine a payment service whose database region has failed. The team has backups, a second region, and a detailed restore script. Yet after three hours, nothing useful is back: engineers restored dashboards first because they were easiest, the payment provider's credentials are in the unavailable secret store, and nobody knows who can authorize a degraded mode.

That is the characteristic failure of a plan that lists components instead of preserving a function. The question is not “How do we restore every server?” It is:

Predict before reading on: with a fixed recovery team and a fixed 90-minute window, should the plan optimize for the largest number of restored services, or for the most important customer promise?

The second choice is the heart of contingency planning. Disruption creates scarcity: time, staff, information, power, suppliers, and safe operating capacity are all limited. A plan is valuable when it makes the trade-off explicit before the trade-off becomes a fight.

Normal modeall promises met Disruptiondependency unavailable Degraded modeessential function survives Restorerebuild safely The plan is the middle box, not just the last one.
Figure 1. A useful plan creates a controlled bridge between failure and full recovery. “Keep everything identical” is often impossible; “keep the essential promise alive” is a design target.
Takeaway: Contingency planning is pre-deciding how the organization will continue its essential work under constrained, abnormal conditions.

2. Start with the function, not the hazard

A hazard is only interesting because of what it prevents you from doing. A region outage, ransomware event, flood, or key supplier failure can all produce the same operational question: which capability is now missing, and how long can the organization tolerate that?

Use a business impact analysis (BIA) to answer that question. For each function, record the consequence of delay, the latest acceptable restoration time, the tolerable data loss, and the resources that make the function possible. NIST describes BIA as a way to determine criticality, impact, and recovery time objectives; FEMA similarly frames it around the consequences of failing to perform essential functions.

Customer promise“accept a payment” Functionpayment authorization Dependenciesdatabase • credentials • provider Time objectiveRTO ≤ 15 min • MTD = 1 h Data objectiveRPO ≈ 0 for accepted payments Ask “what must be true?” before asking “what should we restore?”
Figure 2. The BIA turns a vague service name into a recoverable contract: a function, its dependencies, and the time/data limits that matter.

RTO

Recovery Time Objective: how long the function may be unavailable before the target is missed.

RPO

Recovery Point Objective: how much recent data loss is acceptable, measured backward in time.

MTD

Maximum Tolerable Downtime: the outer limit after which the consequences become unacceptable.

These are not magical constants. They are negotiated consequences. A 15-minute RTO for payments might be justified by lost revenue and trust; a 24-hour RTO for analytics might be entirely reasonable. The point is to spend resilience budget where delay hurts.

FunctionRTORPOAcceptable degraded mode
Accept payment15 minNear zeroQueue signed requests; disable nonessential fraud enrichment
Issue invoice4 h1 hBatch generation when the ledger is healthy
Show analytics24 h24 hServe yesterday’s cached snapshot
Takeaway: A BIA is the bridge from “this system is important” to measurable recovery promises and explicit degraded behavior.

3. The core mechanism: prioritize under scarcity

Once an incident starts, recovery is an allocation problem. Let each function have an impact rate \(c_i\) while unavailable, a restoration cost \(t_i\), and a deadline \(d_i\). With a recovery budget \(B\), the plan chooses actions that reduce the most consequential loss before deadlines:

\[ \text{choose actions } a_i \quad\text{to minimize}\quad \sum_i c_i\,\text{downtime}_i \qquad\text{subject to}\qquad \sum_i t_i a_i \le B \]

You do not need to solve this as a perfect optimization model. The equation exposes the hidden question: if you can only restore two things, which two preserve the mission? Priority is a policy decision informed by the BIA, not an accidental consequence of which runbook is shortest.

90-minute recovery budget payments · 50 min · essential invoices · 30 min 10 min left What is deliberately postponed? dashboards · 40 min · postponeserve cache or wait A plan can be graceful precisely because it says “not now.”
Figure 3. Recovery capacity is a budget. Essential work gets a reserved path; less important work gets a safe fallback rather than competing for the same first 90 minutes.
Demo: why priority beats build order (prioritized-recovery.py — run: python3 prioritized-recovery.py)
# Run: python3 prioritized-recovery.py — proves scarce recovery capacity must follow service priority.
services = {
    "payments": {"time": 50, "essential": True},
    "invoices": {"time": 30, "essential": False},
    "dashboards": {"time": 40, "essential": False},
}
budget = 90

def recover(order):
    used, restored = 0, []
    for name in order:
        if used + services[name]["time"] <= budget:
            restored.append(name)
            used += services[name]["time"]
    return restored, used

# The order is the policy: build order accidentally restores the wrong promise.
for label, order in [
    ("naive build order", ["dashboards", "invoices", "payments"]),
    ("priority order", ["payments", "invoices", "dashboards"]),
]:
    restored, used = recover(order)
    status = "UP" if "payments" in restored else "DOWN"
    print(f"{label:18} -> {', '.join(restored):20} ({used:2} min), payments: {status}")

$ python3 prioritized-recovery.py
naive build order  -> dashboards, invoices (70 min), payments: DOWN
priority order     -> payments, invoices   (80 min), payments: UP
Takeaway: Recovery sequencing is not a technical afterthought; it is the operational encoding of what the organization values most.

4. Contingency MPC: keep an escape trajectory alive

The previous sections plan recovery at the organizational level. Contingency model predictive control (CMPC) applies the same idea to a dynamical system: while the controller follows a high-performance nominal trajectory, it also preserves a feasible alternate trajectory for a known possible emergency.

Why is that different from ordinary MPC? Ordinary MPC asks, “What is the best legal trajectory under the model I currently believe?” If a hazard appears later—ice, an obstacle, loss of an actuator, or a sudden change in friction—the best nominal trajectory may already have spent the state of the vehicle in a way that makes avoidance impossible. CMPC asks one extra question at every solve: if the contingency happens, can I still escape from where this command takes me?

current state same first command nominal objective safe contingency The controller spends a little performance now to keep the lower branch feasible. normal predictionhazard prediction
Figure 5. CMPC optimizes a nominal path and a contingency path in parallel. They share the command applied now, but the contingency branch is constrained for the emergency rather than for nominal comfort or efficiency.

For a linear time-varying system, let \(x_i^n\), \(u_i^n\) be the nominal prediction and \(x_i^c\), \(u_i^c\) the contingency prediction. The two branches can use different models—for example, normal versus low-friction dynamics:

\[ x^n_{i+1}=A_i^n x^n_i+B_i^n u^n_i, \qquad x^c_{i+1}=A_i^c x^c_i+B_i^c u^c_i \]

Both start at the measured state. The key non-negotiable coupling is that the first command is the same, because the future event has not happened yet:

\[ x^n_0=x^c_0=x_k, \qquad u^n_0=u^c_0 \]

After the possible hazard is revealed, the controller may follow the contingency inputs. Before that moment, however, the nominal plan is judged partly by whether it leaves enough room for the emergency plan to work. A compact formulation is:

\[ \begin{aligned} \min_{u^n,u^c,x^n,x^c}\;&J_n(x^n,u^n)+\lambda J_c(x^c,u^c)\\ \text{s.t. }&x^n_{i+1}=f_n(x^n_i,u^n_i),\quad x^n_i\in\mathcal X_n,\;u^n_i\in\mathcal U_n,\\ &x^c_{i+1}=f_c(x^c_i,u^c_i),\quad x^c_i\in\mathcal X_c,\;u^c_i\in\mathcal U_c,\\ &x^n_0=x^c_0=x_k,\quad u^n_0=u^c_0. \end{aligned} \]

Here \(J_n\) rewards ordinary performance, \(J_c\) can simply reward a feasible escape rather than a comfortable one, and \(\lambda\) controls how strongly the nominal plan protects the contingency. In a safety-critical setting, the contingency constraints—not merely its cost—carry the guarantee.

What exactly is shared, and where does branching happen?

In the canonical two-horizon CMPC formulation, only the current command is forced to match. Both branches start from the same measured state \(x_k\), and the command actually sent to the plant is shared:

\[x^n_0=x^c_0=x_k,\qquad u^n_0=u^c_0=u_k\]

But the next predicted states need not match. If the nominal and contingency models differ, then even the same command can produce \(x^n_1\neq x^c_1\). The future inputs \(u^n_1,u^n_2,\ldots\) and \(u^c_1,u^c_2,\ldots\) are allowed to diverge because the controller will re-solve after applying \(u_k\). In the special case of identical dynamics, the common input also gives a common one-step state; that is a consequence of the model, not an additional CMPC coupling constraint.

same inputsbranch / detection time commands shared before the event is distinguishable nominal inputscontingency inputs time\(k\)\(k+\tau_b\)future If detection takes \(\tau_b\) samples, enforce equality through the common prefix.
Figure 6. The branch point is an information-timing decision. Before the event can be detected, both policies must prescribe the same input; after detection, the policies may diverge.
\[u^n_i=u^c_i\quad\text{for }i=0,\ldots,\tau_b-1;\qquad u^n_i\;\text{and}\;u^c_i\;\text{may differ for }i\ge\tau_b\]

Choose \(\tau_b\) from the earliest time at which the real controller can know which world it is in, including sensing, state estimation, event classification, communication, and actuation delay. In the original CMPC setup, the next MPC solve occurs after the current command, so \(\tau_b=1\) is appropriate when the contingency can be detected before the next command is issued. If detection takes three sample periods, use a three-input common prefix. If the event time is uncertain, use several possible branch points in a scenario tree or impose a robust common prefix.

Rule of thumb: branch when information arrives, not when the optimizer wishes it had arrived. Making the branch earlier is physically impossible; making it later is conservative but may waste performance.

Can the branch time move online?

Yes—but the safest design is usually not to let the optimizer pick an arbitrary branch time. Let a monitor update the information state and trigger a branch when the evidence crosses a condition. The branch time then changes with the scenario, while the controller still respects causality.

minimum safe margin event-triggered branch timelater than nominal fixed guessremaining contingency margin The branch occurs when predicted safe escape margin reaches its trigger—not at a preselected clock tick.
Figure 7. A dynamic branch rule can monitor detectability or remaining safe margin. Here the contingency branch activates when the predicted margin reaches a threshold; a different scenario would trigger at a different time.

Two useful versions are common. A measurement-triggered branch activates when a residual, fault classifier, belief probability, or sensor pattern indicates that the contingency is now likely enough to act. A viability-triggered branch activates when delaying the decision would make the contingency set infeasible—for example, when reachable safe corridors no longer overlap or the remaining braking/avoidance margin falls below a threshold.

\[\tau_b(k)=\min\left\{\tau\ge 1:\;\text{evidence}_k(\tau)\ge\eta\;\text{or}\;\mathcal X_{\mathrm{safe}}^n(k+\tau)\cap\mathcal X_{\mathrm{safe}}^c(k+\tau)=\varnothing\right\}\]

This is a conceptual trigger, not a universal formula. The first term says “branch when the evidence is strong enough”; the second says “branch before waiting destroys the escape.” In implementation, one often computes a safe upper bound on postponement and branches no later than that bound. Recent Branch MPC work uses reachability to estimate the maximum feasible decision-postponement time and to prune or cluster scenario branches while preserving safety-relevant modes.

There is a subtle but important difference between dynamic branch activation and dynamic scenario generation. Activation chooses when an already-known contingency policy becomes actionable. Scenario generation changes which futures are represented as new measurements arrive; online scenario-tree adaptation and adaptive MPC address that problem by updating scenarios or uncertainty sets from current data.

A practical dynamic rule

At each MPC update, maintain two clocks. The first is an information clock: when could the real controller distinguish the scenarios? The second is a safety clock: how long can a common control prefix remain viable for all scenarios?

\[\tau_{\mathrm{branch}}(k)=\min\bigl(\tau_{\mathrm{info}}(k),\;\tau_{\mathrm{safe}}(k)\bigr)\]

Here \(\tau_{\mathrm{info}}\) is the first time a causal detector or belief update justifies different actions. \(\tau_{\mathrm{safe}}\) is the latest time at which at least one common-prefix policy still keeps every relevant scenario inside its safe controllable set. If information arrives first, branch on evidence. If safety becomes tight first, keep the controls common but switch to a more conservative emergency-ready policy; do not pretend that scenario identity is known.

For interacting agents, a simpler heuristic is to delay branching while their predicted futures remain close, then branch once their reachable corridors separate beyond a task-dependent threshold. The threshold should be chosen from the control consequence—whether the same action remains safe—not merely from Euclidean distance. Recent reachability-based Branch MPC work uses corridor overlap to reduce scenario complexity and estimates the maximum feasible postponement time for branching decisions.

MethodWhat changes online?When it is useful
Event-triggered CMPCBranch activation timeFault evidence arrives asynchronously or detection quality varies
Reachability/viability triggerLatest safe postponement timeWaiting is acceptable until geometry or constraints make escape impossible
Adaptive scenario-tree MPCScenario set and branch structureNew data changes which uncertainty modes are plausible
Belief-space or POMDP-style MPCBranch probabilities and information stateThe event is partially observed and sensing actions affect future knowledge
Design warning: if the branch time is a decision variable, constrain it by a causal observation model and certify safety for every allowed delay. Otherwise the optimizer can “branch early” using information the real controller does not yet possess.
Important nuance: if the contingency is already confirmed at time \\(k\\), there is no need to keep optimizing a nominal branch—the controller can activate the contingency policy. CMPC is useful while the hazard is plausible but not yet certain.
Connection to contingency planning: the nominal trajectory is “normal operation,” the contingency trajectory is “degraded mode,” and the shared prefix is the activation boundary. CMPC turns a prose fallback into a continuously maintained, dynamically feasible escape plan.

CMPC versus robust and scenario-tree MPC

ApproachWhat it protects againstTypical trade-off
Robust MPCAll disturbances in a prescribed uncertainty setStrong guarantees, often conservative
Scenario-tree MPCSeveral possible uncertainty evolutions, with nonanticipative branch policiesMore recourse, but tree size grows rapidly
Contingency MPCA selected, identified emergency with an explicit escape trajectoryLess blanket robustness; more interpretable and targeted preparation

CMPC is therefore selectively robust: it does not promise safety for every imaginable failure. It spends computation and performance margin on the failure that the designer has identified as consequential. The original CMPC formulation introduced an additional horizon in parallel with classical receding-horizon MPC and coupled the horizons through the first command; related multi-scenario MPC instead represents possible uncertainty evolutions in a scenario tree with nonanticipative inputs.

Takeaway: Contingency MPC makes a fallback trajectory part of every optimization, so the controller avoids states from which the known emergency would be impossible to handle.

5. Turn objectives into an executable plan

A document becomes a plan only when a tired person can use it at 3 a.m. Each recovery action should have an owner, a trigger, prerequisites, a safe stop condition, and a verification step. “Restore the database” is a goal. “On-call lead declares database-region loss; storage owner promotes replica B; verify checksum and write fence; announce read-only mode” is executable.

1. DetectWhat evidence says normal operation is no longer safe?
2. DeclareWho has authority to activate all or part of the plan?
3. StabilizeStop compounding damage; preserve evidence and data integrity.
4. ContinueMove essential functions into the documented degraded mode.
5. ReconstituteReturn to normal only after validation and a deliberate handoff.

Separate three kinds of work. Incident response contains and investigates the event. Continuity keeps essential organizational functions operating. Disaster recovery restores technology and data. They interact, but one runbook should not pretend to replace the others.

Plan elementQuestion it must answerConcrete artifact
Trigger and authorityWhen do we stop normal procedures, and who can say so?Activation matrix with named roles and alternates
DependenciesWhat people, systems, data, facilities, and suppliers are required?Function-to-dependency map
StrategyHow does the function continue if the primary path is gone?Failover, manual, queue, cache, or alternate-supplier design
CommunicationWho needs which fact, through which channel, and how often?Internal/customer message templates and contact tree
VerificationHow do we know the workaround is safe and complete?Checks, acceptance tests, and exit criteria
Engineering test: hand the plan to someone who did not write it. If they must ask “where is the credential?”, “which replica?”, or “who decides?”, the missing answer is a dependency, not a documentation nit.
Takeaway: Make the plan executable by attaching every important action to authority, prerequisites, verification, and a safe exit.

6. Choose controls by failure mode

There is no universal “best” resilience control. Redundancy is useful when one component can fail independently; it is disappointing when both copies share the same region, identity system, power, vendor, or deployment pipeline. A manual fallback is powerful when the workload can be bounded and reconciled; it is dangerous when it creates untracked side effects.

Failure modePossible strategyDesign question
Primary service unavailableFailover, queue, cache, read-only modeWhat can remain truthful while writes are delayed?
Data corruption or ransomwareImmutable/offline backups, point-in-time restoreCan recovery avoid reintroducing the bad state?
People or facility unavailableCross-training, remote work, alternate siteCan another person perform the function safely?
Supplier outageAlternate supplier, inventory buffer, manual processHow quickly can the substitute be activated?
Loss of trust or informationPrewritten messages, status page, communication treeWhat do stakeholders need to decide or tolerate next?

Think in terms of independence. Two replicas in one blast radius are one dependency with extra steps. The right question is not “do we have two?” but “what common cause can still take both away?”

Replica A Replica B Shared identity / pipelinecommon-mode failure Resilience is about independent failure domains, not copy count.
Figure 4. Apparent redundancy can hide a shared dependency. A contingency review searches for common-mode failures across technology, people, suppliers, and authority.
Takeaway: Select controls against concrete failure modes and test their independence; “redundant” is a claim about causes, not a count of replicas.

7. Test the plan as a hypothesis

A plan contains assumptions: the backup is restorable, the alternate supplier answers, the credential works, the on-call person has authority, and the degraded mode is tolerable. An exercise is an experiment that tries to falsify those assumptions while failure is cheap.

1

Walkthrough: read the scenario and narrate actions. This finds missing owners, contacts, and decisions.

2

Functional test: exercise a subsystem or failover path with controlled scope. This finds integration and verification errors.

3

Operational exercise: involve the real people, communications, and constraints. This finds coordination and load problems.

Measure outcomes, not attendance: time to declare, time to preserve the essential function, data loss, unsafe actions avoided, and unresolved decisions. Feed every finding back into the BIA, architecture, or runbook. A plan that is never revised is a historical document.

Productive failure: during a rehearsal, do not rescue the team immediately. Let the missing dependency or ambiguous authority become visible; that is the cheapest time to learn it exists.
Takeaway: Exercises turn contingency planning from confidence theater into an evidence loop: assume, test, observe, fix, repeat.

8. A compact method you can use tomorrow

  1. Name the essential functions. Write customer or mission promises, not component names.
  2. Set RTO, RPO, and MTD. Tie each number to a consequence someone can explain.
  3. Map dependencies and common causes. Include people, data, facilities, suppliers, credentials, and authority.
  4. Design a degraded mode. Decide what stops, what queues, what becomes read-only, and what remains safe.
  5. Write the activation path. Trigger, owner, alternate, first five actions, communication channel.
  6. Define recovery and exit checks. State how to validate correctness before reopening the full system.
  7. Exercise the weakest assumption. Record time, data loss, confusion, and follow-up owners.
\[ \text{resilience} \;\approx\; \text{important function preserved} \;\times\; \text{time under control} \]

The formula is intentionally informal. A service that eventually recovers but loses an irreversible promise may be less resilient than one that operates in a constrained, honest mode. Contingency planning is the design of that honest mode.

Final takeaway: Plan from essential promises, quantify tolerable delay and data loss, design graceful degradation, and rehearse the whole chain until the organization—not just the infrastructure—can recover.

Recommended resources