Pi、OpenCode、DSH架构对比:Harness把复杂度放在哪里
文章对比Pi、OpenCode和DSH三类Agent Harness架构在复杂度落点上的差异:Pi将内环做短,依赖Extension扩展能力;OpenCode通过Server统一边界,提供稳定API与多前端支持;DSH则将运行时可组合性推向极致,以插件树、能力图和事件日志支撑动态替换。三者并非版本迭代关系,而是面向不同工程诉求的分层设计选择。
最近一轮 Agent Harness 测试里,同一个 DeepSeek V4 Flash,接入同一套 MCP 工具,跑 30 个跨应用任务,结果并不一样:Pi 完成 20 个,OpenCode 完成 14 个;按成功任务计算,Pi 的单任务成本约 0.028 美元,Claude Code 是 0.195 美元。DSH 没有参加这轮测试,下面不把它和这组数字并列。
任务也不是单步写代码。比如从 Gmail 找出符合条件的工单,原样写进 Google 表格,补充账号信息,再把统计发到 Slack;中间有一批诱饵工单不能碰,已经排除的记录也不能改写。模型要在几个系统之间取数、核对、写入,还得记住哪些动作已经发生。
数字看起来像排名,背后其实是另一件事:工具说明、提示词、超时、缓存、重试和状态恢复,只要有一处不同,同一个模型就会走出不同的路径。真正托住这条路径的,是模型外面的 Harness。
这也是我比较 Pi、OpenCode 和 DSH 时最关心的地方:它们并不是同一产品的简化版、标准版和平台版,而是把复杂度交给了不同的层。
复杂度落点
把 Agent 想成一个会持续工作的研发同事,模型只负责在当前上下文里决定下一步。它并不知道某个文件句柄由谁创建,也不知道 Slack 消息是否已经成功送达。上下文怎么来、工具怎么执行、失败后如何恢复,都由 Harness 接住。
放在一起看,我会把它们的分工理解成三种放置方式:
- • Pi 管得很近,重点是一次任务的内环和 Extension 运行时;
- • OpenCode 把常用路径做成产品,并用 Server、Instance 和 Session 组织多种客户端;
- • DSH 继续往下管,连 Provider、Consumer、作用域、替换和退出都纳入运行时。
复杂度往下移,不等于系统就更好。它带来更多约束,也带来更多需要学习和排查的边界。
Pi:内环很短
Pi 默认给模型的工具只有 read、bash、edit 和 write。MCP、子 Agent、计划模式、审批界面等,并不是一开始就塞进核心,而是交给 TypeScript Extension 去接入。
Extension 的能力很深:可以注册工具、命令、Provider 和界面,修改提示词,监听会话事件,也能保存自己的状态。一个代码审查扩展可以启动语言服务器,另一个扩展可以监听文件变化;从调用点到实际执行,中间没有很长的框架链路。
换句话说,Pi 把内环做短,把工作流差异留给 Extension。四个默认工具覆盖读、改、跑、验证,更多能力则由扩展按项目需要长出来。
Mario Zechner 转发过一个 Pi 调 DeepSeek V4 Flash 的案例:近 10 亿输入 Token,缓存命中率 99.93%,总成本 2.65 美元。这个数字不能拿来替所有任务估算费用,但它说明了一件工程上的小事:请求前缀保持稳定,缓存命中时,成本差异可能比换一个模型还明显。
短内环的代价也很具体。扩展启动的语言服务器、文件观察器、PTY、临时目录和网络连接,都是扩展作者创建的资源,核心不会替它们推断所有权。
/reload 的实现能说明这个边界。Pi 会向旧 Extension runtime 发出 session_shutdown,重新加载设置、Provider、Extension、Skill 和提示词,构建新的 runtime,再发出 session_start(reason: "reload")。旧命令的调用栈不会因为 reload 自动消失;await ctx.reload() 之后继续使用旧的 ctx,就可能碰到已经失效的运行时。官方文档因此要求把 reload 当作当前 handler 的终点,长期资源在 session_start 创建,在 session_shutdown 幂等清理。
这套生命周期事件很有用,但它只提供边界,不负责替每个扩展写清理代码。扩展越多,依赖关系越靠约定、测试和代码审查维护,Pi 不会在运行时替你生成一张公共依赖关系。
Pi 的会话也有自己的“树”:每条 JSONL 记录带 id 和 parentId,当前 leaf 决定发给模型的活动路径;/tree 可以在同一会话文件中切换分支,/fork 和 /clone 则建立新的会话文件。这解决的是历史导航和上下文选择,不是插件依赖图。把两者混在一起,排障时很容易找错方向。

安全边界同样要单独算。Project Trust 能控制项目级设置和 Extension 是否加载,却不是操作系统沙箱。能够调用 bash、读取环境变量的扩展,权限仍接近运行 Pi 的用户;高风险任务要靠容器、虚拟机或远程执行环境补上隔离。
OpenCode:Server 承担边界
OpenCode 的核心选择不是“再提供几个工具”,而是把 Agent 做成一个有稳定服务边界的产品。
运行 opencode 时,TUI 是客户端,Agent、工具、会话和项目状态由 Server 提供;也可以单独运行 opencode serve,通过 OpenAPI 3.1 接口接入桌面端、IDE、脚本或 SDK。/event 提供 SSE 事件流,客户端不需要把一次任务堵在一个终端进程里。这个边界解释了 OpenCode 为什么能同时服务终端和其他前端,也解释了它为什么更像一个产品而不是一组可随意替换的库。
Server 内部按项目目录维护 Instance。源码里的 InstanceStore 会缓存同一目录的 Instance;加载时初始化配置、插件、LSP、格式化器、快照和 VCS 等服务,释放或 reload 时调用实例级 disposer,把这一目录下的状态一起清掉。两个目录可以各有一套状态,客户端只是通过 HTTP 请求找到对应 Instance。
插件也附着在这个边界上。插件从项目或全局配置加载,形成一组按顺序执行的 hooks;公开接口可以监听 chat.message、chat.params、tool.execute.before/after、permission.ask、shell.env 和事件流,也可以注册工具、Provider 和认证逻辑。插件状态由 InstanceState 按目录创建,Instance 释放时调用 dispose()。它不像 DSH 那样把每个插件 Fiber 和依赖关系公开成一张运行时图,但它的作用域并不是全局变量,而是项目 Instance。
权限也在产品边界内处理。OpenCode 会按工具和路径匹配规则,返回 allow、deny 或 ask;待审批请求和已经批准的规则都属于当前 Instance。这个机制解决的是用户确认和工具可见性,不是进程级隔离。插件仍在同一个 Node.js 进程里运行,能否访问外部资源,取决于部署环境和插件本身。
Session 则落在持久化层。它保存消息、消息 part、工具调用、成本、token、权限和父子关系,HTTP API 支持继续发送、abort、fork、revert 和恢复。OpenCode 的会话可以被不同客户端观察和操作,但它不是 DSH 那种“模型可见即已记录”的事件模型;很多运行时细节仍由 OpenCode 自己的 Session、Event 和数据库投影来定义。
代价也在这里:团队拿到的是统一入口、稳定 API 和一套现成工作流,Agent Loop、Session 投影和资源生命周期则更多跟着产品版本走。想改到公开 hooks 之外,通常要跟随上游实现,或者维护自己的分支。
Composio 测试里,OpenCode 完成 14 个任务,中位耗时 129.7 秒,Pi 是 132.2 秒,时间很接近。通过率的差异可能来自工具编排、提示词、重试或状态恢复;一次公开测试只能说明这组模型、工具和配置下的表现,不能给产品贴上永久标签。
DSH:运行时也可组合
DSH 关注的已经不只是 Agent Loop 本身。官方架构文档写得很明确:模型适配器、工具注册表、会话日志和 Agent Loop 都是插件。这里的“一切皆插件”不是说没有核心,Cordis 的 Context、Service、Fiber、事件和 Loader 仍然是运行时内核;变化的是,大部分 Agent 能力都能挂载、替换和撤销。
DSH 里有三种容易混淆的东西。
插件树描述这次进程实际装了什么。Profile 叠加 Bundle,再应用 Profile、用户目录和命令行 Patch,才得到最终配置。dsh --profile web --dump-config 打印的是这棵插件树;源码里存在某个包,不代表它已经在本次运行中启用。
能力图描述谁提供、谁使用一项能力。一个 Service Definition 定义接口,Provider 提供实现,Consumer 使用它。ctx.fs、ctx.shell、ctx.llm 和 ctx.web 都可以沿这条能力边界替换,工具不必知道后面是本地实现还是远程沙箱。
Session event log 记录执行事实。turn/start、step/start、assistant/*、tool/call 和 tool/result 这些事件可以回放和投影;agent/*、llm/stream、tools/* 则给运行中的拦截和策略留下入口。官方的不变量是 Model-visible means logged:进入模型请求的内容必须能从日志重建,但日志里的每个事件不必都发给模型。

把这三件事分开,很多现象就容易解释了:插件树回答“现在装了什么”,能力图回答“谁依赖谁”,事件日志回答“刚才发生了什么”。
还要分清进程和会话的配置。web、headless 是 Runtime Profile,决定进程以什么形态运行;standard、code、minimal、cordis 是 Agent Preset,决定某个会话里的工具和提示词组合。同一个 Web 进程可以承载多个会话,每个会话使用不同 Preset。运行中的父子任务沿用同一代 Preset,避免任务做到一半换了能力;跨重启要严格复现,还得固定源码、锁文件和 Preset 配置。
Provider 变化
Provider 换掉以后,运行时怎么处理,要看 Consumer 是否长期持有它。
对于 Web Search 这类无状态能力,Provider 登记在 WebRuntime 的 Map 里,search() 或 fetch() 调用时才按配置选择当前 Provider。卸载动作只是移除注册项,使用 ctx.web 的工具不需要全部重启;多个可用 Provider 同时存在时,运行时还会要求显式指定,避免依赖注册顺序。
对于 Shell、文件系统和可能被 Consumer 长期持有的 Service,处理就重一些:旧 Service 从解析空间消失,受影响的 Consumer 退出,旧 Provider Fiber 清理完,新 Service 出现后再满足依赖并激活 Consumer。插件通过 ctx.effect() 登记资源和 disposer,资源放进别的 Service 的 Map,也不会改变它原本属于哪个 Fiber。
effect 能撤销已经登记的监听器、句柄、子进程和注册项,但不能回滚已经发出的 Slack 消息、数据库写入或共享文件修改。DSH 把运行时资源的所有权写得更清楚,却没有把外部世界变成事务系统。
DSH 插件与宿主仍在同一个 Node.js 进程里,inject 是依赖声明,不是安全隔离。Profile、Patch 和能力图也不能替代容器、微型虚拟机、远程沙箱或审批系统。另一个现实边界是:DSH 仍处于 Developer Preview,插件接口、Preset 和启动配置还可能变化。
同一场迁移
把本地 bash 换成远程 sandbox,是研发团队经常会遇到的事情。三套 Harness 的差别,可以落到这条迁移路径上:
| 关注点 | Pi | OpenCode | DSH |
|---|---|---|---|
| 替换入口 | Extension 自己接 Provider | 取决于公开插件和 Server 接口 | Service Definition / Provider / Consumer |
| 资源清理 | session_shutdown 由扩展实现 |
Instance disposer 和插件 dispose() |
Fiber 的 effect disposer |
| 会话事实 | JSONL 会话与分支树 | Session 数据、消息和事件投影 | 追加式 Session event log |
| 主要排查材料 | 扩展代码、当前会话树 | Server API、Instance、Session | 最终插件树、能力图、事件日志 |
| 隔离边界 | 进程权限,需外部沙箱 | 进程权限,需外部沙箱 | 同样需外部沙箱 |
实际迁移时,最容易漏掉的是三件事:已经发出的消息怎样避免重复,旧 Provider 创建的连接和子进程由谁关闭,新 Provider 启动前哪些 Consumer 必须退出。文档里写“支持热替换”还不够;新配置生效、旧 PTY 还留在进程里,是非常实际的故障。

选择的依据
任务只需要一条短而透明的 Coding Agent 内环,Pi 的控制点最靠近代码,适合愿意自己维护 Extension 生命周期的人。团队更看重终端、IDE、桌面端共用一套入口和 API,OpenCode 的 Server/Instance 模型更省整合工作。
当 Provider、宿主、会话作用域和动态替换本身成了主要问题,DSH 的插件树、能力图和事件日志才值得承担学习成本。它提供的是一套更强的运行时约束,不是一个自动带来安全隔离的“万能底座”。
Composio 那组数字最后提醒我的,也不是 Pi 排在前面,而是 Harness 会改变模型的实际表现。落到生产系统,失败以后能不能恢复、Provider 变化时谁负责退出、插件越权时哪一层能拦住,往往比一次榜单名次更值得写进选型记录。
参考资料
- • Composio:DeepSeek V4 Flash Harness 测试
- • Mario Zechner 转发的 Pi + DeepSeek 缓存案例
- • DSH 社区对 Pi 与 DSH 的架构比较
- • OpenCode 官方 Server 文档
- • OpenCode 官方 Plugins 文档
- • OpenCode 官方仓库
- • DeepSeek Harness 官方仓库
- • DSH 源码提交
47f9438 - • DSH 架构文档
- • Pi Extension 文档
- • Pi 会话文档
Composio 数字对应其公开测试的任务、工具和价格设置;DSH 仍处于 Developer Preview,源码和插件接口可能继续变化。文中架构判断以已核对的源码、官方文档和公开讨论为准,不把一次测试结果当作普遍排名。

JOTO 企业落地观察
- 企业部署 AI Agent 时,若核心诉求是快速验证单点任务闭环(如自动化代码评审),Pi 的轻量内环与 Extension 生态可降低初期工程门槛,但需团队具备较强的 TypeScript 工程能力与运行时资源管理意识。
- 当企业已有多个前端载体(IDE、CLI、Web UI)且需统一管控权限、会话与成本,OpenCode 的 Server/Instance 模型提供了清晰的服务边界与 API 标准,但定制深度受限于其公开 hooks 范围,长期演进依赖上游版本节奏。
- DSH 的插件树与能力图设计,对企业构建可审计、可回滚的智能体运行时提出更高要求——它不简化复杂度,而是将复杂度显式建模为可组合、可替换的运行时契约,适用于已建立 RAG 知识工程规范、需精细控制 Provider 生命周期与事件溯源的中大型团队。
- 三类 Harness 均未内置进程级安全隔离,企业若需执行高风险操作(如生产环境数据库变更),必须在 Harness 外部叠加容器、远程沙箱或审批网关,不能依赖 Harness 自身的权限机制替代基础设施治理。
立即咨询 JOTO
JOTO 提供覆盖企业智能体规划与搭建、AI 平台私有化部署、RAG 知识工程、AI 安全治理、FDE 驻场共创及持续运营优化的全周期 AI 落地服务,帮助企业把验证中的 AI 能力转化为安全、可控、可持续迭代的生产力。 联系 JOTO 获取 AI 落地咨询
想把这些做法用到你的业务里?
留下你的场景和痛点,我们帮你判断从哪一步开始。
联系我们


