终端里的 AI 编码代理:我的 OpenCode 实践与本地 / 线上模型选型
终端里的 AI 编码代理:我的 OpenCode 实践与本地 / 线上模型选型
事实依据:本文基于 OpenCode 官方文档(opencode.ai/docs)与本机真实接入配置撰写,撰写于 2026 年 8 月。
一、OpenCode 是什么
OpenCode 是一个开源的 AI 编码代理,主打终端(TUI)界面,也提供桌面应用与 IDE 扩展。它基于 AI SDK 与 Models.dev,官方声称支持 75+ 大模型供应商,同时原生支持接入本地模型。
上手只需两步:/connect 配置供应商,然后在项目目录运行 opencode。但真正决定体验上限的,是下面这些容易被忽略的工程细节——尤其当你把模型跑在本地。
二、我的接入配置:本地为主,云端兜底
我用 llama.cpp 的 llama-server 部署本地模型,服务跑在局域网一台 GPU 机器上,通过 OpenAI 兼容 API 暴露。项目根目录的 opencode.json 这样接入:
{
"provider": {
"local": {
"npm": "@ai-sdk/openai-compatible",
"name": "Local OpenAI-compatible",
"options": { "baseURL": "http://192.168.1.3:8001/v1" },
"models": {
"unsloth/Qwen3.6": {
"name": "unsloth/Qwen3.6",
"modalities": { "input": ["text", "image"], "output": ["text"] },
"limit": { "context": 128000, "output": 65536 }
}
}
}
}
}
几个要点:
- llama.cpp 就是走 OpenAI 兼容协议。只要 llama-server 暴露了
/v1/chat/completions,一个npm: "@ai-sdk/openai-compatible"就能接上,背后是什么推理引擎完全透明。 - 务必声明
limit.context/limit.output。官方 llama.cpp 示例就是这么配的——告诉 OpenCode 真实的上下文窗口上限,它才能合理规划,否则 agent 会在毫不知情下把上下文塞爆。 modalities要如实声明。我的 Qwen3.6 支持图片输入,于是显式声明input: ["text", "image"]——这样终端里拖入设计稿,模型才真正能读图。但这同时也带来了崩溃风险,见坑 2。
云端我只在 auth.json 里保留了一个 DeepSeek 作为兜底(deepseek-v4-flash / deepseek-v4-pro 等)。日常主力是本地模型,重活偶尔切到云端。
三、避坑:本地模型在真实工程里的四个坑
先说一个前提:下面这些问题不全是本地模型的锅。有些是本地推理资源的物理限制,有些其实是 OpenCode 产品/架构层面的特性——本地模型只是让它们更早、更频繁地暴露出来。每个坑我都会标注归因(本地特有 / 本地为主 / 混合),以及对策的实际效力(真解决 / 缓解 / 取决于环境)。产品层面的共性问题单独放在第四节「扩展」里讲。
坑 1:重复任务 / 复读机(本地为主)
症状是:
- 重复描述同一任务:同一段任务描述被反复输出,却迟迟不执行。
- 重复执行同一操作:连续多次调用同一个工具、运行同一条命令、修改同一文件同一位置,产出却没有任何推进。
- 重复写入:同一段代码/文本被插入多遍。
深入看,根因分两层:
- 架构层面(产品):agent 循环本质是"无状态编排 + 上下文回放",框架不维护独立的"任务状态",模型只能从不断变长的转录里反推自己做过什么。这触发了已知的 Lost in the Middle 现象——任何规模的模型在足够长的上下文里都会退化。这一层换云端也躲不掉,详见第四节扩展。
- 模型大小决定阈值(本地):小模型指令遵循、工作记忆都更弱,注意力在更短的上下文长度上就开始稀释,所以复读来得更快更频繁;大模型只是把失忆阈值推得更远。这解释了为什么线上旗舰"看起来没这个问题"——它只是还没到自己的阈值。
所以换更大的模型只是推迟,架构级缓解才治本:
{
"agent": {
"build": { "steps": 30, "temperature": 0.2 },
"plan": { "temperature": 0.1 }
},
"permission": { "doom_loop": "ask" }
}
- 真解决——
steps限制最大迭代轮数:官方文档明确这是"为想控制成本的用户设一个 agentic 操作上限",达到上限后强制转纯文本总结。它能终止死循环,但不能防止重复发生——上限内该复读还是复读,它是安全阀不是解药。 - 真解决——缩短"回放"的上下文:失忆源于上下文太长,压缩就对症。
compaction/prune自动压缩转录、裁掉旧工具输出(配置见坑 3);explore/scout子代理把上下文隔离成小块、只回传摘要;把大任务拆成单步指令,每步独立重推,模型无需记住更早的步骤。这是治本手段,代价是压缩会丢细节(以信息换长度)。 - 缓解——先 Plan 再 Build:把计划"钉"在上下文里,执行时模型照单走,减少即兴重推。对复读有抑制作用,但不保证。
- 缓解——调低
temperature:减少发散有真实作用,但对"复读"这类结构性失败效果有限。遇到循环立刻中断并用/undo回退,才是实际最可靠的操作手段。 - 取决于环境——
doom_loop权限:它触发"卡住恢复提示",但提示发出去后,小模型能不能顺着走出来取决于模型本身——可能有用,也可能只是再复读一轮。
坑 2:多图输入导致 llama.cpp 崩溃(本地特有)
本地模型接了 image modality 后,有一个很现实的崩溃场景:一次上传几张图片,llama-server 直接崩掉。原因通常是视觉输入的 embedding 与 KV cache 急剧膨胀,撑爆了显存/上下文,llama.cpp 没有优雅降级就直接 OOM。这是纯本地问题——云端多模态按需扩容,不存在这个瓶颈。先声明:下面的对策没有一条能保证不崩。
{
"attachment": {
"image": {
"auto_resize": true,
"max_width": 2000,
"max_height": 2000,
"max_base64_bytes": 5242880
}
}
}
- 真解决(彻底回避)——限制图片数量 + 多图切云端:一次只给一两张关键图;密集多图理解直接切 DeepSeek 等云端多模态。这是唯一"确保不崩"的办法,代价是放弃本地多模态。
- 缓解——
auto_resize预压缩图片:发送前把超限图片缩放/降体积(默认 2000×2000、base64 5MB),减少视觉 token → 降低 KV 压力,能降低崩溃概率,但不保证不崩——如果 OOM 出在视觉投影层或显存本身紧张,压图也救不了。 - 取决于环境——
limit.context留余量、--ctx-size/--n-gpu-layers:都是真实参数,能腾出显存/上下文,但怎么调取决于你的显存与模型大小,没有银弹数值,需要对着崩溃日志逐项试。
坑 3:多窗口互相影响,上下文同时过载(本地为主)
另一个我反复踩的坑:opencode 同时开多个窗口会话,它们会互相影响——多个 agent 并行对着同一个本地推理服务器吐 token,显存和 KV cache 被瓜分,最终"同时出现上下文窗口过大",表现为多个会话一起变慢、报上下文溢出或干脆崩溃。
归因同样是两层:本地层面是共享同一块 GPU 的物理限制(云端按需扩容,不存在);产品层面是各窗口各自维护独立上下文、没有统一预算协调(这条换云端也存在,见第四节)。
{
"compaction": {
"auto": true,
"prune": true,
"reserved": 10000
}
}
- 真解决——控制并发窗口数 + 窗口间任务隔离:本地模型算力是共享的,同一时刻主力窗口最多两三个、每个窗口只专注一件事。这是釜底抽薪,但对"你就是要并行干活"的场景等于没用。
- 缓解——开启自动压缩:
compaction.auto(默认 true)在上下文将满时自动把长会话压成摘要;prune: true裁掉旧的工具输出省 token;reserved预留 buffer 避免压缩瞬间溢出。它能把"溢出"推迟,但压缩丢细节,是以信息换长度。 - 取决于环境——
limit.context:告诉模型真实上限,它才不会被并发请求推爆;但模型会不会严格遵守这个上限,依赖模型本身的指令遵循能力。
坑 4:中途停摆与挂起(混合)
本地模型另一个高频问题,是我经历过的两种"卡死"。要先把它们区分开,因为不是同一个问题、解药也不同:
- 中途停摆(本地为主):agent 跑着跑着突然不往下推了——既不报错也不产出。通常是一次推理请求迟迟没有返回(llama-server 忙于长序列解码、或流式响应中途断了),而客户端还在干等下一个数据块。
- 挂起后输入无效(产品为主):启动服务后整个会话被挂住,终端输入不再响应,只能手动停掉进程再重开。这更像 OpenCode 进程自身的问题(TUI 假死),换云端模型同样可能发生,详见第四节。
{
"provider": {
"local": {
"options": {
"timeout": 120000,
"chunkTimeout": 30000
}
}
}
}
- 真解决——
timeout/chunkTimeout(仅针对"中途停摆"):文档明确"无新 chunk 到达即中止请求"。这能真解决网络等待导致的无限挂起——但注意,它把"挂起"变成"报错",让你能重试,不修复服务端本身。 - 无法靠配置解决——"挂起后输入无效":如果挂的是网络等待,上面的超时有效;但如果挂的是 opencode 进程本身,任何客户端配置都救不了,只能 kill 掉重启。可以这样区分:挂起时
curl http://192.168.1.3:8001/v1/models还通 = 服务端正常、是进程假死;不通 = 服务端先卡死。 - 缓解——配好服务端:超时频繁触发时回去看 llama-server——单次请求是不是太长、
--ctx-size是否过大拖慢解码、显存是否已被多窗口占满(回到坑 3)。
四、扩展:哪些"坑"其实是 OpenCode 产品 / 架构的限制
有些问题我在使用中一度归咎于"本地模型不行",后来发现换成云端旗舰模型照样存在——它们其实是 OpenCode 产品/架构层面的特性,本地模型只是把触发时机提前、把出现频率放大。判断标准就一句话:换云端模型后,这个问题是否仍然会发生?
1. 无状态回放架构 → 长上下文失忆
agent 循环不维护独立的"任务状态",模型只能从不断变长的转录里反推自己做过什么。这是坑 1 复读问题的架构根因,任何模型在足够长的上下文里都会退化(Lost in the Middle)。区别只在于:云端大模型阈值高、平时感知不到;本地小模型阈值低、日常就能撞上。对策就是坑 1 里的 steps、压缩与子代理隔离——这不是"配置能根治"的问题,而是架构使然,只能靠工程实践补偿。
2. 会话不沉淀为知识 → 新会话重新踩坑
模型没有跨会话记忆,当次会话的经验和教训留不在任何地方,新会话又从零开始。这不是本地模型的坑,是产品没有"自动学习"机制——/share 只生成分享链接,compaction 的摘要只留在当前会话内,都不会沉淀给新会话。所以知识持久化只能靠人主动做,OpenCode 提供的通道:
- 真解决——经验回写 AGENTS.md 并提交:官方钦定的跨会话知识通道。每次会话结束,把踩的坑、有效的配置、让模型出错的指令习惯补进项目根目录
AGENTS.md的 Gotchas 节,新会话启动时会自动读入——等于带着上次的教训开工。配合/init可原地完善,不用手写格式。 - 真解决——个人经验放全局
~/.config/opencode/AGENTS.md:与项目无关的通用教训放这里,所有会话、所有项目生效。 - 真解决——把高频流程固化成命令/子代理:重复性的工作流写进
.opencode/commands/或.opencode/agents/,把"过程知识"固化在文件里,下次一句话即可复用。 - 缓解——
instructions引用独立经验文件:用opencode.json的instructions字段挂docs/opencode-lessons.md(支持 glob 和远程 URL),模块化维护、团队共享。 - 无法自动解决——别指望工具自己攒经验:落不落盘完全取决于你有没有写。AGENTS.md 就是 agent 的"永久记忆",你越勤于回写,新会话踩坑越少。
3. 多窗口各自独立上下文,无统一预算协调
坑 3 里被"同时过载"的那部分,底层是产品问题:每个窗口各自维护一份完整上下文,OpenCode 不做跨会话的上下文预算协调。云端靠服务端按需扩容掩盖了这一点;本地只有一个 GPU,立刻暴露。对策只能是坑 3 的"窗口纪律"——控制并发数、任务隔离。
4. TUI 假死 / 挂起输入无效(疑似产品 bug)
坑 4 里"输入无效只能 kill 进程"的部分,更可能是 OpenCode 客户端自身的问题而非模型。它不区分本地/云端,配置也救不了,只能杀掉重启。这类问题建议到 GitHub 提 issue,而不是在配置里找解药。
小结:本节列的四条,都是"换模型解决不了"的问题。与其反复折腾模型,不如认清它们是产品边界,用工程实践(AGENTS.md 沉淀、窗口纪律、及时重启)去补偿。
五、最佳实践
1. 用 /init 建立并提交 AGENTS.md
/init 会扫描仓库,自动生成一份简明扼要的 AGENTS.md,涵盖构建/测试命令、架构、工程约定与坑点。它相当于把"项目背景"一次性喂给模型。关键:把它提交到 Git。 这样团队共享,CI 里的 agent 也能获得一致的项目认知。个人偏好放 ~/.config/opencode/AGENTS.md,并可从 Claude Code 迁移兼容 CLAUDE.md。
2. 把探索交给子代理
内置子代理 explore(只读检索代码库)与 scout(只读调研外部依赖)。大任务先 @explore 摸清结构,再由主 agent 汇总。你还能用 markdown 自定义子代理(放 .opencode/agents/ 或 ~/.config/opencode/agents/),例如只读的 code-reviewer(permission: edit: deny)。
3. 用 MCP 补齐 agent 的能力边界
模型再强,不接外部系统也只是"会写代码的编辑器"。我的配置里接了两个 MCP:远程的 mcp_backend(http://localhost:9500/mcp/api)接入后端服务,本地的 mcp_browser(node 直接启动)做浏览器自动化。注意 mymcp_* 通配符可在权限里一次禁用某个 MCP 的全部工具。
4. 权限安全:别用默认全放行
OpenCode 默认允许所有操作而无需确认。生产环境建议收紧:
{ "permission": {
"edit": "ask",
"bash": { "*": "ask", "git status *": "allow" }
} }
bash 支持 glob,git push、rm -rf * 高危命令可按需拦截。本地模型指令遵循弱,权限这道闸更重要——它能把"重复任务"造成的破坏挡在确认环节。
5. 理解配置层级与其他开关
配置文件是合并而非覆盖,优先级从低到高:远程(组织默认)→ 全局 → 自定义 → 项目 → .opencode/ → 托管。所以个人习惯放全局、项目规范放仓库根的 opencode.json 并入库。值得开启的还有:formatter(自动格式化)、lsp(类型感知)、自定义 /command、small_model(廉价模型干标题生成等轻活)。
六、本地模型 vs 线上模型:我的实测对比
基于我用 Qwen3.6(本地 llama.cpp)与 DeepSeek(云端)的实际体感:
| 维度 | 本地模型(llama.cpp + Qwen3.6) | 线上模型(DeepSeek 等) |
|---|---|---|
| 代码质量 | 够用,但复杂重构与多文件联动明显吃力 | 更强,长上下文与跨模块改动更稳 |
| 工具调用稳定性 | 偶发不遵循工具格式,需要重试 | 稳定,agent 流程更顺 |
| 失败模式 | 复读频繁、多图 OOM、并发资源争抢 | 复读阈值高、无 OOM、并发按需扩容 |
| 多模态 | 支持但脆弱,多图易崩 | 依模型而定,通常更稳 |
| 数据隐私 | 全部留在局域网,合规无忧 | 源码需发送到第三方 |
| 依赖 | 依赖局域网 GPU 服务器,离了内网不可用 | 仅需外网,走到哪都能用 |
| 成本 | 硬件一次性投入,用量免费 | 按 token 计费,高频使用累积可观 |
注:表中的"失败模式"差异只是阈值与频率的差异——产品/架构层的四条限制(第四节)对本地和云端是一视同仁的。
我的选型结论
- 主力开发:本地模型足够覆盖日常 CRUD、单文件改动、读代码,且无限试错不要钱——但前提是把第三节四个坑的对策配好,并接受第四节产品边界的客观存在。
- 重型任务(大规模重构、跨模块架构调整、密集多图理解):切到云端 DeepSeek 或更强的商业模型。
- OpenCode 允许为每个 agent 单独指定
model,于是最实用的混合方案是:主 agent 用云端强模型做实现,explore/scout这类高频只读子代理切本地模型省钱。
七、结语
OpenCode 的价值不在于"多一个 AI 面板",而在于 AGENTS.md 规则、Plan/Build 双模式、可编排的子代理、MCP 与细粒度权限——这些把它的定位从"聊天工具"变成了"可配置的工程 agent"。接本地模型尤其如此:steps 卡复读、attachment 压图片、compaction 管上下文、limit 划边界、timeout 断挂起,再配合 AGENTS.md 把经验沉淀到下一个会话。但也要分清哪些问题值得折腾配置、哪些是产品边界——本地模型为你省下 token 费,同时也把这些产品限制照得最清楚。模型选型没有银弹:要隐私与免费选本地,要质量与稳定选线上,而真正高效的团队大多像我一样走"本地为主、云端兜底"的混合路线。