DeepSeek Harness 源码拆解:可组装的 Agent 运行系统

从一个项目助手说起

假设你想给团队做一个项目助手。新人接手仓库时,可以问它:“这个项目是做什么的?怎样启动?要改这个功能,应该先读哪些文件?”

最初,它只需要阅读代码、回答问题,并支持继续追问。之后,你可能想让它自动审查 PR,在网页中展示分析过程,或者从飞书接收任务。

开发这样的助手,可以复用已有 Agent 的模型调用、文件工具和执行循环。随着任务变复杂,还会遇到更具体的问题:一次读多少内容?历史太长时怎样整理?回答不对时,能否查到模型看过什么?某项默认策略不合适,能不能换掉?

DeepSeek Harness,简称 DSH,提供了可以直接使用的 Coding Agent,也把支撑它运行的组件开放给开发者组合和改造。 官方将它定位为本地优先、可扩展的 Coding Agent 与 Agent 开发运行环境。官方介绍、项目定位

下面沿着这个项目助手展开:先理解模型之外需要什么,再组装能力、执行任务、检查记录,最后调整它的工作方式。


模型之外,还需要一套运行系统

先看最简单的读取任务。你把“读取 README,告诉我怎么启动项目”发给模型,同时提供一个 read 工具的定义。模型可能返回这样的调用请求:

1
2
3
4
5
6
{
  "name": "read",
  "arguments": {
    "file_path": "README.md"
  }
}

这时,磁盘上的文件还没有被读取。接下来需要有程序检查参数和权限,读取文件,把结果交回模型,再让模型继续回答。如果它发现 README 信息不够,要求查看 package.json,这个过程就再发生一次。

核心循环可以写成下面的伪代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 省略流式输出、重试和持久化等细节
while True:
    reply = model.generate(messages, tool_definitions)
    messages.append(reply)

    if not reply.tool_calls:
        break

    for call in reply.tool_calls:
        result = execute_with_policy(call)
        messages.append(tool_result(call.id, result))

这段循环周围还需要上下文管理、工具策略和会话状态:历史超长时怎样处理,工具失败后怎样反馈,用户取消任务时怎样停止,程序重启后怎样读取已有过程。Harness 承担的就是这些运行工作。

模型、Harness 和 Agent 可以按下面的关系理解:

概念在项目助手中负责什么
模型理解问题,提出读取哪些文件,根据结果组织回答
Harness准备上下文、执行工具、应用策略、维护会话与执行状态
Agent模型与运行机制共同组成的整体,持续推进阅读和分析任务

官方用 Agent = Model + Harness 表达这种组合关系。加号是架构示意,强调一个能够执行任务的 Agent 同时需要模型判断与承接判断的运行机制。官方说明

模型与 Harness 怎样配合

图:模型通过消息和工具调用参与任务,Harness 连接实际工作环境。用户使用的是它们共同构成的 Agent。

DSH 的 Web、桌面端等应用,已经用这套机制组合出完整的 Coding Agent。开发者也可以从现成组合开始,调整工具、上下文策略或执行组件,再接入自己的业务入口。项目名称中的 Harness,强调的就是这部分可复用的运行机制。应用与插件组合


先把项目助手组装起来

项目助手的第一版需要模型接口、文件工具、执行循环和会话记录。DSH 已经提供了这些组件,我们先用现成组合,再改一个参数:把每次读取文件的上限设为 100 行。

Cordis:让插件连接起来

DSH 用 Cordis 管理服务依赖和插件生命周期。模型适配器提供模型调用接口,文件系统提供方实现文件访问,工具插件把这些能力包装成模型可调用的工具,Agent Loop 负责推进任务。架构文档

以 read 为例,它解析参数、限制读取范围、组织返回格式,实际读取通过 ctx.fs 完成。工具依赖的是文件系统接口,具体提供方实现这个接口。以后要接入远程工作区,可以实现兼容的提供方,再通过配置装入。实现需要遵守路径解析和文件版本等接口约定;如果还要运行 shell,命令执行环境也要指向同一个工作区。文件工具源码

DSH 的插件装配架构

图:Cordis 管理依赖与生命周期,应用配置决定装入哪些组件。图中按职责归类,未展开全部插件。

这种组织方式给改造提供了位置:新增业务工具时接入工具注册服务,替换存储时实现存储接口。插件停用时,Cordis 可以执行它已登记的取消订阅和资源清理;插件需要正确登记这些操作。插件生命周期

Profile、Bundle、Patch:把组合写进配置

假设已经安装 DSH 并配置好模型,在项目目录新建 read-small.patch.yml:

1
2
3
- id: tool-fs
  config:
    readLimit: 100

然后在这个目录运行:

1
2
dsh --profile headless --patch ./read-small.patch.yml \
  "用 read 工具读取 README.md,说明这个项目怎样启动。"

这条命令用了一个 Profile 和一份 Patch,Profile 内部又引用了 Bundle。

Profile 保存一套应用的组合方案。 --profile headless 选择单次命令行任务入口,完成后输出结果并退出。headless 指无图形界面运行;换成 --profile web,选择的就是带浏览器界面的方案。Profile 通常位于 $DSH_HOME/profiles/<name>。Profile 实现

Bundle 分发可复用的插件组合。 Profile 的 package.json 列出所需 Bundle,默认 headless 的相关字段如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-headless"
      ]
    }
  }
}

dsh-base 加入模型接口、Session、文件工具和 Agent Loop 等共享能力;dsh-headless 在其上调整提示词和工具配置,加入命令行任务入口。Bundle 以 npm 包分发,通过 dsh.bundle.patch 指向包内的配置补丁。其他 Profile 也能引用同一个 Bundle。Base 配置、Headless 配置、Bundle 格式

Patch 修改组合中的具体条目。 id: tool-fs 匹配文件工具插件,readLimit: 100 把一次 read 的默认行数和最大行数设为 100。模型仍可多次调用、分页读取;单次请求超过这个上限会返回参数错误。文件工具配置、读取参数检查

因此,这次启动的过程是:

1
2
3
4
5
选择 headless Profile
  → 按顺序组合 dsh-base、dsh-headless Bundle
  → 应用 Profile、用户级配置和本次 --patch 等配置层
  → 得到插件树
  → Cordis 按服务依赖启用插件,接收任务

配置层的完整顺序是 Bundle 列表、Profile 的 cordis.patch.yml、$DSH_HOME/cordis.patch.yml、--patch 文件,最后是启动器生成的补丁。后层可以修改前层条目;插件实际启用顺序由依赖决定。Patch 的 config 按整块替换,需要保留的其他配置字段也要一起写入。配置合成源码

--patch 只影响这次启动。如果希望每次使用这个上限,可以把修改写进对应 Profile 的 cordis.patch.yml。以后为团队增加内部工具和默认设置,也可以将它们打包成业务 Bundle,让应用的 Profile 引用。

Skill:补上阅读项目的方法

现在助手有了读取能力,还需要知道怎样做项目导读。可以在项目根目录创建 .dsh/skills/project-guide/SKILL.md:

1
2
3
4
5
6
7
8
---
name: project-guide
description: 阅读项目结构,说明启动方式,并给出源码依据。
---

1. 先读 README,确认项目用途和启动说明。
2. 按需查看目录与包配置,核对入口和运行命令。
3. 给每个结论注明对应文件;未确认的信息单独列出。

DSH 的默认基础组合包含 Skill 的发现、注册和加载插件。发现这份文件后,模型可以按需加载;用户也可以在任务中输入 /project-guide 说明这个项目怎样启动,显式加入这份说明。加载后,内容进入模型上下文,模型使用已有工具开展任务。Skill 文件格式、Skill 加载机制

这时,各部分在同一次任务中配合:Profile 和 Bundle 装入能力,Patch 把文件工具的读取上限设为 100 行,Skill 提供阅读顺序与输出要求。装配配置由加载器处理,Skill 的任务说明由模型使用。

Skill 也可以附带脚本,生成 Patch、安装插件,甚至启动一个接收 GitHub 事件的服务。这些工作都能成为任务步骤;持续运行的脚本怎样重启、怎样清理资源,由它的托管方式负责。如果把同一服务实现为 DSH 插件,就可以接入 Cordis 的依赖与生命周期管理,随应用配置启停。

因此,一个功能可以同时使用插件和 Skill:插件提供运行能力,Skill 说明怎样使用;Skill 也可以负责安装和配置插件。DSH 还允许插件直接注册 Skill,把工具与使用方法一起交给模型。Skill 注册表


让一次任务跑起来

配置完成后,自己的程序就可以向这个助手提交任务。以已有网页和后端为例:后端接收用户的问题,调用 DSH,再把回答和执行过程显示到页面上。

SDK:程序怎样提交问题和继续追问

DSH 的 TypeScript SDK 通过标准输入输出上的 JSON-RPC 驱动一个运行时子进程。调用方选择 Profile、工作区和补丁,子进程装入对应能力后执行任务。SDK 文档

安装 @deepseek-ai/dsh-sdk-client 并为所选模型配置好凭据后,可以这样调用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { DeepSeekHarness } from '@deepseek-ai/dsh-sdk-client';

// cwd 替换为实际项目路径;补丁文件使用前面创建的那一份。
const harness = new DeepSeekHarness({
  profile: 'sdk',
  cwd: '/path/to/project',
  patches: ['./read-small.patch.yml'],
  provider: 'deepseek-official',
  model: 'deepseek-v4-flash',
});

try {
  const result = await harness.run(
    '用 read 工具读取 README.md,说明这个项目怎样启动。',
  );
  console.log(result.sessionId);
  console.log(result.finalResponse);

  // 指定同一个 sessionId,在已有会话中追问。
  const followUp = await harness.run(
    '根据刚才的分析,列出下一步值得阅读的文件及原因。',
    { sessionId: result.sessionId },
  );
  console.log(followUp.finalResponse);
} finally {
  await harness.close();
}

这里选择 sdk Profile 来提供程序调用入口,继续复用前面的读取上限补丁。cwd 指定 Agent 的项目工作区,相对补丁路径则从调用程序的工作目录解析。模型标识来自本文所读版本,使用时应与实际配置一致。

SDK 在首次使用时启动子进程。run() 提交输入,并收集到 Agent 再次空闲时的结果;finalResponse 是这段执行期间根会话最后提交的助手文本,返回值还包括事件与通知。close() 负责回收子进程。SDK 调用实现

每次省略 sessionId 都会新建会话。 示例中的第二次调用显式指定 ID,在同一运行时中沿用第一次的历史。应用需要保存业务任务与会话 ID 的对应关系,才能把后续追问交给对应的 Agent。

Agent Loop:一次问题怎样持续推进

一次 run() 可能包含多次模型请求。以“说明项目怎样启动”为例,模型先读 README,发现还需要核对包配置,于是继续请求文件,最后组织回答。

DSH 用 Turn 和 Step 描述这个过程。Turn 是一轮任务推进;Step 是其中一次逻辑上的模型请求及其工具处理:

1
2
3
4
Turn:说明这个项目怎样启动
  Step 1:模型要求读取 README       → 工具读取 → 返回结果
  Step 2:模型要求读取 package.json → 工具读取 → 返回结果
  Step 3:模型结合两份信息给出说明  → 本轮结束

每一步开始前,循环接收待处理输入,准备系统提示词、工具定义和消息历史。模型提出工具调用后,结果进入后续历史,成为下一步的依据。Step 是逻辑边界,网络重试可能发生在同一步内部;取消、请求失败和上下文超限也有各自的处理路径。默认循环源码

这些阶段也给插件提供了接入位置。例如,在请求前加入项目背景,在工具执行前检查权限,在一步结束后处理结果。新的应用规则可以沿这些位置进入已有循环。扩展入口

工具执行:处理并发与文件变化

如果模型在一步中提出两个读文件请求,运行系统需要决定它们怎样执行。DSH 的默认调度器只将明确声明当前调用并发安全的工具并行处理,其他调用按独占方式执行。允许并发的一批工具可以同时工作,结果仍按调用顺序提交到模型历史。工具调度源码

以后让助手修改文件时,还会遇到另一个问题:模型读完文件,你又手工修改了它。DSH 的文件观察策略记录已读取的版本,后续文件工具的修改需要与该版本相容。版本不匹配时,工具拒绝基于旧内容修改,模型可以重新读取后再决定如何操作。这项检查作用于文件工具的调用路径;shell 写入由命令执行环境和相应权限策略管理。文件观察策略

到这里,一次任务已经能从输入走到模型、工具和最终回答。下一步要解决的是:回答是否有依据,出现问题时怎样查明过程。


把执行过程记录下来

假设助手给出了错误的启动命令。只看最终回答,很难知道问题出在哪里:它没有读到包配置,还是读到了却理解错了?排查时需要查看它实际收到的信息和做过的操作。

Session Log:保留已经发生的执行

DSH 的 Session Log 是只追加的事件序列,事件带有连续的 seq、时间、类型和数据。消息、工具调用与结果、Turn 和 Step 的状态都可以进入日志。append() 会复制和校验输入、冻结事件,再提交到内存并通知订阅者;某个监听器报错,不会撤销已经提交的记录。Session 实现

前面的 SDK 返回值就包含事件。在拿到 result 后,可以先检查工具调用及其结果:

1
2
3
4
5
for (const event of result.events) {
  if (event.type === 'tool/call' || event.type === 'tool/result') {
    console.dir({ seq: event.seq, type: event.type, data: event.data }, { depth: null });
  }
}

result.events 只包含本次 run() 观察期间的根会话事件,第二轮追问的事件在 followUp.events 中。它们是读取执行过程的入口,各自都不等于完整历史日志。SDK 事件收集

Surface:为下一次请求组织消息历史

项目助手持续处理追问后,历史会越来越长。排查问题希望保留原始记录,模型继续执行则需要适合当前任务的上下文。DSH 用 Log 和 Surface 分别承接这两种需求。

Log 保留已提交的事件,Surface 是从日志派生出的当前消息视图。系统消息、用户消息、助手消息和工具结果可以进入这个视图,执行状态等事件不必直接变成模型消息。调用模型时,deriveMessages() 根据当前 Surface 生成消息历史。Surface 实现

例如,当前历史包含 A、B、C、D,希望用摘要 S 替换较早的 A 到 C。系统先向 Log 追加 S,并记录替换区间,再据此更新 Surface。结果是:Log 保留 A、B、C、D、S,Surface 使用 S、D。

Session Log 与 Surface 的关系

图:原始事件仍留在日志中,模型的当前历史使用摘要。示意省略了受保护的系统提示词。

替换需要遵守系统提示词保护、工具调用与结果配对等结构约束。DSH 还提供大工具结果外溢、历史工具内容裁剪和摘要压缩,分别控制不同来源的上下文占用。保留日志使缩减上下文不必同时销毁原始记录;摘要本身仍可能丢失细节。上下文管理实现

持久化与恢复:哪些过程能在重启后留下来

内存中有日志,还不等于进程重启后能读到它。append() 返回只代表内存提交,默认持久化插件订阅 session/event,缓冲事件,再按顺序写入会话文件。当前默认格式是带 Zstandard 压缩的 JSONL,也支持未压缩的 JSONL。持久化实现

批量写入减少了逐条同步的开销,同时留下一个尚未持久化的时间窗口。默认基础组合为此加入检查点策略:在请求模型、执行顶层工具等关键位置,先等待缓冲事件写入持久存储;失败时,不继续进入对应的请求或工具主体。检查点实现

工具操作与结果落盘仍然是两个步骤。文件可能已经修改成功,进程却在成功结果保存前崩溃。恢复时,日志有调用记录却没有结果,系统不能据此断言修改未发生。DSH 会为中断的执行补上结束记录,区分工具未启动、结果未知等情况;结果未知时,需要检查文件或外部系统后再决定如何继续。恢复修复逻辑

恢复读取的是已保存的历史工具结果,历史工具不会自动重跑。模型可以在恢复后的任务中继续判断和调用工具。流式界面已经显示、但尚未整理成日志的内容,也可能在硬崩溃时丢失。

应用入口还需要接好恢复服务。本文版本的 SDK 示例只支持同一运行时内的会话续接。 SDK 服务端复用内存中的会话,遇到未缓存的 ID 会调用 ctx.agents.create(),没有接入 ctx.agents.resume()。因此,新 SDK 进程仅传入旧 ID,不能直接恢复磁盘历史。SDK 服务端实现

开发重启恢复入口时,需要在宿主侧调用 ctx.agents.resume(),指定 resumeSessionId 并连接原有会话存储。默认 Agent Loop 随后读取日志、修复中断记录,提供可以继续接收任务的 Agent。Agent 服务接口、持久化与恢复约定


PTC:让模型写一段程序处理工具结果

如果执行记录里反复出现大段工具输出,而任务只需要少量标题、字段或统计值,可以考虑先用程序整理结果。PTC 提供了这种调用方式。

以项目助手为例,用户提出这样的要求:

列出 README 前 100 行中的二级标题,也就是以“## ”开头的行。

假设这部分有安装、启动和测试三个章节,其余都是正文。可以先比较两种执行过程。

普通工具调用:读完以后,由 LLM 挑出标题。 LLM 请求 read 工具读取前 100 行,DSH 执行读取,再把这段内容交回 LLM。LLM 从返回的正文中找出标题,整理成回答。

PTC:LLM 先生成程序,让程序读完后挑出标题。 PTC 是 Programmatic Tool Calling,即程序化工具调用。启用这种方式后,LLM 可以根据工具接口生成下面这样的程序:

1
2
3
4
5
6
const file = await tools.read({
  file_path: "README.md",
  limit: 100,
});

return file.lines.filter(line => line.text.startsWith("## "));

这段代码是 LLM 可能生成的示意内容。它先调用 read,再用 filter 保留二级标题。LLM 将程序文本放进 run_code 的 code 参数,发起工具调用;DSH 收到请求后,才真正执行这段程序。 run_code 还要求一个 description 参数,用来简述操作目的。PTC 工具定义

程序执行完成后,交回 LLM 的普通结果是筛选后的标题列表。例如,在这个假设文件中,结果可能是:

1
2
3
4
5
[
  { "number": 8, "text": "## 安装" },
  { "number": 25, "text": "## 启动" },
  { "number": 60, "text": "## 测试" }
]

LLM 再根据这个列表回答用户。两种方式都读取了相同的文件范围,变化在于:普通调用把整段读取结果交给 LLM 筛选,PTC 在程序执行期间完成筛选,再把所需内容交回 LLM。

环节普通工具调用PTC
LLM 提出什么读取文件的请求执行一段程序的请求,程序中包含读取与筛选
谁筛选标题LLM 根据返回内容判断DSH 执行 LLM 生成的筛选代码
下一次 LLM 收到什么前 100 行的读取结果程序返回的标题列表

这个过程也可以扩展到多个工具:程序先取得各自的结果,再做过滤、统计或组合,最后返回下一步需要的信息。

原生工具调用与 PTC

图:多工具场景下,LLM 可以生成组合调用的程序。两种方式都可以并行,PTC 还可以在程序内部处理结果。

程序中的工具调用仍经过 DSH 的策略链,并留下调用记录。筛掉的普通中间内容不必全部进入下一次模型请求;图片等额外内容则可能通过其他通道交给模型。PTC 执行实现

这里用提取标题说明执行过程。对于这么小的任务,生成和执行代码未必划算;工具结果较大、处理规则明确时,程序内筛选才更有机会减少上下文用量。实际收益需要同时看回答是否正确、整次任务的耗时和模型用量,并计入代码生成与出错重试的成本。


什么时候值得基于 DSH 开发

现在可以回到最初的选择:要做这个项目助手,为什么不直接接入 Codex 或 Claude Code?

如果需求是读取项目、按照团队规范回答问题,再把结果显示到自己的页面,三者都有相应的接入方式。DSH 值得关注的地方,是前面这些运行组件被组织成可替换的插件:开发者可以实现新的存储、上下文策略或执行组件,再通过配置与已有能力组合。DSH 扩展接口

开发入口主要接入方式
Codex SDK / App Server启动与恢复线程、提交任务、接收执行事件;App Server 还提供审批和客户端集成接口
Claude Agent SDK使用已有循环,通过工具、权限选项和 hooks 调整行为,例如在工具执行前修改输入或阻止调用
DSH 插件与 SDKSDK 驱动任务;插件还可以提供模型适配、存储、上下文策略和 Agent 驱动等内部组件

前面讲过,DSH 的检查点在顶层工具执行前等待日志持久化。假设团队进一步要求:

审查过程必须保存到自己的数据库;每次顶层工具调用前,确认已有会话记录已经写入数据库,失败时不得继续派发该工具。

在 DSH 中,可以实现一个数据库存储提供方,替换默认 JSONL 后端,并复用已有检查点。新的后端需要兑现创建、读取、追加和 flush() 等接口的语义,正确处理写入顺序、所有权与持久化确认。检查点等待的是持久化服务,因此可以继续参与这个组合。持久化接口、检查点实现

Claude Agent SDK 也提供 SessionStore,支持外部会话存储和恢复。不过其内建路径先写本地,再同步到外部存储;外部同步最终失败时,会报告 mirror_error 并继续任务。因此,仅配置 SessionStore 尚不能满足上面的执行约束。若尝试通过 hooks 补上,还需要设计写入确认与工具放行的协作,并验证 hook 时序能否覆盖所需记录。Claude 会话存储

Codex 的 CLI、SDK 和 App Server 有开源实现。公开扩展入口不能表达某个需求时,也可以修改内部实现,并维护相应源码变更。DSH 为多项内部机制预先定义了插件接口,让自定义实现可以独立组织;两种方式都需要验证升级后的兼容性。Codex 开源范围、DSH 组件接口

选择时,先看需要改到哪一层。已有工具、Skill 和 SDK 足够完成应用,就可以直接接入;如果要持续开发自己的上下文处理、存储或执行机制,DSH 提供了明确的组件接口和配置入口。能减少多少维护工作,仍取决于接口是否匹配需求。本文所读版本处于开发者预览阶段,具体接入能力需要按版本核对。项目说明


项目助手还可以扩展成什么

项目助手有了执行和会话能力之后,可以继续增加工具、界面和任务入口。下面五个示例分别沿着这些方向扩展:动态插件与 GitHub Review 来自官方仓库,GenUI、飞书接入和任务看板来自社区项目。

1. 在对话中给 Agent 添加一个工具

假设项目的测试日志总是这种格式:

1
2
3
PASS test_login
FAIL test_payment: timeout
FAIL test_export: missing column

你经常需要从中提取失败项,希望 Agent 做一个有固定输入和输出、以后可以重复调用的解析工具。可以在对话中提出:

为这种日志格式创建一个工具,叫 parse_test_failures。输入日志文本,返回失败测试的名称和原因。把它注册到当前 Agent 可用的工具中,再用上面的日志验证结果。

这里的 parse_test_failures 是准备创建的工具名。启用 DSH 的动态插件能力后,模型可以按下面的过程完成这个任务:

  1. LLM 编写插件。 它先查询 DSH 的工具注册接口,再生成插件代码:定义工具名称、输入参数和解析函数,并在插件加载时注册这个工具。
  2. LLM 请求 DSH 加载代码。 它先调用 cordis_define 提交插件代码,DSH 检查参数和语法、保存定义;随后调用 cordis_run,DSH 才加载并运行插件,执行其中的工具注册。动态插件工具
  3. LLM 调用新工具验证。 注册成功后,parse_test_failures 可以出现在后续模型请求的工具列表中。LLM 将样例日志交给它,DSH 执行解析函数,返回结果。

对上面这份假设日志,预期结果是:

1
2
3
4
[
  { "test": "test_payment", "reason": "timeout" },
  { "test": "test_export", "reason": "missing column" }
]

如果结果不对,模型可以查看诊断信息、修改插件、提交新版本,再加载验证。工具可用后,下一轮你交给它另一份同格式日志,它就能调用已有的 parse_test_failures,复用这段解析逻辑。

要尝试这个流程,先安装并配置好 DSH,再在源码仓库根目录运行官方配置示例:

1
dsh web --patch apps/cli/config/examples/cordis/cordis.yml

这份 Patch 装入动态插件的执行服务和模型调用入口,让 LLM 获得检查接口、提交代码、加载插件等工具。日志解析器是这里设计的应用场景,需要由模型实际生成和验证。Cordis 配置示例

前面的 PTC 示例用已有工具完成一次读取和筛选;这里则通过插件注册一个新工具,供后续任务调用。这些动态定义保存在进程内存中,DSH 重启后会消失。希望以后启动仍能使用,需要将代码整理成插件包,并加入应用配置。

2. 在 Agent 生成的界面里做选择,再让它继续任务

假设你想安排三个写作时段,可以对 Agent 说:

我会给你这一周的候选空闲时间。做一个可以点选的界面,让我挑三个 90 分钟的写作时段,并保存我的选择。

社区插件 DeepSeek Harness GenUI 支持这种过程。Agent 根据任务编写 React 和 TypeScript 界面,插件负责构建和展示;页面可以嵌在 DSH 的回答中,也可以在对话旁边打开。GenUI 项目与演示

接下来可以分三步看:

  1. Agent 生成界面。 它把候选时间做成可以点击的时间块,并提供保存选择的操作。
  2. 你在界面里选择。 例如选中周一上午、周二下午和周三下午的三个时段,再确认保存。页面将这些时间值保存到任务中。
  3. 你继续在对话中提要求。 比如:“按我刚才保存的三个时段,分别安排资料整理、提纲和初稿。”Agent 读取已保存的选择,再据此规划,你不用把时间重新输入一遍。

下面是项目提供的中文示例。绿色块表示选中的时间,底部可以确认这三个时段:

GenUI 的中文写作时段选择界面

图:GenUI 项目的日程选择示例截图。图片来源。

这里的“继续交互”,指的是界面中保存的选择可以成为下一轮 Agent 的输入。 用户点击和修改页面时,界面先处理这些操作;保存后,后续一轮 Agent 再读取结果、继续任务。日程选择只是收集时间,真正向外部日历创建事件仍需接入对应工具。

作者也展示了光合作用模拟和源码路径浏览等界面。它们适合需要点选、调整参数或查看关系的任务。

按照项目文档,可以安装到受支持的 Web Profile,再启动 DSH:

1
2
dsh plugin --profile web add dsh-plugin-genui
dsh --profile web

使用时需要满足插件声明的 Node.js 和 DSH 版本要求。

3. PR 转为待审查时,自动开始一次审查

官方仓库的 GitHub Review 示例提供了触发审查的配置。它对应一个很具体的动作:作者把草稿 PR 切换为 Ready for review。

1
2
3
4
5
作者将 PR 标为待审查
  → GitHub 发送 pull_request 事件,action 为 ready_for_review
  → DSH 的规则检查事件来源和目标仓库
  → 在配置好的工作区创建只读审查会话
  → Agent 检查 PR,将发现写入当前会话

规则生成的任务要求先刷新 PR 元数据,再围绕事件指定的 head SHA 检查 diff 和相关项目约定,进行必要的只读验证,报告可操作的问题。这个示例的交付位置是 DSH Session;自动向 GitHub 发布评论需要另外实现。GitHub Review 配置与规则

实际接入时,需要填写目标仓库、工作区和 webhook secret,并让 GitHub 能访问 webhook 入口。完成后,审查任务由外部事件发起,开发者不用再手工复制 PR 链接、输入一遍审查要求。

这里,Patch 装入监听与规则插件,规则负责决定什么时候创建任务;开始审查后,Agent 使用已有工具推进工作。团队的代码审查 Skill 还可以补充检查步骤和报告格式。

4. 在飞书里给 Agent 派任务、接收结果

如果 DSH 运行在自己的电脑或服务器上,可以通过社区插件 dsh-lark-link 把它接入飞书/Lark。插件将飞书聊天与 DSH 会话关联,转发任务和回答,并支持接收图片、文件以及回传本地文件。飞书桥接插件

一个可以尝试的使用场景是:离开电脑后,在手机飞书里发一句:

查看这个项目最近的提交,整理一份变更摘要,说明哪些模块值得重点检查。

任务在 DSH 所在机器的工作区执行,结果通过飞书返回。如果模型需要补充信息,插件可以把提问转成卡片,用户答完后继续任务。它还提供 /new、/resume 和 /workspace 等入口,用于新建、恢复会话和切换工作区。

这个插件以 Bundle 形式安装到 Profile 中,使用前需要完成飞书应用认证,配置工作区、访问范围和执行权限。它复用 DSH 的会话和工具能力,增加一个日常通信入口;手机负责发送要求和查看结果,实际执行仍由运行 DSH 的机器承担。

5. 保存一张任务卡,让 Agent 按时间重复执行

假设你每天早上都要打开项目,查看近期提交和未提交改动,再让 Agent 整理摘要。每天的要求差不多,变化的是仓库里的内容。

社区插件 dsh-task-board 可以把这份要求保存成任务卡,并为它设置执行时间。到点后,由 DSH 后台启动 Agent 去完成任务。任务看板项目

例如,你可以创建一张“项目晨间摘要”卡片:

卡片保存什么示例
要做的事查看本地仓库过去一天的提交和当前未提交改动,按模块整理摘要,列出需要人工确认的事项
在哪里做选择要检查的本地项目目录
用什么 Agent 和权限选择 standard 预设,使用只读权限
什么时候做每个工作日上午 9 点,按运行 DSH 的机器所在时区设置

保存并启用计划后,下一次到达执行时间时,会发生这样的过程:

  1. 看板后台读取任务卡,默认创建一个新的 DSH 会话。
  2. 后台应用卡片选定的项目、Agent 配置和权限,把任务要求交给 Agent。
  3. Agent 调用工具查看当时的提交与代码差异,生成这一次的摘要。
  4. 看板记录这次运行,并提供对应会话的入口。 你从卡片打开会话,就能查看分析过程和最终结果。

到了下一个工作日,同一张卡片会再次提交这份要求,Agent 重新读取当时的仓库内容。卡片保存任务要求和执行计划,摘要由 Agent 在运行时根据当时的项目内容生成。 也可以手动启动任务,先检查它能否按预期完成。任务执行与记录

按照作者文档,可以这样安装,随后重启 DSH Web:

1
dsh plugin --profile web add @linxin666/dsh-client-ui-task-board@latest

调度器运行在 DSH 后台,关闭浏览器页面不会停止调度,但 DSH 进程和所在机器需要保持运行。停机或休眠期间错过的时间不会补跑;如果同一卡片上一轮仍在执行,本次触发也会跳过。


回到开头

项目助手最初只是读取文件、说明启动方式。扩展到代码审查或团队服务后,需要调整的部分可能深入上下文、工具调度和会话存储。DSH 将这些运行机制放进同一套插件体系,开发者可以从现成组合起步,再沿接口修改具体组件。

配置描述它怎样运行,会话记录留下实际发生的过程。把两者结合起来,才有条件判断一次修改是否改善了任务效果。DSH 的开发价值,主要体现在组装、观察和改进 Agent 的运行机制上。 具体业务工具、交互流程和应用入口,仍需要围绕使用场景完成。


源码基线:2026-09-10 的提交 c291e7961a,根包版本 0.1.5-rc.2;产品与社区项目文档核对日期为 2026-09-13。本文基于源码与文档分析,代码用于说明接入位置,未启动完整应用、安装验证社区插件或开展三者的同题基准测试。