Pular para o conteúdo principal
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 Assistant, 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):
BlocoConteúdoPor que é estável
CorePersona + regras de formato (Coder/Agent) + hint de idiomaNão muda entre turnos
ToolsDescrições de plugins + hint do workspaceMuda só quando um plugin é carregado/descarregado
OrchestratorCatálogo do orquestrador multi-agenteEstável dentro da sessão
Índice de memóriaDigest compacto da memória (modo index)Não depende do turno — ver seção 7
Sufixo volátil (sem cache marker — muda a cada turno):
BlocoConteúdoPor que é volátil
Memória (full)Recuperação hint-driven de MEMORY.md (modo full)Varia com os hints do turno
SkillsSkills auto-ativadas pela queryDepende da pergunta
MCP channelMensagens push recentes dos servidores MCPAtualiza a cada turno
Contexto dinâmicoData/hora + diretório atualMuda por definição
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; como é tool-less, ele não puxa memória sob demanda (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

ProviderCampo populado em UsageInfo
Anthropic / Bedrock AnthropicCacheReadInputTokens, CacheCreationInputTokens
OpenAI Chat Completions (auto-caching)CacheReadInputTokens (via prompt_tokens_details.cached_tokens)
OpenAI Responses API (auto-caching)CacheReadInputTokens (via input_tokens_details.cached_tokens)
OpenAI reasoning models (o-series / GPT-5)ReasoningTokens (via *_tokens_details.reasoning_tokens) — informativo, já contabilizado em CompletionTokens
Demais providersNão reportado — mas prefixo estável ainda beneficia cache interno
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

VariávelDefaultDescrição
CHATCLI_AGENT_EARLY_EXIT1 (on)Liga/desliga o detector. 0/false/off desativa.
CHATCLI_AGENT_EARLY_EXIT_TURNS3Número de repetições consecutivas para acionar o break (clamp [2, 10]).
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

Valor de CHATCLI_AGENT_SMART_ROUTEComportamento
off | 0 | false | noDesliga completamente. /agent e /run sempre entram no loop.
hint (default) | 1 | on | trueDetecta e imprime uma dica no terminal, mas respeita a intenção do usuário e entra no loop mesmo assim.
auto | redirect | 2Auto-redireciona queries triviais para chat mode. Máxima economia; pode surpreender em casos limítrofes.
/coder nunca é reroteado — esse modo existe para tarefas estruturadas. Mesmo perguntas aparentemente triviais ali são tratadas como pedido de trabalho.

Exemplo

$ /agent "o que é um canal em Go?"
 Dica: essa pergunta parece conversacional o loop /agent foi ignorado.
    Use /chat ou digite a pergunta direto para forçar chat, ou /run para forçar o agente.

# Com CHATCLI_AGENT_SMART_ROUTE=auto, a pergunta vai direto ao chat.
# Com o default (hint), você vê a dica mas o agente roda normalmente.

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.
ParâmetroAntesAgora
max_length default50.000 chars20.000 chars
Auto-save quando body > 10KB sem filtronãosim — salva no scratch dir e retorna preview compacto

Escape hatch

VariávelDescrição
CHATCLI_WEBFETCH_AUTOSAVE_BYTESThreshold em bytes para o auto-save disparar. Default: 10000.
O auto-save sempre persiste o body completo (pré-filtro) em $CHATCLI_AGENT_TMPDIR, e o retorno contém:
[auto-saved: response was 142318 bytes — too large to inline.
 Full body is at /tmp/chatcli-agent-.../webfetch_1712....txt.
 Preview below; use read_file with start/end or rerun with
 filter/from_line/to_line for specific ranges.]

[primeiros ~5000 chars do texto extraído]
...(auto-truncated — full body saved to disk)
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:
PromptTamanho antesTamanho agoraRedução
CoderSystemPrompt~1.647 tokens~1.000 tokens~40%
CoderFormatInstructions~560 tokens~390 tokens~30%
AgentFormatInstructions~324 tokens~230 tokens~30%
OrchestratorSystemPrompt~2.111 tokens~1.050 tokens~50%
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. Os defaults são conservadores para proteger workflows multi-turnos com cross-references (refactors grandes, review sessions). Quem quiser ser mais agressivo pode ajustar:
VariávelDefaultDescrição
CHATCLI_MICROCOMPACT_TRUNCATE_TURNS2Após quantos turnos os tool results antigos são truncados para head+tail preview.
CHATCLI_MICROCOMPACT_SUMMARIZE_TURNS4Após quantos turnos os tool results são substituídos por uma linha de resumo.
CHATCLI_MICROCOMPACT_HEAD_CHARS2000Tamanho do head mantido na truncagem.
CHATCLI_MICROCOMPACT_TAIL_CHARS500Tamanho da cauda mantida na truncagem.
CHATCLI_MICROCOMPACT_MIN_CONTENT3000Tamanho mínimo de tool result para virar candidato à compactação.
Para sessões de chat/lookup onde rapidez e tokens baixos importam mais que recall de longo prazo, tente:
export CHATCLI_MICROCOMPACT_TRUNCATE_TURNS=1
export CHATCLI_MICROCOMPACT_SUMMARIZE_TURNS=3
export CHATCLI_MICROCOMPACT_HEAD_CHARS=1200
export CHATCLI_MICROCOMPACT_TAIL_CHARS=300
export CHATCLI_MICROCOMPACT_MIN_CONTENT=2000

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:
ModoComportamentoQuando usar
index (default)Injeta um índice compacto e estável (resumo do perfil + nomes dos top topics/projects + contagem de fatos por categoria) e uma diretiva para o agente chamar @memory recall quando precisar de detalhe.Default. Custo por turno limitado mesmo com a memória crescendo.
fullInjeta a recuperação hint-driven completa todo turno (comportamento anterior).Quando você quer que o agente sempre veja a memória relevante sem depender de ele puxar.
offNão injeta memória (bootstrap continua valendo).Sessões onde a memória de longo prazo só atrapalha.
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.

Impacto medido

Medição num store real com 500 fatos (MEMORY.md ~32KB, índice de fatos ~270KB):
Por turno (agent/coder)chars~tokens
Push (full)3.946~986
Índice (index)486~121
−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.
Chat é tool-less por design e não pode puxar sob demanda: lá index cai automaticamente em full, e só off suprime a memória. 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. Se notar o agente perdendo contexto que deveria recordar, 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:
Session cost summary
  Provider: CLAUDEAI | Model: claude-sonnet-4-6
  ─────────────────────────────────────────────
  Input tokens:       12_345
  Output tokens:       3_210
  Cache read:         87_650   ← ideal: crescendo a cada turno
  Cache creation:      4_100
  ─────────────────────────────────────────────
  Total cost: $0.0234
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:
VariávelDefaultDesliga com
CHATCLI_MEMORY_MODEindexfull (push) / off
CHATCLI_AGENT_EARLY_EXIT1 (on)0 / false / off / no
CHATCLI_AGENT_EARLY_EXIT_TURNS3— (clamp [2, 10])
CHATCLI_AGENT_SMART_ROUTEhintoff / 0 / false / no
CHATCLI_WEBFETCH_AUTOSAVE_BYTES10000setar valor muito alto
CHATCLI_MICROCOMPACT_TRUNCATE_TURNS2valor alto (ex: 100)
CHATCLI_MICROCOMPACT_SUMMARIZE_TURNS4valor alto
CHATCLI_MICROCOMPACT_HEAD_CHARS2000valor alto
CHATCLI_MICROCOMPACT_TAIL_CHARS500valor alto
CHATCLI_MICROCOMPACT_MIN_CONTENT3000valor muito alto

Próximos passos