Skip to main content
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).
Don’t confuse providers with tools. A tool skill like /add-ollama-tool keeps Claude as the agent’s brain and gives it a local model to call; a provider skill replaces the brain for an entire group.

How the provider is resolved

At spawn time the host resolves the provider name with a fixed precedence (src/container-runner.ts):
  1. sessions.agent_provider — per-session override
  2. container_configs.provider — the group’s container config
  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, Codex’s per-session ~/.codex, OPENCODE_*/OPENAI_* passthrough). 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 capable provider: native slash-command handling, 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.

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. /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 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 codex.ts and codex-app-server.ts from the providers branch, wires the same barrels and guards, and installs the @openai/codex CLI in the Dockerfile (no SDK dependency — Codex is a binary). The provider spawns one codex app-server child process per query and speaks JSON-RPC over stdio, which gives it native session resume, streaming events, and MCP tools. Auth: run codex login on the host to use a ChatGPT subscription (the host copies auth.json into a per-session ~/.codex mount), or set OPENAI_API_KEY + CODEX_MODEL in .env. An experimental third path points OPENAI_BASE_URL at any OpenAI-compatible endpoint — Groq, Together, self-hosted vLLM.

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:
{
  "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.
  • CLAUDE.md imports are emulated. Codex’s app-server doesn’t expand @-import directives or auto-load CLAUDE.local.md, so the provider resolves both itself to reconstruct the composed system prompt.
  • No transcript rotation. Only the Claude provider implements maybeRotateContinuation — the guard against unresumable, oversized session transcripts.

Switching a group

Set the provider (and optionally model/effort) on the group’s container config, then restart:
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.

Next steps

Skills catalog

The three provider-install skills at a glance

Container config

The provider, model, and effort fields in full

Credentials

How provider API keys stay out of containers

Give agents tools

Keep Claude as the brain and add capabilities instead
Last modified on June 10, 2026