O loop só é tão bom quanto o seu done-when. Antes de o agente ler um único arquivo, você escreve um pequeno contrato que diz o que "terminado" significa — de forma mensurável. Acerte isso e o loop sabe exatamente quando parar. Deixe vago e ele roda para sempre, ou para no lugar errado e declara pronto.
Na lição anterior você viu o loop: learn → analyze → execute uma unidade delimitada → verify no boundary real → decide. Essa última palavra, decide, é onde o loop ou roda de novo ou para. Mas parar quando? Algo precisa dizer ao loop, em termos mensuráveis e claros, que o trabalho de fato terminou. Esse algo é o contrato de escopo.
Você o escreve uma única vez, no início, em um arquivo pequeno chamado SCOPE.md. Não é um plano nem uma spec — é mais curto que os dois. Ele responde a seis perguntas: qual é a meta, qual é o contexto, o que não pode mudar, como sabemos que está pronto, o que o agente pode tocar e quem está fazendo o trabalho. O coração dele é uma única linha chamada done-when — a condição de saída.
O truque inteiro é este: um done-when tem que ser algo que uma máquina possa checar, não algo que um humano tenha que sentir. "Deixar o login melhor" nunca pode ser verdadeiro ou falso — então um loop perseguindo isso nunca pode parar. "p95 do login abaixo de 400 ms e zero 5xx em um teste de carga de 10 minutos" ou é verdade ou não é — então o loop sabe, por conta própria, o momento exato de parar.
Uma frase para lembrar: o loop não decide quando está pronto — seu done-when decide. A qualidade dessa única linha define o teto de tudo o que o loop pode fazer.
Pense nisso como… um empreiteiro reformando sua cozinha. Se você disser "deixe bonito", ele pode pintar uma parede e declarar vitória, ou ficar "melhorando" até você ficar sem dinheiro — e você não tem como discutir nem de um jeito nem de outro. Se você disser "armários novos instalados, pia com água quente e fria, inspeção aprovada", todo mundo sabe o instante exato em que o trabalho terminou. Onde isso quebra: diferente de um empreiteiro, o loop não tem bom senso para se apoiar — ele vai levar "deixe bonito" totalmente ao pé da letra, então o contrato precisa carregar todo o sentido.
Mais uma coisa para situar antes de abrirmos isto: SCOPE.md não é o único contrato de que você vai ouvir falar neste curso. Há uma escala de zoom de três. Um PRD é o panorama amplo (por quê, para quem, o produto inteiro). SCOPE.md é o contrato de um loop — a página única que você está aprendendo agora. E GOAL.md é o run-contract verificável por máquina que o Forge compila para uma execução autônoma longa (lições 8 e 10). A mesma ideia em três níveis de zoom.
Um loop é uma estrutura de controle: while (!done) { learn; analyze; execute; verify; }. O predicado done é avaliado a cada ciclo. Se esse predicado não for decidível a partir do estado observável, o loop não tem garantia de terminação — é, formalmente, um programa sem condição de parada definida. "Melhor", "mais limpo", "pronto para produção" não são predicados sobre estado; são julgamentos subjetivos, então o loop nunca consegue avaliá-los e nunca pode sair de forma legítima.
SCOPE.md é o contrato leve, escrito por humano, com que você inicia qualquer loop não-trivial — seis campos, muitas vezes em menos de uma página. É a entrada do loop, a régua contra a qual o passo decide mede. Não é o mesmo que GOAL.md: GOAL.md é o contrato compilado, de execução autônoma que o passo /goal do Forge emite (em blocos XML: goal / context / constraints / verification / done-when) para uma execução longa sem supervisão — você vai conhecê-lo nas lições 8 e 10. E um PRD é ainda mais amplo: mercado, persona, rollout. Pense nisso como uma escala de zoom: PRD (o porquê & o quê, muitas páginas) → SCOPE.md (o contrato de um loop, uma página) → GOAL.md (o run-contract verificável por máquina contra o qual o loop executa).
Uma cláusula done-when é mensurável quando nomeia (1) uma métrica ou artefato observável, (2) uma comparação ou limiar, e (3) o boundary onde é checada. "Os testes passam" é fraco (quais testes? onde?); "npm test sai com 0 e 0 falhas no CI" é forte. O passo verify (lição 6) roda essa checagem no boundary real — nunca uma alegação, nunca um mock — e o passo decide compara o resultado com este contrato.
Aqui está onde o contrato fica dentro do loop. Cada ciclo roda learn → analyze → execute → verify, e então bate num gate. O gate faz uma pergunta, tirada direto do seu done-when: já é verdade? Não → roda de novo. Sim → para. O contrato é o gate.
done-when vago torna o gate não-avaliável — o loop nunca consegue sair de forma legítima.E aqui está o custo de errar, lado a lado. Um contrato vago deixa o gate travado e aberto (o loop não consegue distinguir verdadeiro de falso, então ele deriva); um contrato mensurável dá ao gate um sim/não limpo.
SCOPE.mdUm contrato de escopo tem exatamente seis campos. Nenhum é opcional — cada um fecha uma forma específica de o loop dar errado. Aqui estão eles como uma anatomia rotulada, e depois como uma faixa clicável pela qual você pode navegar: escolha qualquer campo para ver para que serve, como é uma boa versão e o que quebra sem ele.
Para que serve: o único resultado que este loop existe para produzir, em uma frase que um estranho conseguiria ler. Não uma lista de tarefas — o resultado.
# Goal
Cut RHG checkout p95 latency so it stops timing out at peak.Para que serve: o punhado de fatos que o agente precisa saber antes de tocar em qualquer coisa — onde o código vive, qual é o comportamento atual, a única pegadinha que não é óbvia. É isto que o passo LEARN (lição 3) confirma contra a realidade.
services/checkout; o caminho crítico é a chamada de reprecificação do carrinho."# Context
Checkout lives in services/checkout. Hot path is the cart
re-price call on every keystroke. Prod is us-east, behind a CDN.Para que serve: as linhas que o agente não pode cruzar enquanto persegue a meta — a API pública continua estável, nenhuma dependência nova, os design tokens são fixos. A próxima seção transforma isto em algo que você consegue de fato enxergar.
/checkout."# Constraints
- Do NOT change the public /checkout request/response shape.
- No new runtime dependencies.
- Stay on the existing design tokens (no raw hex).Para que serve: a condição de saída mensurável. É a linha que o gate lê a cada ciclo. Cada cláusula precisa nomear uma métrica, um limiar e o boundary onde é checada — para que possa ser verdadeira ou falsa, nunca "mais ou menos".
npm test sai com 0 no CI"# Done-when
- checkout p95 < 400ms over a 10-minute load test at peak RPS
- 0 responses with status >= 500 during that test
- `npm test` exits 0 with 0 failures in CI
- the /checkout API contract test still passes unchangedPara que serve: o conjunto exato de arquivos ou diretórios que o agente pode alterar. Todo o resto é somente-leitura. Isso impede que um "conserto pequeno" reescreva metade do repo sem você notar.
services/checkout/** e seus testes."# Editable surface
- services/checkout/** # implementation
- services/checkout/__tests__/** # its tests
# everything else: READ-ONLYPara que serve: quem faz o quê. No mínimo: quem constrói (o Executor) e quem prova contra este contrato (o Validador). A regra que torna a prova confiável: o Validador nunca é o agente que construiu (lição 9).
# Agents
- Executor: agent-A (tier: execute)
- Validator: agent-B (tier: analyze) # NEVER the builder# Goal Cut RHG checkout p95 latency so it stops timing out at peak. # Context Checkout lives in services/checkout. Hot path is the cart re-price call on every keystroke. Prod is us-east, behind a CDN. # Constraints - Do NOT change the public /checkout request/response shape. - No new runtime dependencies. - Stay on the existing design tokens (no raw hex). # Done-when - checkout p95 < 400ms over a 10-minute load test at peak RPS - 0 responses with status >= 500 during that test - `npm test` exits 0 with 0 failures in CI - the /checkout API contract test still passes unchanged # Editable surface - services/checkout/** - services/checkout/__tests__/** # Agents - Executor: agent-A (tier: execute) - Validator: agent-B (tier: analyze) # never the builder
É só um arquivo Markdown que você escreve à mão antes da execução. Crie ou abra a partir da raiz do repo:
# create it next to the work, then open in your editor $ touch SCOPE.md && $EDITOR SCOPE.md # confirm a loop run is pointed at it $ grep -n "Done-when" SCOPE.md
No front-end Forge (lição 8) esse mesmo raciocínio de seis campos é o que o passo /goal compila no GOAL.md autônomo; para um loop interativo rápido você costuma escrever o SCOPE.md à mão e ir direto para a execução.
done-when — veja a condição de saída virarAgora a habilidade central, na prática. Digite um done-when abaixo — ou carregue um dos chips de exemplo. O painel à direita avalia ao vivo contra as três coisas de que uma condição de saída mensurável precisa: uma métrica, um limiar e um boundary. Veja o veredito virar entre vago → o loop não consegue parar e mensurável → o loop sabe quando sair.
O avaliador é uma pequena função pura sobre o texto. Ele varre três sinais: uma métrica (um substantivo mensurável conhecido ou um número-com-unidade), um limiar (uma palavra/operador de comparação ou um alvo) e um boundary (uma frase de "onde" — no CI, em prod, ao longo de um teste). Uma cláusula é measurable só quando os três estão presentes (mais a checagem de ausência de palavras-sensação no modo estrito). Qualquer coisa menos que isso é vague, e o resultado explicita a peça faltante — exatamente o que o passo decide do loop não conseguiria avaliar.
function grade(text, strict) { const t = text.toLowerCase(); const hasMetric = /\b(p9\d|latency|error rate|uptime|conversion|\d+\s?(ms|s|%))\b/.test(t); const hasThreshold = /(under|below|over|above|<|>|>=|<=|at least|exits 0|\d)/.test(t); const hasBoundary = /(in ci|in prod|production|staging|load test|over a)/.test(t); const feelings = /\b(better|faster|fast|good|nice|clean|improv)/.test(t); const ok = hasMetric && hasThreshold && hasBoundary && (!strict || !feelings); return { ok, hasMetric, hasThreshold, hasBoundary, feelings }; }
O ponto não é o regex — é a forma: métrica + limiar + boundary é o mínimo sobre o qual um passo verify consegue agir. Loops reais deixam um LLM julgar isso em linguagem natural, mas o teste é idêntico: uma máquina, olhando apenas o estado observável, conseguiria retornar verdadeiro ou falso?
O erro de escopo mais comum de todos é um done-when que soa como uma meta mas não pode ser checado. Aqui estão cinco linhas vagas e a reescrita mensurável de cada uma — a mesma intenção, transformada em algo que o gate consegue avaliar. Leia lado a lado: note que toda reescrita adiciona uma métrica, um limiar e um boundary.
Sem número, sem lugar para checar. O loop pode rodar para sempre e nunca estar "errado".
done-when: "checkout feels fast" done-when: "the page is snappy"
Métrica (p95), limiar (< 400ms), boundary (teste de 10 min). Verdadeiro ou falso.
done-when: "checkout p95 < 400ms over a" "10-minute load test at peak RPS"
"Menos" comparado a quê, medido onde? Nada para avaliar.
done-when: "fewer errors" done-when: "more reliable"
Uma taxa, um teto e o boundary em que é lido.
done-when: "5xx error rate below 0.1%" "in production over 24h"
"Passar" é um bom instinto, mas pouco especificado: quais testes, rodados onde?
done-when: "tests pass" done-when: "no bugs"
Um comando exato, um código de saída, um lugar. O passo verify consegue rodá-lo.
done-when: "`npm test` exits 0 with" "0 failures in CI"
"Melhorar" é aberto por definição — sempre há mais.
done-when: "improve signups" done-when: "grow conversion"
Uma taxa-alvo ao longo de uma janela definida — alcançável e verificável.
done-when: "signup conversion >= 22%" "over a 7-day A/B test"
"Pronto para produção" esconde uma dúzia de checagens não-ditas. Definição de quem?
done-when: "make it production-ready" done-when: "ship-quality"
Explicite a dúzia como cláusulas concretas. Agora "pronto" é decidível.
done-when: "p95 < 400ms, 5xx < 0.1%," "tests green, a11y scan 0 errors"
O sinal: se você não consegue imaginar a checagem exata que provaria — um comando, uma métrica lida num lugar — a cláusula ainda está vaga. Continue reescrevendo até conseguir.
Done-when diz quando parar. As constraints dizem o que o agente não pode mexer no caminho. A forma mais limpa de fixar uma constraint é apontar para algo já nomeado e fixo — um design system de tokens é o exemplo clássico: em vez de "mantenha na marca" (vago), você diz "use apenas estes tokens" (verificável). Aqui está o tipo de superfície fixa que um escopo fixa, e o jeito certo vs. errado de referenciá-la.
| A constraint referencia | Significa | Como o loop checa |
|---|---|---|
| apenas design tokens superfície · conjunto fixo | Nenhum hex cru; toda cor vem de um token nomeado. | grep no diff por literais hex # → não pode haver nenhum. |
| API pública congelada contrato · estável | Formato de requisição/resposta de /checkout inalterado. | O teste de contrato da API continua passando inalterado. |
| nenhuma dependência nova superfície · fechada | Nada adicionado ao lockfile. | git diff no lockfile está vazio. |
| editable surface caminhos · allow-list | Só os diretórios nomeados podem mudar. | Todo caminho alterado está sob a allow-list. |
O jeito certo vs. errado de escrever uma destas:
Aponta para um conjunto nomeado e já fixo. Uma máquina consegue verificar.
# Constraints - colors: use design tokens only # (--clay, --olive, …) — no raw hex
"Na marca" é uma sensação. Nada para grepar, nada para provar.
# Constraints - keep it on-brand and tasteful # …says who? checked how?
Escolha um tipo de constraint
Rigor
# constraint
Note que toda boa constraint tem a mesma forma de um bom done-when: uma métrica/observável, um limiar (muitas vezes "inalterado" ou "zero") e um boundary. "Nenhum hex cru no diff" é métrica (contagem de literais hex) + limiar (= 0) + boundary (o diff). É por isso que a mesma lógica de avaliação funciona para os dois — uma constraint é um invariante que o loop precisa manter a cada passo, enquanto um done-when é a condição que o deixa parar. Fixar a um artefato nomeado e fixo (o conjunto de tokens, o teste de contrato, o lockfile) é o que torna a checagem mecânica em vez de subjetiva.
O campo Agents não nomeia só quem está envolvido — ele define o nível de autorização de cada agente: até onde ele pode ir sem um humano. São três, e eles se empilham como permissões:
analyze = só ler e raciocinar (olhar, nunca tocar). execute = fazer a mudança delimitada (editar arquivos, rodar o build). destructive = ações irreversíveis (deploy em prod, apagar dados, force-push). Cada nível mais alto requer o de baixo — você não pode conceder execute sem analyze, nem destructive sem execute.
Acione os interruptores abaixo para conceder níveis. Ligue um nível cujo pré-requisito está desligado e o painel avisa que a concessão não vai valer — do mesmo jeito que um contrato de escopo recusa uma autorização inconsistente. A linha de resumo sempre relê o que o agente de fato está liberado a fazer.
Pense nisso como… chaves num chaveiro. A chave de ler-o-prédio deixa você andar pelos corredores. A chave da oficina só funciona se você já tiver a chave do prédio. A chave mestra que derruba uma parede é inútil sem a chave da oficina antes. Você entrega o menor chaveiro que resolve a tarefa — e a um loop que roda AFK normalmente se entrega analyze + execute, com destructive retido atrás de um humano.
analyze ⊂ execute ⊂ destructive. Você não pode segurar um anel externo sem o interno — e a linha tracejada é onde um loop sem supervisão para e um humano assume o passo irreversível.Ler arquivos, rodar checagens somente-leitura, raciocinar sobre a lacuna. Olha, nunca muda. Os passos LEARN e ANALYZE vivem aqui.
Editar arquivos dentro da editable surface, rodar o build e os testes. O passo EXECUTE. Reversível por um revert.
Ações irreversíveis: deploy em prod, dropar uma tabela, force-push, enviar e-mails reais. Normalmente retido atrás de um handoff humano.
Este agente está liberado a
Cada nível é um booleano que o contrato concede. Um segundo mapa registra o que cada nível requer abaixo dele; um nível concedido cujo pré-requisito está faltando fica insatisfeito — não pode entrar em vigor, então o editor o sinaliza, exatamente como um runner real recusaria agir sobre uma concessão inconsistente. A autorização efetiva que o resumo lê é o maior prefixo satisfeito da cadeia.
const grant = { analyze:false, execute:false, destructive:false }; const requires = { analyze: [], // base tier execute: ['analyze'], // can't change what you can't read destructive: ['execute'] // irreversible needs reversible first }; function missing(tier) { return requires[tier].filter(t => !grant[t]); } function effective() { // what the agent may actually do return Object.keys(grant).filter(t => grant[t] && missing(t).length === 0); }
Uma execução autônoma (lições 7, 9) normalmente recebe analyze + execute e não destructive. O loop pode construir e provar uma mudança o dia todo; o único passo irreversível — colocar em prod — é a bifurcação genuinamente só-do-usuário que dispara um handoff. É também por isso que o Validador recebe só analyze: ele prova, nunca muda. computer-use é apenas-AX e não-bloqueante, então ele nunca sequer alcança o nível execute no sistema real.
Juntando tudo: o contrato não é burocracia, é a fiação da decisão do loop. Done-when é o gate que encerra a execução. As constraints são guardas checadas a cada ciclo. A editable surface e os níveis cercam o que cada ciclo pode fazer. Aqui está tudo como uma máquina de estados.
Tudo neste diagrama roda AFK. O único papel do humano é observabilidade — ler LOOP-LOG.md / status / review.md enquanto a máquina trabalha. O humano não executa nada, nem mesmo o QA. A única exceção é o ramo tracejado destructive: quando o único passo restante é irreversível e genuinamente só-do-usuário, o loop emite um handoff pronto para decisão e bloqueia ali — nunca em outro lugar. Um SCOPE.md bem escrito é o que torna isso seguro: as constraints mantêm os ciclos dentro dos limites sem supervisão, e o done-when mensurável deixa o loop terminar a si mesmo em vez de esperar ser avisado.
done-when — um relatório de status ao vivoComo toda cláusula é mensurável, o progresso do loop contra o contrato se lê como um dashboard. É exatamente isto que o humano observa enquanto o loop roda AFK — nunca tocando nele, só lendo. Cada cláusula done-when é uma linha com um selo ao vivo de passa / pendente / falha; a faixa no topo só fica verde quando todas as cláusulas passam. Aperte Rechecar para puxar uma leitura nova, ou ligue Ao vivo para ver o loop convergir.
Status do done-when — loop do checkout RHG
lendo SCOPE.md · verify @ boundary real · o humano apenas observa
| Cláusula do done-when | Status | Leitura | Boundary |
|---|
Um único array de objetos-cláusula dirige tanto a tabela quanto a faixa. Cada tique relê cada métrica no seu boundary e recalcula o status da cláusula. O gate geral é um AND lógico: a execução está done só quando todas as cláusulas passam — qualquer falha mantém a faixa vermelha e o loop rodando. É o passo decide tornado visível. Note que o humano não faz nada aqui além de ler; o loop recheca a si mesmo e converge.
Uma cláusula pode estar pendente — sua checagem não terminou neste ciclo (ex.: o teste de carga de 10 minutos ainda está rodando). Pendente não é passa; o gate trata só uma passagem afirmativa como satisfeita, então uma checagem pela metade nunca consegue disparar uma saída precoce.
Uma pergunta, sem consultar. Escolha o done-when que um loop conseguiria de fato avaliar e no qual conseguiria parar. Clique em uma opção para ver se ela se sustenta.
Qual done-when deixa o loop decidir, por conta própria, exatamente quando parar?
Por que C: ela nomeia uma métrica (p95, contagem de 5xx), limiares (< 400 ms, zero) e um boundary (um teste de 10 minutos). A, B e D cada uma se apoia numa sensação — "mais rápido", "feliz", "limpo / pronto para produção" — para a qual nenhuma máquina consegue retornar verdadeiro ou falso, então o loop que as persegue nunca pode sair de forma legítima.
Aqui está a forma de como um runner de fato consome o SCOPE.md: ele faz o parse do contrato, roda ciclos dentro das constraints e da editable surface, e avalia o gate contra o done-when toda vez. O humano lê o log; ele não dirige o loop.
const scope = parseScope("SCOPE.md"); // the 6 fields while (!gate(scope.doneWhen)) { // the exit gate const state = learn(scope.context); // see real state const unit = analyze(state, scope.goal); // pick ONE unit execute(unit, { surface: scope.editableSurface, // fence the change constraints: scope.constraints, // guard, every cycle tier: scope.agents.executor.tier // analyze | execute | … }); const proof = verify(unit, scope.doneWhen); // at the REAL boundary log("LOOP-LOG.md", proof); // the human only reads this } // loop exits the instant every done-when clause is true
# the contract you hand-write before a loop $ $EDITOR SCOPE.md # the observability artifacts the human reads (never executes) $ tail -f LOOP-LOG.md # the loop's running record $ less review.md # the AFK QA's observability report # confirm the gate is wired to your done-when $ grep -n "Done-when" SCOPE.md
Para onde isto vai depois: a lição 3 (LEARN) é como o agente confirma o Context contra a realidade; a lição 6 (VERIFY) é o gate de prova no boundary real que avalia o Done-when; a lição 8 (o Forge) mostra o passo /goal compilando esse mesmo raciocínio em um GOAL.md autônomo; a lição 9 explica por que o Validador nunca é o construtor.
done-when para algo em que você está de fato trabalhando, cole no construtor da seção 4 e veja se fica verde. Se continuar vago, me pergunte "qual métrica, limiar e boundary tornariam isto verificável?" e nós o afiamos juntos. A próxima lição, LEARN: enxergue o estado real, é como o loop confirma o campo Context contra a realidade antes de mudar qualquer coisa.