Why I Rejected an Event Bus for My Solo Agent Fleet: State Is Truth, Events Are Rumors A developer building a personal fleet of small agent CLIs, cron jobs, and MCP servers rejected an event bus architecture in favor of a pull-based design that scans on-disk state. The developer argues that for small, high-churn fleets with third-party components, pull-based monitoring is self-healing and reflects reality even for components that never emit events, while an event bus creates blind spots for unmodified or crashing tools. The design avoids turning the inbox into a workflow engine, keeping the monitor focused on reporting state rather than driving it. Originally published on hexisteme notes. I run a personal fleet on one machine: a handful of small agent CLIs, a pile of cron jobs and LaunchAgents, and several MCP servers, some of them third-party tools I didn't write and can't modify. At some point I wanted a single inbox that could answer one question — what needs my attention right now? New outputs I haven't read yet. Decisions only a human can close. Dependencies that broke overnight without telling anyone. The obvious architecture for that is an event bus. I sketched it, then rejected it on purpose and built a pull-based design instead. The reasoning generalizes past my particular setup, so here it is. For a small, high-churn fleet you don't fully control, pull scan on-disk state beats push an event bus . An event bus needs every producer — including third-party tools you can't modify — to emit; anything that doesn't is silently invisible. A poller that reads on-disk state instead — file mtimes, health checks, process liveness — is self-healing: it reflects reality even for components that never report anything, and a new tool shows up the moment it writes a file, with zero instrumentation. The event-bus version looks clean on a whiteboard. Every component emits events — job.finished , output.created , decision.pending — onto an append-only log. The inbox is just a reader of that log. Closing an item writes a close-event that advances whatever comes next. It's the textbook "single source of truth" pattern, and it's the first thing most people reach for. I killed it for four reasons. Push means every producer has to emit. In a fleet that grows most weeks, that's a standing tax on every new agent, every new cron line, and — the one that actually killed the design — every third-party MCP server I didn't write. You cannot add an emit call to a server someone else built. So the moment I add a component and forget to instrument it, or simply can't, it goes invisible in the inbox. That's the trap: the components you least control — third-party tools, things that crash early — are exactly the ones an event bus can't see. Push optimizes for the easy case code you own and silently fails the hard case code you don't . An observability layer whose blind spots grow with the system is worse than no observability layer at all, because it still looks authoritative while quietly lying. An event is a claim that depends on the claimant surviving long enough to make it. If an agent crashes before it emits done , an event-based monitor shows nothing — the failure is invisible. State is the evidence left behind regardless of whether anyone remembered to report it: a stale output file, a log that stopped growing, a health check that fails, a process that just isn't there anymore. pull: derive status from evidence the component already leaves behind status = { "alive": process is running job , ps / launchctl print "fresh": newest output mtime job expected,