> ## Documentation Index
> Fetch the complete documentation index at: https://innateinc-docs-skills-0-7-interface.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Composing Skills

A skill can run other skills. You **declare** them the same way you declare a camera or the base — with a type annotation — and then call them like methods. This is how you build a high-level behavior out of capabilities you (and the platform) already have, without reimplementing navigation, manipulation, or speech.

## Declaring a sub-skill

Import the skill's class and annotate an attribute with it:

```python theme={null}
from innate import Skill
from innate_skills.arm_rest_position import ArmRestPosition
from innate_skills.head_emotion import HeadEmotion


class PackUp(Skill):
    """Fold the arm away and look pleased about it. Use at the end of a task."""

    arm_rest: ArmRestPosition
    head_emotion: HeadEmotion

    def execute(self):
        self.arm_rest()
        self.head_emotion(emotion="proud")
        return "Packed up"
```

Calling one:

* **Blocks** until the sub-skill finishes — no callbacks, no polling.
* **Raises `SkillFailed`** if it fails, and `SkillCancelled` if 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.

Parameters are keyword arguments, taken straight from the sub-skill's `execute()` signature. `timeout=` bounds how long a call may run:

```python theme={null}
self.turn_in_place(angle_degrees=90)
self.navigate(x=2.4, y=-1.1, timeout=120)
```

## 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:

```python theme={null}
from innate import Skill
from physical_skills import PickSocks


class TidyRoom(Skill):
    """Pick up socks from the floor. Use when the user asks to tidy up."""

    pick: PickSocks

    def execute(self):
        self.pick(timeout=60)
        return "Picked up the socks"
```

If the policy isn't on this robot yet — you're writing against one that's still training — declare it by ID instead:

```python theme={null}
from innate import PhysicalSkill, Skill


class TidyRoom(Skill):
    """..."""

    pick = PhysicalSkill("pick_socks")
```

Either way an unknown ID fails at **run start**, not halfway through the routine.

## A worked example

```python theme={null}
from innate import Battery, Skill
from innate.exceptions import SkillFailed
from innate_skills.arm_rest_position import ArmRestPosition
from innate_skills.head_emotion import HeadEmotion
from innate_skills.move_straight import MoveStraight
from innate_skills.turn_in_place import TurnInPlace
from physical_skills import PickSocks


class DemoRoutine(Skill):
    """Run the demo routine: talk, emote, shuffle, turn, and try to pick up a
    sock. Use when the user asks for the demo."""

    battery: Battery
    arm_rest: ArmRestPosition
    emote: HeadEmotion
    move: MoveStraight
    turn: TurnInPlace
    pick: PickSocks

    def execute(self):
        runs = self.storage.get("runs", 0) + 1
        self.storage["runs"] = runs

        self.arm_rest()
        self.emote(emotion="excited")
        self.say(f"Demo number {runs}. Watch this.", wait=True)

        for distance in (0.2, -0.2):
            self.move(distance=distance)

        out = self.turn(angle_degrees=90)
        self.say(f"I turned {out.data.turned_degrees:.0f} degrees.")
        self.turn(angle_degrees=-90, timeout=20)

        try:
            self.pick(timeout=60)          # a trained policy, same call shape
        except SkillFailed:
            self.emote(emotion="disappointed")
            self.say("No socks today.")

        self.say(f"Battery at {self.battery.percentage:.0%}.")
        self.emote(emotion="proud")
        return "Demo complete"
```

### 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`:

```python theme={null}
try:
    self.pick(timeout=60)
except SkillFailed:
    self.emote(emotion="disappointed")
    self.say("No socks today.")
```

Don't catch it and the exception propagates, failing your skill too — which is usually what you want for a step that must succeed.

**Cancellation propagates for free.** Every child shares the parent's cancel latch, so one Stop unwinds the whole routine: the running child raises `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](/software/skills/code-defined-skills) — it declares state, persists counters with `self.storage`, speaks with `self.say(..., wait=True)`, and returns its message.

<Tip>
  Because each sub-skill call is its own step, a composed routine is easy to follow in the app and easy to interrupt — the agent can cancel between steps or in the middle of a long-running one.
</Tip>

## Dynamic IDs

When the skill to run is only known at runtime, use the invoker directly. It takes an ID string and **returns** a `SkillOutput` rather than raising on failure, so you check `.ok` yourself:

```python theme={null}
out = self.skills.run("local/my_skill", timeout=30, some_param=3)
if not out.ok:
    self.fail(f"sub-skill failed: {out.message}")
```

A cancelled routine still raises `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:

```python theme={null}
class GentlePackUp(PackUp):
    """Same routine, but with our slower arm-rest variant."""

    arm_rest: SlowArmRest
```

A skill that declares itself, directly or through a descendant, is rejected when the run is wired — before `execute()` starts — rather than recursing on the robot.

## When to compose vs. write from scratch

| Reach for composition when…                           | Write a flat skill when…                                                                                                         |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| The building blocks already exist as skills           | You need low-level [interface](/software/skills/code-defined-skills/body-control-interfaces) control the sub-skills don't expose |
| You want each step visible and separately cancellable | The steps are tightly coupled and shouldn't be interrupted mid-sequence                                                          |
| You're mixing scripted skills and trained policies    | It's a few interface calls with no reusable sub-behavior                                                                         |

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.
