Skip to content

Multi-Agent Interleaving

When Claude Code spawns sub-agents via the Agent tool, multiple independent content streams interleave on the same stdout. This breaks naive index-based deduplication.

The Problem

With a single lastContentIndex, interleaved events from different agents corrupt the dedup state:

Main agent:   content=[thinking, text, tool_use(Agent)]  → index=3
Sub-agent A:  content=[text_A]                           → length 1 < 3 → reset, index=1
Sub-agent B:  content=[text_B]                           → length 1 = 1 → SKIPS text_B!

Sub-agent B's content array has length 1, same as the current lastContentIndex. The loop starts at index 1, but there's only 1 block (at index 0) — so B's event is silently dropped.

The Solution: Content Fingerprinting

The translator fingerprints the first content block of each assistant event. When the fingerprint changes, it knows the events are from a different agent context and resets the dedup index.

How Fingerprinting Works

Block TypeFingerprint
tool_use"tool_use:{id}" (unique ID is the strongest signal)
thinking"thinking:{first 64 chars}"
text"text:{first 64 chars}"
Other"{type}:{tool_use_id or 'unknown'}"

Behavior on Context Switch

Main agent:   content=[thinking("plan...")]              → fingerprint="thinking:plan..."
                                                           index=1

Sub-agent A:  content=[text("Searching...")]             → fingerprint="text:Searching..."
                                                           ≠ previous → RESET, index=1

Sub-agent B:  content=[text("Running tests...")]         → fingerprint="text:Running tests..."
                                                           ≠ previous → RESET, index=1

Sub-agent A:  content=[text("Searching..."), tool_use]   → fingerprint="text:Searching..."
                                                           = saved → DEDUP, process from index 1

What Gets Re-Emitted

When the translator switches context (fingerprint changes), it resets the index to 0 and re-processes all blocks in the new content array. This means:

  • Returning to an agent after a context switch will re-emit that agent's existing blocks
  • This is a deliberate trade-off: duplicate events are safer than missing events

For a relay/dashboard, duplicates can be handled on the consumer side (e.g., by tracking tool_use IDs). Missing events would be invisible data loss.

Example: Parallel Sub-Agents

STDOUT SEQUENCE:
─────────────────────────────────────────────────────────────
1. system/init
2. assistant [thinking, text, tool_use(Agent_A), tool_use(Agent_B)]   ← main agent
3. assistant [text("Agent A searching...")]                           ← sub-agent A
4. assistant [text("Agent A searching..."), tool_use(Grep)]           ← sub-agent A (cumulative)
5. assistant [text("Agent B testing...")]                             ← sub-agent B (context switch!)
6. assistant [text("Agent B testing..."), tool_use(Bash)]             ← sub-agent B (cumulative)
7. user [tool_result(Grep), tool_result(Bash)]                        ← results
8. user [tool_result(Agent_A), tool_result(Agent_B)]                  ← agent results
9. assistant [text("Both done.")]                                     ← main agent resumes
10. result/success

TRANSLATOR OUTPUT:
─────────────────────────────────────────────────────────────
1 → session_meta
2 → thinking_delta, text_delta, tool_use(Agent_A), tool_use(Agent_B)
3 → text_delta("Agent A searching...")           ← context switch, reset
4 → tool_use(Grep)                               ← dedup within A's context
5 → text_delta("Agent B testing...")             ← context switch, reset
6 → tool_use(Bash)                               ← dedup within B's context
7 → tool_result(Grep), tool_result(Bash)
8 → tool_result(Agent_A), tool_result(Agent_B)
9 → text_delta("Both done.")                    ← context switch, reset
10 → turn_complete

Usage

No special API is needed. The Translator handles this automatically:

typescript
const translator = new Translator()

// Works correctly even with interleaved multi-agent events
for (const line of lines) {
  const event = parseLine(line)
  if (!event) continue
  const relays = translator.translate(event)
  // relays are correctly deduplicated across agent contexts
}

Not affiliated with or endorsed by Anthropic.