Schedules — GUI cron for asmltr (prompt jobs + shell jobs)¶
Shipped
Store shared/schedules.js · tick + endpoints in core/src/scheduler.js + core/src/server.js
(/v2/schedules) · dashboard Schedules view. Prompt jobs run as managed turns (no session
leak); shell jobs run host commands. The morning brief is now a prompt job that calls asmltr notify.
A dashboard-managed scheduler: create jobs that fire on a schedule, each either an LLM prompt (runs a fresh asmltr turn on a reasoning engine) or a shell command / script. This is "cron with a GUI," and it's what powers the morning brief again + becomes the scheduler for asmltr notify.
Why (supersedes the wake-up cron + the dead "daemons" panel)¶
- The retired wake-up alarms were raw
claude -pin crontab → leaked sessions, hardcoded, invisible. - The dashboard "Persistent daemons" panel never populated (no reconcile source ever registers
kind:persistentrows) and belongs in System Info, not Live — remove it. - Schedules replaces both: user-defined, visible, engine-managed (no session leak), and general-purpose.
Job model¶
{ id, name, enabled, schedule, type, ...payload, last_run, next_run, last_status, last_output }
- schedule: friendly (time-of-day + weekdays) with an advanced raw-cron field; stored normalized.
- type: "prompt" → { engine?: default|claude|codex|gemini, prompt, session?: "new"|"<conversation_key>" }.
Runs through the core pipeline as a real turn (NOT claude -p) → no session leak. The prompt can tell
the assistant to do something, e.g. use asmltr notify to deliver a morning brief.
- type: "shell" → { command | script_path, cwd?, timeout_s? }. Runs on the host as the asmltr user
(same power as the crontab it replaces). Output captured → last_output for the GUI.
Scheduler engine¶
A tick in the core (setInterval ~30s; no new dep) evaluates due jobs against next_run; on fire it
dispatches a prompt turn or spawns the shell command, records status/output, computes the next run.
Missed-while-down jobs: run once on next tick if overdue (configurable). Concurrency-guarded per job.
Storage + API + GUI¶
- Store:
~/.asmltr/schedules.json(gitignored) via ashared/schedules.jsmodule. - Core:
GET/POST/PATCH/DELETE /v2/schedules,POST /v2/schedules/:id/run(run-now). - GUI: a Schedules view — list (name, next/last run, last status, enable toggle, run-now, edit, delete) + an add/edit form (name, schedule picker, type, prompt+engine OR command/script). Manifest-driven where possible so the TUI gets it too.
Security¶
Shell jobs execute host commands — gate behind the dashboard's owner-only 2FA (same trust as crontab). Prompt jobs run the engine (already trusted). Surface the shell-exec power clearly in the add form.
Morning brief, rebuilt¶
A prompt job at 08:00 weekdays: "Write a warm ~25-word wake-up and deliver it with asmltr notify
(read-aloud)." → engine runs → asmltr notify → phone reads it over BT (the ladder falls back to push
/ text if the device isn't reachable).
Decisions (as shipped)¶
- Schedule UX — friendly time+weekday picker with an advanced raw-cron field. Both compile to a
standard 5-field cron string that everything downstream evaluates uniformly (
shared/schedules.js). - Scheduler home — a core
setInterval(~30s)tick (core/src/scheduler.js, started fromserver.js); no new process, no new dep. Per-job concurrency guard; overdue jobs fire once on the next tick after downtime. - Shell jobs — arbitrary command/script allowed (cron parity), gated behind the owner-only dashboard. Prompt jobs run at operator trust via an internal, moderation-bypassed Scheduler principal, seeded idempotently on start.