深色模式
01 · Agent Harness 与「一切皆插件」
这一章回答三个问题:什么是 Agent Harness?为什么 DeepSeek 选择了"一切皆插件"的架构?这套架构和我们熟悉的 Web 框架有什么本质区别?
1.1 从一个真实场景说起
假设你接到一个需求:做一个"帮我改代码"的 AI 助手。它要能读文件、跑命令、用模型思考、把结果流式地显示在网页上。
你打开 DeepSeek Harness 的官网,看到的第一句话就是:
DeepSeek Harness (
dsh) is an open-source agent harness developed by DeepSeek AI. It uses an architecture where everything is a plugin — 一切都由插件组成。
你可能会想:"插件架构不新鲜啊,VS Code 不也是插件架构吗?" 没错,但大多数"插件架构"其实长这样:
内核是特权中心:它决定什么时候调用插件、给插件什么数据。插件只能"挂在"内核预留的钩子上。如果你想让模型调用工具的方式改一改?对不起,那是内核代码,改不了。想换掉会话存储?那是内核的一部分,也改不了。
DeepSeek Harness 的做法是反过来:连"内核"都不留特权。 官方架构文档的原话是:
Every part of the product is a plugin, including the model adapter, the tool registry, the session log, and the agent loop itself, so every part is replaceable from configuration.
产品的每个部分都是插件——包括模型适配器、工具注册表、会话日志,甚至 agent 循环本身——因此每个部分都可以从配置中替换。
模型适配器是插件,工具注册表是插件,会话日志是插件,连 Agent 的思考循环本身都是一个插件。这就是"一切皆插件"的字面含义:没有任何一段代码因为"它很重要"就享有不可替换的地位。
1.2 什么是 Agent Harness
先把术语对齐。
- Agent(智能体):一个能"感知 → 思考 → 行动"循环往复的运行时实体。它持有模型、工具和一段持续的记忆。
- Harness(挽具/马具):这个词来自马具。马有力气(模型能力),但你需要一套挽具把力传导到车上,才能把车拉走。Harness 就是那套"把模型能力装配成可用产品"的装置。
所以 Agent Harness = 装配和驱动 Agent 的框架。它要解决的实际问题有:
- 怎么跟各种模型对话? 每家提供方的协议、流式格式、错误码都不一样。
- 模型怎么调用工具? 工具声明、参数校验、执行、把结果喂回模型,这套循环谁来做?
- 对话历史怎么存? 多轮上下文、流式输出、断点续聊、分支(fork)。
- 中间过程谁能干预? 审批、权限、审计、压缩上下文……这些横切关注点加在哪?
- 产品怎么组合? 有的用户要 Web UI,有的要命令行一次性任务,有的要 API——同一个内核怎么长出这么多形态?
一个"普通"的框架会把这五个问题分别硬编码在五个模块里。DeepSeek Harness 的回答是:这五个问题各自对应一类插件,而装配它们的"内核"本身薄到几乎不存在。
1.3 一切皆插件意味着什么
我们逐条看官方架构文档列出的对应关系,每条都是一个"你原来以为要改内核、其实注册个插件就行"的场景:
| 你想做的事 | 在 dsh 里的做法 |
|---|---|
| 接入一个新的模型提供方 | 写一个 LlmAdapter 子类,注册到 ctx.llm |
| 给模型一个会调工具的能力(比如读写文件) | 注册到 ctx.tools,schema 自动进入提示词装配 |
| 给某个会话换一套能力 | 组合一个 agent preset(服务行加 isolate 作用域) |
| 加 shell 执行能力 | 注册一个 ctx.shell 后端 |
| 加一个不用模型的人机命令 | 注册到 ctx.commands |
| 加后台任务 | 注册到 ctx.jobs,配套的 job_* 工具自动出现 |
| 加文件系统访问或策略 | 注册 ctx.fs 提供方,或监听 fs/* 事件 |
| 拦截一次请求 / 工具调用 / 轮次 | 监听对应的 agent/* 或 tools/* 事件 |
| 给模型注入上下文 | 调 agent.inject(),内容进入下一次请求 |
| 加 UI 或编辑器集成 | 驱动 ctx.agents,从 session/event 渲染 |
| 生成会话标题 | 注册唯一的 ctx.sessionTitle 提供方 |
| 管理一个同会话的目标 | 用 ctx.goals,通过 agent/* 继续 |
| 给单个 agent 局部注册 | 用该 agent 自己的 agent.ctx |
注意最后一列里反复出现的 ctx.*。上下文(Context)是这套架构的枢纽:它既不是全局单例,也不是手动传参,而是一个"服务容器"。我们下一章会细讲,这里先记住一个心智模型:
每个插件都站在同一个 Context 面前:往上面挂服务、监听事件、注册可逆的副作用。谁也不知道"整个系统"长什么样,它们只知道自己在跟谁协作。这就是 dsh 的可替换性来源:功能之间的耦合不是代码里的 import,而是运行时的注册。
1.4 为什么这么设计:一个思维实验
你可能想问:把 agent 循环做成插件,会不会过度设计?我们做个思维实验来体会它解决了什么真问题。
问题一:模型调用工具的方式是"正确答案"吗?
2023 年 OpenAI 定义了 Function Calling 协议,2024 年 Anthropic 的 tool use 又不一样,2025 年各家的"原生工具调用"继续演化。如果你把工具循环硬编码在内核里,每一次协议演进都是一次内核手术。
dsh 的做法:工具循环(agent-loop)是插件,工具的执行管线(tools 服务)也是插件,模型与工具的交互协议定义在 llm 插件层。哪个环节变了就换哪个插件,其余全部原样保留。
问题二:产品形态能枚举完吗?
Web UI、CLI 一次性任务、ACP(Agent Client Protocol)服务器、Python SDK、JSON-RPC……每个形态都想要"同一个内核"。如果你为每个形态 fork 一份代码,维护成本是乘法。
dsh 的做法:一个 profile 就是一份"插件清单"(bundle 列表 + patch 覆盖层)。web 和 headless 只是两份不同的清单——产品形态从"代码"变成了"配置":
text
dsh --profile web # Web UI:base bundle + web-app bundle
dsh --profile headless # 一次性任务:base bundle + headless bundle,不装 HTTP 服务器问题三:用户的个性化需求怎么落地?
用户 A 想把所有工具调用都记录到自己的审计系统;用户 B 想在模型请求发出前自动切换路由;用户 C 要给某个会话单独挂一个沙箱。这三件事在传统框架里都需要"提 PR、等发版"。
dsh 的做法:A 监听 tools/result 事件,B 监听 agent/request 瀑布事件,C 用 isolate 作用域。三个需求都是局部的、可独立发布的插件,核心仓库一行都不用改。
1.5 关键概念速览
在进入下一章之前,先把整个教程会用到的核心名词过一遍(完整版见术语表):
| 概念 | 一句话解释 |
|---|---|
| Plugin(插件) | 实现服务的对象:一个函数、一个类、或带 apply(ctx) 的对象。 |
| Context(上下文) | 服务的容器:一个服务占据稳定的 ctx.<key>,其他插件按键查找。 |
| Service(服务) | 挂在 Context 上的命名能力,比如 ctx.llm、ctx.tools、ctx.sessions。 |
| inject(依赖注入) | 插件声明自己需要哪些服务,服务就绪前不启动;加载顺序由依赖决定,而非手动编排。 |
| Event(事件) | 插件间通信:emit(广播)、waterfall(可拦截的中间件链)、parallel(并发扇出)、serial(顺序执行)。 |
| Effect(可逆副作用) | 所有注册(监听器、工具、适配器)都是 effect,插件卸载时自动按逆序撤销。 |
| Profile / Bundle | 产品的"配方":profile 是命名组合,bundle 是可分发的插件集(npm 包)。 |
| Session(会话) | 一次持续对话;其日志是"可回放的事实源"。 |
| Turn / Step | 一次唤醒叫 turn;turn 内的"一次模型请求 + 它调用的工具"叫 step。 |
| Seam(接缝) | 可替换的能力点:接口声明 + 提供方 + 消费方,三位一体。 |
1.6 本章小结与练习
这一章的核心只有一句话:dsh 没有特权内核,一切皆插件,功能之间的耦合发生在运行时的注册里,而不是代码的 import 里。
小练习:给"一切皆插件"找反例
打开你熟悉的任意一个框架(Express、Django、Spring Boot……),问自己:如果我想替换它的"路由匹配逻辑"或"请求日志格式",需要改哪里?是注册一个中间件,还是改框架源码?
然后把同样的问题问一遍 dsh:替换模型适配器(插件)、替换工具执行策略(事件)、替换会话持久化(服务提供方)。体会一下两者在"可替换边界"上的差别。
关于本章的事实来源
"Everything is a plugin"、"Every part of the product is a plugin, including the model adapter, the tool registry, the session log, and the agent loop itself" 均出自官方 README 与 architecture 文档。dsh 的插件机制由 Cordis 提供,其设计思想见论文 A Programming Paradigm for Spatiotemporal Composability。