探讨Agent框架中模块复杂度增长导致Runtime膨胀的问题,分析Memory、Session、Sandbox、Tool等模块在 Harness 内的组织方式。
如前文所述,一个成熟的 Harness 至少需要处理工具执行、Context 管理、会话持久化以及执行控制。但当 Agents 开始承担更复杂的任务时,这些就不够用了。模型需要访问更多工具、文件系统、浏览器、沙箱化的代码执行、长期记忆,有时候还需要与其他 Agents 协作。因此,越来越多的能力开始堆积在 Harness 内部:
Agent Runtime
|
------------------------------------------------
| | | | |
Model Tools Memory Session Sandbox
添加能力本身不是问题。真正的问题在于所有这些最终都会堆积到 Runtime 内部。
最直接的做法就是缺什么加什么。需要 Memory?往 Harness 里加一个 Memory 模块。需要 Sandbox?再加一层 Sandbox。需要 Subagents?给 Agent Loop 扩展多 Agent 调度。这在系统较小时运转得相当不错,但随着系统增长,各个模块开始纠缠在一起。
Agent Loop 需要知道 Memory 应该在什么时候运行。工具系统需要了解 Sandbox 是如何工作的。权限层必须在工具实际执行之前介入。与此同时,会话系统需要记录所有这些组件产生的状态变化。最终,添加一个新能力不再意味着添加一个孤立的模块——而是意味着改变整个现有 Runtime 中的关系。
Harness 很容易最终变成这个样子:
Harness Runtime
------------------------------------------------
LLM code
Tool code
Memory code
Session code
Sandbox code
Permission code
Agent Loop code
Subagent code
...
Harness 原本是用于管理 Agent 的复杂性的,但它本身很容易成为新的复杂性中心。
DeepSeek Harness 对这个问题采取了非常直接的做法:
核心思想是尽可能避免将特定能力硬编码到 Runtime 中。相反,Runtime 成为一个负责加载、组合和管理能力的平台。
因此架构从"Harness 包含一组模块"转变为更接近这样的形态:
Harness Runtime
|
Plugin System
------------------------------------------------
| | | | |
Model Tool Memory Session Agent Loop
Plugin Plugin Plugin Plugin Plugin
这里的关键在于,DeepSeek Harness 中的 Plugin 不仅仅是工具。模型适配器、会话基础设施、Agent Loop 及其他运行时能力都可以纳入同一个插件模型。换句话说,一个 Agent 不再主要由硬编码的 Agent 类来定义。它越来越多地由当前加载到 Runtime 中的一组能力来定义。
这是一个重大的转变。
传统上,一个 Coding Agent 可能实现为类似这样的结构:
Coding Agent
├── LLM
├── Shell
├── File System
├── Git
└── Memory
这些能力通常与 Agent 本身紧密关联。
在基于插件的设计下,同样的 Agent 更适合理解为:
Agent Runtime
+
Model Plugin
+
Shell Plugin
+
File Plugin
+
Session Plugin
+
Memory Plugin
改变组合方式,就能构建出不同的 Agent。Runtime 不需要为 Research Agent、Coding Agent 或 Data Agent 各自准备独立实现。相反,它需要一个稳定的机制来组合各种能力。
但这立即引出了第二个问题:Agent 系统内部的插件比普通软件插件更复杂。
在传统软件中,插件通常不过是一个额外的可调用功能。例如浏览器中的翻译扩展,可能只是暴露了:
translate(text)
你提供文本,得到翻译,插件的工作就完成了。
Agent 系统内的许多能力并不是这样运作的。
以 Memory 为例。如果一个 Memory Plugin 只是:
memory.search()
那它实际上只是另一个工具。
一个真正的 Memory 系统可能需要在任务开始时加载历史记录,在模型推理前检索相关信息,在工具执行后记录重要结果,在任务结束时提取持久化的知识。
换句话说,它参与到 Agent 生命周期的多个阶段:
Task begins
↓
Load history
Before model inference
↓
Inject relevant information
After tool execution
↓
Record results
Task ends
↓
Persist useful experience
所以在 DeepSeek Harness 中,更有用的方式是把 Plugin 理解为:
一个 Plugin 不仅仅描述它能做什么。它还需要参与回答一些问题,比如它应该在什么时候行动,它应该在什么样的运行时环境中行动,以及在它行动之后应该留下什么状态。
这也解释了为什么工具虽然极其重要,但只是 Plugin 的一种形式。
正式地,我们可以这样写:
一个 Shell 工具本身可能很简单:
shell("pytest")
它的职责是执行一条命令。
但在那条命令真正运行之前,Harness 可能需要回答几个问题。当前的 Agent 是否有权限执行它?该操作是否需要用户批准?参数是否有效?命令应该在宿主机上运行还是在沙箱中运行?如果超时了会发生什么?
执行之后,又会出现另一组问题。输出是否太长?是否应该截断?完整的日志应该存储在哪里?结果的哪些部分应该进入下一个 Context?
因此真正的执行路径更像是这样:
Model produces Tool Call
↓
Check capability and permissions
↓
Validate arguments
↓
Enter execution environment
↓
Execute Tool
↓
Process result
↓
Record state and update Context
↓
Continue to the next inference step
在这一点上,普通的"模块化"仍然不够。
Agent Runtime 内部的组件有两个额外的关系尤为重要。
这个能力属于谁?
这个能力应该在什么时候介入?
假设一个主 Agent 创建了一个 Research Agent 和一个 Coding Agent:
Main Agent
/ \
Research Agent Coding Agent
Coding Agent 可能需要 Shell、Git 和文件系统访问权限,而 Research Agent 可能需要浏览器、论文搜索和引文能力。
如果每个 Plugin 都是全局的,两个 Agent 都会看到所有能力。这会造成混乱,还可能有安全隐患。一个只负责检查代码的 Agent 没有理由拥有数据库写入、部署或文件删除的权限。
因此能力需要一个作用域。
某些能力可能被子 Agent 继承。某些应该保持为当前 Agent 的本地能力。还有一些可能需要在子 Agent 中被替换或受限。这就是空间可组合性要解决的问题:
第二个问题来自 Agents 的运行方式。
Agent 不是执行一个单一函数就停止。它反复地在一个循环中运转:
User input
↓
Model inference
↓
Produce action
↓
Execute tool
↓
Receive feedback
↓
Infer again
不同的 Plugin 需要在这个生命周期的不同时间点介入。
Permission Plugin 可能需要在工具执行前检查一个动作。Memory Plugin 可能需要在调用 LLM 之前注入信息。Logger 可能需要在工具返回后记录结果。这些组件不仅仅是附加在 Runtime 旁边,而是嵌入到 Agent 的时间结构本身中。
这就引出了第二个概念:
换句话说:在一个运行时生命周期中,某个能力应该在什么时间点出现,它应该产生什么效果,当该能力消失时这个效果应该如何被移除?
所以 DeepSeek Harness 中"一切皆插件"背后真正的问题不是普通的软件模块化。
把 Agent Runtime 内部的能力从固定结构转变为动态可组合的组件,同时仍然控制这些组件在跨 Agent 时的作用范围,以及在整个运行时生命周期中的介入时机。
这也是为什么 DeepSeek 没有止步于设计一个 Plugin API。
如果 Memory、Tools 和 Sessions 都转换成了 Plugin,但没有机制来管理它们的依赖、作用域和生命周期,那些 Plugin 最终又会缠绕在一起。复杂性只是被挪到了别的地方。
所以在"一切皆插件"之后,更重要的问题变成了:
谁来管理这些 Plugin,以及在一个不断变化的 Agent Runtime 中它们如何保持可组合性?
这就是 Cordis 登场的时候。