Tutorial: Structured PDDL Agent
Goal: Use CLIPS to plan and execute structured PDDL actions with monitoring and timing.
Tutorial level: Advanced
Time: 45–60 minutes
Overview
This tutorial extends the basic PDDL Manager integration by introducing structured action management within CLIPS. The structured agent not only requests a plan but also executes and monitors actions step by step, checking preconditions and tracking execution times.
You will learn how to:
Initialize and manage PDDL-related ROS services and actions,
Define a structured pddl-action fact template,
Plan using the PDDL Manager,
Execute actions while checking feasibility conditions,
Monitor execution progress and print planned vs. actual times.
Prerequisites
Before starting, make sure you’ve completed the Interfacing with a PDDL Manager tutorial.
You need a workspace with:
The
cx_pddl_bringuppackage,The
cx_pddl_clipspackage,A working PDDL Manager instance running via:
ros2 launch cx_pddl_bringup pddl_manager_launch.py
Configuration
The CLIPS agent and its plugins are configured via structured_pddl_agent.yaml.
/**:
ros__parameters:
autostart_node: true
environments: ["structured_pddl_agent"]
structured_pddl_agent:
plugins: ["executive", "ros_msgs",
"ament_index",
"plan_temporal_action",
"timed_plan_action_msg",
"pddl_files",
"files"]
log_clips_to_file: true
watch: ["facts", "rules"]
ament_index:
plugin: "cx::AmentIndexPlugin"
executive:
plugin: "cx::ExecutivePlugin"
ros_msgs:
plugin: "cx::RosMsgsPlugin"
pddl_files:
plugin: "cx::FileLoadPlugin"
pkg_share_dirs: ["cx_pddl_clips", "cx_pddl_bringup"]
batch: [
"clips/deftemplates.clp",
"clips/deftemplate-overrides.clp",
"clips/pddl-no-deftemplates.clp"
]
files:
plugin: "cx::FileLoadPlugin"
pkg_share_dirs: ["cx_pddl_bringup"]
load: ["clips/structured_agent.clp"]
plan_temporal_action:
plugin: "cx::CXCxPddlMsgsPlanTemporalPlugin"
timed_plan_action_msg:
plugin: "cx::CXCxPddlMsgsTimedPlanActionPlugin"
The pddl_files plugin loads reusable templates and overrides for PDDL interaction,
while files loads the main CLIPS rule base implementing the structured agent.
Defining the PDDL Action Template
The structured agent overrides the default pddl-action template to include
execution state, planned timing, and runtime tracking.
(deftemplate pddl-action
(slot instance (type SYMBOL))
(slot id (type SYMBOL))
(slot plan (type SYMBOL))
(slot name (type SYMBOL))
(multislot params (type SYMBOL) (default (create$)))
(slot plan-order-class (type INTEGER))
(slot planned-start-time (type FLOAT))
(slot planned-duration (type FLOAT))
(slot actual-start-time (type FLOAT))
(slot actual-duration (type FLOAT))
(slot state (type SYMBOL) (allowed-values IDLE SELECTED EXECUTING DONE))
)
Each pddl-action fact represents one step in the plan, including its
desired start time, duration, and execution state.
Initializing the PDDL Manager
The rule structured-agent-pddl-init creates all required service clients
and initializes the temporal plan action client.
(defrule structured-agent-pddl-init
" Initiate the service clients for the pddl manager "
(not (pddl-services-loaded))
=>
(bind ?services (create$
add_fluents AddFluents
add_pddl_instance AddPddlInstance
get_fluents GetFluents
set_goals SetGoals
check_action_condition CheckActionCondition
create_goal_instance CreateGoalInstance
get_action_effects GetActionEffects
rm_fluents RemoveFluents
))
(bind ?index 1)
(bind ?length (length$ ?services))
(while (< ?index ?length)
(bind ?service-name (nth$ ?index ?services))
(bind ?service-type (nth$ (+ ?index 1) ?services))
(ros-msgs-create-client
(str-cat "/pddl_manager" "/" ?service-name)
(str-cat "cx_pddl_msgs/srv/" ?service-type)
)
(bind ?index (+ ?index 2))
)
(cx-pddl-msgs-plan-temporal-create-client (str-cat "/pddl_manager" "/temp_plan"))
(assert (pddl-services-loaded))
)
Uploading the PDDL Instance
Once initialized, the rule structured-agent-pddl-add-instance sets up the
domain and problem to be planned for and defines the goal fluents.
(defrule structured-agent-pddl-add-instance
" Setup PDDL instance with an active goal to plan for "
(not (pddl-loaded))
(pddl-services-loaded)
=>
(bind ?share-dir (ament-index-get-package-share-directory "cx_pddl_bringup"))
(assert
(pddl-manager (node "/pddl_manager"))
(pddl-instance
(name test)
(domain "domain.pddl")
(problem "problem.pddl")
(directory (str-cat ?share-dir "/pddl"))
)
(pddl-get-fluents (instance test))
(pddl-create-goal-instance (instance test) (goal active-goal))
(pddl-goal-fluent (instance test) (goal active-goal) (name on) (params a b))
(pddl-goal-fluent (instance test) (goal active-goal) (name on) (params b c))
(pddl-set-goals (instance test) (goal active-goal))
)
)
Planning
The rule structured-agent-pddl-plan triggers the temporal planner once
the set-goals request is completed.
(defrule structured-agent-pddl-plan
" Start the planner once the set-goals request is done "
(cx-pddl-msgs-plan-temporal-client (server ?server&:(eq ?server "/pddl_manager/temp_plan")))
(not (planned))
(pddl-set-goals (state DONE))
=>
(printout green "Start planning" crlf)
(bind ?goal (cx-pddl-msgs-plan-temporal-goal-create))
(cx-pddl-msgs-plan-temporal-goal-set-field ?goal "pddl_instance" "test")
(cx-pddl-msgs-plan-temporal-goal-set-field ?goal "goal_instance" "active-goal")
(cx-pddl-msgs-plan-temporal-send-goal ?goal ?server)
(assert (planned))
)
Processing the Plan Result
Once the PDDL Manager produces a plan, it is parsed into individual pddl-action facts.
(defrule structured-agent-pddl-plan-result
" Retrieve the resulting plan "
?wr-f <- (cx-pddl-msgs-plan-temporal-wrapped-result
(server "/pddl_manager/temp_plan") (code SUCCEEDED) (result-ptr ?res-ptr)
)
=>
(bind ?plan-found (cx-pddl-msgs-plan-temporal-result-get-field ?res-ptr "success"))
(printout green "planning done" crlf)
(bind ?id 0)
(if ?plan-found then
(bind ?plan (cx-pddl-msgs-plan-temporal-result-get-field ?res-ptr "actions"))
(foreach ?action ?plan
(bind ?name (sym-cat (cx-pddl-msgs-timed-plan-action-get-field ?action "name")))
(bind ?args (cx-pddl-msgs-timed-plan-action-get-field ?action "args"))
(bind ?ps-time (cx-pddl-msgs-timed-plan-action-get-field ?action "start_time"))
(bind ?p-duration (cx-pddl-msgs-timed-plan-action-get-field ?action "duration"))
(assert (pddl-action
(id ?id)
(instance test)
(name ?name)
(params ?args)
(planned-start-time ?ps-time)
(planned-duration ?p-duration))
)
(printout t ?ps-time "(" ?p-duration ") " ?name ?args crlf)
(bind ?id (+ ?id 1))
)
else
(printout red "plan not found!" crlf)
)
(cx-pddl-msgs-plan-temporal-result-destroy ?res-ptr)
)
Action Execution Loop
The structured agent continuously selects, executes, and validates actions.
Selecting the first action:
(defrule structured-agent-select-action
" Start executing the first action of the resulting plan "
(not (plan-start ?t))
?pa <- (pddl-action (planned-start-time ?t) (state IDLE))
(not (pddl-action (planned-start-time ?ot&:(< ?ot ?t))))
=>
(modify ?pa (state SELECTED))
(assert (plan-start (now)))
)
Checking preconditions before execution:
(defrule structured-agent-check-action
" Before executing an action check the condition to make sure it is feasible "
(pddl-action (id ?id) (state SELECTED) (name ?name) (params $?params))
(not (pddl-action-condition (action ?id)))
=>
(assert (pddl-action-condition (instance test) (action ?id)))
)
Executing and completing actions:
(defrule structured-agent-executable-action
(plan-start ?t)
(pddl-action-condition (action ?action-id) (state CONDITION-SAT))
?pa <- (pddl-action (id ?action-id) (name ?name) (params $?params) (state SELECTED))
=>
(modify ?pa (state EXECUTING) (actual-start-time (- (now) ?t)))
)
(defrule structured-agent-execution-done
(time ?now)
(plan-start ?t)
?pa <- (pddl-action (id ?id) (state EXECUTING) (planned-duration ?d)
(actual-start-time ?s&:(< (+ ?s ?d ?t) ?now)))
=>
(modify ?pa (state DONE) (actual-duration (- (now) (+ ?s ?t))))
(assert (pddl-action-get-effect (action ?id) (apply TRUE)))
)
Selecting the next action:
(defrule structured-agent-select-next-action
(not (pddl-action (state EXECUTING|SELECTED)))
(not (pddl-action-get-effect (state ~DONE)))
(not (pddl-fluent-change))
?pa <- (pddl-action (planned-start-time ?t) (state IDLE))
(not (pddl-action (state IDLE) (planned-start-time ?ot&:(< ?ot ?t))))
=>
(modify ?pa (state SELECTED))
)
Finalizing Execution
Once all actions have completed, CLIPS prints a timing summary.
(defrule structured-agent-print-exec-times
" Once everything is done, print out planned vs actual times "
(pddl-action)
(not (pddl-action (state ~DONE)))
(not (printed))
=>
(printout blue "Execution done" crlf)
(do-for-all-facts ((?pa pddl-action)) TRUE
(printout green "action " ?pa:name " "
?pa:params " " ?pa:planned-start-time "|" ?pa:planned-duration
" vs actual " ?pa:actual-start-time "|" ?pa:actual-duration crlf
)
)
(assert (printed))
)
Running the Structured Agent
As usual, build and source your workspace:
cd ~/ros2/cx_tutorial_ws/
colcon build --symlink-install
source install/setup.bash
Then launch the agent:
ros2 launch cx_pddl_bringup cx_pddl_launch.py manager_config:=pddl_agents/structured_agent.yaml
You should see the CLIPS output showing initialization, goal setup, planning, and the full action execution process with timing data.
Summary
You now have a fully structured CLIPS PDDL agent capable of:
Setting up planning problems,
Requesting and parsing plans,
Executing and monitoring actions,
Comparing planned vs. actual execution durations.
This provides a powerful base for integrating reasoning and execution control in robotic systems using the ROS2 CLIPS-Executive framework.