Skip to main content
Digital skills enable the robot to interact with external services—sending emails, making API calls, retrieving information. These are always code skills, implementing explicit protocols and handling authentication, errors, and network conditions.

Characteristics

Unlike physical skills that deal with real-world variability, digital skills operate in a deterministic domain:
  • 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 Skills

SendEmail

Sends email notifications, typically for alerts or status updates.
class SendEmail(Skill):
    @property
    def name(self):
        return "send_email"

    def guidelines(self):
        return (
            "Use to send an emergency email notification. Provide a subject and "
            "message. You can optionally provide a list of recipients, otherwise "
            "it will be sent to the default list. This should be used when a "
            "potential emergency is detected and assistance might be required."
        )

    def execute(self, subject: str, message: str, recipients: list[str] = None):
        # Send via SMTP
        # Returns (message, SkillResult)
Parameters:

SendPictureViaEmail

Sends an email with the robot’s current camera view attached.
class SendPictureViaEmail(Skill):
    # Declare camera image dependency - updated at 50Hz while skill runs
    image = RobotState(RobotStateType.LAST_MAIN_CAMERA_IMAGE_B64)

    def execute(self, subject: str, message: str, recipient: str = None):
        if not self.image:
            return "No image available to send", SkillResult.FAILURE
        # Attach image and send
This skill demonstrates state dependencies—using the RobotState descriptor to declare required robot state that the system updates at 50Hz.

RetrieveEmails

Fetches recent emails from configured account.
class RetrieveEmails(Skill):
    def guidelines(self):
        return (
            "Use to retrieve recent emails from the configured email account. "
            "Provide the number of emails to retrieve (default is 5). "
            "Returns email subjects and content."
        )

    def execute(self, count: int = 5):
        # Connect to IMAP, fetch emails
        # Returns formatted email list
Want to build your own version against your own account? There’s a complete implementation in the worked example below.

Creating Digital Skills

Template

import os
from brain_client.skill_types import Skill, SkillResult

class MyDigitalSkill(Skill):
    def __init__(self, logger):
        super().__init__(logger)
        self.api_key = os.environ.get("SERVICE_API_KEY")
        if not self.api_key:
            raise ValueError("SERVICE_API_KEY not configured")

    @property
    def name(self):
        return "my_digital_skill"

    def guidelines(self):
        return "Use when [describe appropriate use cases]"

    def execute(self, param: str):
        try:
            # Call external service
            result = self._call_service(param)
            return f"Success: {result}", SkillResult.SUCCESS
        except TimeoutError:
            return "Service timed out", SkillResult.FAILURE
        except Exception as e:
            return f"Error: {e}", SkillResult.FAILURE

    def cancel(self):
        return "Operation cannot be cancelled"

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, and reference it from an agent as local/retrieve_emails (custom skills are namespaced local/, shipped skills innate-os/):
import imaplib
import email
from brain_client.skill_types import Skill, SkillResult

class RetrieveEmails(Skill):
    def __init__(self, logger):
        super().__init__(logger)
        self.imap_server = "imap.gmail.com"
        self.email = "your_email@gmail.com"
        # Use a Gmail App Password (https://myaccount.google.com/apppasswords),
        # not your main account password.
        self.password = "your_app_password"

    @property
    def name(self):
        return "retrieve_emails"

    def guidelines(self):
        return "Use to retrieve recent emails. Provide count (default 5). Returns subjects and content."

    def execute(self, count: int = 5):
        count = min(max(1, count), 20)
        try:
            mail = imaplib.IMAP4_SSL(self.imap_server, 993)
            mail.login(self.email, self.password)
            # ... fetch and process emails ...
            email_data = "Email 1: Subject, From, Content..."
            self._send_feedback(email_data)
            return f"Retrieved {count} emails with subjects and content", SkillResult.SUCCESS
        except Exception as e:
            return f"Failed to retrieve emails: {str(e)}", SkillResult.FAILURE

    def cancel(self):
        # Every skill must implement cancel(); nothing long-running to
        # interrupt here, so just report it.
        return "Email retrieval cancelled"

Best Practices

Authentication
  • Store credentials in environment variables or secret managers
  • Never hardcode passwords or API keys
  • Validate credentials at initialization
Error Handling
def execute(self, query: str):
    try:
        response = self.client.call(query, timeout=10)
        return f"Result: {response}", SkillResult.SUCCESS
    except RateLimitError:
        return "Rate limit exceeded", SkillResult.FAILURE
    except NetworkError:
        return "Network unavailable", SkillResult.FAILURE
    except Exception as e:
        return f"Unexpected error: {e}", SkillResult.FAILURE
Timeouts
  • Always set explicit timeouts on network calls
  • Prevent blocking indefinitely on slow services
Idempotency
  • Design operations to be safely retryable when possible
  • Consider partial failure scenarios

Requesting Robot State

Skills declare sensor data dependencies using the RobotState descriptor:
from brain_client.skill_types import RobotState, RobotStateType

class MySkill(Skill):
    # Declare state dependencies at class level
    image = RobotState(RobotStateType.LAST_MAIN_CAMERA_IMAGE_B64)
    odom = RobotState(RobotStateType.LAST_ODOM)

    def execute(self):
        if self.image:
            # Use the latest camera frame
            pass
The system automatically updates declared state at 50Hz while your skill runs. Always check for None on first access. Available state types: See Robot State for more details on the RobotState system.

Cancellation

Many digital operations are atomic and cannot be meaningfully cancelled:
def cancel(self):
    return "Email sending is an atomic operation that cannot be canceled once started"
The Innate agent understands this limitation and factors it into planning decisions.