Document current coding-workflow stack state

Snapshot of where opencode + Qwen3-Coder + MCPs + Kimi-Linear + voice
  + Phoenix tracing land today, plus in-flight (oc-tree, kimi-linear
  context ramp) and next (ComfyUI) items with pointers to per-project
  NEXT_STEPS.md guides.
This commit is contained in:
2026-05-10 21:14:43 -04:00
parent 228fe8d1ac
commit a29793032d
35 changed files with 2067 additions and 37 deletions

View File

@@ -5,14 +5,61 @@ stack. `install.sh` deploys it to `~/.config/opencode/` on a Mac.
## What's wired up
- **Local model**: `framework/qwen3-coder:30b` served by Ollama on the
Framework Desktop, reachable over Tailscale.
- **Local models**: two providers, manually switched via `/model`.
- `framework/qwen3-coder:30b` — Qwen3-Coder 30B-A3B via Ollama, the
daily-driver coding model. 128K context, 11434.
- `framework-vllm/kimi-linear` — Kimi-Linear 48B-A3B via vLLM, the
long-context play (hybrid KDA/MLA, MoE 3B active). 32K context for
now (ramps further in P3 of the kimi-linear roadmap), 8000.
**Tools disabled** (`tool_call: false`) — Kimi-Linear is a research
architecture release and isn't strongly tool-trained; the model
knows the Kimi-K2 tool tokens but emits non-structured output when
given an MCP toolbox. Use it for chat / long-context reasoning;
switch to `framework/qwen3-coder:30b` for agentic work.
- **Playwright MCP** ([@playwright/mcp](https://github.com/microsoft/playwright-mcp)) —
browser automation. The model can navigate pages, click, fill forms,
read DOM snapshots. Closes the agentic-browsing gap.
- **SearXNG MCP** ([mcp-searxng](https://github.com/ihor-sokoliuk/mcp-searxng)) —
web search via your self-hosted instance at <https://searxng.n0n.io>.
No external API keys, no rate-limit roulette.
- **Serena MCP** ([oraios/serena](https://github.com/oraios/serena)) —
LSP-backed semantic code navigation (find symbol, references, rename,
insert before/after). Cuts the tokens a local 70B-class model burns on
grep-style flailing by roughly an order of magnitude. Uses a **custom
trimmed context** (`serena-ide-trim.yml`) that exposes only the 8
unique-LSP-value tools — JetBrains tools, line-level edits redundant
with opencode's `Edit`, Serena's own memory tools (basic-memory MCP is
canonical), and onboarding/meta noise are all excluded. Down from 46
raw → 41 ide-context-filtered → **8 active**. Scoped to the cwd via
`--project-from-cwd`.
- **basic-memory MCP** ([basicmachines-co/basic-memory](https://github.com/basicmachines-co/basic-memory)) —
Markdown-backed persistent memory across sessions. Storage lives in
`~/Documents/obsidian/AI-memory/` (symlinked from `~/basic-memory`),
so notes are browsable in Obsidian's graph and search. Replaces
Claude Code's auto-memory write-back, which opencode lacks natively.
- **sequential-thinking MCP** ([modelcontextprotocol/servers/sequentialthinking](https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking)) —
externalizes chain-of-thought as tool calls. Helps weaker local
models stay on-plan over multi-step work; near-zero cost when not
actively used.
- **github MCP** ([github/github-mcp-server](https://github.com/github/github-mcp-server)) —
GitHub repo / issue / PR / code-search access. Launched with
`--read-only` and a narrowed `--toolsets repos,issues,pull_requests,code_security`
allowlist. With a **classic** PAT (`ghp_…`), GitHub's auto-scope-filtering
(Jan 2026) trims tools further by hiding ones whose scopes the token
lacks — saves ~23k tokens of tool-list overhead, meaningful for a 70B's
effective context. Requires `GITHUB_PERSONAL_ACCESS_TOKEN` to be exported
in your shell env (not in opencode.json). Drop `--read-only` from
`opencode.json` once you trust the model's tool calls.
**Note**: This MCP is disabled since the user is utilizing a self-hosted Gitea instance instead of GitHub.
- **task-master MCP** ([eyaltoledano/claude-task-master](https://github.com/eyaltoledano/claude-task-master)) —
Workflow / task-gate MCP. File-based: each project gets a
`.taskmaster/` dir with tasks, complexity, and config — no DB, no
external service. `OLLAMA_BASE_URL` is pre-set in `opencode.json` so
task-master's AI features (parse-prd, expand-task) route through your
framework Ollama. The npm-global install also provides a `task-master`
CLI (`task-master init` to scaffold per-project). Replaces the
workflow-gate role originally proposed for Archon, without Supabase.
- **Phoenix bridge plugin** (`.opencode/plugin/phoenix-bridge.js`) —
exports OpenTelemetry spans for every LLM call, tool call, and
subagent invocation to the Phoenix container running on the Framework
@@ -31,13 +78,22 @@ the plugin. Each step checks before doing work. Specifically:
1. Verifies Homebrew is present (won't install it for you)
2. `brew install node uv jq sst/tap/opencode` (skips if already at latest)
3. Pre-caches Playwright's chromium so the first MCP call is instant
4. `npm install` in `.opencode/plugin/` for the Phoenix bridge OTel deps
5. Generates `~/.config/opencode/opencode.json` from the repo's
4. `uv tool install serena-agent@latest --prerelease=allow` so opencode
can launch Serena as a plain `serena` binary on PATH (faster than
re-resolving via `uvx` on every session)
5. Creates `~/Documents/obsidian/AI-memory/` and symlinks `~/basic-memory`
to it, so basic-memory MCP writes into the Obsidian vault by default
6. `brew install github-mcp-server` and warns if `GITHUB_PERSONAL_ACCESS_TOKEN`
isn't set in your shell — the MCP needs it to authenticate
7. `npm install -g task-master-ai` (workflow MCP, also exposes the
`task-master` CLI for `task-master init` per project)
8. `npm install` in `.opencode/plugin/` for the Phoenix bridge OTel deps
9. Generates `~/.config/opencode/opencode.json` from the repo's
`opencode.json`, rewriting relative plugin paths to absolute so
OpenCode loads the plugin regardless of which directory it's launched
from
Step 5 is the reason the deployed config isn't a plain symlink. The
Step 9 is the reason the deployed config isn't a plain symlink. The
repo's `opencode.json` uses a relative plugin path (`./...`) so it stays
valid in place; the deployed copy is generated with that path resolved
to an absolute one. Edits to the repo's `opencode.json` need a re-run
@@ -57,11 +113,35 @@ Then in opencode:
```
opencode
> /mcp # should list playwright and searxng as connected
> /mcp # should list playwright, searxng, serena, basic-memory,
# sequential-thinking, github, task-master as connected
> search the web for "qwen3-coder benchmarks"
> open https://example.com and tell me the H1
> use serena to find the definition of `parse_request`
> remember: this project ships its memory into the Obsidian vault
> /sequentialthinking think through the trade-offs of X vs Y
> list my recent github PRs across all repos
> task-master init # then ask the model to plan tasks for this project
```
For parallel agents, plain tmux + git worktree is enough at the 70B's
~2-pane concurrency ceiling. A two-line zsh helper covers the
"new isolated worktree → split tmux pane → start opencode" loop:
```sh
work() {
local name="${1:?usage: work <branch-name>}"
local wt="../$(basename "$PWD")-$name"
git worktree add "$wt" -b "$name" && tmux split-window -h -c "$wt" "opencode"
}
unwork() { local wt="$PWD"; cd .. && git worktree remove --force "$wt"; }
```
Serena's first invocation in a project may take a few seconds — it
indexes the workspace via the language server. basic-memory's first
write creates the project layout under `~/Documents/obsidian/AI-memory/`
which Obsidian will pick up on its next vault scan.
## Phoenix tracing
The plugin at `.opencode/plugin/phoenix-bridge.js` boots an OpenTelemetry
@@ -142,3 +222,32 @@ plugin no-ops — the rest of OpenCode still works fine.
using the same `type/command/enabled` shape. The
[official MCP registry](https://registry.modelcontextprotocol.io/)
and [Awesome MCP Servers](https://mcpservers.org/) catalog options.
- **Tool-list bloat is real on a local 70B.** Every tool description
costs context. Five MCP servers exposing ~10 tools each puts the
active-tool list around 50 — manageable, but adding two more
full-spectrum servers (e.g. GitHub MCP at ~70 tools without scope
filtering, plus Context7) starts crowding effective context. Prefer
servers with toolset filtering or per-agent allow-lists in opencode.
- **basic-memory storage path.** The symlink `~/basic-memory`
`~/Documents/obsidian/AI-memory` is created by `install.sh` only if
`~/basic-memory` doesn't already exist. If you'd previously run
basic-memory before this setup, move that directory's contents into
`AI-memory/` first, then delete `~/basic-memory` and re-run
`install.sh`.
- **Serena PATH gotcha.** `uv tool install` puts `serena` in
`~/.local/bin/`. If your shell rc doesn't export that, `opencode`
won't find the binary. The script warns; fix is one line in
`~/.zshrc`: `export PATH="$HOME/.local/bin:$PATH"`.
- **Serena tool trim** (`serena-ide-trim.yml`). The custom context
excludes 28 tools beyond what the built-in `ide` context already
filters. To re-expose any of them, edit
[`serena-ide-trim.yml`](serena-ide-trim.yml) and remove the entry
from `excluded_tools`, then re-run `./install.sh`. The path injection
(`./serena-ide-trim.yml` → absolute) is handled by install.sh's jq
pass at deploy time.
- **GitHub PAT.** Use a **classic** PAT (`ghp_…`) — auto-scope-filtering
only kicks in for classic tokens, not fine-grained ones. Without
it, the GitHub MCP exposes its full ~70-tool surface, which costs
~23k tokens of context the local 70B can ill afford. Generate at
<https://github.com/settings/tokens> with the scopes you actually
want exposed.

View File

@@ -8,8 +8,12 @@
# 1. Verify Homebrew is present
# 2. Install node, uv, opencode, jq (skips if already at latest)
# 3. Pre-cache Playwright's chromium so the first MCP call is instant
# 4. Install the Phoenix bridge plugin's OTel deps
# 5. Generate ~/.config/opencode/opencode.json from the repo's
# 4. Install Serena (uv tool — LSP-backed code navigation MCP)
# 5. Wire basic-memory's storage to the Obsidian vault's AI-memory folder
# 6. Install github-mcp-server + check for GITHUB_PERSONAL_ACCESS_TOKEN
# 7. Install task-master-ai (workflow MCP)
# 8. Install the Phoenix bridge plugin's OTel deps
# 9. Generate ~/.config/opencode/opencode.json from the repo's
# opencode.json with relative plugin paths rewritten to absolute,
# so opencode loads the plugin regardless of where it's launched.
#
@@ -28,14 +32,14 @@ warn() { printf ' \033[33m!\033[0m %s\n' "$*"; }
fail() { printf ' \033[31m✗\033[0m %s\n' "$*"; exit 1; }
# --- 1. Homebrew -------------------------------------------------------------
bold "[1/5] Homebrew"
bold "[1/9] Homebrew"
if ! command -v brew >/dev/null 2>&1; then
fail "brew not found. Install from https://brew.sh, then re-run."
fi
ok "brew $(brew --version | head -1 | awk '{print $2}')"
# --- 2. CLI deps -------------------------------------------------------------
bold "[2/5] CLI dependencies"
bold "[2/9] CLI dependencies"
brew_install_if_missing() {
local pkg="$1"
local bin="${2:-$1}"
@@ -60,7 +64,7 @@ else
fi
# --- 3. Playwright browsers --------------------------------------------------
bold "[3/5] Playwright browser cache"
bold "[3/9] Playwright browser cache"
PW_CACHE="${HOME}/Library/Caches/ms-playwright"
if [[ -d "$PW_CACHE" ]] && find "$PW_CACHE" -name "chrome" -o -name "Chromium*" 2>/dev/null | grep -q .; then
ok "browsers already cached at $PW_CACHE"
@@ -70,8 +74,93 @@ else
ok "browsers cached"
fi
# --- 4. Phoenix bridge plugin deps ------------------------------------------
bold "[4/5] Phoenix bridge plugin deps"
# --- 4. Serena (LSP-backed semantic code navigation MCP) --------------------
# Installed once as a uv tool so opencode can launch it as `serena
# start-mcp-server ...` without paying uvx's resolution cost on every
# session start. --prerelease=allow is required because serena-agent
# ships pre-1.0 versions.
bold "[4/9] Serena MCP"
if uv tool list 2>/dev/null | awk '{print $1}' | grep -qx 'serena-agent'; then
ok "serena-agent already installed ($(serena --version 2>/dev/null | head -1 || echo 'version unknown'))"
else
info "installing serena-agent via uv tool (~30s first run)"
uv tool install -p 3.13 serena-agent@latest --prerelease=allow
ok "serena-agent installed"
fi
if ! command -v serena >/dev/null 2>&1; then
warn "serena binary not on PATH — uv tool's bin dir may not be exported."
warn "Add this to your shell rc: export PATH=\"\$HOME/.local/bin:\$PATH\""
fi
# --- 5. basic-memory storage ------------------------------------------------
# basic-memory defaults its project home to ~/basic-memory. We point that
# at a folder inside the Obsidian vault via symlink so the AI's notes
# show up in Obsidian's graph and search. Symlink (not env var) chosen
# because it's stable across basic-memory's evolving config schema.
bold "[5/9] basic-memory storage"
AI_MEM_PATH="${HOME}/Documents/obsidian/AI-memory"
if [[ ! -d "$AI_MEM_PATH" ]]; then
info "creating $AI_MEM_PATH"
mkdir -p "$AI_MEM_PATH"
ok "AI-memory directory created"
else
ok "AI-memory directory exists at $AI_MEM_PATH"
fi
if [[ -L "${HOME}/basic-memory" ]]; then
link_target="$(readlink "${HOME}/basic-memory")"
if [[ "$link_target" == "$AI_MEM_PATH" ]]; then
ok "~/basic-memory already linked to AI-memory"
else
warn "~/basic-memory points to $link_target — leaving as-is. basic-memory MCP will write there, not to AI-memory."
fi
elif [[ -e "${HOME}/basic-memory" ]]; then
warn "~/basic-memory exists and is not a symlink. Move or remove it for AI-memory linkage."
else
info "linking ~/basic-memory -> $AI_MEM_PATH"
ln -s "$AI_MEM_PATH" "${HOME}/basic-memory"
ok "symlink created"
fi
# --- 6. github-mcp-server (GitHub MCP, classic-PAT auto-scope-filtered) -----
# Homebrew formula tracks upstream releases; the binary is a Go single-file.
# We launch it via opencode.json's mcp.github entry with --read-only and a
# narrowed --toolsets allowlist; auto-scope-filtering on classic PATs (the
# Jan 2026 GitHub feature) cuts ~23k tokens of tool-list overhead — significant
# for a local 70B's effective context. Token itself is NOT in opencode.json
# (it's git-tracked); github-mcp-server inherits GITHUB_PERSONAL_ACCESS_TOKEN
# from the user's shell env.
bold "[6/9] github-mcp-server"
brew_install_if_missing github-mcp-server github-mcp-server
if [[ -z "${GITHUB_PERSONAL_ACCESS_TOKEN:-}" ]]; then
warn "GITHUB_PERSONAL_ACCESS_TOKEN is not set in your shell environment."
warn " github-mcp-server will fail to connect on opencode startup."
warn " Fix:"
warn " 1. Create a classic PAT (starts with ghp_) at"
warn " https://github.com/settings/tokens with the scopes you want"
warn " exposed (auto-filtering hides tools whose scopes the PAT lacks)."
warn " 2. Add to ~/.zshrc:"
warn " export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxxxxxxxxxxx"
warn " 3. Re-source the shell (or open a new terminal) before launching opencode."
else
ok "GITHUB_PERSONAL_ACCESS_TOKEN is set in this shell"
fi
# --- 7. claude-task-master (workflow MCP, npm global) -----------------------
# task-master-ai: workflow/task-gate MCP. File-based (.taskmaster/ in each
# project), no DB, no external service. opencode.json launches it via
# `npx -y task-master-ai`; the global install also provides the `task-master`
# CLI (`task-master init` to scaffold a project's tasks).
bold "[7/9] claude-task-master"
if command -v task-master >/dev/null 2>&1; then
ok "task-master-ai already installed ($(task-master --version 2>/dev/null | head -1 || echo 'version unknown'))"
else
info "installing task-master-ai globally via npm"
npm install -g task-master-ai
ok "task-master-ai installed"
fi
# --- 8. Phoenix bridge plugin deps ------------------------------------------
bold "[8/9] Phoenix bridge plugin deps"
if [[ -d ".opencode/plugin/node_modules" && -f ".opencode/plugin/package-lock.json" ]]; then
# Re-run npm install if package.json is newer than the lockfile, otherwise skip.
if [[ ".opencode/plugin/package.json" -nt ".opencode/plugin/package-lock.json" ]]; then
@@ -87,12 +176,12 @@ else
ok "deps installed"
fi
# --- 5. Generate ~/.config/opencode/opencode.json ---------------------------
# --- 9. Generate ~/.config/opencode/opencode.json ---------------------------
# The repo's opencode.json uses relative plugin paths so it stays valid
# in-place. Rewriting them to absolute paths here makes opencode find the
# plugin regardless of which directory it was launched from. Re-run this
# script after editing opencode.json.
bold "[5/5] Deploy global config"
bold "[9/9] Deploy global config"
mkdir -p "${HOME}/.config/opencode"
src="${HERE}/opencode.json"
dst="${HOME}/.config/opencode/opencode.json"
@@ -109,24 +198,34 @@ if [[ -L "${HOME}/.config/opencode/.opencode" ]]; then
rm "${HOME}/.config/opencode/.opencode"
fi
# Rewrite any relative plugin path (./foo, ../foo) to an absolute path
# rooted at this directory. Absolute paths and npm-package refs pass
# through untouched.
# Rewrite any relative path (./foo, ../foo) to an absolute path rooted at
# this directory. Applies to both the top-level `plugin` array and to any
# string inside `mcp.<name>.command[]` (used for serena's --context arg
# pointing at serena-ide-trim.yml). Absolute paths and npm-package refs
# pass through untouched.
jq --arg here "$HERE" '
.plugin = (
(.plugin // [])
| map(
if type == "string" and (startswith("./") or startswith("../"))
then ($here + "/" + ltrimstr("./") | gsub("/\\./"; "/"))
else .
end
)
)
def rewrite($h):
if type == "string" and (startswith("./") or startswith("../"))
then ($h + "/" + ltrimstr("./") | gsub("/\\./"; "/"))
else .
end;
.plugin = ((.plugin // []) | map(rewrite($here)))
| .mcp = (
(.mcp // {})
| with_entries(
if (.value | type) == "object" and (.value | has("command"))
then .value.command |= map(rewrite($here))
else .
end
)
)
' "$src" > "$dst.tmp"
mv "$dst.tmp" "$dst"
ok "wrote $dst"
info "plugin paths resolved to:"
jq -r '.plugin[]?' "$dst" | sed 's/^/ /'
info "mcp.serena context resolved to:"
jq -r '.mcp.serena.command | map(select(test("\\.yml$"))) | .[]?' "$dst" | sed 's/^/ /'
echo
bold "Done."

View File

@@ -7,7 +7,7 @@
"provider": {
"framework": {
"npm": "@ai-sdk/openai-compatible",
"name": "Framework Desktop (Strix Halo)",
"name": "Framework Desktop (Strix Halo) — Ollama",
"options": {
"baseURL": "http://framework:11434/v1"
},
@@ -20,6 +20,24 @@
}
}
}
},
"framework-vllm": {
"npm": "@ai-sdk/openai-compatible",
"name": "Framework Desktop (Strix Halo) — vLLM",
"options": {
"baseURL": "http://framework:8000/v1",
"apiKey": "dummy"
},
"models": {
"kimi-linear": {
"name": "Kimi-Linear 48B-A3B (long-context, vLLM)",
"limit": {
"context": 32768,
"output": 8192
},
"tool_call": false
}
}
}
},
"mcp": {
@@ -35,6 +53,44 @@
"environment": {
"SEARXNG_URL": "https://searxng.n0n.io"
}
},
"serena": {
"type": "local",
"command": [
"serena", "start-mcp-server",
"--context", "./serena-ide-trim.yml",
"--project-from-cwd",
"--open-web-dashboard", "false"
],
"enabled": true
},
"basic-memory": {
"type": "local",
"command": ["uvx", "basic-memory", "mcp"],
"enabled": true
},
"sequential-thinking": {
"type": "local",
"command": ["npx", "-y", "@modelcontextprotocol/server-sequential-thinking"],
"enabled": true
},
"github": {
"type": "local",
"command": [
"github-mcp-server",
"stdio",
"--read-only",
"--toolsets", "repos,issues,pull_requests,code_security"
],
"enabled": false
},
"task-master": {
"type": "local",
"command": ["npx", "-y", "task-master-ai"],
"enabled": true,
"environment": {
"OLLAMA_BASE_URL": "http://framework:11434/v1"
}
}
},
"model": "framework/qwen3-coder:30b"

View File

@@ -0,0 +1,92 @@
# Custom Serena context for the localgenai stack.
#
# Extends Serena's built-in `ide` context with deeper exclusions tailored to
# opencode + a local 70B-class model. Loaded by Serena via the
# `--context /abs/path/to/this.yml` flag (install.sh rewrites the relative
# path in opencode.json to the repo's absolute path at deploy time).
#
# Trim summary (see opencode/README.md for rationale):
# 46 raw tools → ide context excludes 5 → this context excludes 28 more
# → ~12 visible tools, all unique LSP value
#
# Cut categories:
# - JetBrains backend (11) — language_backend: LSP, never JetBrains
# - Line-level edits (5) — opencode's Edit covers them
# - Memory tools (6) — basic-memory MCP is canonical
# - Onboarding / meta (5) — bootstrap noise the model rarely needs
# - Destructive / dashboard (2) — remove_project, open_dashboard
#
# Kept (the unique LSP value, the reason we installed Serena):
# find_symbol, find_referencing_symbols, get_symbols_overview,
# replace_symbol_body, insert_after_symbol, insert_before_symbol,
# rename_symbol, safe_delete_symbol, restart_language_server,
# list_queryable_projects, query_project (+ activate_project, latent)
description: opencode IDE context, trimmed for local 70B
prompt: |
You are running in an IDE assistant context where file operations,
basic (line-based) edits and reads, and shell commands are handled by
your own, internal tools.
If Serena's tools can be used to achieve your task, you should
prioritize them. In particular, it is important that you avoid reading
entire source code files unless it is strictly necessary! Instead, for
exploring and reading code in a token-efficient manner, use Serena's
symbolic-search tools (find_symbol, find_referencing_symbols,
get_symbols_overview).
excluded_tools:
# Inherited from built-in ide context (opencode built-ins cover these)
- create_text_file
- read_file
- execute_shell_command
- find_file
- list_dir
# Line-level edits — redundant with opencode's Edit and Grep
- replace_content
- delete_lines
- replace_lines
- insert_at_line
- search_for_pattern
# Memory tools — basic-memory MCP is the canonical persistent memory
- write_memory
- read_memory
- list_memories
- delete_memory
- rename_memory
- edit_memory
# Onboarding / meta — bootstrap noise
- check_onboarding_performed
- onboarding
- initial_instructions
- serena_info
- get_current_config
# Destructive / dashboard
- remove_project
- open_dashboard
# JetBrains backend — never used in this setup (language_backend: LSP)
- jet_brains_find_symbol
- jet_brains_move
- jet_brains_safe_delete
- jet_brains_inline_symbol
- jet_brains_find_referencing_symbols
- jet_brains_get_symbols_overview
- jet_brains_type_hierarchy
- jet_brains_find_declaration
- jet_brains_find_implementations
- jet_brains_rename
- jet_brains_debug
tool_description_overrides: {}
# When `single_project: true` and a project is given at startup, Serena
# limits the toolset to what the project actually needs and disables
# `activate_project`. With opencode launching Serena via
# `--project-from-cwd`, a project is always present.
single_project: true