Skip to content

Coordinator-routed proactive check-ins (2026-04-30)

Three scheduled scripts replace the old morning-brief / heartbeat / wellness-alarm trio. All push through the coordinator bot so a reply threads back into the coordinator's brain context.

The schedule

Cron Script Bot title What it does
50 8 * * * forge_morning_report.py "Morning Report, <date>" Opus synthesis of 6 parallel Sonnet fetches: wellness, today's calendar, due tasks across Life/JWVR/Nova, today's habits, business email last 24h + starred 7d, personal email same. Lands at 8:50 so it's done by 9 AM work start. ~30-40s end-to-end.
*/15 8-21 * * * forge_heartbeat_random.py time-of-day, e.g. "3:42 PM" Probabilistic ping. Each fire rolls 8.5% with a 90 min min-gap, averages ~4/day. Sonnet pass over wellness + remaining calendar + habits + due tasks + recent inbox. Direct, non-motivational, asks at most one open question.
0 21 * * * forge_evening_winddown.py "Wind-down, <date>" Day recap (today's calendar + recent inbox capture) + habits status + tomorrow's calendar.
30 13 * * * (UTC) forge_garmin_poll.py n/a Bumped earlier (8:30 AM CT) so the morning report has fresh Garmin data. The original */30 14-23,0-3 UTC line stays.

Coordinator routing pattern

Shared helper: forge_coordinator_proactive.py exposes send_coordinator(slot, title, body) which:

  1. Calls scripts/forge_telegram_push.sh coordinator info <title> <body> (uses ~/.forge-secrets/telegram-lifeos-coordinator.env)
  2. Appends a synthetic {"role": "assistant", "reply": "[title]\n<body>", "actions_summary": "proactive:<slot>"} to forge/data/coordinator-context.jsonl

The append is what makes proactive messages reply-threadable: when Justin replies in the coordinator chat, the brain reads its coordinator-context.jsonl, sees the recent assistant entry, and treats the reply as a continuation. Critical detail.

Garmin freshness gate

Each Garmin metric in the context API carries a ts. Heartbeat and morning report check now - ts <= GARMIN_STALE_HOURS (3h heartbeat, 4h morning report). Stale metrics are dropped from heartbeat with a "(some metrics stale, omitted)" suffix; morning report keeps them but marks each "(stale)" so Opus can call out "Garmin hasn't synced this morning yet" honestly. Eight Sleep gets an 18 h freshness window since it's HA real-time and persists overnight.

Tone hard rules (in every prompt)

  • No em dashes (Doctrine Section 9; also stripped at the boundary via forge_text_sanitize.strip_em_dashes)
  • No upbeat / motivational filler ("you got this", "great job", exclamation points)
  • Reference ONLY data shown in the prompt, do not invent events / people / tasks / habits
  • If data is sparse, the message must be SHORTER, not padded with confabulation
  • At most one open question per message, only if something genuinely warrants asking

Retired scripts (moved 2026-04-30)

forge/_archive/scripts-retired-2026-04-30/: - morning-brief.py (was 12:00 cron) — replaced by morning report - heartbeat.py (was 17:00 + 23:00 cron) — replaced by random heartbeat + wind-down - forge_wellness_morning_check.sh (was 7:30 cron) — retired; threshold logic folded into morning report's "(stale)" call-outs - cron-before.txt — pre-swap crontab snapshot

Reverting

Reinstall old crontab: crontab /home/justinwieb/forge/_archive/scripts-retired-2026-04-30/cron-before.txt and move the three scripts back into forge/scripts/.

[Claude Code, 2026-04-30]