GTRusthop is a Goal-Task-Network (GTN) planning library written in Rust, ported from the Python pip branch of GTPyhop, itself a fork of Dana Nau's GTPyhop main branch.
- Overview
- Let's Begin with the Examples
- Quick Start
- Documentation
- Architecture
- Planning Strategies
- Verbose Output
- Development Workflow
- Comparison with GTPyhop
- Performance Benchmarks
- License
- Acknowledgments
- Citation
- Related Projects
GTRusthop provides Hierarchical Task Network (HTN) planning capabilities with support for both goals and tasks. It features both recursive and iterative planning strategies, comprehensive goal verification, and a flexible domain definition system, thanks to Dana Nau's work.
- Dual Planning Paradigms: Support for both HTN (task-based) and HGN (goal-based) planning
- Pyhop Compatibility: Backward compatibility with original Pyhop planner via
pyhop()function - Multiple Planning Strategies: Choose between recursive and iterative algorithms (see Performance Benchmarks)
- Goal Verification: Automatic verification that methods achieve their intended goals
- Flexible Domain Definition: Easy-to-use API for defining actions, task methods, and goal methods
- Type Safety: Rust's type system ensures memory safety and prevents common planning errors
- Performance: Compiled Rust code provides excellent performance for large planning problems
- Comprehensive Testing: Extensive test suite ensures reliability and correctness
GTRusthop supports two distinct planning approaches:
- Uses: Task methods exclusively (
declare_task_method) - Approach: Decomposes abstract tasks into subtasks and primitive actions
- Examples:
simple_htn_example,blocks_htn_example - Best for: Procedural knowledge, step-by-step task decomposition
- Uses: Multigoal methods (
declare_multigoal_method) - Approach: Decomposes goals into subgoals and actions
- Examples:
simple_hgn_example - Best for: Declarative goals, state-based planning
To run the included examples, first clone the repository and navigate to the project directory:
git clone https://github.com/PCfVW/GTRusthop.git
cd GTRusthopImportant: All commands below must be executed from the GTRusthop project root directory.
Windows (PowerShell/Command Prompt):
# Ensure you're in the GTRusthop directory
GTRusthop> pwd
# Should show: ...\GTRusthop
# Run all examples
GTRusthop> cargo run
# Run with verbose output (PowerShell)
GTRusthop> $env:RUST_LOG = "debug"
GTRusthop> cargo run
# Or in a single line
GTRusthop> cmd /c "set RUST_LOG=debug && cargo run"
# Run tests
GTRusthop> cargo test
# Run specific example
GTRusthop> cargo run --example simple_htnLinux/macOS (bash/zsh):
# Ensure you're in the GTRusthop directory
GTRusthop$ pwd
# Should show: .../GTRusthop
# Run all examples
GTRusthop$ cargo run
# Run with verbose output
GTRusthop$ RUST_LOG=debug cargo run
# Run tests
GTRusthop$ cargo test
# Run specific example
GTRusthop$ cargo run --example simple_htnGTRusthop organizes examples as library modules with test functions. The recommended approach for running specific example modules is to use cargo test commands, which leverage the existing test infrastructure and provide the most straightforward execution method.
Available Example Modules:
simple_htn_example: Basic HTN planning with travel scenarios and Pyhop compatibilityblocks_htn_example: Classic blocks world planning using the Gupta-Nau algorithmbacktracking_htn_example: HTN planning with backtracking capabilitiessimple_hgn_example: Hierarchical Goal Network exampleslogistics_hgn_example: HGN planning for logistics domain problemslazy_lookahead_example: Planning and acting with command failuresregression_tests: Comprehensive regression test suite
Planning Paradigms Demonstrated:
- HTN (Hierarchical Task Network): Uses task methods exclusively (
simple_htn_example,blocks_htn_example,backtracking_htn_example) - HGN (Hierarchical Goal Network): Uses multigoal methods (
simple_hgn_example,logistics_hgn_example) - Mixed: Combines both approaches (
lazy_lookahead_example)
Windows (PowerShell/Command Prompt):
# Run simple HTN examples (travel scenarios) with output
GTRusthop> cargo test simple_htn_example -- --nocapture
# Run blocks world HTN examples with output
GTRusthop> cargo test blocks_htn_example -- --nocapture
# Run simple HGN examples with output
GTRusthop> cargo test simple_hgn_example -- --nocapture
# Run lazy lookahead examples with output
GTRusthop> cargo test lazy_lookahead_example -- --nocapture
# Run regression tests with output
GTRusthop> cargo test regression_tests -- --nocapture
# Run all tests with output (no race conditions due to builder pattern)
GTRusthop> cargo test -- --nocaptureLinux/macOS (bash/zsh):
# Run simple HTN examples (travel scenarios) with output
GTRusthop$ cargo test simple_htn_example -- --nocapture
# Run blocks world HTN examples with output
GTRusthop$ cargo test blocks_htn_example -- --nocapture
# Run simple HGN examples with output
GTRusthop$ cargo test simple_hgn_example -- --nocapture
# Run lazy lookahead examples with output
GTRusthop$ cargo test lazy_lookahead_example -- --nocapture
# Run regression tests with output
GTRusthop$ cargo test regression_tests -- --nocapture
# Run all tests with output (no race conditions due to builder pattern)
GTRusthop$ cargo test -- --nocaptureGTRusthop uses a thread-safe builder pattern architecture that eliminates race conditions. You can run all tests in parallel without any special flags!
The -- --nocapture syntax may look confusing with its double dashes, but both are necessary and serve different purposes in the Cargo command structure:
Syntax Breakdown:
cargo test simple_htn_example -- --nocapture
│ │ │ │ │
│ │ │ │ └── Test runner flag (shows output)
│ │ │ └────── Argument separator
│ │ └──────────────────────── Test filter (run specific test)
│ └───────────────────────────── Cargo subcommand
└─────────────────────────────────── Cargo binaryWhy Both Dashes Are Required:
- First
--: Cargo's argument separator - tells Cargo to stop parsing its own arguments - Second
--nocapture: Test runner flag - tells the test executable to display output instead of capturing it
This is standard Cargo/Rust testing syntax used across the Rust ecosystem.
Why This Method is Recommended:
- Leverages existing infrastructure: Uses the built-in test framework
- No file modifications needed: Doesn't require changing
main.rsor creating custom binaries - Consistent execution: Each example runs in a controlled test environment
- Detailed output available: Use
-- --nocaptureflag to see full example output
Add GTRusthop to your Cargo.toml:
[dependencies]
gtrusthop = "1.2.1"GTRusthop provides two main APIs: the modern Builder Pattern API (recommended) and the legacy Pyhop Compatibility API for backward compatibility.
use gtrusthop::{Domain, State, PlanItem, PlannerBuilder, PlanningStrategy};
use gtrusthop::core::string_value;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a domain
let mut domain = Domain::new("transport_domain");
// Declare an action
domain.declare_action("move", |state: &mut State, args: &[StateValue]| {
if args.len() >= 2 {
if let (Some(obj), Some(dest)) = (args[0].as_str(), args[1].as_str()) {
state.set_var("loc", obj, string_value(dest));
return Some(state.clone());
}
}
None
})?;
// Declare a task method
domain.declare_task_method("transport", |state: &State, args: &[StateValue]| {
if args.len() >= 2 {
if let (Some(obj), Some(dest)) = (args[0].as_str(), args[1].as_str()) {
return Some(vec![
PlanItem::action("move", vec![string_value(obj), string_value(dest)])
]);
}
}
None
})?;
// Create planner with builder pattern
let planner = PlannerBuilder::new()
.with_domain(domain)
.with_strategy(PlanningStrategy::Iterative)
.with_verbose_level(1)?
.build()?;
// Create initial state
let mut state = State::new("initial");
state.set_var("loc", "package", string_value("warehouse"));
// Define task
let todo_list = vec![
PlanItem::task("transport", vec![
string_value("package"),
string_value("customer")
])
];
// Find plan
match planner.find_plan(state, todo_list)? {
Some(plan) => {
println!("Found plan:");
for (i, action) in plan.iter().enumerate() {
println!(" {}: {}", i + 1, action);
}
}
None => println!("No plan found"),
}
Ok(())
}- 🔒 Thread-Safe: No global state, no race conditions
- 🎯 Isolated: Each planner instance is completely independent
- 🔧 Configurable: Fluent API for easy configuration
- ⚡ Performance: No synchronization overhead
- 🧪 Testable: Perfect for parallel testing
GTRusthop supports complex multigoal planning using the builder pattern. Multigoals are registered directly with planner instances, eliminating global state:
use gtrusthop::{PlannerBuilder, Multigoal, State, PlanItem, string_value};
use gtrusthop::examples::blocks_htn_example::create_blocks_htn_domain;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a multigoal
let mut goal = Multigoal::new("my_goal");
goal.set_goal("pos", "a", string_value("table"));
goal.set_goal("pos", "b", string_value("a"));
// Create planner with multigoal
let domain = create_blocks_htn_domain()?;
let planner = PlannerBuilder::new()
.with_domain(domain)
.with_multigoal(goal) // Register multigoal with planner
.with_verbose_level(1)?
.build()?;
// Create initial state
let mut state = State::new("initial");
state.set_var("pos", "a", string_value("b"));
state.set_var("pos", "b", string_value("table"));
state.set_var("clear", "a", true.into());
state.set_var("clear", "b", false.into());
state.set_var("holding", "hand", false.into());
// Plan to achieve the multigoal
let plan = planner.find_plan(state, vec![
PlanItem::task("achieve", vec![string_value("goal_my_goal")])
])?;
match plan {
Some(actions) => {
println!("Found plan with {} actions:", actions.len());
for (i, action) in actions.iter().enumerate() {
println!(" {}: {}", i + 1, action);
}
}
None => println!("No plan found"),
}
Ok(())
}Key Advantages of Builder Pattern Multigoals:
- Instance Isolation: Each planner has its own multigoals
- Thread Safety: No global state, fully concurrent
- No Manual Cleanup: Automatic memory management
- Type Safety: Compile-time multigoal validation
- Zero Thread-Local Storage: Complete elimination of TLS dependencies
- Clean Codebase: All deprecated code removed, no legacy remnants
For users migrating from the original Pyhop planner, GTRusthop provides a pyhop() function that maintains backward compatibility:
use gtrusthop::{pyhop, Domain, State, PlanItem, string_value};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create domain (same as above)
let mut domain = Domain::new("transport_domain");
// Declare actions and task methods (same as above)
domain.declare_action("move", |state, args| { /* ... */ })?;
domain.declare_task_method("transport", |state, args| { /* ... */ })?;
// Create initial state
let mut state = State::new("initial");
state.set_var("loc", "package", string_value("warehouse"));
// Define task
let todo_list = vec![
PlanItem::task("transport", vec![
string_value("package"),
string_value("customer")
])
];
// Use legacy pyhop function (shows deprecation message)
match pyhop(domain, state, todo_list)? {
Some(plan) => println!("Found plan: {:?}", plan),
None => println!("No plan found"),
}
Ok(())
}Migration Path: The pyhop() function displays a deprecation message encouraging users to migrate to the modern PlannerBuilder + find_plan() approach for better control and thread safety.
HTN Planning (Task-Based):
// HTN uses task methods exclusively
domain.declare_task_method("travel", |state, args| {
// Decompose travel task into subtasks
Some(vec![
PlanItem::task("get_taxi", args.to_vec()),
PlanItem::task("ride_taxi", args.to_vec()),
PlanItem::task("pay_taxi", args.to_vec())
])
})?;
// Call with task
let todo_list = vec![PlanItem::task("travel", vec![string_value("alice"), string_value("park")])];HGN Planning (Goal-Based):
// HGN uses multigoal methods
domain.declare_multigoal_method(|state, mgoal| {
// Decompose goals into subgoals and actions
Some(vec![
PlanItem::action("move", vec![string_value("alice"), string_value("park")]),
PlanItem::multigoal(remaining_goals)
])
})?;
// Call with multigoal
let mut goal = Multigoal::new("travel_goal");
goal.set_goal("loc", "alice", string_value("park"));
let todo_list = vec![PlanItem::multigoal(goal)];GTRusthop includes the run_lazy_lookahead algorithm from Ghallab et al. (2016), "Automated Planning and Acting". This algorithm combines planning and acting by executing plans step-by-step and replanning when commands fail.
- Plan: Generate a plan for the current state and goals
- Execute: Try to execute each action in the plan as a command
- Replan: If a command fails, generate a new plan and try again
- Repeat: Continue until goals are achieved or max attempts reached
- Command vs Action Distinction: Actions are for planning, commands (prefixed with
c_) are for execution - Failure Handling: Automatic replanning when commands fail during execution
- Verbose Output: Detailed logging of planning and execution steps
- Thread-Safe: Works with the builder pattern architecture
use gtrusthop::{Domain, State, PlanItem, PlannerBuilder, string_value};
// Create domain with actions and commands
let mut domain = Domain::new("taxi_domain");
// Add action (for planning)
domain.declare_action("call_taxi", |state, args| {
// Planning logic - assume taxi always available
Some(state.clone())
})?;
// Add command (for execution)
domain.declare_command("c_call_taxi", |state, args| {
// Execution logic - might fail in real world
if taxi_available() {
Some(state.clone())
} else {
None // Command failed - will trigger replanning
}
})?;
// Create planner
let planner = PlannerBuilder::new()
.with_domain(domain)
.with_verbose_level(1)?
.build()?;
// Run lazy lookahead
let final_state = planner.run_lazy_lookahead(
initial_state,
todo_list,
max_tries: 10
)?;- Robust Execution: Handles real-world command failures gracefully
- Adaptive Planning: Automatically adjusts plans based on execution results
- Real-World Ready: Bridges the gap between planning and acting
- Debugging Support: Verbose output helps understand execution flow
- API Reference: Complete technical reference with function signatures, error handling, performance considerations, and integration guidelines
- Examples and Tutorials: Progressive learning guide with step-by-step tutorials, common pitfalls, and practice exercises
- Cross-Platform Commands: Platform-specific development setup, build commands, and cross-platform best practices for Windows, Linux, and macOS
- Rust Migration Guide: Comprehensive guide covering installation, architectural differences from Python, and detailed comparison
- Working Examples: Functional code examples demonstrating domain creation, planning strategies, and real-world usage patterns
- Documentation Index: Comprehensive navigation guide with learning paths and detailed cross-references for all documentation
- Changelog: Version history, breaking changes, and migration notes for each release
GTRusthop is organized into several key modules:
core: Core data structures (State, Domain, Multigoal)planning: Planning algorithms and strategiesdomains: Example domain implementationsexamples: Usage examples and test caseserror: Error types and handling
GTRusthop supports two planning strategies:
- Iterative: Uses an explicit stack for planning (default)
- Recursive: Uses the call stack for planning
use gtrusthop::planning::{set_planning_strategy, PlanningStrategy};
// Set iterative strategy (recommended)
set_planning_strategy(PlanningStrategy::Iterative);
// Set recursive strategy
set_planning_strategy(PlanningStrategy::Recursive);Control planning output verbosity:
use gtrusthop::planning::set_verbose_level;
set_verbose_level(0)?; // No output
set_verbose_level(1)?; // Basic output (default)
set_verbose_level(2)?; // Detailed output
set_verbose_level(3)?; // Debug outputNote: All development commands must be executed from the GTRusthop project root directory.
Windows (PowerShell/Command Prompt):
# Clean build artifacts
GTRusthop> cargo clean
# Build in debug mode
GTRusthop> cargo build
# Build in release mode
GTRusthop> cargo build --release
# Run tests
GTRusthop> cargo test
# Run clippy linter
GTRusthop> cargo clippy
# Format code
GTRusthop> cargo fmt
# Generate documentation
GTRusthop> cargo doc --openLinux/macOS (bash/zsh):
# Clean build artifacts
GTRusthop$ cargo clean
# Build in debug mode
GTRusthop$ cargo build
# Build in release mode
GTRusthop$ cargo build --release
# Run tests
GTRusthop$ cargo test
# Run clippy linter
GTRusthop$ cargo clippy
# Format code
GTRusthop$ cargo fmt
# Generate documentation
GTRusthop$ cargo doc --openGTRusthop maintains API compatibility with GTPyhop while providing:
- Better Performance: Compiled Rust code is significantly faster
- Memory Safety: Rust's ownership system prevents memory errors
- Type Safety: Compile-time type checking catches errors early
- Concurrency: Safe parallel planning capabilities
- No Runtime Dependencies: Single binary with no interpreter required
- Pyhop Compatibility: Direct support for original Pyhop syntax via
pyhop()function
From GTPyhop:
# Python GTPyhop
import gtpyhop
plan = gtpyhop.find_plan(state, todo_list)// Rust GTRusthop (modern)
let planner = PlannerBuilder::new().with_domain(domain).build()?;
let plan = planner.find_plan(state, todo_list)?;From Original Pyhop:
# Python Pyhop
import pyhop
plan = pyhop.pyhop(state, todo_list)// Rust GTRusthop (compatibility)
use gtrusthop::pyhop;
let plan = pyhop(domain, state, todo_list)?;See Rust.md for detailed architectural differences and migration guide.
GTRusthop includes comprehensive performance benchmarks comparing iterative vs recursive planning strategies across different problem sizes. Key findings:
- Recursive strategy is 20-50% faster than iterative strategy
- Performance advantage increases with problem complexity
- Recursive strategy scales better for large planning problems
- Memory efficiency is better with recursive approach
| Problem Size | Iterative (µs) | Recursive (µs) | Speedup |
|---|---|---|---|
| 3 blocks | 48.7 | 37.0 | 1.32x |
| 8 blocks | 215.9 | 142.6 | 1.51x |
| 16 blocks | 1441.2 | 757.5 | 1.90x |
Recommendation: Use recursive strategy (default) for best performance.
# Run all performance benchmarks
cargo bench
# Quick test run
cargo bench -- --testFor detailed analysis, methodology, and complete results, see Runtime Benchmark Report.
GTRusthop is licensed under the Apache License, Version 2.0. See LICENSE for details.
Many thanks to Dana Nau for all his work on HTN Planning in general and for Pythonizing HTN planning in particular.
If you use GTRusthop in your research, please cite:
@software{gtrusthop2025,
title = {GTRusthop: Goal-Task-Network Planning in Rust},
author = {\'{E}ric Jacopin},
year = {2025},
month = {August},
url = {https://github.com/PCfVW/GTRusthop},
note = {Rust port of the pip branch of GTPyhop by \'{E}ric Jacopin with the help of Augment Code runnning on Claude Sonnet 4}
}Dana Nau's original Python projects:
Home page for the SHOP, JSHOP, SHOP2, and JSHOP2 planners: Simple Hierarchical Ordered Planners