Thanks for wanting to contribute! This guide will get you from idea to merged PR as smoothly as possible. If you're building great abilities with real, high-impact use cases, also check out What Makes a Good Ability.
- Fork this repo
- Copy
templates/basic-template/tocommunity/your-ability-name/ - Build your Ability (edit
main.py) and README.md - Test it in the OpenHome Live Editor
- Open a Pull Request against
dev
That's it. We'll review it and get it merged.
We use a simplified Git Flow model. All contributions follow this flow:
ability/your-ability-name → dev → main
| Branch | Purpose | Who Merges |
|---|---|---|
main |
Stable, production-ready. Always deployable. | Maintainers only |
dev |
Integration and testing. All PRs target this branch. | Maintainers after review |
ability/* or add-* |
Your working branch for a single Ability or change. | You push; maintainers merge to dev |
Rules:
- Never open a PR directly to
main. All PRs must targetdev. devis merged tomainby maintainers after validation and testing.- Keep your ability branch up to date with
devbefore opening a PR (rebase or merge).
official/ ← Maintained by OpenHome. Don't submit PRs here.
community/ ← Your contributions go here.
templates/ ← Starting points. Copy one to get going.
docs/ ← Guides and API reference.
You submit to community/ only. The official/ folder is maintained by the OpenHome team. Exceptional community Abilities can be promoted to official over time.
Fork the repository on GitHub, then clone your fork:
git clone https://github.com/YOUR_USERNAME/abilities.git
cd abilitiesSet up the upstream remote to stay in sync with the original repo:
git remote add upstream https://github.com/OpenHome-dev/abilities.gitThen make sure you have the dev branch locally and start from it:
git fetch upstream
git checkout dev
git pull upstream devWhy upstream? This ensures you're always branching from the latest
devon the original repo, not a potentially staledevon your fork.
Branch off dev — not main:
git checkout -b add-your-ability-name devUse a descriptive branch name like add-dad-jokes, add-pomodoro-timer, or fix-weather-error-handling.
Choose the template closest to what you're building:
| Template | Use When |
|---|---|
templates/basic-template/ |
Simple ask → respond → done |
templates/api-template/ |
You're calling an external API |
templates/loop-template/ |
Interactive / multi-turn conversation |
Copy it:
cp -r templates/basic-template community/your-ability-nameEdit main.py. Every Ability must:
- Extend
MatchingCapability - Have
register_capability()(copy the boilerplate exactly from the template) - Have
call()that sets up worker + capability_worker and launches async logic - Call
self.capability_worker.resume_normal_flow()on every exit path - Handle errors with try/except
- Use
self.worker.editor_logging_handlerfor logging (neverprint())
Note: Trigger words are configured in the OpenHome dashboard, not in code. The
register_capabilityboilerplate reads a platform-managedconfig.jsonat runtime — you never create or edit that file.
- CapabilityWorker Reference — All available functions for ability creation (
speak,user_response,run_io_loop, file helpers, audio helpers, etc.)
| Blocked | Why | Use Instead |
|---|---|---|
print() |
Bypasses structured logging | self.worker.editor_logging_handler |
open() (raw) |
Unmanaged filesystem access | self.capability_worker.read_file() / write_file() |
redis |
Direct datastore coupling | Platform-provided helpers |
user_config |
Can leak/mutate global state | CapabilityWorker / worker APIs |
exec() |
Insecure dynamic code execution | ❌ Not allowed |
pickle/dill/shelve/marshal |
Insecure deserialization | ❌ Not allowed |
Full list → docs.openhome.com — Blocked Imports and Keywords
Create community/your-ability-name/README.md using this format:
# Your Ability Name


## What It Does
One or two sentences explaining what this Ability does.
## Suggested Trigger Words
- "trigger phrase one"
- "another trigger"
## Setup
- Any API keys needed and where to get them
- Any other setup steps
## How It Works
Brief description of the conversation flow.
## Example Conversation
> **User:** "trigger phrase one"
> **AI:** "Response example..."
> **User:** "follow up"
> **AI:** "Another response..."- Zip your Ability folder
- Go to app.openhome.com → Abilities → Add Custom Ability
- Upload and test in the Live Editor
- Set trigger words in the dashboard
- Make sure all exit paths work (say "stop", "exit", etc.)
Before you push, make sure your branch is current with the latest dev from upstream:
git fetch upstream
git rebase upstream/devIf you prefer merge over rebase:
git fetch upstream
git merge upstream/devResolve any conflicts, then continue.
git add community/your-ability-name/
git commit -m "Add your-ability-name community ability"
git push origin add-your-ability-nameOpen a Pull Request on GitHub:
- Base branch:
dev(notmain) - Compare branch:
add-your-ability-name - Fill out the PR template completely
⚠️ PRs targetingmainwill be closed and you'll be asked to re-open againstdev.
- Automated checks run —
validate-ability,path-check,security-scan, and linting must all pass. - A maintainer reviews — typically within 3–5 business days.
- Feedback round — you may be asked to make changes. Push additional commits to the same branch; the PR updates automatically.
- Merge to
dev— once approved, a maintainer squash-merges your PR intodev. - Promotion to
main— periodically, the maintainer team validatesdevand merges it intomain. Your Ability becomes available on the Marketplace at that point.
You don't need to do anything after step 4. The dev → main promotion is handled by maintainers.
Every community PR is reviewed for:
- PR targets the
devbranch (notmain) - Files are in
community/your-ability-name/(not inofficial/) -
main.pyfollows the SDK pattern (extendsMatchingCapability, hasregister_capability+call) -
README.mdis present with description, suggested trigger words, and setup instructions -
resume_normal_flow()is called on every exit path - No
print()statements (useeditor_logging_handler) - No blocked imports (
redis,user_config,open()) - No
asyncio.sleep()orasyncio.create_task()(usesession_taskshelpers) - No hardcoded API keys (use
"YOUR_API_KEY_HERE"placeholders) - Error handling on all API calls and external operations
- Spoken responses are short and natural (this is voice, not text)
- Exit/stop handling in any looping Ability
- Inline comments explaining non-obvious logic
- Follows patterns from
docs/patterns.md
- Whether an external API will keep working forever
- Whether it's the "best" way to accomplish the task
- Future SDK compatibility (we'll help with migrations)
| Don't | Do Instead |
|---|---|
Open a PR to main |
Target dev — always |
Branch off main |
Branch off dev |
Submit to official/ |
Submit to community/ |
Use print() |
Use self.worker.editor_logging_handler.info() |
Use asyncio.sleep() |
Use self.worker.session_tasks.sleep() |
Use asyncio.create_task() |
Use self.worker.session_tasks.create() |
| Hardcode API keys | Use placeholders + document in README |
Forget resume_normal_flow() |
Call it on every exit path — loops, breaks, errors |
| Write long spoken responses | Keep it short — 1-2 sentences per speak() call |
Import redis, user_config, etc. |
Use CapabilityWorker APIs |
Push directly to dev or main |
Push to your ability branch, open a PR |
Community Abilities that stand out can be promoted to Official status:
| Criteria | Threshold |
|---|---|
| Marketplace installs | 50+ |
| Stability | No critical bugs for 30+ days |
| Code quality | Clean, follows SDK patterns |
| Author responsiveness | Responds to issues |
| Usefulness | Fills a real gap |
When promoted:
- Ability moves from
community/toofficial/ - Gets the 🔷 Official badge on Marketplace
- OpenHome takes over maintenance (author stays credited)
- Author recognized in release notes
- Stuck on code? → Ask in Discord
- Found a bug in an Ability? → Open an issue
- Have an idea for an Ability? → Suggest it also visit Discussions to vote on ablities or suggest some good abilities ideas Discussion
- SDK question? → Check docs/OpenHome_SDK_Reference.md
By submitting a PR, you agree that your contribution is licensed under the MIT License. Original authorship is always credited in your Ability's README and in CONTRIBUTORS.md.