The migration plan that fails between two sensible steps
An engineering assistant receives a familiar request: move a service to a new schema without downtime. It produces five crisp instructions: take a backup, alter the schema, deploy the new application, test it, then switch traffic. Every verb belongs in a migration. The order appears cautious. A reviewer can read it twice and still miss the defect.
The schema change removes a column that the old application still reads. Old instances continue serving traffic until the final switch. The second instruction therefore destroys a condition required throughout the middle of the sequence. The plan is grammatical, conventional and impossible under its own promised constraint.
The missing object is not another sentence. It is an explicit account of what must be true before each action, what becomes true or false afterwards, and which later action depends on that state. Once those commitments are visible, the unsafe alteration is no longer a vague concern. It is a delete effect threatening a causal link.
This is the narrower question that follows governed context acquisition. That article asks what a system may observe before acting. This one asks what must be causally true for several proposed actions to compose. The consequence is practical: a consequential action list should be treated as a model proposal until a planner or validator can establish its dependencies.
Part IA plan is a state-transition claim
Consider a parcel, a robot and two rooms. The initial state says that the parcel and robot are in the store and the gripper is empty. The goal says that the parcel is in dispatch. Three actions seem sufficient: pick up the parcel, move to dispatch, then put it down.
Now exchange the first two actions. “Move, pick, place” remains a perfectly good English instruction. It is not a plan for this state. After moving, the robot is no longer co-located with the parcel, so the precondition of pick is false. Language describes the intended activity; a planning model determines whether the activity is applicable.
| Action | Preconditions | Add effects | Delete effects |
|---|---|---|---|
pick(parcel) | robot and parcel at store; gripper empty | holding parcel | parcel at store; gripper empty |
move(store, dispatch) | robot at store | robot at dispatch | robot at store |
place(parcel) | holding parcel; robot at dispatch | parcel at dispatch; gripper empty | holding parcel |
This compact representation descends from the STRIPS tradition: actions are operators with conditions for applicability and explicit additions and deletions to a symbolic state. The representation is deliberately austere. It assumes a world that can be described by relevant propositions, actions whose important effects are known, and enough stability to reason from one state to the next.1
Here S is the current set of state literals; a is an action; Pre, Add and Del are its preconditions and effects; γ is the transition function; π is the proposed plan, and G is the goal. Adding a precondition narrows when an action may run. Adding a delete effect can invalidate a later action even when the current one succeeds.
A plan is therefore a proof obligation over a model: each step must be applicable in the state produced by its predecessors, and the final state must satisfy the goal. The proof need not be written in theorem-prover syntax. A simulator, model checker or domain-specific validator can discharge it. What matters is that the conditions are externally inspectable rather than left inside a generator’s transient reasoning.
Validity, quality and robustness are different claims
Reaching the formal goal establishes validity, not that the plan is economical, resilient or wise. Two parcel plans may both work while one travels twice as far. Two migration plans may both preserve compatibility while one requires an hour of dual writing and the other requires a week. Cost functions, deadlines and resource constraints rank valid plans; they do not replace the applicability test.
Robustness asks a further question: how much perturbation can occur before the plan loses validity? A plan that drains every old instance before checking new capacity may be valid under exact forecasts and fragile under a modest traffic surge. A slightly longer plan can preserve a fallback path, require two consecutive healthy readbacks and contract the schema only after an observation window. Those additions do not make its logical goal more satisfied. They increase the margin between the assumed world and plausible worlds.
This yields four separate release claims. The plan may be valid in its transition model, preferred under an objective, robust across selected disturbances and executable under current authority and world state. Collapsing these claims is a common source of false assurance. A shortest plan can be brittle; a robust plan can be unauthorised; an authorised plan can be stale before its first step.
{robot_at_store,
parcel_at_store,
gripper_empty}
{robot_at_store,
holding_parcel}
{robot_at_store,
parcel_at_store,
gripper_empty}
then pick
pick lacksrobot_at_storeThought experiment: change one delete effect
Return to the migration. Hold the initial state, goal and action order fixed. In world A, expand_schema only adds new nullable fields. In world B, an action with the same friendly label also deletes old_app_compatible. The sequence succeeds in A and violates zero downtime in B. Nothing about the prose order changed. Only one causal feature changed.
The experiment isolates the source of validity. It does not reside in an action name, a best-practice template or the confidence of the author. It resides in the transition semantics relative to the current state. This is why natural-language plans often feel more complete than they are: nouns and verbs are visible, but negative effects, resource consumption, temporal windows and mutually exclusive conditions remain implicit.
A symbolic state is a selected representation of reality, not reality itself. A correct proof over omitted conditions can still authorise the wrong action. Planning makes assumptions inspectable; it does not make them true.
That qualification is central. Early STRIPS formulations obtained tractability by restricting what actions and worlds could express. Later planning languages added typing, numeric fluents, time, uncertainty and richer constraints, but every extension preserves the same discipline: state what an action requires and changes. The gain is not omniscience. It is the ability to locate disagreement in the model rather than in the eloquence of a proposed sequence.
Part IIDependencies before sequence
A valid plan can be written as one total sequence, but the sequence often contains more commitment than the problem requires. In the migration, taking a snapshot and provisioning a canary both depend on the database being online. Neither necessarily depends on the other. Forcing an arbitrary order hides that independence, reduces scheduling freedom and makes later repair harder.
Partial-order planning starts with a different object: actions plus only the precedence constraints needed to protect the goal. A causal link records that one step establishes a condition consumed by another. In the notation A ─p→ B, action A produces proposition p, and action B requires it. Any action that could delete p between A and B is a threat. A planner resolves the threat by ordering it outside the protected interval, separating its variable bindings or changing the plan.2
Causal links convert “this should happen before that” into “this effect must remain available for that consumer”. The distinction matters because chronology alone does not explain why an order is necessary. Causal support does, and it exposes exactly which action would break the dependency.
A partial-order planner usually begins with open conditions rather than a finished timetable. For each unsatisfied precondition, it chooses an existing or new action that can establish the condition, adds a causal link, and introduces only the ordering constraints needed to make that support possible. It then scans for threats. This alternation between support and protection is what turns a loose task set into a coherent network.
Threat resolution also reveals the cost of a repair. Promotion orders the threatening action after the consumer; demotion orders it before the producer; variable separation prevents two symbolic objects from unifying; another method may remove the threatening action entirely. Each choice propagates constraints through the network. A planner can therefore explain not only that an action moved, but which protected condition forced the move. That explanation is far more useful during review than a regenerated sequence with a different order.
Partial order is sometimes described as “planning in parallel”, but that is too loose. A partial plan identifies legal linearisations. Whether actions may actually run concurrently also depends on shared resources, temporal overlap, locks, capacity and interference. Two tasks with no logical dependency can still contend for the same database connection pool. Conversely, two actions can overlap safely even when one establishes a condition partway through the other, provided the temporal model represents that interval.
Thought experiment: add one cross-branch threat
Imagine two preparation branches. The first snapshots the database and verifies restoration. The second creates the canary environment and runs a smoke test. In world A, the branches touch separate resources. The planner can delay their relative order, allowing either to finish first. In world B, canary creation acquires an exclusive schema lock that invalidates a consistent snapshot. Only that one interference changes.
A rigid template either serialises both worlds, losing time in A, or permits both, risking corruption in B. A dependency model expresses the difference directly: world B gains a threat and therefore an ordering constraint. The value of partial order is not maximal concurrency; it is minimal justified commitment.
The negative control: when a checklist is enough
Suppose a machine has exactly one legal action in every reachable state: open guard, press start, wait for a green lamp, remove the part. No step can overlap, no alternative method exists, no exogenous actor changes state and every transition is verified locally. A linear checklist with precondition checks represents the same solution as a partial-order planner. Causal-link machinery adds explanation but no additional choice or protection.
This negative control prevents planning formalism from becoming ceremony. Use richer plan structure only when alternatives, interactions, hidden prerequisites, recoverable branches or scheduling freedom matter. The central claim is not that every workflow needs an academic planner. It is that every consequential multi-step proposal needs an explicit way to test applicability and preserve dependencies. A state machine, workflow engine or typed runbook may satisfy that burden in a constrained domain.
There is also a repair advantage. If canary provisioning fails, a total sequence invites regeneration of the whole list. A partial plan localises the damage: identify descendants of the failed condition, preserve independent completed work, and search for a replacement producer. This is a more faithful model of operational recovery than asking a generator to “revise the plan” without showing which commitments remain valid.
Part IIIHierarchy without false certainty
State-space planning asks which primitive action to apply next. In a large domain, that can produce an enormous branching factor. Human operators rarely begin at that level. They say “prepare the release”, “migrate data”, “cut over”, and “retire the old path”. Each phrase names a compound task whose accepted methods narrow the primitive choices.
A hierarchical task network, or HTN, formalises this move. A method states how a compound task may decompose into subtasks, together with ordering constraints and applicability conditions. Decomposition continues until the network contains executable primitive actions. The method library can encode domain practice that is awkward to infer from a goal alone: approved ways to rotate credentials, stage a release or investigate an exception.3
Hierarchy guides search by restricting how a goal may be achieved; it does not prove that the chosen decomposition is executable. Primitive actions still need satisfiable preconditions. Cross-branch interactions still need threat checks. An HTN method can be familiar, approved and wrong for the current state.
Applicable when temporary compatibility and dual-write capacity are available.
Rejected because the task includes a zero-downtime constraint.
This expressive power has a price. Classical planning already faces combinatorial search. Unrestricted hierarchical planning can encode problems whose plan existence is undecidable; restrictions such as total ordering, acyclic methods or constrained interactions recover decidable fragments with different complexity bounds.4 The lesson is not that hierarchy is impractical. It is that a method library changes the problem being solved. It may prune vast regions of search, but it can also exclude a valid route that the modeller failed to encode.
That makes hierarchy a form of governed knowledge representation. The method “expand, migrate, contract” carries assumptions about compatibility, temporary duplication, data reconciliation and rollback. Those assumptions need owners, tests and versioning. A method written for a single-region service may fail after data residency rules, asynchronous replication or a new approval boundary changes the environment.
Method applicability should be tested against counterexamples, not inferred from its name. Remove dual-write capacity and the expand-contract method may cease to apply. Add a legal hold and the retirement task may become forbidden even after technical verification. Change only the replication topology and a previously independent backfill branch may acquire a temporal dependency. These interventions turn a runbook into a falsifiable model of operations.
A useful method library therefore stores defeaters as carefully as happy paths. “Use when” conditions select the method; “withdraw when” conditions prevent inherited practice from masquerading as current truth. When neither condition can be established, the planner should expose an unresolved compound task rather than quietly choose the closest familiar decomposition.
Treat each HTN method as an operational hypothesis with an applicability contract. Record which constraints it satisfies, which resources it consumes, which evidence established its use, and which changes should withdraw it from the library.
Hierarchy and partial order solve different problems
Hierarchy answers, “Which decomposition should we consider?” Partial order answers, “Which ordering commitments are actually required within the chosen decomposition?” A plan can use either, both or neither. A totally ordered HTN may be easy to execute but unnecessarily rigid. A flat partial-order plan may preserve flexibility but search too broadly. Combining them lets domain knowledge narrow alternatives while causal links protect the chosen network.
The Planning Domain Definition Language, PDDL, became a common language for classical planning domains and problems, while HDDL extends the style to hierarchical domains.56 Planners such as Fast Downward search formal state spaces; validators such as VAL independently check whether a proposed plan satisfies a domain model.78 These tools matter here less as products than as separation of duties: modelling, search and validation are distinct functions.
That separation also clarifies what a hierarchy cannot do. A method may say that verification follows cutover, but only an action model states what counts as verified. A method may include rollback, but only recovery semantics determine whether rollback remains applicable after a partial external effect. A method may assign a human approval task, but hierarchy alone does not confer identity or authority. Connection and decomposition are not authorisation.
Part IVLanguage models and formal planners
Large language models make the old distinction newly urgent because they are unusually good at producing plausible action sequences. They can recover common procedures, interpret underspecified requests and suggest decompositions from examples. Those strengths address genuine planning bottlenecks, especially model acquisition and heuristic guidance. They do not erase the need to establish applicability.
PlanBench and ACPBench use formal planning tasks to test state tracking, action applicability, goal reasoning and related capabilities. Across their evaluated models and settings, direct language-model planning remained materially weaker than reliable symbolic planning, especially as structure and difficulty increased.910
The strongest interpretation is not “language models cannot plan”. Benchmarks are representations with their own construct limits, and model capabilities change. The more durable result is architectural: an unconstrained generator can emit a sequence without proving that the sequence is executable. Even a higher success rate would not identify which precondition was supported, which action threatened it or whether a new observation invalidated the plan.
Hybrid studies isolate a more productive role. LLM+P uses a language model to translate a natural-language problem into PDDL, delegates search to a classical planner, then translates the solution back.11 A 2025 ACL study found that generating formal planning representations and solving them externally outperformed direct plan generation in its experiments, while performance deteriorated as descriptions became more naturally phrased and less aligned with the formal schema.12 The improvement therefore comes with a new failure surface: mistranslation of the world into symbols.
A 2026 preprint explored another division of labour: language models generated heuristics for totally ordered HTN planning. Across six IPC 2020 domains, the generated heuristics nearly matched the coverage of the strongest compared planner and reduced search effort on 83 per cent of problems solved by both systems. The authors explicitly limit the result to those domains and exclude partial-order, numeric and temporal HTN settings; offline heuristic generation may also be costly.13
The formalisation bottleneck moves, rather than disappears
A solver can validate only the problem it receives. If the formaliser maps “drain the old application” to stopping one instance rather than all serving instances, the resulting plan may be valid in PDDL and unsafe in the service. Errors can enter through entity resolution, omitted negative facts, units, temporal interpretation, closed-world assumptions and the choice of which real condition becomes a predicate.
The hybrid architecture therefore needs tests at both seams. First, test semantic fidelity: can domain experts or independent extractors recover the same objects, initial facts, constraints and goals from the source request? Second, test planning validity inside the formal model. A plan that passes only the second test proves the consistency of a transcription. It does not prove that the transcription captured the operational burden.
Model-generated decompositions or heuristics can improve search without weakening correctness when every proposed method, action and terminal plan remains subject to an independent formal or operational verifier. Generalisation beyond encoded domains remains an empirical question.
Independent verification →
PlanBench, ACPBench
affordance or tool feedback
LLM-generated HTN heuristic + planner
The serious competing explanation
A sufficiently capable model might internally represent state, simulate effects and produce valid plans without an explicit symbolic interface. Humans often plan this way. Requiring PDDL for every restaurant booking, code edit or research task would impose needless modelling cost. Learned world models may also represent continuous and uncertain dynamics more naturally than hand-written propositions.
This objection weakens any claim that symbolic planning is the only route to planning competence. It does not weaken the operational claim of this paper. For consequential action, the system still needs observable evidence that prerequisites held, effects occurred and later dependencies survived. The representation may be symbolic, typed, probabilistic or simulation-based. What cannot remain private is the basis on which effects are released.
Evidence against the preferred architecture would be clear. Direct model-generated plans would need to match verifier-backed systems across renamed and novel domains, longer horizons, resource constraints, adversarially inserted delete effects, mid-execution state changes and incomplete observations. They would also need calibrated abstention and accurate localisation of failure. If that result held repeatedly for a bounded task class, an external planner might be unnecessary there.
The practical boundary is thus representational rather than ideological: use the lightest mechanism that makes dependencies testable at the consequence level. A deterministic workflow may be enough for a stable process. A constraint solver may be needed for scheduling. A probabilistic planner or simulator may be needed under uncertainty. A language model can propose any of these, but proposal is not release.
Part VThe governed plan artefact
The migration can now be rebuilt as an explicit, partially ordered hierarchy. The compound task release_service decomposes into prepare, migrate, cut over and retire. Preparation contains two unordered tasks: take a snapshot and provision a canary. Migration expands the schema compatibly, deploys dual-write code and backfills. Cutover drains the old application before switching traffic. Retirement removes compatibility only after the new path is verified.
This is still a design-time plan. Execution introduces a second burden: the world may no longer match the state used during planning. A snapshot command may be accepted but fail later. A deployment API may time out after committing. Another operator may change traffic weights. The executor must therefore alternate action with observation rather than replay the plan blindly.
A released plan needs both decision validity and effect validity. Decision validity asks whether the action was applicable and authorised from the evidence available. Effect validity asks whether the external system reached the intended postcondition exactly once. The first is established before dispatch; the second requires an effect receipt, readback or independent observation after dispatch.
A production-shaped plan record should therefore retain more than ordered steps. It binds the requested intent to an identity and purpose; identifies the world-state snapshot and context used; records the decomposition and chosen alternatives; attaches authority to each effect; names the evidence supporting every precondition; captures execution and postcondition receipts, and defines release, pause and recovery decisions. Workflow state, enterprise knowledge, episodic history, current world state and operating evidence remain separate stores.
This record is not a demand for one universal database. It is a typed claim structure. A workflow engine may own progress, a configuration service may own world state, an evidence store may retain readbacks, and an identity system may issue scoped capability tokens. Their records become useful only when the plan can reference them without treating connectivity as permission.
Executable artefact: a small htn workflow and validator
The following Python programme operates entirely on synthetic propositions. It recursively expands a hierarchical release task into primitive actions and precedence edges, produces one topological linearisation, and validates preconditions and effects. The positive case reaches the goal. A second legal linearisation swaps the two unordered preparation tasks. The negative case inserts drop_old_column before the old application is drained, and the validator rejects it because old_app_compatible has been deleted.
Actions are deterministic, instantaneous and propositional; one method is selected for each compound task; observations equal the simulated state. Expected output is two passes followed by a failure at
drain_old_app with the missing precondition named.Open the complete runnable Python artefact
Copy the code below into Python 3.9 or later. It uses only the standard library.
from __future__ import annotations
from dataclasses import dataclass
from graphlib import TopologicalSorter
from typing import Dict, FrozenSet, Iterable, List, Mapping, Sequence, Set, Tuple
@dataclass(frozen=True)
class Action:
"""A deterministic planning operator over propositional state."""
name: str
preconditions: FrozenSet[str]
add_effects: FrozenSet[str] = frozenset()
delete_effects: FrozenSet[str] = frozenset()
@dataclass(frozen=True)
class Method:
"""One way to decompose a compound task.
order contains pairs (i, j), meaning subtask i must finish before j starts.
Missing pairs preserve partial-order freedom.
"""
name: str
subtasks: Tuple[str, ...]
order: FrozenSet[Tuple[int, int]] = frozenset()
@dataclass
class Network:
nodes: Dict[str, str]
edges: Set[Tuple[str, str]]
def roots(self) -> Set[str]:
children = {child for _, child in self.edges}
return set(self.nodes) - children
def leaves(self) -> Set[str]:
parents = {parent for parent, _ in self.edges}
return set(self.nodes) - parents
@dataclass(frozen=True)
class ValidationResult:
ok: bool
final_state: FrozenSet[str]
message: str
causal_links: Tuple[Tuple[str, str, str], ...]
ACTIONS: Mapping[str, Action] = {
"take_snapshot": Action(
"take_snapshot",
frozenset({"db_online"}),
frozenset({"snapshot_ready"}),
),
"provision_canary": Action(
"provision_canary",
frozenset({"db_online"}),
frozenset({"canary_ready"}),
),
"expand_schema": Action(
"expand_schema",
frozenset({"db_online", "snapshot_ready"}),
frozenset({"compat_schema"}),
),
"deploy_dual_write": Action(
"deploy_dual_write",
frozenset({"compat_schema", "canary_ready"}),
frozenset({"new_app_deployed"}),
),
"backfill": Action(
"backfill",
frozenset({"new_app_deployed"}),
frozenset({"backfill_complete"}),
),
"drain_old_app": Action(
"drain_old_app",
frozenset(
{
"old_app_serving",
"old_app_compatible",
"new_app_deployed",
"backfill_complete",
}
),
frozenset({"old_app_drained"}),
frozenset({"old_app_serving"}),
),
"switch_traffic": Action(
"switch_traffic",
frozenset({"old_app_drained", "new_app_deployed"}),
frozenset({"traffic_on_new"}),
),
"verify": Action(
"verify",
frozenset({"traffic_on_new"}),
frozenset({"verified"}),
),
"contract_schema": Action(
"contract_schema",
frozenset({"verified", "old_app_drained", "compat_schema"}),
frozenset({"old_schema_removed"}),
frozenset({"compat_schema", "old_app_compatible"}),
),
# Deliberately unsafe in the middle of the workflow.
"drop_old_column": Action(
"drop_old_column",
frozenset({"db_online"}),
frozenset({"old_schema_removed"}),
frozenset({"compat_schema", "old_app_compatible"}),
),
}
METHODS: Mapping[str, Method] = {
"release_service": Method(
"release_service",
("prepare", "migrate", "cutover", "retire"),
frozenset({(0, 1), (1, 2), (2, 3)}),
),
# Snapshot and canary provisioning may occur in either order.
"prepare": Method("prepare", ("take_snapshot", "provision_canary")),
"migrate": Method(
"migrate",
("expand_schema", "deploy_dual_write", "backfill"),
frozenset({(0, 1), (1, 2)}),
),
"cutover": Method(
"cutover",
("drain_old_app", "switch_traffic", "verify"),
frozenset({(0, 1), (1, 2)}),
),
"retire": Method("retire", ("contract_schema",)),
}
INITIAL_STATE = frozenset({"db_online", "old_app_serving", "old_app_compatible"})
GOAL = frozenset({"traffic_on_new", "verified", "old_schema_removed"})
def expand(task: str, prefix: str = "0") -> Network:
"""Recursively compile an HTN task into primitive actions and precedence edges."""
if task in ACTIONS:
return Network(nodes={prefix: task}, edges=set())
method = METHODS[task]
children = [expand(name, f"{prefix}.{index}") for index, name in enumerate(method.subtasks)]
nodes: Dict[str, str] = {}
edges: Set[Tuple[str, str]] = set()
for child in children:
nodes.update(child.nodes)
edges.update(child.edges)
for before, after in method.order:
for leaf in children[before].leaves():
for root in children[after].roots():
edges.add((leaf, root))
return Network(nodes=nodes, edges=edges)
def linearise(network: Network) -> List[str]:
"""Return one deterministic topological ordering of the partial plan."""
graph: Dict[str, Set[str]] = {node: set() for node in network.nodes}
for parent, child in network.edges:
graph[child].add(parent)
node_order = list(TopologicalSorter(graph).static_order())
return [network.nodes[node] for node in node_order]
def validate(
plan: Sequence[str],
initial_state: Iterable[str],
goal: Iterable[str],
) -> ValidationResult:
"""Execute symbolic effects and stop at the first inapplicable action."""
state = set(initial_state)
producer: Dict[str, str] = {literal: "INITIAL_STATE" for literal in state}
links: List[Tuple[str, str, str]] = []
for step, action_name in enumerate(plan, start=1):
action = ACTIONS[action_name]
missing = sorted(action.preconditions - state)
if missing:
return ValidationResult(
ok=False,
final_state=frozenset(state),
message=(
f"step {step} ({action_name}) is inapplicable; "
f"missing: {', '.join(missing)}"
),
causal_links=tuple(links),
)
for literal in sorted(action.preconditions):
links.append((producer.get(literal, "UNKNOWN"), literal, action_name))
state.difference_update(action.delete_effects)
for literal in action.delete_effects:
producer.pop(literal, None)
state.update(action.add_effects)
for literal in action.add_effects:
producer[literal] = action_name
missing_goal = sorted(set(goal) - state)
if missing_goal:
return ValidationResult(
ok=False,
final_state=frozenset(state),
message=f"plan ended without goal literals: {', '.join(missing_goal)}",
causal_links=tuple(links),
)
return ValidationResult(
ok=True,
final_state=frozenset(state),
message="all actions were applicable and the goal was reached",
causal_links=tuple(links),
)
def show(label: str, plan: Sequence[str], result: ValidationResult) -> None:
print(f"\n{label}")
print(" " + " -> ".join(plan))
print(" " + ("PASS" if result.ok else "FAIL") + ": " + result.message)
def main() -> None:
network = expand("release_service")
positive = linearise(network)
positive_result = validate(positive, INITIAL_STATE, GOAL)
show("Positive HTN linearisation", positive, positive_result)
# The two preparation actions have no ordering edge, so this is another legal linearisation.
alternative = positive.copy()
alternative[0], alternative[1] = alternative[1], alternative[0]
alternative_result = validate(alternative, INITIAL_STATE, GOAL)
show("Alternative legal linearisation", alternative, alternative_result)
# Insert an action whose delete effect destroys a later precondition.
negative = positive.copy()
negative.insert(negative.index("drain_old_app"), "drop_old_column")
negative_result = validate(negative, INITIAL_STATE, GOAL)
show("Negative case: premature contraction", negative, negative_result)
assert positive_result.ok
assert alternative_result.ok
assert not negative_result.ok
assert "old_app_compatible" in negative_result.message
if __name__ == "__main__":
main()
Expected console output
Positive HTN linearisation
take_snapshot -> provision_canary -> expand_schema -> deploy_dual_write -> backfill -> drain_old_app -> switch_traffic -> verify -> contract_schema
PASS: all actions were applicable and the goal was reached
Alternative legal linearisation
provision_canary -> take_snapshot -> expand_schema -> deploy_dual_write -> backfill -> drain_old_app -> switch_traffic -> verify -> contract_schema
PASS: all actions were applicable and the goal was reached
Negative case: premature contraction
... -> backfill -> drop_old_column -> drain_old_app -> ...
FAIL: step 7 (drain_old_app) is inapplicable; missing: old_app_compatibleThe programme deliberately stops before production concerns such as temporal duration, retries and authority. That omission is informative. It separates the theorem being checked, action applicability over a model, from the additional guarantees an operational executor must supply. A real implementation would turn each primitive action into a typed action contract with pre-effect freshness, an idempotency key, an execution receipt, a postcondition observer and a compensation path.
Consider the switch_traffic action returning a timeout. Retrying immediately is unsafe because the first request may have committed. Advancing is also unsafe because the traffic postcondition is unknown. The dependency graph supplies the correct pause boundary: descendants requiring traffic_on_new remain blocked while independent evidence collection proceeds. A readback may confirm success, establish failure, or reveal a mixed state that activates a compensating method.
This is where plan structure becomes operationally valuable. Recovery is not a fresh story about what to do next. It is a constrained update over completed actions, surviving causal links, invalidated assumptions and newly observed state. The executor can retain the snapshot and backfill evidence, invalidate the cutover branch, and search only the affected subnetwork.
The dependency-validity release test
Before releasing a consequential plan, inspect ten linked records. The test adapts the wider control taxonomy to planning rather than calling every persistent fact “memory”. A plan fails closed when any required record is absent, stale or internally inconsistent.
A compact decision rule
- Use a checklist or state machine when the domain is linear, closed and has one applicable next action.
- Use causal-link or constraint planning when alternatives, threats, shared resources or partial ordering determine validity.
- Add HTN methods when accepted decompositions materially reduce search or encode reviewable operational practice.
- Add probabilistic planning, simulation or contingent branches when outcomes or observations are uncertain.
- Keep a deterministic release kernel whenever actions have permissions, irreversible effects or unknown outcomes.
Evaluate by breaking dependencies, not by admiring plans
A useful planning benchmark should vary the causal feature that matters. Rename actions to reduce memorised template matching. Insert a delete effect that threatens a later need. Withhold one initial-state fact. Change a resource capacity. Perturb the world after planning but before execution. Return an ambiguous timeout. Compare direct sequence generation with formalisation plus search, and score more than final goal attainment.
At minimum, measure action applicability, goal satisfaction, unsupported preconditions, unresolved threats, unnecessary ordering, plan cost, recovery after perturbation and calibration of abstention. For agentic systems, add identity propagation, authority checks, duplicate prevention and postcondition verification. A fluent invalid plan should not receive partial credit merely because its explanation resembles the reference procedure.
If every benchmark instance uses the same nouns, operator order and surface form as training examples, a model can reproduce familiar sequences without tracking state. Renaming, counterfactual effects and execution-time perturbations are necessary controls for the claimed mechanism.
Where planning stops helping
Explicit planning provides leverage only inside its system boundary. It breaks first when the action model is wrong. A migration operator may omit replication lag, a payment action may omit a downstream limit, or a robot action may assume an object is where perception placed it. The planner can then be perfectly sound relative to a fiction.
It also weakens when state is partially observable, effects are stochastic, actions overlap continuously, agents behave adversarially or goals change during execution. These are not reasons to abandon structure. They change the appropriate structure: belief-state planning, contingent plans, model-predictive control, temporal constraints, game models or frequent replanning may replace a fixed deterministic sequence.
Formal plan validity is neither authority to act nor evidence that the world complied. Identity, policy, consequence and recovery remain outside classical plan semantics unless deliberately added. A planner can find a route to a forbidden goal. An authorised command can time out after succeeding. A readback can reveal that the expected effect never materialised. Each case requires a control beyond search.
Observation freshness →
The most important failure trigger is divergence during execution. Before each consequential action, re-read the conditions whose freshness matters. After dispatch, distinguish rejection, confirmed success, confirmed failure and unknown outcome. On confirmed divergence, invalidate affected descendants in the dependency graph and replan from authoritative state. On unknown outcome, observe before retrying. This avoids both blind continuation and duplicate effects.
Invalidation should be selective. A changed fact does not necessarily erase the whole plan. Trace which open conditions and causal links depend on it, mark their descendants unresolved, and preserve steps supported by independent evidence. Full regeneration is justified only when the changed state alters the goal, method applicability or enough of the network that local repair would be misleading. Selective invalidation is the planning equivalent of containing a fault rather than restarting an estate.
There is a human boundary too. Plans can optimise a specified goal while erasing tacit values that were never formalised. A technically valid staffing plan may be degrading, a clinically efficient pathway may ignore consent, and a financially optimal sequence may transfer unacceptable risk. Human judgement is not a missing action operator to be approximated away. It is sometimes the authority that defines whether the goal itself may be pursued.
Compact glossary
- Precondition
- A fact or constraint that must hold before an action is applicable.
- Effect
- A modelled state change caused by an action, including additions and deletions.
- Causal link
- A protected producer-condition-consumer relation inside a plan.
- Threat
- An action that can negate or interfere with a condition protected by a causal link.
- Partial order
- A set of necessary precedence constraints that permits several legal linearisations.
- HTN method
- An applicability-bound way to decompose a compound task into a task network.
- Plan validation
- Independent checking that a proposed plan is applicable and reaches its formal goal.
- Postcondition readback
- Observation used to establish whether an external action produced the intended state.
Source notes
- Primary and retrospective: Richard E. Fikes and Nils J. Nilsson, “STRIPS: A New Approach to the Application of Theorem Proving to Problem Solving”; Nils J. Nilsson, “The STRIPS System: A Retrospective”. Used for operator semantics, explicit add/delete effects and restrictive world assumptions.
- Primary: David McAllester and David Rosenblitt, “Systematic Nonlinear Planning”; Steve Hanks and Daniel Weld, “A Domain-Independent Algorithm for Plan Adaptation”. Used for open conditions, causal links, threats and least commitment.
- Primary: Kutluhan Erol, James Hendler and Dana Nau, “HTN Planning: Complexity and Expressivity”. Used for HTN task decomposition and expressivity.
- Primary: Erol, Hendler and Nau, “HTN Planning: Complexity and Expressivity”. Used for undecidability and the effect of structural restrictions. The article does not transfer those bounds to every practical HTN implementation.
- Official specification history: Drew McDermott and the AIPS planning community, “PDDL: The Planning Domain Definition Language”.
- Primary: Daniel Höller et al., “HDDL: An Extension to PDDL for Expressing Hierarchical Planning Problems”.
- Primary implementation paper: Malte Helmert, “The Fast Downward Planning System”, Journal of Artificial Intelligence Research.
- Official research record: Richard Howey, Derek Long and Maria Fox, “VAL: Automatic Plan Validation, Continuous Effects and Mixed Initiative Planning Using PDDL”.
- Primary benchmark: Karthik Valmeekam et al., “PlanBench: An Extensible Benchmark for Evaluating Large Language Models on Planning and Reasoning about Change”, NeurIPS Datasets and Benchmarks.
- Primary peer-reviewed benchmark: Harsha Kokel et al., “ACPBench: Reasoning about Action, Change, and Planning”, AAAI 2025; the linked arXiv record includes its later revision. Used only for the bounded evaluation claims stated in the article.
- Primary preprint: Bo Liu et al., “LLM+P: Empowering Large Language Models with Optimal Planning Proficiency”.
- Primary peer-reviewed result: Cassie Huang and Li Zhang, “On the Limit of Language Models as Planning Formalizers”, ACL 2025.
- Recent preprint: Felipe Meneguzzi et al., “Hierarchical Task Network Planning with LLM-Generated Heuristics”. The six-domain, total-order boundary and offline cost limitation are retained explicitly.
The first action is a release decision
The enduring insight of planning is easy to lose beneath algorithms and acronyms. Before a system acts, it should be possible to say what the action requires, what it changes, what later step depends on that change, and what could invalidate the dependency. A sequence that cannot answer those questions is a suggestion, however polished it sounds.
Preconditions and effects turn verbs into state transitions. Causal links turn order into support. Partial order preserves options until an interaction requires commitment. Hierarchical methods contribute reusable knowledge about how work can decompose, while leaving primitive validity open to inspection. None of these devices guarantees that the model matches the world, which is why execution must alternate proposals, deterministic checks, scoped authority, effect receipts and readback.
The architecture decision changes from “Which model should write the plan?” to “Which representation and verifier make this plan’s causal commitments inspectable before release?” Language models may interpret goals, propose methods and generate heuristics. Formal planners, workflow engines, simulators and people may each discharge different proof obligations. The chosen stack should be no heavier than the task requires, but no lighter than its consequences allow.
For the migration, that decision moves schema contraction after drain and verification, preserves independent preparation work, and names the precise condition that a premature action would destroy. More broadly, it turns planning from persuasive future tense into an operational artefact that can be rejected, repaired and resumed before the world absorbs an avoidable mistake.