Ensaio de Gatilho
O que é isso
Um agente que se ativa sozinho em eventos é tão seguro quanto suas regras de gatilho. O agente em si pode ser cuidadoso e ainda assim queimar o orçamento do dia, porque o problema geralmente está um nível acima:
- uma regra reage a um evento que seu próprio trabalho produz, então ela dispara de novo e de novo;
- duas regras se alimentam mutuamente através de um segundo sistema (o agente comenta, o comentário sincroniza, a sincronização dispara um webhook);
- uma fonte barulhenta envia dez edições em dois minutos e cada uma inicia uma nova execução;
- uma linha cron pensada como "verificar de vez em quando" dispara 288 vezes por dia;
- duas regras tratam o mesmo evento e fazem o trabalho duas vezes;
- uma regra que reembolsa, deleta ou envia mensagens para pessoas é executada sem perguntar a ninguém.
Nenhum desses problemas aparece quando você testa o agente em um exemplo isolado. Eles aparecem quando o tráfego real chega. O Ensaio de Gatilho faz o tráfego real chegar antes, no papel: você escreve as regras em um formato neutro, reproduz um dia ou uma semana de eventos através delas e lê a linha do tempo antes que qualquer coisa seja ativada.
Quando usar
- Antes de ativar qualquer regra que permita a um agente iniciar trabalho sem que uma pessoa digite um prompt.
- Ao adicionar uma regra a um conjunto existente (uma nova regra pode fechar um loop que estava aberto antes).
- Quando um agente em segundo plano executou com muito mais ou muito menos frequência do que o esperado.
- Ao escolher janelas de debounce, limites de paralelismo e limites diários, e você quer números em vez de suposições.
- Quando um revisor pergunta "o que acontece se este repositório receber 50 issues em uma hora?"
Não serve para: verificar o que o agente faz dentro de uma execução (isso é uma questão de prompt ou habilidade), ou testar a carga da própria fonte de eventos.
A planilha de regras
Tudo é traduzido para rules.json. Mantenha a tradução honesta: uma regra da planilha por regra real e escreva as proteções que realmente existem, não aquelas que você pretende adicionar.
{
"tz": "America/Sao_Paulo",
"run_s": 180,
"external_sources": ["github"],
"budget": {"runs_per_day": 40, "cost_per_run": 0.15, "cost_per_day": 6},
"rules": [
{
"name": "triagem-nova-issue",
"on": {"source": "github", "types": ["issues.opened", "issues.edited"]},
"where": {"repo": "meu/app"},
"key": "subject",
"debounce_s": 300,
"parallel": 1,
"max_per_day": 20,
"unless_actor": ["meu-bot"],
"unless_label": "triado",
"emits": [{"source": "github", "types": ["issues.labeled"], "actor": "meu-bot", "labels": ["triado"]}],
"sensitive": [],
"asks": false
},
{"name": "resumo-matinal", "on": {"cron": "0 9 * * 1-5"}, "max_per_day": 1}
]
}
| Campo | Significado |
|---|---|
on.source + on.types | Quais eventos iniciam a regra. Deixe types de fora para corresponder a qualquer tipo daquela fonte. |
on.cron | Uma linha cron de cinco campos no fuso horário da planilha. Uma regra tem source ou cron. |
where | Campos do evento que devem ser iguais a um valor (ou estar em uma lista). |
key | Campo do evento que nomeia o assunto (número da issue, ID do ticket, URL). Usado para mesclar repetições. |
debounce_s | Aguarde este tempo após o último evento para um assunto e depois execute uma vez. |
parallel | Quantas execuções desta regra podem estar ativas ao mesmo tempo. Execuções extras aguardam. |
max_per_day | Limite máximo por dia local. Execuções extras são descartadas. |
run_s | Duração típica da execução, usado para fila. Recai para o valor da planilha, depois para 120. |
unless_actor, unless_label | Proteções: ignore eventos feitos por esses atores, ou que carregam este rótulo. |
emits | Eventos que a execução em si produz (comentários, rótulos, mudanças de status, eventos de acompanhamento). Inclua o ator e os rótulos que carregará. |
sensitive + asks | Ações que precisam de uma pessoa (deletar, gastar, reiniciar, enviar). asks: true significa que a execução para e pergunta primeiro. |
external_sources | Fontes alimentadas pelo mundo exterior. Qualquer outra coisa deve ser emitida por uma regra. |
budget | Opcional: execuções por dia, custo por execução e custo por dia usados para o veredito. |
Passo a passo
- Colete as regras reais. Leia a configuração real, não a descrição dela. Arquivos de regras de hubs de agentes, rotinas salvas, blocos
on:de workflows de CI, linhas crontab, telas de automação sem código. Anote qualquer coisa que escreva de volta para uma fonte que outra regra observa. - Traduza para
rules.json. Comece compython3 scripts/rehearse.py new rehearsal/. Para cada regra, faça três perguntas e preencha os campos: o que a ativa, o que uma execução escreve de volta, e o que a impede de ativar com suas próprias escritas. Se a resposta para a terceira for "nada", deixe a proteção de fora. O ensaio serve para pegar isso. - Lint.
python3 scripts/rehearse.py lint rehearsal/rules.json. Corrija todos os HOLD antes de reproduzir; itens REVIEW podem esperar pelos números. - Colete eventos. Melhor: exporte um dia real movimentado da fonte (log de entrega de webhook, linha do tempo de issues, log de auditoria de helpdesk) para
events.jsonl, um objeto JSON por linha com pelo menost,source,type, mais os campos que seukeyewhereusam. Se não houver histórico, escreva uma pequena rajada sintética de propósito: dez edições em três minutos, dois assuntos ao mesmo tempo, um evento feito pelo seu próprio bot. - Reproduza em dias inteiros.
python3 scripts/rehearse.py replay rehearsal/rules.json rehearsal/events.jsonl --from 2026-11-02T00:00:00Z --to 2026-11-03T00:00:00Z --md rehearsal/report.md. Passe--from/--topara que os números por dia não sejam inflados por uma janela curta. - Leia a linha do tempo, não apenas o veredito. Procure por execuções no mesmo assunto com minutos de diferença (debounce ausente ou curto), cadeias com profundidade crescente (loops), longas esperas (paralelismo muito baixo), descartes (limite muito baixo para trabalho real, ou um sinal de que a regra dispara em ruído).
- Ajuste e repita. Mude um parâmetro de cada vez e reproduza os mesmos eventos, para que a diferença seja claramente dessa mudança.
- Decida. ARME apenas quando a última reprodução for ARM em um dia realista. Mantenha o
rules.jsonfinal, o arquivo de eventos ereport.mdjuntos como o registro do motivo pelo qual as regras foram ativadas.
Vereditos
- ARM: nenhum achado HOLD ou REVIEW. Seguro ativar como está.
- TRIM: algo desperdiça execuções ou pode pular trabalho (sem key, sem limite, sobreposição, limite descartou eventos reais, perto do orçamento). Ative apenas após cortar, ou com um motivo por escrito.
- HOLD: não ative. Auto-loop, ciclo desprotegido, uma cadeia que passou da profundidade 5, uma ação sensível sem perguntar, acima do orçamento, ou uma linha cron quebrada.
O código de saída segue o veredito (0, 1, 2; 3 significa entrada inválida), para que um ensaio possa bloquear uma mudança de configuração no CI.
Exemplo
Uma equipe de suporte quer um agente para responder tickets de helpdesk. A planilha deles (em examples/loopy/) tem uma regra de resposta que posta uma réplica, que atualiza o ticket, que corresponde à mesma regra. Uma reprodução de um dia comum dá:
HOLD self-loop responder-ticket: a própria saída da regra corresponde ao seu próprio gatilho sem proteção unless_actor/unless_label
HOLD runaway responder-ticket: cadeia continuou disparando além da profundidade 5 (3 cortes)
HOLD sensitive-unasked verificar-reembolso: declara ações sensíveis ['reembolso'] mas 'asks' não é true
HOLD over-runs-budget -: 100.0/dia contra um orçamento de 30/dia
REVIEW cron-tight verificar-caixa: até 720 disparos/dia
VERDICT HOLD
Três atualizações de ticket se transformaram em 18 execuções de resposta. A correção é pequena: adicione "unless_actor": ["bot-suporte"] à regra de resposta, defina "asks": true na regra de reembolso e substitua a verificação de dois minutos por um script que emite inbox.changed apenas quando a caixa de entrada realmente mudou (veja examples/loopy/rules.fixed.json). A próxima reprodução do mesmo dia cai de 100 execuções para 4 e o veredito vai para TRIM. O único achado restante é que responder e verificar reembolso veem um ticket de reembolso, o que é intencional aqui, então é aceito com um motivo de uma linha por escrito ao lado do relatório.
Dicas
- A melhor proteção contra loop único é uma condição que se torna falsa após a execução: um rótulo que a execução adiciona e a regra exclui, ou um status que a regra não corresponde mais.
- Loops entre sistemas são os mais fáceis de perder. Se o agente escreve no sistema A e o sistema A sincroniza com B, liste o evento sincronizado em
emitscom o ator que ele mostrará. - Debounce adiciona atraso por design. Escolha a janela com base em quanto tempo uma pessoa leva para terminar uma rajada de edições e mantenha-a curta onde alguém espera por uma resposta.
- Debounce não é deduplicação de ações. Para qualquer coisa irreversível, a execução em si ainda deve verificar "já foi feito?" antes de agir.
- Um limite diário é um fusível, não um plano. Se a reprodução de um dia normal o atingir, a regra é muito ampla.
- Polling a cada poucos minutos geralmente é mais barato como um pequeno script que compara o estado e emite um evento na mudança; o agente só acorda quando há algo novo.
- Execute novamente o ensaio sempre que uma regra for adicionada. A maioria dos loops é criada pela segunda regra, não pela primeira.