Agent Mentor Learn
Fundamentos do Harness de Agente: Laços e Controle · Lição 6 de 6

Lição 6: Mão na massa: escrevendo à mão um harness de agente com controles

Objetivos de aprendizado:

  • Transformar o loop de stop_reason das lições anteriores num loop while funcional em cima do @anthropic-ai/sdk, decidindo por conta própria se continua chamando ferramentas ou retorna texto e encerra
  • Construir os blocos de conteúdo tool_use e tool_result exatamente conforme a spec, e enviar os vários resultados de um único turno de volta dentro de uma só mensagem user
  • Equipar esse loop com quatro válvulas de controle — máximo de turnos, teto de orçamento, detecção de não-progresso e aprovação para ações de alto impacto — e dizer com precisão a que passo do loop cada uma pertence

Pré-requisitos: Ler as Lições 2 a 5; entender o loop dirigido por stop_reason, condições de parada, redes de segurança contra descontrole, e a intervenção humana no loop | Anterior: Lição 5 <<

Primeiro, como fica rodando

As primeiras cinco lições desmontaram a máquina peça por peça: como o loop gira, quando ele deve parar, como o descontrole se parece, como uma pessoa entra. Esta lição solda essas peças no menor harness funcional. Antes de qualquer código, veja o que ele faz num terminal — um agente ligado a duas ferramentas de brinquedo (get_time reporta a hora, read_file lê um arquivo dentro do projeto), recebendo uma frase: "leia a primeira linha do README.md, e então me diga que horas são".

text
$ node agent.js "Leia a primeira linha do README.md, e então me diga que horas são"
[turn 1] modelo requisita ferramenta: read_file({"path":"README.md"})[turn 1] ferramenta retornou: "# Agent Harness Fundamentals\n..."[turn 2] modelo requisita ferramenta: get_time({})[turn 2] ferramenta retornou: "2026-08-26T10:42:07+08:00"[turn 3] modelo encerra (end_turn)
A primeira linha do README.md é "# Agent Harness Fundamentals", e são 10:42 de 26 de agosto de 2026.Isso levou 2 turnos de chamadas de ferramenta ao longo de 3 requisições ao modelo.

Olhe de perto o que aconteceu: o usuário disse uma frase, e quantas ferramentas foram chamadas, qual foi primeiro, e quando parar foram todos decididos pelo modelo dentro do loop. Essa é a linha entre um agente e um workflow — o caminho de um workflow é fixo em código, enquanto um agente é o modelo dirigindo dinamicamente seu próprio processo e decidindo quais ferramentas usar1. O código host (o harness que estamos escrevendo nesta lição) nunca especificou "leia o arquivo primeiro, depois cheque a hora". Ele só girou o loop fielmente, rodou a ferramenta que o modelo nomeou, e devolveu o resultado. As duas ferramentas aqui são inofensivas, então nada interrompeu a execução — mas este harness também tem uma válvula de aprovação soldada, e se o modelo se estende para algo de alto impacto como apagar um arquivo ou disparar uma requisição, ele para e espera um aval humano antes de agir (escrevemos isso mais adiante na lição). O resto desta lição constrói, linha por linha, o código por trás daquela saída de terminal.

O loop central: traga o esqueleto, troque pelo SDK de verdade

O callModel da Lição 2: O loop central: de uma ida e volta à operação contínua era pseudocódigo. Agora ele vira o @anthropic-ai/sdk de verdade. O esqueleto do loop é idêntico: envie uma requisição carregando messages, olhe response.stop_reason — se for "tool_use", rode as ferramentas, costure os resultados de volta e envie de novo; se não for (digamos, end_turn), retorne o texto e saia do loop2.

Aqui está a versão mínima sem nenhuma válvula, para o próprio loop ficar visível:

Ponha isto ao lado do esqueleto da Lição 2 e a estrutura não se moveu: a linha do while ainda diz "repita enquanto stop_reason for tool_use", e o corpo ainda são os mesmos quatro passos — empurra assistant, roda ferramentas, empurra tool_result, reatribui response. A única mudança substantiva é callModel virando client.messages.create(...), mais aquela reatribuição no fim do corpo. Essa reatribuição é o que torna a parada possível; tire-a e stop_reason fica no valor antigo para sempre, que é exatamente o loop morto da Lição 4: Descontrole e fallback: loops mortos, giro em falso, esgotamento de orçamento.

Campos de tool_use / tool_result, nenhum deles faltando

runToolUses é onde a ferramenta que o modelo nomeou de fato roda. A coisa mais fácil de errar aqui são os campos do bloco de conteúdo, então siga a spec: um bloco tool_use carrega id / name / input, um bloco tool_result carrega tool_use_id (declarando qual chamada ele responde) e content, e quando a execução da ferramenta falha você adiciona is_error: true3. Há mais uma regra rígida: por mais blocos tool_use que uma resposta contenha, essa mesma quantidade de blocos tool_result tem de voltar, todos empacotados na única mensagem user que segue imediatamente3 — a linha messages.push({ role: "user", content: toolResults }) no corpo do loop acima é o que mantém essa regra.

Note o try/catch: uma ferramenta explodindo não deveria derrubar o harness inteiro junto. Embrulhe o erro num tool_result marcado is_error: true e devolva-o, e o modelo ganha a chance de tentar de novo com argumentos diferentes ou tomar outra rota. Isso é muito mais estável do que lançar e matar o processo.

Parafusando quatro válvulas de controle

O loop gira agora, mas é o loop pelado da Lição 2 — aquele que confia no modelo e não deixa saída para si mesmo. Ele para no turno em que o modelo retorna end_turn, sem fronteira nenhuma no meio do caminho. E a autonomia de um agente significa custos mais altos mais o potencial de erros se acumulando volta após volta do loop, com o modelo potencialmente operando por muitos turnos1 — um loop pelado aposta a decisão inteira de parar-ou-continuar no modelo, o que é arriscado demais. Agora soldamos as quatro válvulas das lições anteriores, uma de cada vez.

Cada válvula guarda uma coisa, e nenhuma das posições é arbitrária:

  • Válvula 1, máximo de turnos (Lição 3: Condições de parada: quando um agente deve desistir): turns >= MAX_TURNS fica bem no topo do corpo, antes de turns++. Significa "antes desta volta, cheque se outra volta ainda é permitida". Esta condição de parada explícita existe para que, ao lado do próprio end_turn do modelo, você mantenha o controle nas suas próprias mãos1.
  • Válvula 2, teto de orçamento (Lição 4: Descontrole e fallback: loops mortos, giro em falso, esgotamento de orçamento): toda vez que uma resposta volta, some os tokens de response.usage e pare no teto. Quando os turnos são poucos mas o contexto de cada turno é enorme, a contagem de turnos sozinha não segura o gasto; você precisa de tokens como uma comporta separada e independente.
  • Válvula 3, detecção de não-progresso (Lição 4): achate as chamadas de ferramenta deste turno numa assinatura e compare-a com a anterior; idênticas significam giro em falso. Isso pega o caso estagnado em que os turnos não passaram do limite e o orçamento não estourou, mas o modelo está andando no lugar, chamando a mesma ferramenta com os mesmos argumentos de novo e de novo.
  • Válvula 4, a válvula de aprovação (Lição 5: Intervenção e direção: interromper, redirecionar, humano no loop): dentro de runToolUses, antes de de fato executar uma ferramenta, ações de alto impacto ganham uma confirmação humana primeiro. Aprovação humana no loop em ações de alto impacto é precisamente o jeito recomendado de segurar o risco de agência excessiva4.

A função de assinatura da Válvula 3 é simples a ponto de ser sem graça — junte os nomes e argumentos de todo bloco tool_use do turno numa única string. Distinguir "o que foi chamado com quais argumentos" é tudo o que ela precisa fazer:

A válvula de aprovação: encaixada no momento antes da execução

Das quatro válvulas, a posição da válvula de aprovação é a que mais importa e a mais fácil de errar. Ela tem de se encaixar no momento em que o modelo nomeou uma ferramenta mas a ferramenta ainda não rodou — imprima a ação prestes a acontecer, espere por um humano, execute só depois da confirmação. Um passo mais tarde e o arquivo já está escrito, a requisição já enviada, e perguntar "confirma?" é inútil. Então ela vai dentro de runToolUses, antes da linha impl(...):

approve é uma função passada de fora; num terminal significa "imprima a ação, leia uma linha de entrada":

Um detalhe que importa: mesmo quando o usuário recusa, você ainda retorna um tool_result marcado is_error: true em vez de não retornar nada. A spec exige que todo tool_use tenha um tool_result correspondente devolvido3; pule-o e a próxima requisição dá erro porque uma chamada de ferramenta não tem resultado. Recusar não é o mesmo que ignorar — uma recusa é ela própria um resultado que o modelo merece ouvir, e um modelo que aprende que foi recusado muitas vezes muda para uma rota que não precisa da ação de alto impacto de jeito nenhum.

Duas ferramentas de brinquedo, para o loop de fato rodar

As válvulas estão no lugar; o que falta são ferramentas que o modelo possa chamar. Esta lição usa só dois brinquedos absolutamente seguros e mantém as operações perigosas fora da porta: get_time reporta a hora atual, e read_file lê um arquivo — com path.resolve fincando-o firmemente dentro do diretório do projeto, para que o modelo (ou um modelo tirado do curso pela saída de uma ferramenta) não consiga ler caminhos fora dos limites como /etc/passwd:

Nenhuma das ferramentas está no conjunto HIGH_IMPACT, então nenhuma dispara aprovação — elas são inofensivas por construção. Para demonstrar a válvula de aprovação, adicione um write_file a toolImpls e a HIGH_IMPACT. Esta lição evita de propósito introduzir uma operação de escrita real para que rodar o exemplo não possa danificar seus arquivos.

Juntando tudo: um ponto de entrada que você roda com node agent.js

Por último, reúna runAgent, runToolUses, as definições de ferramenta e a função de aprovação num ponto de entrada que você roda diretamente — a coisa por trás da saída de terminal no topo desta lição:

Jogue as peças anteriores (import, client, MODEL, runAgent, runToolUses, signatureOf, approveInTerminal, toolImpls, tools, main) num só agent.js, defina ANTHROPIC_API_KEY, rode npm i @anthropic-ai/sdk, e node agent.js "sua tarefa" vai rodar.

Olhe de novo estas cem e tantas linhas e você vai notar que nenhuma delas é um conceito novo: o loop while e o stop_reason vieram da Lição 2, MAX_TURNS da Lição 3, o orçamento e a detecção de giro em falso da Lição 4, e a válvula de aprovação da Lição 5. Um harness não é algum framework profundo; é esta camada de loop-mais-válvulas que você mesmo escreve e controla. Mesmo modelo, mesmas duas ferramentas — mas um harness com essas quatro válvulas e o loop pelado da Lição 2 podem diferir enormemente em quão estável rodam a mesma tarefa, porque o que decide se um agente é confiável é em grande parte essa camada externa de código de controle, não só o modelo dentro dela5.

Mantenha o senso de proporção sobre complexidade também: nem todo agente precisa das quatro válvulas, e uma linha que vale a pena lembrar é que você deveria considerar adicionar complexidade apenas quando ela comprovadamente melhora os resultados1. Uma ferramenta pequena que roda três a cinco turnos num ambiente controlado pode ficar bem só com MAX_TURNS; quatro válvulas são para os casos que rodam muitos turnos seguidos e podem se estender a ações de alto impacto.

Recapitulação

  • O núcleo de um harness funcional ainda é o loop da Lição 2: envie uma requisição carregando messages → cheque stop_reason, e se for tool_use, rode as ferramentas, costure um tool_result de volta e envie de novo; se não for, retorne texto e encerre2. Trocar para o SDK de verdade só transforma callModel em client.messages.create(...)
  • Os campos do bloco de conteúdo seguem a spec sem nenhum faltando: tool_use carrega id / name / input, tool_result carrega tool_use_id / content mais is_error na falha; por mais blocos tool_use que um turno tenha, essa mesma quantidade de blocos tool_result volta, todos empacotados na única mensagem user que segue imediatamente3
  • Cada uma das quatro válvulas de controle guarda um ponto, e suas posições não podem ser embaralhadas: máximo de turnos (Lição 3) e o teto de orçamento (Lição 4) são as fronteiras rígidas que tornam o loop certo de parar, a detecção de não-progresso (Lição 4) pega o andar no lugar, e a válvula de aprovação (Lição 5) tem de se encaixar antes da execução da ferramenta — porque a autonomia de um agente traz custos mais altos e erros que se acumulam, e o modelo pode operar por muitos turnos1, então o próprio end_turn do modelo não consegue segurá-lo
  • A válvula de aprovação exigindo confirmação humana em ações de alto impacto é o jeito recomendado de segurar o risco de agência excessiva4; mesmo numa recusa, retorne um tool_result com is_error e não deixe a chamada pendurada3
  • Um harness não é um framework profundo; é esta camada de loop-mais-válvulas que você mesmo escreve e controla — mesmo modelo, código de controle diferente, e a confiabilidade pode diferir enormemente5. Mas não empilhe válvulas por empilhar tampouco: adicione complexidade apenas quando ela comprovadamente melhora os resultados1

Você terminou este curso. De "o que é um harness" a escrever à mão um loop com quatro válvulas de controle, o que você tem agora nas mãos não é só um conjunto de conceitos — é código de verdade que roda, que você pode editar, e ao qual você pode continuar adicionando controle. Ligue-o às suas próprias ferramentas e deixe-o fazer algum trabalho para você.

Footnotes

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

  2. How tool use works — Claude API — https://platform.claude.com/docs/en/agents-and-tools/tool-use/how-tool-use-works 2

  3. Handle tool calls — Claude API — https://platform.claude.com/docs/en/agents-and-tools/tool-use/handle-tool-calls 2 3 4 5

  4. LLM06:2025 Excessive Agency — OWASP Gen AI Security Project — https://genai.owasp.org/llmrisk/llm062025-excessive-agency/ 2

  5. The 2026 Agent Engineering Roadmap — GitHub (codejunkie99/agent-roadmap-2026) — https://github.com/codejunkie99/agent-roadmap-2026 2

Exercícios

01

Neste momento a válvula de aprovação tem duas configurações: pergunte se for de alto impacto, permita todo o resto. Um colega de produto levanta um requisito mais fino — controle por nome de ferramenta em três níveis: allow (passagem direta, como get_time), ask (confirmação humana exigida antes da execução, como write_file) e deny (sempre recusado, jamais chamável, como um send_email aposentado). Adicione essa válvula de política ao harness: projete sua estrutura de dados, diga a que passo do loop ela pertence e como se relaciona com a válvula de aprovação existente, e escreva o que deve voltar ao modelo quando deny bate.

Nível 1: Adicione uma válvula de controle escalonada por ferramenta ao harness
Critérios de conclusão · marcado localmente
02

Um colega diz que o loop de harness abaixo "funciona", mas no momento em que o modelo para de retornar end_turn por conta própria, ou cai em andar no lugar, ele quebra. Aponte: (1) quais controles lhe faltam e que comportamento de descontrole cada ausência produz; (2) a correção mínima — ao menos uma fronteira rígida que garanta que o loop vai parar, com uma declaração clara de a que passo ela vai.

Nível 2: Que comportas faltam a este loop, e como ele se descontrola
Critérios de conclusão · marcado localmente