# Como agentes de IA concorrentes colidem em contas de produção

Duas execuções autônomas na mesma conta de produção não ficam seguras só porque cada uma recebeu uma descrição de tarefa diferente. Elas compartilham um sistema mutável, e cada plano pode ficar obsoleto antes da próxima chamada de API. Se ambas podem alterar o mesmo recurso, você precisa de um limite de propriedade que o próprio serviço imponha.

A falha mais comum é mais silenciosa do que uma grande indisponibilidade. Um agente adiciona um membro a um grupo enquanto outro substitui a lista completa de membros usando uma leitura antiga. As duas solicitações retornam sucesso. A solicitação posterior remove o membro que a primeira havia adicionado. Cada execução seguiu suas instruções. Foi a API que aceitou uma sequência inválida.

Trate a execução de um agente como um cliente concorrente não confiável, com credenciais reais e tempo de resposta imprevisível. Dê a ele uma área pequena para controlar, torne as gravações condicionais à versão lida e registre contexto suficiente para explicar mais tarde uma alteração aceita ou rejeitada. A revisão humana continua útil, mas não substitui um receptor capaz de detectar estado obsoleto.

## Separe o limite da tarefa do limite de gravação

O limite da tarefa diz o que o agente foi instruído a realizar. O limite de gravação diz quais objetos mutáveis ele pode alterar. São coisas diferentes, e as equipes sofrem quando tratam os dois como equivalentes.

«Atualizar a implantação de staging» parece específico. Ainda assim, pode envolver uma tag de imagem compartilhada, um ponteiro de lançamento, uma regra de tráfego, um registro DNS, um livro-razão de migrações do banco de dados e um canal de notificações. O agente pode obedecer ao texto da tarefa e, ao mesmo tempo, colidir com uma execução de lançamento que controla um desses objetos.

Defina a propriedade em termos que o serviço receptor consiga verificar. Bons limites citam identificadores duráveis de recursos, não categorias vagas de trabalho:

- um ambiente e um registro de implantação
- um tenant ou uma conta de cliente
- um pull request e sua branch
- um ticket de incidente e os recursos nomeados no conjunto de alterações
- uma janela de manutenção com uma lista explícita de destinos

Evite limites como «trabalho de backend» ou «limpeza de produção». Eles são rótulos para pessoas. Não dizem à API qual gravação deve falhar.

Um registro de propriedade útil contém o ID da execução, o ID do recurso, a operação permitida e a validade. Mantenha-o perto do serviço que controla o recurso. Se um controlador de implantação controla o ponteiro de lançamento, esse controlador deve validar quem pode avançá-lo. Uma planilha, uma mensagem no chat ou uma instrução no prompt não conseguem bloquear uma solicitação que chega depois que a pessoa que a escreveu já foi embora.

### Crie um pequeno mapa de conflitos antes de conceder acesso de gravação

Para cada tarefa automatizada, liste os recursos que ela lê, grava, exclui e usa como padrão compartilhado. Depois marque cada par de tarefas que pode gravar o mesmo identificador ou em que a entrada de uma pode ser alterada pela outra. Isso não é burocracia. É uma forma de revelar colisões que as permissões baseadas em funções escondem.

Por exemplo, um agente que troca um token de serviço e outro que atualiza a configuração de uma integração talvez nunca chamem o mesmo endpoint. O agente que grava a configuração pode ler a referência do token atual e depois publicar o documento completo de configuração quando a troca já tiver alterado essa referência. O conflito está na versão do documento, não em um comando idêntico.

Se você não consegue descrever o conjunto de gravações de uma execução, não dê a ela permissão ampla de gravação em produção. Faça com que ela prepare uma proposta ou limite-a a um namespace de recursos até que o limite possa ser descrito.

## Uma resposta de sucesso ainda pode apagar uma alteração correta

O comportamento de «a última gravação vence» é uma política de perda de dados quando os clientes enviam representações completas. Ele parece inofensivo em demonstrações porque cada cliente lê e grava imediatamente. Agentes podem passar minutos examinando logs, criando um plano, pedindo aprovação e repetindo uma chamada depois de um timeout.

Considere um serviço com o recurso `notification-policy`. Ele devolve esta representação ao Agente A:

```json
{
  "id": "prod-alerts",
  "version": 41,
  "destinations": ["oncall@example.test"],
  "severity": "high"
}
```

O Agente A planeja adicionar um destino de backup. Durante a revisão, o Agente B altera `severity` de `high` para `critical` e grava com sucesso a versão 42. Em seguida, o Agente A envia uma substituição completa baseada na versão 41:

```json
{
  "destinations": ["oncall@example.test", "backup@example.test"],
  "severity": "high"
}
```

Se o endpoint aceitar essa solicitação, ele desfaz silenciosamente a alteração de B. Nenhum dos agentes precisa ter um bug. A API permitiu que uma observação antiga substituísse um fato mais recente.

Atualizações parciais reduzem a área de risco, mas não eliminam o problema. Um patch que adiciona um destino ainda pode violar uma nova cota, uma política de roteamento atualizada ou uma exclusão ocorrida depois da leitura. O serviço precisa decidir se o patch continua válido diante do estado atual.

Por isso, «só permitimos que os agentes usem PATCH» não é um projeto de concorrência. É apenas uma forma menor de gravação. Ainda é necessária uma condição que conecte a gravação ao estado observado pelo agente.

## Torne condicional toda solicitação que altera o estado

O controle otimista de concorrência costuma ser a primeira defesa correta para gravações feitas por agentes. O cliente lê uma versão, uma ETag, um número de geração ou um token de revisão. Depois envia esse valor junto da atualização pretendida. O serviço aceita a gravação apenas se o valor atual continuar igual.

A RFC 9110 define `If-Match` exatamente para esse tipo de solicitação. O servidor avalia a condição antes de aplicar o método. Se a ETag não corresponder mais, o servidor rejeita o método com `412 Precondition Failed`. Isso não é um inconveniente da API. É o servidor se recusando a fingir que um plano desatualizado continua correto.

Uma atualização HTTP condicional pode ter esta aparência:

```http
GET /v1/notification-policies/prod-alerts

HTTP/1.1 200 OK
ETag: "41"
Content-Type: application/json

{"destinations":["oncall@example.test"],"severity":"high"}
```

O agente leva essa ETag para a gravação:

```http
PATCH /v1/notification-policies/prod-alerts
If-Match: "41"
Content-Type: application/json
Idempotency-Key: run-7f3c-add-backup

{"destinations":["oncall@example.test","backup@example.test"]}
```

Se outro gravador tiver produzido a ETag `"42"`, devolva uma rejeição clara:

```http
HTTP/1.1 412 Precondition Failed
Content-Type: application/json

{
  "error": "stale_version",
  "resource": "notification-policy/prod-alerts",
  "expected_etag": "41",
  "current_etag": "42",
  "retryable": false
}
```

Não classifique esse erro como repetível se o agente puder reenviar o mesmo corpo sem pensar. Uma nova tentativa precisa começar com uma leitura atualizada e uma nova decisão. Ela pode descobrir que o resultado desejado já existe, que a política mais recente tornou a alteração inválida ou que uma pessoa precisa escolher entre dois resultados concorrentes.

Nos bancos de dados, use o predicado equivalente na própria mutação. Uma atualização típica verifica a versão na cláusula `WHERE` e trata zero linhas afetadas como conflito:

```sql
UPDATE notification_policy
SET destinations = :destinations,
    version = version + 1
WHERE id = :id
  AND version = :observed_version;
```

Nunca leia uma versão e depois faça uma atualização incondicional em uma segunda operação. A verificação e a alteração do estado precisam ocorrer juntas, na autoridade que armazena o estado.

## A idempotência evita duplicatas, não divergências

É comum uma equipe colocar uma chave de idempotência em um endpoint e declarar que as gravações concorrentes estão resolvidas. A chave impede que a mesma solicitação lógica produza seu efeito duas vezes. Ela não diz ao serviço se duas solicitações diferentes são compatíveis.

Um timeout de rede deixa essa diferença clara. Um agente envia uma solicitação para criar uma implantação, mas perde a resposta. Repetir a solicitação com a mesma chave de idempotência deve devolver o resultado original, em vez de criar uma segunda implantação. Isso é supressão de duplicatas.

Agora imagine dois agentes que escolhem candidatos de lançamento diferentes para o mesmo ambiente de produção. Eles enviam corpos diferentes e chaves de idempotência diferentes. As duas solicitações podem ser perfeitamente idempotentes e, ainda assim, uma deve perder porque o ponteiro de lançamento mudou.

Use os dois controles em endpoints de gravação importantes:

- Uma chave de idempotência vincula novas tentativas e entregas duplicadas a uma única operação concluída.
- Uma pré-condição de versão rejeita uma gravação cuja decisão depende de um estado obsoleto do recurso.
- Uma invariante no servidor verifica regras que precisam valer até para uma gravação atual, como o número máximo de credenciais ativas.

Armazene a chave de idempotência com uma impressão digital da solicitação e a resposta concluída. Se o chamador reutilizar a chave com um corpo diferente, rejeite a solicitação. Devolver a primeira resposta para uma operação diferente cria uma confusão na depuração e pode esconder um bug do cliente.

Seja rigoroso com a validade. O serviço deve manter a chave por tempo suficiente para cobrir seu comportamento real de novas tentativas, mas um armazenamento de idempotência não é um histórico permanente de comandos. O histórico pertence ao registro de auditoria.

## Use leases apenas para trabalhos que não podem se sobrepor

Algumas ações demoram tanto que apenas verificações otimistas tornam a experiência ruim. Uma migração de banco de dados, um trabalho destrutivo de reconciliação ou uma virada de tráfego podem envolver muitas gravações dependentes. Nesses casos, dê a uma execução um lease curto no recurso.

Um lease precisa ter um proprietário, uma validade e um valor de fencing. O fencing é importante porque um worker cujo lease expirou pode acordar e continuar depois que outro worker adquiriu um novo lease. Toda gravação protegida precisa carregar o token monotonicamente crescente do lease, e o serviço deve rejeitar um token mais antigo do que o último aceito.

Sem fencing, um serviço de locks pode avisar ao Agente A que seu lease expirou, mas não consegue impedir que uma solicitação atrasada de A chegue ao banco de dados. O serviço de destino precisa rejeitá-la. Essa é a parte que as equipes esquecem quando dizem que têm um lock distribuído.

Mantenha os leases restritos e curtos. Não bloqueie «a produção» durante toda uma investigação autônoma. Bloqueie `migration/customer-1842` ou `release/prod-eu`, e faça a execução renovar o lease apenas enquanto continuar progredindo. O lease deve expirar com segurança se o processo do agente, seu laptop ou sua rede desaparecer.

Não use um lease para cobrir edições normais de configuração que contam com verificações de versão. Locks longos transformam alterações rotineiras em filas, e as pessoas acabam aprendendo a contorná-los. Uma resposta `412` seguida de um plano atualizado custa menos do que uma indisponibilidade causada por um titular de lock obsoleto.

## A identidade do agente precisa atravessar o gateway

Um token de produção compartilhado dá a todas as execuções o mesmo nome no serviço. Depois de uma colisão, você consegue ver que o token agiu, mas não sabe qual processo planejou a alteração, qual aprovação a autorizou ou qual execução deve ser interrompida. Isso torna a limpeza lenta e a revogação ampla.

Dê a cada processo de agente uma identidade de sessão distinta e passe um identificador estável de correlação para toda solicitação enviada ao destino. O serviço de destino deve registrar a identidade, o ID da execução, o ID da solicitação, o recurso-alvo, a versão observada, o resultado e sua própria versão resultante. Não esconda essas informações em prosa dentro de uma mensagem de commit.

O Sallyport mantém as credenciais de API e SSH fora do processo do agente enquanto executa a ação, o que ajuda a preservar a separação entre o contexto de planejamento do agente e o próprio segredo. Sua autorização por sessão pode identificar um processo de agente recém-iniciado antes que ele comece uma execução. Essa autorização controla quem pode agir, mas não substitui as pré-condições no destino.

Não permita que o agente escolha sua própria identidade efetiva em um cabeçalho arbitrário. Faça o gateway ou o serviço de destino vincular a identidade a partir de uma sessão autenticada. Caso contrário, uma execução pode alegar depois que era o coordenador de implantação, e seus logs viram encenação.

Para SSH, o mesmo princípio vale, embora o protocolo de comunicação seja diferente. Use principals separados ou contas restritas para classes distintas de trabalho. Faça os logs dos comandos remotos incluírem um identificador de execução e evite uma única conta de shell compartilhada que possa editar todos os diretórios de aplicações.

## O momento da aprovação não é o momento da transação

Uma pessoa pode aprovar a solicitação de um agente e, ainda assim, aprovar uma gravação que se torna errada dez segundos depois. Isso é normal em um sistema concorrente. A aprovação trata de autoridade e intenção no momento da revisão. Ela não congela o recurso.

O projeto perigoso pede que uma pessoa aprove uma frase ampla, como «atualizar a configuração de produção», e depois permite que o agente faça uma sequência de leituras e gravações quando conseguir executá-las. Um projeto mais seguro mostra o destino e o efeito pretendido, e então faz o serviço impor a versão ou o lease quando a gravação chegar.

Quando uma pré-condição falhar depois da aprovação, não reutilize automaticamente essa aprovação para um plano alterado. O agente deve relatar o conflito em termos concretos: qual recurso mudou, qual versão ele observou, qual campo mudou, se o serviço conseguir determinar isso, e se o resultado proposto ainda é necessário. Uma pessoa pode então aprovar uma nova ação, ou o agente pode fazer uma operação segura sem efeito depois de reler o estado.

A aprovação por chamada é adequada para operações em que cada uso apresenta risco significativo, como excluir uma credencial de produção ou alterar uma regra de roteamento visível externamente. Para lotes normais de gravações restritas e condicionais, a aprovação por execução costuma ser mais útil, pois permite ao operador verificar a identidade e o escopo sem criar o hábito de clicar automaticamente.

Não confunda uma pilha de aprovações com controle. Se os operadores não conseguem ver o ID do recurso, a operação e o resultado atual do conflito, estão aprovando uma frase enquanto o serviço faz o trabalho real em outro lugar.

## Uma resposta de conflito precisa ter um responsável definido

Uma gravação obsoleta rejeitada é um resultado de segurança bem-sucedido, mas apenas se a execução souber o que fazer depois. «Tentar novamente em caso de erro» é o padrão errado. Ele transforma uma divergência em uma corrida automatizada.

Classifique cada caminho de gravação antes de permitir a execução autônoma. A classe determina quem resolve o conflito:

| Tipo de alteração | Em caso de conflito de versão | Responsável |
| --- | --- | --- |
| Adicionar um recurso independente com nome único | Reler e tentar novamente se o nome continuar disponível | Agente |
| Atualizar um campo calculado a partir dos dados atuais | Reler, recalcular e tentar novamente | Agente |
| Avançar um ponteiro de lançamento compartilhado | Parar e apresentar os dois candidatos | Responsável pelo lançamento |
| Alterar membros ou permissões de acesso | Parar e solicitar revisão | Responsável pela conta |
| Excluir ou substituir um documento de configuração compartilhado | Parar, a menos que um lease explícito o cubra | Operador nomeado |

A ideia não é tornar os agentes receosos. É distinguir recálculo de julgamento. Um agente pode repetir com segurança um relatório gerado a partir de entradas atuais. Ele não deve escolher entre duas versões de produção aprovadas, duas decisões de acesso ou dois planos de rollback diferentes apenas porque recebeu um `412`.

Torne as respostas de conflito legíveis por máquina. Inclua a identidade do recurso, a versão atual, a categoria do conflito e a indicação de que o endpoint permite uma nova tentativa automática. Um `409` vago com uma página de erro HTML empurra o agente para o improviso.

### Teste a colisão que você espera que aconteça

Não espere o tráfego de produção provar que suas verificações funcionam. Crie um teste que pause uma execução entre a leitura e a gravação, permita que uma segunda execução altere o mesmo recurso e depois libere a primeira. Verifique quatro resultados:

1. A primeira gravação falha sem alterar o recurso.
2. A resposta identifica uma versão obsoleta, em vez de um erro genérico do servidor.
3. O agente não reenvia automaticamente o corpo antigo.
4. Seus registros conseguem relacionar as duas tentativas às respectivas identidades e aprovações.

Faça o mesmo teste com timeout e nova tentativa para provar que o comportamento de idempotência é separado. São caminhos de falha diferentes e precisam de resultados esperados diferentes.

## Audite tanto a ação tentada quanto o estado resultante

Uma conta de produção precisa de dois registros depois de uma colisão entre agentes: o caminho do comando e o histórico autoritativo do recurso. Os logs do gateway explicam quem solicitou uma ação e por qual sessão aprovada. Os logs do serviço explicam se o estado mudou, qual versão venceu e por que uma solicitação falhou.

Não se contente com um registro de atividade que diga «PATCH concluído». Registre o identificador do recurso, o método, o ID de correlação da solicitação, a pré-condição enviada pelo cliente, a chave de idempotência ou uma referência segura a ela, o status da resposta e a ETag resultante. Se o serviço mantiver histórico em nível de campo, registre ali os campos alterados, em vez de tentar deduzi-los do transcript do agente.

O Sallyport projeta seus registros Sessions e Activity a partir de um log de auditoria criptografado e encadeado por hash. Se você o usa para ações de agentes, execute esta verificação ao investigar uma sequência contestada:

```sh
sp audit verify
```

O comando verifica a cadeia offline sobre o texto cifrado e não precisa da chave do cofre. Ele pode confirmar se o registro local permaneceu intacto. Antes de afirmar que sabe o que aconteceu, compare-o com os logs de solicitações do serviço de destino.

As regras de retenção e acesso também importam. Um transcript de agente pode conter raciocínios falhos ou detalhes operacionais copiados, enquanto um registro de solicitações deve ser um relato factual e compacto. Mantenha as evidências necessárias para reconstruir a autoridade e as transições de estado, e limite quem pode consultá-las.

## O paralelismo deve ficar em conjuntos de recursos independentes

Você não precisa de uma fila global única para todas as execuções autônomas. Precisa de uma regra que permita o trabalho independente e torne explícita a mutação compartilhada. Particione por tenant, ambiente, branch do repositório, serviço ou outro namespace de recursos que o serviço consiga verificar.

Um projeto prático de produção tem um coordenador que atribui a cada execução um conjunto de gravações e concede credenciais ou acesso ao gateway somente para esse conjunto. O coordenador não decide se toda alteração é boa. Ele impede que dois workers recebam autoridade sobreposta por acidente. Os serviços receptores ainda impõem versões e invariantes, porque coordenadores falham, atribuições mudam e pessoas iniciam trabalhos emergenciais fora do caminho normal.

Quando uma ação abrange vários recursos, resista à tentação de chamá-la de alteração atômica se os serviços não conseguem realmente transacionar juntos. Registre o estado pretendido, ordene as gravações para que as etapas posteriores validem as anteriores e defina a compensação antes da execução. Uma ação de compensação também precisa verificar o estado atual. Voltar a um snapshot antigo pode apagar uma alteração legítima ocorrida depois da execução original.

O primeiro teste de produção deve ser deliberadamente simples: escolha um recurso de configuração compartilhado, inicie duas execuções de agentes a partir da mesma versão e faça com que proponham alterações incompatíveis. Se o serviço aceitar as duas, corrija esse endpoint antes de dar a qualquer execução uma responsabilidade maior. A autonomia fica menos interessante depois de uma colisão, e é exatamente por isso que você deve forçar a colisão primeiro em um teste controlado.
