Skip to content

init wizard: multi-provider support — Anthropic / OpenAI / Moonshot via flag, env-var autodetect, or question #324

@dep0we

Description

@dep0we

Surfaced after

Init-wizard arc closer (#94 PR 2 of 2 / #323) shipped 2026-06-02 with three templates and the Add-to-it recovery merge. Discovered during dogfood discussion: the wizard is implicitly Claude-first, but the framework underneath has been provider-agnostic since #87.

What's true today

Credential pre-flight is hardcoded to Anthropic. atomic_agents/init/wizard.py:134 _api_key_preflight() calls _llm._get_key(env_vars=ANTHROPIC_ENV_VARS, keychain_name=\"atomic-agents-anthropic\", config_key=\"anthropic\"). ANTHROPIC_ENV_VARS is fixed at (\"ATOMIC_AGENTS_ANTHROPIC_KEY\", \"ANTHROPIC_API_KEY\") in atomic_agents/init/constants.py:331. The error path at constants.py:351-353 instructs the operator to export ANTHROPIC_API_KEY=sk-ant-....

Template model defaults are hardcoded to Claude. All three shipped templates ship with Claude models in model.md:

  • advisor: claude-opus-4-7 primary + claude-sonnet-4-6 fallback
  • researcher: claude-opus-4-7 primary + claude-sonnet-4-6 fallback
  • writer: claude-sonnet-4-6 primary + claude-haiku-4-5 fallback

Opt-in test call is provider-neutral but credential is not. _test_call routes through _llm.call which uses find_backend_for_model(model, preferred_provider=...). So once model.md points at a non-Claude model, the test call uses the right backend automatically — but the pre-flight blocks the operator before they get there.

What's true underneath

The framework is provider-agnostic. atomic_agents/_llm.py registers three reference LLM backends at import time: Anthropic, OpenAI, Moonshot (per CLAUDE.md status block + #87). find_backend_for_model(\"gpt-5\", preferred_provider=\"openai\") routes correctly. model.md's optional provider: field disambiguates when multiple registered backends could plausibly own a model string.

Per-provider _get_key helpers already exist. _llm.py:171-188 defines _get_anthropic_key, _get_openai_key, _get_moonshot_key thin wrappers around the same _get_key(env_vars, keychain_name, config_key) primitive that wizard pre-flight uses. The plumbing is there; the wizard just doesn't ask which one to call.

The workaround today

OpenAI-only operator path:

  1. Use --from-template advisor (skips pre-flight per amended spec/35 MUST 7 carve-out).
  2. Open <agent>/model.md, replace Claude model strings with OpenAI ones, add provider: openai.
  3. export OPENAI_API_KEY=....
  4. atomic-agents doctor --agent <name> to verify.

Discoverable but graceless. It's the half-day-deploy friction the wizard was supposed to eliminate, just shifted to non-Claude users.

Proposed fix

Three design surfaces, none mutually exclusive:

A. Env-var autodetect (recommended primary, lowest friction). Before pre-flight runs, scan for ANTHROPIC_API_KEY / ATOMIC_AGENTS_ANTHROPIC_KEY, OPENAI_API_KEY / ATOMIC_AGENTS_OPENAI_KEY, MOONSHOT_API_KEY / ATOMIC_AGENTS_MOONSHOT_KEY. If exactly one is set: use that provider, render templates with that provider's default model defaults, pre-flight against that provider's _get_key helper. If multiple are set: fall back to provider question (C below). If zero are set: error message lists all three options.

B. --provider <anthropic|openai|moonshot> flag. Explicit override for the non-interactive path. Composes with --from-template: atomic-agents init my-agent --from-template advisor --provider openai. Skips autodetect; uses the flagged provider's defaults + key check. CLI-friendly for CI / scripted workflows.

C. Provider question Q0 in the interactive Q&A. If autodetect found multiple keys OR autodetect was skipped, ask the operator which provider before any other question. Show one-line plain-English descriptions: "Anthropic (Claude) / OpenAI (GPT) / Moonshot (Kimi)". Selected answer drives pre-flight + template rendering.

Template rendering with per-provider defaults. The 3 existing templates stay; their model.md becomes a string.Template with \\${default_model} / \\${fallback_model} / \\${provider} variables. The wizard supplies provider-specific defaults at render time:

  • Anthropic: opus-4-7 / sonnet-4-6 (advisor + researcher), sonnet-4-6 / haiku-4-5 (writer)
  • OpenAI: gpt-5 / gpt-5-mini (advisor + researcher), gpt-5-mini / gpt-5-nano (writer) — exact model strings TBD at impl time based on current OpenAI catalog
  • Moonshot: moonshot-v1-128k / moonshot-v1-32k or whatever the current Moonshot catalog supports

The model defaults per-provider should be declared as a single constant table in constants.py so the cost guardrails (daily_cap_usd / monthly_cap_usd) can also vary per provider where unit cost differs significantly (e.g. opus-4-7 vs gpt-5 pricing).

Spec/35 amendment. MUST 7's API-key pre-flight wording becomes provider-aware ("matching the configured provider" not "Anthropic"). New MUST documenting provider-selection precedence (--provider flag > env-var autodetect > Q0 question > error).

Acceptance

  • atomic-agents init my-agent --from-template advisor --provider openai scaffolds an OpenAI-shaped advisor with one command and no model.md editing.
  • OPENAI_API_KEY=... atomic-agents init my-agent (no --provider flag) autodetects and scaffolds an OpenAI-shaped agent.
  • ANTHROPIC_API_KEY=... OPENAI_API_KEY=... atomic-agents init my-agent triggers Q0 provider question.
  • Existing Anthropic-only paths unchanged byte-identically (zero-regression on the 3001 tests).
  • Doctor + opt-in test call work end-to-end against each provider.
  • Three new smoke tests: test_smoke_from_template_openai, test_smoke_from_template_moonshot, test_smoke_env_var_autodetect_when_single_key_set.

Why this is higher leverage than other wizard follow-ups

Scope discipline

Out of scope for this issue:

  • Adding new provider backends (Bedrock, Vertex, Azure OpenAI, etc.) — that's [backend] LLMBackend Protocol + canonical types + native reference implementations #87's territory and needs its own arc.
  • Multi-provider failover within a single agent (Sonnet primary + GPT-5 fallback in same model.md) — possible today via model.md if both provider keys are set, but not a wizard concern.
  • Provider-specific persona advice ("OpenAI models tend to be more verbose; consider shorter SOUL.md") — speculative until we actually observe persona drift across providers.

Effort estimate

  • Anthropic-only baseline: shipped.
  • Add autodetect + --provider flag + per-provider template constants + 3 smoke tests + spec/35 MUST 7 amendment: ~1 day of CC time with 2-3 adversarial rounds, similar shape to PR 2 of arc.
  • Q0 question is optional extension if autodetect resolves the common case cleanly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions