Coder Backends¶
snodo executes protocol tasks under interchangeable code generation backends (coders). The coder generates code, while snodo enforces protocol governance, runs validation gates, and maintains an append-only audit log.
Isolation comes first and is not the coder's business. A task never runs in your
working tree unless you ask for that with --no-isolation.
All three write into that same worktree. What differs is which process boundary the write crosses and who makes the commit — everything after is identical.
Supported and External Coders¶
Coder (--coder) |
Description | Type | Requirements | Authentication |
|---|---|---|---|---|
litellm (default) |
Direct LLM completions via LiteLLM (~100+ providers) | Engine-Managed | Python litellm (built-in) |
Provider API keys for the validators, which always run through LiteLLM (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc. or snodo config add) |
openai |
LiteLLM-backed coder using the OpenAI-native message format; selected for OpenAI-style model prefixes | Engine-Managed | Python litellm (built-in) |
Same provider credentials as litellm |
anthropic |
LiteLLM-backed coder for Anthropic models | Engine-Managed | Python litellm (built-in) |
Same provider credentials as litellm |
gemini |
LiteLLM-backed coder for Google Gemini models | Engine-Managed | Python litellm (built-in) |
Same provider credentials as litellm |
opencode |
OpenCode server running in Docker container over HTTP | In-Place Container | Docker daemon running; image opencode:latest |
OpenCode config/env variables inside container |
opencode-cli |
Host opencode run CLI invocation |
In-Place Host CLI | opencode CLI on PATH |
opencode auth login or host provider env vars (OPENROUTER_API_KEY, etc.) — the coder authenticates against your own subscription |
codex-cli |
OpenAI Codex CLI (codex exec) |
In-Place Host CLI | codex CLI on PATH |
codex login — uses your Codex subscription |
claude-cli |
Claude Code CLI (claude -p) |
In-Place Host CLI | claude CLI on PATH |
claude auth login — uses the CLI's own login |
agy |
Antigravity CLI (agy -p) host invocation |
In-Place Host CLI | agy CLI on PATH |
agy login / Google Cloud host credentials — the coder authenticates against your own subscription |
mock |
Deterministic stub for dry-runs and testing | Stub | None | None |
openai, anthropic, and gemini are provider-specific LiteLLM adapters, not
separate CLI integrations. They use the same engine-managed tool loop as
litellm; choosing one selects the corresponding adapter and provider model
family. litellm remains the generic fallback and supports the broader provider
catalog.
Authentication: keys are for the validators¶
Provider API keys are a requirement of the validators, which always run
through LiteLLM — not of the coder. The coder need not use one: opencode-cli,
codex-cli, and agy authenticate against the operator's own subscription, so no provider
key is spent on writing the code. claude-cli likewise uses Claude Code's own
login rather than Snodo's Anthropic API key. --mock needs nothing at all.
Beyond the built-in catalog (Anthropic, OpenAI, Google, OpenRouter, DeepSeek and
Cloudflare Workers AI), any OpenAI-compatible endpoint works by declaring a
provider block with base_url and litellm_provider: openai — that is the
mechanism, and it covers Ollama Cloud, a local Ollama or llama.cpp server,
vLLM, LM Studio, and self-hosted gateways. A local endpoint that needs no key is
not asked for one.
Model Role Separation: Judging vs Execution¶
-m/--modelsets the JUDGING model: The model passed via-m(e.g.-m claude-3-5-sonnetor-m deepseek/deepseek-v4) is resolved through LiteLLM for validators (pre-execute and post-execute gates) and the classifier (intent routing).- External CLI coders use their own model catalogs: Host CLI tools like
agy,codex-cli, oropencode-climaintain their own internal model catalogs and CLI settings. Passing a judging model identifier to an external CLI coder is omitted so the CLI falls back to its own default/last-selected model. - Explicit Coder Model Override: To specify a coder's model explicitly while keeping
-mfor validators, prefix the model string with the coder's namespace:snodo run "implement feature" --coder agy --model agy/gemini-2.5-pro snodo run "implement feature" --coder opencode-cli --model opencode-cli/claude-3-7-sonnet snodo run "implement feature" --coder codex-cli --model codex-cli/gpt-5-codex snodo run "implement feature" --coder claude-cli --model claude-cli/sonnet
Coder Selection Precedence¶
resolve_coder_name() selects the coder in this order:
- Explicit Mock Flag:
--mock/use_mock_coder=True(always returns'mock'). - Explicit CLI Flag:
--coder <name>(e.g.,snodo run "task" --coder agy). - Initial protocol mode:
coder: <name>from the mode named byinitial_modein.snodo/protocol.yml. - Model Prefix Mapping: Inferred from model prefix:
claude-cli/→claude-cli,codex-cli/→codex-cli,opencode-cli/→opencode-cli,opencode/→opencode,agy/→agy,gpt/o1/o3→openai,claude→anthropic,gemini/google/→gemini. - Default Fallback:
litellm.
For a normal snodo run, graph construction resolves the mode-level coder from
the protocol's initial_mode, even if execution has entered or resumed another
current mode. An explicit --coder overrides that mode setting; --mock has
highest priority. Prefix routing applies only when neither is set:
claude-cli/, codex-cli/, opencode-cli/, opencode/, and agy/ select their named
backends; gpt, o1, or o3 select openai; claude selects anthropic;
and gemini or google/ select gemini. Otherwise the coder is litellm.
Availability and coder settings¶
Before dispatching a coder, snodo checks its declared runtime requirements in
the process that will invoke it. If a required executable is missing, the run
is stopped with the missing binary and the adapter's install/remediation hint.
For example, host CLI coders require their CLI on PATH, while opencode
requires Docker. litellm, the provider-specific adapters, and mock do not
declare an external executable requirement.
Explicit llm.coder.* settings that the selected backend does not honour are
reported at info level when that coder is selected. The message names the
setting, its value, and the settings the coder does honour; this is advisory and
does not reject the configuration. Unset defaults are not reported. Current
settings honoured by each backend are:
| Backend(s) | llm.coder.* settings honoured |
|---|---|
litellm, openai, anthropic, gemini |
model, temperature, max_tokens, max_tool_turns |
opencode |
model, workspace, container, sandboxed |
opencode-cli, codex-cli, claude-cli, agy |
model, timeout_seconds, workspace |
mock |
mock_files |
The provider-specific adapters inherit the LiteLLM settings. The in-place host
CLI adapters share the subprocess adapter's settings. Settings supplied through
a mode's coder_config are also checked and reported by their config key.
In-Place Coders vs litellm¶
External coders (opencode, opencode-cli, codex-cli, claude-cli, agy) inherit InPlaceCoderAdapter (skip_engine_commit = True, skip_workspace_write = True):
- In-Place File Writes: External coders edit files directly in the workspace working tree.
- Commit Ownership: The adapter stages and commits changes to git upon completion (
InPlaceCoderAdapter._commit_changes()), advancingHEADso post-execute validators reviewinggit diff HEAD~1..HEADsee the exact change produced. .snodo/Mutation Guard: Any attempt by an in-place coder to modify the.snodo/directory triggers aSnodoMutationErrorand halts execution as asnodo_mutation_blockedblocker (ADR 027).- No Per-Turn Usage or Token Records: Per ADR 034, the absence of turn-by-turn usage and token metrics for external coders is a stated decision (non-goal), not an attestation gap. Token and cost data reside in per-job operational telemetry (
state.jsonviasnodo meta), whereas snodo's hash-chained audit trail attests to governance decisions and verification evidence across all coders.
claude-cli uses Claude Code's documented -p --output-format stream-json
--verbose interface and starts in bypassPermissions so unattended runs can
edit and execute tools. It limits setting discovery to the user's settings
(--setting-sources user), avoiding project-local Claude settings and hooks.
Its claude-cli/<model> namespace is stripped before --model; unprefixed
judging models are not passed to Claude Code. Reported input/output/cache usage,
cost, and served model are captured when Claude Code supplies them. Authentication
is managed by the installed CLI and no credential is passed by Snodo. This is
an in-place host process, not an OS/container sandbox: it runs within the task
worktree and shares the host account's access.
Adding a New Coder Adapter¶
New host CLI coder adapters inherit SubprocessCoderAdapter (snodo.coders.subprocess_adapter), which provides shared subprocess execution, prompt construction, git diff readback, and artifact construction.
Creating a new CLI adapter requires specifying four class attributes and implementing _build_argv:
from typing import List
from snodo.coders.subprocess_adapter import SubprocessCoderAdapter
class CustomCoderAdapter(SubprocessCoderAdapter):
binary: str = "custom-coder"
model_prefix: str = "custom/"
install_hint: str = "Install custom-coder: https://example.com/install"
def _build_argv(self, prompt: str, project_root: str, model: str) -> List[str]:
argv = [self.binary, "run", "--dir", project_root, prompt]
if model:
argv.extend(["--model", model])
return argv
Registering the adapter class in CODER_REGISTRY (snodo/coders/__init__.py) automatically exposes it to --coder, enables model prefix routing, and includes it in the adapter conformance test suite (tests/coders/test_adapter_conformance.py).