# Provider feature parity

Alvin's agent capabilities live in **Alvin's own orchestration** (the
`OpenAICompatibleProvider` tool loop, `subagents.ts`, `alvin-dispatch.ts`,
`personality.ts`), not inside any one vendor SDK. So a non-Claude provider can
reach near-parity with the Claude Agent SDK just by running that loop. This
doc records what each provider tier gets, and why.

## How parity is wired (capability flags, not "is it the SDK")

Behaviour is gated on **capability flags**, never on `config.type === "claude-sdk"`:

- `supportsTools` — runs Alvin's 8-tool executor loop (read/write/shell/web/…).
- `supportsDispatch` — can spawn **detached** sub-agents (`alvin_dispatch_agent`).
- `supportsLiveSteer` — honours mid-turn **/btw** steering.
- `supportsReasoning` — acts on **/effort** (forwards `reasoning_effort` / `--effort`).

For every `OpenAICompatibleProvider`, `supportsDispatch` and `supportsLiveSteer`
**default ON** for any tool-capable endpoint (getters in
`openai-compatible.ts`), so Gemini / Groq / NVIDIA / OpenRouter / Ollama / the
Claude Code bridge all opt in automatically. A preset may force either flag off.

The system prompt is built from **`PromptCaps`** (`promptCapsFromProvider`), so
non-Claude tool providers get the same agentic scaffolding the SDK gets:
action-narration, the available-tools summary, the detached-dispatch rules
(lean `alvin_dispatch_agent` variant), live-steer guidance, **and** the
public-safe **Claude-Code voice** (symbol/emoji conventions, dry humor,
do-then-explain, the occasional unprompted heads-up). The Claude SDK already
has that voice natively, so it's skipped there.

## Parity matrix

| Capability | Claude SDK | Claude bridge | Gemini / Groq / GPT / NVIDIA / OpenRouter | Ollama (local) | Codex CLI |
|---|---|---|---|---|---|
| 8-tool executor loop | ✅ | ✅ | ✅ | ✅ per-model (gemma4:e4b ✓, gemma3n:e4b ✗) | ⚠️ own internal loop |
| Parallel in-process sub-agents | ✅ | ✅ | ✅ (run on the active provider) | ✅ | as a sub-agent only |
| Detached sub-agents (`alvin_dispatch_agent`) | ✅ | ✅ | ✅ → run on **that** provider | ✅ | ❌ (black-box) |
| Live `/btw` steering | ✅ | ✅ | ✅ | ✅ | ❌ |
| `/effort` reasoning | ✅ | ✅ (`--effort`) | ✅ Gemini 2.5/3 · o3 · others ❌ | ❌ | ❌ |
| Workspaces — persona / model / cwd | ✅ | ✅ | ✅ | ✅ | ✅ |
| Workspaces — temperature / toolset | ✅ | ✅ | ✅ | ✅ | ❌ (no Alvin tool loop) |
| Claude-Code voice / personality | native | native | ✅ injected | ✅ injected | ✅ injected |

### Detached sub-agents run on the user's provider

The in-process sub-agent path always ran on the active provider. Detached
dispatch now matches: `selectDispatchBackend` prefers the **generic worker**
when a genuinely non-Claude provider is active (`activeProviderIsClaude:false`),
threading the active `providerKey` into the worker — so a Gemini user's
background audit runs on Gemini, not silently on `claude -p`. Claude-backed
providers (SDK, the bridge, any `claude-*` model) keep `claude -p`, the natural
equivalent worker.

### `/effort` on cloud endpoints

`reasoning_effort` is forwarded only when a preset sets `supportsReasoning`.
Cloud OpenAI-compatible APIs only accept `low|medium|high`, so the value is
clamped `max → high` (`reasoningEffortValue`); the bridge keeps `max` (its
`--effort` accepts it). `supportsReasoning` is enabled per-preset only after the
endpoint is verified to accept the field, to avoid a 400 knocking the request
out of the tool loop. **Enabled:** Gemini 2.5/3 (verified: `reasoning_effort`
→ HTTP 200, live tool-loop with `effort:max` confirmed) and o3 (native OpenAI
reasoning). **Left off:** non-reasoning chat models like `llama-3.3-70b` (Groq
returns HTTP 400 for `reasoning_effort`) and `gpt-4o`/`gpt-4.1`.

## Codex CLI — honest limits

`codex-cli` is the one tier that **cannot** reach full parity, by architecture:
it shells out to `codex exec` (one-shot, `--ephemeral`, `-s read-only`) and runs
its **own** agent loop *inside the Codex binary*. Alvin never sees Codex's tool
calls or mid-turn state, so:

- ✅ **Gets:** the Claude-Code voice + action-narration (injected via the system
  prompt, which `codex exec` folds into its input), the workspace **persona**,
  the **model** override (`-m`), and the working dir (`-C`). Works fine as a
  *sub-agent* of another provider.
- ❌ **Can't get:** Alvin's 8-tool loop, detached `alvin_dispatch_agent`
  dispatch, live `/btw` steering, workspace **toolset** restriction, or
  `temperature`/`effort` forwarding — all of those hook into Alvin's own
  tool-round loop, which Codex bypasses. Codex is also `read-only` sandboxed by
  default (no writes / network from inside Codex).

To bring Codex to full parity you'd need a Codex equivalent of the Claude Code
bridge (an OpenAI-`tool_calls` front for `codex`), which the Codex CLI does not
currently expose. Out of scope until it does.

## Local models (Ollama) — per-model tool support

The Ollama endpoint is auto-detected as tool-capable, so Alvin always *attempts*
the tool loop; whether it works depends on the model emitting OpenAI
`tool_calls`. Verified live on a MacBook Air (2026-06-16, Ollama 0.30.8):

- **`gemma4:e4b` (the default) ✅** — runs the FULL loop: `tool_use` fired,
  `python_execute` returned the correct unfakeable SHA256, and a mid-task /btw
  note folded on the 1st round (pivot to "PARIS"). Real OpenAI tool_calls.
- **`llama3.2:3b` ✅** — full loop + steering (pivot after the running batch).
- **`gemma3n:e4b` ✗** — the older on-device variant emits Gemma's own
  ```` ```tool_code ```` blocks, which Ollama's OpenAI layer does **not** translate
  to `tool_calls`; it hallucinated the hash and never entered the loop. The
  provider degrades cleanly to plain text and still gets the Claude-Code voice.

So local parity is real — pick a model that speaks OpenAI tool_calls
(gemma4, llama3.x, qwen2.5, mistral) for agentic use.
