InsightWorker Logo
  • contact@verticalserve.com
Docs / Apps & scheduling / Scheduling overview

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

SpecMeaning
1hevery hour
30mevery 30 minutes
1h30mevery 90 minutes
45severy 45 seconds (30s minimum)
2devery 2 days
@minutelyevery minute
@hourlyevery hour
@dailyevery day at the same time-of-day relative to start
@weeklyevery 7 days

cron: — full cron syntax

SpecMeaning
0 18 * * 1-5weekdays at 6 PM
0 7 * * 1every Monday at 7 AM
*/15 9-17 * * 1-5every 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
Deterministic apps recommended for scheduling. An 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_email writes the body to /tmp/insightworker-preview-<timestamp>.html and 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.