Here's my approach. A coding agent runs a lot of different tasks: implementation, search, review, test-writing, plan critique. Some of those tasks see private data, some are cheap and disposable. A single-model agent sends them all through the same brain, which wastes expensive context on work that doesn't need it and serializes everything that could run in parallel. In opencode, a model is just a provider/model string, so mapping tasks to models is a config edit, not a platform decision. This is what that mapping looks like in my setup.
How It Got Here
It started as hub-and-spoke on one machine: a big local model for reasoning, a small local one for delegation. It worked, but every delegated task competed with the main session for RAM. Then Ollama moved to a dedicated machine on the LAN, running one permanently-loaded model: qwen3.8:27b-mtp-bf16 at 262K context, keep_alive=-1. The hub is always warm and nothing competes with it.
The last change came from a detail: Ollama doubles as a gateway to cloud models. ollama pull <name>:cloud registers a cloud route with no weights and zero local RAM, and the model shows up in opencode exactly like a local one. Once that existed, the answer to "which model runs the grep?" was obvious: not the one that costs 56GB to keep warm. Everything except the main reasoning thread moved to the cloud. A quantized fallback model was on the list for a day, then deleted: if it's not local bf16, it's cloud.
The Routing Table
The whole policy is one default model plus three agent pins in opencode.json:
"model": "ollama/qwen3.8:27b-mtp-bf16" // default = the local hub
"agent": {
"review": { "model": "ollama/kimi-k2.7-code:cloud" },
"explore": { "model": "ollama/kimi-k2.7-code:cloud" },
"general": { "model": "ollama/kimi-k2.7-code:cloud" }
}
flowchart TB
subgraph OC["opencode session"]
direction TB
MAIN["main session
default model"]
PLAN["plan mode"]
AGENTS["review · explore · general
subagents (pinned)"]
DELEGATE["parallel multi-task
delegation"]
CONSENSUS["consensus panel
('consensus check')"]
SECOND["second opinion
/ vision"]
EDIT["all implementation,
edits, refactors"]
MAIN --- EDIT
end
subgraph LOCAL["Local hub - Ollama box (56GB resident) - private work"]
HUB["qwen3.8:27b-mtp-bf16
ONLY local model
262K ctx - keep_alive=-1"]
end
subgraph CLOUD["Cloud auxiliaries - beginner-tier tasks, zero local RAM"]
KIMI["kimi-k2.7-code:cloud (Moonshot)
review - search - tests - docs"]
NEMO["nemotron-3-super:cloud (NVIDIA)
critique (R1)"]
MINI["minimax-m2.7:cloud (MiniMax)
verdict (R3)"]
GLM["glm-5.3:cloud (Zhipu)
second opinion - vision"]
end
MAIN ==> HUB
PLAN ==> HUB
EDIT ==> HUB
AGENTS ==>|"pinned in config"| KIMI
DELEGATE ==>|"one spoke per task"| KIMI
CONSENSUS ==> R1
SECOND ==> GLM
subgraph PANEL["consensus rounds (fully cloud)"]
direction LR
R1["R1 critique
nemotron"] ==>|"2"| R2["R2 review
kimi"] ==>|"3"| R3["R3 verdict
minimax"]
end
R1 -.-> NEMO
R2 -.-> KIMI
R3 -.-> MINI
classDef local fill:#12291a,stroke:#3ecf6e,color:#d4d4e8;
classDef cloud fill:#0e2238,stroke:#58a6ff,color:#d4d4e8;
classDef core fill:#2b2110,stroke:#e8a020,color:#d4d4e8;
class HUB local;
class KIMI,NEMO,MINI,GLM cloud;
class MAIN,PLAN,AGENTS,DELEGATE,CONSENSUS,SECOND,R1,R2,R3 core;
The Rule: Private Work Stays Local
The split is about privacy more than capacity. Email bodies, meeting notes, drafts, and company code never leave the machine. What goes to the cloud is work that is not sensitive in content and beginner-tier in difficulty: reviewing a diff for style, searching a repo, scaffolding tests. Cost optimization and privacy optimization read the same in the config, but only one of them answers the audit question.
The Consensus Panel
Before committing to a risky plan or diff, typing consensus check runs three rounds, each from a different vendor: nemotron-3-super (NVIDIA) for a terse list of flaws, kimi-k2.7-code (Moonshot) for the deep pass, minimax-m2.7 (MiniMax) for the final verdict. glm-5.3 (Zhipu) handles second opinions and vision elsewhere:
- R1 (nemotron): "List every flaw, gap, or risk. Be terse and ruthless."
- R2 (kimi): "Given that critique, what would you change? Focus on correctness and edge cases."
- R3 (minimax): "Given rounds 1 and 2, approve, reject, or give a revised plan. Be decisive."
Different vendors make different mistakes; three models from one family mostly repeat the same opinion. And because every round runs in the cloud, the panel works while the local hub keeps building the code it's reviewing.
Claude Code Can't Do This
Claude Code is single-provider: every model call resolves inside Anthropic's family. There is no addressable local model and no third-party vendor to route to, so there is no routing table and no local target for a privacy policy. Every call leaves the machine for Anthropic's infrastructure, whatever the content.
What it does have, scattered across a few places:
~/.claude/settings.json→modelSettings: per-modeleffortLevel. (My install listsclaude-sonnet-5and oddlyqwen3.8:27b-mtp-bf16, which Claude Code won't call -- a leftover from an experiment, unused.)/modelor the"model"key: the one model for the main session. A selector, not a table.- Frontmatter in
.claude/agents/*.md: each custom subagent can pin a tier (sonnet / opus / haiku). The closest thing to routing -- different subagents, different model sizes, still one family. I have no~/.claude/agents/dir yet, so no pins exist. - The
modelparam on the Agent tool call: a one-off override per spawn, not a durable policy.
The honest summary: main session → one Claude model, subagents → optionally a different Claude tier via frontmatter. That's the whole topology. You cannot put private work on a local model, run a panel across three vendors, or parallelize subagents without competing for the same GPU. It is a tier selector inside one provider, not a routing table.
Before That: Two Fleets
I worked around the single-provider limit the only way available: multiple separate instances of Claude Code. One fleet on Anthropic models, another pointed at my Ollama box, with private work told to stay on the Ollama side. The model boundary was which terminal I launched, and I was the router.
The privacy line existed from day one, and the fleets ran in parallel. But the dispatch decision lived in my head with no policy behind it, one mis-dispatch put private content on the wrong side, the fleets didn't share memory or skills, and nothing cross-fleet could be expressed at all. opencode made the table a file, and the second fleet got retired.
What I'd Keep
- Treat the model as a string. Once every model is addressable the same way, routing is config, not architecture.
- One always-warm local model is a standing worker, not a per-request resource. Everything else becomes offloadable.
- Route by privacy, not just cost. The routing line is also the data boundary.