Skip to content

Build an Offline Coding Agent with OpenCode and llama.cpp on an Intel Mac

Connect OpenCode 2.1.2 to a Radeon-backed local Qwen 9B server, configure approval-driven tools, recover from an 8K context failure, and verify project file access at 32K.

Cover: OpenCode taking the request to find the homepage button, the built-in tools menu of llama.cpp’s web chat, the OpenCode, llama.cpp and Qwen marks and an offline sign

The agent meets the Radeon

In this episode: a Radeon-backed local model connects to OpenCode 2.1.2, reads a project file through an agent tool after an early context-overflow failure, and goes on to create files and run commands under approval (evaluated in full in Episode 8). Whether it completes complex coding assignments reliably is the question for Episode 9.

1. Architecture: from a local GGUF to file-system tools

The key distinction is that llama.cpp runs the model; OpenCode decides when and how to use tools. The local language model generates responses and tool requests. OpenCode supplies the agent interface, file operations, shell execution and approval prompts. Reliable tool selection is a model behaviour to test; the weights and GPU acceleration leave it open.

Architecture diagram linking OpenCode through a local llama server and Vulkan to Radeon inference and approval-gated tools
OpenCode supplies the agent tools while llama-server supplies local Radeon-backed model inference.
ComponentTested implementationPurpose
Host2019 Intel Core i9 MacBook Pro; 32 GB RAMLocal execution environment
GPUAMD Radeon Pro 5500M, 8 GBModel acceleration via Vulkan/MoltenVK
Inference enginellama-server from the locally built llama.cppHosts the quantized language model
ModelQwen3.5 9B GGUF from the existing Ollama blob cacheProduces text and tool-call requests
HTTP endpointhttp://127.0.0.1:8080/v1OpenAI-compatible model API
Agent clientOpenCode 2.1.2, interactive Terminal interfaceAgent interface and tools
Project~/Documents/app/llm/agent-test/Disposable working directory
Context32,768 tokens, one server slotAvoids the earlier 8K exhaustion
  • OpenCode2.1.2
  • llama.cppllama-server
  • Qwen3.5 9B
  • VulkanSDK 1.4.363.0

The Vulkan and permanent-workspace setup were covered in Episode 4 and Episode 6.

2. Prerequisites: confirm the known-good GPU build

This article assumes the Episode 6 migration is done. Follow it with the build and Qwen weights you already have: the tested system reused existing Ollama model weights and the previously compiled binaries.

Open Terminal and inspect the essential files:

bash
ls -lh "$HOME/Documents/app/llm/llama.cpp/build-vulkan-new/bin/llama-server"
ls -lh "$HOME/.ollama/models/blobs/sha256-02d45dc1cf451ba2475ac33b301c2dd8f985abe4c182ce04a1f2f5bf0260278d"

Expected: a runnable llama-server file and an existing 9B GGUF blob of about 5.23 GiB. The whole Ollama 9B installation, vision projector included, takes around 6.6 GB; this API experiment used the main text model alone, and image input waits for Episode 10.

Check the installed OpenCode executable:

bash
opencode --version

On the test Mac: opencode v2.1.2. Version numbers and configuration formats can change; the instructions below target OpenCode V2. If the shell reports opencode: command not found, check ~/.opencode/bin/opencode and your PATH before installing anything.

The tested installer was obtained from the official OpenCode website. For a new installation, follow the current OpenCode installation instructions rather than blindly rerunning an old installer command from a log.

OpenCode terminal window: a request, the agent’s file searches and reads, and the active model at the bottom
OpenCode’s terminal interface: the request, each tool call and the active model in one window.Credit: OpenCode, MIT licence.

3. Start the Radeon-backed model server

In Terminal A, set up the same Vulkan environment used in the earlier GPU tests, then start the server:

bash
cd "$HOME/Documents/app/llm/llama.cpp"
source "$HOME/VulkanSDK/1.4.363.0/setup-env.sh"
export VK_DRIVER_FILES="$VULKAN_SDK/share/vulkan/icd.d/MoltenVK_icd.json"

./build-vulkan-new/bin/llama-server \
  -m "$HOME/.ollama/models/blobs/sha256-02d45dc1cf451ba2475ac33b301c2dd8f985abe4c182ce04a1f2f5bf0260278d" \
  --alias qwen3.5-9b \
  --device Vulkan0 \
  -ngl 99 \
  -c 32768 \
  -np 1 \
  --flash-attn off \
  --jinja \
  --reasoning off \
  --host 127.0.0.1 \
  --port 8080

Why these settings matter:

  • --device Vulkan0 selects the Radeon device identified in the preceding GPU tests. Confirm device enumeration on your machine rather than assuming device 0 means Radeon everywhere.
  • -ngl 99 requests GPU offloading for eligible layers; some allocations can still sit in system memory. The previous Qwen 9B diagnostic reported 33/33 eligible layers offloaded.
  • -c 32768 provisions a larger context pool. -np 1 gives a single active slot access to that pool rather than dividing the budget across several concurrent sequences.
  • --jinja enables the model's chat-template parsing needed for supported tool-calling workflows. --reasoning off was chosen for a simpler non-thinking agent test. This controls runtime formatting; the model's intelligence stays the same.
  • --host 127.0.0.1 binds the service to the local loopback interface, instead of listening on every network interface.

Log lines that confirm success: model loaded, n_slots = 1, n_ctx_slot = 32768 and listening on http://127.0.0.1:8080.

Recent llama.cpp releases also serve a chat page from llama-server, at the same address in a browser. This episode drives the server from OpenCode; the page is a quick way to talk to the model directly.

llama.cpp web chat page with a message box, recent conversations and a tools menu
The chat page built into llama-server, as the llama.cpp project shows it.Credit: llama.cpp, MIT licence.

Verify the endpoint before opening an agent

Leave Terminal A running. In Terminal B:

bash
curl --max-time 10 http://127.0.0.1:8080/health

Expected output:

json
{"status":"ok"}

To check which model the server advertises:

bash
curl --max-time 10 http://127.0.0.1:8080/v1/models

A healthy server returns JSON including the advertised model; the exact fields depend on the llama.cpp revision.

If /health fails, fix the server, GPU environment or port conflict first, and leave OpenCode for later.

4. Configure OpenCode V2 for the local HTTP model

Create or open a disposable directory rather than allowing an experimental model to modify an existing project:

bash
mkdir -p "$HOME/Documents/app/llm/agent-test"
cd "$HOME/Documents/app/llm/agent-test"

Place the following OpenCode V2 configuration at opencode.json inside that directory. It holds the working provider, model and approval settings from the test, in the documented V2 schema:

json
{
  "$schema": "https://opencode.ai/config.json",
  "model": "localgpu/qwen3.5-9b",
  "providers": {
    "localgpu": {
      "name": "Local Radeon GPU",
      "package": "@opencode/ai/providers/openai-compatible",
      "settings": {
        "baseURL": "http://127.0.0.1:8080/v1"
      },
      "models": {
        "qwen3.5-9b": {
          "name": "Qwen3.5 9B Radeon",
          "modelID": "qwen3.5-9b",
          "capabilities": {
            "tools": true,
            "input": ["text"],
            "output": ["text"]
          },
          "limit": {
            "context": 32768,
            "output": 2048
          }
        }
      }
    }
  },
  "permissions": [
    { "action": "edit", "resource": "*", "effect": "ask" },
    { "action": "shell", "resource": "*", "effect": "ask" },
    { "action": "external_directory", "resource": "*", "effect": "ask" }
  ]
}

Important version distinction: OpenCode V2 uses providers, package, and the ordered permissions rules with action shell. Older V1 documentation often shows a singular provider, npm, and permission with bash. Mixing V1 and V2 examples creates confusing validation or behaviour failures. Verify the current V2 models documentation and V2 permissions documentation if using a different release.

The localgpu/qwen3.5-9b model reference consists of an OpenCode provider identifier and the local model key. The separate modelID must match the --alias advertised by the running server; in this example, both point to qwen3.5-9b.

The security decision matters: edit and shell set to ask request your approval before acting. external_directory set to ask guards attempts to reach beyond the active project. Approval is still a human responsibility: review the proposed commands and file paths themselves, beyond the agent's description of its intent.

Validate the model registration

bash
opencode models | grep '^localgpu/'

On the test Mac: the model identifier localgpu/qwen3.5-9b appeared in the list. If it is missing, check the configuration's location, JSON validity, model key and server health. OpenCode may merge global and project configuration, so conflicting global settings are another possible cause.

5. The failure: an 8K context was too small

The first attempt used a context size of 8,192 tokens. A file-reading instruction caused an effective context/KV-cache overflow. OpenCode then attempted conversation compaction, which also failed to recover that session.

Observed failure sequence:

  1. The model endpoint and OpenCode provider connected.
  2. The agent began a basic project-inspection request.
  3. Tool definitions, agent instructions, conversation history and file content consumed the available context budget.
  4. The request outgrew the server's context, and the OpenCode compaction attempt failed.

The Qwen weights and the Radeon were fine. It was an integration budget mismatch: a local agent sends far more than the latest user message, and tool schemas and system instructions can take thousands of tokens before any visible answer appears. In later tests, OpenCode's opening request alone measured about 7,000 tokens.

Timeline showing an 8192-token failure followed by a 32768-token single-slot recovery and successful file read
The recovery aligned the server and OpenCode at 32,768 tokens with one slot, then started a fresh session.

The tested recovery

The successful fix required both sides to agree on the new context budget:

  • Stop the old llama-server process with Ctrl+C in Terminal A.
  • Restart llama-server using -c 32768 -np 1 (the full command is in Section 3).
  • Change the OpenCode model's limit.context from 8192 to 32768 in opencode.json.
  • Start a fresh OpenCode session, instead of relying on the already-overflowed conversation.

The restarted server logged n_ctx_slot=32768 and the health endpoint returned {"status":"ok"}. In the new session, OpenCode successfully used a tool to read opencode.json and described its model and permissions. The successful session had consumed approximately 7,800 tokens during that test.

Size the context per setup. 32K costs KV-cache memory and some responsiveness, and another model, runtime, project or prompt may want a different limit. What matters is that the server’s capacity, OpenCode’s advertised budget and the real workload agree, and that a test confirms it.

6. Starting the actual Terminal coding agent

From the sandbox directory, with the server already running:

bash
cd "$HOME/Documents/app/llm/agent-test"
opencode

The test used OpenCode's normal full-screen Terminal interface. Its status line showed the local model as Qwen3.5 9B Radeon / Local Radeon GPU, and the agent ran in Build mode throughout.

For a small, read-only first task, enter:

Read opencode.json in the current project folder. Report the selected model, API base URL, context size, and whether editing and shell commands require approval. Do not change any files and do not execute any commands.

Result: OpenCode invoked its file tool, inspected the JSON and answered correctly: a real tool call, beyond the model paraphrasing information from the prompt.

OpenCode session panel showing 11,919 context tokens, 36% used, above the status line Qwen3.5 9B Radeon, Local Radeon GPU
A later OpenCode session: the local Radeon model, with 11,919 tokens of the 32K context in use (36%).

7. A second issue: the Terminal interface froze

The agent later opened an interactive question tool dialog while proposing to create and run a JavaScript test. Raw terminal mouse-control sequences appeared in the interface, and input temporarily stopped responding. The interface remained on-screen, but normal keyboard and mouse control became unreliable.

This was an OpenCode TUI and session-control problem; the model itself kept running. A second Terminal identified the stuck interactive OpenCode process and terminated it. A TERM signal came first; this stuck process eventually needed KILL. The separate OpenCode service process stayed untouched.

To inspect processes safely:

bash
pgrep -fl opencode

If a specific interactive TUI is unresponsive, attempt a normal termination of that process only:

bash
kill -TERM <INTERACTIVE_PID>

If it keeps running, kill -KILL <INTERACTIVE_PID> is the last resort, after checking the PID again. The bracketed values are placeholders: replace them with the real process ID. Target that one process, since an independent OpenCode service may also be running.

Two experimental launch variations were attempted:

bash
OPENCODE_CLI_CONFIG_CONTENT='{"mouse":false}' opencode
OPENCODE_CLI_CONFIG_CONTENT='{"mouse":true}' opencode

Keyboard interaction returned after the stuck TUI was terminated and the Terminal was reset. Mouse selection behaved differently across the following tests. Treat these commands as troubleshooting experiments, short of a documented fix for TUI freezes across terminals or OpenCode versions.

For the file-creation tests that followed, the agent was told to use the file-editing tool directly instead of opening the question tool. That avoided a repeat of the problematic dialog in this test; as a general cure for terminal input bugs, it remains unproven.

8. Permissions: a working agent still needs boundaries

OpenCode's V2 permissions documentation specifies ordered { action, resource, effect } rules, where the last matching rule wins. In the test session, an earlier proposed command was declined and a later file write was approved once: interactive decisions, the opposite of unattended autonomy.

The agent subsequently created a simple hello.js, and a separate shell task executed it. Those are the subject of Episode 8, where the complete read/write/execute outcomes and approvals are evaluated.

For any real project, apply at least these constraints before trusting a local agent:

  • Use a dedicated sandbox or version-controlled working tree, and keep independent backups.
  • Inspect the full command, its arguments, and the paths before approving shell use.
  • Keep external-directory access approval-based; local models can hallucinate targets or paths.
  • Avoid placing credentials, personal documents or sensitive files in the project sandbox.
  • Keep the inference server on the local loopback interface unless access controls are explicitly configured.

One command for the whole stack

Once the stack was proven, one command replaced the two Terminals. qwen9-agent (or qwen4-agent for the smaller model) passes the Radeon gate, starts llama-server on 127.0.0.1:8080, waits for /health to answer, then opens OpenCode. The server's -c comes from the shared context setting (ctx16, ctx32 or ctx64), with -np 1 kept, so the 8K lesson above stays solved with zero script edits.

Starting an agent also creates the job of stopping it. After a session, qwenoff stops OpenCode and the server, and qwenstatus confirms that ports 8080 and 11434 are clear. That makes the local agent an on-demand tool in place of a background service. Episode 6 lists the full command layer.

Offline inference leaves auxiliary features to their own settings. The model endpoint here was local, while optional search tools, plugins, update checks and other integrations may use network services. If strict offline isolation matters, inspect and disable those features or block the network at a lower level.

9. Troubleshooting reference

SymptomLikely cause or first checkNext diagnostic action
curl /health refuses the connectionServer absent or port mismatchCheck the Terminal A log and 127.0.0.1:8080
The local model is missing from opencode modelsConfig location or schema, or a server mismatchCheck opencode.json, the provider key and opencode models
Qwen responds and the tools stay idleChat template, advertised tool capability or agent modeKeep --jinja; inspect tool definitions and agent state
Context or KV-cache errorServer slot too small for the actual agent payloadMatch server -c, parallel slots and limit.context; start fresh
Compaction repeatedly failsSession already exceeds the usable input budgetNew session; reduce history and attachments and check configured limits
OpenCode TUI stops accepting inputTerminal input or UI state problemIdentify the TUI process, terminate that process, reopen Terminal
Unexpected file or shell actionApproval policy or an earlier approvalAudit V2 permissions, check the specific tool call and reset unintended approvals
Model feels slow after a fast GPU benchmarkLong prompts, tool cycles, context pressure and model qualityMeasure end-to-end completion time alongside generation speed

The last row matters in practice: a short llama-bench measurement says little about how quickly an agent handles thousands of prompt tokens and several tool calls. See Episode 5 for the controlled GPU throughput results.

Conclusion: the agent interface worked; reliability remained unproven

The local OpenCode configuration passed a meaningful engineering milestone. On an Intel MacBook Pro, a Qwen3.5 9B model ran behind a Radeon-accelerated, OpenAI-compatible llama-server. OpenCode recognised the model, could invoke file tools, and the 32K/one-slot server resolved the observed context problem. The full-screen Terminal UI was usable again after recovering from an input-control freeze.

The success criteria should stay precise. A successful connection and successful code generation are separate results, and so are a successful tool call and a reliable autonomous workflow. Those distinctions matter more on small local models, where misunderstood instructions and repeated actions can cost a lot of time.

Next: Episode 8: Testing a Local AI Coding Agent: file reads, approved hello.js creation and a Node.js command that returns 5.


References and primary documentation

Published
Author
Booban Net.

Resources

More episodes

View all

Ready when you are

Tell us what you need and a written quote comes back within a working day.

Phone or WhatsApp
What do you need

By sending, you agree to our Privacy Policy and Terms.

or connect with us via :
WhatsAppEmail