Scheduling — --daemon, schedules.yaml, cron
InsightWorker has a built-in scheduler. Drop a YAML file in your workspace, run insightworker --daemon, and the agent fires on a cadence.
File location
<workspace>/.insightworker/schedules.yaml
<workspace> is whatever directory you run insightworker from (or whatever WORKSPACE_DIR is set to). The daemon reads the file once on startup; restart to pick up edits.
Minimum example
schedules:
- name: hello
every: 1h
prompt: "Say hello and exit."
That's it. No required fields beyond name, a cadence (every: or cron:), and a payload (prompt: or app:).
Full schema
schedules:
- name: monitor-apple # required, unique
every: 1h # OR cron: "0 18 * * 1-5" (mutually exclusive)
prompt: | # OR app: <name>
Use perplexity_search recency=hour for "Apple Inc news".
Summarize material updates.
enabled: true # optional, default true
Cadence — two forms
every: — interval
| Spec | Meaning |
|---|---|
1h | every hour |
30m | every 30 minutes |
1h30m | every 90 minutes |
45s | every 45 seconds (30s minimum) |
2d | every 2 days |
@minutely | every minute |
@hourly | every hour |
@daily | every day at the same time-of-day relative to start |
@weekly | every 7 days |
cron: — full cron syntax
| Spec | Meaning |
|---|---|
0 18 * * 1-5 | weekdays at 6 PM |
0 7 * * 1 | every Monday at 7 AM |
*/15 9-17 * * 1-5 | every 15 min during business hours, weekdays |
0 0 1 * * | monthly on the 1st at midnight |
Cron uses the system's local timezone unless TZ env var is set. Timezone respects DST.
Payload — two forms
prompt: — free-form text passed directly to the agent.
- name: digest
cron: "0 18 * * 1-5"
prompt: "Pull JIRA active sprint, classify, email to team@acme.com."
app: — slug of a §15 App bundle under <workspace>/apps/<slug>/ (must contain at minimum app.yaml + workflow.yaml). The daemon invokes the bundle the same way insightworker app run <slug> does, so any deterministic action: shell steps run as scripts — no LLM in the loop.
- name: nightly-cleanup
every: 24h
app: cleanup-stale-dags
inputs: # passed through to /app run as --input k=v
target_dir: /var/airflow/dags
older_than_days: 30
action: shell step in workflow.yaml runs as a plain script — no LLM cost, no model drift, no timeouts on a stuck token stream. Reserve prompt:-based steps for judgement work; use shell for I/O and conversion.enabled: false
Skip a schedule without removing it. Daemon logs [DISABLED] next to it.
Running the daemon
insightworker --daemon
Output:
[daemon] loaded 1 schedule (1 enabled):
- monitor-apple cron "0 18 * * 1-5" prompt="Use perplexity_search…"
[daemon] logs: ~/.insightworker/logs/<schedule>.log
[daemon] press Ctrl+C to stop
[daemon] 17:00:00 monitor-apple=idle next=18:00
[daemon] 18:00:00 monitor-apple=running
[daemon] 18:01:00 monitor-apple=ok next=Tue 18:00
Heartbeat every 60 seconds shows current state and next fire time per schedule.
Logs
Each schedule writes to <workspace>/.insightworker/logs/<name>.log:
[2026-05-04T22:00:00.123Z] START
[2026-05-04T22:00:08.456Z] tool_call perplexity_search
[2026-05-04T22:00:11.789Z] OK Apple Inc reported strong fiscal Q2 2026 results...
tool_call lines log every tool invocation. OK / ERROR close out each tick.
Mutex / overlap protection
Each schedule has a per-schedule mutex. If a previous run is still going when the next tick fires, the tick is skipped (logged as SKIP (previous run still in flight)) and the schedule continues from the next interval. We intentionally do not queue runs; backpressure on a slow agent loop is a feature.
Missed runs
If the daemon was down at the scheduled time, missed runs are not caught up. When the daemon starts, it just waits for the next interval boundary. Keeps semantics simple — if you need catch-up, wrap the daemon with a process supervisor that restarts it quickly.
Production wrap
For unattended runs, wrap the daemon with a process supervisor:
macOS — launchd
<!-- ~/Library/LaunchAgents/com.verticalserve.insightworker.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.verticalserve.insightworker</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/insightworker</string>
<string>--daemon</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/you/projects/myworkspace</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/insightworker.out.log</string>
<key>StandardErrorPath</key>
<string>/tmp/insightworker.err.log</string>
</dict>
</plist>
launchctl load ~/Library/LaunchAgents/com.verticalserve.insightworker.plist
Linux — systemd user unit
# ~/.config/systemd/user/insightworker.service
[Unit]
Description=InsightWorker daemon
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/insightworker --daemon
WorkingDirectory=/home/you/projects/myworkspace
Restart=on-failure
[Install]
WantedBy=default.target
systemctl --user enable --now insightworker
Iterating safely with --dry-run
When a schedule sends emails (or makes other side-effectful tool calls you want to preview), run it manually with --dry-run:
insightworker schedule run --dry-run sprint-digest
In this mode:
send_emailwrites the body to/tmp/insightworker-preview-<timestamp>.htmland opens it in your default browser. The yellow banner shows the recipients and subject. No actual email is sent.- Other tools (
write_file,bash, etc.) still execute for real, so you can verify the full pipeline produces the expected output.
When the email format looks right, drop the flag:
insightworker schedule run sprint-digest # one real send
insightworker --daemon # leave it running
CLI reference
insightworker --daemon # run all enabled schedules
insightworker schedule list # show what's loaded
insightworker schedule run <name> # fire one immediately, real
insightworker schedule run --dry-run <name> # fire one immediately, preview emails
Common patterns
Daily morning digest, weekdays
- name: morning-digest
cron: "0 7 * * 1-5"
prompt: ...
Hourly during business hours
- name: market-watch
cron: "0 9-17 * * 1-5"
prompt: ...
Every 30 minutes, all the time
- name: poll-something
every: 30m
prompt: ...
Once a week
- name: renewal-radar
cron: "0 7 * * 1"
prompt: ...
Multiple schedules, mixed cadences
Just list them — there's no limit.
schedules:
- name: morning-digest
cron: "0 7 * * 1-5"
prompt: ...
- name: hourly-watch
cron: "0 9-17 * * 1-5"
prompt: ...
- name: weekly-renewals
cron: "0 7 * * 1"
prompt: ...
Source: docs/workflows-and-scheduling/overview.md in the public repo. Open a PR with corrections.
