The channel was named after the wrong thing
The model produced five tokens. The browser received 195. The streaming code was fine; the problem was the name on the channel.
Code written by a user runs inside an agent. That code can call another agent. When it does, the answer comes back token by token, and those tokens travel on to the browser.
I ran a single line: print("Hello World"). The model produced five tokens — print, (", Hello, World, "). The browser received 195 SSE events.
Nothing was wrong with the content — the same thing kept coming. The same sentence stacked up on screen dozens of times.
The channel was named after the request ID
When the chat service takes a request, it subscribes to one Redis channel. The channel is named sse.response:{request ID}. Whatever appears there is pushed to the browser. That is the SSE response.
The sandbox publishes to the same channel directly. It has to, if progress messages like "generating code…" are to reach the browser. So far this is the design.
The problem is that the sandbox passed its request ID down unchanged when it called another agent. Same request ID, same channel name. Same channel name, and the second chat service handling that agent subscribes to the very same channel.
01A subscribes to sse.response:ORIG. Correct so far.
One writer, two readers — and one of the readers was the writer.
The shape of the echo
The order in the logs shows the mechanism directly. The first token inflates on its own, and from there the whole run of tokens so far circles as one block.
| Stage | What appeared on the channel |
|---|---|
| 1 | print × 7 |
| 2 | (" + 1 earlier token |
| 3 | Hello + 2 earlier tokens |
| 4 | World + 3 earlier tokens |
| 5 | ") + 4 earlier tokens, the 5-token block repeating 38 times |
By its symptom this is a streaming bug, so the streaming code is where I looked first. Buffering, chunk boundaries, reassembly. None of it. Duplication is not a pipe problem but a subscriber-count problem. One writer and two readers emit twice however well the pipe is written. While you are looking at the pipe, you cannot see that.
The same bug wearing a second face
Weeks later a different symptom arrived. Not a nested call this time but parallel ones: code calling two agents at once for the weather in Seoul and in Los Angeles.
Los Angeles came back clean. Seoul came back like this.
## 🌤# 🌤️# 🌤️# 🌤️ Tomorrow# 🌤️ Tomorrow#'s Weather 🌤️ Tomorrow#'s
Weather 🌤️ in Tomorrow Seoul#'s Weather 🌤 (️Web Search in Tomorrow Seoul#'s …Tokens were not repeating — they were being spliced into each other's positions. While streaming, a citation goes out as a placeholder first, and once the source is resolved an event follows saying "replace this string with that one". Two parallel requests shared one channel, so those replacements landed in each other's text. They applied where a replacement had already happened, and partial matches accumulated.
The symptoms look nothing alike; the cause is one line. The channel is named after the request ID, and the request ID was not unique per stream. Nested, it echoes. Parallel, it interleaves.
Two fixes, one shape
The first thing shipped was a stopgap: move publishing out of the agent level and up to the orchestrator level. Remove the publisher on the inside and the loop cannot close. The symptom stopped; the channel name did not change.
The real fix derives a new ID for the inner call.
# A fresh ID for the inner call — the channels split
inner_request_id = uuid7str()
inner_headers = {
**headers,
"x-request-id": inner_request_id,
"x-request-traces": f"{x_request_id},{inner_request_id}",
}The point is that the first value in x-request-traces stays the original. The publish target is taken from that first value, so the channels split while the path out to the browser stays the single one it always was. The trace chain survives too.
The parallel case ended the same shape. When the shared parallel client builds per-task headers, it derives the request ID as {original}-{call name}-{8 random chars}. Read the name and you can see which original it branched from and which call it is.
What is still broken
The receiving side is still silent. What got fixed was the caller. The chat service still fills in its own name when the originating-agent header is absent. An empty value on a nested call is almost certainly a mistake, and it is neither rejected nor logged.
Sandbox code has no contract. The file behind this incident is user code, one per agent, living outside version control. Checking it now: it forwards the agent-name headers but still passes the original request ID. The shared client handles all thirteen headers for you, and nothing blocks the path around it that hand-rolls an HTTP client. The gap is not knowledge. It is the bypass.
Nothing counts who published, and how often. Request ID, originating pod and timestamp, logged just before each publish, would have made "there are two subscribers" visible on day one. That logging does not exist. The post-mortem records it as needed.
There is no regression test. One check — an agent calls an agent, count the events the browser receives — ends this class of bug. Once the incident closed it stopped feeling urgent, which is exactly why the same cause resurfaced in the parallel path.