This is a background ability template that runs continuously in an endless loop. Unlike normal abilities that respond once and exit, background abilities stay active throughout the agent session, monitoring conditions and triggering actions automatically.
- Runs automatically — Background abilities are auto-triggered when your agent starts
- Endless loop — The
while Truekeeps the ability running continuously - Background operation — Runs silently alongside the normal conversation flow
- All CapabilityWorker functions available — Full SDK access within the background
| Normal Ability | Background Ability |
|---|---|
| User triggers with voice command | Auto-starts when agent initializes |
| Speak → Listen → Respond → Exit | Continuous loop: Monitor → Act → Sleep → Repeat |
Calls resume_normal_flow() to exit |
resume_normal_flow() never reached (intentional) |
| Session-specific | Background for entire agent session |
This basic background template demonstrates:
- Continuous monitoring — Endless
while Trueloop - Message history access — Reads last 10 messages from normal conversation
- Logging — Records activity via
editor_logging_handler - Sleep intervals — 20-second pause between checks
- Commented examples — Audio playback and speech capabilities
Agent Starts
↓
Background Auto-Triggers
↓
Enter while True Loop
↓
┌─────────────────────┐
│ Log "watching" │
│ Get last 10 messages│
│ Log each message │
│ Sleep 20 seconds │
└─────────┬───────────┘
│
└─→ Repeat Forever
1. Background Mode Initialization:
def call(self, worker: AgentWorker, background_daemon_mode: bool)
self.worker = worker
self.background_daemon_mode = background_daemon_mode
self.capability_worker = CapabilityWorker(self)
self.worker.session_tasks.create(self.first_function())background_daemon_mode=Trueindicates this is a background ability- Auto-creates async task that runs in background
2. Endless Loop:
async def first_function(self):
self.worker.editor_logging_handler.info("%s: Background Called" % time())
while True: # ← Never exits
self.worker.editor_logging_handler.info("%s: background watching" % time())
# Your monitoring logic here
...
await self.worker.session_tasks.sleep(20.0) # ← Sleep before next check- Loop runs indefinitely (no break condition)
- Checks conditions every 20 seconds
- Never calls
resume_normal_flow()— this is intentional
3. Access Message History:
message_history = self.capability_worker.get_full_message_history()[-10:]- Returns last 50 messages by default (template uses last 10)
- Each message is a dict with
roleandcontent - Provides context from normal conversation flow
4. Log Activity:
for message in message_history:
self.worker.editor_logging_handler.info(
"Role: %s, Message: %s" % (message.get("role", ""), message.get("content", ""))
)- Always use
editor_logging_handler(notprint) - Logs are visible in dashboard/logs
5. Sleep Between Checks:
await self.worker.session_tasks.sleep(20.0)- Critical: Use
session_tasks.sleep(), NEVERasyncio.sleep() - 20 seconds is template default (adjust to your needs)
6. Commented Examples:
# await self.capability_worker.speak("watching")
# await self.capability_worker.play_from_audio_file("alarm.mp3")- Shows how to trigger audio/speech from background
- Uncomment to test capabilities
message_history = self.capability_worker.get_full_message_history()Returns: List of up to 50 messages (most recent)
Message Format:
{
"role": "user", # or "assistant"
"content": "What's the weather today?"
}Monitor for specific keywords:
message_history = self.capability_worker.get_full_message_history()[-10:]
for message in message_history:
if message.get("role") == "user":
content = message.get("content", "").lower()
if "urgent" in content:
await self.capability_worker.speak("I noticed you said something urgent!")
await self.capability_worker.play_from_audio_file("alert.mp3")
break # Only alert onceTrack conversation patterns:
user_messages = [
msg for msg in message_history
if msg.get("role") == "user"
]
if len(user_messages) > 5:
# User has been very active
self.worker.editor_logging_handler.info("High conversation activity detected")await self.capability_worker.play_from_audio_file("alarm.mp3")Setup:
- Place audio file in your ability's folder (e.g.,
alarm.mp3) - Supported formats:
.mp3,.wav,.ogg - Call from anywhere in the background loop
Example background with audio alerts:
async def first_function(self):
alert_count = 0
while True:
message_history = self.capability_worker.get_full_message_history()[-10:]
# Check for alert conditions
for message in message_history:
if "emergency" in message.get("content", "").lower():
await self.capability_worker.play_from_audio_file("emergency.mp3")
alert_count += 1
break
self.worker.editor_logging_handler.info(f"Alerts fired: {alert_count}")
await self.worker.session_tasks.sleep(10.0)await self.capability_worker.speak("I'm monitoring in the background!")Use cases:
- Periodic reminders
- Alert announcements
- Status updates
Example periodic reminder:
async def first_function(self):
reminder_interval = 300 # 5 minutes
last_reminder = time()
while True:
now = time()
if now - last_reminder >= reminder_interval:
await self.capability_worker.speak("Reminder: Take a break and stretch!")
last_reminder = now
await self.worker.session_tasks.sleep(30.0)Backgrounds have full SDK access. You can use:
await self.capability_worker.speak("Message")
await self.capability_worker.user_response()
message_history = self.capability_worker.get_full_message_history()response = self.capability_worker.text_to_text_response("Analyze this...")await self.capability_worker.write_file("log.txt", data, in_ability_directory=False)
data = await self.capability_worker.read_file("settings.json", in_ability_directory=False)
exists = await self.capability_worker.check_if_file_exists("data.txt", in_ability_directory=False)
await self.capability_worker.delete_file("temp.txt", in_ability_directory=False)await self.capability_worker.play_from_audio_file("sound.mp3")
await self.capability_worker.play_audio(audio_bytes)await self.capability_worker.send_devkit_action("led_on")
await self.capability_worker.send_notification_to_ios("Title", "Body")Example background using multiple SDK functions:
async def first_function(self):
while True:
# Check for new data file
if await self.capability_worker.check_if_file_exists("trigger.txt", in_ability_directory=False):
# Read trigger data
data = await self.capability_worker.read_file("trigger.txt", in_ability_directory=False)
# Use LLM to analyze
analysis = self.capability_worker.text_to_text_response(
f"Summarize this in one sentence: {data}"
)
# Speak the summary
await self.capability_worker.speak(f"Alert: {analysis}")
# Play sound
await self.capability_worker.play_from_audio_file("notification.mp3")
# Clean up trigger file
await self.capability_worker.delete_file("trigger.txt", in_ability_directory=False)
await self.worker.session_tasks.sleep(10.0)Watch conversation for specific words/phrases:
async def first_function(self):
keywords = ["help", "urgent", "emergency", "important"]
while True:
message_history = self.capability_worker.get_full_message_history()[-5:]
for message in message_history:
if message.get("role") == "user":
content = message.get("content", "").lower()
if any(keyword in content for keyword in keywords):
await self.capability_worker.speak("I noticed you need attention!")
await self.capability_worker.play_from_audio_file("alert.mp3")
await self.worker.session_tasks.sleep(15.0)Detect user inactivity and prompt:
async def first_function(self):
last_user_message_time = time()
inactivity_threshold = 300 # 5 minutes
while True:
message_history = self.capability_worker.get_full_message_history()[-1:]
if message_history and message_history[0].get("role") == "user":
last_user_message_time = time()
now = time()
if now - last_user_message_time > inactivity_threshold:
await self.capability_worker.speak("Still here if you need anything!")
last_user_message_time = now # Reset to avoid spam
await self.worker.session_tasks.sleep(60.0)Monitor for new files and process them:
async def first_function(self):
processed_files = set()
while True:
# Check for new data file
if await self.capability_worker.check_if_file_exists("inbox.txt", in_ability_directory=False):
content = await self.capability_worker.read_file("inbox.txt", in_ability_directory=False)
# Create unique ID for this content
content_hash = hash(content)
if content_hash not in processed_files:
# Process new content
await self.capability_worker.speak(f"New data received: {content[:50]}")
await self.capability_worker.play_from_audio_file("notification.mp3")
processed_files.add(content_hash)
# Archive processed data
await self.capability_worker.write_file(
"processed.txt",
f"\n{content}",
False
)
await self.worker.session_tasks.sleep(5.0)Track conversation tone and alert if negative:
async def first_function(self):
while True:
message_history = self.capability_worker.get_full_message_history()[-10:]
# Combine recent messages
recent_conversation = "\n".join([
msg.get("content", "")
for msg in message_history
if msg.get("role") == "user"
])
# Analyze sentiment
if recent_conversation:
sentiment_prompt = f"""Analyze sentiment of this conversation: {recent_conversation}
Return only: "positive", "neutral", or "negative"
"""
sentiment = self.capability_worker.text_to_text_response(sentiment_prompt).lower().strip()
if "negative" in sentiment:
self.worker.editor_logging_handler.warning("Negative sentiment detected")
# Could trigger supportive response
await self.worker.session_tasks.sleep(30.0)# ✅ CORRECT
await self.worker.session_tasks.sleep(20.0)
# ❌ WRONG — Won't clean up properly
await asyncio.sleep(20.0)# ✅ GOOD — Reasonable intervals
await self.worker.session_tasks.sleep(5.0) # Quick monitoring
await self.worker.session_tasks.sleep(30.0) # Moderate checks
await self.worker.session_tasks.sleep(60.0) # Periodic polling
# ❌ BAD — Too frequent, wastes resources
await self.worker.session_tasks.sleep(0.1)
# ⚠️ CAUTION — Too infrequent, may miss events
await self.worker.session_tasks.sleep(600.0)Guidelines:
- Real-time monitoring: 1-5 seconds
- Keyword watching: 10-20 seconds
- Periodic reminders: 30-60 seconds
- Activity tracking: 30-120 seconds
# ✅ CORRECT
self.worker.editor_logging_handler.info("background started")
self.worker.editor_logging_handler.warning("Unusual activity")
self.worker.editor_logging_handler.error(f"Error: {e}")
# ❌ WRONG — Won't appear in logs
print("Background started")async def first_function(self):
while True:
try:
# Your monitoring logic
...
except Exception as e:
self.worker.editor_logging_handler.error(f"Background error: {e}")
await self.worker.session_tasks.sleep(5.0) # Brief pause before retry# ✅ GOOD — Get only what you need
message_history = self.capability_worker.get_full_message_history()[-10:]
# ⚠️ CAREFUL — Full history (50 messages) may be overkill
message_history = self.capability_worker.get_full_message_history()# Track what's been processed
processed_message_ids = set()
while True:
message_history = self.capability_worker.get_full_message_history()[-10:]
for message in message_history:
msg_id = hash(f"{message.get('role')}{message.get('content')}")
if msg_id not in processed_message_ids:
# Process new message
...
processed_message_ids.add(msg_id)
await self.worker.session_tasks.sleep(10.0)# ✅ GOOD — Quick checks
if "keyword" in content:
do_something()
# ❌ BAD — Heavy processing in hot loop
for message in message_history:
# Don't call slow APIs or heavy computation repeatedly
result = expensive_api_call(message) # This slows everything downExamples of background abilities:
- Conversation monitors — Track keywords, sentiment, activity
- Periodic reminders — "Take a break" every 30 minutes
- File processors — Watch for new files and auto-process
- Alert systems — Detect conditions and play audio alerts
- Activity trackers — Log user engagement patterns
- Sentiment analyzers — Monitor conversation tone
- Notification triggers — Send iOS notifications on events
- Data aggregators — Collect and summarize conversation data
Problem: Loop exits unexpectedly
Solutions:
- Add try-catch around entire loop
- Check logs for error messages
- Verify using
session_tasks.sleep()notasyncio.sleep()
Problem: play_from_audio_file() fails
Solutions:
- Verify file exists in ability directory
- Check filename matches exactly (case-sensitive)
- Try different format (.mp3 vs .wav)
Problem: Background consumes too many resources
Solutions:
- Increase sleep interval
- Reduce message history size (use [-5:] instead of [-50:])
- Avoid heavy processing in loop
Problem: get_full_message_history() returns empty list
Explanation: Normal — no messages yet in conversation
Solution: Check if list is empty before processing:
message_history = self.capability_worker.get_full_message_history()
if not message_history:
await self.worker.session_tasks.sleep(20.0)
continueNo Cross-Session Persistence:
- Background stops when agent session ends
- Cannot fire events after user logs out
- Not suitable for true background tasks (24/7 monitoring)
No Proactive Interruption:
- Cannot interrupt user mid-conversation
- Actions happen during natural pauses
- Must respect conversation flow
Resource Constraints:
- Should not run expensive operations continuously
- Sleep intervals prevent excessive resource use
- Keep processing lightweight
Prevent abuse by limiting frequency:
MIN_SLEEP = 5.0 # Minimum 5 seconds between checks
await self.worker.session_tasks.sleep(max(MIN_SLEEP, custom_interval))Don't log sensitive information:
# ❌ BAD — Logs sensitive content
self.worker.editor_logging_handler.info(f"Message: {message.get('content')}")
# ✅ GOOD — Log only metadata
self.worker.editor_logging_handler.info(f"Message count: {len(message_history)}")- Understand Backgrounds run automatically in background
- Know
while Truenever exits (intentional) - Recognize
resume_normal_flow()is unreachable - Understand session-scoped nature (not 24/7)
- Define what to monitor (messages, files, time, etc.)
- Set appropriate sleep interval (10-60 seconds typical)
- Add try-catch around entire loop
- Use
session_tasks.sleep(), notasyncio.sleep() - Use
editor_logging_handlerfor all logging - Test with various scenarios
- Access message history with
get_full_message_history() - Play audio files from ability directory
- Speak updates with
speak() - Write logs to files with file operations
- Track processed items to avoid duplicates
OpenHome Documentation:
If you build something with background mode:
- 🎉 Share your implementation
- 💡 Contribute improvements
- 🤝 Help others understand backgrounds
- 📝 Document your use case
- ✅ Backgrounds run automatically in endless loops
- ✅ Access full conversation history (last 50 messages)
- ✅ Play audio and use all CapabilityWorker functions
- ✅ Background monitoring during active agent sessions
- ❌ Not truly 24/7 background tasks (session-scoped)
- ❌ Never calls
resume_normal_flow()(by design)
Use backgrounds for real-time monitoring, periodic checks, and automated responses during active sessions! 🔄🚀