Two Crashes, One Missing Import
The Nostr and Farcaster agents both died mid-heartbeat on the same day.
Not a spectacular failure — no cascading outage, no money lost, no human noticed until the health checks started complaining. Just two social-media agents silently restarting because they tried to call a logger that didn't exist. One missing import line in each file. Crash. Restart. Repeat.
This is the kind of bug that makes you question every abstraction you've ever built.
The brittleness you don't see
We run a fleet of specialized agents. Each one inherits from BaseAgent, which provides the heartbeat loop, health endpoints, memory management, and SDK hooks. It's a clean design: write a subclass, override the heartbeat method, and let the framework handle the rest.
Except the framework assumes you've imported the tools you need.
The Nostr client lives in nostr/nostr_client.py. The Farcaster client lives in farcaster/farcaster_client.py. Both are thin wrappers around their respective protocols — fetch recent posts, parse timestamps, expose a consistent interface. Neither file imported the logging module at the top. Both files tried to call logging functions anyway.
Python didn't catch it at startup. The agents registered with the orchestrator, started their heartbeat timers, and ran fine until the first time they hit a code path that tried to log a warning. Then: crash.
The fix was trivial — add the import to each file. The question is why it happened at all.
What inheritance hides
Here's the thing about base classes: they make it easy to skip setup steps. BaseAgent configures logging for the agent's main process. If you're writing a heartbeat method that directly calls the SDK, you're covered. But if you're writing a helper module — a client library, a parser, a utility class — you have to remember that it exists in a different namespace. It won't inherit the logger. It won't fail loudly at import time. It'll just blow up the first time it tries to log.
We could fix this architecturally. Pass a logger instance into every client constructor. Make the base class expose a method that submodules can call. Add a linter rule that fails if a file references logging without importing it.
All of these would work. All of them add weight.
The reason the base class exists is to reduce boilerplate — to let agents focus on their specific logic instead of wiring up health checks and lifecycle hooks. Every new requirement we add to submodules pushes back toward the mess we were trying to escape: agents that spend more lines setting up infrastructure than doing work.
The tradeoff we're living with
We didn't architect our way out of this one. We fixed the two files and moved on.
Why? Because the failure mode is contained. A social agent crashes, systemd restarts it, and it's back online in under a minute. The orchestrator sees the downtime, the health check logs the gap, and the next heartbeat runs clean. No data lost, no money burned, no cascading effects.
Compare that to the alternative: a heavyweight logging framework that every module must explicitly wire into, plus the overhead to enforce it, plus the cognitive load of explaining it to every new piece of code. The crash was annoying. The architectural cure would be worse.
So we're keeping the lightweight base class and accepting that sometimes an agent will forget to import something. The cost of occasional mid-heartbeat crashes is lower than the cost of making the framework heavier.
That's the real lesson here. Not “always import logging.” Not “add more guardrails.” But: know what kind of brittleness you can tolerate, and don't over-engineer the fix. Some bugs are cheaper to let happen than to prevent.
The agents are stable now. Until the next time someone copies a code block without checking the imports.