[DeepSeek Harness深度拆解-02]Profile究竟是什么?

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

DeepSeek Harness深度拆解-01:DSH是如何跑起来的?介绍了三种启动DSH的模式。由于plugin模式会将指定的参数转发 pnpm 包管理器,所以我们一般用这种执行模式来安装或卸载提供插件的包。额外两种执行模式(profiledump-config)都与Profile有关,那么Profile究竟是什么呢?

1. Agent运行和交互方式

DSH官方文档针对Profile的解释是:一种可启动形态。说得具体一点,Profile体现了运行Agent的形式,并决定了它与用户的交互方式,DSH内置了如下五种Profile:

  • web:完整 Web 应用。包含全套核心 Agent 循环,内置 Web 服务、API 端点与图形化 UI 插件,适用于需要可视化界面、前后端交互的完整 Web 平台开发;
  • headless:无头无界面预设。精简掉所有 UI 与前端 Web 服务,专注于纯后端、脚本、自动化工作流与命令行(CLI)的高效 Agent 任务执行;
  • sdk:标准开发套件。提供标准的模块化接口、生命周期扩展与类型定义,方便第三方开发者快速将 DSH 核心能力封装或嵌入至既有的外部系统;
  • sdk-minimal:极简开发套件。彻底剔除非必要插件,仅保留微内核最核心的调度与通信能力,为极端追求效能、体积或需要高度定制的场景提供纯净底座;
  • acp:ACP协议专用预设。严格遵循ACP规范进行配置,内置通讯、合规与权限控制插件,专门用于跨平台 Agent 协同、联盟节点对接或特定企业级协议的部署。

对于web这种Profile我已经很熟悉了,下面演示了采用headless这种Profile以源码的方式启动DSH。如指定的命令所示,我们可以直接将发送给Agent的用户提示词(解释一下DeepSeek Hardness所谓的Profile)作为命令行参数,启动的DSH程序会在输出Agent的响应内容后自动关闭。

E:\dsh\deepseek-harness>npx tsx apps/cli/src/bin.ts  --profile headless "解释一下DeepSeek Hardness所谓的Profile"
npm notice run @deepseek-ai/dsh-root@0.1.5-alpha.2 npx
npm notice run tsx apps/cli/src/bin.ts --profile headless 解释一下DeepSeek Hardness所谓的Profile
在 DeepSeek Harness 里,**Profile** 一般指的是一套“会话/运行时组合配置”,用来决定:

- 启动哪些能力插件
- 使用哪些 provider
- 采用什么默认模型、工具、权限与环境
- 以什么方式拼装成一个可运行的 session 或应用入口

可以把它理解成:**“这次运行的角色模板”**,而不是单个功能点。

## 更具体地说

从仓库结构和注释看,`packages/preset/` 负责“per-session agent composition from preset cordis.yml files”,这通常就是 profile 的核心含义之一:
一个 profile 会把若干插件、配置和策略组合起来,形成特定运行场景。

### Profile 通常包含的内容
- **插件集合**:例如 shell、fs、web、llm、subagent、workflow 等
- **插件配置**:每个能力的 provider、路径、开关、策略
- **默认行为**:默认模型、系统提示、工具可用性
- **运行环境约束**:例如只读/可写、是否允许网络、会话持久化策略
- **场景差异化**:比如 headless、development、demo、CI、review 等不同用途

## 为什么要有 Profile

因为同一个 harness 不同场景需要的能力不一样:

- **开发时**可能要完整工具链
- **只读审阅**可能只要文档和检索
- **自动化任务**可能要更少交互、更严格权限
- **演示/测试**可能要固定配置,便于复现

Profile 的作用就是把这些差异收敛成一份可复用的组合定义。

## 在 DSH 里它和普通“配置文件”的区别

不是简单的 key-value 配置,而更像:

- 一个**可装配的运行配方**
- 一个**能力与策略的打包入口**
- 一个**会影响 session 初始化、工具暴露和行为约束**的高层选择

## 一句话总结

**Profile = 一套用于启动 DeepSeek Harness 的预设组合,决定“这个会话拥有什么能力、怎么运行、默认怎么表现”。**

如果你愿意,我可以继续给你讲:
1. **Profile 在代码里大概是怎么落地的**
2. **profile、preset、session、cordis.yml 的关系**
3. **举一个具体 profile 的例子**

E:\dsh\deepseek-harness>

2. Profile配置文件

不论采用何种Profile,被拉起来的DSH本质上都是一个Cordis程序。在Cordis的世界里,一切皆插件,所以DSH涉及的所有配置和启动时指定的参数最终都会转换成针对插件的配置。由于插件被注册到Cordis的某个Context上,而Context具有一个树形化层次结构,我们也称最终构建的所有插件配置为插件树。由于插件配置的来源具有不同的优先级(比如针对当前主机、当前用户和当前启动的DSH进程),所以提交给Cordis的插件是按照固定顺序对这些配置源实施合并后的产物。对插件配置实施合并的过程类似于对当前构建的插件树打上了一个补丁,所以我们也称插件配置为插件补丁。Profile提供的补丁是最终构建的插件配置的重要组成部分。

针对某个Profile的第一次执行将会在DSH运行时目录(通过环境变量DSH_HOME定义,默认位置为~/.dsh或者%userprofile%.dsh)的profiles目录下生成一个以Profile名称命名的子目录,并在其中创建相应的配置文件。以web这个Profile为例,由于我使用的是Windows,在%userprofile%.dsh/profiles/web目录下将会生成如下四个配置文件:

  • package.json:配置用来提供插件的包,每个包会利用dsh.bundle.patch字段提供补丁文件;
  • cordis.patch.yml:在上述包的基础上进一步提供额外的插件补丁,默认为一个空的数组;
  • cordis.yml:为通过包和补丁构建的插件树提供一个统一的根,默认为一个空的数组;
  • pnpm-workspace.yaml:pnpm 包管理器的多项目(Monorepo)工作区配置文件。

如下所示的是package.json文件的默认内容。dsh.profile节点是该文件的核心。

{
  "name": "dsh-profile-web",
  "private": true,
  "dependencies": {},
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app"
      ],
      "patchReload": "live"
    }
  }
}

该节点利用bundles字段提供了如下两个包:

  • @deepseek-ai/dsh-base:提供DeepSeek-Harness 的基础插件。不论采用何种Profile,插件树合并流程使用的第一层(最底层)必然是 dsh-base,它提供了Agent赖以生存的所有核心能力。它负责让 Agent 能思考、有记忆、能干活、守安全,具体提供的插件包括如下几类:
    • LLM适配:处理与 DeepSeek 等大模型的通信与 Token 流;
    • Agent主循环与执行引擎:比如ReAction循环和工作流;
    • 核心工具箱:提供文件读写(readwriteedit等)、Shell 命令执行、Web 搜索等能力;
    • 基础设施:持久化会话历史、沙箱环境、权限与安全审批策略、诊断跟踪等。
  • @deepseek-ai/dsh-web-app:通过提供的插件把底层的无形内核包装成一个看得见、摸得着的网页工作台。负责开启网络通道并注入 UI 渲染/通信插件,以及提供浏览器交互界面与网络服务,最终一同一种基于WebUI的Agent交互方式。主要提供如下的功能:
    • HTTP & WebSocket 服务器:在本地启动一个Web Server作为前后端通信的桥梁;
    • 前端静态资源托管:向浏览器交付构建好的交互式界面,包含聊天面板、模型管理、任务画布看板、设置中心等;
    • 通信与事件中继:将Agent思考、工具调用等底层事件,通过协议实时推送到前端页面上进行可视化渲染。

patchReload字段定义了重载插件补丁的两种行为:

  • live: 启动一个文件监视器,持续监听该Profile目录下的补丁文件(cordis.patch.yml)和全局的产检补丁( $DSH_HOME/cordis.patch.yml)。当监测到补丁文件被修改时,DSH会重新构建插件树,就是我们所谓的动态热重载特性。web Profile会将它作为默认选项;
  • startup: 将打补丁的动作限制在启动阶段,运行期间针对补丁文件的修改不会对当前运行的DSH程序产生任何影响。headless Profile会将它作为默认选项,如下所示的是该Profile对应package.json文件的定义。
{
  "name": "dsh-profile-headless",
  "private": true,
  "dependencies": {},
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-headless"
      ],
      "patchReload": "startup"
    }
  }
}

3. Profile

Profile在DSH中通过如下这个同名接口定义,我们最好结合上面给出的Profile目录结构来理解定义在该接口中的字段成员。

export interface Profile {
  name: string
  dir: string
  layers: ProfileLayer[]
  patchPath: string
  patches: PatchOptions[]
  patchReload: ProfilePatchReload
}

各字段说明如下:

  • name:Profile的唯一标识符,也就是通过命令行参数--profile的值。
  • dir:Profile在DSH运行时目录中的路径,web Profile在Windows下对应的目录路径为%userprofile%.dsh/profiles/web;
  • layers: 根据定义在Profile对应package.json文件中dsh.profile.bundles字段定义的一组ProfileLayer 列表,每个包(bundle)对应一个ProfileLayer,并且于配置保持相同的顺序,最终会按照整个顺序实施插件补丁的合并;
  • patchPath:该Profile对应的插件补丁文件存储路径,通常指向该Profile目录下的 cordis.patch.yml 文件;
  • patches:解析插件补丁文件得到的一组PatchOptions对象;
  • patchReload: 对应package.json文件的dsh.profile.patchReload字段。

4. ProfileLayer

Profile的layers字段返回一组ProfileLayer对象,ProfileLayer接口定义了如下四个字典。

export interface ProfileLayer {
  packageName: string
  packageDir: string
  patchPath: string
  patches: PatchOptions[]
}

四个字段说明如下:

  • packageName: 提供插件的包名;
  • packageDir:该配置包在本地磁盘上的根目录绝对路径;
  • patchPath:包提供的补丁文件的路径;
  • patches:解析patchPath所在文件生成的代表补丁对象的PatchOptions列表。

同上面的内容可知,webProfile对应package.json文件利用dsh.profile.bundles字段提供了@deepseek-ai/dsh-base@deepseek-ai/dsh-web-app两个包,它们对转换成对应的ProfileLayer对象。这两个包的名称和所在的路径将作为ProfileLayer对象的packageNamepackageDir字段。假设在工作目录E:\dsh\deepseek-harness下执行如下的命令启动DSH:

npx tsx apps/cli/src/bin.ts --profile web

对于生成的Profile对象的两个ProfileLayer对象来说,它们的packageNamepackageDir字段分别为:

  • layers[0]:
    • packageName: @deepseek-ai/dsh-base
    • packageDir:E:\dsh\deepseek-harness\apps\cli\node_modules@deepseek-ai\dsh-base
  • layers[1]:
    • packageName: deepseek-ai/dsh-web-app
    • packageDir:E:\dsh\deepseek-harness\apps\cli\node_modules@deepseek-ai\dsh-web-app

ProfileLayer对象的patchPath来源于包对应package.json文件,以@deepseek-ai/dsh-base这个包为例,其package.json文件利用dsh.bundle.patch字段来提供插件补丁文件。

{
  "name": "@deepseek-ai/dsh-base",
  ...
  "dsh": {
    "bundle": {
      "patch": "./cordis.patch.yml"
    }
  }
  ...
}

补丁文件所在的路径将作为ProfileLayer对象的patchPath字段。对于@deepseek-ai/dsh-base@deepseek-ai/dsh-web-app这两个包对应的ProfileLayer对象来说,它们的patchPath字段分别是:

  • ‘E:\dsh\deepseek-harness\apps\cli\node_modules@deepseek-ai\dsh-base\cordis.patch.yml’
  • ‘E:\dsh\deepseek-harness\apps\cli\node_modules@deepseek-ai\dsh-web-app\cordis.patch.yml’

如下所示的就是@deepseek-ai/dsh-base这个包提供的补丁文件的内容。这个YAML文件批量将一系列提供基础功能插件注入DSH中,可以将唯一节点insert下属的每个条目视为一个插件(实际上可以表示一个插件组),除了设置插件idname(真正的 npm 包/模块名)之外,还可以利用configdisabledinject字段提供插件配置、禁用条件和注入的依赖服务列表。ProfileLayer对象的patches字段返回一组PatchOptions对象正是通过解析这个文件创建而成,每个PatchOptions对应一个插件(组)。

# The dsh-base bundle patch: the shared core of each base-backed 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.

- insert:
    - id: timer
      name: '@deepseek-ai/cordis-plugin-timer'

    # Module reload is opt-in per profile. `patchReload: live` config watching
    # uses the launcher's watch-only fallback and does not require this row.
    - id: hmr
      name: '@deepseek-ai/cordis-plugin-hmr'
      disabled: true
      config:
        root: ['.']

    - id: llm
      name: '@deepseek-ai/dsh-llm'

    - id: deepseek-llm-api-extensions
      name: '@deepseek-ai/dsh-deepseek-llm-api-extensions'

    - id: session
      name: '@deepseek-ai/dsh-session'

    - id: session-log-deepseek
      name: '@deepseek-ai/dsh-session-log-deepseek'

    - id: typert
      name: '@deepseek-ai/dsh-typert-registry'

    - id: typert-loader
      name: '@deepseek-ai/dsh-typert-loader'

    - id: typert-gateway
      name: '@deepseek-ai/dsh-api-gateway'

    - id: session-title
      name: '@deepseek-ai/dsh-session-title'
      config:
        fallbackMaxWords: 5
        fallbackMaxBytes: 40
        maxTitleBytes: 80

    - id: session-title-llm
      name: '@deepseek-ai/dsh-session-title-first-prompt-llm'
      config:
        targetWords: 5
        targetCjkCharacters: 10
        maxInputBytes: 4096
        maxOutputTokens: 64
        timeoutMs: 60000

    - id: user-questions
      name: '@deepseek-ai/dsh-user-questions'

    - id: agent
      name: '@deepseek-ai/dsh-agent'

    - id: plugin-package-inventory-deepseek
      name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'

    # The transport-independent default for Agents created by entry points.
    # Settings may supply a saved selection; consumers read it at creation time.
    - id: agent-default-model
      name: '@deepseek-ai/dsh-agent-default-model'
      config:
        provider: deepseek-official
        model: deepseek-v4-flash

    - id: jobs
      name: '@deepseek-ai/dsh-jobs-local'

    - id: llm-retry
      name: '@deepseek-ai/dsh-llm-retry'

    # User-settings document (`$DSH_HOME/settings.yaml`, hot-reloaded): a
    # `llm-deepseek:` or `llm-pi-ai:` section there overrides the adapter entries
    # below without a restart, and is what the web Models page writes.
    - id: settings
      name: '@deepseek-ai/dsh-settings-file'

    # Credential sources: inherited environment over the managed
    # `$DSH_HOME/.credentials.yaml`, with project and user `.env` fallbacks.
    # Adapters resolve references per request; the Models page writes only the
    # managed document, which is never materialized into the process environment.
    - id: credentials
      name: '@deepseek-ai/dsh-credentials-local'

    # The pi-ai multi-provider twin, mounted dormant: zero routes (and no extra
    # models in the picker) until a `llm-pi-ai:` settings section supplies provider
    # profiles — then those routes register live, keys resolving per request
    # through their apiKeyEnv references, and drop again when the section empties.
    # Supplying those profiles is exactly what the web Models page does. Which
    # adapters exist is composition; which providers run is the user's settings
    # document.
    - id: llm-pi-ai
      name: '@deepseek-ai/dsh-llm-pi-ai'

    - id: session-persistence-jsonl
      name: '@deepseek-ai/dsh-session-persistence-jsonl'
      config:
        root: !!js dshHomePath('sessions')

    # Durable image bytes live outside the append-only session log. Messages
    # keep content-addressed references that this shared backend resolves for
    # provider requests and authorized history reads.
    - id: attachment-local
      name: '@deepseek-ai/dsh-attachment-local'

    # Full-text session search is opt-in. `openAt: never` keeps
    # ctx.sessionQuery mounted — exact reads, titles, and lineage traces
    # (session export, subagent-fork Workspace inheritance) stay available —
    # while search calls fail with SESSION_QUERY_SEARCH_DISABLED and SQLite is
    # never opened; the Web sidebar search matches titles and workspace names
    # only. Deployments enabling content search override `openAt` to
    # `first-search` or `startup` in a later patch layer (profile
    # cordis.patch.yml or a --patch overlay), typically with a durable `path`.
    - id: session-query-sqlite
      name: '@deepseek-ai/dsh-session-query-sqlite'
      config:
        path: ':memory:'
        openAt: never

    # Shared projection registry: subagent catalog identity (mode/label) folds
    # through its registered units, so the `list_agents` surface below fails
    # loud without it; web layers reuse this same mount for list rows.
    - id: session-projection
      name: '@deepseek-ai/dsh-session-projection'

    # Durable KV storage: the storage hub, the json backend, and the
    # schema-validated domain form over them. Session-layer persistence (the
    # projection cache below; workspace in web layers)
    # routes through this stack, so it belongs to the shared base.
    - id: storage
      name: '@deepseek-ai/dsh-storage'

    - id: storage-json
      name: '@deepseek-ai/dsh-storage-json'
      config:
        root: !!js dshHomePath('storages')

    - id: storage-domain
      name: '@deepseek-ai/dsh-storage-domain'
      config:
        backend: json

    # Persisted projection cache: throttled write-behind over the
    # session_projcache domain (per-record layout — one version-stamped
    # checkpoint document per session), serving the session listing's
    # projection column.
    - id: session-projection-cache
      name: '@deepseek-ai/dsh-session-projection-cache'
      config:
        writeEveryEvents: 200
        writeIntervalMs: 5000

    # OTel releases a Session-log prefix only after explicit user feedback,
    # regardless of model provider. Ordinary activity never triggers capture.
    # DSH_TELEMETRY_OTLP_URL overrides the production endpoint. A non-empty
    # DSH_TELEMETRY_DISABLED — any value, including '0'/'false' — opts the
    # process out (the launchers patch the row disabled; config cannot disable
    # a row). Exports carry the harness home's anonymous user id ($DSH_HOME/.anonymous-user-id,
    # random UUID; delete the file to reset the identity) as the Resource's
    # user.id. The exporter/processor values normally bound the shutdown drain
    # to ~1s against an unreachable collector: exporter.timeoutMillis is both
    # the per-attempt socket timeout and the retry deadline (1s effectively
    # disables the SDK's 5-try backoff), while maxExportBatchSize == maxQueueSize
    # (both explicit) makes the drain a single batch. The SDK awaits
    # exporter.forceFlush() outside exportTimeoutMillis, so the backend's 3s
    # shutdownTimeoutMillis is the load-bearing outer bound when a transport
    # promise never settles. Every CLI exit path drains it by disposing the root
    # on SIGINT/SIGTERM.
    - id: session-telemetry-otel
      name: '@deepseek-ai/dsh-session-telemetry-otel'
      config:
        mode: !!js process.env.DSH_TELEMETRY_MODE || 'FEEDBACK_ONLY'
        shutdownTimeoutMillis: 3000
        exporter:
          url: !!js process.env.DSH_TELEMETRY_OTLP_URL ?? 'https://harness-telemetry.deepseeksvc.com/v1/logs'
          compression: gzip
          timeoutMillis: 1000
        processor:
          scheduledDelayMillis: 10000
          maxQueueSize: 2048
          maxExportBatchSize: 2048
          exportTimeoutMillis: 1500

    - id: subprocess
      name: '@deepseek-ai/dsh-subprocess-local'

    # Every shipped CLI mode starts with the same file-effect boundary.
    # The environment remains an explicit deployment override; otherwise fresh
    # sessions pin workspace-write + ask through the permission service below.
    - 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: bash-sandbox
      name: '@deepseek-ai/dsh-bash-sandbox'
      disabled: !!js process.platform === 'win32'
      config:
        timeoutMs: 60000

    - id: pwsh-sandbox
      name: '@deepseek-ai/dsh-pwsh-sandbox'
      disabled: !!js process.platform !== 'win32'

    - id: approval
      name: '@deepseek-ai/dsh-user-approval'
      config:
        policy: !!js "(process.env.DSH_PERMISSION_MODE ?? 'workspace-write') === 'danger-full-access' ? 'never' : 'ask'"

    - id: permission
      name: '@deepseek-ai/dsh-permission-presets'
      config:
        presets:
          read-only:
            sandbox: read-only
            approval: ask
          workspace-write:
            sandbox: workspace-write
            approval: ask
          danger-full-access:
            sandbox: danger-full-access
            approval: never

    - id: shell-env
      name: '@deepseek-ai/dsh-shell-env'

    - id: tool-bash
      name: '@deepseek-ai/dsh-tool-bash'
      disabled: !!js process.platform === 'win32'

    - id: tool-pwsh
      name: '@deepseek-ai/dsh-tool-pwsh'
      disabled: !!js process.platform !== 'win32'

    - id: tool-jobs
      name: '@deepseek-ai/dsh-tool-jobs'

    - id: fs-observation-policy
      name: '@deepseek-ai/dsh-fs-observation-policy'

    - id: tool-fs
      name: '@deepseek-ai/dsh-tool-fs'

    - id: tool-fs-search
      name: '@deepseek-ai/dsh-tool-fs-search'
      config:
        sampleOverCapGlobResults: false

    - id: agent-instructions
      name: '@deepseek-ai/dsh-agent-instructions'
      config:
        maxBytes: 65536

    - id: skill
      name: '@deepseek-ai/dsh-skill'

    - id: skill-filesystem
      name: '@deepseek-ai/dsh-skill-filesystem'

    - id: skill-badge
      name: '@deepseek-ai/dsh-skill-badge'
      disabled: true

    - id: tool-skill
      name: '@deepseek-ai/dsh-tool-skill'

    - id: commands
      name: '@deepseek-ai/dsh-commands'

    - id: command-feedback
      name: '@deepseek-ai/dsh-command-feedback'

    - id: goal
      name: '@deepseek-ai/dsh-goal'

    - id: goal-round-driver
      name: '@deepseek-ai/dsh-goal-round-driver'

    - id: command-goal
      name: '@deepseek-ai/dsh-command-goal'

    - id: plan-mode
      name: '@deepseek-ai/dsh-plan-mode'
      config:
        section: |
              You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.

              Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.

              The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed only to keep the request shape stable. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.

              Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.

              Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.

              When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.

    - id: token-meter
      name: '@deepseek-ai/dsh-token-meter'

    - id: compaction-basic
      name: '@deepseek-ai/dsh-compaction-basic'

    # Human `/compact`: one useful reduction below the automatic threshold. Backend
    # independent, so it follows whichever compaction service this leaf mounts.
    - id: command-compact
      name: '@deepseek-ai/dsh-command-compact'

    - id: subagent
      name: '@deepseek-ai/dsh-subagent'

    - id: subagent-spawn-in-process
      name: '@deepseek-ai/dsh-subagent-spawn-in-process'
      config:
        providerName: spawn

    - id: subagent-fork-in-process
      name: '@deepseek-ai/dsh-subagent-fork-in-process'
      config:
        providerName: fork

    # Continuable background children are selected per delegation tool. The
    # separately loaded follow-up tool registers the one global `send_message`.
    - id: tool-subagent-control
      name: '@deepseek-ai/dsh-tool-subagent-control'

    - id: tool-subagent-list-agents
      name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'

    - id: tool-subagent
      name: '@deepseek-ai/dsh-tool-subagent'
      config:
        provider: spawn
        toolName: subagent
        backgroundMode: continuable

    # Fork omits model selection so provider/model stay equal to the parent and
    # the inherited history remains eligible for KV Cache reuse. This base row
    # stays one-shot; preset layers may select continuable mode without adding a
    # child-only system-prompt section or tool schema ahead of that history.
    # See .agents/notes/implemented/feature/2026-08-18-model-selected-subagent-routes.md
    # and .agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.md.
    - id: tool-subagent-fork
      name: '@deepseek-ai/dsh-tool-subagent'
      config:
        provider: fork
        toolName: subagent_fork
        backgroundMode: one-shot

    - id: workflow-worker-thread
      name: '@deepseek-ai/dsh-workflow-worker-thread'
      config:
        provider: spawn

    - id: tool-workflow
      name: '@deepseek-ai/dsh-tool-workflow'

    - id: timeout-policy
      name: '@deepseek-ai/dsh-tool-call-timeout-policy'

    - id: spill-local
      name: '@deepseek-ai/dsh-spill-local'

    - id: spill-policy
      name: '@deepseek-ai/dsh-spill-policy'
      config:
        maxInlineBytes: 50000

    # Durability checkpoints before each model request and top-level dispatch.
    - id: session-checkpoint-policy
      name: '@deepseek-ai/dsh-session-checkpoint-policy'

    # Compacts oversized tool results before the broader conversation compactor
    # runs, preserving the model-visible result within the configured budget.
    - id: tool-result-pruner
      name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
      config:
        thresholdChars: 8192
        headChars: 4096
        tailChars: 1024

    - id: tool-todo
      name: '@deepseek-ai/dsh-tool-todo'
      config:
        allowParallelInProgress: true

    # Persisted same-session goals reach the model and the slash menu here; the
    # domain, driver, and `/goal` command are above.
    - id: tool-goal
      name: '@deepseek-ai/dsh-tool-goal'

    # Fresh-agent Ralph iteration over a build-time-fixed script.
    - id: tool-ralph
      name: '@deepseek-ai/dsh-tool-ralph'
      config:
        subagentProvider: spawn
        maxRounds: 64

    # Consecutive-repeat reminders on the tool chain.
    - id: repeat-tool-reminder
      name: '@deepseek-ai/dsh-repeat-tool-reminder'
      config:
        thresholds: [3, 5, 8]
        argumentsPreviewChars: 500

    # The shared base enables the stable model-facing web_search and web_fetch
    # tools. The Web app disables this host row and composes both tools per agent
    # preset; products with a stricter network policy override tool-web. DeepSeek
    # search resolves the same DEEPSEEK_API_KEY
    # credential the Models page manages for chat, at each search; its Messages
    # endpoint is separate from the chat-completions endpoint, so it takes its own
    # base-URL override. Anonymous fetch accepts only public HTTP(S) destinations,
    # resolves and validates every destination, and pins every actual connection.
    # Search is a full auxiliary model request with server-side retrieval, so this
    # shipped DeepSeek route gets 60s while the provider-neutral tool default
    # remains 30s.
    - id: web
      name: '@deepseek-ai/dsh-web'
      config:
        searchProvider: deepseek-official
        fetchProvider: http

    - id: web-search-deepseek
      name: '@deepseek-ai/dsh-web-search-deepseek'
      config:
        apiKeyEnv: DEEPSEEK_API_KEY

    - id: web-fetch-http
      name: '@deepseek-ai/dsh-web-fetch-http'

    - id: tool-web
      name: '@deepseek-ai/dsh-tool-web'
      config:
        fetch: true
        searchTimeoutMs: 60000

    # ── rows every mode mounts, whose values each overlay may state ──────────────

    # The tool registry. Presentation mode is a deployment choice; omitting it here
    # keeps the schema default (native).
    - id: tools
      name: '@deepseek-ai/dsh-tools'

    # The deployment persona is a deployment choice; plan-mode and tool plugins own
    # their own prompt sections.
    - id: system-prompt
      name: '@deepseek-ai/dsh-system-prompt'
      config:
        personaPrefix: ''

    # Agents created at startup. The base stays empty; raw overlays may create
    # agents, while Web creates sessions on client request.
    - id: agent-loop
      name: '@deepseek-ai/dsh-agent-loop'
      config:
        agents: []

    # The sandboxed filesystem provider. `cwd` defaults to `process.cwd()`; an
    # overlay can pin another workspace.
    - id: fs-sandbox
      name: '@deepseek-ai/dsh-fs-sandbox'

    # The native DeepSeek adapter. No key or endpoint is inlined: both resolve per
    # request from the `llm-deepseek:` settings section over this entry, with the
    # key coming from the credential store below. Thinking defaults are a deployment
    # choice.
    - id: llm-deepseek
      name: '@deepseek-ai/dsh-llm-deepseek'

5. PatchOptions & EntryOptions

对于一个Profile对象来说,其layers字段提供的每个ProfileLayer对象会利用patches字段返回一组PatchOptions对象。其的patches字段同样返回一组PatchOptions对象。每个PatchOptions对象对应一个插件或者一个作为插件容器的插件组。由于插件是Cordis的概念,如果你了解Corids(可以参阅我的系列文章DeepSeek Harness插件内核),可以从PatchOptions接口定义看到很多属性的字段。

export interface PatchOptions {
  id?: string
  insert?: EntryOptions[]
  name?: string
  config?: any
  group?: boolean | null
  disabled?: boolean | null
  inject?: any
  intercept?: any
  isolate?: any
  [key: string]: any
}

PatchOptions的字段都是可选的,表达的含义如下:

  • id:插件或者插件组的标识,在插件配置合并是用来确定具体的插件实例;
  • insert:对应cordis.patch.yml中利用insert插入一组表示插件(组)的EntryOptions对象;
  • name:插件的名称;
  • config:插件的配置;
  • group:确定代表一个插件还是一个插件组;
  • disabled:是否禁用;
  • inject:注入插件(组)中的依赖服务的名称列表;
  • intercept:为依赖服务动态指定的配置;
  • isolate:为插件(组)指定的隔离域;

作为插件补丁文件的cordis.patch.yml可以利用insert插入多个插件(组),这组插入的插件(组)对应PatchOptionsinsert字段返回的一组EntryOptions对象。如下面的代码所示,EntryOptions接口的每个字段在PatchOptions接口中都有对应的定义。

export interface EntryOptions {
  id: string
  name: string
  config?: any
  group?: boolean | null
  disabled?: boolean | null
  inject?: Inject | null
}

由于PatchOptionsEntryOptions都有可以同时表示单个插件和一个作为插件容器的插件组,一个PatchOptions又可以利用insert字段包含一组EntryOptions对象。对于表示插件组的PatchOptionsEntryOptions对象来说,包含其中的插件(组)列表则作为它的配置(对应config字段)。DSH正是利用这种可组合的数据结构来构建具有层次结构的插件树,这棵树应用到Cordis上正好与Context树对应起来。

6. Profile模板

从上面针对Profile的解释可知,一个Profile的核心是一组用来提供插件的包和patchReload配置的组合,这个组合可以被视为Profile的模板。Profile模板通过如下这个ProfileTemplate接口表示。

export interface ProfileTemplate {
  bundles: readonly string[]
  patchReload: ProfilePatchReload
}

开篇介绍的五种Profile对应的模板定义在如下这个可导出的PROFILE_TEMPLATES常量中,它和ProfileTemplate接口都定义在@deepseek-ai/dsh-app-boot包中。可以看出,除了sdk-minimal提供单独的@deepseek-ai/dsh-sdk-minimal包外,其余四种Profile都会使用@deepseek-ai/dsh-base作为基础包。

export const PROFILE_TEMPLATES: Record<string, ProfileTemplate> = {
  acp: {
    bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-acp-app'],
    patchReload: 'startup',
  },
  web: {
    bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'],
    patchReload: 'live',
  },
  headless: {
    bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-headless'],
    patchReload: 'startup',
  },
  sdk: {
    bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-sdk-app'],
    patchReload: 'startup',
  },
  'sdk-minimal': {
    bundles: ['@deepseek-ai/dsh-sdk-minimal'],
    patchReload: 'startup',
  },
}

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值