Răsfoiți Sursa

Merge remote-tracking branch 'origin/master' into dshw/pr-deepseek-harness-deepseek-harness-2781

_Kerman 1 lună în urmă
părinte
comite
e5a41d164b
40 a modificat fișierele cu 113 adăugiri și 335 ștergeri
  1. 2 2
      .agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.i18n.yaml
  2. 1 1
      .agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.md
  3. 1 1
      .agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.zh.md
  4. 2 2
      .agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.i18n.yaml
  5. 2 2
      .agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.md
  6. 2 2
      .agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.zh.md
  7. 2 2
      .agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.i18n.yaml
  8. 1 1
      .agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md
  9. 1 1
      .agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md
  10. 2 2
      apps/cli/reference/README.i18n.yaml
  11. 1 1
      apps/cli/reference/README.md
  12. 1 1
      apps/cli/reference/README.zh.md
  13. 1 1
      apps/cli/src/bin.ts
  14. 2 3
      apps/cli/src/dump-config.ts
  15. 11 11
      apps/cli/src/profile-boot.ts
  16. 1 0
      apps/cli/tests/built-bin.e2e.ts
  17. 2 13
      apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts
  18. 2 2
      docs/config-catalog.i18n.yaml
  19. 3 14
      docs/config-catalog.md
  20. 3 14
      docs/config-catalog.zh.md
  21. 2 2
      packages/core/system-prompt/README.i18n.yaml
  22. 0 1
      packages/core/system-prompt/README.md
  23. 0 1
      packages/core/system-prompt/README.zh.md
  24. 0 4
      packages/core/system-prompt/src/index.ts
  25. 0 15
      packages/core/system-prompt/tests/system-prompt.spec.ts
  26. 2 2
      packages/examples/agent-spine-demo/README.i18n.yaml
  27. 2 2
      packages/examples/agent-spine-demo/README.md
  28. 2 2
      packages/examples/agent-spine-demo/README.zh.md
  29. 3 7
      packages/examples/agent-spine-demo/src/index.ts
  30. 1 5
      packages/examples/agent-spine-demo/tests/agent-core.spec.ts
  31. 2 2
      packages/sdk/server/README.i18n.yaml
  32. 2 2
      packages/sdk/server/README.md
  33. 2 2
      packages/sdk/server/README.zh.md
  34. 0 13
      packages/sdk/server/src/index.ts
  35. 0 7
      packages/sdk/server/src/server.ts
  36. 0 48
      packages/sdk/server/tests/plugin-apply.spec.ts
  37. 0 41
      packages/sdk/server/tests/server.spec.ts
  38. 1 2
      python/sdk/src/deepseek_harness/client.py
  39. 18 23
      python/sdk/tests/test_client.py
  40. 33 78
      scripts/smoke-python-runtime.py

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.md
-2026-08-23-python-sdk-dsh-profile-runtime.md: dcb4f77048d516f0e187b611dc09c224fba47b58
-2026-08-23-python-sdk-dsh-profile-runtime.zh.md: 505b49506ae4c4be4809d588448b2c593d3ebaf5
+2026-08-23-python-sdk-dsh-profile-runtime.md: 4af7812db6818b65c754a43ec1a7f973d1cbcbf9
+2026-08-23-python-sdk-dsh-profile-runtime.zh.md: 155e7d2ae0b0ba3e4163dd85a31de90bef9d588a

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.md

@@ -24,7 +24,7 @@ Every Python launch requires either explicit `dsh_home` or a non-empty `DSH_HOME
 
 Persistent SDK customization uses the same profile interfaces as direct CLI use. `dsh plugin --profile <name> ...` manages external dependencies and bundle order, `$DSH_HOME/profiles/<name>/cordis.patch.yml` owns persistent row changes, the home patch applies machine-local changes across profiles, and Python `patches` supplies invocation-specific overlays. A selected profile is valid only when it retains an SDK server row. Missing profiles, bundles, server rows, and invalid patches fail without a complete-config fallback; a profile that remains alive without serving JSON-RPC fails the independently bounded initialization handshake with a diagnostic naming that profile.
 
-The [standalone sdk-minimal profile](2026-08-24-standalone-sdk-minimal-profile.md) lists one repository-owned bundle that inserts its complete explicit tree without `dsh-base`. Its persistent Bash and string-replace editor are present by composition rather than a server filter; dynamic runtime context, workspace instructions, settings, managed credentials, telemetry, compaction, and every other base row are absent. The same runtime still packages the full `sdk` and `web` profiles as separate choices.
+The [standalone sdk-minimal profile](2026-08-24-standalone-sdk-minimal-profile.md) lists one repository-owned bundle that inserts its complete explicit tree without `dsh-base`. Its persistent Bash and string-replace editor are present by composition; the shared JSON-RPC server exposes no root-agent tool filter. Dynamic runtime context, workspace instructions, settings, managed credentials, telemetry, compaction, and every other base row are absent. The same runtime still packages the full `sdk` and `web` profiles as separate choices.
 
 The runtime wheel installs a `dsh` console command. Ordinary profile and SDK execution remains Node-free; external package management requires a caller-installed `pnpm`.
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.zh.md

@@ -24,7 +24,7 @@ Python SDK 分发一个私有 Node 应用,直接启动完整外部 `cordis.yml
 
 持久 SDK 自定义使用与直接 CLI 相同的 profile 接口。`dsh plugin --profile <name> ...` 管理外部依赖与 bundle 顺序,`$DSH_HOME/profiles/<name>/cordis.patch.yml` 负责持久配置项变更,home patch 对所有 profile 应用机器本地变更,Python `patches` 则提供单次启动 overlay。所选 profile 只有保留 SDK server 配置项时才有效。缺失 profile、bundle、server 配置项或非法 patch 都会直接失败,不存在完整配置回退;保持运行却不提供 JSON-RPC 服务的 profile 会在独立有界的初始化握手中失败,诊断会指明该 profile。
 
-[独立 sdk-minimal profile](2026-08-24-standalone-sdk-minimal-profile.zh.md)只列出一个仓库自有组合包,该组合包会插入不含 `dsh-base` 的完整显式配置树。持久 Bash 与字符串替换 editor 通过组合存在,而不是通过 server 筛选;动态运行时上下文、workspace 指令、settings、托管凭据、遥测、compaction 与其他所有 base 配置项均不存在。同一运行时仍会把完整 `sdk` 与 `web` profile 作为独立选择打包。
+[独立 sdk-minimal profile](2026-08-24-standalone-sdk-minimal-profile.zh.md)只列出一个仓库自有组合包,该组合包会插入不含 `dsh-base` 的完整显式配置树。持久 Bash 与字符串替换 editor 通过组合存在;共享 JSON-RPC server 不暴露根 agent 工具筛选器。动态运行时上下文、workspace 指令、settings、托管凭据、遥测、compaction 与其他所有 base 配置项均不存在。同一运行时仍会把完整 `sdk` 与 `web` profile 作为独立选择打包。
 
 运行时 wheel 安装 `dsh` 控制台命令。普通 profile 与 SDK 运行仍不需要 Node;外部包管理要求调用方自行安装 `pnpm`。
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.md
-2026-08-24-standalone-sdk-minimal-profile.md: 692bf9763f4ad710ff5cc819a7480b2f3e8d9b5f
-2026-08-24-standalone-sdk-minimal-profile.zh.md: f39baad1b4429b73f13d601716dadd8376c71bfb
+2026-08-24-standalone-sdk-minimal-profile.md: 9c8afbaaf1a8e522af119c1d42ca5ad714eaa879
+2026-08-24-standalone-sdk-minimal-profile.zh.md: b1cd2a348c4b77f197e30a658c13691b2f35d26e

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.md

@@ -40,11 +40,11 @@ It also supersedes the minimal-overlay realization in [Python SDK runtime throug
 
 ## Verification
 
-The bundle test pins the exact row and dependency roster. Profile-template and config-dump tests pin the one-bundle manifest, startup-only lifecycle, absence of `dsh-base`, and absence of module HMR. The keyless Python example test boots the real `dsh --profile sdk-minimal` process and asserts the generated manifest, complete system prompt, and two advertised tools. The installed-wheel minimal scenario exercises persistent shell state, editor effects, JSONL persistence, and the committed model-visible snapshot through the packaged executable.
+The bundle test pins the exact row and dependency roster. Profile-template and config-dump tests pin the one-bundle manifest, startup-only lifecycle, absence of `dsh-base`, and absence of module HMR. The keyless source test boots the real `dsh --profile sdk-minimal` process, completes a turn, and asserts the generated manifest. The installed-wheel minimal scenario owns the complete system prompt and two advertised tools in its committed model-visible snapshot while exercising persistent shell state, editor effects, and JSONL persistence through the packaged executable.
 
 ## Alternatives considered
 
-**Keep the minimal mode as an overlay on `sdk`.** Rejected because filtering model-visible tools does not remove base services, prompt contributors, persistence choices, or later runtime behavior. It also makes the minimal application depend on controls in shared SDK server and system-prompt interfaces.
+**Keep the minimal mode as an overlay on `sdk`.** Rejected because filtering model-visible tools does not remove base services, prompt contributors, persistence choices, or later runtime behavior. It also required root-tool filtering in the shared SDK server and a complete-persona shortcut in the system-prompt config; neither shared interface carries those composition controls.
 
 **Restore a Python `cordis` argument or environment-selected complete config.** Rejected because it recreates a Python-owned application composition and bypasses profile plugin management and launcher lifecycle.
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-24-standalone-sdk-minimal-profile.zh.md

@@ -40,11 +40,11 @@ Python 运行时继续打包 `dsh-web-app` 与前端产物。`dsh web` 会从已
 
 ## 验证
 
-组合包测试固定确切配置项与依赖清单。Profile 模板与配置 dump 测试固定单组合包 manifest、仅启动时生命周期、`dsh-base` 缺席与模块 HMR 缺席。Keyless Python 示例测试启动真实 `dsh --profile sdk-minimal` 进程,并断言生成的 manifest、完整系统提示词与两个对外公布的工具。Installed-wheel 极简场景通过打包可执行程序验证持久 shell 状态、editor 文件效果、JSONL 持久化与已提交的模型可见快照。
+组合包测试固定确切配置项与依赖清单。Profile 模板与配置 dump 测试固定单组合包 manifest、仅启动时生命周期、`dsh-base` 缺席与模块 HMR 缺席。Keyless 源码测试启动真实 `dsh --profile sdk-minimal` 进程、完成一个回合并断言生成的 manifest。Installed-wheel 极简场景通过已提交的模型可见快照固定完整系统提示词和两个对外公布的工具,同时经由打包可执行程序验证持久 shell 状态、editor 文件效果与 JSONL 持久化。
 
 ## 考虑过的替代方案
 
-**继续把极简模式作为 `sdk` 上的 overlay。** 否决:筛选面向模型的工具不会移除 base 服务、提示词贡献方、持久化选择或后续运行时行为,还会让极简应用依赖共享 SDK server 与系统提示词接口中的控制项。
+**继续把极简模式作为 `sdk` 上的 overlay。** 否决:筛选面向模型的工具不会移除 base 服务、提示词贡献方、持久化选择或后续运行时行为。该方案还要求共享 SDK server 提供根工具筛选,并要求 system-prompt 配置提供 complete-persona 快捷项;这两个共享接口均不再携带这些组合控制项。
 
 **恢复 Python `cordis` 参数或由环境选择的完整配置。** 否决:这会重新创建 Python 自有应用组合,并绕过 profile 插件管理与 launcher 生命周期。
 

+ 2 - 2
.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md
-2026-08-11-minimal-profiles-bare-two-tool-runtime.md: d201a7557136822d82950466e39ae0efd27a5888
-2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md: b56f5cea26bb9305ad75a10d153b64f11f841e96
+2026-08-11-minimal-profiles-bare-two-tool-runtime.md: ffe8e799e1ba5a71939e3a17d484afd209b4014f
+2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md: 587ba696382575244a75f23fe69c14252caf7544

+ 1 - 1
.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md

@@ -22,7 +22,7 @@ The standalone [`@deepseek-ai/dsh-sdk-minimal` bundle](../../../../packages/bund
 
 The Web replay boots the complete Web host, creates the agent through the preset service, and asserts that the scoped filesystem is bare, no scoped compaction service exists, no system-prompt-owned runtime-context message was appended, and the assembled request contains exactly the fixed prompt and two tools. It then executes persistent Bash and the editor against the real scoped services.
 
-The SDK keyless process test boots real `dsh --profile sdk-minimal`, injects an environment-selected prompt, and asserts the generated one-bundle manifest, assembled prompt, exact two-tool catalog, and absence of every system-prompt-owned runtime-context message. Python SDK bundled-runtime coverage initializes the standalone profile through each available packaged carrier with environment-selected model, model capacity, and prompt values, then executes the selected persistent shell and editor. Cordis validation checks that both configurations resolve their declared plugins and configuration fields.
+The SDK keyless source test boots real `dsh --profile sdk-minimal`, completes a turn with an environment-selected prompt, and asserts the generated one-bundle manifest. The Python SDK bundled-runtime snapshot owns the assembled prompt, exact two-tool catalog, and absence of every system-prompt-owned runtime-context message. Packaged-runtime coverage initializes the standalone profile through each available carrier with environment-selected model, model capacity, and prompt values, then executes the selected persistent shell and editor. Cordis validation checks that both configurations resolve their declared plugins and configuration fields.
 
 ## Alternatives considered
 

+ 1 - 1
.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md

@@ -22,7 +22,7 @@ Web `minimal` preset 与独立 JSON-RPC minimal 组合对外提供持久 `bash`
 
 Web 回放会启动完整 Web 宿主,通过 preset 服务创建 agent,并断言作用域文件系统为裸后端、不存在作用域压缩服务、没有追加 system-prompt 拥有的 runtime-context 消息,而且组装请求只包含固定提示词与两个工具。随后,它通过真实作用域服务执行持久 Bash 和编辑器。
 
-SDK keyless 进程测试启动真实 `dsh --profile sdk-minimal`,注入由环境选择的提示词,并断言生成的单组合包 manifest、组装提示词、精确双工具目录,以及不存在任何 system-prompt 拥有的 runtime-context 消息。Python SDK 内置运行时覆盖会通过每种可用的打包载体,使用环境选择的模型、模型容量和提示词值初始化独立 profile,然后执行所选持久 shell 与 editor。Cordis 校验会检查两份配置能否解析声明的插件和配置字段。
+SDK keyless 源码测试启动真实 `dsh --profile sdk-minimal`,使用环境选择的提示词完成一个回合,并断言生成的单组合包 manifest。Python SDK 打包运行时快照固定组装提示词、精确双工具目录,并固定不存在任何 system-prompt 所拥有的 runtime-context 消息。打包运行时覆盖会通过每种可用载体,使用环境选择的模型、模型容量和提示词值初始化独立 profile,然后执行所选持久 shell 与 editor。Cordis 校验会检查两份配置能否解析声明的插件和配置字段。
 
 ## 考虑过的替代方案
 

+ 2 - 2
apps/cli/reference/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write apps/cli/reference/README.md
-README.md: 33de399dc4b2b8e67ed24ed046fcc0da2ff0a7ac
-README.zh.md: 0f20245f318e31159780d29cca955fc85a5b5481
+README.md: eb33816b3ac859e8173e62635f74abbe13fb8462
+README.zh.md: ed84927aa4f211926fd32750385268dc5153b3a9

+ 1 - 1
apps/cli/reference/README.md

@@ -39,7 +39,7 @@ dsh --profile web --dump-default-config
 dsh --profile web --patch ./extra.yml --dump-config
 ```
 
-`--dump-default-config` prints only the bundle layers; `--dump-config` adds the profile's `cordis.patch.yml`, the home-level `$DSH_HOME/cordis.patch.yml`, and `--patch` overlays. Both print comments naming the file that supplied each row and every overlay that changed it; `!!js` expressions remain unevaluated, relative plugin names in inserted rows resolve beside their patch file, and unmatched patch targets are reported on stderr. A dump never runs app command-line providers, so it shows the composed tree before any app argument is resolved and rejects an invocation that carries app arguments.
+`--dump-default-config` prints only the bundle layers; `--dump-config` adds the profile's `cordis.patch.yml`, the home-level `$DSH_HOME/cordis.patch.yml`, and `--patch` overlays. Both print comments naming the file that supplied each row and every overlay that changed it; `!!js` expressions remain unevaluated, relative plugin names in inserted rows resolve beside their patch file, and unmatched patch targets are reported on stderr. A dump initializes missing profile files but does not prepare the runtime module fallback under `$DSH_HOME/profiles/node_modules`. It never runs app command-line providers, so it shows the composed tree before any app argument is resolved and rejects an invocation that carries app arguments.
 
 ## Plugin management
 

+ 1 - 1
apps/cli/reference/README.zh.md

@@ -39,7 +39,7 @@ dsh --profile web --dump-default-config
 dsh --profile web --patch ./extra.yml --dump-config
 ```
 
-`--dump-default-config` 只打印组合包各层;`--dump-config` 额外加上 profile 的 `cordis.patch.yml`、home 级的 `$DSH_HOME/cordis.patch.yml` 和 `--patch` overlay。两者都会打印注释,标明每行由哪个文件提供,以及哪些 overlay 修改过它;`!!js` 表达式保持未求值,插入行中的相对插件名以各自 patch 文件所在目录解析,找不到目标的 patch 会报告到 stderr。dump 操作不会运行应用的命令行参数提供方,因此展示的是解析任何应用参数之前的组合配置树;如果调用中包含应用参数,dump 会拒绝该调用。
+`--dump-default-config` 只打印组合包各层;`--dump-config` 额外加上 profile 的 `cordis.patch.yml`、home 级的 `$DSH_HOME/cordis.patch.yml` 和 `--patch` overlay。两者都会打印注释,标明每行由哪个文件提供,以及哪些 overlay 修改过它;`!!js` 表达式保持未求值,插入行中的相对插件名以各自 patch 文件所在目录解析,找不到目标的 patch 会报告到 stderr。dump 操作会初始化缺失的 profile 文件,但不会准备 `$DSH_HOME/profiles/node_modules` 下的运行时模块 fallback。它不会运行应用的命令行参数提供方,因此展示的是解析任何应用参数之前的组合配置树;如果调用中包含应用参数,dump 会拒绝该调用。
 
 ## 插件管理
 

+ 1 - 1
apps/cli/src/bin.ts

@@ -41,7 +41,7 @@ switch (invocation.mode) {
   }
   case 'dump-config': {
     const { runDumpConfig } = await import('./dump-config.ts')
-    await runDumpConfig(invocation.profile, invocation.defaultOnly, invocation.patches)
+    runDumpConfig(invocation.profile, invocation.defaultOnly, invocation.patches)
     break
   }
   default:

+ 2 - 3
apps/cli/src/dump-config.ts

@@ -26,10 +26,9 @@ const NAME = 'dsh'
  * (the recovery diagnostic for a broken `cordis.patch.yml`, which is then
  * never parsed).
  * @param patches - `--patch` overlay paths, in argv order.
- * @returns settlement after the profile is healed and the dump is written.
  */
-export async function runDumpConfig(profile: string, defaultOnly: boolean, patches: readonly string[]): Promise<void> {
-  const loaded = await prepareProfile(profile, !defaultOnly)
+export function runDumpConfig(profile: string, defaultOnly: boolean, patches: readonly string[]): void {
+  const loaded = prepareProfile(profile, !defaultOnly)
   const layers: ConfigDumpLayer[] = loaded.layers.map(layer => ({
     label: layer.packageName,
     patches: layer.patches,

+ 11 - 11
apps/cli/src/profile-boot.ts

@@ -103,20 +103,19 @@ export function resolveTelemetryPatch(disabledEnv: string | undefined, hasRow: b
 }
 
 /**
- * Load a resolved profile for `name`: heal the shared module fallback, then
- * (re)write the empty root config. The root is always rewritten: the whole
- * composition is patch layers, and the vendored Loader's tree write-back (a
- * plugin self-disposing persists the current tree) can bake composed rows
- * into this file — which would duplicate every bundle insert on the next
- * boot. The file exists on disk only because the Loader needs a real include
- * root to anchor `baseUrl` at the profile directory (the config dump anchors
- * on the same file, so both compose over the identical base).
+ * Load a resolved profile for `name` and (re)write the empty root config. The
+ * root is always rewritten: the whole composition is patch layers, and the
+ * vendored Loader's tree write-back (a plugin self-disposing persists the
+ * current tree) can bake composed rows into this file — which would duplicate
+ * every bundle insert on the next boot. The file exists on disk only because
+ * the Loader needs a real include root to anchor `baseUrl` at the profile
+ * directory (the config dump anchors on the same file, so both compose over
+ * the identical base).
  * @param name - the profile name.
  * @param userLayer - `false` skips parsing `cordis.patch.yml` (the default dump).
  * @returns the loaded profile.
  */
-export async function prepareProfile(name: string, userLayer = true): Promise<Profile> {
-  await healProfilesModuleFallback(INSTALL_ANCHOR)
+export function prepareProfile(name: string, userLayer = true): Profile {
   const profile = loadProfile(NAME, name, INSTALL_ANCHOR, undefined, { userLayer })
   writeFileSync(join(profile.dir, PROFILE_ROOT_FILENAME), PROFILE_ROOT_CONFIG)
   return profile
@@ -158,7 +157,8 @@ async function composeProfile(
   name: string,
   patchFiles: readonly string[],
 ): Promise<ComposedProfile> {
-  const profile = await prepareProfile(name)
+  await healProfilesModuleFallback(INSTALL_ANCHOR)
+  const profile = prepareProfile(name)
   const homePatches = loadOptionalPatches(NAME, homePatchPath()) ?? []
   const overlays = patchFiles.flatMap(file => loadOverlayPatches(NAME, resolve(file)))
   const bundlePatches = profile.layers.flatMap(layer => layer.patches)

+ 1 - 0
apps/cli/tests/built-bin.e2e.ts

@@ -911,6 +911,7 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
       expect(stdout).toContain('agents: []')
       expect(stdout).toContain('# == @deepseek-ai/dsh-base')
       expect(stdout).toContain("name: '@deepseek-ai/dsh-host-webserver'")
+      expect(existsSync(join(home, 'profiles', 'node_modules'))).toBe(false)
     }, 30_000)
 
     it('prints the headless profile without Host or browser layers', async () => {

+ 2 - 13
apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts

@@ -169,15 +169,11 @@ describe('Python SDK dsh profile keyless smoke', () => {
     }
   }, 40_000)
 
-  it('boots the standalone minimal profile with its exact model-facing roster', async () => {
+  it('boots the standalone minimal profile through its generated manifest', async () => {
     const root = await mkdtemp(join(tmpdir(), 'dsh-python-sdk-minimal-'))
-    const modelRequests: Record<string, unknown>[] = []
     const modelServer = createServer((request, response) => {
-      let body = ''
-      request.setEncoding('utf8')
-      request.on('data', (chunk: string) => { body += chunk })
+      request.resume()
       request.on('end', () => {
-        modelRequests.push(JSON.parse(body) as Record<string, unknown>)
         response.writeHead(200, { 'content-type': 'text/event-stream' })
         response.write('data: {"choices":[{"delta":{"role":"assistant","content":null}}]}\n\n')
         response.write('data: {"choices":[{"delta":{"content":"done"}}]}\n\n')
@@ -237,13 +233,6 @@ describe('Python SDK dsh profile keyless smoke', () => {
         return params?.sessionId === 'minimal' && event?.type === 'turn/end'
       }, () => stderr)
 
-      const request = modelRequests[0] as {
-        messages?: Array<{ role?: string; content?: unknown }>
-        tools?: Array<{ function?: { name?: string } }>
-      }
-      expect(request.messages?.[0]).toMatchObject({ role: 'system', content: 'Minimal allowlist prompt.' })
-      const shellTool = process.platform === 'win32' ? 'pwsh' : 'bash'
-      expect(request.tools?.map(tool => tool.function?.name).sort()).toEqual([shellTool, 'str_replace_editor'].sort())
       const profile = JSON.parse(
         await readFile(join(root, '.dsh', 'profiles', 'sdk-minimal', 'package.json'), 'utf8'),
       ) as { dsh?: { profile?: { bundles?: string[]; patchReload?: string } } }

+ 2 - 2
docs/config-catalog.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/config-catalog.md
-config-catalog.md: d9a428eee76c2f7e30b05f20e2c663a4cd013173
-config-catalog.zh.md: 2df0415a41ef2bdd59655ee6a9e217b12856186d
+config-catalog.md: 41314054b72f22f006ce0c5202886ec7089bfc08
+config-catalog.zh.md: ce70a978e6b1741f974b4f8d35bf8c7a462fb16d

+ 3 - 14
docs/config-catalog.md

@@ -166,9 +166,9 @@ Source: [`packages/preset/agent-presets/src/preset.ts:52`](../packages/preset/ag
  * Bundle config: each field forwarded verbatim to the child that owns it —
  * `agents` to the agent loop (an app that pre-creates no agents, like the ACP
  * bridge, simply omits it), `includeHarnessIdentity`, `includeRuntimeContext`,
- * `persona`, `personaComplete`, and `toolOrder` to the system-prompt plugin
- * (the fixed opener, dynamic-context policy, deployment persona completeness,
- * and explicit model-facing tool order), the `tools` object to the tool
+ * `persona`, and `toolOrder` to the system-prompt plugin (the fixed opener,
+ * dynamic-context policy, deployment persona, and explicit model-facing tool
+ * order), the `tools` object to the tool
  * registry (its presentation `mode`),
  * `dshHome` to bash environment and local skill discovery, `sessionTitle` to
  * the fallback title service, `skills` to the
@@ -197,8 +197,6 @@ export interface Config {
   includeRuntimeContext?: SystemPromptConfig['includeRuntimeContext']
   /** The deployment persona (see dsh-system-prompt's `Config`). */
   persona?: SystemPromptConfig['persona']
-  /** Whether the deployment persona is the complete system prompt. */
-  personaComplete?: SystemPromptConfig['personaComplete']
   /** The explicit model-facing tool order (see dsh-system-prompt's `Config`). */
   toolOrder?: SystemPromptConfig['toolOrder']
   /** The tool registry's config — its presentation `mode` (see dsh-tools' `Config`). */
@@ -1714,13 +1712,6 @@ Requires: `agents`
 export interface JsonRpcConfig {
   /** Report max-token turn/subagent termination as a successful SDK result. */
   maxTokensAsSuccess?: boolean
-  /** Per-root-agent model-facing tool filter; an allow list excludes later unnamed global tools. */
-  toolFilter?: {
-    /** Global tool names that remain visible. */
-    allow?: string[]
-    /** Global tool names removed from visibility. */
-    deny?: string[]
-  }
   /** Transport input override; production uses `process.stdin`. */
   input?: Readable
   /** Transport output override; production uses `process.stdout`. */
@@ -2458,8 +2449,6 @@ export interface Config {
    * `deployment:persona` shadows it; `{{variable}}` references are strict.
    */
   persona?: string
-  /** Treat the deployment persona as the complete system prompt (default false). */
-  personaComplete?: boolean
   /**
    * Model-facing tool names in order, with {@link TOOL_ORDER_REST} exactly once.
    * Invalid fields fail at load and unknown names fail at assembly; known names

+ 3 - 14
docs/config-catalog.zh.md

@@ -168,9 +168,9 @@ export type PresetTrust = 'system' | 'user'
  * Bundle config: each field forwarded verbatim to the child that owns it —
  * `agents` to the agent loop (an app that pre-creates no agents, like the ACP
  * bridge, simply omits it), `includeHarnessIdentity`, `includeRuntimeContext`,
- * `persona`, `personaComplete`, and `toolOrder` to the system-prompt plugin
- * (the fixed opener, dynamic-context policy, deployment persona completeness,
- * and explicit model-facing tool order), the `tools` object to the tool
+ * `persona`, and `toolOrder` to the system-prompt plugin (the fixed opener,
+ * dynamic-context policy, deployment persona, and explicit model-facing tool
+ * order), the `tools` object to the tool
  * registry (its presentation `mode`),
  * `dshHome` to bash environment and local skill discovery, `sessionTitle` to
  * the fallback title service, `skills` to the
@@ -199,8 +199,6 @@ export interface Config {
   includeRuntimeContext?: SystemPromptConfig['includeRuntimeContext']
   /** The deployment persona (see dsh-system-prompt's `Config`). */
   persona?: SystemPromptConfig['persona']
-  /** Whether the deployment persona is the complete system prompt. */
-  personaComplete?: SystemPromptConfig['personaComplete']
   /** The explicit model-facing tool order (see dsh-system-prompt's `Config`). */
   toolOrder?: SystemPromptConfig['toolOrder']
   /** The tool registry's config — its presentation `mode` (see dsh-tools' `Config`). */
@@ -1716,13 +1714,6 @@ export interface Config {
 export interface JsonRpcConfig {
   /** Report max-token turn/subagent termination as a successful SDK result. */
   maxTokensAsSuccess?: boolean
-  /** Per-root-agent model-facing tool filter; an allow list excludes later unnamed global tools. */
-  toolFilter?: {
-    /** Global tool names that remain visible. */
-    allow?: string[]
-    /** Global tool names removed from visibility. */
-    deny?: string[]
-  }
   /** Transport input override; production uses `process.stdin`. */
   input?: Readable
   /** Transport output override; production uses `process.stdout`. */
@@ -2460,8 +2451,6 @@ export interface Config {
    * `deployment:persona` shadows it; `{{variable}}` references are strict.
    */
   persona?: string
-  /** Treat the deployment persona as the complete system prompt (default false). */
-  personaComplete?: boolean
   /**
    * Model-facing tool names in order, with {@link TOOL_ORDER_REST} exactly once.
    * Invalid fields fail at load and unknown names fail at assembly; known names

+ 2 - 2
packages/core/system-prompt/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/core/system-prompt/README.md
-README.md: a52aa3e4c2782993fed5a525cc827aba4e3eaeb0
-README.zh.md: cf0ba43aa2f2f47ea61ef13c74fbf182fbd9f2ee
+README.md: d750a507e628e7609af542227e4528d4d4934ce8
+README.zh.md: ec5b32d742b96c8044a3707c35f15e30ba642b4f

+ 0 - 1
packages/core/system-prompt/README.md

@@ -11,7 +11,6 @@ System prompt assembly registry. Plugins contribute ordered sections, tool schem
 | `includeHarnessIdentity` | `true` | Include the fixed `You are an AI agent powered by DeepSeek Harness.` order-−100 opener. Set false only when a compatibility deployment owns the complete system prompt. |
 | `includeRuntimeContext` | `true` | Include ordered dynamic contexts in assembly. When false, context providers are not evaluated and contexts added by `system-prompt/assemble` listeners are discarded after the waterfall; other services and their enforcement remain active. |
 | `persona` | `''` | The global deployment-persona default: the ONE config-authored prompt fragment, rendered as the order-0 `deployment:persona` section unless an agent-scoped contribution shadows it. A template — complete `{{…}}` groups are interpreted strictly against the registered variables (the shipped loop registers `{{model}}`/`{{cwd}}`), with no escape syntax for literal braces yet. Empty ⇒ the section is dropped at render. |
-| `personaComplete` | `false` | Treat `persona` as the complete system prompt after assembly. Other sections remain registered but are omitted from model requests; tool schemas and variables remain available. |
 | `toolOrder` | — | Explicit model-facing tool order, as a list of `ToolSchema.name`s with one `'<unlisted-tools>'` rest entry (`TOOL_ORDER_REST`): listed tools take their listed position, unlisted tools land at the rest entry in lexicographic name order. Absent ⇒ plain lexicographic name order. Applied to the collected tools BEFORE the `system-prompt/assemble` waterfall — like the sections' `order` sort, it canonicalizes what the registry contributed (registration order is a plugin-load artifact), and a waterfall listener that mutates the list owns the determinism of what it emits. Misconfiguration fails loud: a list without exactly one rest entry, or with duplicates, throws at load; a listed name with no registered tool rejects every `assemble()`; a tool provider returning the reserved rest-entry name also rejects. Under the shipped loop the turn fails before any model request. Why a central list and not per-plugin weights: [Explicit model-facing tool order](../../../.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.md). |
 
 ## Service: `SystemPrompt` (ctx key: `systemPrompt`)

+ 0 - 1
packages/core/system-prompt/README.zh.md

@@ -11,7 +11,6 @@
 | `includeHarnessIdentity` | `true` | 是否包含顺序为 −100 的固定开场白 `You are an AI agent powered by DeepSeek Harness.`。仅当兼容性部署拥有完整系统提示词时设为 false。 |
 | `includeRuntimeContext` | `true` | 是否在组装中包含有序动态上下文。设为 false 时不会求值上下文提供方,并会在 waterfall 后丢弃 `system-prompt/assemble` 监听器添加的上下文;其他服务及其强制机制仍然生效。 |
 | `persona` | `''` | 全局部署 persona 默认值:唯一由配置提供的提示词片段,渲染为顺序为 0 的 `deployment:persona` 段,除非 agent 作用域的贡献将其遮蔽。它是模板,完整的 `{{…}}` 组会严格按已注册变量解释(随附循环注册 `{{model}}`/`{{cwd}}`),目前没有表达字面量花括号的转义语法。为空 ⇒ 渲染时删除该段。 |
-| `personaComplete` | `false` | 在组装后将 `persona` 作为完整系统提示词。其他段仍保持注册,但不会进入模型请求;工具 schema 与变量仍然可用。 |
 | `toolOrder` | 无 | 显式指定面向模型的工具顺序。该列表由 `ToolSchema.name` 组成,并且必须恰好包含一个 `'<unlisted-tools>'` 其余项标记(`TOOL_ORDER_REST`):已列工具按列表位置排列,未列工具则按名称字典序插入该标记所在的位置。缺席 ⇒ 直接按名称字典序排列。该顺序会在 `system-prompt/assemble` waterfall(瀑布式事件)之前应用于已收集的工具。与段的 `order` 排序一样,它会规范化注册表贡献的内容;注册顺序只是插件加载时序的产物。修改列表的 waterfall 监听器对其输出的确定性负责。配置错误会明确失败:列表没有恰好一个其余项或存在重复项,会在加载时抛出;已列名称没有对应已注册工具,会使每次 `assemble()` 被拒绝;工具提供方返回保留的其余项名称也会被拒绝。在随附循环下,轮次会在任何模型请求前失败。为何采用中心列表而非每插件权重,见[显式面向模型工具顺序](../../../.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.zh.md)。 |
 
 ## 服务:`SystemPrompt`(ctx 键:`systemPrompt`)

+ 0 - 4
packages/core/system-prompt/src/index.ts

@@ -193,8 +193,6 @@ export interface Config {
    * `deployment:persona` shadows it; `{{variable}}` references are strict.
    */
   persona?: string
-  /** Treat the deployment persona as the complete system prompt (default false). */
-  personaComplete?: boolean
   /**
    * Model-facing tool names in order, with {@link TOOL_ORDER_REST} exactly once.
    * Invalid fields fail at load and unknown names fail at assembly; known names
@@ -342,7 +340,6 @@ export class SystemPrompt extends Service {
     includeHarnessIdentity: z.boolean().default(true),
     includeRuntimeContext: z.boolean().default(true),
     persona: z.string().default(''),
-    personaComplete: z.boolean().default(false),
     // Preserve omission because an explicit empty order lacks the rest marker.
     toolOrder: z.array(z.string()).default(undefined as unknown as string[]),
   })
@@ -369,7 +366,6 @@ export class SystemPrompt extends Service {
       order: PERSONA_ORDER,
       // The fallback narrows the optional input type; the schema already defaults it.
       text: config.persona ?? '',
-      complete: config.personaComplete ?? false,
     })
     if (!(config.includeRuntimeContext ?? true)) this.suppressRuntimeContext()
   }

+ 0 - 15
packages/core/system-prompt/tests/system-prompt.spec.ts

@@ -49,21 +49,6 @@ describe('SystemPrompt', () => {
       expect(renderPrompt(assembly)).toBe('You are a helpful software engineer assistant.')
     })
 
-    it('can make the deployment persona the complete system prompt', async () => {
-      const ctx = new Context()
-      await ctx.plugin(SystemPrompt, {
-        persona: 'You are a focused SDK agent.',
-        personaComplete: true,
-      })
-      ctx.systemPrompt.section({ name: 'tool:future', order: 100, text: 'Future tool guidance.' })
-
-      const assembly = await ctx.systemPrompt.assemble()
-      expect(assembly.sections).toEqual([
-        { name: 'deployment:persona', text: 'You are a focused SDK agent.' },
-      ])
-      expect(renderPrompt(assembly)).toBe('You are a focused SDK agent.')
-    })
-
     it('can suppress runtime context without evaluating providers or accepting waterfall additions', async () => {
       const ctx = new Context()
       await ctx.plugin(SystemPrompt, { includeRuntimeContext: false })

+ 2 - 2
packages/examples/agent-spine-demo/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/examples/agent-spine-demo/README.md
-README.md: 28e1496a8c941f6524ec4b4dfbfad52d17df5d71
-README.zh.md: c2838bb4cf78d6ac863bac2eba4d2e4df335fa55
+README.md: ef82aa4b413be41049dbbd247deee4f1969cdb48
+README.zh.md: 7701da0d965736efd905a3331401752119b160d9

+ 2 - 2
packages/examples/agent-spine-demo/README.md

@@ -55,11 +55,11 @@ This applies the [Service Definition / Service Provider / Consumer separation](.
 
 ```ts
 import type { Config } from '@deepseek-ai/dsh-agent-spine-demo'
-// { agents?, maxParallelToolCalls?, includeHarnessIdentity?, includeRuntimeContext?, persona?, personaComplete?, toolOrder?, tools?, dshHome?, sessionTitle?, skills?, workspaceContext, toolBash?, jobs?, toolJobs?, goals?, invariants? }
+// { agents?, maxParallelToolCalls?, includeHarnessIdentity?, includeRuntimeContext?, persona?, toolOrder?, tools?, dshHome?, sessionTitle?, skills?, workspaceContext, toolBash?, jobs?, toolJobs?, goals?, invariants? }
 // workspaceContext requires { maxBytes } or false; the other owner schemas supply defaults.
 ```
 
-The bundle forwards each field to the child that owns it. App packages supply any pre-created agents: headless and JSON-RPC compositions create `main`, while the ACP app creates agents on demand at `session/new`. `includeRuntimeContext: false` suppresses all dynamic context snapshots for fresh sessions without disabling their policy services; `personaComplete: true` makes the deployment persona the sole system-prompt section. Prompt, tool, title, skill, agent-instructions, invariant, goal, and task settings retain the schemas and defaults documented by their owning packages; `jobs.maxConcurrentJobsPerOwner` configures the local provider independently of the model-facing `toolJobs` controls. `pickSpineConfig()` copies only fields owned by this bundle, and conflicting `dshHome` values fail during composition.
+The bundle forwards each field to the child that owns it. App packages supply any pre-created agents: headless and JSON-RPC compositions create `main`, while the ACP app creates agents on demand at `session/new`. `includeRuntimeContext: false` suppresses all dynamic context snapshots for fresh sessions without disabling their policy services. Prompt, tool, title, skill, agent-instructions, invariant, goal, and task settings retain the schemas and defaults documented by their owning packages; `jobs.maxConcurrentJobsPerOwner` configures the local provider independently of the model-facing `toolJobs` controls. `pickSpineConfig()` copies only fields owned by this bundle, and conflicting `dshHome` values fail during composition.
 
 For example, `{ invariants: { enabled: true, package_allowlist: ['^@deepseek-ai/dsh-'], package_blocklist: ['agent-loop$'] } }` keeps the package-owned companions mounted but suppresses the blocked owner. Blocklist matches override allowlist matches; see [`dsh-invariants`](../../runtime-diagnostics/invariants/README.md) for regex and lifecycle rules.
 

+ 2 - 2
packages/examples/agent-spine-demo/README.zh.md

@@ -55,11 +55,11 @@
 
 ```ts
 import type { Config } from '@deepseek-ai/dsh-agent-spine-demo'
-// { agents?, maxParallelToolCalls?, includeHarnessIdentity?, includeRuntimeContext?, persona?, personaComplete?, toolOrder?, tools?, dshHome?, sessionTitle?, skills?, workspaceContext, toolBash?, jobs?, toolJobs?, goals?, invariants? }
+// { agents?, maxParallelToolCalls?, includeHarnessIdentity?, includeRuntimeContext?, persona?, toolOrder?, tools?, dshHome?, sessionTitle?, skills?, workspaceContext, toolBash?, jobs?, toolJobs?, goals?, invariants? }
 // workspaceContext requires { maxBytes } or false; the other owner schemas supply defaults.
 ```
 
-组合包将每个字段转发给拥有它的子节点。应用包提供预创建的 agent:无头和 JSON-RPC 组合会创建 `main`,ACP 应用则在 `session/new` 按需创建 agent。`includeRuntimeContext: false` 会为新建会话抑制所有动态上下文快照,但不禁用其策略服务;`personaComplete: true` 会让部署 persona 成为唯一系统提示词段。提示词、工具、标题、skill、工作区上下文、不变式、目标和任务设置沿用其所属包记录的 schema 与默认值;`jobs.maxConcurrentJobsPerOwner` 配置本地 Service Provider,并与面向模型的 `toolJobs` 控制工具相互独立。`pickSpineConfig()` 只复制该组合包拥有的字段,`dshHome` 值冲突会在组合时失败。
+组合包将每个字段转发给拥有它的子节点。应用包提供预创建的 agent:无头和 JSON-RPC 组合会创建 `main`,ACP 应用则在 `session/new` 按需创建 agent。`includeRuntimeContext: false` 会为新建会话抑制所有动态上下文快照,但不禁用其策略服务。提示词、工具、标题、skill、工作区上下文、不变式、目标和任务设置沿用其所属包记录的 schema 与默认值;`jobs.maxConcurrentJobsPerOwner` 配置本地 Service Provider,并与面向模型的 `toolJobs` 控制工具相互独立。`pickSpineConfig()` 只复制该组合包拥有的字段,`dshHome` 值冲突会在组合时失败。
 
 例如,`{ invariants: { enabled: true, package_allowlist: ['^@deepseek-ai/dsh-'], package_blocklist: ['agent-loop$'] } }` 会让包拥有的配套插件保持挂载,但抑制被阻止的拥有者。Blocklist 匹配优先于 allowlist 匹配;正则表达式与生命周期规则见 [`dsh-invariants`](../../runtime-diagnostics/invariants/README.zh.md)。
 

+ 3 - 7
packages/examples/agent-spine-demo/src/index.ts

@@ -70,9 +70,9 @@ export interface GoalConfig {
  * Bundle config: each field forwarded verbatim to the child that owns it —
  * `agents` to the agent loop (an app that pre-creates no agents, like the ACP
  * bridge, simply omits it), `includeHarnessIdentity`, `includeRuntimeContext`,
- * `persona`, `personaComplete`, and `toolOrder` to the system-prompt plugin
- * (the fixed opener, dynamic-context policy, deployment persona completeness,
- * and explicit model-facing tool order), the `tools` object to the tool
+ * `persona`, and `toolOrder` to the system-prompt plugin (the fixed opener,
+ * dynamic-context policy, deployment persona, and explicit model-facing tool
+ * order), the `tools` object to the tool
  * registry (its presentation `mode`),
  * `dshHome` to bash environment and local skill discovery, `sessionTitle` to
  * the fallback title service, `skills` to the
@@ -101,8 +101,6 @@ export interface Config {
   includeRuntimeContext?: SystemPromptConfig['includeRuntimeContext']
   /** The deployment persona (see dsh-system-prompt's `Config`). */
   persona?: SystemPromptConfig['persona']
-  /** Whether the deployment persona is the complete system prompt. */
-  personaComplete?: SystemPromptConfig['personaComplete']
   /** The explicit model-facing tool order (see dsh-system-prompt's `Config`). */
   toolOrder?: SystemPromptConfig['toolOrder']
   /** The tool registry's config — its presentation `mode` (see dsh-tools' `Config`). */
@@ -188,7 +186,6 @@ export function pickSpineConfig(config: Omit<Config, 'agents'>): Omit<Config, 'a
     ...config.includeHarnessIdentity !== undefined ? { includeHarnessIdentity: config.includeHarnessIdentity } : {},
     ...config.includeRuntimeContext !== undefined ? { includeRuntimeContext: config.includeRuntimeContext } : {},
     ...config.persona !== undefined ? { persona: config.persona } : {},
-    ...config.personaComplete !== undefined ? { personaComplete: config.personaComplete } : {},
     ...config.toolOrder !== undefined ? { toolOrder: config.toolOrder } : {},
     ...config.tools !== undefined ? { tools: config.tools } : {},
     ...config.dshHome !== undefined ? { dshHome: config.dshHome } : {},
@@ -230,7 +227,6 @@ export function apply(ctx: Context, config: Config): void {
     includeHarnessIdentity: config.includeHarnessIdentity ?? true,
     includeRuntimeContext: config.includeRuntimeContext ?? true,
     persona: config.persona ?? '',
-    personaComplete: config.personaComplete ?? false,
     ...config.toolOrder !== undefined ? { toolOrder: config.toolOrder } : {},
   })
   ctx.plugin(ToolRuntime, config.tools ?? {})

+ 1 - 5
packages/examples/agent-spine-demo/tests/agent-core.spec.ts

@@ -709,12 +709,11 @@ describe('dsh-agent-spine-demo bundle', () => {
     await ctx.fiber.dispose()
   })
 
-  it('can omit the bundled bash tool and Harness identity for a compatibility deployment', async () => {
+  it('can omit the bundled bash tool, Harness identity, and runtime context', async () => {
     const ctx = await mount({
       includeHarnessIdentity: false,
       includeRuntimeContext: false,
       persona: 'You are a helpful software engineer assistant.',
-      personaComplete: true,
       workspaceContext: false,
       skills: { enabled: false },
       toolBash: false,
@@ -724,7 +723,6 @@ describe('dsh-agent-spine-demo bundle', () => {
     expect(ctx.tools.schemas()).toEqual([])
     ctx.systemPrompt.context({ name: 'policy', order: 0, text: 'hidden policy' })
     expect((await ctx.systemPrompt.assemble()).contexts).toEqual([])
-    ctx.systemPrompt.section({ name: 'hidden', order: 100, text: 'hidden guidance' })
     expect(renderPrompt(await ctx.systemPrompt.assemble()))
       .toBe('You are a helpful software engineer assistant.')
 
@@ -738,7 +736,6 @@ describe('dsh-agent-spine-demo bundle', () => {
       includeHarnessIdentity: false,
       includeRuntimeContext: false,
       persona: 'You are merged.',
-      personaComplete: true,
       toolOrder: ['zulu'],
       tools: { mode: 'native' as const },
       dshHome: '/tmp/dsh-home',
@@ -757,7 +754,6 @@ describe('dsh-agent-spine-demo bundle', () => {
       includeHarnessIdentity: appConfig.includeHarnessIdentity,
       includeRuntimeContext: appConfig.includeRuntimeContext,
       persona: appConfig.persona,
-      personaComplete: appConfig.personaComplete,
       toolOrder: appConfig.toolOrder,
       tools: appConfig.tools,
       dshHome: appConfig.dshHome,

+ 2 - 2
packages/sdk/server/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/sdk/server/README.md
-README.md: e6d36df7e387e895941c082226ac955c69f16ca4
-README.zh.md: 13c026edc491ebdbec298b8c9cd330b03d675db7
+README.md: f10c45e2df06383d6726f249bb6db2015a7fb418
+README.zh.md: 7bf3bcdab2a318fbfcba7b5a6c3a60ff883f47b6

+ 2 - 2
packages/sdk/server/README.md

@@ -10,7 +10,7 @@ The `jsonrpc` plugin serves newline-delimited JSON-RPC over stdio so out-of-proc
 
 ## Config
 
-`maxTokensAsSuccess` defaults to `false` and affects only the deployment-mapped status on `subagent.finished`; root-session prompts have no prompt-level status. Optional `toolFilter.allow` and `toolFilter.deny` restrict each SDK-created root agent through `ctx.tools.restrict()`. An allow list excludes later global tool registrations that it does not name, so a fixed SDK deployment cannot silently gain model-facing tools when its base bundle expands. Unknown names and an empty filter fail when the first session is created. `JsonRpcConfig.input`, `output`, and `exit` are runtime-only transport hooks; production uses process stdio and `process.exit`.
+`maxTokensAsSuccess` defaults to `false` and affects only the deployment-mapped status on `subagent.finished`; root-session prompts have no prompt-level status. The profile composition owns each root agent's tools. `JsonRpcConfig.input`, `output`, and `exit` are runtime-only transport hooks; production uses process stdio and `process.exit`.
 
 ## stdout is the protocol
 
@@ -30,7 +30,7 @@ The plugin answers `shutdown`, flushes the response, disposes the root context s
 
 #### What the model sees
 
-For each accepted `session/prompt`, text and durable content references enter one user message verbatim. Inline `SdkEncodedImageBlock` values are validated and committed through the composition's attachment store first, so the session log retains content-addressed image references rather than base64 bytes. This package adds no system-prompt prose or tool schema; those come from the other plugins in the composition. A configured `toolFilter` projects that composition's global tool registry before the request is assembled and executed.
+For each accepted `session/prompt`, text and durable content references enter one user message verbatim. Inline `SdkEncodedImageBlock` values are validated and committed through the composition's attachment store first, so the session log retains content-addressed image references rather than base64 bytes. This package adds no system-prompt prose or tool schema; those come from the other plugins in the composition.
 
 #### Token effect
 

+ 2 - 2
packages/sdk/server/README.zh.md

@@ -10,7 +10,7 @@
 
 ## 配置
 
-`maxTokensAsSuccess` 默认为 `false`,且只影响 `subagent.finished` 上由部署映射的状态;根会话提示词没有提示词级状态。可选的 `toolFilter.allow` 与 `toolFilter.deny` 通过 `ctx.tools.restrict()` 限制每个由 SDK 创建的根 agent。Allow 列表会排除之后出现但未指名的全局工具,因此固定的 SDK 部署不会在基础 bundle 扩展时静默获得面向模型的新工具。未知名称与空筛选器会在创建首个会话时明确失败。`JsonRpcConfig.input`、`output` 和 `exit` 是仅供运行时使用的传输钩子;生产环境使用进程 stdio 和 `process.exit`。
+`maxTokensAsSuccess` 默认为 `false`,且只影响 `subagent.finished` 上由部署映射的状态;根会话提示词没有提示词级状态。Profile 组合负责每个根 agent 的工具。`JsonRpcConfig.input`、`output` 和 `exit` 是仅供运行时使用的传输钩子;生产环境使用进程 stdio 和 `process.exit`。
 
 ## stdout 即协议
 
@@ -30,7 +30,7 @@ Stdout 只承载 JSON-RPC 帧。部署不得组合 stdout logger;诊断应写
 
 #### 模型看到的内容
 
-对于每个已接受的 `session/prompt`,文本和持久内容引用会原样进入一条用户消息。内联 `SdkEncodedImageBlock` 会先通过组合中的附件存储完成校验与提交,因此会话日志保留内容寻址的图片引用而不是 base64 字节。此包不会添加系统提示词文本或工具 schema;这些内容来自组合中的其他插件。配置的 `toolFilter` 会在请求组装与执行前投影该组合的全局工具注册表。
+对于每个已接受的 `session/prompt`,文本和持久内容引用会原样进入一条用户消息。内联 `SdkEncodedImageBlock` 会先通过组合中的附件存储完成校验与提交,因此会话日志保留内容寻址的图片引用而不是 base64 字节。此包不会添加系统提示词文本或工具 schema;这些内容来自组合中的其他插件。
 
 #### Token 影响
 

+ 0 - 13
packages/sdk/server/src/index.ts

@@ -25,13 +25,6 @@ export const inject = ['agents']
 export interface JsonRpcConfig {
   /** Report max-token turn/subagent termination as a successful SDK result. */
   maxTokensAsSuccess?: boolean
-  /** Per-root-agent model-facing tool filter; an allow list excludes later unnamed global tools. */
-  toolFilter?: {
-    /** Global tool names that remain visible. */
-    allow?: string[]
-    /** Global tool names removed from visibility. */
-    deny?: string[]
-  }
   /** Transport input override; production uses `process.stdin`. */
   input?: Readable
   /** Transport output override; production uses `process.stdout`. */
@@ -42,11 +35,6 @@ export interface JsonRpcConfig {
 
 export const Config: Schema<JsonRpcConfig> = Schema.object({
   maxTokensAsSuccess: Schema.boolean().default(false),
-  // Preserve omission; Schemastery's materialized empty object is not a valid restriction.
-  toolFilter: Schema.object({
-    allow: Schema.array(Schema.string()).default(undefined as unknown as string[]),
-    deny: Schema.array(Schema.string()).default(undefined as unknown as string[]),
-  }).default(undefined as unknown as { allow: string[]; deny: string[] }),
 })
 
 /**
@@ -71,7 +59,6 @@ export function apply(ctx: Context, config: JsonRpcConfig): void {
   const transport = new JsonRpcLineTransport(input, output)
   const server = new HarnessSdkJsonRpcServer(ctx, transport, {
     maxTokensAsSuccess: resolvedConfig.maxTokensAsSuccess,
-    ...resolvedConfig.toolFilter === undefined ? {} : { toolFilter: resolvedConfig.toolFilter },
   })
 
   // Share one exit task so racing shutdown requests cannot dispose the root or

+ 0 - 7
packages/sdk/server/src/server.ts

@@ -15,7 +15,6 @@ import { carrierKeyOf, type Scoped } from '@deepseek-ai/dsh-scope'
 import { SessionId } from '@deepseek-ai/dsh-session'
 import type SubagentRuntime from '@deepseek-ai/dsh-subagent'
 import type { SubagentRunEndInfo } from '@deepseek-ai/dsh-subagent'
-import type { ToolRestriction } from '@deepseek-ai/dsh-tools'
 import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
 import type {
   InitializeParams,
@@ -61,8 +60,6 @@ function subagentParentOf(carrier: Scoped<SubagentRuntime>): Agent {
 export interface HarnessSdkJsonRpcServerOptions {
   /** Report max-token termination as an accepted result instead of an infrastructure error. */
   maxTokensAsSuccess?: boolean
-  /** Restrict each SDK-created root agent to an explicit subset of global tools. */
-  toolFilter?: ToolRestriction
 }
 
 function successStatus(reason: string, options: HarnessSdkJsonRpcServerOptions): 'ok' | 'error' {
@@ -256,7 +253,6 @@ export class HarnessSdkJsonRpcServer {
     // rows in the host plane, so this agent reads them from the global layer. A
     // deployment that configures a roster has to join one here first
     // (@deepseek-ai/dsh-agent-presets README, "Composing a child agent").
-    const toolFilter = this.options.toolFilter
     const handle = await this.ctx.agents.create({
       sessionId: SessionId(sessionId),
       meta: { cwd: this.cwd },
@@ -265,9 +261,6 @@ export class HarnessSdkJsonRpcServer {
         model: this.model,
         ...this.maxTokens === undefined ? {} : { maxTokens: this.maxTokens },
       },
-      ...toolFilter === undefined
-        ? {}
-        : { setup: (agentCtx: Context) => { agentCtx.tools.restrict(toolFilter) } },
     })
     const rec: SessionRecord = { handle }
     this.sessions.set(sessionId, rec)

+ 0 - 48
packages/sdk/server/tests/plugin-apply.spec.ts

@@ -11,7 +11,6 @@ import * as agentCore from '@deepseek-ai/dsh-agent-spine-demo'
 import { LlmAdapter } from '@deepseek-ai/dsh-llm'
 import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
 import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl'
-import { defineTool } from '@deepseek-ai/dsh-tools'
 import * as jsonrpc from '../src/index.ts'
 
 /**
@@ -72,7 +71,6 @@ async function mountPlugin(
     writeDelayMs?: number
     failFlush?: boolean
     beforeServer?: (ctx: Context) => Promise<void> | void
-    toolFilter?: jsonrpc.JsonRpcConfig['toolFilter']
   } = {},
 ): Promise<ApplyHarness> {
   const ctx = new Context()
@@ -122,7 +120,6 @@ async function mountPlugin(
     input,
     output,
     exit,
-    ...options.toolFilter === undefined ? {} : { toolFilter: options.toolFilter },
   })
 
   const frames = (): Record<string, unknown>[] =>
@@ -289,51 +286,6 @@ describe('dsh-sdk-jsonrpc-server plugin apply', () => {
     }
   })
 
-  it('applies the configured root-agent tool filter through the Loader plugin', async () => {
-    const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-apply-tool-filter-'))
-    const llmServer = await mockCompletionServer()
-    vi.stubEnv('DEEPSEEK_API_KEY', 'test-key')
-    vi.stubEnv('DEEPSEEK_BASE_URL', llmServer.url)
-    const harness = await mountPlugin(storageDir, {
-      toolFilter: { allow: ['kept'] },
-      beforeServer: (ctx) => {
-        for (const name of ['kept', 'excluded']) {
-          ctx.tools.register(defineTool({
-            name,
-            description: name,
-            parameters: {},
-            output: {
-              schema: { type: 'string' },
-              render: (_args, value) => [{ type: 'text', text: value }],
-            },
-            execute: async () => name,
-          }))
-        }
-      },
-    })
-    try {
-      harness.send({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { cwd: storageDir, provider: 'deepseek-official', model: 'filtered-model' } })
-      await harness.waitForFrame(frame => frame.id === 1, 'initialize response')
-      harness.send({
-        jsonrpc: '2.0',
-        id: 2,
-        method: 'session/prompt',
-        params: { sessionId: 'filtered', contentBlocks: [{ type: 'text', text: 'inspect tools' }] },
-      })
-      await harness.waitForFrame(
-        frame => frame.method === 'session.status'
-          && (frame.params as { status?: string } | undefined)?.status === 'idle',
-        'filtered session idle status',
-      )
-
-      const request = llmServer.requests[0] as { tools?: Array<{ function?: { name?: string } }> }
-      expect(request.tools?.map(entry => entry.function?.name)).toEqual(['kept'])
-    } finally {
-      await harness.dispose()
-      await rm(storageDir, { recursive: true, force: true })
-    }
-  })
-
   it('answers shutdown before exiting 0 exactly once, even against a racing second shutdown', async () => {
     const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-apply-shutdown-'))
     const harness = await mountPlugin(storageDir, { writeDelayMs: 10 })

+ 0 - 41
packages/sdk/server/tests/server.spec.ts

@@ -14,7 +14,6 @@ import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl'
 import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
 import SubagentRuntime, { type SubagentResult, type SubagentRunEndInfo } from '@deepseek-ai/dsh-subagent'
 import type { JsonRpcTransportPeer } from '@deepseek-ai/dsh-sdk-protocol'
-import { defineTool } from '@deepseek-ai/dsh-tools'
 import { HarnessSdkJsonRpcServer } from '../src/index.ts'
 
 class FakeTransport implements JsonRpcTransportPeer {
@@ -172,46 +171,6 @@ describe('HarnessSdkJsonRpcServer', () => {
     }
   })
 
-  it('allowlists each root session against current and later global tools', { timeout: 15_000 }, async () => {
-    const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-tool-filter-'))
-    const llmServer = await mockCompletionServer()
-    vi.stubEnv('DEEPSEEK_API_KEY', 'test-key')
-    vi.stubEnv('DEEPSEEK_BASE_URL', llmServer.url)
-    const ctx = await makeHarness(storageDir)
-    const tool = (name: string) => defineTool({
-      name,
-      description: name,
-      parameters: {},
-      output: {
-        schema: { type: 'string' as const },
-        render: (_args, value) => [{ type: 'text' as const, text: value }],
-      },
-      execute: async () => name,
-    })
-    ctx.tools.register(tool('kept'))
-    ctx.tools.register(tool('excluded'))
-    const server = new HarnessSdkJsonRpcServer(ctx, new FakeTransport(), {
-      toolFilter: { allow: ['kept'] },
-    })
-    try {
-      await server.initialize({ cwd: storageDir, provider: 'deepseek-official', model: 'filtered-model' })
-      await server.prompt({ sessionId: 'first', contentBlocks: [{ type: 'text', text: 'first' }] })
-      await vi.waitFor(() => { expect(llmServer.requests).toHaveLength(1) })
-      ctx.tools.register(tool('future'))
-      await server.prompt({ sessionId: 'second', contentBlocks: [{ type: 'text', text: 'second' }] })
-      await vi.waitFor(() => { expect(llmServer.requests).toHaveLength(2) })
-
-      expect(llmServer.requests.map((request) => {
-        const tools = (request as { tools?: Array<{ function?: { name?: string } }> }).tools ?? []
-        return tools.map(entry => entry.function?.name)
-      })).toEqual([['kept'], ['kept']])
-      await server.shutdown()
-    } finally {
-      await ctx.fiber.dispose()
-      await rm(storageDir, { recursive: true, force: true })
-    }
-  })
-
   it('queues overlapping prompts for one session without blocking other sessions', async () => {
     const mainFollowup = vi.fn<Agent['followup']>()
     const mainAgent = ({

+ 1 - 2
python/sdk/src/deepseek_harness/client.py

@@ -34,7 +34,6 @@ class HarnessConfig:
     initialize_timeout_seconds: float = 30.0
     request_timeout_seconds: float | None = None
     shutdown_timeout_seconds: float | None = 1.0
-    _launch_args: tuple[str, ...] | None = None
 
 
 class HarnessClient:
@@ -47,7 +46,7 @@ class HarnessClient:
         _launch_args: tuple[str, ...] | None = None,
     ) -> None:
         self.config = config or HarnessConfig()
-        self._launch_args = _launch_args or self.config._launch_args
+        self._launch_args = _launch_args
         self._proc: subprocess.Popen[str] | None = None
         self._lock = threading.Lock()
         self._write_lock = threading.Lock()

+ 18 - 23
python/sdk/tests/test_client.py

@@ -471,9 +471,7 @@ for line in sys.stdin:
 """.strip()
     )
 
-    with HarnessClient(
-        HarnessConfig(_launch_args=(sys.executable, str(script)))
-    ) as client:
+    with HarnessClient(_launch_args=(sys.executable, str(script))) as client:
         init = client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
         assert init.serverInfo.name == "fake-dsh"
 
@@ -611,7 +609,7 @@ for line in sys.stdin:
     def broken_filter(_notification: object) -> bool:
         raise RuntimeError("bad notification filter")
 
-    with HarnessClient(HarnessConfig(_launch_args=(sys.executable, str(script)))) as client:
+    with HarnessClient(_launch_args=(sys.executable, str(script))) as client:
         client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
         with (
             client.subscribe_notifications(broken_filter) as broken,
@@ -648,7 +646,7 @@ for line in sys.stdin:
 """.strip()
     )
 
-    with HarnessClient(HarnessConfig(_launch_args=(sys.executable, str(script)))) as client:
+    with HarnessClient(_launch_args=(sys.executable, str(script))) as client:
         client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
         with pytest.raises(ValueError):
             client.session_prompt("main", [{"type": "text", "text": "fix it"}])
@@ -675,9 +673,7 @@ for line in sys.stdin:
 """.strip()
     )
 
-    with HarnessClient(
-        HarnessConfig(_launch_args=(sys.executable, str(script)))
-    ) as client:
+    with HarnessClient(_launch_args=(sys.executable, str(script))) as client:
         client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
 
         request = client.next_request()
@@ -709,9 +705,7 @@ for line in sys.stdin:
 """.strip()
     )
 
-    with HarnessClient(
-        HarnessConfig(_launch_args=(sys.executable, str(script)))
-    ) as client:
+    with HarnessClient(_launch_args=(sys.executable, str(script))) as client:
         init = client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
         assert init.serverInfo.name == "fake-dsh"
 
@@ -730,10 +724,10 @@ time.sleep(60)
 
     with HarnessClient(
         HarnessConfig(
-            _launch_args=(sys.executable, str(script)),
             profile="web",
             initialize_timeout_seconds=0.1,
-        )
+        ),
+        _launch_args=(sys.executable, str(script)),
     ) as client:
         start = time.monotonic()
         try:
@@ -768,9 +762,9 @@ for line in sys.stdin:
 
     client = HarnessClient(
         HarnessConfig(
-            _launch_args=(sys.executable, str(script)),
             shutdown_timeout_seconds=0.1,
-        )
+        ),
+        _launch_args=(sys.executable, str(script)),
     )
     client.start()
     proc = client._proc
@@ -808,10 +802,10 @@ Path(os.environ["QUIESCED_MARKER"]).write_text("quiesced")
 
     client = HarnessClient(
         HarnessConfig(
-            _launch_args=(sys.executable, str(script)),
             env={"QUIESCED_MARKER": str(marker)},
             shutdown_timeout_seconds=1,
-        )
+        ),
+        _launch_args=(sys.executable, str(script)),
     )
     client.start()
     client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
@@ -838,7 +832,7 @@ for line in sys.stdin:
 """.strip()
     )
 
-    client = HarnessClient(HarnessConfig(_launch_args=(sys.executable, str(script))))
+    client = HarnessClient(_launch_args=(sys.executable, str(script)))
     client.start()
     proc = client._proc
     assert proc is not None
@@ -878,6 +872,7 @@ def test_public_signatures_omit_unsupported_wire_parameters() -> None:
     for removed in ("cordis", "session_root", "runtime_bin", "bridge_bin", "launch_args_override"):
         assert removed not in DeepSeekHarnessConfig.__dataclass_fields__
         assert removed not in HarnessConfig.__dataclass_fields__
+    assert "_launch_args" not in HarnessConfig.__dataclass_fields__
     assert "session_root" not in RunResult.__dataclass_fields__
 
 
@@ -900,7 +895,7 @@ for line in sys.stdin:
 """.strip()
     )
 
-    client = HarnessClient(HarnessConfig(_launch_args=(sys.executable, str(script))))
+    client = HarnessClient(_launch_args=(sys.executable, str(script)))
     client.start()
     client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
     client.close()
@@ -920,9 +915,9 @@ sys.exit(42)
 
     with HarnessClient(
         HarnessConfig(
-            _launch_args=(sys.executable, str(script)),
             request_timeout_seconds=2,
-        )
+        ),
+        _launch_args=(sys.executable, str(script)),
     ) as client:
         with pytest.raises(Exception, match="fatal bridge exploded"):
             client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
@@ -952,9 +947,9 @@ with open(os.environ["SEEN"], "w") as seen:
 
     with HarnessClient(
         HarnessConfig(
-            _launch_args=(sys.executable, str(script)),
             env={"SEEN": str(output)},
-        )
+        ),
+        _launch_args=(sys.executable, str(script)),
     ) as client:
         client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
         threads = [

+ 33 - 78
scripts/smoke-python-runtime.py

@@ -221,6 +221,36 @@ def write_profile_patch(
     return path
 
 
+def write_advanced_profile_patch(root: Path, name: str, sessions: Path) -> Path:
+    """Write the shared custom, snapshot, and restart profile patch."""
+    return write_profile_patch(root, name, sessions, [
+        {"id": "tools", "config": {"mode": "both"}},
+        {
+            "id": "system-prompt",
+            "config": {
+                "persona": "You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.",
+            },
+        },
+        {"id": "session-log-deepseek", "config": {"enabled": True}},
+        *({"id": row_id, "disabled": True} for row_id in LEGACY_CUSTOM_DISABLED_ROWS),
+        {"id": "tool-bash", "disabled": True},
+        {"id": "tool-pwsh", "disabled": True},
+        {
+            "id": "tool-subagent",
+            "config": {
+                "provider": "spawn",
+                "toolName": "subagent",
+                "backgroundMode": "one-shot",
+            },
+        },
+        {"insert": [
+            {"id": "code-runtime", "name": "@deepseek-ai/dsh-code-runtime-worker-thread"},
+            {"id": "cordis-host-runner", "name": "@deepseek-ai/dsh-cordis-host-runner"},
+            {"id": "cordis-tool", "name": "@deepseek-ai/dsh-tool-cordis"},
+        ]},
+    ])
+
+
 def write_mcp_patch(root: Path, sessions: Path, server_script: Path) -> Path:
     """Write a profile patch that mounts the packaged MCP client."""
     return write_profile_patch(root, "mcp.patch.yml", sessions, [{
@@ -923,32 +953,7 @@ def smoke_sdk_custom(base_url: str, executable: Path) -> None:
         root = Path(temporary).resolve()
         dsh_home = root / "home"
         sessions = dsh_home / "sessions"
-        patch = write_profile_patch(root, "custom.patch.yml", sessions, [
-            {"id": "tools", "config": {"mode": "both"}},
-            {
-                "id": "system-prompt",
-                "config": {
-                    "persona": "You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.",
-                },
-            },
-            {"id": "session-log-deepseek", "config": {"enabled": True}},
-            *({"id": row_id, "disabled": True} for row_id in LEGACY_CUSTOM_DISABLED_ROWS),
-            {"id": "tool-bash", "disabled": True},
-            {"id": "tool-pwsh", "disabled": True},
-            {
-                "id": "tool-subagent",
-                "config": {
-                    "provider": "spawn",
-                    "toolName": "subagent",
-                    "backgroundMode": "one-shot",
-                },
-            },
-            {"insert": [
-                {"id": "code-runtime", "name": "@deepseek-ai/dsh-code-runtime-worker-thread"},
-                {"id": "cordis-host-runner", "name": "@deepseek-ai/dsh-cordis-host-runner"},
-                {"id": "cordis-tool", "name": "@deepseek-ai/dsh-tool-cordis"},
-            ]},
-        ])
+        patch = write_advanced_profile_patch(root, "custom.patch.yml", sessions)
         with DeepSeekHarness(
             provider="deepseek-official",
             model="smoke-model",
@@ -1173,32 +1178,7 @@ def smoke_sdk_snapshot(base_url: str, executable: Path, update_snapshots: bool)
         root = Path(temporary).resolve()
         dsh_home = root / "home"
         sessions = dsh_home / "sessions"
-        patch = write_profile_patch(root, "snapshot.patch.yml", sessions, [
-            {"id": "tools", "config": {"mode": "both"}},
-            {
-                "id": "system-prompt",
-                "config": {
-                    "persona": "You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.",
-                },
-            },
-            {"id": "session-log-deepseek", "config": {"enabled": True}},
-            *({"id": row_id, "disabled": True} for row_id in LEGACY_CUSTOM_DISABLED_ROWS),
-            {"id": "tool-bash", "disabled": True},
-            {"id": "tool-pwsh", "disabled": True},
-            {
-                "id": "tool-subagent",
-                "config": {
-                    "provider": "spawn",
-                    "toolName": "subagent",
-                    "backgroundMode": "one-shot",
-                },
-            },
-            {"insert": [
-                {"id": "code-runtime", "name": "@deepseek-ai/dsh-code-runtime-worker-thread"},
-                {"id": "cordis-host-runner", "name": "@deepseek-ai/dsh-cordis-host-runner"},
-                {"id": "cordis-tool", "name": "@deepseek-ai/dsh-tool-cordis"},
-            ]},
-        ])
+        patch = write_advanced_profile_patch(root, "snapshot.patch.yml", sessions)
         with DeepSeekHarness(
             provider="deepseek-official",
             model="smoke-model",
@@ -1247,32 +1227,7 @@ def smoke_sdk_restart_snapshot(base_url: str, executable: Path, update_snapshots
         root = Path(temporary).resolve()
         dsh_home = root / "home"
         sessions = dsh_home / "sessions"
-        patch = write_profile_patch(root, "restart.patch.yml", sessions, [
-            {"id": "tools", "config": {"mode": "both"}},
-            {
-                "id": "system-prompt",
-                "config": {
-                    "persona": "You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.",
-                },
-            },
-            {"id": "session-log-deepseek", "config": {"enabled": True}},
-            *({"id": row_id, "disabled": True} for row_id in LEGACY_CUSTOM_DISABLED_ROWS),
-            {"id": "tool-bash", "disabled": True},
-            {"id": "tool-pwsh", "disabled": True},
-            {
-                "id": "tool-subagent",
-                "config": {
-                    "provider": "spawn",
-                    "toolName": "subagent",
-                    "backgroundMode": "one-shot",
-                },
-            },
-            {"insert": [
-                {"id": "code-runtime", "name": "@deepseek-ai/dsh-code-runtime-worker-thread"},
-                {"id": "cordis-host-runner", "name": "@deepseek-ai/dsh-cordis-host-runner"},
-                {"id": "cordis-tool", "name": "@deepseek-ai/dsh-tool-cordis"},
-            ]},
-        ])
+        patch = write_advanced_profile_patch(root, "restart.patch.yml", sessions)
         first_request = len(MockModelHandler.requests)
 
         def run(prompt: str, session_id: str) -> "RunResult":