Pular para o conteúdo principal
Quando o dispatcher executa múltiplos agents em paralelo, o terminal exibe um painel de progresso em tempo real que mostra o estado de cada agent individualmente. Isso elimina a incerteza de “sera que ainda está rodando?” sem precisar consultar logs.

O Que Você Ve

Durante a execução paralela, o terminal renderiza um display multi-linha atualizado a cada 100ms:
⋮ [claude-sonnet-4-6] [14s] [████████░░░░░░░░░░░░] 2/4 agents (50%)
  ✓ [file]   Read all .go files in pkg/coder/engine/ ─ concluido (2.1s)
  ⋯ [search] Find references to handleRead and... ─ executando...
  ✗ [shell]  Run lint on modified files ─ falhou (1.3s) exit code 1
  ○ [coder]  Refactor read/write separation ─ pendente

Elementos do Display

ElementoDescrição
Spinner (⋮ ⋯ ⋰ ⋱)Animacao rotativa confirmando que o processo está ativo
[modelo]Provedor/modelo LLM em uso pelos workers
[tempo]Tempo total decorrido desde o início do dispatch
Barra de progressoRepresentacao visual da porcentagem concluida
N/M agents (X%)Contagem e porcentagem de agents finalizados

Icones de Status por Agent

IconeStatusDescrição
PendenteAgent aguardando slot no semaforo (max workers atingido)
ExecutandoAgent em execução ativa (spinner animado)
ConcluidoAgent finalizou com sucesso (mostra duracao)
FalhouAgent finalizou com erro (mostra duracao + mensagem)

Arquitetura Interna

O sistema de progresso live usa 3 goroutines independentes comunicando via estado compartilhado protegido por mutex:
┌─────────────────────────┐
│  Worker Goroutines (N×) │   Goroutines do dispatcher
│  executeAgent()         │   Cada agent roda isolado
│                         │
│  Ao iniciar/terminar    │──── envia AgentEvent ────┐
└─────────────────────────┘                          │

                                        ┌────────────────────────┐
                                        │  Goroutine Consumer    │
                                        │  for evt := range      │
                                        │      progressCh        │
                                        │                        │
                                        │  MarkStarted()         │
                                        │  MarkCompleted()       │──── escreve ────┐
                                        │  MarkFailed()          │   (com mutex)   │
                                        └────────────────────────┘                 │

                                                                   ┌──────────────────────┐
┌─────────────────────────┐                                        │  AgentProgressState  │
│  Timer Goroutine        │                                        │  (thread-safe)       │
│  Ticker a cada 100ms    │──── le estado ────────────────────────►│                      │
│                         │     e renderiza                        │  Agents[0]: Running  │
│  FormatDispatchProgress │◄──── snapshot com mutex ───────────────│  Agents[1]: Complete │
│  ()                     │                                        │  Agents[2]: Failed   │
└─────────────────────────┘                                        │  Agents[3]: Pending  │
                                                                   └──────────────────────┘

Fluxo Detalhado

1

Inicializacao

O agent_mode.go cria um AgentProgressState com N slots (um por agent) e um channel progressCh com buffer N×2.
2

Goroutine Consumer

Uma goroutine dedicada consome eventos do progressCh e atualiza o estado compartilhado via métodos Mark*() protegidos por mutex.
3

Timer Display

O turnTimer inicia com um callback que, a cada 100ms:
  1. Limpa as linhas anteriores do terminal (ClearLines)
  2. Le o AgentProgressState (adquirindo o mutex)
  3. Renderiza o display multi-linha atualizado
4

Dispatch com Eventos

O DispatchWithProgress() executa os agents e envia AgentEvent no channel conforme cada agent inicia e termina.
5

Finalizacao

Após todos os agents terminarem, o timer para, o display e limpo, e os resultados finais são renderizados como cards do timeline.

Tipos de Evento

O dispatcher emite tres tipos de evento via o channel de progresso:
type AgentEventType int

const (
    AgentEventStarted   AgentEventType = iota  // agent comecou a executar
    AgentEventCompleted                        // agent finalizou com sucesso
    AgentEventFailed                           // agent finalizou com erro
)
Cada evento carrega contexto completo:
type AgentEvent struct {
    Type     AgentEventType
    CallID   string         // ID único do agent call (ac-1, ac-2...)
    Agent    AgentType      // tipo do agent (file, coder, shell...)
    Task     string         // descrição da tarefa
    Duration time.Duration  // tempo de execução (apenas em Completed/Failed)
    Error    error          // erro (apenas em Failed)
    Index    int            // posicao no batch (0-based)
    Total    int            // total de agents no batch
}

Thread Safety

O AgentProgressState e acessado concorrentemente por duas goroutines:
  1. Consumer goroutine — escreve (via MarkStarted, MarkCompleted, MarkFailed)
  2. Timer goroutine — le (via FormatDispatchProgress)
A sincronizacao e feita por um sync.Mutex interno:
type AgentProgressState struct {
    mu        sync.Mutex
    Total     int
    Agents    []AgentSlot
    StartTime time.Time
}
Cada método publico adquire o mutex antes de ler ou escrever. O FormatDispatchProgress também adquire o mutex e faz um snapshot completo do estado antes de formatar a string de saida.
O channel progressCh usa buffer de N×2 (onde N = número de agents) para evitar que workers bloqueiem ao enviar eventos caso o consumer esteja momentaneamente ocupado.

Renderizacao do Terminal

O display multi-linha usa escape sequences ANSI para atualizar o terminal in-place:
OperaçãoEscape SequenceDescrição
Limpar linha\r\033[KRetorna ao início e apaga a linha
Subir N linhas\033[A\033[K (×N)Move cursor para cima e limpa cada linha
A cada tick do timer (100ms), o callback:
  1. Emite ClearLines(prevLines) para apagar o display anterior
  2. Chama FormatDispatchProgress() que retorna a string multi-linha atualizada
  3. Imprime a nova string
  4. Atualiza prevLines para o próximo ciclo
A latência máxima entre um evento de mudanca e sua exibicao no terminal e de 100ms — imperceptivel para o usuário.

Interação com Policy Prompts

Quando um worker precisa de aprovação de segurança (policy “ask”), o sistema:
  1. Pausa o timer — o display de progresso para de atualizar
  2. Exibe o prompt de segurança — com contexto do agent
  3. Após resposta — resume o timer e o display continua atualizando
Isso evita que o spinner e o prompt de segurança se sobreponham no terminal.

Spinner labels per-call (DescribeCall)

A linha do spinner usa o método DescribeCall(args) de cada plugin quando disponível, surfaceando o alvo concreto da operação em vez de uma label genérica. Comparação:
PluginAntesDepois
@readEXECUTANDO: @read readReading: main.go
@searchEXECUTANDO: @search searchSearching: golang errgroup
@webfetchEXECUTANDO: @webfetch fetchFetching: https://api.example.com/...
@coder execEXECUTANDO: @coder execExecuting: go test ./...
Plugins legados que não implementam DescriberWithInput continuam mostrando o fallback EXECUTANDO: <tool> <subcmd> (locale-resolved, vira RUNNING: em en). Os tools de ação/multimodais (@send, @moa, @osv, @session, @speak, @image, @skill) implementam DescribeCall, então a caixa mostra um label conciso (ex.: 🎨 Gerando imagem: a watercolor fox) em vez da descrição longa do tool.

Spinner animado durante a execução

Tools que esperam I/O de rede (@moa, @image, @speak, @webfetch, @websearch, @osv, @send…) exibiam uma caixa estática — sensação de travado. Agora o loop do agente roda o spinner braille animado (⠋⠙⠹…) enquanto o tool executa, com o label do DescribeCall. Comportamento:
  • Tool bloqueante (rede): o spinner anima até o retorno.
  • Tool que faz stream (ex.: @coder exec): o spinner para na primeira linha de saída e o output flui normal (sem conflito com o repaint por carriage-return).
  • Fora de TTY (gateway/daemon), o spinner é suprimido automaticamente.

Indicador de fila de mensagens (mid-turn)

Durante o turn do agente o spinner exibe (N na fila) quando há mensagens enfileiradas pelo usuário. O contador soma:
  • Mensagens completadas (Enter pressionado) ainda não drenadas pro messageQueue
  • Mensagens que já caíram no messageQueue aguardando o próximo turn
Resultado: pressionar Enter durante o stream do LLM atualiza o indicador imediatamente em vez de esperar o turn fechar.
⠋ claude-opus [1m12s] | Processando... (1 na fila)
Funciona tanto em /agent quanto em /coder (antes só funcionava em /agent).

Ciclo de Vida Completo

[Antes do dispatch]
╭── 🚀 MULTI-AGENT DISPATCH
│  Dispatching 4 agents
╰────────────────────────

╭── 🤖 [file] #1
│  Read all .go files in pkg/coder/engine/
╰────────────────────────

╭── 🤖 [search] #2
│  Find references to handleRead and handleWrite
╰────────────────────────

[Durante o dispatch — atualizado a cada 100ms]
⋮ [claude-sonnet-4-6] [3s] [░░░░░░░░░░░░░░░░░░░░] 0/4 agents (0%)
  ⋯ [file]   Read all .go files in pkg/coder/... ─ executando...
  ⋯ [search] Find references to handleRead... ─ executando...
  ○ [shell]  Run lint on modified files ─ pendente
  ○ [coder]  Refactor read/write separation ─ pendente

... 5 segundos depois ...

⋰ [claude-sonnet-4-6] [8s] [██████████░░░░░░░░░░] 2/4 agents (50%)
  ✓ [file]   Read all .go files in pkg/coder/... ─ concluido (2.1s)
  ✓ [search] Find references to handleRead... ─ concluido (4.8s)
  ⋯ [shell]  Run lint on modified files ─ executando...
  ⋯ [coder]  Refactor read/write separation ─ executando...

... finalizado ...

[Após o dispatch — cards de resultado]
╭── ✅ [file] OK (2.1s, 3 tool calls)
│  Files read: engine.go, reader.go, writer.go
╰────────────────────────

╭── ✅ [search] OK (4.8s, 5 tool calls)
│  Found 12 references across 4 files
╰────────────────────────

╭── ❌ [shell] FAILED
│  exit code 1: unused variable 'tmp' in engine.go:42
╰────────────────────────

╭── ✅ [coder] OK (12.3s, 8 tool calls, 3 em paralelo)
│  Refactored read/write into separate files
╰────────────────────────

╭── 📊 RESUMO
│  3/4 agents concluidos | 16 tool calls executadas | 3 goroutines paralelas | 14.1s total
╰────────────────────────

Componentes de Código

ComponenteArquivoResponsabilidade
AgentEvent / AgentEventTypecli/agent/workers/types.goTipos de evento de progresso
DispatchWithProgress()cli/agent/workers/dispatcher.goDispatcher que emite eventos via channel
AgentProgressStatecli/metrics/display.goEstado thread-safe de cada agent
FormatDispatchProgress()cli/metrics/display.goRenderizacao do display multi-linha
ClearLines()cli/metrics/display.goLimpeza de linhas do terminal
Integraçãocli/agent_mode.goOrquestracao do consumer + timer + dispatch

Comparacao: Antes vs Depois

⋮ [claude-sonnet-4-6] [32s] Aguardando 4 agents...

(Nenhuma informação sobre:
 - Qual agent está rodando
 - Qual já terminou
 - Se houve erro
 - Quanto falta)
O usuário só sabia que algo estava acontecendo pelo spinner girando. Para confirmar que os agents estavam de fato rodando, precisava abrir os logs do ChatCLI em outro terminal.