CLI¶
The l2p CLI provides terminal-based access to PDDL generation, validation,
planning, and LLM agent tooling. It supports both interactive workflows for
human users and stateless command pipelines for automation and LLM agents.
Configuration is stored at ~/.l2p/config.yaml and managed via the
l2p init - Initial Setup and l2p config - Configuration Management commands.
Tip
Non-interactive commands (l2p build, l2p validate,
l2p plan, l2p schema) accept structured JSON input and produce
machine-readable output - ideal for LLM tool-calling agents. See
Agentic Workflow (for automation & LLM agents) below.
Quick Reference¶
Command |
Description |
Audience |
|---|---|---|
Configure LLM provider & model |
Human + Agent |
|
Assemble & render full PDDL domain or problem |
Agent |
|
Validate JSON components or |
Agent |
|
Run a planner on domain/problem strings |
Agent |
|
Output Pydantic JSON Schema for LLM reference |
Agent |
|
Interactive domain & problem generation |
Human |
|
List, switch, and test configured models |
Human + Agent |
|
Show, edit, validate, or reset configuration |
Human + Agent |
|
Create blank PDDL domain or problem file |
Human + Agent |
|
Interactive LLM chat with PDDL editing |
Human |
Interactive Commands (for humans)¶
These commands provide step-by-step prompts and are designed for direct terminal use.
l2p init - Initial Setup¶
Walks through LLM backend selection, provider, model name, and API key
configuration. The resulting config is saved to ~/.l2p/config.yaml.
# Interactive setup
l2p init
# Non-interactive (for scripts)
l2p init --backend openai --provider openai --model gpt-4o-mini
Supports two backends:
unified - uses the simonw/llm library
openai - uses the OpenAI SDK directly
Providers include openai, google, anthropic, deepseek,
mistral, ollama, and ollama-cloud.
l2p generate - Interactive PDDL Generation¶
Step-by-step pipelines for building PDDL domains and problems with LLM assistance.
# Interactive domain generation
l2p generate domain
# Interactive problem generation
l2p generate problem
# Increase LLM retries on parse failure
l2p generate domain --max-retries 5
l2p generate domain guides you through: domain name, description, types,
constants, predicates, functions, and actions - each generated by the LLM,
entered manually, or reviewed before proceeding.
l2p generate problem prompts for: domain source (NL description or existing
.pddl file), problem name, description, objects, initial state, and goal
state.
Tip
At each generation step you can accept the LLM output, provide fix feedback for a regeneration, or enter the data manually.
l2p chat - Interactive LLM Session¶
A REPL-style chat session with the configured LLM, featuring PDDL-specific commands:
l2p chat
# Inside the session:
# /exit - quit
# /edit <file.pddl> - load & edit a PDDL file
# /validate <file> - validate a PDDL file
# <any message> - send to LLM with L2P context
The /edit command loads a PDDL file, takes an edit description, sends it to
the LLM with editing instructions, shows a diff, and prompts to overwrite.
l2p models - Model Management¶
List available models, switch between them, and test LLM connections.
# List configured models
l2p models list
l2p models list --details # show context length, cost, params
l2p models list --provider openai # filter by provider
# Switch model interactively
l2p models switch
# Test connection
l2p models test
l2p models test --simple # config validation only
l2p config - Configuration Management¶
View, edit, validate, and reset the L2P configuration.
# Show configuration
l2p config show
l2p config show --raw # raw YAML output
# Edit in $EDITOR
l2p config edit
# Validate settings
l2p config validate
# Reset to defaults
l2p config reset --force
The edit subcommand auto-detects your editor from the $EDITOR
environment variable and re-validates the config after saving.
l2p new - Blank PDDL Files¶
Create minimal PDDL domain or problem skeleton files.
# Create a domain file
l2p new blocksworld.pddl
# Create a problem file
l2p new pb1.pddl --type problem --domain-name blocksworld
Agentic Workflow (for automation & LLM agents)¶
These commands accept structured JSON input, perform a single operation, and produce machine-readable output. They are designed for non-interactive use by LLM tool-calling agents and shell scripts.
l2p build - Assemble & Render PDDL¶
Build the final PDDL string from a complete DomainDetails or
ProblemDetails JSON, or from individual component files.
# Full JSON - one-shot domain (recommended for agents)
l2p build domain --data '{
"name":"blocksworld",
"types":[{"name":"block","parent":"object"}],
"predicates":[...],
"actions":[...]
}' -o domain.pddl
# Full JSON - one-shot problem
l2p build problem --data '{
"name":"pb1","domain_name":"blocksworld",
"objects":[...],
"initial_state":{...},
"goal_state":{...}
}' -o problem.pddl
# Individual component files
l2p build domain --name blocksworld \
--types types.json --predicates preds.json \
--actions actions.json -o domain.pddl
# Output JSON instead of PDDL
l2p build domain --data '{...}' --json
File references use the @ prefix (e.g. --types @types.json).
l2p validate - Semantic & Syntax Checking¶
Validate individual JSON components or entire .pddl files against L2P’s
rule engine. Rules cover naming conventions, type inheritance, variable scope,
arity matching, undeclared symbols, and uppercase warnings.
# Validate a component JSON snippet
l2p validate types --data '[{"name":"block","parent":"object"}]'
# Validate a .pddl domain file
l2p validate domain domain.pddl
# Validate a .pddl problem file (cross-checked against domain)
l2p validate problem problem.pddl --domain domain.pddl
# Validate full DomainDetails JSON
l2p validate domain --data '{"name":"bw","types":[],"predicates":[],"actions":[]}'
Three input modes: raw JSON string, JSON file, or .pddl file. Exits with
code 1 on validation failure.
l2p plan - Run a Planner¶
Execute a classical planner on PDDL domain and problem strings directly - no temporary files needed (they are managed automatically).
# Run Fast Downward with raw PDDL strings
l2p plan \
--domain '(define (domain test) ...)' \
--problem '(define (problem p) ...)' \
--planner fast-downward --alias lama-first
# Read from files
l2p plan --domain @domain.pddl --problem @problem.pddl --json
# Use Unified Planning backend
l2p plan --domain @d.pddl --problem @p.pddl \
--planner unified --engine aries --json
Output with --json returns a structured PlanningResult:
{
"is_successful": true,
"plan": ["(pickup b1)", "(stack b1 b2)"],
"error_message": null,
"raw_output": "...",
"metrics": {}
}
Two planner backends:
Fast Downward - submodule-based, requires the executable path
Unified Planning - Python API, requires
pip install unified-planning[engine]
l2p schema - JSON Schema Reference for LLMs¶
Output the Pydantic JSON Schema for any PDDL component. LLMs can read this to
know the exact JSON structure expected by l2p build.
# Schema for a single component
l2p schema types
l2p schema predicates
l2p schema actions
# Include example JSON
l2p schema domain --examples
# List all available schemas
l2p schema list
Available schemas: types, constants, predicates, functions,
derived-predicates, actions, durative-actions, events,
processes, constraints, parameters, objects,
initial-state, goal-state, metric, domain, problem,
requirements.
End-to-End Agent Workflow¶
Here is how an LLM agent can chain CLI commands to produce a validated PDDL domain and plan:
# 1. Get the JSON schema the LLM should follow
l2p schema domain --examples
# 2. Build domain from JSON
l2p build domain --data '{
"name":"blocksworld","types":[{"name":"block","parent":"object"}],
"predicates":[...],"actions":[...]
}' -o domain.pddl
# 3. Validate
l2p validate domain domain.pddl
# 4. Build problem
l2p build problem --data '{
"name":"pb1","domain_name":"blocksworld",
"objects":[{"name":"b1","type":"block"}],
"initial_state":{"facts":["(on-table b1)"]},
"goal_state":{"conditions":["(holding b1)"]}
}' -o problem.pddl
# 5. Plan
l2p plan --domain @domain.pddl --problem @problem.pddl --json
AGENTS.md for LLM Agent Tooling¶
LLM-powered coding assistants (such as OpenCode) use
an AGENTS.md file placed at the project root to understand a codebase -
covering setup, architecture, CLI commands, conventions, and testing.
Copy the following AGENTS.md into the root of your L2P checkout so your
agent has all the context it needs to work effectively:
# Agent Instructions for L2P
L2P is a Python library that generates, validates, and plans PDDL models from natural language via LLMs.
## Setup & Dependencies
- Python ≥3.10 (CI uses 3.10)
- Install: `pip install -r requirements.txt` then `pip install -e .`
- Extra install groups (use as needed): `cli`, `openai`, `mistral`, `huggingface`, `planner`, `all`
- `cli` (`llm` + `rich`) is required for the `l2p` CLI
- `planner` (`unified-planning`) required for `UnifiedPlanning` planner backend
- Tests: `pytest` (unittest-based, no coverage/typechecking configured)
- Lint (errors only): `flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics`
- Lint (all warnings): `flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics`
## Environment
- Required: `OPENAI_API_KEY` for OpenAI models
- Optional: `CLAUDE_API_KEY`, `DEEPSEEK_API_KEY`, `OLLAMA_API_KEY`
- LLM config YAMLs: `l2p/llm/utils/llm.yaml` (UnifiedLLM) or `l2p/llm/utils/openaiSDK.yaml` (OPENAI SDK)
## FastDownward Submodule (planner)
- Path: `downward/` — must initialize with `git submodule update --init --recursive`
- Planner executable: `downward/fast-downward.py` (note: may need building; see FastDownward docs)
- Default alias: `lama-first`
## Architecture
Two usage modes: **Python API** (programmatic) and **CLI** (stateless, agent-friendly).
### Core classes (`from l2p import ...`)
| Class | File | Purpose |
|-------|------|---------|
| `DomainBuilder` | `l2p/domain_builder.py` | Construct PDDL domains; has `formalize_component()` (LLM extraction), `set_*()` methods, `generate_domain()` |
| `ProblemBuilder` | `l2p/problem_builder.py` | Construct PDDL problems; same pattern as DomainBuilder |
| `FeedbackBuilder` | `l2p/feedback_builder.py` | LLM-driven feedback loops: diagnose, revise, evaluate, reflect, select |
| `PromptBuilder` | `l2p/prompt_builder.py` | Assemble structured prompts with Role/Format/Rules/Examples/Task sections |
| `FastDownward`, `UnifiedPlanning` | `l2p/planner_builder.py` | Run external planners; returns `PlanningResult` dataclass |
| `UnifiedLLM`, `OPENAI`, `HUGGING_FACE` | `l2p/llm/` | LLM backends |
### PDDL Type Models (`l2p/utils/pddl_types.py`)
All PDDL components are Pydantic v2 models: `PDDLType`, `Predicate`, `Action`, `DurativeAction`, `Function`, `DerivedPredicate`, `Constant`, `Requirement`, `Constraint`, `Event`, `Process`, `PDDLObject`, `InitialState`, `GoalState`, `Metric`, `DomainDetails`, `ProblemDetails`.
Domain components are set via `DomainBuilder.set_*(value, append=False)`. Problem components via `ProblemBuilder.set_*`. Setting `None` clears the list; `append=True` adds to existing list.
### LLM Extraction Pattern (`formalize_component`)
`DomainBuilder.formalize_component()` and `ProblemBuilder.formalize_component()` take:
- `model`: LLM instance
- `component_class`: single class or list of classes (e.g., `Predicate`, `[PDDLType, Predicate]`)
- `description`: natural language description
- `**ctx_kwargs`: context like `types=`, `predicates=`
- Returns `(dict[Type[BaseModel], List[BaseModel]], raw_llm_output)`
The LLM must output JSON wrapped in XML tags like `<types>...</types>`. Default prompts are auto-selected per component class. Using a list of multiple component classes requires a custom `prompt_template`.
Requirements are **auto-generated** by `DomainBuilder.generate_requirements()` based on which components are present — you don't need to set them manually.
## CLI Commands
Entry point: `l2p` (via `l2p.cli.main:main`). All commands are stateless (pass full data each call).
| Command | Purpose | Key Flags |
|---------|---------|-----------|
| `l2p init` | Configure LLM (interactive or `--backend/--provider/--model`) | `--backend unified\|openai` |
| `l2p config show/edit/reset/validate` | Manage config at `~/.l2p/config.yaml` | |
| `l2p models test` | Test LLM connection | |
| `l2p schema <component>` | Show Pydantic JSON Schema for LLM reference | `--examples` |
| `l2p build domain\|problem` | Assemble full PDDL from JSON or component files | `--data`, `-o`, `--json` |
| `l2p validate <component\|domain\|problem>` | Validate components or .pddl files | `--data`, `--file`, path argument for .pddl |
| `l2p plan` | Run planner on PDDL (raw strings or `@file`) | `--domain`, `--problem`, `--planner`, `--alias`, `--json` |
### CLI Data Input
- `--data '...'` — raw JSON string
- `--file path.json` — read from file
- `--stdin` — pipe JSON from stdin
- `@file.pddl` — prefix with `@` to read from file (used in `build` and `plan`)
### CLI Pipeline Pattern (agent-friendly)
```bash
l2p schema types --examples # learn JSON shape
l2p build domain --data '{"name":"bw","types":[...]}' -o domain.pddl
l2p validate domain domain.pddl
l2p plan --domain @domain.pddl --problem @problem.pddl --json
```
### Recommendations for Agents
- **Use the CLI** (`build`/`validate`/`plan`) for stateless, scriptable workflows. The Python API is better for custom loops.
- Before generating any PDDL with an LLM, always run `l2p schema <component> --examples` to get the exact JSON schema an LLM should output.
- Always validate generated PDDL before planning: `l2p validate domain domain.pddl`.
- For FastDownward, default alias is `lama-first`. Other options: `seq-opt-fdss-1`, `seq-opt-bjolp`.
- For UnifiedPlanning backend: `pip install unified-planning unified-planning[engines]` then `--engine aries`.
## Key Conventions
- PDDL keywords output by LLMs (AND/OR) are **lowercased** automatically by `generate_domain()` and `generate_problem()`.
- Use `format_*` functions from `l2p/utils/pddl_format.py` for standalone PDDL formatting.
- Validators in `l2p/validators/` check naming, type hierarchy, param types, variable scope, symbol references, arity.
- Backend (`unified` vs `openai`) is automatically inferred from config_path (`llm.yaml` → unified, `openaiSDK.yaml` → openai).
- The `@require_llm` decorator on `formalize_component()` validates that the model parameter is a `BaseLLM` instance.
This file is designed to be consumed by LLM agent tools like OpenCode
that use AGENTS.md (or .opencode/ skills) to bootstrap context for
the coding assistant - see the OpenCode documentation for more details.