DeepSeek Harness 为什么能热换模型:插件依赖、事件日志与回滚机制

一次 web_search 背后,模型、工具、会话日志与 Agent Loop 如何被动态装配?本文沿完整调用链拆解 DeepSeek Harness 的插件依赖、事件留痕与可逆副作用。

让 Agent 去搜“今日 AI 热点新闻”,表面看只是一次工具调用。真正值得拆的不是搜索本身,而是这次调用之前系统已经完成了什么:模型接缝、工具注册表、系统提示词、会话日志、Web provider 和 Agent Loop 都已经被装进同一个运行时,彼此通过服务名找到对方,又能在替换或卸载时把影响撤干净。

DeepSeek Harness,命令行里叫 dsh,做的就是这层 ​Harness​。它不是模型,也不是单纯的聊天 UI,而是把模型、工具、上下文、权限、会话记录和执行循环连成一套可运行系统的底座。它目前处在 developer preview 阶段,版本仍可能出现破坏性变更,但它的架构取向已经很明确:把 Agent 的每一块都做成插件。

这篇文章不从产品体验聊起,只盯住一个工程问题:

当一套 Agent 系统需要随时换模型、换工具、换运行模式时,怎样保证它不是一堆写死的调用链,而是一棵可以被重新装配、局部替换、失败可回滚的插件树?
inline-01.png

图:运行时只替换受影响的插件与依赖链

先别把 dsh 看成一个固定应用

dsh 的启动过程更像一次 ​运行时编译​。命令行入口接住参数后,并不会直接启动一套写死的业务流程,而是先把多层配置合并成一份插件装配清单,再交给 Cordis 运行时把插件逐个挂上去。

这也是 dsh 文档里那句 “Everything is a plugin” 的实际含义。模型适配器是插件,工具注册表是插件,会话日志是插件,Agent Loop 也是插件。插件之间不直接持有彼此的实现,只认运行时上下文里的服务名,比如 ctx.llmctx.toolsctx.sessionsctx.systemPrompt

可以把整个系统竖着切成六层:

层级负责什么典型内容
Launcher接住 dsh 命令,解析 profile、patch 和启动参数CLI、启动参数、profile 选择
Composition决定这次装哪些插件,以及每个插件用什么参数Bundle、Profile、cordis.patch.yml
Runtime让插件发现服务、声明依赖、注册可撤销副作用Cordis Context、Service、Event、Effect
Agent Plane组织提示词、工具和模型调用,推动任务一步步执行LLM、Tools、System Prompt、Agent Loop
Data Plane记录会话事件,并从事件里重建模型上下文Session Event Log、Projection、持久化
Surface把同一套运行时暴露成不同入口Web、Client、Headless

插件真正活跃在中间四层。Composition 决定装谁,Runtime 保证装得上和拆得掉,Agent Plane 与 Data Plane 负责执行和留痕。Launcher 和 Surface 更像入口与出口。

配置不是参数表,而是插件树的输入

dsh 的配置采用分层叠加。后加载的层覆盖前面的层,同一个字段谁最后写,谁生效。
mermaid-01.png

图:Bundle、Profile、机器配置与临时 patch 逐层合并

前四层主要回答“装什么”:哪些插件启用,参数怎么填,某个插件是否禁用。环境变量更像最后一道总闸,回答“怎么跑”:家目录放哪、是否上报、全局开关怎么取值。它不属于那摞 YAML 清单,但能压过运行时行为。

这套分层的价值不在形式,而在边界清楚。

Bundle 是产品默认值,升级时可以跟着项目走;Profile 是一套使用方式,比如 Web 模式和 Headless 模式;机器级配置处理本地差异;--patch 只管一次调试。几类改动不混在一起,插件树才能在不同机器、不同场景下稳定复现。

例如 system-prompt 插件的 persona,基础 Bundle 可以留空,Web Bundle 再填入“你是一个 coding agent”这类说明。用户自己的 profile 还可以继续覆盖它。源码不需要知道最终是哪句话,启动时合并出的插件树会给出答案。

Cordis 只做运行时,不抢业务插件的活

dsh 使用的底层运行时是 Cordis,代码随仓库放在 vendor/ 目录中。它不负责搜索网页,不负责写代码,也不决定模型该说什么。Cordis 管的是更底层的三件事:

  1. 插件怎样声明自己需要哪些服务。
  2. 某个服务上线或下线后,哪些插件要跟着启动、暂停或重连。
  3. 插件注册过的工具、事件、定时器、连接,卸载时怎样按原路撤销。

这就是为什么 dsh 可以同时说“没有特权业务内核”,又仍然有一个 ​Cordis 运行时​。Cordis 是骨架,不是大脑。它给插件留出服务槽位,负责依赖解析和副作用回滚,但不替任何业务插件做决策。

一个插件通常会声明 inject。如果它需要 toolssystemPrompt,那这两个服务没有 active 之前,它就不会加载。Cordis 会把插件状态放在 fiber 上,常见状态包括:

状态含义
PENDING等待依赖服务上线
LOADING插件正在初始化
ACTIVE插件已经提供服务或完成注册
FAILED配置校验或启动过程失败
UNLOADING正在执行清理逻辑
DISPOSED已移除,不能重新启动

最容易误判的是 PENDING。它不是错误,只是依赖没齐。如果插件装了但没有反应,先看 fiber 状态,比直接翻异常日志更快。

一次 web_search 实际上由六类插件配合完成

用户输入“帮我搜一下今日 AI 热点新闻”以后,模型能调用 web_search,不是因为某个巨大的控制器写死了搜索逻辑,而是几类插件提前完成了各自的注册。

角色提供什么依赖关系
system-prompt带顺序的提示词 section 拼装器无核心依赖
session只追加的会话事件日志依赖类型与持久化基础设施
llm模型消息协议与适配器接缝适配器另行注册
tools工具注册、schema 暴露、执行前后把关依赖 systemPrompt
tool-web注册 web_search,定义参数、超时、结果格式依赖 toolswebsystemPrompt
agent-loop推动一轮轮模型调用和工具调用依赖 agentssessionsllmtoolssystemPrompt

这张表也是加载顺序的近似答案。零依赖插件先亮,需要服务越多的插件越晚亮。agent-loop 依赖最多,所以通常最后上线。它一亮,系统才真正有能力接住用户输入并推动完整任务。

web_search 本身还分了两层。上层 tool-web 负责让模型看到一个叫 web_search 的工具,包括名字、参数 schema、提示词说明和返回格式。下层 ctx.web 才是真正的 provider 接缝,后面可以接 DeepSeek 官方搜索,也可以换成 Exa 或 Perplexity。换搜索源时,模型侧工具定义不需要改。

这层切分很关键。模型只知道自己调用了 web_search,不需要知道背后是哪家搜索服务。工具插件只知道要调用 ctx.web.search(),不需要绑定某个 provider。provider 只负责把关键词变成搜索结果,不需要理解 Agent Loop。

从输入到答案,日志里会留下连续脚印

一次搜索任务可以被拆成八步:
mermaid-02.png

图:从用户输入到最终答案,每一步都经过服务协作并留下事件记录

每个关键动作都会进入会话事件日志。用户消息是 user/message,模型流式输出是 assistant/chunk,完整回复是 assistant/message,工具请求是 tool/call,工具返回是 tool/result,一轮或一步结束还会有 step/endturn/end

这里要分清两个概念:

概念含义
Turn用户发出一句请求,到系统把这件事彻底处理完为止
StepTurn 里的一次“问模型 + 执行模型要求的工具”

一个 Turn 里可以有多个 Step。模型第一次搜到的结果不够新,可以换个关键词再搜一次。③ 到 ⑤ 会循环,直到模型不再发起工具调用。

dsh 的会话日志还有一条强约束:模型能看到的信息,必须能从日志里重建。也就是说,不应该存在“发给模型了但没有记录”的上下文。这样做的收益很直接:任务可以重放,压缩后的上下文可以追溯,UI 和调试系统也能共享同一份事实来源。

默认开搜索,不默认开抓取,这不是随手配置

tool-web 包里不只有 web_search,也有 web_fetch。但发行配置默认只开搜索,fetch: false

原因在风险边界。搜索是模型给关键词,由搜索 provider 返回候选信息;抓取是模型直接指定 URL,让系统去访问目标地址。后者更容易触碰 SSRF 等安全问题,尤其当 provider 把安全防护留给调用方时,就不适合默认开启。

这类配置说明 dsh 的插件化不是“能注册就都开”。工具注册表要承担权限、审批、参数校验和执行前后钩子。能否默认暴露给模型,是 Harness 的安全决策,不只是功能清单。

热替换能局部发生,靠的是服务名和可逆副作用

假设把 DeepSeek 模型适配器换成另一家模型。理想结果不是整套系统重启,而是只影响依赖 llm 的部分。

过程大致是:

  1. 旧 LLM 适配器进入 UNLOADING
  2. 它注册过的 adapter 按倒序撤销。
  3. agent-loop 发现自己依赖的 llm 服务暂时不可用,退回 PENDING 并清理自身注册。
  4. 新 LLM 适配器上线,提供新的 ctx.llm adapter。
  5. agent-loop 依赖重新满足,再次加载。

会话日志、工具注册表、系统提示词和 Web provider 不需要跟着动。它们依赖的是稳定服务契约,不是具体模型实现。

换搜索源也类似。只要 ctx.web 的服务契约不变,tool-web 注册给模型看的 web_search 就不用改。provider 从 DeepSeek 换成 Exa,影响被收在 Web provider 这一层。

这就是插件系统真正有价值的地方:​替换的是局部实现,不是整台机器​。
inline-02.png

图:模型适配器变化时,只有相关依赖进入重连

自己写插件前,先想清楚装和拆

很多工具并不值得单独做成插件。读文件、写文件、改文件,如果没有长期资源、没有后台任务、没有复杂依赖,可以放进同一个工具插件里注册多条 tool。

适合单独做插件的,通常有几个特征:

特征说明
有初始化成本需要创建客户端、连接池、鉴权状态或缓存
有清理责任卸载时必须关闭连接、停止 watcher、清掉事件监听
有独立依赖依赖其他服务,或被其他插件依赖
有替换需求将来可能整包换掉实现,比如搜索 provider、沙箱、模型适配器

写插件时最重要的不是 apply(ctx) 里注册了什么,而是每一次注册是否都有对应的撤销路径。Cordis 自带的 ctx.onctx.tools.registerctx.systemPrompt.section 都会返回可撤销效果。定时器、文件监听、数据库连接这类运行时不知道的资源,需要自己包进 ctx.effect()

一个典型的工具注册逻辑会长这样:

ctx.tools.register(defineTool({
  name: 'web_search',
  description: 'Search the web for current information.',
  parameters: {
    query: {
      type: 'string',
      required: true,
      description: 'The search query.',
    },
  },
  timeoutMs,
  isConcurrencySafe: () => true,
  async execute(args, exec) {
    const input = parseSearchArgs(args)
    const result = await ctx.web.search(
      { query: input.query, maxResults },
      exec.signal,
    )
    return {
      ...result,
      sources: result.sources.map(projectSource),
    }
  },
}))

这段代码真正依赖的不是某家搜索服务,而是 ctx.web 这个接缝。只要接缝不变,provider 就可以换。

这套架构的收益和代价

dsh 的插件系统换来的是替换成本下降。模型、搜索源、会话存储、工具包、Agent Loop 都可以被配置或插件替换,而调用方只依赖服务契约。

代价也很明确。

第一,排查问题的入口变了。以前主要看调用栈,现在要先看插件状态。PENDING 不一定报错,依赖缺失也可能只是静默等待。

第二,写插件要同时考虑上线和下线。只写注册不写撤销,平时可能没事,热重载或局部替换时就会留下幽灵工具、旧监听器或失效连接。

第三,依赖关系本身变成设计工作。inject 写错,插件可能永远等不到依赖;两个插件互相依赖,还可能一起卡在 PENDING

这些都不是模型能力问题,而是 Harness 工程问题。

结语

DeepSeek Harness 最值得看的地方,不是它眼下作为 Coding Agent 是否已经足够顺手,而是它把 Agent 系统拆成了可以装配、可以替换、可以撤销影响的一组运行时组件。

一次 web_search 看上去只是“模型调用了搜索工具”。拆开以后能看到更底层的机制:配置层编译插件树,Cordis 管依赖和副作用,工具注册表暴露能力,会话日志保留事实,Agent Loop 把模型与工具推成一个 Turn。

未来的 Agent 不会只靠更长 Prompt 和更多工具变强。它们需要一套能承受长期运行、局部升级和失败恢复的 Harness。dsh 现在还早,但它已经把这个问题摆得很具体:Agent 的能力不是一团粘在一起的代码,而应该是一棵随时能重组、又不丢失连续性的插件树。

推荐阅读

DeepSeek Harness:Agent 自我改进之前,先让 Harness 可观察、可组合、可回滚

别只给 AI 产品接模型:Agent 能做成事,靠的是策略和 Harness

AX Tree:Agent 操作电脑时,真正需要的不是截图,而是界面语义

拆解 Opus 5 提示词:顶级 Agent 是被设计成可靠的

MCP 这次协议升级,真正改的是 Agent 基础设施的伸缩方式

aaa_compressed_under_1M.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI 小老六

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值