# Jobs assíncronos de API: fluxos de agentes rastreáveis

Um agente que envia uma solicitação de API de longa duração precisa de um fluxo, não de um loop que continua chamando endpoints até algo parecer concluído. Criação, acompanhamento, cancelamento e obtenção do resultado têm modos de falha diferentes. Se você juntar tudo em um prompt e algumas tentativas, mais cedo ou mais tarde enviará o trabalho duas vezes, perderá um resultado concluído ou afirmará que um cancelamento ocorreu quando ele nunca foi aplicado.

A parte difícil não é fazer uma solicitação HTTP. É preservar a intenção depois que o agente reinicia, ocorre um timeout de rede, o provedor fica indisponível ou uma pessoa diz «pare» depois que o serviço remoto já começou a trabalhar. Estruture tudo em torno de um registro local durável do job e faça com que cada chamada externa possa ser explicada depois: o que pedimos, qual job remoto é responsável, que estado observamos e o que fizemos em seguida?

## Uma solicitação de criação precisa estabelecer a propriedade

Um endpoint de criação inicia uma operação assíncrona e retorna antes que ela chegue a um estado final. A primeira resposta precisa dar ao agente informações suficientes para continuar sem repetir a solicitação. Em uma API bem projetada, isso significa um ID de job, um estado inicial e uma URL de status ou uma convenção de endpoint para consultar o job.

Não conclua que uma solicitação foi aceita porque o socket se conectou ou porque o cliente atingiu o tempo limite depois de enviar os bytes. A única prova útil é uma resposta do servidor ou uma consulta posterior que associe a solicitação lógica original a um job. A entrega pela rede é incerta justamente quando o cliente mais quer uma resposta simples.

Dê a cada operação lógica um ID local antes que o agente faça a solicitação. Esse é o seu identificador, não o do provedor. Armazene-o com o corpo da solicitação ou uma impressão digital normalizada desse corpo, o destino pretendido, o token de idempotência, os horários e o responsável que autorizou o trabalho. Grave esse registro de forma durável antes de enviar a solicitação.

Um registro mínimo pode ser assim:

```json
{
  "operation_id": "op_01J7Q5X4D4PA3D",
  "request_fingerprint": "sha256:4f8b...",
  "idempotency_token": "idem_5b5c76c7",
  "remote_job_id": null,
  "state": "create_pending",
  "created_at": "2025-03-08T14:22:11Z",
  "create_deadline": "2025-03-08T14:24:11Z",
  "result_deadline": "2025-03-08T15:22:11Z"
}
```

A impressão digital detecta um erro sutil e frequente: o agente repete uma chamada de criação depois de alterar um parâmetro. Essa é uma nova operação, mesmo que uma pessoa a descrevesse como «a mesma tarefa». Um token associado a um formato de solicitação não pode autorizar silenciosamente outra.

Uma resposta de criação pode ser:

```http
HTTP/1.1 202 Accepted
Location: /v1/jobs/job_7ad2
Content-Type: application/json

{
  "job_id": "job_7ad2",
  "state": "queued",
  "status_url": "/v1/jobs/job_7ad2"
}
```

Assim que a resposta chegar, atualize o registro durável com `remote_job_id`, o estado observado e os metadados da resposta. Só então o agente pode passar ao acompanhamento. Se a API retornar um sucesso síncrono, registre o resultado na mesma operação. O fluxo deve aceitar os dois caminhos sem fingir que eles significam a mesma coisa.

## Idempotência serve para entregas incertas, não para repetições em geral

Um token de idempotência informa ao servidor que várias entregas da mesma solicitação lógica de criação não devem produzir trabalho repetido. Ele não torna toda solicitação segura e não corrige uma API que nunca implementou idempotência no endpoint de criação.

O token deve aparecer na solicitação de criação conforme o contrato do provedor. Algumas APIs aceitam um cabeçalho `Idempotency-Key`. Outras exigem um campo na solicitação. Use o formato documentado e gere um token com entropia suficiente para que operações diferentes não colidam. Preserve o mesmo token até resolver a operação original.

```http
POST /v1/reports HTTP/1.1
Content-Type: application/json
Idempotency-Key: idem_5b5c76c7
X-Trace-ID: tr_0830d3

{
  "account": "acct_218",
  "range": {"start": "2025-02-01", "end": "2025-02-28"},
  "format": "csv"
}
```

A sequência segura de repetição é curta:

1. Gere o token e persista o registro da operação.
2. Envie a solicitação de criação com esse token.
3. Se a resposta for perdida ou o cliente atingir o tempo limite, repita a solicitação idêntica com o token idêntico.
4. Se o servidor retornar o job original, salve o ID e continue.
5. Se precisar de entradas diferentes, encerre ou cancele a operação antiga, se possível. Depois, crie um novo registro e um novo token.

Essa distinção importa porque os agentes naturalmente reescrevem solicitações enquanto raciocinam. Alterar o intervalo de datas, o destino, a conta ou o formato de saída muda o efeito. Reutilizar um token depois de uma alteração cria uma disputa entre cliente e servidor: um servidor cuidadoso rejeita a divergência, enquanto um menos cuidadoso pode retornar uma resposta antiga que já não corresponde à intenção do agente.

O padrão HTTP deixa clara a distinção relevante. A RFC 9110 define métodos idempotentes como aqueles cujo efeito pretendido após várias solicitações idênticas é o mesmo de uma única solicitação. POST não é idempotente por padrão. Um provedor pode adicionar comportamento de idempotência a um endpoint POST, mas o cliente deve tratar isso como um contrato explícito da aplicação, não como uma regra do HTTP.

Uma recomendação ruim e comum diz: «Repita POST apenas uma vez». O número de tentativas não é o ponto principal. Uma única submissão duplicada pode enviar um pagamento, provisionar um ambiente ou iniciar um lote caro. Repita uma solicitação de criação sempre que o prazo e as orientações do provedor permitirem, mas apenas com um token que dê ao servidor uma forma de reconhecer a operação original.

## Um timeout deixa o estado do job desconhecido

Um timeout de criação não significa que o serviço rejeitou a solicitação. Significa que o agente não recebeu uma resposta definitiva antes do próprio prazo. O servidor pode ter aceitado a solicitação, ainda pode estar processando-a ou pode nunca tê-la recebido.

Essa é a falha que expõe fluxos de agentes frágeis. Um agente envia uma solicitação de criação, espera trinta segundos, não recebe resposta e envia uma nova solicitação com um novo token. Agora dois relatórios estão sendo executados. O segundo pode terminar primeiro, tornando o incidente difícil de perceber até que alguém compare cobranças, exportações ou alterações posteriores.

Mantenha um estado explícito `create_pending`. Quando a chamada falhar de forma ambígua, registre a classe do erro, o horário e o número de tentativas, mas não descarte a operação. Depois, use o caminho de reconciliação da API. Os provedores fazem isso de maneiras diferentes:

- Uma repetição usando o mesmo token de idempotência pode retornar a resposta de aceitação original.
- Um endpoint de lista ou busca pode filtrar por uma referência de solicitação do cliente.
- Uma consulta de status pode aceitar um ID de operação fornecido pelo cliente.
- O provedor pode documentar uma consulta compatível para criações recentes usando um identificador de solicitação.

Se nenhuma dessas opções existir, a API não pode oferecer ao cliente semântica confiável de criação no máximo uma vez quando uma resposta é perdida. Diga isso claramente no projeto. Você pode reduzir duplicidades com uma fila de saída local e tentativas moderadas, mas não pode provar que uma repetição não criou trabalho adicional.

Trate uma resposta que contenha um ID de job desconhecido usando o mesmo token como uma violação de contrato que merece investigação. Não substitua o ID antigo. Preserve os dois registros de resposta, interrompa a atividade automática dessa operação e exija uma decisão humana. Escolher um deles silenciosamente é como os históricos de auditoria viram ficção.

Use prazos que diferenciem comunicação de execução. O prazo de criação determina por quanto tempo o agente tentará estabelecer um ID de job remoto. O prazo do resultado determina quanto tempo o processo de negócio aguardará a conclusão. Um job pode sobreviver a um breve timeout da resposta de criação e ainda ter horas para terminar. Juntar os dois em um único cronômetro faz os agentes abandonarem trabalhos recuperáveis ou repeti-los no momento errado.

## As consultas precisam de recuo, propriedade e um horário de parada

Consultar o status é seguro quando um único fluxo durável é responsável pelo job e cada consulta registra uma observação. Isso se torna abusivo quando várias execuções do agente redescobrem o mesmo job e todas o consultam de forma independente.

Coloque o ID do job remoto em um único registro e atribua um lease ao processo que atualmente acompanha a operação. O lease pode ser uma linha de banco de dados com um horário de expiração, uma mensagem de fila com regras de visibilidade ou outro controle de concorrência durável. Se o worker falhar, outro poderá assumir depois que o lease expirar. Sem propriedade, repetições e reinicializações multiplicam as chamadas de status.

Respeite `Retry-After` quando a API o enviar. Se a API não der orientações, use recuo exponencial limitado com variação aleatória. O teto exato depende da rapidez com que o negócio precisa de uma resposta e dos limites de taxa do provedor, mas o formato deve evitar picos sincronizados.

```text
attempt 1: wait a randomized interval near 2 seconds
attempt 2: wait a randomized interval near 4 seconds
attempt 3: wait a randomized interval near 8 seconds
later attempts: keep increasing until the configured cap
```

Não calcule o próximo intervalo com base no resumo em prosa do agente. Armazene o horário da próxima consulta no registro do job. Assim, um worker reiniciado pode retomar o cronograma, e um operador consegue explicar claramente por que o agente está esperando.

Uma resposta de status deve atualizar apenas fatos observados. Por exemplo:

```json
{
  "job_id": "job_7ad2",
  "state": "running",
  "updated_at": "2025-03-08T14:26:40Z",
  "progress": {"completed": 146, "total": 500}
}
```

Registre `state`, o horário do provedor quando fornecido, o horário de obtenção, a referência da resposta bruta e a próxima ação. Não transforme um campo de progresso vago em uma promessa de conclusão. Os provedores costumam informar o progresso com atraso ou em lotes. O progresso ajuda os operadores; o estado final controla o fluxo.

Defina um prazo para o resultado e transforme sua expiração em um estado, não em uma desculpa para esquecer o job. `result_timed_out` significa que o agente interrompeu as consultas automáticas porque o acordo expirou. Isso não significa que o job remoto parou. Se a ação tiver custo ou efeitos reais, mantenha informações suficientes para reconciliá-la depois e decidir se o cancelamento é apropriado.

Webhooks podem reduzir a latência, mas não eliminam o loop de status. Os provedores podem repetir callbacks, entregá-los fora de ordem ou deixar de entregá-los. Verifique o callback de acordo com a documentação do provedor, elimine duplicidades com um ID de evento quando disponível, atualize o mesmo registro do job e faça uma leitura final do status antes de declarar sucesso.

## As transições de estado devem rejeitar suposições otimistas

Uma máquina de estados protege o fluxo contra um agente que interpreta as palavras de forma generosa demais. Defina seus estados locais e as transições permitidas antes de conectar ferramentas à API. Os serviços remotos usam nomes diferentes, mas seu registro deve tornar a incerteza visível.

Um modelo local prático é:

```text
create_pending -> accepted -> observing -> result_collecting -> succeeded
create_pending -> create_unknown -> reconciliation
accepted or observing -> cancel_requested -> cancelling -> cancelled
accepted or observing -> failed
observing -> result_timed_out
```

As setas são regras, não um diagrama decorativo. Um worker deve rejeitar uma transição sem evidência. Não pode marcar `succeeded` porque viu um progresso de 100. Não pode marcar `cancelled` porque enviou `DELETE /jobs/job_7ad2`. Não pode passar de `failed` para `observing` a menos que a API remota ofereça explicitamente uma operação de repetição ou retomada e a nova ação seja registrada separadamente.

Mantenha o estado remoto separado do estado local. `cancel_requested` descreve um fato local: o agente enviou uma solicitação de cancelamento e aguarda confirmação. `cancelled` descreve um fato remoto: o serviço informou um estado final de cancelamento. Essa pequena distinção evita muita confusão durante um incidente.

Use um histórico somente de acréscimo para as transições. Cada entrada precisa do ID da operação, do responsável, do horário, do estado local anterior, do novo estado local, da solicitação ou resposta que disparou a mudança e do motivo. Um histórico compacto basta:

```json
{
  "at": "2025-03-08T14:29:02Z",
  "actor": "worker-3",
  "from": "observing",
  "to": "cancel_requested",
  "cause": "human_request:req_91af",
  "remote_job_id": "job_7ad2"
}
```

Evite usar um único campo mutável `status` como seu único registro. Ele informa o que o fluxo acredita agora, mas não por que acreditava nisso cinco minutos atrás. Quando uma API remota retornar um estado inesperado, o histórico mostrará se o provedor mudou, se o agente repetiu uma chamada ou se um operador interveio.

## O cancelamento precisa de confirmação e de um limite de danos

O cancelamento é uma solicitação para interromper trabalhos futuros. Ele não desfaz o que o provedor já confirmou, e alguns provedores permitem uma corrida em que o job termina no mesmo momento em que chega a solicitação de cancelamento. Estruture o fluxo partindo dessa realidade.

Quando uma pessoa ou política decidir parar um job, registre primeiro a intenção de cancelamento. Inclua quem fez a solicitação, o motivo e o efeito esperado. Depois, chame o endpoint de cancelamento documentado usando o ID de job remoto armazenado. Persista a resposta mesmo que ela diga apenas que o servidor aceitou a solicitação.

Continue consultando o status depois do cancelamento. Os resultados finais aceitáveis normalmente incluem `cancelled`, `succeeded` e `failed`. Um resultado concluído depois de uma solicitação de cancelamento não é automaticamente um erro. Pode ser o resultado legítimo de um job que cruzou seu ponto de confirmação alguns segundos antes. O fluxo deve relatar essa sequência com precisão, em vez de reescrever o histórico para corresponder ao resultado desejado.

Algumas operações precisam de um limite de danos separado do cancelamento. Se um job de exportação gravar um arquivo, cancelá-lo pode deixar um arquivo parcial. Se um job de provisionamento criar recursos, o cancelamento pode deixar alguns recursos criados. O contrato da API deve informar se oferece limpeza, reversão ou detalhes do resultado parcial. Se não oferecer, trate o cancelamento como um controle operacional, não como uma transação.

Não envie chamadas repetidas de cancelamento a partir de cada worker de acompanhamento. Armazene `cancel_requested`, torne a operação de cancelamento idempotente se o provedor permitir e deixe o responsável pelo lease cuidar do acompanhamento. Repetir uma solicitação inofensiva desperdiça capacidade; repetir um cancelamento com efeitos colaterais pode confundir o log de auditoria remoto.

Um prazo de cancelamento também ajuda. Depois de uma espera razoável e documentada, passe para `cancellation_unconfirmed` em vez de afirmar que o cancelamento foi bem-sucedido. Escale o caso com o ID de job remoto, o ID de rastreamento, o histórico de solicitações e todos os IDs de solicitação do provedor. Esse conjunto permite que uma pessoa ou a equipe de suporte do provedor veja a sequência real sem reconstruí-la a partir de mensagens de chat.

## A obtenção do resultado é uma ação separada

Um estado final de sucesso significa que o trabalho remoto terminou. Isso não garante que o resultado foi obtido, validado, armazenado ou entregue ao sistema seguinte. Trate a obtenção como uma ação própria e registrada.

Primeiro, obtenha o resultado usando o ID do job ou a referência de resultado fornecida pela API. Valide o tipo de conteúdo, o esquema, o checksum, o tamanho ou a contagem de registros esperados quando o provedor fornecer essas informações. Armazene a referência do resultado e o resultado da validação no registro do job. Se o resultado for grande, armazene um local durável e dados de integridade, em vez de copiar conteúdo opaco para um log de eventos.

Depois, decida se a própria obtenção precisa de idempotência. Muitos endpoints de resultado são leituras seguras. Outros geram um download temporário, consomem um artefato de uso único ou marcam um job como entregue. Leia o contrato. Um agente que trata todo `GET` como inofensivo ainda pode provocar uma alteração de estado específica do provedor.

Não use apenas um status HTTP bem-sucedido como validação. Um endpoint de relatório pode retornar um arquivo válido contendo uma linha de erro. Um lote de imagens pode retornar um manifesto com itens falhos. Uma exportação de dados pode terminar omitindo registros aos quais a API informa que o chamador não tem acesso. Valide o resultado contra a expectativa de negócio que deu origem ao job.

Para trabalhos em lote, registre os resultados de cada item quando o provedor oferecer esse recurso. Um job finalizado pode conter 498 sucessos e duas falhas. Chamá-lo simplesmente de «concluído com sucesso» obriga o próximo agente a redescobrir a falha parcial no conteúdo do resultado. Seu estado local final pode continuar sendo de sucesso, enquanto o resumo do resultado traz as contagens e uma lista das referências dos itens falhos.

Encerre a operação apenas quando a obtenção cumprir o contrato. `succeeded` deve significar que o resultado pretendido está disponível e foi verificado de acordo com suas regras. Se o provedor concluiu o trabalho, mas a obtenção falhou, use um estado local distinto, como `result_unavailable` ou `result_validation_failed`. O job remoto pode ter terminado, enquanto seu fluxo ainda não.

## IDs de rastreamento conectam ações, enquanto registros de auditoria estabelecem fatos

Use um ID de rastreamento para cada operação e envie-o na criação, nas leituras de status, no cancelamento e na obtenção quando a API aceitar cabeçalhos personalizados. Associe-o ao ID do job remoto assim que você o conhecer. O ID de rastreamento conecta eventos dentro dos seus sistemas; o ID do job permite que o provedor encontre seu próprio item de trabalho.

Não sobrecarregue nenhum dos dois identificadores. Um ID de rastreamento não deve virar um token de idempotência, porque uma operação pode envolver várias solicitações com regras de repetição diferentes. Um ID de job não deve virar seu registro de autorização, porque o provedor o gerou depois da sua decisão local de agir.

Seu registro de auditoria deve responder a perguntas que os logs muitas vezes não conseguem responder: qual processo de agente iniciou a operação, qual aprovação humana a cobriu, qual ação autenticada ocorreu e se alguém editou o histórico depois do fato. Grave um registro conciso da intenção antes da chamada de criação e acrescente observações depois. Preserve IDs de solicitação e metadados de resposta sanitizados. Nunca coloque tokens bearer, senhas, chaves privadas ou payloads sensíveis completos em um log geral.

O Sallyport pode executar chamadas de API HTTP sem expor as credenciais armazenadas ao agente, e suas sessões e chamadas individuais fornecem um histórico à prova de adulteração que pode ser verificado com `sp audit verify`. Isso protege a custódia das credenciais e as evidências das ações. Seu fluxo ainda precisa do próprio registro da operação, porque só ele sabe se `job_7ad2` pertence à tarefa de negócio solicitada.

Um rastreamento só é útil em um dia difícil se todos os componentes o registrarem de forma consistente. Coloque-o no registro local do job, no contexto de execução do agente, nos cabeçalhos das solicitações quando permitido, nas anotações de auditoria quando permitido e nos chamados dos operadores. Não crie um novo rastreamento para cada consulta. Elas são eventos secundários da mesma operação.

## Um loop de referência lida com as falhas comuns

O fluxo abaixo mantém explícitas as decisões difíceis. Ele pressupõe um provedor com um contrato de criação idempotente, um endpoint de status e um endpoint de cancelamento. Adapte os nomes dos endpoints, mas não remova as transições de estado persistentes.

```text
load operation by local operation ID

if no operation exists:
    create and persist record with fingerprint and idempotency token

if remote job ID is absent:
    send create with the stored token
    if response confirms job ID:
        persist job ID and move to observing
    if response is ambiguous:
        move to create_unknown and reconcile using the stored token
    if response rejects request definitively:
        move to failed

while local state requires observation and result deadline has not passed:
    acquire lease for the operation
    read remote status
    append the observation
    if cancellation was requested and remote state is nonterminal:
        send cancellation once and record the attempt
    if remote state is terminal:
        collect and validate result if appropriate
        persist final local state
    otherwise:
        persist next poll time and release lease

if the deadline expires before a terminal observation:
    move to result_timed_out and preserve the reconciliation record
```

Esse loop não tem um número mágico de tentativas, porque os limites dependem do provedor, do custo da operação e do prazo do chamador. Mas ele tem uma regra mais importante: toda repetição se refere a uma operação armazenada, e toda ação visível externamente altera o histórico dessa operação.

Teste o fluxo com injeção de falhas antes de entregá-lo a agentes autônomos. Descarte a resposta de criação depois que o servidor a aceitar. Encerre o worker depois de salvar o ID do job, mas antes de agendar a primeira consulta. Retorne um resultado final durante uma corrida de cancelamento. Entregue o mesmo webhook duas vezes. Reinicie com um lease expirado. Se o fluxo não conseguir explicar e se recuperar de cada caso, ainda não está pronto para iniciar jobs caros ou importantes.

A primeira tarefa de implementação não é empolgante: crie o registro durável da operação e recuse emitir uma solicitação de criação sem ele. Essa única restrição força o agente a preservar a intenção, torna possível evitar duplicidades e dá a todos um registro factual quando o sistema remoto se comporta de forma imperfeita.
