DeepSeek Harness 源码解析系列
第 2 讲:Profile、Bundle 与 Preset 组合机制
基于 DeepSeek Harness 源码 · 2026-08-17
引言
上一讲我们拆解了 DeepSeek Harness 的整体架构与 Cordis 插件框架,理解了 insert/patch/replace 三操作如何组合一棵插件树。但一个实际的问题随之而来:一个 Harness 部署到底由哪些插件构成?用户如何定制 Agent 的能力?答案是三层组合体系——Profile(启动模式)、Bundle(共享配置层)与 Preset(Agent 能力预设)。这三者叠加,决定了每一个 Agent 实例的完整能力集。
一、Bundle:共享配置层
Bundle 是 Harness 的共享配置层。它定义了所有 Profile 共用的插件行,是系统的"地基"。每个 Bundle 包含一个 cordis.patch.yml 文件,通过 patch 操作向空白的 Profile Root 注入插件。
让我们看 dsh-base bundle——所有 CLI 模式共享的核心:
📄 packages/bundle/base/cordis.patch.yml(头部注释,第 1-13 行)
# The dsh-base bundle patch: the shared core of every dsh profile, applied as
# ONE insert over the empty profile root. Later bundle patches and the user's
# profile cordis.patch.yml address these rows by id, with the last write
# winning per row.
#
# A patch replaces the targeted row's whole `config` rather than merging into
# it, so a row whose value differs by mode does NOT live here: it belongs to
# each mode bundle, keeping any single row down to one bundle layer plus the
# user's. Mode-specific rows appear below only with shared plugin identity and
# neutral defaults; each mode bundle restates its complete configuration.
#
# Row order carries no load semantics (activation is service-availability
# driven); the grouping is for readers.
这段注释揭示了 Bundle 设计的三个核心原则:
🔹 按 ID 覆盖(last write winning):后续 Bundle Patch 和用户 Profile 的 cordis.patch.yml 通过 patch 操作按 ID 修改行,后写的覆盖先写的。
🔹 Config 整体替换:Patch 替换整行 config 而非合并,因此不同模式(headless/tui/web)的值差异由各模式 Bundle 单独声明。
🔹 行序无加载语义:插件激活由服务可用性驱动(Cordis 的 inject 依赖),而非文件中的行顺序。
Base Bundle 注入了约 70 个插件行,涵盖 LLM 适配器、Session 持久化、沙箱、工具注册、Subagent、Workflow、Compaction 等。关键行包括:
📄 packages/bundle/base/cordis.patch.yml(核心插件行,第 15-451 行节选)
- insert:
- id: llm
name: '@deepseek-ai/dsh-llm'
- id: session
name: '@deepseek-ai/dsh-session'
- id: agent
name: '@deepseek-ai/dsh-agent'
- id: agent-default-model
name: '@deepseek-ai/dsh-agent-default-model'
config:
provider: deepseek-official
model: deepseek-v4-flash
- id: settings
name: '@deepseek-ai/dsh-settings-file'
- id: credentials
name: '@deepseek-ai/dsh-credentials-local'
- id: session-persistence-jsonl
name: '@deepseek-ai/dsh-session-persistence-jsonl'
config:
root: !!js dshHomePath('sessions')
- id: sandbox
name: '@deepseek-ai/dsh-sandbox-local'
- id: sandbox-policy
name: '@deepseek-ai/dsh-sandbox-policy'
config:
mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
workspaceRoot: !!js process.cwd()
- id: tools
name: '@deepseek-ai/dsh-tools'
- id: system-prompt
name: '@deepseek-ai/dsh-system-prompt'
config:
persona: ''
- id: subagent
name: '@deepseek-ai/dsh-subagent'
- id: workflow-worker-thread
name: '@deepseek-ai/dsh-workflow-worker-thread'
- id: tool-workflow
name: '@deepseek-ai/dsh-tool-workflow'
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
注意 !!js 标记——这是 Cordis 的 YAML JavaScript 表达式,在加载时求值,使得配置可以引用环境变量、函数调用等动态值。例如 !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write' 实现了环境变量的默认值回退。
二、Profile:启动模式选择
Profile 是用户启动 Harness 时选择的运行模式。通过 --profile 命令行参数指定。三种主要 Profile:
| Profile | 用途 | Bundle 层 |
|---|---|---|
| headless | 一次性任务,打印结果后退出 | base → headless |
| tui | 终端交互界面 | base → tui |
| web | Web 客户端 + RPC 网关 | base → web |
Profile 的命令行解析由 dsh-cmdline 包实现。Launcher 只解析自己的标志(--profile、--patch),剩余参数原样传递给 Profile 对应的 Commander 程序:
📄 packages/boot/cmdline/src/index.ts(第 68-72 行)
export function provideCmdline(ctx: Context, host: CmdlineHost): void {
const snapshot: readonly string[] = Object.freeze([...host.args])
ctx.provide('cmdlineArgs', { get: () => snapshot })
ctx.provide('appExit', host.exit)
}
以 headless Profile 为例,它声明了自己的 Commander 程序:
📄 packages/bundle/headless/src/startup.ts(第 31-57 行)
function headlessCommand(): Command {
return new Command()
.name('dsh --profile headless')
.description('Answer one task, print the final assistant message, and exit.')
.helpOption('-h, --help', 'show this help')
.argument('[task...]', 'the task text; multiple words are joined by spaces')
.addHelpText('after', `
Examples:
dsh --profile headless "run the tests" answer one task and exit
`)
}
export function apply(ctx: Context): void {
const program = headlessCommand()
program.action(() => {
const task = program.args.join(' ')
if (task.trim() === '') program.error('error: a task is required...')
ctx.provide(HEADLESS_STARTUP_SERVICE, { task } satisfies HeadlessStartupValues)
})
parseCmdline(ctx, program)
}
设计要点:Launcher 不感知各 Profile 的命令行参数——每个 Profile 的 apply() 函数拥有自己的 Command 程序,自行解析并发布服务。这实现了启动参数与插件树的解耦。
三、Preset:Agent 能力预设
Preset 是三层中最核心的概念。一个 Preset 是一个目录,内部包含 agent.cordis.yml 组配置文件。它定义了 Agent 的能力组合——哪些工具可用、哪些服务挂载、权限如何设置。
3.1 Preset 的数据模型
📄 packages/preset/agent-presets/src/preset.ts(第 21-41 行)
export interface AgentPreset {
/** Stable identifier; the preset directory's name. */
readonly id: string
/** Trust recorded from the root this preset was discovered under. */
readonly trust: PresetTrust
/** Absolute path of the preset's agent composition file. */
readonly path: string
/** Display name from the preset's own metadata; absent falls back to id. */
readonly name?: string
/** One sentence on what this preset is for, when it published one. */
readonly description?: string
/** Declared position within its group; absent sorts after those that declare one. */
readonly order?: number
/** Why this preset cannot compose a session, absent when it can. */
readonly broken?: string
}
export type PresetTrust = 'system' | 'user'
export const PRESET_ID = /^[a-z0-9][a-z0-9-]*$/
关键设计:
🔹 信任分级:system 预设随部署分发,user 预设由本地创建,与 Shell 访问同等级别信任。
🔹 ID 即目录名:PRESET_ID 正则限制为 ^[a-z0-9][a-z0-9-]*$,防止路径穿越。
🔹 Broken 而非隐藏:损坏的 Preset 保留在列表中(broken 字段),而非被跳过——否则其目录名被占用,用户无法删除。
3.2 Preset 发现机制
发现模块扫描配置的 根目录,每次调用时重新读取磁盘(无需重启即可发现新 Preset):
📄 packages/preset/agent-presets/src/discovery.ts(第 26-41 行)
/** The composition file that makes a directory a preset. */
export const COMPOSITION_FILE = 'agent.cordis.yml'
/** Harness-home directory holding locally authored presets. */
export const USER_PRESET_DIR = '.agent-presets'
/**
* Why `rows` cannot be an entry list, or undefined when it can.
* A shallow shape check, deliberately short of the loader's work: it does not
* resolve plugin names or apply configs.
*/
function entryListProblem(rows: unknown, at = ''): string | undefined {
if (!Array.isArray(rows)) {
return at === ''
? 'the composition must be a top-level list of plugin rows'
: `group ${at} must hold a list of plugin rows`
}
for (const [index, row] of rows.entries()) {
const label = at === '' ? `row ${String(index + 1)}` : `${at} row ${String(index + 1)}`
if (typeof row !== 'object' || row === null || Array.isArray(row)) {
return `${label} is not a plugin row (expected a map with a "name")`
}
const { name, group, config } = row
if (typeof name !== 'string' || name === '') {
return `${label} names no plugin (a "name" string is required)`
}
// ... group recursion
}
}
📄 packages/preset/agent-presets/src/discovery.ts(第 139-170 行)
export async function scanRoot(root: PresetRoot): Promise {
const dir = resolve(expandHomePath(root.path))
let children
try {
children = await readdir(dir, { withFileTypes: true })
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return []
throw new Error(`agent-presets: cannot read preset root ${dir}: ${String(error)}`)
}
const found: AgentPreset[] = []
for (const child of children) {
if (!child.isDirectory() || !PRESET_ID.test(child.name)) continue
const directory = join(dir, child.name)
const path = join(directory, COMPOSITION_FILE)
const broken = await isFile(path)
? await compositionProblem(path)
: `the composition file ${COMPOSITION_FILE} is missing...`
const metadata = await readPresetMetadata(directory)
found.push({
id: child.name, trust: root.trust, path, ...metadata,
...broken === undefined ? {} : { broken },
})
}
return found.sort((left, right) => {
const byOrder = (left.order ?? Number.POSITIVE_INFINITY)
- (right.order ?? Number.POSITIVE_INFINITY)
return byOrder === 0 ? left.id.localeCompare(right.id) : byOrder
})
}
发现流程:
Preset 发现流程
1. 扫描根目录:readdir 获取子目录列表,缺失根目录返回空(不报错)
2. 过滤目录名:只接受匹配 PRESET_ID 的目录(如 .DS_Store 被跳过)
3. 健康检查:验证 agent.cordis.yml 是否存在且 YAML 可解析
4. 读取元数据:从 preset.yml 获取显示名和描述(缺失时回退到 ID)
5. 排序:先按 order 字段,再按 ID 字母序
6. 多根合并:discoverPresets 遍历所有根,先出现的 ID 胜出
3.3 Preset 元数据
每个 Preset 目录可包含可选的 preset.yml 元数据文件:
📄 packages/preset/agent-presets/src/metadata.ts(第 28-39 行)
export interface PresetMetadata {
/** Human-facing name; falls back to the preset id when absent. */
readonly name?: string
/** One sentence on what this preset is for. */
readonly description?: string
/** Position within its group; lower comes first. */
readonly order?: number
}
元数据只影响展示,不影响能力。即使元数据文件缺失、格式错误或无法读取,Preset 仍然可以正常挂载——"展示不是能力,损坏的名称不应阻止 Agent 启动"。
四、PresetRoster:生命周期管理
PresetRoster 是 Preset 系统的核心管理类,负责挂载、绑定、重新组合和清理:
📄 packages/preset/agent-presets/src/roster.ts(第 49-120 行,构造函数节选)
export class PresetRoster {
private readonly resolvedRoots: readonly PresetRoot[]
private readonly standing = new Map >()
private readonly bindings = new Map ()
constructor(ctx: Context, config: Config, ...) {
this.resolvedRoots = config.roots.map((root, i) => ({
path: resolve(expandHomePath(root.path)),
trust: root.trust,
}))
// Append user root unless disabled
if (config.includeUserRoot !== false) {
this.resolvedRoots = [
...this.resolvedRoots,
{ path: resolve(dshHome, USER_PRESET_DIR), trust: 'user' },
]
}
// Resolve default id; throws if no root supplies it
this.defaultId = await this.resolve(config.default)
}
内部维护了两个关键数据结构:
🔹 standing Map:缓存每个 Preset 的 StandingMount(共享的挂载实例),使用 Promise 实现单飞模式(single-flight)——同一 Preset 的并发挂载只执行一次。
🔹 bindings Map:记录每个 Agent 的 Scope 绑定,支持运行时切换 Preset。
4.1 挂载与审计
挂载是 Preset 生命周期中最关键的一步。源码中的 ensureStanding 方法实现了文件时间戳检测——磁盘文件被编辑后自动触发重新挂载:
📄 packages/preset/agent-presets/src/roster.ts(第 491-572 行)
private async ensureStanding(preset: AgentPreset): Promise {
const pending = this.standing.get(preset.id)
if (pending !== undefined) {
const mounted = await pending
// Files are the only composition editor (authoring is copy/delete), so
// the stamp is what notices an edit: a changed file starts the next
// generation here, for this and later sessions.
const current = await compositionStamp(preset.path)
if (current !== mounted.stamp) {
// File was edited on disk. Tear down the old generation and mount
// the new one in its place.
await mounted.fiber.dispose()
const fresh = await this.ensureStanding(preset)
this.standing.set(preset.id, Promise.resolve(fresh))
return fresh
}
return mounted
}
// Single-flight: the first caller mounts, concurrent callers wait.
const mount = (async () => {
const ctx = this.ctx.derive(mountKey(preset.id))
try {
await mountPreset(ctx, preset)
return { presetId: preset.id, stamp: current, key: scopeOf(ctx), fiber: ctx.fiber }
} catch (error) {
await ctx.fiber.dispose()
throw error
}
})()
this.standing.set(preset.id, mount)
return await mount
}
挂载过程调用 mountPreset,执行严格的审计:
📄 packages/preset/agent-presets/src/mount.ts(第 332-381 行)
export async function mountPreset(agentCtx: Context, preset: AgentPreset): Promise {
const scope = scopeOf(agentCtx)
if (scope === undefined) {
throw new Error(
`agent-presets: refusing to mount preset "${preset.id}" into an unscoped context; `
+ 'its registrations would apply to every agent in the process',
)
}
const config: Include.Config = { path: pathToFileURL(preset.path).href }
if (agentCtx.baseUrl !== undefined) harnessBase.set(config, agentCtx.baseUrl)
pruneDisposedMounts()
const handle = agentCtx.plugin(PresetTree, config)
try {
await handle.await()
const subtree = mounted.get(config)
if (subtree === undefined) throw new Error('mounted subtree did not publish its entry tree')
const { tree, fiber } = subtree
const unusable = inactiveRows(tree)
if (unusable.length > 0) {
throw new Error(`${String(unusable.length)} row(s) did not activate:\n${unusable.join('\n')}`)
}
const leaked = leakedServices(agentCtx, fiber)
if (leaked.length > 0) {
throw new Error(
`row(s) published process-global service(s) [${leaked.join(', ')}]; `
+ 'a preset service must sit behind an `isolate` realm or move to the host composition',
)
}
mounts.add({ presetId: preset.id, fiber, key: scopeOf(agentCtx) })
} catch (error) {
try { await handle.dispose() } catch { /* swallow teardown failure */ }
throw new PresetMountError(preset.id, `${mountDetail(error)} (${preset.path})`, { cause: error })
}
}
审计的三道防线:
🔹 Scope 检查:拒绝将 Preset 挂载到无 Scope 的 Context(否则注册会污染全局)
🔹 不可用行检测:inactiveRows() 检查是否有插件行未能激活(缺少依赖服务)
🔹 服务泄漏检测:leakedServices() 检查是否有服务发布到了 ROOT realm(进程全局而非会话隔离)
4.2 子 Agent 继承机制
子 Agent 通过 composeFrom 继承父 Agent 的 Preset 组合。注意注释中的关键说明——这是绑定而非挂载:
📄 packages/preset/agent-presets/src/roster.ts(第 286-325 行)
/**
* One agent joins its parent's standing composition.
*
* This is how a child agent inherits its parent's capabilities. It is a bind,
* not a mount: the parent's generation is already composed, so the child gets
* that exact instance — the same plugin objects, the same tool registrations,
* the same prompt sections. Re-resolving the parent's preset by id instead
* would re-read the roster, and a composition file edited since the parent
* started would hand the child a DIFFERENT generation than the one its
* parent's history was produced under (and a preset deleted since would fail
* the child outright while its parent keeps running).
*/
composeFrom(agentCtx: Context, parentCtx: Context): string | undefined {
const agentKey = scopeOf(agentCtx)
if (agentKey === undefined) {
throw new Error('agent-presets: refusing to compose an unscoped context')
}
const standing = standingMountFor(parentCtx)
if (standing === undefined) return undefined
this.bindings.set(agentKey, bindScopeParent(agentKey, standing.key))
return standing.presetId
}
这段注释非常关键:子 Agent 不重新解析 Preset,而是共享父 Agent 的 StandingMount 实例。这保证了父子 Agent 使用完全相同的插件对象、工具注册和 Prompt 片段,即使 Preset 文件在父 Agent 启动后被修改或删除。
4.3 运行时切换 Preset
recompose 方法支持在 Agent 产生任何输出前切换 Preset:
📄 packages/preset/agent-presets/src/roster.ts(第 458-472 行)
async recompose(agentCtx: Context, id: string): Promise {
const agentKey = scopeOf(agentCtx)
if (agentKey === undefined) {
throw new Error('agent-presets: refusing to recompose an unscoped context')
}
const preset = await this.resolveMountable(id)
const standing = await this.ensureStanding(preset)
const binding = this.bindings.get(agentKey)
if (binding === undefined) {
this.bindings.set(agentKey, bindScopeParent(agentKey, standing.key))
} else {
binding.rebind(standing.key)
}
return preset
}
切换是原子操作:新的 StandingMount 在旧绑定断开前就已就绪,如果新 Preset 不可用,Agent 保持原状(无中间态)。
五、Preset 作者系统
用户创建 Preset 的唯一方式是复制现有 Preset。源码中明确拒绝直接传入组配置文本:
📄 packages/preset/agent-presets/src/authoring.ts(第 136-170 行)
export async function copyComposition(
roots: readonly PresetRoot[],
source: AgentPreset,
id: string,
name?: string,
): Promise {
if (!PRESET_ID.test(id)) throw new InvalidPresetIdError(id)
const dir = join(writableRoot(roots), id)
if (await occupied(dir)) throw new PresetExistsError(id)
try {
await cp(dirname(source.path), dir, {
recursive: true, dereference: true, force: false, errorOnExist: true,
})
await tightenModes(dir)
const rendered = renderPresetMetadata({
...name === undefined ? {} : { name },
...source.description === undefined ? {} : { description: source.description },
})
const metadataPath = join(dir, METADATA_FILE)
if (rendered === undefined) {
await rm(metadataPath, { force: true })
} else {
await writeFileAtomic(metadataPath, rendered, { mode: 0o600, dirMode: 0o700 })
}
} catch (error) {
// A half-copied directory would be invisible to discovery at best and a
// mountable-but-incomplete preset at worst; a failed copy leaves nothing.
await rm(dir, { recursive: true, force: true })
throw error
}
return dir
}
安全设计:
🔹 只读源:系统 Preset 不可修改,只能复制到用户根目录
🔹 权限收紧:tightenModes 递归将复制的目录设为 0o700/0o600(仅 owner 可访问)
🔹 原子写入:writeFileAtomic 避免部分写入
🔹 失败回滚:复制失败时删除不完整目录,不留残留
🔹 符号链接解引用:dereference: true 确保副本自包含
删除操作同样有保护——只允许删除 user 信任级别的 Preset,且路径必须在可写根目录下:
📄 packages/preset/agent-presets/src/authoring.ts(第 182-196 行)
export async function deleteComposition(
roots: readonly PresetRoot[],
preset: AgentPreset,
): Promise {
if (preset.trust !== 'user') {
throw new PresetNotWritableError(preset.id, 'it ships with the deployment')
}
const dir = join(writableRoot(roots), preset.id)
// Belt and braces over the id pattern: the resolved directory must still be
// the one the writable root owns, whatever discovery reported.
if (!isAbsolute(preset.path) || !preset.path.startsWith(dir)) {
throw new PresetNotWritableError(preset.id, 'it does not live under the writable preset root')
}
await rm(dir, { recursive: true, force: true })
}
六、Session 中的 Preset 解析
Session 创建时记录的 Preset 是创建时刻的值(深冻结),但 Session 可能在空白期切换 Preset。恢复 Session 时必须读取事件日志,而非仅看 Header:
📄 packages/preset/agent-presets/src/session.ts(第 48-54 行)
export function resolveSessionPreset(session: PresetBearingSession): string | undefined {
for (let index = session.events.length - 1; index >= 0; index -= 1) {
const event = session.events[index]
if (event?.type === 'agent-preset/selected') return event.data.agentPreset
}
return session.header.agentPreset
}
从后向前扫描事件日志,取最后一个 agent-preset/selected 事件。如果没有切换事件,回退到 Header 中的创建值。这保证了恢复的 Session 重建的是实际运行过的组合,而非创建时的组合。
七、三层组合体系总览
Profile → Bundle → Preset 组合层次
Layer 1 — Bundle(共享层)
🔹 dsh-base:所有 Profile 共享的 ~70 个插件行
🔹 dsh-headless / dsh-tui / dsh-web:模式特定的覆盖
🔹 通过 patch 操作按 ID 修改,后写覆盖先写
🔹 !!js YAML 表达式实现动态配置
Layer 2 — Profile(启动模式)
🔹 --profile headless|tui|web 选择运行模式
🔹 每个 Profile 有自己的 Commander 程序解析参数
🔹 Launcher 不感知 Profile 参数——解耦设计
Layer 3 — Preset(Agent 能力)
🔹 目录 = Preset,agent.cordis.yml = 组配置
🔹 PresetRoster 管理发现、挂载、绑定生命周期
🔹 文件时间戳检测 + 单飞挂载 + 三道审计防线
🔹 子 Agent 共享父 Agent 的 StandingMount(绑定非挂载)
🔹 作者系统:只允许复制,权限收紧,失败回滚
八、设计亮点与启示
🔹 组合优于继承:Preset 不是代码类,而是磁盘目录 + YAML 组配置。能力组合通过 Cordis 插件行声明,而非硬编码。
🔹 热更新感知:文件时间戳检测让 Preset 修改后新 Session 自动使用新版本,无需重启进程。
🔹 安全分层:System/User 信任分级、路径穿越防护、权限收紧、原子写入、失败回滚——作者系统的安全设计堪称教科书级别。
🔹 原子性保证:Preset 切换先确保新挂载就绪再断开旧绑定,无中间态。
🔹 日志一致性:Session 恢复时从事件日志而非 Header 读取 Preset,保证回放忠实于实际运行环境。
📚 系列导航
← 第 1 讲:整体架构与 Cordis 插件框架
→ 第 3 讲:事件系统(Session/Agent/Capability events)
关注公众号「AI技术推荐官」获取更多源码解析内容
夜雨聆风