iterion

Backends and credential auto-detection

iterion ships three execution backends — claw (in-process LLM SDK), claude_code (Claude Code CLI subprocess), and codex (Codex CLI subprocess) — and picks one automatically when neither the workflow nor the node spell out which one to use. This page documents the resolution chain, the credentials each backend understands, and how to override the auto-selection.

Cloud BYOK. The auto-detection below is the host/env path. In cloud mode, provider API keys are owned per-org and sealed in Mongo, resolved per-run with a precedence chain (per-webhook override → user default → org default → env fallback). See byok.md.

TL;DR

If you have at least one of:

… then opening the studio, hitting New, and clicking Run will work without any further configuration. The agent in the default template has empty backend: and model: — both are filled in at run time from what’s available.

Resolution chain

When a node, workflow, and env are all silent, the runtime resolves a backend in this order (first non-empty wins):

  1. node.Backend — the backend: line on the agent/judge/router
  2. workflow.default_backend — the workflow-level default_backend:
  3. ITERION_DEFAULT_BACKEND — environment override
  4. Auto — the first backend in ITERION_BACKEND_PREFERENCE whose credentials are detected on the host
  5. claw — last-resort fallback

The empty-template path lands on step 4. The pill in the studio toolbar surfaces what the auto-resolver picked (and turns red when no credential is available). Settings → Backends lists every detected source and the resolved default in full:

Studio settings — detected LLM backend credentials and resolved default

Launch-time per-node/-group overrides

Above step 1 sits an explicit launch-time override: you can retarget which model and/or backend specific nodes use for a single run, without editing the .bot. Because the operator is deliberately re-pointing the bot at launch, these win over the node’s own DSL backend:/model:.

This composes with the mono/dual --review-mode topology (ADR-052): the review mode chooses which family runs (one or two), the override chooses which model/backend each running node uses. Overrides are not yet re-applied on iterion resume (same limitation as --backend/--compress).

Default preference order

claude_code → claw

codex is intentionally not in the default list. The codex SDK has known limitations (see codex C030) and we’d rather have authors opt in explicitly than auto-select it. You can still set backend: codex per-node, or include it in ITERION_BACKEND_PREFERENCE to make it eligible for auto-selection.

claude_code is preferred over claw when the user has the Claude Code OAuth file — that path uses the user’s “forfait” subscription instead of metered API calls. Without OAuth, claw is preferred: same auth (ANTHROPIC_API_KEY) but in-process and faster.

⚠️ The Claude Code forfait is usable only through the claude_code backend, not claw. claw can authenticate with the forfait OAuth token (set ANTHROPIC_AUTH_TOKEN to the claudeAiOauth.accessToken from ~/.claude/.credentials.json, no ANTHROPIC_API_KEY; claw then sends Authorization: Bearer + the anthropic-beta: oauth-2025-04-20 header), but it is effectively unusable: the forfait rate-limiter throttles non-Claude-Code clients to ~zero, so a claw request 429s immediately (rate_limit_error, no Retry-After) even with fresh daily quota — while the official claude CLI on the same token + model works. Anthropic scopes the forfait to Claude Code (Consumer Terms). What claw “lacks” is the Claude Code client identity (User-Agent / client headers); reproducing it would be spoofing, not a fix, so iterion does not. iterion emits a one-time stderr warning if you wire this path anyway. For an anthropic/* model on claw, use ANTHROPIC_API_KEY, z.ai (ZAI_API_KEY), or another provider. (The OpenAI/ChatGPT forfait via claw does work — see below — because there is no equivalent client-identity gate on that path today.)

Overriding the order

Set ITERION_BACKEND_PREFERENCE to a comma-separated list:

# Prefer claw even when Claude Code OAuth is present
export ITERION_BACKEND_PREFERENCE='claw,claude_code'

# Only use codex (must be explicitly listed)
export ITERION_BACKEND_PREFERENCE='codex'

Backends omitted from the list are never auto-selected, even if their credentials exist.

Per-node provider routing & fallback chain (provider:)

The backend: field chooses which execution stack runs a node; the optional provider: field is a finer credential-routing hint within that stack. It is resolved per node after ${VAR} / ${VAR:-default} expansion.

Known hints:

Hint Effect
anthropic Force Anthropic-direct (ANTHROPIC_API_KEY / Claude Code OAuth); skip z.ai even when ZAI_API_KEY is set.
zai Force the z.ai Anthropic-compatible facade (ANTHROPIC_BASE_URL=z.ai + ANTHROPIC_AUTH_TOKEN=$ZAI_API_KEY).
openai Force OpenAI-direct (OPENAI_API_KEY), skipping OPENAI_BASE_URL overrides.
auto / (unset) Default process-env precedence.

Fallback chain

provider: accepts a single value or an ordered, comma-separated chain. The chain is the declarative generalisation of the RESCUE_PROVIDER escape hatch:

agent reviewer:
  backend: "claude_code"
  provider: "${RESCUE_PROVIDER:-zai},anthropic"   # z.ai first, Anthropic on hard failure
  model: "claude-opus-4-8"

Semantics:

Which backends honour the chain

Only claude_code consumes the provider hint today, and it routes within the Anthropic-compatible family (anthropiczai ↔ other facades) — i.e. the same model id served by a different credential lane. This is the validated path and the original RESCUE_PROVIDER use case.

claw derives its provider from the model: prefix (openai/…, anthropic/…), and codex ignores the hint entirely. On those backends a multi-element chain is a no-op: the runtime uses only the first provider, and the compiler emits a C088 warning. For cross-provider failover under claw (e.g. Anthropic → OpenAI), vary the model: per node instead — a credential hint alone cannot switch the model that the API expects.

Unknown hint tokens (typos) are flagged at compile time with C087 (a warning) and ignored at run time (the node falls back to default credential precedence). Fields containing a ${VAR} env ref are left for run-time resolution and not statically validated.

Single-value provider: (and unset) behaviour is unchanged — the chain form is purely additive and fully back-compatible.

Per-element model (provider:model)

A provider-specific model can be pinned per chain element with a provider:model token, so a fall-through swaps both the credential hint and the wire model:

agent reviewer:
  backend: "claude_code"
  provider: "zai:glm-5.2,anthropic:claude-opus-4-8"   # glm-5.2 on z.ai, claude-opus-4-8 on Anthropic

This is the case where the chain’s two providers serve different model ids over the same Anthropic-wire APIglm-5.2 is a z.ai model that Anthropic would reject, so a hint-only swap would break on fall-through. The token is split on the first colon (a model id that itself contains a colon survives intact). An element without a model (anthropic in zai:glm-5.2,anthropic) inherits the node’s model: baseline; an inheriting element after a model-bearing one restores the baseline rather than carrying the previous override. A malformed element — a colon with an empty provider (:glm-5.2) or empty model (zai:) — warns C172 at compile time. Env expansion still runs on the whole field first, so the :- in ${VAR:-x} is never mistaken for a provider:model separator.

Like the hint chain itself, per-element models only take effect on claude_code (the only backend that walks the chain today).

Transient-error & network resilience

A brief internet/API outage should not abort a whole run. Every backend call goes through a retry loop (RetryPolicy) with capped exponential backoff + jitter, and the classifier treats connectivity failures as retryable:

Only after a provider exhausts this budget does the run fall through to the next entry in the provider: chain (above).

Per-backend detection rules

A backend reports Available: true only when both a binary/runtime and a credential are present. Just having the CLI in PATH is not enough — the runtime still needs an API key or an OAuth file to actually make calls.

claude_code

Credential Source
OAuth (forfait) macOS: the Claude Code-credentials item in the Keychain (where Claude Code 2.x stores it by default — no file is written). Linux/WSL: $CLAUDE_CONFIG_DIR/.credentials.json (default ~/.claude/.credentials.json; non-hidden credentials.json also accepted)
Binary claude in $PATH, or ~/.claude/local/claude

On macOS the Keychain is probed for existence only (via /usr/bin/security find-generic-password — the token is never read), so a machine logged into Claude Code is detected even though it has no .credentials.json file. For auto-resolution, OAuth is required. If you only have ANTHROPIC_API_KEY and the binary, claw is preferred (same auth, no subprocess fork). To use claude_code with API-key auth, set backend: claude_code explicitly on the node.

codex

Credential Source
OAuth $CODEX_HOME/auth.json (default ~/.codex/auth.json)
Binary codex in $PATH, ~/.volta/bin/codex, ~/.local/bin/codex, /usr/local/bin/codex, /usr/bin/codex

Same logic as claude_code: only OAuth flips it to “available” for auto-resolution. OPENAI_API_KEY alone routes to claw.

claw

claw is in-process and pluralised across providers. It reports Available: true when any of these is set:

Provider Detection
anthropic ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN
openai OPENAI_API_KEY, or Codex CLI signed in via “Sign in with ChatGPT” (see OpenAI via ChatGPT forfait below)
foundry (Azure) AZURE_OPENAI_API_KEY + AZURE_OPENAI_ENDPOINT
bedrock AWS_REGION or AWS_DEFAULT_REGION (full chain handled by AWS SDK)
vertex GOOGLE_CLOUD_PROJECT

When model: on the agent is also empty, the runtime substitutes a sensible default for the first available provider — currently anthropic/claude-opus-4-8 for Anthropic and openai/gpt-5.4-mini for OpenAI.

OpenAI via ChatGPT forfait (Codex CLI OAuth)

When Codex CLI is signed in via Sign in with ChatGPT (rather than the default API-key mode), iterion’s claw provider can reuse that OAuth token to drive OpenAI calls through the ChatGPT-Codex backend (chatgpt.com/backend-api/codex/responses) — billed against the user’s ChatGPT Plus / Pro / Team subscription instead of the metered api.openai.com endpoint.

Setup:

# 1. Install or update Codex CLI (>= 0.130.0 for gpt-5.5 access).
# 2. Sign in via ChatGPT (NOT API-key):
codex logout                     # if previously logged in via API key
codex login                      # follow prompts → "Sign in with ChatGPT"
# 3. Verify auth.json carries chatgpt mode:
jq '.auth_mode' ~/.codex/auth.json   # → "chatgpt"

Iterion auto-detects this on the next iterion run. The status pill shows the OpenAI provider as available even without OPENAI_API_KEY set.

Precedence:

OPENAI_API_KEY wins when both are present. The reasoning: an explicit env var was a deliberate user action — typically a project-scoped BYOK key, a CI secret, or a shared workspace credential — and silently spending someone else’s ChatGPT subscription would be a surprising default. ChatGPT-OAuth activates when OPENAI_API_KEY is unset.

# Force OAuth even with OPENAI_API_KEY set:
export ITERION_OPENAI_USE_OAUTH=1

# Force API-key only (refuse to use OAuth even if no key is set):
export ITERION_OPENAI_USE_OAUTH=0

# Setting OPENAI_BASE_URL (for OpenRouter/Ollama/vLLM) automatically
# disables OAuth so masquerading codex_cli_rs headers don't reach an
# unintended backend.

The studio status pill renders both detected sources, with the inactive one struck-through and labelled (overridden by …).

Model-version gating. OpenAI’s backend gates model access on the HTTP version: header iterion sends with each call. By default iterion derives this from codex --version; override with ITERION_CODEX_VERSION=X.Y.Z if you need to claim a different version without reinstalling the binary. Concretely: gpt-5.5 requires codex-cli >= 0.130; gpt-5.4 works on older versions.

Refresh. iterion does not implement OAuth refresh — it reads whatever access_token is currently on disk. Codex CLI maintains the file as a side effect of normal use; if your token expires mid-run, just codex --version (or any other Codex command) to trigger a refresh, then re-run iterion.

ToS posture. Unlike Anthropic Pro/Max (whose Consumer Terms scope the subscription to Claude Code only — see the warning under z.ai integration), ChatGPT subscriptions don’t carve out Codex CLI as the only legitimate surface. Reproducing Codex CLI’s OAuth flow from a third-party tool is gray-area but has no explicit prohibition today. We treat this as pragmatic — if OpenAI changes the terms or tightens enforcement, set ITERION_OPENAI_USE_OAUTH=0 and fall back to OPENAI_API_KEY.

Editor UX

The studio calls GET /api/backends/detect at mount time. The status pill in the top-left of the toolbar shows:

The detection result is cached server-side for 30 seconds. Click Refresh in the popover to re-probe after fixing your env.

Workflow-level pinning

To pin a backend across an entire workflow (e.g. force claude_code even when OAuth is missing, expecting CI to inject it later):

default_backend: claude_code

agent reviewer:
  # inherits backend: claude_code
  ...

Per-node overrides take precedence:

agent reviewer:
  backend: claw
  model: anthropic/claude-haiku-4-5-20251001

Using a non-Anthropic provider via the Anthropic wire format (z.ai / GLM)

Some providers ship an Anthropic-compatible HTTP endpoint so existing Claude Code clients can talk to them with zero code change. The most common case today is z.ai’s Coding Plan, which serves GLM-4.5 / GLM-4.6 through https://api.z.ai/api/anthropic (or whatever endpoint your z.ai dashboard lists — confirm there). Anthropic itself encourages this kind of integration for partner providers; z.ai’s own docs describe the Claude Code wiring.

Iterion-desktop sources ~/.iterion/env at startup (commit 84a7fc2) and claudesdk/process.go forwards the entire host env to the spawned Claude Code subprocess. There are two equivalent ways to wire it.

Shortcut: ZAI_API_KEY alone

Recommended path — drop a single line in ~/.iterion/env:

# ~/.iterion/env
ZAI_API_KEY=<bearer token from your z.ai dashboard>

When iterion sees ZAI_API_KEY set AND no ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN set, it automatically configures ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic and ANTHROPIC_AUTH_TOKEN=$ZAI_API_KEY for both the spawned Claude Code subprocess (backend: claude_code) and the in-process claw provider factory (backend: claw). Restart iterion-desktop after editing the file so the launcher re-sources it.

If ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN) is also set, that takes precedence — the shortcut is intentionally “auto-route only when no Anthropic auth is configured”. This lets a user keep a fallback Anthropic key for some workflows without losing the z.ai default.

Explicit form: ANTHROPIC_BASE_URL + ANTHROPIC_AUTH_TOKEN

For full control, set the two env vars directly:

# ~/.iterion/env
ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic
ANTHROPIC_AUTH_TOKEN=<bearer token from your z.ai dashboard>
# Leave ANTHROPIC_API_KEY UNSET — if both are present, Claude Code
# prefers the API key and routes back to Anthropic, defeating the
# purpose.

Workflows then run unchanged: backend: claude_code still selects the same delegate, but the network destination is z.ai and the underlying model is GLM. Model strings stay Anthropic-shaped (claude-opus-4-7, …); z.ai’s gateway maps them to its own GLM families internally.

Important caveats

Client identity (User-Agent + custom headers) — claw backend

Every claw provider (anthropic, openai, vertex, foundry) sends an honest User-Agent: claw-code-go/<version> by default. Before 2026-07 no UA was set at all, so Go’s Go-http-client/2.0 leaked onto the wire — the worst possible fingerprint against endpoints that gate service on the calling tool (z.ai’s Coding Plan risk-control flags “SDK-based access” traffic; repeated violations can suspend the account). Two exceptions: the ChatGPT-OAuth path keeps its protocol-required codex_cli_rs/<codex version> identity, and bedrock requests carry the aws-sdk UA (SigV4-signed).

Override precedence (first non-empty wins), identical on the in-process claw path and the sandboxed __claw-runner (the three env vars below are forwarded into the container):

  1. ITERION_LLM_USER_AGENT — iterion surface, injected into every claw provider factory (pkg/backend/model/registry.go withClientIdentity).
  2. CLAW_USER_AGENT — claw’s own env override (works for any claw-code-go embedder).
  3. The per-path default (claw-code-go/<version>, or codex_cli_rs/<version> in ChatGPT-OAuth mode).

For arbitrary headers, claw honours ANTHROPIC_CUSTOM_HEADERS with Claude Code’s exact semantics: newline-separated Name: Value pairs, applied last on every request so they can override any default header — including the User-Agent:

# ~/.iterion/env — identify as a specific tool against a gated endpoint
ITERION_LLM_USER_AGENT="my-tool/1.0"
# or arbitrary headers, Claude Code style (overrides UA too):
ANTHROPIC_CUSTOM_HEADERS="User-Agent: my-tool/1.0
X-My-Header: value"

A malformed ANTHROPIC_CUSTOM_HEADERS line (no Name: part) fails the request with an explicit parse error rather than being silently dropped.

ToS caveat. Presenting as another tool is a decision between you and the endpoint you target (e.g. your z.ai subscription’s supported- tools policy) — iterion/claw default to the honest identity and never spoof on their own. This does not change the Anthropic-forfait posture above: the Claude Code subscription remains scoped to Claude Code by Anthropic’s Consumer Terms, and claw’s OAuth path stays dev-purpose-only regardless of the UA you configure.

Troubleshooting

Symptom Likely cause Fix
Pill is red No credential detected Set ANTHROPIC_API_KEY or sign in to Claude Code, then click Refresh
Pill is green but Run errors out with “no provider” Workflow uses model: openai/... but only ANTHROPIC_API_KEY is set Switch model to an Anthropic spec, or add OPENAI_API_KEY
Pill says “claude_code” but you wanted “claw” OAuth is found and ranked first export ITERION_BACKEND_PREFERENCE='claw,claude_code'
Pill says “claw” but you wanted Codex Codex isn’t in the default order export ITERION_BACKEND_PREFERENCE='codex,claude_code,claw' and ensure $CODEX_HOME/auth.json exists
Editor pill stale after fixing env Server cache (30s) Click the pill → Refresh