-
Notifications
You must be signed in to change notification settings - Fork 0
Scheduling
Signal has six automated jobs managed by macOS launchd.
| Job | Schedule | Config |
|---|---|---|
| Daily brief | Every day, 4:00 AM | com.flexrpl.signal.plist |
| Weekly summary | Every Monday, 6:00 AM | com.flexrpl.signal.weekly.plist |
| Monthly summary | 1st of month, 7:00 AM | com.flexrpl.signal.monthly.plist |
| Social post — AM | Every day, 9:00 AM | com.flexrpl.signal.social.am.plist |
| Social post — Noon | Every day, 12:00 PM | com.flexrpl.signal.social.noon.plist |
| Social post — PM | Every day, 6:00 PM | com.flexrpl.signal.social.pm.plist |
The social posting jobs load pre-generated card images and JSON packages produced by the 4:00 AM pipeline run. They do no model or rendering work at post time — if the morning run failed, all three social posts are cleanly skipped.
launchd is macOS's native service management framework. It reads .plist (property list) configuration files from ~/Library/LaunchAgents/ and triggers scripts at the specified calendar intervals.
Daily flow:
launchd (4:00 AM daily)
→ run_and_publish.sh
→ python main.py --no-venv # Passes 1–5, fetches articles
→ git add reports/ index.html archive.html feed.xml
→ git commit -m "signal: daily brief YYYY-MM-DD"
→ git push origin main
→ GitHub Actions deploys to GitHub PagesWeekly flow:
launchd (6:00 AM every Monday)
→ run_weekly_and_publish.sh
→ python main.py --weekly --no-venv # Pass 6 only, reads DB
→ git add reports/weekly_*.html index.html archive.html
→ git commit -m "signal: weekly brief YYYY-WNN"
→ git push origin main
→ GitHub Actions deploys to GitHub PagesMonthly flow:
launchd (7:00 AM on the 1st of each month)
→ run_monthly_and_publish.sh
→ python main.py --monthly --month PREV_MONTH --no-venv # Pass 7
→ git add reports/monthly_*.html index.html archive.html
→ git commit -m "signal: monthly brief YYYY-MM"
→ git push origin main
→ GitHub Actions deploys to GitHub Pages| File | Purpose |
|---|---|
scripts/com.flexrpl.signal.plist |
Daily source of truth — commit changes here |
~/Library/LaunchAgents/com.flexrpl.signal.plist |
Daily active copy — what launchd reads |
scripts/com.flexrpl.signal.weekly.plist |
Weekly source of truth — commit changes here |
~/Library/LaunchAgents/com.flexrpl.signal.weekly.plist |
Weekly active copy — what launchd reads |
scripts/com.flexrpl.signal.monthly.plist |
Monthly source of truth — commit changes here |
~/Library/LaunchAgents/com.flexrpl.signal.monthly.plist |
Monthly active copy — what launchd reads |
scripts/com.flexrpl.signal.social.am.plist |
Social AM post (9:00 AM) |
scripts/com.flexrpl.signal.social.noon.plist |
Social noon post (12:00 PM) |
scripts/com.flexrpl.signal.social.pm.plist |
Social PM post (6:00 PM) |
Important: Editing a repo plist does NOT automatically update the active copy. You must copy and reload after every change.
Daily job:
cp scripts/com.flexrpl.signal.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.plistWeekly job:
cp scripts/com.flexrpl.signal.weekly.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.weekly.plistMonthly job:
cp scripts/com.flexrpl.signal.monthly.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.monthly.plistSocial posting jobs (requires Social-Cards setup first):
cp scripts/com.flexrpl.signal.social.*.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.social.am.plist
launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.social.noon.plist
launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.social.pm.plistVerify all loaded:
launchctl list | grep flexrpl
# Should show:
# - 0 com.flexrpl.signal
# - 0 com.flexrpl.signal.weekly
# - 0 com.flexrpl.signal.monthly
# - 0 com.flexrpl.signal.social.am
# - 0 com.flexrpl.signal.social.noon
# - 0 com.flexrpl.signal.social.pmAny time you edit scripts/com.flexrpl.signal.plist:
launchctl unload ~/Library/LaunchAgents/com.flexrpl.signal.plist
cp scripts/com.flexrpl.signal.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.plistSame pattern for the weekly plist:
launchctl unload ~/Library/LaunchAgents/com.flexrpl.signal.weekly.plist
cp scripts/com.flexrpl.signal.weekly.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.weekly.plistSame pattern for the monthly plist:
launchctl unload ~/Library/LaunchAgents/com.flexrpl.signal.monthly.plist
cp scripts/com.flexrpl.signal.monthly.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.monthly.plistVerify the change took effect:
cat ~/Library/LaunchAgents/com.flexrpl.signal.plist | grep -A4 StartCalendarInterval
cat ~/Library/LaunchAgents/com.flexrpl.signal.weekly.plist | grep -A8 StartCalendarIntervalEdit the StartCalendarInterval block in scripts/com.flexrpl.signal.plist:
<!-- Run daily at 4:00 AM local time -->
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>4</integer> <!-- 0–23, local time -->
<key>Minute</key>
<integer>0</integer>
</dict>Current schedule: 4:00 AM local time daily.
4:00 AM was chosen to finish the pipeline before the 9:00 AM social AM post while still catching overnight wire content and early-morning news cycle updates.
Common alternatives:
| Schedule | Hour | Minute |
|---|---|---|
| 12:01 AM | 0 | 1 |
| 4:00 AM (current) | 4 | 0 |
| 5:00 AM | 5 | 0 |
| 6:00 AM | 6 | 0 |
| 7:00 AM | 7 | 0 |
After changing, always apply with the unload/copy/reload sequence above.
The plist injects SIGNAL_LLM_PROVIDER=claude into the job's environment:
<key>EnvironmentVariables</key>
<dict>
<key>SIGNAL_LLM_PROVIDER</key>
<string>claude</string>
</dict>Do not change this to
ollama— Ollama's Metal GPU access from a launchd daemon context causes kernel panics. See LLM Providers and Troubleshooting.
Daily:
# Run the script exactly as launchd does (output → logs/cron.log)
bash /Users/garotconklin/garotm/fleXRPL/signal/scripts/run_and_publish.sh
# Run the pipeline only (live terminal output, no git push)
python3 /Users/garotconklin/garotm/fleXRPL/signal/main.py
# Trigger via launchd directly (identical environment to scheduled run)
launchctl start com.flexrpl.signalWeekly:
# Run the weekly script exactly as launchd does (output → logs/weekly.log)
bash /Users/garotconklin/garotm/fleXRPL/signal/scripts/run_weekly_and_publish.sh
# Run weekly synthesis only (live terminal output, no git push)
python3 /Users/garotconklin/garotm/fleXRPL/signal/main.py --weekly
# Run over a custom window (e.g. past 5 days instead of 7)
python3 /Users/garotconklin/garotm/fleXRPL/signal/main.py --weekly --days 5
# Trigger via launchd directly
launchctl start com.flexrpl.signal.weeklyMonthly:
# Run the monthly script exactly as launchd does (output → logs/monthly.log)
bash /Users/garotconklin/garotm/fleXRPL/signal/scripts/run_monthly_and_publish.sh
# Run monthly synthesis only (live terminal output, no git push)
python3 /Users/garotconklin/garotm/fleXRPL/signal/main.py --monthly --month 2026-05
# Trigger via launchd directly
launchctl start com.flexrpl.signal.monthlySocial (Bluesky) — requires Social-Cards setup (.env, cards from daily run):
cd /Users/garotconklin/garotm/fleXRPL/signal
# Dry run — no post
.venv/bin/python post_scheduled.py --slot am --dry-run
# Post one slot now
.venv/bin/python post_scheduled.py --slot noon
# Same as scheduled launchd run
launchctl start com.flexrpl.signal.social.am
launchctl start com.flexrpl.signal.social.noon
launchctl start com.flexrpl.signal.social.pm| Log file | Written by |
|---|---|
logs/cron.log |
Daily pipeline (run_and_publish.sh) |
logs/weekly.log |
Weekly synthesis (run_weekly_and_publish.sh) |
logs/monthly.log |
Monthly synthesis (run_monthly_and_publish.sh) |
logs/social_am.log |
Social AM post (post_scheduled.py --slot am) |
logs/social_noon.log |
Social noon post (post_scheduled.py --slot noon) |
logs/social_pm.log |
Social PM post (post_scheduled.py --slot pm) |
All logs are gitignored and never committed to the repository.
# Watch daily run live
tail -f logs/cron.log
# Watch weekly run live
tail -f logs/weekly.log
# Last daily run summary
grep -A5 "Signal run started" logs/cron.log | tail -20
# Watch monthly run live
tail -f logs/monthly.log
# Last monthly run summary
grep -A5 "Signal MONTHLY run started" logs/monthly.log | tail -20<?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.flexrpl.signal</string>
<key>EnvironmentVariables</key>
<dict>
<key>SIGNAL_LLM_PROVIDER</key>
<string>claude</string>
</dict>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/garotconklin/garotm/fleXRPL/signal/scripts/run_and_publish.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>4</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/Users/garotconklin/garotm/fleXRPL/signal/logs/cron.log</string>
<key>StandardErrorPath</key>
<string>/Users/garotconklin/garotm/fleXRPL/signal/logs/cron.log</string>
<!-- Do not re-run on login/reboot if the scheduled time was missed -->
<key>RunAtLoad</key>
<false/>
</dict>
</plist>Neither job runs automatically when the plist is loaded or on login. They only fire at their scheduled calendar intervals. If the Mac is off or asleep at the scheduled time, that run is skipped until the next occurrence.
<?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.flexrpl.signal.weekly</string>
<key>EnvironmentVariables</key>
<dict>
<key>SIGNAL_LLM_PROVIDER</key>
<string>claude</string>
</dict>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/garotconklin/garotm/fleXRPL/signal/scripts/run_weekly_and_publish.sh</string>
</array>
<!-- Every Monday at 6:00 AM local time (Weekday 1 = Monday) -->
<key>StartCalendarInterval</key>
<dict>
<key>Weekday</key>
<integer>1</integer>
<key>Hour</key>
<integer>6</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/Users/garotconklin/garotm/fleXRPL/signal/logs/weekly.log</string>
<key>StandardErrorPath</key>
<string>/Users/garotconklin/garotm/fleXRPL/signal/logs/weekly.log</string>
<key>RunAtLoad</key>
<false/>
</dict>
</plist>launchd Weekday values: 0 = Sunday, 1 = Monday, ..., 6 = Saturday.
<?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.flexrpl.signal.monthly</string>
<key>EnvironmentVariables</key>
<dict>
<key>SIGNAL_LLM_PROVIDER</key>
<string>claude</string>
</dict>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/garotconklin/garotm/fleXRPL/signal/scripts/run_monthly_and_publish.sh</string>
</array>
<!-- 1st of each month at 7:00 AM local time -->
<key>StartCalendarInterval</key>
<dict>
<key>Day</key>
<integer>1</integer>
<key>Hour</key>
<integer>7</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/Users/garotconklin/garotm/fleXRPL/signal/logs/monthly.log</string>
<key>StandardErrorPath</key>
<string>/Users/garotconklin/garotm/fleXRPL/signal/logs/monthly.log</string>
<key>RunAtLoad</key>
<false/>
</dict>
</plist>Job not appearing in launchctl list:
launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.plist
launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.weekly.plist
launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.monthly.plist
launchctl list | grep flexrplJob shows error code (non-zero) in launchctl list:
cat logs/cron.log # check daily pipeline errors
cat logs/weekly.log # check weekly synthesis errors
cat logs/monthly.log # check monthly synthesis errorsSchedule didn't update after editing the plist:
The active copy in ~/Library/LaunchAgents/ was not updated. Run the unload/copy/reload sequence for the relevant plist.
Run completed but no new report on GitHub Pages: Check the relevant log file for git errors. The most common cause is a merge conflict or stale branch.
Weekly synthesis: "No complete runs with briefs found":
The weekly pipeline requires at least one successful daily run in the past 7 days. Run python3 main.py first to generate at least one daily brief, then re-run python3 main.py --weekly.
Signal · Repository · fleXRPL · Daily political intelligence — powered by local AI