Agent Mentor Learn
Memória e Estado de Agente · Lição 6 de 6

Lição 6: Mão na massa: adicionando uma camada de memória persistente a um agente

Objetivos de aprendizado:

  • Conectar um conjunto seguro de ferramentas de leitura/escrita de memória a um agente, e reinjetar a memória no histórico quando uma nova sessão começa
  • Escrever à mão uma função de compactação simplificada e a lógica de limpeza de resultados de ferramenta, e entender como elas diferem dos mecanismos nativos
  • Encaixar as peças de leitura/escrita de memória e de poda de histórico no loop de execução de Tool calling de agentes: fazendo agentes agirem de verdade, produzindo um agente que ao mesmo tempo lembra e se poda

Pré-requisitos: Conclua as Lições 1-5, e saiba ler JavaScript / Node.js básico | Anterior: Lição 5 <<

Primeiro, o resultado: a memória de fato atravessa duas sessões separadas

Isto é o que construímos até o fim da lição. Na primeira execução, você conta uma preferência ao agente:

$ node agent.js "Lembre disto: eu não gosto de comida picante, então não recomende restaurantes picantes de agora em diante"
[turn 1] called write_memory { path: 'preferences.md', content: "O usuário não come comida picante; evitar culinárias picantes ao recomendar restaurantes." }
Final answer:Entendido. Vou evitar lugares picantes quando escolher restaurantes para você.

O processo encerra. Inicie um processo novo e pergunte algo completamente sem relação:

$ node agent.js "Tem algum bom restaurante por perto que você recomende?"
[memory backfill] Carreguei a preferência salva na última sessão de preferences.md[turn 1] called read_memory { path: 'preferences.md' }
Final answer:Com base na sua nota anterior de que você não come comida picante, aqui estão alguns lugares com sabores mais suaves...

Entre as duas execuções o processo foi completamente reiniciado e o array messages começou vazio — mesmo assim a segunda execução ainda "lembra" a preferência da primeira. Isso não é acaso. É o efeito combinado das duas peças que construímos nesta lição: um conjunto seguro de ferramentas de leitura/escrita de memória, mais um pouco de lógica que reinjeta ativamente a memória quando a sessão começa. Além disso, esta lição preenche a outra metade que a Lição 2 descreveu mas que o loop de execução de Tool calling de agentes: fazendo agentes agirem de verdade nunca implementou — como o histórico se poda quando cresce demais.

O ponto de partida: o loop de execução do curso de tool calling

Não estamos começando do zero. A Lição 6 de Tool calling de agentes: fazendo agentes agirem de verdade construiu um loop de execução de ferramentas funcional. A forma central: registrar o schema e a implementação de cada ferramenta em uma única tabela TOOLS, depois entrar em loop — enviar a requisição, checar stop_reason, e sempre que for tool_use, percorrer cada bloco de chamada, executá-lo e emendar os resultados de volta em messages, até o modelo parar de pedir chamadas de ferramenta.1

Adicionamos duas coisas novas a esse esqueleto. Primeiro, ferramentas de leitura/escrita de memória, para que o agente possa ativamente gravar conteúdo que vale a pena manter para além da janela. Segundo, um pouco de lógica de poda de histórico, para que uma conversa longa não inche para sempre. As duas se apoiam diretamente nos princípios das cinco primeiras lições; esta lição apenas os transforma em código que roda.

Etapa 1: conecte as ferramentas de leitura/escrita de memória ao agente

Primeiro, defina uma raiz de memória dedicada para os arquivos de memória, junto com a verificação de limite ao redor dela — este é o padrão de limite de caminho da Lição 3: Memória externa: arquivos e recuperação, trazido tal como está:

A condição combinada abs === MEMORY_ROOT || abs.startsWith(MEMORY_ROOT + path.sep) dentro de resolveMemoryPath está lá exatamente pela razão que a Lição 3 deu: um startsWith(MEMORY_ROOT) puro é burlado por um diretório irmão de mesmo prefixo (como memory-evil).

O schema da ferramenta também tem que explicitar o limite do "que armazenar" — não imposto por código, mas moldado para o comportamento do modelo por meio da description:

A Lição 5: Os limites e a segurança da memória fez o ponto de que, uma vez que conteúdo malicioso alcança um armazenamento como a memória — confiado e recarregado repetidas vezes — o atacante não está mais influenciando uma única resposta, mas o raciocínio futuro.2 A linha na description de write_memory — "não grave texto bruto e não confiável lido durante uma tarefa direto, sem nenhuma triagem" — transforma esse princípio em uma instrução explícita que o modelo pode ver. Ela não substitui a revisão real de conteúdo, mas ao menos impede que "gravar qualquer coisa que você leia" seja o comportamento padrão.

Etapa 2: reinjete a memória no histórico quando a sessão começa

As ferramentas agora podem ler e gravar arquivos de memória, mas a menos que alguém o leia ativamente no início de uma nova sessão, preferences.md é apenas um arquivo quieto no disco — ele não vai aparecer sozinho na janela de contexto desta requisição. A Lição 3 cobriu como arquivos de memória como o CLAUDE.md são carregados no contexto no início de toda sessão;3 aqui usamos a mesma ideia para escrever à mão um pouco de lógica de reinjeção de memória entre sessões:

Essa lógica de reinjeção é chamada quando construímos o array messages inicial, de modo que o conteúdo da memória aparece como a primeiríssima mensagem da conversa — assim ele está na janela desde o turno um, sem o modelo ter que chamar read_memory para vê-lo. A Etapa 4 mostra exatamente onde ela se encaixa no loop completo.

Etapa 3: escreva à mão a lógica de compactação e limpeza

No loop de execução do curso de tool calling, o array messages só cresce por acréscimo — ele nunca é podado. A Lição 2: Gerenciando o histórico da conversa: acrescentar, truncar, resumir cobriu como, nos mecanismos nativos reais, a compactação por resumo (compact_20260112, disparando a 150K tokens por padrão) e a limpeza de resultados de ferramenta (clear_tool_uses_20250919, disparando a 100K tokens por padrão e mantendo as últimas 3 chamadas) são duas funcionalidades nativas com trabalhos diferentes.4 Esta lição escreve à mão uma versão simplificada para ajudar você a entender o que cada uma faz — mas antes, um limite precisa ser dito com clareza: o código abaixo é lógica simplificada construída do zero para fins didáticos, não as funcionalidades beta nativas que a Anthropic fornece. Em um projeto real, se o SDK já suporta parâmetros nativos como compact_20260112 e clear_tool_uses_20250919, você deve preferir a implementação oficial em vez de reinventar uma versão feita à mão.

Primeiro, o problema de medir o inchaço do histórico. A contagem real de tokens significa chamar um endpoint de contagem dedicado; aqui, para manter o ensino simples, aproximamos com um orçamento de caracteres grosseiro — note que isto é só uma aproximação, não uma contagem precisa de tokens:

Os exercícios da Lição 2 cobriram uma armadilha: se você fatiar o histórico e cortar por acidente um par tool_use / tool_result no meio, a estrutura do protocolo quebra. A compactação feita à mão, ao decidir "qual histórico entra no resumo e qual fica na parte recente", tem que cortar em limites de ida-e-volta completos, não por contagem de mensagens:

Gerar o resumo aqui significa fazer uma chamada de resumo extra — que é exatamente o custo que a Lição 2 mencionou: a compactação em si queima uma chamada extra ao modelo, e a mensagem de resumo resultante tem perdas, então o detalhe original se foi.

A versão feita à mão da limpeza de resultados de ferramenta é mais leve: sem chamada extra ao modelo, ela só troca o conteúdo de blocos tool_result antigos além da contagem a manter por conteúdo de placeholder, mantendo o registro de que a chamada aconteceu (o tool_use_id continua lá, só o content é substituído):

Etapa 4: monte um loop com memória

Encaixar as ferramentas de leitura/escrita de memória, a reinjeção de memória, a compactação feita à mão e a limpeza de resultados de ferramenta no mesmo loop nos dá o loop com memória desta lição:

No início de todo turno rodamos maybeCompact, e logo depois que os resultados de ferramenta de cada turno são gravados de volta rodamos clearOldToolResults — isto mapeia para o modelo mental da Lição 2: a compactação lida com "a janela inteira está grande demais", a limpeza lida com "dados desatualizados e recuperáveis dentro da janela", e as duas não conflitam, podem estar em vigor ao mesmo tempo.4 Enquanto isso, loadMemoryBackfill é chamada uma única vez no topo de runAgent, fazendo o trabalho de de fato mover a "memória externa" da Lição 3 para a janela desta execução. Essas três peças juntas são a fonte completa do efeito "ainda lembra a preferência depois de um reinício de processo" do início desta lição. Se, depois deste loop, você também precisar lembrar "em que ponto a tarefa está", o ciclo de vida do todo da Lição 4: Estado estruturado: como um agente lembra em que ponto uma tarefa está pode ser transformado em um checkpoint gravado em um arquivo de memória do mesmo jeito — a abordagem é idêntica à de write_memory, só o conteúdo gravado muda de "preferências" para "progresso".5

Recapitulação

  • As ferramentas de leitura/escrita de memória reutilizam o padrão de limite de caminho da Lição 3 (abs === ROOT || abs.startsWith(ROOT + path.sep)), e a description de write_memory deve explicitar "o que armazenar" — mas isso é apenas orientação em nível de prompt e não substitui a revisão real de conteúdo
  • Para a memória de fato fazer efeito, você não pode pular a reinjeção ativa no início da sessão — um arquivo de memória parado no disco não vai aparecer sozinho na janela de contexto desta requisição; ele tem que ser explicitamente lido e explicitamente carregado no início da sessão, do jeito que o CLAUDE.md é
  • A compactação feita à mão e a limpeza feita à mão são implementações simplificadas para fins didáticos, correspondendo respectivamente aos nativos compact_20260112 e clear_tool_uses_20250919 — em um projeto real, se o SDK suporta os parâmetros nativos, prefira a implementação oficial
  • Fatiar o histórico (seja compactando ou limpando) tem que ser feito em limites de ida-e-volta tool_use/tool_result completos, não por contagem de mensagens, ou você corta a estrutura do protocolo
  • Leitura/escrita de memória, reinjeção de histórico e compactação/limpeza mapeiam respectivamente para os princípios ensinados nas Lições 3 e 2 — tudo o que esta lição fez foi transformar esses princípios em código que roda

Você concluiu agora todas as seis lições de Memória e Estado de Agente, indo de "a janela de contexto é toda a memória que um agente tem" até conectar à mão uma camada de memória persistente a um agente. O próximo passo mais valioso não é ler outra lição — é conectar este loop com memória a um cenário real no seu próprio projeto, rodar alguns turnos e observar os logs. Quando estiver em dúvida sobre um parâmetro específico ou um padrão oficial durante a depuração, volte ao sources.md e confira os docs oficiais S1-S5 e o original do blog do OWASP.

Footnotes

  1. How tool use works — https://platform.claude.com/docs/en/agents-and-tools/tool-use/how-tool-use-works

  2. Memory Is a Feature. It Is Also an Attack Surface — https://genai.owasp.org/2026/05/13/memory-is-a-feature-it-is-also-an-attack-surface/

  3. How Claude remembers your project — https://code.claude.com/docs/en/memory

  4. Context engineering: memory, compaction, and tool clearing — https://platform.claude.com/cookbook/tool-use-context-engineering-context-engineering-tools 2

  5. Track todos — https://code.claude.com/docs/en/agent-sdk/todo-tracking

Exercícios

01

Copie o código desta lição para um diretório local vazio, npm install @anthropic-ai/sdk, npm pkg set type=module, e configure ANTHROPIC_API_KEY. Primeiro rode um prompt que dispare uma chamada write_memory e confirme que um arquivo de fato aparece sob memory/; depois rode um segundo processo separadamente, faça uma pergunta que precise dessa memória, e confirme que [memory backfill] aparece nos logs.

Nível 1: coloque para rodar, depois adicione uma ferramenta forget_memory

Uma vez rodando, adicione uma ferramenta forget_memory(path) à tabela TOOLS: ela apaga o arquivo de memória especificado sob a raiz de memória, faz a mesma verificação de limite de caminho, e não pode apagar nenhum arquivo fora do diretório de memória.

Critérios de conclusão · marcado localmente
02

Um colega simplificou maybeCompact, substituindo splitKeepingToolPairs por um corte direto por contagem de mensagens:

Nível 2: encontre o perigo escondido na lógica de compactação

Explique quando essa mudança quebra, e por que esta lição insiste em usar splitKeepingToolPairs em vez de fatiar diretamente.

Critérios de conclusão · marcado localmente