Arquitetura
- Adapter — integração de uma plataforma. Recebe mensagens e envia respostas usando a API HTTP nativa da plataforma (sem SDKs de terceiros no
go.mod). - Runner — roteia mensagens por conversa (
plataforma:chat), com concorrência limitada e shutdown gracioso. Instala um emissor de progresso por mensagem. - Agent loop — cada mensagem vira uma tarefa do
/agent(auto-execução). A narração que o agente já imprime é capturada e transmitida ao chat.
Streaming “ir falando” e throttling
Enquanto o agente trabalha, suas linhas de saída substantivas são coalescidas e enviadas como mensagens de progresso periódicas (no máximo uma a cada poucos segundos, para respeitar os limites de taxa por chat das plataformas). Linhas puramente decorativas (molduras, spinners) são filtradas. Ao terminar, o gateway envia um aviso de conclusão.Indicador “a IA está processando”
Assim que uma mensagem chega, o gateway sinaliza que recebeu e está trabalhando — para você não ficar sem saber se a mensagem chegou em tarefas que demoram um pouco mais:- Canais com indicador nativo (Telegram): mostra o “digitando…” nativo (
sendChatAction), renovado automaticamente antes de expirar enquanto o agente trabalha — sem poluir o chat com mensagens. - Demais canais: envia uma notícia curta (”🤔 …”, localizada via
gateway.thinking) após um pequeno atraso, e só se a resposta ainda não saiu — respostas rápidas não geram aviso nenhum.
Concorrência
Como o agent loop muta estado compartilhado do ChatCLI e oos.Stdout é global, as execuções de agente são serializadas: mensagens chegam em paralelo (até 4 em buffer) mas rodam uma de cada vez. Cada conversa mantém um contexto leve (as últimas requisições do usuário) para dar continuidade entre turnos; o estado durável de verdade vive nos arquivos do workspace que o agente edita.
Modelo em runtime
O gateway espelha o modelo (e o provider) que a sua sessão interativa está usando — não o default do.env. Trocar de modelo ou de provider na REPL com /switch, /model ou /max-tokens propaga para o daemon: ele relê a escolha antes de cada mensagem, então uma conversa em andamento no Telegram passa a responder com o novo modelo sem reiniciar o gateway.
Como o daemon roda em um processo separado, a sincronização passa por um pequeno arquivo de estado em ~/.chatcli/runtime_model.json que a sessão interativa escreve e o daemon lê. Isso resolve os dois casos: subir o gateway depois de trocar o modelo, e trocar com o gateway já rodando.
Ao trocar de provider, o daemon adota o provider novo já com o modelo correto dele — desde que as credenciais desse provider estejam no ambiente que o daemon herdou (normalmente o seu
.env). Ajustes que vivem só em memória, como /switch --realm / --agent-id do StackSpot, não propagam por esse arquivo; defina-os via variável de ambiente ou reinicie o gateway.Respostas conversacionais (não “tom de coder”)
O gateway usa o mesmo motor do/coder — todas as ferramentas (ler/editar arquivos, shell, web, MCP) continuam disponíveis — mas com uma voz própria, conversacional. A resposta final é a mensagem que a pessoa lê no chat, não um resumo técnico de commit: texto direto e natural, sem tabelas, banners, ASCII art ou blocos de código longos (a menos que peçam código). Internamente isso é um system prompt dedicado do gateway, aplicado no lugar do prompt de coder, preservando a mecânica de tool-use.
Idioma dinâmico (segue quem fala)
A resposta sai no idioma da mensagem do usuário, detectado a cada turno — e não preso à locale do daemon. Português → responde em português; espanhol → espanhol; e assim por diante. A diretiva de idioma dinâmica é aplicada em todos os caminhos do gateway (inclusive com persona ativa), então a resposta nunca fica estática num idioma só. No CLI interativo, a diretiva fixa por locale (CHATCLI_LANG) continua valendo — quem muda é só o gateway.
Ativação
O gateway roda como daemon desacoplado:/gateway start re-executa o binário como um processo em background (chatcli gateway, com stdout e log próprios) e devolve a REPL na hora — você continua usando o chat normalmente. O daemon é rastreado por um pidfile em ~/.chatcli/gateway.pid e segue vivo até você pará-lo (não há Ctrl+C; use /gateway stop).
~/.chatcli/gateway.log (o caminho é exibido no start). Cada adaptador só liga quando suas variáveis obrigatórias estão presentes — defina apenas os canais que quiser usar. Referência completa em Variáveis de Ambiente → Chat Gateway.
- Telegram
- Slack
- Discord
- WhatsApp
- Webhook
Long-polling via
getUpdates (sem servidor HTTP exposto).Exemplo de uso
111111111 manda:
“liste os arquivos Go alterados no último commit e resuma o diff”O bot responde com mensagens de progresso conforme o agente roda
git, lê arquivos e raciocina, e fecha com ✅ Tarefa concluída.
Quem não estiver na allow-list é ignorado. Slack e o webhook genérico verificam assinatura/secret antes de processar.
Plataformas suportadas
| Plataforma | Transporte | Obrigatórias | Auth |
|---|---|---|---|
| Telegram | long-polling | CHATCLI_TELEGRAM_BOT_TOKEN | allow-list de user IDs |
| Slack | Events API (HTTP) | ..._BOT_TOKEN, ..._ADDR | signing secret (HMAC) |
| Discord | Gateway WebSocket v10 | CHATCLI_DISCORD_BOT_TOKEN | token do bot |
| Cloud API (webhook) | ..._ACCESS_TOKEN, ..._PHONE_ID, ..._ADDR | verify token | |
| Webhook | HTTP genérico | CHATCLI_WEBHOOK_ADDR | secret (constant-time) |
Mensagens de voz (transcrição)
O gateway aceita áudio / notas de voz em todos os canais. A mensagem é transcrita para texto antes do pipeline — então funciona com qualquer um dos 14 providers de chat (eles só veem texto; não exige modelo multimodal nem redesenho de mensagem). O adapter baixa a mídia, transcreve, e o agente trata como um pedido em texto normal — a transcrição é inclusive gravada no Conversation Hub.| Canal | Origem da voz |
|---|---|
| Telegram | nota de voz / áudio (getFile → download) |
| mensagem de áudio (lookup da Graph media API) | |
| Discord | anexo audio/* (CDN) |
| Slack | arquivo audio/* (url_private, com bearer token) |
| Webhook | audio_b64 (inline base64) ou audio_url |
Backend de transcrição (zero-config, local-first, keyless)
A seleção segue local/sem-chave primeiro — e desde a v1.135 tem um piso embutido: sem nada configurado, o gateway usa o Whisper embarcado (multilíngue, via sherpa-onnx — o mesmo engine do TTS Kokoro), sem API key e sem cgo. O daemon pré-baixa engine + modelo no startup, então a primeira nota de voz já chega com tudo pronto.CHATCLI_TRANSCRIPTION_CMD— um comando STT local seu (qualquer wrapper). Lê o transcript do stdout, ou do.txtescrito em{output_dir}.CHATCLI_TRANSCRIPTION_URL— endpoint OpenAI-compatível self-hosted (whisper.cppwhisper-server, faster-whisper, Speaches). Keyless (a menos queCHATCLI_TRANSCRIPTION_KEY).- Whisper embarcado já provisionado — se o cache (
~/.cache/chatcli/stt/) já tem engine + modelo, ele vence qualquer chave de cloud. - whisper CLI no PATH — se houver
whisper(openai-whisper) ouwhisper-cli(whisper.cpp), é usado automaticamente, com zero config. O modelo ggml é baixado uma vez para o cache (~/.cache/chatcli/whisper/), como o faster-whisper faz. GROQ_API_KEY→ Groq Whisper (free tier).OPENAI_API_KEY→ OpenAI Whisper.- nada configurado → Whisper embarcado: download único (engine ~25MB + modelo
base~200MB) na subida do daemon. Só plataformas sem engine prebuilt (fora de Linux/macOS/Windows x64/arm64) caem na dica de configuração.
CHATCLI_TRANSCRIPTION_PROVIDER fixa um backend (embedded|command|url|groq|openai) — =embedded força o motor embarcado mesmo com whisper/keys presentes. CHATCLI_TRANSCRIPTION_MODEL escolhe o tamanho do modelo embarcado (tiny|base|small|medium|large-v3, default base) ou o modelo cloud; _LANG fixa o idioma (default: auto-detecção do idioma falado); CHATCLI_TRANSCRIPTION_CACHE_DIR realoca o cache (path absoluto — útil para pré-seed air-gapped); CHATCLI_GATEWAY_MAX_AUDIO_BYTES limita o tamanho do download (default 20MB). O backend ativo aparece em /config integrations.
Setup rápido
Zero-config (Whisper embarcado — recomendado):/gateway stop && /gateway start) e mande um áudio.
Respostas em voz
O caminho de volta também fala: por padrão (CHATCLI_GATEWAY_VOICE_REPLY=auto) o gateway responde voz com voz — mensagem de áudio recebe voice note, texto recebe texto — com qualquer backend TTS, incluindo o motor embarcado Kokoro (offline, sem API key). Cada conversa liga/desliga pedindo em linguagem natural (“responde em áudio” / “para de mandar áudio”) via tool @voice, com a preferência persistida por sessão. A resposta falada é escrita para o ouvido (sem emojis, listas ou markdown no áudio) e wav/aiff é transcodado para OGG/Opus quando há ffmpeg, virando voice note nativa no Telegram. Detalhes, modos e troubleshooting em Respostas em Voz.
O gateway também trata o índice de memória do usuário como conhecimento real: perguntas pessoais (“o que você sabe sobre mim?”) consultam a memória persistente via @memory recall antes de qualquer “não sei”.
Continuidade cross-channel
Quando o Conversation Hub está ativo (padrão), o gateway compartilha a conversa com o chatcli do seu notebook: um assunto começado no Telegram continua no terminal e vice-versa. Cada mensagem recebida resolve o principal do remetente, lê o contexto recente e grava o turno no hub — então o que você falou no notebook aparece como contexto no Telegram, sem configurar nada (modo single-user). Para push em tempo real ao CLI conectado, rode o gateway dentro do servidor comCHATCLI_GATEWAY_IN_SERVER=true. Bots multi-usuário usam CHATCLI_HUB_ISOLATE=true + bindings. Detalhes em Conversation Hub.
Veja também
- Conversation Hub — continuidade da conversa entre canais e o notebook
- Modo Agente — o loop que executa cada mensagem
- Segurança — endurecer a auto-execução
- Variáveis de Ambiente