Recuperação de Context Overflow
Quando a API retorna um erro de “context too long”, o ChatCLI aplica até 3 niveis de recuperação antes de desistir. Todo loop recupera, não só o agent/coder: o REPL de chat (o prefixo e a mensagem de contexto do turno são reconstruídos sobre o histórico recuperado), o chat RPC atrás de MCP/ACP/gateway, o one-shot (-p, aviso no stderr para o stdout continuar “pipeável”) e cada participante do MoA (a própria thread é compactada; o painel não falha mais por um overflow). O helper compartilhado é limitado (CHATCLI_MAX_RECOVERY_ATTEMPTS, padrão 3) e dá as mesmas garantias de uma compactação planejada: memória descarregada antes, hooks avisados com o gatilho recovery, mensagens descartadas arquivadas no CCR, rebuild de cache contabilizado.
O classificador que decide “isto é overflow” é compartilhado por todos os loops e pela cadeia de fallback de provedores, e cobre a frase de cada provedor — OpenAI chat e Responses (“context window”, context_length_exceeded), Anthropic (“prompt is too long”), Gemini (“input token count … exceeds”), xAI (“maximum prompt length”), Mistral, Groq, Bedrock — mais uma checagem por status em 400/413 cujo corpo combina uma palavra de token/tamanho com uma de excesso.
- Nivel 1: Orcamento Agressivo
- Nivel 2: Truncamento de Emergencia
- Nivel 3: Truncamento Nuclear
Primeira tentativa: reduz os limites de orcamento pela metade e limpa desalinhamentos.Acoes:
- Repara pareamento de tool results (remove orfaos, injeta sinteticos)
- Aplica 50% de
DefaultTurnBudgetCharseDefaultPerResultMaxCharssó para esta sessão — os defaults do processo nunca são mutados, então outras sessões de um gateway ourpcservenunca veem limites pela metade - Aplica enforcement de orcamento com limites reduzidos
- Trunca mensagens longas do assistente para 5.000 chars
Os limites originais sao restaurados após a aplicacao. Apenas o histórico atual e afetado pela reducao.
Detecção de Erro — overflow do modelo
O sistema reconhece múltiplas formas de erro de overflow:Recuperação de proxy/gateway corporativo
Ambientes corporativos frequentemente rodam atrás de proxies ou gateways que impõem um teto de body size para POSTs — tipicamente 1-5 MB, totalmente independente da janela de contexto do modelo. Você pode estar dentro do limite do Anthropic (200K tokens / ~800 KB) e mesmo assim tomar uma rejeição misteriosa do proxy. Pior: muitos proxies não retornam um 413 limpo — alguns mandam 403 do WAF (Cloudflare, Akamai, mod_security), 431 (header too large), ou simplesmente dropam a conexão TCP no meio do POST, emergindo comoEOF / connection reset no cliente.
O ChatCLI detecta esses três padrões e aplica o mesmo fluxo de recuperação do context overflow.
Detecção de Erro — proxy/gateway
A detecção de WAF é conservadora — um 403 sem sinais de firewall continua sendo tratado como erro de autenticação (refresh OAuth + retry). Só quando o 403 carrega sinais específicos do proxy/WAF é que ele é reclassificado como falha recuperável de payload. Isso evita invalidar credenciais OAuth válidas quando o problema real está na rede.
O caso Bedrock corporativo: quando o proxy/WAF intercepta a requisição POST ao Bedrock Runtime e devolve uma página HTML de bloqueio com status 403, o AWS SDK tenta fazer parse do body como JSON e falha com
"invalid character '<' looking for beginning of value" e RequestID vazio. Esse padrão é um fingerprint inequívoco de middlebox (um 403 real da AWS retorna JSON bem-formado) — o ChatCLI reclassifica para falha recuperável de payload e dispara a mesma ladder de recovery.A detecção de EOF/connection-reset aplica um limiar de tamanho do histórico (500 KB) antes de suspeitar de payload. Requisições pequenas que sofrem EOF continuam sendo tratadas como falhas transitórias de rede (retry normal). Só quando o histórico já está suspeitosamente grande é que o EOF é reclassificado como possível body cap.
Pre-flight check
A cada turno do agente, o histórico é medido antes do request sair. Dois caminhos: ComCHATCLI_MAX_PAYLOAD configurado:
Se o histórico passa de 85% do cap, o BudgetRatio é forçado para 0.40 antes de qualquer coisa — compactação agressiva preventiva. O usuário vê:
Cap adaptativo aprendido
Os providers Bedrock anotam cada erro de transporte com o tamanho exato da requisição que o middlebox rejeitou. Quando uma rejeição de payload acontece, o ChatCLI deriva o limite da sessão diretamente dessa observação — ¾ do tamanho rejeitado — em vez de chutar:CHATCLI_MAX_PAYLOAD já estiver abaixo dele, seu valor vence; uma rejeição menor posterior aperta ainda mais.
Quando o tamanho rejeitado é desconhecido (providers sem anotação de tamanho), vale o comportamento anterior como fallback: assumir 4 MB para o resto da sessão.
Diagnóstico de piso — quando compactar não resolve
O system prompt (charter do agente, personas, skills, docs de ferramentas MCP) nunca é compactado. Quando ele sozinho atinge o tamanho que o gateway acabou de rejeitar, nenhuma compactação de histórico produz uma requisição aceitável — repetir payloads idênticos só queimaria tentativas de recovery. O ChatCLI detecta isso e falha rápido com uma mensagem acionável:Encolhimento de conteúdo — o Nível 3 que funciona de verdade
Dropar mensagens inteiras é no-op quando o histórico é curto — sessões de agente frequentemente têm só o system prompt e um punhado de tool results gigantes, e oMinKeepRecent mantém exatamente as mensagens que carregam o volume. A truncagem de emergência ganhou por isso um passe final: encolhe o conteúdo das mensagens não-system, da maior para a menor, até caber no budget de payload. Mensagens system nunca são tocadas.
Com a camada de compressão ativa, cada mensagem é arquivada verbatim no store CCR antes do primeiro corte e o conteúdo encolhido carrega um marcador <<ccr:KEY>> — o modelo recupera o original a qualquer momento com @recall. A truncagem nuclear (nível 3 da escada de recovery) adicionalmente limita cada mensagem não-system mantida a 4.000 chars.
O que cada nível garante
As mesmas garantias valem em qualquer loop que compacte (chat, agent/coder, one-shot,/compact) e no recovery de overflow:
- Nada que o modelo pediu para manter é tocado. Mensagens marcadas
PreserveVerbatim(um resultado de@recallque o modelo pediu explicitamente por inteiro) ficam fora do segmento sumarizado e são reanexadas logo após o resumo no Nível 2, mantidas inteiras no Nível 3 e ignoradas pelo encolhimento de conteúdo. Um tool result nativo verbatim reanexado sem a tool call dona vira mensagem de usuário, então o reparo de pareamento nunca o apaga. Mensagens verbatim ocupam no máximo um quarto do budget: além disso, as mais antigas são arquivadas de novo no CCR, viram stub com marcador@recalle perdem a flag, então a compactação sempre converge. - Compactação que não muda nada não é compactação. Um no-op, um resumo rejeitado ou uma falha descarta o snapshot de undo (então
/rewind compactnunca “restaura” um histórico idêntico) e dispara oPostCompactpar comoutcome=skipped; restaurar um checkpoint do/rewindusa a mesma contabilidade do/rewind compact(reescrita no journal, rebuild de cache esperado, pilha de undo limpa). - O Nível 3 deixou de ser lossy. As mensagens que ele descarta são arquivadas no store CCR antes, e o aviso de truncagem carrega um marcador
@recallpara o segmento inteiro — a mesma recuperabilidade que os Níveis 1 e 2 já tinham. O recovery de overflow descarrega a memória pendente, arquiva as mensagens que remove e anexa o marcador ao histórico recuperado. - Um resumo ruim nunca substitui o segmento. Um gate de qualidade rejeita recusas e respostas curtas demais para o segmento (piso proporcional ao tamanho, teto de 80 caracteres); o sumarizador é tentado mais uma vez e, se falhar, o pipeline cai para o Nível 3 com seu arquivo.
- O sumarizador não é pago à toa. Quando um resumo não traz o histórico para dentro do budget, as duas compactações seguintes pulam o Nível 2 e vão direto ao Nível 3 em vez de pagar uma chamada de sumarização por turno.
- Hooks veem toda compactação.
PreCompact/PostCompactdisparam comtriggerauto,manualourecovery(hooks). - É contabilizado.
/costmostra o número de compactações, quantas caíram no Nível 3 e quanto o sumarizador custou; os contadores persistem com a sessão (cost tracking).
/compact <instrução> guiado segue as mesmas regras: usa a rota de sumarizador configurada (CHATCLI_COMPACT_MODEL) quando existe, dá ao resumo sua própria janela de 10 minutos em vez do antigo prazo de 60 segundos do comando, mantém mensagens PreserveVerbatim, arquiva o segmento e roda o gate de qualidade.
System notice injetado na história
Após um recovery disparado por payload limit, o ChatCLI injeta uma mensagemuser antes do retry instruindo o modelo a fazer leituras menores no futuro. Isso quebra o loop do modelo de tentar re-ler o mesmo arquivo gigante que causou o 413 originalmente. O notice é injetado no máximo uma vez por sessão — o recovery detecta uma cópia existente em qualquer lugar do histórico (mesmo dobrada num summary de compactação) e nunca empilha uma segunda, já que cada byte extra trabalha contra o próprio limite sendo recuperado:
Escalação de Max Output Tokens
Quando o modelo para de gerar por atingir o limite demax_tokens, o ChatCLI pode escalar automaticamente:
Mensagem de Continuacao
Quando o modelo e interrompido por limite de tokens, o ChatCLI injeta uma mensagem de continuacao:Configuração
Feedback visual durante a compactação
Desde esta versão, o histórico nunca mais “congela” o terminal durante uma compactação longa. OHistoryCompactor emite status em cada fase do pipeline via SetStatusCallback:
Ctrl+C / ESC propaga corretamente e aborta a compactação sem corromper o histórico (retorna ctx.Err() em vez de cair para truncamento de emergência cego).
Flush de memória antes da compactação
O worker de memória destila fatos do histórico vivo alguns turnos atrás da conversa; a compactação substitui o meio desse histórico por um resumo, então o que o worker ainda não tinha alcançado era destilado, no melhor caso, a partir do resumo. Todo ponto de compactação (auto-compact no chat, agent e coder,/compact explícito e guiado, one-shot) agora entrega o segmento ainda não extraído à fila durável do worker antes da reescrita, para que as mensagens originais cheguem à memória de longo prazo intactas, independentemente do que o resumo mantém.
Leituras repetidas (pré-budget)
No mesmo limite de turno,DedupRepeatedReads remove as cópias que uma sessão de coder acumula ao ler o mesmo arquivo repetidas vezes: quando um @coder read posterior do mesmo caminho cobre a mesma faixa de linhas ou uma mais ampla, toda leitura anterior daquele caminho vira um stub de uma linha (arquivado antes no CCR, então @recall restaura) e a leitura mais nova fica intacta. Leituras posteriores mais estreitas nunca substituem uma anterior mais ampla, a saída de @recall nunca é tocada, e cada passagem é reportada na linha do turno. O modelo mantém exatamente uma visão atual por arquivo em vez de cinco.
Microcompact (pré-budget)
A checagem de budget em si é honesta com o payload: pesa o texto das mensagens mais argumentos de tool calls nativas, payloads de imagem e excesso de system-parts, então históricos pesados em tools ou visão cruzam o limiar quando a requisição real cruza — não depois de um proxy já ter rejeitado. Antes doNeedsCompaction checar se o histórico excede o budget, o agent loop aplica ApplyMicrocompact — uma passada pure-Go, sem LLM, sem rede que progressivamente trunca/resume tool results antigos (2+ turnos atrás → head+tail preview; 4+ turnos atrás → one-line summary). Na maioria dos casos isso mantém o histórico dentro do budget sem disparar o Level 2 (caro).
A entrada do sumarizador é orçada contra a janela do próprio modelo sumarizador (metade dela, piso de 20K chars) em vez de um corte fixo cabeça/cauda por mensagem: cada mensagem do segmento recebe uma cota justa, uma mensagem já dobrada num stub <<ccr:KEY>> é restaurada do arquivo CCR quando o original cabe, mensagens longas mantêm cabeça e cauda na proporção 3:1, e tool calls nativas são nomeadas para o resumo listar os comandos executados. A mesma renderização serve o /compact <instrução>.
Com a camada de compressão ativa, o microcompact é sem perda: o tool result original é arquivado no store CCR antes do corte e o stub de preview/summary embute um marcador <<ccr:KEY>>. Os marcadores sobrevivem entre níveis — quando um preview truncado depois vira um summary de uma linha, o marcador permanece no summary — então o modelo sempre pode expandir o original com @recall.
Ratio de Orcamento Agressivo
No nivel 1, os limites de orcamento de tool results sao multiplicados por0.5 (50%). Isso significa:
Fluxo de Recuperação
Interação com Outros Sistemas
A recuperação de contexto trabalha em conjunto com:Tool Result Budget
O orcamento de resultados e a primeira linha de defesa. A recuperação ativa quando o orcamento não foi suficiente.
Microcompactacao
A compactação progressiva reduz o crescimento do contexto ao longo do tempo.
Controle de Conversa
O comando
/compact e a forma proativa de prevenir overflow.Cost Tracking
Monitore o uso de contexto para antecipar quando /compact sera necessário.
Motor de contexto externo (MCP)
CHATCLI_CONTEXT_ENGINE=mcp:<server> entrega o resumo de Nível 2 a um servidor MCP configurado que expõe context_compact(segment, budget_chars, instruction): o segmento renderizado da conversa (a mesma renderização orçada que o sumarizador embutido recebe), o orçamento em chars e, no /compact <instrução> guiado, a instrução do usuário. A resposta substitui o segmento; o arquivo CCR, os checkpoints e o @recall seguem funcionando exatamente como antes. Erro, timeout (3 min) ou resposta vazia caem no sumarizador embutido naquela compactação. Aparece em /config compression.
CHATCLI_CONTEXT_ENGINE=provider soma a edição de contexto server-side do próprio modelo ao pipeline local. Na Anthropic (API key ou OAuth) toda requisição do loop de tools carrega o beta context-management-2025-06-27 e um bloco context_management com uma edição clear_tool_uses_20250919: quando o prompt passa de 100 K tokens de entrada, a própria API limpa os tool results mais antigos, mantendo os cinco tool uses mais recentes e liberando pelo menos 20 K tokens por edição, antes de esses tokens serem cobrados. As edições aplicadas voltam na resposta e são espelhadas localmente: os tool results mais antigos que o servidor limpou viram stubs no histórico local (os originais arquivados no CCR, recuperáveis com @recall), a calibração chars/token pula aquele turno, a próxima escrita de cache é contabilizada como rebuild esperado e o /context status mostra quantas edições, tool results e tokens o provedor limpou. A compactação local, o arquivo CCR e o /rewind compact seguem iguais, então nada se perde que já não fosse recuperável. Provedores sem equivalente server-side documentado ignoram a opção.