# OpenCode Desktop: Build a Low-Cost or Best-Experience Multi-Model Coding Harness

Claude Code is a good example of what a coding harness can be. Its advantage is not only the model. It is the system around it: tools, context management, permissions, recovery, and feedback loops. The limitation is that all of this lives inside **one product ecosystem**. You inherit its model menu, routing decisions, limits, and roadmap. Other vendors ship comparable harnesses; I have not used them enough to judge them, so this article does not.

Meanwhile the model market has stopped being a one-vendor story. Open-weight models from several labs are now good enough for a large share of everyday coding work, at a fraction of frontier prices, and they keep improving. A harness that can only use one vendor's menu cannot take advantage of that.

This guide takes a different trade-off: keep a good desktop harness experience, but pick the models from across the market instead of from one provider's subset. Each request goes to a suitable model, and a failed provider does not end the task. By the end you will have **OpenCode Desktop** configured with one of two packages.

**These are packages, not lists of models.** Each one is a set of models chosen to work together as of November 2026: an orchestrator that actually delegates, tiers sized for their jobs, and backups that can read the same inputs and hold the same output. Swapping one model can quietly break that fit, so section 3 explains the rules I used to choose them, and you can apply them when the market moves. It will move: treat the model names as today's answer and the selection rules as the lasting part.

**Cheap** uses open-weight models from three labs (GLM, DeepSeek, and Qwen), through two providers: DeepSeek directly, and OpenRouter. You pay per token, at a fraction of Best's price.

![The Cheap package, with DeepSeek direct and OpenRouter as columns. GLM-5.3-Flash on OpenRouter orchestrates, with DeepSeek Flash as its backup. DeepSeek Flash from DeepSeek directly is the fast tier, with DeepSeek V4.1 Flash on OpenRouter as backup. Qwen 3.6 and Qwen 3.8 on OpenRouter are the medium and heavy tiers, each with DeepSeek Flash as the first backup and GLM-5.3-Flash as the second](https://cdn.hashnode.com/uploads/gql/6a92730f9a9aa7f72e74fdf4/2d8def52-2abb-4f39-8c92-bd61c186b84b.png)

*Cheap: which provider serves each model. Filled boxes run first; dashed boxes are the backups the harness switches to, in numbered order.*

**Best Experience** uses frontier models for each tier. Today those are Claude models, called directly from Anthropic, with the same models on OpenRouter as the backup. If another vendor's models overtake them, the package changes, not the harness.

![The Best Experience package, with Anthropic direct and OpenRouter as columns. Claude Sonnet 5.5 orchestrates, and Haiku, Sonnet and Opus 5.5 are the fast, medium and heavy tiers, all from Anthropic directly, each with the same Claude model on OpenRouter as its backup](https://cdn.hashnode.com/uploads/gql/6a92730f9a9aa7f72e74fdf4/7c4714bb-059a-4084-8f8e-e6a970323f84.png)

*Best Experience: each tier on Anthropic directly, with the same model on OpenRouter as the backup.*

**There is no 5-hour window.** Claude's Pro and Max subscriptions have a five-hour session limit and a weekly limit. Both packages here pay per token through APIs instead, and none of the three providers uses a session or weekly window. That does not mean there are no limits. They are different ones, covered in section 11: per-minute rate limits, a monthly spend cap on Anthropic's API, your prepaid balance, and concurrency limits on DeepSeek. Failover covers a per-minute 429. Nothing covers an empty balance.

## What held up

I tested both setups in the Desktop app, including deliberate provider failures. This is a working harness with known gaps, not a finished one.

| | Best | Cheap |
|---|---|---|
| Images and PDFs | Yes | Yes, with PDFs converted locally first |
| Routing work to the right tier | Yes | Yes, once I added a rule and made GLM the orchestrator |
| Surviving a 429, a 503 from a test provider (Best only), or a stalled provider | Yes | Yes |
| Project memory across sessions | Yes | Yes |
| Compaction keeping the objective and next action | Yes | Yes |

Three things went differently from what I planned:

- **Free models did not work as a foundation.** My first Cheap setup used OpenCode's free models. They returned a free-usage error after very little use, so I replaced them with low-cost paid models.
- **Semantic memory did not make the cut.** It worked when the agent had the tool, but the tool was not reliably there. I dropped it.
- **The first Cheap orchestrator ignored routing.** Section 7 explains why, and the fix.

Section 12 has the results, and the appendix has a receipt for every test: what it checks, the exact scenario, and what counted as a pass. Model IDs and prices were checked on November 5, 2026, so treat them as a dated snapshot.

## 1. What makes a coding harness good

A model is one component. A dependable harness has to preserve the task, the workspace, and your control when the model or provider misbehaves.

| Theme | What good looks like |
|---|---|
| Context and memory | Resume exact work; project facts survive sessions and model changes |
| Tools and permissions | The agent has only the authority and data exposure you accept |
| Recovery | A bad turn can be reversed without hiding side effects |
| Verification | A fluent answer is not accepted as working code |
| Routing and failover | The right model per task; the task survives a provider failure |
| Observability and cost | You can see what ran, what it cost, and where it failed |

Two features are often confused:

- **Routing** chooses who should handle a task.
- **Failover** keeps the current task alive when the chosen model cannot complete a request.

Adding several models to a picker provides neither. I have covered [what LLM routing is](https://nightthoughts.me/what-is-llm-routing-a-simple-guide-to-saving-money-on-ai), [building a router by hand](https://nightthoughts.me/how-to-build-an-intelligent-router-for-nvidia-and-ollama-with-litellm-jev-and-memori-on-a-mac-mini-part-2), and [why the harness can matter as much as the model](https://nightthoughts.me/the-harness-is-the-agent-inside-google-s-harness) in earlier posts, so I will not repeat them here.

## 2. Why OpenCode

OpenCode gives you the parts that stay the same whichever model you use: sessions, attachments, ordered permission rules, compaction, snapshots, and many providers behind one interface.

Routing and failover come from two third-party plugins:

- `opencode-model-router@2.9.1` for tier-based routing.
- `opencode-runtime-fallback@0.3.1` for retries, timeouts, and failover.

So you are trusting third-party plugins for the two features this article cares most about. They held up in my tests, but pin the versions and retest when you upgrade.

## 3. How I chose the models

You can skip this section and use my choices. Read it if you want to swap a model, because a swap can quietly break a chain.

The models in each package were picked as a set, not one by one:

- **The orchestrator has to delegate.** On Cheap, GLM-5.3-Flash is the orchestrator because it was the model that sent work to the right tier in my tests. Qwen 3.6 and DeepSeek Flash, run as orchestrators, did not delegate reliably (section 7).
- **Each tier has a job.** Fast is read-only exploration, medium is implementation, heavy is architecture and review. The router sends work by tier, so a tier's model needs the skills for that job, not the best general score.
- **Each backup can stand in for its primary.** It has to pass the hard filters below against the model it replaces. GLM's output cap in the Cheap config exists only so that DeepSeek passes that check.
- **Routes are spread out.** On Cheap, the fast tier runs on DeepSeek directly and the other tiers on OpenRouter, and each chain includes a model on the other route. A problem with one account or route then does not take down every tier.

### Hard filters

A candidate that fails any of these is out, as a primary or a backup.

| Filter | Why it matters |
|---|---|
| Context window at least the primary's | A smaller window truncates or forces earlier compaction mid-task |
| Output limit at least the primary's | Long edits and reasoning can be cut off |
| Same input types (image, PDF policy) | A backup that cannot read the attachment is a different workflow |
| Tool calling that works in this harness | Advertised is not the same as reliable |
| Reasoning support | Needed for the tier's job |
| Weight and data policy | Open-weight on Cheap; terms acceptable for your material |
| First backup on a different hosted route | A backup on the same path fails along with the primary |

Two examples of the filters doing their job:

- **Rejected on capability.** `deepseek-v4-pro` costs more than `deepseek-flash` but takes no image input, so it is out. `deepseek-flash` lists images, and it read one correctly in my failover test.
- **Passed, but weak on independence.** Claude through OpenRouter passes every capability filter against Claude from Anthropic. But OpenRouter served my pings through Claude Platform on AWS, which Anthropic operates, so an Anthropic-side outage may reach both routes. This backup protects against account limits, rate limits, and route failures. It may not protect against a vendor-wide outage.

### Soft ranking

Among the models that pass:

- **Fallback similarity.** Capability-compatible does not mean quality-equivalent. On Best the backup is the same model. On Cheap it is not, and I have not measured how much weaker a backup is on real tasks.
- **Caching.** Prices alone mislead. `deepseek-flash` used cached tokens in my runs. Qwen 3.6 through OpenRouter showed none, so its whole prompt was billed at the input price every time.
- **Switch cost.** See section 11 for the measured number.

## 4. Choose a setup

| | Cheap | Best Experience |
|---|---|---|
| Orchestrator | GLM-5.3-Flash | Claude Sonnet 5.5 |
| Fast tier | DeepSeek Flash | Claude Haiku 5.5 |
| Medium tier | Qwen 3.6 | Claude Sonnet 5.5 |
| Heavy tier | Qwen 3.8 | Claude Opus 5.5 |
| Accounts | OpenRouter, DeepSeek | Anthropic, OpenRouter |
| PDFs | Convert locally | Native |
| Illustrative cost per work unit (section 11) | About $3.49 | About $19.25 |
| Main trade-off | Weaker evidence on real coding quality, and it needs a routing rule | Costs about five times as much, and the backup may share an outage with the primary |

Choose Best if you want the option with the most evidence behind it. Choose Cheap if price matters more, and check its edits closely until you trust it on your own work.

## 5. Prerequisites

Most of my early failures came from this section, not from the harness. Every step below is a command you can paste into a terminal, on macOS or Linux.

**I tested on macOS only.** The Linux commands use the official Linux packages and the same config paths, but I have not run them on Linux.

### Accounts and credit

| Package | You need | Notes |
|---|---|---|
| Both | Spend limits, with auto-recharge off | Set them before the first call |
| Best | Anthropic **API credit**, on the organization that owns your key | Check the balance on the Console's billing page and compare organization IDs. Credit on another organization does not fund this key, and a Claude subscription is billed separately |
| Best | OpenRouter credit | OpenCode requested 128,000 output tokens on the Claude routes, and OpenRouter refused the call when the balance could not cover that, even for a one-word reply. For Opus 5.5 that is about $2.56, so keep more than that on the account |
| Cheap | OpenRouter credit | OpenCode requested 256,000 output tokens for GLM. At its output price that is about $0.13, so keep a few dollars there |
| Cheap | DeepSeek API credit | My balance was $0.00 and the account reported itself unavailable. I added $5, and testing used a few cents |

**Do not plan on OpenCode's free models as your main route.** With no OpenCode Console account, all three I tried returned `429 Free usage limit reached` after very little use, with a retry time of about 9.6 hours. The limit is not documented, so yours may differ.

### Tools

You need Git, Node.js 22 or newer (the router plugin requires it), and, for Cheap, `poppler` for the PDF steps in section 10.

macOS, with [Homebrew](https://brew.sh):

```bash
brew install git node poppler
```

Debian or Ubuntu:

```bash
sudo apt install git poppler-utils
```

Fedora:

```bash
sudo dnf install git poppler-utils
```

Distribution Node.js packages are often older than 22, so on Linux install it from [nodejs.org](https://nodejs.org/en/download) or with a version manager, then check:

```bash
node --version
```

### A real project folder

Undo needs a Git repository, so open OpenCode on a project that is one. Check the path shown under the project name on the new-session screen: mine once pointed at my whole home folder, which is not where you want an agent working.

## 6. Install

**1. Install OpenCode Desktop.** I tested 2.2.5. On macOS:

```bash
brew install --cask opencode-desktop
```

On Debian or Ubuntu (x64):

```bash
curl -fLo /tmp/opencode-desktop.deb https://opencode.ai/files/bin/2.2.5/opencode-desktop-linux-amd64.deb && sudo apt install /tmp/opencode-desktop.deb
```

On Fedora (x64):

```bash
curl -fLo /tmp/opencode-desktop.rpm https://opencode.ai/files/bin/2.2.5/opencode-desktop-linux-x86_64.rpm && sudo dnf install /tmp/opencode-desktop.rpm
```

The Homebrew cask installs the latest version and updates itself, so it may be newer than the one I tested. The Linux commands pin 2.2.5. Other downloads are on the [OpenCode download page](https://opencode.ai/download).

**2. Close OpenCode Desktop, and back up your config.** The next section overwrites files in `~/.config/opencode`, the config folder on both macOS and Linux. This copies the whole folder, if it exists, to a dated backup:

```bash
[ -d ~/.config/opencode ] && cp -R ~/.config/opencode ~/.config/opencode.backup-$(date +%Y%m%d-%H%M%S)
```

**3. Write the config** for one package, using the command in section 7.

**4. Open the Desktop and connect your providers.** Run **Connect an integration** from the command palette (`⌘⇧P` on macOS; on Linux probably `Ctrl+Shift+P`, which I have not checked) for each provider in your package: Anthropic and OpenRouter for Best, OpenRouter and DeepSeek for Cheap. Never put real keys in a config file or a Git repository.

In my own runs the keys came from environment variables, so I did not test this connect flow. An app started from the Dock or an app launcher may not see variables exported in your shell profile, which is why I recommend connecting instead.

**5. Quit and reopen the Desktop.** OpenCode installs the two plugins named in the config when it starts, in the background, so the first launch may not have them loaded yet. The second launch should.

**6. Switch on hidden models (Cheap).** The picker showed only 43 of OpenRouter's models. In the model picker, choose **Manage models** and enable `Qwen3.6 35B-A3B`, `Qwen3.8 27B`, `DeepSeek V4.1 Flash` and `GLM-5.3-Flash`.

## 7. Configure

Each package is four files in `~/.config/opencode/`, and each file has its own command below. Paste them one at a time, in order. Each command replaces any earlier version of its file.

First, if an `opencode.json` is there, for example one written by `opencode plugin add`, rename it so that only one config file is loaded:

```bash
[ -f ~/.config/opencode/opencode.json ] && mv ~/.config/opencode/opencode.json ~/.config/opencode/opencode.json.old
```

Both packages use the same permission rules, at the end of `opencode.jsonc`. The last matching rule wins. In testing, `git push` and `rm -rf` were refused with no prompt, and a `.env` read asked first, which is OpenCode's default. These rules are guardrails, **not an operating-system sandbox**. Use a container for untrusted code.

![A Permission required prompt for reading a .env file, with Deny, Allow always, and Allow once buttons](https://cdn.hashnode.com/uploads/gql/6a92730f9a9aa7f72e74fdf4/dc03d7e0-6bce-47de-bca6-5917c52bfc40.png)

*A `.env` read stops and asks, even though nothing in my rules mentions it.*

### Best Experience

**1. `opencode.jsonc`**, the main config: the orchestrator model, the plugins, compaction and the permission rules.

```bash
mkdir -p ~/.config/opencode && cat > ~/.config/opencode/opencode.jsonc <<'EOF'
{
  "$schema": "https://opencode.ai/config.json",

  "model": "anthropic/claude-sonnet-5-5",

  "plugins": [
    "opencode-model-router@2.9.1",
    "opencode-runtime-fallback@0.3.1"
  ],

  "default_agent": "build",

  "compaction": {
    "auto": true,
    "keep": { "tokens": 20000 },
    "buffer": "20%"
  },

  "agents": {
    "title": { "model": "anthropic/claude-haiku-5-5" }
  },

  // Ordered rules: the last matching rule wins.
  "permissions": [
    { "action": "shell", "resource": "*", "effect": "ask" },
    { "action": "shell", "resource": "git status *", "effect": "allow" },
    { "action": "shell", "resource": "git diff *", "effect": "allow" },
    { "action": "shell", "resource": "git push *", "effect": "deny" },
    { "action": "shell", "resource": "git reset --hard *", "effect": "deny" },
    { "action": "shell", "resource": "rm -rf *", "effect": "deny" }
  ]
}
EOF
```

**2. `opencode-model-router.overrides.jsonc`**, the router's tiers: which model handles fast, medium and heavy work.

```bash
cat > ~/.config/opencode/opencode-model-router.overrides.jsonc <<'EOF'
{
  "activePreset": "best-frontier",
  "activeMode": "normal",

  "presets": {
    "best-frontier": {
      "fast":   { "readOnly": true, "model": "anthropic/claude-haiku-5-5",
                  "costRatio": 1,  "steps": 30,  "description": "Exploration and bounded lookup" },
      "medium": { "model": "anthropic/claude-sonnet-5-5",
                  "costRatio": 20, "steps": 60,  "description": "Implementation and verification" },
      "heavy":  { "model": "anthropic/claude-opus-5-5",
                  "costRatio": 40, "steps": 100, "description": "Architecture and review" }
    }
  }
}
EOF
```

**3. `opencode-fallback.jsonc`**, the backup chain for each agent. Each tier falls back to the same Claude model on OpenRouter.

```bash
cat > ~/.config/opencode/opencode-fallback.jsonc <<'EOF'
{
  "timeout_seconds": 45,
  "notify_on_fallback": true,
  "agents": {
    "build":      { "fallback_models": ["openrouter/anthropic/claude-sonnet-5.5"] },
    "plan":       { "fallback_models": ["openrouter/anthropic/claude-sonnet-5.5"] },
    "fast":       { "fallback_models": ["openrouter/anthropic/claude-haiku-5.5"] },
    "medium":     { "fallback_models": ["openrouter/anthropic/claude-sonnet-5.5"] },
    "heavy":      { "fallback_models": ["openrouter/anthropic/claude-opus-5.5"] },
    "compaction": { "fallback_models": ["openrouter/anthropic/claude-sonnet-5.5"] }
  }
}
EOF
```

**4. `AGENTS.md`**, only if you used Cheap before. This removes Cheap's orchestrator rule from your global `AGENTS.md` and leaves the rest of the file alone.

```bash
[ -f ~/.config/opencode/AGENTS.md ] && perl -0pi -e 's/\n*## Orchestrator rule\n.*?(?=\n## |\z)/\n/s' ~/.config/opencode/AGENTS.md
```

Note the ID spelling: direct IDs use dashes (`5-5`), OpenRouter IDs use a dot (`5.5`).

### Cheap

**1. `opencode.jsonc`**, the main config: the orchestrator model, the plugins, compaction and the permission rules.

```bash
mkdir -p ~/.config/opencode && cat > ~/.config/opencode/opencode.jsonc <<'EOF'
{
  "$schema": "https://opencode.ai/config.json",

  // GLM orchestrates. Qwen and DeepSeek do the delegated work.
  "model": "openrouter/z-ai/glm-5.3-flash",

  "plugins": [
    "opencode-model-router@2.9.1",
    "opencode-runtime-fallback@0.3.1"
  ],

  // Capped so that deepseek/deepseek-flash meets the output floor as GLM's first backup.
  "provider": {
    "openrouter": {
      "models": {
        "z-ai/glm-5.3-flash": { "limit": { "context": 1048576, "output": 393216 } }
      }
    }
  },

  "default_agent": "build",

  "compaction": {
    "auto": true,
    "keep": { "tokens": 20000 },
    "buffer": "20%"
  },

  "agents": {
    "title": { "model": "openrouter/deepseek/deepseek-v4.1-flash" }
  },

  // Ordered rules: the last matching rule wins.
  "permissions": [
    { "action": "shell", "resource": "*", "effect": "ask" },
    { "action": "shell", "resource": "git status *", "effect": "allow" },
    { "action": "shell", "resource": "git diff *", "effect": "allow" },
    { "action": "shell", "resource": "git push *", "effect": "deny" },
    { "action": "shell", "resource": "git reset --hard *", "effect": "deny" },
    { "action": "shell", "resource": "rm -rf *", "effect": "deny" }
  ]
}
EOF
```

**2. `opencode-model-router.overrides.jsonc`**, the router's tiers: which model handles fast, medium and heavy work.

```bash
cat > ~/.config/opencode/opencode-model-router.overrides.jsonc <<'EOF'
{
  "activePreset": "cheap-open",
  "activeMode": "normal",

  "presets": {
    "cheap-open": {
      "fast":   { "readOnly": true, "model": "deepseek/deepseek-flash",
                  "costRatio": 1, "steps": 30,  "description": "Exploration and bounded lookup" },
      "medium": { "model": "openrouter/qwen/qwen3.6-35b-a3b",
                  "costRatio": 2, "steps": 60,  "description": "Implementation and verification" },
      "heavy":  { "model": "openrouter/qwen/qwen3.8-27b",
                  "costRatio": 4, "steps": 100, "description": "Architecture and review" }
    }
  }
}
EOF
```

**3. `opencode-fallback.jsonc`**, the backup chain for each agent.

```bash
cat > ~/.config/opencode/opencode-fallback.jsonc <<'EOF'
{
  "timeout_seconds": 45,
  "notify_on_fallback": true,
  "agents": {
    // build and plan run on the GLM orchestrator. No other low-cost model meets its context floor.
    "build":      { "fallback_models": ["deepseek/deepseek-flash"] },
    "plan":       { "fallback_models": ["deepseek/deepseek-flash"] },
    "fast":       { "fallback_models": ["openrouter/deepseek/deepseek-v4.1-flash"] },
    "medium":     { "fallback_models": ["deepseek/deepseek-flash", "openrouter/z-ai/glm-5.3-flash"] },
    "heavy":      { "fallback_models": ["deepseek/deepseek-flash", "openrouter/z-ai/glm-5.3-flash"] },
    "compaction": { "fallback_models": ["deepseek/deepseek-flash"] }
  }
}
EOF
```

**4. `AGENTS.md`**, the orchestrator rule. **Do not skip it on Cheap.** This adds the rule to your global `AGENTS.md`, keeps anything already in that file, and does nothing if the rule is already there.

```bash
grep -qs "^## Orchestrator rule" ~/.config/opencode/AGENTS.md || cat >> ~/.config/opencode/AGENTS.md <<'EOF'

## Orchestrator rule

You are a routing orchestrator. Your job is to dispatch work, not to do it.

- Reading and searching are execution. Send them to `subagent(agent="fast", ...)`. You may make at most 2 direct read-only tool calls per turn.
- Send implementation, edits, tests and commands to `subagent(agent="medium", ...)`. Do not edit files yourself.
- Send architecture decisions and trade-off analysis to `subagent(agent="heavy", ...)`, even when you could answer directly.
- Then combine the results and answer the user.
EOF
```

Two details in these files:

- **GLM's output cap** in `opencode.jsonc` makes DeepSeek a valid backup for GLM under the output-limit filter. It does not save money: OpenCode requested 256,000 tokens anyway.
- **The picker labels** `deepseek/deepseek-flash` as **DeepSeek V4.1 Flash 1017**.

**Why the rule is there.** In my first Cheap run, Qwen 3.6 did everything itself and never delegated. It had not missed anything: when I captured what OpenCode sends, a plain-named model and a `claude-`-named model both received the router's protocol, marked mandatory, and the same `subagent` tool. The router only adds a forceful "dispatch, do not execute" opener for models with Claude names, so other models fall back on their habits.

With the rule, the orchestrators compared like this:

| Orchestrator | Exploration | Edit | Architecture |
|---|---|---|---|
| GLM-5.3-Flash | To DeepSeek | To Qwen 3.6 | To Qwen 3.8 |
| Qwen 3.6 | Mixed | To a subagent | Mixed |
| DeepSeek Flash | Subagent errors, then did it itself | To Qwen 3.6 | Stopped at a permission prompt |

That is why GLM orchestrates, with only one or two runs behind each cell. GLM has orchestrated in my tests but has not made edits. The rule also worked from the global file, tested in a project with no rule of its own.

![A GLM-5.3-Flash session where a Medium subagent has finished an edit, and all 9 tests pass](https://cdn.hashnode.com/uploads/gql/6a92730f9a9aa7f72e74fdf4/78b89516-04e5-4a64-a3fa-b41aaf5bb058.png)

*GLM hands the edit to the Medium tier, then runs the tests itself to confirm the result.*

### Switching packages and the model picker

To switch packages, quit the Desktop, run the other package's four commands, and reopen it. The router's preset in the override file is only a default, and runtime choices made with `/preset` or `/budget` take precedence over it.

**A new session may not use the model in your config.** A brand-new profile opened on the configured model, but after that the Desktop opened new sessions on the last model I had used, even with a different `model` set. Check the picker at the start of a session, especially after switching: this is how a Cheap session could end up on a paid Claude model.

## 8. Check that it works

A connected provider shows that a key exists. It does not show that the key works. Four of my providers failed on their first real call: one had no credit, one could not cover the requested output tokens, one had a Free Tier quota of 0, and one rejected the token type.

**Do this once. It costs a few cents on Cheap, and should stay well under a dollar on Best.**

**1. Find the OpenCode CLI.** The Desktop ships its own CLI, which may not be on your `PATH`. On macOS:

```bash
OC=/Applications/OpenCode.app/Contents/Resources/opencode-cli
```

On Linux, find it in the installed files:

```bash
OC=$(find /opt /usr/lib /usr/share -name opencode-cli -type f 2>/dev/null | head -n1); echo "$OC"
```

If that prints nothing, install the standalone CLI with `curl -fsSL https://opencode.ai/v2/install | bash` and use `OC=opencode`. I have not checked whether the standalone CLI sees the Desktop's plugins.

**2. Confirm both plugins are loaded,** with the Desktop open:

```bash
"$OC" plugin list
```

Both `opencode-model-router` and `opencode-runtime-fallback` should appear. Right after a change the list can be briefly incomplete, so run it again after a few seconds. Do not add `--standalone`, which showed no plugins at all in my runs. If a plugin is still missing after a restart, add it by hand with `"$OC" plugin add opencode-model-router@2.9.1` or `"$OC" plugin add opencode-runtime-fallback@0.3.1`, which is how I installed them, then run the section 7 command again, since `plugin add` writes an `opencode.json`.

**3. Turn fallback off for the check.** This keeps your fallback file and puts a disabled one in its place:

```bash
mv ~/.config/opencode/opencode-fallback.jsonc ~/.config/opencode/opencode-fallback.jsonc.on && echo '{ "enabled": false }' > ~/.config/opencode/opencode-fallback.jsonc
```

**This step matters.** With fallback on, a failed route can be rescued by a backup and still look like a success. A free model of mine failed, a paid backup replied, and the screen showed `pong` as if nothing had happened. The only visible clue was a $0.03 charge.

**4. Ping every model.** Quit and reopen the Desktop. In a new session, pick each model in your package in turn and send `Reply with exactly one word: pong`. Each one should answer `pong` itself, with no fallback notice.

**5. Turn fallback back on,** then quit and reopen the Desktop:

```bash
mv ~/.config/opencode/opencode-fallback.jsonc.on ~/.config/opencode/opencode-fallback.jsonc
```

**6. Run `/tiers`** in a session to confirm the preset and models. It goes through the model, so it costs a little.

![/tiers output in OpenCode Desktop showing the best-frontier preset, with Haiku on fast, Sonnet on medium, and Opus on heavy](https://cdn.hashnode.com/uploads/gql/6a92730f9a9aa7f72e74fdf4/c653f8fb-c3e7-4aeb-b2b4-bbdeea9bad80.png)

*`/tiers` on the Best setup. Check the preset name and the three model IDs.*

## 9. Daily use

**Routing.** Give the task and let the orchestrator dispatch it. Small lookups stay with the orchestrator by design, which allows about 2 direct read-only calls per turn, so seeing no delegation on a tiny question is normal. Useful commands are `/tiers`, `/router`, `/budget budget|normal|quality|deep`, and `/bypass`. To see what actually ran:

```bash
"$OC" stats --days 0 --models --cost
```

`$OC` is the CLI path from section 8.

**Failover.** You should not need to do anything. In testing, the backup continued in the same session after a 429 and kept the completed tool result, and a stalled provider was cut off at the timeout. It preserves history, not side effects, so after a failover check the active model, then `git status` and `git diff`.

![A session where a test provider returns a rate limit error, OpenCode switches to Claude Haiku 5.5, and the backup continues the same task](https://cdn.hashnode.com/uploads/gql/6a92730f9a9aa7f72e74fdf4/f41cff03-07e3-4a72-8d1e-7930000f121a.png)

*A deliberate failure from my test provider, which also wrote the wrong text on purpose. The backup takes over in the same session, sees the earlier write, and corrects it.*

**Undo.** `/undo` and `/redo` are not slash commands in this Desktop build. Use **Rewind to here** on a message, or **Undo** and **Redo** in the command palette (`⌘⇧P` on macOS). Rewinding removed the file and put my prompt back in the composer. Snapshots do not cover ignored files, untracked files over 2 MiB, files outside the project, or side effects such as commits and deployments.

**Sessions and memory.** [Memory is a large topic of its own](https://nightthoughts.me/memory-for-ai-agents-architectures-tools-and-how-to-choose-the-right-one). For this setup, keep stable facts in a short `AGENTS.md`: a fresh session returned a fact stored only there, with no tool calls. Resume a session for the same task, and start fresh at real task boundaries. Compaction is lossy, so after it ask the model for the objective, decisions, and next action, and check the answer. Both setups kept all three in my test.

**Subagents.** Ask for bounds explicitly, for example "use at most two subagents, neither may edit, neither may launch further agents". Both setups respected this in one run each. The router's depth guard only warns by default, so `/router enforce enforced` is the setting if you want a hard stop. I did not test that setting. Denying a permission inside a subagent cancelled that subagent in my run, and the orchestrator then carried on by itself.

## 10. Images and PDFs

**Images** can be attached directly on both setups. Each returned an exact code from a test image, and the backup route did too.

![An attached image containing a code, and Claude Haiku 5.5 replying with that exact code](https://cdn.hashnode.com/uploads/gql/6a92730f9a9aa7f72e74fdf4/32113327-9d1d-48f9-8a78-7a427b3f9d01.png)

*The check I used for images: a random code the model can only get by reading the attachment.*

**PDFs on Best.** Attach the PDF directly. With tools forbidden, both routes read it and returned the right code and page. One caveat: after a failover the answer was still correct, but the first request carried almost no PDF tokens and the model delegated to a subagent. So the PDF may not reach the backup natively. Check the answer if a failover happened mid-task.

**PDFs on Cheap.** These models do not advertise native PDF input, so convert first and attach the results:

```bash
pdftotext -layout requirements.pdf requirements.txt
pdftoppm -f 3 -singlefile -png -r 150 requirements.pdf requirements-page-3
```

The original stays local, but the extracted text and image still go to the hosted model.

## 11. What it costs

**Illustrative monthly cost.** One work unit is 20M input and 1M output tokens, split 70% fast, 25% medium, 5% heavy, with no caching or fallback. Prices per 1M tokens are from November 5, 2026.

| Tier | Best | Cheap |
|---|---|---|
| Fast | Haiku, $0.10 / $0.50 | DeepSeek Flash, $0.15 / $0.60 |
| Medium | Sonnet, $2 / $10 | Qwen 3.6, $0.10 / $0.90 |
| Heavy | Opus, $4 / $20 | Qwen 3.8, $0.15 / $1.875 |
| **Per work unit** | **$19.25** | **$3.49** |

This is a scenario, **not a prediction**. It leaves out Cheap's GLM orchestrator ($0.15 / $0.50), and Haiku costs five times as much above 100K tokens ($0.50 / $2.50). Caching, retries, and session length all move the real number. Measure your own after a week.

**What I measured:**

- **The router adds overhead.** The first request grew from about 8,600 tokens to about 14,900 with the plugins loaded.
- **Switching routes mid-session costs about 11 times a normal turn, once.** On Haiku, a turn went from $0.00015 to $0.0016 because 12,666 tokens had to be written to a new cache. The next turn was back to normal, and switching back stayed cheap while the first cache was warm. On Opus the same switch would cost more, in proportion to its price.
- **All my testing, including failed attempts, cost about $0.72** by OpenCode's figures. I have not finished reconciling that against the provider dashboards.

**Against subscriptions.** Claude Pro ($20 a month), Max ($100), and Ultra ($200) are priced by usage limits, not tokens, so they are not directly comparable. They can be better value when their limits fit your use; paying per token suits bursty work and provider choice. Check the [current pricing](https://claude.com/pricing).

### Limits: no session window, but not unlimited

On Pro and Max, usage is capped by a five-hour session limit and a weekly limit. Ultra has only the weekly limit. The two packages here have no such window. These are the limits that do apply, according to each provider's documentation on November 6, 2026:

| Provider | Limit | What happens |
|---|---|---|
| Anthropic API | Requests and tokens **per minute**, by usage tier, refilled continuously | `429` for a short time. The fallback plugin switches to the OpenRouter backup |
| Anthropic API | A **monthly spend cap** per tier, $500 on the Start tier. New organizations may start on a lower Evaluation tier | Requests stop until the first of the next month, or until you move up a tier. The backup on OpenRouter still works |
| Anthropic API | A spend limit you set yourself | Requests stop with a `400` until you raise it |
| OpenRouter | No platform cap on paid models, but the upstream provider can be rate-limited or full | `429` from the upstream. OpenRouter may retry another upstream; the fallback plugin also switches |
| OpenRouter | Your credit balance, and any limit set on the key | `402`, see section 5 |
| DeepSeek | **Concurrent requests** per account: 2,500 for `deepseek-flash` | `429` only when too many requests run at once |
| All | Your prepaid balance | Nothing works until you top up. A backup on the same account fails too |
| OpenCode free models | Undocumented free-usage limit | `429 Free usage limit reached`, about 9.6 hours in my case. Not used by either package |

So the claim holds in a narrow sense: you will not be locked out for five hours or a week by your plan. You can still be stopped by money, by a monthly cap, or briefly by a per-minute limit. My testing was far too small to reach any of these, so this table comes from the documentation, not from my runs.

## 12. What I tested

Testing is where most of the work in this article went, because a harness is only worth something if it holds when things go wrong. A model reply on screen proves little: in one of my early checks the screen showed the right answer while a different, paid model had produced it.

**How the tests were run:**

- **In the Desktop app**, version 2.2.5, driven by a script that clicked the same controls you would: the model picker, the attachment button, the composer, permission prompts and the command palette.
- **On a disposable Git repository** with one source file (`src/math.mjs`, containing an `add` function), one passing test, an `AGENTS.md` holding a made-up project codename, a `docs/DECISIONS.md`, and an untracked `.env` with a placeholder value. It has no remote, so a real push was impossible.
- **With a local mock provider for failures.** Real providers do not fail on request, so a small local server played a provider that returns `429` after one tool call, one that always returns `503`, and one that never sends a first token. The backup behind them was a real route.
- **With random codes for attachments.** Each image and PDF contained a code generated for the test, so a model could only answer correctly by reading the file.
- **Judged from session exports, not the screen.** Each pass was checked in OpenCode's export of the session: which model produced each step, which tools ran, tokens and cost.

The table below summarizes the results. The appendix has the receipt for each test.

| Test | Best | Cheap |
|---|---|---|
| Startup, `/tiers`, plugins | Pass | Pass |
| Image with a random code | Pass | Pass |
| PDF, code on page 2 | Pass | Pass |
| Routing | Pass | Pass with GLM and the rule; failed on my first setup |
| 429 after a completed tool call | Pass | Pass |
| No first token | Pass | Pass |
| Capability after failover | Partial, see section 10 | Pass (image and tools) |
| Memory: fresh session and resume | Pass | Pass |
| Compaction | Pass | Pass |
| Subagent bounds | Pass | Pass |
| Permissions | Pass | Not repeated: OpenCode applies these rules whichever model asks |
| Rewind and Redo | Pass | Not repeated: snapshots do not involve the model |

**What I did not prove:**

- **Real coding quality.** These are mostly small, deliberate tasks. I did not run a benchmark, so I cannot tell you how much weaker Cheap is on your work, or put a number on how far a backup falls short of its primary.
- **Repeatability.** Most results are one run. Routing in particular varied between runs.
- **Provider independence.** I did not test pinning OpenRouter to a specific upstream.
- **Cost reconciliation.** OpenCode's figures have not been fully checked against provider dashboards. The fallback plugin also documents that it cannot report a different model or cost for compaction.

**Semantic memory is out of scope.** I tried `opencode-supermemory` with a self-hosted server and a local model. When the agent had the tool, it worked. But the tool was often missing from the session, with per-project or global config, even after a full restart, and `plugin list` did not predict when. Without it, one model wrote its own `MEMORY.md` as a substitute. I would not rely on it yet, so this guide stays with `AGENTS.md` and sessions.

The configs, test scripts, and sanitized evidence are in the [companion experiment folder](https://github.com/hslimi/night-thoughts/tree/experiment/opencode-multi-model-harness/experiments/opencode-multi-model-harness).

## 13. Limits and privacy

- **A backup is not an SLA.** Best's backup may share an outage with the primary (section 3).
- **Failover spends money too**, and it can send your content to a different provider.
- **Third-party plugins** do the routing and failover. They are pinned here for that reason.
- **Privacy.** On either setup your code and attachments go to the providers in your chain, and through OpenRouter to its upstreams as well. I have not reviewed the data terms for every route here. Before you attach anything sensitive, read the terms for each provider in your chain, remove credentials and customer data, and remember that a failover can send the same content elsewhere.
- **Local-only work.** These setups are not for material that must not leave the machine.

## 14. Troubleshooting

| Symptom | Likely cause |
|---|---|
| Session runs on the wrong model | The picker kept the last model; check it at session start |
| Model is missing from the picker | Switch it on under **Manage models** |
| `402` asking for more credits, or fewer `max_tokens` | The balance cannot cover the output tokens OpenCode requests |
| `400` credit balance too low | No API credit on the organization that owns the key |
| `429 Free usage limit reached` | OpenCode's free-model limit; use the paid chains here |
| Cheap orchestrator does everything itself | `AGENTS.md` rule missing, or the wrong orchestrator selected |
| No delegation on a small question | Normal: lookups within the 2-call allowance stay direct |
| Router commands absent | Confirm Node 22+, the plural `plugins` key, the exact override filename, and a full restart |
| Fallback never engages | Plugin missing from `plugin list`, wrong file path, or that agent has no chain |
| Paid charge on a free or failed model | A backup replied. Check the session export and `opencode-fallback.log` |
| `/undo` not found | Use **Rewind to here**, or **Undo** in the command palette |
| `/router stats` prints help | It does not exist in 2.9.1; use `"$OC" stats --models --cost` |

## Final thoughts

Best gives you stronger models and more evidence, at about five times the illustrative cost. Cheap is usable, but it asks you to check more of its work and to accept that its quality on real tasks is unmeasured here.

The part I would keep either way is the habit this forced: prove each route, read what actually ran, and do not trust the screen alone. The moments that taught me most were the ones where something looked fine and was not.

## Appendix: test receipts

One receipt per test. Each one says what harness property it checks, how the test was set up, what I sent, what counted as a pass, and how I checked it. The prompts are quoted exactly. Session exports for every run are in the [companion experiment folder](https://github.com/hslimi/night-thoughts/tree/experiment/opencode-multi-model-harness/experiments/opencode-multi-model-harness), under `evidence/`.

"The test repository" is the disposable repository described in section 12. "The mock" is the local provider that fails on purpose.

### Test 1: Startup

| | |
|---|---|
| **Checks** | Both plugins load, and the router uses the intended preset and models |
| **Why** | If a plugin silently fails to load, routing and failover are missing, and nothing on screen says so |
| **Setup** | Package config in `~/.config/opencode/`, Desktop restarted |
| **Scenario** | Run `opencode-cli plugin list`, then send `/tiers` and `/router` in a new session |
| **Pass if** | Both plugins listed with their pinned versions; `/tiers` shows the package's preset (`best-frontier` or `cheap-open`) and the three tier models exactly as configured |
| **Checked in** | CLI output and the session export |
| **Result** | Best: pass. Cheap: pass |

### Test 2: Image input

| | |
|---|---|
| **Checks** | An attached image reaches the model as an image |
| **Why** | A model that does not receive the image may still give a confident answer |
| **Setup** | A PNG containing only a random code generated for the test |
| **Scenario** | Attach the image and send: "What exact code is shown in this image? Reply with only the code." On Cheap the prompt also said "Do not use tools." |
| **Pass if** | The reply contains the exact code. A guess cannot match it |
| **Checked in** | The session export, compared with the generated code |
| **Result** | Best (Haiku): pass. Cheap (Qwen 3.6): pass |

### Test 3: PDF input

| | |
|---|---|
| **Checks** | A PDF can be read, including where the content is |
| **Why** | Native PDF support differs between models, and Cheap's models do not advertise it |
| **Setup** | A multi-page PDF with a random review code on page 2 only. On Cheap, converted first with `pdftotext` and `pdftoppm`, as in section 10 |
| **Scenario** | Best: attach the PDF and send "This document has a review code. Reply with the exact code and the page number it is on." Cheap: attach the text and page image and send "The text and page image come from one document. Reply with the review code and which page it is on. Do not use tools." |
| **Pass if** | Exact code, and page 2 |
| **Checked in** | The session export; on Best, also the input token count, which shows whether the PDF was actually sent |
| **Result** | Best: pass, natively. Cheap: pass, after local conversion |

### Test 4: Routing

| | |
|---|---|
| **Checks** | The orchestrator sends each kind of work to the right tier |
| **Why** | This is the point of the router. Without it, every request runs on the orchestrator's model and price |
| **Setup** | The test repository, a new session on the package's orchestrator |
| **Scenario** | Three prompts in one session. Exploration on Cheap: "Search the src and test folders and summarise what each file does, with file and line references. Do not change anything." On Best, a one-file lookup ("Where is the add function defined, and which test covers it? Do not change anything."), then a larger exploration of a real repository's `experiments` folder, read only. Edit: "Add a subtract(a, b) function to src/math.mjs with a matching test in test/math.test.mjs, then run node --test and report the result." Architecture: "Architecture question, no edits: if this project grew to many math modules across several packages, compare a single shared package with one package per module, considering versioning and test isolation. Recommend one, briefly." Only the `node --test` prompt was approved; any other permission request stopped the test |
| **Pass if** | Exploration runs on the fast model, the edit on the medium model, and architecture on the heavy model, each through a subagent; the tests pass after the edit |
| **Checked in** | The model that produced each step in the export, and `opencode-cli stats --models --cost` before and after each prompt |
| **Result** | Best: pass, with a small lookup kept by the orchestrator as the router intends. Cheap: **fail** with Qwen 3.6 as orchestrator, then pass with GLM-5.3-Flash and the `AGENTS.md` rule. One or two runs per setup |

### Test 5: Failover after a rate limit

| | |
|---|---|
| **Checks** | When a provider returns `429` mid-task, the backup continues the same task without repeating finished work |
| **Why** | Failing over at the start of a task is easy. Failing over after a tool has already changed a file is where duplicated or lost work happens |
| **Setup** | The mock as the primary, set to make one `write` tool call (creating `notes.txt`) and then return `429`. A real backup: Haiku on Best, DeepSeek Flash on Cheap |
| **Scenario** | "Create notes.txt with the text 'written once'. Then read src/math.mjs and tell me the name of the first exported function." |
| **Pass if** | The backup continues in the **same session** (no new session created); it works on the existing `notes.txt` rather than writing it again; it reads `src/math.mjs`; the export records the switch to the backup model |
| **Checked in** | The export, the fallback plugin's log, the session count before and after, and the file on disk |
| **Result** | Best: pass. Cheap: pass. The mock deliberately wrote slightly different text, and both backups noticed and corrected it |

A second variant used a mock that always returns `503`, with "Reply with exactly one word: pong". Best failed over the same way.

### Test 6: Stalled provider

| | |
|---|---|
| **Checks** | A provider that accepts the request and never answers is cut off, and the backup answers |
| **Why** | A stall is worse than an error: without a timeout the session just hangs |
| **Setup** | The mock accepting the request and sending nothing. Fallback timeout lowered to 20 seconds for the test; the packages use 45 |
| **Scenario** | "Reply with exactly one word: pong" |
| **Pass if** | The backup replies `pong` in the same session, shortly after the timeout |
| **Checked in** | The export and the plugin log, which records the timeout |
| **Result** | Best: pass. Cheap: pass. 38 seconds in total on both |

### Test 7: Capabilities after failover

| | |
|---|---|
| **Checks** | Attachments and tools still work once the backup has taken over. On Best, this test used the OpenRouter backup |
| **Why** | Test 5 shows the backup can continue. This shows it can still read what the primary was given |
| **Setup** | The mock returning `503` as the primary, a backup on the other route, and **new** codes, different from tests 2 and 3, so an earlier answer could not be reused |
| **Scenario** | Image: attach it and send "What exact code is shown in this image? Then read src/math.mjs and name its first exported function." PDF (Best only): attach it and send "This document has a review code. Reply with the exact code and the page number it is on." |
| **Pass if** | The backup returns the exact new code and calls the `read` tool; for the PDF, the code and page 2, with the PDF delivered to the backup natively |
| **Checked in** | The export, including input token counts |
| **Result** | Cheap: pass. Best: **partial**. Image and tools passed. The PDF answer was correct, but the first request to the backup carried almost no PDF tokens and the model used a subagent to get the answer, so native delivery after a failover is not confirmed |

### Test 8: Memory across sessions

| | |
|---|---|
| **Checks** | Project facts reach a fresh session, and a resumed session keeps its own context |
| **Why** | These are the two kinds of memory this guide relies on, in place of a memory plugin |
| **Setup** | The test repository, whose `AGENTS.md` contains a made-up codename that appears nowhere else |
| **Scenario** | Fresh session: "What is this project's codename? Answer from what you already know, without using tools." Resume: in another session send "Our objective for this session is to rename add to sum, and the next action is updating the test. Do not do it yet. Just confirm in one sentence.", leave it, reopen it from the sidebar, and ask "What was our objective and the next action? Answer in one sentence." |
| **Pass if** | The codename comes back with **no tool calls**, so it came from `AGENTS.md` and not from reading files; the resumed session states both the objective and the next action, and no new session was created |
| **Checked in** | The export, including the tool list |
| **Result** | Best: pass. Cheap (GLM): pass |

### Test 9: Semantic memory

Out of scope, for the reasons in section 12. It is listed so the numbering matches the evidence files.

### Test 10: Compaction

| | |
|---|---|
| **Checks** | After the context is compacted, the task survives |
| **Why** | Compaction is lossy by design. The question is whether it keeps what matters |
| **Setup** | A session with an objective, a decision and a next action stated up front, followed by turns that read files |
| **Scenario** | "Our objective: add a multiply function to src/math.mjs. Decision: keep it dependency free, per docs/DECISIONS.md. Next action: write the test first. Do not edit anything. Confirm in one sentence." Then two file-reading prompts, then `/compact`, then "State the objective, the decision, and the next action. Do not use tools." |
| **Pass if** | The export shows that compaction ran, and the answer after it names all three: multiply, no dependencies, and test first |
| **Checked in** | The export, before and after compaction |
| **Result** | Best: pass. Cheap: pass |

### Test 11: Subagent bounds

| | |
|---|---|
| **Checks** | The orchestrator respects limits on how many subagents it starts and what they may do |
| **Why** | Unbounded subagents multiply cost, and nested ones are hard to follow |
| **Setup** | The test repository |
| **Scenario** | "Use at most two subagents in parallel: one to summarise src/math.mjs and one to summarise test/math.test.mjs. Neither may edit, and neither may launch further agents. Then combine the two summaries in two sentences." |
| **Pass if** | Exactly two subagents, no edit tools, no nested subagents, and a combined answer |
| **Checked in** | The tool calls in the export |
| **Result** | Best: pass. Cheap (GLM with the rule): pass. One run each |

### Test 12: Permissions

| | |
|---|---|
| **Checks** | The permission rules in section 7 allow, deny and ask as written |
| **Why** | These rules are what stands between the agent and a destructive command |
| **Setup** | The test repository, with the untracked `.env` and no remote |
| **Scenario** | Five prompts, each asking the agent to run one command exactly: `git status --short`, `git push --dry-run origin main`, `rm -rf build`, a read of `.env`, and `ls src`. Every prompt that appeared was answered **Deny** |
| **Pass if** | `git status` runs with no prompt; `git push` and `rm -rf` are refused with no prompt; the `.env` read and `ls` ask first; `HEAD` does not move |
| **Checked in** | Tool states in the export, and `git rev-parse HEAD` before and after |
| **Result** | Best: pass. Not repeated on Cheap: OpenCode applies these rules before a command runs, whichever model asked |

### Test 13: Rewind and Redo

| | |
|---|---|
| **Checks** | A bad turn can be undone and redone |
| **Why** | Recovery is what makes it safe to let an agent edit |
| **Setup** | The test repository with earlier edits committed, so only the new change is in play |
| **Scenario** | "Create the file scratch.txt containing exactly the word alpha. Nothing else." Then **Rewind to here** on that message, then **Redo** from the command palette |
| **Pass if** | After rewind the file is gone and the prompt is back in the composer; after redo the file is back |
| **Checked in** | The file on disk and `git status` after each step |
| **Result** | Best: pass. Not repeated on Cheap: snapshots do not involve the model |

### Test 14: Cost

| | |
|---|---|
| **Checks** | What the harness reports matches what providers bill, and what switching routes costs |
| **Why** | Failover changes where tokens go, and a route switch can lose the prompt cache |
| **Setup** | One session on Haiku |
| **Scenario** | Five one-word turns: two on Anthropic, two after switching to OpenRouter, one after switching back. "Reply with exactly one word: one", then "two", and so on |
| **Pass if** | Per-turn cost and cache tokens are recorded for each turn, and OpenCode's totals match the provider dashboards |
| **Checked in** | The export, then the dashboards |
| **Result** | Route-switch cost measured (section 11). **Dashboard comparison not finished** |

## References

- [Companion experiment folder](https://github.com/hslimi/night-thoughts/tree/experiment/opencode-multi-model-harness/experiments/opencode-multi-model-harness)
- [OpenCode Desktop downloads](https://opencode.ai/download)
- [OpenCode v2 models](https://opencode.ai/v2/docs/models/)
- [OpenCode v2 compaction](https://opencode.ai/v2/docs/compaction/)
- [OpenCode v2 permissions](https://opencode.ai/v2/docs/permissions/)
- [OpenCode v2 snapshots](https://opencode.ai/v2/docs/snapshots/)
- [OpenCode rules](https://opencode.ai/docs/rules)
- [opencode-model-router](https://github.com/marco-jardim/opencode-model-router)
- [opencode-runtime-fallback](https://github.com/youngbinkim0/opencode-fallback)
- [OpenRouter limits](https://openrouter.ai/docs/api/reference/limits)
- [Claude plans and pricing](https://claude.com/pricing)
- [models.dev catalog](https://models.dev)

