Skip to content

Scheduling

Garot Conklin edited this page Jun 1, 2026 · 7 revisions

Scheduling

Signal has five automated jobs managed by macOS launchd.

Job Schedule Config
Daily brief Every day, 5:00 AM com.flexrpl.signal.plist
Weekly summary Every Monday, 6:00 AM com.flexrpl.signal.weekly.plist
Social post — AM Every day, 7: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 5: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.


How it works

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 (5:00 AM daily)
    → run_and_publish.sh
        → python main.py --no-venv        # Passes 1–5, fetches articles
        → git add reports/ index.html archive.html
        → git commit -m "signal: daily brief YYYY-MM-DD"
        → git push origin main
    → GitHub Actions deploys to GitHub Pages

Weekly flow:

launchd (11:00 PM every Sunday)
    → 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 Pages

Files

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.social.am.plist Social AM post (7: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.


One-time setup

Daily job:

cp scripts/com.flexrpl.signal.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.plist

Weekly job:

cp scripts/com.flexrpl.signal.weekly.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.weekly.plist

Social 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.plist

Verify all loaded:

launchctl list | grep flexrpl
# Should show:
# -  0  com.flexrpl.signal
# -  0  com.flexrpl.signal.weekly
# -  0  com.flexrpl.signal.social.am
# -  0  com.flexrpl.signal.social.noon
# -  0  com.flexrpl.signal.social.pm

Applying plist changes

Any 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.plist

Same 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.plist

Verify the change took effect:

cat ~/Library/LaunchAgents/com.flexrpl.signal.plist | grep -A4 StartCalendarInterval
cat ~/Library/LaunchAgents/com.flexrpl.signal.weekly.plist | grep -A8 StartCalendarInterval

Changing the schedule

Edit the StartCalendarInterval block in scripts/com.flexrpl.signal.plist:

<!-- Run daily at 5:00 AM local time -->
<key>StartCalendarInterval</key>
<dict>
    <key>Hour</key>
    <integer>5</integer>   <!-- 0–23, local time -->
    <key>Minute</key>
    <integer>0</integer>
</dict>

Current schedule: 5:00 AM local time daily.

5am was chosen to capture overnight wire service content and early-morning news cycle updates before peak traffic hits news servers.

Common alternatives:

Schedule Hour Minute
5:00 AM (current) 5 0
6:00 AM 6 0
7:00 AM 7 0
12:01 AM 0 1

After changing, always apply with the unload/copy/reload sequence above.


Switching LLM provider for scheduled runs

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.


Manually triggering a run

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.signal

Weekly:

# 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.weekly

Logs

Log file Written by
logs/cron.log Daily pipeline (run_and_publish.sh)
logs/weekly.log Weekly synthesis (run_weekly_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

# Last weekly run summary
grep -A5 "Signal WEEKLY run started" logs/weekly.log | tail -20

Complete plist references

<?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>5</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>

RunAtLoad: false

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.


Weekly plist reference

<?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 Sunday at 11:00 PM local time (Weekday 0 = Sunday) -->
    <key>StartCalendarInterval</key>
    <dict>
        <key>Weekday</key>
        <integer>0</integer>
        <key>Hour</key>
        <integer>23</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.


Troubleshooting launchd

Job not appearing in launchctl list:

launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.plist
launchctl load ~/Library/LaunchAgents/com.flexrpl.signal.weekly.plist
launchctl list | grep flexrpl

Job shows error code (non-zero) in launchctl list:

cat logs/cron.log     # check daily pipeline errors
cat logs/weekly.log   # check weekly synthesis errors

Schedule 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.

Clone this wiki locally