# Polling de endpoint de status: como agentes esperam sem loops

Um agente que inicia uma tarefa externa não pode continuar perguntando «já terminou?» até que um provedor, um orçamento ou uma pessoa desista. O polling de um endpoint de status precisa de um contrato explícito: o que conta como progresso, quando a próxima solicitação pode ocorrer, quando a espera termina e quem decide o que acontece depois.

Já vi verificações de status aparentemente inofensivas se transformarem em centenas de chamadas porque o identificador da tarefa era válido, o endpoint continuava retornando HTTP 200 e ninguém havia informado ao agente que «running» deixava de ser uma resposta aceitável depois de um prazo. A solução não é um agendamento engenhoso. É transformar a espera em uma ação limitada, com evidências e um caminho de escalonamento.

## Um status em execução permite esperar, não agir

Um estado não terminal permite fazer outra observação mais tarde. Ele não autoriza o agente a buscar a saída, iniciar trabalho dependente, repetir o envio original ou ampliar sua autoridade.

Essa distinção importa porque APIs assíncronas costumam retornar uma resposta HTTP bem-sucedida para todos os estados. Uma resposta como esta informa que o endpoint de status funcionou. Ela não informa que a tarefa foi concluída.

```json
{
  "job_id": "exp_71c",
  "state": "running",
  "updated_at": "2025-04-18T10:24:00Z"
}
```

Trate o resultado HTTP e o resultado da tarefa como dois fatos separados. O primeiro responde: «O provedor respondeu a esta solicitação?» O segundo responde: «O fluxo de trabalho pode avançar?» As equipes confundem essas duas coisas o tempo todo. Depois, um agente baixa uma exportação incompleta ou publica um resultado que nunca existiu.

Escreva uma pequena tabela de estados para cada integração com um provedor. Não deduza o significado a partir de um nome de campo como `status`; os provedores usam a mesma palavra para ciclos de vida muito diferentes. Uma tabela útil inclui estas categorias:

- **Estados pendentes** permitem uma nova solicitação de status mais tarde, como `queued`, `running` ou `processing`.
- **Estados de sucesso** permitem a ação seguinte especificamente definida, como recuperar uma URL de resultado.
- **Estados de falha** interrompem a execução e preservam o erro do provedor.
- **Estados de cancelamento e expiração** interrompem a execução sem reenviar a tarefa, a menos que uma pessoa peça isso explicitamente.
- **Estados desconhecidos** interrompem a execução, porque uma integração não pode adivinhar com segurança se `paused`, `awaiting_review` ou um valor recém-adicionado é inofensivo.

A resposta de status também deve ser verificada quanto a contradições. Uma tarefa que informa `succeeded`, mas não tem a referência de resultado necessária, não está pronta para ser consumida. Uma tarefa que informa `running` depois do próprio horário de expiração precisa de escalonamento, não de mais confiança no polling.

Mantenha juntos o ID da tarefa, o contexto da conta do provedor, a impressão digital da solicitação original e os estados terminais esperados. Se o agente perder essa associação, poderá consultar a tarefa errada depois de uma reinicialização ou tratar acidentalmente uma tarefa de uma solicitação anterior como a atual.

## Intervalos fixos criam pressão sincronizada

Um intervalo fixo parece organizado no código e se comporta mal em uma frota. Se cinquenta agentes enviarem trabalho perto do mesmo minuto e cada um consultar a cada dez segundos, eles tendem a atingir o provedor em grupos. Esses grupos continuam depois de uma breve interrupção, porque todos os agentes repetem a tentativa no mesmo relógio.

Use um atraso crescente com jitter. Comece com um atraso curto apenas quando o provedor normalmente terminar rapidamente ou expuser o status atualizado sem demora. Aumente a espera depois de cada resposta pendente, estabeleça um limite e varie o atraso real de cada execução.

Um agendamento prático pode seguir esta regra:

```text
base_delay = 5 seconds
max_delay = 120 seconds
attempt = number of completed polls
raw_delay = min(max_delay, base_delay * 2^attempt)
actual_delay = random value between 50% and 100% of raw_delay
```

A faixa aleatória importa. Uma sequência determinística de 5, 10, 20, 40 e 80 segundos apenas desloca a sincronização para ondas mais espaçadas. O jitter total, em que o valor aleatório pode variar de zero até o limite, também funciona em alguns sistemas. Prefiro um limite inferior para polling de tarefas, porque uma execução que escolhe repetidamente atrasos próximos de zero começa a se parecer com uma tempestade de retries.

Não aplique um agendamento genérico de backoff sem considerar a duração da tarefa. Uma conversão de documento que normalmente termina em um minuto se beneficia de observações iniciais. Uma exportação em lote que informa um tempo estimado de conclusão não deve receber uma chamada de status a cada poucos segundos só porque o agente está ocioso.

Se a API fornecer um campo `next_check_at`, `poll_after_seconds` ou semelhante, trate-o como uma orientação do provedor. Valide o valor antes de aceitá-lo. Rejeite valores negativos, esperas absurdamente longas que ultrapassem o prazo da operação e timestamps que não possam ser interpretados. O agente pode esperar até o horário indicado ou até o próprio prazo, o que ocorrer primeiro.

Um atraso não é uma promessa de que o agente fará uma solicitação exatamente naquele instante. Um processo local pode dormir, reiniciar, perder a conexão ou voltar muito mais tarde. Ao acordar, verifique primeiro se o prazo passou. Não compense intervalos perdidos enviando várias chamadas de status em sequência.

## Um prazo e um orçamento de solicitações detectam falhas diferentes

Toda execução de polling precisa de um prazo de relógio e de um número máximo de solicitações de status. Adicione um limite separado para falhas de transporte se o provedor for remoto ou pouco confiável.

Um prazo controla por quanto tempo o fluxo pode continuar sem resolução. Ele impede que um agente mantenha uma tarefa obsoleta viva durante um fim de semana porque a API ainda informa `queued`. Escolha o prazo com base no impacto comercial do atraso, no período de retenção documentado pelo provedor e no momento depois do qual uma pessoa deve decidir. Não o derive apenas do tempo médio de execução. As médias escondem as tarefas que ficam presas.

Um orçamento de solicitações controla a pressão que o agente coloca sobre o provedor e sobre a credencial usada. Ele detecta um erro de agendamento mesmo quando o tempo passa lentamente e limita o custo quando a API cobra por chamadas. O orçamento deve incluir chamadas de status feitas depois de uma ambiguidade de rede. Se você não contabilizar essas chamadas, um agente poderá consumir a cota do provedor enquanto acredita ter feito apenas algumas tentativas.

Um orçamento de falhas tem uma função mais específica. Conte recusas de conexão, erros de DNS, falhas de TLS e respostas 5xx que impeçam o agente de descobrir o estado da tarefa. Um único timeout não prova que a tarefa falhou. Repetir a mesma solicitação com erro durante uma hora não prova paciência.

Use um registro de operação semelhante a este, armazenado de forma persistente antes do primeiro polling:

```json
{
  "operation_id": "report-export-2025-04-18-01",
  "provider_job_id": "exp_71c",
  "started_at": "2025-04-18T10:20:00Z",
  "deadline_at": "2025-04-18T11:00:00Z",
  "max_status_requests": 12,
  "max_transport_failures": 3,
  "status_requests_used": 0,
  "transport_failures_used": 0,
  "last_known_state": "queued"
}
```

Os valores são exemplos, não padrões para todos os provedores. Doze verificações em quarenta minutos podem servir para uma exportação lenta. Seria absurdo para uma operação que normalmente termina em três segundos e perigoso para outra que o provedor pede para consultar uma vez a cada quinze minutos.

Verifique o prazo antes da solicitação, não apenas depois dela. Caso contrário, um processo que acorde tarde poderá fazer uma chamada extra não autorizada. Verifique o orçamento imediatamente antes de agendar e incremente-o imediatamente antes da transmissão. Essa ordem importa quando um processo falha entre o agendamento e o envio. É melhor ter uma reserva ocasionalmente não usada do que uma solicitação extra invisível.

## As condições de parada precisam ser executáveis, não aspiracionais

«Pare se demorar demais» é uma observação para uma pessoa, não uma condição que um agente possa impor. Defina as condições de parada como predicados sobre o registro e a resposta atuais.

Interrompa com sucesso apenas quando o provedor informar um estado terminal de sucesso aceito e todos os campos necessários para a próxima ação passarem pela validação. Se a próxima ação baixar um artefato, valide a referência do artefato antes de declarar sucesso. Se a próxima ação afetar outro sistema, registre a resposta terminal antes de realizá-la.

Interrompa com falha quando o provedor informar uma falha terminal, quando a resposta não puder ser interpretada ou quando um estado estiver fora da lista permitida pela integração. O tratamento de estados desconhecidos merece a mesma seriedade de uma negação de autorização. Um provedor pode adicionar um estado `needs_payment`, `manual_review` ou `blocked` sem aviso. Adivinhar que isso significa «esperar» transforma uma mudança de software do lado deles em um loop infinito do seu lado.

Interrompa por timeout quando o prazo chegar, mesmo que o status tenha acabado de mudar. O agente deve informar o último estado observado, mas não deve conceder a si mesmo outro atraso completo só porque viu algo que parece progresso. Se uma espera mais longa for aceitável, transforme-a em uma nova decisão de autorização, com um novo prazo.

Interrompa por esgotamento do orçamento quando a próxima solicitação de status ultrapassar a quantidade permitida. Não reinicie a contagem apenas porque o agente reiniciou, mudou de sessão ou recebeu um novo prompt. O provedor vê um único chamador e uma única tarefa, não os limites internos do seu processo.

Interrompa com entrega ambígua depois que uma solicitação de status sofrer timeout e o orçamento de falhas estiver esgotado. O agente não pode saber se o provedor recebeu a solicitação, mas as solicitações de status devem ser seguras para repetir. Se o endpoint mudar o estado, cobrar dinheiro ou atualizar um artefato a cada GET, ele não é um endpoint de status no sentido operacional. Trate-o como uma ação e exija controles mais fortes.

É aqui que as equipes cometem um erro caro: presumem que GET significa inofensivo. O HTTP define GET como um método seguro no sentido semântico, ou seja, o cliente não solicitou uma mudança de estado. Isso é um contrato que o servidor deve respeitar, não uma propriedade mágica de uma URL. Verifique o comportamento do provedor com uma conta de teste e consulte a documentação antes de classificar uma chamada como observacional.

## Respeite os sinais de protocolo que os provedores já enviam

O HTTP tem alguns sinais que devem mudar imediatamente o comportamento de polling de um agente. Ignorá-los porque o loop tem seu próprio temporizador é uma atitude ruim com o provedor e normalmente torna a recuperação mais lenta.

A RFC 9110 define `Retry-After` como uma indicação do tempo mínimo que um cliente deve esperar antes de fazer uma nova solicitação. O campo pode conter um atraso em segundos ou uma data HTTP. Interprete os dois formatos. Para uma solicitação de status que receba 503 com `Retry-After: 120`, não use seu backoff normal de trinta segundos e tente novamente antes da hora. Espere pelo menos dois minutos, desde que o prazo da operação permita.

A RFC 6585 define HTTP 429, Too Many Requests, e informa que uma resposta pode incluir `Retry-After`. Essa formulação permite que os provedores omitam o cabeçalho, por isso o agente ainda precisa do próprio backoff. Um 429 sem orientação deve aumentar bastante o atraso e consumir o orçamento de falhas ou de rate limit definido por você. Uma execução que continua recebendo 429 deve ser escalonada. Ela não deve esticar o temporizador indefinidamente.

Para 202 Accepted, examine o corpo e os cabeçalhos da resposta em busca de uma localização, um ID de tarefa e o recurso de status indicado. Não construa uma URL presumida a partir do endpoint de envio. Alguns provedores usam uma localização de resultado, outros uma localização de status e outros devolvem a representação final mais tarde. Siga o contrato documentado.

Para 404, não presuma sempre que a tarefa nunca existiu. Uma tarefa recém-enviada pode estar sujeita a consistência eventual, e uma tarefa concluída pode desaparecer depois que a retenção expirar. O contrato original do provedor determina qual explicação é plausível. Se o comportamento documentado não explicar o caso, classifique-o como falha de integração e escale com o ID da tarefa e os timestamps.

Para 401 ou 403, pare o polling. Repetir falhas de autorização com a mesma credencial adiciona ruído e pode acionar as defesas do provedor. A ação humana é verificar acesso, rotação da credencial, escopo da conta ou configuração do gateway. A ação de polling terminou.

Para respostas 5xx e falhas de rede, use o orçamento de falhas de transporte. Preserve o código da resposta, o horário da solicitação e qualquer ID de solicitação do provedor. Esses detalhes importam quando o suporte ou uma pessoa responsável precisa distinguir uma resposta perdida de uma falha da tarefa.

## O escalonamento deve pedir uma decisão, não despejar um log

Um agente precisa de um ponto definido em que pare de esperar e entregue a situação a uma pessoa ou a outro fluxo explicitamente autorizado. Escalonamento não é uma notificação decorativa enviada depois que o agente já repetiu todas as possibilidades.

Torne o motivo do escalonamento legível por máquina. Use categorias como `deadline_exceeded`, `request_budget_exhausted`, `rate_limited`, `unknown_state`, `authorization_denied` ou `provider_failure`. Cada categoria deve determinar a próxima ação permitida. Uma pessoa que veja `unknown_state` pode aprovar uma pausa temporária enquanto alguém verifica mudanças no provedor. Quem veja `authorization_denied` não deve aprovar outra solicitação idêntica.

A mensagem de escalonamento deve conter contexto suficiente para uma decisão sem expor segredos:

```text
External job needs a decision
Operation: report-export-2025-04-18-01
Provider job: exp_71c
Last state: running
Elapsed time: 40 minutes
Status requests: 12 of 12
Last HTTP result: 200 at 10:58 UTC
Stopped because: request_budget_exhausted
Safe options: extend waiting once, cancel at provider, inspect provider console
```

Não formule a escolha como «continuar?». Isso convida a pessoa responsável a aprovar um loop indefinido. Ofereça opções limitadas. «Estender por quinze minutos com quatro verificações adicionais» informa o custo e cria um novo limite. «Cancelar no provedor» só deve aparecer se a integração tiver uma ação de cancelamento documentada e a pessoa compreender seus efeitos.

Escale mais cedo quando o resultado final tiver uma janela curta de utilidade. Uma validação de implantação que chegue depois do fim da janela de implantação talvez não mereça mais polling. Já uma exportação de documento fiscal pode justificar a espera durante um incidente do provedor. O estado técnico sozinho não decide essa prioridade, então registre separadamente o prazo comercial do fluxo quando ele for diferente do prazo da tarefa no provedor.

Evite reenviar automaticamente depois de um timeout, a menos que o provedor ofereça idempotência e você persista o token de idempotência. Uma falha de polling não prova que a tarefa original falhou. Reenviar pode criar faturas, e-mails, implantações ou exportações duplicadas. As pessoas chamam isso de «autocorreção» até o momento em que precisam limpar a bagunça.

## Um controlador de polling precisa de estado persistente e um único responsável

Um controlador de polling confiável persiste seu registro e garante que apenas um worker seja responsável por uma tarefa de cada vez. Sem essas duas propriedades, a recuperação após reinicialização e os agentes paralelos gerarão verificações duplicadas ou ações posteriores contraditórias.

O responsável pode ser um lease de processo, um lock de banco de dados ou outro mecanismo persistente adequado ao seu ambiente. O mecanismo deve expirar se o worker morrer, e um novo responsável deve carregar o registro completo antes de enviar qualquer coisa. Um booleano apenas em memória não representa propriedade quando existe um segundo processo de agente.

Persista depois de cada evento relevante: envio da tarefa, agendamento da próxima verificação, envio da solicitação de status, recebimento da resposta, mudança de estado e escalonamento. Você não precisa de um diário separado para cada detalhe de depuração, mas precisa recuperar os fatos que afetam autoridade e orçamentos.

Este pseudocódigo mostra a ordem que evita a maioria dos loops acidentais:

```text
load operation
if operation is terminal or escalated:
    exit
if current_time >= operation.deadline_at:
    record timeout and escalate
    exit
if operation.status_requests_used >= operation.max_status_requests:
    record budget exhaustion and escalate
    exit
if current_time < operation.next_poll_at:
    schedule wakeup and exit

acquire ownership lease
reload operation
increment status_requests_used and persist
send one status request
persist response metadata

if response has terminal success and required result fields are valid:
    record success
else if response has terminal failure or unknown state:
    record stop reason and escalate
else if response requires waiting:
    calculate next_poll_at with provider guidance, backoff, and jitter
    persist next_poll_at
else:
    record integration failure and escalate
```

O segundo carregamento depois de adquirir a propriedade é deliberado. Outro worker pode ter terminado a tarefa enquanto este aguardava o lease. Se você ignorar essa etapa, acabará vendo dois agentes recuperar ou publicar o mesmo resultado.

Não mantenha um lease enquanto dorme. Mantenha-o apenas enquanto atualiza o registro e envia a única solicitação, se o seu modelo de propriedade permitir isso com segurança. Um lease mantido durante toda a duração de uma tarefa externa lenta se transforma em um problema de órfãos quando um laptop entra em suspensão ou um processo morre.

Um endpoint de status deve permanecer somente leitura do ponto de vista do agente. Mantenha a recuperação da saída, o cancelamento e a publicação downstream como ações separadas, cada uma com seus próprios registros. Combinar tudo dentro de uma única função de polling é como um temporizador inofensivo se transforma em um mecanismo de fluxo de trabalho oculto.

## A aprovação humana pertence ao limite de escalonamento

A maioria dos pollings rotineiros não precisa de um clique humano se permanecer dentro da operação originalmente aprovada, do escopo da credencial, do prazo e do orçamento de solicitações. Exigir aprovação para cada leitura ensina as pessoas a aprovar sem ler e atrasa o trabalho sem ganho de segurança.

A aprovação é necessária quando o agente pede uma nova autoridade: mais tempo que o prazo original, um orçamento maior de solicitações, uma chamada de cancelamento, um reenvio, outra credencial ou o uso de uma saída que falhou na validação. Essas são mudanças na operação, não observações comuns.

O Sallyport pode manter a credencial da API fora de um agente compatível com MCP enquanto o aplicativo realiza a chamada de status e devolve o resultado. Sua configuração de chave por chamada serve para operações em que cada uso de uma determinada credencial, inclusive uma solicitação de status, precisa de aprovação explícita, mas essa configuração não corrige um design de polling sem limites.

Escolha o modelo de aprovação com base na consequência. Uma chamada de status de baixo risco pode permanecer dentro de uma autorização por sessão. Uma API de status privilegiada que revele metadados sensíveis da tarefa pode exigir aprovação por chamada ou uma credencial mais restrita. Em qualquer caso, defina os limites do controlador antes de decidir como autorizar uma solicitação.

Não permita que um prompt decida se uma extensão de prazo é inofensiva. A solicitação deve informar o estado atual, o tempo decorrido, as chamadas tentadas, o orçamento adicional proposto e o efeito esperado de continuar. Assim, a pessoa responsável pode rejeitar uma extensão que perderia uma janela de lançamento ou produziria dados desatualizados.

## Audite a decisão de esperar, não apenas a solicitação

Um log de solicitações informa que um agente chamou `/jobs/exp_71c`. Ele não informa se a chamada era permitida pelo plano de polling, se o agente ignorou `Retry-After` ou se a tarefa já havia ultrapassado o prazo.

Registre a decisão do controlador junto de cada chamada: estado atual, próximo horário agendado, origem do atraso, quantidade de solicitações, prazo, código de resposta, estado interpretado da tarefa e decisão resultante. A origem do atraso pode ser `provider_retry_after`, `provider_poll_hint`, `local_backoff` ou `manual_extension`. Esse pequeno campo economiza muita discussão depois de um incidente.

Uma sequência de auditoria útil parece uma história:

```text
10:20:00 submitted job exp_71c, deadline 11:00:00, budget 12
10:20:05 polled, state queued, next poll 10:20:14 from local backoff
10:20:14 polled, state running, next poll 10:20:31 from local backoff
10:20:31 received 503, Retry-After 120, next poll 10:22:31 from provider guidance
10:22:31 polled, state running, next poll 10:23:48 from local backoff
10:58:00 polled, state running, request budget exhausted, escalated
```

Esse registro torna evidente um controlador ruim. Se os timestamps mostrarem uma solicitação por segundo apesar do atraso indicado pelo provedor, você terá a prova. Se um agente afirmar que uma tarefa expirou, mas o estado final era `succeeded`, terá algo concreto para corrigir.

O Sallyport projeta sessões de agentes e chamadas individuais a partir de um log de auditoria criptografado e encadeado por hash, e `sp audit verify` pode verificar essa cadeia offline sobre o texto cifrado. Esse tipo de evidência é mais útil quando o próprio registro da operação explica por que cada chamada observada aconteceu.

Não avalie a qualidade do polling pelo fato de as tarefas terminarem algum dia. Avalie se cada tarefa chega a um resultado terminal ou a um escalonamento limitado, se os agentes respeitam as instruções de espera do provedor e se uma pessoa consegue reconstruir uma decisão contestada. A primeira mudança que eu faria em um loop sem controle é simples: persistir um prazo e um orçamento de solicitações antes que o agente faça sua primeira chamada de status.
