ferroplan
A fast, data-parallel PDDL planner in Rust — a deterministic planning core for the age of AI.
The bet: an LLM should be the author and supervisor of a planner, not its
runtime. The same reason you don't ask a model to add a column of numbers — you have
it emit code that does the arithmetic deterministically, and for free — applies one
level up: don't ask an LLM to be the planner for a whole village of agents. Have it
author a PDDL domain that then plans deterministically, cheaply, and inspectably at
scale, and let it only nudge that domain at runtime. PDDL is the auditable interface
between your intent, the model's authoring, and a fast solver — and ferroplan is
that solver.
Why PDDL, not prompt-spaghetti:
- Cost — a solved domain plans essentially for free; an LLM call per decision per agent does not.
- Determinism — same problem, same plan; you can regression-test it.
- Inspectability — you can read a domain and an axiom; you cannot read a model's weights.
- Scale — a village of agents each replanning is tractable for a fast solver, not as a wall of LLM calls.
▶ Try it live in your browser — pick a built-in example or paste your own PDDL; it plans entirely client-side via WebAssembly, no install. There's also a browser visualizer + block editor.
ferroplan is a from-scratch reimplementation of the FF family of planners with a
data-oriented core (bitset states, structure-of-arrays / CSR operator tables),
enforced hill-climbing (EHC) with a best-first fallback, parallel grounding
and parallel heuristic evaluation, plus an SGPlan-style partition-and-resolve mode,
PDDL3 preference/metric optimization, and PDDL2.1 temporal planning (durative
actions). It ships both a library (with a structured, JSON-serializable API)
and the ff command-line binary — a drop-in for Metric-FF's
ff -o domain -f problem.
On classical and ADL benchmarks it runs within ~1.4× of the heavily-optimized C Metric-FF (EHC reaches goals in dozens of evaluations, not thousands); numeric trails and IPC-5 preference quality is competitive-not-winning — see Benchmarks.
Status: v0.3.0 —
ferroplan+ferroplan-cliare on crates.io. APIs may shift before 1.0.
What's new in 0.3.0 — solver depth, measured on a 75-instance temporal corpus as 65 → 73 solved, zero regressions: goal-relevance pruning is on by default (a 5-step chain that exhausted the search in ~45 s now solves in ~30 ms, with an unmasked complete backstop so completeness is unconditional); an on-failure escalation ladder (a failed search automatically retries at the Full demand tier, then hands off to the goal decomposer — no flags, though it does mean a search that used to fail fast can now take longer before giving up:
FF_NO_ESCALATErestores the old behavior); statically unproducible goals fail in microseconds instead of burning the whole node budget; and a newSessionAPI — ground once, replan many — for embedding the planner in a live loop (~10× per-tick on small contracts). See the CHANGELOG for the full breakdown, including 0.2.2's "forge" visual identity, the browser animator's scrubbable transport bar and temporal timescale (Gantt) view, and the move to Bevy 0.19.
Features
- EHC + best-first — enforced hill-climbing with helpful actions (the FF
speed default), falling back to weighted best-first when it stalls. Selectable
per solve (
--search auto|ehc|best-first|…). - FF heuristic — delete-relaxation relaxed-plan heuristic over a
data-oriented task, deferred evaluation, tunable
g/hweights. - Data parallelism — parallel grounding and parallel batch heuristic
evaluation (
std::thread); the plan found is identical for any thread count. - PDDL coverage — STRIPS, typing, negative/disjunctive preconditions,
numeric fluents (Metric-FF style), ADL (conditional effects,
forall/exists, equality), and derived predicates / axioms (:derived, static/stratified — closed into the initial state via a datalog fixpoint). - PDDL3 preferences — soft goal preferences (incl.
forall-quantified and precondition preferences) compiled away, with anytime branch-and-bound metric optimization. (Exact-optimal on small/medium instances; best-found, flagged, on the largest — see Limitations.) - PDDL2.1 temporal —
:durative-actions withat start/over all/at endconditions & effects, constant or parameter-dependent durations, and required concurrency, via a decision-epoch forward search; output in the IPC temporal plan format (t: (action) [dur]) with a makespan. - SGPlan-style partitioning — an optional partition-and-resolve mode.
- Robust — a published library shouldn't crash: malformed/pathological PDDL (incl. deeply-nested forms) returns a typed error, never a panic.
- Structured output — the library returns typed,
serde-serializable results; the CLI emits classic FF text or JSON.
GUI
ferroplan-bevy is a Bevy app that visualizes a
domain+problem as a typed graph, animates the plan, and edits both problems and
domains in a Blockly-style block editor (cargo run -p ferroplan-bevy).

Install / build
CLI (ff)
# drop-in: classic Metric-FF text output
# structured JSON solution
# pick a mode / search strategy
# temporal (durative actions) — auto-detected; prints the IPC temporal plan
# decompose a too-big temporal goal into ordered, individually-solved contracts
# (the "LLM authors, planner decomposes" bet, made inspectable — text or --json)
# self-contained JSON job: {"domain": "...", "problem": "...", "options": {...}}
Run ff --help for all flags (--search, --weight-g/--weight-h,
--max-evaluated, --satisfice, --threads, …).
Library
use ;
let domain = read_to_string?;
let problem = read_to_string?;
// Syntax-check before solving (no grounding/solving) — fast authoring feedback.
let report = parse;
assert!;
let solution = solve?;
if let Some = solution.plan
# Ok::
The public, serde-serializable surface: solve (plan a domain+problem),
decompose (split a too-big temporal goal into validated contracts),
parse (syntax-check + summarize PDDL without solving),
Session (ground once, replan many — for a live loop that re-solves the same
world every tick), and plan::validate_plan (independently check a plan). See
examples/ for solve, parse, json_api, and
replan (Session vs. re-solving from scratch, with timings).
Configuration
Every solver knob lives on one Options struct (library-first, serde-
serializable). The CLI flags and JSON job options map to the same fields; omitted
JSON fields fall back to the defaults shown.
solve?;
CLI equivalents: --mode, --search, --no-helpful, --weight-g/--weight-h,
--max-evaluated, --satisfice, --threads. Via JSON:
{"domain": "...", "problem": "...", "options": {"search": "best-first"}}.
Workspace layout
| crate | what |
|---|---|
ferroplan |
the library: engine + modes + solve / decompose / Session API |
ferroplan-cli |
the ff binary (clap + JSON) |
ferroplan-mcp |
an MCP server exposing solve / validate / decompose over stdio — so an LLM agent can author PDDL and drive the planner |
ferroplan-bevy |
Bevy app: visualize, inspect & animate a domain+problem (cargo run -p ferroplan-bevy [domain.pddl problem.pddl]) |
Examples
examples/ collects worked domains that exercise the full feature set:
rpg-world— a ~120-action crafting/economy domain (durative actions, numeric resources, renewable capacities, a reachability axiom) with a corpus of validated contracts, a flavor-×-scalesuite/, an adversarialhard/batch, and an industrial-city showcase that runs a whole metal/stone/wood industry as a pipeline of contracts.logistics— transshipment: per-location goods, trucks with capacity, a train line.jobshop— scheduling with machine-exclusion (scales to 100 concurrent jobs).BORDERS.md— a measured map of where one-shot planning solves vs. where a goal must be decomposed into contracts. ThedecomposeAPI /ff --decomposeacts on that border: it splits a too-big temporal goal into ordered, individually-solved contracts and stitches them into one validated plan (e.g.hard/order-8→ 8 named contracts), falling back to a monolithic solve when a goal can't be split.
Benchmarks
Compared against the C Metric-FF and SGPlan6 planners over a subset of the IPC contest suites. Headline (native Metric-FF, EHC default):
| category | ferroplan solved | speed vs Metric-FF |
|---|---|---|
| STRIPS | 40/40 | 0.71× (~1.4× slower) |
| ADL | 23/24 | 0.77× (~1.3× slower) |
| numeric | 36/40 | 0.22× |
Full results + the IPC-5 preference scoreboard: benchmarks/results.md
(and the project site). The oracles
are not bundled (GPL / non-commercial licences) — reproduce per
benchmarks/COMPARING.md.
Profiling & perf tracking: PROFILING.md — a deterministic
metrics harness (benchmarks/perf.py run/compare against a committed baseline,
so improvement/regression is measurable across machines) plus the samply /
flamegraph / criterion-baseline workflow for finding and tracking hotspots.
Limitations
- Numeric trails Metric-FF: EHC's helpful-action lookahead stalls on some numeric domains and falls back to (complete, slower) best-first.
- IPC-5 preferences: compiled away + anytime branch-and-bound. Coverage is on
par with SGPlan6 (≈39/48 on the simple-preferences suite), but on the hardest
instances the metric quality trails SGPlan6's specialised partition-and-penalty
search (best-found, flagged not proven optimal). Closing that gap needs the
full ESPC penalty-coordination loop — specced in
docs/espc-preferences-spec.md, not yet built. - Temporal: durative actions with constant or parameter-dependent durations
and required concurrency are supported, and plans are VAL-validated on real
IPC temporal domains (44/45 produced plans valid — see
benchmarks/temporal-results.md). Coverage is currently search-limited (the decision-epoch search times out on large instances). Duration inequalities ((>= ?duration L)/(<= ?duration U)/andranges) are supported — the search commits to the shortest feasible duration — as are timed initial literals ((at <time> <literal>)in:init). Continuous (#t) effects are not yet supported. - Derived predicates (
:derived): static/stratified axioms are supported (closed into the initial state); dynamic derived predicates (bodies over changing facts) are not yet.
Acknowledgments
This project is built in deep respect for the planners that came before it.
SGPlan (SGPlan5 / SGPlan6), by Chih-Wei Hsu and Benjamin W. Wah at the University of Illinois, has been the standard to beat in this corner of automated planning for the better part of two decades — the IPC-winning system whose constraint-partitioning and extended-saddle-point penalty-coordination ideas still define the state of the art for satisficing planning with preferences and with temporal/resource constraints. I've followed that line of research for many years, and to build something that even comes close to it on a slice of the benchmarks is, genuinely, an honor. Enormous credit to that team for the depth, rigor, and sheer durability of the work — ferroplan is in no small part an attempt to learn from it, in Rust.
Equal thanks to Jörg Hoffmann's FF / Metric-FF, whose relaxed-plan heuristic and enforced hill-climbing are the backbone of this engine; to the IPC organizers and domain authors whose benchmarks make progress measurable; and to Derek Long and Maria Fox's VAL, used here to independently validate the temporal plans.
License
Dual-licensed under either of MIT or Apache-2.0, at your option.