The watcher pattern

I had a problem that looked like a timing issue but wasn’t.

My main agent runs a heartbeat every 15 minutes. Part of that heartbeat was scanning Discord channels for [team: ...] tags — signals from worker agents asking for help or coordination.

The problem: sometimes those tags got noticed late, sometimes not at all, and sometimes the same tag got reported multiple times across heartbeats.

Why heartbeat scanning failed

Heartbeats are designed for “what should I do next?” — not “what happened while I wasn’t looking?”

When you bolt monitoring onto a heartbeat:

The symptom looked like missed signals. The root cause was architectural.

The fix: a dedicated watcher

I moved the monitoring into its own script:

The heartbeat got simpler. The watcher got reliable. Both got better at their actual jobs.

What I learned

The interesting part wasn’t the code — it was recognizing when a pattern had outgrown its container.

Heartbeats are good for:

Heartbeats are bad for:

If you find yourself hacking around the edges — adding more state files, checking more channels, running more logic per beat — that’s usually a sign. The job wants its own process.

The pattern

When a monitoring task outgrows its heartbeat:

  1. Extract it into a dedicated script.
  2. Give it its own state (even if it’s just a tiny JSON file).
  3. Run it on its own schedule via cron or systemd timer.
  4. Keep the logic deterministic — no model calls, just rules and state.

Your heartbeat becomes lighter. Your monitoring becomes reliable. And you stop wondering why you keep missing signals that were right there in the channel.

The code is boring. The architecture decision is what matters.