O que é Isolamento de Contexto?
Isolamento de Contexto (Context Isolation) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Contexto e conhecimento. Rodar subtarefas em contextos separados e trazer de volta só o resultado.
Context Isolation
Isolamento de Contexto (Context Isolation) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Contexto e conhecimento. Rodar subtarefas em contextos separados e trazer de volta só o resultado.
Rodar subtarefas em contextos separados e trazer de volta só o resultado. Serve para sistemas em que o material lido, o raciocínio intermediário ou as instruções de uma etapa atrapalhariam as outras se ficassem no mesmo contexto.
Salas separadas numa redação: quem apura não escreve na mesa de quem edita, e só a matéria apurada atravessa a porta.
Sofia mantém um revisor automático de pull requests. Para revisar bem, ele precisa ler o diff, os arquivos tocados, os testes relacionados e o histórico de commits do arquivo. Tudo isso entrava no mesmo contexto da revisão.
Dois efeitos apareceram. O primeiro, previsível: o contexto passava de 100 mil tokens em PRs médios, e a revisão ficava cara e lenta. O segundo, menos óbvio: o revisor passou a comentar código que não estava no diff. Ele tinha lido o arquivo inteiro e apontava problemas antigos, que não eram assunto do PR. Os comentários úteis se perdiam entre os fora de escopo.
Resumir o material não resolvia. O revisor precisava, sim, olhar arquivos e testes, mas só para responder perguntas específicas (“esta função tem teste?”, “quem mais chama este método?”), não para carregar tudo enquanto escrevia a revisão.
Isso é irmão do Contexto Podre, com uma diferença: o material pesado nem deveria ter entrado no contexto que escreve. Compactar depois é limpar o que já contaminou. Isolar é não deixar entrar.
A pergunta que este padrão responde: como uma etapa usa muito material sem que esse material contamine o resto do trabalho?
Dê a cada subtarefa um contexto próprio, criado do zero, com só o que ela precisa: uma instrução autocontida, as ferramentas de leitura e, quando necessário, um pedaço do material. Quando a subtarefa termina, o contexto é descartado. Volta só um resultado curto, num formato combinado.
O contexto principal nunca vê o material bruto. Ele vê perguntas e respostas: “a função apply_discount tem testes?” e “sim, em tests/test_pricing.py, linhas 40 a 88, cobrindo desconto zero e desconto maior que o total”.
Isolamento é o princípio; Delegação a Subagentes é a forma mais comum de aplicá-lo, mas ele também vale para chamadas simples sem loop e para etapas de um workflow.
O PR de Sofia muda só pricing.py. O revisor precisa saber se apply_discount tem teste, não precisa do arquivo de testes inteiro na mesa.
O contexto principal manda uma pergunta autocontida:
Pergunta: a função apply_discount em src/pricing.py, alterada neste PR, tem testes?
Escopo: responda só sobre cobertura desta função. Ignore falhas antigas em outras funções.
Formato: { "answer": string, "evidence": [{ "path": string, "lines": string }] }
O contexto isolado lê o que precisar e devolve:
{
"answer": "Sim. tests/test_pricing.py cobre desconto zero e desconto maior que o total. Não vi caso de desconto negativo.",
"evidence": [{ "path": "tests/test_pricing.py", "lines": "40-88" }]
}
O principal recebe umas 80 palavras e cita as linhas. Os milhares de tokens do arquivo de teste morreram no contexto isolado.
A conta, ilustrativa. Sem isolamento, um PR médio carrega diff (3 mil tokens) mais três arquivos (45 mil) em cada uma de 8 voltas: uns 384 mil tokens de entrada. Com isolamento, o principal fica com o diff e as respostas curtas (digamos 8 mil por volta, 64 mil no total) e os contextos isolados pagam a leitura uma vez. O custo total pode até subir, se houver muitas perguntas. O que cai, e isso é o ponto, é o contexto que escreve a revisão, e com ele os comentários fora de escopo.
A instrução tem que carregar as restrições. O contexto isolado não viu o diff. Se a pergunta omite “ignore falhas antigas”, ele reporta o teste que falta há dois anos.
flowchart TD
M(["1. Contexto principal: revisão"]):::llm --> Q1["2. Pergunta autocontida"]:::tool
Q1 --> C1(["3. Contexto isolado A"]):::llm
C1 --> F[("4. Arquivos, testes, histórico")]:::store
C1 -->|"5. resposta curta"| M
M --> Q2["2. Outra pergunta"]:::tool
Q2 --> C2(["3. Contexto isolado B"]):::llm
C2 --> F
C2 -->|"5. resposta curta"| M
M --> OUT(["6. Resultado focado"]):::io
schema Answer { answer: string, evidence: list[{ path: string, lines: string }] }
function isolated(question, tools, budget):
# 3. contexto novo: nada do histórico do principal entra aqui
ctx = new_context(system=READER_PROMPT, messages=[question])
result = run_agent(ctx, tools=tools, budget=budget, output=Answer) # 4
trace.span("isolated", question, tokens=ctx.tokens)
return result # 5. ctx é descartado
function review(diff):
state.messages = [diff]
while budget.check():
reply = llm_with_tools(REVIEW_PROMPT, [ask_codebase], state) # 1
if reply.is_final:
return reply.text # 6
for call in reply.tool_calls: # 2
answer = isolated(call.args.question, READ_TOOLS, budget.share(10))
state.add(tool_result(call, answer))
evidence vazio no código.while True é Loop Sem Fim numa sala ao lado. O teto vale para a árvore.| Aspecto | O que acontece | Como aparece no trace |
|---|---|---|
| Chamadas | Uma ou mais por pergunta isolada | spans isolated filhos |
| Custo total | Pode subir, porque contextos isolados releem material | soma de tokens dos spans isolated |
| Custo do principal | Cai muito, porque o contexto fica pequeno | tokens de entrada estáveis no principal |
| Latência | Perguntas independentes podem rodar em paralelo | spans isolated sobrepostos |
| Falha: pergunta incompleta | O contexto isolado não sabe uma restrição do principal | resposta correta para a pergunta errada |
| Falha: resposta sem evidência | O principal confia numa afirmação inventada | campo evidence vazio |
{ "diff": "fixtures/pr-512.diff", "expected_comments": 2, "max_out_of_scope_comments": 0 }
Meça os dois custos. Time que olha só a fatura total às vezes desfaz o padrão, sem ver que os comentários fora de escopo que sumiram valiam mais.
Contextos isolados são o lugar certo para ler conteúdo não confiável. Dê a eles só ferramentas de leitura, e trate a resposta como dado no contexto principal.
Greshake et al. (2023) documentaram injeção indireta: instruções escondidas numa página ou num e-mail que o modelo lê. Se essa página entra no mesmo contexto das ferramentas de efeito, o texto hostil vira ordem. Isolar a leitura não substitui o portão nas ferramentas, mas corta o canal mais comum.
| Prós | Contras |
|---|---|
| Contexto principal pequeno e focado | Custo total pode subir |
| Material lido não contamina o raciocínio | A pergunta precisa ser autocontida |
| Partes com instruções e ferramentas próprias | Mais spans para depurar |
| Leitura de conteúdo hostil longe das ferramentas de efeito | A resposta curta pode perder detalhe útil |
As abas expõem uma ferramenta ask_codebase que responde perguntas num contexto novo, sem o histórico da revisão. Python e TypeScript zeram a lista de mensagens na mão. LangChain faz o mesmo com um create_agent que só recebe a pergunta: o histórico do revisor não entra no dicionário.
Python. Use quando a revisão roda em Python e a leitura isolada é uma função comum.
import anthropic
from pydantic import BaseModel
client = anthropic.Anthropic()
class Evidence(BaseModel):
path: str
lines: str
class Answer(BaseModel):
answer: str
evidence: list[Evidence]
READER = "Você responde uma pergunta sobre o repositório usando só as ferramentas de leitura. Seja breve e cite arquivo e linhas."
def ask_codebase(question: str, max_steps: int = 10) -> Answer:
messages = [{"role": "user", "content": question}] # 3. nada da revisão entra aqui
for _ in range(max_steps):
reply = client.messages.create(
model="claude-haiku-4-5", max_tokens=4000, system=READER,
tools=READ_TOOLS, messages=messages,
)
if reply.stop_reason != "tool_use":
break
messages.append({"role": "assistant", "content": reply.content})
messages.append({"role": "user", "content": run_tools(reply)}) # 4
messages.append({"role": "user", "content": "Responda agora no formato pedido."})
return client.messages.parse( # 5. só a resposta curta sai daqui
model="claude-haiku-4-5", max_tokens=1000, system=READER,
messages=messages, output_format=Answer,
).parsed_output
ASK_TOOL = {
"name": "ask_codebase",
"description": "Responde uma pergunta sobre código fora do diff (testes, chamadas, histórico). "
"A pergunta deve ser autocontida. Devolve resposta curta com arquivo e linhas.",
"input_schema": {"type": "object", "properties": {"question": {"type": "string"}}, "required": ["question"]},
}O que você ganhou: a revisão nunca carrega arquivos inteiros, e a leitura usa um modelo barato. O que você perdeu: cada pergunta relê o que precisa; para muitas perguntas sobre os mesmos arquivos, considere cache.
TypeScript. Use quando a revisão roda no runner do SDK e a leitura isolada é uma ferramenta tipada.
import Anthropic from "@anthropic-ai/sdk";
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
import { z } from "zod";
const client = new Anthropic();
const READER =
"Você responde uma pergunta sobre o repositório usando só as ferramentas de leitura. " +
'Termine com JSON {"answer": string, "evidence": [{"path": string, "lines": string}]}.';
export const askCodebase = betaZodTool({
name: "ask_codebase",
description:
"Responde uma pergunta sobre código fora do diff (testes, chamadas, histórico). " +
"A pergunta deve ser autocontida. Devolve resposta curta com arquivo e linhas.",
inputSchema: z.object({ question: z.string() }),
run: async ({ question }) => {
// 3. um runner novo = um contexto novo, sem o histórico da revisão
const final = await client.beta.messages.toolRunner({
model: "claude-haiku-4-5",
max_tokens: 4000,
system: READER,
tools: READ_TOOLS,
messages: [{ role: "user", content: question }],
max_iterations: 10,
});
return final.content.find((b) => b.type === "text")?.text ?? "{}"; // 5
},
});
// revisão: tools: [askCodebase]; o diff é a única coisa grande no contexto principalO que você ganhou: o isolamento é estrutural, porque cada chamada da ferramenta cria seu próprio runner. O que você perdeu: a resposta chega como texto; valide o JSON antes de confiar.
Python + LangChain. Use quando o leitor isolado é um create_agent e a chamada dele recebe só a pergunta.
from langchain.agents import create_agent
from langchain.tools import tool
from pydantic import BaseModel
class Evidence(BaseModel):
path: str
lines: str
class Answer(BaseModel):
answer: str
evidence: list[Evidence]
# 3. este agente não recebe as mensagens da revisão
reader = create_agent(
"anthropic:claude-haiku-4-5",
tools=READ_TOOLS,
system_prompt="Você responde uma pergunta sobre o repositório usando só as ferramentas de leitura. "
"Seja breve e cite arquivo e linhas.",
response_format=Answer,
)
@tool
def ask_codebase(question: str) -> str:
"""Responde uma pergunta sobre código fora do diff. A pergunta deve ser autocontida."""
out = reader.invoke(
{"messages": [{"role": "user", "content": question}]}, # só a pergunta
{"recursion_limit": 20},
)
return out["structured_response"].model_dump_json() # 5READ_TOOLS são as ferramentas de leitura. O revisor principal recebe ask_codebase e não recebe READ_TOOLS. As outras formas desse loop estão em Loop ReAct.
O que você ganhou: o histórico da revisão não tem onde entrar, porque a chamada não o recebe. O que você perdeu: recursion_limit conta passos, não reais.
Isolamento de Contexto (Context Isolation) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Contexto e conhecimento. Rodar subtarefas em contextos separados e trazer de volta só o resultado.
Nos cenários reais Agente de código, Pesquisa profunda com citações e Revisor automático de PR.