HTN core concepts and data flow

This chapter explains the core HTN concepts and how they relate to each other, so you can read Domains, Plans, and debug traces.

Concept map

flowchart TD domain["Domain\na task-decomposition knowledge graph"] compound["Compound Task\nhow to decompose"] method["Method\nconditions + subtask sequence"] primitive["Primitive Task / Operator\nconditions + effects + body"] cond["Condition\nboolean check on WorldState"] effect["Effect\nmodification of WorldState"] ws["WorldState\ntyped key/value blackboard"] plan["Plan\nordered primitive tasks"] domain --> compound domain --> primitive compound --> method method -->|by priority| primitive method --> cond primitive --> cond primitive --> effect cond -->|reads| ws effect -->|writes| ws primitive -->|decomposition emits| plan

WorldState

The agent’s view of the facts—a typed key/value blackboard. Every key has a fixed type (locked at compile time) and a default value. Planning runs on a read-only snapshot of WorldState; operator effects are “simulated” on the snapshot, and only committed to the real state by phase during execution.

  • The value type HtnValue is a closed eight-type variant (Bool / Int / Float / Vector3 / EntityId / Tag / Uuid / String); see WorldState and scripting.
  • Keys must be declared in the Domain (WorldState Key nodes); at runtime you read/write by key name (AZ::Crc32).
  • Every successful write bumps an internal revision—one of the triggers for opportunistic replan.

Task

Tasks are the planning unit, in two kinds:

  • Compound: not directly runnable; holds an ordered list of Methods describing “how to decompose”.
  • Primitive (operator): directly runnable; carries preconditions, an effect list, and an operator id whose runtime body is an IHtnOperator.

A third kind, ImportedCompound, is a placeholder reference to a compound task in another Domain (cross-domain reuse; see Domain Import in Authoring Domains). Tasks take at most 8 parameters, bound from the parent task’s argument list during decomposition.

Method

One way to decompose a compound task = a precondition set + an ordered subtask list. The planner tries methods in declaration order and takes the first whose conditions hold; subtask slot order is execution order. This is where “priority” comes from—write the preferred tactic first.

Condition and Effect

Conditions come in two atom kinds:

  • Compare: compares a WorldState key against an operand (literal / another key / a parameter). Operators: Equal / NotEqual / Less / LessEqual / Greater / GreaterEqual / IsSet / IsNotSet.
  • Predicate: calls a registered C++ predicate (IHtnPredicate) for checks that a simple comparison cannot express.

A condition set holds at most 64 atoms; an empty set is always true. Two common misconceptions:

  • IsSet / IsNotSet test whether the current value differs from the schema default, not whether the key “has ever been written”.
  • Float equality is exact (no epsilon)—that is the determinism contract. For continuous quantities prefer ordering comparisons over Equal.

Effects declare a modification to a key. Operators: Set / Add / Subtract / ClearToDefault. Each effect has one of three phases—a key HTN design point:

PhaseMeaning
PlanOnlyApplies only to the simulated state during planning, so later tasks “see” the expected result; never writes the real state
ApplyOnStartWritten to the real state when the operator starts
ApplyOnSuccessWritten to the real state when the operator completes successfully

During planning all effects apply to the simulated snapshot, letting decomposition reason about “what the world looks like after this step”—this is why plans can look ahead. During execution the phase decides when effects land on the real WorldState.

Plan and MTR

The planner emits a Plan: an ordered list of primitive task instances with bound arguments, plus the MTR (Method Traversal Record)—the sequence of method indices chosen at each decomposition level.

Lexicographic MTR comparison = plan priority comparison: lower indices (earlier declarations) win. During replan, the MTR decides whether a new plan is strictly better than the current one:

  • Better → preempt the current plan (opportunistic replan);
  • Not better → discard the new plan and keep executing (plan inertia, which prevents thrash; the mtrDiscard counter in htn_dumpStats counts these discards).

Planning and execution data flow

flowchart LR subgraph plan [Planning FindPlan] snap["Copy WorldState snapshot"] --> dfs["Deterministic DFS decomposition\ncompounds try methods, primitives join the plan"] dfs -->|condition fails| bt["Backtrack\ntry next method"] bt --> dfs dfs -->|stack empty| done["Emit Plan + MTR"] end subgraph exec [Execution HtnPlanRunner] step["Take next step"] --> recheck["Recheck conditions"] recheck --> onstart["Apply ApplyOnStart effects"] onstart --> begin["Operator Begin / Tick"] begin -->|Succeeded| onsucc["Apply ApplyOnSuccess effects"] onsucc --> step begin -->|Failed| replan["Trigger replan"] end done --> step
  • Determinism: the same Domain + the same WorldState takes the same code path and produces a byte-identical Plan. One-shot solving and time-sliced incremental planning give identical results.
  • Time slicing: all agents share a global per-frame decomposition step budget (default 2048 steps/frame); large-Domain planning spans multiple frames. See runtime configuration in Reference.
  • Replan triggers: operator failure, failed condition recheck, plan completion (continuous mode), WorldState change (opportunistic), explicit request, Domain hot reload.
  • Failure handling: plan failures (NoPlan / StepLimitExceeded / DomainNotReady) put the agent into exponential backoff until WorldState changes or the backoff expires.

The execution state machine and trigger details are in HTN Agent component.


Copyright © 2026 DawnEngine. All rights reserved.

DawnEngine is a commercial 3D engine distributed under the DawnEngine end-user license agreement. Engine binaries and source are proprietary and are not covered by the licenses below.

Documentation only: the prose and templates on this site are a derivative work of Open 3D Engine (O3DE) documentation by the O3DE Contributors, used under CC BY 4.0 (documentation content), Apache 2.0 (site code), and the MIT license (inline code samples).

The open-source 3D engine that DawnEngine is built on top of is Open 3D Engine . DawnEngine is not affiliated with, endorsed by, or sponsored by The Linux Foundation or the O3DE project. “O3DE” and “Open 3D Engine” are trademarks of The Linux Foundation.