Skip to main content

Introduction

Custom Abilities are the cornerstone of extending OpenHome’s functionality. They allow developers to:
  • Add personalized features to AI agents.
  • Integrate third-party APIs for dynamic interactions.
  • Customize logic for enhanced user engagement.
This guide walks you through:
  • Structuring and registering an Ability.
  • Using CapabilityWorker for seamless I/O management.
  • Examples showcasing how to create powerful custom Abilities.

Adding an Ability

File Structure

Each Ability resides in its folder and requires a main.py file to define the logic.

Example File: main.py

Here’s a basic template for building a new Ability:

Key Components

  • #{{register_capability}}: is essential.
  • call: Executes the Ability’s logic when triggered.

Custom API Keys (Third-Party Services)

If your Ability needs credentials for external services (for example OpenAI, SendGrid, or Twilio), configure those keys in the dashboard and read them at runtime using get_api_keys("key_name").

Setup Flow

For Developers

Step 1 — Declare keys You can declare a custom API key in either of two ways:
  • While creating/editing the Ability: Under Ability Behavior → API Keys, add each key by name and include a provider URL (required). The key value is not set here; values are always managed from Settings → API Keys. Declaring API keys in the Ability editor
  • From Settings: Go to Settings → API Keys → Third-party Keys and create the key directly, then link it to an Ability by creating or editing one in the Ability editor. Pre-creating third-party API keys from Settings
Step 2 — Tag keys as required After declaring a key, under Ability Behavior → API Keys, tag the API key to mark it as required. This tells the platform to prompt users for the value at install time. Tagging a required API key in the Ability editor
Important: Untagged keys will not trigger an install-time prompt for users.
Step 3 — Read values at runtime

For Users

When installing an Ability from the marketplace, a pop-up lists all required keys with direct links to each provider. Users can enter values immediately or skip and add them later from Settings → API Keys. The Ability will not work until all required keys have values set. Install-time prompt for required API keys
Security note: Never hardcode secrets in your Ability code. Always read keys at runtime.

Reading Linked Account Tokens with get_token()

For providers that support OAuth-based account linking, OpenHome stores the user’s access token on the user’s behalf when they connect the account from Settings → Linked Accounts in the Dashboard. Inside an Ability, use get_token() to retrieve that token and call the provider’s API on behalf of the user without handling the OAuth flow yourself. This is different from Custom API Keys above: API keys are values the user pastes in for arbitrary third-party services, while get_token() returns an OAuth access token managed by OpenHome for a supported provider.

Signature

get_token() is synchronous — do not await it.

Parameters

Returns

  • A non-empty str access token if the user has linked the requested provider.
  • None or an empty value if the provider is not linked for the user. Always guard the return value before using it.

Example: token lookup with a connect-account fallback

The recommended pattern is to read the token at the start of run() and, if it is missing, speak a clear instruction telling the user where to connect the account, then exit cleanly.
Substitute the provider key ("slack", "discord", "microsoft", "tesla") and the spoken instruction to match the account your Ability needs.

Reference Abilities Using get_token()

The following community Abilities use get_token("google") in production. Review them for end-to-end patterns covering token retrieval, API calls, and error handling: For the user-facing account-linking flow, see Linked Accounts in the Dashboard reference.

Making HTTP Requests

When your Ability calls an API, always route the request through self.worker.session_tasks. This lets the SDK manage the request for you: it paces outbound calls and smooths out latency, so a slow or heavy API doesn’t block your Agent or affect other Abilities. Do not call requests, httpx, or aiohttp directly — use the matching session_tasks helper instead. This applies to code you write by hand and to code generated by an AI assistant. The simplest option is get(). It works just like the requests library: give it a URL and read the response with .json().
This is all most Abilities need. get() accepts the usual arguments (params, headers, timeout, auth) and returns a standard requests.Response.

Which helper should I use?

Most Abilities can just use get(). You only need the others if you want an async call or prefer a different library. When you do, the choice depends on how your method is written:
  • Normal def method → use a sync helper and call it directly, with no await: get() or httpx_get().
  • async def method (for example, a background-daemon loop) → use an async helper and await it: get_async(), httpx_get_async(), or aiohttp_get_async(). Async helpers don’t freeze the rest of your Ability while the request is running, so prefer them whenever your method is already async.
requests has no async version of its own, so get_async() is there to give you a requests-style request that you can await. Every library has the same helpers, so pick the one you’re comfortable with:

Examples

With requests and httpx, you read the response directly with .json(), .text, and .status_code. With aiohttp, reading the body is also asynchronous, so use await resp.json() and await resp.text().

Understanding CapabilityWorker

The CapabilityWorker class simplifies I/O interactions, enabling:
  • Speech synthesis: Using text-to-speech (TTS).
  • Listening for user input: Capturing and processing responses.
  • Running interaction loops: Supporting conversational flows.

CapabilityWorker Quick Reference

Use these functions directly on self.capability_worker.

Conversation

Speak text to the user using the configured TTS.
Wait for a single user reply and return the transcription.
Speak once, then wait for one reply.
Ask a yes/no question and return True or False.
Wait for the user’s full transcription.
Get full session message history.
Get the current user’s timezone.
Get the user’s approximate location from their IP. Returns a dict with city, region, country, postal, timezone, ip, loc, and more.
Get linked account access token.
Get a custom API key value by key name.
Append context/instructions to the active Agent prompt.
Return control back to normal Agent flow when your ability is done.

Text Generation

Return plain text from the model (no speech).
Alternate text generation routed through OpenRouter.
Web-search-backed short answer.
Tool-calling flow.

File Helpers

Read a file.
Write content to a file.
Delete a file.
Check if a file exists.
List user data files.
Note: Storage Scope Usage
  • Use in_ability_directory=False for persistent user-level storage shared across abilities.
  • Use in_ability_directory=True for ability-scoped data that should remain isolated within the ability session.

Context Storage (Key-Value)

Audio and Streaming

WebSocket / Device Actions

Using Specific Voice IDs for Text-to-Speech

The CapabilityWorker class supports the use of specific Voice IDs for text-to-speech (TTS) functionality. This allows you to customize the voice used for speech synthesis by specifying a Voice ID from the provided list.

Available Voice IDs

You can use any of the following Voice IDs for TTS:

text_to_speech Function

The text_to_speech function converts the provided text into speech using the specified Voice ID and streams it to the user via WebSocket.

Parameters

  • text (str): The text to be converted into speech.
  • voice_id (str): The Voice ID to be used for speech synthesis.

Exiting an Ability

An Ability returns control to the Agent when it finishes its work by calling resume_normal_flow(). Beyond that normal completion, there are two ways to leave a Skill Ability on demand: the built-in exit phrases the SDK recognizes, and exit handling you add in your own code.

Built-in exit phrases

While a Skill Ability is active, the SDK recognizes a fixed set of spoken exit phrases. Speaking any of them ends the current Ability and returns control to the Agent’s conversation flow. This is the same outcome as calling resume_normal_flow(), but initiated by the user rather than by the Ability. This is built in and requires no additional code, so a user always has a way out of an Ability, including one that is mid-conversation or waiting for input. The recognized exit phrases are:
  • “open home exit”
  • “openhome exit”
  • “open home quit”
  • “openhome quit”
  • “open home, quit”
  • “open home, exit”
  • “exit open home”
  • “quit open home”
  • “exit, open home”
  • “quit, open home”
Note: Built-in exit phrases apply to Skill Abilities.

Handling exit in your own code

You can also end an Ability from your own code. Detect an exit condition in the user’s input, such as your own stop words, and call resume_normal_flow() to return control to the Agent.

Advanced CapabilityWorker Functions

Audio Processing Functions

The CapabilityWorker provides comprehensive audio handling capabilities:
  • play_audio: Play audio content directly or file objects
  • play_from_audio_file: Play audio files stored in the capability directory
  • send_audio_data_in_stream: Stream processed audio data over WebSocket

Text Generation Functions

Multiple options for text generation:
  • text_to_text_response: Standard text generation with history and system prompts
  • generate_ttt_using_openrouter: Alternate text generation using OpenRouter
  • llm_search: Web-search-backed short answer
  • llm_tools: Tool-calling with the model

Streaming and Communication

Advanced communication features:
  • stream_init and stream_end: Manage audio streaming sessions
  • send_data_over_websocket: Send custom data over WebSocket
  • send_interrupt_signal: Interrupt ongoing output and hand control back to user input
  • send_agent_message_without_audio: Send a text reply without TTS
  • send_devkit_action: Trigger a Devkit action
  • send_devkit_mqtt_action: Trigger a DevKit MQTT action to control a smart device. See Controlling MQTT Devices for the full reference and an example Ability.

Context and Session Helpers

  • get_timezone: Read the current user’s timezone for local-time-aware behavior
  • get_token: Read the access token of a user-linked provider account ("google", "slack", "discord", "microsoft", "tesla"). See Reading Linked Account Tokens with get_token() for the full reference and a run() example with a fallback message when the account isn’t connected.
  • get_api_keys: Read custom API key values from Settings → API Keys by key name
  • get_full_message_history: Read full session message history for context-aware responses
  • update_personality_agent_prompt: Append context/instructions to the Agent personality prompt
  • create_key / update_key / delete_key: Manage structured key-value context storage
  • get_single_key / get_all_keys: Read one or all stored context entries

Recording and Local Audio

  • get_audio_recording: Load the latest user recording bytes
  • get_audio_recording_length: Duration in seconds for the latest recording
  • flush_audio_recording: Clear the current recording before a new capture
  • play_from_audio_file: Play an audio file stored in the Ability directory

Session Task Utilities (replace raw asyncio usage)

To ensure Abilities run within the agent’s managed lifecycle, avoid using raw asyncio helpers directly.
  • Use self.worker.session_tasks.sleep(seconds: float) instead of asyncio.sleep(...):
These helpers ensure proper cancellation, cleanup, and session scoping.

Background Daemon Entry Point (background.py)

Background daemons run automatically when a session starts. Use a separate background.py file with this entry signature:
Daemon rules:
  • Keep daemon logic inside a continuous while True loop.
  • Use await self.worker.session_tasks.sleep(...) between cycles.
  • Do not call resume_normal_flow() inside daemon loops.
  • Call await self.capability_worker.send_interrupt_signal() before daemon speech/audio.

Example 1: Basic Capability

This Ability creates a daily life advisor that:
  1. Asks the user for a problem: Initiates a conversation to gather user input.
  2. Provides advice: Offers a solution based on user input.
  3. Collects feedback: Captures user satisfaction with the advice.

Code

Key Functions

  • speak: Introduces the advisor and provides the solution.
  • user_response: Captures user input (e.g., their problem).
  • run_io_loop: Combines speaking the solution and listening for feedback.
  • resume_normal_flow: Resumes the agent’s default workflow after interaction.

Example 2: Weather Capability

This Ability integrates a weather API to fetch and share weather updates based on user-provided locations.

Code

Key Features

  • External API Call: Fetches real-time weather data.
  • Geolocation: Validates and processes user-provided locations.
  • Error Handling: Provides meaningful feedback for invalid inputs.

Allowed/Disallowed Libraries and Patterns

The following imports, keywords, and patterns are not allowed in Abilities. Use the safe alternatives.

Blocked Imports and Keywords

Guidance:
  • Avoid direct storage/infra access. Use platform-provided helpers within CapabilityWorker/worker or request an API if needed.
  • Use the provided logging (editor_logging_handler) instead of prints.
  • For files, prefer platform abstractions and per-user capability folders; ask for an approved helper if you need persistent storage.
  • For HTTP GET requests, always use the self.worker.session_tasks helpers (get, get_async, httpx_get, httpx_get_async, aiohttp_get_async). Never call requests, httpx, or aiohttp directly. See Making HTTP Requests.

Security Guidance

Avoid insecure or unsafe patterns such as runtime assert checks, exec() of dynamic code, binding servers to all interfaces, hardcoded secrets, swallowing exceptions, insecure deserialization (pickle/dill/shelve/marshal), weak hashes like MD5, or weak cipher modes (e.g., ECB). If you have a special case, request approval and an approved wrapper/utility.

Conclusion

Building Abilities in OpenHome empowers developers to create custom functionalities for AI agents. With the examples like the Basic Advisor and Weather Capability, you can:
  • Core Communication: Use speak, run_io_loop, and user_response for basic interactions.
  • Advanced Audio: Play custom audio files, and stream audio data.
  • Text Generation: Leverage multiple text-to-text options with history and system prompts.
  • Voice Customization: Use specific voice IDs for varied and engaging responses.
  • External APIs: Integrate third-party services for dynamic functionality.
The examples demonstrate everything from basic conversational flows to advanced audio processing and device control. The CapabilityWorker provides all the tools needed to create sophisticated, interactive Abilities.
Start creating innovative Abilities and push the boundaries of voice AI with OpenHome! 🎉
Note: For GET requests to third-party APIs, use self.worker.session_tasks.get() (see Making HTTP Requests). It takes the same arguments as the requests module’s get() and returns the same response. For other request types, use the requests module and avoid other libraries; if you need a library that isn’t available, you can request that we add it.

Example 3: Read/Write File (from example_main.py)

This is the simplest pattern for per-user storage.