> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nanoclaw.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent providers

> Swap the agent brain per group — Claude Code by default, OpenCode or Codex via provider skills, or a local Ollama model with no provider code at all.

A **provider** is the agent CLI that runs inside a group's container — the thing that actually plans, calls tools, and writes replies. By default that's Claude Code via the Claude Agent SDK. The provider is pluggable: the agent runner selects an implementation from an open registry at startup, and the trunk ships only `claude` (plus a `mock` provider for tests).

<Note>
  Don't confuse providers with [tools](/extend/tools). A tool skill like `/add-ollama-tool` keeps your existing provider as the agent's brain and gives it a local model to call; a provider skill **replaces** the brain for an entire group.
</Note>

## How the provider is resolved

At spawn time the host resolves the provider name with a fixed precedence ([`src/container-runner.ts`](https://github.com/nanocoai/nanoclaw/blob/main/src/container-runner.ts)):

1. `sessions.agent_provider` — per-session override
2. `container_configs.provider` — the group's [container config](/reference/container-config#provider)
3. `'claude'`

The result is lowercased. The host then asks its provider registry whether that provider contributes extra mounts or env vars (OpenCode's XDG dirs and `OPENCODE_*` passthrough; Codex's per-group `~/.codex` state dir and its own `AGENTS.md` surfaces). `mock` contributes nothing, and `claude` contributes only when setup has wired a custom Anthropic-compatible endpoint — `ANTHROPIC_BASE_URL` from `.env` plus a placeholder auth token for the vault gateway to overwrite. Inside the container, the runner reads `provider` from `container.json` — materialized fresh from the database at every spawn — and instantiates it from the in-container registry, passing the group's `model` and `effort` through unchanged.

Every code path that creates a session on trunk writes `agent_provider = null`, and `ncl sessions` is read-only (`list`/`get`) — so the per-session override exists in the resolver, but nothing ships that sets it. In practice you switch providers per group.

## Claude (default)

The Claude provider wraps the Claude Agent SDK. Its `model` accepts an alias (`sonnet`, `opus`, `haiku`) or a full model ID, and `effort` is one of `low`, `medium`, `high`, `xhigh`, `max` — both optional, both falling back to the SDK default. It's also the most fully integrated provider in-tree: native slash-command handling, mid-turn input streaming, and transcript rotation that archives oversized session histories before a cold resume can wedge the container.

To point it at any Anthropic-compatible endpoint, set `ANTHROPIC_BASE_URL` in `.env` — the real token stays in the vault and is injected on the wire. See [Credentials](/operate/credentials#anthropic-credentials).

## Two ways to run a different brain

The three provider skills split into two mechanisms:

* **Real provider implementations** — OpenCode and Codex are TypeScript providers that live on the `providers` [registry branch](/extend/overview#how-a-code-carrying-skill-installs). `/add-opencode` and `/add-codex` copy the files in, wire them into the host and container barrels, and rebuild the image.
* **No provider code at all** — Ollama speaks the Anthropic API natively (`/v1/messages`), so `/add-ollama-provider` keeps the Claude provider and just redirects it: env overrides plus a model setting.

### OpenCode (`/add-opencode`)

Copies `opencode.ts` (host and container sides) from the `providers` branch, appends one import line to each provider barrel, adds `@opencode-ai/sdk` to the agent runner, installs the `opencode-ai` CLI in the Dockerfile, and copies registration/Dockerfile guard tests so the wiring stays verified. The provider keeps a shared `opencode serve` runtime — spawned once, reused across queries, and torn down on abort or when the config changes — and routes models through OpenCode's own config: OpenRouter, DeepSeek, OpenAI, Google, Anthropic, OpenCode Zen.

Configure via host `.env`: `OPENCODE_PROVIDER`, `OPENCODE_MODEL` (in `provider/model` form), optional `OPENCODE_SMALL_MODEL`, and `ANTHROPIC_BASE_URL` pointed at the upstream provider's API base for non-Anthropic providers. API keys go in the [OneCLI vault](/operate/credentials) with a matching host pattern — never in `.env`. SDK and CLI versions are pinned together at 1.4.17; `latest` silently breaks session IDs.

### Codex (`/add-codex`)

Copies the Codex payload from the `providers` branch — `codex.ts` and `codex-app-server.ts` (container side), the host contribution (`src/providers/codex.ts`, `codex-agents-md.ts`), and the setup module — wires one import into each of the three provider barrels, and adds the pinned `@openai/codex` CLI to the container manifest (`container/cli-tools.json`) rather than a hand-edited Dockerfile layer (no SDK dependency — Codex is a binary). The provider spawns one `codex app-server` child process per query and speaks JSON-RPC over stdio: native session resume, streaming events, and MCP tools, with conversation history kept server-side — the continuation is a thread id, so there's no on-disk transcript.

Codex owns its agent surfaces (`providesAgentSurfaces`), so the host skips the default Claude compose and the provider supplies its own, composed fresh on every spawn:

* **`AGENTS.md`** — Codex's project doc (`codex-agents-md.ts`), mounted read-only over the group dir. If the group was created from a template, its persona (`instructions.prepend.md`, the same provider-neutral file the Claude compose inlines) opens the doc. It carries a 32 KB Codex cap and degrades by dropping the largest instruction sections — the persona is exempt and never dropped — rather than blocking a spawn.
* **`.agents/skills`** — Codex-native skill links synced to the group's selected skills, plus any template skills mirrored in as real directories (templates stamp them on the Claude plane in `.claude-shared/skills`, which Codex never reads). The `.agents` dir is mounted read-only twice — at `/workspace/agent/.agents` and `/home/node/.agents` — because Codex only scans a workspace-level `.agents/skills` when the workspace is a git repo (the agent workspace isn't one); the `$HOME`-level mount is what makes the skills discoverable.
* **`~/.codex`** — a per-group private state dir (`.codex-shared`), persistent across sessions so thread metadata and `config.toml` survive respawns.

**Auth is vault-only**, the same invariant as every other provider: the credential — a ChatGPT subscription token or an OpenAI API key — lives in the [OneCLI vault](/operate/credentials) under an `api.openai.com` / `chatgpt.com` host pattern, and the gateway injects it on the wire while the container sees only an `onecli-managed` stub `auth.json`. Nothing goes in `.env`, and the host strips `OPENAI_API_KEY` from the Codex process. Run the walk-through with `pnpm exec tsx setup/index.ts --step provider-auth codex` (ChatGPT browser/device login or API key) — the same path the [setup picker](#choosing-a-provider-during-setup) uses. Model and effort come from the group's [container config](/reference/container-config) (`ncl groups config update --model/--effort`), never env.

### Ollama (`/add-ollama-provider`)

No files copied, no rebuild of provider code. The skill first checks that `ContainerConfig` supports the `env` and `blockedHosts` fields (patching `src/container-config.ts` and `src/container-runner.ts` if not), then writes the group's container config:

```json theme={null}
{
  "env": {
    "ANTHROPIC_BASE_URL": "http://host.docker.internal:11434",
    "ANTHROPIC_API_KEY": "ollama",
    "NO_PROXY": "host.docker.internal",
    "no_proxy": "host.docker.internal"
  },
  "blockedHosts": ["api.anthropic.com"]
}
```

`ANTHROPIC_BASE_URL` redirects the SDK to Ollama, the placeholder key satisfies it (Ollama ignores it), `NO_PROXY` bypasses the OneCLI credential gateway for the host hostname, and `blockedHosts` resolves `api.anthropic.com` to `0.0.0.0` so config drift can't silently bill your account. The model is set in the group's shared `settings.json` (`data/v2-sessions/<group-id>/.claude-shared/settings.json`) — Claude Code reads its model from there, not from env. Pick models that handle tool calls reliably; the upstream notes recommend Gemma 4 or Qwen 3 Coder over small 3B models.

## What changes on a non-Claude provider

Verified against the provider sources on the `providers` branch:

* **Slash commands aren't native.** OpenCode and Codex declare `supportsNativeSlashCommands = false`, so the poll loop formats slash commands as ordinary chat text instead of executing them.
* **No mid-turn input.** Both queue follow-up messages and drain them between turns; the Claude provider streams them into the live turn.
* **No `@-import` expansion.** Codex's app-server doesn't expand `@-import` directives, so the Codex provider composes its own `AGENTS.md` (group instructions plus skills, under a 32 KB cap) fresh on each spawn instead of leaning on the host's composed `CLAUDE.md`.
* **No transcript rotation.** Only the Claude provider implements `maybeRotateContinuation` — the guard against unresumable, oversized session transcripts.

## Provider capability hooks

A provider implementation declares a handful of optional capabilities. The runner and host read them by name and apply them generically — never branching on a hardcoded provider name — so each is opt-in and default-off: a provider that declares none behaves exactly as before the hooks existed. The two that shape the agent's environment:

* **Exchange archiving** (`onExchangeComplete`). The poll loop calls this after every completed exchange — a result, a wrapping retry, or an error — handing the provider a `ProviderExchange` (the prompt, the result, the continuation id, and a `completed` / `undelivered` / `error` status). A provider whose harness already keeps an on-disk transcript (the Claude Agent SDK writes `.jsonl`) omits it; one that keeps no transcript implements it to archive each round-trip itself, e.g. as markdown under the agent's `conversations/` dir. It's best-effort — the loop catches and logs anything it throws.

* **Owning the agent surfaces** (`providesAgentSurfaces`). By default the host composes the project doc, skill-discovery links, and provider state dir, then mounts them into the container — the composed `CLAUDE.md`, the skill fragments, and the provider-neutral `instructions.prepend.md` staging. A provider can declare at registration time that it owns these surfaces; the host then skips all of that, and the provider's own config fn composes and mounts its equivalents (it receives the group dir and the resolved skill list to do so).

[Persistent memory](/concepts/agent-memory) is deliberately **not** a hook (there is no `usesMemoryScaffold` flag): every provider shares one persistent `memory/` tree. The runner scaffolds it unconditionally at boot in the agent's host-backed workspace (`/workspace/agent`) — `memory/index.md` plus the `memory/system/` doctrine files — writing only what's missing, so the agent's own edits and accumulated memory are never clobbered. Because the tree lives in the group dir, it persists across container restarts. What each provider *must* implement is `registerMemorySessionHook`, the interface method that wires the shared tree into that provider's own harness.

## Choosing a provider during setup

[Setup](/quickstart) asks which agent runtime should power your assistant. Claude is the default — press Enter and the standard Claude auth runs. **Codex** is offered as an inline install: pick it and setup runs its installer, rebuilds the image, and walks its vault-only auth (a ChatGPT subscription or an OpenAI API key). OpenCode and Ollama aren't in the picker; add them after setup with their `/add-*` skills.

The pick is **not** an install-wide default — it sets the provider on the first agent group as a per-group database property. Every group's provider is independent, and a spawned subagent inherits its creator's provider so a single-provider install never spawns a child on a runtime it can't reach.

## Switching a group

Set the provider (and optionally model/effort) on the group's container config, then restart:

```bash theme={null}
ncl groups config update --id <group-id> --provider opencode --model deepseek/deepseek-chat
ncl groups restart --id <group-id>
```

Config changes are saved immediately but only take effect on the next container spawn — the restart forces it. Switching back is `--provider claude` (or clearing the field; the fallback is `claude` either way). To back the code out entirely, OpenCode and Codex each ship a `REMOVE.md`; Ollama needs no removal — its SKILL.md's "Reverting to Claude" steps are just deleting the `env` and `blockedHosts` keys from the container config and the `model` setting from the shared settings file.

### What carries across a switch

Provider-neutral state stays put: group identity, wiring, members, roles, destinations, container config, skills, MCP servers, mounts, workspace files (`groups/<folder>/`), and conversation archives (`conversations/`). Agent surfaces are composed fresh at every spawn, so nothing migrates there.

**Agent memory carries too.** The `memory/` tree is universal and provider-neutral — it lives in the group's host-backed workspace, and every provider scaffolds and reads the same tree — so the new provider picks up the memory where the old one left off. Standing instructions (`instructions.prepend.md`) are equally provider-neutral. (`/migrate-memory` still exists, but it's a one-time cutover for legacy v1-era stores — `CLAUDE.md` / `CLAUDE.local.md` / `.seed.md` — into the tree, not part of a provider switch.)

One thing is provider-specific and **does not** carry across: **in-flight conversation context**. Continuations are per-provider, so the new provider starts a fresh thread; the old slot is kept, so switching back resumes where you left off. Recent context is recoverable from the `conversations/` archives.

Switching to a provider that isn't installed (or a typo'd name) fails the container at boot — the host logs `Container exited non-zero` with a `stderrTail` like `Unknown provider: codexx. Registered: claude, codex`.

## Next steps

<CardGroup cols={2}>
  <Card title="Skills catalog" icon="list" href="/reference/skills-catalog#provider-installs">
    The three provider-install skills at a glance
  </Card>

  <Card title="Container config" icon="box" href="/reference/container-config#provider">
    The provider, model, and effort fields in full
  </Card>

  <Card title="Credentials" icon="key" href="/operate/credentials">
    How provider API keys stay out of containers
  </Card>

  <Card title="Give agents tools" icon="screwdriver-wrench" href="/extend/tools">
    Keep the same brain and add capabilities instead
  </Card>
</CardGroup>
