Scheduler
daggle serve starts a long-running daemon that monitors DAG files and executes triggers automatically.
Starting and stopping
# Start the scheduler
daggle serve
# Start with REST API enabled
daggle serve --port 8787
# Stop the scheduler
daggle stopdaggle stop sends SIGTERM for graceful shutdown. The scheduler stops accepting new runs and waits up to 5 minutes for in-flight runs to finish.
What the scheduler does
- Scans all DAG sources (global directory, registered projects, cwd) for files with
trigger:blocks - Manages all trigger types: cron, file watcher, webhook, DAG completion, condition polling, git
- Polls every 30 seconds for both DAG-file changes (SHA-256 hashing) and due cron entries — the poll interval determines cron firing precision (see Firing precision)
- Immediate reload on
kill -HUP <pid>(SIGHUP) - Enforces overlap policy per DAG (
skiporcancel) - Limits to 4 concurrent DAG runs
- Writes a PID file at
~/.local/share/daggle/proc/scheduler.pid - Serves a read-only status dashboard when
--portis specified — open the port URL in a browser - Optionally serves the REST API when
--portis specified
Without the scheduler
daggle run works without daggle serve – the scheduler is only needed for automated triggers. You can use daggle purely as a manual DAG runner.
Multi-project scheduling
The scheduler watches DAGs from multiple sources:
- Global directory (
~/.config/daggle/dags/) - All registered projects (from
~/.config/daggle/projects.yaml) .daggle/in the current working directory
Register projects so the scheduler picks up their triggers:
cd my-project
daggle registerdaggle register and daggle unregister send SIGHUP to the running scheduler for immediate reload. DAG names must be unique across all sources.
Running as a system service
The scheduler doesn’t auto-start on host restart. Use your OS service manager for persistent operation.
macOS (launchd)
Create ~/Library/LaunchAgents/com.cynkra.daggle.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.cynkra.daggle</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/daggle</string>
<string>serve</string>
<string>--port</string>
<string>8787</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/Library/Frameworks/R.framework/Resources/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/daggle.stdout.log</string>
<key>StandardErrorPath</key>
<string>/tmp/daggle.stderr.log</string>
</dict>
</plist># Load (starts immediately and on login)
launchctl load ~/Library/LaunchAgents/com.cynkra.daggle.plist
# Unload
launchctl unload ~/Library/LaunchAgents/com.cynkra.daggle.plist
# Check status
launchctl list | grep daggleLinux (systemd, user-level)
Create ~/.config/systemd/user/daggle.service:
[Unit]
Description=daggle scheduler
After=network.target
[Service]
Type=simple
Environment=PATH=/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin
ExecStart=/usr/local/bin/daggle serve --port 8787
ExecStop=/usr/local/bin/daggle stop
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=default.target# Enable and start
systemctl --user daemon-reload
systemctl --user enable daggle
systemctl --user start daggle
# Check status
systemctl --user status daggle
# View logs
journalctl --user -u daggle -fFor system-level (all users), place the unit file in /etc/systemd/system/daggle.service, add a User= directive, and use systemctl without --user.
Troubleshooting: tool not found
A system service often starts with a minimal PATH (launchd uses /usr/bin:/bin:/usr/sbin:/sbin; systemd and cron are similarly bare), missing the /usr/local/bin, /opt/homebrew/bin, or R-framework directories where your shell finds Rscript and quarto. There are two distinct failure modes:
daggle can’t find Rscript / quarto / git
This is daggle’s own invocation failing. daggle auto-detects tool paths the first time you run any command from an interactive shell and saves them to ~/.config/daggle/config.yaml. To prime the config, run:
daggle doctorThis resolves and persists absolute paths for Rscript, quarto, git, and sh. Future scheduler runs (including as a system service) use the saved paths and no longer depend on PATH. If auto-detection picks the wrong binary, edit the config directly:
tools:
rscript: /usr/local/bin/Rscript
quarto: /opt/homebrew/bin/quartoQuarto reports “Unable to locate an installed version of R”
This one is subtler. daggle launches quarto fine (via the resolved absolute path above), but Quarto then spawns Rscript itself, using its own PATH lookup to run the R chunks in your .qmd. daggle’s tools: config has no influence over that nested lookup. Under a minimal daemon PATH you’ll see:
ERROR: Error executing 'Rscript': Failed to spawn 'Rscript': entity not found
Unable to locate an installed version of R.
even though your manual daggle run works (your interactive shell’s PATH includes R).
daggle mitigates this automatically: it prepends the directories of all resolved tools onto the PATH it hands to every step, hook, and deadline-hook subprocess. So as long as daggle doctor has resolved Rscript (step 1 above), Quarto inherits a PATH that contains it.
For belt-and-suspenders — or if you invoke other PATH-dependent tools from within R or Quarto — also set an explicit PATH on the service itself (EnvironmentVariables in the launchd plist, Environment= in the systemd unit, both shown above). That fixes PATH at the source for the daemon and everything it spawns.
See File Layout for details.