OS Harnesses: OpenCode

Goal Mode and Model Updates

Posted by Isaac on Tuesday, November 10, 2026

I dance between Harnesses, I do. I like the big players like Copilot, Claude and Antigravity CLI, but really the up and comers are the ones that catch my attention.

I lasted spoke on OpenCode in August of 2025. While I noted it was from sst.dev I didn’t really dig into what SST was (or why one would use it). At that time, I was comparing OpenCode to OllamaCode and Qwen3 Coder Plus (along with the much loved and now defunct Gemini CLI).

A lot has changed in a year and today I’m using OpenCode yet again, though this time it’s much more functional.

Setup

We can see the install steps from Opencode.ai. The easiest is just

$ curl -fsSL https://opencode.ai/v2/install | bash

Or just natively in NodeJS

$ npm install -g @opencode/cli

Let’s do the latter

isaac@isaac-G707:~/Workspaces/innersourcedemo2$ nvm use lts/jod
Now using node v22.23.2 (npm v10.9.8)
isaac@isaac-G707:~/Workspaces/innersourcedemo2$ npm install -g @opencode/cli

added 5 packages in 7s

If we fire it up and look at models with /models we can see there are a few free ones but at the bottom you also see two in Ollama

/img/2026-11-opencode-01.png

I know it was finding the local Ollama by looking locally

isaac@isaac-G707:~/Workspaces/innersourcedemo2$ ollama list
NAME              ID              SIZE      MODIFIED
medgemma1.5:4b    433252621ab1    3.3 GB    3 weeks ago
gemma4:12b        4eb23ef187e2    7.6 GB    2 months ago

Let’s add some models from an Ollama running locally

isaac@isaac-G707:~/Workspaces/innersourcedemo2$ vi ~/.config/opencode/opencode.json
isaac@isaac-G707:~/Workspaces/innersourcedemo2$ cat ~/.config/opencode/opencode.json
{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "ollama": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "Ollama (220)",
      "options": {
        "baseURL": "http://192.168.1.220:11434/v1"
      },
      "models": {
        "qwen3.5:latest": {
          "name": "qwen3.5"
        },
        "qwen3.8:27b": {
          "name": "qwen3.8 27b"
        },
        "gemma4:e4b": {
          "name": "Gemma4 e4b"
        },
        "gemma4:12b": {
          "name": "Gemma4 12b"
        },
        "ministral-3:8b": {
          "name": "Ministral-3 8b"
        },
        "granite4.1:8b": {
          "name": "Granite4.1 8b"
        },
        "gpt-oss:20b": {
          "name": "gpt-oss 20b"
        }
      }
    }
  }
}

Now I can see them all in the list

/img/2026-11-opencode-02.png

Here is where some of the new features come into to play with OpenCode (I’m running version 2.2.5)

We can use “goal” mode to run until completion

/img/2026-11-opencode-03.png

Let’s use /goal with a detailed goal for a full featured To-Do app

/img/2026-11-opencode-04.png

I’ll fire it up and it will go to work

/img/2026-11-opencode-05.png

It quickly moved past this error, but I scrolled up to show you - in Goal mode, if it hits a bump, it just moves on and keeps working

/img/2026-11-opencode-06.png

Without any inputs, it ran to completion (while I went upstairs and refreshed my coffee)

/img/2026-11-opencode-07.png

I did see an error, but I know it’s just a comment line in a Dockerfile not in the correct place

/img/2026-11-opencode-08.png

My next error looks suspiciously like a missing requirements.txt entry for sqlalchemy (used for db migrations)

/img/2026-11-opencode-09.png

I’m still sticking with local Qwen, but prompting with a hint - that I suspect we are missing an import and noting the container output:

/img/2026-11-opencode-10.png

Qwen concurred and added the proper import and suggested I try again

/img/2026-11-opencode-11.png

More errors - perhaps it didn’t test the app:

web-1 exited with code 1 (restarting)
web-1  | Traceback (most recent call last):
web-1  |   File "/app/app/app.py", line 42, in <module>
web-1  |     @app.route('/')
web-1  |      ^^^
web-1  | NameError: name 'app' is not defined
web-1 exited with code 1 (restarting)

web-1  | Traceback (most recent call last):
web-1  |   File "/app/app/app.py", line 42, in <module>
web-1  |     @app.route('/')
web-1  |      ^^^
web-1  | NameError: name 'app' is not defined
web-1 exited with code 1 (restarting)
web-1  | Traceback (most recent call last):
web-1  |   File "/app/app/app.py", line 42, in <module>
web-1  |     @app.route('/')
web-1  |      ^^^
web-1  | NameError: name 'app' is not defined
web-1 exited with code 1 (restarting)

Another fix

/img/2026-11-opencode-12.png

And another error

Container innersourcedemo2-web-1 Stopping
web-1  | Traceback (most recent call last):
web-1  |   File "/app/app/app.py", line 42, in <module>
web-1  |     app = create_app()  # Initialize Flask app instance at module level
web-1  |           ^^^^^^^^^^^^
web-1  |   File "/app/app/app.py", line 15, in create_app
web-1  |     db.init_app(app)
web-1  |   File "/usr/local/lib/python3.11/site-packages/flask_sqlalchemy/extension.py", line 374, in init_app
web-1  |     engines[key] = self._make_engine(key, options, app)
web-1  |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web-1  |   File "/usr/local/lib/python3.11/site-packages/flask_sqlalchemy/extension.py", line 665, in _make_engine
web-1  |     return sa.engine_from_config(options, prefix="")
web-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web-1  |   File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/create.py", line 832, in engine_from_config
web-1  |     return create_engine(url, **options)
web-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web-1  |   File "<sqlalchemy generated warned() wrapper for sqlalchemy.engine.create.create_engine>", line 2, in create_engine
web-1  |   File "/usr/local/lib/python3.11/site-packages/sqlalchemy/util/deprecations.py", line 281, in warned
web-1  |     return fn(*args, **kwargs)  # type: ignore[no-any-return]
web-1  |            ^^^^^^^^^^^^^^^^^^^
web-1  |   File "/usr/local/lib/python3.11/site-packages/sqlalchemy/engine/create.py", line 602, in create_engine
web-1  |     dbapi = dbapi_meth(**dbapi_args)
web-1  |             ^^^^^^^^^^^^^^^^^^^^^^^^
web-1  |   File "/usr/local/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/psycopg.py", line 497, in import_dbapi
web-1  |     import psycopg
web-1  | ModuleNotFoundError: No module named 'psycopg'
```A

I'm going to try `/goal` mode again here to say work until the `docker compose up` works

[![/img/2026-11-opencode-13.png](/img/2026-11-opencode-13.png)](/img/2026-11-opencode-13.png)

I won't capture and reply all the output, but it waiting for the containers to launch, but then checked logs to see if there were missing modules in output.  It found a minor compile error, fixed that, then noted the race condition on the DB launch

[![/img/2026-11-opencode-14.png](/img/2026-11-opencode-14.png)](/img/2026-11-opencode-14.png)

I watched it work until the containerrs were running successfully

[![/img/2026-11-opencode-15.png](/img/2026-11-opencode-15.png)](/img/2026-11-opencode-15.png)

However, I still get refused (likely forgot to add 0.0.0.0 as allowed hosts)

[![/img/2026-11-opencode-16.png](/img/2026-11-opencode-16.png)](/img/2026-11-opencode-16.png)

I asked

[![/img/2026-11-opencode-17.png](/img/2026-11-opencode-17.png)](/img/2026-11-opencode-17.png)

And fix

[![/img/2026-11-opencode-18.png](/img/2026-11-opencode-18.png)](/img/2026-11-opencode-18.png)

Let's see if 5th times the charm...

It's running but giving me a 404 on the root document

web-1 | * Serving Flask app ‘app’ web-1 | * Debug mode: on web-1 | WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. web-1 | * Running on all addresses (0.0.0.0) web-1 | * Running on http://127.0.0.1:5000 web-1 | * Running on http://172.27.0.3:5000 web-1 | Press CTRL+C to quit web-1 | * Restarting with stat web-1 | * Debugger is active! web-1 | * Debugger PIN: 991-369-536 web-1 | 172.27.0.1 - - [03/Nov/2026 12:48:08] “GET / HTTP/1.1” 404 - web-1 | 172.27.0.1 - - [03/Nov/2026 12:48:08] “GET /favicon.ico HTTP/1.1” 404 - web-1 | 172.27.0.1 - - [03/Nov/2026 12:48:09] “GET / HTTP/1.1” 404 - web-1 | 172.27.0.1 - - [03/Nov/2026 12:48:12] “GET / HTTP/1.1” 404 - web-1 | 172.27.0.1 - - [03/Nov/2026 12:49:02] “GET / HTTP/1.1” 404 -


[![/img/2026-11-opencode-19.png](/img/2026-11-opencode-19.png)](/img/2026-11-opencode-19.png)

hmm.. i do see the proper "/" route in the app python code.

I worked it a bit and you can see it rewrite (i should have committed first) and have more errors. I restart the last session and switch to goal mode:

<video controls width="100%"><source src="/vid/2026-11-opencode-20.webm" type="video/webm"></video>

The goal mode worked for a while, fixing things till it hit a wall on token limits

[![/img/2026-11-opencode-21.png](/img/2026-11-opencode-21.png)](/img/2026-11-opencode-21.png)

And I now see it working!

[![/img/2026-11-opencode-22.png](/img/2026-11-opencode-22.png)](/img/2026-11-opencode-22.png)

And in browser:

[![/img/2026-11-opencode-23.png](/img/2026-11-opencode-23.png)](/img/2026-11-opencode-23.png)

i even went wide screen, while it runs, it's a bit goofy

[![/img/2026-11-opencode-24.png](/img/2026-11-opencode-24.png)](/img/2026-11-opencode-24.png)

It shows up and has the basic criteria, but it does not work

<video controls width="100%"><source src="/vid/2026-11-opencode-25.webm" type="video/webm"></video>

## Gemma4 12b

Let's use a punchier heavier model to see if we can sort things out - same harness, same destination GPU enabled Ollama host, just different model

[![/img/2026-11-opencode-26.png](/img/2026-11-opencode-26.png)](/img/2026-11-opencode-26.png)

But 12b just timed out and e4b didnt do the work

[![/img/2026-11-opencode-27.png](/img/2026-11-opencode-27.png)](/img/2026-11-opencode-27.png)

Though Qwen 3.5 was convinced it sorted the issue

[![/img/2026-11-opencode-28.png](/img/2026-11-opencode-28.png)](/img/2026-11-opencode-28.png)

The UI is still trash though.  It needs help

# Browser MCP

One of my go-tos to help LLMs test is [Browser MCP](https://browsermcp.io).

We can add it to OpenCode by [following the docs](https://browsermcp.io/docs/ai-apps)

~/.config/opencode/opencode.json { “$schema”: “https://opencode.ai/config.json", “mcp”: { “browsermcp”: { “type”: “local”, “command”: [“npx”, “@browsermcp/mcp@latest”], “enabled”: true } } }


For instance, here it is in mine
```json
{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "ollama": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "Ollama (220)",
      "options": {
        "baseURL": "http://192.168.1.220:11434/v1"
      },
      "models": {
        "qwen3.5:latest": {
          "name": "qwen3.5"
        },
        "qwen3.8:27b": {
          "name": "qwen3.8 27b"
        },
        "gemma4:e4b": {
          "name": "Gemma4 e4b"
        },
        "gemma4:12b": {
          "name": "Gemma4 12b"
        },
        "ministral-3:8b": {
          "name": "Ministral-3 8b"
        },
        "granite4.1:8b": {
          "name": "Granite4.1 8b"
        },
        "gpt-oss:20b": {
          "name": "gpt-oss 20b"
        }
      }
    }
  },
  "mcp": {
    "browsermcp": {
      "type": "local",
      "command": ["npx", "@browsermcp/mcp@latest"],
      "enabled": true
    }
  }
}

I tried with Gemma4 in the hopes that the Google model would succeed, but it just didn’t.

I went back to Qwen 3.5 which seemed to note the problems pretty quickly

/img/2026-11-opencode-30.png

And you can see the OpenCode session tied to Chrome in the window

/img/2026-11-opencode-31.png

The layout looks good, but the state change movement still fails as does assignment

Let’s see if the same pattern can help here. Note: before I did this next step, I did save the app as it stood

/img/2026-11-opencode-33.png

I would make progress, but then hit a wall on context limits.. I decided to to try /compact

/img/2026-11-opencode-34.png

I could now go back and try and prompt it to the next fix

/img/2026-11-opencode-35.png

However, it would just get back to broken trying to fix drag and drop which would fail

/img/2026-11-opencode-36.png

Because I check in along the way, I can revert the wild JavaScript edits so at least assignee works

/img/2026-11-opencode-37.png

However, later, when thinking about how to handle this, I did one more docker compose down and docker compose up --build -d to test and low and behold the movement works

Though, as you see, it doesn’t stick

A bit more prompting and testing and I got it so 1 move works (and sticks) but a sortable.js error keeps cropping up.

Now I did get this to work by moving to Qwen 3.8 27b. It took a good 50+ minutes on my 12Gb 3070 to get a response. I was accepting of some rather crazy times because frankly I was asking my hardware to do things well outside the bounds of norm.

/img/2026-11-opencode-40.png

So here it is, a finally preview of Kanban app written by local LLMs (primarily Qwen models) over the course of a couple of days.

Summary

I found it easy to setup and use the latest OpenCode’s latest 2.2.5 version. The game changer for me was the very functional /goal (plan) mode that would let me set some objectives to meet before getting my attention.

I tried several Ollama based models running on a Lenovo Legion with a 12Gb 3070 card. It was quick and easy to setup and use. I added the browsermcp server in the later stages to debug issues with the browser.

In comparing with frontier models like Gemini in Antigravity or Copilot, I might argue this process took longer. I operated in spurts over a 2 to 3 days. I found the last bit hardest as Qwen kept rewriting the JavaScript which my browsers kept caching.

But I did this to prove we can do this locally. It is possible to build out basic apps without falling back on paid frontier models.