Blog

A falta de memória escondida em um rótulo de progresso

Numa noite de fim de setembro, o agente de IA no workspace de um usuário travou com um erro de falta de memória no meio de um turno. Os suspeitos óbvios, muitos subagentes rodando ao mesmo tempo ou um contexto que tinha crescido demais, estavam ambos errados. A causa real era um rótulo de status: enquanto o modelo transmitia um comando de shell grande alguns caracteres por vez, reenviávamos o comando inteiro até ali como rótulo de progresso a cada pedaço. Um comando de 124 KB virou cerca de 41.000 eventos e aproximadamente 690 MB de strings enfileiradas.

Como o agente transmite o seu trabalho

Quando você constrói no Mythex, o agente roda dentro do sandbox privado de nuvem do seu projeto. Conforme o modelo escreve, a saída é transmitida de volta para o seu navegador: o texto aparece palavra por palavra, e quando ele executa uma ferramenta, como um comando de shell que grava um arquivo, você vê um rótulo dela sendo atualizado ao vivo, para saber o que está fazendo.

O modelo transmite em pequenos pedaços. Com o modelo que estávamos usando, esses pedaços muitas vezes tinham apenas três caracteres.

As primeiras hipóteses estavam erradas

A falha parecia um problema de escala, então começamos por aí.

  • Subagentes em paralelo? O turno usou apenas dois, cada um com cerca de 1,5 MB de estado. Não era suficiente.
  • Rastros retidos? Nosso rastreamento mantinha uma árvore de execuções na memória, mas o tamanho estava muito longe do tamanho do heap no momento da falha.

As duas eram histórias plausíveis, e as duas eram baratas de acreditar. Nenhuma reproduziu a falha. A lição que continuamos reaprendendo: uma hipótese não é uma causa enquanto não reproduzir a falha.

Reproduzindo de verdade

Montamos um sandbox descartável com os mesmos limites de memória da produção e um modelo falso que transmitia um arquivo grande em pedaços realistas de três caracteres. Também deixamos lento o leitor do outro lado do socket, como uma rede real.

Isso fez aparecer o problema. O heap subiu e o processo morreu com o mesmo erro.

O mecanismo era simples depois que pudemos observá-lo. Enquanto o modelo escrevia um comando grande, neste caso um heredoc que gravava um arquivo de 124 KB, nosso código de transmissão atualizava o rótulo da ferramenta a cada pedaço. O rótulo não era os três caracteres novos; era o comando inteiro até ali. Assim, os eventos cresciam: 3 caracteres, depois 6, depois 9, … até 124 KB, cerca de 41.000 vezes. O socket não conseguia enviar tão rápido quanto eram produzidos, então se acumulavam no buffer de escrita como strings, cerca de 690 MB delas.

A correção

  • Limitar o rótulo. Um rótulo de status só precisa dizer o que está acontecendo. Agora ele é limitado a 200 caracteres, e reduzimos a frequência com que reexaminamos o comando parcial.
  • Acabar com a concatenação quadrática. Unir os pedaços transmitidos com concatenação repetida de strings é O(n²) para saídas grandes. Passamos a uma abordagem por blocos que acrescenta em tempo linear.

Depois procuramos qualquer outra coisa que pudesse crescer sem limite diante de uma enxurrada de saída, e a limitamos:

  • A saída de comandos ao vivo mostrada no chat é agrupada e mantém apenas os últimos 8.000 caracteres.
  • Linhas de evento individuais são limitadas a 4 MB.
  • O buffer de replay que um navegador usa para se atualizar após reconectar é limitado a 8 MB por projeto.
  • Checkpoints antigos são podados conforme o turno avança, mantendo o estado mais recente e as âncoras de que turnos anteriores precisam.

Testando enxurradas com honestidade

Duas coisas tornaram isso mais difícil de testar do que deveria:

  1. Modelos reais não colaboram. Peça a um modelo para imprimir um arquivo gigantesco e ele normalmente vai recusar, resumir ou escrever de forma mais sensata. Para testar enxurradas, você precisa de um modelo roteirizado que faça exatamente o que você mandar, rodando pelo código real do agente.
  2. Os roteiros precisam se comportar como modelos. Nosso primeiro modelo roteirizado reutilizava o mesmo ID de resposta em cada resposta, então a estrutura do agente tratava cada resposta como substituta da anterior e encerrava os turnos cedo. Parecia um bug de atualização da interface. Não era: era o harness de teste nos enganando. Também tivemos de impor no roteiro o limite real de contexto do modelo, ou os números de memória ficavam irreais.

O que tiramos disso

  • Interfaces de streaming multiplicam pequenos erros. Qualquer coisa que você envia “por pedaço” é enviada dezenas de milhares de vezes. Envie deltas, ou limite o payload.
  • Limite todo buffer que cruza uma fronteira lenta. Sockets, logs de replay e checkpoints precisam de um teto.
  • Reproduza antes de corrigir. Nossas duas primeiras explicações soavam certas e estavam erradas.

Se você viu um arquivo muito longo aparecer no chat do Mythex recentemente sem o agente travar, esta é parte do motivo.

Mythex · Pricing · Crie qualquer coisa com IA · Templates de sites e apps · Mythex comparado · Glossário de criação de apps com IA · Ferramentas grátis para criar apps com IA · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)