Arquitetura de IA

Padrão inbox para agentes de IA: como evitar duplicidade

Aprenda a usar o padrão inbox em agentes de IA para reconhecer mensagens repetidas, gravar efeitos uma vez e recuperar consumidores após falhas.

O banco confirmou e o consumidor caiu antes do ACK

Um worker recebe o evento que autoriza a criação de uma tarefa no CRM. Ele grava a tarefa no banco e tenta confirmar o processamento para a fila. A confirmação não chega porque o processo cai ou a conexão é interrompida.

A fila entrega a mesma mensagem novamente. Para o broker, esse comportamento protege o trabalho contra perda. Para o processo empresarial, a segunda entrega pode criar outra tarefa, outro lançamento ou outra mudança de estado.

O padrão inbox coloca um registro durável na entrada do consumidor. A identidade da mensagem e o efeito local entram na mesma transação. Quando ocorre uma reentrega, o consumidor encontra o registro já confirmado, reconhece a repetição e encerra aquela tentativa sem aplicar o efeito outra vez.

Este artigo trata da fronteira entre broker e consumidor. O guia sobre idempotência em agentes de IA cobre a proteção geral de efeitos, inclusive chamadas externas. O inbox resolve uma parte mais específica: como registrar o recebimento e a alteração local sob um único compromisso atômico.

Por que uma mensagem pode chegar novamente

Sistemas de mensageria costumam favorecer continuidade. Se não existe confirmação de conclusão, o trabalho volta para processamento.

A repetição pode surgir quando:

  • o consumidor grava no banco e cai antes de confirmar a mensagem;
  • o bloqueio da mensagem vence durante uma execução lenta;
  • a confirmação chega tarde ou se perde na rede;
  • uma instância é encerrada durante deploy ou redução de escala;
  • o operador reenvia um item da fila de erros;
  • o produtor repete a publicação depois de um timeout;
  • duas entregas equivalentes chegam com IDs distintos;
  • consumidores concorrentes disputam cópias do mesmo trabalho.

A Microsoft documenta que brokers com entrega pelo menos uma vez podem reenviar mensagens depois de falha do consumidor, expiração de lock ou perda do ACK. A própria resiliência cria a obrigação de tratar repetição.

Trocar para um modo que apaga a mensagem antes do processamento desloca o risco. O sistema reduz duplicidade e passa a aceitar perda quando o consumidor cai depois do recebimento. Em processos empresariais, uma entrega repetida pode ser reconhecida; um pedido perdido silenciosamente costuma deixar menos caminhos de recuperação.

O que o padrão inbox registra

A inbox é uma tabela ou coleção de mensagens já processadas por um consumidor. Cada registro representa uma identidade que aquele consumidor aceitou e aplicou.

Campos úteis incluem:

consumer_name
message_id
event_type
source
business_key
payload_hash
contract_version
tenant_id
received_at
processed_at
result_reference
retention_until

consumer_name participa da identidade porque uma mensagem pode alimentar consumidores independentes. Um evento pedido_confirmado pode atualizar estoque, faturamento e atendimento. Se a tabela usar somente message_id, o primeiro consumidor que gravar a chave pode bloquear os outros dois.

Uma chave composta costuma seguir esta forma:

consumer_name + message_id

Quando a origem pode republicar o mesmo fato com outro ID, acrescente uma chave empresarial estável para proteger a consequência. Exemplo:

criar_tarefa_followup + oportunidade_2481 + reuniao_184

O ID detecta a reentrega da mesma mensagem. A chave empresarial identifica duas mensagens diferentes que pedem o mesmo efeito.

Inbox, outbox e DLQ protegem fronteiras diferentes

Os três mecanismos aparecem juntos e cumprem trabalhos próprios.

Inbox protege o recebimento

O consumidor registra que uma mensagem foi aplicada. A reentrega encontra esse registro e vira uma conclusão sem novo efeito.

Outbox protege a publicação

O padrão outbox para agentes de IA grava o estado local e o evento que precisa sair na mesma transação. Ele impede que a aplicação confirme uma mudança e perca a publicação correspondente.

DLQ isola o que não pode avançar

A fila de erros recebe mensagens que esgotaram tentativas seguras ou exigem correção. O reenvio posterior precisa conservar a identidade original para que efeitos já concluídos continuem reconhecíveis.

Um fluxo completo pode usar inbox na entrada, alteração de negócio e outbox na saída dentro da mesma transação local. A DLQ recebe o que permaneceu inválido ou bloqueado depois da política de tentativas.

A garantia depende de uma única transação

Uma implementação frágil executa três passos separados:

  1. consulta a inbox;
  2. altera o dado empresarial;
  3. grava a inbox.

Se o processo cair entre os passos 2 e 3, o efeito existe e o marcador não. Na próxima entrega, a consulta não encontra o registro e a alteração acontece outra vez.

Outro desenho grava a inbox antes de fazer o trabalho. Se a aplicação cair depois do marcador e antes do efeito, a próxima tentativa considera a mensagem concluída e descarta trabalho que nunca aconteceu.

O desenho seguro usa uma transação local:

BEGIN
  inserir (consumer_name, message_id) na inbox
  aplicar a mudança no dado empresarial
COMMIT
confirmar a mensagem no broker

A restrição de unicidade decide quem pode prosseguir. Se duas instâncias tentarem inserir a mesma chave, apenas uma confirma. A outra reconhece o conflito como entrega já tratada e não aplica o efeito.

A ordem entre commit e ACK continua relevante. O ACK vem depois do commit. Se ele se perder, haverá reentrega, mas a inbox absorve a repetição. Confirmar antes do commit abre a possibilidade de remover a mensagem e perder o trabalho local.

Use o banco como árbitro da concorrência

Fazer uma consulta e depois inserir deixa uma janela de corrida:

  1. worker A consulta e não encontra a chave;
  2. worker B consulta e também não encontra;
  3. A executa o efeito;
  4. B executa o mesmo efeito;
  5. ambos tentam gravar o marcador.

A proteção precisa ocorrer na escrita atômica. Use chave primária, restrição única, inserção condicional ou operação equivalente no armazenamento escolhido.

Um esquema relacional simples pode usar:

CREATE TABLE inbox (
  consumer_name TEXT NOT NULL,
  message_id TEXT NOT NULL,
  payload_hash TEXT NOT NULL,
  processed_at TIMESTAMP NOT NULL,
  PRIMARY KEY (consumer_name, message_id)
);

A tentativa de inserção deve ocorrer dentro da mesma transação da alteração empresarial. Tratar a violação de unicidade como erro técnico genérico provoca retentativas inúteis. Nesse ponto, o conflito esperado significa que outra execução já possui ou concluiu aquela identidade.

Ainda assim, compare o conteúdo. O mesmo message_id com outro hash de payload indica reutilização indevida de identidade ou mudança de conteúdo. Essa colisão deve ser bloqueada e investigada, não descartada como repetição legítima.

Escolha a chave pela unidade de trabalho

Uma boa chave permanece igual em toda reentrega e distingue trabalhos realmente diferentes.

Evite usar:

  • horário de recebimento;
  • número da tentativa;
  • ID gerado pelo consumidor;
  • posição que muda durante replay;
  • texto livre criado pelo modelo;
  • correlation_id compartilhado por várias mensagens.

Prefira:

  • ID do evento definido pelo produtor;
  • combinação source + event_id;
  • ID empresarial com tipo e versão da ação;
  • identidade CloudEvents quando o contrato adotado a preserva;
  • ID de mensagem estável que sobrevive a DLQ e redrive.

Uma oportunidade pode receber vários eventos dentro da mesma conversa comercial. Usar apenas opportunity_id como chave faria o primeiro evento bloquear todos os seguintes. A chave deve identificar a unidade, como oportunidade + reunião + criar follow-up.

Também defina a identidade do consumidor. Estoque e faturamento precisam processar o mesmo evento de pedido de forma independente. A chave compartilhada por ambos deve incluir o nome lógico de cada consumidor.

Deduplicação do broker ajuda, mas possui escopo

Alguns brokers filtram publicações repetidas durante uma janela.

O Azure Service Bus mantém o MessageId em um histórico configurável e descarta novos envios com o mesmo identificador durante aquela janela. A documentação informa suporte nos níveis Standard e Premium e orienta usar um ID ligado ao processo empresarial.

O Amazon SQS FIFO também usa um identificador de deduplicação e documenta uma janela de cinco minutos para repetição do envio.

Esses recursos reduzem duplicatas criadas pelo produtor. Eles não cobrem todos os caminhos do consumidor. Um worker pode aplicar o efeito e cair antes de concluir a mensagem. O broker então redireciona a mesma mensagem porque ainda não recebeu confirmação válida.

A inbox continua necessária quando uma reentrega pode repetir uma consequência. A configuração do broker funciona como primeira camada, e a transação no consumidor protege a gravação local.

Propague idempotência para chamadas externas

A transação da inbox alcança somente recursos que participam do mesmo banco ou mecanismo atômico. Ela não engloba, por mágica, um e-mail, uma cobrança, uma atualização no CRM externo ou outra API.

Considere este fluxo:

  1. inserir o marcador na inbox;
  2. chamar uma API externa;
  3. gravar o resultado local;
  4. confirmar a mensagem.

Se a API concluir e a transação local falhar, a reentrega tentará de novo. A inbox sozinha não sabe se a chamada externa produziu efeito.

Para essa fronteira, use uma combinação de:

  • chave de idempotência enviada ao destino;
  • outbox para separar a intenção da execução externa;
  • referência externa pesquisável;
  • consulta de confirmação;
  • estado resultado_incerto;
  • reconciliação antes de repetir;
  • saga ou compensação quando o processo exigir.

A regra prática: mantenha a inbox e a alteração local na mesma transação. Transforme o efeito externo em outra unidade governada, com identidade e confirmação próprias.

Trate mensagens iguais com conteúdo diferente

A identidade pode colidir por erro do produtor. O evento evt-482 chega primeiro com quantidade 10 e depois com quantidade 12.

Descartar a segunda entrega silenciosamente esconde corrupção contratual. Processá-la como nova ignora que o mesmo ID passou a representar dois fatos.

Registre um hash canônico do conteúdo relevante e compare durante a repetição:

  • chave e hash iguais: repetição compatível;
  • chave igual e hash diferente: colisão ou mutação;
  • chave diferente e ação empresarial igual: duplicidade semântica;
  • chave e ação diferentes: novo trabalho.

O hash precisa excluir campos voláteis que mudam por transporte, como horário de recebimento ou número da tentativa. Também não substitui validação do schema e da versão do contrato.

Defina retenção com base na possibilidade de reentrega

Apagar a inbox cedo demais faz uma mensagem antiga parecer nova. Guardar tudo indefinidamente aumenta custo e exposição.

A retenção deve considerar:

  • janela máxima de reentrega do broker;
  • retenção da fila principal e da DLQ;
  • prazo de redrive operacional;
  • duração de incidentes e contingências;
  • tempo em que o efeito continua repetível;
  • obrigação de auditoria;
  • ciclo de vida do objeto empresarial;
  • política de dados e minimização.

Se a DLQ retém mensagens por 30 dias, uma inbox eliminada no sétimo dia deixa o redrive desprotegido. Se uma cobrança pode ser contestada por meses, a evidência da identidade pode precisar de outra política.

Faça a limpeza por tipo de consumidor e consequência. Preserve a chave e os metadados mínimos necessários. Payload completo, documento e dado pessoal devem seguir retenção e acesso próprios.

Exemplo local com mensagens sintéticas

Em 9 de outubro de 2026, a Júpiter executou um laboratório local com SQLite e quatro entregas sintéticas. Duas identidades foram enviadas duas vezes cada.

A tabela inbox usou chave primária composta por consumidor e mensagem. A criação da tarefa ocorreu na mesma transação. O resultado observado foi:

deliveries=4
accepted=2
duplicates=2
inbox_rows=2
business_effects=2

O teste demonstra a propriedade local do padrão: quatro entregas produziram duas gravações empresariais, uma por identidade. Ele não representa benchmark de broker, volume, latência ou garantia para chamadas externas.

Para reproduzir o cenário em outra tecnologia, mantenha os mesmos critérios:

  1. duas mensagens lógicas;
  2. duas entregas de cada mensagem;
  3. chave única por consumidor e mensagem;
  4. marcador e efeito na mesma transação;
  5. confirmação ao broker somente depois do commit;
  6. contagem final de dois efeitos.

Teste as janelas de falha

O caminho feliz prova pouco. Inclua cenários que interrompem o consumidor em pontos diferentes:

  • antes de abrir a transação;
  • depois da inserção e antes da alteração empresarial;
  • depois da alteração e antes do commit;
  • depois do commit e antes do ACK;
  • durante renovação do lock;
  • com duas entregas concorrentes;
  • após redrive da DLQ;
  • com o mesmo ID e outro payload;
  • com outro ID e a mesma ação empresarial;
  • depois da expiração da retenção;
  • durante deploy ou desligamento de worker;
  • com chamada externa concluída e resposta perdida.

Para cada caso, confira:

  • quantidade de registros na inbox;
  • quantidade de efeitos empresariais;
  • estado da mensagem no broker;
  • logs de repetição e colisão;
  • estado de chamadas externas;
  • evidência entregue ao responsável;
  • capacidade de retomar sem intervenção improvisada.

Monitore repetição, colisão e atraso

A inbox também produz sinais sobre a saúde da integração.

Acompanhe:

  • mensagens recebidas por consumidor;
  • duplicatas reconhecidas;
  • colisões de ID e payload;
  • conflitos simultâneos na chave única;
  • tempo entre recebimento e commit;
  • mensagens confirmadas depois de reentrega;
  • redrives processados;
  • chaves expiradas antes de nova entrega;
  • efeitos externos em estado incerto;
  • falhas por versão de contrato;
  • crescimento e limpeza da tabela;
  • consumidores sem identidade conhecida.

Uma alta súbita de duplicatas pode indicar produtor repetindo envio, lock curto, consumidor lento, queda frequente ou redrive em andamento. O alerta precisa apontar processo, causa provável, consequência e responsável.

Checklist de implementação

Antes de colocar o consumidor em produção, confirme:

  • [ ] A unidade lógica da mensagem possui identidade estável?
  • [ ] A chave inclui a identidade do consumidor quando necessário?
  • [ ] Existe proteção para ações equivalentes com IDs diferentes?
  • [ ] Inbox e efeito local entram na mesma transação?
  • [ ] A unicidade é aplicada pelo armazenamento?
  • [ ] O ACK ocorre somente depois do commit?
  • [ ] Repetição compatível e colisão de payload têm tratamentos distintos?
  • [ ] A chave sobrevive a retentativa, DLQ e redrive?
  • [ ] Chamadas externas recebem idempotência ou reconciliação própria?
  • [ ] Retenção cobre a maior janela plausível de reentrega?
  • [ ] Dados armazenados seguem finalidade e minimização?
  • [ ] Testes interrompem o fluxo antes e depois do commit?
  • [ ] Métricas mostram duplicatas, colisões, atraso e expiração?
  • [ ] Existe dono para investigar crescimento anormal?

A reentrega precisa terminar em uma conclusão conhecida

Filas, webhooks e workers recuperam trabalho porque aceitam que uma entrega pode acontecer novamente. O consumidor precisa transformar essa repetição em um resultado previsível.

O padrão inbox cria uma memória transacional do recebimento. Uma chave estável identifica a mensagem, o banco decide a concorrência, o marcador acompanha o efeito local e o ACK ocorre depois do commit. Quando a mesma unidade volta, o sistema reconhece o que já concluiu.

A proteção completa ainda exige idempotência nos destinos externos, outbox para publicações e reconciliação para estados incertos. A inbox ocupa um lugar preciso nessa arquitetura: impedir que a recuperação do consumidor repita a gravação local.

Fontes oficiais verificadas em 9 de outubro de 2026: