Skip to main content
O ChatCLI é um projeto Go modular organizado em pacotes com responsabilidades bem definidas. Após a Fase 3 de decomposição de monolitos, todos os arquivos principais foram divididos em módulos focados seguindo princípios SOLID. Esta página documenta a arquitetura interna para contribuidores e usuários avançados.

Visão geral dos pacotes


Struct principal: ChatCLI

A struct ChatCLI 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, de main.go até o loop interativo:

detectProjectDir()

Caminha do diretório atual até a raiz procurando marcadores de projeto:
  1. .agent/ (marcador explícito do ChatCLI) — prioridade
  2. .git/ (convencao comum)
Retorna o caminho do projeto ou "" 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 como y/n:
  • TTY flushTCIFLUSH (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.
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 de panic/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 em cli/agent/toolcall_parser.go usa um scanner stateful (não regex) para robustez:

Formatos suportados

Algoritmo do scanner

  1. Busca <tool_call case-insensitive
  2. Verifica que o próximo char é whitespace ou > (não parte de outra tag)
  3. scanTagEnd(): avança respeitando aspas (single e double quotes)
    • Dentro de aspas, > é tratado como texto literal
    • Suporta entidades HTML (&gt;, &quot;, etc.)
  4. Extrai atributos name e args independente da ordem
  5. Se self-closing falha, tenta paired tags com </tool_call>
  6. 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 em agent_tool_sanitizer.go:

Compactação de histórico (3 níveis)

O HistoryCompactor 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

O rewind.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 via init() no pacote llm/registry:

ProviderInfo

Fluxo de descoberta

Este padrão elimina blocos switch/case. Para adicionar um novo provedor, basta criar o pacote com register.go e implementar a interface LLMClient.

Memory Worker (processo background)

O memoryWorker em cli/memory_worker.go extrai memórias da conversa em background:

Parâmetros

Triggers

  1. nudge(): Chamado após cada resposta do LLM. Se houver >= 4 novas mensagens e cooldown expirado, executa extração
  2. Ticker de 3 minutos: O loop background verifica periodicamente (para sessões longas onde o usuário digita pouco)
  3. Compaction ticker (6h): Consolida fatos antigos com scores baixos
  4. Cleanup ticker (24h): Remove notas diárias expiradas

Pipeline de extracao


Componentes principais

CLI e Modos

A struct ChatCLI 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 pacote cli/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 em cli/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-drivenLLMClient e ToolAwareClient para 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 /, nunca filepath.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 como SystemParts com CacheControl no modelo de mensagem, permitindo cache automático por provider (Anthropic cache_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