Como o lemon-code decide entre um CLI autenticado na sua máquina e uma API HTTP, e por que um não pode virar o outro em silêncio
English summary: How lemon-code can prefer an eligible agent CLI installed on your machine over an HTTP provider, with explicit precedence and no silent fallback from one to the other.
Publicado: 21 de outubro de 2026
Tempo de leitura: 8 min
Série: lemon-code em 30 dias, parte 2 de 13
Durante os primeiros meses, o agente principal do lemon-code só sabia falar com APIs HTTP. Os CLIs externos (Claude Code, Codex, Gemini CLI, OpenCode) existiam apenas como subagentes, chamados de forma explícita.
O resultado era absurdo: eu estava logado no Claude Code e no Codex, com assinatura ativa, e o lemon-code ainda pedia uma chave de API para responder. Isso contradizia a premissa local primeiro do projeto.
Em julho registrei a mudança numa ADR: no modo automático, o lemon-code prefere um CLI elegível quando a preferência CLI-first está ativa; o usuário também pode escolher um runtime ou provider de forma explícita. Este post explica como isso funciona e onde estão as armadilhas.
Pense em dois atalhos independentes para chegar ao mesmo resultado. O CLI usa o próprio binário e sua autenticação; o provider HTTP usa uma credencial separada. O dispatch escolhe um alvo explícito para cada turno.
A mudança começa num tipo. Antes, o roteador devolvia só provider/model. Agora ele devolve um alvo que pode ser de dois tipos:
// alvo de dispatch (reescrito e simplificado)
type RuntimeKind = "claude" | "codex" | "gemini" | "agy" | "opencode" | "copilot" | "grok" | "cursor";
type DispatchTarget =
| { type: "runtime"; runtime: RuntimeKind; model?: string }
| { type: "provider"; provider: string; model: string };
Com isso, o código que executa o turno não precisa adivinhar nada: se o alvo é runtime, ele chama o CLI; se é provider, chama a API. Nunca os dois no mesmo turno.
--runtime, depois --provider, depois CLI-first automático quando a preferência está ativa e há um runtime elegível, e só então a pontuação de providers HTTP.codex falha, o erro é do Codex, e não uma chamada escondida à API da OpenAI.A tentação é tratar codex como apelido de openai, ou claude como apelido de anthropic. Parece prático. É um erro, e a ADR proíbe isso de forma explícita.
| Runtime (CLI) | Binário | Provider que ele não implica |
|---|---|---|
claude | claude | anthropic |
codex | codex | openai |
gemini | gemini | google |
agy | agy | google |
opencode | opencode | API HTTP do OpenCode |
O motivo é separar autenticação e destino. Um CLI usa o próprio binário e o login dele. Uma API usa uma chave. Se o código confunde os dois, uma falha de login no CLI pode disparar uma chamada HTTP sem você perceber.
Produtos gratuitos e pagos para transformar ideias em uma base que você consegue executar.
13 produtos disponíveisContinue explorando tópicos similares
Meu agente escolhia o modelo e eu não sabia por quê. Hoje cada escolha vem com uma linha como 'anthropic score 71.5 (risk +20, order +50)'. Este post mostra como chegar nisso.
O lemon-code tem seis ADRs com o número 0049, todas criadas no mesmo dia. Esse erro ensina mais sobre memória de projeto com vários agentes do que qualquer ADR bem numerada.
A telemetria do meu agente vem desligada, aponta para localhost e não exporta nenhum prompt. Mesmo assim ela responde quanto custou cada turno e por que um modelo foi escolhido.
Checklist prático para validar segurança, custos, avaliações e observabilidade antes de colocar seu app com IA em produção.
Templates testados em produção, usados por desenvolvedores. Economize semanas de setup no seu próximo projeto.
// precedência de dispatch (ilustrativa)
function resolveTarget(opts: {
runtime?: RuntimeKind;
provider?: string;
installed: RuntimeKind[]; // binários achados no PATH
preferCli: boolean; // padrão: true
scoreApis: () => DispatchTarget;
}): DispatchTarget {
if (opts.runtime && opts.provider) throw new Error("use --runtime OU --provider");
if (opts.runtime) return { type: "runtime", runtime: opts.runtime };
if (opts.provider) return { type: "provider", provider: opts.provider, model: "default" };
if (opts.preferCli && opts.installed.length > 0) {
return { type: "runtime", runtime: opts.installed[0] };
}
return opts.scoreApis(); // reserva: pontuação e prontidão das APIs
}
--runtime <nome> escolhe só aquele CLI. Binário ausente ou login inválido gera um erro acionável do runtime. Nunca cai para a API.--provider <nome> escolhe só aquela API. Pula a descoberta de CLIs.As flags --runtime e --provider são mutuamente exclusivas. Passar as duas é erro, de propósito.
Uma decisão pequena que evitou dor: no modo automático, o lemon-code só verifica se o binário existe no PATH. Ele não tenta abrir sessão nem testar autenticação remota a cada turno. Isso deixa o dispatch rápido e sem efeito colateral.
Para diagnosticar login, existe um comando explícito (lc doctor). Separar "descobrir" de "sondar" mantém o caminho quente barato e o diagnóstico completo disponível quando você precisa.
Cada decisão vai para o histórico de dispatch em SQLite com o tipo de alvo (runtime:codex, por exemplo) e o motivo. O que não vai: variáveis de ambiente, chaves, headers, cookies ou saída ilimitada do CLI. A saída do subprocesso é truncada e higienizada antes de qualquer log.
--help instalado e cobrir cada CLI com um teste de contrato antes de habilitar.--runtime codex sem login precisa dizer "faça login no Codex", e não "configure OPENAI_API_KEY". Parece detalhe; é o que faz a pessoa confiar na regra.Os critérios de aceite da ADR servem como checklist para qualquer orquestrador:
--runtime X executa só o CLI X.--provider Y executa só a API Y.O último item é o que eu recomendo copiar primeiro. Ele pega a classe inteira de bugs de fallback silencioso.
Usar primeiro os CLIs elegíveis parece uma simplificação operacional. O ganho maior foi de clareza: cada turno tem um alvo, e esse alvo não muda sem você saber. O dispatch considera providers HTTP quando a preferência CLI-first está desligada ou nenhum runtime elegível aparece; --provider continua disponível para escolha explícita.
No próximo post, sexta, mudo de camada: como o lemon-code desenha a interface do terminal com React, via Ink, rodando sob Bun.
Se você está desenhando um orquestrador de agentes e quer separar destino, autenticação e roteamento desde o começo, é um dos temas em que ajudo times na LEMON.DEV.