Esta página documenta como foi feito o comparativo entre OpenCode, Cline e Command Code publicado no artigo OpenCode vs Cline vs Command Code: qual gasta menos?. A ideia é que qualquer pessoa consiga repetir o teste, com outro modelo ou com outros harnesses, e chegar a números comparáveis.
O objetivo é isolar o harness. Fixamos o modelo, as tarefas, o estado inicial do repositório, o prompt e o modo de permissão; a única coisa que muda é quem orquestra. O modelo entra como uma coluna no registro, não como eixo, pra que a mesma bateria rode com outro modelo daqui a três meses e os números continuem comparáveis.
Ambiente: Windows 11, sem Docker. Harnesses: OpenCode 2.0.14, Cline 3.0.62 e Command Code 1.66.0. Modelo: Space Bunny (preview stealth, setembro de 2026).
Algumas armadilhas nesse caminho produzem números publicáveis e errados. Quatro delas apareceram na nossa primeira execução e são erros do instrumento, ou seja, de como a gente chama o harness. Uma quinta apareceu no teste do Pixel Canary e é diferente: é uma propriedade do próprio harness. Todas estão documentadas aqui porque são a parte mais reaproveitável desta metodologia.
Os resultados de todos os testes feitos com esta metodologia ficam reunidos no Stealth Models Benchmark.
Atualizado em 1º de outubro de 2026, com o teste do Fledge Alpha: olhar a distribuição do cache por rodada e não só a mediana (seção 4), queda de conexão registrada à parte, regra pra rodada refeita e uma correção no seed (sobra de .git/objects). Em 27 de setembro de 2026: armadilha 3.4 (cache só no system prompt), variação de eixo para modelos disponíveis em um único harness e novo item no checklist.
1. Desenho do experimento
A variável isolada
Ficaram fixos em todas as rodadas: modelo, conjunto de tarefas, prompt literal, estado inicial do repositório, modo de permissão (autoaprovação total), esforço de raciocínio (o padrão do provedor) e modo de compactação.
O que o harness faz de diferente (system prompt, definição de ferramentas, política de leitura de arquivo, compactação de contexto) não é ruído. É o tratamento, e é justamente o que a gente quer ver aparecer.
Confundidores e o que fazer com cada um
Rota até o modelo. Cada harness chega ao modelo pelo seu próprio gateway, então latência e cota são diferentes por construção. Sem uma chave própria unificada (BYOK) não dá pra eliminar isso; dá pra declarar e não tirar conclusão de tempo. Se você tiver um provedor comum (OpenRouter ou equivalente) e ele aguentar o ritmo, unificar a rota é o desenho mais limpo.
Versão do harness. CLIs se atualizam sozinhas e mudam o catálogo de modelos debaixo de você. Fixe a versão, registre em cada linha do resultado e desative o auto-update durante a execução.
Ordem das rodadas. Não rode 15 de um e depois 15 do outro: a carga da API ao longo do dia vira diferença de harness. Embaralhe com semente fixa.
Quando o modelo só existe em um harness
Modelo stealth às vezes aparece no catálogo de um único harness. Aí não dá pra fixar o modelo e variar o harness. A saída é inverter o eixo: fixar o harness e variar o modelo, comparando o modelo novo com outro que já rodou no mesmo harness. Foi o que fizemos com o Pixel Canary, que só existia no Command Code, e com o Fledge Alpha, que só existia no OpenCode.
Esse resultado não se mistura com o comparativo principal. O jeito de o harness gastar contexto e usar cache está embutido nos dois lados, então toda tabela precisa dizer qual eixo variou. Registre a disponibilidade em cada catálogo (harnesses e OpenRouter) com a data da checagem, e rode a bateria completa se o modelo aparecer em outros harnesses depois.
Isolamento sem Docker
Container em benchmark público existe pra garantir reprodutibilidade entre máquinas. Pra comparar harnesses dentro da mesma máquina, basta repor o estado com rigor:
- Um diretório
seed/imutável. Cada rodada copia o seed pra um diretório descartável. - Reposição por cópia, nunca por
git checkout. O checkout não remove arquivo não rastreado que o agente criou. - Remova o
.gitdo seed. Com histórico, o agente acha o bug injetado pelo diff em vez de ler o código. - Confira que não sobrou nada do
.git. Nas nossas três primeiras baterias, o seed ainda tinha uma sobra de.git/objects(sem HEAD nem refs, irreconhecível pelo git) deixada pelo setup. Valeu igualmente pra todos os modelos e harnesses, então não muda as comparações, mas foi corrigido pras próximas.
Perfis limpos
Cada harness roda com HOME e XDG_CONFIG_HOME apontando pra um perfil descartável que contém apenas a credencial. Sem MCP, sem skills, sem plugins, sem agents, sem AGENTS.md ou CLAUDE.md, e sem arquivos de preferência aprendida (o Command Code mantém um diretório taste/ que precisa ficar de fora). As configurações reais do usuário não são tocadas.
Confira no dry run que nenhum dos harnesses carregou nada, e registre essa verificação.
Tarefas
Os bugs foram injetados em módulos diferentes de um repositório público real, escolhidos pra que o sintoma fique longe da causa. É isso que obriga o agente a ler o código em vez de resolver com um grep.
Evite tarefas do tipo “implemente a feature X que existe no upstream”: em repositório conhecido, o modelo responde de memória e o harness nunca precisa gerenciar contexto. Bug injetado derrota a memorização e ainda vem com a suíte de avaliação de graça.
Nossa escolha foi o pallets/jinja 3.2.0.dev: Python puro, uma única dependência, 911 testes que rodam em 4 segundos no Windows, 14 mil linhas em 25 arquivos, licença BSD-3. A suíte rápida importa: com 45 rodadas, uma correção lenta dominaria o relógio. Os cinco bugs ficaram em sandbox.py, lexer.py, runtime.py, loaders.py e compiler.py.
Testes visíveis, correção com cópia pristina
Deixamos a pasta tests/ visível pro agente. Escondê-la (no estilo SWE-bench) tiraria o ciclo de rodar o teste e ver a falha, e esse ciclo é uma das maiores diferenças entre harnesses.
A integridade vem na correção: antes de avaliar, tests/ é substituída por uma cópia pristina e a suíte roda contra ela. Adulterar teste não passa, e o diff anterior de tests/ vira um detector de trapaça.
Execução
Três repetições por par tarefa × harness, ordem embaralhada com semente fixa: 45 rodadas, 22.957.016 tokens processados em 1,5 hora de execução.
2. Métricas
Resultado. Passou ou falhou na suíte, por tarefa. Timeout é um desfecho separado de “não resolveu”: tivemos rodada com timeout e com a correção já gravada. Queda de conexão no fim da sessão (ECONNRESET) segue a mesma lógica: se a correção já está no disco e a suíte passa, a rodada conta como resolvida, e a queda fica registrada à parte como instabilidade do gateway.
Custo e eficiência. Entrada não cacheada, leitura de cache, escrita de cache, saída, raciocínio, turnos, tool calls e tempo.
Específicas do harness, que são o que justifica o exercício:
- Scaffolding: o contexto do primeiro request, antes de qualquer trabalho. System prompt mais definições de ferramenta. É o imposto fixo por tarefa.
- Contexto total: entrada não cacheada mais leitura de cache.
- Taxa de cache: leitura de cache dividida pelo contexto total.
- Cache turno a turno:
inputTokenscontracacheReadTokensem cada turno de uma rodada. Mostra se o cache acompanha a conversa ou fica preso num bloco fixo (ver armadilha 3.4). - Tool calls e turnos, pra enxergar a estratégia (fatiar muito ou puxar tudo de uma vez).
- Tool calls malformadas e retries de edição: com o modelo fixo, isso mede a qualidade da interface que o harness oferece ao modelo.
Comportamento. Escrita fora do diretório de trabalho, teste modificado, arquivo deletado. Qualquer falha aqui invalida a rodada.
Conversão em dinheiro. Os tokens foram convertidos pelos preços do DeepSeek V4.1 Flash, com preço separado por categoria: US$ 0,15 por milhão de entrada, US$ 0,60 por milhão de saída e US$ 0,003 por milhão de leitura de cache.
3. As armadilhas
3.1 O shim .cmd corta o prompt (Windows)
CLIs instaladas por npm no Windows expõem um shim .cmd. Chamar esse shim passa pelo cmd.exe, que corta a linha de comando na primeira quebra de linha do argumento e descarta o resto.
Prompt de várias linhas num shim .cmd significa que todas as flags depois dele somem. No nosso caso isso derrubou o -m: a rodada rodou no modelo padrão em vez do modelo em teste, terminou a tarefa com sucesso, devolveu exit code 0 e teria entrado na tabela como resultado legítimo.
Chame os executáveis reais, nunca os shims:
opencode.exe
cline/node_modules/@cline/cli-windows-x64/bin/cline.exe
node command-code/dist/index.mjs
A salvaguarda que nasceu disso: registre em cada rodada qual modelo o harness disse ter usado, extraído do stream de eventos, em vez de confiar na flag que você passou. Nem todo harness expõe isso; quando não expõe, declare a limitação.
3.2 text=True decodifica em cp1252
Em Python no Windows, subprocess.run(..., text=True) decodifica com a codificação do locale. A saída dos agentes é UTF-8. A thread de leitura quebra com UnicodeDecodeError, o stdout vira None, e a rodada se perde de um jeito que parece erro do harness.
Use sempre encoding="utf-8", errors="replace".
3.3 Token de entrada cru engana
A métrica que todo comparativo publica é a mais enganosa de todas aqui. Entre o OpenCode e o Cline, “tokens de entrada” mostrava uma diferença de cerca de 9 vezes. Somando o cache, os dois mandaram um contexto parecido, e a diferença real estava na taxa de cache: 92% contra 47%.
Entrada crua vs. contexto total
Tokens por tarefa, mediana. A entrada crua sugere 9× de diferença; o contexto total é parecido.
Entrada não cacheada (paga preço cheio)
Contexto total (entrada + leitura de cache)
Leitura de cache custa uma fração da entrada nova (no DeepSeek V4.1 Flash, US$ 0,003 por milhão contra US$ 0,15, ou seja, 50 vezes menos). Reporte contexto total e taxa de cache como métricas primárias, e converta pra dinheiro com preços separados por categoria. Explico o conceito com mais calma no artigo sobre o que é cache hit.
3.4 cache_control só no system prompt engana a taxa de cache
Um harness pode devolver taxa de cache baixa (17% a 48% no nosso caso) mesmo quando o provedor suporta cache incremental de verdade. A causa pode estar no próprio harness, e não no modelo nem no provedor.
No Command Code, a função que monta a mensagem de sistema (toWireSystem) só marca cache_control: {type: "ephemeral"} em dois blocos fixos: as instruções e a definição de ferramentas. O histórico de turnos, os resultados de tool call e tudo o que cresce durante a tarefa nunca recebem esse marcador. O teto de cache hit nesse harness é, na prática, o tamanho daquele bloco fixo em relação ao contexto total. Não é cache incremental.
Cache turno a turno: Pixel Canary na tarefa do lexer
Tokens por turno, no Command Code. A entrada cresce com a conversa; o cache fica preso em poucos valores fixos.
Turno 1
Turno 2
Turno 3
Turno 4
Turno 5
Turno 6
Com dois modelos no mesmo Command Code (Space Bunny com 48%, Pixel Canary com 17%) e o mesmo Space Bunny chegando a 92% no OpenCode, o teto aparece: a limitação é do harness, não do modelo. A diferença de 48% para 17% dentro do Command Code provavelmente vem de quão bem o backend de cada modelo honra a diretiva de cache que o harness oferece. É uma variação residual em cima de um teto que já é baixo por desenho.
Como checar: olhe token a token dentro de uma rodada (inputTokens contra cacheReadTokens por turno, no log bruto). Se o cache_read não cresce junto com o histórico da conversa, e fica pulando entre uns poucos valores fixos enquanto o input sobe, o cache está limitado a um bloco estático, e não ao prefixo inteiro. Não assuma que a taxa de cache anunciada pelo fornecedor de um harness se aplica à conversa completa: ela pode se referir só ao trecho fixo, ou a um cenário melhor do que o comportamento observado.
Não é bug do rig de teste. Diferente das outras armadilhas desta seção, que vêm de como nós chamamos o harness, essa é uma propriedade real do harness em uso normal. Qualquer usuário da ferramenta sofre a mesma limitação. Não “corrija” isso mexendo no pacote instalado pra igualar aos outros harnesses: isso mediria uma versão que ninguém roda de verdade e destruiria justamente o que o comparativo deveria revelar.
3.5 __pycache__ vira falso positivo de trapaça
O pytest cria __pycache__ dentro de tests/ quando roda. Um detector de adulteração que compare diretórios inteiros acusa toda rodada que executou a suíte. Compare só os arquivos-fonte.
Salvaguarda geral: guarde o log bruto
Nossa primeira versão apagava o diretório da rodada, com o log dentro, logo depois de avaliar. Sem log bruto não existe diagnóstico depois. Guarde a saída completa de toda rodada fora do diretório descartável.
4. Estatística e registro
No mínimo 3 repetições por par tarefa × harness, 5 se houver orçamento. Reporte mediana e dispersão, nunca média: uma rodada que estourou o tempo distorce a média e não distorce a mediana.
Olhe a distribuição, não só a mediana. No teste do Fledge Alpha, o cache hit mediano foi de 91%, praticamente igual ao do Space Bunny (92%). Mas rodada a rodada o cache era bimodal: 11 rodadas entre 88% e 97% e 4 rodadas entre 9% e 28%. As rodadas ruins custaram 3,3 vezes mais que as boas. Sempre que uma métrica pesar no custo, reporte também a faixa por rodada e conte quantas rodadas ficam em cada grupo.
Cache hit por rodada: a faixa de cada grupo
Da menor à maior taxa de cache entre as rodadas do grupo, numa escala de 0 a 100%.
Rodada refeita se declara. Se uma rodada cair por falha do runner, e não do modelo ou do harness, refaça a rodada e registre que foi refeita e quando. No Fledge Alpha, uma rodada caiu na limpeza de diretório e foi refeita duas horas depois das outras 14.
A métrica principal não é a taxa de acerto, é o custo e os tokens por tarefa resolvida. Com agentes convergidos, o acerto empata e a eficiência separa.
Uma linha JSONL por rodada, com schema fixo, contendo model, harness, harness_version, task, rep, timestamp e todas as métricas. O modelo como coluna é o que torna o bench reutilizável: rode com outro modelo, acrescente no mesmo arquivo e o comparativo sai pronto.
5. Checklist antes de publicar qualquer número
- Executáveis reais, não shims
.cmd encoding="utf-8"em toda captura de subprocesso- Modelo informado pelo harness registrado e conferido em toda rodada
- Log bruto guardado fora do diretório da rodada
.gitremovido do seed, sem sobras (nem.git/objects)- Perfis limpos verificados no dry run
- Correção feita com
tests/pristino restaurado - Detector de trapaça ignorando artefatos de execução
- Contexto total e taxa de cache, não só tokens de entrada
- Timeout registrado separado de “não resolveu”
- Taxa de cache checada token a token (o
cache_readacompanha o crescimento da conversa ou fica preso num bloco fixo?) - Distribuição do cache por rodada conferida, não só a mediana
- Rodadas refeitas e quedas de conexão declaradas
- Ordem das rodadas embaralhada
- Rotas até o modelo declaradas, e tempo interpretado à luz delas
Faça um dry run de uma tarefa em todos os harnesses antes da bateria completa. O nosso primeiro dry run quebrou em quatro pontos, todos do instrumento e nenhum dos harnesses. Três deles teriam produzido uma tabela publicável e errada.