▞ jazzdocsblogpersonas
github

Scheduled — unattended runs on a clock

How to have Jazz do something every morning without you being there.

For an always-on host without launchd or cron, switch to in-process mode and run jazz daemon. The daemon then checks due schedules once per minute and runs each latest due slot once. This mode is opt-in; launchd and cron remain the defaults on supported platforms.

jazz config set scheduler.mode in-process   # persists across restarts
# or, for a single run without touching config:
JAZZ_SCHEDULER=in-process jazz daemon

You can also flip this from Scheduler in jazz config. See Configuration → scheduler for both settings.

A scheduled run is a workflow handed to your OS scheduler. Jazz writes the launchd plist or crontab entry for you; from then on the run happens with no terminal, no TUI, and nobody to answer an approval prompt.

jazz workflow schedule daily-standup-prep
jazz workflow scheduled                    # confirm it's installed
jazz workflow history daily-standup-prep   # see what happened

What actually gets installed

flowchart TD
    WF["WORKFLOW.md<br/>schedule: 0 9 * * 1-5<br/>autoApprove: read-only"]
    CMD["jazz workflow schedule &lt;name&gt;"]

    WF --> CMD
    CMD --> OS{"Platform?"}

    OS -->|macOS| LD["launchd<br/>~/Library/LaunchAgents/<br/>StartCalendarInterval"]
    OS -->|Linux| CR["cron<br/>crontab entry"]

    LD --> RUN["jazz workflow run &lt;name&gt; --auto-approve"]
    CR --> RUN

    RUN --> POLICY["Tools gated by the<br/>workflow's autoApprove tier"]
    RUN --> LOGS["~/.jazz/logs/&lt;name&gt;.log<br/>~/.jazz/logs/&lt;name&gt;.error.log"]
    RUN --> HIST["Run history<br/>jazz workflow history"]

    classDef sched fill:#4f9d9d,stroke:#2f6d6d,color:#ffffff
    classDef out fill:#f9a03f,stroke:#b3541e,color:#1a1a1a
    class LD,CR,RUN sched
    class POLICY,LOGS,HIST out

Two platform details worth knowing up front:

  • launchd doesn’t do cron arithmetic. StartCalendarInterval accepts plain integers and wildcards only — no step values (*/15), no ranges (1-5), no lists (1,3,5). Jazz expands what it can into multiple entries and rejects what it can’t with an explicit error rather than silently scheduling the wrong thing.
  • Schedulers start with a minimal environment. launchd jobs don’t inherit your shell’s PATH, so Jazz writes an explicit one into the plist. If a workflow shells out to a tool installed somewhere unusual, use an absolute path.

The unattended shift

Scheduled runs differ from terminal runs in exactly one meaningful way: nobody is there to say yes. The workflow’s autoApprove: tier decides in advance, and anything above the tier is declined rather than queued.

autoApproveAuto-approvesGood for
falseNothingNot useful when scheduled — the run will stall out on the first gated tool
read-onlyReads, search, web, git status/log/diffDigests, reports, watchdogs
low-risk+ manage_todos, spawn_subagentDigests that track state
high-risk+ file writes, shell, git commit and pushAnything that writes files, or any skill that shells out

⚠️ low-risk is narrower than it sounds. In the built-in toolset it adds only manage_todos, update_work_state, and spawn_subagent. Email, calendar, and Obsidian are skills that shell out via execute_command (unknown), so a low-risk run cannot archive an email. Keep the tier low and allowlist the binary instead: {"autoApprovedCommands": ["himalaya"]} in ~/.jazz/config.json. See Tools reference.

Pick the lowest tier that lets the job finish. See Tools & approval for how tiers are assigned.


Missed runs and catch-up

Neither launchd nor cron fires a job whose slot passed while the machine was asleep. A 6 AM workflow on a laptop that wakes at 9 simply doesn’t run.

Jazz handles this explicitly rather than pretending otherwise:

jazz workflow catchup      # list what missed its slot, pick, run

Catch-up is age-bounded — by default a missed run older than 24 hours is not worth running anymore, and per-workflow maxCatchUpAge overrides that. A “good morning” briefing at 4 PM is noise, not recovery.

If the schedule genuinely can’t be missed, run Jazz somewhere always-on. Same commands, different host: see Airgapped & self-hosted, and Scheduling: behavior & limitations for the full treatment including keep-awake options and always-on device setups.


Debugging a scheduled run

jazz workflow scheduled                  # is it actually installed?
jazz workflow history <name>             # did it run? what did it do?
tail -f ~/.jazz/logs/<name>.log          # stdout
tail -f ~/.jazz/logs/<name>.error.log    # stderr
jazz workflow run <name> --auto-approve  # reproduce it by hand, same policy

That last command is the one to reach for first — it runs the identical code path the scheduler uses, in your terminal, where you can see it.

Most scheduled-run failures are one of three things: the machine was asleep (see above), a tool was declined by the policy tier, or a binary the workflow shells out to isn’t on the minimal PATH.


machine-readable: /docs/use-cases/scheduled.md · /llms.txt · /llms-full.txt