How Claude Code Agent Teams Actually Works - reverse Claude Code Agent Teams use CC

Metadata

TL;DR

Claude Code Agent Teams lets you spin up multiple Claude Code sessions that work together as a coordinated team β€” with a lead agent, teammate agents, shared task lists, and inter-agent messaging. But what's really going on under the hood? This post goes beyond the docs: we reverse-engineered the compiled source to understand the filesystem-based IPC, React/Ink polling architecture, three spawn backends, and the 13-operation TeammateTool that makes it all work.

Claude Code Agent Teams β€” Infographic

Why This Post Exists

The official docs tell you what Agent Teams does. This post tells you how it does it β€” by reading the actual compiled source code (cli.js), tracing function calls, and mapping out the internal architecture. If you want to build on top of Agent Teams, extend it with hooks, or just understand the engineering decisions behind multi-agent coordination, this is for you.


The Big Picture Architecture

At its core, Agent Teams is a filesystem-coordinated, polling-based, multi-process orchestration system built on top of Claude Code's existing Task/subagent infrastructure.

Big Picture Architecture

The critical design decision: All coordination happens through the local filesystem, not sockets, pipes, or HTTP. Every message, task update, and team config change is a JSON file read/write with file locking. This is simple, debuggable, and works across all spawn backends without protocol negotiation.


Enabling the Feature Gate

Agent Teams is guarded by a feature flag function (lA() in the compiled source) that checks:

Both lA() (feature enabled) and team membership detection (Mz(), which checks if the current process is a teammate) must pass before any team functionality activates. The gate controls everything: tool registration, inbox polling, team context injection, and teammate spawning.


The Seven Core Primitives

The entire system is built on seven primitives that the lead agent uses as tools:

Primitive
What It Does
File System Effect

TeamCreate

Initializes team namespace

Creates ~/.claude/teams/{name}/config.json

Task (with team_name)

Spawns an independent teammate

Starts new process + writes to config.json members

TaskCreate

Creates a work item

Writes {id}.json to ~/.claude/tasks/{name}/

TaskUpdate

Changes task state

Updates JSON file with locking

TaskList

Discovers available work

Reads all task JSON files

SendMessage (write)

Direct message to one teammate

Appends to recipient's inbox JSON

SendMessage (broadcast)

Message all teammates

Appends to every teammate's inbox JSON


The Mailbox System β€” Reverse Engineering IPC

This is the most interesting part of the architecture. Let's trace exactly how Agent A sends a message to Agent B.

Inbox File Structure

Each agent gets a personal inbox file:

Each inbox is a JSON array of messages:

The Write Path β€” writeToMailbox()

From the compiled source, the write path works like this:

The file locking uses lockSync() from a lock library (likely proper-lockfile), creating a .lock companion file. This prevents race conditions when multiple teammates try to write to the same inbox simultaneously.

The Read Path β€” InboxPoller

The InboxPoller is a React/Ink component (Claude Code's TUI is built with Ink) that runs inside each agent's render loop. On each poll cycle:

The message routing is the key insight: the inbox is a multiplexed channel carrying both human-readable messages and structured protocol messages (serialized as JSON strings inside the text field).

Message Type Hierarchy

Message Type Hierarchy

The text field does double duty: for regular messages, it's plain text. For protocol messages, it's a JSON string that gets parsed by type-checking functions (isShutdownRequest(), isPlanApprovalResponse(), etc.) β€” each implemented as a safeParse() call against a Zod schema.


Three Spawn Backends

When the lead spawns a teammate, the system picks one of three backends based on runtime detection:

Three Spawn Backends

InProcessBackend (Default)

The in-process backend runs teammates within the same Node.js process as the lead. Each teammate is tracked as an InProcessTeammateTask in the React/Ink AppState:

Switching between teammates uses Shift+Up/Down keyboard shortcuts, which cycle through the selectedIPAgentIndex in AppState. Each teammate gets its own conversation context within the shared process.

PaneBackendExecutor (tmux/iTerm2)

For split-pane mode, the PaneBackendExecutor creates a real terminal pane and sends a CLI command to it:

The teammate process starts as a completely independent Claude Code session. It discovers it's a teammate by checking the --agent-id flag, reads the team config to find the lead, and registers a "Stop" hook to send an idle_notification when it finishes.

Backend Detection Priority

The "auto" teammate mode setting follows this cascade. You can override with "in-process" to force single-terminal mode regardless of available backends.


The Task System β€” Shared Work Queue

Task File Format

Each task is an individual JSON file:

Self-Claiming Mechanism

When a teammate finishes its current task:

  1. It calls TaskList to read all task files

  2. Filters for tasks where status === "pending" and blockedBy is empty (all resolved)

  3. Attempts to claim by writing status: "in_progress" and owner: agentName with file locking

  4. If the lock fails (another teammate claimed it first), tries the next task

The file locking prevents double-claiming, but the system relies on eventual consistency β€” teammates poll the task list rather than receiving push notifications when tasks unblock.

Dependency Resolution

Dependencies are expressed as arrays of task IDs:

When a teammate marks task 1 as completed, task 3 checks if all entries in its blockedBy list are completed. The check happens at poll time β€” no push notification. This is why "task status can lag" is a documented limitation.


Team Configuration β€” The Central Registry

The teamAllowedPaths field enables team-wide permission grants β€” the lead can authorize all teammates to write to specific directories without per-agent permission prompts.


The TeammateTool β€” 13 Operations

The TeammateTool is the lead's orchestration interface, exposed as a single tool with an operation discriminator:

TeammateTool β€” 13 Operations

Each operation maps to filesystem operations:

  • spawnTeam β†’ creates directories + config.json

  • write β†’ appends to one inbox file

  • broadcast β†’ appends to ALL inbox files (expensive: one write per teammate)

  • requestShutdown β†’ writes structured message to teammate's inbox

  • approveShutdown β†’ teammate writes back, lead removes from config

  • cleanup β†’ checks no active members, then deletes team directory


The Idle Notification Flow

This is the handshake that closes the feedback loop when a teammate finishes:

Idle Notification Flow

The idle notification includes an optional summary field β€” a compressed summary of the teammate's recent conversation, produced by the getLastPeerDmSummary() function. This gives the lead context about what the teammate accomplished without reading their full conversation history.


Hooks Integration β€” Quality Gates

Two hook events integrate with the team lifecycle:

TeammateIdle Hook

Fires when a teammate is about to go idle. The hook receives the teammate's context and can:

  • Exit 0: Allow idle (normal)

  • Exit 2: Send feedback message and keep the teammate working

Use case: Run eslint on the teammate's changes. If linting fails, exit 2 with feedback telling the teammate to fix the issues.

TaskCompleted Hook

Fires when a task is being marked complete. Same exit code semantics:

  • Exit 0: Allow completion

  • Exit 2: Reject completion, send feedback

Use case: Run the test suite on affected files. No task can be marked "done" until tests pass.

TeammateInit β€” The Registration Hook

When a teammate starts (reconstructed from _p8 function in the source):

  1. Reads team config to find the lead agent's name

  2. Applies teamAllowedPaths permissions to its own session

  3. If this agent is NOT the lead, registers a "Stop" hook that sends an idle_notification to the lead when the teammate finishes

This registration is why idle notifications work automatically β€” every teammate registers the hook at startup.


Permission Delegation

One of the more sophisticated features: teammates can delegate permission requests to the lead.

Permission Delegation

This means permission prompts from all teammates bubble up to the lead's terminal, keeping you as the single approval point. The sandbox_permission_request/response variant handles file system sandbox permissions specifically.


Agent Teams vs. Subagents β€” The Architecture Difference

Aspect
Subagents (Task tool)
Agent Teams (Task + TeammateTool)

Process model

Background async tasks within one session

Independent Claude Code sessions

Context

Share parent's context window

Completely isolated context windows

Communication

Return results to parent only

Peer-to-peer messaging via inbox files

Coordination

Parent manages everything

Shared task list with self-claiming

IPC

In-memory (same process)

Filesystem JSON with file locking

Lifecycle

Managed by parent's abort controller

Independent with shutdown protocol

Spawning

Task({ subagent_type, prompt })

Task({ team_name, name, prompt })

Cost

Lower (shared context)

Higher (each gets full context window)

The architectural analogy: subagents are threads (lightweight, shared memory), agent teams are processes (isolated, communicate via IPC).


Real-World Patterns

Pattern 1: Parallel Specialists

Pattern 2: Competing Hypotheses

Pattern 3: Pipeline with Dependencies


Design Decisions Worth Noting

Why Filesystem IPC?

The filesystem approach has clear advantages for this use case:

  • Debuggable: You can cat any inbox file to see message history

  • Backend-agnostic: Works identically for in-process, tmux, and iTerm2 backends

  • Crash-resilient: Messages persist on disk even if a process dies

  • No server: No daemon, no port, no protocol negotiation

  • Cross-platform: Works on macOS, Linux, and WSL

The tradeoff is latency β€” polling a JSON file is slower than a socket push. But for agent coordination where messages are infrequent (seconds between them, not milliseconds), this is an excellent tradeoff.

Why Polling, Not File Watchers?

The InboxPoller reads the inbox file on each render cycle rather than using fs.watch() or chokidar. This is likely because:

  • File watchers are notoriously unreliable across platforms

  • The polling happens naturally as part of the Ink render loop (already on a tick)

  • No additional resource cleanup needed on process exit

Why JSON, Not SQLite?

Individual task files (one per task) rather than a database means:

  • No shared database lock contention

  • Each task can be locked independently

  • Easy to inspect and debug

  • No migration story needed

The downside is lack of atomic multi-task operations, which is why "task status can lag" is a documented limitation.


Known Limitations (and Why They Exist)

Limitation
Root Cause

No session resumption with in-process teammates

In-process teammates are React state β€” not serialized to disk

Task status can lag

Polling-based, no push notifications for state changes

One team per session

Team config is a singleton in AppState

No nested teams

Teammates check Mz() (isTeammate) and refuse to spawn

Lead is fixed

leadAgentId in config.json is set at creation, never updated

No split panes in VS Code terminal

No tmux support in VS Code's pty implementation


Quick Reference

Action
How

Enable

CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 in settings.json

Start a team

Describe a parallel task in your prompt

Display mode

--teammate-mode in-process or tmux (auto-detected)

Talk to teammate

Shift+Up/Down (in-process) or click pane (split)

Delegate mode

Shift+Tab (restricts lead to coordination-only tools)

Assign tasks

Tell the lead to assign, or let agents self-claim

Require approval

Include "require plan approval" in spawn prompt

Shut down one

"Ask [name] teammate to shut down"

Clean up all

"Clean up the team"

Quality hooks

TeammateIdle (exit 2 = keep working), TaskCompleted (exit 2 = reject)


Key Takeaways

  1. Agent Teams is filesystem-based IPC β€” JSON inbox files with file locking, no sockets or databases

  2. InboxPoller is a React/Ink component that polls on each render cycle and routes 10+ message types

  3. Three spawn backends (in-process, tmux, iTerm2) detected at runtime with automatic fallback

  4. 13 TeammateTool operations provide the full orchestration API: lifecycle, messaging, shutdown, plan approval

  5. Task self-claiming uses file locking to prevent race conditions across concurrent teammates

  6. Permission delegation bubbles all approval requests from teammates up to the lead

  7. Idle notifications are sent automatically via a "Stop" hook registered at teammate startup

  8. The design prioritizes simplicity and debuggability over performance β€” a smart tradeoff for agent coordination


Sources

Last updated