Skip to content

DeepSeek Harness 系列(五):把控制权还给运行时

开篇

我们可能不再需要一个 Agent Loop 了。

这不是标题党。系列前几篇已经从结构、运行、接入三个角度拆过 DSH:01 解释为什么值得看,02 拆解四层与 Cordis 树,03 把 Web UI 跑起来,04 谈企业业务接入。

这一篇专攻一个被前几篇略过的话题:控制权

DSH 把模型、工具、会话、执行循环、产品表层全部做成可组合能力之后,剩下的真正问题不是"还能不能跑 Agent",而是——

  • Agent Loop 该不该继续掌握最终控制权?
  • 当循环、工具和权限策略都能被替换时,谁还能保证系统行为是可信的?
  • 运行事实保存在哪里,谁有权修改它?

这三个问题分别对应本文的三个层次:控制权交接、事实层、治理


一、为什么"会写循环"远远不够

入门版 Agent 看起来很简单:把消息发给模型,把回答显示出来。一个 while 循环足以。这种结构让早期 Agent demo 看起来很轻:几十行代码就能跑通对话。

但一旦进入工程,循环中心化会立刻产生三个具体后果。

第一个后果:每次能力扩展都要碰核心循环。 加一个工具?改循环。加审批?改循环。换模型?改循环。循环成为单点瓶颈——任何能力变化都必须从循环出发。

第二个后果:循环的状态语义被复用,迁移成本极高。 循环里塞满了"先 A 再 B 然后 C"的隐式顺序。这些顺序不是协议,而是约定。一旦循环需要被替换,新的循环必须复刻同样的隐式顺序,否则行为变化会带来兼容性灾难。

第三个后果:可观测性被绑定在循环输出上。 调试、日志、回放都要穿过循环,循环本身成了观测盲点。你想回答"模型看到了什么",必须先穿透循环;想回答"工具为什么被调",也得先穿透循环。

这三个后果叠加起来意味着:循环中心化在工程上不是一个稳定形态。它只是 demo 阶段可用的临时结构。

DSH 的回答不是"取消循环",而是"把循环本身也变成可替换的插件"。它选择了一条比"循环中心化"更难、但也更有扩展性的路线——让 Agent Loop 退化到默认驱动器的位置。


二、Agent Loop:默认驱动器,不是最终裁决者

页面打开不是终点。真正的运行时是用户输入的那一刻才激活的。链路是:

用户输入 → Session 记录 → Agent Loop 领取任务
  → Prompt 组装上下文与工具 Schema → LLM 发起请求
  → Tools 检查并执行调用 → 结果回写 Session
  → Agent Loop 决定继续或结束

这不是简单的函数调用链,而是一连串控制权交接:

  • Agent Loop 推进轮次。
  • System Prompt 决定模型看见什么。
  • LLM 适配器 负责连接具体模型。
  • Tools 负责执行前检查、实际调用和结果处理。
  • Session 记录这次运行形成的持久事实。

权限、审批、超时、重试、上下文注入还可以通过事件进入流程,而不是写死在循环里。

因此,DSH 的 Agent Loop 更像默认驱动器,而不是不可替换的最终裁决者。这种结构避免把所有规则塞进一个巨型循环。新增审批策略不必改写模型驱动逻辑,替换执行环境也不必复制整套工具链。

代价是产品语义会跨服务、跨事件分布。一个新人想理解 DSH,光读 run() 函数不够。他需要知道 Agent Loop 推进轮次、System Prompt 决定模型看见什么、Tools 决定执行边界、Session 决定事实留痕——这四类对象在源码里谁也没有特权。

简单换成了复杂,集中换成了可解释。


三、Session 事件日志:运行时的事实层

控制权可以分散。但运行事实不能分散。

如果一个插件在请求发出前临时修改了模型上下文——比如注入了额外的 system message——但这种修改没有留下记录,系统就无法回答三个基本问题:

  1. 模型看到了什么?
  2. 当前结果如何恢复?
  3. 审计记录应该相信什么?

DSH 的答案是追加式 Session 事件日志

用户消息、模型输出、工具调用、工具结果、轮次边界先写入事件流;模型历史、Web 页面、回放、恢复、遥测再从日志投影出来。

DSH 把这条规则浓缩成一句话:模型可见,即应被记录。

这条规则的意义比"保存聊天记录"更大。它要求每一项会影响模型判断的输入,都能从事件日志重新构造。插件不能只改写临时请求对象,再让这段变化消失在内存里。这使 Session 成为运行时的事实层——插件负责改变行为,Session 负责保留行为发生过的证据。

为什么这条规则比"权限收紧"或"插件审批"更重要?因为它是事后唯一的还原手段。权限收紧只能降低坏事件发生的概率;插件审批只能降低坏插件进入系统的概率。但只要系统在运行、模型在思考,意外就一定会有——临时改写、版本回滚、模型升级、网络中断、上下文超限。意外发生后,唯一能依靠的就是事件日志。如果日志能完整重建"模型当时看到了什么",就能定位问题、恢复状态、重放过程;否则所有事后归因都变成猜测。

代价也很明确。事件结构一旦持久化,就不再是普通内部实现。新增能力时需要同时考虑序列化、回放、历史兼容和投影逻辑。插件可以快速迭代,日志协议却必须谨慎演进。


四、插件可替换 ≠ 插件可信

插件架构容易制造一种错觉:局部测试全绿,就认为产品可靠。事实上,单元测试只能证明局部行为。插件通过真实 Loader 组合后,仍可能因为依赖注入、默认导出、生命周期或构建产物差异而失效。

DSH 对源码设置按文件 100% 行覆盖率门槛,但项目文档明确指出,覆盖率只能证明代码执行过,不能证明功能以交付方式正常工作。因此,它还需要快照测试、真实 API E2E、浏览器快照和构建产物冒烟测试。它们分别验证真实组件组合、模型服务接入、用户可见页面和正式发布入口。这不是测试数量的堆砌,而是对插件架构的必要回应:系统越依赖动态组合,就越不能只验证单个包。

更棘手的问题是,插件可替换不等于插件天然可信。

DSH 支持动态 Cordis 包,允许模型定义并运行新的插件能力。动态插件即使运行在 VM 中,也不自动等于安全隔离。只要它可以通过受声明的服务访问文件系统、Web、终端或子进程,就已进入运行时控制面。

这时候摆上台面的问题是五个:

  • 插件来源是否可信?
  • 它声明了哪些能力?
  • 权限是否可以撤销?
  • 运行结果能否归因?
  • 错误插件如何回滚?

这些问题无法仅靠"插件化"解决。插件化解决的是能力怎样接入系统,治理解决的则是谁有资格接入、接入后能做什么、系统如何留下证据。

因此,一个可信的 Agent 运行时仍然需要普通插件无法绕过的基础能力:最小权限、能力声明、配置准入、隔离执行、审计记录和故障回滚。

最后一层是产品组合责任。

横向能力——模型、存储、沙箱、遥测——天然适合拆成插件。它们有明确接口,也可以跨产品表层复用。

但产品级能力——比如"制定计划并委派子 Agent 执行"——通常不是某一个插件的职责。它会同时涉及会话事件、Agent Loop、目标状态、权限审批、Host API、Web UI 和端到端测试。局部能力越容易替换,完整产品行为越可能分散。

Profile 和 Bundle 因此不能只承担"安装插件"的职责。它们还需要承担产品组合责任:一项能力由哪些插件构成,依赖哪些事件,失败后如何降级,通过什么场景验收。没有这层约束,插件树会越来越丰富,却越来越难解释完整产品究竟由谁负责。


回到三个问题

DSH 当前模块布局透露了它的野心:Web、Headless、ACP、SDK 对应不同交互表层;文件系统、进程、沙箱 Provider 对应不同执行环境;Goal、Job、Schedule、Workflow、Subagent 把一次对话延伸为持续任务。它试图构建的,是一套可被多种 Agent 产品形态复用的运行时基础设施。

但 DSH 的成败不取决于它能增加多少插件,而取决于它能否始终回答三个问题:

谁改变了这次运行?模型当时看到了什么?系统凭什么相信这次执行?

Agent Loop 与 Cordis 让能力成为可组合;Session 事件日志让事实可以重建;测试与治理机制则负责让变化不至于失控。这也是 DSH 最值得研究的地方:它没有假设未来 Agent 只有一种运行方式,而是试图管理变化本身。

下篇会拆开 Session 事件协议向上下游投影的具体结构——它会解释为什么日志协议必须谨慎演进,以及投影层与事件结构之间的契约。


系列目录