Timeouts de ferramentas de agentes: planos de recuperação que evitam repetições
Timeouts de ferramentas de agentes exigem um plano de recuperação que separe solicitações falhas de trabalhos concluídos, com tentativas idempotentes, reconciliação e evidências de auditoria.

Um timeout é uma observação sobre o cliente, não um veredito sobre o trabalho. O chamador desistiu de esperar. É só isso que ele sabe. Quando um agente transforma essa observação em «falha» e repete uma chamada que altera o estado, pode criar um segundo pagamento, uma segunda implantação, um segundo ticket de suporte ou um comando remoto executado duas vezes em uma máquina que já está sob pressão.
Timeouts de ferramentas de agentes exigem um plano de recuperação porque os agentes agem mais rápido que as pessoas que os supervisionam e tendem a tratar a saída das ferramentas como verdade absoluta. Uma resposta ausente não é verdade absoluta. O caminho de recuperação precisa decidir se deve tentar novamente, esperar, consultar evidências ou parar e pedir uma decisão humana. Crie esse caminho antes de dar ao agente permissão para fazer chamadas importantes.
Um timeout deixa três históricos plausíveis
Depois de um timeout do cliente, a solicitação pertence a um de três históricos gerais: o serviço nunca a recebeu; o serviço a recebeu e ainda não terminou; ou o serviço terminou, mas o cliente nunca recebeu o resultado. Falhas de rede podem ocorrer antes de uma conexão ser aberta, enquanto o corpo da solicitação viaja, enquanto o serviço trabalha ou enquanto a resposta volta. O mesmo tipo de exceção pode abranger os quatro casos.
Essa distinção muda a próxima ação. Se uma consulta DNS falhou antes de qualquer conexão, uma nova tentativa pode fazer sentido. Se o serviço aceitou uma solicitação para excluir um recurso e a resposta desapareceu, repetir pode executar novamente uma operação destrutiva. Se o serviço colocou um trabalho assíncrono na fila, uma segunda submissão pode criar um trabalho concorrente enquanto o primeiro ainda está em andamento.
O HTTP não oferece um indicador mágico que diga ao cliente qual desses históricos ocorreu. A RFC 9110 descreve os métodos de solicitação e o significado das respostas, incluindo a distinção entre métodos seguros e idempotentes. Ela não promete que um cliente consiga deduzir a execução no servidor a partir de uma resposta perdida. Essa limitação é física, não uma opção ausente do SDK.
As equipes costumam misturar duas perguntas diferentes:
- Esta solicitação pode ser enviada novamente sem mudar o estado final pretendido?
- A tentativa original realmente chegou ao serviço e o afetou?
A idempotência responde à primeira pergunta. A reconciliação responde à segunda. Um sistema precisa das duas. Um PUT idempotente pode ser repetido com segurança, mas um timeout ainda deixa você sem saber se o trabalho posterior acionado por essa solicitação terminou. Uma consulta de status pode estabelecer o resultado, mas não impede trabalho duplicado se o serviço aceitar duas criações indistinguíveis.
Os agentes precisam dessa distinção em seus contratos de ferramentas. Um erro de texto simples, como request timed out, convida à improvisação. Um resultado estruturado que informa outcome: unknown diz ao agente que ele precisa sair do caminho de repetição e entrar no caminho de busca de evidências.
Mapeie o caminho da solicitação antes de escolher uma tentativa
Um bom plano de recuperação identifica os limites onde pode haver evidências. Comece pelo processo do agente, depois passe pelo wrapper da ferramenta, pelo pool de conexões, pelo gateway ou proxy, se houver, pela entrada do serviço, pela aplicação, pelo armazenamento persistente e por qualquer worker que trate o trabalho assíncrono. Um timeout em um limite não diz nada confiável sobre o limite seguinte.
Considere uma chamada para criar uma implantação. O agente envia uma solicitação por meio de uma ferramenta. O cliente escreve o corpo completo da solicitação, o servidor confirma um registro de implantação e, em seguida, a conexão é interrompida antes que a resposta chegue ao cliente. A ferramenta emite um timeout. O agente repete a solicitação. Agora o servidor tem dois registros de implantação, cada um válido do seu próprio ponto de vista.
Agora mude um detalhe: o cliente sofre timeout enquanto envia o corpo, e o servidor rejeita o corpo incompleto antes que o código da aplicação seja executado. A mesma ferramenta ainda pode retornar timeout. Nesse caso, uma nova tentativa pode criar exatamente uma implantação. O chamador não consegue distinguir esses casos apenas pelo timeout.
Registre as evidências que cada componente pode produzir. Para uma ação HTTP típica, isso inclui:
- Horários do cliente, destino escolhido, resumo criptográfico do corpo da solicitação e um ID de operação gerado pelo chamador.
- Registros de acesso do serviço que mostram se a entrada aceitou a solicitação.
- Um registro da aplicação que armazena o ID da operação com um resultado confirmado.
- Registros de workers ou filas para ações que continuam depois da solicitação síncrona.
- Um endpoint de leitura que retorna o estado atual ou o status da operação.
Não faça dos registros de rede sua única fonte de verdade. Um registro do balanceador de carga pode mostrar que os bytes chegaram, mas não prova que a transação do banco foi confirmada. Um registro do banco pode provar a confirmação, mas talvez não prove que um provedor externo recebeu um efeito posterior. O registro autoritativo precisa corresponder à ação que você está tentando comprovar.
Para o envio de um e-mail, o identificador da mensagem aceita pelo provedor é uma evidência mais forte que o registro da aplicação dizendo «prestes a enviar». Para uma migração de banco de dados, uma tabela de migrações ou um registro de transação é mais forte que um código de saída de processo de shell que o chamador nunca recebeu. Para criar um recurso na nuvem, uma URL de operação ou uma etiqueta de recurso com um ID gerado pelo chamador é melhor que uma solicitação de criação repetida.
A idempotência precisa pertencer à operação, não à tentativa
Um esquema de repetição só funciona quando todas as tentativas de uma mesma ação pretendida carregam o mesmo identificador persistente. Gere o identificador antes da primeira chamada de rede. Persista-o junto com a descrição da ação. Reutilize-o depois de reiniciar o processo, reiniciar a ferramenta ou transferir o caso para um operador humano.
Não gere um novo UUID dentro de um loop de tentativas. Esse padrão parece cuidadoso em uma revisão de código, mas destrói todo o propósito. O servidor vê cada tentativa como uma nova solicitação, exatamente como surgem as operações duplicadas.
Uma solicitação pode carregar um valor de idempotência em um cabeçalho ou em um campo do corpo, dependendo da API. O detalhe do transporte importa menos que a regra do servidor. O servidor precisa associar atomicamente esse valor à operação e ao resultado. Se duas solicitações idênticas chegarem ao mesmo tempo, o servidor deve serializá-las ou fazer uma delas observar a outra. Um cache que expira antes que tentativas atrasadas terminem não oferece proteção confiável contra duplicatas.
Uma solicitação HTTP prática poderia ser assim:
POST /deployments HTTP/1.1
Content-Type: application/json
Idempotency-Key: op_7d5d4d8e4e5a
X-Correlation-ID: run_42_task_9
{"repository":"api","revision":"a1b2c3d4","environment":"staging"}
O servidor deve armazenar o valor de idempotência com uma impressão digital dos campos relevantes da solicitação e o ID da implantação ou operação resultante. Se o mesmo valor chegar com uma revisão ou ambiente diferente, rejeite-o. Retornar o primeiro resultado para uma carga diferente aplica silenciosamente a intenção errada.
Para uma operação assíncrona, devolva uma referência persistente à operação assim que o servidor aceitar o trabalho:
{
"operation_id": "dep_1842",
"state": "accepted",
"status_url": "/operations/dep_1842"
}
Depois de um timeout, o agente consulta op_7d5d4d8e4e5a ou dep_1842 antes de considerar outra submissão. Se a API não oferecer um valor de idempotência nem uma consulta por referência externa, classifique a escrita como ambígua por definição. Isso pode ser aceitável para um recurso de teste descartável. É uma escolha ruim para uma ação autônoma que gera custos ou altera o estado de produção.
Não considere um método seguro apenas porque ele usa POST com uma biblioteca de repetição. Os nomes dos métodos HTTP são indicações da semântica pretendida, não uma proteção contra uma implementação do servidor que duplica o trabalho. Leia a documentação específica da API e teste você mesmo o comportamento diante de duplicatas.
Dê aos agentes um estado explícito de resultado desconhecido
Uma ferramenta que pode afetar o mundo externo não deve retornar apenas success ou error a um agente. Ela precisa de um terceiro resultado: unknown. Esse estado impede o comportamento mais prejudicial dos modelos, que é tratar um histórico incompleto como autorização para tentar uma versão ligeiramente diferente do mesmo comando.
Use um contrato de resultado que registre a fase que falhou, reconhecendo que essa fase pode ser incerta. Por exemplo:
{
"outcome": "unknown",
"operation_id": "op_7d5d4d8e4e5a",
"correlation_id": "run_42_task_9",
"transport_observation": "response deadline exceeded after request write",
"retry_allowed": false,
"reconcile": {
"method": "GET",
"path": "/operations/by-id/op_7d5d4d8e4e5a"
}
}
O campo retry_allowed precisa vir da definição da ferramenta ou da ação, não de um palpite do agente sobre verbos em inglês. Um agente não pode concluir com segurança que create_release é inofensivo porque o destino é um ambiente de staging. Uma implantação em staging ainda pode enviar notificações, consumir uma cota compartilhada ou alterar um canal de release.
Faça o agente seguir uma sequência de recuperação limitada:
- Preserve o objetivo da ação, o ID da operação, o destino e a observação sobre o timeout no registro da execução.
- Consulte a fonte de status autoritativa usando o mesmo ID de operação ou uma referência fornecida pelo serviço.
- Continue somente diante de um resultado terminal confirmado. Tente novamente apenas se a definição da ação permitir e a fonte de status mostrar que nenhuma operação foi aceita.
- Pare e apresente as evidências quando o serviço não conseguir estabelecer o resultado dentro do prazo de recuperação da ação.
A condição de parada importa. Um agente que consulta indefinidamente consome atenção e pode manter uma tarefa ativa muito depois de seu propósito original desaparecer. Um agente que tenta cinco variações de uma solicitação de escrita pode criar um projeto de limpeza para outra pessoa. Dê a cada operação um prazo de recuperação separado do prazo da solicitação.
Uma aprovação humana, por si só, não resolve um resultado desconhecido. A aprovação responde «este chamador pode tentar esta ação?». Ela não responde «a tentativa anterior teve sucesso?». Mantenha as evidências de autorização e execução separadas tanto na interface quanto nos registros.
Timeouts de leitura merecem tratamento diferente das escritas
Um timeout de leitura costuma trazer menos risco de duplicação, mas ainda pode levar o agente a decisões erradas. Um agente pode sofrer timeout ao listar recursos, receber em outro lugar uma resposta incompleta ou desatualizada do cache e concluir que um recurso não existe. Em seguida, tenta criá-lo e entra em conflito com a realidade.
Classifique as leituras pela decisão que elas apoiam. Uma atualização inofensiva de painel pode ser repetida com recuo limitado. Uma leitura usada para decidir se uma escrita deve ser feita precisa de uma regra explícita de consistência. Se o serviço oferecer uma ETag, versão, número de geração ou endpoint de status de leitura após escrita, use-o. Se ele oferecer apenas consistência eventual, deixe visíveis ao agente o período de espera e a condição de falha.
Evite uma quantidade universal de tentativas. Uma consulta curta de metadados pode tolerar duas tentativas rápidas. Uma consulta de relatório que carrega um data warehouse pode precisar de um único prazo longo e nenhuma repetição imediata. Uma chamada que retorna 429 Too Many Requests ou um valor explícito de nova tentativa do serviço precisa de um tratamento diferente do timeout de socket. Tratar todo erro como um problema temporário de rede é como um agente transforma uma interrupção parcial em carga evitável.
O recuo ajuda a proteger os serviços, mas não resolve a ambiguidade. Ele apenas espaça as solicitações duplicadas. Combine o recuo com um valor de idempotência ou uma consulta de status para toda escrita importante.
Use escritas condicionais quando a API oferecer esse recurso. Uma solicitação If-Match com uma ETag conhecida pode impedir que um agente sobrescreva um recurso que mudou depois da leitura. Uma criação que aceite um ID de recurso escolhido pelo cliente pode fazer as tentativas convergirem para um único objeto. Esses mecanismos protegem transições de estado, mas não substituem um registro que mostre se efeitos externos a esse recurso ocorreram.
O SSH esconde a execução remota atrás de um único fluxo interrompido
A recuperação de timeouts SSH exige ainda mais cautela que a recuperação HTTP. Uma sessão SSH perdida pode ocorrer depois que o host remoto inicia um comando, durante a transferência da saída, depois que o comando termina ou enquanto um processo filho continua depois que o pai se desconecta. Uma mensagem do shell local não consegue dizer qual desses casos ocorreu.
O padrão perigoso é um comando composto longo:
ssh deploy@host 'download-release \u0026\u0026 migrate-db \u0026\u0026 restart-service'
Se a conexão cair depois de migrate-db, repetir o comando completo pode executar as migrações duas vezes ou reiniciar um serviço cuja nova versão nunca terminou de ser baixada. O histórico do terminal reuniu várias transições de estado em um único resultado opaco.
Divida o trabalho remoto em operações com marcadores persistentes e verificáveis. Uma implantação pode registrar um ID de release antes de começar, armazenar as versões das migrações no banco e expor a revisão ativa por meio de um comando de status local. Uma ferramenta de recuperação se reconecta e consulta esses marcadores antes de fazer qualquer outra coisa.
Por exemplo, um agente pode usar um comando de status remoto cuja saída seja preparada para máquinas, não para pessoas:
ssh deploy@host '/usr/local/bin/release-status --json'
{
"release_id": "rel_202",
"phase": "migrated",
"active_revision": "9f24c1",
"migration_version": "20250308_02"
}
Agora a ação de recuperação tem uma base para decidir. Se phase for migrated, não execute as migrações novamente. Se o host não informar nenhum release_id, o agente só poderá iniciar a operação se o comando remoto garantir que essa ausência significa que nenhuma execução anterior ocorreu. Se o SSH não conseguir se reconectar, o resultado continua desconhecido. Não o substitua por uma nova tentativa esperançosa quando a ação altera um host de produção.
Use bloqueios remotos com cuidado. Um bloqueio pode impedir execuções simultâneas, mas um bloqueio antigo depois de uma falha do host pode impedir a recuperação. Coloque o ID da operação e uma política de expiração no registro do bloqueio e permita sua inspeção sem excluí-lo às cegas. Um comando de limpeza que remove todos os bloqueios antigos também é uma operação que altera o estado e precisa de suas próprias evidências.
O Sallyport pode manter as credenciais SSH fora do processo do agente enquanto seu utilitário sp-ssh incluído executa a conexão, mas o isolamento das credenciais não torna segura a repetição de uma sessão interrompida. O comando remoto ainda precisa de um ID de operação, marcadores persistentes e um caminho de reconciliação.
Registros de auditoria ajudam na investigação, mas não provam a conclusão
Um registro de ação deve preservar detalhes suficientes para reconstruir a intenção e a recuperação sem armazenar segredos. Registre a sessão do chamador, o horário, a identidade do destino, o ID da operação, o resumo da solicitação ou o modelo do comando, o evento de autorização, o resultado do transporte e o resultado final reconciliado. Não registre tokens de portador, chaves privadas nem corpos brutos de solicitações que possam conter credenciais ou dados pessoais.
Separe uma ação tentada de uma ação concluída. Uma linha que diz POST /deployments timeout é um registro de tentativa. Uma consulta posterior que retorna uma operação no estado succeeded é evidência de conclusão. Mantenha os dois. Substituir o primeiro registro por um sucesso final apaga a parte mais útil do incidente: o período em que o chamador não sabia o que havia acontecido.
A evidência contra adulteração importa quando uma execução do agente provoca uma disputa posterior. Você precisa responder qual processo fez a chamada, o que ele estava autorizado a fazer, se recebeu um resultado e como a equipe estabeleceu o estado final. Uma tabela de atividades mutável é fácil de consultar, mas oferece evidências fracas se um processo comprometido puder reescrever o histórico.
O Sallyport registra sessões de agentes e chamadas individuais em um registro de auditoria criptografado e encadeado por hash, que não permite escrita, e sp audit verify pode verificar a cadeia offline sobre o texto cifrado. Esse registro pode mostrar a ação do gateway e o histórico do chamador, enquanto o serviço ou host remoto continua sendo a autoridade sobre a conclusão do trabalho pretendido.
Não confunda um evento de auditoria do gateway com uma transação da aplicação. Se o gateway registrou uma solicitação de saída, ela ainda pode ter falhado antes de o serviço confirmá-la. Se o serviço a confirmou, o gateway talvez nunca tenha visto a resposta. A investigação funciona quando os registros dos dois lados compartilham um ID de operação ou de correlação.
Teste a ambiguidade de propósito antes que uma interrupção faça isso por você
Um plano de timeout que nunca enfrentou uma resposta perdida é apenas uma suposição. Teste a falha exata em que o serviço conclui a operação, mas o cliente perde o resultado. Esse é o caso que muitas equipes ignoram porque os dublês comuns de teste não conseguem expressá-lo.
Crie um endpoint de teste ou um fixture de proxy que aceite uma solicitação, confirme seu registro persistente e depois atrase ou descarte a resposta. Envie o mesmo ID de operação duas vezes. Verifique se o serviço retorna uma única operação lógica, se o agente consulta o status depois do timeout e se o histórico de auditoria preserva as duas tentativas e o resultado da reconciliação.
Depois teste o caso oposto: interrompa a solicitação antes que o serviço a aceite. Confirme que a recuperação só pode tentar novamente depois de não encontrar nenhum registro da operação. Os dois testes podem produzir a mesma exceção no cliente. O contrato da ferramenta precisa levar a ações diferentes porque as evidências do serviço são diferentes.
Teste também estes casos:
- O serviço aceita a operação, mas o worker continua pendente além do prazo de recuperação do agente.
- Dois processos de agentes enviam o mesmo ID de operação quase ao mesmo tempo.
- O endpoint de status fica indisponível enquanto o endpoint de escrita principal continua saudável.
- Um comando SSH inicia um processo filho e depois a conexão termina antes da saída final.
- Uma pessoa retoma uma execução pausada depois que outro operador já reconciliou a ação.
O último caso identifica um problema que aparece nas operações reais: o estado da recuperação precisa viver fora da memória conversacional do agente. Armazene o ID da operação e a descoberta atual em um registro persistente de execução. Um agente reiniciado deve ler esse registro e continuar a reconciliação, em vez de inventar uma ação nova porque não consegue ver a conversa anterior.
Configure alertas para resultados desconhecidos que ultrapassem seus prazos de recuperação. Não alerte a cada primeiro timeout se tentativas comuns resolvem leituras seguras. Alerte quando uma escrita importante não tiver um estado terminal confirmado, quando IDs de operação duplicados carregarem cargas diferentes ou quando marcadores remotos contradisserem a progressão esperada. Esses são os casos que precisam de uma pessoa antes que o agente continue.
Um plano de recuperação deve tornar a recusa algo normal
O comportamento mais forte diante de um timeout costuma ser a recusa: «Não consigo confirmar se a solicitação de implantação foi concluída, por isso não enviarei outra». Isso não é uma falha da ferramenta. É a resposta correta diante da falta de evidências em torno de uma ação irreversível ou cara.
Torne essa resposta útil. Mostre o ID da operação, o destino, o último estado confirmado, os horários e a consulta de status exata ou a inspeção remota que resolveria a dúvida. Se não existir uma consulta autoritativa, diga isso claramente e encaminhe a decisão a alguém que entenda as consequências de uma duplicação.
As equipes resistem a isso porque uma nova tentativa parece produtiva e uma pausa parece lenta. Depois de duplicações suficientes e implantações pela metade, a troca fica clara. Um minuto gasto na reconciliação custa menos que descobrir que dois sistemas acreditam ter realizado a única ação que o agente deveria executar.
Comece pelas escritas que movimentam dinheiro, enviam mensagens externas, fazem releases, alteram acessos ou excluem dados. Para cada uma, exija três respostas do responsável pela API: qual identificador vincula as tentativas a uma única operação, onde o chamador pode consultar o resultado e que prova existe quando a conexão cai. Se alguma resposta estiver faltando, faça a ferramenta retornar unknown e exija uma decisão humana deliberada, em vez de ensinar o agente a adivinhar.
FAQ
Um timeout de API significa que a solicitação falhou?
Um timeout apenas informa que o cliente parou de esperar antes de receber uma resposta utilizável. O serviço pode nunca ter recebido a solicitação, ainda pode estar trabalhando nela ou pode tê-la concluído, mas perdido a resposta no caminho de volta. Considere o resultado desconhecido até reconciliá-lo com evidências do lado do serviço.
Quando é seguro um agente tentar novamente depois de um timeout?
Faça uma nova tentativa automática somente quando a operação puder ser repetida com segurança ou quando o serviço aceitar um token de idempotência que vincule todas as tentativas à mesma operação lógica. Solicitações somente de leitura costumam ser seguras, mas até elas podem aumentar a carga durante uma interrupção. Para mudanças de estado, faça a reconciliação primeiro, a menos que a API documente como trata duplicatas.
Como saber se uma solicitação que atingiu o tempo limite foi concluída?
Use um ID de operação exclusivo, gerado antes da primeira tentativa, e armazene-o com a ação e o destino pretendidos. Consulte o serviço usando esse ID ou verifique um endpoint de status da operação, um registro de auditoria ou o estado do objeto. O contador de tentativas do cliente não é uma prova, porque outro processo do agente pode fazer a próxima tentativa.
O que é uma chave de idempotência e por que ela importa?
Uma chave de idempotência é um valor estável que representa uma única mudança de estado pretendida, como criar uma fatura ou enviar uma solicitação de implantação. O cliente envia o mesmo valor novamente depois de um timeout, e o serviço devolve o resultado original em vez de executar a ação duas vezes. Isso só funciona quando o servidor persiste e aplica essa associação.
Posso repetir com segurança um comando SSH depois que a conexão cai?
Não repita cegamente um comando SSH que altera o estado remoto. Primeiro reconecte-se e verifique um marcador persistente, como um ID de release, um registro de transação, uma versão de pacote ou um arquivo de status específico do comando. Uma conexão SSH interrompida diz muito pouco sobre a conclusão do processo remoto.
Que timeout um agente de IA deve usar para ferramentas de API?
Passe um prazo para a ferramenta, mas mantenha esse prazo menor que o orçamento total da tarefa do agente, para que ainda haja tempo de reconciliar e relatar o resultado. O valor correto depende da operação e da latência normal dela. Usar o mesmo timeout para leituras, implantações e migrações de banco de dados é uma má prática.
Os agentes devem usar IDs de solicitação ou IDs de correlação?
Use o ID de operação do servidor quando ele existir e inclua também seu próprio ID de correlação nas solicitações e nos registros. O ID do servidor ajuda a consultar aquele serviço; seu ID conecta o plano do agente, a aprovação, as tentativas e a investigação posterior. Não trate um cabeçalho de rastreamento como garantia de idempotência, a menos que a API diga que ele tem essa função.
Como um agente deve informar um resultado desconhecido ao usuário?
Retorne um resultado estruturado de estado desconhecido, em vez de declarar sucesso ou falha. Inclua o ID da operação, o destino, o último estado de transporte conhecido e a ação precisa de reconciliação necessária. O agente deve pausar um fluxo importante quando não conseguir determinar o resultado.
Por que as tentativas automáticas são perigosas para operações de escrita?
Não. As tentativas podem duplicar cobranças, e-mails, tickets, implantações e comandos destrutivos quando a primeira tentativa foi concluída, mas sua resposta se perdeu. As tentativas devem seguir regras específicas da operação, suporte à idempotência e recuo limitado.
O que devo registrar quando uma chamada de ferramenta do agente sofre um timeout?
Mantenha um registro imutável da ação tentada, da identidade da credencial usada, da sessão do solicitante, dos horários e do resultado final da reconciliação. Os registros, sozinhos, não tornam segura uma ação ambígua, mas permitem determinar o que o agente tentou fazer e revogar uma sessão que ainda esteja em execução. Um registro que evidencie adulterações é especialmente útil quando o processo original já terminou.