Compaction é uma decisão que você toma antes da window encher, não depois
Prepare o summary em background para que o limite seja cruzado sem um travamento.
Apollo Space Research
Apollo Space
Um agent está doze tool calls dentro de um trabalho longo, lendo arquivos, rodando checagens, costurando um plano, e o context window está quase cheio. O próximo token não vai caber. Então o runtime para tudo, devolve a transcrição inteira ao modelo e espera ele escrever um summary do que acabou de acontecer. Quarenta segundos de nada. O usuário, no meio de um pensamento, fica olhando um spinner. O agent, no meio da tarefa, escureceu para pensar sobre pensar.
Essa pausa tem um nome no nosso código, e passamos muito tempo tentando deixá-la mais curta. Estávamos resolvendo o problema errado.
A pausa não é lenta porque resumir é lento. É lenta porque começamos a resumir no pior momento possível, exatamente no instante em que o agent não tinha mais espaço para fazer mais nada. A correção não é um summary mais rápido. É um summary que já estava escrito.
Prepare o summary em background para que o limite seja cruzado sem um travamento.
Este post é sobre por que um agent de execução longa precisa gerenciar sua própria memória do jeito que um sistema operacional gerencia a sua, e por que o truque que faz isso parecer instantâneo é o mesmo truque que seu laptop usa há décadas.
A versão ingênua: compactar quando você fica sem espaço
A forma óbvia de lidar com um context window cheio é esperar até ele encher.
Você deixa o agent rodar. Você observa a contagem de tokens subir. Quando ela cruza algum limiar, digamos, a window está quase esgotada e o próximo passo não vai caber, você dispara a compaction: você pede ao modelo para ler tudo até agora e produzir um summary enxuto, você joga fora a transcrição bruta, você mantém o summary e retoma com o espaço liberado. É correto. Não perde nada importante se o summary for bom. E é a versão que quase todo framework de agent entrega primeiro, porque é a versão que você recorre quando está pensando em correção e ainda não está pensando em na pessoa esperando.
Funciona bem num benchmark, onde ninguém está olhando o relógio entre os turns. Aí você coloca na frente de um usuário real numa tarefa real e a costura aparece.
O problema é timing, não técnica. Compaction é cara, você está enviando uma transcrição grande pelo modelo e esperando um summary completo voltar. E você arranjou para que essa coisa cara aconteça no único momento em que o agent está mais ocupado e o usuário mais engajado: no fundo de uma tarefa, momentum alto, o limite atingido sem aviso. O agent não desacelera. Ele congela. Tudo o que estava fazendo para enquanto ele vai embora comprimir o próprio histórico, e a pessoa do outro lado não faz ideia de por que a coisa que voava um segundo atrás agora é um spinner.
Um compactor ingênuo não está errado sobre o que fazer. Está errado sobre quando. Ele faz o trabalho certo na pior hora.
A ideia que pegamos emprestada: prepare a página antes de precisar dela
Não inventamos a correção. Roubamos da camada embaixo de tudo.
Seu sistema operacional tem o mesmo problema, escalado para a máquina inteira. A memória física é finita. Programas pedem mais do que cabe. A resposta ingênua seria esperar até a memória estar completamente esgotada e então, em pânico, achar algo para despejar e escrevê-lo em disco enquanto todo programa trava. Nenhum sistema operacional é entregue assim, porque pareceria exatamente como nosso spinner, a máquina congelando no pior momento para abrir espaço que deveria ter aberto antes.
Em vez disso, o OS trabalha à frente do muro. Ele mantém um pouco de memória livre em reserva. Um processo em background observa a pressão e, bem antes de as coisas ficarem críticas, silenciosamente escolhe páginas que não estão sendo usadas e as escreve para fora, para que, quando um programa finalmente precisar de espaço, o espaço já esteja lá. O despejo ainda acontece. O custo ainda é pago. Mas é pago fora do critical path, em background, antes do momento da necessidade, então o programa que pede memória a recebe instantaneamente e nunca fica sabendo de nada.
Esse é o movimento inteiro, e ele transfere limpo:
Não pague o custo quando você bate no muro. Pague mais cedo, em background, para que bater no muro seja de graça.
Compaction é a versão do paging do agent. A transcrição é memória sob pressão. O summary é a página despejada, escrita num lugar mais barato. E a lição da camada de baixo é que resumir nunca foi o problema, fazê-lo de forma síncrona, no limite, com o usuário esperando, era. Então nós o movemos.
Prepare o summary em background para que o limite seja cruzado sem um travamento.
Como o summary preparado realmente funciona
O mecanismo é menos esperto do que parece, que é como você sabe que está certo.
Enquanto o agent trabalha, um segundo job observa a mesma contagem de tokens que a versão ingênua espera, mas em vez de esperar pelo muro, ele traça uma linha mais suave bem antes dele. Suponha que a window comporta espaço para uma conversa longa; o observador traça sua linha em, digamos, dois terços de capacidade, um limiar que você ajustaria para deixar uma folga confortável. Quando a transcrição cruza essa linha mais suave, nada visível acontece. Em background, fora do caminho que o usuário observa, o runtime começa a construir o summary do histórico até agora. O agent continua tomando seus turns. O usuário continua recebendo respostas. Um summary é silenciosamente montado em reserva.
Aí uma de duas coisas acontece, e ambas estão bem.
Se o agent termina a tarefa antes de a window de fato encher, o summary preparado nunca é necessário. Gastamos um pouco de compute que não precisávamos. Essa é a reserva que mantemos livre de propósito, seguro barato, pago aconteça ou não o incêndio.
Se o agent de fato alcança o limite, não há pausa. O summary já existe. O runtime troca a transcrição bruta pelo summary que preparou momentos atrás, a window tem espaço de novo, e o próximo turn sai como se nada tivesse acontecido. O limite que costumava ser um travamento de quarenta segundos agora é uma coisa que acontece entre dois tokens. O trabalho da compaction foi idêntico. A experiência dele foi de um congelamento para nada, porque a parte cara estava terminada antes de alguém precisar dela.
Há uma sutileza que vale nomear, porque é onde o trabalho ingênuo em background dá errado. O summary tem que ser construído a partir de um snapshot consistente do histórico, você não pode resumir uma transcrição que está sendo acrescida embaixo de você, ou você obtém um summary que já está desatualizado quando aterrissa. Então o summary preparado é construído contra um ponto marcado na conversa, e se o agent adiciona mais alguns turns antes do limite, esses últimos turns são simplesmente carregados em cima do summary. Você resume o passado já assentado em background e mantém o presente fresco bruto. O limite, quando chega, é uma troca limpa sem recomputar.
Por que “só deixar o summary mais rápido” era uma armadilha
O instinto que todo mundo tem primeiro, incluindo nós, é atacar a duração. Se a pausa é de quarenta segundos, deixe o summary mais curto, use um modelo menor para escrevê-lo, faça cache de parte dele, faça qualquer coisa para arrastar o número para baixo.
É uma armadilha, e vale dizer por quê, porque é o tipo de armadilha que parece progresso.
Cada segundo que você corta de uma compaction síncrona é um segundo que o usuário ainda espera, você deixou o congelamento mais curto, não o eliminou. E você comprou esse congelamento mais curto com um summary pior: um modelo menor ou um budget mais apertado comprime o histórico de forma menos fiel, então o agent retoma com uma memória mais embaçada do que estava fazendo, e a qualidade que você perde lá na frente é mais difícil de ver do que os segundos que você economizou no começo. Você está trocando a coisa que o usuário sente pela coisa que o agent precisa, e chamando isso de otimização.
A abordagem em background recusa a troca por inteiro. Como o summary é construído fora do critical path, sua duração quase não importa, ele pode levar seu tempo, usar o modelo completo, produzir a compressão fiel que o agent vai realmente depender, e ainda custar zero ao usuário, porque o usuário nunca esteve esperando por ele. A versão ingênua força uma escolha entre uma pausa rápida e um bom summary. A versão preparada dissolve a escolha: um bom summary que o usuário nunca espera de jeito nenhum.
Essa é a diferença entre otimizar um número e movê-lo para fora do caminho. Um deixa a dor menor. O outro faz a dor acontecer onde ninguém está parado.
A virada: um agent que roda por horas tem que gerenciar seu próprio esquecimento
Pergunte o que de fato é preciso para um agent trabalhar em algo por uma hora em vez de um minuto, e você chega a algum lugar mais silencioso que as demos.
Uma conversa curta nunca enche sua window, então nunca tem que esquecer nada, então nada disso importa. O problema inteiro só aparece quando o trabalho é longo, quando um agent está genuinamente vivendo numa tarefa ao longo de muitos turns, acumulando mais histórico do que qualquer window comporta, e tem que decidir, continuamente, o que manter nítido e o que comprimir na essência. Essa decisão é gerenciamento de memória, e um sistema que vai rodar trabalho real por durações reais não pode tratá-la como algo secundário que lida em pânico no muro. Ele tem que fazer o que todo sistema de execução longa antes dele aprendeu a fazer: olhar à frente, abrir espaço antes de ficar sem espaço, e pagar o custo onde o custo não pode ser sentido. Prepare o summary em background para que o limite seja cruzado sem um travamento.
O esquecimento sempre ia acontecer. Uma window finita o garante. A única pergunta que um agent pode responder é quando ele esquece, num travamento que o usuário observa, ou numa passada silenciosa em background que ele nunca vê. Achamos que a diferença entre essas duas respostas é a maior parte do que separa uma demo impressionante por cinco minutos de um sistema em que você confiaria para trabalhar a tarde inteira.
A window sempre vai encher. O que você decide antes que ela encha é se a pessoa do outro lado precisa ficar sabendo.
Esse é o tipo de coisa que estamos construindo na Apollo Space, agents que gerenciam sua própria memória do jeito que o OS sob seus dedos já gerencia a sua, para que os trabalhos longos pareçam tão suaves quanto os curtos. Se você já viu algo rápido ficar subitamente, inexplicavelmente parado bem na hora em que você mais precisava, você já sabe qual desses dois momentos estamos tentando deletar.
A Apollo cuida da operação repetitiva da sua empresa pro seu time não precisar.
Entre na lista de espera: acesso antecipado, preço de usuário fundador e um lugar na primeira fila enquanto a gente constrói.
Entrar na lista de esperaO imposto oculto dos agents em paralelo é um diamante de migrations
Seis agents escrevendo para um schema conflitam no banco de dados, não no código, e a CI morre em "multiple heads".
EngenhariaUm orchestrator que não sobrevive ao próprio crash não é um
Um crash que apaga o raciocínio do orchestrator perde a única coisa que você não consegue reconstruir.
EngenhariaColoque um portão determinístico na frente do seu revisor mais esperto
A pega-defeito mais barata é um script burro que checa se duas branches mergeadas ainda sobem antes de qualquer julgamento.