DeepSeek 开源了他们的 Agent Harness(dsh),这不是又一个 LangChain 翻版,而是一套从底座重新设计的插件化 Agent Runtime。本文拆解它最值得关注的 5 个架构决策。
一、它不是"又一个 Agent 框架"
市面上 Agent 框架的典型架构长这样:
框架核心(不可变) ├── ModelClient(内置 OpenAI 适配器) ├── ToolRegistry(内置工具注册) ├── AgentLoop(内置 ReAct 循环) └── 你的代码(调用框架 API)
核心是"上帝类",你的代码是外围。要改循环逻辑?继承重写。要换工具策略?包装代理。改多了就变成框架 fork。
DeepSeek Harness 走了另一条路:没有不可变的核心。
dsh 启动 ├── 加载 profile(声明式配置) │ ├── dsh-base bundle(基础插件层) │ │ ├── agent-loop 插件 ← 循环是插件 │ │ ├── tools 插件 ← 工具系统是插件 │ │ ├── session 插件 ← 会话日志是插件 │ │ ├── llm 插件 ← 模型适配是插件 │ │ ├── sandbox 插件 ← 沙箱是插件 │ │ └── ... │ ├── dsh-web-app bundle(Web UI 层) │ └── cordis.patch.yml(你的覆盖) └── 运行
agent-loop 本身是插件。这意味着你可以用一个完全不同的循环实现替换默认循环,不用 fork 任何代码,只在配置里换一行。
二、5 个独特设计
设计 1:Cordis —— 注册是可逆的副作用
大多数插件系统的"注册"是单向的:register(tool) 之后,工具就永远存在。卸载?没有这个概念。
dsh 底层的 Cordis 框架做了一个根本性的改变:注册是 effect(副作用),effect 有 disposer(清理器)。
// 注册一个工具,返回清理函数const dispose = ctx.effect(() => { ctx.tools.register(myTool); return () => ctx.tools.unregister(myTool); // 卸载时自动调用});// 热卸载插件时dispose(); // 所有注册自动回滚
这意味着:
- • 插件可以热加载:运行中加载一个新工具插件,立即可用
- • 插件可以热卸载:卸载时所有注册自动回滚,进行中的 Agent 不受影响
- • 插件可以热升级:先卸载旧版(注册回滚),再加载新版(重新注册)
你的 Agent 正在执行第 3 步,此时有人加载了一个新的搜索工具插件。Agent 在第 4 步就能看到这个新工具。不需要重启,不需要暂停。
设计 2:Session Log —— 事实的唯一来源
大多数 Agent 框架的"状态"是一个 mutable 对象:
// 典型做法class AgentState { List<Message> messages; // 不断追加 int currentStep; Status status;}
dsh 把状态变成了 append-only 事件日志:
turn/start ← 一轮对话开始 user/message ← 用户说了什么 step/start ← 一步开始 assistant/chunk ← 模型流式输出的一个片段 assistant/chunk assistant/message ← 模型完整回复 tool/call ← 模型要调工具 tool/result ← 工具执行结果 step/end step/start ← 下一步 ...turn/end
关键原则:Model-visible means logged(模型可见的必定被记录)。
任何到达模型请求的内容,都必须能从日志重建。这不是建议,是运行时断言——如果你往模型请求里塞了日志里没有的内容,直接报错。
这个设计带来 5 个能力:
| 能力 | 说明 |
|---|---|
| 回放 | 从日志重放整个对话,逐步查看状态变化 |
| Fork | 复制一个会话到新分支,两个分支各自独立演进 |
| Resume | 从日志断点恢复执行 |
| 审计 | 谁、什么时候、做了什么,全有记录 |
| 可观测 | 所有变化都是事件,监听事件就能看到一切 |
设计 3:Capability Seam —— 一个能力三个角色
这是 dsh 最精巧的设计模式。一个"能力"不是"一个接口 + 一个实现",而是三个角色:
Service Definition(声明接口) "文件系统能做什么:读、写、编辑"Service Provider(实现接口) "本地文件系统" or "沙箱文件系统" or "远程 E2B 文件系统"Consumer(使用接口) "文件读写工具"(模型可调用的 tool)
为什么是三角色而不是一个接口?
因为"声明"、“实现”、"使用"的演进节奏不同:
- • 声明很少变(文件系统操作就那几个)
- • 实现经常换(本地 -> 沙箱 -> 远程)
- • 使用可能增加(新工具消费同一个文件系统)
如果合成一个角色,任何一个变化都牵动另外两个。
实际效果:dsh 的文件系统 Provider 从 fs-local(本地)换成 fs-sandbox(沙箱隔离)或 fs-e2b(远程 E2B 云沙箱),所有消费文件系统的工具代码一行都不用改:
ctx.fs(Service Definition) ├── fs-local(Provider:本地文件系统) ├── fs-sandbox(Provider:沙箱文件系统) └── fs-e2b(Provider:E2B 远程文件系统)tool-fs(Consumer:文件读写工具) -> 只依赖 ctx.fs,不关心背后是哪个 Provider
同样的模式用在整个产品上。换沙箱后端?Bash、终端、LSP 全跟着走,因为它们都通过 ctx.subprocess 消费子进程能力,而子进程的 Provider 从 subprocess-local 换成 subprocess-e2b 即可。
设计 4:Waterfall —— 运行时组合的中间件
传统装饰器是"编译时组合":你写代码时决定套几层、什么顺序。
dsh 用 Cordis 的 waterfall 事件实现"运行时组合":
// 拦截模型请求(在运行时动态注册)ctx.on('agent/request', (request, next) => { // 前置:改写请求 request.metadata.traceId = generateId(); // 委托给下一个监听器 const response = next(request); // 后置:记录响应 log(response); return response;});// 另一个插件:给请求加 retryctx.on('agent/request', (request, next) => { try { return next(request); } catch (e) { if (isRetryable(e)) return next(request); throw e; }});// 又一个插件:注入上下文ctx.on('agent/request', (request, next) => { request.messages.push({ role: 'system', content: '当前时间: ...' }); return next(request);});
三个插件互不知道对方存在,但它们会按注册顺序组成管道。不需要在 main 里手动套娃,每个插件独立注册,运行时自动组合。
和装饰器的区别:
| 维度 | 装饰器 | Waterfall |
|---|---|---|
| 组合时机 | 编译时(写代码时) | 运行时(插件加载时) |
| 组合方式 | 构造函数嵌套 | 事件注册 |
| 动态增删 | 不行 | 可以(effect + disposer) |
| 顺序控制 | 构造顺序 | 注册顺序 + prepend |
设计 5:Turn / Step —— 事件驱动的循环
大多数 Agent 框架的循环是一个 while 循环加 if-else:
while (hasSteps && !done) { response = model.chat(request); if (response.hasToolCalls()) { executeTools(); } else { done = true; }}
dsh 的循环是事件流,每一步都是事件,每个事件都是扩展点:
turn/start │ ├── agent/pre-step ← 插件可拦截/改写/拒绝 │ ↓ ├── step/start │ ├── user/message ← 用户消息写入日志 │ ├── agent/request ← 插件可改写模型请求(waterfall) │ ├── llm/stream ← 插件可拦截模型调用(waterfall) │ ├── assistant/chunk* ← 流式片段写入日志 │ ├── assistant/message ← 完整回复写入日志 │ │ │ ├── tool/call* ← 工具调用写入日志 │ │ ├── tools/pre-execute ← 策略/权限/沙箱检查(waterfall) │ │ ├── tools/execute ← 实际执行(waterfall,可包装 timeout/retry) │ │ ├── tools/post-execute ← 结果改写/审计(waterfall) │ │ └── tool/result* ← 结果写入日志 │ │ │ └── step/end │ ├── agent/turn-stopping ← 插件可决定是否继续(serial) │ └── turn/end
关键区别:循环逻辑不写在代码里,而是散布在事件中。
想加"每次调模型前注入当前时间"?不用改循环代码,监听 agent/request 事件即可。想加"危险工具调用前需要审批"?不用改工具执行代码,监听 tools/pre-execute 事件即可。
工具执行管道本身就有 5 层:
tools/pre-execute ← hooks、权限、沙箱策略 ↓monotonic guards ← 不可重排序的安全检查 ↓tools/execute ← timeout、retry、metrics 包装 ↓tools/post-execute ← 接受/拦截/替换/追加上下文 ↓tools/result ← 冻结最终结果,写入日志
三、对比:它比"主流方案"强在哪
vs LangChain / LangGraph
LangChain 的核心是"链"——预定义的调用序列。扩展靠继承和包装。
dsh 的核心是"插件树"——运行时组装的能力集合。扩展靠注册事件监听。
| 维度 | LangChain | dsh |
|---|---|---|
| 扩展方式 | 继承/包装 | 插件注册 |
| 循环可改 | 不行(内置 ReAct) | 可以(agent-loop 是插件) |
| 热加载 | 不支持 | 支持(effect + disposer) |
| 状态管理 | 内存对象 | append-only 事件日志 |
| 工具治理 | 无内置 | pre-execute / guards / post-execute |
vs Claude Code / Cursor
Claude Code 和 Cursor 是闭源产品。dsh 把同类架构开源了:
| 维度 | Claude Code | dsh |
|---|---|---|
| 沙箱 | 内置 | 插件化(landlock / E2B / Docker) |
| 工具系统 | 固定 | 可扩展(Capability Seam) |
| 模型适配 | 只有 Claude | 多 Provider(DeepSeek / pi-ai / replay) |
| 可定制 | 配置文件 | 插件 + YAML 配置 |
| 持久化 | 内置 | 插件化(JSONL / SQLite) |
四、工程实践亮点
1. 声明式组合:Profile + Bundle
# cordis.yml —— 一个 profile 的声明plugins: - name: dsh-llm-deepseek # 用 DeepSeek 模型 config: api-key: ${DEEPSEEK_API_KEY} - name: dsh-tools # 工具系统 - name: dsh-sandbox-local # 本地沙箱 - name: dsh-agent-loop # 循环驱动 config: max-steps: 50 - name: my-custom-trace-plugin # 你自己的插件 config: endpoint: https://...
换模型 = 改一行配置。换沙箱 = 改一行配置。加自定义逻辑 = 加一个插件行。不写一行胶水代码。
2. 补丁层叠
配置是分层叠加的:
bundle 层(dsh-base 声明的默认插件) ↓ 覆盖profile 层(cordis.patch.yml) ↓ 覆盖home 层(用户全局覆盖) ↓ 覆盖命令行 --patch(一次性覆盖)
每一层都可以覆盖下层任何一行的配置。不需要 fork bundle,打补丁即可。
3. 不变量断言
dsh 不依赖"约定"保证一致性,而是用运行时断言:
- • “模型可见的必定被记录” → 运行时检查,违反直接报错
- • “一个 Provider 只能有一个实现” → 加载时检查
- • “插件注册必须有 disposer” → 编译时检查
这些不是文档里写的"最佳实践",是代码里强制执行的规则。
五、对自研 Agent 框架的启示
如果你在构建自己的 Agent Runtime,dsh 至少给了 5 个可以直接借鉴的设计:
1. 把循环变成事件流
不要写 while (true) { model.chat(); executeTools(); }。把每一步拆成事件,让扩展通过监听事件注入逻辑,而不是改循环代码。
2. 状态用事件日志,不用 mutable 对象
mutable state 不可回放、不可 fork、不可审计。事件日志天然支持这些。
3. 能力拆成三角色
不要只写 interface + impl。拆成 Definition(声明)+ Provider(实现)+ Consumer(使用)。换 Provider 不影响 Consumer。
4. 注册要有清理机制
register() 返回 disposer。支持热加载/卸载的前提是注册可逆。
5. 声明式配置替代胶水代码
最终用户不应该写代码来组装 Agent。YAML 配置 + 插件发现 + 补丁层叠,才是产品级的组装方式。
六、总结
DeepSeek Harness 的核心设计哲学可以用一句话概括:
没有不可变的核心。一切皆插件,注册皆可逆,模型可见皆被记录。
这不是"又造了一个轮子",而是重新定义了轮子的组装方式。从"框架核心 + 用户外围"变成"插件平铺 + 声明式组合"。这种架构的扩展成本极低——加任何能力都是"写一个插件 + 加一行配置",不需要理解、更不需要修改核心代码。
对于正在构建 Agent Runtime 的团队,dsh 是一份完整的参考实现。不用抄它的 TypeScript 代码,但它的 5 个设计决策值得逐个对标。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】


136

被折叠的 条评论
为什么被折叠?



