Plugins can declare cron tasks that run on a timer, independent of any conversation.
"capabilities": {
"schedule": [
{
"name": "Daily Digest",
"cron": "0 9 * * *",
"handler": "schedule/digest.py",
"description": "Morning email summary",
"enabled": true,
"chance": 100
}
]
}| Field | Type | Default | Description |
|---|---|---|---|
name |
string | Required | Display name |
cron |
string | 0 9 * * * |
Standard 5-field cron |
handler |
string | Required | Path to handler file |
description |
string | — | What the task does |
enabled |
bool | true | Whether it runs |
chance |
int | 100 | Percent chance to fire (1-100) |
Tasks appear in the Schedule UI and are removed when the plugin is disabled.
def run(event):
"""Called by continuity scheduler on cron match.
event dict:
system: VoiceChatSystem instance
config: System config module
task: Task definition dict
plugin_state: PluginState instance
"""
system = event["system"]
state = event["plugin_state"]
# Do work...
state.save("last_run", "2025-01-01")
return "Done" # Optional — logged to activityThe handler file must export a run(event) function. The return value is optional — if provided, it's logged to the schedule activity feed.
| Key | Type | Description |
|---|---|---|
system |
VoiceChatSystem | Full system access (TTS, STT, LLM, etc.) |
config |
module | System config (settings) |
task |
dict | The task definition from the manifest |
plugin_state |
PluginState | Persistent key-value store for this plugin |
Track state across scheduled runs:
def run(event):
state = event["plugin_state"]
last = state.get("last_check")
# ... do work ...
state.save("last_check", "2025-06-15T09:00:00")
state.save("run_count", state.get("run_count", 0) + 1)
return "Check complete"Scheduled tasks can trigger the AI to speak or respond:
def run(event):
system = event["system"]
if system and system.llm_chat:
response = system.llm_chat.chat("Give me a morning briefing")
if system.tts:
system.tts.speak(response)
return "Briefing delivered"