{"slug": "claude-code-agent-loop-deep-dive-9-sidechains-sub-agents-and-agentid-routing", "title": "Claude Code Agent Loop Deep Dive (9): Sidechains, Sub-agents, and agentId Routing", "summary": "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.", "body_md": "The previous eight posts described one loop: a user presses Enter, the loop runs until completion, and control returns to the user.\n\nClaude Code also has a more surprising arrangement: **a loop can run inside another loop**.\n\nWhen 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.\n\nThe central question is whether this requires a separate sub-agent loop implementation—or whether it is the same loop with a different context.\n\n`queryLoop`, called recursively\nThe 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.\n\nStarting a sub-agent follows this shape:\n\n```\nThe main loop sees an Agent tool_use\n    ↓\nCreate a new toolUseContext\n    ├── agentId = a new generated ID\n    ├── messages = [] (a fresh history)\n    ├── system prompt = a sub-agent-specific prompt\n    └── other scoped fields\n    ↓\nCall queryLoop(newToolUseContext) recursively\n    ↓\nThe sub-agent reaches completed and returns its result\n    ↓\nAppend that result to the main history as tool_result\n```\n\nIt is one function, recursively invoked. A sub-agent can even start another sub-agent, which is simply another recursive call.\n\nThe benefit is maintenance: a bug fix, recovery transition, or scheduling improvement applies to both primary and sub-agent runs without being implemented twice.\n\n`agentId` routes the main-thread-only behavior\nShared code still needs to know which context it is running in. Claude Code uses a single flag:\n\n`toolUseContext.agentId === undefined`\n`toolUseContext.agentId` is a generated identifier\nThe loop contains several checks like `if (!toolUseContext.agentId)`. They select behaviors that exist only on the primary thread:\n\n`CLAUDE.md` loading\nOne 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.\n\nMain-loop messages are persisted in a session transcript such as:\n\n```\n~/.claude/projects/<encoded-project>/<sessionId>.jsonl\n```\n\nEach line is a message and includes a `parentUuid` pointing to its predecessor.\n\nSub-agent messages do **not** go into that primary file. They are appended independently to a sidechain transcript:\n\n```\n.claude/subagents/agent-<agentId>.jsonl\n```\n\nThis 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.\n\nThe 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.\n\n`parentUuid` creates a cross-file tree\nThe 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:\n\n```\nMain tree:\n  user message\n    └── assistant: Agent tool_use\n          └── user: tool_result containing the final sub-agent result\n\nSidechain tree:\n  sub-agent message 1 (parentUuid = Agent tool_use UUID)\n    └── sub-agent message 2\n          └── ...\n```\n\nLogically, 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.\n\nAs [Part 1: From Tool Declarations to Pre-Execution Approval](https://dev.to/_94be737e156beb4d74df2/claude-code-agent-loop-deep-dive-1-from-tool-declarations-to-pre-execution-approval-397e) 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.\n\nThe 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.\n\nThis 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:\n\n`allowedTools` allowlist.\nThe code is shared and the capability set is broad, but each sub-agent sees only its own context.\n\nClaude 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:\n\n```\nFork started — processing in background\n```\n\nWhy 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.\n\nArchitecturally, forks are an extreme application of the same shared loop: many ordinary sub-agent loops can run with no separate execution engine.\n\nSub-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.\n\nThe 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.\n\nThis 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.\n\nBecause sub-agents reuse `queryLoop`, the mechanisms from the previous posts still apply inside them:\n\nThe 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.\n\nThe 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.\n\n**Primary source locations (v2.1.220):**\n\n`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.\n**Related reading:**", "url": "https://wpnews.pro/news/claude-code-agent-loop-deep-dive-9-sidechains-sub-agents-and-agentid-routing", "canonical_source": "https://dev.to/_94be737e156beb4d74df2/claude-code-agent-loop-deep-dive-9-sidechains-sub-agents-and-agentid-routing-28ne", "published_at": "2026-10-19 02:08:37+00:00", "updated_at": "2026-10-19 02:18:57.902427+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "large-language-models"], "entities": ["Claude Code", "Anthropic", "queryLoop", "agentId"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/claude-code-agent-loop-deep-dive-9-sidechains-sub-agents-and-agentid-routing", "markdown": "https://wpnews.pro/news/claude-code-agent-loop-deep-dive-9-sidechains-sub-agents-and-agentid-routing.md", "text": "https://wpnews.pro/news/claude-code-agent-loop-deep-dive-9-sidechains-sub-agents-and-agentid-routing.txt", "jsonld": "https://wpnews.pro/news/claude-code-agent-loop-deep-dive-9-sidechains-sub-agents-and-agentid-routing.jsonld"}}