cd /news/ai-agents/claude-code-agent-loop-deep-dive-9-s… · home › topics › ai-agents › article
[ARTICLE · art-152601] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

Claude Code Agent Loop Deep Dive (9): Sidechains, Sub-agents, and agentId Routing

A developer's deep-dive analysis of Claude Code's agent loop reveals that sub-agents run the same recursively invoked queryLoop function as the main agent, distinguished only by whether toolUseContext.agentId is undefined. Sub-agent messages are written to separate sidechain transcripts (.claude/subagents/agent-<agentId>.jsonl) rather than the main session JSONL, with the sidechain's first message's parentUuid pointing back to the Agent tool-use message in the main tree.

by read5 min views5 publishedOct 19, 2026

The previous eight posts described one loop: a user presses Enter, the loop runs until completion, and control returns to the user.

Claude Code also has a more surprising arrangement: a loop can run inside another loop.

When the primary conversation requests the Agent tool, Claude Code starts a sub-agent to perform an independent task and return only its final result. From the main loop's perspective, this is a long-running tool that eventually returns text. From the sub-agent's perspective, it is a complete loop with its own messages, tool execution, and stop decisions.

The central question is whether this requires a separate sub-agent loop implementation—or whether it is the same loop with a different context.

queryLoop, called recursively The obvious design would define two implementations, perhaps MainLoop and SubagentLoop. Claude Code takes the opposite approach: main agents and sub-agents run the same queryLoop function.

Starting a sub-agent follows this shape:

The main loop sees an Agent tool_use
    ↓
Create a new toolUseContext
    ├── agentId = a new generated ID
    ├── messages = [] (a fresh history)
    ├── system prompt = a sub-agent-specific prompt
    └── other scoped fields
    ↓
Call queryLoop(newToolUseContext) recursively
    ↓
The sub-agent reaches completed and returns its result
    ↓
Append that result to the main history as tool_result

It is one function, recursively invoked. A sub-agent can even start another sub-agent, which is simply another recursive call.

The benefit is maintenance: a bug fix, recovery transition, or scheduling improvement applies to both primary and sub-agent runs without being implemented twice.

agentId routes the main-thread-only behavior Shared code still needs to know which context it is running in. Claude Code uses a single flag:

toolUseContext.agentId === undefined toolUseContext.agentId is a generated identifier The loop contains several checks like if (!toolUseContext.agentId). They select behaviors that exist only on the primary thread:

CLAUDE.md One flag covers many branch points. It costs some local readability—each branch needs a reason—but preserves the much larger advantage of shared loop code.

Main-loop messages are persisted in a session transcript such as:

~/.claude/projects/<encoded-project>/<sessionId>.jsonl

Each line is a message and includes a parentUuid pointing to its predecessor.

Sub-agent messages do not go into that primary file. They are appended independently to a sidechain transcript:

.claude/subagents/agent-<agentId>.jsonl

This separation matters because the message histories are separate. If they shared a file, resuming a session would need to disentangle primary and sub-agent messages. A dedicated sidechain keeps the main transcript clean while preserving the sub-agent trace for debugging.

The main transcript records only the sub-agent's final tool_result, paired to the Agent tool-use ID. Its intermediate reasoning and tool calls stay in the sidechain.

parentUuid creates a cross-file tree The primary session's parentUuid values form a conversation tree. A sub-agent has its own independent chain in its own JSONL file, but its first message points back to the UUID of the main-loop message that requested the Agent tool:

Main tree:
  user message
    └── assistant: Agent tool_use
          └── user: tool_result containing the final sub-agent result

Sidechain tree:
  sub-agent message 1 (parentUuid = Agent tool_use UUID)
    └── sub-agent message 2
          └── ...

Logically, the sidechain hangs from the parent tool-use node. A tool can merge both trees when a complete trace is needed, while normal session viewing remains focused on the clean main tree.

As Part 1: From Tool Declarations to Pre-Execution Approval explains, a sub-agent does not inherit session-level alwaysAllow decisions from the main agent. It keeps immutable CLI-level configuration but starts with its own allowedTools policy.

The principle is conservative by design: trusting the main agent to “always allow Bash” does not automatically mean trusting an independent sub-agent the same way. The trade-off is that a sub-agent may ask for approval again; the benefit is safer default behavior.

This is especially important because shared queryLoop code means a sub-agent otherwise has the same underlying capabilities: tools, hooks, and recovery mechanisms. The isolation happens through context, not a stripped-down code path:

allowedTools allowlist. The code is shared and the capability set is broad, but each sub-agent sees only its own context.

Claude Code also supports forks. A normal sub-agent starts with a fresh context and one assigned instruction. A fork inherits the main agent's full message history, but replaces every historical tool_result with the placeholder:

Fork started — processing in background

Why replace results? Forks are commonly launched in batches for similar independent tasks. If every fork preserved a different set of detailed tool results, their prompts would diverge and prompt-cache reuse would disappear. Replacing results makes the fork histories byte-identical, producing high cache reuse and sharply reducing the cost of a large batch.

Architecturally, forks are an extreme application of the same shared loop: many ordinary sub-agent loops can run with no separate execution engine.

Sub-agents do not create their own AbortController; they receive the main loop's controller through toolUseContext. Ctrl-C on the primary loop therefore interrupts every active sub-agent too.

The reverse does not apply to errors. If a sub-agent hits prompt_too_long or another unrecoverable issue, it runs its own recovery path first. Only its final failure returns to the main loop as an error tool_result, and the main agent decides what to do next.

This is a useful isolation boundary: primary cancellation propagates downward, while sub-agent failures remain contained unless the main loop chooses to act on their reported result.

Because sub-agents reuse queryLoop, the mechanisms from the previous posts still apply inside them:

The lasting insight is simple: one loop, two contexts. Claude Code's loop is not only a mechanism for a user talking to an LLM; it is a general mechanism for an AI to autonomously advance a task. The main conversation, a sub-agent, and a fork are all instances of it.

The final article closes the series by extracting five core insights and assembling the ten posts into one complete map of a general-purpose agent loop.

Primary source locations (v2.1.220):

src/query.ts: recursive src/tools/AgentTool/AgentTool.tsx: Agent-tool entry point. src/tools/AgentTool/runAgent.ts: sub-agent execution, permission reset, and isolated storage.src/utils/sessionStorage.ts: sidechain transcript paths. src/tools/AgentTool/forkSubagent.ts: fork behavior and tool-result placeholders. Related reading:

── more in #ai-agents 4 stories · sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/claude-code-agent-lo…] indexed:0 read:5min 2026-10-19 · —