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

l2p init - Initial Setup

Configure LLM provider & model

Human + Agent

l2p build - Assemble & Render PDDL

Assemble & render full PDDL domain or problem

Agent

l2p validate - Semantic & Syntax Checking

Validate JSON components or .pddl files

Agent

l2p plan - Run a Planner

Run a planner on domain/problem strings

Agent

l2p schema - JSON Schema Reference for LLMs

Output Pydantic JSON Schema for LLM reference

Agent

l2p generate - Interactive PDDL Generation

Interactive domain & problem generation

Human

l2p models - Model Management

List, switch, and test configured models

Human + Agent

l2p config - Configuration Management

Show, edit, validate, or reset configuration

Human + Agent

l2p new - Blank PDDL Files

Create blank PDDL domain or problem file

Human + Agent

l2p chat - Interactive LLM Session

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:

AGENTS.md - place at project root
# 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.