Scheduled Tasks
Written by ddxfish on .
- Docs
- Plugin Author Guide
- Scheduled Tasks
- All docs ↓
You are reading the dev branch of the docs, which may describe unreleased changes. Read the main docs.
Plugins can declare cron tasks that run on a timer, independent of any conversation.
Manifest Declaration
"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 — also becomes the task's initial_message (the prompt sent to the AI when it fires) |
enabled |
bool | true | Whether it runs |
chance |
int | 100 | Percent chance to fire (1-100) |
time_setting |
string | — | Plugin-settings key holding "HH:MM" or a bare hour (9, "9", 9.0) — builds a daily cron from the user's setting. Falls back to cron if unset, and logs a warning if the value is unparsable |
enabled_setting |
string | — | Plugin-settings key holding a boolean — the task only runs while that setting is on, and users toggle it from the plugin's settings panel |
Tasks appear in the Triggers UI and are removed when the plugin is unloaded (disable, uninstall, or hot-reload).
Settings-linked schedules
time_setting / enabled_setting make a task user-tunable without code: declare a settings field in the manifest, name its key here, and the loader resolves it at registration and re-syncs the live task whenever the plugin's settings are saved. Edits made directly in the Triggers UI do NOT persist for plugin tasks — they're re-registered from the manifest (and settings) on every boot, so settings are the durable source of truth.
Bind the schedule instead of polling. If your feature already has an "hour" setting and an on/off switch, name them here rather than declaring */15 * * * * and re-checking the clock inside the handler: a bound task fires once, at the configured time, only while the feature is on — instead of waking 96 times a day to learn it's disabled. (Sub-hourly plugin tasks are treated as maintenance ticks and hidden from the AI's "upcoming events" summary, so a polling schedule isn't visible as an event either.)
Handler Contract
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 activity
The handler file must export a run(event) function. The return value is optional — if provided, it's logged to the schedule activity feed.
Event Fields
| 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 |
Using Plugin State
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"
Sending a Message via LLM
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"
Important: run() vs Hook Handlers
Schedule handlers use def run(event). Hook handlers do NOT — they use the hook point name (e.g., def pre_chat(event)) or def handle(event) as fallback. If you name a hook handler run(), it will silently fail to register.
| Context | Function Name |
|---|---|
| Schedule handler | def run(event) |
| Hook handler | def pre_chat(event), def prompt_inject(event), etc. |
| Hook fallback | def handle(event) |
Note on Webhook Tasks
Webhook tasks are created by users in the Triggers UI (Webhooks), not declared in plugin manifests. They trigger when an HTTP request hits /api/events/webhook/{path}. Every webhook task gets an auto-generated secret — callers must include it in the x-webhook-secret header.
For full webhook documentation, see DAEMONS-WEBHOOKS.md.
Reference for AI
PLUGIN SCHEDULED TASKS:
- Manifest:
capabilities.schedule= [{name (required), cron (5-field, default "0 9 "), handler (required, path relative to plugin dir), description (also becomes the task's initial_message), enabled (default true), chance (1-100, default 100), time_setting? (plugin-settings key holding "HH:MM" OR a bare hour -> daily cron; falls back tocron, warns on an unparsable value), enabled_setting? (plugin-settings key holding a bool — the task runs only while it's on)}]. Bind an existing hour/toggle setting instead of declaring a `/15` poll that guards the hour in the handler. - Tasks appear under Triggers (Scheduled); removed when the plugin unloads (disable/uninstall/hot-reload). Plugin tasks are re-registered from the manifest+settings on every boot — Triggers-UI edits to them do NOT persist; settings-linked fields are the durable knobs, and the loader re-syncs the live task on every plugin settings save.
- Handler contract: the file must export
def run(event). event = {"system": VoiceChatSystem, "config": config module, "task": task dict, "plugin_state": PluginState}. Optional return value is logged to the schedule activity feed. - PluginState:
state.get(key, default)/state.save(key, value)— persistent per-plugin key-value store. - Naming rule:
run(event)is ONLY for schedule handlers. Hook handlers use the hook-point name (def pre_chat(event),def prompt_inject(event), ...) ordef handle(event)as fallback — a hook handler namedrunsilently fails to register. - Webhook tasks are user-created in Triggers (Webhooks), not manifest-declared: HTTP hits
/api/events/webhook/{path}with the auto-generated secret in thex-webhook-secretheader. Full docs: docs/DAEMONS-WEBHOOKS.md.
Edit this page on GitHub · docs/plugin-author/schedule.md @ a5fa790 · synced
