Como escrever o prompt de um subagente para ele devolver dado, e não um relatório

estrutura de prompt de subagente formatada para devolver dado em JSON, não relatório em texto
Resposta rápida

O prompt de subagente que devolve dado é diferente do prompt que devolve conversa: como só a mensagem final do subagente volta pro agente pai, essa mensagem final É o valor de retorno da etapa, e não um relatório pra humano ler. Na prática isso significa escrever no prompt todo o contexto operacional (o subagente roda em conversa nova, sem o histórico do pai), declarar o formato exato de saída campo a campo, listar o que é proibido (saudação, resumo, markdown extra) e, quando a etapa seguinte for código, travar o formato com saída estruturada validada por JSON Schema no Agent SDK

Fala aí, beleza? Você monta a orquestração, dispara o subagente, ele faz o trabalho direitinho e volta com um "Analisei os arquivos e encontrei três pontos interessantes! Vou te explicar cada um…"

Aí a etapa seguinte, que esperava uma lista de objetos pra iterar, quebra no primeiro JSON.parse

O detalhe que muda tudo: só a mensagem final do subagente volta pro agente pai. As chamadas de ferramenta e os resultados intermediários ficam dentro do contexto do subagente, não sobem

Ou seja: aquela mensagem final não é um relatório pra alguém ler, é o valor de retorno da etapa 😀

E valor de retorno tem contrato

O que você precisa saber antes de escrever o prompt

Antes de sair escrevendo, três fatos que sustentam o resto do post

1. O subagente roda em uma conversa nova

O contexto dele começa do zero, sem a conversa do pai

O único conteúdo que atravessa é a string de prompt passada na ferramenta Agent. Então caminho de arquivo, mensagem de erro e decisão já tomada precisam estar escritos no próprio prompt, senão simplesmente não existem pra ele

2. Cada subagente tem system prompt, ferramentas e janela de contexto próprios

É isso que faz o padrão valer a pena: aquele bloco gigante de instrução mora no corpo do subagente e nunca entra na conversa do pai

Pro pai volta só a mensagem final

3. Onde o subagente vive muda conforme a ferramenta

No Claude Code, subagente personalizado é um arquivo Markdown com frontmatter YAML, em duas pastas que o Claude Code observa: ~/.claude/agents/ (nível de usuário, vale em todos os projetos) e .claude/agents/ (nível de projeto)

Formação Claude Code
Formação Recomendada

Formação Claude Code

Domine Claude Code do absoluto zero até o avançado

  • 120 aulas
  • 4 projetos
  • 9h 45min

Os campos do frontmatter são name, description, tools (lista separada por vírgula) e model. Abaixo do frontmatter vem o system prompt em Markdown

---
name: coletor-de-rotas
description: Lista as rotas HTTP de um diretório e devolve JSON
tools: Read, Grep, Glob
model: sonnet
---

Você coleta rotas HTTP e devolve dado estruturado.

Um aviso importante: a partir da v2.1.198, rodar /agents não abre mais o assistente interativo de criação, ele só imprime um lembrete pra pedir ao Claude ou editar .claude/agents/ direto. A criação hoje é pedir pro Claude escrever o arquivo ou escrever na mão

No Agent SDK, o equivalente é um AgentDefinition, com description (quando usar), prompt (comportamento), e tools/model opcionais. Ele também aceita disallowedTools, skills, mcpServers, maxTurns, effort, background e permissionMode

Como escrever o prompt do subagente passo a passo

A lógica aqui é sempre a mesma: você não está conversando com o subagente, você está definindo uma interface

Bora ver na prática?

  1. Declare que a mensagem final é o valor de retorno

Essa é a linha que muda o comportamento do agente por inteiro. Ele precisa saber que ninguém vai ler aquilo com olhos humanos, que outra etapa vai consumir

   Sua mensagem final É o valor de retorno desta etapa.
   Ela será consumida por outro processo, não lida por uma pessoa.
   Responda APENAS com o dado no formato especificado abaixo.

O erro comum deste passo: assumir que o pai vai enxergar o raciocínio e as ferramentas que o subagente usou pra chegar lá. Não vai. Isso tudo fica no contexto do subagente. Se um dado importa, ele tem que estar na mensagem final

  1. Coloque TODO o contexto operacional dentro do prompt

Caminho de arquivo, mensagem de erro literal, decisão que já foi tomada, o que já foi descartado. Se não estiver escrito, o subagente não sabe

   Analise o arquivo apps/worker/src/jobs/sync.ts
   O erro observado é: "AggregateError" sem mensagem, na primeira query
   Já descartamos problema de credencial (outras queries no mesmo processo funcionam)

O erro comum deste passo: escrever "corrija aquele bug que discutimos". O subagente não participou de discussão nenhuma, ele acabou de nascer 😛

  1. Especifique o formato exato, campo por campo, com tipo

Não basta "responda em JSON". Diz o nome do campo, o tipo e o que vai dentro

   Formato da resposta (JSON, sem cercas de código):
   {
     "rotas": [
       { "metodo": "GET|POST|PUT|DELETE", "caminho": "string", "arquivo": "string", "linha": number }
     ],
     "observacoes": "string curta, ou string vazia se não houver"
   }
   Se não encontrar nenhuma rota, devolva "rotas": []

Repara na última linha, ela é a que mais salva: diga o que fazer quando não houver resultado

O erro comum deste passo: esquecer o caso vazio. Aí o agente, tentando ser prestativo, responde "Não encontrei nenhuma rota nesse diretório, mas notei que…" e seu código recebe prosa onde esperava um array

  1. Escreva a lista de proibições, explícita

Modelo bem treinado em ser simpático vai ser simpático se você não proibir

   PROIBIDO:
   - saudação, "Claro!", "Aqui está", qualquer preâmbulo
   - resumo antes ou depois do dado
   - cercas de código ou markdown fora do formato especificado
   - fazer pergunta de volta: se faltar informação, devolva o campo com valor vazio

Aquele último item é sutil e importante: subagente não tem com quem conversar. Se ele "pergunta", a pergunta vira a mensagem final e a etapa morre ali

O erro comum deste passo: proibir preâmbulo e esquecer as cercas “`json, que o modelo adora colocar por conta própria. Ou você proíbe, ou seu parser tira antes

  1. Restrinja ferramentas e escopo

Subagente com ferramenta demais tende a sair explorando e voltar contando o que fez. Corta na raiz: no frontmatter do Claude Code é o campo tools, no Agent SDK é tools (e disallowedTools pra bloquear o que não quer)

   ---
   name: coletor-de-rotas
   description: Lista rotas HTTP e devolve JSON
   tools: Read, Grep, Glob
   ---

Um agente que só lê não tem como te surpreender escrevendo. Esse mesmo raciocínio de limitar o raio de ação vale ainda mais quando o subagente encosta em coisa perigosa: dá pra delegar migrations de banco com bem mais tranquilidade quando as ferramentas dele são as certas

O erro comum deste passo: deixar tudo liberado "por garantia" e depois culpar o prompt quando a resposta vem cheia de narrativa do que ele explorou

  1. Reinicie a sessão depois de criar o arquivo

Esse aqui pega muita gente: arquivos de subagente são carregados só no início da sessão

Se você criou o arquivo com o Claude Code já rodando, ele não existe pra sessão atual

O erro comum deste passo: editar o .md, testar, não ver diferença nenhuma e sair mexendo no prompt achando que a instrução é que está fraca. Não está, ela só não foi carregada. Reinicia e testa de novo

Como garantir o formato com saída estruturada em vez de confiar no prompt

Prompt bem escrito reduz muito o problema, mas ele continua sendo pedido, não garantia

Quando a etapa seguinte é código, dá pra subir uma camada e travar o formato de verdade

  1. No Agent SDK, passe outputFormat nas opções da query

Você declara type: "json_schema" e entrega o schema. O SDK valida a saída contra o schema e re-prompta em caso de divergência

   const result = query({
     prompt: "Liste as rotas HTTP em apps/web/src/app",
     options: {
       outputFormat: {
         type: "json_schema",
         schema: {
           type: "object",
           properties: {
             rotas: {
               type: "array",
               items: {
                 type: "object",
                 properties: {
                   metodo: { type: "string" },
                   caminho: { type: "string" },
                   arquivo: { type: "string" }
                 },
                 required: ["metodo", "caminho", "arquivo"]
               }
             }
           },
           required: ["rotas"]
         }
       }
     }
   })
  1. Leia o objeto no lugar certo

O JSON validado chega na mensagem final: msg.type === "result" com msg.subtype === "success", e o objeto vem em msg.structured_output

   for await (const msg of result) {
     if (msg.type === "result" && msg.subtype === "success") {
       const dados = msg.structured_output
       // dados.rotas já é array, sem parse na mão
     }
   }

O erro comum deste passo: continuar dando parse no texto da mensagem final por hábito, ignorando o campo que já vem pronto e validado

  1. Trate o caminho de erro, não só o feliz

Se a validação do schema não passar dentro do limite de tentativas, o resultado é erro e não dado: o subtype retornado é error_max_structured_output_retries

   if (msg.type === "result" && msg.subtype === "error_max_structured_output_retries") {
     // aqui NÃO tem structured_output, sua etapa precisa decidir o que fazer
   }

O erro comum deste passo: tratar schema como se fosse infalível e escrever só o if do sucesso. Aí o pipeline segue com undefined e o problema aparece três etapas depois, bem longe da causa

  1. Na API do Claude, são duas formas complementares

Uma é a saída JSON via output_config.format. A outra é o uso estrito de ferramenta com strict: true, que garante validação de schema no nome e nos inputs da ferramenta

Vale conhecer as duas: nem sempre o que você quer é a resposta final em JSON, às vezes o que precisa estar certinho é a chamada de ferramenta

  1. Fique de olho na mudança de nome do parâmetro

O parâmetro de saída estruturada saiu do beta: output_format passou a ser output_config.format, e o header de beta não é mais necessário

O header antigo (structured-outputs-2025-11-13) e o output_format seguem funcionando por um período de transição, então código velho não quebra de imediato, mas já dá pra atualizar

Na API do Claude, os modelos com structured outputs em disponibilidade geral são Claude Mythos Preview, Opus 4.7, Opus 4.6, Sonnet 4.6, Sonnet 4.5, Opus 4.5 e Haiku 4.5

Qual a diferença real entre schema e pedir JSON no prompt?

Essa é a parte que muita gente passa batido

Pedir JSON no prompt é torcer. Structured outputs restringe a geração: a saída é forçada a seguir o schema, por decodificação restrita

São coisas de natureza diferente, não é a mesma feature com nome bonito

Pedir JSON no prompt Schema (structured outputs)
Como funciona instrução que o modelo tenta seguir geração restrita ao schema
Onde mora texto do prompt opção da query / da chamada
Quando diverge você descobre no parse o SDK valida e re-prompta
Falha final texto inesperado no seu código subtype de erro explícito

O erro comum aqui é manter os dois e deixar em desacordo: o prompt pedindo um campo titulo e o schema exigindo title

Quando isso acontece, o modelo fica preso entre a instrução e a restrição, e você gasta tentativa à toa. Escolhe uma fonte da verdade pro formato e mantém a outra alinhada com ela

E sim, isso é engenharia de prompt de verdade, no sentido chato e útil da palavra, bem longe de prompt mágico. Se você anda pensando se um certificado de engenharia de prompt resolve, o teste é justo esse: a pessoa sabe desenhar contrato de saída ou só sabe pedir bonito?

Quando pedir dado e quando um relatório em prosa ainda serve

Nem todo subagente precisa devolver JSON, beleza? Depende de quem consome

A etapa seguinte é código ou outro agente

Aqui não tem discussão: dado estruturado, schema obrigatório

Se o retorno vai virar um for, um filtro ou o input de outra chamada, prosa é dívida técnica na hora

Fan out de vários subagentes com agregação no pai

Esse é o caso mais dolorido de errar

Você dispara cinco subagentes na mesma tarefa, cada um numa parte do código, e o pai precisa juntar, deduplicar e ordenar

Com cinco textos livres, o pai vira um parser humano e você paga contexto pra ele reinterpretar cada estilo diferente. Com campos estáveis (arquivo, linha, severidade), dedup vira comparação de chave e acabou

Texto bonito não deduplica. Campo estável deduplica 🙂

O caso legítimo de prosa

Existe, e é honesto: quando a saída do subagente vai direto pro humano e o pai só repassa

Explicação, resumo de investigação, aquele "me conta o que tá acontecendo nesse módulo". Nesse cenário, forçar JSON só piora a leitura

A pergunta que resolve é sempre a mesma: quem lê isso depois?

O caso do contexto pesado

Esse merece parágrafo próprio porque é o motivo de existir de metade dos subagentes

A instrução longa, as regras do domínio, os exemplos, tudo isso mora no corpo do subagente e nunca entra na conversa do pai

Ou seja: você gastou uma janela de contexto inteira lá dentro justamente pra devolver pouca coisa pro pai

Se o retorno vier em três parágrafos narrando o processo, você jogou fora o ganho. Retorno enxuto não é frescura, é o ponto todo

Conclusão

Recapitulando: o prompt de subagente é contrato de interface, não conversa

A mensagem final é o valor de retorno, o subagente nasce sem o histórico do pai, e o que ele fez no meio do caminho não sobe. Tudo o que importa precisa estar escrito na entrada e caber na saída

Próximo passo prático, bem direto: pega um subagente que você já usa hoje e reescreve só a última seção do prompt como contrato de saída (formato, campos com tipo, caso vazio, lista de proibições)

Se a etapa seguinte for código, pluga o schema em cima disso e trata o caminho de erro

Ah, e um cuidado de organização antes de sair criando arquivo: o Claude Code lê as pastas de agentes recursivamente, então subpasta é permitida

Porém, se dois arquivos sob a mesma pasta declararem o mesmo name, só um é carregado, escolhido pela ordem de leitura do sistema de arquivos, sem precedência documentada

Tome cuidado! Nome duplicado ali é o tipo de coisa que funciona na sua máquina e some na do colega, sem erro nenhum na tela…

até o próximo post!

Perguntas frequentes

Como faço o subagente devolver JSON em vez de um texto explicativo?

No prompt de subagente, declare explicitamente que a mensagem final é o valor de retorno da etapa e não um texto para humano lerem. Especifique o formato campo por campo, com tipo, e liste as proibições (saudação, preâmbulo, cercas de código, pergunta de volta). Sem essas três coisas escritas, o modelo tende a ser prestativo e responder com prosa.

Por que o subagente não lembra do que foi discutido na conversa principal?

Porque o subagente roda em uma conversa nova, que começa do zero, sem acesso ao histórico do agente pai. O único conteúdo que atravessa é a string de prompt passada na ferramenta Agent, então caminho de arquivo, mensagem de erro e decisão já tomada precisam estar escritos ali dentro. Se não estiver no prompt, para o subagente aquilo simplesmente não existe.

Qual a diferença entre pedir JSON no prompt e usar structured outputs?

Pedir JSON no prompt é uma convenção que o modelo pode ou não seguir à risca. Structured outputs restringe a própria geração por decodificação restrita, forçando a saída a seguir o schema. No Agent SDK, isso é o outputFormat com type: "json_schema" nas opções da query: o SDK valida a saída contra o schema e re-prompta em caso de divergência.

O que acontece se o subagente não conseguir seguir o schema pedido?

No Agent SDK, se a validação não passar dentro do limite de tentativas de re-prompt, o resultado vem como erro e não como dado. Ou seja, a etapa falha de forma explícita em vez de devolver algo fora do formato esperado, e o seu código precisa tratar esse caminho.

Onde crio o arquivo de um subagente personalizado no Claude Code?

O Claude Code observa duas pastas: ~/.claude/agents/ para subagentes de nível de usuário, que valem em todos os projetos, e .claude/agents/ para subagentes de nível de projeto. Cada subagente é um arquivo Markdown com frontmatter YAML (name, description, tools, model) seguido do system prompt em Markdown.

O comando /agents ainda serve para criar subagentes no Claude Code?

A partir da v2.1.198, rodar /agents não abre mais o assistente interativo de criação: ele só imprime um lembrete para pedir ao Claude ou editar .claude/agents/ diretamente. Vale lembrar também que arquivos de agente são carregados só no início da sessão, então criar um novo com o Claude Code já rodando exige reiniciar a sessão.



Escrito por | Matheus Battisti

Matheus Battisti
Fundador da Hora de Codar

Programador apaixonado pelo mundo das tecnologias, sempre buscando em aprender e se aprofundar em linguagens, frameworks e o que mais for necessário para executar um bom trabalho. Agora tem uma nova missão que é de passar seu conhecimento adiante para formar novos programadores e especializar mais os que já são.

Subscribe
Notify of
guest

0 Comentários
Oldest
Newest Most Voted

Formações

Formação Vibe Coding

Formação Vibe Coding

Do Prompt ao Produto: Crie Software Real com IA

  • 474 aulas
  • 20 projetos
  • 39h 27min

Blog | Mais populares