Aesop User Guide
Introduction
Welcome to Aesop, a planning system designed to help create intelligent, adaptive AI behavior for NPCs. This guide will introduce you to the core concepts of Hierarchical Task Network (HTN) planning and help you reason about how decisions are made by the planner.
What is an HTN?
A Hierarchical Task Network (HTN) is a way of organizing behaviors from high-level tasks down to specific actions.
The Basic Structure
Why Hierarchical?
The power of HTNs comes from their hierarchical nature. Instead of listing out every possible sequence of actions your character might take, you define high-level tasks that break down into more specific tasks, which eventually break down into concrete actions.
For example:
Task: Harvest Crops
Method: Harvest Wheat
Step 1: Get Tool (this is a subtask)
Method: Get Sickle
Step 1: Walk to Tool Shed (action)
Step 2: Pick Up Sickle (action)
Step 2: Walk to Crop (action)
Step 3: Harvest Crop (action)
Step 4: Walk to Barn (action)
Step 5: Deposit Crop (action)
This hierarchy lets you compose complex behaviors from reusable pieces, and it lets the planner make intelligent choices about how to achieve goals based on the current situation.
How Does HTN Planning Work?
The planning process is like solving a puzzle where the planner tries to find a complete sequence of actions that will accomplish the goal.
The Planning Process
A Simple Example
Let's walk through a concrete example:
Initial Situation:
Farmer is at the farmhouse
Farmer has_sickle = false
Wheat field has crops ready to harvest
Carrot field has crops ready to harvest
Barn is ready to receive crops
Root Task Given to Planner: Harvest Crops
Planning Process:
Planner tries "Harvest Wheat" method
Precondition: Wheat field has harvestable crops ✓
Step 1 is "Get Tool" (subtask for sickle)
This is a subtask that decomposes into "Walk to Tool Shed" → "Pick Up Sickle"
Simulates effect: Farmer location changes to tool shed, then has_sickle = true
Step 2 is "Walk to Crop" (action with location = wheat field)
Simulates effect: Farmer location changes to wheat field
Step 3 is "Harvest Crop" (action with crop_type = wheat)
Precondition: has_sickle = true ✓ (obtained in Step 1)
Simulates effect: Wheat is collected, farmer is carrying wheat
Step 4 is "Walk to Barn" (action)
Simulates effect: Farmer location changes to barn
Step 5 is "Deposit Crop" (action)
Simulates effect: Wheat is stored in barn, farmer is no longer carrying crops
Plan is valid! The planner returns:
Walk to Tool Shed
Pick Up Sickle
Walk to Crop (wheat field)
Harvest Crop (wheat)
Walk to Barn
Deposit Crop
What if the Sickle is Broken?
If the sickle were broken or unavailable, the "Get Tool" subtask would fail. The planner would:
Backtrack from "Harvest Wheat"
Try "Harvest Carrots" instead
This method doesn't require a tool (carrots can be pulled by hand)
Plan becomes: Walk to Crop (carrot field) → Harvest Crop (carrots) → Walk to Barn → Deposit Crop
This adaptive behavior is the key power of HTN planning - the same domain can produce different plans based on the current situation.
What Decisions Does the Planner Make?
Understanding what the planner decides helps you design better domains. The planner makes two main types of choices:
Method Selection
For every task (including the root task and all subtasks), the planner chooses which method to use from the available options.
Example: For the task "Harvest Crops", the planner might choose between:
Harvest Wheat
Harvest Carrots
Harvest Tomatoes
The planner tries these in the order they are authored, picking the first one whose conditions are satisfied.
Parameter Value Assignment
Many actions and tasks need specific information to work with - these are called parameters. For example:
Which crop type to harvest
Which location to walk to
Which tool to pick up
For each parameter, the planner needs to assign a value. This value might be:
Fixed - Specified directly in the method (e.g., "always harvest wheat")
Queried - Found by searching the world state (e.g., "find the nearest ripe crop")
Passed along - Inherited from a parameter in an earlier step
Where Do These Options Come From?
All the choices available to the planner - both which methods exist and what values parameters can take - are defined in the domain. The domain is your authored collection of tasks, methods, actions, conditions, and effects as well as the characters, roles, props, and prop types involved in the various behaviors the planner uses.
Think of the domain as the "vocabulary" that the planner uses to construct plans. The planner can only create plans using the building blocks you provide in the domain.
How to Control Agent Behavior
As a designer working with Aesop, you have two main ways to shape how your characters behave:
Author the Domain (Primary Approach)
The domain is your main tool for defining behavior. By carefully crafting tasks, methods, conditions, and effects, you control what the planner can and will do.
Use Conditions to Guide Choices:
If the planner is selecting an option you don't want in certain situations, add a condition to prevent it.
Example: Suppose your farmer sometimes tries to harvest crops that aren't ripe yet, and you want to prevent this:
Add a condition to the "Harvest Crop" action: "Crop ripeness must be 100%"
Now the planner will only choose to harvest when crops are ready
Create Multiple Methods for Flexibility:
Give tasks multiple methods to handle different situations.
Example: For the task "Harvest Crops":
Method 1: "Harvest Wheat" - Condition: Wheat field has ripe crops
Method 2: "Harvest Carrots" - Condition: Carrot field has ripe crops
Method 3: "Harvest Tomatoes" - No conditions (fallback, tomatoes are always available)
Manage the World State (Secondary Approach)
The world state contains all the information about characters, objects, and their relationships. What you include in the world state affects what the planner can choose.
Control Available Options Through Registration:
The planner can only work with what's in the world state.
Example: Suppose your game has 20 different crop fields on a large farm, but only 3 are ready for harvest:
Only register those 3 fields to the world state used for planning
The planner can only assign parameters to these 3 fields
This makes planning faster and more predictable
Example: Suppose certain tools should only be chosen in specific contexts:
Don't register every tool in the game to the world state
Only register tools appropriate for the current harvest task
The planner's choices are naturally constrained to sensible options
Update World State to Reflect Game Changes:
As your game progresses, keep the world state synchronized:
When a farmer picks up a tool, update has_sickle to true in the world state
When crops are harvested, update the field's crop count
When the farmer's stamina drops, update the stamina value
This ensures the planner always works with current information.
Best Practices
Following these guidelines will help you create domains that produce reliable, performant AI behavior.
Keep Plans Short
Why: Shorter plans are more robust and easier to work with.
Less prone to interruption — In games, situations change rapidly. A 3-step plan is less likely to become invalid mid-execution than a 10-step plan
Easier to reason about — When debugging or tuning behavior, short plans are simpler to understand
Faster to replan — When a plan fails and needs to be regenerated, short plans complete more quickly
How to Keep Plans Short:
Design high-level actions that accomplish more in a single step
Use subtasks to break complex behaviors into phases that can be replanned independently
Consider having characters create new plans frequently rather than planning far ahead
Example: Instead of planning individual footsteps, create a "Move to Position" action that handles pathfinding.
Branch Only Where You Need To
Why: Every choice point (method selection or parameter assignment) increases the search space the planner must explore. Unnecessary branching makes planning slower and less predictable.
Be Discerning with Queries:
Queries that search the world state can return many possible values. Use queries when you need dynamic selection, but avoid them when a fixed value works.
Example:
✓ Good: "Find the ripest crop field" when you want to prioritize harvest quality
✗ Overkill: "Find any wheat field" when you just need any wheat — use a fixed value instead
Share Parameters Where Possible:
If multiple steps need the same information, pass the parameter from step to step rather than querying multiple times.
Example:
Task: "Harvest Crops"
Step 1: "Select Field" - Query finds the nearest ripe field, assigns to parameter
field_locationStep 2: "Walk to Field" - Uses parameter
field_locationfrom Step 1Step 3: "Harvest from Field" - Uses parameter
field_locationfrom Step 1
Create Focused Subtasks:
If a subtask has many methods but you only want a few in a specific context, create a new specialized subtask.
Example: Instead of reusing a "Get Tool" subtask that has 15 different tool methods:
Create a "Get Harvesting Tool" subtask with only 3 methods (sickle, scythe, basket)
Use this focused subtask in harvest-related methods
This prevents the planner from considering irrelevant options like "Get Hammer"
Register Only What You Need to the World State
Why: Everything in the world state increases the search space and can lead to unexpected behavior.
Performance Impact:
If 20 crop fields are registered but only 3 are relevant, the planner must consider all 20 when evaluating queries. This slows down planning.
Predictability Impact:
Irrelevant options can lead to surprising choices. If the planner can choose from 10 tools but only 2 make sense for the current task, you might see unintended behavior.
Best Practice:
Before planning, register only the characters and objects relevant to the current goal
After planning completes, you can add or remove objects as needed for the next planning cycle
Express Conditions and Effects at the Right Abstraction Level
Why: The planner works by predicting the future. If your conditions and effects rely on low-level details or rapidly changing values, predictions become unreliable.
Avoid Granular Features:
Low-level details like exact position coordinates, rotation angles, or pixel-perfect distances are hard to predict.
Example:
✗ Poor: Condition checks "Character is at exactly X=105.3, Y=200.7"
✓ Better: Condition checks "Character is in the Courtyard area"
Avoid Highly Dynamic Values:
Values that change rapidly or unpredictably make planning unreliable.
Example:
✗ Poor: Condition checks "Crop ripeness is exactly 87%"
✓ Better: Condition checks "Crop is Ready/Not Ready"
Use High-Level Abstractions:
Design conditions and effects that map to meaningful game-level changes, not implementation details.
Example: For a stealth game:
✗ Poor: "Character is 23.5 units from guard, and guard's view angle is 45 degrees, and line-of-sight is unobstructed..."
✓ Better: "Character is Hidden from guard" (computed by your game systems, exposed as a simple boolean)
Benefits:
Plans remain valid longer because abstract conditions are more stable
Behavior is more understandable because it maps to game concepts
You can change implementation details without breaking planning
Last updated