Skip to main content
Requires OS 0.7.0 or newer.
A skill can call other skills. Declare the one you want as a type annotation, then call it like a method — the runtime runs it to completion and returns control to your code. This is how you build a high-level behavior out of capabilities you already have, without re-implementing navigation, manipulation, or speech.

Declare, then call

Sub-skills are declared exactly like robot state — annotate the attribute with the skill’s class:
Each call:
  • Blocks until the sub-skill finishes — no callbacks or polling.
  • Raises SkillFailed if the sub-skill fails, so a failing step stops the routine unless you catch it.
  • Returns a SkillOutput.message, .data, and .ok.
  • Shows up as its own step in the app timeline, so a composed routine is legible while it runs.
Declaring a sub-skill also means a missing or broken dependency fails the run up front, before the robot moves, instead of halfway through.

Learned policies call the same way

A physical skill — a trained policy or recorded demonstration — has no class of its own, so the catalog generates a typed reference for it inside its own folder. Import and declare it exactly like a code skill:
The caller doesn’t need to know how a sub-skill is implemented. See Policy-Defined Skills for how these get trained.

Reading a sub-skill’s output

Skills that return structured data expose it on .data:

Handling failure

A sub-skill that fails raises SkillFailed. Catch it to recover, or let it propagate to fail your skill too — which is often what you want for a step that must succeed:
Never catch bare Exception around a sub-skill call and swallow it. Cancellation travels as SkillCancelled, which must reach the runtime for a Stop to work. Catch SkillFailed specifically.

A worked example

Talk, emote, shuffle, turn, and attempt a learned pick — six skills chained into one:
Everything else is an ordinary code-defined skill: it declares robot state with annotations, persists counters with self.storage, speaks with self.say(..., wait=True), and returns a plain success message.
Because each sub-skill call is its own step, a composed routine is easy to follow in the app and easy to interrupt — a Stop unwinds the whole chain, wherever it is.

Choosing a skill at run time

When you don’t know which skill to call until the routine is running, dispatch by id:
self.skills.run() returns a failed SkillOutput rather than raising, so you check .ok yourself. Prefer a declaration whenever the skill is known ahead of time — it’s typed, your editor catches renames, and the dependency is verified before the run starts.

When to compose vs. write from scratch

Composing is also how a one-off demo becomes a reusable capability: name the routine, give it guidelines(), and the agent can trigger the whole chain with a single skill call.