HARNESS / 代理工程下载 EPUB ↓
6 / 32

第五章:DeepSeek Harness 的日志、投影与插件边界

5.1 请求从可重建历史产生

默认 ReactLoopAgent 持有 Session、inbox、scope 和运行 phase。它的源码说明每次请求由会话日志派生。对贯穿案例而言,用户输入、助手消息、工具调用及结果不是孤立字符串,而是有顺序和身份的会话事实。DeepSeek Harness · agent.ts L96–110

Session 的 deriveMessages() 从当前有效 surface 生成模型历史,并根据 generation 维护派生缓存。这说明原始事件全集与本次模型消息不是同一个集合:日志可以保存很多事实,模型只接收投影后的有效视图。DeepSeek Harness · index.ts L982–996

5.2 持久事实和活跃事件分开

一个字符刚从模型流中到达,可以立刻展示,却未必需要每个字符都成为独立持久事件。工程区分会话日志、活跃 Agent 事件和能力扩展事件。研究某项信息时,先问它在进程重启后是否还必须存在。

例如,工具最终结果需要被恢复和后续请求使用;“当前屏幕显示了一段增量”则首先是活跃观察。二者可以有关联,但不能因为界面已展示一些内容,就把尚未结算的流当成完整助手消息。

5.3 插件协议决定谁能阻止流程

AgentEventDispatch 把 emit、serial 和 waterfall 分开。通知型 emit 不应否决生命周期推进;serial 明确等待串行处理;waterfall 可以包围下一段实现。DeepSeek Harness · dispatch.ts L52–66

这比“我们有事件总线”更具体。如果用通知事件实现权限阻断,调用方可能根本不等待你的异步判断;如果把所有 UI 监听都放进关键串行链路,一个缓慢展示插件又可能拖住执行。选扩展点之前,必须先确定它是观察、准入还是包围执行。

5.4 工具调度有保守默认值

ctx.tools.executionMode() 只有在工具声明并发安全且返回严格的 true 时,才选择并行。缺少声明、抛出错误或返回其他值都回退独占。DeepSeek Harness · index.ts L1309–1323

executeToolCalls() 及同文件的调度逻辑使用独占屏障和有界并行池。执行可以交错,但政策、结果和结果上下文仍然按模型顺序完成提交;中断时停止补充新工作,并等待已开始调用收尾。DeepSeek Harness · tool-calls.ts L58–72

这不是“所有读工具天然并行”的规则。工具可能读取共享会话状态,或被插件重新分类,所以工程在未开始调用前重新确认模式。并发安全是调用契约,不只是根据工具名称猜测。

5.5 插件卸载也是运行时工作

工厂层的 FactoryOwnership 追踪启动任务、活跃 Agent 和清理函数。插件卸载不仅是删掉注册表条目,还需要停止接受新工作、取消相关活动,并等待已有工作结束。DeepSeek Harness · index.ts L102–116

如果一个文件系统提供方先被卸载,依赖它的工具仍在运行,程序就可能进入无法解释的中间状态。由生命周期所有者统一安排顺序,是插件化的成本之一。对于小型 Harness,没有必要一开始复制整个容器,但应明确谁负责释放资源。

练习:一个分析插件只是统计 token 使用量,它的失败是否应该阻止工具执行?一般不应;但预算准入属于不同职责,可能需要同步否决。先确定用途,再选择通知或准入链路。

非官方学习读物 · 固定源码快照 · 原始代码保留各自许可。GitHub · 源码依据 · 实验代码