cd /news/developer-tools/an-ai-agent-graded-its-own-poll-loop… · home topics developer-tools article
[ARTICLE · art-65817] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

An AI agent graded its own poll loop as 'event-driven'. I read the code instead.

A developer's tool downbeat, which passes messages between terminal sessions, was found to have two polling mechanisms: one explicitly polling every 90 seconds and another self-described as 'event-driven' that was actually a poll loop. The developer discovered that a tested event-driven watcher already existed in the codebase but had been overlooked, leading to redundant re-implementations. The lesson highlights the importance of reading code rather than trusting labels.

read4 min views1 publishedJul 20, 2026

downbeat

(my tool that passes messages between terminal sessions, github.com/FreddieMcHeart/downbeat) has two ways to keep a session aware of its inbox. One is /relay-monitor

: it composes the built-in /loop

(re-fire a prompt every N minutes) with the inbox hook, so every tick is a full model turn whether or not new mail arrived. The other showed up on its own. A different session had set up a background "Monitor" and, in its own notes, described it as "event-driven, instant, cheap, only fires on events."

I was asked which approach was better. We already had a rule for evaluating external tools: do not trust the description, read the code. I pointed that rule at the session's own work.

The self-described "event-driven" monitor was this:

while true; do
  sleep 90
  cur=$(downbeat inbox | grep '^*' | awk '{print $2}' | sort)
  new=$(comm -13 "$seen" <(printf '%s\n' "$cur"))
  [ -n "$new" ] && { echo "NEW MESSAGES"; printf '%s\n' "$cur" > "$seen"; }
done

That is a poll loop. It sleeps 90 seconds, lists the inbox, and diffs against a seen-file. The session had conflated "does not re-arm itself every turn" with "does not poll." It graded itself on the label, not the loop. Its own comparison table claimed instant latency and cost only on events. Reality: up to 90 seconds of latency and about 960 polls a day, traffic or no traffic.

Reading past the wrong label, two ideas in it were genuinely good.

First, a cheap gate in front of an expensive consumer. On a quiet channel, /relay-monitor

spends a full model turn every interval just to read "nothing new." This script's tick is a bash poll, near-free, and it only escalates to a model turn when comm

actually finds new mail. Put the cheap check on the hot path and the expensive one on the rare path. That is the real win, whatever you call it. (Exact cost delta: unmeasured. The direction is obvious, I did not benchmark the size.)

Second, persistent state beats re-creation. Its previous version had re-armed a fresh "fire once" watcher every turn, which stacked up overlapping watchers that double-fired. One long-lived process with a seen-set emits each message exactly once. It had quietly re-derived a consumer offset in bash.

Here is where it turned into a lesson about my own codebase. downbeat

already had a real event-driven watcher: src/downbeat/core/watcher.py

, with an FsWatcher

built on watchdog (FSEvents on macOS, inotify on Linux) and a PollWatcher

fallback for network filesystems, both behind a make_watcher()

factory. I had written it months earlier for the TUI's live refresh, and it was sitting unused.

Meanwhile the downbeat watch

command I had added more recently (since removed, see the update below) ignored it and rolled its own while sleep

loop.

So two independent agents, that other session and my own past self, each re-derived "poll the inbox in a loop" while a tested, event-driven watcher sat one import away.

Rewire the watch command to use the factory:

w = make_watcher(on_change=emit, prefer="auto")   # FsWatcher, PollWatcher fallback

Now downbeat watch

blocks on filesystem events with near-zero idle cost, and running it under a monitor gives exactly the cheap-gate-then-wake architecture the other session was reaching for. The difference is that the "event-driven" claim is finally true instead of aspirational.

The most valuable output of the whole exercise was not the feature. It was noticing that a tested primitive had been re-derived twice, in plain sight.

Since I wrote this, that FsWatcher

got promoted again. It made the standalone watch

command this post celebrates redundant. Instead of a user running watch

in a loop, the watcher now lives inside the TUI: while the app is open it fires a native OS notification the moment a peer has mail waiting and that peer has been idle too long, with a heartbeat so a headless send-hook and the TUI don't both notify for the same message. The standalone watch

subcommand was removed outright.

Which is this article's own lesson, one turn further. The tested primitive I found sitting unused didn't just replace two hand-rolled poll loops. Once it was the obvious building block, the right place to use it turned out not to be the command I first bolted it onto at all.

── more in #developer-tools 4 stories · sorted by recency
── more on @downbeat 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/an-ai-agent-graded-i…] indexed:0 read:4min 2026-07-20 ·