22. z-ai/glm-5.3-flash via OpenRouter como LLM padrão; local como segunda opção
- Status: Accepted (2026-09-13, decisão do dono registrada na issue #1180)
- Deciders: @michelbr84 (decisão final) + Claude (levantamento e execução, sessão autônoma 2026-09-13)
- Date: 2026-09-13
- Tags: llm, provider, configuracao, custo, onboarding
- Supersedes: none
- Superseded by: none
- Links:
- Issue: #1180 —
"Fixar
z-ai/glm-5.3-flash(OpenRouter) como LLM padrão oficial; local/Ollama como segunda opção" - Issues relacionadas (experiência de primeira execução): #1177, #1178
- Precedência de resolução de modelo:
docs/configuration.md§"Provider / model resolution precedence" (GAR-576) - Guardrail de custo do MCP:
docs/cli-mcp-server.md§"Cost policy" (GAR-587)
- Issue: #1180 —
"Fixar
Context and Problem Statement
O projeto não tinha uma identidade de LLM padrão. A pergunta "qual modelo o Garra usa quando o usuário não escolhe nada?" tinha quatro respostas diferentes, e qual delas valia dependia de por onde o usuário entrou:
| Superfície | Padrão antes deste ADR |
|---|---|
garra chat / garra ask | openrouter/auto (chat.rs::hardcoded_default_model) |
Wizard garraia init | openrouter/auto, e "Local-first" pré-selecionado quando havia GPU |
MCP (garra mcp-server, garra mcp-agent) | openrouter/free |
Garra Desktop (config.default.yml) | default_provider: "lmstudio" |
Cada valor tinha sido escolhido por um motivo local e defensável — o
openrouter/free do MCP, em particular, era um guardrail de custo explícito
(GAR-587): um host MCP pode chamar garra_ask em loop, sem ninguém olhando,
e o padrão precisava ser um modelo incapaz de gerar fatura. O problema não era
nenhuma das escolhas isoladas; era não existir uma decisão de projeto acima
delas, o que fez a resposta derivar quatro vezes e deixou a instalação nova
imprevisível.
Enquanto isso, z-ai/glm-5.3-flash já era o modelo que o projeto de fato usava
e documentava como exemplo canônico (garraia-gateway/src/anthropic_api.rs,
garraia-cli/src/config_cmd.rs, garraia-cli/src/main.rs,
docs/cli-mcp-server.md, .claude/agents/implementer.md) — sem nunca ter sido
promovido a padrão.
Decision Drivers
- Previsibilidade da instalação nova. O que o usuário recebe deve ser uma decisão do projeto, não um efeito colateral da porta de entrada.
- Custo não-assistido. O padrão roda em loop dentro de agentes; precisa ser barato o bastante para isso não ser um risco financeiro.
- Qualidade suficiente para trabalho real.
openrouter/freeera barato e ruim; era um padrão que o usuário tinha que trocar antes de fazer qualquer coisa séria. - Local continua sendo cidadão de primeira classe — mas como fallback com circuit breaker, não como primário imposto por detecção de GPU.
- Nada deixa de ser possível. Demover um modelo de "padrão" não pode torná-lo inalcançável.
Considered Options
- OpenRouter +
z-ai/glm-5.3-flashcomo padrão único, Ollama como fallback (escolhida). - Manter
openrouter/autocomo padrão em todas as superfícies. Unifica, mas transforma o padrão num roteador de preço variável — exatamente o que o guardrail do MCP existia para evitar. - Manter
openrouter/freecomo padrão em todas as superfícies. Unifica e é gratuito, mas entrega uma primeira experiência ruim o bastante para que o usuário conclua que o produto é ruim. - Local-first universal (Ollama
qwen3.8:latest). Zero custo e privado, mas exige ~18 GB de download e uma GPU antes do primeiro "olá" — e em máquina sem GPU não há padrão nenhum.
Decision Outcome
Escolhida a opção 1, conforme decisão do dono na issue #1180:
z-ai/glm-5.3-flashvia OpenRouter é o LLM padrão oficial de toda instalação nova.- Local (Ollama,
qwen3.8:latest) é sempre a segunda opção: entra emagent.fallback_providers, e só vira primário quando o usuário pede. openrouter/autoeopenrouter/freedeixam de ser padrão em qualquer superfície. Continuam disponíveis como escolha explícita; nenhum caminho de código os seleciona sozinho.
A implementação central é uma constante única em
crates/garraia-config/src/defaults.rs, lida por chat.rs, wizard/mod.rs e
mcp_server.rs (e, através dele, mcp_agent.rs) no lado da CLI, e por
bootstrap/mod.rs no lado do gateway, com teste travando a tabela nos dois
crates. É isso que impede a próxima divergência: não existe mais literal
repetido para alguém "melhorar" sozinho.
A constante nasceu em crates/garraia-cli/src/defaults.rs, que é
pub(crate) dentro do binário garraia. Isso deixava o gateway de fora:
um bloco openrouter sem model: explícito caía num openai/gpt-4o
hardcoded em bootstrap/mod.rs — a quinta fonte de verdade, invisível para o
teste de trava da CLI. Por isso a constante desceu para garraia-config, o
crate que CLI e gateway já compartilham; crates/garraia-cli/src/defaults.rs
virou um reexport e guarda o único lock que só ele pode guardar (o
config.default.yml do Desktop, um recurso YAML que não lê constante Rust).
O guardrail de custo do MCP foi preservado de propósito, não por acaso. O
doc-comment de MODEL_DEFAULT diz explicitamente que aquela linha é um
guardrail de gasto e que o flash-tier foi escolhido para manter a propriedade;
GARRAIA_MCP_MODEL_ALLOWLIST continua sendo o teto duro do operador.
Consequences
- Boa: uma instalação limpa (Linux, Windows, Desktop, Termux) termina em
agent.default_provider: openrouter+z-ai/glm-5.3-flashsem o usuário escolher nada, e isso é verificável por teste. - Boa: o wizard deixa de empurrar ~18 GB de download como caminho
principal só porque detectou uma GPU. Concretamente: no caminho
"Cloud-first" o confirm de instalar o Ollama nasce em
falsee o seletor de modelo local nasce na linha "pular o download" — o download passa a ser um "sim" digitado, nunca a consequência de apertar Enter. No caminho "Local-first" os defaults continuam onde estavam (instalar sim, primeiro modelo da lista), porque ali é o que o usuário acabou de pedir. A regra mora emLocalStackRole(wizard/mod.rs), testada sem prompt. - Boa: o autodetect do
garra chat(a heurística que roda quando não háagent.default_provider) tenta a nuvem antes do Ollama. Antes ele tentava o Ollama local primeiro, incondicionalmente, o que contradizia a decisão 2 em toda máquina comOPENROUTER_API_KEYexportada e umollama serveligado. A ordem vive emautodetect_order(chat.rs), função pura e testada; o Ollama continua na lista, sempre por último, que é o papel de fallback local que a decisão 2 quer preservar. - Boa: o boot do gateway aplica
agent.default_providerexplicitamente. Sem isso, o default efetivo era o primeiro provider registrado ao iterarconfig.llm— umHashMap, ou seja, ordem não-determinística: com dois providers configurados (oconfig.default.ymldo Desktop tem exatamente dois), "o Desktop nasce em OpenRouter" era sorteio a cada boot. Umdefault_providerque não corresponda a nenhum provider registrado vira aviso no log e mantém o que havia — fail-safe, não fail-fast, porque derrubar o boot por causa de uma chave de API ausente seria pior. - Ruim / aceita: o padrão agora exige uma chave de API. Sem
OPENROUTER_API_KEY(ou chave noconfig.yml), a cadeia de autodetect cai para o fallback local — que é exatamente o papel reservado a ele, mas é uma mudança real para quem instalava sem chave e caía emlmstudio/Ollama por padrão. O perfil endurecido (config.hardened.example.yml) segue local de propósito, agora com a divergência anotada no próprio arquivo. - Ruim / aceita: o padrão passa a ter custo diferente de zero. É por isso
que ele é flash-tier e não
auto, e por isso o allowlist do operador continua existindo. - Risco fechado (validação externa): o slug
z-ai/glm-5.3-flashnão pôde ser validado contrahttps://openrouter.ai/api/v1/modelsde dentro do ambiente de execução deste PR — o egress paraopenrouter.aié bloqueado por política (403 no CONNECT). A validação foi feita fora desse ambiente, durante a revisão do #1180: o modelo existe na OpenRouter, é pago (~US$0,15/M tokens de entrada, ~US$0,50/M de saída) e não tem tier gratuito. Isso fecha o risco de "slug errado deixa toda instalação nova sem LLM primário" e reforça a consequência de custo acima: é exatamente por o padrão ser pago queGARRAIA_MCP_MODEL_ALLOWLISTcontinua existindo como teto duro do operador.
Compliance / Follow-ups
crates/garraia-config/src/defaults.rsé a fonte de verdade;crates/garraia-cli/src/defaults.rsreexporta. Os testes que travam a tabela:defaults::tests::default_llm_identity_is_lockedeno_default_is_a_retired_literal(garraia-config),hardcoded_default_model_table_is_lockededesktop_default_config_matches_the_shared_constants(garraia),openrouter_sem_model_explicito_cai_no_default_do_projeto(garraia-gateway). Mudar o padrão exige mexer em todos, nos docs e neste ADR.- Operadores que fixaram
GARRAIA_MCP_MODEL_ALLOWLIST=openrouter/freeprecisam incluirz-ai/glm-5.3-flashno allowlist (ou removê-lo): o allowlist é avaliado depois deresolve_overridesaplicar o default, e sem o ajuste toda chamada MCP semmodelexplícito passa a responderinvalid_params. Está no fragmento de changelog do #1180. - Instaladores (
install.sh/install.ps1) não fixam modelo — nenhum dos dois precisou mudar, e a paridade da regra 16 doCLAUDE.mdsegue intacta. - O slug foi validado fora deste ambiente durante a revisão do #1180 (ver "Risco fechado" acima) — follow-up encerrado.