Chapter 20 · Build a bounded language-to-ROS 2 task executor
Today in the field story
One problem, then the next
Each Courier-17 task node now becomes a typed tool. place_card names entity identity, target identity, units, frames, scene version, preconditions, timeout, cancellation, feedback, result codes, and a postcondition that another observation can verify. Invalid or missing fields fail before dispatch. You keep the Week 19 action decoder behind the skill implementation, not inside the planner prompt. The courier can therefore explain what it intends while a deterministic adapter owns what request is legal.
- Why now
Typed skills turn a prose plan into inspectable execution boundaries.
- Ignore today
Do not expose raw controller topics or accept inferred defaults.
- Unlocks next
A planner-callable interface whose failures have stable meanings.
Understand
Build the physical picture first
A robot skill is a narrow typed socket: the planner may propose a plug, but the executor decides whether it fits current physical reality.
Give every skill one stable name and a closed input schema. move_to_pose needs more than three coordinates: it needs a pose type, unit convention, frame ID, target timestamp or scene version, speed profile, request identity, and declared optional fields. Object skills receive stable entity IDs rather than descriptions such as “that cup.” Reject unknown fields, missing units, unsupported frames, non-finite values, and values outside allowed ranges before any ROS request is created.
A contract also defines eligibility. Preconditions are evaluated against a fresh authoritative snapshot, not copied from the planner's text. A navigation skill might require localized state, an active map, sufficient battery reserve, a reachable goal, a current transform, no stop state, and an unexpired authority lease. A manipulation skill may additionally require collision-scene freshness, a known tool, payload limits, and a reserved workspace. A valid JSON shape with a failed precondition is still an invalid physical call.
ROS 2 actions provide the transport lifecycle for long-running skills: a typed goal is accepted or rejected, feedback reports progress, and a result ends in succeeded, aborted, or canceled status. Accepted is not completed, a cancel request is not proof of cancellation, and a succeeded result is not yet the physical postcondition. Retain the goal UUID, watch status and feedback, bound silence and total duration, and wait for or reconcile the terminal state before another conflicting skill begins.
Return structured results that help the orchestrator choose only preapproved branches. Include skill version, request and goal IDs, terminal status, machine-readable error code, last verified state version, observed effect summary, and whether compensation or human review is required. Keep free-form diagnostics for people, not branch logic. The executor maps known result codes to re-observe, alternate skill, replan, handoff, or abort; an unknown code fails closed.
Words you need
Name each idea precisely
- Skill contract
The versioned definition of a skill's typed inputs, eligibility rules, lifecycle, outputs, errors, side effects, and completion evidence.
Physical example:The
move_to_pose/v2contract requires metres, a map frame, scene version, speed limit, request ID, and a postcondition tolerance.- Goal
The typed request sent to a ROS 2 action server describing the bounded outcome that one skill should attempt.
Physical example:A navigation goal contains a stamped pose in
map; it does not contain the natural-language sentence that caused the planner to select it.- Feedback
Intermediate progress information emitted while an accepted action remains active, useful for monitoring but not a terminal outcome.
Physical example:Remaining distance decreases from 2.0 to 0.4 metres while the navigation goal is executing.
- Terminal status
The final ROS action state indicating that execution succeeded, aborted, or completed cancellation.
Physical example:A goal moves from executing to canceling and only later to canceled after the action server finishes its stop and cleanup behavior.
- Structured error
A finite machine-readable failure category with documented meaning and allowed recovery, separate from a human diagnostic message.
Physical example:STALE_SCENEsends the orchestrator to re-observe, whileLIMIT_REJECTEDforbids automatic retry and records the rejected value.
Math, one line at a time
Work through today’s relationship
Prerequisite rescue · optionalTask graphs, timeouts, and retries
An autonomous task is a state machine with measurable guards, not one long prompt.
- t_deadline
- latest allowed completion timeUnit: seconds (s)
- N_retry
- maximum retry countUnit: attempts
- P(success)
- observed success frequencyUnit: probability
A grasp skill gets a 5 s timeout and at most 2 retries.
The maximum planned attempt time is 3×5 = 15 s, excluding recovery.
After each failure, re-observe and check a typed precondition before retrying; do not replay a stale command.
It resembles a workflow engine with typed APIs, except retries require fresh perception of a changed world.
One initial try plus three retries, each capped at 4 s, permits how much attempt time?
4 attempts × 4 s = 16 s.
A call is eligible only when every required guard is true:
For , the result is false, so the executor must reject the call.
Define and evaluate the `move_to_pose/v2` skill
A simulated mobile base exposes a ROS 2 navigation action. The orchestrator must move to a staging marker without letting free-form planner output reach the action server.
Define required inputs:
request_idas a unique string,target_poseas finite x-y-yaw values,frame_idfrom an allowlist,scene_versionas a nonnegative integer,max_speed_mpswithin0.05..0.40, anddeadline_mswithin1000..60000. Reject additional physical-control fields the contract does not own.Define preconditions from executor-owned state: localization is valid, the
maptransform is younger than 200 ms, scene version equals the current authoritative version, battery is above reserve, the goal is in the allowed zone, no protective or application stop is active, and this executor holds the navigation lease.Map the validated request to one ROS 2 action goal, store the returned goal UUID beside
request_id, and record whether the server accepted or rejected it. Rejection ends this attempt; it is not an executing goal.While executing, collect timestamped feedback and enforce two clocks: feedback silence may not exceed two seconds, and total goal time may not exceed the declared deadline. Progress is informative, but arrival is not declared from remaining-distance feedback alone.
On timeout, request cancellation for the exact goal UUID and enter
CANCELING. Do not dispatch another navigation goal until status reaches canceled, aborted, or succeeded, or until a separately defined reconciliation path proves which goal can still command the robot.After a succeeded result, obtain fresh measured pose and require position error at most 0.10 m, yaw error at most 0.15 rad, near-zero commanded motion, and the same intended staging entity. If these fail, return
POSTCONDITION_FAILEDrather than success.Return one structured result containing request ID, goal UUID, contract version, terminal ROS status, postcondition verdict, final state version, duration, last feedback age, and error code; map every non-success code to a declared next state.
A well-typed call becomes one correlated ROS goal, malformed or ineligible calls fail before dispatch, active work remains cancelable and observable, and only a fresh measured arrival closes the skill successfully.
Types prevent malformed requests, preconditions prevent physically ineligible requests, action status bounds execution, and postcondition observation decides whether the world actually changed as required.
Physical examples
Where this appears in real life
Stamped navigation call
A planner proposes move_to_pose for a simulated cart using target (2.2, 1.1), frame map, speed 0.35 m/s, scene version 84, and request ID R-901.
Schema validation succeeds, but dispatch still waits for localization, transform freshness, battery, authority, stop-state, reachability, and scene-version guards.
Wrong-frame pick request
A pick call contains a plausible object pose measured in the camera frame while the skill contract requires a current transform into robot_base; that transform is missing.
The executor returns FRAME_UNAVAILABLE before motion, logs the rejected frame and snapshot, and does not ask the language planner to invent a transform.
Hands-on exercise
Make the idea observable
Use a simulated ROS 2 action or a paper action-server state machine. Do not connect an experimental skill contract directly to powered motion.
Choose one existing long-running skill and write its versioned input schema, units, allowed frames, range limits, request identity, optional fields, and unknown-field policy.
Write executor-owned preconditions, expected postconditions, maximum feedback silence, total timeout, cancel behavior, and the finite result-code table.
Evaluate at least five calls: nominal, missing frame, NaN or non-finite value, stale scene version, and target outside the approved workspace. Record that only the nominal call reaches dispatch.
Run the nominal action and capture goal acceptance, goal UUID, at least one feedback sample, terminal status, and a fresh postcondition observation under one request ID.
Run a slow case, request cancellation by exact goal UUID, and verify the state passes through canceling before canceled or another documented terminal result.
Force a controller success while the final observed pose or object state remains outside tolerance; verify the wrapper returns
POSTCONDITION_FAILEDand blocks the next dependent skill.
Schema checks catch malformed proposals, state guards catch valid-looking but ineligible calls, ROS status explains the transport lifecycle, and postcondition checks catch physical mismatch after apparent success.
Every test produces one documented result code, no invalid call reaches the action server, canceling is distinguished from canceled, and no dependent skill runs before a fresh postcondition passes.
Build today
Build an agent that converts a natural-language goal into inspectable ROS 2 actions and recovers from one failure.
Evidence to save
DONE when “Robot skills as typed tools with preconditions” runs from one documented command and the nominal plus boundary outputs are attached.
Common mistakes
Catch the wrong mental model
Calling a tool safe because its JSON parsed and every required field was present.
Validate types, finite values, units, frames, ranges, scene version, authority, and current physical preconditions before constructing the ROS goal.
Treating an accepted goal, a cancel acknowledgment, or a controller success result as the final task outcome.
Track the exact goal through terminal status and then verify the declared physical postcondition from fresh authoritative state.
Branching on arbitrary diagnostic text returned by a skill.
Use a closed versioned error-code set with documented recovery mappings; retain diagnostic text only for operators and debugging.
Job connection
How this becomes employable evidence
Wrap navigation, manipulation, docking, inspection, or device actions in versioned contracts that validate model-proposed arguments, preserve ROS goal identity and cancellation, expose bounded errors, and verify the real or simulated postcondition before workflow continuation.
Relevant target roles
- Robot Learning Deployment / Physical AI Integration Engineer
- Robotics Application / ROS 2 Integration Engineer
- Robotics Software Engineer — ROS 2 / AMR
- Robotics Deployment, Integration & Validation Engineer
Chapter 20 interview drill
Interview questions: Robot skills as typed tools with preconditions
Practise a 60–90 second answer: define the idea, connect it to a physical robot, state assumptions, frames, and units when relevant, then finish with the failure signal or evidence you would inspect.
Primary interview scenario
Design a typed pick_object tool over a ROS 2 action. Explain its schema, frames and units, preconditions, feedback, timeout, cancel lifecycle, result codes, and the observation that must pass after the action reports success.
Answer shape: clarify the situation → trace the physical and software path → test the most likely boundaries → name the evidence that would confirm the result.
Technical follow-up questions
Q1Why can a schema-valid `move_to_pose` call still be rejected?
Current localization, transform freshness, scene version, workspace, battery, stop state, reachability, or authority may violate the skill's preconditions.
Q2What is the difference between canceling and canceled?
Canceling means a cancel request was accepted and cleanup or stopping is in progress; canceled is the terminal state reached after that work completes.
Q3What must happen after a ROS action reports succeeded?
The wrapper must obtain fresh authoritative state and verify the skill's physical postcondition within its declared tolerances before dependent work proceeds.