Seu agente tomou a decisão certa — e mesmo assim seu sistema quebrou. O usuário clicou em “confirmar reembolso”, a página foi atualizada, o banner de aprovação desapareceu, e ninguém sabe se o reembolso deve ser executado. Pior: a requisição chegou ao gateway de pagamento, o backend reiniciou antes de gravar o estado local, e a recuperação disparou o reembolso de novo. Duas vezes.
Essa é a diferença entre “um agente consegue rodar um loop” e “um agente consegue rodar um produto”. O ReAct te dá Thought → Action → Observation, mas a produção te joga atualizações de página, reinícios de processo, aprovações que levam horas e respostas perdidas. Este post detalha como transformar o julgamento e as ações do agente em fatos de execução recuperáveis e auditáveis — construído sobre a série de engenharia KDC da vivo (links no final).
A ideia central: três tipos de fatos, não misture
- Realidade do domínio — o que realmente aconteceu (o reembolso caiu na conta do usuário). O software só a observa indiretamente, por meio de interfaces, eventos e confirmações.
- Fatos de julgamento de negócio — o que o sistema concluiu e por quê (o pedido é elegível, a política exige confirmação). “Fato” aqui significa que o julgamento foi de fato formado, não que estava correto.
- Fatos de execução — o que esta execução já fez (execução iniciada, aprovação pendente, ferramenta disparada, checkpoint em X).
tool.call.completed ≠ dinheiro chegou. pendingApproval = null ≠ o usuário concordou. Mantenha-os separados e conecte-os com IDs estáveis: runId, turnId, reasoningObjectId, capabilityId, approvalId, toolCallId, feedbackId. É assim que o runtime sabe qual aprovação pertence a qual julgamento, e qual chamada de ferramenta veio de qual capability.
Padrão 1: State / View / Control
A falha clássica é o frontend inferir o estado da execução a partir do texto do chat (“o agente disse que precisa de confirmação → renderizar um botão”). Isso funciona em uma demo e desmorona no refresh. Em vez disso:
- State — fatos de execução persistidos e autoritativos:
activeRun,pendingApprovals,toolCalls,checkpoint. - View — exibição derivada do State:
isBusy,approvalBanner,canStop. Pode mudar junto com a sua UI, mas nunca é a fonte da verdade. - Control — comandos, não escritas de estado:
resume(approvalId, decision),stop(runId),retry(toolCallId).
Regra prática: o runtime grava fatos, a View lê fatos, o Control envia comandos. A UI nunca fabrica fatos de execução.
Padrão 2: fatos de execução com event sourcing
Um único log append-only de eventos fortemente tipados, com projeções por preocupação:
{
"eventId": "event-109",
"eventType": "approval.required",
"runId": "run-20260727001",
"turnId": "turn-003",
"producer": "policy-runtime",
"reasoningObjectId": "reasoning-021",
"capabilityId": "refund-order-v2",
"correlationId": "refund-intent-031",
"causationId": "event-108",
"schemaVersion": 2,
"occurredAt": "2026-07-27T16:42:10+08:00",
"payload": { "approvalId": "approval-017", "risk": "high", "expiresAt": "2026-07-27T18:00:00+08:00" }
}
O estado de aprovação deve ser derivado de eventos de aprovação — nunca porque o modelo disse “o usuário provavelmente vai concordar”. O mesmo log pode gerar estado de execução, visões do usuário, registros de auditoria e traces de avaliação; cada projeção mantém seu próprio schema, dono e direitos de uso.
Padrão 3: chaves de idempotência e result_unknown
Chamadas de alto impacto precisam de uma chave de idempotência estável. E quando uma resposta é perdida, result_unknown é um limite de recuperação distinto — não uma forma rebuscada de dizer “falhou”:
{
"toolCalls": [{
"toolCallId": "tool-call-031",
"capabilityId": "refund-order-v2",
"status": "result_unknown",
"idempotencyKey": "refund:order-001:intent-031",
"externalRequestId": "payment-request-8841",
"dispatchedAt": "2026-07-27T16:48:12+08:00",
"acceptedAt": null,
"resultRef": null,
"reconciliationStatus": "pending",
"lastError": "response_timeout"
}]
}
Se o canal de pagamento pode ter aceitado a requisição, o estado não pode regredir para “não chamado” — mantenha o mesmo toolCallId e idempotencyKey. Na recuperação: consulte o sistema externo pelo externalRequestId, reconcilie e então decida concluir / compensar / escalar para um humano. Nunca faça retry às cegas até authorized.
Padrão 4: contratos de retomada (checkpoint ≠ execução durável)
Um checkpoint só diz onde retomar. Ele não garante que efeitos colaterais aconteceram exatamente uma vez, nem que o replay é seguro. O Interrupt do LangGraph repete o node do início, então efeitos colaterais pré-interrupção devem ser seguros para reexecutar; o Temporal repete o histórico de eventos e confina efeitos colaterais às Activities. Um Resume Contract transforma as regras em algo que o runtime pode executar e testar:
- Chaves de idempotência estáveis para chamadas de alto impacto
- Distinguir comando-enviado / aceito-externamente / confirmado-em-negócio
- Em resultados desconhecidos: consultar ou reconciliar antes de repetir
- Mensagens transacionais ou um outbox para fechar a lacuna entre estado local e publicação de eventos
- Aprovações não podem ser consumidas em dobro; revalide política, permissões e pré-condições na retomada
Também acompanhe separadamente o status de execução e o status de verificação de resultado (proposed → authorized → dispatching → accepted → completed/failed vs not_observed → pending → confirmed_success/failure/inconclusive) — uma execução pode terminar enquanto o resultado ainda está pendente.
Recomendações práticas
- Comece com a separação State / View / Control e tire as aprovações do texto do chat — o maior ganho por unidade de esforço.
- Um log de eventos, eventos tipados, projeções por preocupação. “Um armazenamento” é aceitável; “adivinhar a partir de prosa” não é.
- Desacople a Session (registro append-only), o Harness (loop, contexto, controle) e o sandbox de execução — um harness que quebra pode ser reconstruído a partir da Session, e credenciais de alto privilégio nunca entram no contexto do modelo.
- Para tarefas de vários dias, adicione um Progress Contract estruturado (itens verificados, itens em aberto, próximo passo seguro) para que um agente novo não superestime o progresso, refaça trabalho ou quebre o que já funciona.
- O estado de execução é temporário: arquive checkpoints, feche aprovações, revogue credenciais de curta duração. Promova apenas lições verificadas e com escopo definido para a Memory/Knowledge — persistência ≠ memória.
Recursos
- Original: 如何把 Agent 的判断与行动变成可恢复的软件事实 (KDC engineering supplement, InfoQ, em chinês)
- Anthropic: Managed Agents — harness sem estado, sessão durável, execução em sandbox
- Anthropic: Effective harnesses for long-running agents
- LangGraph persistence e interrupts
- Temporal: event history
- 12-Factor Agents: unify execution state