Part 2 · Case study
We built a Twitter reply agent without Jev. Here's what broke.
An autonomous agent that reads a developer's X feed, writes useful replies and posts them, ten per cycle, around the clock. It ran for days. Nine things went wrong, and every one of them made Slapify better.
The job
The brief was simple to say and hard to do well: act as an engagement assistant for a developer who posts about AI, Next.js, React and TypeScript. Search for recent posts on those topics, pick ones worth answering, write a short reply that adds something, post it, and move on. Ten replies per cycle, then sleep ten minutes, then do it again, until a human says stop. No original tweets, only replies. No approval step. And no em dashes, because the account owner doesn't write with them.
The setup, all of it existing Slapify features:
- Headless Chrome with a persistent profile, so the login survives restarts and no window is needed.
- Stealth mode, so the browser doesn't advertise itself as automated (Part 4).
- Memory to track which posts it had already answered.
- Telegram, to get status updates and send it instructions from a phone.
- A small supervisor script to restart the process if it ever exited.
What it didn't have yet was any help making the many small decisions a run like this is made of: which of these forty buttons is the reply box, did my reply actually post, is this a login wall. The main model made every one of them by reading the page. That turns out to matter, and it's what Part 3 is about. First, the story.
Nine things that broke
Each of these showed up in a real run, usually at the worst possible time. Click through them: what we saw, why it happened, and what changed in Slapify as a result.
Incident log
In the order we hit them.
The rule that stopped being a rule
The most instructive failure was the smallest. The prompt said, plainly, never use an em dash. For the first stretch of every run the agent obeyed perfectly. Then, somewhere past the first hour, dashes started appearing, and in one run more than half of the replies had one.
Replies containing an em dash
Every reply the agent typed across our runs, split at the first time its history was compacted.
The cause was compaction. When the conversation history gets long, older turns are summarised to make room. The summary restated the original task in its own words, and from then on the model worked from the restatement. The rule didn't vanish in one step. It lost its force: a rule the model reads as a summary of a preference, rather than as a line the user wrote, is one it feels free to trade off.
Two fixes, because each covers a different failure:
- The goal is never paraphrased. Compaction now keeps the original task word for word and only summarises what happened since. (It also never cuts between a tool call and its result, which could leave the model looking at an answer to a question it no longer remembered asking.)
- Rules that must hold every time belong in config, not in the prompt.
text_filtersrewrites text before it is typed into the page. In the runs after we added it, the model still produced an em dash seven times, and all seven were corrected before they reached X.
text_filters:
" — ": ", "
"—": ", "
The general lesson: a prompt is a request, and a request gets weighed against everything else in context. Anything that must be true every time should be enforced by the harness, where the model can't weigh it at all.
Ten per cycle, then three
Here is every cycle of the 26.8-hour run. The goal said ten replies per cycle.
Replies per cycle, 26.8-hour run
Hover or tap a bar for the cycle's details.
The first two cycles did ten. Then the number fell to three or four and stayed there for a day. Nothing crashed, and nothing looked wrong in the logs. We found two causes, and both are the kind of thing a long run surfaces and a short test never does:
- A one-off instruction that became permanent. In one session the owner sent a Telegram message asking for a lighter cycle, once. The agent saved it to memory and kept applying it every cycle after, because it was the most recent thing the user had said. Live instructions now apply to the cycle they arrive in. They carry forward only if the user says so ("from now on…"), and when compaction carries one forward it quotes it exactly. The goal stays the authority for everything else.
- Running out of fresh posts. The agent remembered every post it had answered, and searched the same fourteen queries in rotation. As the hours went on, more of each cycle went to finding posts, recognising them as already answered and skipping them (you can see it in the log: "already replied in a prior session… avoiding duplicate engagement"). We relaxed the dedupe to what matters, never the same post twice, and rotated queries more widely.
Where the calls go
An autonomous run spends most of its time looking, not acting. This is every tool call in the long run:
Tool calls by type, 26.8-hour run
Reading the page dominates. Every snapshot is roughly 20,000 characters the model has to read.
That first bar is the expensive one. Each get_page_state result averaged 20,577 characters, about five thousand tokens, and the agent took 825 of them. Most were for a narrow question: where is the reply box on this page? Answering that by reading five thousand tokens is like re-reading a whole menu to find the dessert section. Hold that thought.
What it cost
We price everything at list API rates. The run's log records every model turn, so we rebuilt the prompt the model saw on each of the 2,583 calls (system prompt, tool definitions, the goal, and history trimmed exactly as the harness trims it), counted about four characters per token, and priced the result.
| Run | Hours | Model calls | Replies | No caching | With caching | Per reply |
|---|
Two things stand out. First, prompt caching is not optional for an agent like this: 92% of each prompt is identical to the one before it, so caching cuts the bill by about 80%. Second, each reply costs about sixteen model calls. Finding the post, opening it, locating the reply box, typing, finding the button, checking it worked, remembering it. Most of those calls exist to make small decisions.
What would it cost you?
Uses the measured shape of this run: 15.8 model calls per reply, about 36,000 input tokens and 57 output tokens per call, 92% of input cacheable.
What we'd take from it
- Enforce, don't ask. Text filters, pinned goals, scoped live instructions: every fix that stuck moved a rule out of the prompt and into the harness.
- Never let the agent die of something temporary. A usage limit is not a crash. Neither is a truncated response or one bad turn.
- Guidance beats blocking. The old loop guard stopped the agent from doing legitimate work; telling it "this has failed twice, change approach" fixed the real problem without the side effects.
- Most of the bill is small decisions. Sixteen calls per reply, most of them some version of "which element" or "did that work". That is exactly the kind of question a smaller, specialised model should answer.
Which brings us to Jev.