What to keep in mind
Talking to an external service is a more deterministic domain than acting in the physical world, but it comes with its own constraints:- Protocol-based: Follow defined APIs and standards
- Atomic: Many operations cannot be cancelled once started
- Reliable: Once working, behavior is consistent
- Network-dependent: Must handle connectivity issues
Built-in examples
SendEmail
Sends email notifications, typically for alerts or status updates. The guidelines are written out explicitly here — the agent-facing text carries usage policy (“emergency only”) that goes beyond a one-line description:SendEmail is callable as send_email — no name property needed.
Parameters:
SendPictureViaEmail
Sends an email with the robot’s current camera view attached.image is declared without | None, the run fails up front if no
frame arrives, so execute() never needs a guard.
RetrieveEmails
Fetches recent emails from configured account.Building a service skill
Template
Worked example: RetrieveEmails
Here’s a complete, runnable custom skill that fetches your latest Gmail messages over IMAP. Save it as~/innate-os/workspace/custom_skills/retrieve_emails.py on the robot, then list it in an agent with from custom_skills.retrieve_emails import RetrieveEmails:
Best Practices
Authentication- Store credentials in environment variables or secret managers
- Never hardcode passwords or API keys
- Validate credentials at initialization
- Always set explicit timeouts on network calls
- Prevent blocking indefinitely on slow services
- Design operations to be safely retryable when possible
- Consider partial failure scenarios
Requesting Robot State
Skills declare sensor data dependencies with class-level type annotations:Cancellation
Many service operations are atomic and cannot be meaningfully cancelled. A skill that never blocks on a framework call — noself.sleep(), no sub-skill — simply runs to completion, which is usually the right behavior for a network request. Mention it in the guidelines if the agent should know:

