Skip to main content
O ChatCLI implementa uma série de otimizações para manter o consumo de tokens sob controle em sessões longas com os modos /agent e /coder. Esta página explica o que roda sem você mexer em nada e quais interruptores estão disponíveis quando o comportamento default não serve para o seu workflow.
Todas as otimizações desta página funcionam em todos os provedores suportados (Anthropic direto, Bedrock, OpenAI, xAI, ZAI, MiniMax, Moonshot (Kimi), Google AI, Ollama, Copilot, GitHub Models, OpenRouter, OpenAI Responses, StackSpot). Providers com prompt caching explícito (Anthropic, Bedrock Anthropic) ou auto-caching (OpenAI, xAI) aproveitam ganhos adicionais.

O problema real

Um loop ReAct mal calibrado consegue transformar uma pergunta trivial num consumo absurdo de tokens. Sem as otimizações abaixo, uma query como “resultado do jogo do Flamengo” pode facilmente queimar 20k+ tokens porque:
  1. O system prompt inteiro é re-enviado em cada turno (sem cache).
  2. As definições de tools (15+ schemas JSON) também vão re-enviadas por turno.
  3. Nada quebra o loop quando o modelo repete a mesma tool_call sem convergir.
  4. Bodies grandes do @webfetch entram crus no contexto.
Em agregado, cada turno pesa 4-8k tokens de overhead puro, multiplicado por 3-5 turnos de uma sessão normal = 12-40k tokens antes mesmo de contar a resposta útil.

1. Prompt Caching estruturado

O cache da Anthropic (e o auto-cache de prefixo do OpenAI/xAI) funciona por prefixo: um breakpoint só dá hit quando todos os bytes antes dele são idênticos a uma requisição anterior. A regra de ouro é simples — blocos estáveis primeiro, blocos voláteis no fim. O system prompt é montado exatamente assim: Prefixo estável (cacheado, cache_control: ephemeral): Sufixo volátil (sem cache marker — muda a cada turno):
Por que a ordem importa (defeito corrigido): antes, o bloco de workspace+memória carregava cache_control mas continha o timestamp ao segundo e a recuperação hint-driven — ambos voláteis — no topo do prompt. Isso garantia cache miss nesse bloco a cada turno e envenenava todos os blocos cacheados abaixo dele (contextos /context, pinned skills, catálogo MCP): pagava-se cache creation (1.25×) toda vez e nunca se colhia um read. O timestamp agora vive num bloco próprio no fim, e a memória volátil saiu do prefixo. Os blocos verdadeiramente estáveis formam um prefixo contíguo que casa o cache.
Cada bloco estável carrega cache_control: ephemeral para os providers Anthropic (respeitando o teto de 4 breakpoints, com coalescing automático). Para providers com auto-caching de prefixo (OpenAI, xAI), a ordem estável faz o cache casar naturalmente. O chat segue a mesma lógica de ordenação — e como a exceção da ferramenta de memória do chat carrega o caminho de pull, o chat honra o modo index exatamente como agent/coder (ver seção 7).

Cache nas tool definitions

A última definição de ferramenta enviada para a Anthropic também recebe cache_control: ephemeral, o que transforma todo o array de tools num prefixo cacheável. Em uma sessão /coder com 15 ferramentas coder + 2 web tools, isso representa ~19KB que deixam de ser re-tokenizados a cada turno.

Visibilidade do cache

Cached tokens do OpenAI não exigem opt-in — prompt caching é automático em gpt-4o e mais novos (incluindo o-series e GPT-5), disparado quando o prefixo do prompt tem ≥1.024 tokens, com hits servidos em incrementos de 128 tokens. Para streaming Chat Completions, o ChatCLI envia stream_options: {include_usage: true} para o chunk terminal de usage chegar antes do [DONE]; na Responses API, o usage vem no evento SSE response.completed sem flag adicional.
Verifique o impacto real da sua sessão com /cost — o hit de cache aparece como linha separada. O envelope do chat também mostra N↑ M↓ na borda direita para qualquer provider que reporte usage, incluindo todas as APIs do OpenAI.

2. Detector de estagnação (early-exit)

Quando o modelo entra em reflection loop — emitindo exatamente a mesma batch de tool_calls turno após turno sem informação nova — o ChatCLI detecta e quebra o loop.

Como funciona

A cada turno, o fingerprint das tool_calls (nome + args normalizados, order-independent, SHA-256 truncado) é computado. Três turnos consecutivos com o mesmo fingerprint → o loop é encerrado com uma mensagem clara ao usuário.

Parâmetros

Fingerprint é order-independent: [read A, read B] e [read B, read A] produzem o mesmo hash, então reordenamentos cosméticos não enganam o detector.

3. Smart Routing chat ↔ agent

Nem toda query precisa de um loop ReAct inteiro. Perguntas conversacionais ou factuais (“o que é um mutex?”, “diferença entre slice e array”) são respondidas por um único turno em chat mode. O classificador identifica queries triviais usando sinais léxicos:
  • Palavras-chave de pergunta (o que, por que, how does, explain, …)
  • Ausência de sinais de task (create, build, run, fix, …)
  • Ausência de referências ao workspace (@file, @git, paths, extensões de código)
  • Tamanho curto + ponto de interrogação

Modos

/coder nunca é reroteado — esse modo existe para tarefas estruturadas. Mesmo perguntas aparentemente triviais ali são tratadas como pedido de trabalho.

Exemplo


4. WebFetch com auto-save inteligente

O @webfetch foi calibrado para nunca vomitar páginas gigantes no contexto. Veja também WebFetch & WebSearch para a documentação completa.

Escape hatch

O auto-save sempre persiste o body completo (pré-filtro) em $CHATCLI_AGENT_TMPDIR, e o retorno contém:
O agente tipicamente emite um read_file apontando para esse caminho com o start/end apropriado, pagando apenas pelas linhas que importam.

5. System prompts enxutos

Os prompts que acompanham cada modo foram condensados sem perda semântica — todas as regras originais permanecem, apenas redundância e exemplos repetidos foram removidos: Como esses prompts são cacheados no bloco core, modelos que suportam cache (Anthropic/Bedrock/OpenAI) enxergam a redução só no primeiro turno da sessão. Modelos sem cache ganham a economia em todo turno.

6. Compactação proativa de resultados de tool

Resultados de ferramentas antigas (file reads, search, git-diff, etc.) são progressivamente comprimidos na história para não inflar o payload. Veja Tool Result Management para os detalhes completos. Contabilidade honesta de budget: o gatilho de compactação agora pesa o payload inteiro da requisição, não só o texto das mensagens — argumentos de tool calls nativas, payloads de imagem (via estimador de tokens) e excesso de system-parts contam para o budget. Históricos pesados em tools ou visão disparam a compactação antes de estourar o cap de payload de um proxy. Em agent/coder há também um ciclo de vida de skills no mesmo boundary: blocos de skill mid-loop envelhecem para stubs compactos após CHATCLI_SKILL_AGE_TURNS turnos (recuperáveis via @recall, re-ativáveis após cooldown) — veja Skill Registry › Budget de injeção de skills. Os defaults são conservadores para proteger workflows multi-turnos com cross-references (refactors grandes, review sessions). Quem quiser ser mais agressivo pode ajustar:
Para sessões de chat/lookup onde rapidez e tokens baixos importam mais que recall de longo prazo, tente:

7. Memória pull-first (índice + recall)

Empurrar a memória inteira no system prompt a cada turno não escala: o custo cresce com o tamanho do store e é re-enviado em todo turno. A partir de agora o ChatCLI usa por padrão um modelo pull: injeta só um digest estável e deixa o agente puxar o detalhe sob demanda via a ferramenta @memory recall. Controle pela variável CHATCLI_MEMORY_MODE: O index é estável (não depende dos hints do turno, sem timestamp), então entra no prefixo cacheado (seção 1) e tem tamanho limitado independente do tamanho do store. O @memory recall usa o stack completo de recuperação (HyDE + busca vetorial por cosseno + extração de keywords), então o detalhe puxado tem a mesma qualidade do antigo push. O auto-recall proativo (CHATCLI_MEMORY_AUTORECALL, on por padrão) adiciona um bloco minúsculo de top-3 fatos guiado por hints a cada turno, mais uma linha Related (graph): apontando os vizinhos desses fatos no grafo de conhecimento — mas ele viaja na região final não-cacheada junto do contexto de relógio, nunca no digest estável, então o prefixo cacheado permanece byte-idêntico e nada da economia acima é devolvido. Veja Bootstrap e Memória para detalhes.

Impacto medido

Medição num store real com 500 fatos (MEMORY.md ~32KB, índice de fatos ~270KB): −87,7% no bloco de memória por turno — e, ao contrário do full (limitado pelo CHATCLI_MEMORY_RETRIEVAL_BUDGET), o índice não cresce conforme a memória cresce.
O chat também honra index: a exceção da ferramenta de memória do chat (CHATCLI_CHAT_MEMORY, on por padrão) é seu caminho de pull, então o chat tem a mesma economia de digest + recall de agent/coder. Só com essa exceção desabilitada o chat cai em full; off suprime a memória em todos os modos. O modo é exibido em /config memory.
No modo index o agente/coder não enxerga mais a memória inteira automaticamente — ele precisa chamar @memory recall. O índice dá o “mapa” (o que existe) para ele saber o que puxar. O bloco proativo de auto-recall (com sua linha Related (graph):) e a expansão do grafo em todo pull de @memory recall amortecem exatamente esse risco. Se ainda notar contexto faltando, volte para CHATCLI_MEMORY_MODE=full (a economia de cache da seção 1 continua valendo).

Como medir o impacto

Rode sua sessão normalmente e olhe o /cost ao final:
Os sinais que indicam que as otimizações estão ativas e funcionando:
  • Cache read > 0 e crescendo por turno → caching estruturado está casando o prefixo.
  • Poucos/nenhum FORMAT ERROR no log → reminders estão segurando o formato nos modelos menores.
  • Turnos com tool_calls = 0 seguidos de conclusão rápida → early-exit detectou convergência.
  • Marker [auto-saved: response was N bytes] em respostas de @webfetch → o limite inline está protegendo o contexto.

Resumo das variáveis

Todas as variáveis desta página em um só lugar:

Próximos passos