Declaring a sub-skill
Import the skill’s class and annotate an attribute with it:- Blocks until the sub-skill finishes — no callbacks, no polling.
- Raises
SkillFailedif it fails, andSkillCancelledif a Stop lands, so stop-on-first-failure is just Python. - Returns a
SkillOutput—.message,.data,.status,.ok. - Shows up as its own step in the app, so a composed routine is legible while it runs.
execute() signature. timeout= bounds how long a call may run:
Declaring a trained policy
Physical skills — trained ACT policies and recorded replays — are data on disk, not classes you can import from your own code. They get the same declaration block and the same call shape, in either of two spellings. The robot generates a typed reference for each of its physical skills, so the readable spelling is:A worked example
What each piece is doing
Sub-skills sit in the declaration block with everything else.battery: Battery and move: MoveStraight are declared identically — the type says what it is. A code skill and a trained policy are indistinguishable at the call site, which is the point: the caller doesn’t need to know how the sub-skill is implemented.
Read structured output from .data. MoveStraight and TurnInPlace return a SkillOutput carrying a typed payload, so out.data.turned_degrees reports how far the robot actually turned. A skill that returns a bare message has .data as None.
Handle failures with SkillFailed. A failing sub-skill raises, so recovery is a plain try/except:
SkillCancelled, the base is braked, the arm halted, and the run reports CANCELLED. Never catch SkillCancelled to keep going.
Everything else is a normal skill. DemoRoutine is an ordinary code-defined skill — it declares state, persists counters with self.storage, speaks with self.say(..., wait=True), and returns its message.
Dynamic IDs
When the skill to run is only known at runtime, use the invoker directly. It takes an ID string and returns aSkillOutput rather than raising on failure, so you check .ok yourself:
SkillCancelled out of run() — you never handle CANCELLED by hand.
Prefer the declared form everywhere else: a class reference is checked by your editor, an ID string isn’t.
Overriding a sub-skill
Composition runs the class you declared, so specializing a routine means subclassing it and re-declaring the attribute with your own class — never shadowing by name:execute() starts — rather than recursing on the robot.
When to compose vs. write from scratch
Composing is also how you turn a one-off demo into a reusable capability: give the class a name and a docstring, and the agent can trigger the whole chain with a single skill call.

