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:
- consulta a inbox;
- altera o dado empresarial;
- 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:
- worker A consulta e não encontra a chave;
- worker B consulta e também não encontra;
- A executa o efeito;
- B executa o mesmo efeito;
- 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_idcompartilhado 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:
- inserir o marcador na inbox;
- chamar uma API externa;
- gravar o resultado local;
- 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:
- duas mensagens lógicas;
- duas entregas de cada mensagem;
- chave única por consumidor e mensagem;
- marcador e efeito na mesma transação;
- confirmação ao broker somente depois do commit;
- 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:
- Microsoft Azure Architecture Center: Idempotent Consumer pattern, para entrega pelo menos uma vez, chave estável, identidade por consumidor, transação atômica, inbox e limites da deduplicação no broker.
- Microsoft Learn: duplicate detection no Azure Service Bus, para
MessageId, níveis compatíveis e janela configurável de detecção no envio. - Microsoft Learn: perda e processamento duplicado no Azure Service Bus, para Peek Lock, reentrega, ACK, idempotência no consumidor e diferença entre deduplicação de envio e processamento.
- AWS: exactly-once processing em filas SQS FIFO, para o escopo de deduplicação de envio e a janela documentada de cinco minutos.