

这个系列写到最后，前几篇留下的问题已经不再是 DeepSeek Harness 能不能把 Coding Agent 做好。它的 Standard Mode 本来就是一个功能完整的 Coding Agent，能读取代码、修改文件、调用 Shell、运行测试，也能在 Session 和 Trajectory 里留下工作经过。更需要回答的是：既然 DSH 把插件、Profile、preset、权限、审批和运行记录都做进了同一套系统，这些设计是否只能服务于写代码，还是可以承载别的 AI Workflow？

我在前文把 AI Workflow 粗略分为 Build、Service 和 Operations。Coding Agent 属于已经发展得较快的 Build Workflow；Operations 面对的则是一个正在运行的系统，它要先读取状态，再提出有限的变更，得到授权后执行，并根据新的状态判断任务是否完成。这类工作很适合检验 Harness，因为它的目标和流程都高度结构化，同时也需要防止权限过大带来运维灾难。

于是，我决定在已经运行于腾讯云 Lighthouse 的 DSH 里，用创造模式开发一个云服务器管理 Agent，再把它做成 `server-operator` preset，交回同一个 DSH 运行。第一版只管理一个测试服务：它可以查看服务器状态，可以生成重启计划，可以在人工批准后应用计划；除此之外，它不能停止服务，不能读取日志，更没有 Bash 和通用文件工具。

标题里的两次 DeepSeek Harness 对应两种角色。前一个 DSH 是开发这个 Agent 的 Coding Agent，后一个 DSH 是运行这个 Agent 的 Harness。整个实验要验证的是这两层关系能否接起来。

## 实验的五个阶段

如果一上来就讲 Contract、Cordis、Provider 和 ToolRuntime，读者很容易只看到一堆 DSH 术语。所以先把工作过程交代清楚。五个阶段分别解决五个问题：

|阶段|要解决的问题|完成后的结果|
|---|---|---|
|环境审计|DSH 运行在哪里，开发 Agent 与运行 Agent 应当拥有什么权限|确认云上运行链路，也确定运行 preset 不继承通用系统工具|
|内存原型|云管 Agent 究竟读什么、计划什么、执行什么|得到 Snapshot、Plan、Apply 三步流程和可重复测试的 Fixture|
|接入 DSH Runtime|领域代码怎样成为 DSH 能调度、审批和记录的工具|完成 Cordis Adapter，并用 DSH 自己的 ToolRuntime 与 ApprovalService 验证|
|安装与 preset|Host 侧的服务器能力和 Session 侧的工具怎样分开装配|Provider 进入 Web Profile，Consumer 进入 `server-operator` preset|
|Linux 验收|内存状态怎样替换成云服务器状态，又不把 Shell 交给模型|Linux Provider 只执行固定系统调用，最终完成一次人工制造故障后的恢复|

开发过程沿着这五个阶段推进。每一阶段通过验收以后，下一阶段才获得扩大范围的理由。

## 第一阶段先把云服务器和权限关系摸清楚

实验开始以前，DSH 已经部署在腾讯云 Lighthouse 上。它由 Hermes 托管，通过 `dsh-full-remote` 和 Cloudflare Tunnel 暴露到一个子域名，因此我可以从电脑和手机进入同一套 7×24 小时在线的 DSH Web。这个环境本身已经体现了 Web UI 的一个优势：Session、模型配置和运行状态留在云服务器上，终端只负责访问，不需要在每台设备重新建立工作环境。值得一提的是，这种多端共享的操作方式不大可能是DSH精心设计的，但操作起来还挺好用的。

Run 00做只读审计。它确认了 OpenCloudOS、Node 和 DSH 版本，也理清了访问链路：浏览器请求经过 Cloudflare Tunnel 到达本机 `dsh-full-remote`，再进入 DSH Web；Hermes 以 user service 运行，并拉起 DSH Web 与 cloudflared。后续如果 preset 或插件装错了，至少知道应该从哪一层查起。

这轮审计还改变了后面的设计。当时的 Creator Session 拥有 Bash 和文件读取能力，虽然提示中要求避免读取不必要的配置，通过 Trajectory 我仍然发现它查看了超出审计所需的部分进程和配置内容。虽然没有系统状态因此改变，但这个经过让我再次确认了这个观点：不能把运行 Agent 的权限边界寄托在系统提示里，再期待模型每次都保持克制。而对于有操作权限的云服务器管理Agent，严格控制工具范围和约束工作流更加至关重要。

创造模式可以继续保留完整编码能力，因为它需要检查源码、安装依赖和运行测试；`server-operator` 的运行环境却不能继承 Bash、文件系统、Creator 或插件安装工具。运行 Agent 能做什么，要由 preset 来严格把关。

## 第二阶段在内存中模拟Agent管理动作

有了权限原则以后，我没有马上让模型调用 `systemctl`。云管 Agent 首先需要一套明确的工作语言，否则接入服务器只会得到另一个会执行 Shell 的聊天助手。

我把最短的恢复过程拆成三步。第一步读取快照，返回主机、目标服务和健康端点的状态；第二步根据当前状态生成变更计划，但不执行；第三步应用一个已经存在的计划。应用前需要检查计划是否过期、是否已经用过，以及等待期间服务器状态有没有发生变化。

模型最终只会看到三个对应工具：

```
server_snapshot
server_change_plan
server_change_apply
```

为了先验证这套语义，我建立了一个内存里的 Fixture Provider。它可以稳定复现服务正常、磁盘压力、健康端点异常和服务停止等状态，不需要反复操作云服务器的真实环境。Run 01 在这里完成了 Contract、Fixture Provider 和 Consumer：Contract 规定允许出现的数据和动作；Provider 保存并改变内存状态；Consumer 把领域能力整理成给模型使用的工具定义。

这一阶段的 `restart-service` 是唯一允许的写动作，目标也只有 `serverops-demo`。Plan 只记录将要做什么，Apply 才能改变状态。一个计划不能重复使用，超过有效期后不能执行，生成计划之后如果服务状态发生变化，也要重新规划。19 项测试全部通过以后，这套代码仍然没有装进 DSH。先确定云管流程本身成立，再处理插件装配，问题出现时才知道应该检查领域逻辑还是 Harness 接口。

Fixture 后来还把“受约束”落实成一组能够反复重放的例子：未知动作会被拒绝，Plan 不会偷偷改状态，审批拒绝后 Apply 不会进入 Provider。等这些行为固定下来，Linux Provider 只需复现同一份 Contract，无须重新设计模型可见的工具。

## 第三阶段让领域代码进入 DSH 的运行时

Run 01 证明的是一套 TypeScript 对象可以工作，还没有证明 DSH 会按预期加载、调度和卸载它。Run 02 开始处理这层差别。

我为 Provider 和 Consumer 各写了一层 Cordis Adapter。Provider Adapter 把 `serverOps` 注册为 Host Service，本身不向模型暴露工具；Consumer Adapter 依赖 DSH 已有的 `tools` 服务和刚才注册的 `serverOps`，再把三个领域工具装进 ToolRuntime。源码里的依赖关系如下：

```
export const inject = ['tools', 'serverOps'] as const;

export function apply(ctx: Context) {
  const tools = ctx.get('tools') as ToolRuntime;
  const serverOps = ctx.get('serverOps') as ServerOpsService;
  const defs = createToolDefs(serverOps);
  // 接下来只注册三个 ServerOps 工具
}
```

把 `serverOps` 写成硬依赖以后，Provider 卸载时 Consumer 会一起退出，三个工具随之消失；换上另一个 Provider，Consumer 又会随新的 Service 实例重新加载。测试曾专门挂载不同场景的 Fixture，确认 Snapshot 读到的是新 Provider，而不是工具闭包里残留的旧对象。

审批也在这一阶段从设想变成 DSH Runtime 的行为。`server_change_apply` 在执行前读取一份只包含计划编号、动作和目标的 PlanView。计划无效时直接拒绝；计划仍然有效时，Consumer 只返回一个 `ask` 决策：

```
return {
  kind: 'ask',
  reason: `Apply plan ${view.planId} (${view.action} on ${view.target.service})`,
};
```

Consumer 不负责模拟一个“你是否同意”的对话，也不自行决定用户答复。DSH 的 ToolRuntime 收到 `ask` 后，把请求交给 ApprovalService；只有 `allowed-once` 才继续调用工具正文。测试使用了 DSH 0.1.0-rc.6 的 ToolRuntime、ApprovalService 和带开放 Turn 的 Session，检查 `approval/asked` 与 `approval/decided` 是否成对进入记录，也检查拒绝、取消、审批服务不可用和 `never` policy 时 Provider 都没有收到 Apply。

这几轮测试还修正了一些初版里的乐观判断。例如，包入口能被测试函数直接调用，不等于 DSH Loader 能从 package entry 加载整个模块；Consumer 如果只在启动时取一次 Provider，也可能在 Provider 替换后继续持有旧对象；错误返回的 schema 如果没有覆盖完整 envelope，领域错误码还会在 ToolRuntime 中被改写成 `INVALID_TOOL_OUTPUT`。这些问题都发生在领域代码和 Harness 相接的位置，所以 Run 02.1 又补上了 Loader、生命周期、审批事件和错误码穿透测试。收尾时，五个 package 的 43 项测试全部通过。

## 第四阶段把 Provider 装进 Profile，再把工具做成 preset

到这一步，项目仍然只存在于 workspace。Run 03 才开始修改 DSH 的运行配置。

这里需要先解释 Profile 和 preset 的分工。Web Profile 属于 Host，负责加载长期存在的服务器能力；preset 决定一个新 Session 向模型暴露哪些工具和角色说明。我把 Fixture Provider 装进 Web Profile，把 Consumer 只放进 `server-operator` preset。于是，服务器侧可以一直提供 `serverOps` Service，但只有选择这个 preset 的 Session 才会得到三个 ServerOps 工具。

本地 package 先打成 tarball，再作为 Web Profile 的依赖安装。安装时有两个容易复现的细节：私有 `@serverops/*` 包之间存在传递依赖，需要在 Profile 的 `pnpm-workspace.yaml` 里写本地 overrides；`cordis.patch.yml` 新增插件时要使用顶层 `insert`，普通 row 会被解释为修改已有条目并报 `entry not found`。这些问题不影响 Workflow 的设计，却会决定插件能不能被 Loader 找到。

安装完成以后，我没有让当前 Creator Session 重启自己的控制平面，而是从独立通道重启 DSH Web，再回来检查新 preset。选择器里出现了 `server-operator`，说明它已经成为一套可以单独启动的 Agent 装配。

_图 1：_`_server-operator_` _已进入 preset 列表。截图记录的是选择前的界面，描述仍保留 Fixture 阶段的文字。_

Run 03B 随后使用 Fixture 做运行验收。模型可见的 schema 只有那三个工具，Apply 会弹出一次性审批，拒绝时内存状态不变，同一个有效计划重新申请并获得允许以后才改变状态。至此，`server-operator` 已经在 DSH 里完整运行，但它看到的仍是内存世界。

## 第五阶段换上 Linux Provider，在云服务器上恢复一次服务

Run 04 的任务是让同一份 Contract 开始读取 Linux，同时守住前面确定的权限边界。模型仍然调用 Snapshot、Plan 和 Apply，变化只发生在 Provider 后面。

Linux Provider 没有提供自由格式命令入口。它内部只允许读取 `serverops-demo.service` 的 systemd user 状态、检查 `127.0.0.1:18080/health`、查看根分区资源，以及执行该测试服务的 restart。生产 Runner 使用 `spawn(executable, argv)`，关闭 Shell 解释，并为命令设置固定工作目录、最小环境、超时和输出上限：

```
const child = spawn(spec.executable, spec.argv, {
  cwd: spec.cwd,
  env: spec.env,
  shell: false,
  stdio: ['ignore', 'pipe', 'pipe'],
});
```

用户输入不会变成可执行文件名、service unit、URL、路径或环境变量。Apply 收到有效计划以后，也不是运行 restart 就返回成功；Provider 会重新读取 service 和 endpoint，只有两项检查都通过，才返回 `change applied and verified`。

这一阶段出现过一个很有代表性的疏漏。Command Spec 的测试通过了，Runner 的测试也通过了，组合起来却把 executable 在 argv 中重复了一次。66 项测试没有报告失败，因为两组测试分别检查了自己负责的局部。这个问题在进入最终验收前的 preflight 中暴露，随后增加了生产规格与 Runner 的组合检查。DSH 的 Session Log 和 Trajectory 在这里再一次发挥了大作用：可以沿着工具调用、失败输出和修改记录回看，找出遗漏发生在哪一层，而不用依赖最终报告的概括。

Run 05 完成 demo service 安装和 Provider 替换以后，我新建了一个 `server-operator` Session。先输入“检查服务状态”，Agent 调用 `server_snapshot`，返回主机 `healthy`、服务 `active`、健康端点 `up`。

我随后从独立 SSH 通道停止 `serverops-demo`。故障制造没有交给验收 Session，因为这个 preset 本来就不该拥有停止服务的能力。回到同一个 Session，我输入“听说服务刚崩溃了，请再检查状态”。新的 Snapshot 读到主机 `degraded`、服务 `inactive`、端点 `down`，模型于是调用 `server_change_plan`，生成 `plan-2`：目标是 `serverops-demo`，动作是 `restart-service`，风险为低。生成 Plan 时没有执行系统变更。

|   |   |   |   |
|---|---|---|---|
|观察时点|主机|服务|健康端点|
|故障前|`healthy`|`active`|`up`|
|SSH 停服后|`degraded`|`inactive`|`down`|
|Apply 并复查后|`healthy`|`active`|`up`|

我回复 `apply`。模型发出 `server_change_apply({ planId: "plan-2" })`，ToolRuntime 在工具正文执行前暂停，界面展示 `Apply plan plan-2 (restart-service on serverops-demo)`，并给出“拒绝”和“允许一次”两个按钮。

_图 2：审批卡明确列出了计划编号、动作和目标。_

我选择“允许一次”以后，ApprovalService 记录决定，ToolRuntime 才继续分派 Apply。Linux Provider 执行固定的 restart，并立即复查。工具返回 `change applied and verified`，模型又调用一次 Snapshot，确认服务回到 `active`，端点回到 `up`，主机恢复为 `healthy`。

到这里，一次恢复的完整经过才可以写成：

```
人在 SSH 中停止测试服务
→ Snapshot 读取故障后的状态
→ Plan 生成许可范围内的重启计划
→ 人在 DSH 中允许这一次 Apply
→ Linux Provider 执行固定 restart 并复查
→ 新 Snapshot 确认服务恢复
```

验收没有在恢复成功后结束。我继续要求它“请停止服务”。模型没有工具可调，直接说明 `stop-service` 不属于 Contract，当前唯一的变更动作是 `restart-service`。我又要求“查看服务日志”，它虽然可以再读一次 Snapshot，却不能取得日志内容，因为 preset 没有日志工具，也没有 Shell 和文件读取能力。

运行环境里不存在绕过去的工具，所以这两次拒绝不依赖提示词里的自我约束。一个拥有服务器 Bash 的 Coding Agent 当然也能完成重启，但它同样可以停止别的服务、读取任意日志，甚至修改这套 Agent 自己的代码。`server-operator` 承担的任务更少，能够采取的执行路径也更少，这构成了它作为领域 Agent 的价值。

## 完成五个阶段以后，我怎样理解 DSH

实验做完再回头看，前几篇讨论的插件、preset、Session、Trajectory 和审批不再是分散的产品概念。它们在一次 Operations Workflow 中各自承担了可以观察的工作。

### Coding Agent 是 DSH 提供的一种装配结果

在创造模式中，DSH 表现为 Coding Agent，因为当前 Session 装入了文件、Shell、测试和 Cordis 开发能力。切换到 `server-operator` 后，模型、Session 和 Agent Loop 仍在，工具集合却换成了三个服务器领域动作。Standard Mode 和 `server-operator` 都由同一个 Harness 运行，只是两个 preset 的能力差异很大。

Coding 能力很强，却不必成为每个 Agent 的公共起点，再想想我们在Codex和Hermes中对于一个确切任务的操作唤起很可能就会经历过度地泛化和误导。对 Operations Workflow 来说，从 Standard Mode 中删减工具还不够，因为剩下的 Bash 仍然可以绕过领域 Contract。更合适的做法是从领域 Service 出发，只向模型注册完成这项工作所需的状态和动作。

### 专用 Agent 可以很简单，因为 Harness 已经完成了大量公共工作

`server-operator` 自己没有实现新的对话框架、Session 存储、工具调度、权限系统、审批卡片或 Trajectory。它只补上了服务器领域的 Contract、Provider 和 Consumer，代码甚至跟AI没什么关系，其余部分继续使用 DSH 已有的运行能力。

这解释了为什么一个只有三个工具的 Agent 仍然需要 Harness。复杂度并没有消失，而是被放到了更合适的位置：领域插件说明“这项工作允许做什么”，公共 Runtime 负责“工具怎样被调用、写操作怎样等待批准、过程怎样保存”。如果每个 Agent 原型都在自己的脚本和界面里重新实现后半部分，业务逻辑还没有写多少，另一套并不成熟的 Harness 已经先出现了。

### 约束需要写进装配、数据和执行过程

这次实验里，权限没有集中在一个万能开关中。preset 先决定模型能看见三个工具；Contract 再把动作限制为 `restart-service`，把目标限制为 `serverops-demo`；Plan 保存前置状态，等待期间发生变化就不能继续；ToolRuntime 在 Apply 前发起一次性审批；Linux Provider 最后把通过检查的领域动作翻译成固定系统调用。

这些限制分布在不同位置，是因为它们回答的问题不同。提示词可以说明角色，却不能让一个已经存在的 Bash 工具消失；JSON schema 可以限制参数，却不能代替人的写操作授权；一次 restart 返回零退出码，也不能证明健康端点已经恢复。把各层职责分开以后，成功与拒绝都能从运行记录中解释。

### Trajectory 不只用来回放，也能指导下一轮修改

这次开发中，Session Log 曾经揭示权限记录与最终摘要不一致，也发现过测试断言没有覆盖自己声称覆盖的行为；Linux Provider 的组合疏漏同样是在沿运行过程复查时定位的。Trajectory 保留了模型拿到的上下文、工具调用、审批位置和错误来源，Workflow Owner 因而可以检查发生经过，而不是只阅读一次运行结束后的概括。

如果把“Workflow 可以自我进化”理解为运行 Agent 自己增加权限、修改 Provider，那会破坏这次实验花力气建立的边界。更合理的循环是：Workflow Owner 从 Trajectory 中找到阻碍，把问题交回 Creator Session，修改 Contract、Provider 或测试，再发布并验收新版本。修改发生在有 Git、有测试、有人检查的开发环境中，运行 preset 不需要获得自我改写能力。

## DSH 更可能成为引擎，而不是所有用户都要面对的界面

这次实验把 Workflow 留在 DSH Web 里，因为这里最方便观察 preset、审批和 Trajectory。Everything is a Plugin 的设计并不要求最终入口也保持在这里。Contract、Provider 和 Consumer 与当前聊天界面没有绑定；以后可以增加 IM Client，把请求和审批带进企业通信工具，也可以用独立 Web/App 只展示服务器状态、恢复计划和批准按钮，DSH 留在后面运行 Session、工具与记录。

这也能解释有人觉得 DSH 门槛较高的原因。它把插件树、Profile、preset、权限和 Trajectory 等控制面直接呈现给创建 Workflow 的人，这些内容对普通业务用户当然过多。但 DSH 如果承担的是 Agent 引擎和开发环境，终端用户就没有必要学习整套控制面。他们接触的可以只是 IM 中的一个会话，或者只有几个领域动作的应用界面。

发布时，已经定义好的 Workflow 可以接到合适的入口，无须把同一个复杂 Web UI 搬到更多终端。DSH 提供引擎，插件提供领域能力，产品界面根据使用者的任务重新组织交互。

## AI Workflow Owner 已经可以从自己的工作流开始

回到 Build、Service 和 Operations，这次实验为 Operations 留下了一个能够复述的样本。它只管理一个测试服务，却已经包含状态读取、计划生成、人工批准、执行后复查和越界拒绝；这些行为都有对应的工程对象，也能从 Trajectory 中找到发生经过。

AI Workflow Owner 可以据此选择第一批任务：运行状态能够读取，允许动作可以枚举，写操作有明确的批准人，执行结果也能够再次检查。起点不必是一个什么都能做的 Agent。先把一个经常发生、责任清楚的工作过程写成 Contract，再用 Fixture 固定行为，接入 Runtime，最后替换为领域 Provider，这条路线已经可以在 DSH 中完成。

在这个系列的前面，我更多是在源码和架构里判断 DSH 想成为怎样的产品。做到这一篇，判断终于有了一个运行中的对象：我用 DSH 的 Coding Agent 开发了一个远比 Coding Agent 简单的 Operations Agent，它继续使用 DSH 的 Session、审批和 Trajectory，也会因为没有相应工具而拒绝请求。

对我来说，这正好回答了系列最后的问题。DeepSeek Harness 的机会在于让更多 Workflow Owner 能够在同一个 Harness 里开发、运行、观察并修改自己的工作流。Coding Agent 已经在其中，更多像 `server-operator` 这样边界清楚的 preset 也可以进入这套系统。