Why model-agnostic
Every major AI lab now ships an agent framework that works best (or only) with their own models. That creates fragility: today's best model is rarely tomorrow's, and enterprise teams can't afford to rewrite orchestration every time they evaluate a new provider.
Bernstein decouples orchestration from model choice. The orchestrator is deterministic Python — it schedules tasks, routes models, and verifies results without talking to any LLM. Each CLI agent is a thin subprocess adapter. Swapping the adapter is a one-line config change.
Concrete benefits:
- Cost arbitrage — route simple formatting tasks to free-tier models, save expensive capacity for architecture and security work.
- Provider redundancy — hit a rate limit on one provider? Re-route open tasks to another without restarting the run.
- Future-proof — your task graph, roles, and prompts outlive any single model generation.
Feature matrix
| Feature | Aider | Amp | Claude Code | Codex CLI | Cursor | Gemini CLI | Kilo | Qwen | Roo Code | Generic |
|---|---|---|---|---|---|---|---|---|---|---|
| Model selection | Any (via --model) | opus 4.6 / gpt-5.4 | opus / sonnet / haiku | gpt-5.4 / o3 / o4-mini | sonnet 4.6 / opus 4.6 / gpt-5.4 | gemini-3-pro / 3-flash / 2.5-pro | Any (provider/model) | qwen3-coder / qwen-max | opus 4.6 / sonnet 4.6 / gpt-4o | Pass-through |
| Effort levels | — | — | max / high / medium / low | — | — | — | — | — | — | — |
| MCP config injection | — | — | ✓ | — | ✓ (--add-mcp) | — | ✓ (--mcp) | — | — | — |
| Structured JSON output | — | — | ✓ (stream-json) | — | — | — | — | — | — | — |
| Tier detection | — | — | ✓ (API key prefix) | ✓ (org ID / key prefix) | ✓ (~/.cursor/ present) | ✓ (GCP project / key format) | ✓ (~/.kilo/ or API key) | ✓ (provider env vars) | — | — |
| Auto-approval | --yes |
Built-in | --dangerously-skip-permissions |
--approval-mode full-auto |
Built-in agent mode | --sandbox none |
--yes |
-y |
Via config | Via extra_args |
| Web search | Depends on model | Native | Via MCP | Native | Native | Native | Depends on model | Via Tavily (optional) | Via MCP | Depends on CLI |
| OpenAI-compatible backends | ✓ | — | — | — | — | — | — | ✓ (OpenRouter, Together, Oxen) | ✓ | — |
| Local / offline models | ✓ (via OpenAI-compat server) | — | — | — | — | — | — | ✓ (via OpenAI-compatible server) | ✓ | ✓ |
Aider
Provider: OpenAI / Anthropic / any OpenAI-compatible | CLI flag: --cli aider
pip install aider-chat
export OPENAI_API_KEY=sk-... # or ANTHROPIC_API_KEY
Aider is a popular open-source coding assistant that works with any model provider. Bernstein runs it with --yes --no-git --message <prompt> for non-interactive headless operation. Model selection is passed through via --model. Aider supports local models through any OpenAI-compatible API server (LM Studio, Ollama, vLLM).
Amp
Provider: Sourcegraph | CLI flag: --cli amp
brew install amp
# Auth via Sourcegraph account
Amp is Sourcegraph's coding agent. Bernstein uses its headless mode for autonomous operation. Model selection depends on the user's Sourcegraph subscription tier.
Claude Code
Provider: Anthropic | CLI flag: --cli claude
npm install -g @anthropic-ai/claude-code
export ANTHROPIC_API_KEY=sk-ant-...
The Claude adapter has the most integration depth: it uses --output-format stream-json to parse structured output, maps effort levels to --effort and --max-turns, and passes MCP server config directly via --mcp-config.
Model mapping:
| Bernstein tier | Claude model |
|---|---|
opus | claude-opus-4-6 |
sonnet | claude-sonnet-4-6 |
haiku | claude-haiku-4-5-20251001 |
Codex CLI
Provider: OpenAI | CLI flag: --cli codex
npm install -g @openai/codex
export OPENAI_API_KEY=sk-proj-...
Runs in --approval-mode full-auto with --quiet to suppress interactive prompts. Pass any OpenAI model name directly via --model. Recommended model: gpt-5.4 (Mar 2026). The o-series reasoning models (o3, o4-mini) are also supported. Tier detection checks for OPENAI_ORG_ID (enterprise) and key prefix (sk-proj = pro).
Cursor
Provider: Cursor AI | CLI flag: --cli cursor
# Install Cursor from https://www.cursor.com
# Sign in via the Cursor app — auth is stored in ~/.cursor/
cursor agent --version
Cursor is a VS Code fork with built-in AI. The cursor agent subcommand runs the agent headlessly in the terminal. Bernstein uses --user-data-dir to isolate each session's state, and --add-mcp to inject MCP server config. The adapter does not pass --model because Cursor selects the model from the user's subscription settings.
Authentication is via the Cursor app OAuth session — no separate API key is needed. Bernstein detects login by checking for ~/.cursor/. Tier detection reports Pro when logged in.
Gemini CLI
Provider: Google | CLI flag: --cli gemini
npm install -g @google/gemini-cli
export GOOGLE_API_KEY=AIza...
Runs with --sandbox none. The default model is gemini-3-pro (preview, Mar 2026). Gemini 2.5 Pro/Flash are GA but deprecating June 2026. For Vertex AI / enterprise use, set GOOGLE_CLOUD_PROJECT — tier detection will pick this up and apply higher rate limits accordingly.
Kilo
Provider: Stackblitz (multi-provider) | CLI flag: --cli kilo
# Install from https://kilocode.ai
export KILO_API_KEY=...
kilo --version
Kilo is a multi-provider agent CLI from Stackblitz. It supports headless mode, ACP/MCP protocols, and session management. Bernstein invokes it as kilo run --prompt "<task>" --model <provider/model> --yes. Pass any provider/model string — e.g. anthropic/claude-sonnet-4-6, openai/gpt-5.4, or google/gemini-2.5-pro.
Authentication is via KILO_API_KEY (preferred) or OAuth session stored in ~/.kilo/. MCP server config is injected via --mcp. Tier detection reports Pro when any auth is found.
Qwen
Provider: Alibaba / OpenRouter / any OpenAI-compatible | CLI flag: --cli qwen
npm install -g qwen-code
The Qwen adapter is the most flexible: it routes to different backends depending on which API keys are set. Priority order:
| Backend | Env var | Use case |
|---|---|---|
| OpenRouter (paid) | OPENROUTER_API_KEY_PAID | Best quality via OpenRouter |
| OpenRouter (free) | OPENROUTER_API_KEY_FREE | Free-tier routing, rate-limited |
| Together.ai | TOGETHERAI_USER_KEY | Open-source models, fast inference |
| Oxen | OXEN_API_KEY | Enterprise open-source |
| Default (native Qwen) | DASHSCOPE_API_KEY | Alibaba Cloud, qwen-max / coder |
This makes Qwen a good choice for cost-sensitive runs: route simple tasks to OpenRouter free-tier, keep expensive capacity for complex ones.
Roo Code
Provider: Anthropic / OpenAI / any | CLI flag: --cli roo-code
# Install Roo Code VS Code extension from https://github.com/RooVetGit/Roo-Code
# Headless CLI mode available via the extension's CLI interface
Roo Code is a VS Code extension with a headless CLI mode. It supports multiple providers (Anthropic, OpenAI, and OpenAI-compatible backends) and can use local models. Bernstein spawns it in headless mode for autonomous task execution.
Generic adapter
CLI flag: --cli generic
The generic adapter wraps any CLI agent without writing Python. Configure it via bernstein.yaml:
cli: generic
cli_options:
command: aider # the binary name
prompt_flag: --message
model_flag: --model
extra_args:
- --yes
- --no-git
Bernstein calls: aider --model <model> --yes --no-git --message <prompt>. Any coding agent with a non-interactive mode works.
OpenClaw experimental
Type: Runtime bridge (not a CLI adapter) | Status: Stub ready, awaiting OpenClaw API GA
OpenClaw provides ephemeral, network-isolated cloud sandboxes for running CLI coding agents remotely. Instead of spawning agents on your local machine, Bernstein can delegate execution to OpenClaw containers — useful for CI/CD, resource-constrained machines, or security-sensitive runs.
Configuration in bernstein.yaml:
bridges:
openclaw:
endpoint: https://api.openclaw.dev/v1
region: us-east-1
sandbox_class: large
Note: The OpenClaw bridge is currently a stub (NotImplementedError). It will be fully wired when the OpenClaw API reaches general availability. Track progress in the project roadmap.
Add a custom adapter
If you need deeper integration than the generic adapter provides (structured output parsing, custom tier detection, etc.), implement the CLIAdapter interface:
from bernstein.adapters.base import CLIAdapter, SpawnResult
from bernstein.core.models import ModelConfig
from pathlib import Path
from typing import Any
class MyAgentAdapter(CLIAdapter):
def spawn(
self,
*,
prompt: str,
workdir: Path,
model_config: ModelConfig,
session_id: str,
mcp_config: dict[str, Any] | None = None,
) -> SpawnResult:
log_path = workdir / ".sdd" / "runtime" / f"{session_id}.log"
log_path.parent.mkdir(parents=True, exist_ok=True)
cmd = ["my-agent", "--model", model_config.model, "-p", prompt]
import subprocess
with log_path.open("w") as log_file:
proc = subprocess.Popen(
cmd, cwd=workdir,
stdout=log_file, stderr=subprocess.STDOUT,
start_new_session=True,
)
return SpawnResult(pid=proc.pid, log_path=log_path)
def name(self) -> str:
return "My Agent"
Register it in your project:
# bernstein_plugins.py (auto-discovered at startup)
from bernstein.adapters.registry import register_adapter
from my_package.adapter import MyAgentAdapter
register_adapter("my-agent", MyAgentAdapter())
Then use it: bernstein run --cli my-agent.
Mixing adapters in one run
Different task roles can use different adapters. Configure per-role overrides in bernstein.yaml:
goal: "Add auth, write tests, and document the API"
# Default adapter for all roles (auto-detects installed agents)
cli: auto
# Per-role overrides
role_config:
qa:
cli: codex # GPT-5.4 for test generation
model: gpt-5.4
docs:
cli: gemini # Gemini Flash for docs — cheaper
model: gemini-3-flash
The orchestrator assigns each task to the right adapter based on role. All tasks write to the same .sdd/ state — adapter choice is transparent to the rest of the system.
See the multi-model example for a working demo with three adapters in a single run.