同样叫 Harness,DeepSeek Harness 和 Pi 根本不在同一层

0 阅读18分钟

从源码看 Agent 工程的两条路线,以及它们为什么根本不在同一层

如果只看 README,DeepSeek Harness 和 Pi 很容易被归为同一类产品:都能连接模型、调用工具、操作文件、执行命令、保存会话,也都强调扩展能力。

但把源码调用链展开以后,会看到两套明显不同的工程哲学:

DeepSeek Harness 首先是一个“插件化 Agent 平台”;Pi 首先是一个“可嵌入的轻量 Agent 内核与 Coding Agent 组件集”。

它们并不是简单的竞品关系。一个很有意思的源码事实是:DeepSeek Harness 自己的多 Provider 适配器 dsh-llm-pi-ai,底层就依赖 Pi 的 pi-ai。换句话说,DeepSeek Harness 可以把 Pi 的模型接入层装进自己的插件树里。

我这边也有一些AI Coding(SDD Agent AI工具等) 和 Node技术交流交流群,感兴趣的可以加微信 ikoala520  进群,一起学习,共同进步。

代充

本文基于以下源码快照分析,而不是仅依据产品文档:

  • DeepSeek Harness:commit b150a551b8d4,版本 0.1.1-rc.2,2026-08-21。
  • Pi:commit ccfe79ed2386,2026-08-27;当前 npm 包名为 @earendil-works/*。

DeepSeek Harness 仍明确标注为 Developer Preview,官方提示会发生兼容性破坏。本文讨论的是这个源码快照体现出来的架构,不把尚未稳定的 API 当成长期承诺。


一、Harness 到底解决什么问题

模型 API 只负责“给定消息,生成下一段内容”。一个真正能完成编码任务的 Agent,还要在模型外部补齐很多东西:

  • 把系统提示、上下文、工具 Schema 组装成请求;
  • 解析模型输出中的 Tool Call;
  • 校验参数,执行文件、终端、搜索、MCP 等工具;
  • 把 Tool Result 写回上下文,再次请求模型;
  • 处理取消、重试、审批、权限和沙箱;
  • 保存会话,支持恢复、分支、压缩和重放;
  • 把底层流式事件映射成 UI 能稳定渲染的消息、工具卡片和状态。

其中最小的闭环是:

模型请求 → 模型返回 Tool Call → 执行工具 → 写入 Tool Result → 再请求模型

驱动这个闭环的控制逻辑通常被称为 Agent Loop。而 Harness 一般比 Agent Loop 更大:它还需要承载会话、工具、权限、扩展、宿主接入和 UI 协议。

为了避免后面混淆,可以先建立四层概念:

层级核心问题典型内容
Model Provider如何调用不同模型鉴权、请求格式、流式协议、Reasoning、Usage
Agent Core如何让模型持续行动Agent Loop、Tool Call、上下文转换、停止条件
Agent Harness如何让 Agent 可长期运行Session、恢复、权限、审批、扩展、可观测性
Product Host如何形成具体产品Web/桌面 UI、工作空间、业务流程、插件管理

DeepSeek Harness 覆盖了从 Provider 到 Product Host 的大部分层次;Pi 则把能力拆成可以按需组合的包,重点落在 Provider、Agent Core 和 Coding Agent Runtime。 image.png

下面用一个贯穿案例理解两者:

用户说:“检查支付模块的失败用例,修改代码并运行测试;写文件和执行高风险命令前需要我确认。”

这个任务至少会经历一次模型请求、若干次读文件、一次写文件审批、一次测试命令,以及多轮模型继续推理。


二、DeepSeek Harness:把 Agent 产品拆成一棵插件树

2.1 它是 Web-first,但不是只有 Web UI

DeepSeek Harness 默认最显眼的入口是:

npx @deepseek-ai/dsh web

这很容易让人以为它是一个“开源 Web 聊天页面”。源码里的真实边界要大得多。

DeepSeek Harness 用 Profile 表达一套可运行的产品组合,官方自带 web 和 headless 模板:

  • web Profile 在基础能力上增加浏览器应用;
  • headless Profile 提供没有服务器的一次性执行入口;
  • 两者都建立在共享的 dsh-base Bundle 上。

因此 Web 只是一个宿主形态,Agent、Session、Tool、LLM、Sandbox 并不属于 React 页面本身。这个分离很重要:同一套 Runtime 可以被 Web、CLI、测试程序,甚至未来的桌面壳驱动。

2.2 Cordis:不是“支持插件”,而是“万物皆插件”

DeepSeek Harness 的架构文档直接写道:Everything is a plugin。

它底层使用 Cordis。插件向共享 Context 注册三类东西:

  • Service:例如 ctx.sessions、ctx.tools、ctx.llm;
  • Typed Event:例如 session/event、agent/pre-step、tools/pre-execute;
  • Reversible Effect:插件卸载时,注册行为能够反向撤销。

这里的关键不是“能额外装几个工具”,而是模型适配器、工具注册表、Session Log、Agent Loop 乃至 Web UI 本身都是插件。官方架构说明甚至强调,没有一个需要修改的“特权核心”;扩展行为的正常方式,是把新插件挂到已有插件旁边。

Profile、Bundle、Plugin Tree

DeepSeek Harness 的启动组合可以理解为三层:

  1. Profile 决定要叠加哪些 Bundle,例如 web 或 headless;
  2. Bundle 提供一组 Cordis 配置行和对应插件;
  3. 用户的 cordis.patch.yml 可以按 id 替换配置行或插入新行。

image.png 在 dsh-base 的默认配置中,可以直接看到 Session、Agent、LLM、JSONL 持久化、Sandbox、Approval、Permission Presets、Bash、文件工具、Skill 等插件。多 Provider 的 llm-pi-ai 也被默认挂载,只是在没有用户 Provider 配置时处于 dormant 状态。

这意味着所谓“替换某一部分”不是架构愿景,而是实际的装配机制。例如:

  • 增加模型 Provider:向 ctx.llm 注册 Adapter;
  • 改造文件系统:替换 ctx.fs Provider;
  • 把本地 Bash、PTY、LSP 一起迁入远端 Sandbox:替换共享执行世界的能力 Provider;
  • 增加 UI 卡片:注册 Conversation Node Definition 和对应 Renderer;
  • 拦截请求或工具:监听对应 Waterfall Event。

2.3 Agent Loop:Turn 与 Step 是两层状态机

DeepSeek Harness 对执行粒度做了清楚区分:

  • Step:一次模型请求,加上这次响应触发的工具执行;
  • Turn:从一次输入被认领开始,到系统不再欠任何后续工作为止,可包含 0 到多个 Step。

以“修改支付模块并运行测试”为例,第一次模型请求可能调用 read;读取结果回填后,第二次请求调用 edit;审批并修改后,第三次请求调用 bash;测试结果回填后,第四次请求才给出总结。这是一个 Turn、四个 Step。

sequenceDiagram
  participant U as User / Web
  participant A as Agent Inbox
  participant L as Agent Loop
  participant M as LLM Adapter
  participant T as Tool Pipeline
  participant S as Session Log

  U->>A: prompt / steer / queue
  L->>S: turn/start
  loop 一个 Turn 内可以有多个 Step
    L->>S: step/start + user/message
    L->>M: 组装历史、System Prompt、Tool Schema
    M-->>L: assistant/chunk*
    L->>S: assistant/chunk* + assistant/message
    opt 模型返回 Tool Call
      L->>S: tool/call
      L->>T: pre-execute → execute → post-execute
      T-->>L: Tool Result
      L->>S: tool/result
    end
    L->>S: step/end
  end
  L->>S: turn/end

源码中,agent/pre-step 可以重写或拒绝将进入模型的消息;agent/request 和 llm/stream 可以拦截请求链路;agent/turn-stopping 则是最终停止点。

这套设计带来的价值是:压缩、重试、目标继续、上下文注入等能力不必全部硬编码到循环主体里,它们可以在明确的生命周期节点上参与决策。

有一个容易踩坑的术语差异:**DeepSeek 的 Step,大致对应 Pi 事件语义里的一个 turn_start → 模型 → 工具 → turn_end;DeepSeek 的 Turn 则可能包住多个这样的 Step。**所以不能只按名字把两边的 Turn 一一对应。

2.4 Tool Pipeline:工具调用不是一个 execute() 就结束

DeepSeek Harness 的工具执行被拆成一条受控流水线:

image.png

这里有几个工程细节值得关注:

第一,Tool Call 在执行前就被记录,因此 UI 可以先出现“等待执行”的卡片,崩溃恢复时也知道系统停在什么位置。

第二,权限与审批不是散落在每个工具里的 if。tools/pre-execute、单调 Guard、ctx.approval、Sandbox 和 tools/post-execute 共同形成治理链。

第三,审批采用 fail-closed:需要审批但没有可用回答者时,不会悄悄放行。

第四,工具最终结果经过归一化后再成为唯一的模型可见 tool/result,避免模型上下文、持久化记录和 UI 展示各有一套结果。

这也是 DeepSeek Harness 比普通 Tool Calling SDK 更像“平台”的地方:它不只解决“工具怎么调”,还解决“工具在什么权限下调、谁能拦截、怎样恢复、怎样展示”。

2.5 SessionEvent:把会话当作事件源,而不是消息数组

DeepSeek Harness 的 Session 是 append-only 的 SessionEvent 日志。核心事件包括:

  • turn/start、turn/end;
  • step/start、step/end;
  • user/message;
  • assistant/chunk、assistant/message;
  • tool/call、tool/result;
  • 以及插件通过类型扩展增加的事件。

它有一句非常关键的设计约束:

Model-visible means logged——只要内容会进入模型,就必须能从日志重建。

因此模型历史不是另一份独立保存的 messages[],而是通过 deriveMessages() 从 Session Log 投影出来。会话恢复、Fork、Transcript、Telemetry、持久化和 UI 重放也都基于同一条事件流。

这解决了 Agent 产品常见的“多份真相”问题:模型看到一份消息,Web 保存一份消息,工具卡片又维护另一份状态,断线恢复后彼此对不上。事件源设计把“发生过什么”固定下来,再允许不同消费者形成各自视图。

代价也很明确:事件协议需要版本治理;投影器需要兼容历史事件;原始 chunk 会增加存储体积;任何新的模型可见输入都必须先定义可持久化事件,而不能临时塞进请求。

2.6 Web UI 并不只是渲染 SDK 返回值

DeepSeek Harness 的 Web 链路同样分层。简化后如下:

Client Session.prompt()
  → API Proxy / RPC
  → Host Agent Inbox 与 Agent Loop
  → SessionEvent
  → Mux / Host Stream
  → Client Session
  → ConversationNodeAssembler
  → ConversationSnapshot / keyed views
  → React UI

浏览器侧的 Session 持有当前事件窗口、Pending Interaction、运行状态和可观察快照。ConversationNodeAssembler 不只是把 JSON 转为 JSX,而是把连续的原始事件窗口增量折叠成业务节点,再为不同 View 构建快照。

因此,同一条 SessionEvent 可以被投影为:

  • 普通聊天消息;
  • Assistant 流式节点;
  • Tool Call/Result 卡片;
  • Turn/Step 位置;
  • Compaction 标记;
  • 重试提示;
  • 独立的 Trajectory 视图。

审批和用户提问则属于“等待人类响应的交互状态”。Host 通过带稳定 rpcId 的请求帧推送,Client 将其维护为 PendingInteraction;断线重连时,Mux 会重放仍未解决的请求。这样“审批弹窗消失了,但后台还在等”不会成为默认行为。

所以 DeepSeek 的 Web 层也有一套会话映射和状态机,而且并不薄。它负责的是客户端事件窗口、业务投影、交互态和视图扩展;Agent 执行、工具权限、持久化真相仍属于 Host Runtime。


三、Pi:把最小 Agent Loop 做成可嵌入组件

3.1 Pi 不是一个单体 CLI

Pi Monorepo 的稳定主链路分为四层:

包职责
pi-ai统一多 Provider 模型、鉴权、流式事件、Reasoning、Usage、Tool Call
pi-agent-core有状态 Agent、Agent Loop、工具执行与事件流
pi-coding-agent编码工具、Session、压缩、重试、Extension、CLI/RPC/SDK
pi-tui终端 UI 与增量渲染

这个分层使 Pi 同时具备两种身份:终端里,它是一个完整 Coding Agent;代码里,它又可以只作为 Provider 层或 Agent Core 被别的产品嵌入。

image.png

3.2 pi-ai:统一 Provider,不替模型做 Agent 决策

pi-ai 解决的是模型世界的差异:

  • 不同 Provider 的模型目录和 API 类型;
  • API Key、OAuth 等鉴权;
  • SSE/WebSocket 等流式传输;
  • 文本、Thinking、Tool Call 的增量事件;
  • Context Window、Token Usage、Cost 等元数据;
  • OpenAI、Anthropic、Google、DeepSeek、OpenRouter、本地 llama.cpp 等 Provider。

多 Provider 是 Pi 很突出的现成能力,但它不是 Agent Loop 本身。**Provider Adapter 解决协议差异,Agent Loop 解决控制流。**这两个概念不能合并。

3.3 pi-agent-core:核心就是一个清晰的双层循环

Pi 的 packages/agent/src/agent-loop.ts 很适合用来理解 Agent Loop,因为控制流相对直接。

它包含两个循环:

  • 内层循环:只要还有 Tool Call 或 Steering Message,就继续下一轮模型调用;
  • 外层循环:Agent 原本准备停止时,再检查是否有 Follow-up Message,有则重新进入内层循环。

image.png

一个 Tool Call 的处理过程也很规整:

  1. 根据名称查找工具;
  2. prepareArguments 预处理参数;
  3. 按 Tool Schema 校验参数;
  4. 调用 beforeToolCall,允许扩展阻断;
  5. 执行 Tool;
  6. 调用 afterToolCall,允许改写结果;
  7. 发出 tool_execution_end;
  8. 生成标准 ToolResultMessage 并放回上下文。

同一条 Assistant Message 中的多个工具可以顺序执行,也可以并行执行。并行模式仍按模型原始顺序生成 Tool Result Message,保证回填上下文的顺序稳定。

Pi 还专门防御了一类细节问题:如果模型因为输出 Token 上限而截断,可能留下“碰巧可以解析但参数不完整”的 Tool Call。Pi 会把这批 Tool Call 全部标为失败,而不是冒险执行。

3.4 AgentMessage 与模型消息分离

Pi 允许应用定义自有 AgentMessage。但模型只认识标准的 User、Assistant 和 Tool Result,所以每次请求前会经过:

AgentMessage[]
  → transformContext()
  → AgentMessage[]
  → convertToLlm()
  → 标准 Message[]
  → Provider

transformContext 可以做裁剪、压缩或上下文注入;convertToLlm 可以过滤纯 UI 消息,或把业务消息转换成模型可见格式。

这是很实用的边界:会话里可以存在比模型消息更多的业务信息,但只有经过显式投影的内容才进入模型。

与 DeepSeek 相比,Pi 稳定主链路更偏“消息与状态对象驱动”,而不是把所有运行事实先统一为一份跨宿主的 SessionEvent 协议。

3.5 pi-coding-agent:在 Agent Core 上补齐产品能力

pi-agent-core 只提供通用循环。真正让 Pi 成为 Coding Agent 的是 pi-coding-agent:

  • 默认提供 read、write、edit、bash;
  • AgentSession 连接 Agent 事件、Session 持久化、自动压缩、重试和扩展;
  • AgentSessionRuntime 管理切换会话、切换工作目录时的资源重建与清理;
  • SessionManager 管理 JSONL 会话树;
  • ExtensionRunner 分发扩展生命周期和工具钩子;
  • 支持 Interactive、Print/JSON、RPC、SDK 四种使用方式。

这说明 Pi 的价值不只是那几十行循环,而是围绕小内核形成了一套可嵌入的编码 Agent 运行时。

3.6 JSONL 会话树:天然支持分支

Pi 的 Session 也使用 JSONL,但结构与 DeepSeek 的顺序事件日志不同。

每个 Entry 有 id 和 parentId,所有 Entry 在一个文件里构成树;当前 leaf 指向正在使用的分支。追加消息就是在当前 leaf 下增加子节点,跳转历史节点后继续工作则形成新分支。

因此 Pi 可以自然支持:

  • /tree 在同一文件中查看并切换分支;
  • /fork 从历史消息创建新 Session 文件;
  • /clone 复制当前活动路径;
  • Compaction 后保留原始历史,需要时仍可回到旧节点。

DeepSeek 更强调“完整执行事件可以重放和投影视图”,Pi 更强调“对话 Entry 的树形导航和轻量本地持久化”。两者都用 JSONL,不代表数据模型相同。

3.7 Extension:足够实用,但不是 Cordis 式“万物皆插件”

Pi Extension 可以注册:

  • Tool、Command、Shortcut、Flag;
  • Provider;
  • 自定义消息和 Entry Renderer;
  • 输入、上下文、模型请求、Tool Call、Tool Result、Session 生命周期等事件;
  • TUI 状态栏、Widget、Overlay,甚至替换编辑器组件。

这已经是很强的扩展机制,而且 TypeScript 文件可以通过 Jiti 直接加载。对于个人工作流或团队 Coding Agent,它比重量级插件平台更容易上手。

但 Pi 的 Extension 主要围绕 pi-coding-agent 预设的扩展点工作。AgentSession、Session Manager、TUI 等仍然是明确的核心对象;它不像 Cordis 那样把 Agent Loop、Session 服务、LLM 服务和 UI 组件全部放进同一种可装卸插件模型。

可以简化为一句话:

Pi 是“小内核 + 丰富扩展点”;DeepSeek Harness 是“插件容器 + 由插件组成的产品”。

3.8 权限边界:Project Trust 不等于运行时 Sandbox

Pi README 明确说明:Pi 不内置限制文件系统、进程、网络或凭据访问的权限系统,默认继承启动它的用户与进程权限。需要强隔离时,应使用容器、微型虚拟机或外部 Sandbox。

Pi 有 Project Trust,用于决定是否加载项目本地设置、扩展和资源;但它解决的是“是否信任项目代码”,不等于每一次文件写入或命令执行的细粒度审批。

这与 DeepSeek Harness 默认组合中的 Sandbox、Approval、Permission Presets 有明显差异。

3.9 注意:Pi 新增的 AgentHarness 仍是演进中脚手架

当前 Pi 源码里已经出现新的 packages/agent/src/harness/agent-harness.ts,接口设计包含 durable Session、Lane、Run、Resume、Queue、Hook、Watch 等概念,看起来是在向更完整、可恢复的 Harness API 演进。

但在本文所用 commit 中,prompt、compact、resume、steer、watch 等大量关键方法仍返回 HarnessNotImplemented,恢复已有记录也尚未实现。

因此评估 Pi 时必须区分两件事:

  • 已经稳定可用的主链路:pi-ai、Agent/agentLoop、AgentSession、SessionManager、Extension;
  • 正在形成的新方向:durable AgentHarness API。

不能因为仓库中出现了 AgentHarness 类,就把规划中的接口当成已经可以替换生产 Runtime 的成熟实现。


四、本质差异:一个在设计“平台”,一个在设计“内核”

维度DeepSeek HarnessPi
首要定位插件化 Agent 平台可嵌入 Agent 内核 + Coding Agent
核心抽象Cordis Context、Service、Event、Effect、Plugin TreeModel、Agent、Agent Loop、AgentSession、Extension
组合方式Profile + Bundle + Patch,几乎所有模块均为插件分层 npm 包 + Coding Agent 扩展点
Agent 循环Turn 包含 0~多个 Step,生命周期扩展点细双层循环直接清晰,Steering/Follow-up 内建
会话真相append-only SessionEvent,模型历史与 UI 均从日志派生JSONL Entry 树,活动 leaf 决定上下文分支
UIWeb Client Runtime 也插件化,事件组装为 Conversation Node/View以 TUI 为主,也提供 JSON、RPC、SDK 嵌入方式
工具治理Pre/Guard/Approval/Execute/Post/Sandbox 完整流水线before/after hooks、顺序/并行执行;隔离交给外部环境
多 Provider自有 DeepSeek Adapter,也可通过 llm-pi-ai 使用 Pipi-ai 是核心优势,Provider 范围广
插件强度可替换基础服务和 Loop,支持可逆生命周期擅长扩展 Tool、Provider、命令、事件和 TUI
学习与改造成本高,概念和组合机制较重低到中,适合先嵌入再扩展
当前成熟度提醒Developer Preview,明确会 breaking change稳定主链路可用;新 durable AgentHarness 尚未完成

如果用建筑作比喻:

  • Pi 提供发动机、变速箱和一辆可改装的轻型车;
  • DeepSeek Harness 提供一套模块化底盘、总线、装配规范和已有驾驶舱。

因此,问“DeepSeek Harness 和 Pi 哪个更好”并不完整。更好的问题是:

我们缺的是一个可控的 Agent Loop,还是一个能持续装配多类 Agent 产品的插件宿主?


五、一个容易被忽略的事实:DeepSeek Harness 正在使用 Pi

DeepSeek Harness 的 @deepseek-ai/dsh-llm-pi-ai 包依赖:

"@earendil-works/pi-ai": "^0.82.1"

在默认 dsh-base Bundle 里,这个插件以 dormant 状态挂载;当用户在设置中配置 Provider Profile 时,它动态注册相应模型路由。

这揭示了两者最真实的关系:

DeepSeek Harness
  负责插件装配、Agent 生命周期、SessionEvent、工具治理、Web 投影
        ↓
  dsh-llm-pi-ai Adapter
        ↓
Pi pi-ai
  负责多 Provider、模型目录、鉴权和流式协议

所以“用 DeepSeek 还是用 Pi”可能是假二选一。一个合理系统完全可以同时借用两者处在不同层级的能力。


总结:我们要靠近的是思想,不是仓库形状

DeepSeek Harness 最有价值的地方,不是它恰好有一个 Web UI,而是它把 Agent 产品理解为可组合的插件树,并用 SessionEvent 和 Projection 连接 Runtime 与界面。

Pi 最有价值的地方,也不只是原生支持很多 Provider,而是它把模型访问、Agent Loop、Coding Agent 产品能力拆成不同包,让宿主可以只拿自己需要的层。


我这边也有一些AI Coding(SDD Agent AI工具等) 和 Node技术交流交流群,感兴趣的可以加我微 ikoala520  进群,一起学习,共同进步。

源码索引

DeepSeek Harness

Pi