Alejandro Rojas
Operator Guide · Chapter 09

Cron and automation

The harness runs on automation. Without scheduled scripts, you'd have to remember to generate the morning brief, run loop signal checks, audit cron health, back up the vault. Operators who try to remember these things forget half of them.

This chapter is how to set up the automation layer reliably.

The always-on machine

Cron requires a machine that's running when the cron fires. For an operator, that means an always-on machine — not your laptop, which closes the lid at 5pm.

Three options:

Option A — A Mac Mini (recommended)

A base-model Mac Mini M4 is $599. It runs 24/7 on ~5W of power. Headless (no monitor needed), accessed via SSH or Screen Sharing. Encrypted with FileVault. Set to auto-restart on power loss.

Option B — A VPS

Linode, DigitalOcean, Hetzner — all reliable. ~$10-40/mo depending on size. Trade-off: your vault would live on a remote server (or you'd sync to it), which adds complexity.

Option C — A Raspberry Pi

~$50-150 depending on model. Underpowered for AI workloads (Ollama struggles). Fine for lightweight cron.

The harness defaults to Option A. The cron entries in this chapter assume macOS conventions (date -v -1d, launchctl). Adapt for Linux if you go Option B.

Setting up the Mac Mini

If you're starting from scratch:

  1. Buy the Mac Mini. Base model is enough unless you'll run Ollama 8B+ continuously.
  2. First-boot setup: Standard macOS install. Create one user account. Set computer name (used in SSH).
  3. Energy Saver settings (System Settings → Energy):
    • Sleep: Never (or set to "Never when plugged in")
    • Display sleep: 10 min (the display turns off, machine stays awake)
    • Start up automatically after a power failure: ON
    • Wake for network access: ON
  4. FileVault: Turn on. Save the recovery key somewhere secure (not in the vault).
  5. SSH: System Settings → General → Sharing → Remote Login ON.
  6. Add your SSH key:
    # From your laptop ssh-copy-id <username>@<mac-mini-ip>
  7. Tailscale or equivalent: install for stable remote access (your local IP can change; Tailscale MagicDNS gives you a stable name).
  8. External SSD: attach. Format with the filesystem you prefer (APFS for Mac-only, ExFAT for cross-platform).
  9. Mount discipline: the drive should auto-mount on boot. Set up a watchdog that alerts if it disappears.

The crontab

Edit your cron with crontab -e. Each line is:

[minute] [hour] [day] [month] [day-of-week] [command]

Use * for "any." Examples:

# Every 5 minutes */5 * * * * /bin/bash /Volumes/T9/your-vault/_System/scripts/watchdog.sh # Daily at 6 AM 0 6 * * * /bin/bash /Volumes/T9/your-vault/_System/scripts/morning-brief.sh # Hourly during business hours (8-22) 0 8-22 * * * /Library/Developer/CommandLineTools/usr/bin/python3 /Volumes/T9/your-vault/_System/scripts/inbox-watcher.py # Daily at 2 AM (backup window) 0 2 * * * /bin/bash /Volumes/T9/your-vault/_System/scripts/backup.sh

Critical: macOS Full Disk Access (FDA)

On macOS Mojave and later, cron jobs need Full Disk Access granted explicitly. Without it, your scripts will fail with "Operation not permitted" — silently, in some cases.

Grant FDA to:

System Settings → Privacy & Security → Full Disk Access → Add each of the above.

This is one of the most common sources of "my cron isn't running and I don't know why" — silent permission failures.

ExFAT quirks

If your external drive is ExFAT (cross-platform), cron cannot execute scripts directly from it. Workaround: prefix bash scripts with /bin/bash:

# Won't work on ExFAT: */5 * * * * /Volumes/T9/your-vault/_System/scripts/script.sh # Works on ExFAT: */5 * * * * /bin/bash /Volumes/T9/your-vault/_System/scripts/script.sh

Python scripts use the full Python path:

*/5 * * * * /Library/Developer/CommandLineTools/usr/bin/python3 /Volumes/T9/your-vault/_System/scripts/script.py

The crontab.md document

Maintain a single document, _AI/Schedules/crontab.md, that mirrors what's actually in your crontab. This is the human-readable source of truth.

Update it in the same session you modify cron. If the two ever drift, the document is wrong (cron is what actually runs).

Format:

| Schedule | Script | Purpose | Last verified | Failure mode | |-------------|------------------|------------------|---------------|--------------| | 0 6 * * * | morning-brief.sh | Daily brief | 2026-05-20 | Email late | | */5 * * * * | inbox-watcher.py | Inbox classifier | 2026-05-15 | Inbox piles |

The Last Verified column matters. Stale entries (>30 days) are signal that the cron may have stopped working without you noticing.

The watchdog pattern

Every cron should report success or failure. The harness pattern: each script writes one row to an agent_timeline_events table (or JSONL log) at end of main().

# At end of every cron script write_event( source="local:morning-brief.sh", title="Morning brief generated", severity="info", description=f"brief.md written to {output_path}" )

The brain (or a daily audit) reads this and surfaces crons that haven't reported recently. Without watchdog reporting, crons fail silently — and the failure mode of silent-fail is usually "we discovered it weeks later."

The cron-doctor pattern

A meta-cron that watches the other crons. Runs every few hours:

# Pseudo-code def check_cron_health(): expected_schedule = { "morning-brief.sh": "daily", "inbox-watcher.py": "every 5 min", "backup.sh": "daily", ... } failures = [] for script, cadence in expected_schedule.items(): last_event = query_latest_event(source=f"local:{script}") if not last_event or stale_for(last_event, cadence): failures.append(f"{script}: last seen {last_event.timestamp}") if failures: alert_via_imessage_or_email(failures) write_event(source="local:cron-doctor.py", ...)

This catches cron failures that watchdog reporting alone misses (e.g., a script that runs but doesn't reach its watchdog call because of an early exception).

Backup discipline

Cron-driven, daily. The vault itself should be backed up to at least one other location:

The vault is the entire memory of your operation. Losing it would be expensive. Set up two backup channels before you put critical data inside.

Don't trust a single backup. Verify backups every quarter by restoring a random file to a test location.

Common failure modes

"Cron isn't running my script"

Almost always one of:

  1. Full Disk Access not granted
  2. Script path wrong in crontab
  3. Script not executable (chmod +x)
  4. Script using a binary not in cron's PATH (use full paths)
  5. ExFAT drive — needs /bin/bash prefix

Test the script manually first: bash /path/to/script.sh. If it works manually but not via cron, it's almost always #1.

"Cron ran but no output"

Cron sends output to email by default, which on macOS goes nowhere visible. Redirect to a log file:

0 6 * * * /bin/bash /path/to/script.sh >> /tmp/morning-brief.log 2>&1

Now you can inspect /tmp/morning-brief.log to see what happened.

"Cron worked, then stopped"

Usually:

The cron-doctor pattern catches these within hours.

How many cron jobs is reasonable?

10-15 is normal for a small operation. 25+ is a sign that you're over-automating and should consolidate.

If two crons fire 5 minutes apart and do similar things, combine them. If a cron hasn't actually been useful in the last quarter, kill it.

The cron list should be readable in one screen. If yours isn't, you've accumulated complexity that's no longer paying for itself.

Next chapter: document filing rules, especially around PII.