Continuidade¶
Desde a 0.18.0, uma janela de contexto cheia não encerra mais uma run do Leopold.
Antes encerrava: o engine in-session parava com context_budget quando o transcript
passava de max_context_mb (padrão 5 MB), e a recuperação era um ato humano — alguém
precisava perceber, voltar e digitar /leopold-run. Agora pressão de contexto é
manutenção, não morte: a run faz checkpoint, a janela rola, e a janela seguinte
continua o plano — com continuity: auto (o padrão), sem humano no circuito.
O governador de uma run autônoma é o progresso durável — itens do plano fechados. A run continua enquanto o plano avança e para honestamente quando as janelas param de produzir; nunca porque uma janela encheu, e nunca por causa de um contador de custo (veja por que USD não é governador).
O roll de janela¶
A janela de contexto é um consumível; a run não é. O Stop hook
(hooks/stop-continuity.sh) mede o transcript contra max_context_mb a cada turno:
- Em ~80% do budget, o turno é bloqueado com uma instrução de checkpoint: escrever
ou fazer merge do
.leopold/CHECKPOINT.md(o contrato abaixo) e então continuar o plano. A instrução se repete a cada turno na faixa, e o merge é idempotente, então o checkpoint fica atual enquanto ainda há contexto para escrevê-lo. - Em 100%, a janela fecha. O stop mantém o motivo histórico —
stopped_reason: context_budget, consumidores o leem — mas o estado diz roll, não morte:windowsé incrementado, o vetor de checkboxes do plano é fotografado para o gate de livelock, echeckpoint_writtenregistra se o checkpoint realmente existe. Um checkpoint ausente é nomeado em voz alta na mensagem de stop — a próxima janela retomaria só do brief e doPLAN.md— e a mensagem sempre nomeia o caminho de retomada. - A próxima janela ressemeia.
/leopold-runem um projeto com checkpoint e itens abertos continua o plano sem que ninguém diga nada: brief primeiro, checkpoint depois. Comcontinuity: auto, oleopold watchdetecta o roll e relança a run headless (claude -p/codex exec) no harness dono da sessão; comcontinuity: manual, o ponteiro de retomada na mensagem de stop é a história toda e um humano roda/leopold-run.
Toda continuação é re-aterrada: a ressemeadura lê os próprios arquivos do brief
(nunca um resumo deles), o prompt nomeia a janela (Window N/max), e a run é instruída
a tratar o workspace, os resultados de ferramentas e o estado durável como autoridade
acima de qualquer narração anterior.
O contrato do checkpoint¶
.leopold/CHECKPOINT.md tem um formato com um único contrato de escrita/parse —
packages/driver/src/checkpoint.ts — que o hook, a skill /leopold-run, o driver e o
watcher falam. O documento é a linha de título # Leopold Checkpoint seguida de
exatamente estas sete seções ##, nesta ordem:
- In-Flight Item
- Files and Code
- Errors and Fixes
- Decisions This Run
- Learned Constraints
- Current Work
- Next Step
Três regras o mantêm honesto:
- Estado da run, nunca estado do brief.
MISSION.md,CHARTER.md,GUARDRAILS.mdePLAN.mdsão arquivos duráveis que a próxima janela relê sozinha. Eles estão estruturalmente ausentes da lista de seções, e o parser rejeita qualquer heading fora dela — um checkpoint não pode ganhar uma seção "Mission" por acidente. Um checkpoint que reconta a missão está errado mesmo quando exato: resumir o brief para dentro dele é como o brief deriva. - Merge, nunca aninhar. O checkpoint N+1 consolida o checkpoint N em um documento plano. In-Flight Item, Current Work e Next Step são fotografias — a visão da janela nova substitui a anterior por inteiro. As outras quatro são livros-razão — linhas anteriores ainda verdadeiras são mantidas, novas são anexadas, obsoletas são descartadas. Um documento contendo um checkpoint anterior literal (seu título, ou um heading de seção duplicado) é um merge falho, e tanto o serializador quanto o parser o recusam.
- Falhar alto, nunca truncar. O documento serializado tem teto de 32768 bytes. Um
checkpoint acima do teto falha a escrita com o tamanho no erro e nada chega ao disco
— truncar poderia remover o próximo passo e ainda parecer autoritativo. Um checkpoint
que não pode ser escrito, mesclado ou parseado é nomeado na mensagem de stop e no
leopold doctor, nunca abafado.
O checkpoint também é a passagem de bastão entre engines: o driver SDK escreve o
mesmo arquivo pelo mesmo módulo ao fim de cada run, e qualquer engine pega o checkpoint
do outro — a cadeira passa entre uma run in-session e uma run do driver por um único
artefato. Quando uma run termina limpa, o checkpoint é arquivado com a run
(.leopold/runs/<timestamp>/), para que o estado de uma janela morta jamais semeie a
próxima run como se fosse continuação.
Mais uma fronteira: o checkpoint é dado, não instrução. O que uma janela passada escreveu nele não tem autoridade sobre a janela seguinte — o prompt de continuação o enquadra explicitamente como estado de janela passada não confiável, a verificar contra o workspace, nunca como ordens a seguir.
Compactação: o outro jeito de uma janela ser cortada¶
Um window roll é uma decisão do próprio Leopold. Uma compactação é do harness: ele
descarta o meio do transcript no ritmo dele, e a sessão segue com um resumo no lugar do
que acabou de fazer. Sem intervenção, isso é a mesma amnésia de um roll, menos o handoff —
por isso o hooks/compact-checkpoint.sh monta nas duas metades dela.
PreCompact— antes de o harness cortar, o hook escreve o.leopold/CHECKPOINT.mdpelo mesmo contrato acima e logacompact_checkpointcom o trigger (autooumanual).PostCompact— depois do corte, ele logacompact_resumede devolve o checkpoint à sessão comoadditionalContext, de modo que a janela compactada é re-aterrada no estado durável do run, e não no resumo em prosa do harness.- Ele conta:
compact_checkpointsnostate.jsoné o número de compactações que este run sobreviveu, distinto dewindows.
O checkpoint é composto a partir de estado durável — state.json, PLAN.md, o journal
— e nunca do payload da compactação. Isso não é preferência estilística; é o que torna a
capacidade portátil. O hooks/hook-matrix.tsv marca o compact-checkpoint como
available nos dois harnesses: a sonda pegou o Codex CLI 0.152.1 disparando
PreCompact e PostCompact com trigger mais turn_id e model. O que o Codex não
manda é o custom_instructions antes e o compact_summary depois do Claude Code — e um
hook que tivesse montado seu checkpoint a partir do resumo teria sido Claude-only por
construção. Composto a partir de estado durável, isso não custa nada (capturas:
Hook Events,
PostCompact).
O teto é o do contrato, não um novo: um merge grande demais escreve nada, loga
checkpoint_oversize com a contagem de bytes e diz isso no systemMessage; um checkpoint
que não pode ser mesclado loga checkpoint_unmergeable. Truncar para caber semearia a
próxima janela com uma mentira confiante, então isso não acontece.
O gate de livelock: rolar é de graça, produzir é obrigatório¶
Ressemeadura não é progresso; só item de plano fechado é. A cada roll o hook compara o
vetor de checkboxes do plano com a fotografia tirada quando a janela começou e registra
quantos itens a janela que termina fechou (window_progress no state.json, um número
por janela):
- Uma janela que fechou pelo menos um item zera a sequência de zeros e o roll prossegue.
- Duas janelas consecutivas fechando zero itens param a run com
no_progress_across_windows, nomeando as duas janelas e o item em que travou. Nenhum ponteiro de retomada é escrito e nada relança uma terceira janela para "dar mais uma chance" — essa terceira janela é exatamente o livelock que o gate existe para parar. Uma pessoa olha o item travado; desbloqueado,/leopold-runinicia uma nova tentativa deliberada.
Budgets sobrevivem ao roll¶
Uma ressemeadura que renovasse um budget seria um loop desgovernado com passos extras, então nada renova:
iteration/max_iterationsé o teto da run, não da janela. O contador atravessa as janelas; a janela 4 retoma na iteração em que a janela 3 terminou.max_windows(padrão 10, definido noGUARDRAILS.md) limita o total de janelas de contexto que uma run pode consumir. Alcançá-lo para a run commax_windows, nomeando o teto — sem roll, sem ponteiro de retomada.- One-shots gastos continuam gastos. O resgate de falha e o reparo de deadlock são uma-vez-por-run; o estado os carrega por toda ressemeadura.
- O kill switch vence o
continuity: auto, sempre — e espera, nunca queima. Enquanto o.leopold/STOPexistir nada dispara e nada é registrado; remova-o e a decisão de relançamento é feita de novo, do zero. O watcher também rechecar por conta própriamax_windowse o gate de livelock antes de relançar, recusando com um eventowindow_relaunch_refusedque nomeia o motivo. Um relançamento nunca limpa oSTOP, nunca renova um budget. - Um roll velho pertence a um humano. O watcher só relança um roll que acabou de
ver (até 10 minutos — a mesma linha de staleness que a skill da run já traça). Abrir
o
leopold watchnum projeto cuja run rolou horas atrás recusa comstale_rollem vez de criar um agente headless como efeito colateral de abrir um dashboard; o/leopold-runretoma quando você quiser. - Disparado não é retomado. Depois de relançar, o watcher verifica que o filho
realmente reativou a run; um filho que nunca reativa é registrado como
window_relaunch_failede dito no terminal — a cadeira volta pra você, nunca fica vazia em silêncio.
O watcher não é um agendador: ele reage a um roll de janela detectado (um evento
window_relaunch marca cada um), nunca acorda uma run por timer.
Um erro de API é uma espera, não um veredito¶
Um turno que morre na API não é um roll nem uma falha do trabalho: o Stop nunca dispara
para um turno que falhou, então o hooks/stop-failure.sh é a única testemunha — ele para
a run com stopped_reason: api_error e um bloco api_error: {type, at, retryable, hint}
(Claude Code; no Codex CLI 0.152.1 o evento não existe, então um erro de API encerra a run
como uma parada comum e o /leopold-run a retoma na mão).
Sob continuity: auto, o leopold watch trata uma classe retryable (rate_limit,
overloaded, server_error) como trata um roll — relança a run headless pelo mesmo
comando claude -p / codex exec — mas com backoff que dobra:
| Tentativa | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| Dispara depois de | 30s | 120s | 120s | 240s | 480s |
-
Todo degrau depois do primeiro espera a carência de reativação. O primeiro é medido a partir da recusa da própria API, então mantém seus 30s. Os demais são medidos a partir do nosso spawn da tentativa anterior, e um agente relançado pode legitimamente levar até 120s (
CONTINUITY_REACTIVATE_SECS, a mesma carência que o caminho do roll lhe dá) para viraractive: true— uma partida a frio cuja primeira chamada está sendo throttled é exatamente a condição que produziu a parada. Por isso o backoff que dobra tem esse piso: um segundo degrau de 60s sem piso colocaria um segundoclaude -p /leopold-runno mesmo checkout enquanto o primeiro ainda sobe, dois donos disputando ostate.json. -
Cinco tentativas, depois uma pessoa. A sexta é recusada com
api_error_relaunch_refused(motivoattempts), nomeando a classe do erro. Cinco tentativas em ~15 minutos é uma instabilidade passageira; além disso é uma queda que você precisa ver. - Uma classe não-retryable é recusada uma vez, pelo nome. Autenticação, cobrança,
requisição inválida ou uma classe que o Leopold não reconhece: o que falhou foram as
credenciais ou a entrada, não o trabalho, então nada é retentado e a recusa nomeia a
classe. O veredito é do hook — classificado lexicamente a partir do campo
errordo próprio harness — e o watcher nunca o re-deriva. windowsnunca é tocado. Nenhuma janela foi consumida, então nenhuma é cobrada: cobrar uma gastaria o teto de janelas da run com a queda da API e, no teto, transformaria um 429 passageiro numa parada permanente pormax_windows. O teto de tentativas é o que limita a retentativa.max_windows, o kill switch, o frescor econtinuity: manualcontinuam valendo, na mesma ordem em que valem para um roll.- Exatamente uma vez, mesmo com restart. A contagem de tentativas e o horário da
próxima vivem no
state.json(api_error_attempts,api_error_next_at,api_error_relaunch_at,api_error_result), escritos antes do spawn — um watcher reiniciado no meio da escada a retoma em vez de recomeçá-la. A pílula de status mostrastopped · api_error (retry in 47s)enquanto espera. - O reseed carrega os budgets. O Passo 1 do
/leopold-runcarregaiteration,windows,window_plan_vector,window_zero_streakewindow_progressnumapi_errorexatamente como num roll. Sem isso, uma run rate-limitada ganharia bolso novo em cada uma das cinco retentativas.
Cada relançamento loga api_error_relaunch (tentativa, atraso, tipo do erro, harness);
cada recusa loga api_error_relaunch_refused com o motivo. Os dois aparecem no
leopold watch.
Custo em billing API. O cap do checkpoint é proporcional à janela —
min(32KB, 2% de max_context_mb) — então o único knob que governa custo por turno
governa o checkpoint também: um projeto em API que abaixa max_context_mb ganha turnos
mais baratos, rolls mais cedo e checkpoint proporcionalmente menor, automaticamente. Um
max_checkpoint_kb: explícito no GUARDRAILS.md sobrepõe a fórmula. O cap nunca deriva
de billing nem do comportamento da própria run — um cap que a run pudesse crescer
sofrendo seria um budget que se aumenta sozinho.
O que ainda para uma run¶
Contexto não para mais uma run; estes param. É a mesma lista completa de O que ainda para a run — as duas condições novas na 0.18.0 estão marcadas:
| Stop | O que significa |
|---|---|
plan_complete |
Nenhum item aberto resta no PLAN.md. |
kill_switch |
.leopold/STOP existe. Vence o continuity: auto, sempre. |
no_progress_across_windows — novo |
Duas janelas consecutivas fecharam zero itens do plano (o gate de livelock). Sem relançamento. |
max_windows — novo |
A run consumiu seu teto de janelas (padrão 10). Sem relançamento. |
api_error |
Um turno morreu na API (só no Claude Code — o Stop não dispara para um turno que falhou). Classes retryable são relançadas até cinco vezes com backoff que dobra; uma classe não-retryable para aqui. |
iteration_budget |
O contador de iterações da run inteira alcançou max_iterations (padrão 50) — somando todas as janelas. |
repeated_failure |
A mesma falha bateu o teto depois da única mudança de abordagem conduzida por persona. |
no_progress |
N turnos seguidos sem mudança na assinatura do plano (dentro de uma janela). |
budget_exceeded |
O gasto real cruzou --budget-usd, se você optou por ele (driver, cobrança por API). |
escalation, deadlock / invalid_graph |
Um fork ou grafo de plano que nada conseguiu resolver. |
routed_complete |
Uma rota levou a run a um nó terminal; os itens restantes são nomeados no relatório. |
awaiting_human |
Só com autonomy: ask. |
E a fronteira que nunca se moveu: o lock de git. Uma run que sobrevive a dez
janelas ainda prepara e reporta; o humano é quem entrega. context_budget ainda
aparece como stopped_reason — mas agora marca um roll de janela em uma run que
continua, não uma morte.
Um dono por run¶
Um run é conduzido por uma sessão, registrada como owner no state.json quando ele é
ativado. O hook de Stop continua e conta só essa sessão; qualquer outra sessão que pare
dentro do checkout — uma segunda janela aberta para uma pergunta sem relação, um
claude -p headless, os próprios workers do driver — pode parar e é avisada de quem é o
dono (Hooks: propriedade da sessão).
Propriedade e roll se encaixam. Um roll encerra o estado ativo do run, então o
/leopold-run da próxima janela — o relançamento headless do watcher ou o seu — assume
o assento sem esperar; uma sessão mantém o id através de --resume, então uma janela
que compacta ou retoma continua conduzindo. O que uma sessão nova não pode fazer é
começar ao lado de um dono vivo: /leopold-run se recusa enquanto o dono mostra sinal de
vida dentro de dez minutos (um pid do harness vivo, um turno contado, uma escrita na
transcrição) e assume um abandonado, registrando owner_takeover; --takeover força e
fica registrado como forçado. /leopold-stop se recusa a encerrar o run de outra sessão
viva sem --force.
As três memórias¶
Desde a 0.19.0 uma run lembra em três camadas distintas. Elas não se sobrepõem — cada uma responde uma pergunta diferente, e uma feature que borra duas delas está errada mesmo quando conveniente:
| Memória | O que ela lembra | Vida útil | Funciona offline |
|---|---|---|---|
.leopold/CHECKPOINT.md |
O estado de trabalho DESTA run: item em andamento, erros e correções, próximo passo. | Morre com a run — arquivado no fim limpo, nunca semeia a run seguinte. | Sim — arquivo simples, sem rede. |
.leopold/runs/ + leopold recall |
O arquivo de decisões do próprio projeto: MISSION, PLAN e DECISIONS.md de cada run concluída, com o Reversal anexado. leopold recall <query> busca nele, ranqueado e limitado ao cwd; toda run começa com um digest limitado das decisões mais recentes. |
Versionado com o repo — greppável, revisável, sobrevive a tudo que o repo sobrevive. | Sim — busca lexical, zero rede, zero dependências. |
| ovmem (OpenViking) | Aprendizado destilado entre runs: o que as sessões da máquina ensinaram, deduplicado e reconsolidado no servidor, compartilhado entre projetos e harnesses. | Sobrevive a tudo — vive fora do repo, no store de memória da máquina. | Não — precisa do servidor OpenViking local (e do provedor de embedding). Opcional por construção: ausente, tudo acima continua funcionando. |
Duas regras amarram as três:
- Texto de run passada é conteúdo não confiável. Um checkpoint, um trecho do recall, um digest de início de run, uma memória reidratada: tudo dado, nunca instrução. Toda superfície que injeta texto de run passada em um prompt carrega a mesma frase de enquadramento, e um teste de drift falha se qualquer ponto de injeção a perder.
- Memória é enriquecimento, nunca dependência. Um projeto sem runs arquivadas começa byte-idêntico a um sem a feature; um servidor ovmem morto loga uma linha e a run continua.
Quando uma run está ativa, os flushes do ovmem são cientes da run: a sessão OV
ganha o título leopold · <projeto> · run <stamp> · window N of M · <engine> em vez
de um UUID cru, então um humano navegando a memória distingue janelas de chats.
Workers do driver SDK são efêmeros — os hooks deles disparam sim (verificado ao vivo,
evidência em SDK Worker Hooks), então o ovmem
suprime flushes por item por padrão e a sessão do condutor carrega a run.
Por que USD não é governador¶
total_cost_usd não reflete a contabilidade real na cobrança por assinatura, então um
teto em USD não pode ser o governador padrão de uma run autônoma — ele mentiria para a
maioria dos usuários. O governador é o progresso durável: itens de plano fechados são
reais em qualquer plano de cobrança e qualquer harness. Usuários cobrados por API que
querem um teto rígido de custo ainda podem optar pelo --budget-usd do driver — é o
teto deles, nunca o padrão. Veja Custo & Segurança.
Configuração¶
No .leopold/GUARDRAILS.md:
## Continuity
- continuity: auto # auto | manual
- max_windows: 10 # total de janelas de contexto que uma run pode ocupar
max_context_mb (padrão 5) continua o que era: o tamanho de uma janela. O leopold
doctor reporta a postura de continuidade, a contagem de janelas contra max_windows,
o progresso da última janela, e se o checkpoint está presente, ausente ou malformado —
um checkpoint malformado é um problema nomeado, nunca silêncio.