Automation
Botholomew no longer ships an OS-level watchdog. Earlier versions installed a launchd plist or a systemd user service that kept a single daemon alive; we dropped that because the install was heavy and opaque. Instead, you choose how and when workers run.
This doc covers the common patterns. None of them are installed for you — you copy the recipe that matches your needs.
The shape of a scheduled run
botholomew worker run (one-shot, default mode) does one thing and exits:
- Register a worker row in the DB.
- Start a heartbeat
setIntervalso other workers know it's alive. - Evaluate any due schedules and enqueue their tasks.
- Claim the next eligible pending task.
- Run the LLM tool loop until the task is complete / failed / waiting.
- Mark the worker
stoppedand exit.
If there's no eligible task, the worker exits immediately — safe to run on a tight cron without overlapping concerns.
Two things make this safe to run concurrently with other workers:
- Task claims are atomic (
UPDATE ... WHERE status='pending' RETURNING *). - Schedule evaluation is gated by an atomic claim + a minimum-interval window, so two workers can't enqueue duplicate task batches from the same schedule.
See architecture.md.
Pattern: cron (recommended)
One line. Put this in crontab -e:
# Every 5 minutes, advance one task in ~/projects/inbox-bot
*/5 * * * * cd ~/projects/inbox-bot && /usr/local/bin/botholomew worker run >> .botholomew/cron.log 2>&1- Fire as often as you like; each fire is one task at most.
- Overlap is fine. If two fires start close together, one will claim the task and the other will exit without work.
- Resolve
botholomewwith a full path. cron'sPATHis minimal;which botholomewfrom your shell gives you the right answer. - Redirect to
.botholomew/cron.log(or anywhere you like) so you can see what happened if a run misbehaves.
More aggressive variants
If you have a backlog you want drained quickly, spawn background workers every minute:
* * * * * cd ~/projects/inbox-bot && botholomew worker start >> .botholomew/cron.log 2>&1Each worker still exits after one task; they just overlap freely. A crashed worker is reaped within ~60s and its task goes back into the queue.
Pattern: a single long-running worker
Simplest UX for a workstation that's on most of the day: open a tmux or screen pane and run a persist worker in it.
tmux new -s botholomew
botholomew worker run --persist
# Ctrl+B, D to detachIt'll tick every tick_interval_seconds (default 300) when the queue is empty and back-to-back while there's work. Ctrl+C to stop cleanly (the shutdown handler marks the worker stopped).
No cron, no watchdog, no systemd — and when you want to upgrade, you stop the pane and start it again.
Pattern: launchd (macOS, optional)
If you want Botholomew to survive logouts and start on boot without cron or tmux, a minimal ~/Library/LaunchAgents/com.example.botholomew.plist looks like:
<?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.example.botholomew</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/botholomew</string>
<string>--dir</string>
<string>/Users/you/projects/inbox-bot</string>
<string>worker</string>
<string>run</string>
</array>
<key>StartInterval</key><integer>300</integer>
<key>StandardOutPath</key>
<string>/Users/you/projects/inbox-bot/.botholomew/launchd.log</string>
<key>StandardErrorPath</key>
<string>/Users/you/projects/inbox-bot/.botholomew/launchd.log</string>
</dict>
</plist>Then:
launchctl load ~/Library/LaunchAgents/com.example.botholomew.plistThis runs worker run every 300s. You own the plist; Botholomew doesn't touch it. If Botholomew lives in a folder launchd can't read (e.g., under ~/Desktop on newer macOS), grant Full Disk Access to whichever program invokes the binary.
Pattern: systemd user timer (Linux, optional)
Two files in ~/.config/systemd/user/:
botholomew-inbox.service:
[Unit]
Description=Run one Botholomew worker tick
[Service]
Type=oneshot
WorkingDirectory=/home/you/projects/inbox-bot
ExecStart=/usr/local/bin/botholomew worker run
StandardOutput=append:/home/you/projects/inbox-bot/.botholomew/systemd.log
StandardError=append:/home/you/projects/inbox-bot/.botholomew/systemd.logbotholomew-inbox.timer:
[Unit]
Description=Run Botholomew every 5 minutes
[Timer]
OnBootSec=60
OnUnitActiveSec=5min
Unit=botholomew-inbox.service
[Install]
WantedBy=timers.targetEnable with:
systemctl --user daemon-reload
systemctl --user enable --now botholomew-inbox.timerSame concurrency story as cron: each fire is one task at most.
Troubleshooting
- "Nothing's happening."
botholomew worker listshows every worker the DB has ever seen. Filter with--status runningto see who's alive right now. If you see zero running and a non-empty queue, spawn one:botholomew worker start --persist. - "I see dead workers piling up." Reaped crashes stay in the table as forensic evidence; only clean exits (
status='stopped') get auto-pruned (afterworker_stopped_retention_seconds, default 1 hour). If dead rows are bothering you,DELETE FROM workers WHERE status='dead'clears them safely.botholomew worker list --status deadshows the list first. - "Cron runs aren't firing." Check
grep CRON /var/log/syslog(Linux) orlog show --predicate 'process == "cron"'(macOS). Common causes: minimalPATH, or a relative path tobotholomew. - "Two workers keep claiming the same task." They don't — by design. The
claimed_bycolumn is stamped by the atomic UPDATE, so only one wins. If you're seeing duplicate output, it's because the task was re-run after its worker was reaped — checkworker list --status dead. - "The log is getting huge." Rotate it yourself (logrotate, newsyslog). Botholomew used to do this inside the old watchdog; it no longer does.
Why no built-in watchdog?
Feedback from early users: installing launchctl/systemctl entries was heavy, platform-specific, and opaque — and because it was installed per project, it accumulated in ~/Library/LaunchAgents/ faster than users expected. Replacing it with "run worker run however you already run things" makes the footprint predictable and the failure modes familiar. If you do want boot-time survival, the templates above give you what the old watchdog provided, without the magic.