Context Isolation via Subagents
Also known as: context quarantine, exploration sandboxing, clean-room subtasks
Send noisy, exploratory work to a subagent with a disposable context and keep only its conclusion in the main thread.
The problem
Exploration is dirty. Finding the right file takes twelve searches that return mostly irrelevant results; diagnosing an error takes six failed hypotheses. All of that lands in the main agent's context and stays there, and the residue actively hurts: the model keeps referring back to rejected hypotheses, re-reads paths it already ruled out, and reasons increasingly poorly as the ratio of noise to signal climbs.
The pattern
Push exploratory work into a subagent with its own context. The subagent burns as much context as the search requires, and returns only its conclusion — a file path, a diagnosis, a short answer — to the parent. The parent's context grows by a sentence instead of thirty thousand tokens, and the failed attempts vanish with the subagent.
Use when
- A subtask has a high ratio of intermediate noise to final signal — search, triage, exploration
- The answer is small even though finding it is expensive
- The main agent is degrading over a long run as context fills with dead ends
- You want to try several approaches without contaminating the main line of reasoning
Avoid when
- The parent needs the intermediate detail — it is evidence, not noise
- The subtask is cheap enough that spawn overhead dominates
- The subagent would need most of the parent's context to do its job, defeating the isolation
Tradeoffs
GainMain-thread context stays clean, so late-run reasoning stays as good as early-run
CostThe parent cannot see how the conclusion was reached, which complicates debugging
GainFailed exploration costs nothing permanent — it is discarded with the subagent
CostA subagent that misunderstands its brief fails invisibly and confidently
GainSeveral approaches can be tried in parallel without interfering
CostToken spend rises; the parent pays for exploration it never sees
This is orchestrator–worker applied for a different reason. There, the motive is parallelism; here, it is hygiene — you delegate not to go faster but to keep the mess out of the room where the important reasoning happens.
Delegate the search, keep the answer
The parent asks a question, the subagent answers it. What makes the pattern work is a return contract narrow enough to force compression: a schema with a short answer field, the evidence that mattered, and an explicit confidence. Without a schema, subagents return everything they saw and the isolation buys nothing.
class Finding(BaseModel):
answer: str = Field(max_length=1200) # the conclusion, not the journey
evidence: list[str] = Field(max_length=5)
confidence: Literal["high", "medium", "low"]
caveats: str | None = None
INVESTIGATE_TOOL = {
"name": "investigate",
"description": (
"Delegate an open-ended search or diagnosis to a fresh agent with its own "
"context. Use when finding the answer will take many steps but the answer "
"itself is short. The subagent sees only what you pass in `context` — it "
"cannot see this conversation."
),
"parameters": {
"type": "object",
"properties": {
"question": {"type": "string", "description": "One specific question to answer."},
"context": {"type": "string", "description": "Everything the subagent needs. It has no other context."},
"done_when": {"type": "string", "description": "What a complete answer contains."},
},
"required": ["question", "context", "done_when"],
},
}The subagent cannot see the parent's conversation, and parents forget this constantly — dispatching "find where that's configured" with no antecedent for "that". Make the context parameter required, and validate that it is non-trivial before spawning.
Bounding the sandbox
An isolated context is also an unsupervised one. Give every subagent a step ceiling, a token ceiling, and a read-only tool set by default — exploration should not be able to mutate anything. A subagent that exhausts its budget returns what it has with low confidence rather than failing outright, which is usually still useful to the parent.
async def investigate(question: str, context: str, done_when: str) -> Finding:
budget = Budget(max_steps=15, max_tokens=60_000, wall_clock_s=90)
try:
return await agent_loop(
system=INVESTIGATOR_SYSTEM,
task=f"Question: {question}\n\nContext:\n{context}\n\nComplete when: {done_when}",
tools=READ_ONLY_TOOLS, # exploration never mutates
budget=budget,
response_schema=Finding,
)
except BudgetExhausted as e:
# Partial knowledge beats a hard failure — say so honestly.
return Finding(
answer=e.best_partial_answer or "Could not determine within budget.",
evidence=e.evidence[:5],
confidence="low",
caveats=f"Stopped after {e.steps} steps: {e.reason}",
)Keeping the trail without keeping the tokens
The cost of isolation is that the parent cannot see the work. Resolve that outside the context: persist the subagent's full transcript to storage keyed by an id, and include only the id in what returns to the parent. Debugging retrieves the transcript on demand; the context never pays for it.
- Force compression with a narrow return schema — otherwise you get the whole transcript back
- Require an explicit context parameter; the subagent cannot see the parent's thread
- Cap steps, tokens, and wall clock; return low-confidence partials instead of failing
- Default subagents to read-only tools — unsupervised exploration should not mutate
- Persist full transcripts out of band and return only an id for later inspection
Watch the ratio of subagent tokens spent to characters returned. A subagent that burns 50k tokens to return two sentences is doing exactly its job; one that burns 50k and returns 8k of prose is leaking the mess back into the parent and needs a tighter schema.