Until now, every Ability in OpenHome had one job: wait for a hotword, run, and exit. That model works well for a lot of things. But it made an entire category of features impossible. You could not set an alarm that fires on its own. You could not take notes while a conversation was happening. You could not watch for something in the background and speak up when it mattered.
That changes with this update. Abilities can now run background threads that start automatically when a session begins and stay alive for the entire call or persists
How It Works
Add a file called background.py to your ability folder. That is the entire interface. The platform detects it, starts it when the user connects to a Personality, and keeps it running in a separate thread for the whole session. No trigger word. No user action required.
Your existing abilities are not affected. Everything built with main.py alone works exactly the same way it did before. This update adds a new pattern without changing the old one.
What you can build now:
Alarms and reminders that fire at the right time without any re-prompt
Note-taking that runs silently while a conversation unfolds
Conversation monitoring and real-time summarization
Scheduled tasks and background polling against APIs or files
Ability Categories
This update also formalizes how abilities are categorized in the dashboard. When you create an ability, you now pick a category that tells the platform how it should behave.
Skill is the original pattern. The user says a trigger phrase, the ability runs, and it exits with resume_normal_flow(). Nothing about this has changed.
Brain Skills are triggered automatically by the Personality's brain when it needs more information to respond or needs to delegate an action. For example, fetching weather data for a location the user mentioned, or running a smart home command the LLM does not know the exact syntax for. Templates for Brain Skills are still being finalized.
Background Daemon is the new category covered in this post. The background thread starts on session connect and runs continuously. It works even when the Personality is in sleep mode.
What's Different About background.py
The call() signature takes an extra parameter: background_daemon_mode: bool. The CapabilityWorker constructor is identical to main.py. The main loop is a while True block that runs for the life of the session.
Two things worth knowing upfront. First, self.worker and self.background_daemon_mode must be assigned before you call CapabilityWorker(self). The constructor reads from self internally, so order matters. Second, you do not call resume_normal_flow() from a background daemon. It runs as an independent thread and does not block the main conversation.
Three new SDK methods ship with this update:
get_timezone()— Returns the user's timezone string. Use this any time your daemon needs to reason about local time.get_full_message_history()— Returns the full conversation transcript so far. Background daemons can use this to monitor what's being said without needing a trigger.send_interrupt_signal()— Stops whatever the Personality is currently saying. Call this beforespeak()orplay_audio()from a daemon, otherwise audio overlaps.
Coordinating main.py and background.py
When an ability uses both files, they communicate through shared file storage. Both read and write to the same user-scoped files. The alarm ability is the clearest example of this pattern.
The user says "set an alarm for 3pm." The main.py interactive flow parses the time, writes the alarm to a JSON file, confirms to the user, and calls resume_normal_flow(). background.py has been polling that file since the session started. When the target time arrives, it sends an interrupt signal, plays the alarm audio, speaks the notification, and updates the alarm status.
One practical note: write_file() appends rather than overwrites. For JSON, always delete the file first, then write the full object. Appending to JSON corrupts it.
Things to Keep in Mind
Use
session_tasks.sleep()for your poll interval. Neverasyncio.sleep(). Session tasks handle cleanup properly when the session ends.Poll intervals of 10 to 30 seconds are the right range for most use cases.
The JSON file your daemon is watching may not exist yet when the session starts. Check before reading.
Log with
editor_logging_handler. Background daemons run silently with no other output.Multiple daemons can run in parallel. Each is its own independent thread.
Where to Start
The alarm template is the best place to start. It shows exactly how main.py and background.py coordinate through shared file storage, and it covers the most common patterns you will run into.
Alarm template (Interactive Combined in the app or githhub): https://github.com/openhome-dev/abilities/tree/dev/templates/Alarm
Standalone template (in the app or github): https://github.com/openhome-dev/abilities/tree/dev/templates/Background
Questions? Check out our #dev-help channel in Discord to connect with our team real time!