Skip to main content
Every community PR is reviewed against this checklist. Use it before submitting to ensure a smooth review process.

Hard Requirements (Must Pass)

These are non-negotiable. Your PR will not be merged without meeting all of these:
1

PR targets dev branch

Base branch must be dev, not main
2

Files in community folder

All files must be in community/your-ability-name/, not in official/
3

Follows SDK pattern

main.py extends MatchingCapability and has both register_capability() and call() methods
4

README included

README.md present with description, suggested trigger words, and setup instructions
5

resume_normal_flow() called

Must be called on every exit path (success, errors, user cancellation)
6

No print() statements

Use self.worker.editor_logging_handler.info() instead
7

No blocked imports

No usage of blocked imports (see full list below)
8

No asyncio sleep/create_task

Use self.worker.session_tasks.sleep() and .create() instead
9

No hardcoded secrets

Use "YOUR_API_KEY_HERE" placeholders and document in README
10

Error handling present

All API calls and external operations wrapped in try/except blocks
Failing any hard requirement will block your PR from merging until fixed.

Blocked Imports and Keywords

These imports and patterns are not allowed for security, isolation, and multi-tenant safety:
BlockedWhyUse Instead
print()Bypasses structured loggingself.worker.editor_logging_handler
open() (raw)Unmanaged filesystem accessself.capability_worker.read_file() / write_file()
redisDirect datastore couplingPlatform-provided helpers
connection_managerBreaks isolation & multi-tenant safetyCapabilityWorker APIs
user_configCan leak/mutate global stateCapabilityWorker / worker APIs
exec()Insecure dynamic code executionNot allowed
eval()Insecure dynamic code executionNot allowed
pickleInsecure deserializationUse json instead
dillInsecure deserializationUse json instead
shelveInsecure deserializationUse json instead
marshalInsecure deserializationUse json instead
asyncio.sleep()Session management bypassself.worker.session_tasks.sleep()
asyncio.create_task()Session management bypassself.worker.session_tasks.create()
Full documentation: Blocked Imports and Keywords

Common Violations

Don’t do this:
print(f"User said: {user_input}")
Do this instead:
self.worker.editor_logging_handler.info(f"User said: {user_input}")
Don’t do this:
with open('data.txt', 'r') as f:
    data = f.read()
Do this instead:
data = await self.capability_worker.read_file('data.txt')
Don’t do this:
await asyncio.sleep(5)
Do this instead:
await self.worker.session_tasks.sleep(5)
Don’t do this:
asyncio.create_task(my_background_task())
Do this instead:
self.worker.session_tasks.create(my_background_task())

SDK Pattern Requirements

Your ability must follow the correct SDK structure:

Required Class Structure

class YourAbility(MatchingCapability):
    def __init__(self, worker, user, ability_data):
        super().__init__(worker, user, ability_data)
        self.capability_worker = None
    
    @staticmethod
    def register_capability():
        # Copy from template - reads config.json
        pass
    
    async def call(self):
        # Set up worker and capability_worker
        # Launch your async logic
        # MUST call resume_normal_flow() on every exit
        pass

resume_normal_flow() on Every Exit

This is one of the most common issues. Every path out of your ability must call resume_normal_flow():
async def call(self):
    self.capability_worker = CapabilityWorker(self.worker)
    
    try:
        await self.capability_worker.speak("Here's your result")
    except Exception as e:
        self.worker.editor_logging_handler.error(f"Error: {e}")
    finally:
        # ✓ Always called, even on error
        self.capability_worker.resume_normal_flow()
Missing resume_normal_flow() on any exit path will cause the AI to hang or behave unexpectedly after your ability runs.

Nice to Have (Strongly Encouraged)

These improve quality but won’t block your PR:
  • Spoken responses are short and natural (1-2 sentences per speak() call)
  • Exit/stop handling in looping abilities (“exit”, “stop”, “cancel”)
  • Inline comments explaining non-obvious logic
  • Follows patterns from docs/patterns.md
  • Graceful degradation when external services are unavailable
  • Helpful error messages for users when things go wrong
Remember: This is voice interaction, not text chat. Keep responses conversational and concise.

What We Don’t Review For

We explicitly don’t block PRs for:
  • Whether an external API will keep working forever (APIs change - that’s okay)
  • Whether it’s the “best” possible implementation (good enough is good enough)
  • Future SDK compatibility (we’ll help with migrations when needed)
  • Highly specific edge cases (we focus on common paths)

Common Issues and Fixes

Problem: Only calling resume_normal_flow() in the success caseFix: Use a finally block to ensure it’s always called:
try:
    # your logic
except Exception as e:
    # error handling
finally:
    self.capability_worker.resume_normal_flow()  # Always runs
Problem: api_key = "sk-abc123xyz"Fix: Use placeholders and document in README:
api_key = "YOUR_API_KEY_HERE"  # Replace with your actual key
Then in README:
## Setup
1. Get an API key from [service.com/api]
2. Replace `YOUR_API_KEY_HERE` in main.py with your key
Problem: Multi-paragraph responses that work in text but sound robotic when spokenFix: Break into shorter, conversational chunks:
# Don't do this
await self.capability_worker.speak(
    "Here's everything you need to know about this topic. "
    "First, let me explain the background... [3 more paragraphs]"
)

# Do this instead
await self.capability_worker.speak("Let me break this down for you.")
await self.capability_worker.speak("First, the key point is...")
response = await self.capability_worker.user_response(
    "Want to hear more details?"
)
Problem: API calls with no try/exceptFix: Always wrap external operations:
try:
    response = await external_api.fetch(query)
    if response.status == 200:
        # handle success
    else:
        await self.capability_worker.speak("Sorry, the service is unavailable")
except Exception as e:
    self.worker.editor_logging_handler.error(f"API error: {e}")
    await self.capability_worker.speak("I'm having trouble connecting")
Problem: Submitted to official/ or root directoryFix: All community contributions must be in:
community/your-ability-name/
├── main.py
├── README.md
└── requirements.txt (if needed)

Pre-Submission Self-Review

Before submitting, go through this quick checklist:
- [ ] Tested in Live Editor with multiple conversation paths
- [ ] Tested "exit"/"stop" commands if it's a loop
- [ ] Checked all exit paths call resume_normal_flow()
- [ ] Searched code for print() - replaced with editor_logging_handler
- [ ] Verified no blocked imports used
- [ ] README has suggested trigger words and setup instructions
- [ ] PR targets dev branch, not main
- [ ] Branch is up to date with upstream/dev

Getting Review Feedback

When you receive review feedback:
  1. Read the full review - Reviewers often explain why changes are needed
  2. Ask questions - If something is unclear, ask in the PR comments
  3. Make changes - Push new commits to your branch (PR updates automatically)
  4. Respond to comments - Let reviewers know what you changed
  5. Be patient - Reviews can take 3-5 business days
Maintainers want to merge your PR! Review feedback is to ensure quality and security, not to block contributions.

Build docs developers (and LLMs) love