Crons as a Heartbeat
Before this, read:
- Logging & instrumentation — crons without logs are blind crons
A single scheduled job is a task. 155–160 of them are a heartbeat. The distinction matters because a heartbeat is not a list of automations — it’s a commitment that the whole system runs continuously, even when no one is actively talking to it.
What “heartbeat” means here
Section titled “What “heartbeat” means here”Every hour, at staggered times across the hour, 8 domain agents wake up and write a report.md. The school agent at :03. The family agent at :08. The health agent at :12. They read their domain state, process any pending items, and produce a structured output — not a chat response, not a question back to JD. A file.
Those reports are what the morning briefing reads. They’re what the Nerve Center cockpit surfaces. They’re what the CEO consults when making routing decisions. The domain agents aren’t interactive all day — they pulse once an hour and produce a fact.
That pulse — the fact that work is happening on a schedule regardless of JD’s attention — is what makes this an autonomous system rather than a chatbot JD has to poke.
As of mid-June 2026, the crontab holds approximately 155–160 active jobs across ~87 scripts (ARCHITECT-BRIEF count, 2026-06-10). These range from the 8 hourly domain heartbeats to the 2-minute merch fulfillment drainer that polls for new Stripe orders.
The crontab as a single source of truth
Section titled “The crontab as a single source of truth”All scheduled work lives in one crontab, managed by one user account on the Mac Mini. No LaunchAgents for cron work (LaunchAgents are for persistent processes and session-scoped daemons only). This means:
# See everything that's scheduledcrontab -l
# Count active jobscrontab -l | grep -v '^#' | grep -v '^$' | wc -lThat one-liner shows you the system’s entire pulse. If a job is not in the crontab, it does not run.
Serializing crontab edits
Section titled “Serializing crontab edits”The crontab is a shared resource. Two agents editing it concurrently can produce undefined output. The rule: only one session edits the crontab at a time, via:
# Safe pattern: write to file, validate, then installcrontab -l > /tmp/crontab.bak # always back up first# ... add or change lines in the file ...crontab /tmp/crontab.new # install atomicallyNever pipe inline Python or shell to crontab -. In June 2026, an inline Python SyntaxError piped to crontab - wiped all 148 jobs by feeding empty stdin. The daily auto-snapshot cron (added the same day) bounds future loss to under 24 hours by running crontab -l > ~/.crontab-snapshot/$(date +%Y-%m-%d).txt every morning at 6am.
The staggered offset pattern
Section titled “The staggered offset pattern”All 8 domain heartbeats run hourly, but not at the same minute:
3 * * * * school heartbeat8 * * * * family heartbeat12 * * * * health heartbeat17 * * * * growth heartbeat22 * * * * ai-foundry heartbeat32 * * * * siemens heartbeat42 * * * * consulting heartbeat52 * * * * life-ops heartbeatStaggering is not just politeness — it prevents the Mac Mini from hitting peak CPU load on 8 simultaneous LLM calls. A 49-minute spread across the hour is comfortable; 8 calls at :00 would collide with other hourly jobs and risk timeout cascades.
The same principle applies to any cluster of related jobs: give each a different minute offset within its cadence bucket.
What a cron entry looks like in practice
Section titled “What a cron entry looks like in practice”Domain heartbeats use domain-heartbeat.sh, a wrapper that:
- Sources
~/agent-system/.envfor credentials - Sets
PYTHONPATH(missing this is a common cron failure — scripts that run fine interactively fail in cron because the PATH is minimal) - Calls the domain agent with an imperative prompt
- Writes stdout/stderr to
~/agent-system/logs/domains/<domain>-heartbeat.log - On completion, the agent writes its
report.md
The PYTHONPATH prelude is load-bearing. Two cron jobs in the system were found running under Python 3.14 (a non-standard install) because the PATH resolution in a cron environment differs from a terminal session. Every cron that invokes Python must explicitly set cd ~/agent-system && PYTHONPATH=. python3.12 -m ....
Cron vs. LaunchAgent: when to use which
Section titled “Cron vs. LaunchAgent: when to use which”On macOS, both cron and LaunchAgents schedule work. The split in this system:
Cron — scheduled work, scripted jobs, agents that run on a pulse. Everything that should run whether or not a GUI session is active. The ~155–160 jobs.
LaunchAgent — persistent processes that need to stay alive, restart on crash, or respond to session events. The Telegram bot (claude Code session), the MCP servers, the Cloudflare tunnel.
A useful test: if the job produces an artifact and exits, it’s a cron. If it loops forever waiting for input, it’s a LaunchAgent.
The minimal viable heartbeat
Section titled “The minimal viable heartbeat”If you’re starting a new system, you don’t need 155 cron jobs. You need enough of a heartbeat that the system keeps its state current without you touching it. A reasonable starting point:
- One hourly job per domain that writes a status file
- One daily job that synthesizes the domain files into a briefing
- One daily job that backs up critical state (the crontab itself, database exports)
- One job per active pipeline (email poll, health data sync, etc.) at whatever cadence that pipeline needs
The system starts small and accumulates jobs as real needs arise. The 155 is the result of ~2 months of building one piece at a time, not a day-one design.
Next: Not all scheduled work should be crons. The loops doctrine explains when to use cron vs. watcher vs. session-loop vs. durable one-shot — and the rule that every future obligation must be wired to the right primitive. The loops doctrine.