上个月我连写两篇拆解 DeepSeek Harness(dsh):一切皆插件:DeepSeek Harness 的野心与收敛鸿沟 说它开源的不是工具,是运行时;一切皆插件:真正硬核的是三个细节 拆了注册即副作用、事件日志做脊柱、乱序完成按序写回这三个工程承诺。当时我的结论是「可以吹,但不必急着用」——预览版、破坏性变更写在官方文档里,日常编码不如用成熟的 Coding Agent。
几周后打脸了:我们把一支真实的钉钉数字员工团队装在了它上面。项目叫 DWH(DingTalk Workforce Harness),四个员工——通用助手 default、管理员 dev、HR 助手 hr、老板秘书 assistant——各自是一个独立的 dsh 进程,在钉钉里各管一摊、各有权限、互相平级。这篇讲为什么「不必急着用」的判断没错,但「怎么用」的答案错了:开源 Agent 运行时的正确用法,不是把它当产品直接用,而是把它当操作系统,在上面建一层「员工管理制度」。
一、框架给你一个全能 Agent,企业要的是一群有限权的员工
先说清楚为什么要多包一层。
dsh(以及 OpenClaw 这类框架)的默认形态是一个全能 Agent:一个 loop、一套工具、一份记忆,什么都能干。个人助理场景这是优点。但企业场景里,「什么都能干」恰恰是第一个要消灭的属性——我在钉钉数字员工架构实践里写过这个区别:数字员工不是装进钉钉的 OpenClaw,它是组织架构里的一个岗位,岗位的第一要求是稳重——不能做它不应该做的事。
岗位这个概念拆开是三样东西,而这三样框架都不提供:
| 组织概念 | 对应到 Agent | dsh 提供的原料 |
|---|---|---|
| 岗位说明书 | 这个员工能做什么、怎么算做好 | 无(只有 persona 插槽) |
| 权限边界 | 哪些工具、哪些数据、哪些会话 | 插件装卸机制 |
| 绩效考核 | 可重复的验收,而不是「看起来能跑」 | 事件日志 / 轨迹 |
DWH 做的事,就是把这三样补成制度,然后用 dsh 的插件机制把制度编译成员工。
二、一个员工 = 一份 SPEC 编译出的一个 profile
DWH 的组装公式:
基础能力裁剪(_base.patch.yml,全员共享)
+ 通用插件(事件桥、前缀寻址、能力清单)
+ 岗位能力插件(policy-qa、web-search、employee-loop…)
+ 本机 overlay(~/.dwh/employees/<name>/patch.yml)
= 一个 dsh profile = 一个独立进程 = 一个员工
每个员工一份 SPEC.md:谁触发、做什么、输入输出、权限、失败行为、验收用例。关键纪律是一份 SPEC 应当能确定性地编译出一个 profile——挂哪些插件、禁哪些、什么 persona、什么访问控制,全部从合同推导,而不是散在五个 yaml 里靠人对齐。改行为必须在同一个 change 里改对应的 SPEC 段落。
这里踩过最值得分享的一个认知坑:裁剪是安全边界,不是优化。 DWH 的员工 profile 禁掉了约 50 个插件——全部 ui-*、tool-bash、tool-fs、所有 subagent/workflow,以及 agent-presets 本身。面向外部用户的数字员工不得开 bash/fs,这不是「暂时用不上」,这是攻击面。
两个非直觉的细节,换任何插件化运行时都会遇到:
- 禁 presets 是其余禁用生效的前提。 dsh 的 web-app bundle 关掉约 22 个面向模型的插件,前提是「每个 session 会挂一个 preset 把它们补回来」。presets 一关,没人补——这正是想要的锁定,但它静默降级,不报错。你以为的最小工具集,其实取决于一个你没注意的补货机制。
- 两种失败模式音量完全不同。 patch 里写错插件
id:是静默的——只往没人读的 ring buffer 打一行日志,你以为禁掉的插件照常运行;而inject:依赖不满足是致命且响亮的——boot 期直接拒绝启动。推论:禁用一个服务提供者时,必须把所有 inject 它的插件一并显式禁用。唯一的静默侧检测手段是干跑--dump-config。
这正是 #348 里那三个细节的另一面:可替换、可回滚、可追溯是运行时给的承诺,但承诺不替你写配置——配置层的语义陷阱,得自己用 make check 这类闸门兜住。
三、消息主线:Router 只广播,员工各自准入
多个员工共用一个钉钉登录态,消息怎么分?DWH 的答案反直觉地保守:Router 不按员工拆流,只广播。
dws event consume(仅 Router 持有)
→ 入站 journal 持久保存
→ Unix socket 广播(员工 ACK,重连补投)
→ 各员工独立:防自环 → 通道准入 → 前缀寻址 → 控制口分流 → LLM
→ 回复持久保存 + 平台送达确认
三条设计决策值得单拎:
- 寻址只减不增。
!hr 年假怎么算只有 hr 应答;!foo全员静默;无前缀消息由开启了「主动说话」的前台员工各自按准入判定。寻址插件是纯函数,不注册工具、不经 LLM——路由这种确定性决策不该消耗模型的不确定性。 - 重放不等于重做。 Router 持久记录入站消息,按员工 ACK 重放未确认消息;回复投递另有持久记录。进程崩了重启,补的是「没送达的消息」,不是「没做完的业务」。
- 准入 fail-closed。
channel.json缺文件 = 全部拒绝;配置损坏、会话未登记、发送者不在名单,一律拒。而且过滤发生在返回给模型之前——无权的文档连标题都不能进上下文,否则模型会说「有一份《管理层薪酬办法》但你没权限」,标题本身就是泄漏。
插件层还有一条流过血的规矩:调外部命令用 ctx.subprocess(argv 数组、不经 shell 解释),永远不用 ctx.shell(bash -c 单字符串)——模型生成的任何自由文本参数,审批意见、备注,都是活的注入点。
四、dev:把 HR 制度也做成一个员工
最有意思的角色是 dev。它不是超级管理员后门,而是一个权限被同样裁剪过的员工:没有 shell、没有文件系统工具,只能通过注册的工具做管理操作——!dev 入职 hr、!dev 离职 default、!dev 给 hr 主动说话 开、!dev 装载 <plugin> 到 <name>。代码工作?它委派给本机 Coding Agent(Claude Code / OpenCode),双循环异步跑,钉钉会话不被 20 分钟的编码任务堵住。
dev 有两条制度性约束,我认为比任何技术细节都重要:
- 最后一位前台保护。 dev 不能移除最后一位在岗、通道有效、开启无前缀应答的前台员工。组织不能把自己裁到没人接待。
- dev 不能对自己入转调离,也不能卸载自己的默认插件。管理权不含自我豁免,也不含自我了断。
换前台的流程被刻意设计成「先入职并开启新员工,再关闭原员工」——和真实 HR 的交接纪律同构。在钉钉里发几条消息就能完成一次换岗,而每一步都有工具回执和状态回读,这套体验是「员工管理即聊天」的:管理者不需要 SSH。
五、EVAL 分层:考核指标刻意不合并
「看起来能跑」和「验收通过」之间隔着整个绩效体系。DWH 的 evals 按对象分层,且刻意不合并:
| 层 | 量什么 | 在链路哪 | 速度 |
|---|---|---|---|
| 权限单测 | 该拒的有没有拒 | 纯函数,全离线 | ms |
| 检索 eval | 正确文本有没有交给模型 | LLM 调用上游 | 秒 |
| persona eval | 模型说出来的话有没有破规则 | LLM 调用下游 | 秒 |
| e2e 冒烟 | 链路今天通不通 | 真机、真钉钉 | 分钟 |
为什么不合并?因为混在一起,分数掉了你分不清是检索退化还是措辞退化。这背后有一组让我印象深刻的真实数字:某次真机对话同时违反了 3 条 persona 规则,而当时 422 条离线单测全绿、检索 eval 100%、8 次工具调用零报错、配置干跑干净——整套验收一条都没红。原因不是闸门写得差,是它们全部活在 LLM 调用的上游或旁路,没有任何一道在看「模型最终说出来的那段话」。
这就是我在私有 Eval 是终极护城河里说的判据分层问题的实战版:RSI 的输入不是更强的模型,是更多互相独立的判据。 检索那一层后来靠换成本地段级 BM25 把 answer 从 44.1% 拉到 100%;persona 那一层靠 13 条用例先量检查器自己的假阳/假阴,再去量模型。目前全量 1127 条测试通过,外加一轮 31 个关键步骤的真机验证。
六、轨迹演进:让员工在真机数据上被改进
最后一块拼图回到 #348 说的「事件日志是脊柱」。dsh 的 session 是仅追加的事件日志,模型看见的一切都能回到日志里找到——DWH 把这份日志变成了改进员工的原料。
dev 装载了 employee-evolution 插件,流程是严格的五步:
取证(list → inspect,保存证据快照)
→ 形成方案(propose:完整 SPEC/EVAL/提示词 + 事件引用,不改运行状态)
→ 回读(get)
→ 应用(apply,版本冲突就拒绝)
→ 验证(另行运行 EVAL,比较实际响应)
两条边界设计比功能本身更值得学:
- 轨迹是分析材料,不是操作授权。 员工在轨迹里「说」自己需要什么权限,不构成任何权限变更的依据。
- 应用成功只证明保存了配置,不证明质量变好。 方案里永久记录
behaviorVerification: not_run,行为验证另行报告 pass / fail / blocked。「配置已保存」和「效果已验证」是两个字段,不允许互相冒充。
这正是我在数字员工的自我进化:从 Harness 开始里的论点——RSI 跑在生产数据上,不跑在算法上。dsh 把轨迹做成了一等公民,DWH 只是顺着这个脊柱往上接了「诊断 → 方案 → 应用 → 验证」的管线。运行时给机制,制度给纪律。
七、模型会吞噬框架,但吞不掉职责
必须正面回应一个反方观点。我在模型正在吞噬 Agent 框架里论证过:模型每强一代,框架的能力层就被吃掉一层——loop、工具调度、上下文管理,迟早都是模型内置的。那 DWH 这层「员工管理制度」会不会也是过渡产物?
我的判断是不会,因为被吞噬的和不被吞噬的属于不同层:
- 能力层(loop、工具执行、会话压缩)——模型和运行时的事,dsh 自己都说「没有不可替换的核心」,这层随它进化;
- 职责层(谁能做什么、对谁负责、怎么验收、出错找谁)——这是组织问题,不是模型问题。模型再强,也不会自带「最后一位前台不能被移除」「无权的文档标题不能进上下文」「配置保存不等于效果验证」这些制度。
DWH 的赌注正押在这条分界线上:员工定义(SPEC/EVAL/patch/轨迹改进方案)全部是框架无关的合同资产,插件全部挂在 dsh 旁边而不是打进 dsh 里面。哪天运行时换了——dsh 转正也好、被更强的东西取代也好——迁移的是编译目标,不是员工本身。这也是为什么敢 pin 一个 pre-1.0 的 0.1.1-rc.2:版本风险被隔离在「操作系统」一层,制度资产不参与陪葬。
八、几条带走的规矩
如果你也要在开源 Agent 运行时上建多员工系统,这几条是从 DWH 的血泪里能直接抄的:
- 裁剪是安全边界。 面向外部的员工禁 bash/fs/subagent,且确认没有 preset 机制把它们静默补回来;
- fail-closed 没有例外。 配置缺失、损坏、查不到,全部判拒绝——「没查到限制」永远不是放行理由;
- 过滤在返回模型之前。 无权内容连标题都不进上下文;每一步独立判权,read 不信任 search 的结论,因为模型会编 id;
- 确定性决策不经 LLM。 路由、寻址、能力清单都是纯函数控制口,可离线单测;
- 考核分层且不合分。 上游判「喂对了没」,下游判「说对了没」,真机判「通没通」;
- 保存 ≠ 生效 ≠ 变好。 三个状态分开记录,谁也不许冒充谁。
写在最后
回到开头那个打脸。「可以吹,但不必急着用」针对的是把 dsh 当日常工具——这个判断我维持不变,它仍是预览版。但把它当运行时底座、在上面建员工管理制度,是另一回事:它「一切皆插件、没有特权核心」的设计,恰好让「一个员工 = 一份合同的编译产物」成为可能。
一切皆插件是 dsh 的口号;把它推到组织层,就是一切皆员工——岗位说明书可编译、权限边界可裁剪、绩效可分层验收、改进可循轨迹回溯。框架负责让 Agent 跑起来,制度负责让它配得上一个岗位。
你的组织里,第一个该入职的数字员工是哪个岗位?它的「岗位说明书」能编译成什么?欢迎留言聊聊。