Agent Mentor Learn
Gerenciamento e persistência de estado: fazendo tarefas longas sobreviverem à interrupção · Lição 2 de 6

Lição 2: Checkpoints: gravando a cena de execução em disco

Objetivos de aprendizado:

  • Nomear os seis campos que pertencem ao checkpoint.json e, para cada um, dizer com o que a retomada esbarra quando ele falta
  • Distinguir os dois pontos de gravação dentro de um único turno do loop (depois que o modelo indica uma ferramenta, depois que o resultado da ferramenta é registrado) e explicar para o que você se expõe ao gravar apenas um deles
  • Escrever um saveCheckpoint que não consegue corromper o próprio arquivo de checkpoint — gravar um arquivo temporário e depois renomear atomicamente, em vez de sobrescrever no lugar

Pré-requisitos: Leia a Lição 1 e saiba distinguir memória de estado de execução; esteja à vontade com o array messages e com o esqueleto de loop dirigido pelo stop_reason de "Agent Harness Fundamentals: Loops and Control" | Anterior: Lição 1 << | Próxima: Lição 3 >>

A cena de execução vive em memória por padrão

A Lição 1 separou memória e estado de execução: memória é o que você entrega ao modelo, estado de execução é a cena em andamento que o próprio harness está segurando — o array messages, o contador de turnos, a chamada de ferramenta cujo resultado ainda não foi registrado. Por padrão, essa cena existe apenas na memória do processo. Quando o processo morre, ela vai junto, e mesmo com todos os outros arquivos em disco intactos, a tarefa só pode recomeçar do zero.

Gravar essa cena em disco — transformá-la em algo que um processo reiniciado possa ler de volta — é o que um checkpoint é. O que de fato sustenta a confiabilidade de tarefas longas na prática normalmente não é pedir ao modelo que absorva sozinho toda falha; é combinar "the adaptability of AI agents built on Claude with deterministic safeguards like retry logic and regular checkpoints"1 (a adaptabilidade de agentes de IA construídos sobre o Claude com salvaguardas determinísticas como lógica de retentativa e checkpoints regulares). Esta lição cobre a metade dos checkpoints: o que entra em um, onde no loop gravá-lo, e como realizar a gravação em si — porque um checkpoint gravado errado pode te deixar em situação pior do que checkpoint nenhum.

O que salvar: os seis campos do checkpoint.json

Um checkpoint não é “despejar tudo o que está em memória em um arquivo”. É “registrar o que a retomada do loop precisa — nem mais, nem menos”. Todas as lições seguintes deste curso rodam sobre o mesmo protocolo:

  • version: o número de versão do protocolo. Este formato vai mudar em algum momento (compressão para messages, um novo formato para pendingToolUse), e version permite que o caminho de retomada pergunte “eu reconheço este checkpoint?” antes de qualquer outra coisa — em uma versão que ele não conhece, deveria se recusar a carregar e falhar de forma barulhenta, em vez de cerrar os dentes e seguir analisando.
  • task: a tarefa original do usuário, em palavras. Depois de um reinício, o código do harness não lembra o que estava fazendo; tudo o que ele pode ler é este arquivo em disco. Sem task, o harness não consegue nem dizer a qual tarefa o checkpoint pertence, quanto mais reportar ao usuário o progresso da retomada.
  • turns: quantos turnos já rodaram. É o que decide se as condições de parada de "Agent Harness Fundamentals: Loops and Control" disparam (um teto máximo de turnos, digamos), e é o número a partir do qual a retomada continua contando, em vez de recomeçar do zero.
  • tokensUsed: o gasto acumulado de tokens. O limiar de compactação de "Context Engineering: Spending Finite Attention Where It Counts" dispara a partir desse número. Deixe-o de fora do checkpoint e a retomada ou finge que a contagem começa em zero — pondo toda decisão de compactação fora de compasso — ou tem que reestimar o uso de cada mensagem em messages, e na maioria das montagens os números históricos de uso simplesmente não estão mais disponíveis.
  • messages: a cena inteira da conversa, cada mensagem user / assistant / tool_result que o modelo viu. É a maior coisa do checkpoint e a única que você não pode pular: o modelo não tem memória própria, e tudo o que ele sabe sobre o que aconteceu antes é este array que você lhe entrega na requisição seguinte. Descarte-o e o que você retoma não é “seguir em frente” — é uma tarefa novinha começando do zero, arrastando junto todo efeito colateral que a execução antiga já produziu.
  • pendingToolUse: ou null, ou um registro no formato { id, name, input } — uma ferramenta que o modelo indicou e cujo resultado ainda não foi registrado. O que fazer com esse campo é assunto da Lição 3, onde a retomada faz a reconciliação contra ele; aqui você só precisa saber que ele é a vaga designada do checkpoint para marcar um estado pela metade. Para manter seu formato simples, todos os exemplos desta lição supõem um bloco tool_use por turno; quando um turno emite várias chamadas de ferramenta concorrentes, transforme-o em um array — o raciocínio é o mesmo.

Quando salvar: dois pontos de gravação por turno

Jogue esses campos dentro do loop e o momento de gravar acaba não sendo tão simples quanto “gravar uma vez ao fim de cada turno”. Há dois pontos de gravação:

O ponto A fica depois que a resposta do modelo chega e antes de a ferramenta rodar: registre o bloco tool_use da resposta em pendingToolUse e então grave. O ponto B fica depois que o resultado da ferramenta foi acrescentado a messages: devolva pendingToolUse a null e então grave de novo.

Só o B basta? A exposição é a janela entre A e B — o modelo indicou uma ferramenta e a ferramenta está rodando, ou terminou mas o resultado não chegou a messages nem foi gravado em disco. Se o processo morre nessa janela, o último checkpoint em disco ainda é o que o B gravou no turno anterior, e ele não sabe nada sobre a chamada deste turno: não é que algum detalhe se perdeu, é que essa chamada de ferramenta não deixou rastro nenhum em disco. A Lição 3 reconcilia na retomada — aquela ferramenta realmente terminou, ela precisa ser reexecutada — e o que ela reconcilia é exatamente o pendingToolUse que o A gravou. Esta lição só cava o buraco; os exercícios da lição 6 põem na sua frente um checkpoint só com B e pedem que você diagnostique o que dá errado na retomada.

Como salvar: você não pode sobrescrever no lugar

A abordagem óbvia é passar o objeto state por JSON.stringify e jogá-lo com fs.writeFileSync direto por cima do velho checkpoint.json. Isso funciona bem quando o processo termina normalmente — mas “terminar normalmente” é exatamente o caso para o qual checkpoints não servem. Checkpoints existem para o processo ser morto a qualquer momento, para a queda de energia, para o contêiner ser despejado. Gravar um arquivo não é uma operação atômica. Se o processo for interrompido no meio da gravação, o checkpoint.json deixado em disco pode estar pela metade: nem a versão antiga, nem a nova, só JSON truncado. A retomada seguinte lança uma exceção no JSON.parse, e aquele arquivo era a única cópia da cena da tarefa — não há versão mais antiga para a qual recuar.

A jogada é “gravar um arquivo temporário e então renomear atomicamente”. Grave o conteúdo completo em checkpoint.json.tmp; se você cair na metade desse passo, a única baixa é o arquivo temporário, e o checkpoint.json de verdade continua sendo a versão anterior intacta, de antes da queda, que a retomada lê sem problema. Assim que o arquivo .tmp estiver completo, use fs.renameSync para movê-lo sobre o nome real. No mesmo sistema de arquivos, rename é uma substituição atômica de um passo só: o sistema operacional ou aponta a entrada de diretório para o novo arquivo por inteiro, ou a deixa apontando para o antigo. Não existe um estado de meio-renomeado no meio do caminho.

Referência de produto: como é um checkpoint no Claude Code

O protocolo que esta lição ensina é para tarefas longas sem supervisão, com granularidade de dois pontos de gravação por turno do loop. Para contraste, veja onde um produto real — o Claude Code — põe a palavra “checkpoint”: "checkpointing automatically captures the state of your code before each user prompt."2 (o checkpointing captura automaticamente o estado do seu código antes de cada prompt do usuário.) "Every user prompt creates a new checkpoint"2 (Todo prompt do usuário cria um novo checkpoint), e "Claude Code saves checkpoints with the conversation, so you can still run /rewind after you resume a session"2 (o Claude Code salva checkpoints junto com a conversa, então você ainda pode rodar /rewind depois de retomar uma sessão).

O cenário que ele atende não é este. Os checkpoints do Claude Code são feitos para uma sessão com humano no loop — o usuário pode parar tudo a qualquer momento, testar uma abordagem, decidir voltar para antes de alguma mensagem e tentar de novo — então a unidade natural é “o usuário disse algo”. O que você está construindo aqui é para tarefas longas sem supervisão: ninguém está de plantão para pedir parada, a unidade é “o loop deu uma volta”, e dentro de um único turno ela se divide outra vez nos pontos de gravação A e B, porque uma queda pode cair entre “o modelo indicou uma ferramenta” e “o resultado foi registrado”. Os dois não estão resolvendo o mesmo problema. Pô-los lado a lado serve, sobretudo, para deixar uma coisa clara: quão fino cortar um checkpoint, e com que frequência gravá-lo, depende de a que o checkpoint serve. Não existe uma resposta única.

Checkpoints não são de graça

Checkpoints custam alguma coisa. Sob o protocolo desta lição, um turno do loop grava em disco duas vezes. Para uma tarefa curta que termina em três ou cinco turnos, isso é puro custo adicional — o processo roda até o fim e esses arquivos de checkpoint nunca são lidos. Se vale a pena pôr esse maquinário no seu próprio harness é algo a medir contra a regra de que "you should consider adding complexity only when it demonstrably improves outcomes."3 (você deveria considerar acrescentar complexidade apenas quando isso melhora comprovadamente os resultados.) Quanto mais longa a tarefa e mais alto o custo de uma queda, melhor fica essa troca; para algo que termina em poucos segundos, você provavelmente não vai precisar dela.

💻 Exercícios

Recapitulação

  • A cena de execução vive em memória por padrão e morre com o processo. O que os checkpoints fazem é poupar uma tarefa longa de reexecutar do zero depois de cada queda, para que ela possa continuar de onde quebrou1
  • O checkpoint.json guarda seis campos: version, task, turns, tokensUsed, messages, pendingToolUse. messages é a maior peça, e sem ele o modelo não tem em que se apoiar sobre o que aconteceu antes; pendingToolUse é o marcador da chamada órfã contra o qual a Lição 3 reconcilia
  • Um turno do loop tem dois pontos de gravação: A depois que o modelo indica uma ferramenta e antes de ela rodar, B depois que o resultado da ferramenta está inteiramente registrado em messages. Salvar só em B deixa um ponto cego ao longo da janela em que o modelo indicou uma ferramenta que ainda não terminou
  • Sobrescrever o arquivo de checkpoint no lugar não é seguro. O processo pode ser morto a qualquer momento, e uma queda no meio da gravação transforma a única cópia da cena em meio documento JSON. Grave primeiro um arquivo .tmp e mova-o para o lugar com fs.renameSync — é isso que garante que o que está em disco a qualquer instante seja uma versão completa
  • Os checkpoints do Claude Code funcionam em outra granularidade — capturados automaticamente antes de cada prompt do usuário2, servindo a uma sessão com humano no loop. O que esta lição constrói é para tarefas longas sem supervisão. Os pontos de corte diferem, mas ambos respondem à mesma pergunta: quando algo dá errado, para onde você volta?
  • Checkpoints não são de graça. Duas gravações em disco por turno são puro custo adicional em uma tarefa curta, e se vale a pena acrescentá-los se resume a se isso melhora comprovadamente o resultado, não a supor que mais é melhor3

>> Lição 3: Retomando de um checkpoint: reiniciando o loop

Footnotes

  1. How we built our multi-agent research system — Anthropic Engineering — https://www.anthropic.com/engineering/multi-agent-research-system 2

  2. Checkpointing — Claude Code Docs — https://code.claude.com/docs/en/checkpointing 2 3 4

  3. Building Effective AI Agents — Anthropic Engineering — https://www.anthropic.com/engineering/building-effective-agents 2

Exercícios

01

Alguém escreveu saveCheckpoint assim:

Nível 1: Complete um checkpoint incompleto

Contra o protocolo que esta lição estabeleceu, quais campos ainda faltam neste checkpoint? Para cada campo faltante, diga especificamente: se você tentasse retomar a partir deste checkpoint incompleto, onde exatamente ele desmoronaria?

Critérios de conclusão · marcado localmente
02

Um colega escreveu o runAgent abaixo para rodar uma tarefa que chama ferramentas duas vezes seguidas. Ele parece correto no dia a dia, mas no instante em que o processo é morto no meio da execução, a cena que você recupera ou não abre ou não bate. Encontre os dois pontos que cedem sob uma queda, diga a que cada um leva e corrija-os — o código corrigido tem que rodar de verdade.

Nível 2: Duas falhas latentes, corrigidas
Critérios de conclusão · marcado localmente