Visão geral dos pacotes
Struct principal: ChatCLI
A structChatCLI em cli/cli.go é o coração do sistema. Seus campos mais importantes estão organizados por responsabilidade:
LLM e Provider
Histórico e Compactação
Controle de Execução
Subsistemas
Estado Auxiliar
Fluxo de inicialização
O diagrama abaixo mostra a sequência completa de boot, demain.go até o loop interativo:
detectProjectDir()
Caminha do diretório atual até a raiz procurando marcadores de projeto:.agent/(marcador explícito do ChatCLI) — prioridade.git/(convencao comum)
"" se nenhum marcador for encontrado.
Fluxo de mensagens: modo chat
No modo interativo normal, cada mensagem do usuário segue este pipeline:Type-ahead (messageQueue)
Mensagens digitadas enquanto o LLM processa são armazenadas em uma fila FIFO (messageQueue). Após cada resposta, o sistema drena a fila e processa cada mensagem sequencialmente, sem necessidade de re-digitação. O modo coder tem sua própria fila com indicador visual em tempo real (▼ N msg fila) quando há mensagens enfileiradas.
Input guard (anti-typeahead em prompts de segurança)
Distinto do typeahead “bom” acima, quando uma security box aparece no meio de um turn — três camadas descartam input em queue para evitar consumir bytes comoy/n:
- TTY flush —
TCIFLUSH(Linux),TIOCFLUSH(BSD/Darwin),FlushConsoleInputBuffer(Windows). - Drain channel — esvazia o buffer non-blocking do reader.
- 250ms debounce — descarta input nos primeiros 250ms após a box renderizar.
atualize também o changelog) nunca responde ao prompt, mas também não é mais jogada fora: o drain a reenfileira e ela chega ao modelo na próxima fronteira de turno. Do drain, só continuam descartadas as linhas em forma de resposta de prompt (y, n, yes, no, sim, a, always, d, deny ou Enter vazio). O mesmo buffer de resgate mantém vivas as linhas na fila quando o leitor de stdin é suspenso (prompt de resposta final, overlay do @ask).
Adicionalmente, no início de cada turn do agente é feito stty sane no /dev/tty controlador para recuperar de um teardown anterior do go-prompt que pudesse ter deixado o terminal em raw mode.
Fluxo de mensagens: modo Agent/Coder
A entrada no modo agente usa um mecanismo depanic/recover para sair do loop do go-prompt:
Anchor Reminder
A cada turno, o agente injeta um lembrete curto no histórico para manter o LLM focado na tarefa original. Isso evita drift em conversas longas.Parsing de tool calls
O parser emcli/agent/toolcall_parser.go usa um scanner stateful (não regex) para robustez:
Formatos suportados
Algoritmo do scanner
- Busca
<tool_callcase-insensitive - Verifica que o próximo char é whitespace ou
>(não parte de outra tag) scanTagEnd(): avança respeitando aspas (single e double quotes)- Dentro de aspas,
>é tratado como texto literal - Suporta entidades HTML (
>,", etc.)
- Dentro de aspas,
- Extrai atributos
nameeargsindependente da ordem - Se self-closing falha, tenta paired tags com
</tool_call> - Fallback para JSON: tenta
parseJSONToolCalls()em paralelo
Por que não regex?
Argumentos de tools frequentemente contêm>, ", e JSON aninhado. Regex não consegue distinguir > dentro de um atributo quoted de > que fecha a tag. O scanner stateful resolve isso rastreando o estado de quote.
Pipeline de sanitização de argumentos
Após o parsing, cada tool call passa por um pipeline de 7 etapas emagent_tool_sanitizer.go:
Compactação de histórico (3 níveis)
OHistoryCompactor em cli/history_compactor.go gerencia o tamanho do histórico através de um pipeline progressivo:
Trigger no modo agente
No modo agente, a compactação é verificada a cada turno do loop ReAct. O trigger ocorre quando o uso de tokens ultrapassa 60% do budget do modelo.Sistema de checkpoint/rewind
Orewind.go implementa um sistema de snapshots do histórico:
Estrutura
Comportamento
- Quando salva: Antes de cada chamada ao LLM (
saveCheckpoint()) - Limite: Máximo de 20 checkpoints (FIFO — os mais antigos são descartados)
- Deep copy: Cada checkpoint contém uma cópia completa e independente do histórico
- Trigger: Pressionar Esc+Esc (dois Esc em menos de 500ms) abre o menu de rewind
- Restauração: O usuário escolhe um checkpoint, e o histórico é substituído pela cópia salva
Registry de provedores
Padrão de auto-registro
Cada provedor LLM se registra automaticamente viainit() no pacote llm/registry:
ProviderInfo
Fluxo de descoberta
switch/case. Para adicionar um novo provedor, basta criar o pacote com register.go e implementar a interface LLMClient.
Memory Worker (processo background)
OmemoryWorker em cli/memory_worker.go extrai memórias da conversa em background:
Parâmetros
Triggers
- nudge(): Chamado após cada resposta do LLM. Se houver >= 4 novas mensagens e cooldown expirado, executa extração
- Ticker de 3 minutos: O loop background verifica periodicamente (para sessões longas onde o usuário digita pouco)
- Compaction ticker (6h): Consolida fatos antigos com scores baixos
- Cleanup ticker (24h): Remove notas diárias expiradas
Pipeline de extracao
Componentes principais
CLI e Modos
A structChatCLI em cli/cli.go é o ponto central (~923 linhas após decomposição). O método Start() inicia o modo interativo usando Bubble Tea (Charmbracelet). Métodos auxiliares, gerenciamento de histórico, troca de modos, formatação de saída, construção de prompts, gerenciamento de sessões e tratamento de tools foram extraídos para arquivos dedicados (cli_helpers.go, cli_history.go, cli_mode.go, cli_output.go, cli_prompt.go, cli_session.go, cli_tools.go).
A troca entre modos usa um mecanismo de
panic/recover para sair do loop do go-prompt.
O ChatCLI usa um histórico unificado (cli.history) compartilhado entre todos os modos. Ao trocar de modo, o contexto completo é preservado. Os comandos /compact e /rewind operam diretamente sobre esse histórico único.
Message Bus
O pacotecli/bus implementa um barramento de mensagens tipado com:
- Pub/sub com filtros por canal e tipo
- Request-reply com correlation IDs
- Métricas atômicas de throughput
Multi-Agent
O sistema de orquestração emcli/agent/workers gerencia 12 agents especialistas que executam em goroutines paralelas com semáforo configurável. Cada worker possui:
- Mini ReAct loop isolado (observe -> reason -> act)
- Skills próprias (scripts aceleradores e descritivas)
- File locks (mutex per-filepath)
- Timeout e max turns configuráveis
Tecnologias
Padrões de design
- Auto-registro via
init()— Provedores se registram automaticamente - Interface-driven —
LLMClienteToolAwareClientpara polimorfismo - Fallback chain — Classificacao inteligente de erros + cooldown exponencial
- Stateful parser — Parsing de XML com atributos escapados (mais robusto que regex)
- embed.FS — Arquivos i18n embarcados no binário (sempre usa
/, nuncafilepath.Join) - Panic/recover — Troca de modos no go-prompt sem reiniciar o processo
- Histórico unificado — Um único array de mensagens compartilhado entre chat, agent e coder
- Checkpoint/rewind — Deep copy do histórico antes de cada chamada LLM, com restauração seletiva
- Compactação em 3 níveis — Trimming near-lossless, sumarização estruturada, truncamento de emergência
- Type-ahead queue — Mensagens digitadas durante processamento são enfileiradas e drenadas automaticamente
- Workspace context injection — Bootstrap + memória injetados automaticamente em todo system prompt
- Context injection via system prompt — Contextos attached (
/context attach) são injetados comoSystemPartscomCacheControlno modelo de mensagem, permitindo cache automático por provider (Anthropiccache_control: ephemeral, OpenAI prompt caching, Google context caching) - Background memory extraction — Worker em goroutine extrai e consolida memórias da conversa sem bloquear o fluxo principal