Aula 03 · Planejamento

Deixe o agent gerenciar seu próprio progresso

"The agent can track its own progress — and I can see it." Deixe o modelo criar sua própria lista e use um mecanismo simples para que ele se lembre de atualizá-la.

⏱ ~10 min · 📝 3 componentes interativos · 🧑‍💻 Baseado em shareAI-lab · s03_todo_write.py

Self-planning estruturado

Quando o Claude Code está trabalhando, frequentemente precisa de vários passos: grep para encontrar referências → ler alguns arquivos → modificar o código → rodar testes. Se você deixar o modelo "avançar por intuição", os primeiros passos costumam ser bons, mas no meio do caminho ele começa a esquecer coisas e, no fim, para no meio.

A solução do s03 é dar ao modelo uma ferramenta de lista de tarefas: o próprio modelo chama a ferramenta todo para inserir itens, o TodoManager valida a estrutura, persiste e retorna a visão atual. Isso tem duas vantagens:

  • O modelo é forçado a tornar explícito o que precisa ser feito — só escrever já ajuda a organizar o raciocínio.
  • Um humano pode ver o que ele está pensando. A experiência de depuração melhora dez vezes.
# Visão do TODO, cada item é estruturado
[ ] #1: grep "TODO" across src/
[>] #2: read src/app.py and list comments     # em andamento
[ ] #3: generate summary markdown
[ ] #4: write to TODO_LIST.md

(0/4 completed)

Uma regra rígida: apenas um in_progress por vez

O TodoManager.update() tem uma validação:

if in_progress_count > 1:
    raise ValueError("Only one task can be in_progress at a time")

Parece rígido, mas na verdade está ajudando o modelo. Se você permitir 3 itens "em andamento" simultaneamente, ele vai abrir múltiplas frentes sem fechar nenhuma. Forçar a progressão linear faz com que ele complete um item antes de abrir o próximo.

O widget abaixo permite que você assuma o papel do modelo, enviando vários payloads de todo, e veja quais passam na validação e quais são rejeitados.

Nag reminder: ficou 3 rodadas sem atualizar? Toma um empurrão

Mesmo com a ferramenta todo disponível, o modelo às vezes "esquece" de atualizar a lista — faz um monte de coisas, mas o in_progress ainda está travado no item 2. A solução do s03 é um contador simples:

rounds_since_todo = 0
while True:
    response = LLM(messages, tools)
    ...
    used_todo = any(b.name == "todo" for b in tool_uses)
    rounds_since_todo = 0 if used_todo else rounds_since_todo + 1
    if rounds_since_todo >= 3:
        results.append({"type":"text", "text":"<reminder>Update your todos.</reminder></reminder>"})

Quando o contador chega a 3, um reminder é inserido na próxima mensagem do usuário. O modelo reage naturalmente e vai chamar o todo. Isso é transformar uma restrição suave ("por favor mantenha atualizado") em um estímulo forçado via engenharia.

Como esse padrão se chama?

No universo de design de agents, isso se chama structured self-planning with soft nudges — dar ao modelo um estado estruturado que ele precisa escrever, combinado com reminders inseridos em momentos oportunos. O código real do Claude Code usa um padrão semelhante, mas mais contido (frequência menor, linguagem neutra).

Por que não escrever "atualize o todo a cada passo" direto no system prompt? É possível escrever, mas a obediência do modelo a instruções genéricas no system prompt cai conforme a conversa fica longa. Reinjetar o reminder periodicamente é muito mais estável.
Interativo

Widget 1 · Kanban · evolução do todo por turno

Clique em Step e veja como o modelo move as tarefas de pending para in_progress e depois para completed. Note que sempre há apenas um in_progress.

[ ] pending
[>] in_progress
[x] completed
Pronto para começar…
Interativo

Widget 2 · Validação · quais dos 5 payloads passam?

Quando o modelo chama a ferramenta todo, ele envia um array de items. O TodoManager executa uma série de validações: text não pode ser vazio, status deve ser válido, no máximo um in_progress, total ≤ 20. Clique para julgar cada payload.

Acertos: 0 / 5
Interativo

Widget 3 · Nag Counter · o que acontece depois de 3 rodadas sem atualizar o todo

Clique em Next Turn e veja se o contador aciona a injeção do reminder. A cada rodada, "chamar todo" ou "não chamar" é escolhido aleatoriamente — assim como acontece com o modelo na prática.

rounds_since_todo: 0