Thank you, you're welcome, thank you: the loop that ended my software factory's mail
The previous post ended with a premise: one session per task, an agent that can work on several tasks, and learning that runs apart from the work. This post is about what happened when I built it, and why I ended up throwing most of it away, including the mail.
I did this with Claude Code again. A fair number of my messages in these sessions are corrections. I'll leave those in where they matter.
Starting blank
On Oct 7 I told Claude to throw out all preconceptions: if one agent held several tasks, learnings would land in one place instead of splitting across two builders. I also wanted to fix how I experimented, because one experiment at a time was too slow.
Along the way I rejected several of its ideas. A git worktree per task made
the wake script know about repositories, and I said a builder decides itself
whether to clone. Putting prompts inside .mjs files meant that
changing a prompt looked like changing running code. A consolidation prompt
that wrote a skill for everything the model already did well was pointless: a
skill is only needed when the model trips repeatedly.
The result was a role-agnostic machinery built on
pi-durable, with builder, lead and validator as three roles on one shared setup. I
restored the old images and tooling around it. A new "floor" image hosted all
the accounts on one Postfix.
The thank-you loop
I ran the family-hub scenario on it on Oct 8. By Oct 9 something odd showed up in the watch output. The lead and the builder were exchanging sign-offs: "Awesome — the factory is in great shape... 馃彮". Two threads of it ran for about 2.5 hours. I sent a correction and it stopped. A builder–validator loop then ran to 44, 47, 50 messages, made of emoji and little else, and it survived two corrections. While that one went on, a third started between lead and builder, after the first had been fixed.
- Per-thread correction doesn't work. The tendency recurs opportunistically on different pairs.
- It's tooling, not wording. I added a line to AGENTS.md about not replying when there's nothing to say. I wasn't convinced it would hold the weight of a rule, and it didn't.
I stopped the run with two of three loops unresolved, and wrote it up as a finding.
Mail machinery that kept breaking
The loop wasn't the only trouble. Task tracking over mail was a bug factory. Replies landed in the wrong task's session; an agent wrote files to track message-to-task relations; the model reached for Python to send mail. On Oct 11 I told Claude, in so many words, that this was the n-th time it was fixing the mail machinery for task tracking.
The transcript was the answer. The reply mechanism and the
File: path in the turn input had never been used in any earlier
run. They came in with the session-per-task design on Oct 6, after the last
real run. In the records, one run had 167 mail -s and 0
reply, the other 80 and 0. I had built the factory on a mechanism
that had never worked.
Is mail the problem?
At that point I stopped asking how to fix the mail and asked whether the agent should know about mail at all. I looked at what others do: people building factories with large custom frames that launch one agent per PR, ticket or CI failure. And I noticed that I was telling the model it was receiving mail, to use mail to answer, and to use mail to talk to others. That was leaking a transport into the agent.
I tried tools named reply and ask, then went back to
the "old good" Pi, and kept the task id in an environment variable. Then it
got worse. On Oct 12 the lead didn't make tool calls at all and wrote the bash
commands as plain text. I tried prompt changes, other tool names and a
different model, and I suspected the jump to a new Pi version. Then I moved to
the stronger Qwen3-Coder-Next, partly to test whether a bigger model would
ease some of the pain. On Oct 14 the lead was going in circles: tests pass,
write STATUS.md, re-verify, edit, re-run, and never call reply.
I stopped. The last implementation changes were, in my words, "a total failure". The old repository stays as a branch for the record.
A detour that was useful
Between the failures I did two things that mattered later:
- I tried a bench for skills: variants of one skill scored against assertions, with the skill's own token cost counted. In a user-need-to-idea test the shortest variant won: 90 tokens, 100% correct. That was also where I concluded that turning a need into a product idea needs a conversation with a person, so it isn't a single agent turn.
-
I spent a day on local inference: other models, pruning, other servers. It
isn't a story for this post, but it left
docs/model-server.md, with benchmarks and a survey of what fits on 6 GB of VRAM.
What I take from this
- Politeness is a failure mode. Left alone, two agents that have nothing to decide will keep each other busy.
- Don't build on a mechanism no run has used. The record said so, and I hadn't looked.
- What the agent knows about the transport is a design choice. I haven't answered it yet. I do know I don't want the agent to depend on it.
- Frame work still beats model work, again.
What's next
Start from the factory's goal, and not from the agents. A task goes in, tests and an implementation are written without seeing each other, and a plain program decides when it's done. Agents wouldn't know there is a factory at all.

Comments
Post a Comment