opencode 开源终端编码 Agent 拆解:服务端分离、双主智能体与权限规则

2026-08-04

本文基于 opencode 仓库 commit 7fe9938(2026-08-04)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/anomalyco/opencode 最新代码与文档为准。

读 opencode 这个仓库,真正值钱的不是它有多少个内置工具,而是它把「这次操作要不要经你点头」做成了一层可以写死在配置里的规则,而不是靠系统提示词求模型自觉。 这一点决定了你把它放进一个真实工程仓库时,敢开到什么程度、出事之后能不能复盘。其余的形态问题——它是终端界面还是桌面端、用哪个模型、装哪个包管理器——都排在这条后面。

下面按四节走:先看它把进程拆成了什么,再看两个主智能体和三个子智能体的分工,然后是权限规则这块硬骨头,最后是边界与上手清单。站内已经有三篇相邻文章,分工不同:开源 Agent 项目三题对比 比的是项目层面的取舍,Hermes 四方对比 以 Hermes 为轴心展开,Agent 框架对比 谈的是框架选型方法;本篇只做一件事,把 opencode 自己这一个仓库读透,附带一节跨仓库的设计取向对照。

一、它先把「界面」和「跑 Agent 的那个进程」拆开了

仓库 packages/ 下有 32 个包。这个数字本身不说明什么,但里面几个包的分工很说明问题:packages/tui 是终端界面,packages/server 是服务端,packages/desktop 是桌面端,packages/sdkpackages/sdk-next 是给外部调用用的,packages/web 装的是文档站。

文档 packages/web/src/content/docs/server.mdx 里把这个关系讲得很直白:你运行 opencode 时,它同时起了一个 TUI 和一个服务端,TUI 是客户端,服务端暴露 OpenAPI 3.1 规范端点,SDK 就是从这个端点生成的。你也可以只跑服务端:

opencode serve [--port <number>] [--hostname <string>] [--cors <origin>]

默认端口 4096,默认主机名 127.0.0.1。如果要给服务端加口令,设置 OPENCODE_SERVER_PASSWORD 环境变量走 HTTP 基本认证,用户名默认是 opencode,可以用 OPENCODE_SERVER_USERNAME 覆盖。

这对你意味着什么?意味着「换个界面」和「换个 Agent」在这个项目里是两件事。终端里那套按键、桌面端、IDE 扩展,面对的是同一个服务端;你要写个脚本批量跑任务,也是接同一个 HTTP 面。反过来说,这也意味着你在本机跑它的时候,机器上是有一个监听端口的进程在的——默认只听回环地址,一旦你为了在另一台机器上开界面而改了 --hostname,那口令就不是可选项了。

二、build 与 plan:两个主智能体,不是两个「模式」

README.md 里写得很短:内置两个智能体,用 Tab 键切换。build 是默认的、全量权限的开发智能体;plan 是只读的分析与代码探索智能体,默认拒绝文件编辑,运行 bash 命令前会先问。文档 agents.mdx 把这套体系摊开讲:智能体分主智能体(primary)和子智能体(subagent)两类,内置两个主智能体 build 与 plan,三个内置子智能体 general、explore、scout。general 是通用型、除 todo 外有全量工具访问,explore 是只读的快速代码库探索,scout 是只读的外部文档与依赖调研——它会把依赖仓库克隆进 opencode 管理的缓存目录里看源码,而不动你的工作区。

另外还有三个隐藏的系统智能体:compaction(把长上下文压成摘要)、title(生成会话标题)、summary(生成会话总结)。它们自动运行,界面里选不到。

子智能体不是在同一条对话里继续说话,它们会开子会话。文档给了导航键位:session_child_first 进入第一个子会话,session_child_cyclesession_child_cycle_reverse 在子会话之间来回切,session_parent 回到父会话。这个细节值得记住——不知道有子会话这回事的人,会在父会话里翻半天找不到子智能体到底干了什么。

自定义智能体有两种写法。JSON 写在 opencode.jsonagent 字段下;Markdown 则放在全局的 ~/.config/opencode/agents/ 或项目级的 .opencode/agents/,文件名即智能体名。文档里这份示例是完整可抄的形状:

---
description: Code review without edits
mode: subagent
permission:
  edit: deny
  bash:
    "*": ask
    "git diff": allow
    "git log*": allow
    "grep *": allow
  webfetch: deny
---

Only analyze code and suggest changes.

description 是必填项,因为主智能体是靠描述来判断什么时候该把活派给它的。mode 可以是 primarysubagentall,不写默认 allsteps 用来限制单次能做多少轮智能体迭代,达到上限后模型会收到一段特殊系统提示,要求它总结已完成的工作和剩余任务——这是个成本闸门,不是正确性闸门。另外 hidden: true 可以把子智能体从 @ 自动补全里藏起来,但藏起来不等于禁用,模型仍能通过 Task 工具调它。

还有一个容易被忽略的字段:permission.task。它控制的是「某个智能体能不能派活给哪些子智能体」,用 glob 匹配。设成 deny 时,那个子智能体会被整个从 Task 工具的描述里摘掉,模型压根看不到它。这是把编排权限也纳入同一套规则的做法。

三、权限规则:三态、模式匹配、最后匹配的那条赢

这是整个仓库我建议你花最多时间读的一块,对应文档 packages/web/src/content/docs/permissions.mdx

规则解析成三种动作之一:"allow" 直接跑、"ask" 弹出确认、"deny" 直接拦。可以全局用 * 设一个基线,再按工具名覆盖:

{
  "$schema": "https://opencode.ai/config.json",
  "permission": {
    "bash": {
      "*": "ask",
      "git *": "allow",
      "npm *": "allow",
      "rm *": "deny",
      "grep *": "allow"
    },
    "edit": {
      "*": "deny",
      "packages/web/src/content/docs/*.mdx": "allow"
    }
  }
}

三个必须记住的语义。第一,规则按模式匹配求值,最后一条匹配上的规则获胜——所以兜底的 "*" 要写在最前面,具体规则往后排。这跟很多人写防火墙规则的习惯正好相反,写反了就是「我明明配了 rm 拒绝,怎么还是跑了」。第二,通配符是简单匹配:* 匹配零个或多个任意字符,? 匹配恰好一个字符,其余字符按字面匹配。第三,bash 规则匹配的是解析后的命令,写 "grep" 只对裸命令生效,带参数就落不到这条上,得写 "grep *"

可配的权限键按工具名走,另外带两个安全护栏:readedit(覆盖 editwriteapply_patch)、globgrepbashtaskskilllspquestionwebfetchwebsearch,加上 external_directorydoom_loop。后两个是护栏:external_directory 在工具碰到项目工作目录之外的路径时触发;doom_loop 在同一个工具调用带着完全相同的输入重复 3 次时触发。

默认值这一段请务必读原文:大多数权限默认是 "allow",只有 doom_loopexternal_directory 默认 "ask"read 默认允许,但 .env 类文件默认被拒(*.env*.env.* 拒绝,*.env.example 放行)。也就是说,开箱状态下它是偏信任的,边界画在「你启动它的那个工作目录」上。

--auto 这个启动参数只做一件事:把原本会来问你的请求自动通过,显式的 "deny" 规则仍然生效。TUI 里也可以从命令面板开关,开启时提示行的智能体旁边会显示一个灰的 auto 标记。

"ask" 弹出来时有三个选项:once 只批这一次,always 批准后续匹配建议模式的请求(只在当前会话内有效),reject 拒绝。always 会批准哪些模式是由工具自己给的,比如 bash 通常会把一个安全的命令前缀白名单化。这个设计的代价在下一节说。

跨目录取证要靠 external_directory,文档特意强调了一句:~$HOME 开头只是模式的写法展开,并不会把外部路径变成当前工作区的一部分,工作目录之外的路径仍然必须经 external_directory 放行。而且被放行的目录会继承当前工作区的默认值——既然 read 默认 allow,那些目录下的读取也就跟着放行了,要限制得再补显式规则。

四、仓库里这些东西各自在哪

组成部分它负责什么仓库位置你什么时候会碰到它
内置工具实现read/edit/grep/glob/shell/lsp/task/skill/webfetch 等工具的代码与各自的说明文本packages/opencode/src/tool/(25 个 .ts + 15 个 .txt想知道某个权限键到底管住了哪些行为时
分模型的系统提示词按模型家族分文件的提示词,另有 plan 模式专用的几份packages/opencode/src/session/prompt/(14 份 .txt换模型后行为变了、想知道差在哪时
服务端暴露 OpenAPI 端点,界面与 SDK 都接它packages/serveropencode serve、写自动化脚本时
终端界面你日常打交道的那个 TUI 客户端packages/tui每天
桌面端独立的桌面应用形态packages/desktop不想常驻终端时
SDK给外部程序调用的封装,由 OpenAPI 端点生成packages/sdkpackages/sdk-next把它嵌进自己的流水线时
文档站源文全部英文文档packages/web/src/content/docs/(36 份 .mdx任何时候——遇事先读这里,别猜
项目自身的开发规约这个仓库对贡献者和 AI 助手的要求根目录 AGENTS.md想给它提 PR,或想抄一份规约写法时

顺带一提,根目录有 21 份 README 翻译(README.zh.md 是简体中文那份),全仓有 6358 个受版本控制的文件。许可证是 MIT(LICENSE,Copyright 2025 opencode)。

它自己的 AGENTS.md 也值得读一遍,因为那是一份写给模型看的工程规约样本:默认分支是 dev;分支名不超过三个单词、用连字符、不许 feat/ 这类前缀;提交与 PR 标题用 type(scope): summary;代码风格上要求少用 try/catch、避免 any、避免 else、优先早返回、少做无谓解构、不给单次使用的逻辑预先抽小函数。这些规矩你未必认同,但它演示了一件事——把团队口味写成模型能执行的条款,比在每次对话里重复口头强调更划算。这个思路可以直接迁移到你自己的项目:把改动边界、命名规矩、提交格式写成条款,放进仓库根目录。

五、和另外四个开源项目摆一起看:五处设计取向差异

这一节的每一条差异,两边都在各自仓库的文件里找得到依据,不排座次。

一、边界画在配置层还是进程外。 opencode 把权限做成配置里的一等公民(permissions.mdx 整篇都在讲三态规则与模式匹配)。而 pi 的 README.md 里写得很干脆:Pi 不包含用于限制文件系统、进程、网络或凭据访问的内建权限系统,默认以启动它的用户和进程的权限运行;需要更强边界就去容器化,README 指向 packages/coding-agent/docs/containerization.md 里的三种模式(Gondolin 扩展、纯 Docker、OpenShell)。同一个问题,一个在配置层解,一个交给操作系统解。

二、往核心里加工具的门槛。 hermes-agent 的 AGENTS.md 里有一节叫 The Footprint Ladder,明确按「扩展现有代码 → CLI 命令加技能 → 服务门控工具 → 插件 → MCP 服务器 → 新核心工具」排优先级,理由写在那里:每加一个模型工具都会在每次 API 调用里发送出去,所以核心工具是最后手段。opencode 走的是另一条路——工具实实在在铺在 packages/opencode/src/tool/ 里,收紧动作发生在下游,靠 permission 和每个智能体的配置决定谁能调、能调到什么粒度。

三、操作对象不一样,定位也就不一样。 browser-use 的 README.md 第一句就是让 AI 智能体像你一样使用浏览器——开页面、点按钮、打字、填表单,示例任务是填求职申请、导出关注者数据、给本地网站做 QA。而 opencode 的工具目录里是 read、edit、grep、glob、shell、lsp,面向的是代码仓库。更有意思的是 browser-use 把自己定位成能力供给方:README 的 Quickstart 直接让你把一段提示贴给你已有的智能体,让它自己 browser-use skill install 注册技能。

四、自己是宿主,还是装进别人的宿主。 ECC 的 README.md 把自己称作装进各种 harness 的工程系统,安装表里列着 Cursor、OpenCode、Gemini CLI、Zed、Hermes 等目标,OpenCode 那一行是 npm install && npm run build:opencode && ./install.sh --profile full --target opencode,仓库里还有 .opencode/ 目录放插件、命令和说明。opencode 自己则是被安装的那一端——它是宿主。这不是高下问题,是分层问题:一个提供循环与工具,一个提供方法论与流程。

五、多端指的是什么端。 hermes-agent 的 AGENTS.md 里,多端是把同一个智能体核心接到 CLI、消息网关(Telegram、Discord、Slack 等约二十个平台)、TUI 和 Electron 桌面应用;它的 gateway/platforms/ 下一个平台一个适配器。opencode 的多端是围绕同一个工作区展开的:终端界面、桌面端、IDE 扩展、HTTP 服务端。前者的轴是「你在哪跟它说话」,后者的轴是「你在哪个仓库里干活」。

六、边界与代价:它明确不管的那些事

默认是偏信任的。 大多数权限默认 allow,这意味着装好就用的状态下,它能读你工作目录里的文件、能改、能跑 shell 命令。这类工具会在你的机器上执行命令、直接改你的代码文件、把代码内容发给你配置的模型服务商——三件事都是真的。误删误改、私有代码外泄面、密钥被读走,都不是理论风险。.env 默认拒读是个好起点,但那条规则挂在 read 这个键上,bash 是另一个键;两个键各管各的,不互相兜底。要挡就两边都写。

always 是会话级的宽恕,不是精确授权。 批一次 always,接下来这个会话里匹配那组模式的请求都不再问你。模式是由工具建议的,不是你逐条挑的。长会话里这条很容易累积成「其实已经全放开了」而你没察觉。

doom_loop 是防呆不是防错。 它触发的条件是同一个工具调用带着完全相同的输入重复 3 次。参数稍有变化的死循环、或者方向错了但每次输入都不同的空转,它不管。真要止损,得靠你自己在外面设判据:盯轮次、盯改动量、盯同一个文件被反复覆写的次数。

它不替你管模型服务商那一侧。 你的代码内容要发到你配置的提供商去,各家在数据保留、训练使用上的规则不同且会调整,以官方最新说明为准,别拿别处的结论套。/share 会给当前会话生成一个链接并复制到剪贴板,文档明确写了会话默认不共享——但按下去之后就是公开链接,团队里要不要允许这个动作,是你的制度问题不是它的功能问题。

它不替你定义什么叫「改完了」。 /init 会让它分析项目并在根目录生成 AGENTS.md,文档建议你把这个文件提交进 Git。但里面写什么、验收标准是什么,仓库不给你,得你自己写。

跨目录不是默认能力。 external_directory 默认 ask,工作目录之外的路径必须显式放行。这对需要跨仓库取证的场景是摩擦;但反过来,这也是它为数不多的默认收紧项,别为了省事一把放行整个家目录。想把隔离做扎实,参考 工作区隔离 里的分仓与只读挂载做法。

七、上手与避坑清单

1. 权限规则写反顺序。 会踩是因为大多数人按「具体在前、兜底在后」的习惯写规则。这里是最后一条匹配的赢,兜底 "*" 写在最后会把前面所有具体规则盖掉。怎么避:"*" 永远第一行,具体规则往下排,改完拿一条真实命令试一次。

2. bash 规则漏掉参数通配。 会踩是因为 "grep""grep *" 在这套匹配里不是一回事,前者只对裸命令生效。怎么避:凡是会带参数的命令,一律写成 "命令 *" 的形式;文档里那条提示专门讲了这点。

3. 把 --auto 当成「省事开关」。 会踩是因为它读起来像自动模式,实际语义是把所有原本要你点头的请求静默通过。怎么避:陌生代码库先用 plan 主智能体走一遍(Tab 切换),确认它的动作范围符合预期,再决定 build 智能体开到什么程度;把 rm *git push * 这类写成显式 deny,因为 deny 在 auto 模式下仍然拦得住。

4. 以为 .env 默认拒读就等于密钥安全。 会踩是因为那条默认规则只作用在 read 这一个权限键上。怎么避:bash 单独配规则,别指望一个键的默认值给另一个键兜底;更稳的做法是让开发环境里根本没有生产密钥,思路见 最小权限设计

5. 被 external_directory 弹窗烦到直接放行家目录。 会踩是因为默认 ask,跨目录读几次就烦。怎么避:在 permission.external_directory 里写具体路径模式(比如某个只读参考仓库的目录),并且记住被放行的目录会继承工作区默认值,需要禁改就再补一条 editdeny

6. 把服务端暴露出去却没设口令。 会踩是因为为了在别的设备上连界面,顺手改了 --hostname。怎么避:改主机名的同时必须设 OPENCODE_SERVER_PASSWORD,用户名要换就设 OPENCODE_SERVER_USERNAME--cors 也只加你真正需要的来源,它可以传多次。

7. 在父会话里找子智能体的产出。 会踩是因为子智能体开的是子会话,父会话里看不到全过程。怎么避:记住 session_child_first 进子会话、session_child_cycle 切换、session_parent 返回这几个键位;派活之前想清楚你要的是 general(可改文件)、explore(只读探索)还是 scout(只读查外部依赖)。

8. 装新版之前没清理旧安装。 会踩是因为 README 顶部那条提示很容易被跳过——它明确要求安装前先移除更早的旧版本。怎么避:装之前先确认机器上没有残留的旧安装;README.md 的安装段落里同时列了脚本安装和各平台包管理器两条路,选一条,别叠着来。

收束:接下来该读哪个文件

如果你只打算再花二十分钟,按这个顺序读:先 packages/web/src/content/docs/permissions.mdx(决定你敢开到什么程度),再 packages/web/src/content/docs/agents.mdx 的 Permissions 与 Task permissions 两节(决定谁能派活给谁),最后扫一眼 packages/opencode/src/tool/ 的文件名(决定你写的权限键到底盖住了什么)。

动手前给自己过一遍这份自检:我的 "*" 兜底规则是不是写在第一行;rmgit push 这类不可逆动作是不是显式 deny 而不是 ask;这个仓库里有没有真实密钥,如果有,bash 那一侧是不是也挡住了;服务端有没有对外监听、有没有设口令;这次任务需不需要子智能体,需要的话我知不知道去哪看它的输出。这五条都答得上来,再让它动手不迟。

本篇属于一个把开源AI 编程 Agent 项目 opencode逐层拆开讲的系列,整体地图见 opencode 是什么:终端里的开源 AI 编程 Agent 全景地图;沿着这条线往下,还可以看 开源终端编码 Agent opencode:什么时候别用它干活终端 Agent 项目 opencode 仓库结构导读:32 个包里哪些是主干,改功能从哪进

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。