My AI Agent Catches What I Forget. Here's How.
A hardcoded heartbeat checklist checks what you told it to check. A memory-driven heartbeat reads what your agent actually knows — and surfaces what fell through the cracks. Here's the 200-line script that replaced my task list.
I built an AI agent to run my business. It monitors revenue, tracks leads, posts content, and runs analytics across six products. It knows more about what's happening day-to-day than I do.
For months it kept missing the same category of thing: the stuff that fell through the cracks.
Not because it lacked data. Because it only checked what I told it to check. The most important things — overdue follow-ups, stalled priorities, commitments made and forgotten — weren't in any checklist. I hadn't known to add them when I built it.
The Checklist Trap
Every always-on agent needs a heartbeat — a scheduled check that runs while you sleep and flags what needs attention. Mine started as a markdown file with fourteen tasks.
Check Stripe for failed charges. Pull new leads. Monitor GA4 for traffic anomalies. Each task fired on schedule. Each task returned data. The checklist worked exactly as designed.
The problem: a checklist only knows what you put in it. It checks fourteen things. It never asks what it's forgetting.
A cron job executes a script. An agent reads what it knows and decides what matters.
That distinction is what the checklist approach misses. The agent accumulates knowledge constantly — every conversation, every decision, every open loop lands in memory files. By the time a heartbeat fires, the agent knows considerably more than any list I would write.
What the Agent Actually Knows
The follow-up I promised but didn't send. The feature scoped but never built. The pricing discrepancy sitting in two different memory files. The prospect who replied once and then went quiet.
None of that is in a database. It's in the agent's memory. The hardcoded checklist never looks there.
Memory accumulates in structured markdown files. MEMORY.md holds durable facts: MRR, active campaigns, key decisions. Daily notes capture open loops and commitments as they happen. Topic files track leads, vendors, competitors, product decisions over time.
By the sixth week, the agent had 60,000 characters of accumulated context. The checklist was still checking the same fourteen things.
Building a Memory-Driven Heartbeat
The fix is a 200-line Python script. Run it on every heartbeat tick — it exits silently if it fired in the last six hours. Fire it manually with --force to bypass the cadence.
Each run does four things:
- Reads all memory files — MEMORY.md first, then daily notes newest-first, then topic files — up to 60,000 characters total
- Sends the full memory dump to an LLM with a signal-extraction prompt
- Receives up to five structured signals with type, urgency, product, and suggested action
- Routes each signal to the right Discord channel, marks it seen for three days, and posts
The script self-throttles. The dedup state lives at /opt/cmo-analytics/data/proactive_heartbeat_seen.json. Same signal won't resurface for three days — keyed on a stable fingerprint slug, not a timestamp.
Five Signal Types
The LLM extraction prompt targets five categories:
- Stale open loops — items tagged as ongoing with no recent update in the record
- Unfollowed commitments — "next step" or "I will" entries with no completion marker
- Contradictions — two memory entries that conflict with each other
- Silent people — named contacts with open items and no update
- Priority drift — something flagged urgent with no visible progress in seven or more days
The prompt is deliberately minimal. No elaborate instructions about business context. Just: read the memory, find what fell through the cracks, output structured JSON. The LLM does the rest.
What the First Run Found
We ran it with --force to bypass the cadence check. Five signals in under 30 seconds:
🔴 Follow-up with Mark (Sunshine Cleaning) pending
Silent Person · KAICALLS
No response since March 13 follow-up. $299/mo potential.
→ Reach out again before the end of the week.
🔴 BWK churn investigation stalled
Priority Drift · BUILDWITHKAI
30 failed charges flagged. No progress in 7 days.
→ Pull failed charge list from Stripe and triage.
🟡 Mystery shop outbound — never started
Stale Open Loop · KAICALLS
Plan to call 80 businesses. Still on the shelf.
→ Initiate or formally defer. Close the loop.
🟡 ABP follow-up agent — prompt written, not built
Stale Open Loop · ABP
Vapi integration scoped but not implemented.
→ Build or reschedule.
🔴 Pricing contradiction in memory
Contradiction · MEMORY.md vs 2026-03-11.md
$67/mo in one file, $69/mo in another.
→ Standardize. The correct price is $69/mo.
Every signal was real. The pricing contradiction was a genuine error sitting across two memory files. Mark's follow-up was overdue. The mystery shop had been sitting for weeks. None of these were in the hardcoded checklist.
Channel Routing
Signals route to product channels automatically. KaiCalls signals go to #kai-calls. ABP signals go to #awesomebackyard. Zehrava signals go to #zeharva. Unroutable signals default to #meet-kai.
Routing uses keyword matching on signal title and detail. The product field in the JSON output guides it. No configuration required — the LLM assigns product context based on what it reads in memory.
The HEARTBEAT.md entry is now one block:
### Proactive Memory Scan (every heartbeat — 6h self-throttle)
cd /opt/cmo-analytics && source venv/bin/activate \
&& python3 scripts/proactive_heartbeat.py --json --force
# Read /tmp/proactive_heartbeat_signals.json
# Post each signal.message to signal.channel_id
What It Doesn't Replace
The hardcoded data checks still run. Stripe at-risk alerts. Lead counts. GA4 anomalies. Those require live database queries — memory files don't have real-time revenue data.
The memory scan runs alongside those checks, not instead of them. Think of it as two parallel layers:
- Data checks — surface what's happening in your systems right now
- Memory checks — surface what's happening in your thinking over time
Both are gaps. You need both. The rule: if it requires a database query, it's a data check. If it requires knowing context, it's a memory check.
Three Requirements
The memory scan works only if memory exists. Build these habits first:
- Tag open loops explicitly. Use consistent markers like
[open_loop]so the LLM can distinguish pending from complete. The signal extraction depends on this. - Write commitments to memory as they happen. "Next step: follow up with Mark by March 15." That entry becomes a signal the moment it goes stale.
- Date your entries. Without timestamps, the LLM can't identify what's stale versus current. Staleness is the core detection mechanism.
The script itself costs pennies per run. Six-hour cadence means roughly four runs per day. At GPT-4o-mini pricing, that's under $1/month for the analysis layer.
What Changed
The biggest change isn't operational — it's psychological. The background anxiety about forgotten commitments mostly disappeared. Before, I carried a low-level worry that something important was slipping through. A deal going cold because I missed a window. A promise made and not kept.
Now the agent catches it — not because I told it to look, but because it reads what it knows and makes a judgment.
That's the difference between a system that monitors and a system that thinks.
— Connor