Um agente de IA que processa pagamentos executa uma cobrança via Stripe e, ao receber um timeout na resposta, decide tentar novamente. O cliente é cobrado duas vezes. Um agente de suporte que envia um e-mail de confirmação interpreta a resposta da tool call como inconclusiva e dispara o mesmo e-mail novamente. O destinatário recebe duas mensagens idênticas em trinta segundos. O problema não é o retry em si, que é comportamento esperado em sistemas distribuídos, e sim a ausência de uma camada que distinga uma nova execução de uma repetição de execução já concluída. Essa camada é a idempotência, e a razão pela qual ela se torna mais urgente em sistemas agênticos é que agentes LLM repetem tool calls com uma frequência significativamente maior do que clientes HTTP convencionais, motivados por timeouts, erros de validação ou incerteza do próprio modelo sobre o resultado obtido.
Já vi isso acontecer em produção mais de uma vez, e o padrão se repete: agentes amplificam o problema da duplicação de forma estrutural. Um cliente HTTP convencional repete uma chamada quando recebe um erro de rede ou um status 5xx, e a lógica de retry é determinística: mesma URL, mesmo payload, mesmo header. O agente opera num regime diferente. Quando a resposta de uma tool call não satisfaz o critério interno de sucesso do modelo, o agente pode reformular a chamada com parâmetros ligeiramente diferentes, pode chamar a mesma ferramenta duas vezes em sequência com argumentos que diferem apenas em formatação, ou pode invocar uma segunda ferramenta cujo efeito colateral se sobrepõe ao da primeira. A superfície de duplicação vai além do retry explícito, abrangendo o conjunto de decisões que o modelo toma quando interpreta que a ação anterior não produziu o resultado esperado. E essa interpretação depende do conteúdo da context window, que varia entre turnos.
Na prática, o que resolve isso na camada determinística é a idempotency key derivada de identificadores de negócio. Em vez de gerar um UUID aleatório por chamada, a chave é construída a partir de elementos estáveis da operação: o identificador do workflow, o nome da ferramenta e um hash dos argumentos semanticamente relevantes. Essa construção garante que chamadas equivalentes produzam a mesma chave independentemente de quem as originou ou quantas vezes foram tentadas. A biblioteca agent-ledger, camada transacional pra side effects de agentes (ainda recente, mas já funcional), implementa exatamente esse padrão: ao interceptar a chamada na fronteira entre o agente e a ferramenta, computa o hash de (workflow_id, tool, args) e consulta um ledger persistente antes de executar. Se a chave já existe com resultado registrado, o resultado armazenado é devolvido sem reexecução.
Aqui é onde a coisa fica menos trivial: definir o que constitui argumentos semanticamente equivalentes. Dois payloads que diferem apenas em ordem de campos ou em formatação de datas representam a mesma operação, mas produzem hashes diferentes se o hash for computado sobre a representação literal. A normalização dos argumentos antes do hash é o que separa uma implementação que funciona num ambiente controlado de uma que funciona em produção com agentes reais, até porque agentes reais produzem variação cosmética entre chamadas que um cliente HTTP nunca produziria. A normalização precisa ser específica por ferramenta: uma ferramenta de envio de e-mail normaliza o campo de destinatário pra lowercase, uma ferramenta de pagamento normaliza o valor pra centavos inteiros. O agent-ledger aborda isso com o parâmetro idempotency_keys, que permite declarar quais campos da tool call compõem a identidade da operação, ignorando o resto do payload. Essa especificidade impede que a camada de idempotência seja tratada como middleware genérico e exige que o design da ferramenta inclua, desde o início, a definição dos campos que identificam a operação.
Pra mim, esse é o trade-off mais difícil de calibrar: proteção contra duplicação versus capacidade de correção. Se a chave de idempotência for ampla demais, cobrindo poucos campos, operações genuinamente distintas serão tratadas como duplicatas e suprimidas. Se for estreita demais, cobrindo todos os campos incluindo os cosméticos, chamadas equivalentes produzirão chaves diferentes e a proteção desaparece. O padrão que se sustenta é começar com uma chave conservadora (ampla) e estreitar progressivamente à medida que falsos positivos são identificados. A direção oposta, começar sem proteção e adicionar depois, é mais cara porque os efeitos colaterais duplicados já terão ocorrido e a remediação costuma ser manual.
A segunda camada de proteção é o registro de efeitos com estado de conclusão. Não basta registrar que a operação foi tentada. O ledger precisa armazenar o resultado completo, incluindo sucesso, falha e tipo de falha, porque o comportamento correto diante de um retry depende de como a execução anterior terminou. Se completou com sucesso, o retry recebe o resultado armazenado. Se completou com falha transiente, o retry deve reexecutar. Se completou com falha permanente, o retry deve devolver a falha sem reexecução. Sem essa distinção, o sistema ou reexecuta operações que já tiveram sucesso (duplicando efeitos) ou bloqueia retries de operações que falharam por motivo temporário (impedindo recuperação legítima).
A maioria dos times descobre tarde que a camada de idempotência pertence à fronteira entre o agente e o mundo externo. Colocar a lógica de deduplicação dentro do agente, via instrução no prompt, transfere a responsabilidade pro modelo, e o modelo não tem garantia de consistência entre turnos.
Na minha avaliação, a posição arquitetural correta é um interceptor na camada de tool execution, entre a decisão do agente e a execução real, onde o código determinístico tem acesso ao ledger e pode tomar a decisão de executar ou devolver resultado armazenado sem depender do comportamento do modelo. Essa posição é análoga ao middleware de deduplicação em sistemas de mensageria e resolve o mesmo problema: garantir que a semântica de entrega (at-most-once pra efeitos colaterais) não dependa do comportamento do produtor da mensagem. A idempotência não torna o agente mais inteligente. Torna o sistema tolerante à inteligência imperfeita do agente.