Lição 3: Memória externa: arquivos e recuperação
Objetivos de aprendizado:
- Explicar por que a memória que precisa sobreviver entre sessões deve ser escrita fora da janela, em arquivos
- Distinguir arquivos de memória escritos por humanos, como o CLAUDE.md, de arquivos de memória escritos pelo modelo, como a Auto memory
- Enunciar o trade-off entre "recuperar sob demanda" e "carregar tudo de antemão"
- Adicionar uma verificação segura de fronteira de caminho a uma ferramenta que lê e escreve arquivos de memória
Pré-requisitos: concluída a Lição 2, você entende a diferença entre compactação e limpeza de resultados de ferramenta | Anterior: Lição 2 << | Próxima: Lição 4 >>
Quando a sessão termina, tudo na janela some
A Lição 2 encerrou com um problema sem solução: um assistente de agendamento a quem um usuário disse na semana passada "eu não como comida apimentada", e nesta semana o usuário abre uma conversa nova com uma janela vazia — e o agente não tem ideia de que essa frase um dia foi dita.
Truncagem, compactação e limpeza de resultados de ferramenta não conseguem consertar isso. Os três lidam com "ficamos sem espaço dentro desta única conversa". O problema aqui é diferente: esta conversa não tinha nada do conteúdo da semana passada desde o primeiríssimo turno. O Cookbook oficial traça a linha sem rodeios — a limpeza e a compactação operam ambas no contexto atual; nenhuma ajuda quando uma nova sessão começa e a janela está vazia. A memória resolve esse problema1. A janela, como contêiner, só vive enquanto durar esta única sessão. Feche a sessão, e qualquer coisa na janela que não tenha sido movida para outro lugar está de fato perdida.
O único jeito de manter a informação viva além desta sessão é escrevê-la, antes de a sessão terminar, em algum lugar fora da janela — na memória externa: armazenamento que não está preso ao ciclo de vida desta conversa, normalmente só um arquivo em disco. Quando a próxima sessão começa, você lê esse arquivo de volta e carrega seu conteúdo na nova janela de contexto.
CLAUDE.md: escrito por humanos, carregado por inteiro toda vez
O padrão mais direto de memória externa é ter um humano mantendo um arquivo de memória, guardado no projeto e lido por inteiro no início de cada sessão. O CLAUDE.md no Claude Code é o caso representativo: a documentação oficial diz que um arquivo CLAUDE.md é carregado na janela de contexto no início de cada sessão, gastando tokens lado a lado com a própria conversa, e a meta de tamanho recomendada é manter cada arquivo abaixo de 200 linhas — quanto mais longo o arquivo, mais contexto ele consome e menor a aderência do agente às instruções.2 Note que 200 linhas é uma recomendação branda; o limite rígido de verdade é 4 MiB: um CLAUDE.md maior que isso é ignorado por completo.2
Um detalhe deste arquivo vale a atenção: a documentação oficial explica que comentários HTML de nível de bloco no CLAUDE.md são removidos antes de o conteúdo ser injetado no contexto do agente.2 Em outras palavras, o que quer que você escreva dentro de <!-- --> é visível quando um humano abre o arquivo, mas a versão que o agente lê não inclui esse comentário — o que dá a um humano uma forma de "deixar um lembrete para mim mesmo sem gastar o orçamento de tokens do agente".
O CLAUDE.md tem outra propriedade que se liga diretamente à compactação da Lição 2: a documentação nota que um CLAUDE.md na raiz do projeto sobrevive à compactação — depois do /compact, o Claude o relê do disco e o reinjeta na sessão.2 Em outras palavras, um arquivo assim não é "incidentalmente preservado" pela compactação; ele é relido e reinjetado separadamente — ele não depende em nada de a compactação ter mantido seu conteúdo no resumo.
Auto memory: escrita pelo modelo, recuperada sob demanda
O CLAUDE.md é escrito por humanos e carregado por inteiro toda vez. Há um padrão complementar: deixar o modelo anotar o que vale a pena lembrar conforme a conversa avança, guardando isso em seus próprios arquivos de memória — o Claude Code chama esse mecanismo de Auto memory. Sua divisão de trabalho com o CLAUDE.md é complementar, e uma tabela de comparação coloca a diferença de forma clara: o CLAUDE.md é escrito por você, a Auto memory é escrita pelo Claude.2
A memória que o próprio modelo escreve costuma se dividir em duas camadas: um arquivo de índice (digamos, MEMORY.md) mais uma pilha de arquivos de memória específicos separados por tópico. O arquivo de índice também não é carregado sem limite — a regra que a documentação dá é: no início de cada conversa, apenas as primeiras 200 linhas do MEMORY.md, ou os primeiros 25KB, o que vier primeiro, são carregados; conteúdo além desse limiar não é carregado no início da sessão.2
Isso é a recuperação sob demanda: no início de uma sessão o agente vê apenas os resumos das entradas do índice (algo como "as anotações detalhadas sobre este tópico vivem em algum arquivo"), não o conteúdo completo de cada arquivo de memória específico. A documentação é direta a respeito: arquivos de tópico não são carregados na inicialização; o Claude os lê sob demanda com suas ferramentas de arquivo padrão quando precisa da informação2. Só quando a tarefa atual de fato exige um dado tópico é que o conteúdo daquele arquivo de memória específico é puxado para a janela de contexto desta rodada.
Lado a lado, o CLAUDE.md e a Auto memory tratam duas dimensões diferentes de memória:
- CLAUDE.md — regras e convenções curadas por humanos, disciplinadas em tamanho, que se aplicam toda vez; um encaixe para informação estável do tipo "é assim que o projeto deve funcionar", carregada por inteiro de antemão.
- Auto memory — detalhes específicos que podem ser grandes em número e só importam para tarefas particulares; um encaixe para recuperação sob demanda, para que o orçamento da janela não seja desperdiçado com memória que esta tarefa não precisa.
Ambas são memória externa. As únicas diferenças são "quem escreve" e "quando é carregada" — o que ecoa o modelo mental da Lição 2: o objetivo da memória é mover informação para fora da janela para que ela sobreviva entre sessões, e se essa informação é carregada por inteiro de antemão ou recuperada sob demanda depende de quão estável ela é e com que frequência é usada.
Adicionando uma fronteira segura à leitura e escrita de arquivos de memória
Seja um arquivo escrito por humanos como o CLAUDE.md ou um escrito pelo modelo como a Auto memory, assim que um agente tem uma ferramenta para ler e escrever arquivos de memória, há uma questão concreta de engenharia a encarar: essa ferramenta pode ser convencida a ler ou escrever arquivos fora do diretório do projeto?
Uma verificação de caminho que só faz uma correspondência de prefixo de string parece bloquear pedidos de "escapar do diretório de memória", mas ela tem um buraco clássico. Se a raiz da memória é /project/memory, uma verificação ingênua com startsWith("/project/memory") também vai deixar passar um caminho como /project/memory-evil, porque ele de fato começa com essa string — mesmo que seja um diretório completamente diferente sentado fora da raiz da memória. A forma segura é exigir que o caminho ou seja exatamente igual à raiz, ou comece com "a raiz mais um separador de caminho":
A combinação abs === MEMORY_ROOT || abs.startsWith(MEMORY_ROOT + path.sep) é o que de fato garante que só um caminho "igual à própria raiz" ou "começando com a raiz mais um separador" passe — /project/memory-evil não será confundido com um caminho dentro de /project/memory, porque ele não satisfaz nenhuma das duas condições. Esse padrão é reutilizado diretamente na Lição 6, quando construímos as ferramentas de leitura/escrita para uma camada de memória persistente, e a Lição 5 vai deixar claro que tipo de alvo de ataque um arquivo de memória vira se essa verificação de fronteira for banguela.
Recapitulação
- Quando uma sessão termina, qualquer coisa na janela que não foi movida para fora está perdida para sempre; para manter a informação entre sessões, você tem de escrevê-la na memória externa, fora da janela, antes de a sessão terminar
- O CLAUDE.md é escrito por humanos, carregado por inteiro no contexto a cada sessão, com uma meta oficial de tamanho de 200 linhas (limite rígido 4 MiB, arquivos maiores ignorados por completo); comentários HTML de nível de bloco são removidos antes da injeção, e um CLAUDE.md na raiz do projeto é relido e reinjetado depois do
/compact2
- A Auto memory é escrita pelo modelo, dividida em um arquivo de índice mais arquivos de tópico específicos; o índice carrega só suas primeiras 200 linhas ou 25KB, e os arquivos de tópico não são carregados na inicialização, são lidos sob demanda quando necessário2, para que o orçamento da janela não seja desperdiçado com memória que não vai ser usada
- Os dois são complementares: o CLAUDE.md encaixa em regras estáveis úteis toda vez; a Auto memory encaixa em detalhes de grande volume necessários só para tarefas particulares
- A ferramenta de leitura/escrita de um arquivo de memória deve fazer uma verificação segura de fronteira de caminho; a condição combinada
abs === ROOT || abs.startsWith(ROOT + path.sep) precisa das duas metades, já que uma verificação startsWith sozinha tem um buraco de burla por prefixo igual
>> Lição 4: Estado estruturado: como um agente lembra em que ponto uma tarefa está