| name | scheduler |
| description | Schedules and runs cron jobs and the nanika publishing pipeline via scheduler CLI. Use when scheduling jobs, managing cron tasks, running the daemon, checking job history, or setting up the daily publishing pipeline. |
| allowed-tools | Bash(scheduler:*) |
| argument-hint | [job-id] |
| keywords | scheduler, cron, jobs, daemon, pipeline, automation, history |
| category | productivity |
| version | 1.1.0 |
Scheduler — Cron Job Runner + Publishing Pipeline
Manages recurring shell jobs with cron expressions. Powers the nanika publishing pipeline. All state is persisted in SQLite. The default DB path is ~/.alluka/scheduler/scheduler.db; the active path is set by db_path in ~/.alluka/scheduler/config (run scheduler configure show to check).
When to Use
- User wants to schedule a recurring shell command or script
- User wants to run the daemon that executes scheduled jobs
- User wants to set up or modify the nanika publishing pipeline
- User asks about cron jobs, periodic tasks, or automation
- User wants to view execution history or check job logs
- User wants to verify their scheduler setup is working
- User wants to enable, disable, or run a job immediately
Commands
Doctor
scheduler doctor
scheduler doctor --json
Daemon
The daemon polls every 30 seconds and executes any job whose next_run_at has passed. It writes a JSON event to ~/.alluka/events/scheduler.jsonl after every job run.
scheduler daemon
scheduler daemon --notify
scheduler daemon --once
scheduler daemon --stop
Production tip: Run the daemon in the background with your process supervisor of choice. A minimal launchd approach:
scheduler daemon >> ~/.alluka/logs/scheduler.log 2>&1 &
Init — Scheduler Database
Initializes the scheduler database. Scheduler itself does NOT register any
cross-plugin cron jobs — each plugin owns its own jobs via its own init
command. For example, shu propose --init (from the nen plugin) registers
the nen self-improvement loop jobs (propose-remediations, dispatch-approved,
close-sweep, evaluate-weekly).
scheduler init
scheduler init --force
If you want the nanika publishing pipeline (scout gather, engage scan + draft, weekly scout intel), install the scout and engage plugins and
add the jobs manually via scheduler jobs add, or use whatever init
command those plugins ship.
After running scheduler init, start the daemon to activate any jobs
registered by other plugins:
scheduler init
shu propose --init
scheduler daemon
Jobs
scheduler jobs
scheduler jobs add --name "daily-backup" --cron "0 2 * * *" --command "tar czf /tmp/backup.tgz ~/docs"
scheduler jobs add --name "health-check" --cron "*/5 * * * *" --command "curl -s localhost:8080/health"
scheduler jobs add --name "slow-job" --cron "0 1 * * *" --command "heavy-script.sh" --timeout 3600
scheduler jobs enable <job-id>
scheduler jobs disable <job-id>
scheduler jobs remove <job-id>
Run
Run a job immediately, ignoring its schedule.
scheduler run <job-id>
Logs
View execution output for a specific job.
scheduler logs <job-id>
scheduler logs <job-id> --limit 10
Status
Overview of daemon state, job counts, and next scheduled run.
scheduler status
scheduler status --json
History
Shows the most recent job run events from ~/.alluka/events/scheduler.jsonl, newest first.
scheduler history
scheduler history --limit 20
Output columns: TIME | STATUS | JOB | EXIT | DURATION | STDERR
STATUS is ok for exit code 0, FAILED otherwise
STDERR is truncated to 40 chars for readability
Query
JSON-native subcommands for agent use.
scheduler query status --json
scheduler query items --json
scheduler query action run <job-id> --json
scheduler query action enable <job-id> --json
scheduler query action disable <job-id> --json
scheduler query actions --json
Plugin Ownership of Cron Jobs
Scheduler provides the execution infrastructure (daemon, cron parsing, DB,
history). It does not ship default jobs for other plugins. Each plugin
that wants recurring behavior registers its own jobs via its own init
command. This keeps cross-plugin dependencies explicit and prevents
install-set drift (if you don't have engage installed, nothing tries to
run engage scan).
Example — the nen self-improvement loop registers four jobs via its
own init command:
shu propose --init
Registers:
| Job | Schedule | Command |
|---|
propose-remediations | every 4h | shu propose --json |
dispatch-approved | every 15m | shu dispatch --max-concurrent 1 --max-per-hour 6 |
close-sweep | every 15m | shu close --sweep --json |
evaluate-weekly | Mondays 10am | ko evaluate-proposals --json |
Adding your own jobs — the manual path:
scheduler jobs add --name "daily-backup" --cron "0 2 * * *" --command "tar czf /tmp/backup.tgz ~/docs"
scheduler jobs add --name "health-check" --cron "*/5 * * * *" --command "curl -s localhost:8080/health"
Starting the daemon:
scheduler init
shu propose --init
scheduler daemon
scheduler jobs
scheduler history
Configuration
Config file: ~/.alluka/scheduler/config (key=value format)
db_path = /Users/<you>/.alluka/scheduler/scheduler.db
log_level = info
shell = /bin/sh
max_concurrent = 4
Run scheduler configure to create or update it interactively. All keys are optional — missing keys use the defaults above.
DB path note: Installations created with scheduler-cli configure (the old binary name) may have db_path pointing to the legacy location ~/.scheduler/scheduler.db. Run scheduler configure show to see the active path. To migrate to the canonical location, update db_path to ~/.alluka/scheduler/scheduler.db and copy the database file.
Cron Expression Reference
Scheduler supports three cron formats: standard 5-field expressions, descriptors, and natural-language phrases.
Standard 5-Field Cron
┌─── minute (0–59)
│ ┌─── hour (0–23)
│ │ ┌─── day of month (1–31)
│ │ │ ┌─── month (1–12)
│ │ │ │ ┌─── day of week (0–7, 0=Sunday, 1=Monday)
* * * * *
*/5 * * * * every 5 minutes
0 * * * * every hour at :00
0 8 * * * daily at 8 AM
0 10 * * 1 Monday at 10 AM
0 2 * * 0 weekly on Sunday at 2 AM
Cron Descriptors
Short aliases for common schedules:
@hourly every hour at minute 0
@daily every day at midnight
@weekly every Sunday at midnight
@monthly first day of every month at midnight
@yearly/@annually January 1 at midnight
Natural-Language Cron Phrases
Scheduler accepts human-readable phrases that are converted to standard cron expressions:
Every N time units
every 5m every 5 minutes (→ */5 * * * *)
every 2h every 2 hours (→ */2 * * * *)
every 30 minutes every 30 minutes (→ */30 * * * *)
every 4 hours every 4 hours (→ */4 * * * *)
every 1 day every day at midnight (→ 0 0 * * *)
Daily at a specific time
daily at 9am every day at 9:00 AM (→ 0 9 * * *)
daily at 9:30am every day at 9:30 AM (→ 30 9 * * *)
daily at 2pm every day at 2:00 PM (→ 0 14 * * *)
daily at 2:30 PM every day at 2:30 PM (→ 30 14 * * *)
daily at 12:00 AM every day at midnight (→ 0 0 * * *)
daily at 12:00 PM every day at noon (→ 0 12 * * *)
Weekly on a specific day and time
weekly on monday at 9am Monday at 9:00 AM (→ 0 9 * * 1)
weekly on Friday at 6pm Friday at 6:00 PM (→ 0 18 * * 5)
weekly on sunday at 6:30 PM Sunday at 6:30 PM (→ 30 18 * * 0)
weekly on Wed at 2:00 AM Wednesday at 2:00 AM (→ 0 2 * * 3)
Examples
scheduler jobs add --name "morning-digest" --cron "daily at 9am" --command "scout gather && engage draft"
scheduler jobs add --name "backup" --cron "weekly on monday at 2am" --command "backup.sh"
scheduler jobs add --name "health-check" --cron "every 30m" --command "curl localhost:8080/health"
scheduler jobs add --name "hourly-task" --cron "@hourly" --command "check.sh"
scheduler jobs add --name "precise" --cron "*/15 * * * *" --command "task.sh"
Event Log
Every job run appends a JSON line to ~/.alluka/events/scheduler.jsonl:
{"type":"schedule.completed","job_id":1,"job_name":"daily-scout","command":"scout gather","duration_ms":4201,"exit_code":0,"ts":"2026-03-25T08:00:04Z"}
{"type":"schedule.failed","job_id":2,"job_name":"daily-engage","command":"engage scan && engage draft --reschedule-post","duration_ms":312,"exit_code":1,"stderr":"connection refused","ts":"2026-03-25T09:00:00Z"}
Use scheduler history to view this log in a readable tabular format, or tail it directly:
tail -f ~/.alluka/events/scheduler.jsonl | jq .
Examples
User: "schedule a daily email digest at 8am"
Action: scheduler jobs add --name "daily-digest" --cron "0 8 * * *" --command "gmail inbox --unread --json"
User: "check what scheduled jobs are running"
Action: scheduler jobs
User: "run the nightly backup job now"
Action: scheduler query action run <job-id>
User: "show the scheduler logs"
Action: scheduler logs
Build
cd plugins/scheduler
go build -ldflags "-s -w" -o bin/scheduler ./cmd/scheduler-cli
ln -sf $(pwd)/bin/scheduler ~/.alluka/bin/scheduler