| name | pddlpy |
| description | Parse PDDL domain/problem files and solve them with pddlpy, via the pddlpy CLI (JSON output) or the Python API. Use when working with PDDL planning files — parsing a domain/problem, inspecting the initial state/goals/operators, grounding actions, evaluating numeric fluents or action costs, or running a reference planner (BFS/A*/GBFS/UCS) to produce a plan. |
pddlpy
pddlpy parses PDDL into an object model and solves STRIPS-family problems
with built-in reference planners. It supports STRIPS, typing, numeric fluents,
and action costs; durative actions are parsed but not solved.
Setup
pip install pddlpy
pip install "pddlpy[mcp]"
The CLI/MCP/validate surface needs pddlpy >= 1.1; uvx pddlpy ... runs the
CLI with zero install. Working inside a checkout of the
pddl-lib repo instead: uv sync,
then prefix commands with uv run.
Import: from pddlpy import DomainProblem and from pddlpy.planning import get.
Quick path: the CLI (no Python needed)
Installing the package also installs a pddlpy command (#85). For one-off
parse/ground/solve questions, prefer it over writing Python — JSON on stdout,
pipeable to jq:
pddlpy parse domain.pddl problem.pddl
pddlpy ground domain.pddl problem.pddl move
pddlpy solve domain.pddl problem.pddl --planner ucs
pddlpy validate domain.pddl problem.pddl
If you write or edit PDDL yourself (e.g. translating a problem from
natural language), run validate before solve and fix what it reports:
it collects syntax errors (a bare parse error-recovers past them), atoms
over undeclared predicates, unknown objects, operators grounding to zero
instances, and malformed durative actions. Exit 0 = clean, 1 = issues
in the JSON issues list.
Exit codes: 0 success, 1 search found no plan / validate found issues,
2 bad input (missing file, unknown operator/planner, unsupported
:requirements). Drop to the Python API below when you need
State/GroundedTask, custom binders, or anything the subcommands don't
cover. (The MCP server pddlpy-mcp exposes the same four operations as
tools over stdio.)
Parse a domain + problem
from pddlpy import DomainProblem
dp = DomainProblem("domain.pddl", "problem.pddl")
dp.initialstate()
dp.goals()
list(dp.operators())
dp.requirements()
dp.functions()
dp.metric()
Ground an operator
for op in dp.ground_operator("move"):
op.variable_list
op.precondition_pos
op.effect_pos / op.effect_neg
op.precondition_num / op.effect_num
Solve
from pddlpy.planning import get
plan = get("astar").solve(dp)
if plan is not None:
print(plan.cost)
print(plan.action_names())
Planner choice: bfs (fewest actions), astar (goal-count heuristic, optimal
for unit costs), gbfs (greedy, fast), ucs (cost-optimal for :action-costs
domains). A planner raises UnsupportedRequirementsError if a domain declares
:requirements it does not support.
Work with states directly
from pddlpy.planning import State, GroundedTask
task = GroundedTask(dp)
s = task.initial
for action, succ in task.successors(s):
...
task.is_goal(s)
s.applicable(op); s.apply(op)
Gotchas
initialstate()/goals() return Atom objects with no value equality.
A grounded operator's pre/effects are plain tuples. Normalize with
from pddlpy.planning import atom_tuple (or compare reprs / use State).
- Type hierarchies (#22) are supported: a parameter typed with a supertype
binds objects of any transitive subtype (
dp.types() / dp.subtypes_of(t)
expose the hierarchy). Only (either ...) union types are unhandled.
- Durative actions (
:durative-actions) parse into DurativeAction
(dp.durative_operators()), but the reference planners are non-temporal and
will not solve them.
- Uppercase keywords (
(:INIT ...)) are fine — keywords are case-insensitive;
identifiers keep their case.
Developing pddl-lib itself (repo checkout only)
make
make coverage
make lint
make typecheck
Full model reference:
docs/object-model.md.