JOTO
联系我们
← AI 智库
大语言模型

Pi、OpenCode、DSH架构对比:Harness把复杂度放在哪里

2026 年 8 月 19 日

文章对比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 默认给模型的工具只有 readbasheditwrite。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 记录带 idparentId,当前 leaf 决定发给模型的活动路径;/tree 可以在同一会话文件中切换分支,/fork/clone 则建立新的会话文件。这解决的是历史导航和上下文选择,不是插件依赖图。把两者混在一起,排障时很容易找错方向。

Pi 与 DSH 把复杂度放在不同位置
Pi 与 DSH 把复杂度放在不同位置

安全边界同样要单独算。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.messagechat.paramstool.execute.before/afterpermission.askshell.env 和事件流,也可以注册工具、Provider 和认证逻辑。插件状态由 InstanceState 按目录创建,Instance 释放时调用 dispose()。它不像 DSH 那样把每个插件 Fiber 和依赖关系公开成一张运行时图,但它的作用域并不是全局变量,而是项目 Instance。

权限也在产品边界内处理。OpenCode 会按工具和路径匹配规则,返回 allowdenyask;待审批请求和已经批准的规则都属于当前 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.fsctx.shellctx.llmctx.web 都可以沿这条能力边界替换,工具不必知道后面是本地实现还是远程沙箱。

Session event log 记录执行事实。turn/startstep/startassistant/*tool/calltool/result 这些事件可以回放和投影;agent/*llm/streamtools/* 则给运行中的拦截和策略留下入口。官方的不变量是 Model-visible means logged:进入模型请求的内容必须能从日志重建,但日志里的每个事件不必都发给模型。

DSH 的运行图与事件流
DSH 的运行图与事件流

把这三件事分开,很多现象就容易解释了:插件树回答“现在装了什么”,能力图回答“谁依赖谁”,事件日志回答“刚才发生了什么”。

还要分清进程和会话的配置。webheadless 是 Runtime Profile,决定进程以什么形态运行;standardcodeminimalcordis 是 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 还留在进程里,是非常实际的故障。

DSH 在工具结果未知时如何恢复
DSH 在工具结果未知时如何恢复

选择的依据

任务只需要一条短而透明的 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,源码和插件接口可能继续变化。文中架构判断以已核对的源码、官方文档和公开讨论为准,不把一次测试结果当作普遍排名。
Pi、OpenCode、DSH架构对比:Harness把复杂度放在哪里 配图 4

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 落地咨询

想把这些做法用到你的业务里?

留下你的场景和痛点,我们帮你判断从哪一步开始。

联系我们
联系我们

开启企业级 AI 落地

留下你的行业、部门和当前痛点,我们会在 1 个工作日内与你联系,帮你判断适合先做什么、需要准备哪些数据、适合什么平台。

微信咨询
扫码添加,一对一沟通
JOTO 微信咨询二维码
发送邮件
jotoai@jototech.cn

填写需求单

收到你的信息后,我们将在 1 个工作日内与你取得联系。