Browse Source

Merge pull request #2948 from deepseek-harness/worktree/unify-dsh-launch-profiles-reviewable-20260823

feat: unify application launch under dsh profiles
Tianyi Cui 1 tháng trước cách đây
mục cha
commit
92f8fb6c4a
100 tập tin đã thay đổi với 1650 bổ sung và 1285 xóa
  1. 2 2
      .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml
  2. 3 3
      .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md
  3. 3 3
      .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md
  4. 2 2
      .agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.i18n.yaml
  5. 1 1
      .agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.md
  6. 1 1
      .agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.zh.md
  7. 2 2
      .agents/notes/implemented/architecture/2026-08-11-repository-naming-contract-and-rename-ledger.i18n.yaml
  8. 6 2
      .agents/notes/implemented/architecture/2026-08-11-repository-naming-contract-and-rename-ledger.md
  9. 6 2
      .agents/notes/implemented/architecture/2026-08-11-repository-naming-contract-and-rename-ledger.zh.md
  10. 6 0
      .agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml
  11. 101 0
      .agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md
  12. 101 0
      .agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md
  13. 2 2
      .agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.i18n.yaml
  14. 1 1
      .agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.md
  15. 1 1
      .agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.zh.md
  16. 2 2
      .agents/notes/implemented/feature/2026-07-06-approval-seam.i18n.yaml
  17. 1 1
      .agents/notes/implemented/feature/2026-07-06-approval-seam.md
  18. 1 1
      .agents/notes/implemented/feature/2026-07-06-approval-seam.zh.md
  19. 2 2
      .agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.i18n.yaml
  20. 1 1
      .agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md
  21. 1 1
      .agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md
  22. 2 2
      .agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.i18n.yaml
  23. 7 7
      .agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.md
  24. 7 7
      .agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.zh.md
  25. 2 2
      .agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.i18n.yaml
  26. 2 2
      .agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md
  27. 2 2
      .agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md
  28. 2 2
      .agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.i18n.yaml
  29. 1 1
      .agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.md
  30. 1 1
      .agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.zh.md
  31. 2 2
      .agents/notes/implemented/testing/2026-08-13-python-minimal-model-visible-snapshot.i18n.yaml
  32. 1 1
      .agents/notes/implemented/testing/2026-08-13-python-minimal-model-visible-snapshot.md
  33. 1 1
      .agents/notes/implemented/testing/2026-08-13-python-minimal-model-visible-snapshot.zh.md
  34. 4 3
      .github/workflows/e2e.yml
  35. 10 10
      AGENTS.md
  36. 2 2
      apps/cli/README.i18n.yaml
  37. 6 4
      apps/cli/README.md
  38. 6 4
      apps/cli/README.zh.md
  39. 5 0
      apps/cli/package.json
  40. 2 2
      apps/cli/reference/README.i18n.yaml
  41. 7 5
      apps/cli/reference/README.md
  42. 7 5
      apps/cli/reference/README.zh.md
  43. 6 1
      apps/cli/src/plugin.ts
  44. 47 18
      apps/cli/src/profile-boot.ts
  45. 5 0
      apps/cli/src/sdk-source.cordis.patch.yml
  46. 188 0
      apps/cli/tests/built-bin.e2e.ts
  47. 42 0
      apps/cli/tests/profile-hmr.spec.ts
  48. 1 1
      apps/web/tests/snapshots/bash-abort-row/ui.expected.md
  49. 1 1
      apps/web/tests/snapshots/skill-tool-row/ui.expected.md
  50. 2 2
      docs/architecture.i18n.yaml
  51. 12 2
      docs/architecture.md
  52. 12 2
      docs/architecture.zh.md
  53. 2 2
      docs/config-catalog.i18n.yaml
  54. 13 63
      docs/config-catalog.md
  55. 13 63
      docs/config-catalog.zh.md
  56. 2 2
      docs/cookbook/extension-cookbook.i18n.yaml
  57. 1 1
      docs/cookbook/extension-cookbook.md
  58. 1 1
      docs/cookbook/extension-cookbook.zh.md
  59. 2 2
      docs/module-graph.i18n.yaml
  60. 9 15
      docs/module-graph.md
  61. 9 15
      docs/module-graph.zh.md
  62. 2 2
      docs/testing.i18n.yaml
  63. 1 1
      docs/testing.md
  64. 1 1
      docs/testing.zh.md
  65. 2 2
      docs/user/guide/python-sdk.i18n.yaml
  66. 3 3
      docs/user/guide/python-sdk.md
  67. 3 3
      docs/user/guide/python-sdk.zh.md
  68. 2 2
      examples/README.i18n.yaml
  69. 2 2
      examples/README.md
  70. 2 2
      examples/README.zh.md
  71. 2 2
      examples/acp-agent/README.i18n.yaml
  72. 2 2
      examples/acp-agent/README.md
  73. 2 2
      examples/acp-agent/README.zh.md
  74. 48 37
      examples/acp-agent/advanced.cordis.snapshot.yml
  75. 35 25
      examples/acp-agent/advanced.cordis.yml
  76. 44 37
      examples/acp-agent/agent-instructions.cordis.snapshot.yml
  77. 27 22
      examples/acp-agent/agent-instructions.cordis.yml
  78. 42 31
      examples/acp-agent/background-job-admission.cordis.snapshot.yml
  79. 27 18
      examples/acp-agent/background-job-admission.cordis.yml
  80. 46 36
      examples/acp-agent/both-mode.cordis.snapshot.yml
  81. 33 24
      examples/acp-agent/both-mode.cordis.yml
  82. 56 46
      examples/acp-agent/child-question.cordis.snapshot.yml
  83. 8 12
      examples/acp-agent/child-question.cordis.yml
  84. 52 40
      examples/acp-agent/code-mode-image.cordis.snapshot.yml
  85. 34 23
      examples/acp-agent/code-mode-image.cordis.yml
  86. 44 33
      examples/acp-agent/code-mode-workspace-context.cordis.snapshot.yml
  87. 31 21
      examples/acp-agent/code-mode-workspace-context.cordis.yml
  88. 46 36
      examples/acp-agent/code-mode.cordis.snapshot.yml
  89. 33 25
      examples/acp-agent/code-mode.cordis.yml
  90. 15 79
      examples/acp-agent/composition.md
  91. 5 10
      examples/acp-agent/cordis-tools.cordis.yml
  92. 42 59
      examples/acp-agent/cordis.snapshot.yml
  93. 27 148
      examples/acp-agent/cordis.yml
  94. 56 49
      examples/acp-agent/depth-two.cordis.snapshot.yml
  95. 6 11
      examples/acp-agent/depth-two.cordis.yml
  96. 51 44
      examples/acp-agent/fs.cordis.snapshot.yml
  97. 10 14
      examples/acp-agent/fs.cordis.yml
  98. 42 33
      examples/acp-agent/image-text-route.cordis.snapshot.yml
  99. 27 23
      examples/acp-agent/image-text-route.cordis.yml
  100. 46 39
      examples/acp-agent/image.cordis.snapshot.yml

+ 2 - 2
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.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-07-10-single-file-executable-sdk-runtime-distribution.md
-2026-07-10-single-file-executable-sdk-runtime-distribution.md: 40433d99e5d1aa569c3fdf094a280d3de62ad588
-2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: cff72ae10eb82c65c499123cc559cc6ad7e440ab
+2026-07-10-single-file-executable-sdk-runtime-distribution.md: 24d95be871b2cb06e07706e6d5e0a628e30ef1bd
+2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: eb9ba37c28e89a894ae7f5132592aa98fa56b977

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 3 - 3
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 3 - 3
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md


+ 2 - 2
.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.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-09-headless-direct-core-entry-point.md
-2026-08-09-headless-direct-core-entry-point.md: cf6b4a92a6e9b390c7fcaca17f56b4c652cc9319
-2026-08-09-headless-direct-core-entry-point.zh.md: 9f45fcdaf87ddcccbd331eeacce0dc7035c61f19
+2026-08-09-headless-direct-core-entry-point.md: 8ed979794afa008588d1b849f0074e8696e6e43f
+2026-08-09-headless-direct-core-entry-point.zh.md: 512d4b88c921431fe26afd9f62c34a1939ac5bdd

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.md

@@ -12,7 +12,7 @@ The direct entry point still needs the same deployment model state as Web-create
 
 ## Decision
 
-The shipped `headless` profile contains `dsh-base` and `dsh-headless`. The headless bundle supplies its persona and tool mode, disables HMR, mounts the Code Mode worker explicitly, and inserts `headless-runner`. Its tree contains no `@deepseek-ai/dsh-host-*` package, ApiProxy, HTTP server, Web runtime, or browser client. Code Mode and Session persistence are one-shot Agent capabilities independent of Web presentation.
+The shipped `headless` profile contains `dsh-base` and `dsh-headless`. The base supplies the disabled module-HMR default; the headless bundle supplies its persona and tool mode, mounts the Code Mode worker explicitly, and inserts `headless-runner` without overriding that policy. Its tree contains no `@deepseek-ai/dsh-host-*` package, ApiProxy, HTTP server, Web runtime, or browser client. Code Mode and Session persistence are one-shot Agent capabilities independent of Web presentation.
 
 `headless-runner` is a direct core entry point. After Loader settlement, it reads `ctx.agentDefaultModel.currentSelection()`, creates a fresh persisted Agent through `ctx.agents.create`, installs that `ModelSelection` in the Agent scope, waits for startup quiescence, anchors the Session sequence, submits one ordinary user message, and waits for quiescence again. It awaits `ctx.sessions.flush`, folds its durable event interval for the last non-empty assistant text and final `turn/end` reason, writes the text plus one newline to stdout, and requests bounded launcher shutdown with exit 0 exactly when the reason is `completed`. A terminal `error` reason writes its durable code and message to stderr; unexpected driver failures also use stderr and exit 1.
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.zh.md

@@ -12,7 +12,7 @@ Status: implemented
 
 ## 决策
 
-随附的 `headless` profile 包含 `dsh-base` 与 `dsh-headless`。headless 组合包提供自身的 persona 与工具模式、禁用 HMR(热模块替换)、显式挂载 Code Mode worker,并插入 `headless-runner`。其插件树不包含任何 `@deepseek-ai/dsh-host-*` 包、ApiProxy、HTTP server、Web 运行时或浏览器客户端。Code Mode 与会话持久化均为独立于 Web 呈现的一次性 Agent 能力。
+随附的 `headless` profile 包含 `dsh-base` 与 `dsh-headless`。base 提供默认禁用模块 HMR(热模块替换)的策略;headless 组合包提供自身的 persona 与工具模式、显式挂载 Code Mode worker,并在不覆盖该策略的情况下插入 `headless-runner`。其插件树不包含任何 `@deepseek-ai/dsh-host-*` 包、ApiProxy、HTTP server、Web 运行时或浏览器客户端。Code Mode 与会话持久化均为独立于 Web 呈现的一次性 Agent 能力。
 
 `headless-runner` 是直接使用核心服务的入口。Loader 完全加载后,它读取 `ctx.agentDefaultModel.currentSelection()`,通过 `ctx.agents.create` 创建一个新的持久化 Agent,在 Agent 作用域中安装该 `ModelSelection`,等待启动工作完全停稳,锚定会话事件序号,提交一条普通用户消息,再次等待完全停稳。随后,它等待 `ctx.sessions.flush`,折叠自身持有的持久事件区间,以取得最后一条非空 assistant 文本和最终 `turn/end` 结束原因,将文本连同一个换行写入 stdout,并且仅在结束原因为 `completed` 时请求启动器以退出状态 0 有界关闭。结束原因为 `error` 时,其持久化错误码与消息写入 stderr;驱动器的意外失败也写入 stderr 并以 1 退出。
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-11-repository-naming-contract-and-rename-ledger.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-11-repository-naming-contract-and-rename-ledger.md
-2026-08-11-repository-naming-contract-and-rename-ledger.md: bf69f40884d8fee69cede839cd9ceb7c0d926c38
-2026-08-11-repository-naming-contract-and-rename-ledger.zh.md: 373bafaaa5590ccac7d33ba9ac27ce29f6dbefe5
+2026-08-11-repository-naming-contract-and-rename-ledger.md: 2de88df5f1a5037d32daa9a8ee9cd1edfc60528a
+2026-08-11-repository-naming-contract-and-rename-ledger.zh.md: 0cbbbee561358614da0b20e49c610fbee1d4e537

+ 6 - 2
.agents/notes/implemented/architecture/2026-08-11-repository-naming-contract-and-rename-ledger.md

@@ -274,10 +274,14 @@ Keep MCP, Todo, and the Plan Mode package, key, events, and tool names. This dec
 | `E2BSandboxService` | `E2BRuntime` | The class creates, reuses, and disposes the E2B execution environment used by filesystem and subprocess adapters. It is broader than one sandbox handle and narrower than a generic owner. Keep `@deepseek-ai/dsh-e2b`, `ctx.e2b`, and the `e2b/` group. |
 | `@deepseek-ai/dsh-frontend-static` | `@deepseek-ai/dsh-host-frontend-static` | The package is the Host plugin that serves the frontend assets. The prefix distinguishes it from frontend application code. |
 | `PluginInventoryService` | `PluginInventoryGateway` | The class is a Remote-only adapter from the live Loader tree to the `pluginInventory/list` RPC. It owns no same-process service, cache, history, or mutation path. `Gateway` states the role that exists. |
-| `@deepseek-ai/dsh-jsonrpc-demo` | `@deepseek-ai/dsh-sdk-jsonrpc-demo` | The example demonstrates the runtime SDK over JSON-RPC. It belongs to the one SDK meaning. |
+| `@deepseek-ai/dsh-jsonrpc-demo`, `@deepseek-ai/dsh-sdk-jsonrpc-demo` | `@deepseek-ai/dsh-sdk-python-runtime` | The private package carries only the temporarily separate Python SDK runtime; the [single dsh launcher decision](2026-08-22-single-dsh-application-launcher.md) owns its application-boundary change. |
+| `packages/examples/jsonrpc-demo/` | `packages/sdk/python-runtime/` | The carrier is production packaging infrastructure for the Python SDK, not a demo bundle. |
+| `examples/jsonrpc-agent/` | `examples/python-sdk-agent/` | The direct-config runnable example belongs specifically to the Python SDK exception. |
+| `@deepseek-ai/dsh-acp-demo` | `@deepseek-ai/dsh-acp-app` | The package is the ACP profile's application bundle, not a standalone demo bin. |
+| Deploy-root manifest `dsh-jsonrpc-agent-pkg` | `dsh-sdk-python-runtime-closure` | The manifest defines the private Python runtime dependency closure. The Python-visible executable basename remains fixed until its documented profile migration. |
 | `@deepseek-ai/dsh-frontend` | `@deepseek-ai/dsh-web-frontend` | The application is the web frontend. Keep its physical `apps/web/` folder. |
 
-Keep atomic-write, brand, native-command, timeout utility, directory-picker, `dsh-base`, `dsh-web-app`, app boot, CLI names, and the `headless` package, bundle, and example identity. `headless` is the intended product essence and may later support more than one-shot execution.
+Keep atomic-write, brand, native-command, timeout utility, directory-picker, `dsh-base`, `dsh-web-app`, `dsh-sdk-app`, `dsh-acp-app`, app boot, CLI names, and the `headless` package, bundle, and example identity. `headless` is the intended product essence and may later support more than one-shot execution.
 
 ### Client runtime and UI
 

+ 6 - 2
.agents/notes/implemented/architecture/2026-08-11-repository-naming-contract-and-rename-ledger.zh.md

@@ -274,10 +274,14 @@ PascalCase 标识符中的首字母缩略词使用首字母大写格式:`Ui`
 | `E2BSandboxService` | `E2BRuntime` | 该类创建、复用和释放文件系统与子进程适配器所使用的 E2B 执行环境。它比单个沙箱句柄的职责更广,又比通用所有者更具体。保留 `@deepseek-ai/dsh-e2b`、`ctx.e2b` 和 `e2b/` 组。 |
 | `@deepseek-ai/dsh-frontend-static` | `@deepseek-ai/dsh-host-frontend-static` | 该包是提供前端资源的 Host 插件。此前缀可将它与前端应用代码区分开。 |
 | `PluginInventoryService` | `PluginInventoryGateway` | 该类只负责把实时 Loader 树适配到 `pluginInventory/list` RPC。它不拥有同进程服务、缓存、历史或修改路径。`Gateway` 准确说明现有角色。 |
-| `@deepseek-ai/dsh-jsonrpc-demo` | `@deepseek-ai/dsh-sdk-jsonrpc-demo` | 该示例演示通过 JSON-RPC 使用运行时 SDK,属于 SDK 的唯一含义。 |
+| `@deepseek-ai/dsh-jsonrpc-demo`、`@deepseek-ai/dsh-sdk-jsonrpc-demo` | `@deepseek-ai/dsh-sdk-python-runtime` | 该私有包只承载暂时独立的 Python SDK 运行时;其应用边界变更由[单一 dsh 启动器决策](2026-08-22-single-dsh-application-launcher.zh.md)负责。 |
+| `packages/examples/jsonrpc-demo/` | `packages/sdk/python-runtime/` | 该载体是 Python SDK 的生产打包基础设施,不是演示组合包。 |
+| `examples/jsonrpc-agent/` | `examples/python-sdk-agent/` | 直读配置的可运行示例专属于 Python SDK 例外。 |
+| `@deepseek-ai/dsh-acp-demo` | `@deepseek-ai/dsh-acp-app` | 该包是 ACP profile 的应用组合包,不是独立 demo bin。 |
+| 部署根 manifest `dsh-jsonrpc-agent-pkg` | `dsh-sdk-python-runtime-closure` | 该 manifest 定义私有 Python 运行时依赖闭包。面向 Python 的可执行文件基本名称保持不变,直至完成已记录的 profile 迁移。 |
 | `@deepseek-ai/dsh-frontend` | `@deepseek-ai/dsh-web-frontend` | 该应用是 Web 前端。保留其物理目录 `apps/web/`。 |
 
-保留 atomic-write、brand、native-command、timeout 实用工具、目录选择器、`dsh-base`、`dsh-web-app`、应用启动、CLI(命令行界面)名称,以及 `headless` 包、组合包和示例身份。`headless` 是预期的产品本质,未来也可以支持不止一次性执行。
+保留 atomic-write、brand、native-command、timeout 实用工具、目录选择器、`dsh-base`、`dsh-web-app`、`dsh-sdk-app`、`dsh-acp-app`、应用启动、CLI(命令行界面)名称,以及 `headless` 包、组合包和示例身份。`headless` 是预期的产品本质,未来也可以支持不止一次性执行。
 
 ### 客户端运行时与 UI
 

+ 6 - 0
.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml

@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# 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-22-single-dsh-application-launcher.md
+2026-08-22-single-dsh-application-launcher.md: 69188e806d9192d230d1f1b52daf27a3b30481be
+2026-08-22-single-dsh-application-launcher.zh.md: a6bb1909b019ea0d386bd2f4a1bb8f5199ef1974

+ 101 - 0
.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md

@@ -0,0 +1,101 @@
+# Agent Note: One dsh launcher for application profiles
+
+Status: implemented
+
+English | [中文](2026-08-22-single-dsh-application-launcher.zh.md)
+
+## Problem
+
+DeepSeek Harness application processes need one owner for composition, plugin resolution, environment discovery, shutdown, and user customization. A dedicated app bin with a complete `cordis.yml` creates a second lifecycle beside profile launch: plugins installed into a profile do not reach it, behavior drifts from `dsh-base`, and SDK callers learn arbitrary process argv instead of the product's composition model.
+
+The Python SDK distributes a native executable and three platform wheels whose embedded direct-config runtime cannot change launch architecture without rebuilding and validating the complete VFS closure. That distribution needs an explicit temporary exception, not a second general Node application pattern.
+
+## Decision
+
+### Launch scope
+
+Every supported Node application starts through the `dsh` CLI and one named profile. The shipped application commands are `dsh web`, `dsh --profile headless`, `dsh --profile sdk`, and `dsh --profile acp`; `dsh web` is the deliberate convenience alias for `--profile web`, not another application entry.
+
+Vendor CLIs, build-only and test-only executables, direct in-process plugin mounting, and the private browser WebWorker preview are outside the application-launch inventory. A package app bin or root demo that launches a package entry is not an accepted extension point.
+
+### Profile applications
+
+`@deepseek-ai/dsh-sdk-app` and `@deepseek-ai/dsh-acp-app` compose the protocol applications over `@deepseek-ai/dsh-base`. The SDK bundle adds the JSON-RPC server plus app-owned help and stdio lifetime; the ACP bundle adds the automation-only ACP server plus the same application responsibilities. Both adopt the base model, tools, persistence, settings, credentials, policy, and environment behavior.
+
+Profile manifests own patch reload:
+
+| Profile | `patchReload` |
+|---|---|
+| `web` | `live` |
+| `headless` | `startup` |
+| `sdk` | `startup` |
+| `acp` | `startup` |
+
+Custom profiles default to `live`. A startup profile still applies its bundle, profile, home-level, and invocation `--patch` layers, but it does not watch them after boot. `dsh-base` inserts the module-HMR row disabled; a profile with a tested source-module reload lifecycle must enable it explicitly. None of the shipped profiles enable server module HMR: `patchReload: live` uses the launcher's config-only watcher while the startup profiles install no watcher. SDK and ACP cannot safely replace their server, agents, persistence, or tool registry inside one owned stdio connection.
+
+The shipped protocol profiles reserve stdout for protocol frames, expose help without starting transport, and route stdin EOF and signals through bounded root disposal. ACP remains automation-only. The SDK JSON-RPC methods, notification fields, and `initialize.serverInfo.name` remain stable. Model-visible tool and persistence defaults come from `dsh-base`, and runnable snapshots own those assembled application outputs.
+
+### TypeScript SDK customization
+
+`@deepseek-ai/dsh-sdk-client` depends on the same-version `@deepseek-ai/dsh` package, resolves its installed CLI module, runs it through the current Node executable, and selects `sdk` by default. Both client layers expose `dshBin`, `profile`, ordered `patches`, `dshHome`, process cwd, environment, and timeouts; arbitrary command/argv launch remains an internal fake-runtime adapter.
+
+SDK users customize plugins through profiles. `dsh plugin --profile <name> ...` manages persistent dependencies and bundle order, the profile's `cordis.patch.yml` owns persistent row changes, and launch `patches` supply ordered ephemeral overrides. A custom profile must retain `@deepseek-ai/dsh-sdk-app` or another SDK server row. Relative CLI-module, patch, explicit home, and process-cwd paths become absolute before spawn, and initialization has a finite bound whose diagnostic names the selected profile.
+
+Direct SDK use follows normal Harness-home resolution: explicit `dshHome`, inherited `DSH_HOME`, then `~/.dsh`. `subagent-dsh-sdk` instead requires an explicit absolute home, so a nested runtime cannot discover a person's profiles, installed plugins, credentials, or sessions through the operating-system home. DSH-specific ACP child examples also pass an isolated home; the ACP backend itself remains generic for non-DSH agents.
+
+### Python exception and names
+
+The Python SDK's direct-config application lives in the private `packages/sdk/python-runtime` package named `@deepseek-ai/dsh-sdk-python-runtime`. Its only packaged executable entry is `lib/packaged-bin.js`, consumed by the private `dsh-sdk-python-runtime-closure` deploy root. It has no public npm bin. The runnable direct Python example is `examples/python-sdk-agent`.
+
+Python-observable behavior remains fixed: Python API, SDK wire, default `cordis.yml`, environment variables, wheel distribution names, packaged executable names, sidecar names, explicit runtime options, zero-config behavior, and supported platforms. The stable SDK family remains `@deepseek-ai/dsh-sdk-client`, `@deepseek-ai/dsh-sdk-protocol`, `@deepseek-ai/dsh-sdk-jsonrpc-server`, and wire identity `deepseek-harness-sdk-runtime`; `@deepseek-ai/dsh-acp` remains the ACP protocol plugin. There is no compatibility package, forwarding executable, fallback parser, or SDK/ACP launcher alias.
+
+### Enforcement
+
+`verify-application-entrypoints` scans application/package manifests, executable sources, and root demo scripts. The allowlist classifies the `dsh` product bin, vendor-excluded scope, the private WebWorker build tool, test support, and the private Python carrier. An unclassified shebang, a new package bin, or a demo wrapper that bypasses `apps/cli/src/bin.ts` fails hygiene and the primary/static CI aggregates.
+
+## Deferred Python migration
+
+The Python runtime follow-up must move the packaged process through `dsh --profile sdk`, preserve the wheel's closed dependency and native sidecar behavior, and delete `@deepseek-ai/dsh-sdk-python-runtime`. Only after those conditions pass on Linux x64, Linux arm64, and macOS arm64 does the executable family change from `dsh-jsonrpc-agent-pkg-<platform>-<arch>` to `deepseek-harness-sdk-runtime-<platform>-<arch>`. The temporary carrier and current artifact names make that obligation visible without weakening current Python compatibility.
+
+## Existing decisions and supersession
+
+This decision supersedes the application-launch and package-name facts in [profile plugin bundles](2026-08-05-profile-plugin-bundles.md), [TypeScript SDK client and subagent backend](../feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.md), [remove the SDK project toolchain](../simplification/2026-08-11-remove-sdk-project-toolchain.md), and [single-file Python SDK runtime distribution](2026-07-10-single-file-executable-sdk-runtime-distribution.md). Those notes retain independent authority for profile layering, client/wire semantics, deleted project tooling, and native packaging.
+
+The [ACP automation-only protocol](../simplification/2026-07-23-acp-automation-only-protocol.md) remains authoritative for ACP wire and interaction scope. The [repository naming contract](2026-08-11-repository-naming-contract-and-rename-ledger.md) remains authoritative for role-based package names. No active note is fully superseded or eligible for archival.
+
+## Alternatives considered
+
+**Keep direct bins and state that profiles are preferred.** Rejected: documentation cannot make profiles own plugin installation, environment loading, shutdown, and tests while a supported executable bypasses them.
+
+**Keep forwarding compatibility bins.** Rejected: a forwarding executable remains another public launch name and compatibility promise. The pre-release repository can move callers directly to profiles.
+
+**Put complete standalone Cordis trees behind profile wrappers.** Rejected: that centralizes argv without centralizing application composition. `dsh-base` plus thin app bundles gives shared policy one owner while retaining protocol-specific negative guarantees.
+
+**Accept inline plugins or a complete `cordis.yml` in the TypeScript constructor.** Rejected: the SDK would become another package installer and application composer. Named profiles and patch files already provide persistent and per-launch customization through one resolution model.
+
+**Resolve `dsh` only from `PATH`.** Rejected: ordinary Node processes do not reliably inherit a project-local `.bin` path. A same-version package dependency provides a deterministic runtime.
+
+**Enable module HMR in `dsh-base` and make unsafe profiles disable it.** Rejected: the shared base also underlies custom profiles, so an enabled default makes every new application remember to opt out of source-module replacement. A disabled base makes module HMR an explicit profile capability while leaving `patchReload: live` config watching available.
+
+**Hot-reload protocol profiles.** Rejected: replacing a protocol server or its dependencies can invalidate pending frames and SDK-owned agents. Process restart is the adoption boundary for SDK and ACP configuration changes.
+
+**Move the Python executable through profiles without a separate packaging proof.** Rejected: the native VFS closure, three platform wheels, ripgrep and spawn-helper sidecars, default config discovery, and clean-install behavior require their own migration evidence.
+
+## Verification
+
+- Source and built CLI acceptance cover `sdk` and `acp` help, transport startup, stdout purity, EOF, signals, and root disposal.
+- Bundle configuration tests pin module HMR disabled in `dsh-base` and absent from shipped mode overrides; the custom live-profile e2e pins config reload through the launcher's watch-only fallback.
+- Focused unit suites cover profile launch resolution, initialization bounds, SDK retries, server readiness, and nested isolated homes with 100% coverage on the changed runtime sources.
+- Keyless ACP and SDK snapshots boot real `dsh` profiles and pin protocol output plus persisted logs; the nested SDK composition boots a second real profile runtime.
+- The real-API workflow caps file parallelism at four because one profile e2e file can own several complete `dsh` subprocess trees; workflow tests pin that resource bound.
+- The Python suite exercises exe and node carriers; all packaged-runtime scenarios, native macOS executable construction, both wheels, and clean-wheel default/MCP smokes retain the existing artifact names.
+- `verify-application-entrypoints` includes invalid fixtures for package bins, executable sources, package-launching demo wrappers, and unclassified demos.
+
+## Consequences
+
+- A user changes an SDK application's plugin composition through a named profile and ordered patches, using the same installation and resolution model as every other dsh application.
+- A custom profile receives live config watching without server module HMR and opts into source-module replacement only through an explicit row override.
+- SDK and ACP share the complete base application and one set of policy and tools; snapshots present intentional assembled differences explicitly.
+- Adding `@deepseek-ai/dsh` increases the TypeScript client's install size in exchange for a deterministic same-version runtime.
+- Trusted user patches can add a plugin that writes to stdout and corrupt their own protocol stream; shipped profiles guarantee purity, not arbitrary third-party composition.
+- Python keeps a visibly private, narrowly allowed direct-config carrier until its platform artifact migration is independently proven.

+ 101 - 0
.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md

@@ -0,0 +1,101 @@
+# Agent Note: 由一个 dsh 启动应用 profile
+
+Status: implemented
+
+[English](2026-08-22-single-dsh-application-launcher.md) | 中文
+
+## Problem
+
+DeepSeek Harness 应用进程需要由同一个机制负责组合、插件解析、环境发现、关闭和用户自定义。带完整 `cordis.yml` 的专用应用 bin 会在 profile 启动之外形成第二套生命周期:安装到 profile 的插件无法到达它,行为会与 `dsh-base` 偏离,SDK 调用方还需要学习任意进程 argv,而不是产品的组合模型。
+
+Python SDK 分发一个原生可执行文件和三个平台 wheel 包;其中嵌入的直读配置运行时只有在重建并验证完整 VFS 闭包后才能改变启动架构。该分发需要一个明确的临时例外,而不是另一种通用 Node 应用模式。
+
+## Decision
+
+### 启动范围
+
+所有受支持的 Node 应用都通过 `dsh` CLI 与一个具名 profile 启动。随附应用命令是 `dsh web`、`dsh --profile headless`、`dsh --profile sdk` 与 `dsh --profile acp`;`dsh web` 是刻意为 `--profile web` 保留的便捷别名,不是另一个应用入口。
+
+Vendor CLI、仅用于构建和测试的可执行文件、进程内直接挂载插件以及私有浏览器 WebWorker 预览都不属于应用启动清单。包应用 bin 或直接启动包入口的根 demo 都不是可接受的扩展点。
+
+### Profile 应用
+
+`@deepseek-ai/dsh-sdk-app` 与 `@deepseek-ai/dsh-acp-app` 在 `@deepseek-ai/dsh-base` 之上组合协议应用。SDK 组合包增加 JSON-RPC 服务器、应用自有帮助和 stdio 生命周期;ACP 组合包增加仅用于自动化的 ACP 服务器与相同的应用职责。两者都采用 base 层的模型、工具、持久化、settings、credentials、策略和环境行为。
+
+Profile manifest 负责 patch 重载:
+
+| Profile | `patchReload` |
+|---|---|
+| `web` | `live` |
+| `headless` | `startup` |
+| `sdk` | `startup` |
+| `acp` | `startup` |
+
+自定义 profile 默认为 `live`。`startup` profile 仍会应用组合包、profile、home 级与调用时 `--patch` 各层,但启动后不会监视这些文件。`dsh-base` 插入的模块 HMR(热模块替换)配置项默认禁用;具有经过验证的源码模块重载生命周期的 profile 必须显式启用它。随附 profile 均不启用服务器模块 HMR:`patchReload: live` 使用启动器的仅配置 watcher,`startup` profile 则不安装 watcher。SDK 与 ACP 无法在一个自有 stdio 连接内安全替换其服务器、agent、持久化或工具注册表。
+
+随附协议 profile 将 stdout 保留给协议帧,显示帮助时不启动 transport,并通过有界根节点 dispose(资源释放)处理 stdin EOF 与信号。ACP 继续仅用于自动化。SDK JSON-RPC 方法、通知字段与 `initialize.serverInfo.name` 保持稳定。模型可见工具与持久化默认值来自 `dsh-base`,可运行快照负责钉住这些已组装的应用输出。
+
+### TypeScript SDK 自定义
+
+`@deepseek-ai/dsh-sdk-client` 依赖同版本的 `@deepseek-ai/dsh` 包,解析其已安装 CLI 模块,通过当前 Node 可执行文件运行该模块,并默认选择 `sdk`。两层客户端都暴露 `dshBin`、`profile`、有序 `patches`、`dshHome`、进程 cwd、环境和超时;任意 command/argv 启动只保留为 fake-runtime 测试的内部适配器。
+
+SDK 用户通过 profile 自定义插件。`dsh plugin --profile <name> ...` 管理持久依赖与组合包顺序,profile 的 `cordis.patch.yml` 负责持久配置项变更,启动时 `patches` 提供有序临时覆盖。自定义 profile 必须保留 `@deepseek-ai/dsh-sdk-app` 或另一个 SDK 服务器配置项。相对 CLI 模块、patch、显式 home 与进程 cwd 路径会在 spawn 前变为绝对路径;初始化具有有限时限,诊断会写明所选 profile。
+
+直接使用 SDK 时遵循普通 Harness home 解析:显式 `dshHome`、继承的 `DSH_HOME`,最后是 `~/.dsh`。`subagent-dsh-sdk` 则要求显式绝对 home,因此嵌套运行时不会通过操作系统 home 发现个人 profile、已安装插件、凭据或会话。DSH 专用 ACP 子进程示例同样传入隔离 home;ACP 后端自身继续适用于非 DSH agent。
+
+### Python 例外与命名
+
+Python SDK 的直读配置应用位于私有 `packages/sdk/python-runtime` 包,名称是 `@deepseek-ai/dsh-sdk-python-runtime`。它唯一的打包可执行入口是 `lib/packaged-bin.js`,由私有 `dsh-sdk-python-runtime-closure` 部署根消费。它没有公开 npm bin。可运行的直启 Python 示例是 `examples/python-sdk-agent`。
+
+Python 可观察行为保持不变:Python API、SDK 协议格式、默认 `cordis.yml`、环境变量、wheel 包分发名称、打包可执行文件名称、伴随文件名称、显式运行时选项、零配置行为与支持平台。稳定 SDK 包族继续是 `@deepseek-ai/dsh-sdk-client`、`@deepseek-ai/dsh-sdk-protocol`、`@deepseek-ai/dsh-sdk-jsonrpc-server`,协议 identity 继续是 `deepseek-harness-sdk-runtime`;`@deepseek-ai/dsh-acp` 继续作为 ACP 协议插件。仓库不保留兼容包、转发可执行文件、后备解析器或 SDK/ACP 启动别名。
+
+### 强制校验
+
+`verify-application-entrypoints` 扫描应用/包 manifest、可执行源码和根 demo 脚本。允许清单对 `dsh` 产品 bin、排除的 vendor 范围、私有 WebWorker 构建工具、测试支持以及私有 Python 载体进行分类。未分类的 shebang、新包 bin 或绕过 `apps/cli/src/bin.ts` 的 demo wrapper 都会使 hygiene 与 primary/static CI 聚合失败。
+
+## 暂缓的 Python 迁移
+
+Python 运行时后续工作必须把打包进程迁移到 `dsh --profile sdk`,保持 wheel 包的封闭依赖与原生伴随文件行为,并删除 `@deepseek-ai/dsh-sdk-python-runtime`。只有这些条件在 Linux x64、Linux arm64 与 macOS arm64 全部通过后,可执行文件族才会从 `dsh-jsonrpc-agent-pkg-<platform>-<arch>` 改名为 `deepseek-harness-sdk-runtime-<platform>-<arch>`。临时载体与当前产物名称使这项义务清晰可见,同时不削弱当前 Python 兼容性。
+
+## 既有决策与取代关系
+
+本决策取代 [profile 插件组合包](2026-08-05-profile-plugin-bundles.zh.md)、[TypeScript SDK 客户端与 SDK subagent 后端](../feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.zh.md)、[移除 SDK 项目工具链](../simplification/2026-08-11-remove-sdk-project-toolchain.zh.md)和[单文件 Python SDK 运行时分发](2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md)中的应用启动与包名事实。这些 Note 对 profile 分层、客户端/协议语义、已删除的项目工具链与原生打包仍分别具有独立权威。
+
+[ACP 仅自动化协议](../simplification/2026-07-23-acp-automation-only-protocol.zh.md)继续负责 ACP 协议格式与交互范围。[仓库命名约定](2026-08-11-repository-naming-contract-and-rename-ledger.zh.md)继续负责基于角色的包名。没有任何活跃 Note 被完全取代,也没有 Note 符合归档条件。
+
+## 考虑过的替代方案
+
+**保留直启 bin,只声明推荐 profile。** 拒绝:只要受支持的可执行文件仍然绕过 profile,文档就无法让 profile 真正负责插件安装、环境加载、关闭和测试。
+
+**保留转发兼容 bin。** 拒绝:转发可执行文件仍然形成另一个公开启动名称与兼容承诺。预发布仓库可以让调用方直接迁移到 profile。
+
+**把完整独立 Cordis 树放到 profile wrapper 后面。** 拒绝:这只集中 argv,没有集中应用组合。`dsh-base` 加轻量应用组合包让共享策略只有一个归属,同时保留协议专属的负面保证。
+
+**在 TypeScript 构造函数中接受内联插件或完整 `cordis.yml`。** 拒绝:SDK 会因此成为另一个包安装器和应用组合器。具名 profile 与 patch 文件已通过统一解析模型提供持久与逐次启动自定义。
+
+**只从 `PATH` 解析 `dsh`。** 拒绝:普通 Node 进程不一定继承项目本地 `.bin` 路径。同版本包依赖可以提供确定的运行时。
+
+**在 `dsh-base` 中启用模块 HMR,再由不安全的 profile 逐一禁用。** 拒绝:共享 base 同样承载自定义 profile;默认启用会要求每个新应用都记得退出源码模块替换。base 默认禁用会让模块 HMR 成为显式的 profile 能力,同时保留 `patchReload: live` 配置监视。
+
+**热重载协议 profile。** 拒绝:替换协议服务器或其依赖可能破坏待处理协议帧与 SDK 自有 agent。进程重启是 SDK 与 ACP 配置变更的采用边界。
+
+**不做独立打包证明就把 Python 可执行文件迁移到 profile。** 拒绝:原生 VFS 闭包、三个平台 wheel 包、ripgrep 与 spawn-helper 伴随文件、默认配置发现和干净安装行为都需要自己的迁移证据。
+
+## 验证
+
+- 源码与构建后 CLI 验收覆盖 `sdk` 和 `acp` 的帮助、transport 启动、stdout 纯净性、EOF、信号与根节点 dispose。
+- 组合包配置测试钉住 `dsh-base` 默认禁用模块 HMR,随附模式覆盖层不再重复该策略;自定义 live profile 的 e2e 钉住启动器仅监视 fallback 提供的配置重载。
+- 聚焦单元套件覆盖 profile 启动解析、初始化时限、SDK 重试、服务器就绪和嵌套隔离 home,并对变更后的运行时源码实现 100% 覆盖率。
+- 免密钥 ACP 与 SDK 快照启动真实 `dsh` profile,并钉住协议输出与持久化日志;嵌套 SDK 组合会启动第二个真实 profile 运行时。
+- 真实 API 工作流把文件并行度限制为 4,因为一个 profile e2e 文件可能拥有多个完整 `dsh` 子进程树;工作流测试会钉住该资源上限。
+- Python 套件同时测试 exe 与 node 载体;全部打包运行时场景、原生 macOS 可执行文件构建、两个 wheel 包以及干净 wheel 默认/MCP 冒烟测试都保留既有产物名称。
+- `verify-application-entrypoints` 包含包 bin、可执行源码、直启包的 demo wrapper 与未分类 demo 等非法 fixture(测试前置数据)。
+
+## 影响
+
+- 用户通过具名 profile 与有序 patch 更改 SDK 应用的插件组合,使用与其他所有 dsh 应用相同的安装与解析模型。
+- 自定义 profile 可以在不启用服务器模块 HMR 的情况下获得实时配置监视,只有显式覆盖配置项才会启用源码模块替换。
+- SDK 与 ACP 共享完整 base 应用和同一份策略与工具;快照以显式差异呈现刻意采用的组装变化。
+- 增加 `@deepseek-ai/dsh` 会扩大 TypeScript 客户端的安装体积,换来确定的同版本运行时。
+- 受信任用户 patch 可以增加写入 stdout 的插件并破坏自己的协议流;随附 profile 保证纯净,不为任意第三方组合提供保证。
+- Python 保留一个清晰可见的私有直读配置载体,直到其平台产物迁移得到独立证明。

+ 2 - 2
.agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.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/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.md
-2026-08-10-minimal-preset-owns-rl-composition.md: 65d24f9a03eedffac34f0c0141a8e2f643a48b7b
-2026-08-10-minimal-preset-owns-rl-composition.zh.md: 983fea50dfd4a262204b68937d07910c69018614
+2026-08-10-minimal-preset-owns-rl-composition.md: 47a1bbe9f8f4875e2437b3ff955663c4b3de7dce
+2026-08-10-minimal-preset-owns-rl-composition.zh.md: 0fab1e1f23a314bec80b5fd355abcb2881c1b1a5

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.md

@@ -22,7 +22,7 @@ The process-wide `core-web.cordis.yml` patch is absent. Browser UI, workspace at
 
 System-prompt and persona package tests prove final complete-section and runtime-context suppression, including waterfall mutation and duplicate rejection. The shipped-preset composition test asserts the exact prompt, Bash description, absolute editor schema, and two-tool catalog under the default native presentation. The keyless Web replay sends a real request through a `minimal` agent while global identity, Web-orientation text, dynamic policy contexts, and a test section are registered, asserts that no runtime-context snapshot exists, the entry-local filesystem is bare, and compaction is absent, then executes two persistent Bash calls to prove environment and cwd state survive and executes the editor through an absolute path.
 
-The standalone [`minimal.cordis.yml`](../../../../examples/jsonrpc-agent/minimal.cordis.yml) is the complete two-tool composition for the bundled JSON-RPC runtime. The [bare two-tool runtime decision](../feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md) owns its launch-specific environment configuration, bare filesystem, and absence of compaction. Its keyless SDK replay asserts the assembled system prompt and two-tool catalog, executes persistent Bash across calls, and exercises the editor; the Python SDK tutorial provides the runnable entry point.
+The standalone [`minimal.cordis.yml`](../../../../examples/python-sdk-agent/minimal.cordis.yml) is the complete two-tool composition for the bundled JSON-RPC runtime. The [bare two-tool runtime decision](../feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md) owns its launch-specific environment configuration, bare filesystem, and absence of compaction. Its keyless SDK replay asserts the assembled system prompt and two-tool catalog, executes persistent Bash across calls, and exercises the editor; the Python SDK tutorial provides the runnable entry point.
 
 ## Alternatives considered
 

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.zh.md

@@ -22,7 +22,7 @@ preset persona 恰好是 `You are a helpful software engineer assistant.`,它
 
 系统提示词与 persona 包测试证明了 complete 段最终约束与 runtime-context 抑制,包括 waterfall 修改与重复项拒绝。交付 preset 组合测试在默认原生呈现下断言精确的提示词、Bash 描述、要求绝对路径的编辑器 schema 和双工具目录。无密钥 Web 回放通过 `minimal` agent 发送一个真实请求,同时注册全局身份、Web 定位文本、动态策略上下文和一个测试段落;它断言不存在 runtime-context 快照、entry 本地文件系统是裸后端且压缩不存在,随后执行两次持久 Bash 调用,证明环境与 cwd 状态能够保留,并通过绝对路径执行编辑器。
 
-独立的 [`minimal.cordis.yml`](../../../../examples/jsonrpc-agent/minimal.cordis.yml) 是内置 JSON-RPC 运行时的完整双工具组合。[裸双工具运行时决策](../feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md)说明其启动方式专属的环境配置、裸文件系统和无压缩选择。其无密钥 SDK 回放会断言组装后的系统提示词与双工具目录,跨调用执行持久 Bash,并使用编辑器;Python SDK 教程提供可运行的入口。
+独立的 [`minimal.cordis.yml`](../../../../examples/python-sdk-agent/minimal.cordis.yml) 是内置 JSON-RPC 运行时的完整双工具组合。[裸双工具运行时决策](../feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md)说明其启动方式专属的环境配置、裸文件系统和无压缩选择。其无密钥 SDK 回放会断言组装后的系统提示词与双工具目录,跨调用执行持久 Bash,并使用编辑器;Python SDK 教程提供可运行的入口。
 
 ## 考虑过的替代方案
 

+ 2 - 2
.agents/notes/implemented/feature/2026-07-06-approval-seam.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-07-06-approval-seam.md
-2026-07-06-approval-seam.md: ace41ebbb94cc24c2fdd3e7ae2d3a69b28b169af
-2026-07-06-approval-seam.zh.md: 63b7478b0903c149fc87fd944c95e94cc8de2359
+2026-07-06-approval-seam.md: d7f8d90d408bb85c20f6a4dd0373de1aff9b180b
+2026-07-06-approval-seam.zh.md: 8f2481ff0e25193e91118d5b8181cb4dd3cf40e5

+ 1 - 1
.agents/notes/implemented/feature/2026-07-06-approval-seam.md

@@ -25,7 +25,7 @@ One `cordis.yml` entry mounts the seam. Not loading it is the fail-closed opt-ou
   #   policy: never   # deployment default for sessions without an override; 'ask' when omitted
 ```
 
-The entry alone provides mechanism, not a channel: with no answerer composed, every ask resolves `unavailable` and the asking tool call denies — fail-closed needs no configuration. Composing the ACP app (`@deepseek-ai/dsh-acp-demo`, as in [the acp-agent example's default tree](../../../../examples/acp-agent/README.md)) completes the loop: its [automation-only bridge](../simplification/2026-07-23-acp-automation-only-protocol.md) registers an answerer that sends `session/request_permission` to the owning client with the exact tool-call id and one-shot allow/reject options. `policy: never` is the unattended stance — every ask auto-rejects deterministically, and the current value joins the runtime-context snapshot. `policy` is validated against the closed list at plugin load; anything else throws.
+The entry alone provides mechanism, not a channel: with no answerer composed, every ask resolves `unavailable` and the asking tool call denies — fail-closed needs no configuration. Composing the ACP profile app (`@deepseek-ai/dsh-acp-app`, as in [the acp-agent example](../../../../examples/acp-agent/README.md)) completes the loop: its [automation-only bridge](../simplification/2026-07-23-acp-automation-only-protocol.md) registers an answerer that sends `session/request_permission` to the owning client with the exact tool-call id and one-shot allow/reject options. `policy: never` is the unattended stance — every ask auto-rejects deterministically, and the current value joins the runtime-context snapshot. `policy` is validated against the closed list at plugin load; anything else throws.
 
 What a composed deployment observes: `allowed-once` lets exactly that call proceed; rejection, dismissal, and channel absence deny with three distinct reasons the model can tell apart; a successful in-turn request lands a durable `approval/asked`/`approval/decided` pair on the asking agent's session log; nothing about a grant persists past the call that asked. An idle request or audit append failure rejects instead of returning an unaudited decision.
 

+ 1 - 1
.agents/notes/implemented/feature/2026-07-06-approval-seam.zh.md

@@ -25,7 +25,7 @@ Status: implemented
   #   policy: never   # deployment default for sessions without an override; 'ask' when omitted
 ```
 
-仅有这条条目只提供机制,不提供通道:没有组合应答者时,每次 ask 都解析为 `unavailable`,发起请求的工具调用会被拒绝——无需配置即可做到故障时默认拒绝。组合 ACP 应用(`@deepseek-ai/dsh-acp-demo`,如 [acp-agent 示例的默认树](../../../../examples/acp-agent/README.zh.md))即可闭环:其[仅面向自动化的桥接层](../simplification/2026-07-23-acp-automation-only-protocol.zh.md)注册一个应答者,向拥有该会话的客户端发送 `session/request_permission`,携带精确的工具调用 id 和一次性 allow/reject 选项。`policy: never` 是无人值守姿态:每次 ask 都会被确定性地自动拒绝,当前值也会加入运行时上下文快照。`policy` 在插件加载时对照封闭列表校验;非法值直接抛异常。
+仅有这条条目只提供机制,不提供通道:没有组合应答者时,每次 ask 都解析为 `unavailable`,发起请求的工具调用会被拒绝——无需配置即可做到故障时默认拒绝。组合 ACP profile 应用(`@deepseek-ai/dsh-acp-app`,如 [acp-agent 示例](../../../../examples/acp-agent/README.zh.md))即可闭环:其[仅面向自动化的桥接层](../simplification/2026-07-23-acp-automation-only-protocol.zh.md)注册一个应答者,向拥有该会话的客户端发送 `session/request_permission`,携带精确的工具调用 id 和一次性 allow/reject 选项。`policy: never` 是无人值守姿态:每次 ask 都会被确定性地自动拒绝,当前值也会加入运行时上下文快照。`policy` 在插件加载时对照封闭列表校验;非法值直接抛异常。
 
 组合部署的可观测行为:`allowed-once` 仅允许该次调用继续;拒绝、关闭和通道缺失以三种不同原因拒绝,模型可以区分;轮次内成功的请求会在发起请求的 agent 的会话日志上落一对持久化的 `approval/asked`/`approval/decided` 事件;授权不会在发起请求的调用结束后继续存在。空闲时的请求或审计追加失败会拒绝,而不会返回未经审计的决策。
 

+ 2 - 2
.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.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-07-12-subagent-persona-tool-filter-and-depth.md
-2026-07-12-subagent-persona-tool-filter-and-depth.md: 08dffb459621b0d76c7e08ac6ed9a566fcc6e131
-2026-07-12-subagent-persona-tool-filter-and-depth.zh.md: e484319f4ea76f4bc3e833e8296142480d374516
+2026-07-12-subagent-persona-tool-filter-and-depth.md: 26349e87c008ff89fa8f7ebfd7fce30e446180d4
+2026-07-12-subagent-persona-tool-filter-and-depth.zh.md: 6628cd84a008c59c991549668c76a2ce118edcf6

+ 1 - 1
.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md

@@ -53,7 +53,7 @@ The depth limit bounds recursive delegation independently of tool visibility. A
 
 The effective parent depth is the greater of durable `SessionHeader.delegationDepth` and runtime `AgentOptions.subagentDepth`. An in-process child records its derived depth in the session header, and resume restores that header, so a restart cannot lower the recursion count.
 
-Every public entry validates the domain rather than relying on one model-facing configuration path. Negative values, fractions, negative zero, non-finite values, unsafe integers, malformed stored parent depth, and derived overflow all reject. A direct `SubagentStartRequest` may omit the cap to leave depth unbounded; loader-resolved `dsh-tool-subagent` configuration instead defaults to `3`, accepts a numeric override, and uses explicit `'provider-managed'` to omit the cap for an out-of-process provider whose deployment owns its recursion budget. Three is a small finite default that still permits a root plus three descendant generations: the [JSON-RPC example](../../../../examples/jsonrpc-agent/cordis.yml) uses that general policy, while the ACP and headless examples pin one. A numeric tool cap fails at provider mount when the provider lacks `depthLimit`.
+Every public entry validates the domain rather than relying on one model-facing configuration path. Negative values, fractions, negative zero, non-finite values, unsafe integers, malformed stored parent depth, and derived overflow all reject. A direct `SubagentStartRequest` may omit the cap to leave depth unbounded; loader-resolved `dsh-tool-subagent` configuration instead defaults to `3`, accepts a numeric override, and uses explicit `'provider-managed'` to omit the cap for an out-of-process provider whose deployment owns its recursion budget. Three is a small finite default that still permits a root plus three descendant generations: the [JSON-RPC example](../../../../examples/python-sdk-agent/cordis.yml) uses that general policy, while the ACP and headless examples pin one. A numeric tool cap fails at provider mount when the provider lacks `depthLimit`.
 
 A deployment can combine depth and filtering, but the numeric cap does not synthesize a filter. The delegation tool stays visible at the cap because authorization may depend on runtime state; every attempted start checks the calling agent's current durable and runtime depth, and a rejected start returns an errored tool result without publishing a child. A deployment may separately deny delegation tools in children when its visibility policy is static. Neither choice changes the provider's conversation-history behavior.
 

+ 1 - 1
.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md

@@ -55,7 +55,7 @@ subagent 启动有三个独立的组合控制:`persona`、`toolFilter` 和 `ma
 
 有效父级深度取持久 `SessionHeader.delegationDepth` 与运行时 `AgentOptions.subagentDepth` 中的较大值。进程内子 agent 把推导出的深度记录在会话 header 中,恢复时会重新载入该 header,因此重启无法降低递归计数。
 
-每个公开入口都自行验证值域,而非依赖单一的面向模型配置路径。负值、小数、负零、非有限值、不安全整数、格式错误的存储父级深度以及推导溢出均被拒绝。直接的 `SubagentStartRequest` 可以省略上限,让此机制不约束深度;经 loader 解析的 `dsh-tool-subagent` 配置则默认值为 `3`、接受数值覆盖,并使用显式的 `'provider-managed'` 来省略由进程外提供方部署拥有递归预算时的上限。三是一个较小的有限默认值,仍允许 root 加三代后代:[JSON-RPC 示例](../../../../examples/jsonrpc-agent/cordis.yml)采用这项通用策略,而 ACP 与 headless 示例固定为一。提供方缺少 `depthLimit` 时,数值工具上限会在提供方挂载阶段失败。
+每个公开入口都自行验证值域,而非依赖单一的面向模型配置路径。负值、小数、负零、非有限值、不安全整数、格式错误的存储父级深度以及推导溢出均被拒绝。直接的 `SubagentStartRequest` 可以省略上限,让此机制不约束深度;经 loader 解析的 `dsh-tool-subagent` 配置则默认值为 `3`、接受数值覆盖,并使用显式的 `'provider-managed'` 来省略由进程外提供方部署拥有递归预算时的上限。三是一个较小的有限默认值,仍允许 root 加三代后代:[JSON-RPC 示例](../../../../examples/python-sdk-agent/cordis.yml)采用这项通用策略,而 ACP 与 headless 示例固定为一。提供方缺少 `depthLimit` 时,数值工具上限会在提供方挂载阶段失败。
 
 部署可以组合深度与过滤,但数值上限不会合成过滤器。委派工具在上限处仍然可见,因为授权可能依赖运行时状态;每次尝试启动都会检查调用方 agent 当前的持久与运行时深度,被拒绝的启动返回错误工具结果,且不发布子 agent。可见性策略固定的部署可以另外在子 agent 中 deny 委派工具。两种选择都不改变提供方的对话历史行为。
 

+ 2 - 2
.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.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-07-27-typescript-sdk-and-sdk-subagent-backend.md
-2026-07-27-typescript-sdk-and-sdk-subagent-backend.md: 84314eaf5827464767666b1b9c65e105ea4e869a
-2026-07-27-typescript-sdk-and-sdk-subagent-backend.zh.md: a822aac655ea3577660f09b2f2a2986f2a780d7a
+2026-07-27-typescript-sdk-and-sdk-subagent-backend.md: eda9d3a3de91944a298070d6cc22f632294f7a28
+2026-07-27-typescript-sdk-and-sdk-subagent-backend.zh.md: 1a72c6b1cdb7453b8468d6e6be37aec76323f714

+ 7 - 7
.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.md

@@ -13,19 +13,19 @@ The stdio JSON-RPC serving surface (`@deepseek-ai/dsh-sdk-jsonrpc-server`, the [
 Three packages, layered exactly like the existing Python stack, plus one Service Provider registration:
 
 - **`@deepseek-ai/dsh-sdk-protocol`** (`packages/sdk/protocol/`) — the wire made shared and nominal. `JsonRpcLineTransport` moves here verbatim from `dsh-sdk-jsonrpc-server` (which now imports it), and `types.ts` names every payload the server speaks: `InitializeParams/Result`, `SessionPromptParams/Result`, the four notification payloads, and the `HarnessSdkRequestMap`/`HarnessSdkNotificationMap` indexes. The package root explicitly exports that complete interface and provides no source-module deep imports. The server's `notify()` call sites are typed against these named payloads, so server drift breaks compilation, not clients. One behavioral change: an error response now rejects with `JsonRpcResponseError` carrying the wire `code`/`data` (the Python client already preserved these; the old transport threw a bare `Error` with only the message).
-- **`@deepseek-ai/dsh-sdk-client`** (`packages/sdk/client/`) — the TypeScript twin of `python/sdk`: `HarnessClient` (spawn, frame, fan out notifications, typed error surfaces, close-to-quiescence via the shared dispose ladder) under `DeepSeekHarness`/`HarnessSession` (lazy start, memoized `initialize`, `run()` pairing one `session/prompt` with its `session.finished`). Its package-root consumer interface explicitly exports both client layers, caller-facing types, and the protocol-owned `JsonRpcResponseError`; source modules, normalization helpers, and the notification producer stay internal. `TurnResult.events` contains only the root session's typed events, while `notifications` retains session ids across the root and descendants discovered from `subagent.started`; session-tree scoping is client-side, mirroring `client.py`. Deliberate asymmetries with Python: the launch spec is explicit `command`/`args` (no bundled-runtime resolution — that is a distribution concern with no TS consumer yet); `env` replaces rather than merges (callers own credential policy; `scrubbedParentEnv` from the subprocess seam is one import away); `TurnResult` carries the structured `reason` (Python exposes only `status`); teardown walks a private stdin-EOF → SIGTERM → SIGKILL ladder to actual exit (the client runs outside any harness context, so it cannot ride `ctx.subprocess`).
-- **`@deepseek-ai/dsh-subagent-dsh-sdk`** (`packages/subagent/subagent-dsh-sdk/`) — the second out-of-process `SubagentProvider`, structured as `subagent-acp`'s sibling: same all-false capabilities and `inheritsParentContext: false`, same publish-after-handshake ownership transaction, same result-never-rejects flattening through an `onError` sink, same parent-namespace run id. The child answer is read from streamed `session.event`s — the last complete `assistant/message`, else accumulated `text-delta` chunks, so partial answers survive cancellation. Stop reasons map from the child's structured `TurnEndReason` (`completed`/`max-tokens`/`aborted` pass through; everything else, including a settled-without-turn child, is `error`). Its `provider`/`model` config feeds the child's `initialize`; `env` is where deployments pass the child's own key and `DSH_CORDIS_CONFIG`.
+- **`@deepseek-ai/dsh-sdk-client`** (`packages/sdk/client/`) — the TypeScript twin of `python/sdk`: `HarnessClient` (spawn, frame, fan out notifications, typed error surfaces, close-to-quiescence via the shared dispose ladder) under `DeepSeekHarness`/`HarnessSession` (lazy start, memoized `initialize`, `run()` pairing one `session/prompt` with its `session.finished`). Its package-root consumer interface explicitly exports both client layers, caller-facing types, and the protocol-owned `JsonRpcResponseError`; source modules, normalization helpers, and the notification producer stay internal. `RunResult.events` contains only the root session's typed events, while `notifications` retains session ids across the root and descendants discovered from `subagent.started`; session-tree scoping is client-side, mirroring `client.py`. The launch interface resolves the same-version `@deepseek-ai/dsh` dependency and selects a named profile, with optional `dshBin`, ordered patches, an explicit Harness home, process cwd, environment, and timeouts; arbitrary command/argv launch remains an internal fake-runtime adapter. A clean checkout without `lib/bin.js` uses that package's source entry through an absolute `tsx/esm` loader and an internal patch that omits build-generated Typert contribution loading, which the SDK protocol does not consume. `env` replaces rather than merges and is read when `start()` spawns, so callers own credential policy and can finish preparing it before first use. `RunResult` carries the structured `reason` (Python exposes only `status`); teardown walks a private stdin-EOF → SIGTERM → SIGKILL ladder to actual exit (the client runs outside any harness context, so it cannot ride `ctx.subprocess`).
+- **`@deepseek-ai/dsh-subagent-dsh-sdk`** (`packages/subagent/subagent-dsh-sdk/`) — the second out-of-process `SubagentProvider`, structured as `subagent-acp`'s sibling: same all-false capabilities and `inheritsParentContext: false`, same publish-after-handshake ownership transaction, same result-never-rejects flattening through an `onError` sink, same parent-namespace run id. The child answer is read from streamed `session.event`s — the last complete `assistant/message`, else accumulated `text-delta` chunks, so partial answers survive cancellation. Stop reasons map from the child's structured `TurnEndReason` (`completed`/`max-tokens`/`aborted` pass through; everything else, including a settled-without-turn child, is `error`). Its `dshBin`/profile/patch/home config selects an isolated SDK application, `provider`/`model` feeds the child's `initialize`, and `env` supplies explicit child-only values such as its API key.
 - **The subagent seam grows `out-of-process.ts`**: the provider-side vocabulary both out-of-process backends share — `NO_START_CAPABILITIES`, timing-bound validation, child cwd resolution (config override, else the delegating parent session's workspace), the never-reject `settleRunResult`, and the `subprocessRunHandle` publication. Process mechanics (spawn, env scrub, tree-scoped teardown) live in the `dsh-subprocess` seam; `subagent-acp` spawns through `ctx.subprocess`, while this backend spawns through the SDK client (the subprocess README's documented exception for SDK-managed transports) and applies the seam's `scrubbedParentEnv()` itself.
 
-`dsh-sdk-jsonrpc-server` keeps serving unchanged (the wire is byte-identical); `dsh-jsonrpc-agent-pkg` (the Python runtime closure) gains the `dsh-sdk-protocol` dependency line.
+`dsh-sdk-jsonrpc-server` keeps serving unchanged (the wire is byte-identical); the private `@deepseek-ai/dsh-sdk-python-runtime` carrier consumes the shared protocol through its packaged closure.
 
 ## Testing
 
 Four tiers, per [testing policy](../../../../docs/testing.md):
 
 - **Keyless unit** — `sdk-client` drives a scripted fake runtime (`tests/fake-runtime.ts`, env-scripted, protocol-only — the Python `test_client.py` pattern) over real stdio; `subagent-dsh-sdk` drives the same fake through the real provider. 100% per-file coverage on all three packages.
-- **Keyless Loader composition** — `subagent-dsh-sdk/tests/loader-composition.e2e.ts` boots a test-only cordis.yml (`examples/jsonrpc-agent/tests/fixtures/subagent/subagent-dsh-sdk/`) where the child is a REAL second harness runtime with its own cordis.yml; asserts the parent tool result and the child's own persisted transcript both carry the parent session's cwd. The child launch resolves through `resolveExampleLaunch`, so src/lib modes both hold.
-- **Keyless snapshot** — `examples/jsonrpc-agent/tests/sdk.snapshot.ts` is the jsonrpc example's first snapshot suite: the real `dsh-jsonrpc-agent` runtime driven through the real `dsh-sdk-client`, replaying recorded fixtures via `llm-replay` behind the new `cordis.snapshot.yml` overlay (passed explicitly through `DSH_CORDIS_CONFIG`; the jsonrpc bin performs no snapshot config swap of its own). Three scenarios — text turn, bash tool, spawn subagent — each pinning the normalized notification stream, the SDK turn result, and the persisted parent+child logs. This also closes the protocol-tier gap the single-exe note's Python-side snapshot left on the vitest side.
+- **Keyless Loader composition** — `subagent-dsh-sdk/tests/loader-composition.e2e.ts` boots a test-only cordis.yml (`examples/python-sdk-agent/tests/fixtures/subagent/subagent-dsh-sdk/`) where the child is a real second `dsh --profile sdk` runtime with its own isolated home and ordered patch; asserts the parent tool result and the child's own persisted transcript both carry the parent session's cwd.
+- **Keyless snapshot** — `examples/python-sdk-agent/tests/sdk.snapshot.ts` drives the real `dsh --profile sdk` runtime through the real `dsh-sdk-client`, replaying recorded fixtures through an ordered `llm-replay` patch. Four scenarios — text turn, bash tool, spawn subagent, and the minimal persistent-tool composition — each pin the normalized notification stream, SDK turn result, and persisted parent and child logs. This also closes the protocol-tier gap the single-exe note's Python-side snapshot left on the vitest side.
 - **With-key e2e** — the snapshot suite's `DSH_SNAPSHOT=record` mode is the live-API path (it produced the committed fixtures); the composition e2e needs no key by design.
 
 ## Alternatives considered
@@ -36,7 +36,7 @@ Four tiers, per [testing policy](../../../../docs/testing.md):
 
 **Fold the SDK backend into `subagent-acp` with a transport switch.** The two backends share the subprocess lifecycle but nothing about the wire (ACP SDK connection vs harness JSON-RPC), the child contract (any ACP agent vs a harness runtime), or the result extraction (`agent_message_chunk` accumulation vs session-event reading). A config discriminant would bury two protocols in one package; the genuinely shared provider-side parts moved into the subagent seam's `out-of-process.ts`, and the process mechanics live in the `dsh-subprocess` seam.
 
-**Give the TS SDK bundled-runtime resolution parity with Python.** Python's carrier resolution exists to ship wheels to users without Node. A TypeScript consumer definitionally has Node and (in-repo) the workspace; inventing a distribution story with no consumer violates the require-current-need rule. Deferred until a real npm-distribution consumer appears.
+**Resolve `dsh` only from `PATH`.** Rejected: a Node consumer does not reliably inherit a project-local `.bin` directory. The same-version package dependency supplies the built CLI for installed consumers and the source entry for a clean checkout.
 
 **Export source modules, normalization helpers, and subscription producer operations.** These are implementation details with no caller need; exposing them would make callers learn how the client validates and distributes wire input. The package roots instead enumerate the supported client and protocol interfaces, and the client re-exports the one protocol error callers must distinguish.
 
@@ -44,6 +44,6 @@ Four tiers, per [testing policy](../../../../docs/testing.md):
 
 ## Consequences
 
-**Bought**: the SDK runtime protocol now has named, compiler-checked types shared by its server and both client SDKs; TypeScript consumers get the same subprocess-driving capability Python has, with typed errors, structured turn reasons, and package roots that expose only caller-owned operations; the subagent seam gains a harness-native out-of-process backend whose children are full peers (own config, persistence, tools) — the recursive-composition story the seam note anticipated; the jsonrpc example finally has snapshot coverage, through the SDK path itself.
+**Bought**: the SDK runtime protocol has named, compiler-checked types shared by its server and both client SDKs; TypeScript consumers get the same subprocess-driving capability Python has, with typed errors, structured turn reasons, and package roots that expose only caller-owned operations; the subagent seam has a harness-native out-of-process backend whose children are full peers (own config, persistence, tools); the SDK profile has snapshot coverage through the SDK path itself.
 
 **Paid**: a third package in the `sdk/` group and a fourth subagent backend to keep current; the SDK backend boots a complete plugin tree per child (heavier per-run than an ACP child; pooling remains future work, same as ACP); the wire still has no cancel method, so both the SDK's `RequestTimeoutError` and the backend's dispose settle locally while the server-side turn runs on until process teardown; fixtures for the snapshot suite were recorded against `deepseek-v4-flash` and re-record on model-behavior drift like every other recorded corpus.

+ 7 - 7
.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.zh.md

@@ -13,19 +13,19 @@ stdio JSON-RPC 对外服务接口(`@deepseek-ai/dsh-sdk-jsonrpc-server`,见[
 三个包,分层与既有 Python 栈完全一致,外加一个 Service Provider 注册:
 
 - **`@deepseek-ai/dsh-sdk-protocol`**(`packages/sdk/protocol/`)—— 把线协议做成共享且具名。`JsonRpcLineTransport` 从 `dsh-sdk-jsonrpc-server` 原样移入(后者现在导入它),`types.ts` 为服务器所说的每个载荷命名:`InitializeParams/Result`、`SessionPromptParams/Result`、四个通知载荷,以及 `HarnessSdkRequestMap`/`HarnessSdkNotificationMap` 索引。该包根显式导出这一完整接口,且不提供指向源模块的深层导入。服务器的 `notify()` 调用点以这些具名载荷标注类型,服务器漂移会先破坏编译而不是破坏客户端。一处行为变化:错误响应现在以携带线上 `code`/`data` 的 `JsonRpcResponseError` 拒绝(Python 客户端本就保留这些;旧传输只抛携带消息的裸 `Error`)。
-- **`@deepseek-ai/dsh-sdk-client`**(`packages/sdk/client/`)—— `python/sdk` 的 TypeScript 孪生:`HarnessClient`(spawn、分帧、通知扇出、有类型的错误表面、经共享 dispose(资源释放)阶梯关闭至完全停稳)之上是 `DeepSeekHarness`/`HarnessSession`(惰性启动、记忆化 `initialize`、`run()` 把一个 `session/prompt` 与其 `session.finished` 配对)。其包根消费方接口显式导出两层客户端、面向调用方的类型,以及协议包所拥有的 `JsonRpcResponseError`;源模块、规范化辅助函数和通知投递端都保留为内部实现。`TurnResult.events` 只包含根会话的类型化事件,而 `notifications` 则保留根会话及从 `subagent.started` 发现的后代各自的会话 id;基于 `subagent.started` 血缘边的会话树范围限定在客户端完成,镜像 `client.py`。与 Python 的刻意不对称:启动规格是显式 `command`/`args`(无捆绑运行时解析——那是尚无 TS 消费方的发行问题);`env` 整体替换而非合并(凭据策略归调用方;subprocess seam 的 `scrubbedParentEnv` 一个 import 即得);`TurnResult` 携带结构化 `reason`(Python 只暴露 `status`);拆除走私有的 stdin-EOF → SIGTERM → SIGKILL 阶梯直到真正退出(客户端运行在任何 harness 上下文之外,无法搭乘 `ctx.subprocess`)。
-- **`@deepseek-ai/dsh-subagent-dsh-sdk`**(`packages/subagent/subagent-dsh-sdk/`)—— 第二个进程外 `SubagentProvider`,采用与 `subagent-acp` 对等的结构:同样的全 false 能力与 `inheritsParentContext: false`,同样的握手后发布所有权事务,同样通过 `onError` sink 将结果归一为绝不拒绝,同样的父命名空间 run id。子答案从流式 `session.event` 读取——最后一条完整 `assistant/message`,否则累积的 `text-delta` 块,部分答案在取消时得以保留。停止原因由子进程的结构化 `TurnEndReason` 映射(`completed`/`max-tokens`/`aborted` 直通;其余一切、包括未运行任何轮次便已结束的子进程,都是 `error`)。其 `provider`/`model` 配置喂给子进程的 `initialize`;`env` 是部署传入子进程自有密钥与 `DSH_CORDIS_CONFIG` 的地方。
+- **`@deepseek-ai/dsh-sdk-client`**(`packages/sdk/client/`)—— `python/sdk` 的 TypeScript 孪生:`HarnessClient`(spawn、分帧、通知扇出、有类型的错误表面、经共享 dispose(资源释放)阶梯关闭至完全停稳)之上是 `DeepSeekHarness`/`HarnessSession`(惰性启动、记忆化 `initialize`、`run()` 把一个 `session/prompt` 与其 `session.finished` 配对)。其包根消费方接口显式导出两层客户端、面向调用方的类型,以及协议包所拥有的 `JsonRpcResponseError`;源模块、规范化辅助函数和通知投递端都保留为内部实现。`RunResult.events` 只包含根会话的类型化事件,而 `notifications` 则保留根会话及从 `subagent.started` 发现的后代各自的会话 id;基于 `subagent.started` 血缘边的会话树范围限定在客户端完成,镜像 `client.py`。启动接口解析同版本 `@deepseek-ai/dsh` 依赖并选择具名 profile,可选配置包括 `dshBin`、有序 patch、显式 Harness home、进程 cwd、环境和超时;任意 command/argv 启动只作为内部 fake-runtime 适配器。干净 checkout 中若不存在 `lib/bin.js`,client 会通过绝对 `tsx/esm` loader 使用该包的源码入口,并应用一个省略构建期生成 Typert 贡献加载的内部 patch;SDK 协议不消费这些贡献。`env` 整体替换而非合并,并在 `start()` spawn 时读取,因此凭据策略归调用方,且调用方可在首次使用前完成环境准备。`RunResult` 携带结构化 `reason`(Python 只暴露 `status`);拆除走私有的 stdin-EOF → SIGTERM → SIGKILL 阶梯直到真正退出(client 运行在任何 harness 上下文之外,无法搭乘 `ctx.subprocess`)。
+- **`@deepseek-ai/dsh-subagent-dsh-sdk`**(`packages/subagent/subagent-dsh-sdk/`)—— 第二个进程外 `SubagentProvider`,采用与 `subagent-acp` 对等的结构:同样的全 false 能力与 `inheritsParentContext: false`,同样的握手后发布所有权事务,同样通过 `onError` sink 将结果归一为绝不拒绝,同样的父命名空间 run id。子答案从流式 `session.event` 读取——最后一条完整 `assistant/message`,否则累积的 `text-delta` 块,部分答案在取消时得以保留。停止原因由子进程的结构化 `TurnEndReason` 映射(`completed`/`max-tokens`/`aborted` 直通;其余一切、包括未运行任何轮次便已结束的子进程,都是 `error`)。其 `dshBin`/profile/patch/home 配置选择隔离的 SDK 应用,`provider`/`model` 写入子进程 `initialize`,`env` 则提供子进程专用的显式值,例如其 API key。
 - **subagent seam 新增 `out-of-process.ts`**:两个进程外后端共享的 provider 侧词汇——`NO_START_CAPABILITIES`、时限校验、子进程 cwd 解析(配置覆盖、否则发起委托的父会话工作区)、绝不拒绝的 `settleRunResult`、以及 `subprocessRunHandle` 发布。进程机制(spawn、环境清理、进程树清理)属于 `dsh-subprocess` seam;`subagent-acp` 经 `ctx.subprocess` spawn 子进程,本后端则经 SDK 客户端 spawn 子进程(subprocess README 记载的 SDK 托管传输例外)并自行应用该 seam 的 `scrubbedParentEnv()`。
 
-`dsh-sdk-jsonrpc-server` 的服务不变(协议字节完全一致);`dsh-jsonrpc-agent-pkg`(Python 运行时闭包)增加 `dsh-sdk-protocol` 一行依赖。
+`dsh-sdk-jsonrpc-server` 的服务不变(协议字节完全一致);私有 `@deepseek-ai/dsh-sdk-python-runtime` 载体通过其打包闭包消费共享协议。
 
 ## 测试
 
 四层,依[测试政策](../../../../docs/testing.zh.md):
 
 - **免密钥单元**——`sdk-client` 通过真实 stdio 驱动脚本化伪运行时(`tests/fake-runtime.ts`,环境变量脚本化、纯协议——即 Python `test_client.py` 的模式);`subagent-dsh-sdk` 经真实提供方驱动同一伪运行时。三个包全部 100% 逐文件覆盖。
-- **免密钥 Loader 组合**——`subagent-dsh-sdk/tests/loader-composition.e2e.ts` 启动仅测试用 cordis.yml(`examples/jsonrpc-agent/tests/fixtures/subagent/subagent-dsh-sdk/`),其中子进程是真实的第二个 harness 运行时、带自己的 cordis.yml;断言父工具结果与子进程自己持久化的 transcript(文本记录)都携带父会话 cwd。子启动经 `resolveExampleLaunch` 解析,src/lib 两种模式都成立。
-- **免密钥快照**——`examples/jsonrpc-agent/tests/sdk.snapshot.ts` 是 jsonrpc 示例的第一个快照套件:真实 `dsh-jsonrpc-agent` 运行时经真实 `dsh-sdk-client` 驱动,在新的 `cordis.snapshot.yml` 覆盖层后经 `llm-replay` 回放已录制 fixture(测试前置数据)(经 `DSH_CORDIS_CONFIG` 显式传入;jsonrpc bin 自身不做快照配置切换)。三个场景——文本轮次、bash 工具、spawn subagent——各自钉住规范化通知流、SDK 轮次结果与持久化的父+子日志。这也补上了单文件可执行 Note 的 Python 侧快照在 vitest 侧留下的协议层缺口。
+- **免密钥 Loader 组合**——`subagent-dsh-sdk/tests/loader-composition.e2e.ts` 启动仅测试用 cordis.yml(`examples/python-sdk-agent/tests/fixtures/subagent/subagent-dsh-sdk/`),其中子进程是真实的第二个 `dsh --profile sdk` 运行时,拥有独立 home 与有序 patch;断言父工具结果与子进程自己持久化的 transcript(文本记录)都携带父会话 cwd。
+- **免密钥快照**——`examples/python-sdk-agent/tests/sdk.snapshot.ts` 通过真实 `dsh-sdk-client` 驱动真实 `dsh --profile sdk` 运行时,并通过有序 `llm-replay` patch 回放已录制 fixture(测试前置数据)。文本轮次、bash 工具、spawn subagent 与极简持久工具组合四个场景分别钉住规范化通知流、SDK 轮次结果,以及持久化的父日志与子日志。这也补上了单文件可执行 Note 的 Python 侧快照在 vitest 侧留下的协议层缺口。
 - **带密钥 e2e**——快照套件的 `DSH_SNAPSHOT=record` 模式即真实 API 路径(已提交 fixture 由它产出);组合 e2e 设计上无需密钥。
 
 ## 考虑过的替代方案
@@ -36,7 +36,7 @@ stdio JSON-RPC 对外服务接口(`@deepseek-ai/dsh-sdk-jsonrpc-server`,见[
 
 **把 SDK 后端折进 `subagent-acp`、用传输开关区分。** 两个后端共享子进程生命周期,但协议(ACP SDK 连接 vs harness JSON-RPC)、子进程约定(任意 ACP agent vs harness 运行时)、结果提取(`agent_message_chunk` 累积 vs 会话事件读取)毫无共享。配置判别字段会把两个协议埋进一个包;真正共享的提供方侧部分移入 subagent seam 的 `out-of-process.ts`,进程机制则住在 `dsh-subprocess` seam。
 
-**给 TS SDK 与 Python 对等的捆绑运行时解析。** Python 的载体解析是为了给没有 Node 的用户发 wheel 包。TypeScript 消费方按定义就有 Node,且仓库内消费方还有工作区;为尚不存在的消费方编造发行方案违反「只实现当前需求」的规则。推迟到真实的 npm 发行消费方出现时再处理。
+**只从 `PATH` 解析 `dsh`。** 拒绝:Node 消费方不一定继承项目本地 `.bin` 目录。同版本包依赖为已安装消费方提供构建后 CLI,并为干净 checkout 提供源码入口。
 
 **导出源模块、规范化辅助函数和订阅投递端操作。** 这些都是调用方不需要的实现细节;暴露它们会让调用方不得不理解客户端如何校验与分发协议输入。各包根转而枚举受支持的客户端接口与协议接口,客户端则只重新导出调用方必须区分的那一种协议错误。
 
@@ -44,6 +44,6 @@ stdio JSON-RPC 对外服务接口(`@deepseek-ai/dsh-sdk-jsonrpc-server`,见[
 
 ## 后果
 
-**收益**:SDK 运行时协议现在拥有服务器与两个客户端 SDK 共享的、编译器校验的具名类型;TypeScript 消费方获得与 Python 相同的子进程驱动能力,且带类型化错误与结构化轮次原因,包根也只暴露归调用方所有的操作;subagent seam 获得一个 harness 原生的进程外后端,其子进程是完整对等体(自有配置、持久化、工具)——正是 seam Agent Note 所设想的递归组合方式;jsonrpc 示例终于有了快照覆盖,而且走的就是 SDK 路径本身。
+**收益**:SDK 运行时协议拥有服务器与两个客户端 SDK 共享的、编译器校验的具名类型;TypeScript 消费方获得与 Python 相同的子进程驱动能力,且带类型化错误与结构化轮次原因,包根也只暴露归调用方所有的操作;subagent seam 拥有一个 harness 原生的进程外后端,其子进程是完整对等体(自有配置、持久化、工具);SDK profile 通过 SDK 路径本身获得快照覆盖。
 
 **代价**:`sdk/` 组多了第三个包、subagent 多了第四个要保持最新的后端;SDK 后端每个子进程启动完整插件树(单次成本高于 ACP 子进程;池化与 ACP 一样留作未来工作);协议仍无取消方法,SDK 的 `RequestTimeoutError` 与后端的 dispose 都只在本地结算、服务器侧轮次会继续运行到进程清理为止;快照 fixture 录制于 `deepseek-v4-flash`,与其他录制语料一样随模型行为漂移而重录。

+ 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: 2f26dda5ddc905076dbc2f6c681f462973bde793
-2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md: 1931a7468029e145d8e4792da1f889f2bc59d6d5
+2026-08-11-minimal-profiles-bare-two-tool-runtime.md: 7d068aebb6642602aac0a039c8635acf555ccfe8
+2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md: c32a9a2e09ff0acda592062f780427c636fd65dd

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

@@ -14,9 +14,9 @@ The two launch paths also have different configuration owners. Web mounts a per-
 
 Both shipped minimal profiles expose exactly persistent `bash` and `str_replace_editor`, mount no context-compaction provider, suppress every `dsh-system-prompt` runtime-context contribution for fresh sessions, and run the editor against `@deepseek-ai/dsh-fs-local`. The Web preset isolates `ctx.fs` inside the agent entry and mounts `fs-local` beside the editor, so other Web agents retain the host filesystem provider. Its persona remains the fixed complete prompt owned by the earlier [minimal-preset composition decision](../bug-fix/2026-08-10-minimal-preset-owns-rl-composition.md) and applies runtime-context suppression only to that agent scope. The standalone spine forwards the same setting to its process-owned system-prompt service. Sandbox and approval services remain mounted and enforce their policies; only their model-facing dynamic context is absent.
 
-The standalone [`minimal.cordis.yml`](../../../../examples/jsonrpc-agent/minimal.cordis.yml) remains a complete JSON-RPC process composition. It mounts `dsh-sdk-jsonrpc-server`, the local PTY and subprocess services required by persistent Bash, `fs-local`, the two tool consumers, and uncompressed JSONL persistence. It does not mount `token-meter`, `compaction-basic`, `fs-sandbox`, or `fs-observation-policy`. Persistent Bash still consumes the deployment's danger-full-access sandbox policy; the editor is not confined by that policy.
+The standalone [`minimal.cordis.yml`](../../../../examples/python-sdk-agent/minimal.cordis.yml) remains a complete JSON-RPC process composition. It mounts `dsh-sdk-jsonrpc-server`, the local PTY and subprocess services required by persistent Bash, `fs-local`, the two tool consumers, and uncompressed JSONL persistence. It does not mount `token-meter`, `compaction-basic`, `fs-sandbox`, or `fs-observation-policy`. Persistent Bash still consumes the deployment's danger-full-access sandbox policy; the editor is not confined by that policy.
 
-`DSH_SYSTEM_PROMPT` selects the standalone persona. `DSH_MODEL` names the DeepSeek provider catalog entry, and `DSH_CONTEXT_WINDOW` supplies that entry's capacity. Because the SDK client owns the JSON-RPC `initialize` request, [`minimal.py`](../../../../examples/jsonrpc-agent/minimal.py) also uses `DSH_MODEL` as its default `model` argument; an explicit `--model` remains authoritative. Endpoint and credential variables stay owned by the DeepSeek adapter's existing environment-resolution path.
+`DSH_SYSTEM_PROMPT` selects the standalone persona. `DSH_MODEL` names the DeepSeek provider catalog entry, and `DSH_CONTEXT_WINDOW` supplies that entry's capacity. Because the SDK client owns the JSON-RPC `initialize` request, [`minimal.py`](../../../../examples/python-sdk-agent/minimal.py) also uses `DSH_MODEL` as its default `model` argument; an explicit `--model` remains authoritative. Endpoint and credential variables stay owned by the DeepSeek adapter's existing environment-resolution path.
 
 ## Verification
 

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

@@ -14,9 +14,9 @@ Web `minimal` preset 与独立 JSON-RPC minimal 组合对外提供持久 `bash`
 
 两种随附 minimal profile 都只对外提供持久 `bash` 与 `str_replace_editor`,不挂载上下文压缩提供方,为新建会话抑制每个 `dsh-system-prompt` runtime-context 贡献,并让编辑器使用 `@deepseek-ai/dsh-fs-local`。Web preset 在 agent entry 内隔离 `ctx.fs`,将 `fs-local` 与编辑器一起挂载,因此其他 Web agent 仍使用宿主文件系统提供方。其 persona 继续采用较早的 [minimal preset 组合决策](../bug-fix/2026-08-10-minimal-preset-owns-rl-composition.zh.md)所拥有的固定 complete 提示词,并仅为该 agent 作用域实施 runtime-context 抑制。独立 spine 将同一设置转发给其进程拥有的 system-prompt 服务。沙箱与批准服务仍保持挂载并强制其策略;只有它们面向模型的动态上下文缺席。
 
-独立的 [`minimal.cordis.yml`](../../../../examples/jsonrpc-agent/minimal.cordis.yml) 仍是完整的 JSON-RPC 进程组合。它挂载 `dsh-sdk-jsonrpc-server`、持久 Bash 所需的本地 PTY 和子进程服务、`fs-local`、两个工具消费方,以及未压缩的 JSONL 持久化。它不挂载 `token-meter`、`compaction-basic`、`fs-sandbox` 或 `fs-observation-policy`。持久 Bash 仍消费部署的 danger-full-access 沙箱策略;编辑器不受该策略限制。
+独立的 [`minimal.cordis.yml`](../../../../examples/python-sdk-agent/minimal.cordis.yml) 仍是完整的 JSON-RPC 进程组合。它挂载 `dsh-sdk-jsonrpc-server`、持久 Bash 所需的本地 PTY 和子进程服务、`fs-local`、两个工具消费方,以及未压缩的 JSONL 持久化。它不挂载 `token-meter`、`compaction-basic`、`fs-sandbox` 或 `fs-observation-policy`。持久 Bash 仍消费部署的 danger-full-access 沙箱策略;编辑器不受该策略限制。
 
-`DSH_SYSTEM_PROMPT` 选择独立组合的 persona。`DSH_MODEL` 命名 DeepSeek 提供方目录项,`DSH_CONTEXT_WINDOW` 提供该目录项的容量。由于 SDK 客户端拥有 JSON-RPC `initialize` 请求,[`minimal.py`](../../../../examples/jsonrpc-agent/minimal.py)也使用 `DSH_MODEL` 作为 `model` 参数的默认值;显式 `--model` 仍具有最高优先级。端点与凭据变量继续由 DeepSeek 适配器现有的环境解析路径持有。
+`DSH_SYSTEM_PROMPT` 选择独立组合的 persona。`DSH_MODEL` 命名 DeepSeek 提供方目录项,`DSH_CONTEXT_WINDOW` 提供该目录项的容量。由于 SDK 客户端拥有 JSON-RPC `initialize` 请求,[`minimal.py`](../../../../examples/python-sdk-agent/minimal.py)也使用 `DSH_MODEL` 作为 `model` 参数的默认值;显式 `--model` 仍具有最高优先级。端点与凭据变量继续由 DeepSeek 适配器现有的环境解析路径持有。
 
 ## 验证
 

+ 2 - 2
.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.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/simplification/2026-07-20-remove-stdio-and-echo-agents.md
-2026-07-20-remove-stdio-and-echo-agents.md: 8761c360e492d6d315e738ed93441929584d1e20
-2026-07-20-remove-stdio-and-echo-agents.zh.md: e34672ce9739ab22e76e44c305c22efb30b6a0c4
+2026-07-20-remove-stdio-and-echo-agents.md: 1d71b74fda229f7eaed502b3badba2ab39e5ea54
+2026-07-20-remove-stdio-and-echo-agents.zh.md: 8445a82d7e366bf57ea4d3ad4595a596b67f1fa8

+ 1 - 1
.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.md

@@ -20,7 +20,7 @@ The remaining application roles are explicit:
 
 - `@deepseek-ai/dsh-tui` owns terminal-interactive execution. It rejects non-TTY streams before Loader boot; `apps/cli/config/base.cordis.yml` plus the `tui.cordis.yml` overlay own the complete coding composition, with PTY plus terminal-snapshot coverage in `apps/cli/tests/`.
 - [`dsh --profile headless`](../../../../apps/cli/README.md) owns non-interactive execution. Its `headless` profile is the product composition; `examples/headless-agent` owns replay snapshots, generic real-agent suites, and an unexported keyless Loader driver.
-- [`@deepseek-ai/dsh-acp-demo`](../../../../packages/examples/acp-demo/README.md) and `@deepseek-ai/dsh-sdk-jsonrpc-server` own their framed protocol integrations.
+- [`dsh --profile acp`](../../../../apps/cli/README.md) and `@deepseek-ai/dsh-sdk-jsonrpc-server` own their framed protocol integrations.
 
 The SDK project model that carried the `stdio` run-interface option is deleted by the [SDK project toolchain removal](2026-08-11-remove-sdk-project-toolchain.md). Repository-facing demo documentation requires a DeepSeek API key and leads with a current runnable product.
 

+ 1 - 1
.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.zh.md

@@ -20,7 +20,7 @@ DeepSeek Harness 在 TUI 和 Headless coding agent 之外,还提供了两个
 
 - `@deepseek-ai/dsh-tui` 负责终端交互式执行。它会在 Loader 启动前拒绝非 TTY 流;`apps/cli/config/base.cordis.yml` 与 `tui.cordis.yml` overlay 拥有完整 coding 组装,PTY 与终端快照覆盖则位于 `apps/cli/tests/`。
 - [`dsh --profile headless`](../../../../apps/cli/README.zh.md) 负责非交互式执行。其 `headless` profile 是产品组装;`examples/headless-agent` 负责回放快照、通用真实 agent 测试套件和未导出的无密钥 Loader driver。
-- [`@deepseek-ai/dsh-acp-demo`](../../../../packages/examples/acp-demo/README.zh.md) 和 `@deepseek-ai/dsh-sdk-jsonrpc-server` 负责各自的分帧协议集成。
+- [`dsh --profile acp`](../../../../apps/cli/README.zh.md) 和 `@deepseek-ai/dsh-sdk-jsonrpc-server` 负责各自的分帧协议集成。
 
 承载 `stdio` 运行接口选项的 SDK 项目模型已由 [SDK 项目工具链移除决策](2026-08-11-remove-sdk-project-toolchain.zh.md)删除。仓库中的演示文档要求 DeepSeek API key,并优先引导到当前可运行的产品。
 

+ 2 - 2
.agents/notes/implemented/testing/2026-08-13-python-minimal-model-visible-snapshot.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/testing/2026-08-13-python-minimal-model-visible-snapshot.md
-2026-08-13-python-minimal-model-visible-snapshot.md: 66cfa1ed667d9a60579b0d27ddca2667614d7e1c
-2026-08-13-python-minimal-model-visible-snapshot.zh.md: 866988bddab6f257af1bdb7b37e8b238888259dc
+2026-08-13-python-minimal-model-visible-snapshot.md: 37c76be93fb4f18fa99ceec7c15d20e572f2dfb3
+2026-08-13-python-minimal-model-visible-snapshot.zh.md: 5c2b0dd5fcd6ec5ea3a68eb884e19b0917bbe0be

+ 1 - 1
.agents/notes/implemented/testing/2026-08-13-python-minimal-model-visible-snapshot.md

@@ -6,7 +6,7 @@ English | [中文](2026-08-13-python-minimal-model-visible-snapshot.zh.md)
 
 ## Problem
 
-The Python lane never compared what the minimal composition actually shows the model. Dynamic runtime context reaches history as a user message, so the mock model's assertion that system-role messages equal the deployment persona could not see it, and the advanced executable snapshot replaces each request header's assembled system prompt with a token and each tool schema with its name. The sandbox-policy runtime-context message therefore rode along in the checked-in [minimal composition](../../../../examples/jsonrpc-agent/minimal.cordis.yml) while `python-runtime` stayed green, and any plugin that adds a system section, a tool, or another context message could do the same.
+The Python lane never compared what the minimal composition actually shows the model. Dynamic runtime context reaches history as a user message, so the mock model's assertion that system-role messages equal the deployment persona could not see it, and the advanced executable snapshot replaces each request header's assembled system prompt with a token and each tool schema with its name. The sandbox-policy runtime-context message therefore rode along in the checked-in [minimal composition](../../../../examples/python-sdk-agent/minimal.cordis.yml) while `python-runtime` stayed green, and any plugin that adds a system section, a tool, or another context message could do the same.
 
 ## Decision
 

+ 1 - 1
.agents/notes/implemented/testing/2026-08-13-python-minimal-model-visible-snapshot.zh.md

@@ -6,7 +6,7 @@ Status: implemented
 
 ## 问题
 
-Python 通道从未比对极简组合实际展示给模型的内容。动态运行时上下文以 user 消息进入历史,因此 mock 模型"system 角色消息等于部署 persona"的断言看不见它;而进阶可执行文件快照会把每个请求头中已组装的系统提示词换成占位符、把每个工具 schema 换成其名称。于是 sandbox-policy 的运行时上下文消息一直搭车留在签入的[极简组合](../../../../examples/jsonrpc-agent/minimal.cordis.yml)里,而 `python-runtime` 始终是绿的;任何新增系统分段、工具或其他上下文消息的插件都能照此蒙混过关。
+Python 通道从未比对极简组合实际展示给模型的内容。动态运行时上下文以 user 消息进入历史,因此 mock 模型"system 角色消息等于部署 persona"的断言看不见它;而进阶可执行文件快照会把每个请求头中已组装的系统提示词换成占位符、把每个工具 schema 换成其名称。于是 sandbox-policy 的运行时上下文消息一直搭车留在签入的[极简组合](../../../../examples/python-sdk-agent/minimal.cordis.yml)里,而 `python-runtime` 始终是绿的;任何新增系统分段、工具或其他上下文消息的插件都能照此蒙混过关。
 
 ## 决策
 

+ 4 - 3
.github/workflows/e2e.yml

@@ -60,8 +60,9 @@ jobs:
     if: >-
       github.event_name != 'pull_request'
       || !(github.event.pull_request.head.repo.fork || github.event.pull_request.user.login == 'dependabot[bot]')
-    # Bounded file parallelism (DSH_E2E_MAX_WORKERS), 120s/test, retry 2. 45m
-    # still bounds retry storms against a slow API while the happy path fans out.
+    # Profile e2e files can each own several complete dsh subprocess trees, so
+    # four file workers preserve process/PTY headroom. Tests retain 120s/test
+    # and retry 2; 45m still bounds retry storms against a slow API.
     timeout-minutes: 45
     steps:
       - uses: actions/checkout@v6
@@ -115,6 +116,6 @@ jobs:
         env:
           DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}
           DEEPSEEK_BASE_URL: https://api.deepseek.com
-          DSH_E2E_MAX_WORKERS: 14
+          DSH_E2E_MAX_WORKERS: 4
           DSH_EXAMPLE_MODE: lib
         run: pnpm run test:e2e

+ 10 - 10
AGENTS.md

@@ -4,7 +4,11 @@ DeepSeek Harness is an all-plugin agent harness on vendored Cordis. Read [docs/a
 
 ## Pre-release stance: foundation over blast radius
 
-**Remove this section at the first tagged release.** With no external consumers, prefer the correct foundation over compatibility shims: rename or repackage freely and update every reference together. Backends reject old on-disk formats. SQLite uses monotonic `SCHEMA_VERSION`; `dsh-session` keeps `SESSION_FORMAT_VERSION` at `0` with no compatibility promise.
+**Remove at the first tagged release.** Until then, prefer correct foundations over compatibility shims and update every reference together. Backends reject old disk formats; SQLite increments `SCHEMA_VERSION`, while `dsh-session` holds `SESSION_FORMAT_VERSION` at `0` without a compatibility promise.
+
+## Application launch
+
+Node apps launch only through `dsh` profiles; application-package bins, demos, and SDK argv escape hatches are forbidden. The private Python runtime is the sole temporary exception. [Architecture](docs/architecture.md#application-launch) owns scope and deferred artifact rename; `pnpm run verify-application-entrypoints` enforces it.
 
 ## Repository layout
 
@@ -41,9 +45,9 @@ packages/    @deepseek-ai/dsh-<pkg> workspaces at packages/<group>/<pkg>/
   credentials/ credential/authorization capabilities + env/.env provider
   acp/         automation-only Agent Client Protocol server
   interaction/ approval/interaction capabilities, permission, commands, ask-user
-  boot/        shared app-bin glue
-  sdk/         JSON-RPC protocol, server, and TypeScript client
-  examples/    demo bundles (agent-spine + CLI/ACP/JSON-RPC bins)
+  boot/        shared profile/application boot glue
+  sdk/         JSON-RPC protocol, server, TypeScript client, and private Python carrier
+  examples/    reusable demo bundles (agent-spine)
   experimental/ private prototypes excluded from official releases
   support/     dev/test infrastructure
   util/        zero-dependency utilities
@@ -83,15 +87,11 @@ pnpm run demo:acp       # ACP automation server (needs DEEPSEEK_API_KEY)
 
 ### Host sandbox failures
 
-When required `gh`, `pnpm`, build, test, or generator commands fail because the agent sandbox blocks credentials, network, IPC, file watching, or nested `sandbox-exec`, retry unchanged with the narrowest host escalation before diagnosing authentication or project failure. Require sandbox evidence; never bypass genuine test failures or the product sandbox under test.
+If a required `gh`, `pnpm`, build, test, or generator command fails because the sandbox blocks credentials, network, IPC, watching, or nested `sandbox-exec`, retry unchanged with the narrowest host escalation. Require sandbox evidence; never bypass test failures or the product sandbox.
 
 ### Run relevant checks locally
 
-Run checks before pushes via [dsh-pre-push-checks](.agents/skills/dsh-pre-push-checks/SKILL.md); report only commands run. After `gh stack sync`, validate immediately; do not merge before checks pass.
-
-- Match evidence to the surface: focused tests for behavior, snapshots for model or user output, `doc-sync` for docs, build/hygiene and built smokes for published paths, and real-API e2e for provider behavior.
-- Never default to the full suite or repeat a passing check for commit or push. CI owns exhaustive coverage and the platform matrix; rehearse all locally only by explicit request, for CI diagnosis, or for an irreducibly repository-wide change.
-- `test:coverage`, not `test`, is the CI coverage gate ([why](docs/testing.md)).
+Before pushes, use [dsh-pre-push-checks](.agents/skills/dsh-pre-push-checks/SKILL.md) to choose the smallest diff-covering checks; after `gh stack sync`, validate immediately and never merge before they pass. Report commands only. Match evidence to its surface: focused behavior tests, model/user snapshots, `doc-sync`, build/hygiene plus built smokes for published paths, and real-API e2e for provider behavior. CI owns exhaustive coverage and the platform matrix; run them locally only by request, for CI diagnosis, or for an irreducibly repository-wide change. `test:coverage`, not `test`, is the CI coverage gate ([why](docs/testing.md)).
 
 ## Secrets / .env
 

+ 2 - 2
apps/cli/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/README.md
-README.md: 9a8d722b044ed5d8e31e3c27e54f8c9ef0839f82
-README.zh.md: c092414e2d15e90133d8ea27f0af5cadbe527b22
+README.md: d59b8264093dafb0160893c942371a4daa942c14
+README.zh.md: 6a209f5ad64b38c138ae1712a05ed9e840d9a74f

+ 6 - 4
apps/cli/README.md

@@ -2,18 +2,20 @@
 
 English | [中文](README.zh.md)
 
-The `dsh` command is the product launcher for profiles: ordered stacks of plugin-bundle patch layers under the user's own overrides. [`src/args.ts`](src/args.ts) owns the command grammar, and [`src/bin.ts`](src/bin.ts) loads only the selected runner. Invalid commands, options from another mode, configuration errors, and boot failures exit nonzero.
+The `dsh` command is the sole supported Node application launcher: profiles are ordered stacks of plugin-bundle patch layers under the user's own overrides. SDK and ACP are profiles, not separate public bins. [`src/args.ts`](src/args.ts) owns the command grammar, and [`src/bin.ts`](src/bin.ts) loads only the selected runner. Invalid commands, options from another mode, configuration errors, and boot failures exit nonzero.
 
 ## Entry modes
 
 | Command | Purpose |
 |---|---|
 | `dsh --profile <name>` | Boot the named profile under `$DSH_HOME/profiles/<name>`. |
+| `dsh --profile acp` | Serve automation clients over ACP stdio until disconnect. |
 | `dsh --profile headless "job"` | Run one fresh persisted session, print the final answer, and exit. |
+| `dsh --profile sdk` | Serve SDK clients over JSON-RPC stdio until shutdown or disconnect. |
 | `dsh web` | Alias of `--profile web`. |
 | `dsh plugin --profile <name> <pnpm args>` | Manage a profile's plugins by forwarding to pnpm in the profile directory. |
 
-The invoking directory is the default workspace root. The `web` and `headless` profiles auto-initialize on first use from shipped templates; any other profile must be created through `dsh plugin`.
+The invoking directory is the default workspace root. The `web`, `headless`, `sdk`, and `acp` profiles auto-initialize on first use from shipped templates; any other profile must be created through `dsh plugin`.
 
 ## App arguments
 
@@ -29,14 +31,14 @@ dsh --help                          # the launcher's own help
 
 ## Profiles
 
-A profile directory holds a `package.json` (out-of-tree plugin dependencies plus the profile manifest `dsh.profile` with its ordered `bundles` list) and a `cordis.patch.yml` (the user's own patch layer).
+A profile directory holds a `package.json` (out-of-tree plugin dependencies plus the profile manifest `dsh.profile` with its ordered `bundles` list and `patchReload` lifecycle) and a `cordis.patch.yml` (the user's own patch layer). `patchReload: live` watches the profile and home-level patch files; `startup` applies them once.
 
 The tree composes over an empty root:
 - each bundle's patch in `dsh.profile.bundles` order
 - then the profile's `cordis.patch.yml`, then the home-level `$DSH_HOME/cordis.patch.yml`
 - then `--patch` overlays
 
-Bundles named in `dsh.profile.bundles` resolve from the dsh installation first (`@deepseek-ai/dsh-base`, `@deepseek-ai/dsh-web-app`, `@deepseek-ai/dsh-headless`), then from the profile's own `node_modules`, where pnpm installs out-of-tree plugins.
+Bundles named in `dsh.profile.bundles` resolve from the dsh installation first (`@deepseek-ai/dsh-base`, `@deepseek-ai/dsh-web-app`, `@deepseek-ai/dsh-headless`, `@deepseek-ai/dsh-sdk-app`, `@deepseek-ai/dsh-acp-app`), then from the profile's own `node_modules`, where pnpm installs out-of-tree plugins.
 
 Use `--dump-default-config` and `--dump-config` to inspect the composed tree without booting it.
 

+ 6 - 4
apps/cli/README.zh.md

@@ -2,18 +2,20 @@
 
 [English](README.md) | 中文
 
-`dsh` 是 DeepSeek Harness 中用于启动 profile 的命令;profile 由多个插件组合包 patch 层按顺序叠加而成,其上再应用用户自己的覆盖配置。[`src/args.ts`](src/args.ts) 负责命令语法,[`src/bin.ts`](src/bin.ts) 只加载选中的运行器。无效命令、来自其他模式的选项、配置错误和启动失败都会以非零状态退出。
+`dsh` 是唯一受支持的 Node 应用启动器;profile 由多个插件组合包 patch 层按顺序叠加而成,其上再应用用户自己的覆盖配置。SDK 与 ACP 都是 profile,而不是独立的公开 bin。[`src/args.ts`](src/args.ts) 负责命令语法,[`src/bin.ts`](src/bin.ts) 只加载选中的运行器。无效命令、来自其他模式的选项、配置错误和启动失败都会以非零状态退出。
 
 ## 入口模式
 
 | 命令 | 用途 |
 |---|---|
 | `dsh --profile <name>` | 启动位于 `$DSH_HOME/profiles/<name>` 的指定 profile。 |
+| `dsh --profile acp` | 通过 ACP stdio 为自动化 client 提供服务,直至断开连接。 |
 | `dsh --profile headless "job"` | 运行一个全新的持久化会话,打印最终答案并退出。 |
+| `dsh --profile sdk` | 通过 JSON-RPC stdio 为 SDK client 提供服务,直至关闭或断开连接。 |
 | `dsh web` | `--profile web` 的别名。 |
 | `dsh plugin --profile <name> <pnpm args>` | 通过在 profile 目录中转发给 pnpm 来管理该 profile 的插件。 |
 
-运行命令时所在的目录将作为默认 workspace 根目录。`web` 和 `headless` profile 在首次使用时会从随附模板自动初始化;其他任何 profile 都必须通过 `dsh plugin` 创建。
+运行命令时所在的目录将作为默认 workspace 根目录。`web`、`headless`、`sdk` 和 `acp` profile 在首次使用时会从随附模板自动初始化;其他任何 profile 都必须通过 `dsh plugin` 创建。
 
 ## 应用参数
 
@@ -31,14 +33,14 @@ dsh --help                          # the launcher's own help
 
 ## Profile
 
-profile 目录包含一个 `package.json`,其中记录树外插件依赖,以及 profile manifest(元数据清单)`dsh.profile` 和其中按顺序排列的 `bundles` 列表;还包含一个 `cordis.patch.yml`,其中保存用户自己的 patch 层。
+profile 目录包含一个 `package.json`,其中记录树外插件依赖,以及 profile manifest(元数据清单)`dsh.profile`、其中按顺序排列的 `bundles` 列表与 `patchReload` 生命周期;还包含一个 `cordis.patch.yml`,其中保存用户自己的 patch 层。`patchReload: live` 监视 profile 与 home 级 patch 文件,`startup` 则只应用一次。
 
 配置树以空根为起点,依次叠加以下配置层:
 - `dsh.profile.bundles` 中各组合包的 patch
 - profile 自身的 `cordis.patch.yml`,然后是 home 级的 `$DSH_HOME/cordis.patch.yml`
 - `--patch` 指定的覆盖层
 
-`dsh.profile.bundles` 中列出的组合包先从 dsh 安装目录解析(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`),再从 profile 自身的 `node_modules` 解析;pnpm 会将树外插件安装到该目录。
+`dsh.profile.bundles` 中列出的组合包先从 dsh 安装目录解析(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`、`@deepseek-ai/dsh-sdk-app`、`@deepseek-ai/dsh-acp-app`),再从 profile 自身的 `node_modules` 解析;pnpm 会将树外插件安装到该目录。
 
 使用 `--dump-default-config` 和 `--dump-config` 可在不启动的情况下检查组合后的配置树。
 

+ 5 - 0
apps/cli/package.json

@@ -29,6 +29,7 @@
     "@deepseek-ai/cordis-plugin-include": "workspace:^",
     "@deepseek-ai/cordis-plugin-loader": "workspace:^",
     "@deepseek-ai/cordis-plugin-timer": "workspace:^",
+    "@deepseek-ai/dsh-acp-app": "workspace:^",
     "@deepseek-ai/dsh-agent-tool-presentation": "workspace:^",
     "@deepseek-ai/dsh-app-boot": "workspace:^",
     "@deepseek-ai/dsh-base": "workspace:^",
@@ -47,6 +48,8 @@
     "@deepseek-ai/dsh-headless": "workspace:^",
     "@deepseek-ai/dsh-mcp-client": "workspace:^",
     "@deepseek-ai/dsh-home-paths": "workspace:^",
+    "@deepseek-ai/dsh-hooks-claude-code": "workspace:^",
+    "@deepseek-ai/dsh-hooks-codex": "workspace:^",
     "@deepseek-ai/dsh-persona": "workspace:^",
     "@deepseek-ai/dsh-plan-mode": "workspace:^",
     "@deepseek-ai/dsh-terminal": "workspace:^",
@@ -55,6 +58,7 @@
     "@deepseek-ai/dsh-pwsh-sandbox": "workspace:^",
     "@deepseek-ai/dsh-session-projection": "workspace:^",
     "@deepseek-ai/dsh-session-reference": "workspace:^",
+    "@deepseek-ai/dsh-sdk-app": "workspace:^",
     "@deepseek-ai/dsh-time-context": "workspace:^",
     "@deepseek-ai/dsh-skill": "workspace:^",
     "@deepseek-ai/dsh-skill-filesystem": "workspace:^",
@@ -91,6 +95,7 @@
     "node-addon-require-builtin": "^0.1.4"
   },
   "devDependencies": {
+    "@agentclientprotocol/sdk": "1.4.0",
     "@deepseek-ai/dsh-agent": "workspace:^",
     "@deepseek-ai/dsh-host-frontend-static": "workspace:^",
     "@deepseek-ai/dsh-host-apiproxy": "workspace:^",

+ 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: 2a5740f87c76a184c3100461ea0c86343b734b93
-README.zh.md: dd25f413c663fcc836f9b50d53dee26463a939bd
+README.md: 0f407afa3b06d144681550d5096bf96c498e6451
+README.zh.md: bf4dc4ca9f49c1801d108234411123de459c0444

+ 7 - 5
apps/cli/reference/README.md

@@ -6,17 +6,17 @@ This reference defines the profile, web-alias, plugin-management, and config-dum
 
 ## Profile boot
 
-`dsh --profile <name>` boots the profile at `$DSH_HOME/profiles/<name>`. The effective tree is composed over an empty root by applying, in order: each bundle patch named in the profile manifest's `dsh.profile.bundles` list, the profile's own `cordis.patch.yml`, the home-level `$DSH_HOME/cordis.patch.yml` (machine-local preferences shared by every profile, so it outranks the per-profile layer), and each `--patch <path>` overlay in argv order. Later layers win per row; a patch replaces the targeted row's complete `config` value rather than deep-merging keys, and may insert new rows. A parse, schema, resolution, or plugin boot failure is reported and exits nonzero. SIGINT and SIGTERM dispose the mounted root before exit.
+`dsh --profile <name>` boots the profile at `$DSH_HOME/profiles/<name>`. The effective tree is composed over an empty root by applying, in order: each bundle patch named in the profile manifest's `dsh.profile.bundles` list, the profile's own `cordis.patch.yml`, the home-level `$DSH_HOME/cordis.patch.yml` (machine-local preferences shared by every profile, so it outranks the per-profile layer), and each `--patch <path>` overlay in argv order. Later layers win per row; a patch replaces the targeted row's complete `config` value rather than deep-merging keys, and may insert new rows. `dsh.profile.patchReload` selects `live` patch-file watching or `startup` one-time loading; omission defaults a custom profile to `live`. A parse, schema, resolution, or plugin boot failure is reported and exits nonzero. SIGINT and SIGTERM dispose the mounted root before exit.
 
-Bundle names resolve from the dsh installation first, then from the profile directory. In-box bundles (`@deepseek-ai/dsh-base`, `@deepseek-ai/dsh-web-app`, `@deepseek-ai/dsh-headless`) therefore always come from the same installation as the running `dsh`; out-of-tree bundles come from the profile's pnpm-managed `node_modules`. A bare plugin `name` in any patch row resolves through the profile directory's Node parent-walk, which reaches the maintained installation fallback `$DSH_HOME/profiles/node_modules` (one symlink per package the installation's app and bundles depend on, healed on every launch).
+Bundle names resolve from the dsh installation first, then from the profile directory. In-box bundles (`@deepseek-ai/dsh-base`, `@deepseek-ai/dsh-web-app`, `@deepseek-ai/dsh-headless`, `@deepseek-ai/dsh-sdk-app`, `@deepseek-ai/dsh-acp-app`) therefore always come from the same installation as the running `dsh`; out-of-tree bundles come from the profile's pnpm-managed `node_modules`. A bare plugin `name` in any patch row resolves through the profile directory's Node parent-walk, which reaches the maintained installation fallback `$DSH_HOME/profiles/node_modules` (one symlink per package the installation's app and bundles depend on, healed on every launch).
 
-The `web` and `headless` profiles auto-initialize from shipped templates on first use (`web`: base + web-app; `headless`: base + headless). Any other missing profile fails loud with a hint to run `dsh plugin --profile <name> add <package>`.
+The `web`, `headless`, `sdk`, and `acp` profiles auto-initialize from shipped templates on first use (`web`: base + web-app with live patches; `headless`: base + headless with startup-only patches; `sdk`: base + sdk-app with startup-only patches; `acp`: base + acp-app with startup-only patches). Any other missing profile fails loud with a hint to run `dsh plugin --profile <name> add <package>`.
 
 ### App arguments
 
 The launcher's flags come first and end at the first token it does not recognize; everything from there on is handed to the booted profile verbatim through `ctx.cmdlineArgs`, where any injected app plugin may parse it ([`dsh-cmdline`](../../../packages/boot/cmdline/README.md)). `dsh --profile web --port 8080` therefore reaches the web app's `--port`, `dsh --profile web --help` prints that app's help and boots nothing, and `dsh --help` (no profile to hand it to) prints the launcher's own. `-V`/`--version` prints the launcher's version when it appears before the app-argument boundary.
 
-A composition mounts once. An ordinary plugin injects `cmdlineArgs`, parses this app's arguments, and provides what it resolved as a service; each row configured from flags injects that service, and Loader waits for it before evaluating the row's config (`port: !!js ctx.webStartup.port ?? 3080`). A flag therefore beats the value written beside it. This precedence requires the row to retain that expression; a user patch that replaces the whole `config` with literals removes the runtime read. Help and rejected arguments request exit — nonzero for a rejection, 0 for help — without activating rows that depend on the provider's service. A live `cordis.patch.yml` edit re-evaluates expressions against services that are still up, so it cannot reset a served port.
+A composition mounts once. An ordinary plugin injects `cmdlineArgs`, parses this app's arguments, and provides what it resolved as a service; each row configured from flags injects that service, and Loader waits for it before evaluating the row's config (`port: !!js ctx.webStartup.port ?? 3080`). A flag therefore beats the value written beside it. This precedence requires the row to retain that expression; a user patch that replaces the whole `config` with literals removes the runtime read. Help and rejected arguments request exit — nonzero for a rejection, 0 for help — without activating rows that depend on the provider's service. In a `patchReload: live` profile, a patch-file edit re-evaluates expressions against services that are still up, so it cannot reset a served port.
 
 Launcher flags must come before app arguments, and the launcher's parser consumes one `--`: an app argument that must arrive as a literal `--` needs `-- --`. A first app argument equal to `web` or `plugin` selects that subcommand instead. `ctx.cmdlineArgs.get()` is a shared immutable read: multiple plugins may parse the same snapshot, while a profile with no reader ignores its app arguments.
 
@@ -26,6 +26,8 @@ The shipped apps own these command lines:
 |---|---|
 | `web` | `--host`, `--port`, repeatable `--trusted-host`, `--no-open` |
 | `headless` | the task text, as the positional argument |
+| `sdk` | no options; stdio carries the JSON-RPC protocol |
+| `acp` | no options; stdio carries Agent Client Protocol |
 
 A one-shot task (`dsh --profile headless "run the tests"`) creates one fresh persisted Agent through the core registry, submits the task, waits for quiescence, and flushes the Session before deriving the last non-empty assistant text and final `turn/end` reason from its durable interval. It prints the text on stdout and exits 0 for `completed`, else 1. An invocation with no task is a usage error from that app. The shipped headless profile mounts no ApiProxy, Host, HTTP server, Web runtime, or browser client; a successful run writes nothing to stderr and opens no listening port.
 
@@ -78,7 +80,7 @@ The production Web runner needs built package and frontend artifacts (`pnpm run
 
 Process shutdown gives the plugin tree up to five seconds to dispose. The first `SIGINT`/`SIGTERM` starts that graceful drain — `SIGTERM` is a supervisor's ordinary stop request and exits 0 on every surface, `SIGINT` reports 130; a second signal forces immediate exit. If one-shot normal completion is already stuck in disposal, the first `Ctrl+C` is the escalation and exits immediately instead of being swallowed.
 
-All modes treat the invoking directory as the default workspace root, load applicable `AGENTS.md` or `CLAUDE.md` instructions with a 65,536-byte render budget, and use an in-memory SQLite session content index. Every profile boot watches valid edits of both `cordis.patch.yml` layers (profile and home) and reapplies them transactionally; a one-shot surface exits through its bounded shutdown, which disposes the watchers.
+All modes treat the invoking directory as the default workspace root, load applicable `AGENTS.md` or `CLAUDE.md` instructions with a 65,536-byte render budget, and use an in-memory SQLite session content index. A `patchReload: live` profile watches valid edits of both `cordis.patch.yml` layers (profile and home) and reapplies them transactionally; a `startup` profile applies them once. A one-shot surface exits through its bounded shutdown, which disposes any live watchers.
 
 New sessions default to the `workspace-write` permission preset. Bash and filesystem mutations are restricted to the session workspace and platform temporary roots; reads and network access are not confined, while process visibility depends on the selected sandbox backend — bwrap runs commands in a private PID namespace that hides host processes, and Landlock and Seatbelt leave host process visibility unchanged. `DSH_PERMISSION_MODE` changes the process fallback. Stored General-settings permissions affect later Web sessions, not an already-open one.
 

+ 7 - 5
apps/cli/reference/README.zh.md

@@ -6,17 +6,17 @@
 
 ## Profile 启动
 
-`dsh --profile <name>` 启动位于 `$DSH_HOME/profiles/<name>` 的 profile。生效配置树以空根节点为起点,依次叠加 profile manifest(元数据清单)的 `dsh.profile.bundles` 列表中指定的各组合包 patch、profile 自身的 `cordis.patch.yml`、home 级的 `$DSH_HOME/cordis.patch.yml`(这是各 profile 共享的机器本地偏好,因此优先于逐 profile 配置层),以及按 argv 顺序指定的各个 `--patch <path>` 覆盖层。对同一配置行,后应用的层优先。patch 会替换目标行的整个 `config` 值,而不是深度合并其中的键;patch 也可以插入新行。配置解析、schema 校验、模块解析或插件启动失败时,系统会报告错误并以非零状态退出。收到 SIGINT 或 SIGTERM 时,挂载的根节点会先 dispose(资源释放)再退出。
+`dsh --profile <name>` 启动位于 `$DSH_HOME/profiles/<name>` 的 profile。生效配置树以空根节点为起点,依次叠加 profile manifest(元数据清单)的 `dsh.profile.bundles` 列表中指定的各组合包 patch、profile 自身的 `cordis.patch.yml`、home 级的 `$DSH_HOME/cordis.patch.yml`(这是各 profile 共享的机器本地偏好,因此优先于逐 profile 配置层),以及按 argv 顺序指定的各个 `--patch <path>` 覆盖层。对同一配置行,后应用的层优先。patch 会替换目标行的整个 `config` 值,而不是深度合并其中的键;patch 也可以插入新行。`dsh.profile.patchReload` 可选择 `live` patch 文件监视或 `startup` 单次加载;自定义 profile 省略该值时默认使用 `live`。配置解析、schema 校验、模块解析或插件启动失败时,系统会报告错误并以非零状态退出。收到 SIGINT 或 SIGTERM 时,挂载的根节点会先 dispose(资源释放)再退出。
 
-组合包名称先从 dsh 安装目录解析,再从 profile 目录解析。因此,内置组合包(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`)始终来自当前运行的 `dsh` 所属的安装;树外组合包则来自 profile 中由 pnpm 管理的 `node_modules`。patch 行中的裸插件 `name` 会从 profile 目录开始,按照 Node 的模块解析规则逐级向父目录查找,直至由 dsh 维护的安装后备目录 `$DSH_HOME/profiles/node_modules`。该目录为 dsh 安装中的应用和组合包所依赖的每个包各维护一个符号链接,并在每次启动时修复这些链接。
+组合包名称先从 dsh 安装目录解析,再从 profile 目录解析。因此,内置组合包(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`、`@deepseek-ai/dsh-sdk-app`、`@deepseek-ai/dsh-acp-app`)始终来自当前运行的 `dsh` 所属的安装;树外组合包则来自 profile 中由 pnpm 管理的 `node_modules`。patch 行中的裸插件 `name` 会从 profile 目录开始,按照 Node 的模块解析规则逐级向父目录查找,直至由 dsh 维护的安装后备目录 `$DSH_HOME/profiles/node_modules`。该目录为 dsh 安装中的应用和组合包所依赖的每个包各维护一个符号链接,并在每次启动时修复这些链接。
 
-`web` 和 `headless` profile 首次使用时会从随附模板自动初始化(`web`:base + web-app;`headless`:base + headless)。其他缺失的 profile 会显式报错,并提示运行 `dsh plugin --profile <name> add <package>`。
+`web`、`headless`、`sdk` 和 `acp` profile 首次使用时会从随附模板自动初始化(`web`:base + web-app,实时应用 patch;`headless`:base + headless,只在启动时应用 patch;`sdk`:base + sdk-app,只在启动时应用 patch;`acp`:base + acp-app,只在启动时应用 patch)。其他缺失的 profile 会显式报错,并提示运行 `dsh plugin --profile <name> add <package>`。
 
 ### 应用参数
 
 启动器自身的 flag 必须写在最前面,并在遇到第一个无法识别的 token 时结束;从该 token 开始的所有内容都会通过 `ctx.cmdlineArgs` 原样交给已启动的 profile,注入该 profile 的任意应用插件都可以解析这些内容([`dsh-cmdline`](../../../packages/boot/cmdline/README.zh.md))。因此,`dsh --profile web --port 8080` 会将 `--port` 交给 web 应用;`dsh --profile web --help` 只打印该应用的帮助信息,不启动应用;`dsh --help` 没有可供交付参数的 profile,因此会打印启动器自身的帮助信息。`-V`/`--version` 位于应用参数边界之前时,会打印启动器的版本。
 
-每套组合只会挂载一次。普通插件注入 `cmdlineArgs`,解析所属应用的参数,并将解析结果作为服务提供。每个从 flag 取值的配置行都会注入该服务;Loader 会等到服务激活后,再对该行的配置求值(`port: !!js ctx.webStartup.port ?? 3080`),因此 flag 的优先级高于配置行中写明的值。要维持这一优先级,配置行必须保留该表达式;如果用户 patch 用字面量替换整个 `config`,也会随之移除运行时读取。帮助参数和被拒绝的参数都会请求退出:参数被拒绝时以非零状态退出,显示帮助时以 0 退出;依赖该提供方服务的配置行不会激活。在线编辑 `cordis.patch.yml` 时,系统会根据仍在运行的服务重新计算表达式,因此不会重置当前正在使用的端口。
+每套组合只会挂载一次。普通插件注入 `cmdlineArgs`,解析所属应用的参数,并将解析结果作为服务提供。每个从 flag 取值的配置行都会注入该服务;Loader 会等到服务激活后,再对该行的配置求值(`port: !!js ctx.webStartup.port ?? 3080`),因此 flag 的优先级高于配置行中写明的值。要维持这一优先级,配置行必须保留该表达式;如果用户 patch 用字面量替换整个 `config`,也会随之移除运行时读取。帮助参数和被拒绝的参数都会请求退出:参数被拒绝时以非零状态退出,显示帮助时以 0 退出;依赖该提供方服务的配置行不会激活。在 `patchReload: live` profile 中,编辑 patch 文件会根据仍在运行的服务重新计算表达式,因此不会重置当前正在使用的端口。
 
 启动器的 flag 必须写在应用参数之前,且启动器的解析器会消耗掉一个 `--`:必须以字面量 `--` 送达应用的参数需要写成 `-- --`。如果应用的第一个参数恰好等于 `web` 或 `plugin`,会选择对应的子命令。`ctx.cmdlineArgs.get()` 是共享的不可变读取:多个插件可以解析同一份快照,没有读取方的 profile 则会忽略自己的应用参数。
 
@@ -26,6 +26,8 @@
 |---|---|
 | `web` | `--host`、`--port`、可重复的 `--trusted-host`、`--no-open` |
 | `headless` | 任务文本,作为位置参数 |
+| `sdk` | 无选项;stdio 携带 JSON-RPC 协议 |
+| `acp` | 无选项;stdio 携带 Agent Client Protocol |
 
 一次性任务(`dsh --profile headless "run the tests"`)通过核心注册表创建一个全新的持久化 Agent(智能体),提交任务、等待完全停稳并对会话执行 flush,再从其持久化事件区间中推导最后一个非空 assistant 文本与最终 `turn/end` 原因。它在 stdout 打印文本,并在原因为 `completed` 时以 0 退出,否则以 1 退出。没有任务的调用是该应用的用法错误。随附 headless profile 不挂载 ApiProxy、Host、HTTP 服务器、Web 运行时或浏览器客户端;成功运行不会向 stderr 写入任何内容,也不会打开监听端口。
 
@@ -78,7 +80,7 @@ dsh web --help
 
 进程关闭时,插件树最多有 5 秒完成 dispose。首次收到 `SIGINT` 或 `SIGTERM` 时会开始优雅排空:`SIGTERM` 是监督进程发出的常规停止请求,在所有运行模式下都以 0 退出;`SIGINT` 则报告 130。第二次收到信号时会立即强制退出。如果一次性运行在正常结束时已经卡在 dispose 阶段,第一次按下 `Ctrl+C` 就会直接升级为强制退出,而不会被忽略。
 
-所有模式都将运行命令时所在的目录作为默认 workspace 根目录,以 65,536 字节渲染预算加载适用的 `AGENTS.md` 或 `CLAUDE.md` 指令,并使用内存 SQLite 会话内容索引。每次启动 profile 时,系统都会监视 profile 与 home 两个 `cordis.patch.yml` 配置层的有效变更,并以事务方式重新应用;一次性运行模式通过有界关闭流程退出,该流程会先 dispose 监视器。
+所有模式都将运行命令时所在的目录作为默认 workspace 根目录,以 65,536 字节渲染预算加载适用的 `AGENTS.md` 或 `CLAUDE.md` 指令,并使用内存 SQLite 会话内容索引。`patchReload: live` profile 会监视 profile 与 home 两个 `cordis.patch.yml` 配置层的有效变更,并以事务方式重新应用;`startup` profile 则只应用一次。一次性运行模式通过有界关闭流程退出,该流程会 dispose(资源释放)所有实时监视器。
 
 新会话默认使用 `workspace-write` 权限预设。Bash 和文件系统修改仅限于会话 workspace 与平台临时根目录;读取和网络访问不受限制,进程可见性则取决于所选沙箱后端——bwrap 在私有 PID 命名空间中运行命令并隐藏宿主进程,Landlock 与 Seatbelt 保持宿主进程可见性不变。`DSH_PERMISSION_MODE` 更改进程后备值。General settings 中存储的权限影响后续 Web 会话,不改变已打开的会话。
 

+ 6 - 1
apps/cli/src/plugin.ts

@@ -120,7 +120,12 @@ function anchorPathSpec(argument: string, cwd: string): string {
 export function runPlugin(profile: string, args: readonly string[]): number {
   const dir = resolveProfileDir(profile)
   if (!existsSync(join(dir, 'package.json'))) {
-    initProfile(dir, PROFILE_TEMPLATES[profile] ?? DEFAULT_PROFILE_BUNDLES)
+    const template = PROFILE_TEMPLATES[profile]
+    initProfile(
+      dir,
+      template?.bundles ?? DEFAULT_PROFILE_BUNDLES,
+      template?.patchReload,
+    )
     process.stderr.write(`${NAME}: initialized profile ${profile} at ${dir}\n`)
   }
   const before = readProfileManifest(NAME, dir)

+ 47 - 18
apps/cli/src/profile-boot.ts

@@ -2,8 +2,8 @@
  * Shared profile boot for every `dsh` surface: resolve the profile, stack its
  * patch layers (bundle layers in `dsh.profile.bundles` order, the profile's
  * own `cordis.patch.yml`, `--patch` overlays, the telemetry switch), mount the
- * tree over the profile's empty root config, keep the profile patch layer
- * live, and wire fail-loud plus bounded shutdown.
+ * tree over the profile's empty root config, apply its selected patch-reload
+ * lifecycle, and wire fail-loud plus bounded shutdown.
  *
  * App flags are not the launcher's business: the invocation's inner arguments
  * are provided to the tree through `ctx.cmdlineArgs`, where any injected app
@@ -35,11 +35,35 @@ import { resolveDshHome } from '@deepseek-ai/dsh-home-paths'
 const SHIPPED_PRESET_ROOT = fileURLToPath(new URL('../config/agent-presets/', import.meta.url))
 
 import { DSH_LAUNCH_ENVIRONMENT_KEY, type LaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment'
-import { provideCmdline } from '@deepseek-ai/dsh-cmdline'
+import { provideCmdline, type AppReady } from '@deepseek-ai/dsh-cmdline'
 import { createProcessShutdown, type ProcessShutdown } from './process-shutdown.ts'
 
 const NAME = 'dsh'
 
+/** Launcher-owned readiness signal committed only after boot and host setup succeed. */
+function createAppReady(): { service: AppReady; commit(): void } {
+  let ready = false
+  const listeners = new Set<() => void>()
+  return {
+    service: {
+      onReady(listener) {
+        if (ready) {
+          listener()
+          return () => {}
+        }
+        listeners.add(listener)
+        return () => { listeners.delete(listener) }
+      },
+    },
+    commit() {
+      if (ready) return
+      ready = true
+      for (const listener of [...listeners]) listener()
+      listeners.clear()
+    },
+  }
+}
+
 /**
  * The home-level user patch layer (`$DSH_HOME/cordis.patch.yml`), applied
  * over every profile's own layer. Resolved per call, not at module load:
@@ -207,6 +231,7 @@ function suppressShutdownError(ctx: Context, signal: AbortSignal, error: unknown
 export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Context; shutdown: ProcessShutdown }> {
   const composed = composeProfile(options.profile, options.patchFiles)
   const app: { current?: Context } = {}
+  const appReady = createAppReady()
   const shutdown = createProcessShutdown(async () => { await app.current?.fiber.dispose() })
   const signalShutdown = new AbortController()
   const interrupt = (code: number): void => {
@@ -255,27 +280,26 @@ export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Con
     provideCmdline(hostCtx, {
       args: options.args,
       exit: code => void shutdown.shutdown(code),
+      ready: appReady.service,
     })
   })
   app.current = ctx
-  // A surface can dispose the whole tree while boot or this post-boot watcher
-  // setup is still in flight — a signal, or a fast one-shot's appExit. Loader
-  // presence and fiber state own liveness; the initial check skips a tree
-  // that already exited, and the catch below re-checks for an exit that
-  // landed mid-setup. Watching is unconditional: a one-shot surface exits
-  // through its bounded shutdown, which disposes the watchers before the
-  // loop drains.
-  if (!signalShutdown.signal.aborted
+  // A live-reload profile can dispose the whole tree while post-boot watcher
+  // setup is in flight — a signal or appExit. Loader presence and fiber state
+  // own liveness; the initial check skips a tree that already exited, and the
+  // catch below re-checks for an exit that landed mid-setup. Startup-frozen
+  // profiles apply every user layer above but install no HMR fallback or watcher.
+  if (composed.profile.patchReload === 'live'
+    && !signalShutdown.signal.aborted
     && ctx.fiber.state === FiberState.ACTIVE
     && ctx.get('loader') !== undefined) {
     try {
-      // Config-only HMR for the live profile patch layer: the web bundle
-      // disables the shared module-reload `hmr` row (its reload lifecycle is
-      // untested), so when the composition leaves no HMR service, mount a
-      // watch-only instance with no module roots — cordis.patch.yml edits stay
-      // live on every long-lived surface. A silent skip would break the
-      // documented hot-reload contract. HMR injects the timer service, which a
-      // bare custom profile may not mount either.
+      // Config-only HMR for the live profile patch layer: dsh-base disables
+      // module reload by default, so when no profile explicitly enabled that
+      // service, mount a watch-only instance with no module roots —
+      // cordis.patch.yml edits stay live without replacing source modules. A
+      // silent skip would break the documented reload contract. HMR injects
+      // the timer service, which a bare custom profile may not mount either.
       if (ctx.get('hmr') === undefined) {
         if (ctx.get('timer') === undefined) {
           await ctx.loader.create({ name: '@deepseek-ai/cordis-plugin-timer' })
@@ -296,5 +320,10 @@ export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Con
       suppressShutdownError(ctx, signalShutdown.signal, error)
     }
   }
+  if (!signalShutdown.signal.aborted
+    && ctx.fiber.state === FiberState.ACTIVE
+    && ctx.get('loader') !== undefined) {
+    appReady.commit()
+  }
   return { ctx, shutdown }
 }

+ 5 - 0
apps/cli/src/sdk-source.cordis.patch.yml

@@ -0,0 +1,5 @@
+# Clean source checkouts have no build-generated Typert contributor modules.
+# The SDK JSON-RPC application does not consume the Typert remote gateway;
+# installed builds retain the complete dsh-base row.
+- id: typert-loader
+  disabled: true

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

@@ -1,7 +1,16 @@
 import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
 import { tmpdir } from 'node:os'
 import { join } from 'node:path'
+import { createInterface } from 'node:readline'
+import { Readable, Writable } from 'node:stream'
 import { fileURLToPath, pathToFileURL } from 'node:url'
+import {
+  client as createAcpClientApp,
+  methods,
+  ndJsonStream,
+  PROTOCOL_VERSION,
+  type SessionNotification,
+} from '@agentclientprotocol/sdk'
 import { startMockLlmServer } from '@deepseek-ai/dsh-llm-mock-server'
 import { execa } from 'execa'
 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
@@ -356,6 +365,22 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
       expect(headlessHelp.stderr).toBe('')
       expect(headlessHelp.stdout).toContain('Usage: dsh --profile headless')
 
+      const sdkHelp = await runBuiltBin(['--profile', 'sdk', '--help'], {
+        DSH_HOME: home,
+        DSH_TELEMETRY_DISABLED: '1',
+      })
+      expect(sdkHelp.code).toBe(0)
+      expect(sdkHelp.stderr).toBe('')
+      expect(sdkHelp.stdout).toContain('Usage: dsh --profile sdk')
+
+      const acpHelp = await runBuiltBin(['--profile', 'acp', '--help'], {
+        DSH_HOME: home,
+        DSH_TELEMETRY_DISABLED: '1',
+      })
+      expect(acpHelp.code).toBe(0)
+      expect(acpHelp.stderr).toBe('')
+      expect(acpHelp.stdout).toContain('Usage: dsh --profile acp')
+
       const missingTask = await runBuiltBin(['--profile', 'headless'], {
         DSH_HOME: home,
         DSH_TELEMETRY_DISABLED: '1',
@@ -367,6 +392,169 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
     }
   }, 30_000)
 
+  it('reports SDK startup failure when stdin reaches EOF first', async () => {
+    const home = mkdtempSync(join(tmpdir(), 'dsh-built-sdk-startup-failure-'))
+    const patch = join(home, 'broken-sdk.cordis.yml')
+    writeFileSync(patch, [
+      '- insert:',
+      '    - id: missing-sdk-startup-plugin',
+      '      name: "@deepseek-ai/dsh-missing-sdk-startup-plugin"',
+      '',
+    ].join('\n'))
+    try {
+      const result = await runBuiltBin(['--profile', 'sdk', '--patch', patch], {
+        DSH_HOME: home,
+        DSH_TELEMETRY_DISABLED: '1',
+        DEEPSEEK_API_KEY: 'built-sdk-startup-failure-no-call',
+      }, home)
+      expect(result.code).toBe(1)
+      expect(result.stdout).toBe('')
+      expect(result.stderr).toContain('plugin tree failed to load')
+      expect(result.stderr).toContain('@deepseek-ai/dsh-missing-sdk-startup-plugin')
+    } finally {
+      rmSync(home, { recursive: true, force: true })
+    }
+  }, 30_000)
+
+  it('serves the SDK protocol through the sdk profile and exits after shutdown', async () => {
+    const home = mkdtempSync(join(tmpdir(), 'dsh-built-sdk-'))
+    const child = execa(process.execPath, [dshBin, '--profile', 'sdk'], {
+      cwd: home,
+      reject: false,
+      timeout: 25_000,
+      killSignal: 'SIGKILL',
+      env: {
+        ...process.env,
+        DSH_HOME: home,
+        DSH_TELEMETRY_DISABLED: '1',
+        DEEPSEEK_API_KEY: 'built-sdk-profile-no-call',
+      },
+      extendEnv: false,
+    })
+    const stdoutLines = createInterface({ input: child.stdout, crlfDelay: Infinity })[Symbol.asyncIterator]()
+    let stderr = ''
+    child.stderr.on('data', (chunk: Buffer) => { stderr += chunk.toString('utf8') })
+    const response = async (id: number): Promise<Record<string, unknown>> => {
+      for (;;) {
+        const line = await stdoutLines.next()
+        if (line.done) throw new Error(`SDK profile stdout closed before response ${String(id)}; stderr=${stderr}`)
+        let value: Record<string, unknown>
+        try {
+          value = JSON.parse(line.value) as Record<string, unknown>
+        } catch {
+          throw new Error(`SDK profile wrote non-JSON stdout: ${line.value}`)
+        }
+        if (value.id === id) return value
+      }
+    }
+    try {
+      child.stdin.write(`${JSON.stringify({
+        jsonrpc: '2.0',
+        id: 1,
+        method: 'initialize',
+        params: { cwd: home, provider: 'deepseek-official', model: 'deepseek-v4-flash' },
+      })}\n`)
+      expect(await response(1)).toMatchObject({
+        jsonrpc: '2.0',
+        id: 1,
+        result: { serverInfo: { name: 'deepseek-harness-sdk-runtime' } },
+      })
+      child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'shutdown' })}\n`)
+      expect(await response(2)).toEqual({ jsonrpc: '2.0', id: 2, result: {} })
+      const result = await child
+      expect(result.exitCode, `signal=${String(result.signal)}; stderr=${stderr}`).toBe(0)
+      expect(stderr).toBe('')
+    } finally {
+      child.kill('SIGKILL')
+      await child
+      rmSync(home, { recursive: true, force: true })
+    }
+  }, 30_000)
+
+  it('runs a mock-backed ACP turn through the acp profile and exits on disconnect', async () => {
+    const apiKey = 'built-acp-profile-key'
+    const server = await startMockLlmServer({
+      sequence: ['success'],
+      apiKey,
+      successText: 'ACP BUILT PROFILE OK',
+    })
+    const home = mkdtempSync(join(tmpdir(), 'dsh-built-acp-'))
+    const child = execa(process.execPath, [dshBin, '--profile', 'acp'], {
+      cwd: home,
+      reject: false,
+      timeout: 25_000,
+      killSignal: 'SIGKILL',
+      env: {
+        ...process.env,
+        DSH_HOME: home,
+        DSH_TELEMETRY_DISABLED: '1',
+        DEEPSEEK_API_KEY: apiKey,
+        DEEPSEEK_BASE_URL: server.baseURL,
+        DSH_PERMISSION_MODE: 'danger-full-access',
+      },
+      extendEnv: false,
+    })
+    const rawOut: string[] = []
+    const passthrough = new Readable({ read() {} })
+    child.stdout.on('data', (chunk: Buffer) => {
+      rawOut.push(chunk.toString('utf8'))
+      passthrough.push(chunk)
+    })
+    child.stdout.on('end', () => { passthrough.push(null) })
+    const stream = ndJsonStream(
+      Writable.toWeb(child.stdin) as WritableStream<Uint8Array>,
+      Readable.toWeb(passthrough) as ReadableStream<Uint8Array>,
+    )
+    const updates: SessionNotification['update'][] = []
+    const clientApp = createAcpClientApp({ name: 'dsh-built-acp-profile' })
+      .onNotification(methods.client.session.update, ({ params }) => {
+        updates.push(params.update)
+        return Promise.resolve()
+      })
+      .onRequest(methods.client.session.requestPermission, () => {
+        return Promise.resolve({ outcome: { outcome: 'cancelled' } })
+      })
+    const client = clientApp.connect(stream).agent
+    try {
+      const initialized = await client.request(methods.agent.initialize, {
+        protocolVersion: PROTOCOL_VERSION,
+        clientCapabilities: {},
+      })
+      expect(initialized.agentInfo).toMatchObject({ name: 'deepseek-harness-acp' })
+      expect(initialized.agentCapabilities).toEqual({
+        mcpCapabilities: { http: true },
+        promptCapabilities: { image: false, audio: false, embeddedContext: false },
+        sessionCapabilities: { close: {}, list: {}, resume: {} },
+      })
+      expect('_meta' in initialized).toBe(false)
+      const session = await client.request(methods.agent.session.new, { cwd: home, mcpServers: [] })
+      expect(session.sessionId).toBeTruthy()
+      expect(await client.request(methods.agent.session.prompt, {
+        sessionId: session.sessionId,
+        prompt: [{ type: 'text', text: 'reply from the built ACP profile' }],
+      })).toEqual({ stopReason: 'end_turn' })
+      expect(updates).toContainEqual(expect.objectContaining({
+        sessionUpdate: 'agent_message_chunk',
+        content: { type: 'text', text: 'ACP BUILT PROFILE OK' },
+      }))
+      const message = updates.find(update => update.sessionUpdate === 'agent_message_chunk')
+      expect(message !== undefined && 'messageId' in message && typeof message.messageId === 'string').toBe(true)
+      expect(server.requests).toHaveLength(1)
+      child.stdin.end()
+      const result = await child
+      expect(result.exitCode, `signal=${String(result.signal)}; stderr=${result.stderr}`).toBe(0)
+      expect(result.stderr).toBe('')
+      for (const line of rawOut.join('').split('\n').filter(value => value.trim() !== '')) {
+        expect(() => JSON.parse(line) as unknown).not.toThrow()
+      }
+    } finally {
+      child.kill('SIGKILL')
+      await child
+      await server.close()
+      rmSync(home, { recursive: true, force: true })
+    }
+  }, 30_000)
+
   it('runs the headless profile through its app-owned task positional', async () => {
     const apiKey = 'built-dsh-headless-key'
     const server = await startMockLlmServer({

+ 42 - 0
apps/cli/tests/profile-hmr.spec.ts

@@ -0,0 +1,42 @@
+/** Module-HMR ownership across the real shipped profile bundle layers. */
+
+import { join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { describe, expect, it } from 'vitest'
+import { composeEntries, loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
+import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
+
+const REPOSITORY_ROOT = fileURLToPath(new URL('../../../', import.meta.url))
+
+/** Load one shipped bundle patch through the same parser as profile boot. */
+function bundle(name: 'acp-app' | 'base' | 'headless' | 'sdk-app' | 'web-app'): PatchOptions[] {
+  return loadOverlayPatches('profile-hmr test', join(REPOSITORY_ROOT, 'packages', 'bundle', name, 'cordis.patch.yml'))
+}
+
+/** Resolve the effective HMR row after the supplied layers. */
+function hmr(layers: PatchOptions[][]) {
+  const row = composeEntries(layers).find(entry => entry.id === 'hmr')
+  if (row === undefined) throw new Error('the base bundle must insert the hmr row')
+  return row
+}
+
+describe('profile module-HMR policy', () => {
+  it.each(['web-app', 'headless', 'sdk-app', 'acp-app'] as const)(
+    '%s inherits the disabled base row without a mode override',
+    (mode) => {
+      const modePatches = bundle(mode)
+      expect(modePatches.some(patch => patch.id === 'hmr')).toBe(false)
+      expect(hmr([bundle('base'), modePatches])).toMatchObject({
+        disabled: true,
+        config: { root: ['.'] },
+      })
+    },
+  )
+
+  it('requires an explicit later layer to enable source-module reload', () => {
+    expect(hmr([bundle('base'), [{ id: 'hmr', disabled: false }]])).toMatchObject({
+      disabled: false,
+      config: { root: ['.'] },
+    })
+  })
+})

+ 1 - 1
apps/web/tests/snapshots/bash-abort-row/ui.expected.md

@@ -25,7 +25,7 @@
 - textbox "Message the agent"
 - button "Commands":
   - img
-- 'button "Access mode, current: Workspace Write"': Workspace Write
+- 'button "Access mode, current: Full access"': Full access
 - button "Select model, current DeepSeek-V4-Flash":
   - text: DeepSeek-V4-Flash
   - img

+ 1 - 1
apps/web/tests/snapshots/skill-tool-row/ui.expected.md

@@ -44,7 +44,7 @@
 - textbox "Message the agent"
 - button "Commands":
   - img
-- 'button "Access mode, current: Workspace Write"': Workspace Write
+- 'button "Access mode, current: Full access"': Full access
 - button "Select model, current DeepSeek-V4-Flash":
   - text: DeepSeek-V4-Flash
   - img

+ 2 - 2
docs/architecture.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/architecture.md
-architecture.md: 6f5a0475f1b5b6818cd831968816d4c630bab3a2
-architecture.zh.md: a681fc17354150d86acf6ee311f9c9d73ee2b980
+architecture.md: 06d5163566816eb732b51ce784cd2ab92218d4fd
+architecture.zh.md: 55162553b62300e5e1bb34bab468cb3956877e56

+ 12 - 2
docs/architecture.md

@@ -16,16 +16,18 @@ There is no privileged core to patch: you extend dsh by mounting a plugin beside
 
 A running `dsh` is a plugin tree composed at boot from ordered layers.
 
-A **profile** is a named composition stored in the Harness home. It lists the bundles it stacks, holds any out-of-tree plugins it installs, and keeps the user's own `cordis.patch.yml`. `web` and `headless` ship as templates.
+A **profile** is a named composition stored in the Harness home. It lists the bundles it stacks, holds any out-of-tree plugins it installs, and keeps the user's own `cordis.patch.yml`. `web`, `headless`, `sdk`, and `acp` ship as templates.
 
 A **bundle** is a distribution format for Cordis config rows and the code they mount, so whatever it inserts stays patchable by the layers above it.
 
 Each declares itself in its own `package.json` under a `dsh` field: `dsh.profile` lists a profile's bundles, and `dsh.bundle` points at a bundle's patch file.
 
-[`dsh-base`](../packages/bundle/base/README.md) is the first layer of every profile: model adapters, tools, persistence, sandbox and approval policy, settings, credentials, telemetry. [`dsh-web-app`](../packages/bundle/web-app/README.md) adds the browser application; [`dsh-headless`](../packages/bundle/headless/README.md) adds a one-shot runner with no server at all.
+[`dsh-base`](../packages/bundle/base/README.md) is the first layer of every profile: model adapters, tools, persistence, sandbox and approval policy, settings, credentials, telemetry. [`dsh-web-app`](../packages/bundle/web-app/README.md) adds the browser application, [`dsh-headless`](../packages/bundle/headless/README.md) adds a one-shot runner with no server, [`dsh-sdk-app`](../packages/bundle/sdk-app/README.md) adds the SDK JSON-RPC server, and [`dsh-acp-app`](../packages/bundle/acp-app/README.md) adds the automation-only ACP server.
 
 Layers apply to an empty entry list in this order: each bundle in the profile's listed order, then the profile's `cordis.patch.yml`, then the home-level one, then any `--patch` overlay. A patch targets a row by id and replaces its whole config, or inserts new rows.
 
+Custom profiles default to live patch reload. The shipped `web` profile is live; `headless`, `sdk`, and `acp` apply all layers once at startup because replacing a one-shot or stdio application's dependencies after it owns work would invalidate that lifecycle.
+
 To see the tree your machine actually boots:
 
 ```sh
@@ -36,6 +38,14 @@ Any row it prints can be replaced by a patch of your own.
 
 Composition mechanics are in [app-boot](../packages/boot/app-boot/README.md#profiles); config fields are in the generated [config catalog](config-catalog.md).
 
+## Application launch
+
+Every supported Node application starts at the `dsh` CLI with a named profile. The shipped applications are `dsh web` (the deliberate alias for `--profile web`), `dsh --profile headless`, `dsh --profile sdk`, and `dsh --profile acp`. The TypeScript SDK resolves its same-version `dsh` dependency and selects `sdk`; custom plugin composition remains a profile plus ordered patch files, not another executable or inline application tree.
+
+Vendored CLIs, build-only and test-only executables, direct in-process plugin mounting, and the private browser WebWorker preview are not Harness application launchers. [`verify-application-entrypoints`](../scripts/verify-application-entrypoints.ts) keeps every package bin, executable source, and root demo in an explicit class and rejects a Node application path that bypasses `dsh`.
+
+The packaged Python SDK runtime is the sole temporary application exception. Its private [`dsh-sdk-python-runtime`](../packages/sdk/python-runtime/README.md) carrier and `dsh-sdk-python-runtime-closure` deploy manifest preserve the current Python API, wire, default `cordis.yml`, environment variables, wheel names, `dsh-jsonrpc-agent-pkg-<platform>-<arch>` executables, sidecars, and platform set. A later Python migration will launch `dsh --profile sdk`, delete the private direct-config carrier, and then rename that executable family to `deepseek-harness-sdk-runtime-<platform>-<arch>`.
+
 ## Core packages
 
 Here are some core packages that contribute to the Cordis tree.

+ 12 - 2
docs/architecture.zh.md

@@ -16,16 +16,18 @@
 
 运行中的 `dsh` 是一棵插件树,由启动时按序叠加的各层组合而成。
 
-**profile** 是存放在 Harness home 中的具名组装。它列出自己叠放的组合包,存放自己安装的树外插件,并保存用户自己的 `cordis.patch.yml`。`web` 和 `headless` 作为模板随发行版交付。
+**profile** 是存放在 Harness home 中的具名组装。它列出自己叠放的组合包,存放自己安装的树外插件,并保存用户自己的 `cordis.patch.yml`。`web`、`headless`、`sdk` 和 `acp` 作为模板随发行版交付。
 
 **组合包**是 Cordis 配置项及其挂载代码的分发格式,因此它插入的内容始终可被其上各层 patch。
 
 两者都在各自的 `package.json` 中通过 `dsh` 字段声明自己:`dsh.profile` 列出一个 profile 的组合包,`dsh.bundle` 指向一个组合包的 patch 文件。
 
-[`dsh-base`](../packages/bundle/base/README.zh.md) 是每个 profile 的第一层:模型适配器、工具、持久化、沙箱与审批策略、设置、凭据、遥测。[`dsh-web-app`](../packages/bundle/web-app/README.zh.md) 增加浏览器应用;[`dsh-headless`](../packages/bundle/headless/README.zh.md) 增加一次性运行器,且完全不带服务器。
+[`dsh-base`](../packages/bundle/base/README.zh.md) 是每个 profile 的第一层:模型适配器、工具、持久化、沙箱与审批策略、设置、凭据、遥测。[`dsh-web-app`](../packages/bundle/web-app/README.zh.md) 增加浏览器应用,[`dsh-headless`](../packages/bundle/headless/README.zh.md) 增加不带服务器的一次性运行器,[`dsh-sdk-app`](../packages/bundle/sdk-app/README.zh.md) 增加 SDK JSON-RPC 服务器,[`dsh-acp-app`](../packages/bundle/acp-app/README.zh.md) 增加仅用于自动化的 ACP 服务器。
 
 各层按此顺序应用在空条目列表之上:先按 profile 列出的顺序应用每个组合包,然后是 profile 的 `cordis.patch.yml`,然后是 home 级的那份,最后是任意 `--patch` overlay。一条 patch 按 id 定位某个条目并替换其整个 config,或插入新条目。
 
+自定义 profile 默认实时重载 patch。随附的 `web` profile 使用实时重载;`headless`、`sdk` 和 `acp` 则只在启动时应用一次所有配置层,因为一次性应用或 stdio 应用拥有工作之后,替换其依赖会破坏该生命周期。
+
 要查看你的机器实际启动的配置树:
 
 ```sh
@@ -36,6 +38,14 @@ dsh --profile web --dump-config
 
 组装机制见 [app-boot](../packages/boot/app-boot/README.zh.md#profiles);配置字段见生成的[配置目录](config-catalog.zh.md)。
 
+## 应用启动
+
+所有受支持的 Node 应用都从 `dsh` CLI 与具名 profile 启动。随附应用是 `dsh web`(刻意为 `--profile web` 保留的别名)、`dsh --profile headless`、`dsh --profile sdk` 与 `dsh --profile acp`。TypeScript SDK 会解析其同版本 `dsh` 依赖并选择 `sdk`;自定义插件组合继续由 profile 与有序 patch 文件表达,而不是另一个可执行文件或内联应用树。
+
+Vendored CLI、仅用于构建和测试的可执行文件、进程内直接挂载插件以及私有浏览器 WebWorker 预览都不属于 Harness 应用启动器。[`verify-application-entrypoints`](../scripts/verify-application-entrypoints.ts)将每个包 bin、可执行源码与根 demo 归入显式类别,并拒绝任何绕过 `dsh` 的 Node 应用路径。
+
+打包后的 Python SDK 运行时是唯一的临时应用例外。其私有 [`dsh-sdk-python-runtime`](../packages/sdk/python-runtime/README.zh.md) 载体与 `dsh-sdk-python-runtime-closure` 部署 manifest 保持当前 Python API、协议格式、默认 `cordis.yml`、环境变量、wheel 包名称、`dsh-jsonrpc-agent-pkg-<platform>-<arch>` 可执行文件、伴随文件及平台集合不变。后续 Python 迁移会改为启动 `dsh --profile sdk`、删除私有直读配置载体,然后把该可执行文件族重命名为 `deepseek-harness-sdk-runtime-<platform>-<arch>`。
+
 ## 核心包
 
 以下是向 Cordis 树贡献内容的部分核心包。

+ 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: b2cb3faea338e0fe7d796aea626da4e91539b48b
-config-catalog.zh.md: c1e33e3724a78749e6a90202d642564c8b3d106d
+config-catalog.md: 9a1eda647e8643543d4ab23c877267ad06c445f5
+config-catalog.zh.md: ac0b8255a71c62770441c77b890eade0e051d561

+ 13 - 63
docs/config-catalog.md

@@ -33,61 +33,6 @@ Depends on: `Stream` (`@agentclientprotocol/sdk`)
 
 Source: [`packages/acp/acp/src/index.ts:74`](../packages/acp/acp/src/index.ts)
 
-<a id="deepseek-aidsh-acp-demo"></a>
-
-## `@deepseek-ai/dsh-acp-demo`
-
-```ts config-catalog
-/**
- * App config: the swappable per-deployment values. `provider` and `model` configure
- * each agent the ACP bridge creates at `session/new`; `persona` is the
- * deployment persona (forwarded to the system-prompt plugin); `toolOrder` is
- * the explicit model-facing tool order (forwarded to the system-prompt plugin);
- * `tools` is the tool registry's config (its presentation `mode`, forwarded
- * through agent-spine-demo); `persistenceRoot` is the JSONL backend's directory.
- */
-export interface Config {
-  /** Provider route for ACP-created agents. */
-  provider: string
-  /** Model name for ACP-created agents (must have a registered adapter). */
-  model: string
-  /** Bundled agent-loop concurrency cap; `1` is serial and omission uses its default. */
-  maxParallelToolCalls?: number
-  /** Deployment persona (the system-prompt plugin's `persona` config). */
-  persona?: string
-  /** Explicit model-facing tool order (the system-prompt plugin's `toolOrder` config; see dsh-system-prompt). */
-  toolOrder?: string[]
-  /** Tool-registry config — its presentation `mode` (forwarded through agent-spine-demo; see dsh-tools). */
-  tools?: ToolsConfig
-  /** DeepSeek Harness home directory exposed to bash and used for local skill discovery. */
-  dshHome?: string
-  /** Fallback session-title limits forwarded through agent-spine-demo. */
-  sessionTitle?: NonNullable<agentCore.Config['sessionTitle']>
-  /** Directory for JSONL sessions and the derived query index. Defaults to `./.sessions`. */
-  persistenceRoot?: string
-  /** Write delta-chunk runs as packed storage rows (the JSONL backend's `packChunks`). Defaults to `true`. */
-  packChunks?: boolean
-  /** JSONL artifact encoding; defaults to checksummed Zstandard frames. */
-  persistenceCompression?: JsonlCompression
-  /** Controls automatic AGENTS.md/CLAUDE.md loading; configure a byte budget or set `false`. */
-  workspaceContext: agentCore.Config['workspaceContext']
-  /** Skill registry, local-provider, and model-facing consumer config forwarded to agent-spine-demo. */
-  skills?: agentCore.SkillConfig
-  /** Model-facing bash tool config forwarded through agent-core. */
-  toolBash?: NonNullable<agentCore.Config['toolBash']>
-  /** Process-local background-job admission config forwarded through agent-core. */
-  jobs?: NonNullable<agentCore.Config['jobs']>
-  /** Generic background-job controls forwarded through agent-core; set false to omit their tools. */
-  toolJobs?: NonNullable<agentCore.Config['toolJobs']>
-  /** Persisted same-session goals; owner defaults enable them, or false disables the stack and tools. */
-  goals?: agentCore.GoalConfig | false
-}
-```
-
-Depends on: [`agentCore`](../packages/examples/agent-spine-demo/src/index.ts) · [`JsonlCompression`](../packages/session/session-persistence-jsonl/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools)
-
-Source: [`packages/examples/acp-demo/src/index.ts:39`](../packages/examples/acp-demo/src/index.ts)
-
 <a id="deepseek-aidsh-agent-default-model"></a>
 
 ## `@deepseek-ai/dsh-agent-default-model`
@@ -2327,10 +2272,14 @@ Requires: `subagents`
 export interface Config {
   /** Provider name on `ctx.subagents` (default `dsh-sdk`). */
   providerName: string
-  /** The executable to spawn for each run (the child runtime bin or packaged exe). */
-  command: string
-  /** Arguments passed to {@link command} (typically the child's `cordis.yml` path). */
-  args: string[]
+  /** Explicit dsh CLI module, resolved and checked at plugin load; omission uses the SDK dependency. */
+  dshBin?: string
+  /** Named child profile (default `sdk`). */
+  profile: string
+  /** Ordered per-launch profile patch files, resolved and checked at plugin load. */
+  patches: string[]
+  /** Absolute isolated Harness home for every nested child process. */
+  dshHome: string
   /**
    * Working directory override for the child process and its SDK session
    * workspace. Must be non-empty; a relative path resolves against the
@@ -2348,8 +2297,7 @@ export interface Config {
   maxTokens?: number
   /**
    * Extra environment variables for the child process — e.g. the child
-   * runtime's own `DEEPSEEK_API_KEY`, or `DSH_CORDIS_CONFIG` naming its
-   * config. Forwarded on top of a credential-scrubbed copy of the parent
+   * runtime's own `DEEPSEEK_API_KEY`. Forwarded on top of a credential-scrubbed copy of the parent
    * env, so an explicit key here reaches the child while ambient secrets do
    * not leak implicitly.
    */
@@ -2367,7 +2315,7 @@ export interface Config {
 }
 ```
 
-Source: [`packages/subagent/subagent-dsh-sdk/src/index.ts:29`](../packages/subagent/subagent-dsh-sdk/src/index.ts)
+Source: [`packages/subagent/subagent-dsh-sdk/src/index.ts:31`](../packages/subagent/subagent-dsh-sdk/src/index.ts)
 
 <a id="deepseek-aidsh-subagent-fork-in-process"></a>
 
@@ -3278,6 +3226,7 @@ Source: [`packages/workflow/workflow-worker-thread/src/index.ts:32`](../packages
 
 These load from a `cordis.yml` entry with no `config:` block; they declare no configuration API.
 
+- `@deepseek-ai/dsh-acp-app` — requires `cmdlineArgs` ([`packages/bundle/acp-app/src/index.ts`](../packages/bundle/acp-app/src/index.ts))
 - `@deepseek-ai/dsh-agent` ([`packages/core/agent/src/index.ts`](../packages/core/agent/src/index.ts))
 - `@deepseek-ai/dsh-api-gateway` — requires `typert` ([`packages/api/gateway/src/index.ts`](../packages/api/gateway/src/index.ts))
 - `@deepseek-ai/dsh-api-remotes` ([`packages/api/remotes/src/index.ts`](../packages/api/remotes/src/index.ts))
@@ -3333,6 +3282,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co
 - `@deepseek-ai/dsh-llm` ([`packages/llm/llm/src/index.ts`](../packages/llm/llm/src/index.ts))
 - `@deepseek-ai/dsh-lsp` ([`packages/lsp/lsp/src/index.ts`](../packages/lsp/lsp/src/index.ts))
 - `@deepseek-ai/dsh-schedule` — requires `agents` · `sessions` · `tools` · `sessionPersistence` ([`packages/schedule/schedule/src/index.ts`](../packages/schedule/schedule/src/index.ts))
+- `@deepseek-ai/dsh-sdk-app` — requires `cmdlineArgs` ([`packages/bundle/sdk-app/src/index.ts`](../packages/bundle/sdk-app/src/index.ts))
 - `@deepseek-ai/dsh-session` ([`packages/core/session/src/index.ts`](../packages/core/session/src/index.ts))
 - `@deepseek-ai/dsh-session-checkpoint-policy` — requires `llm` · `sessionPersistence` · `sessions` · `tools` ([`packages/session/session-checkpoint-policy/src/index.ts`](../packages/session/session-checkpoint-policy/src/index.ts))
 - `@deepseek-ai/dsh-session-log-export` — requires `commands` ([`packages/session-query/session-log-export/src/index.ts`](../packages/session-query/session-log-export/src/index.ts))
@@ -3401,8 +3351,8 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them.
 - `@deepseek-ai/dsh-sandbox-windows-acl` ([`packages/sandbox/sandbox-windows-acl/src/index.ts`](../packages/sandbox/sandbox-windows-acl/src/index.ts))
 - `@deepseek-ai/dsh-scope` ([`packages/core/scope/src/index.ts`](../packages/core/scope/src/index.ts))
 - `@deepseek-ai/dsh-sdk-client` ([`packages/sdk/client/src/index.ts`](../packages/sdk/client/src/index.ts))
-- `@deepseek-ai/dsh-sdk-jsonrpc-demo` ([`packages/examples/jsonrpc-demo/src/index.ts`](../packages/examples/jsonrpc-demo/src/index.ts))
 - `@deepseek-ai/dsh-sdk-protocol` ([`packages/sdk/protocol/src/index.ts`](../packages/sdk/protocol/src/index.ts))
+- `@deepseek-ai/dsh-sdk-python-runtime` ([`packages/sdk/python-runtime/src/index.ts`](../packages/sdk/python-runtime/src/index.ts))
 - `@deepseek-ai/dsh-session-telemetry` ([`packages/session/session-telemetry/src/index.ts`](../packages/session/session-telemetry/src/index.ts))
 - `@deepseek-ai/dsh-session-title-llm` ([`packages/session/session-title-llm/src/index.ts`](../packages/session/session-title-llm/src/index.ts))
 - `@deepseek-ai/dsh-subagent-in-process-driver` ([`packages/subagent/subagent-in-process-driver/src/index.ts`](../packages/subagent/subagent-in-process-driver/src/index.ts))

+ 13 - 63
docs/config-catalog.zh.md

@@ -35,61 +35,6 @@ export interface AcpConfig {
 
 来源:[`packages/acp/acp/src/index.ts:74`](../packages/acp/acp/src/index.ts)
 
-<a id="deepseek-aidsh-acp-demo"></a>
-
-## `@deepseek-ai/dsh-acp-demo`
-
-```ts config-catalog
-/**
- * App config: the swappable per-deployment values. `provider` and `model` configure
- * each agent the ACP bridge creates at `session/new`; `persona` is the
- * deployment persona (forwarded to the system-prompt plugin); `toolOrder` is
- * the explicit model-facing tool order (forwarded to the system-prompt plugin);
- * `tools` is the tool registry's config (its presentation `mode`, forwarded
- * through agent-spine-demo); `persistenceRoot` is the JSONL backend's directory.
- */
-export interface Config {
-  /** Provider route for ACP-created agents. */
-  provider: string
-  /** Model name for ACP-created agents (must have a registered adapter). */
-  model: string
-  /** Bundled agent-loop concurrency cap; `1` is serial and omission uses its default. */
-  maxParallelToolCalls?: number
-  /** Deployment persona (the system-prompt plugin's `persona` config). */
-  persona?: string
-  /** Explicit model-facing tool order (the system-prompt plugin's `toolOrder` config; see dsh-system-prompt). */
-  toolOrder?: string[]
-  /** Tool-registry config — its presentation `mode` (forwarded through agent-spine-demo; see dsh-tools). */
-  tools?: ToolsConfig
-  /** DeepSeek Harness home directory exposed to bash and used for local skill discovery. */
-  dshHome?: string
-  /** Fallback session-title limits forwarded through agent-spine-demo. */
-  sessionTitle?: NonNullable<agentCore.Config['sessionTitle']>
-  /** Directory for JSONL sessions and the derived query index. Defaults to `./.sessions`. */
-  persistenceRoot?: string
-  /** Write delta-chunk runs as packed storage rows (the JSONL backend's `packChunks`). Defaults to `true`. */
-  packChunks?: boolean
-  /** JSONL artifact encoding; defaults to checksummed Zstandard frames. */
-  persistenceCompression?: JsonlCompression
-  /** Controls automatic AGENTS.md/CLAUDE.md loading; configure a byte budget or set `false`. */
-  workspaceContext: agentCore.Config['workspaceContext']
-  /** Skill registry, local-provider, and model-facing consumer config forwarded to agent-spine-demo. */
-  skills?: agentCore.SkillConfig
-  /** Model-facing bash tool config forwarded through agent-core. */
-  toolBash?: NonNullable<agentCore.Config['toolBash']>
-  /** Process-local background-job admission config forwarded through agent-core. */
-  jobs?: NonNullable<agentCore.Config['jobs']>
-  /** Generic background-job controls forwarded through agent-core; set false to omit their tools. */
-  toolJobs?: NonNullable<agentCore.Config['toolJobs']>
-  /** Persisted same-session goals; owner defaults enable them, or false disables the stack and tools. */
-  goals?: agentCore.GoalConfig | false
-}
-```
-
-依赖:[`agentCore`](../packages/examples/agent-spine-demo/src/index.ts) · [`JsonlCompression`](../packages/session/session-persistence-jsonl/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools)
-
-来源:[`packages/examples/acp-demo/src/index.ts:39`](../packages/examples/acp-demo/src/index.ts)
-
 <a id="deepseek-aidsh-agent-default-model"></a>
 
 ## `@deepseek-ai/dsh-agent-default-model`
@@ -2329,10 +2274,14 @@ export type CodexPermissionMode =
 export interface Config {
   /** Provider name on `ctx.subagents` (default `dsh-sdk`). */
   providerName: string
-  /** The executable to spawn for each run (the child runtime bin or packaged exe). */
-  command: string
-  /** Arguments passed to {@link command} (typically the child's `cordis.yml` path). */
-  args: string[]
+  /** Explicit dsh CLI module, resolved and checked at plugin load; omission uses the SDK dependency. */
+  dshBin?: string
+  /** Named child profile (default `sdk`). */
+  profile: string
+  /** Ordered per-launch profile patch files, resolved and checked at plugin load. */
+  patches: string[]
+  /** Absolute isolated Harness home for every nested child process. */
+  dshHome: string
   /**
    * Working directory override for the child process and its SDK session
    * workspace. Must be non-empty; a relative path resolves against the
@@ -2350,8 +2299,7 @@ export interface Config {
   maxTokens?: number
   /**
    * Extra environment variables for the child process — e.g. the child
-   * runtime's own `DEEPSEEK_API_KEY`, or `DSH_CORDIS_CONFIG` naming its
-   * config. Forwarded on top of a credential-scrubbed copy of the parent
+   * runtime's own `DEEPSEEK_API_KEY`. Forwarded on top of a credential-scrubbed copy of the parent
    * env, so an explicit key here reaches the child while ambient secrets do
    * not leak implicitly.
    */
@@ -2369,7 +2317,7 @@ export interface Config {
 }
 ```
 
-来源:[`packages/subagent/subagent-dsh-sdk/src/index.ts:29`](../packages/subagent/subagent-dsh-sdk/src/index.ts)
+来源:[`packages/subagent/subagent-dsh-sdk/src/index.ts:31`](../packages/subagent/subagent-dsh-sdk/src/index.ts)
 
 <a id="deepseek-aidsh-subagent-fork-in-process"></a>
 
@@ -3280,6 +3228,7 @@ export interface Config {
 
 这些插件通过 `cordis.yml` 中不含 `config:` 块的条目加载;它们未声明任何配置接口。
 
+- `@deepseek-ai/dsh-acp-app` — 需要 `cmdlineArgs`([`packages/bundle/acp-app/src/index.ts`](../packages/bundle/acp-app/src/index.ts))
 - `@deepseek-ai/dsh-agent`([`packages/core/agent/src/index.ts`](../packages/core/agent/src/index.ts))
 - `@deepseek-ai/dsh-api-gateway` — 需要 `typert`([`packages/api/gateway/src/index.ts`](../packages/api/gateway/src/index.ts))
 - `@deepseek-ai/dsh-api-remotes`([`packages/api/remotes/src/index.ts`](../packages/api/remotes/src/index.ts))
@@ -3335,6 +3284,7 @@ export interface Config {
 - `@deepseek-ai/dsh-llm`([`packages/llm/llm/src/index.ts`](../packages/llm/llm/src/index.ts))
 - `@deepseek-ai/dsh-lsp`([`packages/lsp/lsp/src/index.ts`](../packages/lsp/lsp/src/index.ts))
 - `@deepseek-ai/dsh-schedule` — 需要 `agents` · `sessions` · `tools` · `sessionPersistence`([`packages/schedule/schedule/src/index.ts`](../packages/schedule/schedule/src/index.ts))
+- `@deepseek-ai/dsh-sdk-app` — 需要 `cmdlineArgs`([`packages/bundle/sdk-app/src/index.ts`](../packages/bundle/sdk-app/src/index.ts))
 - `@deepseek-ai/dsh-session`([`packages/core/session/src/index.ts`](../packages/core/session/src/index.ts))
 - `@deepseek-ai/dsh-session-checkpoint-policy` — 需要 `llm` · `sessionPersistence` · `sessions` · `tools`([`packages/session/session-checkpoint-policy/src/index.ts`](../packages/session/session-checkpoint-policy/src/index.ts))
 - `@deepseek-ai/dsh-session-log-export` — 需要 `commands`([`packages/session-query/session-log-export/src/index.ts`](../packages/session-query/session-log-export/src/index.ts))
@@ -3402,8 +3352,8 @@ export interface Config {
 - `@deepseek-ai/dsh-sandbox-windows-acl`([`packages/sandbox/sandbox-windows-acl/src/index.ts`](../packages/sandbox/sandbox-windows-acl/src/index.ts))
 - `@deepseek-ai/dsh-scope`([`packages/core/scope/src/index.ts`](../packages/core/scope/src/index.ts))
 - `@deepseek-ai/dsh-sdk-client`([`packages/sdk/client/src/index.ts`](../packages/sdk/client/src/index.ts))
-- `@deepseek-ai/dsh-sdk-jsonrpc-demo`([`packages/examples/jsonrpc-demo/src/index.ts`](../packages/examples/jsonrpc-demo/src/index.ts))
 - `@deepseek-ai/dsh-sdk-protocol`([`packages/sdk/protocol/src/index.ts`](../packages/sdk/protocol/src/index.ts))
+- `@deepseek-ai/dsh-sdk-python-runtime`([`packages/sdk/python-runtime/src/index.ts`](../packages/sdk/python-runtime/src/index.ts))
 - `@deepseek-ai/dsh-session-telemetry`([`packages/session/session-telemetry/src/index.ts`](../packages/session/session-telemetry/src/index.ts))
 - `@deepseek-ai/dsh-session-title-llm`([`packages/session/session-title-llm/src/index.ts`](../packages/session/session-title-llm/src/index.ts))
 - `@deepseek-ai/dsh-subagent-in-process-driver`([`packages/subagent/subagent-in-process-driver/src/index.ts`](../packages/subagent/subagent-in-process-driver/src/index.ts))

+ 2 - 2
docs/cookbook/extension-cookbook.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/cookbook/extension-cookbook.md
-extension-cookbook.md: 9618a3522c5566636fe3e49f7eca93d1e113d51a
-extension-cookbook.zh.md: 665968f0a91c05d124b9985c61bdf89c4e10cefa
+extension-cookbook.md: 1081166674cb946675d8864091c50def018f9f97
+extension-cookbook.zh.md: defdda53e8f8ea849d2f236566bef48751f4ad96

+ 1 - 1
docs/cookbook/extension-cookbook.md

@@ -90,7 +90,7 @@ export function apply(ctx: Context) {
 
 ## Runnable wirings
 
-Runnable leaves load their plugin trees from `examples/*/cordis.yml`; the root `demo:*` scripts and those leaf directories are the authoritative inventory. The product `dsh` launcher owns Web and one-shot headless execution, ACP leaves use [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo), and JSON-RPC leaves use [`@deepseek-ai/dsh-sdk-jsonrpc-demo`](../../packages/examples/jsonrpc-demo). The headless snapshot leaf mounts [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo) and JSONL persistence explicitly, then drives them through an example-owned test fixture rather than a shipped app package.
+Runnable leaves contribute profile patches from `examples/*/cordis.yml`; the root `demo:*` scripts and those leaf directories are the authoritative inventory. The product `dsh` launcher owns Web, ACP, SDK, and one-shot headless execution through named profiles. The JSON-RPC leaf remains only for the temporarily held-back Python SDK runtime. The headless snapshot leaf mounts [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo) and JSONL persistence explicitly, then drives them through an example-owned test fixture rather than a shipped app package.
 
 ## The feature → mechanism map
 

+ 1 - 1
docs/cookbook/extension-cookbook.zh.md

@@ -92,7 +92,7 @@ export function apply(ctx: Context) {
 
 ## 可运行的组装示例
 
-可运行叶子从 `examples/*/cordis.yml` 加载各自的插件树;根目录的 `demo:*` 脚本和这些叶子目录是权威清单。产品 `dsh` 启动器负责 Web 和一次性 headless 执行,ACP 叶子使用 [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo),JSON-RPC 叶子使用 [`@deepseek-ai/dsh-sdk-jsonrpc-demo`](../../packages/examples/jsonrpc-demo)。headless 快照叶节点显式挂载 [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo) 和 JSONL 持久化,再通过示例自有的测试 fixture(测试前置数据)驱动这些组件,而不是通过已交付的 app 包。
+可运行叶子通过 `examples/*/cordis.yml` 贡献 profile patch;根目录的 `demo:*` 脚本和这些叶子目录是权威清单。产品 `dsh` 启动器通过具名 profile 负责 Web、ACP、SDK 与一次性 headless 执行。JSON-RPC 叶子只为暂缓迁移的 Python SDK runtime 保留。headless 快照叶节点显式挂载 [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo) 和 JSONL 持久化,再通过示例自有的测试 fixture(测试前置数据)驱动这些组件,而不是通过已交付的 app 包。
 
 <a id="the-feature--mechanism-map"></a>
 

+ 2 - 2
docs/module-graph.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/module-graph.md
-module-graph.md: dc08df9d58de0610bb50b61e1dc6934ef016373b
-module-graph.zh.md: 72e27b972d97c8ebeb054b0084a781e7810d0201
+module-graph.md: 3836bd415d75aba666442a559add2e9a587dd29f
+module-graph.zh.md: a1207017f705a787c64067215ae1824b88eee861

+ 9 - 15
docs/module-graph.md

@@ -116,8 +116,10 @@ flowchart TD
     pkg_cmdline["cmdline"]
   end
   subgraph group_bundle["packages/bundle"]
+    pkg_acp_app["acp-app"]
     pkg_base["base"]
     pkg_headless["headless"]
+    pkg_sdk_app["sdk-app"]
     pkg_web_app["web-app"]
   end
   subgraph group_client["packages/client"]
@@ -192,9 +194,7 @@ flowchart TD
     pkg_subprocess_e2b["subprocess-e2b"]
   end
   subgraph group_examples["packages/examples"]
-    pkg_acp_demo["acp-demo"]
     pkg_agent_spine_demo["agent-spine-demo"]
-    pkg_sdk_jsonrpc_demo["sdk-jsonrpc-demo"]
   end
   subgraph group_experimental["packages/experimental"]
     pkg_experimental_agent_team["experimental-agent-team"]
@@ -269,6 +269,7 @@ flowchart TD
     pkg_sdk_client["sdk-client"]
     pkg_sdk_jsonrpc_server["sdk-jsonrpc-server"]
     pkg_sdk_protocol["sdk-protocol"]
+    pkg_sdk_python_runtime["sdk-python-runtime"]
   end
   subgraph group_session["packages/session"]
     pkg_session_checkpoint_policy["session-checkpoint-policy"]
@@ -356,20 +357,22 @@ flowchart TD
   pkg_deepseek_llm_api_extensions --> pkg_invariants
   pkg_scope --> pkg_invariants
   pkg_cmdline --> pkg_invariants
+  pkg_acp_app --> pkg_invariants
   pkg_base --> pkg_invariants
+  pkg_sdk_app --> pkg_invariants
   pkg_client_ui_primitives --> pkg_invariants
   pkg_client_ui_slots --> pkg_invariants
   pkg_client_web --> pkg_invariants
   pkg_code_runtime --> pkg_invariants
   pkg_code_runtime_python --> pkg_invariants
   pkg_e2b --> pkg_invariants
-  pkg_sdk_jsonrpc_demo --> pkg_invariants
   pkg_experimental_webworker_packer --> pkg_invariants
   pkg_host_directory_picker --> pkg_invariants
   pkg_host_directory_picker_browse --> pkg_invariants
   pkg_host_directory_picker_native --> pkg_invariants
   pkg_host_webserver --> pkg_invariants
   pkg_sandbox_windows_acl --> pkg_invariants
+  pkg_sdk_python_runtime --> pkg_invariants
   pkg_storage --> pkg_invariants
   pkg_subprocess --> pkg_invariants
   pkg_win32_process --> pkg_invariants
@@ -1223,16 +1226,6 @@ flowchart TD
   pkg_api_gateway --> pkg_client_connection
   pkg_api_gateway --> pkg_invariants
   pkg_api_gateway --> pkg_typert_registry
-  pkg_acp_demo --> pkg_acp
-  pkg_acp_demo --> pkg_agent_instructions
-  pkg_acp_demo --> pkg_agent_spine_demo
-  pkg_acp_demo --> pkg_app_boot
-  pkg_acp_demo --> pkg_invariants
-  pkg_acp_demo --> pkg_session_checkpoint_policy
-  pkg_acp_demo --> pkg_session_persistence_jsonl
-  pkg_acp_demo --> pkg_session_query
-  pkg_acp_demo --> pkg_session_query_sqlite
-  pkg_acp_demo --> pkg_tools
   pkg_api_remotes --> pkg_agent
   pkg_api_remotes --> pkg_agent_presets
   pkg_api_remotes --> pkg_api_gateway
@@ -1526,20 +1519,22 @@ flowchart TD
 | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | `llm` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`scope`](../packages/core/scope) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`cmdline`](../packages/boot/cmdline) | `boot` | [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`acp-app`](../packages/bundle/acp-app) | `bundle` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`base`](../packages/bundle/base) | `bundle` | [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`sdk-app`](../packages/bundle/sdk-app) | `bundle` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`client-ui-primitives`](../packages/client/ui-primitives) | `client` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`client-ui-slots`](../packages/client/ui-slots) | `client` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`client-web`](../packages/client/web) | `client` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`code-runtime`](../packages/code-runtime/code-runtime) | `code-runtime` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`code-runtime-python`](../packages/code-runtime/code-runtime-python) | `code-runtime` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`e2b`](../packages/e2b/e2b) | `e2b` | [`invariants`](../packages/runtime-diagnostics/invariants) |
-| [`sdk-jsonrpc-demo`](../packages/examples/jsonrpc-demo) | `examples` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`experimental-webworker-packer`](../packages/experimental/webworker-packer) | `experimental` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`host-directory-picker`](../packages/host/directory-picker) | `host` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`host-directory-picker-browse`](../packages/host/directory-picker-browse) | `host` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`host-directory-picker-native`](../packages/host/directory-picker-native) | `host` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`host-webserver`](../packages/host/webserver) | `host` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`sandbox-windows-acl`](../packages/sandbox/sandbox-windows-acl) | `sandbox` | [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`sdk-python-runtime`](../packages/sdk/python-runtime) | `sdk` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`storage`](../packages/storage/storage) | `storage` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`subprocess`](../packages/subprocess/subprocess) | `subprocess` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`win32-process`](../packages/subprocess/win32-process) | `subprocess` | [`invariants`](../packages/runtime-diagnostics/invariants) |
@@ -1709,7 +1704,6 @@ flowchart TD
 | [`sdk-jsonrpc-server`](../packages/sdk/server) | `sdk` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`scope`](../packages/core/scope), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
 | [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sdk-client`](../packages/sdk/client), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) |
 | [`api-gateway`](../packages/api/gateway) | `api` | [`client-connection`](../packages/client/connection), [`invariants`](../packages/runtime-diagnostics/invariants), [`typert-registry`](../packages/typert/registry) |
-| [`acp-demo`](../packages/examples/acp-demo) | `examples` | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/boot/app-boot), [`invariants`](../packages/runtime-diagnostics/invariants), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`tools`](../packages/core/tools) |
 | [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`commands`](../packages/interaction/commands), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`credentials`](../packages/credentials/credentials), [`file-reference`](../packages/context/file-reference), [`goal`](../packages/goal/goal), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-reference`](../packages/context/session-reference), [`settings`](../packages/settings/settings), [`typert-registry`](../packages/typert/registry) |
 | [`client-runtime`](../packages/client/runtime) | `client` | [`agent`](../packages/core/agent), [`api-remotes`](../packages/api/remotes), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`commands`](../packages/interaction/commands), [`host-apiproxy`](../packages/host/apiproxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-title`](../packages/session/session-title), [`tool-todo`](../packages/todo/tool-todo), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry) |
 | [`client-ui-renderer`](../packages/client/ui-renderer) | `client` | [`client-runtime`](../packages/client/runtime), [`invariants`](../packages/runtime-diagnostics/invariants) |

+ 9 - 15
docs/module-graph.zh.md

@@ -118,8 +118,10 @@ flowchart TD
     pkg_cmdline["cmdline"]
   end
   subgraph group_bundle["packages/bundle"]
+    pkg_acp_app["acp-app"]
     pkg_base["base"]
     pkg_headless["headless"]
+    pkg_sdk_app["sdk-app"]
     pkg_web_app["web-app"]
   end
   subgraph group_client["packages/client"]
@@ -194,9 +196,7 @@ flowchart TD
     pkg_subprocess_e2b["subprocess-e2b"]
   end
   subgraph group_examples["packages/examples"]
-    pkg_acp_demo["acp-demo"]
     pkg_agent_spine_demo["agent-spine-demo"]
-    pkg_sdk_jsonrpc_demo["sdk-jsonrpc-demo"]
   end
   subgraph group_experimental["packages/experimental"]
     pkg_experimental_agent_team["experimental-agent-team"]
@@ -271,6 +271,7 @@ flowchart TD
     pkg_sdk_client["sdk-client"]
     pkg_sdk_jsonrpc_server["sdk-jsonrpc-server"]
     pkg_sdk_protocol["sdk-protocol"]
+    pkg_sdk_python_runtime["sdk-python-runtime"]
   end
   subgraph group_session["packages/session"]
     pkg_session_checkpoint_policy["session-checkpoint-policy"]
@@ -358,20 +359,22 @@ flowchart TD
   pkg_deepseek_llm_api_extensions --> pkg_invariants
   pkg_scope --> pkg_invariants
   pkg_cmdline --> pkg_invariants
+  pkg_acp_app --> pkg_invariants
   pkg_base --> pkg_invariants
+  pkg_sdk_app --> pkg_invariants
   pkg_client_ui_primitives --> pkg_invariants
   pkg_client_ui_slots --> pkg_invariants
   pkg_client_web --> pkg_invariants
   pkg_code_runtime --> pkg_invariants
   pkg_code_runtime_python --> pkg_invariants
   pkg_e2b --> pkg_invariants
-  pkg_sdk_jsonrpc_demo --> pkg_invariants
   pkg_experimental_webworker_packer --> pkg_invariants
   pkg_host_directory_picker --> pkg_invariants
   pkg_host_directory_picker_browse --> pkg_invariants
   pkg_host_directory_picker_native --> pkg_invariants
   pkg_host_webserver --> pkg_invariants
   pkg_sandbox_windows_acl --> pkg_invariants
+  pkg_sdk_python_runtime --> pkg_invariants
   pkg_storage --> pkg_invariants
   pkg_subprocess --> pkg_invariants
   pkg_win32_process --> pkg_invariants
@@ -1225,16 +1228,6 @@ flowchart TD
   pkg_api_gateway --> pkg_client_connection
   pkg_api_gateway --> pkg_invariants
   pkg_api_gateway --> pkg_typert_registry
-  pkg_acp_demo --> pkg_acp
-  pkg_acp_demo --> pkg_agent_instructions
-  pkg_acp_demo --> pkg_agent_spine_demo
-  pkg_acp_demo --> pkg_app_boot
-  pkg_acp_demo --> pkg_invariants
-  pkg_acp_demo --> pkg_session_checkpoint_policy
-  pkg_acp_demo --> pkg_session_persistence_jsonl
-  pkg_acp_demo --> pkg_session_query
-  pkg_acp_demo --> pkg_session_query_sqlite
-  pkg_acp_demo --> pkg_tools
   pkg_api_remotes --> pkg_agent
   pkg_api_remotes --> pkg_agent_presets
   pkg_api_remotes --> pkg_api_gateway
@@ -1528,20 +1521,22 @@ flowchart TD
 | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | `llm` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`scope`](../packages/core/scope) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`cmdline`](../packages/boot/cmdline) | `boot` | [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`acp-app`](../packages/bundle/acp-app) | `bundle` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`base`](../packages/bundle/base) | `bundle` | [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`sdk-app`](../packages/bundle/sdk-app) | `bundle` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`client-ui-primitives`](../packages/client/ui-primitives) | `client` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`client-ui-slots`](../packages/client/ui-slots) | `client` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`client-web`](../packages/client/web) | `client` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`code-runtime`](../packages/code-runtime/code-runtime) | `code-runtime` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`code-runtime-python`](../packages/code-runtime/code-runtime-python) | `code-runtime` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`e2b`](../packages/e2b/e2b) | `e2b` | [`invariants`](../packages/runtime-diagnostics/invariants) |
-| [`sdk-jsonrpc-demo`](../packages/examples/jsonrpc-demo) | `examples` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`experimental-webworker-packer`](../packages/experimental/webworker-packer) | `experimental` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`host-directory-picker`](../packages/host/directory-picker) | `host` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`host-directory-picker-browse`](../packages/host/directory-picker-browse) | `host` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`host-directory-picker-native`](../packages/host/directory-picker-native) | `host` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`host-webserver`](../packages/host/webserver) | `host` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`sandbox-windows-acl`](../packages/sandbox/sandbox-windows-acl) | `sandbox` | [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`sdk-python-runtime`](../packages/sdk/python-runtime) | `sdk` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`storage`](../packages/storage/storage) | `storage` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`subprocess`](../packages/subprocess/subprocess) | `subprocess` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`win32-process`](../packages/subprocess/win32-process) | `subprocess` | [`invariants`](../packages/runtime-diagnostics/invariants) |
@@ -1711,7 +1706,6 @@ flowchart TD
 | [`sdk-jsonrpc-server`](../packages/sdk/server) | `sdk` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`scope`](../packages/core/scope), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
 | [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sdk-client`](../packages/sdk/client), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) |
 | [`api-gateway`](../packages/api/gateway) | `api` | [`client-connection`](../packages/client/connection), [`invariants`](../packages/runtime-diagnostics/invariants), [`typert-registry`](../packages/typert/registry) |
-| [`acp-demo`](../packages/examples/acp-demo) | `examples` | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/boot/app-boot), [`invariants`](../packages/runtime-diagnostics/invariants), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`tools`](../packages/core/tools) |
 | [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`commands`](../packages/interaction/commands), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`credentials`](../packages/credentials/credentials), [`file-reference`](../packages/context/file-reference), [`goal`](../packages/goal/goal), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-reference`](../packages/context/session-reference), [`settings`](../packages/settings/settings), [`typert-registry`](../packages/typert/registry) |
 | [`client-runtime`](../packages/client/runtime) | `client` | [`agent`](../packages/core/agent), [`api-remotes`](../packages/api/remotes), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`commands`](../packages/interaction/commands), [`host-apiproxy`](../packages/host/apiproxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-title`](../packages/session/session-title), [`tool-todo`](../packages/todo/tool-todo), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry) |
 | [`client-ui-renderer`](../packages/client/ui-renderer) | `client` | [`client-runtime`](../packages/client/runtime), [`invariants`](../packages/runtime-diagnostics/invariants) |

+ 2 - 2
docs/testing.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/testing.md
-testing.md: e2179effa6365a58584cb9f1dc7e4c40c5533741
-testing.zh.md: 0741aa485d4f3f8bdf4b90e35188b849974d9fc6
+testing.md: 6c484f4404cc8b00bba2a99461a3b17c17d1d6be
+testing.zh.md: 30cc970b6299e6ac14ddd457aaa36c7d38b190ca

+ 1 - 1
docs/testing.md

@@ -46,4 +46,4 @@ An e2e assertion re-runs the command or re-reads the file externally; a keyword
 
 ## When a snapshot test is required
 
-Every non-trivial model-, protocol-, or human-visible change adds or updates a keyless scenario in the same PR through a runnable example's owning snapshot suite. Package tests, e2e assertions, mock/test-only compositions, and PR rationale do not replace the assembled transcript; extend the harness when needed. ACP automation scenarios use `examples/<name>/tests/snapshots/`, a scenario table over the [`dsh-acp-snapshot`](../packages/test-support/acp-snapshot/README.md) suite factory (`examples/acp-agent` is primary); `examples/headless-agent` owns the internal canonical-event JSONL snapshots and replay fixtures. The `pwsh-tool-turn` ACP scenario boots real `pwsh` and skips where it is absent. Completed interactive-terminal journeys use JSONL-driven scenarios under `apps/cli/tests/snapshots/`; transient presentation uses the package-local semantic matrix, with a PTY case when input, Loader selection, or terminal teardown changes. Browser-rendered web GUI journeys use `apps/web/tests/snapshots/`. The two SDKs project the agent loop, session lifecycle, and `SessionEventMap` independently, so changing any of those updates both: `examples/jsonrpc-agent/tests/snapshots/` owns the TypeScript client; `scripts/snapshots/python-sdk-single-exe/` owns the Python client, which only the required `python-runtime` CI job runs. New capability seams, lifecycle variants, or transcript surfaces name every coverage tier at plan time and verify the harness can express it before implementation.
+Every non-trivial model-, protocol-, or human-visible change adds or updates a keyless scenario in the same PR through a runnable example's owning snapshot suite. Package tests, e2e assertions, mock/test-only compositions, and PR rationale do not replace the assembled transcript; extend the harness when needed. ACP automation scenarios use `examples/<name>/tests/snapshots/`, a scenario table over the [`dsh-acp-snapshot`](../packages/test-support/acp-snapshot/README.md) suite factory (`examples/acp-agent` is primary); `examples/headless-agent` owns the internal canonical-event JSONL snapshots and replay fixtures. The `pwsh-tool-turn` ACP scenario boots real `pwsh` and skips where it is absent. Completed interactive-terminal journeys use JSONL-driven scenarios under `apps/cli/tests/snapshots/`; transient presentation uses the package-local semantic matrix, with a PTY case when input, Loader selection, or terminal teardown changes. Browser-rendered web GUI journeys use `apps/web/tests/snapshots/`. The two SDKs project the agent loop, session lifecycle, and `SessionEventMap` independently, so changing any of those updates both: `examples/python-sdk-agent/tests/snapshots/` owns the TypeScript client; `scripts/snapshots/python-sdk-single-exe/` owns the Python client, which only the required `python-runtime` CI job runs. New capability seams, lifecycle variants, or transcript surfaces name every coverage tier at plan time and verify the harness can express it before implementation.

+ 1 - 1
docs/testing.zh.md

@@ -46,4 +46,4 @@ e2e 断言应重新运行命令或从外部重新读取文件;对 agent 自身
 
 ## 何时需要快照测试
 
-每项非平凡的模型可见、协议可见或人类可见变更,都必须在同一 PR 中,通过可运行示例所属的快照套件添加或更新无密钥场景。包测试、e2e 断言、mock 与仅测试组合、PR 理由都不能取代组装后的 transcript;必要时应扩展 harness。ACP 自动化场景使用 `examples/<name>/tests/snapshots/`,即基于 [`dsh-acp-snapshot`](../packages/test-support/acp-snapshot/README.zh.md) 套件工厂的场景表(`examples/acp-agent` 为主套件);`examples/headless-agent` 拥有内部规范事件 JSONL 快照与回放 fixture。`pwsh-tool-turn` ACP 场景启动真实 `pwsh`,在无 `pwsh` 的主机上跳过。已完成的交互式终端旅程使用 `apps/cli/tests/snapshots/` 下由 JSONL 驱动的场景;瞬态呈现使用包内语义矩阵,输入、Loader 选择或终端清理发生变化时还要添加 PTY 用例。浏览器渲染的 Web GUI 旅程使用上述 Web 应用快照套件。两个 SDK 各自独立地投影 agent loop、会话生命周期与 `SessionEventMap`,因此改动其中任何一项都要同时更新两者:`examples/jsonrpc-agent/tests/snapshots/` 拥有 TypeScript 客户端;`scripts/snapshots/python-sdk-single-exe/` 拥有 Python 客户端,且只有必需的 `python-runtime` CI 作业会运行它。新的能力 seam、生命周期变体或 transcript 呈现接口在计划阶段就要列出每个覆盖层级,并在实现前验证 harness 能够表达它们。
+每项非平凡的模型可见、协议可见或人类可见变更,都必须在同一 PR 中,通过可运行示例所属的快照套件添加或更新无密钥场景。包测试、e2e 断言、mock 与仅测试组合、PR 理由都不能取代组装后的 transcript;必要时应扩展 harness。ACP 自动化场景使用 `examples/<name>/tests/snapshots/`,即基于 [`dsh-acp-snapshot`](../packages/test-support/acp-snapshot/README.zh.md) 套件工厂的场景表(`examples/acp-agent` 为主套件);`examples/headless-agent` 拥有内部规范事件 JSONL 快照与回放 fixture。`pwsh-tool-turn` ACP 场景启动真实 `pwsh`,在无 `pwsh` 的主机上跳过。已完成的交互式终端旅程使用 `apps/cli/tests/snapshots/` 下由 JSONL 驱动的场景;瞬态呈现使用包内语义矩阵,输入、Loader 选择或终端清理发生变化时还要添加 PTY 用例。浏览器渲染的 Web GUI 旅程使用上述 Web 应用快照套件。两个 SDK 各自独立地投影 agent loop、会话生命周期与 `SessionEventMap`,因此改动其中任何一项都要同时更新两者:`examples/python-sdk-agent/tests/snapshots/` 拥有 TypeScript 客户端;`scripts/snapshots/python-sdk-single-exe/` 拥有 Python 客户端,且只有必需的 `python-runtime` CI 作业会运行它。新的能力 seam、生命周期变体或 transcript 呈现接口在计划阶段就要列出每个覆盖层级,并在实现前验证 harness 能够表达它们。

+ 2 - 2
docs/user/guide/python-sdk.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/user/guide/python-sdk.md
-python-sdk.md: 71c588ce8c22a8de7ea6c8ed79989b310dcf812a
-python-sdk.zh.md: 00723640ae8f9cff099dfd49816bb3e358f84f6a
+python-sdk.md: 21c7a908a8524b16baf8f98453746f59b5d5efc8
+python-sdk.zh.md: bb883f6f3799225bda590a80883063ecb29e15b4

+ 3 - 3
docs/user/guide/python-sdk.md

@@ -40,7 +40,7 @@ export DEEPSEEK_API_KEY=sk-your-key-here
 Run one task against an isolated workspace and session directory:
 
 ```sh
-python examples/jsonrpc-agent/minimal.py \
+python examples/python-sdk-agent/minimal.py \
   --workspace /absolute/path/to/workspace \
   --session-root /absolute/path/to/sessions \
   --session-id example-001 \
@@ -58,7 +58,7 @@ from pathlib import Path
 
 from deepseek_harness import DeepSeekHarness
 
-config = Path("examples/jsonrpc-agent/minimal.cordis.yml").resolve()
+config = Path("examples/python-sdk-agent/minimal.cordis.yml").resolve()
 workspace = Path("/absolute/path/to/workspace").resolve()
 sessions = Path("/absolute/path/to/sessions").resolve()
 
@@ -101,4 +101,4 @@ The composition omits harness identity, workspace prompt text, skills, one-shot
 
 The composition uses `danger-full-access`. Run it only inside a disposable checkout or container: Bash and the editor can modify any path allowed to the runtime process. The persistent PTY backend requires a POSIX terminal substrate, so this composition does not support Windows agents.
 
-The [`jsonrpc-agent` example reference](../../../examples/jsonrpc-agent/README.md) owns the exact composition. The [Python SDK reference](../../../python/sdk/README.md) covers lifecycle, results, notifications, runtime selection, and configuration; the [Cordis primer](../../cordis-primer.md) covers composition syntax.
+The [`python-sdk-agent` example reference](../../../examples/python-sdk-agent/README.md) owns the exact composition. The [Python SDK reference](../../../python/sdk/README.md) covers lifecycle, results, notifications, runtime selection, and configuration; the [Cordis primer](../../cordis-primer.md) covers composition syntax.

+ 3 - 3
docs/user/guide/python-sdk.zh.md

@@ -40,7 +40,7 @@ export DEEPSEEK_API_KEY=sk-your-key-here
 针对隔离的 workspace 和会话目录运行一个任务:
 
 ```sh
-python examples/jsonrpc-agent/minimal.py \
+python examples/python-sdk-agent/minimal.py \
   --workspace /absolute/path/to/workspace \
   --session-root /absolute/path/to/sessions \
   --session-id example-001 \
@@ -58,7 +58,7 @@ from pathlib import Path
 
 from deepseek_harness import DeepSeekHarness
 
-config = Path("examples/jsonrpc-agent/minimal.cordis.yml").resolve()
+config = Path("examples/python-sdk-agent/minimal.cordis.yml").resolve()
 workspace = Path("/absolute/path/to/workspace").resolve()
 sessions = Path("/absolute/path/to/sessions").resolve()
 
@@ -101,4 +101,4 @@ print(result.final_response)
 
 该组合使用 `danger-full-access`。只能在可丢弃的 checkout 或容器内运行:Bash 与编辑器可以修改运行时进程有权访问的任何路径。持久 PTY 后端需要 POSIX 终端环境,因此该组合不支持 Windows agent。
 
-准确的组合内容归 [`jsonrpc-agent` 示例参考](../../../examples/jsonrpc-agent/README.zh.md)所有。[Python SDK 参考](../../../python/sdk/README.zh.md)介绍生命周期、结果、通知、运行时选择和配置;[Cordis primer](../../cordis-primer.zh.md)介绍组合语法。
+准确的组合内容归 [`python-sdk-agent` 示例参考](../../../examples/python-sdk-agent/README.zh.md)所有。[Python SDK 参考](../../../python/sdk/README.zh.md)介绍生命周期、结果、通知、运行时选择和配置;[Cordis primer](../../cordis-primer.zh.md)介绍组合语法。

+ 2 - 2
examples/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 examples/README.md
-README.md: dcd431640a4b865c1aebdf63585d060984d65e6f
-README.zh.md: b1a4109197e3f6d07a7a8529bc2da03001342c23
+README.md: 51e1650b54d89d11d29f2b2d61530ad3bc323d03
+README.zh.md: 65d6d27794e2888a7f0cb0e8b8503cb8b984d7c4

+ 2 - 2
examples/README.md

@@ -12,9 +12,9 @@ Optional overlays that connect supported third-party memory servers through the
 
 A non-interactive agent that accepts one task, runs it, and emits a selected machine-readable or human-readable output format. See the [headless example reference](headless-agent/README.md).
 
-## jsonrpc-agent
+## python-sdk-agent
 
-An unattended coding agent driven through the Python SDK and JSON-RPC. See the [JSON-RPC example reference](jsonrpc-agent/README.md).
+An unattended coding agent driven through the Python SDK and JSON-RPC. See the [Python SDK agent reference](python-sdk-agent/README.md).
 
 ## web-cordis
 

+ 2 - 2
examples/README.zh.md

@@ -12,9 +12,9 @@
 
 非交互式 agent(智能体):接受一项任务并运行,然后以选定的机器可读或人类可读格式输出结果。详见[无头示例参考](headless-agent/README.zh.md)。
 
-## jsonrpc-agent
+## python-sdk-agent
 
-由 Python SDK 和 JSON-RPC 驱动的无人值守编码 agent。详见 [JSON-RPC 示例参考](jsonrpc-agent/README.zh.md)。
+由 Python SDK 和 JSON-RPC 驱动的无人值守编码 agent。详见 [Python SDK agent 示例参考](python-sdk-agent/README.zh.md)。
 
 ## web-cordis
 

+ 2 - 2
examples/acp-agent/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 examples/acp-agent/README.md
-README.md: 61c6efafe9dde4f91385beebdfd426c57006187b
-README.zh.md: c8cdd248e1e71626aac007a8a6923ae7382ea691
+README.md: 81545a8b3a631653256c6f3c6688ba97136b5db1
+README.zh.md: 12a40cde8c1e9f063b32da7080bafed68fed705c

+ 2 - 2
examples/acp-agent/README.md

@@ -9,11 +9,11 @@ pnpm run demo:acp             # needs DEEPSEEK_API_KEY (repo-root .env or env)
 pnpm run demo:code-mode       # same protocol with the Code Mode tool transport
 ```
 
-The leaf loads the ACP app, DeepSeek adapter, sandboxed bash and filesystem stacks, one-shot approval policy, compaction, subagents, workflows, hooks, a derived session-query index, and repeat guard. The app creates one fresh agent per `session/new`, persists sessions to JSONL, and keeps stdout protocol-pure. Optional overlays add session queries, filesystem spill storage, Code Mode, or web fetching.
+The `dsh` launcher applies the shipped `acp` profile (`dsh-base` plus [`dsh-acp-app`](../../packages/bundle/acp-app/README.md)), then this leaf's `cordis.yml` patch. The profile supplies the ACP bridge, DeepSeek adapter, sandboxed shell and filesystem stacks, approval policy, compaction, subagents, workflows, a session-query index, and repeat guard; the leaf pins demo and snapshot values and adds hook bridges. The bridge creates one fresh agent per `session/new`, persists sessions to JSONL, and keeps stdout protocol-pure. Optional patch overlays add session-query tools, spill settings, Code Mode, or web fetching.
 
 ## Protocol channel
 
-Stdout carries only newline-delimited ACP JSON-RPC. `@deepseek-ai/dsh-acp-demo` installs no stdout logger; leaf additions must use stderr for diagnostics.
+Stdout carries only newline-delimited ACP JSON-RPC. `@deepseek-ai/dsh-acp-app` and this patch install no stdout logger; added plugins must use stderr for diagnostics.
 
 The automation contract — supported methods, baseline prompt content, committed-text output, and the intentionally absent UI surfaces — lives in [`@deepseek-ai/dsh-acp`](../../packages/acp/acp/README.md).
 

+ 2 - 2
examples/acp-agent/README.zh.md

@@ -9,11 +9,11 @@ pnpm run demo:acp             # needs DEEPSEEK_API_KEY (repo-root .env or env)
 pnpm run demo:code-mode       # same protocol with the Code Mode tool transport
 ```
 
-该叶节点加载 ACP 应用、DeepSeek 适配器、受沙箱限制的 bash 与文件系统栈、一次性批准策略、压缩(compaction)、subagent、工作流、钩子、派生会话查询索引和重复守卫。应用为每次 `session/new` 创建一个新 agent,将会话持久化到 JSONL,并保持 stdout 只含协议内容。可选 overlay 可添加会话查询、文件系统 spill 存储、Code Mode 或 Web 抓取。
+`dsh` 启动器先应用随附 `acp` profile(`dsh-base` 加 [`dsh-acp-app`](../../packages/bundle/acp-app/README.zh.md)),再应用本叶节点的 `cordis.yml` patch。profile 提供 ACP bridge、DeepSeek 适配器、受沙箱限制的 shell 与文件系统栈、批准策略、压缩(compaction)、subagent、工作流、会话查询索引和重复守卫;本叶节点固定 demo 与 snapshot 值并添加 hook bridge。bridge 为每次 `session/new` 创建一个新 agent,将会话持久化到 JSONL,并保持 stdout 只含协议内容。可选 patch overlay 可添加会话查询工具、spill 设置、Code Mode 或 Web 抓取。
 
 ## 协议通道
 
-Stdout 只携带以换行分隔的 ACP JSON-RPC。`@deepseek-ai/dsh-acp-demo` 不安装 stdout logger;该叶节点新增的组件必须使用 stderr 输出诊断信息。
+Stdout 只携带以换行分隔的 ACP JSON-RPC。`@deepseek-ai/dsh-acp-app` 与本 patch 均不安装 stdout logger;新增插件必须使用 stderr 输出诊断信息。
 
 自动化约定(支持的方法、基线提示词内容、已提交文本输出,以及有意缺少的 UI 界面)位于 [`@deepseek-ai/dsh-acp`](../../packages/acp/acp/README.zh.md)。
 

+ 48 - 37
examples/acp-agent/advanced.cordis.snapshot.yml

@@ -1,40 +1,51 @@
 # Replay counterpart to advanced.cordis.yml; only the live model is replaced.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  disabled: true
+
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
+  config:
+    provider: deepseek-official
+    model: deepseek-v4-flash
+
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: none
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: 'none'
-          workspaceContext:
-            maxBytes: 65536
-          tools:
-            mode: both
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+    maxBytes: 65536
+
+- id: tools
+  name: '@deepseek-ai/dsh-tools'
+  config:
+    mode: both
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: code-runtime
-            name: '@deepseek-ai/dsh-code-runtime-worker-thread'
-          - id: cordis-host-runner
-            name: '@deepseek-ai/dsh-cordis-host-runner'
-          - id: tool-cordis
-            name: '@deepseek-ai/dsh-tool-cordis'
-          - id: llm-replay
-            name: '@deepseek-ai/dsh-llm-replay'
-            config:
-              providers:
-                - id: deepseek-official
-                  name: DeepSeek
-                  models:
-                    - id: deepseek-v4-flash
-                    - id: deepseek-v4-pro
+- insert:
+    - id: code-runtime
+      name: '@deepseek-ai/dsh-code-runtime-worker-thread'
+    - id: cordis-host-runner
+      name: '@deepseek-ai/dsh-cordis-host-runner'
+    - id: tool-cordis
+      name: '@deepseek-ai/dsh-tool-cordis'
+    - id: llm-replay
+      name: '@deepseek-ai/dsh-llm-replay'
+      config:
+        providers:
+          - id: deepseek-official
+            name: DeepSeek
+            models:
+              - id: deepseek-v4-flash
+              - id: deepseek-v4-pro

+ 35 - 25
examples/acp-agent/advanced.cordis.yml

@@ -1,29 +1,39 @@
 # Add Code Mode and Cordis tools to the base spawn/workflow stack, exercising
 # all four boundaries in one ACP snapshot.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-pro
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
-          workspaceContext:
-            maxBytes: 65536
-          tools:
-            mode: both
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+    provider: deepseek-official
+    model: deepseek-v4-pro
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: code-runtime
-            name: '@deepseek-ai/dsh-code-runtime-worker-thread'
-          - id: cordis-host-runner
-            name: '@deepseek-ai/dsh-cordis-host-runner'
-          - id: tool-cordis
-            name: '@deepseek-ai/dsh-tool-cordis'
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none'''
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
+  config:
+    maxBytes: 65536
+
+- id: tools
+  name: '@deepseek-ai/dsh-tools'
+  config:
+    mode: both
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
+
+- insert:
+    - id: code-runtime
+      name: '@deepseek-ai/dsh-code-runtime-worker-thread'
+    - id: cordis-host-runner
+      name: '@deepseek-ai/dsh-cordis-host-runner'
+    - id: tool-cordis
+      name: '@deepseek-ai/dsh-tool-cordis'

+ 44 - 37
examples/acp-agent/agent-instructions.cordis.snapshot.yml

@@ -1,39 +1,46 @@
-# Keyless replay counterpart of agent-instructions.cordis.yml. Patches do not
-# compose across includes, so this applies the scenario config and model swap
-# directly to the live tree.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+# Keyless replay counterpart of agent-instructions.cordis.yml: apply the
+# scenario values and model swap to the ACP profile.
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  disabled: true
+
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
+  config:
+    provider: deepseek-official
+    model: deepseek-v4-flash
+
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: none
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
+  config:
+    maxBytes: 65536
+    dshHome: !!js process.cwd() + '/.dsh'
+    projectRootMarkers:
+      - .dsh-project
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: 'none'
-          workspaceContext:
-            maxBytes: 65536
-            dshHome: !!js process.cwd() + '/.dsh'
-            projectRootMarkers:
-              - .dsh-project
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: llm-replay
-            name: '@deepseek-ai/dsh-llm-replay'
-            config:
-              providers:
-                - id: deepseek-official
-                  name: DeepSeek
-                  models:
-                    - id: deepseek-v4-flash
-                    - id: deepseek-v4-pro
-          - id: workspace-context-compaction
-            name: './tests/fixtures/workspace-context-compaction.ts'
+- insert:
+    - id: llm-replay
+      name: '@deepseek-ai/dsh-llm-replay'
+      config:
+        providers:
+          - id: deepseek-official
+            name: DeepSeek
+            models:
+              - id: deepseek-v4-flash
+              - id: deepseek-v4-pro
+    - id: workspace-context-compaction
+      name: './tests/fixtures/workspace-context-compaction.ts'

+ 27 - 22
examples/acp-agent/agent-instructions.cordis.yml

@@ -1,24 +1,29 @@
-# Workspace-context snapshot overlay: keep project-root and user-global
-# discovery inside the scenario's temporary cwd. The app config patch replaces
-# the whole base config, so the base fields are restated verbatim.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+# Workspace-context snapshot patch: keep project-root and user-global
+# discovery inside the scenario's temporary cwd.
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-pro
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
-          workspaceContext:
-            maxBytes: 65536
-            dshHome: !!js process.cwd() + '/.dsh'
-            projectRootMarkers:
-              - .dsh-project
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+    provider: deepseek-official
+    model: deepseek-v4-pro
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none'''
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
+  config:
+    maxBytes: 65536
+    dshHome: !!js process.cwd() + '/.dsh'
+    projectRootMarkers:
+      - .dsh-project
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.

+ 42 - 31
examples/acp-agent/background-job-admission.cordis.snapshot.yml

@@ -1,36 +1,47 @@
 # Keyless counterpart to background-job-admission.cordis.yml: replace the
 # DeepSeek adapter with replay while preserving the app's one-task admission
 # config and the recorded flash route.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  disabled: true
+
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
+  config:
+    provider: deepseek-official
+    model: deepseek-v4-flash
+
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: none
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
+  config:
+    maxBytes: 65536
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
+
+- id: jobs
+  name: '@deepseek-ai/dsh-jobs-local'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: none
-          workspaceContext:
-            maxBytes: 65536
-          tasks:
-            maxConcurrentJobsPerOwner: 1
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+    maxConcurrentJobsPerOwner: 1
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: llm-replay
-            name: '@deepseek-ai/dsh-llm-replay'
-            config:
-              providers:
-                - id: deepseek-official
-                  name: DeepSeek
-                  models:
-                    - id: deepseek-v4-flash
-                    - id: deepseek-v4-pro
+- insert:
+    - id: llm-replay
+      name: '@deepseek-ai/dsh-llm-replay'
+      config:
+        providers:
+          - id: deepseek-official
+            name: DeepSeek
+            models:
+              - id: deepseek-v4-flash
+              - id: deepseek-v4-pro

+ 27 - 18
examples/acp-agent/background-job-admission.cordis.yml

@@ -2,23 +2,32 @@
 # configuring its task provider to allow one active task per exact owner. The
 # scenario starts a real background Bash process, observes the second producer
 # rejection, and cleans up the first task by its returned id.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
-          workspaceContext:
-            maxBytes: 65536
-          tasks:
-            maxConcurrentJobsPerOwner: 1
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+    provider: deepseek-official
+    model: deepseek-v4-flash
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none'''
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
+  config:
+    maxBytes: 65536
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
+
+- id: jobs
+  name: '@deepseek-ai/dsh-jobs-local'
+  config:
+    maxConcurrentJobsPerOwner: 1

+ 46 - 36
examples/acp-agent/both-mode.cordis.snapshot.yml

@@ -1,38 +1,48 @@
-# Keyless both mode combines the runtime/registry patch with the DeepSeek-to-replay
-# swap. Include patches cannot target entries behind a nested include, so this file
-# applies both overlays directly to `cordis.yml`.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+# Keyless both mode combines the runtime/registry changes with the
+# DeepSeek-to-replay swap in one profile patch.
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  disabled: true
+
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
+  config:
+    provider: deepseek-official
+    model: deepseek-v4-flash
+
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: none
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: 'none'
-          workspaceContext:
-            maxBytes: 65536
-          tools:
-            mode: both
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+    maxBytes: 65536
+
+- id: tools
+  name: '@deepseek-ai/dsh-tools'
+  config:
+    mode: both
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: code-runtime
-            name: '@deepseek-ai/dsh-code-runtime-worker-thread'
-          - id: llm-replay
-            name: '@deepseek-ai/dsh-llm-replay'
-            config:
-              providers:
-                - id: deepseek-official
-                  name: DeepSeek
-                  models:
-                    - id: deepseek-v4-flash
-                    - id: deepseek-v4-pro
+- insert:
+    - id: code-runtime
+      name: '@deepseek-ai/dsh-code-runtime-worker-thread'
+    - id: llm-replay
+      name: '@deepseek-ai/dsh-llm-replay'
+      config:
+        providers:
+          - id: deepseek-official
+            name: DeepSeek
+            models:
+              - id: deepseek-v4-flash
+              - id: deepseek-v4-pro

+ 33 - 24
examples/acp-agent/both-mode.cordis.yml

@@ -1,27 +1,36 @@
 # Both mode adds `ctx.codeRuntime` while keeping native tools on the wire and
-# adding `run_code` plus its generated TypeScript SDK prompt. The app bin selects
-# this overlay for snapshot recording and the sibling overlay for replay. A config
-# patch replaces the whole app config, so unchanged base fields are restated below.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+# adding `run_code` plus its generated TypeScript SDK prompt. Recording applies
+# this profile patch; replay applies its sibling patch.
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-pro
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
-          workspaceContext:
-            maxBytes: 65536
-          tools:
-            mode: both
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+    provider: deepseek-official
+    model: deepseek-v4-pro
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: code-runtime
-            name: '@deepseek-ai/dsh-code-runtime-worker-thread'
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none'''
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
+  config:
+    maxBytes: 65536
+
+- id: tools
+  name: '@deepseek-ai/dsh-tools'
+  config:
+    mode: both
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
+
+- insert:
+    - id: code-runtime
+      name: '@deepseek-ai/dsh-code-runtime-worker-thread'

+ 56 - 46
examples/acp-agent/child-question.cordis.snapshot.yml

@@ -1,50 +1,60 @@
 # Keyless counterpart to child-question.cordis.yml: keep the real interaction
 # seam, model-facing tool, and tripwire provider while replacing DeepSeek with
 # per-session replay.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  disabled: true
+
+- id: sandbox
+  name: '@deepseek-ai/dsh-sandbox-local'
+  config:
+    runnerCommand:
+      - bash
+      - -c
+      - while [ "$1" != "--" ]; do shift; done; shift; exec "$@"
+      - passthrough-runner
+    runnerFailureSignatures:
+      - 'passthrough-runner: profile rejected'
+
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
+  config:
+    provider: deepseek-official
+    model: deepseek-v4-flash
+
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: none
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
+  config:
+    maxBytes: 65536
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - id: sandbox
-        name: '@deepseek-ai/dsh-sandbox-local'
-        config:
-          runnerCommand:
-            - bash
-            - -c
-            - while [ "$1" != "--" ]; do shift; done; shift; exec "$@"
-            - passthrough-runner
-          runnerFailureSignatures:
-            - 'passthrough-runner: profile rejected'
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: none
-          workspaceContext:
-            maxBytes: 65536
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
-
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: llm-replay
-            name: '@deepseek-ai/dsh-llm-replay'
-            config:
-              providers:
-                - id: deepseek-official
-                  name: DeepSeek
-                  models:
-                    - id: deepseek-v4-flash
-                    - id: deepseek-v4-pro
-          - id: user-questions
-            name: '@deepseek-ai/dsh-user-questions'
-          - id: tool-ask-user
-            name: '@deepseek-ai/dsh-tool-ask-user'
-          - id: child-question-tripwire
-            name: './tests/fixtures/child-question-tripwire.ts'
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
+
+- insert:
+    - id: llm-replay
+      name: '@deepseek-ai/dsh-llm-replay'
+      config:
+        providers:
+          - id: deepseek-official
+            name: DeepSeek
+            models:
+              - id: deepseek-v4-flash
+              - id: deepseek-v4-pro
+    - id: tool-ask-user
+      name: '@deepseek-ai/dsh-tool-ask-user'
+    - id: child-question-tripwire
+      name: './tests/fixtures/child-question-tripwire.ts'
+
+- id: user-questions
+  name: '@deepseek-ai/dsh-user-questions'

+ 8 - 12
examples/acp-agent/child-question.cordis.yml

@@ -1,14 +1,10 @@
 # Snapshot-only human-interaction composition. The provider is a tripwire: the
 # runtime-owned child must be rejected by the seam before any UI wait begins.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
-  config:
-    path: ./cordis.yml
-    patches:
-      - insert:
-          - id: user-questions
-            name: '@deepseek-ai/dsh-user-questions'
-          - id: tool-ask-user
-            name: '@deepseek-ai/dsh-tool-ask-user'
-          - id: child-question-tripwire
-            name: './tests/fixtures/child-question-tripwire.ts'
+- insert:
+    - id: tool-ask-user
+      name: '@deepseek-ai/dsh-tool-ask-user'
+    - id: child-question-tripwire
+      name: './tests/fixtures/child-question-tripwire.ts'
+
+- id: user-questions
+  name: '@deepseek-ai/dsh-user-questions'

+ 52 - 40
examples/acp-agent/code-mode-image.cordis.snapshot.yml

@@ -1,44 +1,56 @@
 # Keyless replay combines Code Mode with the durable image store and an exact
 # image-capable replay route. The scenario generates its tiny PNG inside the
 # run_code program, then exercises read_image as a nested dispatch.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  disabled: true
+
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
+  config:
+    provider: deepseek-official
+    model: deepseek-v4-flash-vision-exp
+
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: none
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
+  config:
+    maxBytes: 65536
+
+- id: tools
+  name: '@deepseek-ai/dsh-tools'
+  config:
+    mode: code
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash-vision-exp
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: none
-          workspaceContext:
-            maxBytes: 65536
-          tools:
-            mode: code
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
-
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: attachment-local
-            name: '@deepseek-ai/dsh-attachment-local'
-          - id: code-runtime
-            name: '@deepseek-ai/dsh-code-runtime-worker-thread'
-          - id: llm-replay
-            name: '@deepseek-ai/dsh-llm-replay'
-            config:
-              providers:
-                - id: deepseek-official
-                  name: DeepSeek
-                  models:
-                    - id: deepseek-v4-flash
-                      inputModalities: [text]
-                    - id: deepseek-v4-pro
-                      inputModalities: [text]
-                    - id: deepseek-v4-flash-vision-exp
-                      inputModalities: [text, image]
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
+
+- insert:
+    - id: code-runtime
+      name: '@deepseek-ai/dsh-code-runtime-worker-thread'
+    - id: llm-replay
+      name: '@deepseek-ai/dsh-llm-replay'
+      config:
+        providers:
+          - id: deepseek-official
+            name: DeepSeek
+            models:
+              - id: deepseek-v4-flash
+                inputModalities: [text]
+              - id: deepseek-v4-pro
+                inputModalities: [text]
+              - id: deepseek-v4-flash-vision-exp
+                inputModalities: [text, image]
+
+- id: attachment-local
+  name: '@deepseek-ai/dsh-attachment-local'

+ 34 - 23
examples/acp-agent/code-mode-image.cordis.yml

@@ -1,28 +1,39 @@
 # Code Mode image overlay: mounts the worker runtime and durable attachment
 # store so a nested read_image result can cross the generic rich-result bridge.
 # The live config selects the shipped vision route for manual use.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash-vision-exp
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
-          workspaceContext:
-            maxBytes: 65536
-          tools:
-            mode: code
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+    provider: deepseek-official
+    model: deepseek-v4-flash-vision-exp
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: attachment-local
-            name: '@deepseek-ai/dsh-attachment-local'
-          - id: code-runtime
-            name: '@deepseek-ai/dsh-code-runtime-worker-thread'
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none'''
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
+  config:
+    maxBytes: 65536
+
+- id: tools
+  name: '@deepseek-ai/dsh-tools'
+  config:
+    mode: code
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
+
+- insert:
+    - id: code-runtime
+      name: '@deepseek-ai/dsh-code-runtime-worker-thread'
+
+- id: attachment-local
+  name: '@deepseek-ai/dsh-attachment-local'

+ 44 - 33
examples/acp-agent/code-mode-workspace-context.cordis.snapshot.yml

@@ -1,37 +1,48 @@
 # Keyless replay counterpart of code-mode-workspace-context.cordis.yml. It adds
 # Code Mode to the default filesystem suite and swaps in replay.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  disabled: true
+
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
+  config:
+    provider: deepseek-official
+    model: deepseek-v4-flash
+
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: none
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: 'none'
-          workspaceContext:
-            maxBytes: 65536
-          tools:
-            mode: code
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+    maxBytes: 65536
+
+- id: tools
+  name: '@deepseek-ai/dsh-tools'
+  config:
+    mode: code
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: code-runtime
-            name: '@deepseek-ai/dsh-code-runtime-worker-thread'
-          - id: llm-replay
-            name: '@deepseek-ai/dsh-llm-replay'
-            config:
-              providers:
-                - id: deepseek-official
-                  name: DeepSeek
-                  models:
-                    - id: deepseek-v4-flash
-                    - id: deepseek-v4-pro
+- insert:
+    - id: code-runtime
+      name: '@deepseek-ai/dsh-code-runtime-worker-thread'
+    - id: llm-replay
+      name: '@deepseek-ai/dsh-llm-replay'
+      config:
+        providers:
+          - id: deepseek-official
+            name: DeepSeek
+            models:
+              - id: deepseek-v4-flash
+              - id: deepseek-v4-pro

+ 31 - 21
examples/acp-agent/code-mode-workspace-context.cordis.yml

@@ -1,25 +1,35 @@
 # Code Mode agent-instructions snapshot recording overlay. The default filesystem
 # tools trigger nested instruction discovery after a read.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-pro
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
-          workspaceContext:
-            maxBytes: 65536
-          tools:
-            mode: code
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+    provider: deepseek-official
+    model: deepseek-v4-pro
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: code-runtime
-            name: '@deepseek-ai/dsh-code-runtime-worker-thread'
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none'''
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
+  config:
+    maxBytes: 65536
+
+- id: tools
+  name: '@deepseek-ai/dsh-tools'
+  config:
+    mode: code
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
+
+- insert:
+    - id: code-runtime
+      name: '@deepseek-ai/dsh-code-runtime-worker-thread'

+ 46 - 36
examples/acp-agent/code-mode.cordis.snapshot.yml

@@ -1,38 +1,48 @@
-# Keyless Code Mode combines the runtime/registry patch with the DeepSeek-to-replay
-# swap. Include patches cannot target entries behind a nested include, so this file
-# applies both overlays directly to `cordis.yml`.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+# Keyless Code Mode combines the runtime/registry changes with the
+# DeepSeek-to-replay swap in one profile patch.
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  disabled: true
+
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
+  config:
+    provider: deepseek-official
+    model: deepseek-v4-flash
+
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: none
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: 'none'
-          workspaceContext:
-            maxBytes: 65536
-          tools:
-            mode: code
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+    maxBytes: 65536
+
+- id: tools
+  name: '@deepseek-ai/dsh-tools'
+  config:
+    mode: code
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: code-runtime
-            name: '@deepseek-ai/dsh-code-runtime-worker-thread'
-          - id: llm-replay
-            name: '@deepseek-ai/dsh-llm-replay'
-            config:
-              providers:
-                - id: deepseek-official
-                  name: DeepSeek
-                  models:
-                    - id: deepseek-v4-flash
-                    - id: deepseek-v4-pro
+- insert:
+    - id: code-runtime
+      name: '@deepseek-ai/dsh-code-runtime-worker-thread'
+    - id: llm-replay
+      name: '@deepseek-ai/dsh-llm-replay'
+      config:
+        providers:
+          - id: deepseek-official
+            name: DeepSeek
+            models:
+              - id: deepseek-v4-flash
+              - id: deepseek-v4-pro

+ 33 - 25
examples/acp-agent/code-mode.cordis.yml

@@ -1,28 +1,36 @@
 # Code Mode adds `ctx.codeRuntime` and changes the registry to one wire tool,
-# `run_code`, plus its generated TypeScript SDK prompt. The app bin selects this
-# overlay for `demo:code-mode` and snapshot recording, and selects the sibling
-# replay overlay for `DSH_SNAPSHOT=replay`. A config patch replaces the whole app
-# config, so unchanged base fields are restated below.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+# `run_code`, plus its generated TypeScript SDK prompt. The demo and snapshot
+# recorder apply this profile patch; replay applies its sibling patch.
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-pro
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
-          workspaceContext:
-            maxBytes: 65536
-          tools:
-            mode: code
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+    provider: deepseek-official
+    model: deepseek-v4-pro
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: code-runtime
-            name: '@deepseek-ai/dsh-code-runtime-worker-thread'
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none'''
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
+  config:
+    maxBytes: 65536
+
+- id: tools
+  name: '@deepseek-ai/dsh-tools'
+  config:
+    mode: code
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
+
+- insert:
+    - id: code-runtime
+      name: '@deepseek-ai/dsh-code-runtime-worker-thread'

+ 15 - 79
examples/acp-agent/composition.md

@@ -1,78 +1,33 @@
 <!-- Generated by scripts/gen-doc-graphs.ts - do not edit by hand.
      Run `pnpm run gen-doc-graphs` to regenerate. -->
 
-# ACP Automation App Composition
+# ACP Automation Profile Patch
 
-The ACP demo exposes fresh baseline-prompt agent sessions to programmatic clients over JSON-RPC stdio, with no stdout logger, human UI, or pre-created agent.
+The ACP example patches the shipped base + acp-app profile for demos and snapshots; dsh owns launch, and the ACP bridge exposes fresh automation sessions without a stdout logger or pre-created agent.
 
 ```mermaid
 flowchart LR
   cfg["examples/acp-agent<br/>cordis.yml"]
-  plugin_acp_deepseek_llm_api_extensions["deepseek-llm-api-extensions<br/>@deepseek-ai/dsh-deepseek-llm-api-extensions"]
-  cfg --> plugin_acp_deepseek_llm_api_extensions
-  plugin_acp_session_log_deepseek["session-log-deepseek<br/>@deepseek-ai/dsh-session-log-deepseek"]
-  cfg --> plugin_acp_session_log_deepseek
-  plugin_acp_plugin_package_inventory_deepseek["plugin-package-inventory-deepseek<br/>@deepseek-ai/dsh-plugin-package-inventory-deepseek"]
-  cfg --> plugin_acp_plugin_package_inventory_deepseek
   plugin_acp_llm_deepseek["llm-deepseek<br/>@deepseek-ai/dsh-llm-deepseek"]
   cfg --> plugin_acp_llm_deepseek
-  plugin_acp_sandbox["sandbox<br/>@deepseek-ai/dsh-sandbox-local"]
-  cfg --> plugin_acp_sandbox
   plugin_acp_sandbox_policy["sandbox-policy<br/>@deepseek-ai/dsh-sandbox-policy"]
   cfg --> plugin_acp_sandbox_policy
-  plugin_acp_subprocess["subprocess<br/>@deepseek-ai/dsh-subprocess-local"]
-  cfg --> plugin_acp_subprocess
-  plugin_acp_bash["bash<br/>@deepseek-ai/dsh-bash-sandbox"]
-  cfg --> plugin_acp_bash
   plugin_acp_approval["approval<br/>@deepseek-ai/dsh-user-approval"]
   cfg --> plugin_acp_approval
-  plugin_acp_acp_agent["acp-agent<br/>@deepseek-ai/dsh-acp-demo"]
-  cfg --> plugin_acp_acp_agent
-  plugin_acp_acp_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-spine-demo"]
-  plugin_acp_acp_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"]
-  plugin_acp_acp_agent --> entrypoint_acp["@deepseek-ai/dsh-acp<br/>automation-only JSON-RPC stdio<br/>fresh sessions created by client"]
-  bundle_agent_core --> spine_llm["ctx.llm"]
-  bundle_agent_core --> spine_sessions["ctx.sessions"]
-  bundle_agent_core --> spine_tools["ctx.tools + tool-bash"]
-  bundle_agent_core --> spine_loop["ctx.agents + ctx.agentLoop"]
-  plugin_acp_token_meter["token-meter<br/>@deepseek-ai/dsh-token-meter"]
-  cfg --> plugin_acp_token_meter
-  plugin_acp_compaction_basic["compaction-basic<br/>@deepseek-ai/dsh-compaction-basic"]
-  cfg --> plugin_acp_compaction_basic
-  plugin_acp_session_projection["session-projection<br/>@deepseek-ai/dsh-session-projection"]
-  cfg --> plugin_acp_session_projection
-  plugin_acp_subagent["subagent<br/>@deepseek-ai/dsh-subagent"]
-  cfg --> plugin_acp_subagent
-  plugin_acp_subagent_spawn_in_process["subagent-spawn-in-process<br/>@deepseek-ai/dsh-subagent-spawn-in-process"]
-  cfg --> plugin_acp_subagent_spawn_in_process
-  plugin_acp_subagent_fork_in_process["subagent-fork-in-process<br/>@deepseek-ai/dsh-subagent-fork-in-process"]
-  cfg --> plugin_acp_subagent_fork_in_process
-  plugin_acp_tool_subagent_control["tool-subagent-control<br/>@deepseek-ai/dsh-tool-subagent-control"]
-  cfg --> plugin_acp_tool_subagent_control
-  plugin_acp_tool_subagent_list_agents["tool-subagent-list-agents<br/>@deepseek-ai/dsh-tool-subagent-control/list-agents"]
-  cfg --> plugin_acp_tool_subagent_list_agents
-  plugin_acp_tool_subagent_report["tool-subagent-report<br/>@deepseek-ai/dsh-tool-subagent-report"]
-  cfg --> plugin_acp_tool_subagent_report
+  plugin_acp_session_persistence_jsonl["session-persistence-jsonl<br/>@deepseek-ai/dsh-session-persistence-jsonl"]
+  cfg --> plugin_acp_session_persistence_jsonl
+  plugin_acp_acp["acp<br/>@deepseek-ai/dsh-acp"]
+  cfg --> plugin_acp_acp
+  plugin_acp_system_prompt["system-prompt<br/>@deepseek-ai/dsh-system-prompt"]
+  cfg --> plugin_acp_system_prompt
+  plugin_acp_agent_instructions["agent-instructions<br/>@deepseek-ai/dsh-agent-instructions"]
+  cfg --> plugin_acp_agent_instructions
   plugin_acp_tool_subagent["tool-subagent<br/>@deepseek-ai/dsh-tool-subagent"]
   cfg --> plugin_acp_tool_subagent
   plugin_acp_tool_subagent_fork["tool-subagent-fork<br/>@deepseek-ai/dsh-tool-subagent"]
   cfg --> plugin_acp_tool_subagent_fork
-  plugin_acp_workflow_worker_thread["workflow-worker-thread<br/>@deepseek-ai/dsh-workflow-worker-thread"]
-  cfg --> plugin_acp_workflow_worker_thread
-  plugin_acp_tool_workflow["tool-workflow<br/>@deepseek-ai/dsh-tool-workflow"]
-  cfg --> plugin_acp_tool_workflow
-  plugin_acp_tool_ralph["tool-ralph<br/>@deepseek-ai/dsh-tool-ralph"]
-  cfg --> plugin_acp_tool_ralph
-  plugin_acp_tool_todo["tool-todo<br/>@deepseek-ai/dsh-tool-todo"]
-  cfg --> plugin_acp_tool_todo
-  plugin_acp_repeat_tool_reminder["repeat-tool-reminder<br/>@deepseek-ai/dsh-repeat-tool-reminder"]
-  cfg --> plugin_acp_repeat_tool_reminder
   plugin_acp_fs_sandbox["fs-sandbox<br/>@deepseek-ai/dsh-fs-sandbox"]
   cfg --> plugin_acp_fs_sandbox
-  plugin_acp_fs_observation_policy["fs-observation-policy<br/>@deepseek-ai/dsh-fs-observation-policy"]
-  cfg --> plugin_acp_fs_observation_policy
-  plugin_acp_tool_fs["tool-fs<br/>@deepseek-ai/dsh-tool-fs"]
-  cfg --> plugin_acp_tool_fs
   plugin_acp_hooks_claude_code["hooks-claude-code<br/>@deepseek-ai/dsh-hooks-claude-code"]
   cfg --> plugin_acp_hooks_claude_code
   plugin_acp_hooks_codex["hooks-codex<br/>@deepseek-ai/dsh-hooks-codex"]
@@ -81,38 +36,19 @@ flowchart LR
 
 | Plugin id | Package / module |
 | --- | --- |
-| `deepseek-llm-api-extensions` | `@deepseek-ai/dsh-deepseek-llm-api-extensions` |
-| `session-log-deepseek` | `@deepseek-ai/dsh-session-log-deepseek` |
-| `plugin-package-inventory-deepseek` | `@deepseek-ai/dsh-plugin-package-inventory-deepseek` |
 | `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
-| `sandbox` | `@deepseek-ai/dsh-sandbox-local` |
 | `sandbox-policy` | `@deepseek-ai/dsh-sandbox-policy` |
-| `subprocess` | `@deepseek-ai/dsh-subprocess-local` |
-| `bash` | `@deepseek-ai/dsh-bash-sandbox` |
 | `approval` | `@deepseek-ai/dsh-user-approval` |
-| `acp-agent` | `@deepseek-ai/dsh-acp-demo` |
-| `token-meter` | `@deepseek-ai/dsh-token-meter` |
-| `compaction-basic` | `@deepseek-ai/dsh-compaction-basic` |
-| `session-projection` | `@deepseek-ai/dsh-session-projection` |
-| `subagent` | `@deepseek-ai/dsh-subagent` |
-| `subagent-spawn-in-process` | `@deepseek-ai/dsh-subagent-spawn-in-process` |
-| `subagent-fork-in-process` | `@deepseek-ai/dsh-subagent-fork-in-process` |
-| `tool-subagent-control` | `@deepseek-ai/dsh-tool-subagent-control` |
-| `tool-subagent-list-agents` | `@deepseek-ai/dsh-tool-subagent-control/list-agents` |
-| `tool-subagent-report` | `@deepseek-ai/dsh-tool-subagent-report` |
+| `session-persistence-jsonl` | `@deepseek-ai/dsh-session-persistence-jsonl` |
+| `acp` | `@deepseek-ai/dsh-acp` |
+| `system-prompt` | `@deepseek-ai/dsh-system-prompt` |
+| `agent-instructions` | `@deepseek-ai/dsh-agent-instructions` |
 | `tool-subagent` | `@deepseek-ai/dsh-tool-subagent` |
 | `tool-subagent-fork` | `@deepseek-ai/dsh-tool-subagent` |
-| `workflow-worker-thread` | `@deepseek-ai/dsh-workflow-worker-thread` |
-| `tool-workflow` | `@deepseek-ai/dsh-tool-workflow` |
-| `tool-ralph` | `@deepseek-ai/dsh-tool-ralph` |
-| `tool-todo` | `@deepseek-ai/dsh-tool-todo` |
-| `repeat-tool-reminder` | `@deepseek-ai/dsh-repeat-tool-reminder` |
 | `fs-sandbox` | `@deepseek-ai/dsh-fs-sandbox` |
-| `fs-observation-policy` | `@deepseek-ai/dsh-fs-observation-policy` |
-| `tool-fs` | `@deepseek-ai/dsh-tool-fs` |
 | `hooks-claude-code` | `@deepseek-ai/dsh-hooks-claude-code` |
 | `hooks-codex` | `@deepseek-ai/dsh-hooks-codex` |
 
 Source config: [`examples/acp-agent/cordis.yml`](cordis.yml).
 
-Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source.
+Maintenance mode: hybrid: the patch row list is parsed from its `cordis.yml`; the scope summary is curated.

+ 5 - 10
examples/acp-agent/cordis-tools.cordis.yml

@@ -1,12 +1,7 @@
 # Add the self-referential Cordis tools without changing the base ACP tool
 # presentation mode.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
-  config:
-    path: ./cordis.yml
-    patches:
-      - insert:
-          - id: cordis-host-runner
-            name: '@deepseek-ai/dsh-cordis-host-runner'
-          - id: tool-cordis
-            name: '@deepseek-ai/dsh-tool-cordis'
+- insert:
+    - id: cordis-host-runner
+      name: '@deepseek-ai/dsh-cordis-host-runner'
+    - id: tool-cordis
+      name: '@deepseek-ai/dsh-tool-cordis'

+ 42 - 59
examples/acp-agent/cordis.snapshot.yml

@@ -1,61 +1,44 @@
-# Keyless replay includes the live `cordis.yml`, disables the key-requiring
-# DeepSeek adapter, and inserts `llm-replay` to serve recorded JSONL without a key
-# or network; every other app entry remains shared. It also restates the acp-agent
-# config to re-pin `deepseek-v4-flash`: `cordis.yml` ships `deepseek-v4-pro`, but the
-# recorded corpus (request headers, provenance, system prompt) was captured on flash,
-# so replay holds the recorded model to stay reproducible without a re-record. A config
-# patch replaces the whole app config, so the base fields are restated verbatim.
-# With `DSH_SNAPSHOT=replay`, the app bin reads `DSH_SNAPSHOT_FILE` and optional
-# `DSH_SNAPSHOT_OVERRIDE` from the harness. The one-shot patch applies at include
-# load time, and stdout remains reserved for ACP JSON-RPC.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+# Replay-only patch layered after `cordis.yml`: replace the network adapter
+# with recorded JSONL, pin the recorded top-level model, and keep persistence
+# raw for fixture harvesting.
+
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  disabled: true
+
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
+  config:
+    provider: deepseek-official
+    model: deepseek-v4-flash
+
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
   config:
-    path: ./cordis.yml
-    patches:
-      # `name` asserts the target: a mismatch skips the patch and warns only when
-      # a logger exists. A renamed id leaves a stale adapter entry, but replay still
-      # short-circuits through `llm-replay`.
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          # Replay fixtures are raw JSONL; the whole-config patch must restate
-          # the compression choice or the default zstd frames hide the logs
-          # from the harness's harvest.
-          persistenceCompression: none
-          workspaceContext:
-            maxBytes: 65536
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? dshHomePath('sessions')
+    compression: none
+
+- id: sandbox
+  name: '@deepseek-ai/dsh-sandbox-local'
+  config:
+    runnerCommand:
+      - bash
+      - -c
+      - while [ "$1" != "--" ]; do shift; done; shift; exec "$@"
+      - passthrough-runner
+    runnerFailureSignatures:
+      - 'passthrough-runner: profile rejected'
+
+- insert:
+    - id: llm-replay
+      name: '@deepseek-ai/dsh-llm-replay'
+      config:
+        providers:
+          - id: deepseek-official
+            name: DeepSeek
+            models:
+              - id: deepseek-v4-flash
+              - id: deepseek-v4-pro
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - id: sandbox
-        name: '@deepseek-ai/dsh-sandbox-local'
-        config:
-          runnerCommand:
-            - bash
-            - -c
-            - while [ "$1" != "--" ]; do shift; done; shift; exec "$@"
-            - passthrough-runner
-          runnerFailureSignatures:
-            - 'passthrough-runner: profile rejected'
-      - insert:
-          - id: llm-replay
-            name: '@deepseek-ai/dsh-llm-replay'
-            config:
-              providers:
-                - id: deepseek-official
-                  name: DeepSeek
-                  models:
-                    - id: deepseek-v4-flash
-                    - id: deepseek-v4-pro
-          # Authored scenarios can fence a later parent action on the real
-          # child settlement edge without exposing a test-only model tool.
-          - id: subagent-settlement-marker
-            name: './tests/fixtures/subagent-settlement-marker.ts'
+    - id: subagent-settlement-marker
+      name: './tests/fixtures/subagent-settlement-marker.ts'

+ 27 - 148
examples/acp-agent/cordis.yml

@@ -1,20 +1,7 @@
-# ACP automation server and backend snapshot-record composition. With
-# `DSH_SNAPSHOT=record`, the app bin runs the real DeepSeek adapter and the
-# harness harvests its persisted log. The bin loads the gitignored root `.env`
-# before this config. This tree has no stdout logger or HMR because stdout
-# carries ACP JSON-RPC.
+# ACP demo and snapshot-record patch over the shipped `acp` profile. The dsh
+# launcher owns environment loading, plugin resolution, and process shutdown;
+# stdout remains reserved for ACP JSON-RPC.
 
-- id: deepseek-llm-api-extensions
-  name: '@deepseek-ai/dsh-deepseek-llm-api-extensions'
-
-- id: session-log-deepseek
-  name: '@deepseek-ai/dsh-session-log-deepseek'
-
-- id: plugin-package-inventory-deepseek
-  name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
-
-# The DeepSeek adapter. Shipped default: full thinking at max effort on every
-# request; exact-model resolution materializes request defaults before logging.
 - id: llm-deepseek
   name: '@deepseek-ai/dsh-llm-deepseek'
   config:
@@ -26,103 +13,41 @@
       - id: deepseek-v4-flash-vision-exp
         inputModalities: [text, image]
 
-# The default composition confines bash AND the filesystem tools to the
-# workspace and asks before a wider retry. Snapshot runs select
-# danger-full-access so the established scenarios remain runner-independent;
-# DSH_PERMISSION_MODE provides the same explicit deployment/test override
-# outside the snapshot harness. The sandbox default + fallback root live on
-# ctx.sandboxPolicy; agent calls resolve both families against the session cwd.
-- 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 ?? (process.env.DSH_SNAPSHOT === undefined ? 'workspace-write' : 'danger-full-access')"
     workspaceRoot: !!js process.cwd()
 
-# Managed child-process groups for the bash executor (spawn/kill/output plumbing).
-- id: subprocess
-  name: '@deepseek-ai/dsh-subprocess-local'
-
-- id: bash
-  name: '@deepseek-ai/dsh-bash-sandbox'
-  config:
-    timeoutMs: 60000
-
 - id: approval
   name: '@deepseek-ai/dsh-user-approval'
   config:
     policy: !!js "(process.env.DSH_PERMISSION_MODE ?? (process.env.DSH_SNAPSHOT === undefined ? 'workspace-write' : 'danger-full-access')) === 'danger-full-access' ? 'never' : 'ask'"
 
-# The ACP automation app: agent spine + JSONL persistence + protocol bridge.
-# Persistence root: $DSH_SNAPSHOT_SESSIONS_ROOT when the snapshot harness sets it
-# (so it can harvest / isolate the log), else ./.sessions for the demo.
-# Snapshot modes use raw JSONL fixtures; ordinary runs keep the compressed default.
-- id: acp-agent
-  name: '@deepseek-ai/dsh-acp-demo'
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? dshHomePath('sessions')
+    compression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
+
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
   config:
     provider: deepseek-official
     model: deepseek-v4-pro
-    persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-    persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
-    workspaceContext:
-      maxBytes: 65536
-    # Keep the persona to identity and behavior; tool plugins own tool guidance.
-    # The loop resolves {{model}} and each ACP session's client-supplied {{cwd}}.
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
     persona: |
       You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
 
       Verify your work by running the code or tests. Keep answers brief and factual.
 
-# Replay-aware request pressure; the routed adapter supplies model capacity.
-- id: token-meter
-  name: '@deepseek-ai/dsh-token-meter'
-
-# Summarize an older range after measured pressure or a canonical provider overflow.
-# Ratios scale against the routed model's context window.
-- id: compaction-basic
-  name: '@deepseek-ai/dsh-compaction-basic'
-  config:
-    thresholdRatio: 0.8
-    retainRatio: 0.08
-    maxTokens: 8192
-    compactionRetries: 1
-
-# Projection registry: subagent catalog identity (mode/label) folds through
-# its registered units; the catalog surfaces (`list_agents`, subagent listing)
-# fail loud without the capability.
-- id: session-projection
-  name: '@deepseek-ai/dsh-session-projection'
-
-# Expose fresh-child `spawn` and completed-prefix `fork` through separate tool
-# names so multi-child scenarios exercise both transports. These leaves follow
-# the app because it provides `ctx.agents` and `ctx.tools`.
-- id: subagent
-  name: '@deepseek-ai/dsh-subagent'
-
-- id: subagent-spawn-in-process
-  name: '@deepseek-ai/dsh-subagent-spawn-in-process'
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
   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 control package registers the global `send_message`; its
-# list plugin registers `list_agents`, served through the sessionProjections
-# registry mounted above. `report` is installed only in continuable child scopes.
-- 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-report
-  name: '@deepseek-ai/dsh-tool-subagent-report'
+    maxBytes: 65536
 
 - id: tool-subagent
   name: '@deepseek-ai/dsh-tool-subagent'
@@ -132,10 +57,6 @@
     backgroundMode: continuable
     maxDepth: 1
 
-# Fork stays one-shot because a continuable child's `report` tool and prompt
-# section precede the inherited history a fork reuses; `run_in_background` is off
-# as an explicit foreground-only choice even though agent-spine-demo mounts the
-# generic Job runtime. See .agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.md.
 - id: tool-subagent-fork
   name: '@deepseek-ai/dsh-tool-subagent'
   config:
@@ -145,60 +66,18 @@
     enableRunInBackground: false
     maxDepth: 1
 
-
-# The worker-thread workflow engine fans a model-written JavaScript script's
-# `agent()` calls out through the spawn backend; the adjacent tool exposes it to the model.
-- id: workflow-worker-thread
-  name: '@deepseek-ai/dsh-workflow-worker-thread'
-  config:
-    provider: spawn
-
-- id: tool-workflow
-  name: '@deepseek-ai/dsh-tool-workflow'
-
-- id: tool-ralph
-  name: '@deepseek-ai/dsh-tool-ralph'
-# `todo_write` replaces the logged whole list for later model requests.
-- id: tool-todo
-  name: '@deepseek-ai/dsh-tool-todo'
-  config:
-    allowParallelInProgress: true
-
-# Identical repeat calls trigger advisory context, never a block, at the default
-# thresholds [3, 5, 8]. Only the repeat-tool-reminder snapshot scenario reaches them.
-- id: repeat-tool-reminder
-  name: '@deepseek-ai/dsh-repeat-tool-reminder'
-
-# The filesystem stack rides the SAME sandbox policy as bash: dsh-fs-sandbox
-# replaces dsh-fs-local behind ctx.fs and fences write/edit by the effective
-# mode (read-only denies, workspace-write contains to the workspace + temp
-# roots, danger-full-access passes through), so read/write/edit are available
-# under every mode. fs-observation-policy (read-before-edit) composes orthogonally on top.
 - id: fs-sandbox
   name: '@deepseek-ai/dsh-fs-sandbox'
   config:
     cwd: !!js process.cwd()
 
-- id: fs-observation-policy
-  name: '@deepseek-ai/dsh-fs-observation-policy'
+- insert:
+    - id: hooks-claude-code
+      name: '@deepseek-ai/dsh-hooks-claude-code'
+      config:
+        configPath: ./hooks.json
 
-- id: tool-fs
-  name: '@deepseek-ai/dsh-tool-fs'
-
-# `configPath` is read once at load and resolves from the server launch cwd, not
-# `session/new.cwd`; one `hooks.json` therefore applies to every session and a
-# project-local file is not discovered. Missing config registers nothing. Hook
-# commands still run in the session cwd. Warnings use `ctx.logger`, never stdout;
-# see packages/hooks/hooks-claude-code/README.md for the deferred per-session design.
-- id: hooks-claude-code
-  name: '@deepseek-ai/dsh-hooks-claude-code'
-  config:
-    configPath: ./hooks.json
-
-# Codex uses its own `codex-hooks.json` and snake_case five-event dialect; it
-# cannot share Claude's file. It has the same process-level, read-once, missing-is-no-op,
-# logger-only contract. Shipping both bridges lets a scenario seed and exercise either dialect.
-- id: hooks-codex
-  name: '@deepseek-ai/dsh-hooks-codex'
-  config:
-    configPath: ./codex-hooks.json
+    - id: hooks-codex
+      name: '@deepseek-ai/dsh-hooks-codex'
+      config:
+        configPath: ./codex-hooks.json

+ 56 - 49
examples/acp-agent/depth-two.cordis.snapshot.yml

@@ -1,53 +1,60 @@
 # Keyless counterpart to depth-two.cordis.yml: apply the depth patch and replace
 # the live adapter with per-session replay.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  disabled: true
+
+- id: sandbox
+  name: '@deepseek-ai/dsh-sandbox-local'
+  config:
+    runnerCommand:
+      - bash
+      - -c
+      - while [ "$1" != "--" ]; do shift; done; shift; exec "$@"
+      - passthrough-runner
+    runnerFailureSignatures:
+      - 'passthrough-runner: profile rejected'
+
+- id: tool-subagent
+  name: '@deepseek-ai/dsh-tool-subagent'
+  config:
+    provider: spawn
+    toolName: subagent
+    backgroundMode: continuable
+    maxDepth: 2
+# Re-pin the recorded flash model for this scenario's corpus.
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
+  config:
+    provider: deepseek-official
+    model: deepseek-v4-flash
+
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - id: sandbox
-        name: '@deepseek-ai/dsh-sandbox-local'
-        config:
-          runnerCommand:
-            - bash
-            - -c
-            - while [ "$1" != "--" ]; do shift; done; shift; exec "$@"
-            - passthrough-runner
-          runnerFailureSignatures:
-            - 'passthrough-runner: profile rejected'
-      - id: tool-subagent
-        name: '@deepseek-ai/dsh-tool-subagent'
-        config:
-          provider: spawn
-          toolName: subagent
-          backgroundMode: continuable
-          maxDepth: 2
-      # Re-pin the recorded model: cordis.yml ships deepseek-v4-pro, but this
-      # scenario's corpus was captured on flash. A config patch replaces the
-      # whole app config, so the base fields are restated verbatim.
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: none
-          workspaceContext:
-            maxBytes: 65536
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: none
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
+  config:
+    maxBytes: 65536
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: llm-replay
-            name: '@deepseek-ai/dsh-llm-replay'
-            config:
-              providers:
-                - id: deepseek-official
-                  name: DeepSeek
-                  models:
-                    - id: deepseek-v4-flash
-                    - id: deepseek-v4-pro
+- insert:
+    - id: llm-replay
+      name: '@deepseek-ai/dsh-llm-replay'
+      config:
+        providers:
+          - id: deepseek-official
+            name: DeepSeek
+            models:
+              - id: deepseek-v4-flash
+              - id: deepseek-v4-pro

+ 6 - 11
examples/acp-agent/depth-two.cordis.yml

@@ -1,14 +1,9 @@
 # Depth-limit snapshot overlay: keep the default composition and allow two
 # generations of spawn children before runtime enforcement rejects another.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+- id: tool-subagent
+  name: '@deepseek-ai/dsh-tool-subagent'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: tool-subagent
-        name: '@deepseek-ai/dsh-tool-subagent'
-        config:
-          provider: spawn
-          toolName: subagent
-          backgroundMode: continuable
-          maxDepth: 2
+    provider: spawn
+    toolName: subagent
+    backgroundMode: continuable
+    maxDepth: 2

+ 51 - 44
examples/acp-agent/fs.cordis.snapshot.yml

@@ -1,45 +1,52 @@
-# Keyless filesystem snapshots apply the spill and replay overlays directly
-# because include patches cannot target entries behind a nested include. The
-# sandboxed filesystem stack already lives in the base cordis.yml. This file also
-# re-pins the acp-agent model to `deepseek-v4-flash`: `cordis.yml` ships
-# `deepseek-v4-pro`, but the recorded corpus was captured on flash, and a config
-# patch replaces the whole app config, so the base fields are restated verbatim.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+# Keyless filesystem snapshots combine spill settings with replay. The
+# sandboxed filesystem stack already lives in `dsh-base`; the ACP row re-pins
+# `deepseek-v4-flash` for the recorded corpus.
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  disabled: true
+
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
+  config:
+    provider: deepseek-official
+    model: deepseek-v4-flash
+
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: none
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
+  config:
+    maxBytes: 65536
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
+
+- insert:
+    - id: llm-replay
+      name: '@deepseek-ai/dsh-llm-replay'
+      config:
+        providers:
+          - id: deepseek-official
+            name: DeepSeek
+            models:
+              - id: deepseek-v4-flash
+              - id: deepseek-v4-pro
+
+- id: spill-local
+  name: '@deepseek-ai/dsh-spill-local'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SPILL_ROOT ?? './.spill'
+
+- id: spill-policy
+  name: '@deepseek-ai/dsh-spill-policy'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: none
-          workspaceContext:
-            maxBytes: 65536
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
-
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: spill-local
-            name: '@deepseek-ai/dsh-spill-local'
-            config:
-              root: !!js process.env.DSH_SNAPSHOT_SPILL_ROOT ?? './.spill'
-          - id: spill-policy
-            name: '@deepseek-ai/dsh-spill-policy'
-            config:
-              maxInlineBytes: 800
-          - id: llm-replay
-            name: '@deepseek-ai/dsh-llm-replay'
-            config:
-              providers:
-                - id: deepseek-official
-                  name: DeepSeek
-                  models:
-                    - id: deepseek-v4-flash
-                    - id: deepseek-v4-pro
+    maxInlineBytes: 800

+ 10 - 14
examples/acp-agent/fs.cordis.yml

@@ -1,17 +1,13 @@
 # Filesystem-scenario overlay: the sandboxed filesystem stack already lives in
-# the base cordis.yml, so this overlay adds only the local tool-result spill
+# `dsh-base`, so this patch changes only the local tool-result spill
 # storage those scenarios exercise.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+
+- id: spill-local
+  name: '@deepseek-ai/dsh-spill-local'
   config:
-    path: ./cordis.yml
-    patches:
-      - insert:
-          - id: spill-local
-            name: '@deepseek-ai/dsh-spill-local'
-            config:
-              root: !!js process.env.DSH_SNAPSHOT_SPILL_ROOT ?? './.spill'
-          - id: spill-policy
-            name: '@deepseek-ai/dsh-spill-policy'
-            config:
-              maxInlineBytes: !!js process.env.DSH_SNAPSHOT && 800 || 50000
+    root: !!js process.env.DSH_SNAPSHOT_SPILL_ROOT ?? './.spill'
+
+- id: spill-policy
+  name: '@deepseek-ai/dsh-spill-policy'
+  config:
+    maxInlineBytes: !!js process.env.DSH_SNAPSHOT && 800 || 50000

+ 42 - 33
examples/acp-agent/image-text-route.cordis.snapshot.yml

@@ -2,38 +2,47 @@
 # image.cordis.snapshot.yml overlay except the replay catalog leaves flash
 # text-only, so the strict read_image gate refuses and no image ever enters
 # the durable log.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  disabled: true
+
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
+  config:
+    provider: deepseek-official
+    model: deepseek-v4-flash
+
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: none
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: none
-          workspaceContext:
-            maxBytes: 65536
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+    maxBytes: 65536
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
+
+- insert:
+    - id: llm-replay
+      name: '@deepseek-ai/dsh-llm-replay'
+      config:
+        providers:
+          - id: deepseek-official
+            name: DeepSeek
+            models:
+              - id: deepseek-v4-flash
+                inputModalities: [text]
+              - id: deepseek-v4-pro
+                inputModalities: [text]
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: attachment-local
-            name: '@deepseek-ai/dsh-attachment-local'
-          - id: llm-replay
-            name: '@deepseek-ai/dsh-llm-replay'
-            config:
-              providers:
-                - id: deepseek-official
-                  name: DeepSeek
-                  models:
-                    - id: deepseek-v4-flash
-                      inputModalities: [text]
-                    - id: deepseek-v4-pro
-                      inputModalities: [text]
+- id: attachment-local
+  name: '@deepseek-ai/dsh-attachment-local'

+ 27 - 23
examples/acp-agent/image-text-route.cordis.yml

@@ -1,27 +1,31 @@
 # Text-route image overlay: the attachment store registers read_image, but the
 # strict execution gate refuses on a route that does not declare image input,
-# so a text-only deployment keeps its durable history text-clean. The app
-# config is restated to re-pin `deepseek-v4-flash` (base ships pro; the
-# authored fixture and the pinned header class are flash), because a config
-# patch replaces the whole app config.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+# so a text-only deployment keeps its durable history text-clean. The ACP row
+# re-pins `deepseek-v4-flash` for the authored fixture and header class.
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: none
-          workspaceContext:
-            maxBytes: 65536
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+    provider: deepseek-official
+    model: deepseek-v4-flash
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: attachment-local
-            name: '@deepseek-ai/dsh-attachment-local'
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: none
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
+  config:
+    maxBytes: 65536
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
+
+- id: attachment-local
+  name: '@deepseek-ai/dsh-attachment-local'

+ 46 - 39
examples/acp-agent/image.cordis.snapshot.yml

@@ -1,43 +1,50 @@
-# Keyless replay for the read-image success scenario. Include patches cannot
-# target entries behind a nested include, so this restates the replay overlay
-# directly over the base cordis.yml (the fs.cordis.snapshot.yml pattern) and
-# re-pins the recorded vision model. The replay catalog declares image input,
+# Keyless replay for the read-image success scenario. This profile patch swaps
+# the adapter and re-pins the recorded vision model. The replay catalog declares image input,
 # so the strict read_image gate accepts the route and the tool result carries
 # the durable image block.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  disabled: true
+
+- id: acp
+  name: '@deepseek-ai/dsh-acp'
+  config:
+    provider: deepseek-official
+    model: deepseek-v4-flash-vision-exp
+
+- id: session-persistence-jsonl
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    compression: none
+
+- id: agent-instructions
+  name: '@deepseek-ai/dsh-agent-instructions'
   config:
-    path: ./cordis.yml
-    patches:
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - id: acp-agent
-        name: '@deepseek-ai/dsh-acp-demo'
-        config:
-          provider: deepseek-official
-          model: deepseek-v4-flash-vision-exp
-          persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
-          persistenceCompression: none
-          workspaceContext:
-            maxBytes: 65536
-          persona: |
-            You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+    maxBytes: 65536
+
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+  config:
+    persona: |
+      You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
+
+      Verify your work by running the code or tests. Keep answers brief and factual.
+
+- insert:
+    - id: llm-replay
+      name: '@deepseek-ai/dsh-llm-replay'
+      config:
+        providers:
+          - id: deepseek-official
+            name: DeepSeek
+            models:
+              - id: deepseek-v4-flash
+                inputModalities: [text]
+              - id: deepseek-v4-pro
+                inputModalities: [text]
+              - id: deepseek-v4-flash-vision-exp
+                inputModalities: [text, image]
 
-            Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: attachment-local
-            name: '@deepseek-ai/dsh-attachment-local'
-          - id: llm-replay
-            name: '@deepseek-ai/dsh-llm-replay'
-            config:
-              providers:
-                - id: deepseek-official
-                  name: DeepSeek
-                  models:
-                    - id: deepseek-v4-flash
-                      inputModalities: [text]
-                    - id: deepseek-v4-pro
-                      inputModalities: [text]
-                    - id: deepseek-v4-flash-vision-exp
-                      inputModalities: [text, image]
+- id: attachment-local
+  name: '@deepseek-ai/dsh-attachment-local'

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác