Log de execução em JSONL: como a gente mediu 123 rodadas de agente sem dashboard
Voltar para o Blog
Artigoverboo codedev toolstutorialautomação

Log de execução em JSONL: como a gente mediu 123 rodadas de agente sem dashboard

Mafra5 de novembro de 20268 min de leitura

Duas rodadas de agente terminaram com post publicado. Vista de fora, são idênticas: mesmo pipeline, mesmo dia, mesmo resultado no ar. Vista de dentro, uma levou 25 minutos e a outra levou 65, e a diferença não esteve no modelo nem no assunto. Esteve em três comandos que o gate de permissão barrou e no tempo gasto improvisando contorno.

O resultado esconde o caminho. Por isso a gente grava uma linha por execução num arquivo JSONL e lê esse arquivo uma vez por semana. Não é observabilidade de produção nem dashboard: é texto que cabe num grep e que responde "o que se repetiu" com número, não com memória.

O que é um log de execução em JSONL?

É um arquivo de texto com um objeto JSON por linha, uma linha por execução, acrescentando sempre no fim. JSON Lines é lido linha a linha: uma linha quebrada não derruba o arquivo inteiro, e cada rodada nova é um append, nunca uma reescrita.

O formato importa menos do que a regra de escrita. O que faz esse log funcionar é ele ser append-only: a linha de hoje não pode alterar a de ontem. Se o registro pode ser reescrito, ele deixa de ser histórico e vira relatório, e relatório sempre fica bonito.

Quais campos entram na linha?

Só o que muda a decisão da rodada seguinte. O nosso tem oito campos, e cada um responde a uma pergunta diferente:

CampoPara que serve
dataQuando rodou, em ISO 8601. É o que permite comparar semana com semana.
trilhaQue tipo de tarefa era. Mostra desequilíbrio antes de virar problema.
temaO assunto em uma frase. Serve para checar duplicata e para ler o histórico depois.
origemDe onde veio o tema: fila, release, issue, busca ou material interno. É o campo que mede se o planejamento funcionou.
statuspublished, draft ou blocked. Sem isso, "deu certo" é opinião.
notaAutoavaliação curta, com critério fixo. Sem critério, vira autoelogio.
urlsOnde o resultado ficou, um endereço por idioma. Fecha o ciclo entre execução e entrega.
atritosLista de frases sobre o que custou tempo. É o único campo que ninguém preenche bem de memória, por isso ele é escrito durante a rodada.

Formato, com valores de exemplo:

{"data": "2026-11-05", "trilha": "D", "tema": "log de execucao em JSONL",
 "origem": "fila", "status": "published", "nota": "A",
 "urls": {"pt": "https://verboo.ai/pt/blog/jsonl-execution-log",
          "en": "https://verboo.ai/en/blog/jsonl-execution-log",
          "zh": "https://verboo.ai/zh/blog/jsonl-execution-log"},
 "tempo_min": 40,
 "atritos": ["gerador de imagem devolveu 503 na primeira chamada"]}

Como registrar sem depender de memória?

Com uma função que abre o arquivo em modo append e serializa a linha inteira. Fazer isso no código, e não escrevendo à mão no fim, é a diferença entre 123 registros e 3 registros esquecidos.

import json
from datetime import date

def registrar(caminho, **campos):
    linha = {"data": date.today().isoformat(), **campos}
    with open(caminho, "a", encoding="utf-8") as f:
        f.write(json.dumps(linha, ensure_ascii=False) + "\n")

registrar(
    "log-execucoes.jsonl",
    trilha="D",
    tema="log de execucao em JSONL",
    status="published",
    tempo_min=40,
    atritos=["gerador de imagem devolveu 503 na primeira chamada"],
)

Depois de gravar, valide o arquivo inteiro. O jq lê um fluxo de valores JSON, então -s (slurp) só termina se todas as linhas forem válidas, e ainda conta quantas são:

jq -s 'length' log-execucoes.jsonl
# 123

Se uma linha estiver quebrada, o comando falha e aponta onde. Melhor isso do que descobrir semanas depois que a análise estava lendo 118 de 123 registros e chamando de total.

Como analisar o arquivo sem virar planilha?

Com um script pequeno, só com a biblioteca padrão. O objetivo não é contar tudo. É separar atrito recorrente de caso isolado, porque só o recorrente justifica mudar processo.

import json, re, sys

ATRITOS = {
    "shell bloqueado": r"bloqueou|pediu aprovacao|heredoc",
    "fila sem item":   r"sem item|esgotad",
    "busca externa":   r"Reddit|Composio",
    "imagem fora do ar": r"503|alta demanda",
}

linhas = [json.loads(l) for l in open(sys.argv[1], encoding="utf-8") if l.strip()]
total = len(linhas)
print(f"{total} rodadas")
for nome, padrao in ATRITOS.items():
    n = sum(1 for l in linhas if re.search(padrao, " ".join(l.get("atritos", []))))
    print(f"{nome:20} {n:3}  {n/total:.0%}")

Rodando no nosso arquivo, a saída foi esta:

123 rodadas
shell bloqueado       30  24%
fila sem item         43  35%
busca externa         34  28%
imagem fora do ar     11   9%

A contagem é piso, não teto. O script procura palavra no texto livre do atrito, então uma rodada descrita como "contornado com um script" entra na conta geral mas não na do shell. Buscar só por contornad acha 50 rodadas. Texto livre subconta: é bom para descobrir padrão e ruim para auditoria.

O que 123 execuções revelaram?

Que o gargalo não estava onde a gente olhava. Entre 3 de setembro e 5 de novembro de 2026 foram 123 rodadas, 122 publicadas e 1 bloqueada, com 59 horas de execução registradas em 115 rodadas e mediana de 30 minutos por rodada. O atrito mais comum foi operacional, não de modelo.

Medida nas 123 rodadasRodadasLeitura
Fila da trilha sem item pendente43 (35%)A pauta, não a escrita, decidiu o ritmo.
Busca externa atrapalhou (Reddit, Composio)34 (28%)Entrada instável, exige fonte primária.
Comando de shell bloqueado30 (24%)Contorno manual antes de começar a tarefa.
Gerador de imagem indisponível (503)11 (9%)Fallback precisa estar escrito, não improvisado.

Teve também um achado de schema: 4 linhas puseram um parágrafo inteiro no campo nota, que era para ser A, B ou C. A maior tinha 1.761 caracteres. Não é falha de análise, é sintoma de campo sem tipo. Se o formato aceita texto livre, alguém vai escrever texto livre, e a contagem por nota quebra sem avisar.

Fila sem item: o gargalo é a pauta

Em 35% das rodadas, a fila da trilha não tinha item pendente e o tema saiu de garimpo na hora. O campo origem existe para isso: ele mostra qual fonte sustentou aquele mês. Sem esse número, a conclusão seria "estamos sem assunto", que não é acionável. Com ele, a conclusão é "a fila de duas trilhas seca em três semanas", que é.

Shell bloqueado: o comando composto é o vilão

Em 24% das rodadas, um comando foi barrado pelo gate de permissão, quase sempre pelo mesmo motivo: heredoc com aspas e chaves, encadeamento com && e redirecionamento para fora do projeto. Nenhum desses casos é falha do agente. É o gate fazendo o trabalho dele, e o contorno é sempre o mesmo: escrever o script num arquivo e rodar um comando só.

Busca externa: entrada, não verdade

Em 28% das rodadas, a busca comunitária atrapalhou mais do que ajudou: ruído no resultado, serviço fora do ar ou fonte sem cobertura do assunto. A regra que saiu disso vale para qualquer pipeline que use busca: resultado de busca é pista, não fato. Número só entra no texto depois de confirmado em fonte oficial ou jornalística, e o atrito fica registrado quando a confirmação não aparece.

Gerador de imagem fora do ar

Em 9% das rodadas, o gerador devolveu 503 de alta demanda. O que evitou retrabalho não foi tentar de novo em silêncio, foi a ordem de fallback estar escrita: primeiro o modelo principal, depois o alternativo, e o motivo registrado no atrito. Fallback improvisado gera resultado diferente a cada rodada.

Como transformar atrito em regra?

Com uma escada de três degraus, e nenhum deles é reunião:

  1. Primeira vez: anota no campo atritos e segue. Caso isolado não vira regra.
  2. Segunda vez: vira regra escrita no processo, com o comando ou a ordem que evita o problema.
  3. Terceira vez: vira checagem automática no script. Regra que depende de alguém lembrar não é regra.

Um caso passou pelos três degraus: o gerador de diagrama cortava texto na borda do card. Aconteceu quatro vezes em seis dias, sempre corrigido à mão, até virar função que mede a largura antes de desenhar. O caso está contado aqui, com o código da correção.

Quanto custa manter um log assim?

Uma linha por execução. O arquivo com 123 rodadas e 59 horas registradas ocupa 270 KB, abre em qualquer editor e responde ao jq em menos de um segundo. O custo real é a disciplina de gravar antes de encerrar a sessão, e uns 10 minutos de leitura por semana.

O que faz o log existir 123 vezes, e não 3, é ser passo obrigatório do processo. No nosso caso, a skill que o agente segue termina com a gravação da linha. Se fosse opcional, o dia corrido venceria.

Se você já tem um agente rodando tarefas em sequência, o próximo passo é barato: grave a linha antes de encerrar a rodada e rode o script acima uma vez por semana. Na Verboo Code o agente executa a análise e a rodada seguinte com token ilimitado, então repetir a leitura custa o mesmo que rodar uma vez.

Gostou deste artigo?
Compartilhe conhecimento com sua rede.
// Leia também

Artigos relacionados