标签归档:DSH

DeepSeek Harness 的 Harness 到底做了什么

DeepSeek Harness 解决的核心问题,是多轮模型调用、工具执行与会话状态之间的协调:输入何时进入请求,工具结果如何写回历史,请求失败后从哪里重试,上下文压缩后保留哪些信息,以及任务中断后能够恢复到什么状态。

围绕这些问题,框架将运行过程拆分为插件装配、回合驱动、会话日志、工具管道和异常处理。分析其架构的关键,在于各部分的状态边界,以及这些边界提供的保证与限制。

本篇文章限定在提交 477b4f4205(2026 年 9 月 25 日)对应的实现。

1. 插件装配

插件是 DeepSeek Harness 的核心能力,插件的配置决定 Agent 的运行能力。

DeepSeek Harness 通过 Cordis 插件组织运行能力。Cordis 提供 Context、Service、Plugin 和 Event 等抽象,插件通过依赖注入与事件系统协作,并由框架管理生命周期。

LLM 服务、Session、Agent、AgentLoop 和工具注册表等基础组件,由 bundle 统一挂载。启动时,dsh 以 profile 为入口加载配置,按 id 定位配置行,并替换对应的整段 config。

这里的配置语义是整段替换,而非字段级合并。覆盖某个组件的配置时,原配置中未被保留的字段也可能随之消失。

插件装配因此直接影响 Agent 的实际行为:模型服务是否可用、哪些工具被注册、哪些事件处理器参与执行,都取决于最终加载的插件与配置。仓库包含某项能力,并不等于当前运行实例已经启用该能力。

这种设计便于替换组件和组合能力,但也让执行逻辑分布在多个插件中。分析一次请求的行为,需要同时查看核心循环、插件注册关系和最终生效的配置。

2. 核心循环

Agent 循环是 Agent 的核心逻辑,其主要区分回合、步骤与请求重试。

从实际的工作逻辑上来看,agent-loop 负责驱动模型与工具之间的循环。其主要执行路径包括:

  1. 准备模型请求,解析配置并选择适配器。
  2. 投影系统提示词,提交本次需要进入历史的用户输入。
  3. 从会话构造请求,接收并累积流式响应。
  4. 将 assistant 消息写入 Session。
  5. 执行模型提出的工具调用,并将结果交给后续模型请求。
  6. 在没有工具调用或满足结束条件时,结束本轮执行。

理解这条路径,需要分三个层次:

  • turn:由用户输入驱动的回合。
  • step:回合内部的执行边界。
  • 请求尝试:一次实际发往模型的调用,失败后可以在同一个 step 内重试。

因此,step 不能简单等同于一次网络请求。重试可能产生多次请求尝试,但不应重复消费输入或重复提交用户消息。

Agent 维护 next-turn 和 next-step 两个队列,分别承接下一回合与下一步的输入。队列修改以 agent/inbox/spliced 事件写入 Session,待处理输入可以从日志投影恢复。

这一设计区分了「系统已经收到消息」和「模型已经消费消息」。

执行过程中到达的补充要求,需要在相应边界进入后续请求。它不能自动改变已经发出的模型请求,也不能撤销已经发生的工具副作用。输入排队与执行取消因此属于不同机制。

将队列变化写入日志,还能避免恢复时只找回聊天记录,却丢失尚未处理的输入。

3. Session

Session 机制实现了运行记录与模型历史分离。

Session 被设计为只追加的事件日志,事件通过递增序列号确定顺序,以 type 区分类型,以 body 保存内容。

日志不仅记录用户与 assistant 消息,也记录请求失败、输入队列变化、工具执行和压缩过程等运行事实。

但日志中存在的事件,不一定进入模型上下文。

真正发送给模型的 messages,由 Session 的 surface 通过 deriveMessages() 重建,再冻结为不可变请求。只有进入 surface 的消息事件参与历史投影;assistant/attempt 等运行记录不会自动成为模型消息。

这一分离有两个作用:

  • 保留排障信息:请求失败与恢复过程可以完整记录,而不必全部占用上下文窗口。
  • 允许调整可见历史:压缩或替换模型上下文时,仍能保留原始事件。

Session 的 append 还会检查 JSON 可序列化性、事件规则及 surface 替换的合法性,以减少无法持久化或无法正确投影的数据进入日志。

日志恢复不等于外部动作回滚

只追加日志为审计与状态重建提供了基础,但不能单独保证任意中断点都能无损恢复。

例如,工具已经修改文件,但结果尚未写入 Session 时进程退出,外部环境与日志之间就可能出现状态差异。恢复日志可以还原已提交的记录,却不能仅凭「缺少结果」判断工具没有执行。

因此,恢复能力需要区分:

  • 已提交事件及其投影的恢复;
  • 外部副作用的确认与处理。

后者还依赖工具的幂等性、状态检查能力或额外的事务机制,不能仅由 Session 的追加式设计推导出来。

4. 工具管道

工具管道实现了从模型意图到受控执行。

工具注册表只向模型暴露 name、description、parameters 等白名单字段。执行函数、超时设置和并发属性保留在运行时。

这将模型侧的工具描述与运行时的执行控制分开:模型负责提出调用,框架负责判断调用是否可执行,以及如何执行。

一次工具调用依次经过:

  1. 解析模型生成的参数。
  2. 查找当前 Agent 可见的工具。
  3. 通过 tools/pre-execute 进行执行前检查,允许或拒绝调用。
  4. 进入 tools/execute 包装链。
  5. 通过 tools/post-execute 处理结果。
  6. 将结果写回 Session,供后续模型请求使用。

权限检查、执行包装和结果处理因而拥有独立的介入位置,不必全部写进工具函数。

并发执行与顺序提交分离

同一条 assistant 消息可能提出多个工具调用。调度器通过工具的 isConcurrencySafe 属性分类:

  • 明确返回 true 的调用进入并行池。
  • 其他调用作为独占屏障处理。

并发是显式声明的能力,未声明安全的工具不会默认并行执行。

工具本体可以并发,但准备、后处理、tool/result 写入及附加 context 入队,按模型调用顺序提交。这样可以避免工具完成时间的随机性直接改变会话中的结果顺序。

代价是可能出现等待:后面的工具即使先完成,也需要等待前面的调用提交。并发能够缩短部分执行时间,却不保证结果可以立即进入历史;提前完成的结果还可能需要暂存。

此外,提交顺序稳定不等于外部副作用确定。并发工具是否真正安全,仍取决于它们是否访问共享状态、是否存在执行依赖,以及并发声明是否准确。

5. 安全隔离

DSH 在实现上让审批与执行限制分别生效,以实现安全隔离。

Harness 的沙箱模式包括:

type SandboxMode =
  | 'read-only'
  | 'workspace-write'
  | 'danger-full-access'

策略涉及文件路径权限、命令执行限制和权限提升。工具可以通过 justification 与 sandbox_permissions 请求提升权限,再由 approval 机制处理审批。

基础 bundle 默认挂载 workspace-write 文件沙箱与 ask 审批策略;sandbox-policy 将当前文件操作模式解析给实际后端。

这一结构包含三个不同层次:

  • 工具可见性:模型能够提出哪些调用。
  • 执行审批:某次调用是否获得许可。
  • 后端隔离:获准执行后,实际能够访问哪些资源。

三者不能相互替代。审批通过只说明动作获得许可,不代表执行环境具有无限权限;模型看不到某个工具,也不能据此证明其他工具无法访问相同资源。

安全边界最终取决于策略配置与实际后端的共同约束,而不只是沙箱模式的名称。

6. 异常收敛

异常是应用中很常见的东西,也是 Harness 的重点处理对象。

DSH 通过重试、压缩与取消各自处理不同问题。

6.1 请求重试

通过请求重试来保留失败记录,避免重复提交输入。

模型请求失败后,系统先写入 assistant/attempt,再交给 agent/request-error 瀑布链处理。

llm-retry 根据适配器提供的 retry policy,判断错误类型、重试次数与退避方式。计划重试与实际启动分别记录为 llm/retry 和 llm/retry-started。

重试在同一个 step 中重新准备请求,不重复提交首次用户消息。这一边界避免了网络层重试演变成会话层的重复输入。

分别记录重试计划与启动,也使日志能够区分“决定重试”与“已经开始下一次尝试”。

6.2 上下文压缩

上下文压缩实现了替换可见历史,保留原始事件。

自动压缩在 agent/pre-step 检测上下文压力,并记录:

  • compaction/start
  • compaction/summary
  • compaction/end

随后,系统将选中的连续历史区间替换为一个 checkpoint 用户消息。

这里发生的是 surface replace:模型可见历史缩短,原始日志仍然保留。因此,压缩减少的是请求上下文,不等于同步减少持久化存储。

压缩也引入了信息损失的可能。原始约束即使仍在日志中,只要没有进入摘要或剩余 surface,后续模型就无法直接使用。压缩是否有效,除了看上下文长度,还取决于任务目标、限制条件和未完成事项是否得到保留。

6.3 取消

通过取消来传递停止信号,而非撤销既有结果。

AbortSignal 贯穿回合、请求和工具,使取消信号能够沿执行链传递。

但信号传递不等于立即停止所有动作。工具是否及时退出,取决于其是否检查并响应取消;已经完成的文件写入或远端操作,也不会因 AbortSignal 自动回滚。

取消机制提供的是停止继续执行的控制路径,外部副作用的撤销仍需要独立实现。

7. 小结

DSH 的 Harness 实际上是一种架构的取舍,其取舍考量的是可追溯性与运行成本。

如下的机制共同形成了 DeepSeek Harness 的主要工程取舍:

机制 提供的能力 成本或边界
Cordis 插件装配 组件替换与能力组合 行为分布在配置和多个处理器中
turn / step 与输入队列 明确输入归属和执行边界 增加队列状态与事件维护
追加式 Session 审计与已提交状态重建 日志持续增长,不保证外部动作恰好执行一次
surface 投影 分离运行记录与模型上下文 需要维护投影一致性与替换规则
并发执行、顺序提交 利用并发,同时稳定历史顺序 可能产生提交等待与结果暂存
沙箱与审批 分层控制动作权限 实际保证依赖配置和执行后端
自动压缩 降低上下文窗口压力 增加摘要成本,并可能丢失任务信息
AbortSignal 统一传递取消意图 依赖工具配合,不能自动回滚副作用

DeepSeek Harness 的核心价值,是将 Agent 执行中的关键状态显式化:输入有队列,执行有边界,请求有可见历史,工具有调度与审批,失败有独立的处理路径。

这些机制使多轮任务能够被追踪、检查和恢复,同时也明确了框架的能力边界:日志恢复不能替代副作用确认,顺序提交不能替代并发安全,权限审批不能替代环境隔离,上下文压缩也不能保证信息无损。

以上。

从 Pi 到 DSH:Agent Harness 如何从「可扩展」走向「自生长」

大模型能力持续提升之后,Agent 系统的竞争焦点正在发生变化。

过去,我们主要关心模型能不能理解任务、调用工具、编写代码。现在,更重要的问题逐渐变成:模型之外的系统,能否为 Agent 提供一个可以持续变化的运行环境?

这个运行环境通常被称为 Agent Harness。

Harness 负责把模型、提示词、工具、记忆、上下文、权限和外部环境连接起来。它决定模型能够看到什么、调用什么、保存什么,以及一次行动会产生怎样的系统影响。

从这个角度看,Agent 的能力并不只来自模型,用一个公式来表达,大概如下:

Agent 能力
=
模型能力
× 上下文组织
× 工具系统
× 运行时结构
× 生命周期治理

如果 Harness 是固定的,那么即使模型越来越强,Agent 的能力边界仍然主要由开发者预先决定。

如果 Harness 可以扩展,用户和开发者就能不断为 Agent 增加工具、技能和工作流。

但如果我们希望 Agent 进一步具备「自生长」能力,仅仅提供扩展接口还不够。系统还必须允许 Agent:

  1. 发现自己的能力缺口;
  2. 生成或引入候选能力;
  3. 在运行时安全地激活这些能力;
  4. 验证变化是否有效;
  5. 在失败时完整回滚;
  6. 将成功经验沉淀为可复用组件;
  7. 淘汰已经失效或长期无用的能力。

Pi 与 DSH 可以被视为这条演进路径上的两个阶段。

Pi 回答的是:

如何构建一个足够小、足够开放,可以持续扩展的 Agent Harness?

DSH 进一步追问:

如何让 Agent 不只是使用外部提供的扩展,而是安全地参与自身能力结构的形成?

这不是从一种插件格式切换到另一种插件格式,而是 Agent Harness 从开放扩展平台向能力生长环境的转变。

1. 什么是 Agent Harness

一个最简单的 Agent,可以被表示为:

Model
  ↓
Prompt
  ↓
Tool Call
  ↓
Environment

模型接收上下文,生成工具调用,再根据工具结果继续推理。

但在真实系统里,Agent 还需要处理更多问题:

  • 当前有哪些工具可用?
  • 工具由谁注册?
  • 不同任务应该使用什么模型?
  • 哪些信息应该进入上下文?
  • 哪些记忆需要跨会话保存?
  • 如何限制文件、网络和进程权限?
  • 工具调用失败后如何恢复?
  • 新能力如何加载?
  • 组件移除后如何清理副作用?
  • 不同组件之间的依赖如何表达?

于是,Agent 的实际结构更接近于:

                ┌─────────────┐
                │    Model    │
                └──────┬──────┘
                       │
                ┌──────▼──────┐
                │ Agent Loop  │
                └──────┬──────┘
                       │
       ┌───────────────┼────────────────┐
       │               │                │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│    Tools    │ │    Memory   │ │   Context   │
└─────────────┘ └─────────────┘ └─────────────┘
       │               │                │
       └───────────────┼────────────────┘
                       │
                ┌──────▼──────┐
                │ Environment │
                └─────────────┘

Harness 并不是模型外面的一层简单包装。

它实际上规定了 Agent 的:

  • 能力边界;
  • 行为边界;
  • 状态边界;
  • 权限边界;
  • 演化边界。

因此,当我们讨论 Agent 是否可以扩展、重构甚至自生长时,本质上讨论的不是模型参数如何变化,而是 Harness 能否安全地改变自身结构。

2. Pi:以最小内核换取最大扩展空间

Pi 的核心思想非常明确:

不要预先替用户决定完整工作流,而是提供足够小的基础能力,让用户自己构建需要的 Harness。

Pi 将自己定义为一个最小化的 Agent Harness,并通过 Extensions、Skills、Prompt Templates、Themes 和 Packages 提供扩展能力。它允许用户修改工具、命令、模型提供商、工作流、上下文处理和终端界面,也支持将这些资源打包后通过 npm 或 Git 分发。(pi.dev)

这背后是一种「原语优先」的设计哲学:

不是:
内置所有功能

而是:
提供少量原语
+
开放扩展接口
+
允许用户自行组合

2.1 最小化不是能力不足

传统 Agent 产品通常倾向于内置更多功能:

  • 规划模式;
  • 子 Agent;
  • 权限确认;
  • MCP 集成;
  • 后台任务;
  • 长期记忆;
  • 沙箱;
  • Todo 系统。

Pi 选择了另一条路线:这些能力不一定需要成为内核的一部分,它们可以通过扩展或外部环境实现。

Pi 当前提供文件读取、文件修改、内容搜索和命令执行等内置工具,同时允许通过命令行配置工具集合,也允许 Extension 注册新的工具或覆盖现有工具。(pi.dev)

这种设计的意义把稳定的内核和变化的能力分开:

稳定部分:
模型调用、Agent Loop、会话、基础工具、扩展 API

变化部分:
工具、技能、上下文、工作流、权限策略、界面、模型路由

只要扩展接口足够开放,功能就不必全部固化在核心代码中。

2.2 Pi 的四类扩展资源

从 Harness 的角度看,Pi 的可扩展性主要体现在以下几个层面。

2.2.1 Extensions:改变运行时行为

Extension 是可以进入 Agent 运行时的代码模块。

它可以:

  • 注册工具和命令;
  • 监听工具调用;
  • 拦截或修改行为;
  • 改变上下文;
  • 切换模型;
  • 增加权限检查;
  • 实现自定义 UI;
  • 覆盖内置工具;
  • 连接外部系统。

因此,Extension 改变的不只是模型“知道什么”,还可以直接改变 Harness“如何运行”。

2.2.2 Skills:注入按需能力

Skill 更接近一种可复用的过程知识。

它可以告诉 Agent:

  • 在什么情况下使用某种方法;
  • 如何执行特定工作流;
  • 如何调用外部命令;
  • 如何处理某类项目;
  • 如何检查输出质量。

Pi 将 Skill 设计为按需加载的能力包,使 Agent 不必在每次会话开始时把所有知识都放进上下文。(pi.dev)

2.2.3 Prompt Templates:固化交互模式

Prompt Template 将常见任务封装成可复用提示词,例如:

  • 代码审查;
  • 架构分析;
  • 发布检查;
  • 测试生成;
  • 故障排查。

它改变的是任务入口和交互结构。

2.2.4 Packages:分发完整能力

Package 可以将 Extensions、Skills、Prompt Templates 和 Themes 组合起来,通过 npm、Git 或本地路径安装。Pi 还支持临时加载 Package,使组件只对当前运行生效。(github.com)

这使 Pi 不只是一个 Agent,也成为一个 Harness 能力分发平台:

Pi Package
├── Extensions
├── Skills
├── Prompts
└── Themes

2.3 Pi 的关键突破:Agent 可以参与修改 Harness

Pi 最值得关注的地方,不只是「插件多」。

它的官方描述明确鼓励用户让 Pi 编写自己需要的扩展,并在修改后通过重新加载继续当前工作。(pi.dev)

这意味着一种新的工作方式正在出现:

Agent 发现缺少某项能力
        ↓
Agent 编写 Extension
        ↓
用户检查或确认
        ↓
重新加载 Harness
        ↓
Agent 使用新增能力继续任务

过去,Agent 与开发工具之间的关系通常是:

人类开发 Harness
Agent 使用 Harness

在 Pi 中,它开始变成:

人类定义目标
Agent 修改 Harness
Agent 使用修改后的 Harness

这是从「可扩展」走向「自生长」的重要一步。

但是,这还不等于完整的自生长。

3. 行为自治不等于结构自治

理解 Pi 与 DSH 的关系,需要区分两种不同的自治。

3.1 行为自治

行为自治指 Agent 可以在已有能力范围内自主决定:

  • 下一步做什么;
  • 调用哪个工具;
  • 如何拆解任务;
  • 是否修改文件;
  • 是否运行测试;
  • 如何根据结果调整计划。

此时,Agent 的选择空间是动态的,但能力集合通常是预先确定的。

固定工具集合
    ↓
Agent 自主选择工具
    ↓
完成任务

3.2 结构自治

结构自治指 Agent 可以改变自己的能力结构:

  • 创建新工具;
  • 安装新组件;
  • 替换 Memory;
  • 修改上下文策略;
  • 调整模型 Router;
  • 增加权限规则;
  • 组合新的执行流程;
  • 卸载无效组件。
当前能力结构
    ↓
Agent 发现能力缺口
    ↓
生成候选组件
    ↓
改变 Harness
    ↓
形成新的能力结构

Pi 已经为结构自治打开了入口。

但它的扩展机制主要回答的是:

如何把一项新能力加入系统?

真正的自生长还必须回答:

  • 新能力应该在什么条件下激活?
  • 它依赖哪些已有能力?
  • 它失败后能否完整退出?
  • 它注册的工具和监听器由谁撤销?
  • 如何判断变化带来了正收益?
  • 如何防止不同扩展相互冲突?
  • 如何把一次临时变化沉淀成长期能力?
  • 如何淘汰不再适用的组件?

这些问题将我们从扩展系统带到了组件运行时。

4. DSH:为 Harness 自生长建立运行时基础

本文讨论的 DSH,不只是另一个插件系统。

它试图将 Agent Harness 表达为一个可以在运行中不断组合和重组的组件系统。

其基本结构可以概括为:

Component
=
Context
+
Effect
+
Cleanup
+
Coeffects

这四个概念分别回答四个问题:

概念 回答的问题
Context 组件可以看到和使用什么?
Effect 组件加入系统后会产生什么影响?
Cleanup 组件退出时如何撤销影响?
Coeffects 组件依赖哪些外部能力或其他组件?

DSH 的目标不是让所有模块都能任意修改系统,而是让变化成为一种显式、可追踪、可逆的运行时操作。

4.1 Context:为能力提供明确边界

一个组件不能直接访问所有系统状态。

它应该通过 Context 获取所需能力,例如:

Context
├── Model Registry
├── Tool Registry
├── Memory
├── Event Bus
├── Session
├── Logger
├── Storage
├── Permission
└── Environment

Context 的第一层作用是解耦。

组件不需要知道某个能力的具体实现,只需要知道运行环境向它提供了什么。

例如,一个模型路由组件可能只依赖:

Model Registry
Task Metadata
Usage Metrics

一个长期记忆组件可能只依赖:

Session Events
Embedding Service
Storage
Context Injector

一个工具组件则可能只依赖:

Tool Registry
Credentials
Network
Logger

Context 的第二层作用是限制能力边界。

如果组件只能通过 Context 访问环境,那么系统就可以决定:

  • 哪些资源向它开放;
  • 哪些能力只读;
  • 哪些调用需要权限;
  • 哪些状态不能跨组件共享;
  • 哪些操作只能在 Sandbox 中执行。

因此,Context 不只是依赖注入,也是一种能力安全模型:

组件能做什么
≈
Context 向它暴露什么

4.2 Effect 与 Cleanup:让变化可以被撤销

当组件加入 Harness 时,通常会产生副作用。

例如:

  • 注册一个工具;
  • 添加一个事件监听器;
  • 打开数据库连接;
  • 启动后台任务;
  • 修改模型路由;
  • 向上下文注入内容;
  • 挂载文件目录;
  • 注册快捷键;
  • 创建缓存;
  • 改变权限策略。

这些变化可以统一理解为 Effect:

Component
    ↓
Effect
    ↓
Harness 状态发生变化

问题在于,大多数插件系统只强调如何创建 Effect,却没有把撤销 Effect 当作同等重要的能力。

于是,组件卸载后可能出现:

  • 工具仍然留在 Registry 中;
  • 事件监听器重复注册;
  • 后台任务继续运行;
  • 网络连接没有关闭;
  • 临时文件没有删除;
  • Context 中残留失效状态;
  • 新旧路由规则同时生效。

在 DSH 中,Effect 应该返回对应的 Cleanup:

Effect(Context) → Cleanup

例如:

function activate(context) {
  const unregister = context.tools.register(myTool)

  return function cleanup() {
    unregister()
  }
}

更复杂的组件可能产生多个副作用:

function activate(context) {
  const stopTool = registerTool(context)
  const stopListener = registerListener(context)
  const stopWorker = startWorker(context)
  const closeConnection = connectDatabase(context)

  return async function cleanup() {
    await stopWorker()
    stopListener()
    stopTool()
    await closeConnection()
  }
}

这使组件具备完整生命周期:

创建
  ↓
激活
  ↓
运行
  ↓
停用
  ↓
清理

4.3 时间可组合性:让 Agent 有资格试错

Effect 与 Cleanup 提供的是一种时间可组合性。

一个组件可以在某个时刻进入系统,也可以在另一个时刻完整退出:

t₀:Harness 原始状态

t₁:组件进入
    → 注册工具
    → 启动服务
    → 注入上下文

t₂:组件运行

t₃:组件退出
    → 移除工具
    → 停止服务
    → 撤销上下文

t₄:Harness 恢复到可预测状态

这对普通插件系统很重要,但对自生长系统更重要。

因为自生长必然包含试错。

Agent 创建的新工具、新路由器、新记忆策略,不可能每次都正确。如果一次失败的尝试不能被完整撤销,那么每次实验都会向系统中留下残余状态。

最终,所谓“生长”就会退化成不可控的复杂度积累。

因此,自生长的基本过程不应该是:

生成组件
  ↓
永久安装

而应该是:

生成候选组件
      ↓
执行受控 Effect
      ↓
验证运行结果
   ┌──┴──┐
 成功    失败
  │       │
保留   Cleanup

Cleanup 的意义不只是“支持热卸载”。

它赋予 Agent 一种更关键的能力:

在不永久污染系统的前提下试验新的自身结构。

4.4 Coeffects:让依赖关系成为运行时的一部分

时间可组合性解决的是组件何时进入、何时退出。

但 Agent Harness 的组件并不是彼此孤立的。

如果依赖关系只是组件内部的隐式假设,运行时就无法回答:

  • 组件现在能否启动?
  • 它缺少什么能力?
  • 某个依赖消失后,哪些组件应该停用?
  • 替换一个 Provider 会影响哪些组件?
  • 哪些组件可以被安全卸载?
  • 一个候选组件是否与当前环境兼容?

Coeffects 用于显式表达组件所需的外部条件:

Effect:
组件对系统做什么

Coeffects:
组件需要系统提供什么

例如:

const vectorMemory = {
  coeffects: [
    "embedding-model",
    "vector-storage",
    "session-events"
  ],

  effect(context) {
    // 激活 Memory
    return cleanup
  }
}

运行时可以根据当前 Context 判断组件是否具备激活条件:

依赖全部满足
    ↓
组件激活

依赖不完整
    ↓
组件保持未激活

依赖运行中消失
    ↓
执行 Cleanup

这使 Harness 不再只是一个插件列表,而是一个动态依赖图:

Credential
    ↓
GitHub Provider
    ↓
GitHub Tool
    ↓
Repository Agent

或者:

Embedding Model ─┐
                 ├→ Vector Memory → Context Injector
Vector Storage ──┘

4.5 空间可组合性:让生长不是无序堆积

Coeffects 提供的是一种空间可组合性。

组件不是按照固定顺序硬编码在系统中,而是根据能力和依赖形成运行时拓扑。

当一个能力出现时,依赖它的组件可以被激活;当这个能力消失时,相关组件可以被停用:

Sandbox 出现
    ↓
Code Executor 激活
    ↓
Test Runner 激活
    ↓
Coding Agent 获得安全执行能力

如果 Sandbox 被移除:

Sandbox 消失
    ↓
Code Executor Cleanup
    ↓
Test Runner Cleanup
    ↓
相关工具从 Agent 能力集合中撤销

这与传统插件系统有一个关键区别。

传统插件系统更像:

安装了什么
=
系统拥有什么

而动态组件运行时更像:

当前可用能力
=
当前组件
× 当前依赖
× 当前环境
× 当前权限

这使 Harness 的结构可以随着任务和环境变化,而不是不断向全局系统中永久添加模块。

真正的生长,不是组件数量持续增加,而是系统能够形成更适合当前任务的能力结构。

4.6 从动态重组到自生长闭环

Context、Effect、Cleanup 和 Coeffects 为动态重组提供了基础。

但必须强调:

动态重组是自生长的必要条件,不是自生长的全部。

完整的自生长至少需要五个阶段。

4.6.1 第一阶段:发现能力缺口

Agent 首先要判断自己缺少什么。

例如:

  • 当前没有访问某类数据的工具;
  • 现有 Tool 无法处理特定协议;
  • Memory 无法支持长期任务;
  • 当前模型不适合某类输入;
  • 缺少项目专用的验证流程;
  • 当前执行环境不满足安全要求;
  • 工具调用成本或延迟过高。

能力缺口不能只由「工具调用失败」判断。

它还可能来自:

  • 任务规划;
  • 运行指标;
  • 用户反馈;
  • 测试结果;
  • 历史失败记录;
  • 现有能力清单;
  • 环境变化。

因此,自生长的起点不是生成代码,而是:

目标要求
-
当前能力
=
能力缺口

4.6.2 第二阶段:生成或引入候选能力

识别缺口后,Agent 可以选择不同方式补齐能力:

  • 编写新的 Tool;
  • 生成新的 Skill;
  • 安装已有 Package;
  • 包装外部 API;
  • 组合已有组件;
  • 替换 Memory Provider;
  • 调整模型 Router;
  • 创建新的工作流;
  • 启动一个专用子 Agent;
  • 为现有组件增加适配层。

这里的关键词是「候选」。

Agent 生成的组件不能因为能够编译或加载,就自动成为系统的永久能力。

生成成功
≠
能力有效
≠
适合长期保留

4.6.3 第三阶段:在受控环境中激活

候选组件应该先进入受限 Context。

系统需要明确:

  • 它可以访问哪些目录;
  • 可以调用哪些网络服务;
  • 是否可以读取凭证;
  • 可以注册哪些工具;
  • 能否修改全局状态;
  • 资源消耗上限是多少;
  • 运行时间是否受限;
  • 哪些副作用必须登记。

这一点尤其重要,因为扩展代码通常拥有较高权限。

以 Pi 为例,其官方安全文档明确说明,内置工具和 Extension 默认继承启动 Pi 的进程权限;第三方 Package 可能执行代码或指示模型执行操作,因此需要代码审查,并在需要时依赖容器或虚拟化提供真正的隔离边界。(github.com)

如果 Agent 开始自行生成和加载组件,权限问题只会更加突出。

所以,自生长必须建立在受控运行时上,而不能建立在「默认信任所有新代码」之上。

4.6.4 第四阶段:验证、保留或回滚

候选能力激活后,系统需要对它进行验证。

验证可以包括:

  • 能否完成目标任务;
  • 是否通过单元测试;
  • 是否破坏已有功能;
  • 是否产生未声明的副作用;
  • 是否扩大了不必要的权限;
  • 是否降低了任务成本;
  • 是否提高了成功率;
  • 是否造成明显的上下文膨胀;
  • 是否与现有组件冲突;
  • Cleanup 是否完整。

验证结果通常有三种:

验证通过
    ↓
保留或固化

部分通过
    ↓
修改后重新试验

验证失败
    ↓
Cleanup + 回滚

因此,自生长不是一次性的代码生成,而是一个选择过程:

Generate
→ Activate
→ Observe
→ Evaluate
→ Select

没有评估的生成,只是变化。

经过评估和选择的变化,才可能成为生长。

4.6.5 第五阶段:沉淀、复用与淘汰

一次任务中的成功,不代表组件值得永久保留。

系统还需要决定:

  • 这项能力是否跨会话有效?
  • 它适用于哪些任务?
  • 应该在什么条件下重新激活?
  • 它依赖哪些版本?
  • 它的权限范围是什么?
  • 它由谁生成?
  • 经过了哪些测试?
  • 是否允许自动升级?
  • 何时应该重新评估?
  • 何时应该被淘汰?

因此,一个可复用组件除了代码,还需要附带元数据:

Component
├── Implementation
├── Capability Description
├── Coeffects
├── Permissions
├── Validation Records
├── Version
├── Provenance
├── Metrics
├── Effect
└── Cleanup

真正的自生长闭环应当是:

发现缺口
  ↓
生成候选能力
  ↓
隔离激活
  ↓
观察与验证
  ↓
保留或回滚
  ↓
沉淀为可复用组件
  ↓
持续评估
  ↓
升级或淘汰

5. Pi 与 DSH 的核心差异

Pi 与 DSH 并不是简单的替代关系。

它们关注的是两个不同层次的问题。

维度 Pi DSH
核心目标 构建开放、可定制的 Agent Harness 构建支持动态变化的组件运行时
能力来源 Extensions、Skills、Prompts、Packages 运行时组件及其依赖关系
主要变化发起者 用户、开发者,也可以由 Agent 辅助 用户、系统或 Agent
组件组织方式 扩展资源与 Package 动态依赖图
激活方式 启动加载、临时加载或重新加载 根据 Context 与 Coeffects 激活
生命周期 以扩展加载和配置为中心 Effect 与 Cleanup 对称管理
依赖治理 主要由扩展代码和包依赖处理 将运行时能力依赖显式化
失败处理 依赖扩展实现、进程或会话边界 通过 Cleanup 支持组件级回滚
演化方向 从固定产品走向开放平台 从开放平台走向受控自生长
核心价值 让 Harness 容易被改变 让 Harness 可以安全地持续改变

可以把两者的关系概括为:

Pi:
稳定核心
+
开放扩展点
+
能力分发机制

DSH:
组件化 Context
+
显式 Coeffects
+
可逆 Effect
+
动态生命周期

Pi 解决了「能力能否加入」的问题。

DSH 更关注「能力如何进入、如何协作、如何退出,以及如何在运行时形成新的结构」。

6. 从可扩展到自生长,中间还差什么

可以把 Agent Harness 的演进分为三个阶段。

6.1 第一阶段:固定能力

模型
+
固定提示词
+
固定工具

系统能够执行任务,但能力边界在设计阶段已经确定。

如果缺少能力,只能由开发者修改产品代码并重新发布。

6.2 第二阶段:开放扩展

稳定核心
+
Extensions
+
Skills
+
Packages
+
开放 API

用户和开发者可以持续增加能力。

Agent 也可以帮助编写扩展,但扩展的发现、安装、验证和维护通常仍然依赖人工流程。

Pi 代表了这一阶段的重要方向:Harness 不再是一个封闭产品,而成为可以根据个人工作流持续定制的平台。

6.3 第三阶段:受控自生长

稳定内核
+
能力缺口识别
+
候选组件生成
+
动态组件运行时
+
显式依赖
+
可逆副作用
+
隔离验证
+
自动回滚
+
能力沉淀
+
持续淘汰

Agent 不再只是等待外部为它安装能力。

它可以根据任务识别不足,提出新的能力结构,在受控环境中试验,并根据结果决定保留、修改还是撤销。

这时,Agent 的角色从能力消费者变成了能力形成过程的参与者。

7. 自生长不等于无限增长

「自生长」并不是 Agent 不断为自己增加工具和代码。

无限增加不是生长,而是熵增。

一个只会添加能力、不会清理能力的系统,最终会出现:

  • 工具数量不断膨胀;
  • 相似能力重复实现;
  • 组件版本相互冲突;
  • 权限范围持续扩大;
  • 上下文被大量描述占据;
  • 路由规则越来越难以理解;
  • 无效监听器和后台任务持续运行;
  • Agent 无法判断应该使用哪个工具;
  • 系统行为逐渐不可预测。

因此,自生长必须同时包含四种能力:

增加
+
选择
+
清理
+
遗忘

可以进一步表示为:

可持续生长
=
生成新能力
-
淘汰无效能力
+
重组已有能力

这也是 Cleanup 和生命周期管理的重要性所在。

真正成熟的 Agent 不仅要知道「我还需要什么」,还要知道:

  • 什么已经不需要了;
  • 什么正在产生负面影响;
  • 什么应该暂时停用;
  • 什么可以被更简单的组件替代;
  • 什么经验只适用于过去的环境。

遗忘不是生长的反面。

受控遗忘本身就是生长的一部分。

9. Pi 与 DSH 不是替代,而是演进

Pi 的价值在于证明了一件事:

Agent Harness 不需要是一个功能固定的封闭产品。

通过开放 Extension、Skill、Prompt 和 Package,Harness 可以被用户重新塑造,也可以让 Agent 辅助编写自身需要的扩展。

这已经比传统 Agent 产品向前迈出了一大步。

DSH 所代表的方向,则是在此基础上进一步抽象:

如果 Agent 可以创建新能力,那么这些新能力应该如何成为运行时的一等公民?

答案不能只是把更多代码放进扩展目录。

系统还需要:

  • 明确组件所处的 Context;
  • 显式声明组件依赖的 Coeffects;
  • 管理组件产生的 Effect;
  • 在组件退出时执行 Cleanup;
  • 根据环境变化重新计算能力拓扑;
  • 对候选能力进行验证;
  • 将有效能力沉淀下来;
  • 将失效能力安全淘汰。

因此,二者可以形成一种递进关系:

Pi:
让 Harness 可以被修改

        ↓

DSH:
让修改具有显式生命周期

        ↓

自生长 Agent:
让 Agent 可以发起、验证并沉淀修改

从这个角度看,DSH 不是对 Pi 的否定。

它是在 Pi 所代表的开放扩展思路上,将“变化”进一步提升为运行时的核心抽象。

10. Harness 正在成为 Agent 的生长环境

如果说 Pi 的核心贡献,是把 Agent 从一个封闭产品变成一个可编程、可扩展的平台,那么 DSH 所探索的下一步,就是让 Agent 逐渐成为自身能力变化的参与者。

它不只是使用已有工具,还可以:

  • 发现能力缺口;
  • 创建候选组件;
  • 调整能力组合;
  • 在受控环境中试验;
  • 根据结果保留或回滚;
  • 将成功经验沉淀为长期能力。

但自生长不等于无限增加。

一个只会安装新工具、写入新记忆、叠加新策略的 Agent,最终只会被自己积累的复杂度拖垮。

真正可持续的生长,必须同时包含:

发现
→ 生成
→ 激活
→ 验证
→ 选择
→ 清理
→ 沉淀
→ 淘汰

Pi 回答了:

如何让 Agent Harness 可以被持续扩展?

DSH 则进一步追问:

如何让 Agent 安全地参与自身能力的形成?

从 Pi 到 DSH,变化的不只是扩展机制,而是 Harness 的角色。

它正在从承载工具与插件的工程框架,转变为支持能力试验、筛选、重组和沉淀的运行环境。

未来的 Agent Harness 可能不再只是模型与工具之间的连接层。

它还将成为 Agent 管理自身变化的基础设施:

  • 允许新能力出现;
  • 阻止错误变化扩散;
  • 保存经过验证的经验;
  • 清除已经失效的结构;
  • 在保持稳定内核的同时持续重组外围能力。

只有当 Agent 既能增加能力,也能验证、回滚、清理和遗忘时,它才真正具备从「可扩展的软件」走向「可持续生长的系统」的可能。

以上。