# O agente caiu depois da mudança. Você deve repetir?

A ferramenta mudou o alvo, mas o processo morreu antes de confirmar o resultado. Dois experimentos locais mostram por que um timeout não prova falha, como um recibo permite recuperar a operação e quando o estado correto ainda exige investigação.

- URL: https://fernando.moretes.com/blog/agente-caiu-depois-da-mudanca-retry-recuperacao

- Markdown: https://fernando.moretes.com/blog/agente-caiu-depois-da-mudanca-retry-recuperacao/article.md?lang=pt

- Published: 2026-10-27T03:42:00.000Z

- Category: IA & Agentes

- Tags: AI Agents, Idempotency, Crash Recovery, MCP, Security

- Reading time: 14 min

- Source: [Persistent ledger and crash recovery lab v0.4.0](https://github.com/fernando-moretes/lab-security-remediation/blob/v0.4.0/DURABILITY.md)

---

O momento mais difícil de uma automação costuma estar entre duas confirmações: o sistema de destino aceitou a mudança, mas o executor ainda não registrou que terminou. Reiniciar o agente resolve a disponibilidade do processo; não resolve a dúvida sobre o efeito anterior. Neste laboratório, eu derrubo o processo exatamente nesse intervalo e inspeciono o que sobrevive. A decisão seguinte depende de evidências persistentes, não da confiança do modelo.

## O que foi verificado

- **Implementação:** lab-security-remediation, tag v0.4.0; testes locais executados em 27/10/2026.
- **Resultado observado:** 22 testes passaram. No exercício com recibo: versão 2, um recibo e nenhuma segunda mudança após recuperação e repetição.
- **Escopo:** SQLite local, alvo sintético, revisão simulada. Nenhuma credencial ou chamada AWS necessária.
- **Não demonstrado:** Não comprova execução exatamente uma vez em APIs remotas, resistência a perda de energia ou autorização pronta para produção.

## Comece pela pergunta que o log não responde

Imagine uma ferramenta que corrige uma configuração. Ela recebe uma proposta revisada, verifica a versão do alvo e aplica a mudança. Antes de devolver sucesso, o processo termina. O cliente vê uma interrupção. O alvo talvez já tenha mudado. Uma linha de log dizendo que a chamada começou não permite escolher entre essas possibilidades.

Neste exercício, o alvo é apenas uma linha SQLite que representa um bucket sintético. A propriedade `blockPublicAccess` serve como estado didático; não é uma chamada ao S3. A proposta identifica a operação e a versão esperada. Um registro de intenção, chamado ledger, guarda o pedido revisado. Um recibo registra que aquele pedido produziu aquela versão do alvo.

Essa separação permite formular uma pergunta verificável: **qual evidência liga esta operação a esta mudança?** Ver a configuração desejada não responde quem a mudou. Encontrar um processo reiniciado não responde se o processo anterior concluiu. Uma mensagem de erro no cliente descreve a observação do cliente, e não necessariamente a realidade do destino.

## Onde a evidência precisa sobreviver

Arquitetura do laboratório local. As tabelas estão no mesmo arquivo SQLite, mas intenção, efeito e conclusão usam transações distintas. A atualização do alvo e seu recibo são atômicos entre si.

### Local CLI

- Reviewed executor (compute)
- Recovery reader (compute)

### SQLite · separate commits

- Operation ledger (storage)
- Target + receipt (data)
- Audit events (storage)

### Fluxos

- executor -> ledger: INTENÇÃO
- executor -> target: EFEITO
- target -> recovery: EVIDÊNCIA
- ledger -> recovery: PEDIDO
- recovery -> ledger: CLASSIFICA
- recovery -> events: DECISÃO

## Prepare uma execução que outra pessoa consiga repetir

Use Node.js 22.13 ou posterior, com `node:sqlite` disponível. O código-fonte da tag é a referência deste exercício; não presuma que a versão publicada no npm seja a mesma. A instalação de dependências não é necessária para executar este laboratório. Faça o clone em uma pasta nova, confira a tag e rode os testes antes de alterar qualquer cenário.

Escolha nomes de banco novos em cada repetição completa. Um banco reaproveitado preserva histórico, e esse histórico muda o resultado esperado. Preserve também a proposta original: gerar outra depois da mudança pode criar um digest diferente para o mesmo identificador e deve resultar em conflito, não em uma autorização implícita para executar novamente.

Os comandos com falha intencional estão separados dos comandos de inspeção. Em um script com `set -e`, capture explicitamente o código 86 para que a interrupção esperada não encerre sua investigação. O teste de incerteza retorna 2, outro resultado previsto. Esses códigos fazem parte da evidência; não devem desaparecer em um `|| true` indiscriminado.

_Preparação: código fixado e testes locais_

```bash
git clone https://github.com/fernando-moretes/lab-security-remediation.git
cd lab-security-remediation
git checkout v0.4.0
node --version
npm test
```

## Experimento 1: o efeito existe; a confirmação se perdeu

Crie o banco e exporte a proposta. O comando `run` com `--approve` simula uma revisão explícita e grava a intenção. O ponto `after-target` encerra o processo depois do commit que atualiza o alvo e escreve o recibo, antes de marcar a operação como concluída. Não é uma exceção capturada dentro de uma única função: o CLI termina e os comandos seguintes abrem o banco em outros processos.

Depois da queda, inspecione três coisas juntas: o status da operação, a versão do alvo e a existência do recibo. Na execução de 27 de outubro, a operação estava `pending`, o alvo estava na versão 2 com `blockPublicAccess: true`, e havia um único recibo associado ao digest da proposta. Essa combinação demonstra por que repetir só porque o ledger está pendente seria uma decisão mal informada.

_Queda intencional: run retorna 86; continue com inspect_

```bash
node bin/cli.mjs durable init ./receipt.sqlite
node bin/cli.mjs durable plan ./receipt.sqlite > receipt-proposal.json
node bin/cli.mjs durable run ./receipt.sqlite receipt-proposal.json --approve --crash after-target
# Expected exit: 86
node bin/cli.mjs durable inspect ./receipt.sqlite
node bin/cli.mjs durable recover ./receipt.sqlite synthetic-finding-001
node bin/cli.mjs durable run ./receipt.sqlite receipt-proposal.json --approve
node bin/cli.mjs durable inspect ./receipt.sqlite
```

## Recuperar o registro não exige repetir o efeito

O comando `recover` consulta a operação, o recibo e o estado atual. Neste caso, muda a classificação para `reconciled` e registra a decisão no histórico. Ele não altera o alvo. A repetição da proposta original também retorna `reconciled`, com `retryable: false`. Na inspeção final, a versão permanece 2 e a quantidade de recibos permanece 1.

Esses são os invariantes que vale conferir. O teste não consiste apenas em procurar a palavra sucesso na saída. Compare o efeito observável antes e depois da recuperação. Compare também antes e depois da repetição. Se a versão tivesse aumentado de novo, um status aparentemente correto esconderia uma segunda execução.

O recibo é evidência histórica, não um selo permanente de saúde. Se o alvo sofrer uma mudança posterior, o histórico da operação anterior pode continuar válido enquanto o estado atual diverge. A implementação expõe `driftAfterReceipt` para distinguir essas perguntas. Restaurar o estado desejado, quando necessário, exige uma nova decisão revisada sobre o estado atual.

## Resultados observados nos dois experimentos
| Inspeção | Com recibo | Sem recibo, com drift |
| --- | --- | --- |
| Depois da interrupção / drift | pending; versão 2; 1 recibo | pending; versão 2; 0 recibos |
| Recuperação | reconciled; saída 0 | uncertain; saída 2 |
| Reenvio da proposta original | reconciled; versão permanece 2 | uncertain; versão permanece 2 |
| Chamadas à nuvem | 0 | 0 |

## Experimento 2: o estado parece certo; a autoria é desconhecida

Agora use outro banco. Interrompa a execução em `after-intent`, antes de aplicar o efeito. Em seguida, use o comando didático `drift` para mudar o alvo por outro caminho. Ao recuperar, a propriedade desejada estará verdadeira e a versão será 2, assim como no primeiro experimento. A diferença decisiva é a ausência de recibo.

O resultado observado é `uncertain`, `retryable: false`, com código de saída 2. Reenviar a proposta original mantém essa classificação e não modifica novamente o alvo. O sistema não deduz autoria a partir de coincidência de valores. Esse comportamento troca disponibilidade automática por uma decisão que pode ser investigada.

Não apague a operação para destravar a fila. Isso elimina a memória usada para reconhecer a duplicata. Preserve o banco, a proposta e os resultados; investigue a mudança e defina o próximo passo como uma nova decisão. O laboratório não oferece um botão mágico de resolução administrativa, nem deve sugerir que editar o ledger manualmente seja um procedimento seguro.

_A configuração final coincide, mas o recibo não existe_

```bash
node bin/cli.mjs durable init ./uncertain.sqlite
node bin/cli.mjs durable plan ./uncertain.sqlite > uncertain-proposal.json
node bin/cli.mjs durable run ./uncertain.sqlite uncertain-proposal.json --approve --crash after-intent
# Expected exit: 86
node bin/cli.mjs durable drift ./uncertain.sqlite true
node bin/cli.mjs durable inspect ./uncertain.sqlite
node bin/cli.mjs durable recover ./uncertain.sqlite synthetic-finding-001
# Expected exit: 2, status: uncertain
node bin/cli.mjs durable run ./uncertain.sqlite uncertain-proposal.json --approve
# Expected exit: 2; no additional target change
```

## Três commits explicam as janelas de falha

A implementação persiste a intenção revisada em uma transação. Em outra, compara a versão esperada e grava o alvo junto com o recibo. Em uma terceira, marca a conclusão no ledger. O recibo e a mudança são atômicos entre si; a execução completa não é uma transação única. Essa escolha cria deliberadamente a janela que acabamos de testar.

`BEGIN IMMEDIATE` serializa os escritores no arquivo local, com espera de contenção limitada. O armazenamento usa WAL e `synchronous=FULL`. Essas escolhas dão semântica transacional ao exercício, mas os testes de encerramento de processo não equivalem a testes de energia, disco, corrupção ou sistema de arquivos de rede. O custo da serialização também importa: um único arquivo local não representa escalabilidade distribuída.

A suíte cobre outras fronteiras: queda antes do commit do alvo, antes do commit de conclusão e depois da conclusão; processos concorrentes; identificadores repetidos com payload diferente; propostas sem revisão e alterações de estado. Os 22 testes que passaram são verificações dessas propriedades no laboratório, não uma porcentagem de disponibilidade ou uma taxa de sucesso de um agente em produção.

## C4

Uma pessoa executa um laboratório de recuperação e inspeciona evidências sintéticas. O modelo não chama serviços AWS nem precisa de um LLM.

### Containers
- **CLI** (Node.js): Processos independentes para plan, run, inspect e recover.
- **Persistent store** (SQLite local file): Operações, alvo, recibos e histórico persistidos no mesmo arquivo.

### Components
- **Proposal validation**: Valida o pedido, a revisão simulada e a versão esperada.
- **Conditional mutation**: Atualiza alvo e recibo no mesmo commit.
- **Recovery classifier**: Lê evidências e registra a classificação sem executar a mutação.

## O que muda quando a ferramenta chama AWS

Em uma integração real, gravar intenção em um banco e chamar uma API remota são operações em sistemas diferentes. O commit local não engloba a API. O laboratório coloca alvo e recibo juntos justamente para tornar a evidência inspecionável; transportar o desenho para a nuvem exige reavaliar essa fronteira.

A AWS Builders' Library explica o valor de um identificador explícito fornecido pelo cliente para reconhecer a intenção de repetição. Confira o contrato de idempotência da operação concreta: escopo, validade do token, comportamento com parâmetros diferentes e resposta em chamadas repetidas. Não derive uma garantia universal porque outro serviço aceita um token.

Para cada ferramenta mutável, registre quais evidências remotas estão disponíveis, quanto tempo permanecem consultáveis e quem resolve um resultado inconclusivo. Quando o destino não oferece uma prova suficiente, mantenha um estado de incerteza visível. Adicionar uma fila, um framework de agentes ou um servidor MCP não fecha sozinho o intervalo entre efeito e confirmação.

Também separe a autorização atual da evidência histórica. `--approve` neste laboratório é apenas uma simulação local de revisão. Um serviço real precisa verificar identidade, escopo e validade antes da mutação; uma aprovação revogada não deve virar permissão de nova execução só porque existe um registro antigo. Uma consulta de recuperação e uma tentativa de mutação exigem decisões distintas.

## Decisão: incerteza é um resultado operacional

**Status:** accepted

**Contexto**

O cliente pode perder a resposta depois de um efeito confirmado no alvo.

**Decisão**

Usar evidência persistente para reconciliar; bloquear repetição quando não for possível atribuir o efeito à operação. No laboratório, recover nunca altera o alvo.

**Consequências**
- Exige um responsável por investigar estados incertos.
- Retenção de recibos e propostas faz parte do contrato de recuperação.
- Pode exigir intervenção mesmo quando o estado atual parece desejável.

## Checklist para revisar sua própria ferramenta

1. **Defina a intenção**: Escolha um identificador estável para a mesma operação e uma regra explícita para payload divergente. Salve a proposta original e a versão do contrato.

2. **Localize o commit real**: Marque onde o alvo muda e onde a confirmação é persistida. Provoque uma interrupção entre eles, sem recursos de produção.

3. **Compare efeitos**: Observe versão, recibos e histórico antes e depois do replay. Um status de sucesso isolado não demonstra ausência de duplicata.

4. **Exercite a ambiguidade**: Faça outro ator alterar o alvo sem recibo da operação. Verifique que valor coincidente não se transforma em autoria presumida.

5. **Defina retenção e operação**: Documente a janela em que a evidência existe, o responsável pela investigação e o procedimento de cópia consistente do banco. Feche os processos antes de copiar o arquivo do laboratório.

## Leitura pelos pilares Well-Architected

- **operational-excellence**: Transforme uncertain em uma fila de investigação com responsável e evidências mínimas. A recuperação precisa de runbook e exercício, além de observabilidade.
- **security**: A revisão local não autentica ninguém. Em produção, proteja o ledger, minimize dados sensíveis e revalide a autorização da próxima mutação.
- **reliability**: Teste a perda da confirmação, não somente indisponibilidade antes da chamada. Separe reconhecimento de duplicata, reconciliação e nova tentativa autorizada.
- **performance**: Meça contenção e tempo de reconciliação na arquitetura real. Serialização de escritores no SQLite não é evidência de throughput distribuído.
- **cost**: Os exercícios não fazem chamadas AWS. Para uma implementação remota, estime armazenamento de evidências e consultas de recuperação; não invente economia a partir deste teste local.
- **sustainability**: Evitar efeitos e tentativas redundantes reduz trabalho desnecessário, mas o laboratório não mede energia nem emissões. Não converta essa hipótese em um número.

## Armadilhas que o exercício torna visíveis

- Repetir toda chamada interrompida sem consultar a evidência do efeito.
- Tratar o valor final desejado como prova de que esta operação o produziu.
- Gerar uma nova proposta depois da mudança e chamar isso de repetição da original.
- Apagar recibos ou ledger sem uma política que preserve a janela de reconhecimento de duplicatas.
- Apresentar uma transação SQLite local como garantia de execução exatamente uma vez em serviços remotos.

## Replay para diagnóstico e retry de mutação são coisas diferentes

Para compartilhar um caso reproduzível, leve a versão do código, o estado inicial sintético, a proposta, o ponto de interrupção, os códigos de saída e as inspeções antes e depois. Uma captura de terminal ajuda a comunicar; os arquivos e comandos permitem verificar. Remova credenciais e dados reais antes de abrir uma issue.

A evidência pública associada a este artigo contém os resultados dos dois cenários. A proposta é abrir discussão sobre um contrato concreto: qual observação permitiria reconciliar, qual permitiria tentar novamente e qual exige investigação? Uma contribuição pequena, como acrescentar um caso de falha com resultado esperado, pode ser mais útil do que adicionar mais uma ferramenta ao catálogo.

Se quiser experimentar, execute os dois cenários no repositório e descreva qual fronteira de falha você testaria em seguida. Para trazer isso à sua arquitetura AWS, comece por uma única operação mutável e documente seu contrato de idempotência e recuperação. A partir daí, o teste deixa de ser uma demonstração genérica de agente e passa a ser uma propriedade que a equipe consegue revisar.

## Referências

- [Observed local results: October 27, 2026 (JSON)](https://fernando.moretes.com/community-evidence/2026-10-27-recovery-lab.json)
- [Source and exercise, lab v0.4.0](https://github.com/fernando-moretes/lab-security-remediation/blob/v0.4.0/DURABILITY.md)
- [Executable recovery tests](https://github.com/fernando-moretes/lab-security-remediation/blob/v0.4.0/test/durable.test.mjs)
- [AWS Builders' Library: Making retries safe with idempotent APIs](https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/)
- [SQLite transactions](https://sqlite.org/lang_transaction.html)
- [Node.js SQLite API](https://nodejs.org/api/sqlite.html)

## A pergunta para levar à revisão

Antes de autorizar uma nova tentativa, peça a evidência que distingue efeito confirmado, efeito ausente e efeito desconhecido. Neste laboratório, um recibo permite reconciliar sem executar de novo; sua ausência diante de drift exige parar. O valor do exercício é tornar essa decisão reproduzível e discutível por outra pessoa.
