2026年8月18日 · 阅读 —

Pi vs DeepSeek Harness:从架构到实战的全方位技术对比

Agent 与 Skills知识与内容工具

2026年8月13日,DeepSeek Harness 开源。24小时破7万 star,到检索日已超14万。另一边,Pi 经过一年打磨、5600+ 次提交,积累9万 star。两个项目都是 MIT、TypeScript,都叫”harness”,代码结构却隔着整个方法论光谱。


一、先看总表:32 项技术维度全面对比

在进入细节之前,先把两个项目拆到芯片级,一张表说清全部差异。

维度子项PiDeepSeek Harness (dsh)
项目基础出品方earendil-works(社区,Mario Zechner)DeepSeek AI(官方)
首次发布2025年8月2026年8月13日
当前版本v0.84.2(稳定)v0.1(开发者预览)
提交次数5,683 commits约 1,000+ commits
开源协议MITMIT
技术栈TypeScript monorepo、Bun 运行时TypeScript + pnpm workspaces
设计哲学核心口号“极简默认”(Default to Minimal)“一切皆插件”(Everything is a Plugin)
内核框架自研五包 monorepoCordis(论文驱动的组合式框架)
Agent Loop 可替换否,固定内核是,Agent Loop 本身也是插件
默认工具数量4 件(读/写/改/执行命令)全插件式,按 profile 组装
系统提示词长度约 200 token视插件组合而定,可扩展
安装与启动安装方式npm install -g @earendil-works/pi-coding-agentnpx @deepseek-ai/dsh web 或源码编译
启动时间<1 秒5-10 秒(含 Web UI 初始化)
用户界面终端 TUI(差分渲染)Web UI(:3080)
首次配置复杂度低,仅需 API Key中,需 profile + 插件组合
模型生态模型提供商OpenAI、Anthropic、Google、Kimi、MiniMax 等多家DeepSeek、Anthropic、OpenAI 及任意兼容端点
模型适配层pi-ai 统一 APICordis 插件式 LLM Service
运行时切换模型需重启会话支持热切换(插件重载)
缓存优化99.93% 缓存命中率(Pi + DeepSeek)原生 DeepSeek 推理内容回放优化
架构规模包数量5 个核心包50+ 个包
代码文件数约 500+约 8,600+
插件/扩展数按需安装(Extension + Skill)6,000+(dsh-plugin 话题仓库)
扩展机制Extension + Skill 钩子系统Cordis 插件(effect + inject + 事件)
事件系统事件模式生命周期钩子(session_start/tool_call 等)4 种类型化模式(emit/waterfall/parallel/serial)
事件类型化无,约定式接口有,TypeScript declaration merging
事件持久化无有,SessionEvent 日志流
上下文管理压缩策略摘要模型 + 纯文本简报Surface 替换 + 规则裁剪
压缩结果可读性人可读,可跨会话移植系统级,保留回放完整性
会话回放不支持支持(事件流 deriveMessages())
插件生命周期资源清理方式扩展作者在 session_shutdown 中手动清理Cordis effect 自动逆序撤销
依赖管理无,宿主不追踪扩展关系有,inject 声明依赖 + Fiber 追踪
热插拔不支持支持(运行时加载/卸载插件)
插件退出影响范围仅限于当前会话运行时自动检查依赖链,级联停用消费者
安全与沙箱内置权限系统无(文档明确说明)有审批策略 + 沙箱 Service 层
进程隔离3 种模式(Gondolin/Docker/OpenShell)通过 ctx.sandbox + ctx.subprocess
供应链安全严格(锁定版本 + shrinkwrap + audit)待观察(开发者预览阶段)
成本与性能单次成功任务成本~0.028 美元(最低, Composio 实测)暂无公开基准
启动耗时<1 秒5-10 秒
构建产物大小轻量(5 包)重型(50+ 包,8600 文件)
适用场景目标用户编码开发者(单人)Agent 开发者/平台构建者
典型场景本地写代码、改 bug、重构多 Agent 编排、自研工作流、Agent 平台
上手门槛低高
生态成熟度成熟(年迭代)早期(开发者预览)

这张表不是堆数字,而是两个项目的”设计签名”——每一行差异都指向同一个核心分歧:Pi 把 Agent 当成工具来打磨,dsh 把 Agent 当成平台来构建。


二、我是怎么测的

两套都从源码跑起来了。Pi 用的 pi-mono monorepo,dsh 基于 47f9438 提交。测试场景选了三个:做一个简单的 CLI 工具(检测系统状态)、写一个带 API 的 Web 服务(TODO 列表)、修一个故意引入的 bug。目的是看两个框架在真实开发流程里的手感和开销。


三、安装和启动:从零到能跑

先看第一个差异:上手路径。

Pi

# 安装
npm install -g @earendil-works/pi-coding-agent

# 启动
pi

# 指定模型
pi --provider openai --model gpt-4o

装完就能跑,默认只读环境变量里的 API Key。没有额外配置,没有 Web 服务,没有数据库初始化。终端里直接进入对话。

dsh

# 从 npm 跑
npx @deepseek-ai/dsh web

# 或者从源码
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web

启动后浏览器打开 http://127.0.0.1:3080,看到完整的 Web UI。第一次启动会生成 ~/.dsh/ 目录,里面是 profile、config、session 数据。

差异在哪? Pi 是终端 CLI,面向”打开就用”的场景;dsh 是 Web UI + 插件系统,首次启动后要经历浏览器加载、插件初始化、profile 组合。前者是”终端里干活”,后者是”浏览器里调试”。


四、架构哲学:两张图说清

Pi 的架构流

Pi 的核心是一个五包 monorepo:

flowchart LR
    subgraph CLI["用户层"]
        PI[pi-coding-agent CLI]
        TUI[pi-tui 差分渲染]
    end
    
    subgraph CORE["Agent 内核"]
        AC[pi-agent-core]
        AI[pi-ai 多提供商LLM]
        IM[内置工具 x4]
    end
    
    subgraph EXT["扩展"]
        EXT1[Extension]
        EXT2[Skill]
    end
    
    PI --> AC
    AC --> AI
    AC --> IM
    EXT1 --> AC
    EXT2 --> AC
    TUI --> PI

Pi 的设计哲学很清晰:Agent Loop 是核心,其他都是扩展。内置工具极少(读、写、改、执行命令),但 Extension 和 Skill 可以深入各个环节——包括工具调用、会话事件、模型 Provider、甚至压缩逻辑。

// Pi 的扩展 API 示例 — 注册一个自定义工具
import { defineTool } from "@earendil-works/pi-agent-core";

export const myTool = defineTool({
  name: "system_status",
  description: "获取系统 CPU 和内存状态",
  parameters: {
    type: "object",
    properties: {},
  },
  execute: async (_, context) => {
    const cpu = await context.exec("top -l 1 -n 0 | grep 'CPU usage'");
    const mem = await context.exec("vm_stat | head -5");
    return `CPU: ${cpu}\nMEM: ${mem}`;
  },
});

dsh 的架构流

dsh 的架构完全不同:一切皆插件,连 Agent Loop 本身都是插件之一。

flowchart LR
    subgraph PROFILE["Profile 层"]
        P1["dsh-base"]
        P2["dsh-web-app"]
        P3["用户 patch"]
    end
    
    subgraph CORDIS["Cordis 运行时"]
        CTX["Context(服务仓库)"]
        EF["effect 系统"]
        EV["事件总线"]
        LD["Loader"]
    end
    
    subgraph SERVICE["核心服务"]
        SESS["session 日志"]
        SP["system-prompt"]
        TOOL["tools 注册表"]
        AG["agent Loop"]
        LLM["llm 适配"]
        SHELL["shell 执行"]
        FS["filesystem"]
        COMP["compaction"]
    end
    
    subgraph PLUGIN["插件生态"]
        P11["社区插件 6000+"]
        P12["dsh-plugin topic"]
    end
    
    PROFILE --> CORDIS
    CORDIS --> SERVICE
    PLUGIN --> CORDIS
    CTX --> SESS
    CTX --> SP
    CTX --> TOOL
    CTX --> AG
    CTX --> LLM
// dsh 的插件注册示例 — 注册一个工具到 Cordis 上下文
import { Context } from "@deepseek-ai/cordis";

export function apply(ctx: Context) {
  // 注册工具 — 这是一个 effect,插件卸载时自动撤销
  ctx.effect(() => {
    const id = ctx.tools.register("system_status", {
      description: "获取系统 CPU 和内存状态",
      parameters: {
        type: "object",
        properties: {},
      },
      execute: async (args, context) => {
        const cpu = await context.shell.exec("top -l 1 -n 0 | grep 'CPU usage'");
        const mem = await context.shell.exec("vm_stat | head -5");
        return `CPU: ${cpu}\nMEM: ${mem}`;
      },
    });
    // 返回 disposer — 卸载时自动清理
    return () => ctx.tools.unregister(id);
  });
}

关键区别:Pi 的扩展是”在核心上挂东西”,dsh 的插件是”跟核心住在一起”。Pi 的 Agent Loop 是固定的,你可以扩展它的行为;dsh 的 Agent Loop 本身可以被替换掉。


五、事件系统:两种解耦方式

这是一个很能体现设计差异的地方。

Pi 的事件模型

Pi 的事件系统基于生命周期钩子:

// Pi 的会话事件钩子
export default {
  session_start: async (ctx) => {
    // 新会话开始
    await ctx.db.connect();
  },
  session_shutdown: async (ctx) => {
    // 会话关闭 — 清理资源
    await ctx.db.disconnect();
    ctx.fileWatcher?.close();
  },
  tool_call: async (ctx, toolName, args) => {
    // 工具调用前拦截
    if (toolName === "execute_command") {
      // 安全检查
      if (args.command.includes("rm -rf /")) {
        throw new Error("危险命令被拦截");
      }
    }
  },
};

Pi 把生命周期事件做成约定的接口,扩展按需实现。基于 session_start 和 session_shutdown 做资源管理,扩展作者负责清理自己的东西。宿主不追踪跨扩展依赖。

dsh 的 Cordis 事件模型

Cordis 有四种事件分派模式:

// dsh 的四种事件模式
// 1. emit — 广播通知,不等待
ctx.emit("session/created", { id: "xxx" });

// 2. waterfall — 链式处理,每个监听器可修改结果
ctx.waterfall("agent/request", messages, (ctx, msgs, next) => {
  // 可以修改消息再传给下一个监听器
  return next(msgs.filter(m => m.role !== "system"));
});

// 3. parallel — 并行执行
ctx.parallel("tools/pre-execute", toolCall, (ctx, call, next) => {
  return next(); // 必须调用 next() 否则链断
});

// 4. serial — 串行执行,带返回
ctx.serial("fs/read", { path: "/etc/passwd" }, (ctx, args, next) => {
  // 可以拒绝或允许
  if (args.path.includes("..")) throw new Error("路径遍历");
  return next();
});

这是个有论文支撑的设计。Cordis 的论文《A Programming Paradigm for Spatiotemporal Composability》定义了插件之间的时空可组合性——时间上保证撤销顺序(逆序执行),空间上通过 Context 作用域隔离。

flowchart TD
    subgraph PI["Pi 事件模式"]
        A1["插件 1 实现钩子"]
        A2["插件 2 实现钩子"]
        A3["宿主顺序调用"]
    end
    
    subgraph DSH["dsh 事件模式"]
        B1["emit(广播)"]
        B2["waterfall(链式修改)"]
        B3["parallel(并行)"]
        B4["serial(串行)"]
    end

差异:Pi 的事件是”约定式的钩子”,dsh 的事件是”类型化的基础设施”。Pi 适合可控范围内的扩展,dsh 适合需要精细控制事件流的场景。


六、上下文压缩:同源但不同追求

长程 Agent 最头疼的问题:上下文满了怎么办。两个项目给出的解法高度同构,但工程实现方向完全不同。

Pi 的压缩

Pi 把压缩做成”可换模型的能力”:

# Pi 的压缩配置
compaction:
  enabled: true
  # 可换便宜模型做摘要
  summarizer_model: "deepseek-chat"  # 可以换成 gpt-4o-mini
  # 触发条件:token 超过 80%
  trigger_ratio: 0.8
  # 保留最近 N 条消息
  keep_last: 20
  # 输出纯文本简报
  format: "text"

关键设计:纯文本简报可跨会话移植。Pi 压缩出来的内容是人可读的,你可以在新会话里直接粘贴进去用。这跟 Pi 的”人能接管”哲学一致——压缩结果对使用者透明,你随时可以检查、修改、甚至手动敲回去。

# Pi 压缩后的简报示例
[会话摘要]
用户正在开发一个 TypeScript Web 服务,使用 Express 框架。
已完成:
- 路由配置(GET /api/todos, POST /api/todos)
- 数据库连接(SQLite via better-sqlite3)
- 错误处理中间件
当前问题:POST 请求返回 400,原因是 body parser 未启用
已尝试:检查了路由注册顺序,未发现问题
下一步:检查 app.use(express.json()) 是否在路由之前注册

dsh 的压缩

dsh 把压缩做成”可换后端的插件”:

# dsh 的压缩配置(cordis.yml)
plugins:
  compaction:
    config:
      # 压力阈值
      threshold: 0.8
      # 压缩策略
      strategy: "surface"
      # 回放安全
      replay_safe: true
      # 裁剪规则
      tool_result_rules:
        max_length: 2000
        truncate: "keep_head"
        drop: ["cat_*.log", "node_modules/**"]

关键设计:回放安全的 surface 替换。dsh 压缩时不会破坏会话日志的可回放性——替换的不是原始事件,而是展示层(surface layer)。这意味着即使压缩后,日志仍然可以完整回放,只是模型看到的上下文被精简了。

flowchart LR
    subgraph PI_COMP["Pi 压缩流程"]
        P1["监测 token 比率"] --> P2["触发(>80%)"]
        P2 --> P3["调用摘要模型"]
        P3 --> P4["生成纯文本简报"]
        P4 --> P5["替换旧上下文"]
    end
    
    subgraph DSH_COMP["dsh 压缩流程"]
        D1["每步精确测压"] --> D2["溢出(>0.8)"]
        D2 --> D3["规则裁剪 tool-result"]
        D3 --> D4["surface 替换"]
        D4 --> D5["保留完整回放日志"]
    end

差异的本质:Pi 偏”人能接管”——压缩结果可读、可拷、可手动改;dsh 偏”系统能扩展”——压缩策略可配、可换、不影响回放。Pi 承担了 prompt cache 被打穿的代价,dsh 用分层设计避免了这个问题。


七、实际开发场景对比

场景 1:做一个系统状态检测 CLI 工具

在 Pi 里:

$ pi
> 创建一个 Node.js CLI 工具,读取 /proc/stat 和 /proc/meminfo 并格式化输出

Pi 直接执行:读文件、写文件、执行命令。不到 30 秒,文件就写好了。过程中模型只调用了 4 次工具,输入 token 总量约 15K。

在 dsh 里:

$ npx @deepseek-ai/dsh web
# 浏览器打开 :3080,在 Web UI 中输入同样指令

dsh 一样能完成,但因为 Web UI 本身有额外的渲染和通信开销,第一次启动到可用需要约 5-10 秒初始化。工具的注册和调用通过 Cordis 事件路由,多了一层插件解析。

Pi 更适合”打开终端说句话就干活”的场景。

场景 2:写一个带 API 的 TODO 服务,中途换模型

Pi:

# 启动时直接指定
pi --provider openai --model gpt-4o

# 或者运行时切换(需要重启会话)
# 但 Pi 的 pi-ai 统一 API 让切换只改一行

Pi 的 pi-ai 包统一对接 OpenAI、Anthropic、Google 等多家提供商,API 接口一致。切换模型只需要改 Provider 参数,但需要重启会话。

dsh:

# dsh 的模型配置(cordis.yml)
plugins:
  llm:
    config:
      provider: "deepseek"
      model: "deepseek-v4-flash"
    # 运行时热切换
    # 通过插件热插拔,不需要重启

dsh 的模型适配器是插件,通过 Cordis 的运行时动态加载/卸载机制,可以在不重启会话的情况下切换 Provider。这一点对长时间运行的 Agent 任务很有价值。

dsh 更适合”需要运行时切换的能力组合”的场景。

场景 3:修一个 bug,要排查历史上下文

这是一个很实际的场景。Agent 跑了 50 轮对话后,上下文满了,压缩已经开始,现在要回退到某个历史状态看看。

Pi:压缩后的纯文本简报可读,你可以手动翻看。但如果简报对某个历史细节做了精简,需要重新跑上下文才能看到原始信息。没有完整的事件回放。

dsh:会话日志是 append-only 的 SessionEvent 流,任何压缩都不破坏原始日志。通过 dsh session log <id> 可以回放完整的事件序列——包括每一步的 tool call、tool result、assistant message。这不仅仅是”查看历史”,而是可验证的完整回放。

# dsh 回放会话
dsh session log --id session-xxxx --replay

dsh 的 deriveMessages() 函数从日志投影模型历史,回放时能精确重建每一步的上下文。这跟 Pi 的”纯文本简报”是完全不同的设计思路——一个是人可读的摘要,一个是机器可回放的事件流。


八、插件系统:从”退出”看差异

给 Agent 加一个插件通常不难。真正让人头疼的是插件离开以后——配置里明明删掉了,文件监听器还在跑;Provider 换了,某个工具却仍握着旧对象。

Pi 的退出策略

Pi 的文档说得很清楚:扩展作者在 session_shutdown 里清理资源,宿主负责调用这个钩子。

// Pi 扩展的退出模式
export default {
  session_start: async (ctx) => {
    ctx.watcher = fs.watch("/some/path", callback);
  },
  session_shutdown: async (ctx) => {
    // 扩展作者自己清理
    ctx.watcher?.close();
    ctx.connections.forEach(c => c.destroy());
  },
};

Pi 的分工:宿主把门关上再打开,屋里有什么东西,由住在里面的人自己清点。扩展不多时,这套模式很省心。

dsh 的退出策略

dsh 借 Cordis 的 effect 系统做资源追踪:

// dsh 插件的退出模式
export function apply(ctx: Context) {
  // effect 注册 — 退出时自动撤销
  ctx.effect(() => {
    const watcher = fs.watch("/some/path", callback);
    return () => {
      watcher.close(); // 插件卸载时自动执行
    };
  });
  
  // 依赖注入 — 声明需要哪些服务
  ctx.inject(["ctx.shell", "ctx.fs"], (shell, fs) => {
    // 只有 shell 和 fs 都可用时,这段代码才会执行
  });
}

Cordis 记住了三件事:系统想让谁运行,资源由谁撤销,依赖失效会影响谁。

flowchart TD
    subgraph CORDIS_EXIT["Cordis 退出机制"]
        LOAD["Loader 读取配置"]
        LOAD --> FIBER["创建 Fiber"]
        FIBER --> EFFECT["插件注册 effect"]
        EFFECT --> DEP["声明依赖 inject"]
        DEP --> CHECK["检查依赖是否满足"]
        
        CHECK -->|满足| ACTIVE["激活插件"]
        CHECK -->|不满足| WAIT["等待依赖就绪"]
        
        ACTIVE -->|卸载| DISPOSE["逆序执行 disposer"]
        WAIT -->|依赖消失| DEACTIVE["停用消费者"]
    end

差异的本质:Pi 把退出边界交给扩展作者,dsh 把一部分跨插件关系交给运行时管理。前者在扩展少的时候更简单,后者在扩展复杂时更能保证清理的完整性。


九、选型决策树

flowchart TD
    Q1["你要做什么?"]
    Q1 -->|"写代码、改 bug"| Q2["需要多模型切换吗?"]
    Q1 -->|"搭建 Agent 平台"| Q3["需要运行时热插拔吗?"]
    Q1 -->|"研究 Agent 架构"| DSH2["dsh,看 Cordis 怎么管运行时"]
    
    Q2 -->|"否,固定用 DeepSeek"| PI["Pi,省成本"]
    Q2 -->|"是"| PI2["Pi,pi-ai 统一 API"]
    
    Q3 -->|"是"| DSH["dsh,Cordis 的 effect 系统"]
    Q3 -->|"否"| Q4["插件生态重要吗?"]
    
    Q4 -->|"是"| DSH["dsh,6000+ 插件"]
    Q4 -->|"否"| PI["Pi,简单可靠"]

十、不是替代,是分化

写到最后,我想说清楚一件事。

Pi 和 dsh 不是同一个产品的两个版本,而是 Harness 这一层正在分化的两极。

Pi 代表”最小内核 + 用户掌控”。它的核心判断是:模型已经够聪明了,不需要你用一整套产品规则把它喂饱。给模型最少的工具、最少的提示词,让它自己发挥。代价是扩展能力有限,不适合做平台。

dsh 代表”可替换平台 + 生态供给”。它的核心判断是:Agent 的竞争力不在模型,而在于 Harness 能组装多少能力。模型适配器、工具注册表、甚至 Agent Loop 本身,全都可以换。代价是复杂度高,学习曲线陡峭。

它们选择了两条相反的路径,但共同接受了一个前提:模型是可替换的后端,决定体验差异的是 Harness。

所以别再问”哪个更好”了。问自己:你是想用 Agent 干活,还是想造 Agent 给别人用?