Pular para o conteúdo principal

O corpo da tarefa

O corpo do SKILL.md são as instruções que o agente segue. Numa execução, ele roda dentro do wrapper /kadu:agent-run-task, que já preparou tudo: helpers de instrumentação, data de referência, registro das chamadas HTTP e a finalização. Não refaça nada disso.

Os helpers​

Os helpers ficam em /tmp/agent_state.sh. Cada bloco de shell do corpo começa com source /tmp/agent_state.sh.

HelperAlimentaPara quê
emit_event '<json>'aba Históricoum evento por marco da execução, não em laço apertado
append_log '<texto>'aba Logslinhas de log
log_http_call '<texto>'aba Logstoda chamada a API de sistema externo, com os secrets mascarados
create_todo '<json>'aba Pendênciaspendência que sobrevive à execução
mark_body_started—sentinela obrigatória (abaixo)

:::danger Não redefina os helpers Redefinir qualquer um deles no corpo da skill tira o envio do caminho que mascara os secrets. Use os que o wrapper gravou. :::

A sentinela​

No fim da execução, o wrapper confere se o corpo marcou que começou. Sem a marca, ele registra execution.body_skipped e reprova a execução (exit 2). Então o corpo começa assim:

source /tmp/agent_state.sh
emit_event "$(jq -nc '{type:"step.started",level:"info",step:"1",message:"Buscando os pagamentos de ontem"}')"
mark_body_started

Eventos: type e message são obrigatórios​

Todo evento precisa de type e message; level, step e payload são opcionais. Um evento sem message é recusado (400) e o envio ignora o erro: o evento some do histórico, sem nada no log. Se um passo sumiu do histórico, suspeite disso primeiro.

ChaveConvenção
typestep.started e step.completed por passo
stepnúmero do passo como string ("1", "2")
levelthinking, info, success, warning ou error
messageo que está acontecendo, em uma frase, no idioma da execução
payloaddados estruturados do passo (contagens, ids)

Logs e chamadas HTTP​

log_http_call é para chamadas de API de integração (Fintera, Pagar.me, Linear, Slack), uma entrada por chamada:

source /tmp/agent_state.sh
RESP=$(curl -sS -w '\n%{http_code}' -u "$FINTERA_TOKEN:X" \
"https://financeiro.fintera.com.br/entities/$FINTERA_ENTITY_ID/receivable_accounts?per_page=50")
log_http_call "$(printf 'GET /receivable_accounts?per_page=50\n→ %s' "$(tail -n1 <<<"$RESP")")"

Ferramenta de alto volume (scanner, crawler, carga em lote) faz centenas ou milhares de requisições: logar cada uma afoga a aba Logs e esconde as chamadas que importam. Resuma numa linha de append_log (nuclei: 1.240 requisições → 3 achados) e reserve log_http_call para as APIs.

Não há usuário na execução​

A execução é automática. Toda pergunta é um travamento silencioso: a tarefa fica parada até estourar o limite de 60 minutos. Onde uma skill interativa perguntaria, a tarefa decide e registra:

SituaçãoO que fazer
Dado ambíguoescolha a leitura mais provável, diga qual escolheu e crie uma pendência com a dúvida
Um item falhoucrie uma pendência com o erro real e siga para os próximos
Volume acima do esperadoprocesse até o teto e crie uma pendência listando o excedente
Falta um pré-requisito absolutograve o resultado dizendo onde parou e saia com código diferente de zero
source /tmp/agent_state.sh
create_todo "$(jq -nc --arg doc "$CNPJ" \
'{title:"Pagamento sem conta a receber correspondente",description:"Nenhuma conta em aberto para o CNPJ no valor pago.",payload:{cnpj:$doc,valor:1520.00}}')"

Escreva a skill para os dois modos quando ela também for útil no Claude Code, com uma pessoa do outro lado:

if [ -n "${GITHUB_ACTIONS:-}" ]; then MODE=task; else MODE=local; fi

Entradas​

VariávelO que traz
$PARAMETERSos parâmetros do disparo, em JSON, já validados contra o parameters_schema
$REFERENCE_DATEa data de referência (AAAA-MM-DD): o "hoje" da execução
$LOCALEo idioma de quem disparou; escreva eventos e resultado nele
$INPUT_FILESum caminho por linha, só com accepts_input_files: true. Os arquivos já estão no disco.
$OUTPUT_FILES_DIRsó com generates_output_files: true: tudo o que for salvo aqui é publicado
secretscada chave de required_secrets chega como variável de ambiente ($FINTERA_TOKEN)
source /tmp/agent_state.sh
PARAMS="${PARAMETERS:-}"; [ -z "$PARAMS" ] && PARAMS='{}'
REPO_URL=$(jq -r '.repo_url // empty' <<<"$PARAMS")

Não invente valor ausente. Parâmetro essencial vazio é falha honesta, e não motivo para adivinhar.

A finalização: você grava o arquivo, o wrapper envia​

No fim, grave ./.execution-result.json com result e summary:

source /tmp/agent_state.sh
RESULT=$(cat <<'MD'
## Resumo

12 pagamentos baixados, 2 pulados (ver pendências).
MD
)
SUMMARY='{"processados":12,"pulados":2}'
jq -n --arg result "$RESULT" --argjson summary "$SUMMARY" \
'{result:$result, summary:$summary}' > ./.execution-result.json
  • result não é status. É o Markdown final que a aba Resultado mostra, no idioma de $LOCALE. {"result":"success"} faz a aba exibir a palavra "success".
  • summary é um objeto de contadores, e não uma frase.
  • Quem envia o resultado ao console é o wrapper, a partir desse arquivo. Não chame a finalização no corpo e nunca reenvie os logs no fim: a aba Logs é alimentada aos poucos, e um reenvio a sobrescreve.

Erro fatal no meio: grave mesmo assim o .execution-result.json dizendo onde parou, e saia com código diferente de zero. O status de falha vem do código de saída; não tente marcar a falha por conta própria.

Tarefas conversacionais​

Com conversational: true, a pessoa pode pedir ajustes depois que a execução termina (card Conversa no console, ou POST /v1/executions/{id}/continue). Cada pedido cria uma execução filha, e a rodada filha recebe:

  • os mesmos parâmetros, data de referência e ambiente da anterior. O pedido chega em $ADDITIONAL_INSTRUCTIONS;
  • a sessão do agente da anterior: o modelo vê o que leu, rodou e produziu;
  • os arquivos da anterior, nos mesmos caminhos: .execution-files/input/, .execution-files/output/ ($OUTPUT_FILES_DIR) e .execution-files/state/. O resto do diretório de trabalho é descartado entre as rodadas.

O wrapper e o corpo rodam de novo, do começo: o modelo decide, pelo pedido e pelo histórico, o que refazer. Isso muda como o corpo é escrito:

RegraPor quê
Guarde em .execution-files/state/ o que valha reaproveitar (roteiro, assets, projeto de edição)É o único diretório de trabalho preservado, além das entradas e saídas.
Grave as saídas em $OUTPUT_FILES_DIR como sempre, sobrescrevendo o arquivo que mudaSaída restaurada que volta sem mudança não é publicada de novo; a alterada é publicada como saída da nova rodada.
Torne idempotente todo efeito em sistema externoO corpo roda de novo a cada rodada. Sem conferir antes de criar, uma rodada que só ajusta um texto duplica a cobrança, a conta ou o e-mail da primeira.
Escreva o result de cada rodada como o estado atual completoA aba Resultado de cada execução mostra só o result dela.
Não dependa de nada fora de .execution-files/Pacotes instalados, /tmp e o resto do diretório de trabalho não sobrevivem.
Secret no histórico aparece como [secret NOME]: leia de novo do ambienteValor de secret nunca entra na sessão salva. O histórico da rodada anterior traz o nome da variável; arquivos de .execution-files/ voltam com o valor atual da secret.
Evite intermediários grandesSessão, entradas, saídas e estado somam no máximo 1 GiB. Acima disso a execução termina normalmente, mas não pode ser continuada.

Os dois executores continuam conversas: o claude_code retoma o transcript, o goose a sessão dele (goose run --resume). A sessão é do executor que a gravou: se o nível ou o override de executor da tarefa mudar entre as rodadas, a próxima é recusada (409 EXECUTOR_CHANGED) e a tarefa precisa ser disparada de novo. No goose, os modelos mais baratos dos níveis 1 e 2 recebem um histórico que cresce a cada rodada: confira se o resultado se sustenta depois de algumas rodadas.

Auxiliar que roda dentro de uma tarefa​

Uma auxiliar pequena (enviar uma mensagem no Slack) não precisa de instrumentação própria: a tarefa que a chama registra o passo. Mas, quando a tarefa orquestra auxiliares que fazem o grosso do trabalho, auxiliares mudas deixam o histórico e os logs vazios durante quase toda a execução.

A auxiliar chamada dentro de uma execução roda no mesmo processo e enxerga os helpers. Então ela instrumenta os próprios passos:

AbaQuem alimenta
Históricoa tarefa abre e fecha cada fase; a auxiliar emite step.started/step.completed dos passos dela, com contagens (o item individual vai para arquivo, não para evento)
Logsa auxiliar registra uma linha por ferramenta ou lote
Pendênciasa auxiliar cria as dela
Resultadosó a tarefa grava o .execution-result.json; a auxiliar devolve arquivo e resumo

Documente isso numa seção "Instrumentação do Kadu" da auxiliar, para quem a lê saber em que aba procurar.

Modelo de corpo​

# Olá, Kadu

Tarefa de exemplo: não acessa nenhum sistema externo e não faz perguntas.

## Passo 1 — início

```bash
source /tmp/agent_state.sh
emit_event "$(jq -nc '{type:"step.started",level:"info",step:"1",message:"Preparando o cumprimento"}')"
mark_body_started
PARAMS="${PARAMETERS:-}"; [ -z "$PARAMS" ] && PARAMS='{}'
NOME=$(jq -r '.nome // "time"' <<<"$PARAMS")
append_log "Parâmetros recebidos: $PARAMS"
```

## Passo 2 — resultado

Escreva um cumprimento curto para `$NOME`, no idioma de `$LOCALE`, citando a
data de referência (`$REFERENCE_DATE`). Grave o resultado:

```bash
source /tmp/agent_state.sh
RESULT="## Olá, $NOME!"
jq -n --arg result "$RESULT" '{result:$result, summary:{cumprimentos:1}}' > ./.execution-result.json
emit_event "$(jq -nc '{type:"step.completed",level:"info",step:"2",message:"Cumprimento gravado"}')"
```