Lição 2: Gerenciando o histórico da conversa: acrescentar, truncar, resumir
Objetivos de aprendizado:
- Explicar por que o histórico da conversa só cresce e nunca encolhe por padrão
- Dizer o que a truncagem descarta, o que ela mantém e que estrutura ela pode quebrar
- Distinguir os problemas que a compactação e a limpeza de resultados de ferramenta resolvem cada uma
- Julgar qual mecanismo acionar com base no uso da janela e no tipo de conteúdo que a está inchando
Pré-requisitos: concluir a Lição 1 e entender do que uma janela de contexto é feita | Anterior: Lição 1 << | Próxima: Lição 3 >>
Acrescentar é o padrão: por que o histórico continua crescendo
A Lição 1 destacou que o histórico que o modelo vê é aquilo que o aplicativo host reenvia a cada turno. Então como ele é de fato enviado? A implementação mais simples é acrescentar (append): quando um turno termina, você anexa as novas mensagens daquele turno (as palavras do usuário, a resposta do modelo, as chamadas de ferramenta e seus resultados) ao final do array messages existente, e no próximo turno você reenvia o array inteiro como está.
A documentação oficial coloca esse padrão de forma clara: conforme a conversa avança, cada mensagem do usuário e resposta do modelo se acumula na janela de contexto, e todo turno anterior é mantido por inteiro. "As the conversation advances through turns, each user message and assistant response accumulates within the context window, and previous turns are preserved completely."1 (Conforme a conversa avança pelos turnos, cada mensagem do usuário e resposta do assistente se acumula dentro da janela de contexto, e os turnos anteriores são preservados por completo.) Ninguém está apagando nada ativamente, então o histórico só sobe — dez turnos adentro, a janela guarda o conteúdo de todos os dez turnos, não um resumo do último turno e não um conjunto de destaques filtrado automaticamente.
Numa conversa curta isso é um não-problema. Mas para um agente que roda por muito tempo, o problema faz bola de neve: os argumentos completos e o valor de retorno completo de cada chamada de ferramenta são enfiados no histórico, e uma tarefa que lê arquivos e roda comandos repetidamente pode facilmente empurrar o array messages para dezenas de milhares de tokens depois de algumas dezenas de turnos. A Lição 1 cobriu que a janela tem um teto de capacidade rígido, e quanto mais cheia ela fica, mais perto você está de atingi-lo. O custo mais sutil é o context rot — quanto mais longo e bagunçado o histórico, mais difícil para o modelo achar ali a única linha que de fato importa agora1. Deixe o histórico crescer sem controle e você acaba pagando as duas contas.
Truncagem: a opção mais simples e mais grosseira
A resposta mais direta é a truncagem (truncation): quando a janela está quase cheia, corte de vez o lote mais antigo de mensagens e mantenha apenas os N turnos mais recentes. Isso é a coisa mais fácil de construir — nenhuma chamada de modelo extra, nenhum formato de resumo para projetar. Uma única linha de messages.slice(-N) resolve.
Mas o que a truncagem descarta se foi para sempre. Se o lote que você cortou continha uma restrição-chave que o usuário declarou lá no turno 3 ("o orçamento fica abaixo de $5.000") e o agente está agora no turno 40 prestes a fazer um pedido, essa informação simplesmente some. O modelo não vai saber que um dia "viu" e depois "esqueceu" — ele só se comporta como se nunca tivesse sido informado.
A truncagem tem uma armadilha mais oculta também, uma que se liga diretamente ao protocolo de ida e volta do curso anterior, Tool calling de agentes: fazendo agentes agirem de verdade, Lição 2 "A ida e volta completa de uma chamada de ferramenta": se você trunca fatiando ingenuamente para "as últimas N mensagens", pode facilmente cortar no meio de um par tool_use / tool_result — mantendo a mensagem assistant que disparou a chamada mas fatiando fora a mensagem tool_result que veio logo depois dela. Envie esse histórico ao modelo e o próprio protocolo está quebrado. A documentação é explícita de que "Tool result blocks must immediately follow their corresponding tool use blocks in the message history."2 (Os blocos de resultado de ferramenta devem seguir imediatamente os blocos de uso de ferramenta correspondentes no histórico de mensagens.) Um erro como "tool_use ids were found without tool_result blocks immediately after" é o sinal de que o pareamento está quebrado2 — o modelo vê que "iniciou uma chamada" mas nunca recebe o resultado dela, e a próxima requisição falha de imediato.
Compactação: espreme a janela para um único resumo
O problema da truncagem é que ela descarta trechos inteiros. Existe uma forma de liberar espaço sem jogar informação fora por completo? É esse o problema que a compactação (compaction) resolve. O Cookbook oficial a define assim: "Compaction distills the contents of a context window into a high-fidelity summary, letting the agent continue with minimal performance degradation when the conversation gets long."3 (A compactação destila o conteúdo de uma janela de contexto em um resumo de alta fidelidade, deixando o agente continuar com degradação mínima de desempenho quando a conversa fica longa.)
Diferente do "apague um trecho inteiro" da truncagem, a compactação é um "reescreva tudo": o histórico anterior da conversa é comprimido em um único resumo de alta fidelidade que substitui a longa sequência de mensagens brutas e permanece à frente da janela. O que o resumo mantém é "o que aconteceu e o que foi concluído"; o que ele descarta é o detalhe palavra-por-palavra do diálogo bruto.
A documentação detalha os parâmetros desse mecanismo. Há um limiar de disparo padrão — a compactação dispara automaticamente quando o uso da janela atinge 150K tokens; o limiar é configurável mas não pode ir abaixo de 50K tokens, um piso imposto pelo servidor3 4. Cada disparo é uma substituição discreta: o grande trecho de histórico é trocado pelo resumo, e novas mensagens continuam acrescentando normalmente depois dele. Isso não é um evento único — a documentação é explícita de que uma conversa longa pode compactar mais de uma vez, e "The last compaction block reflects the final state of the prompt, replacing content prior to it with the generated summary."4 (O último bloco de compactação reflete o estado final do prompt, substituindo o conteúdo anterior a ele pelo resumo gerado.) Quando ela compacta de novo, o bloco de compactação anterior é dobrado no novo resumo junto com o resto do histórico; a compactação é uma operação de transcrição inteira em que "user messages, assistant messages, tool calls, tool results, even prior compaction blocks are all flattened into the summary."3 (mensagens do usuário, mensagens do assistente, chamadas de ferramenta, resultados de ferramenta, e até blocos de compactação anteriores são todos achatados no resumo.)
A compactação não é de graça. O ato de compactar custa uma chamada de modelo extra (o modelo sumarizador roda)3, e por mais cuidadoso que seja o texto, o resumo é uma versão com perdas do original — "The summary preserves key decisions and facts but may drop specific numbers or exact phrasing."3 (O resumo preserva decisões e fatos-chave mas pode descartar números específicos ou o fraseado exato.) Se um passo posterior por acaso depende de um detalhe minúsculo que foi resumido para fora (a grafia exata de alguma variável, digamos), esse detalhe pode ter sumido. É também por isso que a compactação serve ao problema grosso de "o contexto geral ficou grande demais" em vez de servir como cura para todo tipo de inchaço de histórico.
Limpeza de resultados de ferramenta: limpe só a parte que fica obsoleta
Um grande contribuinte para o inchaço do histórico são as próprias chamadas de ferramenta. Toda vez que um agente lê um arquivo ou roda um comando, o valor de retorno completo é enfiado no histórico — leia um arquivo de alguns milhares de linhas e essas milhares de linhas ficam no array messages como estão, mesmo dez turnos depois, quando ninguém precisa mais do detalhe. O Cookbook aponta isso diretamente: "Tool-result clearing addresses the bloat from tool use itself. As an agent pulls in tools and calls them, the results pile up, and deciding how much of that tool output to keep becomes an increasingly important part of managing context."3 (A limpeza de resultados de ferramenta trata o inchaço do próprio uso de ferramenta. Conforme um agente puxa ferramentas e as chama, os resultados se empilham, e decidir quanto dessa saída de ferramenta manter se torna uma parte cada vez mais importante de gerenciar o contexto.)
A limpeza de resultados de ferramenta (tool-result clearing) é o mecanismo mirado diretamente nisso: ela "drops old, re-fetchable results while keeping the record that the call happened."3 (descarta resultados antigos e rebuscáveis mantendo o registro de que a chamada aconteceu.) Essa é a distinção-chave — a limpeza descarta o conteúdo concreto que a ferramenta retornou (aquelas poucas milhares de linhas de conteúdo de arquivo), mas ela não apaga o registro de que "o agente chamou read_file com este caminho". Se esse conteúdo for necessário de novo mais tarde, o agente sabe qual ferramenta chamou e quais argumentos passou, e pode decidir se a chama de novo para buscar o conteúdo de volta.
Seu limiar de disparo e sua política de retenção também têm padrões claros: a limpeza dispara quando o uso da janela atinge 100K tokens, e por padrão mantém os resultados completos das 3 chamadas de ferramenta mais recentes, limpando os resultados de ferramenta mais antigos3. O disparo de 100K é menor que os 150K da compactação, o que combina com seu papel — lide primeiro com a saída de ferramenta, a parte que "incha mais fácil e é a mais fácil de rebuscar", e se isso não bastar, entregue a janela geral para a compactação.
Escolhendo entre os três: um modelo mental
Agora temos dois mecanismos, e adicionar a memória externa que a Lição 3 cobre faz três. O Cookbook dá um modelo mental compacto que organiza a divisão de trabalho deles: "compaction compresses the whole window when it grows too large, clearing drops stale re-fetchable data inside the window, and memory moves information out of the window so it survives across sessions."3 (a compactação comprime a janela inteira quando ela cresce demais, a limpeza descarta dados obsoletos e rebuscáveis dentro da janela, e a memória move informação para fora da janela para que ela sobreviva entre sessões.)
As prioridades e os casos de uso deles não competem — eles são em camadas:
- A limpeza de resultados de ferramenta lida com "este conteúdo ainda está na janela mas ficou obsoleto, e descartá-lo é aceitável porque ele pode ser rebuscado" — o mais direcionado, o menos custoso.
- A compactação lida com "a janela inteira ficou grande demais", independentemente de onde o conteúdo veio, reescrevendo tudo em um resumo — alcance mais amplo, mas com perdas e custa uma chamada de modelo extra.
- A memória (tema da próxima lição) lida com "esta informação não deveria viver só nesta conversa, ela precisa durar até a próxima sessão" — ela não está resolvendo "a janela não cabe tudo" de forma alguma, mas "assim que esta conversa termina, tudo na janela desaparece".
De volta à pergunta com que esta lição abriu: o histórico só cresce porque ninguém o limpa ativamente. Truncagem, compactação e limpeza de resultados de ferramenta são três formas de limpá-lo a custos diferentes e para situações diferentes — qual você escolhe depende do que você quer manter e de quanto está disposto a pagar para mantê-lo.
Recapitulação
- O histórico da conversa só cresce por padrão: as mensagens de cada turno se empilham na janela, os turnos anteriores são mantidos por inteiro, e sem ninguém limpando ativamente, ele sobe sem limite
- A truncagem é a mais simples, mas o que ela descarta é irreversível, e se o ponto de corte cai no meio de um par
tool_use / tool_result, ela quebra a estrutura do protocolo da chamada de ferramenta
- A compactação reescreve o histórico da janela inteira em um resumo de alta fidelidade, disparando por padrão em 150K tokens (o limiar não pode ir abaixo de 50K, imposto pelo servidor), ao custo de ser com perdas e de uma chamada de modelo extra; uma conversa longa pode compactar mais de uma vez, com os blocos de resumo anteriores dobrados no novo resumo3 4
- A limpeza de resultados de ferramenta só descarta saída de ferramenta obsoleta e rebuscável mantendo o registro da chamada, disparando por padrão em 100K tokens e mantendo os resultados das últimas 3 chamadas — mais direcionada que a compactação
- Os três têm trabalhos diferentes: a limpeza lida com dados obsoletos e rebuscáveis, a compactação lida com uma janela geral grande demais, a memória lida com sobreviver entre sessões — qual você escolhe depende da fonte específica do inchaço e de se você pode se dar ao luxo de perder detalhe
>> Lição 3: Memória externa: arquivos e recuperação