gp

module
v0.2.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 11, 2026 License: MIT

README

gp

CI Go Reference

PDDL planning solver exposed as an MCP server and Go library.

gp implements the Graphplan algorithm (Blum & Furst 1997) for STRIPS and ADL planning domains. It ships as:

  • gp-server -- an MCP server that any AI assistant can call
  • graphplan -- a Go library for embedding planning in your apps
  • pddl -- a PDDL parser (STRIPS + ADL) that feeds into the solver

Background

What is automated planning?

Automated planning finds a sequence of actions that transforms an initial state into a goal state. You describe what you want -- not how to get there.

A planning problem has three parts:

  • Domain -- the types of objects and actions available
  • Problem -- the specific objects, starting state, and goal
  • Plan -- the output: a sequence of grounded actions
PDDL: Planning Domain Definition Language

PDDL is the standard input format for planners. A domain defines predicates (facts about the world) and operators (actions with preconditions and effects):

(define (domain logistics)
  (:requirements :strips :typing)
  (:types city cargo rocket)
  (:predicates (at ?x - cargo ?c - city)
               (in ?x - cargo ?r - rocket)
               (rocket-at ?r - rocket ?c - city)
               (has-fuel ?r - rocket))
  (:action load
    :parameters (?c - cargo ?r - rocket ?loc - city)
    :precondition (and (at ?c ?loc) (rocket-at ?r ?loc))
    :effect (and (not (at ?c ?loc)) (in ?c ?r)))
  ;; ... more actions
)

A problem specifies objects, initial facts, and goals:

(define (problem deliver)
  (:domain logistics)
  (:objects r1 - rocket  a b - cargo
            london paris - city)
  (:init (at a london) (at b london)
         (rocket-at r1 london) (has-fuel r1))
  (:goal (and (at a paris) (at b paris))))
The Graphplan algorithm

Graphplan (Blum & Furst 1997) builds a layered planning graph alternating between proposition levels and action levels. At each level it tracks mutual exclusion (mutex) constraints -- pairs of actions or propositions that cannot coexist.

The algorithm:

  1. Extend the graph one level (add applicable actions, compute new mutexes)
  2. Check if all goals are present and pairwise non-mutex
  3. Search backward through the graph for a valid plan
  4. Repeat until a plan is found or the graph levels off with no solution

Key properties:

  • Finds shortest parallel plans (fewest time steps)
  • Guaranteed complete -- if a plan exists, it will find it
  • Memoization prunes the search space across iterations

Supported PDDL Features

The solver implements a substantial subset of PDDL, covering classical STRIPS planning and many ADL (Action Description Language) extensions.

Requirements

The parser recognizes these :requirements keywords:

Requirement Description
:strips Basic STRIPS planning (conjunctive preconditions, positive/negative effects)
:typing Hierarchical type system for objects and parameters
:equality Equality predicates (= ?x ?y) and (not (= ?x ?y)) in preconditions
:negative-preconditions Negated atoms (not (pred ...)) in preconditions
:disjunctive-preconditions Disjunction (or ...) and implication (imply ...) in preconditions
:existential-preconditions Existential quantification (exists ...) in preconditions
:universal-preconditions Universal quantification (forall ...) in preconditions and effects
:quantified-preconditions Shorthand for :existential-preconditions + :universal-preconditions
:conditional-effects Conditional effects (when ...) in action effects
:derived-predicates Derived predicates (axioms) computed via fixed-point evaluation
:adl Shorthand enabling all ADL features above
Type system
  • Typed declarations -- objects, parameters, and constants use ?var - type or name - type syntax
  • Type hierarchy -- subtypes inherit from parent types (e.g. truck airplane - vehicle); untyped names default to object
  • Type-aware grounding -- action parameters bind only to objects of the declared type or its subtypes
Domain definition

A domain may contain:

  • :types -- type declarations with optional parent types
  • :constants -- domain-scoped typed objects available to all problems
  • :predicates -- named predicates with typed parameter lists
  • :action -- operator schemas (see below)
  • :derived -- derived predicate axioms (see below)
Actions

Action schemas support:

  • :parameters -- typed parameter list (can be empty)
  • :precondition -- a formula describing when the action is applicable
  • :effect -- a formula describing how the world state changes
Formula language

Preconditions and effects share a common formula language. Not all formula types are valid in both contexts:

Formula In preconditions In effects Description
(pred ?x ?y) yes yes Atomic predicate (positive)
(not (pred ?x ?y)) yes yes Negated atom (delete effect or negative precondition)
(and f1 f2 ...) yes yes Conjunction
(or f1 f2 ...) yes -- Disjunction
(imply f1 f2) yes -- Implication (semantically (or (not f1) f2))
(forall (?v - T) body) yes yes Universal quantification
(exists (?v - T) body) yes -- Existential quantification
(when cond eff) -- yes Conditional effect
(= ?x ?y) yes -- Equality constraint (resolved at grounding time)
(not (= ?x ?y)) yes -- Inequality constraint (resolved at grounding time)

Multi-variable quantifiers are supported and are internally nested into single-variable layers:

;; These are equivalent:
(forall (?x - T1 ?y - T2) body)
(forall (?x - T1) (forall (?y - T2) body))
Derived predicates (axioms)

Derived predicates define facts computed from other facts rather than produced by actions. They support:

  • Recursive definitions -- a derived predicate can reference itself, enabling transitive-closure patterns (e.g. reachability)
  • Chained axioms -- one derived predicate can reference another derived predicate
  • Fixed-point evaluation -- axioms are evaluated repeatedly at each proposition level until no new facts are derived
  • Full formula body -- the axiom body can use any precondition-valid formula (and, or, exists, not, etc.)
;; Recursive transitive closure
(:derived (reachable ?x - location ?y - location)
  (or
    (connected ?x ?y)
    (exists (?z - location)
      (and (connected ?x ?z)
           (reachable ?z ?y)))))
Conditional effects

The when clause applies an effect only if a condition holds at effect time. Combined with forall, this enables powerful patterns:

;; Alert every responder present in the zone
(:action sound-alarm
  :parameters (?z - zone)
  :precondition (exists (?i - incident) (incident-at ?i ?z))
  :effect (forall (?r - responder)
    (when (at-zone ?r ?z) (alerted ?r))))
Negative preconditions and closed-world assumption

When an action uses (not (pred ...)) in its precondition, the solver seeds the initial proposition level with negated instances for all ground atoms of that predicate that are not in the initial state. This implements the closed-world assumption: any fact not explicitly stated is assumed false.

Equality

Equality constraints (= ?x ?y) and (not (= ?x ?y)) are resolved at grounding time by filtering variable bindings. They do not appear as runtime propositions -- instead, ground actions are only generated for bindings that satisfy the constraints.

Problem definition

A problem specifies:

  • :domain -- reference to the domain name
  • :objects -- typed object declarations (combined with domain constants for grounding)
  • :init -- ground positive facts defining the initial state
  • :goal -- a formula describing the desired end state (supports the full precondition formula language)
JSON input format

In addition to PDDL text, domains and problems can be specified as JSON. Formulas use a discriminated-union envelope:

{
  "type": "forall",
  "variable": "?r",
  "var_type": "responder",
  "body": {
    "type": "when",
    "condition": {"type": "atom", "predicate": "at-zone", "args": ["?r", "?z"]},
    "effect":    {"type": "atom", "predicate": "alerted", "args": ["?r"]}
  }
}

The type field selects the formula kind: atom, and, or, imply, forall, exists, or when. Each kind uses the relevant fields (predicate/args/negated for atoms, children for and/or, antecedent/consequent for imply, variable/var_type/body for quantifiers, condition/effect for when).

Solver properties
  • Shortest parallel plans -- the Graphplan algorithm finds plans with the fewest time steps; actions within a step are guaranteed non-interfering and can execute in parallel
  • Completeness -- if a plan exists, the solver will find it; if the planning graph levels off with no solution, it reports unsolvable
  • Memoization -- failed goal sets are cached at each level, pruning the backward search across iterations
  • Configurable depth -- an optional MaxLevels cap prevents runaway expansion on hard problems
Unsupported PDDL features

The following PDDL features are not supported:

  • Numeric fluents (:fluents, :numeric-fluents)
  • Temporal planning (:durative-actions, :timed-initial-literals)
  • Action costs and preferences
  • :either type unions
  • Negation on complex formulas (only (not atom) is allowed, not (not (and ...)))
  • Object fluents and functions

Examples

Rocket cargo transport

A rocket must transport two pieces of cargo from London to Paris.

domain.pddl
(define (domain rocket)
  (:requirements :strips :typing :equality)
  (:types rocket place cargo - object)
  (:predicates
    (at ?x - object ?y - place)
    (in ?c - cargo ?r - rocket)
    (has-fuel ?r - rocket))
  (:action move
    :parameters (?r - rocket ?from - place ?to - place)
    :precondition (and
      (not (= ?from ?to))
      (at ?r ?from)
      (has-fuel ?r))
    :effect (and
      (at ?r ?to)
      (not (at ?r ?from))
      (not (has-fuel ?r))))
  (:action load
    :parameters (?r - rocket ?p - place ?c - cargo)
    :precondition (and (at ?r ?p) (at ?c ?p))
    :effect (and
      (in ?c ?r)
      (not (at ?c ?p))))
  (:action unload
    :parameters (?r - rocket ?p - place ?c - cargo)
    :precondition (and (at ?r ?p) (in ?c ?r))
    :effect (and
      (at ?c ?p)
      (not (in ?c ?r)))))
problem.pddl
(define (problem rocket-2cargo)
  (:domain rocket)
  (:objects
    r1 - rocket
    london paris - place
    a b - cargo)
  (:init
    (at r1 london)
    (at a london)
    (at b london)
    (has-fuel r1))
  (:goal (and
    (at a paris)
    (at b paris))))

Plan (3 time steps):

Step 0: load(r1, london, a)  load(r1, london, b)
Step 1: move(r1, london, paris)
Step 2: unload(r1, paris, a)  unload(r1, paris, b)

Both cargo items load in parallel, the rocket moves, and both unload in parallel -- the shortest possible plan.

Blocks world (Sussman anomaly)

The classic AI planning benchmark. Three blocks must be stacked in order, but the initial configuration forces the planner to undo partial progress.

domain.pddl
(define (domain blocks-world)
  (:requirements :strips :typing)
  (:types block - object)
  (:predicates
    (on ?x - block ?y - block)
    (on-table ?x - block)
    (clear ?x - block)
    (holding ?x - block)
    (arm-empty))
  (:action pick-up
    :parameters (?x - block)
    :precondition (and
      (clear ?x)
      (on-table ?x)
      (arm-empty))
    :effect (and
      (holding ?x)
      (not (on-table ?x))
      (not (clear ?x))
      (not (arm-empty))))
  (:action put-down
    :parameters (?x - block)
    :precondition (holding ?x)
    :effect (and
      (on-table ?x)
      (clear ?x)
      (arm-empty)
      (not (holding ?x))))
  (:action stack
    :parameters (?x - block ?y - block)
    :precondition (and
      (holding ?x)
      (clear ?y))
    :effect (and
      (on ?x ?y)
      (clear ?x)
      (arm-empty)
      (not (holding ?x))
      (not (clear ?y))))
  (:action unstack
    :parameters (?x - block ?y - block)
    :precondition (and
      (on ?x ?y)
      (clear ?x)
      (arm-empty))
    :effect (and
      (holding ?x)
      (clear ?y)
      (not (on ?x ?y))
      (not (clear ?x))
      (not (arm-empty)))))
problem.pddl (Sussman anomaly)
(define (problem sussman-anomaly)
  (:domain blocks-world)
  (:objects a b c - block)
  (:init
    (on c a)
    (on-table a)
    (on-table b)
    (clear c)
    (clear b)
    (arm-empty))
  (:goal (and
    (on a b)
    (on b c))))

Initial state: C is on A, A and B are on the table. Goal: A on B, B on C (stack: A-B-C from top to bottom).

This is the Sussman anomaly -- you cannot achieve both subgoals independently without undoing one. The planner must unstack C from A before building the target stack.

Emergency response (ADL features)

An emergency domain that combines several ADL features:

  • Derived predicate with exists -- can-handle is true when a qualified, available responder exists
  • Disjunctive precondition -- dispatch requires the responder to be at the zone or backup to be available
  • Existential precondition -- sound-alarm fires when any incident exists in the zone
  • Universal conditional effect -- sound-alarm alerts every responder present in the zone via forall/when
domain.pddl
(define (domain emergency)
  (:requirements
    :strips :typing :derived-predicates
    :disjunctive-preconditions
    :universal-preconditions :conditional-effects)
  (:types
    responder incident zone - object)
  (:predicates
    (qualified ?r - responder ?i - incident)
    (available ?r - responder)
    (at-zone ?r - responder ?z - zone)
    (incident-at ?i - incident ?z - zone)
    (dispatched ?r - responder ?i - incident)
    (alerted ?r - responder)
    (can-handle ?i - incident)
    (alarm-sounded ?z - zone)
    (backup-available ?z - zone))

  (:derived (can-handle ?i - incident)
    (exists (?r - responder)
      (and (qualified ?r ?i)
           (available ?r))))

  (:action dispatch
    :parameters
      (?r - responder ?i - incident ?z - zone)
    :precondition (and
      (qualified ?r ?i)
      (available ?r)
      (incident-at ?i ?z)
      (or (at-zone ?r ?z) (backup-available ?z)))
    :effect (and
      (dispatched ?r ?i)
      (not (available ?r))))

  (:action sound-alarm
    :parameters (?z - zone)
    :precondition
      (exists (?i - incident) (incident-at ?i ?z))
    :effect (and
      (alarm-sounded ?z)
      (forall (?r - responder)
        (when (at-zone ?r ?z) (alerted ?r)))))

  (:action call-backup
    :parameters (?z - zone)
    :precondition (alarm-sounded ?z)
    :effect (backup-available ?z)))
problem.pddl
(define (problem respond-to-fire)
  (:domain emergency)
  (:objects
    medic1 fire1-resp - responder
    fire1 - incident
    zone-a zone-b - zone)
  (:init
    (qualified medic1 fire1)
    (qualified fire1-resp fire1)
    (available medic1)
    (available fire1-resp)
    (at-zone medic1 zone-a)
    (at-zone fire1-resp zone-b)
    (incident-at fire1 zone-a))
  (:goal (and
    (dispatched medic1 fire1)
    (alerted medic1)
    (alarm-sounded zone-a))))

Plan (1 time step):

Step 0: dispatch(medic1, fire1, zone-a)  sound-alarm(zone-a)

The planner dispatches the medic and sounds the alarm in parallel. sound-alarm uses forall/when to alert every responder in zone-a (medic1), while dispatch uses the or precondition -- medic1 is already at zone-a so backup is not needed.

Installation

Download the latest release for your platform:

Linux / macOS:

curl -sSL https://raw.githubusercontent.com/byte4ever/gp/master/install.sh | sh

Windows (PowerShell):

irm https://raw.githubusercontent.com/byte4ever/gp/master/install.ps1 | iex

Verify:

gp-server --version
Build from source
go install github.com/byte4ever/gp/cmd/gp-server@latest

Requires Go 1.25+.

MCP Configuration

Claude Desktop

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "gp": {
      "command": "gp-server"
    }
  }
}
Claude Code

Add to your project's .mcp.json:

{
  "mcpServers": {
    "gp": {
      "command": "gp-server",
      "type": "stdio"
    }
  }
}

Or run directly:

claude mcp add gp gp-server
Other MCP clients

gp-server speaks MCP over stdio. Point any MCP-compatible client at the gp-server binary with no arguments.

MCP Tools

One-shot solving
Tool Parameters Description
solve_problem format (pddl or json), domain, problem Parse and solve an ADL planning problem in a single call.
Session-based solving

For multi-turn workflows where the AI assistant builds up the domain and problem incrementally:

Tool Parameters Description
create_session name Create a named planning session.
set_domain session, domain Set the PDDL domain on a session.
set_problem session, problem Set the PDDL problem on a session.
solve_session session Solve the problem stored in a session.
list_sessions -- List all active session names.
delete_session session Delete a session by name.
Response format

All solve tools return a list of time steps. Actions within the same step are guaranteed non-interfering and can be executed in parallel. Steps must be executed sequentially:

{
  "solvable": true,
  "steps": [
    {"time": 0, "actions": ["load(r1, london, a)", "load(r1, london, b)"]},
    {"time": 1, "actions": ["move(r1, london, paris)"]},
    {"time": 2, "actions": ["unload(r1, paris, a)", "unload(r1, paris, b)"]}
  ]
}

When no plan exists:

{
  "solvable": false,
  "error": "no valid plan found"
}

Go Library

Three packages, each usable independently:

go get github.com/byte4ever/gp
graphplan -- solver engine (STRIPS + ADL)
import "github.com/byte4ever/gp/graphplan"

Build domain and problem structs programmatically, then solve:

solver, err := graphplan.NewSolver(nil)
plan, err := solver.Solve(problem)

See graphplan/README.md for full API.

pddl -- parser
import "github.com/byte4ever/gp/pddl"

Parse PDDL strings into graphplan types:

adapter := pddl.NewAdapter()
domain, err := adapter.ParseDomain(domainPDDL)
problem, err := adapter.ParseProblem(problemPDDL, domain)

Supports :strips, :typing, :equality, :disjunctive-preconditions, :conditional-effects, :quantified-preconditions, and :derived-predicates requirements. See pddl/README.md for full API.

mcpserver -- MCP transport
import "github.com/byte4ever/gp/mcpserver"

Embed the MCP server in your own application:

srv, err := mcpserver.NewServer(cfg, solver, adapter, adapter)
srv.Run()

See mcpserver/README.md for full API.

Project Structure

cmd/gp-server/      Entrypoint binary
graphplan/           Solver engine (Graphplan algorithm)
pddl/                PDDL parser and AST-to-graphplan converter
mcpserver/           MCP server wiring and tool handlers
testdata/            PDDL test fixtures
docs/                Design and implementation plans
install.sh           Linux/macOS installer
install.ps1          Windows installer
.goreleaser.yaml     Cross-platform release config

License

MIT

Directories

Path Synopsis
cmd
gp-server command
Command gp-server runs the Graphplan MCP server over stdio.
Command gp-server runs the Graphplan MCP server over stdio.
Package graphplan implements the Graphplan algorithm for solving STRIPS and ADL planning problems.
Package graphplan implements the Graphplan algorithm for solving STRIPS and ADL planning problems.
examples/rocket command
Command rocket demonstrates solving the rocket transport problem using the graphplan package.
Command rocket demonstrates solving the rocket transport problem using the graphplan package.
Package mcpserver exposes the Graphplan planner as an MCP server.
Package mcpserver exposes the Graphplan planner as an MCP server.
Package pddl provides a parser for PDDL (Planning Domain Definition Language) files.
Package pddl provides a parser for PDDL (Planning Domain Definition Language) files.
examples/parse_rocket command
Command parse_rocket parses the rocket PDDL files and prints the resulting domain and problem.
Command parse_rocket parses the rocket PDDL files and prints the resulting domain and problem.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL