Background & scheduled tasks
SenClaw's two automation systems — scheduled tasks that reply in a chat, and background tasks that run unattended into run records — plus calendar reminders: creating, managing, lifecycle, safety limits and the REST APIs.
SenClaw has two systems for running work automatically, and they are fully separate — two DB tables, two schedulers, two UIs. Pick the right one:
| Scheduled task (recurring chat) | Background task | |
|---|---|---|
| Where it runs | In the schedule's own chat session — the agent replies to you there | No chat — runs unattended and writes a run record |
| Good for | "Summarise my messages every morning at 7", recurring reports you want to read in chat | Silent chores: collecting, checking, cleaning up — you read the history when you care |
| Created from | Chat ("schedule every morning at 7…"), web Space → "Định kỳ" (Recurring), desktop Plugins → Schedules | Chat, desktop Background, mobile "Tác vụ nền" — the Web UI has no Background screen yet |
| Output | Messages in the schedule's chat | A run record (status + result + transcript) in the Background screen |
Part C at the end covers Reminders (calendar-event alerts) — a third system, easily confused with the two above.
Part A — Scheduled tasks
A1. Creating one
From chat (easiest): "schedule a news summary every morning at 7", "every Monday at 9 remind me about the meeting", "run this once at 9 tonight". The agent uses the space_recurring_* toolset to create the schedule along with its own chat session.
From the Web UI — Space → "Định kỳ" (Recurring) tab → "Thêm lịch định kỳ" (Add a schedule):
- The prompt for the agent (required) plus an optional name.
- Frequency: Daily / Weekdays / Weekly (pick the day) / Monthly (day 1–28), plus the run time (machine-local). A collapsed "Advanced: Cron expression" section covers complex schedules.
- A run mode of Agent/DAG/Plan (see the caveat in A5).
From the desktop app — Plugins → Schedules (also: the New-chat dialog has a create-schedule toggle, and the dashboard shows an "Upcoming schedules" panel). The desktop form adds a Profile (the persona/skills/MCP of the schedule's chat) and a per-schedule Model. Frequency additionally offers once / once_delete — run once and keep the history, or run once and then delete the schedule itself.
Cron expressions (the Advanced section): five fields minute hour day month weekday, evaluated in the local timezone of the machine running the daemon. Examples: 0 7 * * * (7am daily), 0 9 * * 1-5 (9am Monday–Friday), 0 */6 * * * (every 6 hours).
A2. How a schedule runs
- The scheduler scans every 30 seconds; when a schedule is due, its prompt is pushed into the schedule's own chat session (jid
schedule:<id>) — the agent runs and replies there like any normal chat (using the Profile's persona and skills when one is set). - Reading the results: open the schedule's chat (the "Mở chat session" / Open chat session button on the schedule card), or "Lịch sử chạy" (Run history) in the detail drawer — every run with ✓ OK / ✗ error, duration and result.
- There is no OS notification when a run finishes — the output lives in the chat.
A3. Managing
- Run now ("Chạy ngay"): queues the schedule; it fires within ≤30 seconds.
- Pause / Resume: ⚠️ a schedule paused for a long time may fire immediately on resume (its next-run mark is still in the past) — for a clean resume, re-save the run time after switching it back on.
- Edit: prompt, name, schedule and model can all change — editing the schedule recomputes the next run in local time.
- Delete: removes the schedule together with its chat session — no undo (the UI asks first).
A4. Daemon downtime — missed runs
- Cron schedules do not catch up: missed marks are skipped and the next future mark runs.
- Interval schedules only shift a window: after long downtime one can fire several times back-to-back (once per tick) until it has caught up.
A5. Caveats & limits
- The DAG/Plan run modes are currently labels only — schedules always execute as a plain agent; the dropdown is stored but not applied.
- The "Last run" field on the schedule card is currently always empty (known bug) — read the latest outcome from the ✓/✗ icon and from Run history (that source is correct).
- Create schedules through chat (
space_recurring_*) or the UI. The rawsenclaw-scheduletoolset (schedule_task…) is internal plumbing: schedules it creates with a group folder are usually silently disabled by a migration at the next daemon restart (onlyschedule_*folders survive), and schedules created over MCP can fire their first run on UTC time (hours off from local) before later runs settle onto local time. - Day-of-month accepts only 1–28 (so short months can never skip).
- Tasks due at the same tick run sequentially — one slow task delays the ones behind it in that tick.
Part B — Background tasks
B1. The model
A background task runs with no chat: every run is a run record with a status, a result, a transcript and the tokens it spent. Built for silent recurring work — collecting data, periodic checks, cleanup.
A task is defined by:
| Setting | What it does |
|---|---|
| Schedule | Hourly / Daily / Weekly / Monthly / Every N minutes / Advanced cron / Once at a time / Manual only (runs only when you press Run now) |
| Prompt source | Static — a fixed prompt. Template — before each run, GET a Context URL (JSON) and substitute {{variables}} into the prompt; an empty JSON ⇒ the run auto-skips at zero token cost (made for "only act when there are new orders"). Generated — one LLM turn writes the real prompt from your description (double the tokens) |
🔔 Notify-only (notify) | No agent at all — at the scheduled time, push an OS notification with the title and body. Zero tokens, deterministic, a solid bare-bones reminder |
| Memory across runs | Fresh (default — every run starts clean) or Remembers (summaries of the 5 most recent runs are injected into context — for progress-tracking tasks) |
| If the previous run is still going | Skip (default — records a skipped run) / Wait / Cancel it |
| Catch up after downtime | On: run once to catch up when the daemon was off past a due mark (more than 5 minutes late); off: record a skipped run explaining why |
| Persona + Tools | Which persona runs it, and a cap on the tools the agent may use |
B2. The Background screen (desktop)
The Background nav item — "Tasks SenClaw runs by itself — no chat, no reply":
- Quick task: describe it in one sentence → the AI prefills the form → you review before anything is created (deliberately two steps).
- New task: the full form from B1.
- Left column: Runs / Success / Avg / Tokens stats over a 24h/7d/30d window, an "attention" band (tasks currently failing), status filters, and the task list.
- Right column: the prompt, Run history, and Pause/Resume · Run now · Edit · Delete · Cancel run.
- Clicking a run opens the Background session dialog: status, duration, trigger, turn count, tokens, "Prompt sent" (the real prompt after template/generator resolution), the Result / "Why it skipped" / Error, and a Cancel button while it runs. A running task streams live (the transcript arrives over WebSocket).
- Mobile (channel_app): the "Tác vụ nền" (Background tasks) drawer item. The Web UI has no Background screen yet — use desktop, mobile or REST.
Ordinary runs do not fire a notification when they finish (only 🔔 notify-only tasks do, and desktop shows the toast only while the window is unfocused) — follow along in Run history.
B3. Creating tasks from chat
The MCP server **senclaw-background** is present in every chat: background_create, background_list, background_get, background_pause, background_resume, background_delete, background_run_now, background_stats.
Three safety layers when the agent creates a task for you:
- The task's tools must be a subset of the tools that chat itself may use.
- An "outward-facing" task (a generated prompt source, or send / browser / post / mail / message tools…) is created PAUSED — you have to switch it on by hand in the Background screen.
- A quota of at most 20 tasks per owner.
Note: background_run_now from chat only queues the run (it starts within seconds); the Run now button in the UI starts it immediately and returns the run id.
B4. Lifecycle & self-protection
- Run statuses:
running · success · error · timeout · cancelled · skipped(skipped does not count as a failure). Task statuses:active · paused · completed · failed · cancelled. - Default limits: 3 concurrent runs system-wide, 1 per owner, a 5-minute timeout per run, at most 40 turns.
- Consecutive errors → progressive backoff (60s·2ⁿ⁻¹, capped at 1h); **5 consecutive errors flip the task to
failed(self-quarantine) and into the "attention" band — press Resume** to reset the counter and carry on. - A daemon restart mid-run marks the run
error "daemon stopped while this run was in flight". - Run history is kept for 30 days, then pruned automatically.
B5. Tuning (environment variables)
| Variable | Default |
|---|---|
SENCLAW_BACKGROUND_ENABLED | true |
SENCLAW_BACKGROUND_INTERVAL_SECS | 20 (floor 5) |
SENCLAW_BACKGROUND_MAX_CONCURRENT | 3 |
SENCLAW_BACKGROUND_PER_OWNER | 1 |
SENCLAW_BACKGROUND_TIMEOUT_SECS | 300 |
SENCLAW_BACKGROUND_MAX_TURNS | 40 |
SENCLAW_BACKGROUND_RETENTION_DAYS | 30 |
SENCLAW_BACKGROUND_MAX_TASKS_PER_OWNER | 20 |
SENCLAW_BACKGROUND_BACKOFF_MAX_SECS | 3600 |
B6. REST API (quick reference)
GET /api/background/tasks # ?status=&limit=&offset=… → {tasks, total}
POST /api/background/tasks # create (supports "paused": true)
POST /api/background/parse # one-sentence description → JSON spec (creates nothing)
GET /api/background/tasks/:id # {task, runs}
PATCH /api/background/tasks/:id # edit; app/system tasks can only change status
DELETE /api/background/tasks/:id # user tasks only; cancels a running run first
POST /api/background/tasks/:id/run-now # run immediately → run_id
GET /api/background/tasks/:id/runs # history (limit ≤500)
GET /api/background/runs/:id # {run, activity} — the transcript
POST /api/background/runs/:id/cancel
GET /api/background/stats?window=24h|7d|30dWebSocket events for clients: bg:run:started · bg:run:activity · bg:run:finished · bg:task:changed · notification (kind background).
Part C — Reminders (calendar-event alerts)
Different from both parts above: these are alerts for calendar events (space events).
- The daemon scans every 60 seconds: it fires ahead-of-time alerts according to each event's reminder settings, and an "event starting" alert at
start_atfor every event. - Desktop: an OS toast with Open / Delete. Open launches an interactive reminder dialog: chat with the agent about that event (all reminder conversations share one pinned chat named "Reminders"), with quick chips to snooze 10 minutes, move the event to tonight at 20:00, or delete the reminder (plus an "Open <app>" chip when the event belongs to a Space App) — and a push-to-talk mic: speak instead of typing, and spoken replies are read back with TTS.
- The web receives reminders over WebSocket but has no interactive dialog or voice yet.
- For a one-shot "remind me at 9 tonight", just ask the agent in chat — it can create an event with a reminder, a
onceschedule (A1), or a 🔔 notify-only background task (B1). All three arrive; they differ in where you receive the result.
In the SenClaw repo
Scheduled tasks live in src/scheduler/ (task_scheduler.rs, executor.rs), the agent-facing tools in src/mcp/space_server.rs (space_recurring_*) with skills/schedule/SKILL.md, the web UI in web/src/components/space/schedules/ and REST under /api/space/schedules* (src/gateway/ui_server/space.rs). Background tasks: the engine in src/background/ (scheduler + runner), the MCP server in src/mcp/background_server.rs, REST in src/gateway/ui_server/background.rs, the desktop UI in desktop_app/lib/features/background/ and the env knobs in src/config.rs. Reminders: src/scheduler/event_notifier.rs and the desktop dialog in desktop_app/lib/features/chat/reminder_interaction.dart.