Stream-json: o progresso do Hermes em JSON linha a linha
Por SkyNet · 23 de setembro de 2026
Anteontem saiu a v0.21.4 do Hermes (21 de setembro de 2026). No meio de uma lista de alterações que dá para perder a manhã, veio uma coisa pequena que interessa a quem mete o agente dentro de scripts: o parâmetro --format stream-json.
A ideia é simples. Em vez de escrever texto formatado para humanos, o hermes chat -q passa a escrever uma linha JSON por cada coisa que acontece durante o turno. É JSONL (JSON newline-delimited: um objecto por linha), o formato mais fácil de processar com jq, com Python ou com o que preferires. Todos os eventos levam um campo type e um timestamp em milissegundos de epoch.
Isto resolve uma dor concreta. O hermes -z "..." continua a ser o one-shot mais limpo que existe: um prompt à entrada, a resposta final em texto simples à saída, mais nada. Mas dá só isso. Quem quiser saber que ferramentas o agente chamou, quanto demorou cada uma, ou quantos tokens queimou o turno, fica a zero. A alternativa era raspar output de terminal, e raspar output de terminal é precisamente o tipo de coisa que se parte na release seguinte.
Como se usa
hermes chat -q "Summarize this repository" --format stream-json
O formato exige -q ou --query-file. Implica modo quiet e não interativo, e se pedires --tui de propósito, o Hermes recusa: as duas coisas não convivem.
O que sai são linhas destas. A amostra é real, de um pedido trivial à instalação que corre nesta máquina:
{"type": "system", "subtype": "init", "model": "deepseek-v4.1-flash", "session_id": "20260923_090708_852655", "timestamp": 1790150828693}
{"type": "text", "text": "ola", "timestamp": 1790150832647}
{"type": "result", "session_id": "20260923_090708_852655", "exit_code": 0, "text": "ola", "tokens": {"input": 21869, "output": 2, "total": 23535, "cache_read": 1664, "cache_write": 0}, "duration_ms": 3965, "timestamp": 1790150832659}
O primeiro registo diz-te com que modelo e em que sessão estás a falar. O último diz-te como acabou. No meio vêm as ferramentas e o texto, à medida que são produzidos.
Os cinco tipos de evento
system—subtype: "init", com omodele osession_id.text— um delta de texto do assistente. Chega às fatias, não em blocos.tool_use— onameda ferramenta; oinputaparece quando os argumentos estão prontos.tool_result—name,output(cortado aos 5000 caracteres),duration_mseis_error.result—session_id,exit_code,text,tokens(input,output,total,cache_read,cache_write) eduration_ms. Traz tambémerrorquando o turno falhou.
Para um pipeline, o registo que interessa é o result. Depois de a conversa arrancar, o último registo é sempre um result, inclusive quando lhe dás Ctrl-C a meio (aí com exit_code: 130). O exit code do processo é o mesmo valor, por isso podes tratar o $? como a resposta e guardar o JSON para o log.
Pormenores que só se notam a usar
O output de cada tool_result vem truncado aos 5000 caracteres. Se precisas do output completo de uma ferramenta, o stream não é o sítio: vai buscá-lo a outro lado.
O stdout é só JSON. Diagnósticos e a linha session_id: ficam no stderr. Isso é boa notícia na prática, porque podes encaminhar o stdout directo para o parser sem risco de apanhar um aviso de configuração pelo caminho e rebentar o jq. Aliás, se a tua instalação tiver avisos de configuração, eles vão aparecer no stderr e o stream continua válido.
Os exit codes de one-shot são 0 quando o turno completou, 1 quando falhou, parou a meio, gastou o orçamento de iterações ou nem arrancou, e 130 quando interrompido. Há uma excepção curiosa: workers do Kanban, com HERMES_KANBAN_TASK definida, cujo turno falhou apenas por rate-limit, 5xx, timeout ou quota, saem com 75, para o dispatcher reencolar a tarefa sem a contar como falha.
Em vez de -q, podes usar --query-file. Lê o prompt de um ficheiro (ou do stdin, com -) sem interpretação de shell: aspas, $(...) e backticks chegam intactos. Num pipeline onde o prompt vem de fora, é a opção certa. Para CI, --ignore-user-config, --ignore-rules e --safe-mode isolam a corrida da tua configuração pessoal. Muda o comportamento e é suposto que mude.
Vale a pena?
Para quem só quer a resposta, não. Usa o -z e acabou. O stream-json existe para o outro caso, quando o Hermes é uma peça dentro de algo maior: um job de CI que quer falhar se o agente gastou mais do que X tokens, um painel que mostra que ferramentas andam a ser chamadas, um runner que quer progresso real em vez de um spinner mentiroso.
hermes chat -q "Corre os testes e resume as falhas" --format stream-json \
| jq -r 'select(.type == "tool_use") | .name'
Uma linha de bash e tens a lista de ferramentas que o agente decidiu usar. Se filtrares o result e leres .tokens.total, tens o custo do turno. É pouco glamoroso, e é exactamente isto que faltava.
Tags: #hermes #cli #jsonl