DeepSeek 开源了名为 Harness 的 Agent 开发框架,采用「面包板」式模块化架构,文件系统、终端、LSP、Web访问等均为独立插拔包,适合需要自建 Agent 编排系统的团队参考其设计思路。
DeepSeek V4 Pro 发布半天后,DeepSeek Harness(开发者预览版)就开源了。
我读完代码库结构后的第一反应:这不是又一个 Codex。这是一个面包板。
超过 230 个 workspace 成员。文件系统、终端、子进程、PTY、语言服务器、Web 访问、skills、子 agent、工作流、plan 模式、会话持久化、设置、凭据、遥测——几乎每种能力都对应一个独立的包。
如果说一个典型的 agent 项目是一台预组装电脑,那 Harness 就是一块非常大的面包板:模型、工具、UI、存储、安全策略和上下文管理都可以插上去——也可以拔下来。
我们自己也在做 agent 编排和模型网关,所以我读这个代码库时带着一个很具体的问题:哪些设计我们明天就该抄过来?以下是我学到的东西。
一个值得停下来玩味的细节:为什么叫 Harness。
这个词的本意是马具、线束、约束装置。抽象出来说:它把动力连接到能做功的机械上,同时防止那股动力失控乱窜。
应用到 AI 上,Harness 把模型连接到文件系统、shell、代码编辑器、Web 及其他 agent——同时记录它做了什么、约束它能做什么,并在失败时决定是重试、取消、压缩上下文,还是把问题交回给用户。
这个命名本身就是一个判断:模型是马,不是车。你不需要它跑得更快;你需要的是它拉的东西真的能到达目的地。
这也解释了代码量的问题。仅凭三个问题——工具调用能并行执行吗、cancel 真的能杀掉子进程吗、工具结果会不会污染上下文——就足以支撑起一堆包。
这个项目构建在 Cordis 微内核之上。一个运行中的 Harness 本质上就是一个 Cordis Context:包向它注册服务、事件和能力,一个配置文件把它们组合成一个可用的 agent。
packages/core/ 存放 Session、System Prompt、Tools、Agent 和 Agent Loop。围绕它的是各种能力包:llm/ 用于模型适配器和流式输出,shell/ subprocess/ terminal/ 用于一次性命令、进程树和持久终端,fs/ 用于文件 IO 和策略限制,lsp/ 让 agent 拥有语义级代码导航而非文本搜索,web/ 用于搜索和抓取,skill/ 用于可复用技能,subagent/ 和 workflow/ 把单个 agent 扩展为可委托、可编排的系统。
但让我停下来的是三层分离:接口、实现、消费者。
以 Bash 为例。接口定义了"执行一条命令"是什么意思。本地实现真正地派生进程。面向模型的工具包把这种能力转换成模型能理解的 schema 和结果。
如果将来本地 shell 变成了远程容器、云沙箱或企业执行平台,你只需替换实现层——不用重写模型工具或 agent 循环。
这一点对我们来说直接可用。运行网关最痛苦的地方在于,"换一个上游"通常意味着动业务代码。这个分离想说的是:定义一种能力、实现它、向模型呈现它,是三件不同的事。不要把它们写在同一个地方。
插件架构落在 cordis.yml 上:插件名称、稳定 ID、参数——决定当前 agent 拥有哪些能力。
同一套代码库可以变成截然不同的产品。加一个 LLM 适配器、文件系统、Bash 和 TUI,你得到一个终端编码 agent。把 UI 换成 Web 插件,你得到一个浏览器应用。用无头入口,它接收一个任务、完成模型和工具轮次、打印答案然后退出。给它加上 ACP 或 JSON-RPC 前门,它就变成一个供其他程序驱动的自动化服务。
配置支持叠加层——TUI 和 Web UI 共享一个基础配置,再堆叠各自的 UI 插件,个人配置放在最后。
有一个坑值得记录:配置补丁是替换目标插件的整个配置。不是深度合并。写一个新字段,现有的 API key、base URL 或其他参数可能就跟着没了。这种行为是明确的,但不符合初次使用者的直觉。我敢打赌这会成为最高频的问题类型。
大多数早期 agent 项目简化为几行代码:发消息给模型、执行任何工具调用、发回结果、重复直到输出文本。
Harness 也这样做,但把它拆成了严格的生命周期。用户输入打开一个 Turn;一个 Turn 包含多个 Step;一个 Step 对应一次模型请求及其后续的工具执行。在请求之前,它组装 system prompt、运行时环境、工具 schema 和会话消息。之后,流式片段、完整消息、工具调用、工具结果和 finish reason 都进入事件流。
工具也不是"拿到函数名、调用它"这么简单。每个工具都经过 pre-policy(一个不可逆的安全 guard)、执行、post-processing、内容正规化和结果通知。一个工具可以声明在某些参数下调用是并发安全的,因此调度器可以并行化连续的只读工作;任何会修改状态或无法证明安全的操作成为一个 barrier,必须在前置工作排干后单独运行。
原文有一句话我很喜欢:这看起来像在一条乡镇公路上安装空管系统。但是一旦 agent 在搜索十个文件、运行测试、同时接受用户随时可取消的新指令,这些规则很快就从"过度工程"变成了"根据事故报告,你本来希望自己有这套东西"。
我完全同意,因为几乎我们踩过的每一个坑都在这个层面。用户在半途输入了一些内容——那是下一个任务,还是对当前任务的修正?Harness 区分了队列消息、注入的上下文和引导,并使用收据来确认某条引导指令是否真的进入了特定的模型请求。
它不仅关心消息是否被收到。它关心模型在哪一步看到了它。任何构建过 agent 的人都能立刻认识到这个差异。
这是我最想抄过来的单一设计。
规则:模型看到过的任何东西,都必须能通过日志重建出来。
用户消息、运行时上下文、模型请求信息、流式输出、工具调用和结果、压缩事件、权限切换、取消原因——所有这些都作为事件进入一个只追加的会话流。UI、持久化、resume、fork、遥测和回放不需要各自维护自己"大致正确"的状态;它们都从同一个事件源派生。
它解决了 agent 系统中最难的问题:当一次运行出错时,我们真的能知道模型当时在看什么吗?
如果你只存储最终的聊天文本,关键因素就没了。也许工作区状态在请求前刚刚被注入。也许工具结果被截断了。也许模型路由自动切换了。也许用户在半途改变了方向。
这对网关工作至关重要。我们之前发不过一个评估:请求说 kimi-k3,响应自报 kimi-k2.7-code。它正确回答了问题,但身份错了。没有能重建现场日志,这类问题你永远发现不了——更不用说事后归因了。
会话持久化本身也是一个插件,有 JSONL 和 SQLite 两种后端。Resume 继续原始会话;fork 从一个确定的历史边界派生出一个新的会话。
Web UI 附带四个 agent 预设。它们不是四个独立的 agent,也不是 prompt 风格的改变——它们是同一个 Harness 主机在会话中加载不同工具、prompt 和运行时能力的结果。
Standard 是完整的编码 agent:文件编辑、shell、搜索、skills、plan、goals、子 agent 和工作流。
PTC 保留一切,但通过 Code Mode SDK 呈现工具——模型写 TypeScript 并在一个 run_code 内组合多个步骤,减少了长调用链上的往返次数。
Minimal 只给两个工具:持久化 Bash 和 str_replace_editor。更小的工具集意味着更少的选择负担和上下文负担,适合定义良好的任务——你只想让 agent 直接行动。
Creative 添加了运行时检查、临时插件实验和预设创作——agent 可以探索和重新组合自己的运行时。
最有说明力的是 Minimal。它反着证明了一件事:更多工具不一定更好;大的工具集本身就是一种上下文负担。我们都有一种冲动,给 agent"多配几个工具以防万一"。在这里,更少地给出工具被做成了一个官方预设。
在 Creative 模式背后是一套自引用的 Cordis 工具:agent 可以检查运行中的插件树,并动态挂载或卸载临时插件。
这听起来像是在汽车行驶中换发动机,所以它默认不开启。
我觉得比这个能力更值得注意的是它的处理方式:动态插件仍然在 Cordis Context 和 Effect 语义下运行,注销有明确的清理路径。自修改 agent 很容易退化成 demo;Harness 至少把它放到了一个现有的插件生命周期里。
一旦编码 agent 拥有了文件系统和 shell 访问权,它就能修改代码、安装依赖、启动进程、在主机上动手脚,超出工作区范围。
Harness 把这当作基础设施,而不是一个勾选框。默认是 workspace-write,把执行和文件更改限制在工作区和允许的临时目录内,升级需要 ask 审批策略。一种更宽松的 danger-full-access 存在,但部署者必须明确选择它。它没有被包装成看起来无害的兼容性选项。
有三个细节让我觉得异常专业:
被 guard 拒绝的操作不能被后续插件重新允许。这堵住了"绕过去"的路。
文件系统、Bash 和子进程共享一个沙箱策略。不存在"命令受限但文件工具可以绕过"这种分裂边界——这正是最近 OpenAI–Hugging Face 事件的形态,模型没有互联网,但它能调用的包服务有。
它Fail closed。如果系统无法确认隔离真的在生效,它就拒绝运行,而不是悄悄降级到无保护执行。
最后这一点值得强调。大多数系统,在不确定时会选择"先启动再说"。这个系统选择"停下"。考虑到这个 agent 真的可以操作你的机器,这个默认设置比任何安全功能列表都更能说明这个团队的判断力。
关于 SDK 与产品。只看 Web UI,很容易把 Harness 解读为 DeepSeek 的 Codex。但这个代码库的重心是可替换的能力接口、事件驱动的生命周期、权威的会话日志和声明式组合——成品 agent 只是这个 SDK 的第一个客户。这是一个根本不同的产品命题,它决定了天花板的高度。
关于模型和 Harness 的分工。模型设定了智能天花板;Harness 决定这种智能如何进入真实环境、使用工具、保留状态和在权限边界内工作。对于企业开发者来说,后者通常比聊天窗口里多几个按钮更重要,因为它决定了这个系统能否被审计、扩展、替换和维护。
而值得停下来琢磨的那一条:它重新定义了,我们视为理所当然的三件事。Agent 不应该是一个越来越臃肿的循环,而应该是一组可组合、可观察、可替换的能力。会话不应该是聊天记录,而应该是一份实际运行过的记录。工具不应该是函数,而应该同时携带策略、日志和呈现协议。
我把这三句话放在我们自己 agent 编排设计文档的顶部——因为我们之前写的在所有三个方面都恰好相反:不断膨胀的循环、只存储聊天的日志、仅仅是函数的工具。
所以值得关注的不是 Harness 是否会取代你今天使用的编码助手。真正值得关注的是,它把"如何构建一个 agent"从写代码变成了组装积木。
来源:机器之心,"DeepSeek Harness 开源:一切皆插件"。代码库:github.com/deepseek-ai/deepseek-harness。Cordis 设计论文:github.com/cordiverse/paper。技术细节基于原文和官方代码库;手工测试结果引用自机器之心的预览测试,非我方测试。