Estado Durável de Agentes: Retomar, Repetir e Reconciliar

Estado Durável de Agentes: Retomar, Repetir e Reconciliar

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

Notícias Relacionadas