TL;DR: Quando seu consumidor principal é um LLM, XML e JSON desperdiçam tokens repetindo estrutura em cada elemento. Um formato posicional compacto reduz o consumo em 50%. Descobri que essa ideia já tinha nome: TOON (Token-Oriented Object Notation). Mesma pressão seletiva — tokens caros e chaves repetitivas — mesma solução.


Anthropic usa XML para tudo. Seus system prompts são envoltos em <instructions>, seus exemplos em <example>, suas ferramentas em <function>. Quem trabalha com o Claude está cercado por tags.

Então, quando pedi ao Claude que projetasse a saída de uma CLI pensada para ser consumida por LLMs, a tentação óbvia foi: XML. Se o modelo recebe informações em XML, não seria lógico que a CLI devolvesse as mesmas informações em XML?

Acontece que não.

O problema: repetição compulsiva

Imagine que sua CLI lista problemas de um rastreador de projetos. São 50 problemas. Em XML, cada um deles se parece com isso:

<issue>
  <id>PROD-587</id>
  <state>Backlog</state>
  <labels>backend</labels>
  <title>Importar sessões de backup do NAS</title>
  <age_days>14</age_days>
</issue>

Bonito. Autodescritivo. Cada campo tem um nome claro. Um parser XML sabe exatamente o que cada parte representa.

Agora multiplique isso por 50 problemas. As tags <issue>, <id>, <state>, <labels>, <title>, <age_days> se repetem 50 vezes cada uma. São 300 tags que não trazem mais nenhuma novidade depois da primeira repetição. Aproximadamente 70 tokens por problema, 3500 tokens para listar 50 problemas.

JSON melhora um pouco — elimina as tags de fechamento — mas ainda repete as chaves:

{
  "id": "PROD-587",
  "state": "Backlog",
  "labels": ["backend"],
  "title": "Importar sessões de backup do NAS",
  "age_days": 14
}

Cada linha repete "id":, "state":, "labels":, "title":, "age_days":. Cerca de 50 tokens por problema, 2500 tokens para os 50.

E se eliminarmos toda essa repetição?

PROD-587 [Backlog] backend — Importar sessões de backup do NAS (14d)

25 tokens. Sem chaves. Sem tags. Sem aspas ou colchetes. 1250 tokens para 50 problemas.

Preste atenção: metade do JSON, menos de um terço do XML. Fazendo exatamente o mesmo.

“Mas um LLM precisa de estrutura”

Aqui é onde as pessoas me olham torto. “E como o LLM sabe o que é cada campo?”

É uma pergunta legítima. E a resposta é: da mesma forma que você sabe.

Olhe para esta linha:

PROD-587 [Backlog] backend — Importar sessões de backup do NAS (14d)

Você precisa que alguém diga que PROD-587 é o ID? Que [Backlog] é o estado? Ou que o texto depois do travessão é o título? Não. Você deduz pelo formato e pela posição visual.

Os LLMs fazem exatamente isso. Eles são basicamente máquinas de reconhecer padrões em textos. Um formato posicional consistente — ID primeiro, estado entre colchetes, labels soltos, título após o travessão, metadados entre parênteses — eles entendem instantaneamente. Não precisam de <state>Backlog</state> para deduzir que “Backlog” é o estado.

Aqui é importante distinguir duas operações que parecem iguais, mas são bem diferentes:

LER é o que um LLM faz. Ele tem contexto, compreende a semântica e deduz a estrutura. Um humano lendo um relatório não precisa que cada palavra tenha uma etiqueta — entende pela posição e pela convenção.

FAZER PARSE é o que um programa faz. Ele não tem contexto, não entende semântica e exige delimitadores explícitos para extrair campos. Um jq '.state' precisa da chave "state" porque não sabe o que é um estado.

XML e JSON foram projetados para fazer parse. São formatos de troca entre máquinas que não entendem o conteúdo. Os LLMs lêem. Para eles, a estrutura explícita é redundante — e essa redundância custa tokens.

O espectro: quando usar o quê

Não estou dizendo que XML e JSON são ruins. Mas eles são ineficazes neste caso específico. Para simplificar, veja este espectro:

FormatoTokens/problemaAutodescritivoMelhor para
XML~70TotalAPIs SOAP, configs, documentos com schema
JSON~50TotalAPIs REST, troca entre serviços
JSONL~50TotalScripts, jq, pipelines de dados
Posicional~25NãoLLMs, humanos, painéis concisos

A regra é simples: se o consumidor compreende o contexto (humano, LLM), você pode eliminar a estrutura explícita. Se ele não compreende (script, parser), você precisa das chaves.

Por isso minha CLI tem dois modos: o formato posicional compacto por padrão (para o LLM e para você no terminal) e --json caso precise processar a saída com jq ou um script.

O twist: alguém já havia pensado nisso

Agora vem a parte interessante.

Semanas após adotar o formato que o Claude sugeriu, alguém me perguntou: “Você já ouviu falar sobre o TOON?”

TOON — Token-Oriented Object Notation — é um formato que surgiu em novembro de 2025. Sua proposta: um modelo compacto do padrão JSON, projetado especificamente para minimizar o consumo de tokens em prompts para LLMs.

Como ele é? Assim:

issues[3]{id,state,labels,title,age_days}:
 PROD-587,Backlog,backend,Importar sessões de backup do NAS,14
 PROD-612,Todo,tokamak,Fix auth token refresh,2
 PROD-501,Done,frontend,Migrar base de dados para novo schema,30

Veja só. Um cabeçalho com os campos ({id,state,labels,title,age_days}) e o número de elementos ([3]). Depois, apenas dados posicionais separados por vírgulas. Sem repetir chaves. Sem tags. Sem aspas nas strings.

É exatamente o que Claude propôs para minha CLI — com a diferença de incluir um cabeçalho explícito que torna o formato autodescritivo.

A versão da minha CLI:

PROD-587 [Backlog] backend — Importar sessões de backup do NAS (14d)
PROD-612 [Todo] tokamak — Fix auth token refresh (2d)
PROD-501 [Done] frontend — Migrar base de dados para novo schema (30d)

Basicamente a mesma coisa. Formato posicional, sem repetição de chaves, cerca de 25 tokens por linha. A diferença: TOON inclui um cabeçalho com o schema; o formato que escolhi para minha CLI utiliza convenções visuais (colchetes para estado, travessão para separar o título).

Evolução convergente

Na biologia existe um conceito fascinante: a evolução convergente. Espécies que não estão relacionadas desenvolvem características semelhantes porque enfrentam a mesma pressão seletiva. Os olhos do polvo e do humano são estruturalmente parecidos, mas evoluíram de forma completamente independente. Mesma pressão — necessidade de enxergar — mesma solução.

TOON e o formato da minha CLI são evolução convergente aplicada ao design de software. A pressão seletiva é idêntica: tokens caros, chaves repetitivas, consumidor que entende por posição. A solução convergente: eliminar repetição e confiar na ordem.

A diferença entre TOON e o que minha CLI faz é o cabeçalho. E essa diferença importa.

Sem cabeçalho, o formato funciona bem quando o LLM já tem contexto sobre quais são os campos (porque isso foi definido no system prompt ou porque o padrão é óbvio). Mas se alguém vê a saída pela primeira vez, terá que adivinhar. O TOON resolve isso: o cabeçalho informa os campos de uma vez e o LLM não precisa que sejam repetidos.

É a diferença entre um contrato implícito e um explícito. E na engenharia, contratos explícitos tendem a vencer a longo prazo.

Devo migrar para TOON?

Perguntei isso a mim mesmo. A resposta honesta é: depende de quem consome sua saída.

Se sua CLI é consumida sempre pelo mesmo agente com o mesmo system prompt que descreve o formato, o formato posicional sem cabeçalho funciona bem. Ele é mais compacto (não inclui a linha de cabeçalho) e o contexto já foi estabelecido.

Se outros agentes ou pessoas sem contexto prévio podem consumir sua CLI, o TOON é melhor. Uma linha extra de cabeçalho custa pouco, mas compra a capacidade de ser autodescritivo.

No meu caso, o consumidor sempre é o mesmo agente com um CLAUDE.md que descreve o formato. Então fico com minha “gambiarra” posicional. Mas, se eu empacotasse a CLI para uso público, migraria para TOON sem hesitar.

O que aprendi

Três coisas:

1. Não projete para parsers se o consumidor não for um parser. XML e JSON são ótimos para máquinas que precisam de chaves explícitas. LLMs não são essa máquina. Projete com base em como seu consumidor processa as informações, não em como você acha que deveria processá-las.

2. Repetição é o inimigo. Em uma lista com 50 itens, cada chave repetida 50 vezes é lixo informativo. É como colocar “Nome:” na frente de cada item em uma lista de compras. Depois do segundo, seu cérebro não presta atenção — mas ainda ocupa espaço.

3. Se sua solução converge com algo que já existe, provavelmente você está no caminho certo. Não estou dizendo que reinventar a roda é sempre ruim. Estou dizendo que, se for redonda — seja você quem propôs ou sua ferramenta — é uma boa sinalização.

Na próxima vez que você projetar a saída de uma ferramenta para ser consumida por LLMs, faça a si mesmo uma pergunta antes de usar serde_json::to_string(): meu consumidor precisa fazer parse disso ou apenas ler?

Se a resposta for “ler”, cada chave repetida é um token que você está desperdiçando por nada. E tokens, como dinheiro no balcão do bar, desaparecem mais rápido do que você imagina.