Claude-code’s most ruthless move: launching another agent is a tool call. From the parent’s perspective, Agent is just another tool—same level as Bash("ls"). Under the hood, it forks a new sub‑agent loop with its own memory, cache, and permissions. That’s the fork‑exec pattern for LLMs.
The agent as three layers
1. Configuration — AgentDefinition
src/tools/AgentTool/loadAgentsDir.ts:162: AgentDefinition = BuiltInAgentDefinition | CustomAgentDefinition | PluginAgentDefinition. The base definition packs everything you need to spin up a child agent:
agentType-
tools(a subset of the parent’s available tools) disallowedToolsmodelpermissionModemaxTurnsskillsmcpServershooksbackground-
isolation(worktreeorremote)
These definitions come from three places: built‑in TypeScript in src/tools/AgentTool/built-in/, user YAML frontmatter in .claude/agents/*.md files, and plugins via the MCP mechanism.
2. Runtime — isolated sub‑conversation loop
When the parent invokes an agent, the tool spawns an isolated query loop. Inside that loop:
- a fresh message history
[] - its own
fileStateCache - a separate
abortController - an independent
toolPermissionContext - default permission mode
acceptEdits(set atAgentTool.tsx:575)
Everything runs in the same Node.js process unless you set isolation=remote.
3. User‑facing — just another tool
From the parent Claude’s standpoint the agent is a plain tool:
-
name:
Agent -
input:
{ description, prompt, subagent_type, model, run_in_background } -
output:
{ result: string }
The closest system analogy: fork() + exec(). You fork a child Claude, give it a specific configuration and a task, let it work in an isolated context, and when it’s done you read back a result string. No shared state, no entanglement.
Where agent calls fit in the Task system
Claude Code models background tasks as a fixed set of TaskTypes. The agent tool maps to these types:
-
local_bash– likesubprocess.run()for a shell command -
local_agent– fork a sub‑process running another Claude agent (our fork‑exec) -
remote_agent– an HTTP call to a remote inference service - > TODO: list the remaining four TaskTypes and when they’re used
The Task interface exposes exactly one control: kill() — essentially SIGTERM for agent processes. Background tasks in LangGraph (e.g., an async embedding) follow the same pattern, hardened here into a small typed enumeration.
What you’d grind on in an interview
If someone tells you they built an agent‑as‑tool‑call system like this, don’t let them wave their hands. Ask:
- How are messages isolated? (Is each sub‑agent truly stateless from the parent, or does any context leak through system prompts or shared memory?)
- How are tools isolated? (Can a child agent call tools the parent didn’t explicitly allow? What about side effects, like writing to an MCP server?)
- How do you prevent concurrent file‑write collisions? (When two agents mutate the same file, who wins? Is there a worktree, file locking, or something else?)
Those three questions cover the real complexity. The fork‑exec metaphor is clean until two processes touch the same disk.
Top comments (0)