AI Agents
Why Claude Code subagents run in isolated contexts, what isolation means for tool access and memory.
Developers building their first multi-agent Claude Code system hit a consistent confusion: "Why does my subagent not know what the parent agent just did?" The answer is isolation - each Claude Code subagent runs in its own context, with its own message history, its own tool call budget, and no access to the parent session's accumulated state. This is by design, not a limitation, and understanding the boundaries of isolation is what separates developers who design multi-agent systems that work from those who spend hours debugging why an agent "forgot" something.
When Claude Code invokes a subagent, it starts a new Claude API session with only the subagent's own system prompt (from the .claude/agents/[name].md instruction body) and the task it was given. The subagent does not receive the parent's conversation history, does not know what tools the parent called, and cannot read the parent's in-progress work. Isolation boundaries apply to:
messages[] array is not passed to the subagentWhat is NOT isolated (shared across agents):
Isolation prevents three failure modes that emerge when agents share context naively:
Isolation makes each subagent's behaviour attributable solely to the task it was given and the tools it called - which is the property that makes multi-agent systems debuggable.
Because isolation is intentional, bridging it must also be intentional. Three patterns for passing context across agent boundaries:
// Pattern 1 - Explicit context injection in the task description
// The parent agent summarises relevant state and includes it in the subagent's task.
// The subagent receives exactly what it needs, nothing more.
function buildSubagentTask(
taskGoal: string,
parentContext: {
completedWork: string[];
relevantFindings: string[];
constraints: string[];
}
): string {
const contextBlock = [
parentContext.completedWork.length > 0
? `Already completed:
${parentContext.completedWork.map(w => `- ${w}`).join('
')}`
: '',
parentContext.relevantFindings.length > 0
? `Relevant context:
${parentContext.relevantFindings.map(f => `- ${f}`).join('
')}`
: '',
parentContext.constraints.length > 0
? `Constraints:
${parentContext.constraints.map(c => `- ${c}`).join('
')}`
: '',
].filter(Boolean).join('
');
return contextBlock
? `${contextBlock}
Your task: ${taskGoal}`
: taskGoal;
}
// Pattern 2 - File-based context sharing
// The parent writes a context file; the subagent's instruction body includes a step
// to read it before starting work. Uses the shared filesystem.
async function writeContextFile(taskId: string, context: object): Promise {
const path = `.claude/context-${taskId}.json`;
await writeFile(path, JSON.stringify(context, null, 2));
return path;
}
// In the subagent definition (.claude/agents/my-agent.md), the instruction body includes:
// "1. Read .claude/context-{TASK_ID}.json to understand the current task state."
// "2. Complete your assigned task."
// "3. Write your output to .claude/output-{TASK_ID}.json."
// Pattern 3 - Shared database / external store
// Parent and subagent use the same Supabase/Redis instance.
// The parent writes a task record; the subagent reads it by task ID.
// This is the most robust pattern for complex pipelines.
async function createTaskRecord(db: Database, task: AgentTask): Promise {
const { data } = await db.from('agent_tasks').insert({
goal: task.goal,
context: task.context,
status: 'pending',
created_at: new Date().toISOString(),
}).select('id').single();
return data!.id;
}
async function getTaskRecord(db: Database, taskId: string): Promise {
const { data } = await db.from('agent_tasks').select('*').eq('id', taskId).single();
return data as AgentTask;
}
Subagent tool access is defined by the tools: list in the agent's frontmatter, not inherited from the parent. A parent with access to Write, Bash, WebSearch, and Read spawns a subagent that has only the tools listed in that subagent's definition file. This is the tool-scoping aspect of isolation - a read-only subagent cannot write files even if the parent can, preventing accidental side effects from workers that should only read.
The agent definition body (the markdown instruction below the frontmatter) contains stable, task-independent instructions: the agent's role, its process, its output format, its constraints. The task description (what the orchestrator passes when invoking the subagent) contains task-specific state: the specific goal, the context from the parent, the constraints that vary per invocation. Mixing these - putting task-specific details in the definition body - means the agent definition must change with every new task, which defeats the purpose of having reusable agent definitions.
For how to write the agent definition body and frontmatter correctly, including which fields are available and how the description field controls routing, see the Claude Code subagents guide. For how to structure the orchestration layer that spawns and manages isolated subagents, see the agent supervisor pattern.