乐于分享
好东西不私藏

DeepSeek Harness 源码-Profile、Bundle 与 Preset 组合机制

DeepSeek Harness 源码-Profile、Bundle 与 Preset 组合机制

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技术推荐官」获取更多源码解析内容