Local AI · AMD Ryzen AI NPU · WSL

Local coding agents on AMD Ryzen AI (FastFlowLM + WSL)

Run a coding agent (opencode, Claude Code, codex) against locally hosted LLMs on an AMD Ryzen AI 7 PRO 350W NPU, and use the same models from IntelliJ via the Continue plugin. Models are served by FastFlowLM ("FLM") on Windows; the agents run inside WSL.

Platform facts

Host OSWindows 11, build 10.0.26200.8655 (25H2)
WSL2.7.11.0, kernel 6.18.33.2, systemd enabled
NPUAMD Ryzen AI 7 PRO 350W
Inference serverFastFlowLM, OpenAI-compatible API on port 52625
Agentopencode 1.18.13 (in WSL), default model local/qwen3:8b
Machinedomain-joined
Correction

Earlier notes said "Windows 10". That was wrong, and it matters: build 26200 is Windows 11 25H2, which does support WSL mirrored networking. See Alternative: mirrored networking.

Topology

Windows                                  WSL (Ubuntu)
┌────────────────────────────┐           ┌──────────────────────────┐
│ flm serve --host 0.0.0.0   │           │ opencode                 │
│   :52625  (OpenAI API)     │◀──────────│   http://flm-host:52625  │
│                            │  gateway  │                          │
│ IntelliJ + Continue        │  172.30.  │ /etc/hosts: flm-host ──▶ │
│   http://localhost:52625   │  80.1     │   <current gateway IP>   │
└────────────────────────────┘           └──────────────────────────┘
                                              ▲
                              rewritten at every WSL start by
                              /usr/local/bin/update-flm-host.sh

Running FastFlowLM

Interactive terminal chat (PowerShell):

flm run qwen3:8b

To serve the API for agents — must bind all interfaces so WSL can reach it:

flm serve --host 0.0.0.0 --port 52625

Windows Firewall needs to allow inbound TCP 52625 from the WSL subnet. Binding 0.0.0.0 also exposes the port to the LAN; on an untrusted network, scope the firewall rule to the WSL interface.

Reaching FastFlowLM from WSL

The problem

WSL2 in its default NAT mode gets a new virtual network on every start, so the Windows host address (the default gateway, e.g. 172.30.80.1) changes. Hardcoding it breaks after a reboot; exporting it into a shell variable does not help, because neither opencode's config.json nor Continue's config.yaml expand shell variables.

The solution

Introduce a stable hostname, flm-host, and re-point it at the current gateway on every WSL start. Configs reference the name, never an IP, so nothing downstream ever needs to change again.

/usr/local/bin/update-flm-host.sh (installed, executable, idempotent) reads the default route and rewrites the flm-host entry in /etc/hosts. Run it by hand any time the address looks stale. Repo copy for a fresh machine: scripts/update-flm-host.sh — install with sudo install -m 755 scripts/update-flm-host.sh /usr/local/bin/.

It is wired into every WSL boot via /etc/wsl.conf:

[boot]
systemd=true
command = /usr/local/bin/update-flm-host.sh

Verify:

/usr/local/bin/update-flm-host.sh          # update-flm-host: flm-host -> 172.30.80.1
getent hosts flm-host
curl -s http://flm-host:52625/v1/models -H "Authorization: Bearer flm-dummy-key"

Why not the gateway IP or bridge mode?

The gateway IP is the moving target we are solving for; bridge mode requires Hyper-V virtual-switch setup and tends to fight corporate VPN clients. The hosts-alias approach is a few lines, needs no Windows-side network config, and survives VPN changes.

Alternative: mirrored networking

Because this is Windows 11, WSL mirrored networking is available and is the cleaner long-term fix: WSL shares the Windows network stack, so localhost:52625 works from WSL directly. Then both sides use the identical URL, the flm-host alias becomes unnecessary, and FLM can bind 127.0.0.1 instead of 0.0.0.0 — no LAN exposure, no firewall rule.

Create C:\Users\<UserProfile>\.wslconfig:

[wsl2]
networkingMode=mirrored

then wsl --shutdown. Untested here. Mirrored mode changes DNS and VPN behaviour, and this machine is domain-joined, so validate VPN, DNS and internal resources before adopting it. Rollback: delete .wslconfig, wsl --shutdown.

opencode

Two config files, both under ~/.config/opencode/ (note: opencode here runs as root, so that is /root/.config/opencode/).

Model provider — config.json

Full file: configs/opencode-config.json. The part that matters:

{
  "provider": {
    "local": {
      "npm": "@ai-sdk/openai-compatible",
      "options": {
        "baseURL": "http://flm-host:52625/v1",
        "apiKey": "flm-dummy-key"
      }
    }
  },
  "model": "local/qwen3:8b"
}

apiKey is a dummy — FLM does not authenticate, but the OpenAI-compatible client requires the field to be present.

Why not a shell variable?

Previously this read http://${WINDOWS_HOST_IP}:52625/v1. opencode does not perform shell-style ${VAR} expansion (its substitution syntax is {env:VAR}), so that URL was used literally and could not resolve. Fixed by the flm-host alias, which needs no substitution at all.

MCP servers — opencode.jsonc

This guide was published through the pontiswerk.eu MCP server (free account):

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "pontiswerk-local-agents": {
      "type": "remote",
      "url": "https://mcp.pontiswerk.eu/mcp",
      "headers": { "Authorization": "Bearer <credential-id>.<secret>" }
    }
  }
}

To refresh an expired token, use the server's reconnect tool, which starts the OAuth flow. Files under docs-public/ are the ones published to the web.

IntelliJ + Continue plugin

Config lives at C:\Users\<UserProfile>\.continue\ or <ProjectDir>\.continue\agents\*.config.yaml.

Full config: configs/continue.config.yaml — all FLM models mapped to chat / edit / apply / autocomplete roles.

Continue runs on Windows, so it talks to http://localhost:52625/v1 and is unaffected by the WSL IP problem. It does not expand shell variables like ${WINDOWS_HOST_IP} in config.yaml; use a literal host or a ${{ secrets.* }} reference.

Model notes

Observations on which models can actually drive an agent loop — see also MODEL-NOTES.txt.

ModelVerdict
qwen3:4bCannot follow complex instructions
qwen3:8bHandles multi-step instructions; current default

Extend this table as models are benchmarked. Throughput/latency numbers on the NPU are still to be collected.

Open items

Maintaining this document

This is the single source of truth for the setup; NOTES.txt has been retired. When something changes: update the relevant section rather than appending, keep full config files in configs/ and only the meaningful excerpt inline, and record why a choice was made — the reasoning is what dates fastest and is missed most.