Skip to main content
Build your first OpenHome Ability from scratch and see it running in under 5 minutes.

What You’ll Need

1

Clone the repository and pick a template

Clone the abilities repository and copy the basic template:
git clone https://github.com/openhome-dev/abilities.git
cp -r abilities/templates/basic-template my-first-ability
cd my-first-ability
You now have one file to edit: main.py — your entire Ability logic lives here.
2

Understand the basic structure

Open main.py. Every Ability follows this pattern:
import json
from src.agent.capability import MatchingCapability
from src.main import AgentWorker
from src.agent.capability_worker import CapabilityWorker

class BasicTemplateCapability(MatchingCapability):
    worker: AgentWorker = None
    capability_worker: CapabilityWorker = None

    #{{register capability}}

    async def run(self):
        # Your logic goes here
        pass

    def call(self, worker: AgentWorker):
        # Initialize the worker and capability worker
        self.worker = worker
        self.capability_worker = CapabilityWorker(self)

        # Start the template functionality
        self.worker.session_tasks.create(self.run())
The #{{register capability}} comment is required boilerplate — copy it exactly. The platform uses this tag to automatically register your Ability. You never need to create or edit config.json manually.
3

Edit the run() method

The run() method contains your Ability’s logic. Here’s the complete basic template:
async def run(self):
    """
    The main function for the basic template capability.
    It greets the user, listens for their response, generates a reply, and asks for feedback.
    """

    # 1. SPEAK - Greet the user
    await self.capability_worker.speak("Hi! How can I help you today?")

    # 2. LISTEN - Get the user's response
    user_input = await self.capability_worker.user_response()

    # 3. RESPOND - Generate a response using the LLM
    response_prompt = f"Give a short, helpful response to: {user_input}"
    response = self.capability_worker.text_to_text_response(response_prompt)

    # Speak the response and ask for feedback
    response_with_feedback = response + " Are you satisfied with the response?"
    user_feedback = await self.capability_worker.run_io_loop(response_with_feedback)

    # Thank the user and exit
    await self.capability_worker.speak("Thank you for using the advisor. Goodbye!")

    # IMPORTANT: Always call this when done
    self.capability_worker.resume_normal_flow()

The Three Core Parts

Every Ability follows this pattern:
# Use speak() to send text-to-speech to the user
await self.capability_worker.speak("Hello! What can I do for you?")
Always call self.capability_worker.resume_normal_flow() when your Ability is done. Without it, the Agent goes silent and the user has to restart the conversation.
4

Zip and upload to OpenHome

  1. Zip your folder:
    cd ..
    zip -r my-first-ability.zip my-first-ability/
    
  2. Go to app.openhome.com
  3. Navigate to AbilitiesAdd Custom Ability
  4. Upload your .zip file
  5. Fill in the name and description
  6. Set your trigger words — the phrases that will activate your Ability (e.g., “help me”, “give advice”)
Trigger words are configured in the OpenHome dashboard when you upload an Ability, not in the code itself.
5

Test in the Live Editor

After uploading, click Live Editor on your Ability. Here you can:
  • Edit files directly in the browser
  • Click Start Live Test to test your Ability
  • Check trigger words
  • View logs in real time
  • Commit changes when satisfied
Test by saying one of your trigger phrases, and your Ability will activate.

Available SDK Methods

The CapabilityWorker class provides all I/O methods for your Ability:
MethodTypeDescription
speak(text)asyncText-to-speech using the Agent’s default voice
user_response()asyncWait for user input, returns string
run_io_loop(text)asyncSpeak + listen in one call
run_confirmation_loop(text)asyncAsk yes/no question, returns bool
text_to_text_response(prompt, history, system_prompt)syncGenerate LLM response (no await!)
play_audio(file_content)asyncPlay audio from bytes
play_from_audio_file(filename)asyncPlay audio file from Ability folder
resume_normal_flow()syncRequired - return control to Agent

Type Signatures

# Core imports
from src.agent.capability import MatchingCapability
from src.main import AgentWorker
from src.agent.capability_worker import CapabilityWorker

# Speaking
async def speak(text: str) -> None
async def text_to_speech(text: str, voice_id: str) -> None

# Listening
async def user_response() -> str
async def wait_for_complete_transcription() -> str

# Combined
async def run_io_loop(text: str) -> str
async def run_confirmation_loop(text: str) -> bool

# LLM (synchronous!)
def text_to_text_response(
    prompt_text: str,
    history: list = [],
    system_prompt: str = ""
) -> str

# Audio
async def play_audio(file_content: bytes) -> None
async def play_from_audio_file(file_name: str) -> None

# Flow control
def resume_normal_flow() -> None
async def send_interrupt_signal() -> None

# Logging (via self.worker)
self.worker.editor_logging_handler.info(message: str)
self.worker.editor_logging_handler.error(message: str)
self.worker.editor_logging_handler.warning(message: str)

Common Patterns

async def run(self):
    await self.capability_worker.speak("What would you like to know?")
    user_input = await self.capability_worker.user_response()
    response = self.capability_worker.text_to_text_response(
        f"Answer briefly: {user_input}"
    )
    await self.capability_worker.speak(response)
    self.capability_worker.resume_normal_flow()

Next Steps

Explore Templates

Start with pre-built patterns for API calls, loops, audio, and more

API Reference

Complete SDK documentation with all methods and examples

Pattern Examples

Copy-paste examples for common use cases

Contributing Guide

Share your Ability with the OpenHome community

Important Notes

Common Mistakes to Avoid:
  • Forgetting to call resume_normal_flow() (Agent gets stuck)
  • Using await on text_to_text_response() (it’s synchronous!)
  • Using print() instead of self.worker.editor_logging_handler.info()
  • Using asyncio.sleep() instead of self.worker.session_tasks.sleep()
Pro Tips:
  • Always log before and after API calls for easier debugging
  • Use the Live Editor to test changes without re-uploading
  • Check conversation history with get_full_message_history() for context-aware responses
  • Use persistent file storage to remember user preferences across sessions

Build docs developers (and LLMs) love