Decision records
ADR 0050 — appgen --model is provider-agnostic (any model, not just local Gemma)
- Status: Accepted (implemented 2026-07-13) —
@loom/llmgains aGeminiProvider; the CLI--modelflag takes a provider spec (PR). - Date: 2026-07-13
- Amends: ADR 0042 (AI-native brownfield appgen) — specifically its “optional local Gemma model” framing.
- Builds on: the existing
@loom/llmLlmProviderSPI +@loom/reasoningModelRouteladder.
Context
ADR 0042 shipped the appgen NL→plan stage with an optional model behind @loom/appgen-model, framed
as a local Gemma planner (--model <gguf> + node-llama-cpp). In review that default looked
premature: it pinned users to a local model with a multi-GB download and a native dependency, when
most would rather point appgen at a model they already use (Anthropic, OpenAI, Gemini, …).
Crucially, the architecture was already right: planChangesModel(intent, appModel, { routes })
takes @loom/reasoning ModelRoutes, each carrying any LlmProvider. Only two things pinned it
to Gemma: the CLI --model <gguf> flag (which only built a gemmaRoute) and the framing/docs. So
this is not a rewrite — it’s exposing the pluggability that was already there.
Decision
--model takes a provider spec; the planner reasons over whichever model is named.
--model anthropic:<model>/openai:<model>/gemini:<model>→ the matching@loom/llmprovider, API key from the provider’s env var (ANTHROPIC_API_KEY/OPENAI_API_KEY/GEMINI_API_KEY), checked upfront with a clear error.--model <path>.gguf/gemma:<path>/local:<path>→ the local Gemma provider (the optional@loom/appgen-model+node-llama-cpp), exactly as before — now one option among several, not the only one.--model-big <spec>names a stronger tier for the cheap-first elevation ladder; any spec form.
To make the three named API providers concrete, @loom/llm gains a GeminiProvider (a thin
fetch client, same shape as OpenAiProvider/AnthropicProvider). Any LlmProvider works
programmatically; these are wired by name in the CLI for convenience.
The fallback contract is unchanged: a missing key/provider, a too-weak model, or an unparseable plan
falls back to the deterministic parser — appgen is never left without a valid ChangeSpec.
Consequences
- Local Gemma is no longer privileged — it’s the offline option, chosen deliberately, not by
default. Users bring the model they already pay for.
@loom/appgen-modelstays optional and lazily loaded (itsnode-llama-cppnative dep is only needed for the local.ggufpath). - The spec-resolution seam lives in the CLI (
routeFromSpec), keeping@loom/appgen/@loom/appgen-modelfree of provider-selection policy. New providers drop in by adding anLlmProviderto@loom/llmand onecaseto the resolver. - No change to determinism/provenance: the plan header + lockfile still record which planner ran; the generated code is identical regardless of model.
- Cost/privacy is now the user’s explicit choice (local vs. a hosted API), rather than an implicit local-only default — which is what made the original framing feel wrong.
Source: docs/adr/0050-appgen-model-provider-agnostic.md in the loom repository.