> ## Documentation Index
> Fetch the complete documentation index at: https://docs.innate.bot/llms.txt
> Use this file to discover all available pages before exploring further.

# Digital Skills

export const DigitalStateTypesTable = () => {
  const rows = [{
    stateType: "LAST_MAIN_CAMERA_IMAGE_B64",
    description: "Latest camera frame (base64 JPEG)."
  }, {
    stateType: "LAST_ODOM",
    description: "Current odometry."
  }, {
    stateType: "LAST_MAP",
    description: "Occupancy grid."
  }, {
    stateType: "LAST_HEAD_POSITION",
    description: "Head tilt angle."
  }];
  return <div className="interface-methods-table-wrap">
      <table className="interface-methods-table">
        <thead>
          <tr>
            <th>State Type</th>
            <th>Description</th>
          </tr>
        </thead>
        <tbody>
          {rows.map(row => <tr key={row.stateType}>
              <td>
                <span className="interface-param-badge">{row.stateType}</span>
              </td>
              <td>{row.description}</td>
            </tr>)}
        </tbody>
      </table>
    </div>;
};

export const DigitalSendEmailParametersTable = () => {
  const rows = [{
    parameter: "subject",
    type: "str",
    required: "Yes",
    description: "Email subject line."
  }, {
    parameter: "message",
    type: "str",
    required: "Yes",
    description: "Email body content."
  }, {
    parameter: "recipients",
    type: "list[str]",
    required: "No",
    description: "Recipients (defaults to configured list)."
  }];
  return <div className="interface-methods-table-wrap">
      <table className="interface-methods-table">
        <thead>
          <tr>
            <th>Parameter</th>
            <th>Type</th>
            <th>Required</th>
            <th>Description</th>
          </tr>
        </thead>
        <tbody>
          {rows.map(row => <tr key={row.parameter}>
              <td>
                <span className="interface-method-pill">{row.parameter}</span>
              </td>
              <td>
                <span className="interface-param-badge">{row.type}</span>
              </td>
              <td>
                <span className="interface-param-badge">{row.required}</span>
              </td>
              <td>{row.description}</td>
            </tr>)}
        </tbody>
      </table>
    </div>;
};

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.

```python theme={null}
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:**

<DigitalSendEmailParametersTable />

### SendPictureViaEmail

Sends an email with the robot's current camera view attached.

```python theme={null}
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.

```python theme={null}
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](#worked-example-retrieveemails) below.

## Creating Digital Skills

### Template

```python theme={null}
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/`):

```python theme={null}
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**

```python theme={null}
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:

```python theme={null}
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:

<DigitalStateTypesTable />

See [Robot State](/software/skills/code-defined-skills/robot-state) for more details on the RobotState system.

## Cancellation

Many digital operations are atomic and cannot be meaningfully cancelled:

```python theme={null}
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.
