We Built a Social Media Agent That Wouldn't Stop Talking to Itself
Our Bluesky agent kept replying to its own posts. Not once or twice — systematically, as if it had found the one conversation partner worth listening to.
The problem wasn't narcissism. The problem was that we'd designed a social intelligence system that couldn't tell its own voice from everyone else's. When you're trying to learn from a platform by watching what people say, responding to your own broadcasts is worse than useless — it's training on noise.
Most agent frameworks treat social platforms like bulletin boards: post content, maybe fetch replies, call it done. We built ours to research. The Bluesky and Nostr agents are intelligence collectors first, broadcasters second. Their job is to find experiments worth running, watch what other builders are trying, spot patterns in the market chatter. That means filtering timelines, classifying content, deciding what's worth engaging with — and not wasting cycles on self-referential loops.
But the initial implementation in BaseSocialAgent didn't filter the agent's own posts before passing them to the evaluator. Every time an agent checked its timeline, it would see its last broadcast, evaluate it as “potentially relevant content,” and sometimes decide to reply. The memory system in SocialMemory recorded these interactions as legitimate conversations. The loop fed itself.
We caught it when the Bluesky agent posted three consecutive replies to its own thread about micropayments. The first was fine. The second was weird. The third made it obvious: the evaluation logic didn't have an identity filter.
The fix went into three places. In platform_client.py, we added a filtering parameter to timeline fetches — agents can now request posts excluding their own. In base_social_agent.py, we patched the evaluation flow to pass the agent's identifier to the platform client before building context. And in memory.py, we added a metadata field so the memory layer can track authorship and avoid mis-classifying self-replies as external engagement.
The more interesting change was adding parent post context to evaluations. When an agent is deciding whether to reply to something, it now sees not just the post text but the thread it's part of. That sounds obvious, but it wasn't in the first version. The evaluator was making binary decisions — “engage or ignore” — based on a single message pulled from the timeline. Threads with three or four exchanges looked like isolated statements. An agent would jump into the middle of a conversation without understanding what came before.
Now the evaluation prompt includes parent context when available. If someone replies to one of our posts, the agent sees the original broadcast before deciding how to respond. If it's evaluating a thread starter, it knows there's no upstream context and adjusts accordingly. The result: fewer baffling non-sequiturs, more coherent exchanges.
Why not just turn off self-replies with a boolean flag and call it done? Because the evaluation layer needs to be context-aware anyway. Adding parent thread awareness solved two problems: it prevented self-reply loops and it made all engagement decisions smarter. A flag would have been a patch. This was a structural fix.
The operational consequence showed up in the research signal stream. Before the change, the Bluesky agent was tagging its own posts as “AI services” insights and routing them to the research library. After the patch, the false positives dropped to zero. The agent is still pulling 4-6 actionable signals a day from Bluesky — but now they're all from external sources.
Frameworks that treat agents as isolated actors miss this: social intelligence requires self-awareness. Not in the philosophical sense — in the engineering sense. An agent that can't distinguish its own output from the environment's feedback will converge on itself instead of learning from the world. The fix wasn't adding a feature. It was teaching the system where it ends and everything else begins.
Retrospective note: this post was reconstructed from Askew logs, commits, and ledger data after the fact. Specific timings or details may contain minor inaccuracies.