Переглянути джерело

Merge pull request #4119 from deepseek-harness/worktree/workflow-sandbox-ptc

fix(workflow): execute orchestration through sandboxed PTC
Tianyi Cui 1 тиждень тому
батько
коміт
2214891606
100 змінених файлів з 518 додано та 293 видалено
  1. 2 2
      .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml
  2. 1 1
      .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md
  3. 1 1
      .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md
  4. 2 2
      .agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml
  5. 15 5
      .agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md
  6. 5 5
      .agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md
  7. 2 2
      .agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.i18n.yaml
  8. 2 2
      .agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.md
  9. 2 2
      .agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.zh.md
  10. 2 2
      .agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.i18n.yaml
  11. 1 1
      .agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.md
  12. 1 1
      .agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.zh.md
  13. 6 0
      .agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.i18n.yaml
  14. 41 0
      .agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.md
  15. 41 0
      .agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.zh.md
  16. 2 2
      .agents/notes/implemented/feature/2026-07-05-dynamic-workflows.i18n.yaml
  17. 10 16
      .agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md
  18. 10 16
      .agents/notes/implemented/feature/2026-07-05-dynamic-workflows.zh.md
  19. 2 2
      .agents/notes/implemented/feature/2026-07-16-harness-level-loop.i18n.yaml
  20. 1 1
      .agents/notes/implemented/feature/2026-07-16-harness-level-loop.md
  21. 1 1
      .agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md
  22. 2 2
      .agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.i18n.yaml
  23. 4 4
      .agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.md
  24. 4 4
      .agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.zh.md
  25. 2 2
      .agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.i18n.yaml
  26. 1 1
      .agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md
  27. 1 1
      .agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.zh.md
  28. 4 1
      .github/workflows/ci.yml
  29. 6 3
      apps/cli/composition.md
  30. 1 1
      apps/cli/package.json
  31. 2 2
      docs/capability-seams.i18n.yaml
  32. 5 4
      docs/capability-seams.md
  33. 5 4
      docs/capability-seams.zh.md
  34. 2 2
      docs/config-catalog.i18n.yaml
  35. 6 12
      docs/config-catalog.md
  36. 7 13
      docs/config-catalog.zh.md
  37. 2 2
      docs/cookbook/extension-cookbook.i18n.yaml
  38. 1 1
      docs/cookbook/extension-cookbook.md
  39. 1 1
      docs/cookbook/extension-cookbook.zh.md
  40. 2 2
      docs/module-graph.i18n.yaml
  41. 11 8
      docs/module-graph.md
  42. 11 8
      docs/module-graph.zh.md
  43. 2 2
      docs/subsystems/ptc-runtime.i18n.yaml
  44. 9 6
      docs/subsystems/ptc-runtime.md
  45. 9 6
      docs/subsystems/ptc-runtime.zh.md
  46. 2 2
      docs/subsystems/workflow.i18n.yaml
  47. 8 8
      docs/subsystems/workflow.md
  48. 8 8
      docs/subsystems/workflow.zh.md
  49. 2 0
      package.json
  50. 2 2
      packages/README.i18n.yaml
  51. 1 1
      packages/README.md
  52. 1 1
      packages/README.zh.md
  53. 5 2
      packages/bundle/base/cordis.patch.yml
  54. 3 2
      packages/bundle/base/package.json
  55. 2 2
      packages/bundle/headless/README.i18n.yaml
  56. 1 1
      packages/bundle/headless/README.md
  57. 1 1
      packages/bundle/headless/README.zh.md
  58. 0 4
      packages/bundle/headless/cordis.patch.yml
  59. 0 1
      packages/bundle/headless/package.json
  60. 1 4
      packages/bundle/web-app/cordis.patch.yml
  61. 0 1
      packages/bundle/web-app/package.json
  62. 1 1
      packages/core/tools/tests/ptc.spec.ts
  63. 2 2
      packages/experimental/ptc-runtime-python/README.i18n.yaml
  64. 1 0
      packages/experimental/ptc-runtime-python/README.md
  65. 1 0
      packages/experimental/ptc-runtime-python/README.zh.md
  66. 2 0
      packages/experimental/ptc-runtime-python/tests/runtime.spec.ts
  67. 4 4
      packages/extensions/tool-cordis/src/api-catalog.ts
  68. 1 2
      packages/host/directory-picker-native/tests/built-worker.e2e.ts
  69. 2 2
      packages/host/directory-picker-native/tsdown.config.ts
  70. 2 2
      packages/preset/agent-presets/presets/cordis/agent.cordis.yml
  71. 2 2
      packages/preset/agent-presets/presets/cordis/skills/editing-cordis-compositions/SKILL.md
  72. 2 2
      packages/preset/agent-presets/presets/ptc/agent.cordis.yml
  73. 2 2
      packages/preset/agent-presets/presets/standard/agent.cordis.yml
  74. 2 2
      packages/preset/agent-presets/tests/shipped-root.spec.ts
  75. 2 2
      packages/ptc-runtime/ptc-runtime-node/README.i18n.yaml
  76. 5 5
      packages/ptc-runtime/ptc-runtime-node/README.md
  77. 5 5
      packages/ptc-runtime/ptc-runtime-node/README.zh.md
  78. 6 5
      packages/ptc-runtime/ptc-runtime-node/src/index.ts
  79. 20 6
      packages/ptc-runtime/ptc-runtime-node/src/process.ts
  80. 26 0
      packages/ptc-runtime/ptc-runtime-node/tests/host-failures.spec.ts
  81. 69 0
      packages/ptc-runtime/ptc-runtime-node/tests/process-main.spec.ts
  82. 1 0
      packages/ptc-runtime/ptc-runtime-node/tests/process.spec.ts
  83. 5 4
      packages/ptc-runtime/ptc-runtime-node/tests/runtime.spec.ts
  84. 2 2
      packages/ptc-runtime/ptc-runtime/README.i18n.yaml
  85. 5 5
      packages/ptc-runtime/ptc-runtime/README.md
  86. 5 5
      packages/ptc-runtime/ptc-runtime/README.zh.md
  87. 1 1
      packages/ptc-runtime/ptc-runtime/src/index.ts
  88. 8 5
      packages/ptc-runtime/ptc-runtime/src/types.ts
  89. 2 2
      packages/workflow/README.i18n.yaml
  90. 3 3
      packages/workflow/README.md
  91. 3 3
      packages/workflow/README.zh.md
  92. 2 2
      packages/workflow/tool-ralph/README.i18n.yaml
  93. 2 2
      packages/workflow/tool-ralph/README.md
  94. 2 2
      packages/workflow/tool-ralph/README.zh.md
  95. 1 1
      packages/workflow/tool-ralph/package.json
  96. 25 9
      packages/workflow/tool-ralph/tests/integration.spec.ts
  97. 2 2
      packages/workflow/tool-workflow/README.i18n.yaml
  98. 2 2
      packages/workflow/tool-workflow/README.md
  99. 2 2
      packages/workflow/tool-workflow/README.zh.md
  100. 1 1
      packages/workflow/tool-workflow/package.json

+ 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: aba6f09a3d470abdf51683a1e7efd15803ff01d2
-2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: ffa9071d738fb658f3423d2165e4fc4e004d6a23
+2026-07-10-single-file-executable-sdk-runtime-distribution.md: 665364e6d39a78f7ac497198f18ed9aa160a4cbd
+2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 8b39df71c615dc59f3ef1bf622a5736a70b085b3

Різницю між файлами не показано, бо вона завелика
+ 1 - 1
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md


Різницю між файлами не показано, бо вона завелика
+ 1 - 1
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md


+ 2 - 2
.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.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-12-agent-scope-runtime-design.md
-2026-07-12-agent-scope-runtime-design.md: ca300d4eeeab878a4e41b8e68a669be418617181
-2026-07-12-agent-scope-runtime-design.zh.md: 870690d6ace9fefd859557a2e73e88b9b1da6206
+2026-07-12-agent-scope-runtime-design.md: cd916ab1bf9f4bb7a02cb5c6a8458630b757e09d
+2026-07-12-agent-scope-runtime-design.zh.md: a0db51d7129d744b96946782a920e99b0cd7b518

+ 15 - 5
.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md

@@ -60,6 +60,8 @@ Product helpers therefore construct the carrier and pass the domain subject sepa
 
 A Cordis waterfall is middleware-style dispatch. Each listener receives `next()`: calling it delegates to the remaining listeners and base operation, while returning without it short-circuits or replaces the downstream result. Waterfalls power prompt assembly and tool policy; ordinary emit events notify synchronously, and parallel events await all listeners without a veto result.
 
+<a id="scope-routing-one-opaque-key-selects-one-layer"></a>
+
 ## Scope routing: one opaque key selects one layer
 
 The scope package implements the smallest object needed for Cordis routing. Its carrier holds only a composed service filter and scope predicate, while the package records the opaque key privately and exposes the scope fiber's quiescent disposer separately.
@@ -160,6 +162,8 @@ Every teardown request joins one memoized path. The order is:
 
 This order lets final agent and session events use the matching scoped listeners and keeps persistence observers attached through the final flush. Scope disposal comes last because registration revocation is the externally visible lifetime boundary.
 
+<a id="session-append-materialize-validate-commit-notify"></a>
+
 ## Session append: materialize, validate, commit, notify
 
 Session events cross a durable boundary, so append owns their data. The rest of the algorithm uses one attached entry and one commit point.
@@ -232,6 +236,8 @@ This is a trusted same-process extension point, not an authority boundary. A lis
 
 Scope solves the real isolation problem directly. Structured-output contributions register in the child's exact scope, while PTC mode derives its transport and SDK from the same resolved tool view. A second named-protection system would need another ownership and collision rule across arbitrary schema providers—including providers that intentionally contribute duplicate names—without creating a new trust boundary.
 
+<a id="structured-output-commits-only-authoritative-outcomes"></a>
+
 ### Structured output commits only authoritative outcomes
 
 Structured output combines child-scoped composition with a two-phase execution commit. The child registers its `structured_output` tool and instruction before publication; a trusted assembly listener may transform those ordinary contributions and is responsible for preserving the protocol if the child is expected to complete. The tool body validates a candidate and stages it by the current `ToolExecution`, but successful capture is decided only by immutable `tools/result` observations.
@@ -244,6 +250,8 @@ Once a value is pending or committed, a scoped monotonic guard denies later tool
 
 Pure PTC mode's registry contribution omits `structured_output` from native wire schemas and exposes it through the generated SDK. The assembly waterfall may deliberately change that presentation; execution still validates against the child-scoped definition, and the listener owns the consistency of any alternate model-visible route it creates.
 
+<a id="three-execution-boundaries-are-deliberately-one-way"></a>
+
 ### Three execution boundaries are deliberately one-way
 
 Prompt assembly is intentionally cooperative, but three execution facts need one-way settlement after their extensible stages:
@@ -294,19 +302,21 @@ Start resolves only after `initialize` and `newSession` succeed. Abort, spawn fa
 
 Worker and child-process bridges need more state than same-process registries because messages, process death, and cleanup can settle independently. Their state is organized around those real facts rather than duplicate cancellation protocols.
 
+<a id="workflow-children-are-pending-starts-or-published-records"></a>
+
 ### Workflow children are pending starts or published records
 
 The workflow host keeps pending provider-start promises and published child records. A child moves from pending to published only when async `SubagentRuntime.start()` fulfills; rejected starts clean their partial provider work and produce no child lifecycle pair.
 
-One host-owned AbortController supplies the required signal to pending and live children. Closing workflow admission aborts that signal, so there is no duplicate `ChildCancel` worker RPC or explicit host-side `run.cancel()` fanout. Quiescence waits for both pending starts and published child disposal.
+One host-owned AbortController supplies the required signal to pending and live children. Closing workflow admission aborts that signal; quiescence waits for both pending starts and published child disposal. [Workflow sandbox reuse](2026-09-13-workflow-ptc-sandbox-reuse.md) owns PTC process cancellation and the absence of a separate workflow cleanup timer.
 
-The worker boundary still serializes requests and outcomes. The host retains first-terminal-outcome arbitration, exact child accounting, worker-death handling, grace termination, late/duplicate message rejection, and bounded cleanup because result receipt, worker exit, and child quiescence are genuinely independent facts.
+PTC serializes requests and outcomes and owns process termination. The workflow adapter retains terminal-outcome arbitration and child ownership because program settlement, process exit and child quiescence remain independent facts.
 
 ### Terminal result and physical cleanup remain separate
 
-The workflow result records the first accepted terminal outcome according to the public precedence rules. Cleanup can continue after that result is chosen: live children still need disposal, a worker still needs termination, and a slow external backend may outlive the configured grace bound.
+The workflow result records the first accepted terminal outcome according to the public precedence rules. Choosing that outcome does not release resources: the PTC process and live children still need cleanup, and child disposal must fulfill its provider contract.
 
-Public disposal claims its memoized promise before invoking callbacks. Worker death closes admission before processing any queued late child request, synthesizes missing lifecycle ends, and starts child/process cleanup without rewriting an outcome already claimed.
+Public disposal joins one cleanup operation. Run settlement closes child admission, synthesizes missing lifecycle ends and cleans up children without rewriting an outcome already claimed.
 
 ### ACP prompt settlement does not depend on update delivery
 
@@ -334,7 +344,7 @@ The plugin does not police trusted setup by scanning registries or reject prompt
 
 The event catalog, service catalog, producer/consumer matrix, configuration catalog, module graph, tool catalog, type-equivalence blocks, and scoped-event resolver map are generated or freshness-gated from source. The [TypeScript semantic-gates Agent Note](../../archived/process/2026-07-14-typescript-program-backed-semantic-gates.md) owns Program construction, semantic event discovery, and resolver-generation rules.
 
-Behavioral tests pin scoped routing and disposal, final-entry collision cleanup, publication rollback, ordered quiescence, durable pre/post-commit behavior, live tool filtering across presentation and execution, cooperative prompt assembly, structured-output commit in native and PTC mode, async subagent startup and signal cancellation, worker terminal arbitration, ACP settlement, and process teardown.
+Behavioral tests pin scoped routing and disposal, final-entry collision cleanup, publication rollback, ordered quiescence, durable pre/post-commit behavior, live tool filtering across presentation and execution, cooperative prompt assembly, structured-output commit in native and PTC mode, async subagent startup and signal cancellation, workflow terminal arbitration, ACP settlement, and process teardown.
 
 ## Alternatives considered
 

+ 5 - 5
.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md

@@ -308,15 +308,15 @@ Worker 和子进程桥接比同进程注册表需要更多状态,因为消息
 
 工作流宿主保持待定的提供方 start promise 和已发布的子级记录。子级仅在异步 `SubagentRuntime.start()` 兑现时才从待定变为已发布;被拒绝的 start 清理其部分提供方工作且不产生子级生命周期对。
 
-一个宿主拥有的 AbortController 向待定和活跃子级提供必需的 signal。关闭工作流准入中止该 signal,因此没有重复的 `ChildCancel` worker RPC 或显式的宿主侧 `run.cancel()` 扇出。完全停稳需要等待待定 start 和已发布子级 dispose 两者。
+一个宿主拥有的 AbortController 向待定和活跃子级提供必需的 signal。关闭工作流准入中止该 signal完全停稳需要等待待定 start 和已发布子级 dispose 两者。[工作流沙箱复用](2026-09-13-workflow-ptc-sandbox-reuse.zh.md)负责 PTC 进程取消和不另设工作流清理定时器的规则。
 
-Worker 边界仍然序列化请求和结果。宿主保留首个终端结果仲裁、精确的子级计数、worker 死亡处理、优雅终止、迟到/重复消息拒绝和有界清理,因为结果接收、worker 退出和子级完全停稳是真正独立的事实。
+PTC 序列化请求和结果并负责进程终止。工作流适配器保留终态结果仲裁和子级归属,因为程序结算、进程退出和子级完全停稳仍是独立事实。
 
 ### 终端结果与物理清理保持分离
 
-工作流结果按公开优先级规则记录首个被接受的终端结果。该结果选定后清理可以继续:活跃子级仍需 dispose,worker 仍需终止,慢速外部后端可能超出配置的优雅期限
+工作流结果按公开优先级规则记录首个被接受的终端结果。选定结果不会释放资源:PTC 进程和活跃子级仍需清理,子级资源释放必须履行其提供方约定
 
-公开 dispose 在调用回调之前取得其记忆化 promise 的所有权。Worker 死亡在处理任何排队的迟到子级请求之前关闭准入,合成缺失的生命周期结束,并启动子级/进程清理而不重写已声明的结果。
+公开 dispose 汇入同一个清理操作。运行结算关闭子级准入,合成缺失的生命周期结束并清理子级,不重写已经认领的结果。
 
 ### ACP 提示词结算不依赖更新投递
 
@@ -344,7 +344,7 @@ TypeScript 无法管控 JavaScript 强制转换、直接 Cordis dispatch、进
 
 事件目录、服务目录、生产者/消费方矩阵、配置目录、模块图、工具目录、type-equiv 块和作用域事件解析器映射都是从源码生成或受新鲜度门禁约束的。[TypeScript 语义门禁 Agent Note](../../archived/process/2026-07-14-typescript-program-backed-semantic-gates.md) 拥有 Program 构造、语义事件发现和解析器生成规则。
 
-行为测试固定了作用域路由和 dispose、最终写入注册表时的碰撞清理、发布回滚、有序完全停稳、持久化前/后提交行为、跨展示和执行的活跃工具过滤、协作式提示词组装、原生和 PTC mode 中的结构化输出提交、异步 subagent 启动和信号取消、worker 终端仲裁、ACP 结算和进程拆除。
+行为测试固定了作用域路由和 dispose、最终写入注册表时的碰撞清理、发布回滚、有序完全停稳、持久化前/后提交行为、跨展示和执行的活跃工具过滤、协作式提示词组装、原生和 PTC mode 中的结构化输出提交、异步 subagent 启动和信号取消、工作流终态仲裁、ACP 结算和进程拆除。
 
 ## 曾考虑的替代方案
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.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-27-outbound-proxy-policy.md
-2026-08-27-outbound-proxy-policy.md: ef6ad24cead3b0c2c2f4ac5fff9ec1dc9b602a5f
-2026-08-27-outbound-proxy-policy.zh.md: d824fc705723b298b1ffc05b6d7996219d01459e
+2026-08-27-outbound-proxy-policy.md: e588ff751fd18762fbe5e24aa17f9bbbc4ff8deb
+2026-08-27-outbound-proxy-policy.zh.md: 57faea256120f5f76ecc4dab1bd8709003672fd0

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.md

@@ -40,7 +40,7 @@ This keeps `proxyForUrl()` and the dispatcher answering from one set of values.
 
 The URL-level policy is untouched: `http(s)` only, no embedded credentials, the length cap, and the cross-origin redirect refusal all still apply on every hop.
 
-**A spawned child gets the policy through its environment; a model-executing worker gets nothing.** `proxyEnvironmentForChild()` merges into `scrubbedParentEnv()`, the one function every spawner already shares. The workflow worker does NOT receive it: it executes the model-authored script body, and a proxy URL may carry `user:password`. That is the same containment the PTC runtime keeps and `docs/defensive-patterns.md` requires, so a workflow's own requests go direct.
+**Ordinary child processes receive proxy policy; PTC program environments omit it.** `proxyEnvironmentForChild()` merges into `scrubbedParentEnv()`. PTC also executes workflow scripts and excludes these settings from program environments because a proxy URL may carry `user:password`; direct program requests go direct.
 
 The child keeps the user's own values, and that is what once broke it. Node parses `HTTP_PROXY` and `HTTPS_PROXY` under `NODE_USE_ENV_PROXY` before running the program and exits on any scheme other than `http:` or `https:`; a `socks4://` kept for `curl` therefore ended every Node child — MCP servers, subagent CLIs, `npm` — before its first line, while this process had reported only that the scheme stayed direct. Measured on Node 24.17: `socks4://`, `ftp://`, and a malformed value all exit 1; `socks5://` happens to be accepted there. The flag is now withheld whenever a value the child receives is one this package refused, so such a child connects directly and `curl` still reads the value it was kept for. Handing the child the resolved value instead would have kept Node proxied at the price of silently rewriting what the user set for another tool.
 
@@ -70,7 +70,7 @@ Weighed against that, telemetry is the one outbound channel whose loss costs the
 
 **Read the operating system's proxy settings.** Rejected for this change. Only Codex and Reasonix among six surveyed products do it, and Codex keeps it behind a default-off flag. Measured on the author's machine, it would have found nothing: the proxy application had written the setting to the Wi-Fi service while the primary interface was a USB ethernet adapter with no proxy, so `scutil --proxy` reported none while the exported variables worked. It also needs its own bypass matcher, because an operating system list carries CIDR entries that neither undici nor Node matches.
 
-**Give model-authored code the proxy too.** Rejected because a proxy URL may carry credentials. Node ptc-runtime processes and workflow workers keep those settings outside the program environment; direct network use remains subject to the program's execution policy.
+**Give model-authored code the proxy too.** Rejected because a proxy URL may carry credentials. PTC processes, including workflow execution, keep those settings outside the program environment; direct network use remains subject to the program's execution policy.
 
 ## Consequences
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.zh.md

@@ -40,7 +40,7 @@ Node 内置的 `fetch` 会忽略 `HTTP_PROXY` 与 `HTTPS_PROXY`。开发者运
 
 URL 层策略未受影响:仅 `http(s)`、禁止内嵌凭据、长度上限与跨域重定向拒绝在每一跳上依然生效。
 
-**派生的子进程通过环境获得策略;执行模型代码的 worker 什么也不获得。** `proxyEnvironmentForChild()` 并入 `scrubbedParentEnv()`——每个 spawner 本就共享的那一个函数。workflow worker **不**接收它:它执行的是模型编写的脚本体,而代理 URL 可能携带 `user:password`。这与 PTC runtime 保持的隔离相同,也是 `docs/defensive-patterns.md` 的要求,因此 workflow 自身的请求直连。
+**普通子进程接收代理策略;PTC 程序环境省略它。** `proxyEnvironmentForChild()` 并入 `scrubbedParentEnv()`。PTC 也执行工作流脚本,并从程序环境中排除这些设置,因为代理 URL 可能携带 `user:password`;程序的直接请求采用直连。
 
 子进程拿到的是用户自己的值,而这恰恰曾把它弄坏。Node 在 `NODE_USE_ENV_PROXY` 下会在运行程序之前先解析 `HTTP_PROXY` 与 `HTTPS_PROXY`,遇到 `http:`/`https:` 之外的协议直接退出;于是一个为 `curl` 保留的 `socks4://` 会让每个 Node 子进程——MCP server、subagent CLI、`npm`——在第一行之前就终结,而本进程此前只报告过该协议保持直连。在 Node 24.17 上实测:`socks4://`、`ftp://` 与畸形值均以 1 退出;`socks5://` 恰好在该版本被接受。现在只要子进程收到的某个值是本包拒绝过的,就扣下该标志,这样的子进程直连,`curl` 仍读到为它保留的值。若改为把解析后的值交给子进程,Node 固然能继续走代理,代价却是悄悄改写用户为另一工具设置的值。
 
@@ -70,7 +70,7 @@ URL 层策略未受影响:仅 `http(s)`、禁止内嵌凭据、长度上限与
 
 **读取操作系统的代理设置。** 本次变更中被否决。所调研的六个产品中只有 Codex 与 Reasonix 这样做,且 Codex 把它放在默认关闭的开关之后。在作者机器上实测,它什么也读不到:代理软件把设置写在了 Wi-Fi 服务上,而主接口是一块没有代理的 USB 以太网卡,因此 `scutil --proxy` 报告无代理,而导出的环境变量却工作正常。它还需要自带的绕过匹配器,因为操作系统的列表含有 undici 与 Node 都不匹配的 CIDR 条目。
 
-**也把代理配置交给模型编写的代码。** 不采纳,因为代理 URL 可能携带凭据。Node ptc-runtime 进程与 workflow worker 不在程序环境中提供这些设置;直接网络访问仍受程序执行策略约束。
+**也把代理配置交给模型编写的代码。** 不采纳,因为代理 URL 可能携带凭据。PTC 进程(包括工作流执行)不在程序环境中提供这些设置;直接网络访问仍受程序执行策略约束。
 
 ## Consequences
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.md
-2026-09-11-sandboxed-node-ptc-runtime.md: 02d76bd607ab8352e208d78f3cf0442fc0794917
-2026-09-11-sandboxed-node-ptc-runtime.zh.md: 40390243b6a15404a2bd9fd2f9d5f9700ad883e6
+2026-09-11-sandboxed-node-ptc-runtime.md: 242b3cfedb49b7ab60c47c6ee03005ddb821bb33
+2026-09-11-sandboxed-node-ptc-runtime.zh.md: d1ab47550e49129ee3e1fefbdfa54cda397374a3

+ 1 - 1
.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.md

@@ -30,7 +30,7 @@ Program completion, timeout, cancellation and protocol failure all close executi
 
 ### Resource limits
 
-The default elapsed deadline is 120 seconds, capped at 600 seconds by default. It includes runtime setup and nested tool or approval waits. V8 old-generation memory, serialized outer output and control traffic have separate configured bounds. The heap limit excludes native allocations and descendant memory, and elapsed time is not a process-tree CPU budget.
+The default elapsed deadline is 120 seconds, capped at 600 seconds by default. Trusted service consumers may request `timeoutMs: null` to disable this timer; [workflow sandbox reuse](2026-09-13-workflow-ptc-sandbox-reuse.md) owns that caller-controlled lifetime. Omitted and numeric requests, including model-facing `run_code`, retain the numeric defaults and caps. An enabled deadline includes runtime setup and nested tool or approval waits. V8 old-generation memory, serialized outer output and control traffic have separate configured bounds. The heap limit excludes native allocations and descendant memory, and elapsed time is not a process-tree CPU budget.
 
 ## Alternatives considered
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.zh.md

@@ -30,7 +30,7 @@ Node worker 隔离 JavaScript 状态,但不应用调用 Session 的 OS 沙箱
 
 ### 资源限制
 
-默认经过时间截止为 120 秒,默认上限为 600 秒。包括运行时准备以及嵌套工具或审批等待。V8 老生代内存、序列化外层输出与控制通信具有独立配置的上限。堆限制不包含原生分配和后代内存,经过时间也不是进程树 CPU 预算。
+默认经过时间截止为 120 秒,默认上限为 600 秒。可信服务消费方可以请求 `timeoutMs: null` 来禁用该定时器;[工作流沙箱复用](2026-09-13-workflow-ptc-sandbox-reuse.zh.md)负责这种由调用方控制的生命周期。省略 timeout 或使用数值的请求(包括面向模型的 `run_code`)保留数值默认值与上限。启用的截止包括运行时准备以及嵌套工具或审批等待。V8 老生代内存、序列化外层输出与控制通信具有独立配置的上限。堆限制不包含原生分配和后代内存,经过时间也不是进程树 CPU 预算。
 
 ## 考虑过的替代方案
 

+ 6 - 0
.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.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-09-13-workflow-ptc-sandbox-reuse.md
+2026-09-13-workflow-ptc-sandbox-reuse.md: 9454ffef28e1ab5ba7791a30f4bcd67093a34ba3
+2026-09-13-workflow-ptc-sandbox-reuse.zh.md: c51706065703866520bfcad11beffc02eb1f5551

+ 41 - 0
.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.md

@@ -0,0 +1,41 @@
+# Agent Note: Reuse the PTC Node sandbox for workflows
+
+Status: implemented
+
+English | [中文](2026-09-13-workflow-ptc-sandbox-reuse.zh.md)
+
+## Problem
+
+Dynamic workflows evaluate model-written JavaScript and start subagents. A worker thread keeps script execution off the host event loop, but code escaping its VM can use Node with the host process's file authority. PTC already owns a Node process implementation with OS file confinement, isolated program state, bounded output and control traffic, and managed cleanup. Maintaining a second launcher would duplicate those responsibilities.
+
+## Decision
+
+`dsh-workflow-ptc` implements `WorkflowEngine` through the shared Node `PtcRuntime`. Each run keeps the existing VM and workflow helpers inside one PTC process. Host bindings connect the guest to the configured subagent provider and workflow observers; the host supplies the calling Agent and resolves its Session's standing file policy and cwd.
+
+The VM defines the helper API and cooperative concurrency, total-agent and item caps. It is not a security boundary, and those counters are not host-enforced security quotas. File enforcement, V8 heap limits, output and control limits, and managed process cleanup remain owned by PTC and its sandbox/subprocess providers. Network access and provider-specific containment limits remain the same as PTC.
+
+Workflow execution passes `timeoutMs: null`, which explicitly disables the elapsed timer in the Node runtime. Omitted and numeric PTC requests keep their configured defaults and caps; `run_code` continues to accept only positive numeric overrides. The initial VM slice retains its own synchronous timeout. A caller's abort signal, including an enclosing tool deadline, still cancels the workflow.
+
+Cancellation immediately aborts the PTC process and the signal shared by pending and active child agents. The adapter awaits pending starts and child disposal, including a child that publishes after cancellation. PTC stops the program; its caller remains responsible for host bindings already in flight. There is no additional workflow cleanup timer or guest cancellation acknowledgement.
+
+Progress uses one binding call at a time. The first batch starts synchronously; later events queue in order and drain before child disposal and the final result. This prevents ordinary log bursts from exhausting PTC's pending-call limit. Child-result waits stop on cancellation while child disposal remains awaited.
+
+The Node bootstrap keeps its control pipe open after sending the terminal frame until the host closes it. Unawaited binding replies may still be in flight, so eager child-side close would let an `EPIPE` race an already completed program.
+
+The [dynamic-workflows decision](../feature/2026-07-05-dynamic-workflows.md) retains the script, structured-output, event and tool semantics; this note supersedes only its execution substrate and trust realization. The [sandboxed Node PTC decision](2026-09-11-sandboxed-node-ptc-runtime.md) retains execution and control guarantees; the explicit null deadline extends its service options. The [agent-scope runtime design](2026-07-12-agent-scope-runtime-design.md#workflow-children-are-pending-starts-or-published-records) retains pending-start and child-cleanup ownership.
+
+## Alternatives considered
+
+**Retain worker-thread execution.** Worker termination cannot apply the Session's OS file policy or provide the managed process cleanup already required by direct Node code.
+
+**Create a separate workflow subprocess runtime.** Another launcher, control transport and sandbox adapter would maintain the same execution responsibilities twice. PTC already accepts programs and named asynchronous host bindings without knowing about tools or Sessions.
+
+**Replace the VM with direct Node workflow APIs.** The VM and helpers preserve the existing script semantics, synchronous-slice timeout and JSON materialization. Removing them is unnecessary for OS confinement.
+
+**Apply PTC's numeric deadline to workflows.** A workflow may await a long series of subagents. Explicit `null` preserves caller-controlled lifetime without changing ordinary PTC defaults or treating an arbitrarily large number as no deadline.
+
+## Consequences
+
+Workflow and opt-in Ralph execution share PTC's security and process lifecycle implementation. Ralph remains disabled in shipped defaults. Scripts still use the same hooks and result envelope; no new authoritative progress ledger or host child-count quota is introduced.
+
+Cancellation does not wait for cooperative script progress. The adapter waits for child cleanup, so a subagent provider that does not fulfill its lifecycle contract can delay disposal. Process cleanup retains the selected subprocess provider's managed-range limitations; this change does not claim process-tree CPU/RSS accounting or universal descendant termination.

+ 41 - 0
.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.zh.md

@@ -0,0 +1,41 @@
+# Agent Note: 工作流复用 PTC Node 沙箱
+
+Status: implemented
+
+[English](2026-09-13-workflow-ptc-sandbox-reuse.md) | 中文
+
+## 问题
+
+动态工作流执行模型编写的 JavaScript 并启动 subagent。worker 线程把脚本执行移出宿主事件循环,但逃逸 VM 的代码可以使用 Node 并拥有宿主进程的文件权限。PTC 已有 Node 进程实现,负责 OS 文件约束、隔离的程序状态、有界输出与控制通信,以及受管清理。维护第二套启动器会重复这些职责。
+
+## 决策
+
+`dsh-workflow-ptc` 通过共享的 Node `PtcRuntime` 实现 `WorkflowEngine`。每次运行在一个 PTC 进程中保留既有 VM 与工作流辅助函数。Host 绑定将 guest 连接到配置的 subagent 提供方及工作流观察器;Host 提供发起调用的 Agent,并解析其 Session 的常设文件策略与 cwd。
+
+VM 定义辅助 API,以及协作式并发、agent 总数和条目上限。它不是安全边界,这些计数器也不是 Host 强制的安全配额。文件强制、V8 堆限制、输出与控制限制、受管进程清理仍由 PTC 及其沙箱/子进程提供方负责。网络访问与提供方特有的约束限制保持与 PTC 相同。
+
+工作流执行传入 `timeoutMs: null`,显式禁用 Node 运行时的经过时间定时器。省略 timeout 或使用数值的 PTC 请求保留配置的默认值与上限;`run_code` 仍只接受正数覆盖值。最初的 VM 片段保留独立的同步超时。调用方的中止信号(包括外层工具截止)仍会取消工作流。
+
+取消立即中止 PTC 进程,以及待启动和活跃子 agent 共享的信号。适配器等待待完成启动与子 agent 资源释放,包括取消后才发布的子 agent。PTC 停止程序;已经进行中的 Host 绑定仍由其调用方负责。不增加工作流清理定时器,也不等待 guest 取消确认。
+
+进度同时只使用一个绑定调用。首批同步发起,后续事件按序排队,并在子 agent 资源释放及最终结果之前完成传递。这避免普通日志突发耗尽 PTC 的待完成调用上限。取消会停止对子 agent 结果的等待,但仍等待子 agent 资源释放。
+
+Node 引导程序发送终态帧后保持控制管道打开,直到 Host 将其关闭。未被等待的绑定回复可能仍在传输,因此子进程提前关闭会让 `EPIPE` 与已经完成的程序结果发生竞争。
+
+[动态工作流决策](../feature/2026-07-05-dynamic-workflows.zh.md)保留脚本、结构化输出、事件与工具语义;本文只取代其执行基底与信任实现。[沙箱化 Node PTC 决策](2026-09-11-sandboxed-node-ptc-runtime.zh.md)保留执行与控制保证;显式 null 截止扩展其服务选项。[agent 作用域运行时设计](2026-07-12-agent-scope-runtime-design.zh.md#workflow-children-are-pending-starts-or-published-records)保留待启动与子 agent 清理的归属规则。
+
+## 曾考虑的替代方案
+
+**保留 worker-thread 执行。** 终止 worker 无法应用 Session 的 OS 文件策略,也无法提供直接 Node 代码所需的受管进程清理。
+
+**创建独立的工作流子进程运行时。** 另一套启动器、控制传输与沙箱适配器会重复维护相同执行职责。PTC 已经接受程序和具名异步 Host 绑定,无需了解工具或 Session。
+
+**用直接 Node 工作流 API 替换 VM。** VM 与辅助函数保留既有脚本语义、同步片段超时与 JSON 物化。实现 OS 约束不需要移除它们。
+
+**对工作流应用 PTC 的数值截止。** 工作流可能等待一长串 subagent。显式 `null` 保留调用方控制的生命周期,不改变普通 PTC 默认值,也不把任意大的数值当作没有截止。
+
+## 后果
+
+工作流与显式启用的 Ralph 执行共享 PTC 的安全与进程生命周期实现。Ralph 在已发布默认组合中保持禁用。脚本仍使用相同钩子与结果信封;不增加新的权威进度台账或 Host 子 agent 数量配额。
+
+取消不等待脚本协作推进。适配器等待子 agent 清理,因此不履行生命周期约定的 subagent 提供方可能延迟资源释放。进程清理保留所选子进程提供方对受管范围的限制;本变更不宣称进程树 CPU/RSS 计量或普遍的后代终止保证。

+ 2 - 2
.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.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-05-dynamic-workflows.md
-2026-07-05-dynamic-workflows.md: 6d7dc43121df251d71a3305c083055b43d63f0f5
-2026-07-05-dynamic-workflows.zh.md: aaf527637f6fb5f58e088ab6021f5dcb9d4e0b5a
+2026-07-05-dynamic-workflows.md: c7245704b0734c1508fe7f001e6429f594ebdd1d
+2026-07-05-dynamic-workflows.zh.md: a4502d7d6b3acf1aebc4a5b36caccf50859bab01

+ 10 - 16
.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md

@@ -22,19 +22,13 @@ One deliberate strictness DIVERGENCE from CC: hook misuse — unknown or deferre
 
 `ctx.workflowEngine` is an abstract `WorkflowEngine` in the bash shape — one engine per context, no named-provider registry (engines are deployment swaps, not co-residents). `start(request)` throws synchronously for a script that cannot begin; a returned `WorkflowRun`'s `result` NEVER rejects (failures resolve as `stopReason: 'error' | 'cancelled'`). The `workflow/*` events are observe-only emits carrying DATA SNAPSHOTS (id + meta; `workflow/end` omits the result value), per-listener contained, mirroring `subagent/start`/`subagent/end` — control stays with the run's holder. Vocabulary details: [subsystems/workflow.md](../../../../docs/subsystems/workflow.md).
 
-### The engine (dsh-workflow-worker-thread): one worker thread per run
+### The engine (dsh-workflow-ptc): shared Node process execution
 
-**Trust premise**: workflow scripts have the same trust as the model's bash access. The engine contains buggy scripts and guarantees settled results, JSON-safe values, and cancellation quiescence; it does not defend against hostile code. A vm context and worker thread are not security boundaries: a script can escape to Node APIs with process-wide authority. Sandboxing requires a separate-process or isolated-vm engine behind this seam.
+The [workflow sandbox reuse decision](../architecture/2026-09-13-workflow-ptc-sandbox-reuse.md) supersedes the worker-thread execution and trust realization. The engine retains the VM and helpers inside a sandboxed PTC Node process. The VM defines the script API; OS file policy and managed process cleanup belong to the shared execution provider.
 
-**Why `node:worker_threads`**: each run gets one unpooled worker. A vm context limits the documented script API, while message-port RPC bridges `agent()` to host-side child loops. The worker prevents synchronous script work from blocking the host, provides a serialization boundary, and permits forced termination after cancellation. `isolated-vm` was rejected because of its maintenance state and deployment requirements.
+The host validates metadata and parses the body before publication. Host bindings connect the guest to subagents and workflow observers. Pending starts and published child records share a cancellation signal; the [agent-scope runtime-design Agent Note](../architecture/2026-07-12-agent-scope-runtime-design.md#workflow-children-are-pending-starts-or-published-records) owns their lifecycle rules.
 
-The host validates metadata and parses the body before publication. Private enum-keyed payload maps define the wire protocol; pending starts, published child records, one cancellation signal, worker-death reaping, result precedence, and disposal quiescence preserve the subagent run contract across it. The [agent-scope runtime-design Agent Note](../architecture/2026-07-12-agent-scope-runtime-design.md#workflow-children-are-pending-starts-or-published-records) owns those race algorithms.
-
-The engine exposes an in-process `MessageChannel` test path because main-process V8 coverage cannot see worker execution.
-
-**Meta is data**: the schema-validated `meta` field reaches the seam as JSON and is only shape-validated. The host never evaluates a metadata literal, which would let script-controlled accessors run outside the worker's isolation.
-
-**Value boundary**: `materializeFromRealm` copies outbound values and rejects functions, symbols, nested `undefined`, exotic prototypes, cycles, sparse arrays, and non-finite numbers. Data-property copies make `"__proto__"` safe; getters are read normally and a throwing getter fails loudly. `args` crosses through `workerData` and is cloned again before exposure. Realm functions are invoked rather than copied, and thrown values use a total renderer so `result` cannot reject. Hook errors are host-realm `WorkflowError`s, so scripts branch on `name` or `code` rather than `instanceof Error`, as documented in the engine README. Concurrency, total-agent, item, timeout, and grace limits are validated config.
+**Meta is data**: the host never evaluates a metadata literal. **Values are lossless JSON**: guest-side realm materialization rejects unsupported values before PTC transport. Getters run inside the confined process, and hook errors retain their stable `name` and `code` fields across realms. Cooperative helper caps and the initial synchronous-slice timeout remain; no overall workflow elapsed timer is added.
 
 ### The Consumer (`dsh-tool-workflow`)
 
@@ -52,7 +46,7 @@ An output schema makes a schema-valid committed capture mandatory for successful
 
 ## Testing
 
-Worker-side logic runs through an in-process `MessageChannel` so V8 coverage measures it. Unit tests cover script helpers, fatal and nullable failures, JSON boundaries, caps, cancellation, child ownership, and structured output through real loops. A built-bin smoke runs the separately bundled `lib/worker.cjs` under plain Node, a with-key e2e drives real child agents, and model-facing workflow behavior is snapshot-covered through its owning example.
+Verification belongs to the workflow helper and host-lifecycle tests, the shared Node PTC confinement tests, and source/built workflow execution through the shipped profile. Recorded workflow and opt-in Ralph scenarios own the assembled model transcript; replay configurations using a passthrough sandbox do not establish OS enforcement.
 
 ## Deferred (documented non-goals)
 
@@ -60,14 +54,14 @@ Worker-side logic runs through an in-process `MessageChannel` so V8 coverage mea
 - **Journaling + resume** (`resumeFromRunId`, cached agent() prefixes) — implementing it reintroduces CC's determinism bans as a script-contract tightening (scripts may read the clock).
 - **Saved/bundled workflows** (a `.deepseek/workflows/` registry, slash-command API) and **script persistence to a run directory** (the tool-call event already records the script durably).
 - **Nested `workflow()`**, **token `budget`**, and the `effort`/`isolation`/`agentType` agent options (each rejects loud with a message naming it deferred).
-- **An overall run wall-clock timeout** — cancellation always frees the caller (result settles within the grace), so a cap on total run time is a policy knob for the background redesign, not a correctness need here.
-- **Engine hardening beyond worker threads**: an isolated-vm or separate-process engine behind the same seam (actual sandboxing; memory limits).
+- **An overall run wall-clock timeout** — workflow lifetime remains caller-controlled; explicit cancellation stops PTC execution and awaits child cleanup.
 - **ACP-backend structured output** and **`toolFilter`** (both still capability-gated `false`).
 
 ## Alternatives considered
 
-- **Hostile-value containment in the host** (trap-free proxy rejection, accessor-never-invoked descriptor walks, realm-side pre-rendering of thrown values, realm-built promises/arrays/error clones with structural fatal recognition): rejected because every defense targets an author the trust premise accepts, while the thread's serialization boundary already makes cross-realm values total by construction.
-- **In-process `node:vm` execution**: mechanically simplest — no RPC, no thread — but `start()` blocks the caller for the script's initial synchronous slice, a synchronous spin past the first await cannot be killed in-process (the vm `timeout` covers only that first slice), and `dispose()` could only abandon an unsettling script on the host loop. The worker-thread engine keeps the same vm-context script API while unblocking the host and making termination real.
+- **Host-side defenses for VM values** (proxy rejection, descriptor walks and cross-realm clones): these cannot enforce OS file authority. VM evaluation and materialization belong inside the confined process; the shared PTC provider owns validation at the process transport.
+- **In-process `node:vm` execution**: mechanically simplest — no RPC, no thread — but `start()` blocks the caller for the script's initial synchronous slice, a synchronous spin past the first await cannot be killed in-process (the vm `timeout` covers only that first slice), and `dispose()` could only abandon an unsettling script on the host loop. The PTC process keeps the vm-context script API while unblocking the host and providing managed termination.
+- **`isolated-vm`**: adding a separate JavaScript engine brings native dependency and deployment requirements; the shared PTC provider already supplies process confinement.
 - **Background execution as the default** (CC's shape): deferred; foreground-synchronous matches `dsh-tool-subagent`'s cut, and background semantics should be designed ONCE across shell/subagent/workflow rather than per-tool.
 - **Workflow-layer JSON parsing for `agent({schema})`**: duplicating a seam concern at one consumer while the seam's capability flag stayed dishonestly `false`.
 - **Meta embedded in the script as `export const meta = {...}`** (CC's exact format): keeps scripts self-contained and CC scripts drop-in, but obtaining meta requires evaluating model-written text on the host. Even an empty timed vm context cannot bound script-controlled getters when the host reads the resulting object. A JSON parameter removes the scanner, evaluation, and host-spin hole; the cost is that a CC script's meta header must move into the parameter (the body stays drop-in).
@@ -78,4 +72,4 @@ Worker-side logic runs through an in-process `MessageChannel` so V8 coverage mea
 
 ## Consequences
 
-Fan-out plans now live in rerunnable scripts, and `outputSchema` provides authoritative structured child results. Each run pays worker startup and message-port RPC costs, but host startup stays non-blocking, cancellation can terminate the worker, and serialization enforces the value boundary. Worker threads are not a security boundary. Invalid options fail rather than degrading to Claude Code's `null`; consumers retain control through the run handle while observers receive snapshots only. Top-level Web users also receive a durable, replayable workflow record without widening the execution seam or coupling the original tool card to workflow-specific UI.
+Fan-out plans now live in rerunnable scripts, and `outputSchema` provides authoritative structured child results. Each run pays PTC process startup and binding RPC costs. Host execution stays non-blocking, cancellation stops the managed process, and JSON serialization separates guest values from the host. Invalid options fail rather than degrading to Claude Code's `null`; consumers retain control through the run handle while observers receive snapshots only. Top-level Web users also receive a durable, replayable workflow record without widening the execution seam or coupling the original tool card to workflow-specific UI.

+ 10 - 16
.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.zh.md

@@ -22,19 +22,13 @@ harness 可以通过 `dsh-tool-subagent` 将一个任务委派给一个子 agent
 
 `ctx.workflowEngine` 是 bash 形态的抽象 `WorkflowEngine`——每个上下文一个引擎,无命名提供方注册表(引擎是部署级替换,不是共存者)。`start(request)` 对无法启动的脚本同步抛出;返回的 `WorkflowRun` 的 `result` 永不 reject(失败时结算为 `stopReason: 'error' | 'cancelled'`)。`workflow/*` 事件是仅观察的 emit,携带数据快照(id + meta;`workflow/end` 省略 result 值),按监听器隔离,与 `subagent/start`/`subagent/end` 对称——控制权留在 run 的持有者手中。词汇详情见 [subsystems/workflow.md](../../../../docs/subsystems/workflow.zh.md)。
 
-### 引擎(dsh-workflow-worker-thread):每次运行一个 worker 线程
+### 引擎(dsh-workflow-ptc):共享 Node 进程执行
 
-**信任前提**:工作流脚本与模型的 bash 访问具有相同的信任级别。引擎会约束有缺陷脚本的影响,并保证结果已 settled、值可安全表示为 JSON、取消后完全停稳;它不防御恶意代码。vm 上下文和 worker 线程不是安全边界:脚本可以逃逸到具有进程级权限的 Node API。沙箱化需要在此 seam 背后使用独立进程或 isolated-vm 引擎
+[工作流沙箱复用决策](../architecture/2026-09-13-workflow-ptc-sandbox-reuse.zh.md)取代 worker-thread 执行与信任实现。引擎在沙箱化 PTC Node 进程中保留 VM 与辅助函数。VM 定义脚本 API;OS 文件策略和受管进程清理由共享执行提供方负责
 
-**为何选择 `node:worker_threads`**:每次运行获得一个非池化的 worker。vm 上下文限定了文档中说明的脚本 API,而消息端口 RPC 将 `agent()` 桥接到宿主侧的子循环。worker 防止脚本的同步工作阻塞宿主,提供序列化边界,并允许取消后强制终止。`isolated-vm` 因其维护状态和部署要求被否决
+宿主在发布前校验元数据并解析正文。Host 绑定将 guest 连接到 subagent 和工作流观察器。待启动与已发布子记录共享取消信号;[agent 作用域运行时设计 Agent Note](../architecture/2026-07-12-agent-scope-runtime-design.zh.md#workflow-children-are-pending-starts-or-published-records)负责其生命周期规则
 
-宿主在发布前校验元数据并解析正文。私有枚举键 payload 映射定义协议格式;待启动记录、已发布子记录、单一取消信号、worker 死亡回收、结果优先级与 dispose(资源释放)时的完全停稳,在此协议上保持 subagent run 约定。这些竞态算法由 [agent 作用域运行时设计 Agent Note](../architecture/2026-07-12-agent-scope-runtime-design.zh.md#workflow-children-are-pending-starts-or-published-records) 定义。
-
-引擎暴露一条进程内 `MessageChannel` 测试路径,因为主进程 V8 覆盖率无法观测 worker 执行。
-
-**Meta 是数据**:经 schema 校验的 `meta` 字段以 JSON 形式到达 seam,仅做形状校验。宿主从不执行元数据字面量,否则脚本控制的访问器可以在 worker 隔离之外运行。
-
-**值边界**:`materializeFromRealm` 复制出站值,并拒绝函数、symbol、嵌套 `undefined`、异域原型、循环引用、稀疏数组和非有限数字。数据属性复制使 `"__proto__"` 安全;getter 正常读取,抛出异常的 getter 会明确报错。`args` 通过 `workerData` 传入,暴露前再次克隆。realm 函数被调用而非复制,抛出的值使用对所有输入均有定义的渲染器,因此 `result` 不会 reject。钩子错误是宿主 realm 的 `WorkflowError`,脚本应基于 `name` 或 `code` 分支而非 `instanceof Error`,如引擎 README 所述。并发、total-agent、item、超时和宽限限制均为经校验的配置。
+**Meta 是数据**:Host 从不执行元数据字面量。**值是无损 JSON**:guest 侧 realm 物化在 PTC 传输前拒绝不支持的值。getter 在受限进程内运行,钩子错误跨 realm 保留稳定的 `name` 与 `code` 字段。保留协作式辅助函数上限与最初同步片段超时;不增加整体工作流经过时间定时器。
 
 ### Consumer(`dsh-tool-workflow`)
 
@@ -52,7 +46,7 @@ harness 可以通过 `dsh-tool-subagent` 将一个任务委派给一个子 agent
 
 ## 测试
 
-worker 侧逻辑通过进程内 `MessageChannel` 运行,使 V8 覆盖率能够度量它。单元测试覆盖脚本辅助函数、fatal 与 nullable 失败、JSON 边界、上限、取消、子 agent 所有权和通过真实循环的结构化输出。构建后二进制文件的冒烟测试在纯 Node 下运行单独打包的 `lib/worker.cjs`,带密钥的 e2e 驱动真实子 agent,面向模型的工作流行为通过其所属示例进行快照覆盖
+验证由工作流辅助函数和 Host 生命周期测试、共享 Node PTC 约束测试,以及通过已发布 profile 的源码/构建后工作流执行负责。已记录工作流与显式启用的 Ralph 场景负责组装后的模型转录;使用 passthrough 沙箱的回放配置不能证明 OS 强制能力
 
 ## 延迟(明确的非目标)
 
@@ -60,14 +54,14 @@ worker 侧逻辑通过进程内 `MessageChannel` 运行,使 V8 覆盖率能够
 - **日志化 + 恢复**(`resumeFromRunId`、缓存的 agent() 前缀):实现它会以脚本约定收紧的形式重新引入 CC 的确定性禁令(脚本可以读取时钟)。
 - **保存/打包的工作流**(`.deepseek/workflows/` 注册表、斜杠命令 API)和**脚本持久化到运行目录**(工具调用事件已经持久记录了脚本)。
 - **嵌套 `workflow()`**、**token `budget`**,以及 `effort`/`isolation`/`agentType` agent 选项(每个都会明确拒绝,并在消息中注明其已延迟实现)。
-- **整体运行的挂钟超时**:取消总能释放调用方(result 在宽限期内 settle),因此总运行时间上限是后台重设计的策略旋钮,不是此处的正确性需求。
-- **超越 worker 线程的引擎加固**:在同一 seam 背后使用 isolated-vm 或独立进程引擎(真正的沙箱化;内存限制)。
+- **整体运行的挂钟超时**:工作流生命周期仍由调用方控制;显式取消停止 PTC 执行并等待子 agent 清理。
 - **ACP(Agent Client Protocol)后端结构化输出**和 **`toolFilter`**(两者仍以能力标志 `false` 门控)。
 
 ## 曾考虑的替代方案
 
-- **宿主侧的恶意值防护**(无 trap 代理拒绝、从不调用访问器的描述符遍历、realm 侧预渲染抛出值、realm 构建的 promise/array/error 克隆加结构化 fatal 识别):否决。每项防御针对的都是信任前提所接受的作者,而线程的序列化边界已经从构造上保证跨 realm 值的处理对所有输入都有确定结果。
-- **进程内 `node:vm` 执行**:机械上最简——无 RPC、无线程——但 `start()` 会在脚本的初始同步切片期间阻塞调用方,第一个 await 之后的同步自旋无法在进程内终止(vm `timeout` 仅覆盖第一个切片),且 `dispose()` 只能在宿主循环上放弃一个未 settle 的脚本。worker 线程引擎保持相同的 vm 上下文脚本 API,同时解除宿主阻塞并使终止成为现实。
+- **VM 值的 Host 侧防护**(代理拒绝、描述符遍历和跨 realm 克隆):这些无法强制 OS 文件权限。VM 求值与物化属于受限进程内部;共享 PTC 提供方负责进程传输处的验证。
+- **进程内 `node:vm` 执行**:机械上最简——无 RPC、无线程——但 `start()` 会在脚本的初始同步切片期间阻塞调用方,第一个 await 之后的同步自旋无法在进程内终止(vm `timeout` 仅覆盖第一个切片),且 `dispose()` 只能在宿主循环上放弃一个未 settle 的脚本。PTC 进程保持 vm 上下文脚本 API,同时解除宿主阻塞并提供受管终止。
+- **`isolated-vm`**:引入另一套 JavaScript 引擎会增加原生依赖与部署要求;共享 PTC 提供方已提供进程约束。
 - **后台执行作为默认**(CC 的形态):延迟。前台同步与 `dsh-tool-subagent` 的当前形态一致,后台语义应在 bash、subagent 和工作流之间统一设计一次,而非逐工具设计。
 - **工作流层为 `agent({schema})` 做 JSON 解析**:在一个消费方重复 seam 关注点,而 seam 的能力标志仍不诚实地为 `false`。
 - **Meta 嵌入脚本中作为 `export const meta = {...}`**(CC 的确切格式):保持脚本自包含且 CC 脚本可直接使用,但获取 meta 需要在宿主上执行模型编写的文本。即使一个空的限时 vm 上下文也无法约束脚本控制的 getter(当宿主读取结果对象时)。JSON 参数消除了扫描器、执行和宿主自旋漏洞;代价是 CC 脚本的 meta 头必须移入参数(正文保持可直接使用)。
@@ -78,4 +72,4 @@ worker 侧逻辑通过进程内 `MessageChannel` 运行,使 V8 覆盖率能够
 
 ## 后果
 
-扇出计划现在存在于可重运行的脚本中,`outputSchema` 提供权威的结构化子 agent 结果。每次运行付出 worker 启动和消息端口 RPC 成本,但宿主启动保持非阻塞,取消可以终止 worker,序列化强制执行值边界。worker 线程不是安全边界。无效选项会失败而非退化为 Claude Code 的 `null`;消费方通过 run handle 保持控制权,观察者仅接收快照。顶层 Web 用户还会得到持久、可回放的工作流记录,同时不扩宽执行 seam,也不把原工具卡耦合到工作流专属 UI。
+扇出计划现在存在于可重运行的脚本中,`outputSchema` 提供权威的结构化子 agent 结果。每次运行付出 PTC 进程启动与绑定 RPC 成本。Host 执行保持非阻塞,取消停止受管进程,JSON 序列化将 guest 值与 Host 分开。无效选项会失败而非退化为 Claude Code 的 `null`;消费方通过 run handle 保持控制权,观察者仅接收快照。顶层 Web 用户还会得到持久、可回放的工作流记录,同时不扩宽执行 seam,也不把原工具卡耦合到工作流专属 UI。

+ 2 - 2
.agents/notes/implemented/feature/2026-07-16-harness-level-loop.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-16-harness-level-loop.md
-2026-07-16-harness-level-loop.md: 43b8f867ae9af92f22fdae0cecef37803b49be30
-2026-07-16-harness-level-loop.zh.md: 40af80d95127974bcbed4f7114dec048e0dbdc57
+2026-07-16-harness-level-loop.md: 54adc1aad63e8968c9e0d276a075cd42abf021fd
+2026-07-16-harness-level-loop.zh.md: 9e1a7e73fbf1fab1807b59b3b7816cdfcc6f45df

+ 1 - 1
.agents/notes/implemented/feature/2026-07-16-harness-level-loop.md

@@ -76,7 +76,7 @@ Base-backed profiles mount the shared command registry and complete goal stack b
 
 Ralph is a first-class model tool in its own plugin, demonstrating that a sophisticated fixed execution policy can be composed without a new loop core. The plugin owns a fixed workflow script over `ctx.workflowEngine` and `ctx.subagents`; it does not create session-goal state or add a branch to `dsh-agent-loop`.
 
-Each round uses an explicit `WorkflowStartRequest.subagentProvider`, defaulting to `spawn`. The provider must exist, support structured output, and declare that it does not inherit parent context. Ralph also passes its resolved round cap as `WorkflowStartRequest.maxTotalAgents`; the worker engine validates both per-run policies before publishing work, so provider misconfiguration or an engine ceiling below the requested Ralph scale fails before a run exists. The child inherits cwd and lineage but receives only the immutable objective, round/cap, workspace-as-authority instruction, and previous normalized report.
+Each round uses an explicit `WorkflowStartRequest.subagentProvider`, defaulting to `spawn`. The provider must exist, support structured output, and declare that it does not inherit parent context. Ralph also passes its resolved round cap as `WorkflowStartRequest.maxTotalAgents`; the PTC workflow engine validates both per-run policies before publishing work, so provider misconfiguration or an engine ceiling below the requested Ralph scale fails before a run exists. The child inherits cwd and lineage but receives only the immutable objective, round/cap, workspace-as-authority instruction, and previous normalized report.
 
 A report contains status, summary, evidence, next steps, and blocker text. Status-specific invariants and serialized size are validated inside the fixed script and again at the consumer boundary. `maxRounds` is configurable, defaults to `256`, and is the ceiling for a call override. `maxHandoffChars` defaults to `16384`; oversized reports fail rather than being silently truncated. `maxResultChars` separately defaults to `16384` and bounds the complete successful parent-facing text, including its envelope and truncation marker.
 

+ 1 - 1
.agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md

@@ -76,7 +76,7 @@ Goal Round 驱动器为每个特定的实时 agent 至多拥有一个待定预
 
 Ralph 是位于自有插件中的一等模型工具,展示了复杂固定执行策略可以在没有新 loop 核心的情况下组合完成。该插件拥有构建在 `ctx.workflowEngine` 与 `ctx.subagents` 之上的固定工作流脚本;它不会创建会话目标状态,也不会为 `dsh-agent-loop` 增加分支。
 
-每个 Round 都使用显式 `WorkflowStartRequest.subagentProvider`,默认为 `spawn`。该提供方必须存在、支持结构化输出,并声明不继承父上下文。Ralph 还会把解析后的 Round 上限作为 `WorkflowStartRequest.maxTotalAgents` 传递;工作线程引擎会在发布工作前验证两项每次运行策略,因此提供方配置错误或低于所请求 Ralph 规模的引擎上限会在运行创建前失败。子 agent 继承 cwd 与谱系,但只接收不可变目标、当前 Round/上限、以工作区为权威的指令和上一份规范化报告。
+每个 Round 都使用显式 `WorkflowStartRequest.subagentProvider`,默认为 `spawn`。该提供方必须存在、支持结构化输出,并声明不继承父上下文。Ralph 还会把解析后的 Round 上限作为 `WorkflowStartRequest.maxTotalAgents` 传递;PTC 工作流引擎会在发布工作前验证两项每次运行策略,因此提供方配置错误或低于所请求 Ralph 规模的引擎上限会在运行创建前失败。子 agent 继承 cwd 与谱系,但只接收不可变目标、当前 Round/上限、以工作区为权威的指令和上一份规范化报告。
 
 报告包含状态、摘要、证据、下一步与阻塞文本。固定脚本内部和消费方边界都会验证状态专用不变量与序列化大小。`maxRounds` 可配置,默认为 `256`,并作为调用覆盖值的上限。`maxHandoffChars` 默认为 `16384`;过大报告会失败,而不会被静默截断。`maxResultChars` 单独默认为 `16384`,并限制面向父级的完整成功文本,包括外层文本与截断标记。
 

+ 2 - 2
.agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.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-09-12-ralph-off-in-shipped-defaults.md
-2026-09-12-ralph-off-in-shipped-defaults.md: 2cae5a2f1cdeb223c4122afe60a2b062d7d7b8e2
-2026-09-12-ralph-off-in-shipped-defaults.zh.md: 8e61197a0dfd2f73a56bfdd33e18141e64c133b7
+2026-09-12-ralph-off-in-shipped-defaults.md: 4ae434a0e9d6283c2934cdf74a562df360daa12e
+2026-09-12-ralph-off-in-shipped-defaults.zh.md: 490b33d85be411826d03c5def08b1905062ff830

+ 4 - 4
.agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.md

@@ -14,7 +14,7 @@ That tool shipped enabled in `packages/bundle/base/cordis.patch.yml` and in thre
 
 `packages/bundle/base/cordis.patch.yml` declares its `tool-ralph` row `disabled: true`, and the `standard`, `ptc`, and `cordis` presets declare their own `tool-ralph` rows the same way. The `minimal` preset carries no such row. The package, its tool contract, and its tests remain: this changes which default compositions mount the row, not whether the capability exists.
 
-The `ptc` preset also declares `workflow-worker-thread` disabled. That preset kept the engine for `ralph` alone after it dropped the general `workflow` tool, so disabling `ralph` left the engine with no consumer in that composition.
+The `ptc` preset also declares `workflow-ptc` disabled. That preset kept the engine for `ralph` alone after it dropped the general `workflow` tool, so disabling `ralph` left the engine with no consumer in that composition.
 
 Each disabled row carries its restore recipe in a local comment. An overlay row re-enables the capability for a base-backed profile from `$DSH_HOME/cordis.patch.yml` or a `--patch` file. Preset files take no patches (`packages/preset/agent-presets/README.md`), so a Web session that wants `ralph` duplicates the preset under a **new id** into `$DSH_HOME/.agent-presets` and drops `disabled`; a copy reusing the shipped id is shadowed by the shipped root, which wins duplicate ids (`packages/preset/agent-presets/src/index.ts`), and `copy()` refuses an id any root already supplies. In `ptc` the duplicate drops `disabled` from the tool row and the engine row together, because `tool-ralph` injects `ctx.workflowEngine`.
 
@@ -32,7 +32,7 @@ Each disabled row carries its restore recipe in a local comment. An overlay row
 
 **Demote the goal tools alongside `ralph`.** Both surfaces defer independent evaluation, so the bar reads the same. Goal tools are the supported long-running path — the `ralph` description points ordinary long-running work at them — and they carry a product surface, so demoting both would leave no supported way to run long work.
 
-**Keep `workflow-worker-thread` enabled in `ptc`.** A user who removes `disabled` from `tool-ralph` in a duplicated `ptc` preset would then need one edit instead of two. It retains a provider with no consumer in the shipped composition, which `packages/AGENTS.md` rejects; the restated comment names the dependency instead.
+**Keep `workflow-ptc` enabled in `ptc`.** A user who removes `disabled` from `tool-ralph` in a duplicated `ptc` preset would then need one edit instead of two. It retains a provider with no consumer in the shipped composition, which `packages/AGENTS.md` rejects; the restated comment names the dependency instead.
 
 **Document the opt-in recipe in `docs/`.** A guide page would reach users who never open a composition file. The recipes differ by plane and are three lines each, so the row comments carry them at the point of use.
 
@@ -40,10 +40,10 @@ Each disabled row carries its restore recipe in a local comment. An overlay row
 
 A default Web, headless, sdk, acp, or custom base-backed session no longer offers `ralph`, and neither do the `standard`, `ptc`, and `cordis` presets. To restore it a user edits a composition, so the capability is opt-in rather than merely discouraged. Existing sessions that already logged `ralph` calls still replay and render: the tool package is installed and its event types are unchanged.
 
-`ptc` mode loses the engine as well. A duplicated `ptc` preset that restores `tool-ralph` without restoring `workflow-worker-thread` leaves the tool row with an unresolved injection, which is why both rows carry the dependency in their comments.
+`ptc` mode loses the engine as well. A duplicated `ptc` preset that restores `tool-ralph` without restoring `workflow-ptc` leaves the tool row with an unresolved injection, which is why both rows carry the dependency in their comments.
 
 ## Verification
 
-`packages/preset/agent-presets/tests/shipped-root.spec.ts` pins that every preset carrying `tool-ralph` disables it, that the `minimal` roster carries no such row, that `ptc` disables `workflow-worker-thread`, and that `standard` and `cordis` keep the engine enabled for their `workflow` tool. `apps/cli/tests/web-agent-presets.e2e.ts` and `apps/web/tests/shipped-composition.e2e.ts` pin the exact default and PTC tool catalogs, so a row that stops contributing is a test failure rather than a silently shorter list. `scripts/verify-cordis-config.ts` continues to pass its plane-separation check against the disabled rows.
+`packages/preset/agent-presets/tests/shipped-root.spec.ts` pins that every preset carrying `tool-ralph` disables it, that the `minimal` roster carries no such row, that `ptc` disables `workflow-ptc`, and that `standard` and `cordis` keep the engine enabled for their `workflow` tool. `apps/cli/tests/web-agent-presets.e2e.ts` and `apps/web/tests/shipped-composition.e2e.ts` pin the exact default and PTC tool catalogs, so a row that stops contributing is a test failure rather than a silently shorter list. `scripts/verify-cordis-config.ts` continues to pass its plane-separation check against the disabled rows.
 
 Most affected recorded-session sidecars were regenerated with `DSH_SNAPSHOT=refresh pnpm run test:snapshot`, and `pnpm run test:snapshot` replays the corpus. Six sidecars were curated by hand instead, because no local run produces them. Four belong to `pwsh-tool-turn` and `persistent-pwsh-tool-turn`, which this host skips for a missing `pwsh`; their removed text is byte-identical to what the refresh removed elsewhere. The remaining two are `snapshots/web/schedule-catalog`, which no executing test writes or reads: `schedule-after.e2e.ts` reads only that directory's `catalog.expected.md` and `session.v3.jsonl`, and `assertFixtureInventory` checks only that the four files exist, so the `header.pin: true` in its `snapshot.yml` is not enforced. `snapshots/session/ralph-loop` passes under its own composition patch, which is the evidence that the demotion removed the row from the defaults without removing coverage of the tool.

+ 4 - 4
.agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.zh.md

@@ -14,7 +14,7 @@ Status: implemented
 
 `packages/bundle/base/cordis.patch.yml` 把它的 `tool-ralph` 行声明为 `disabled: true`,`standard`、`ptc`、`cordis` 三个 preset 也以同样方式声明各自的 `tool-ralph` 行。`minimal` preset 没有该行。包本身、工具的对外约定和它的测试都保留:这次改的是哪些默认组合挂载该行,而不是该能力是否存在。
 
-`ptc` preset 还额外把 `workflow-worker-thread` 声明为禁用。该 preset 在去掉通用 `workflow` 工具之后只为 `ralph` 保留了这个引擎,因此禁用 `ralph` 使该组合中的引擎不再有消费方。
+`ptc` preset 还额外把 `workflow-ptc` 声明为禁用。该 preset 在去掉通用 `workflow` 工具之后只为 `ralph` 保留了这个引擎,因此禁用 `ralph` 使该组合中的引擎不再有消费方。
 
 每个被禁用的行都在本地注释里带上恢复方法。对基于 base 的档位,用一行 overlay 即可从 `$DSH_HOME/cordis.patch.yml` 或 `--patch` 文件重新启用。preset 文件不接受补丁(`packages/preset/agent-presets/README.md`),因此想要 `ralph` 的 Web 会话要以**新 id** 把 preset 复制到 `$DSH_HOME/.agent-presets` 并删掉 `disabled`;沿用随附 id 的副本会被随附 root 遮蔽,因为重复 id 由更靠前的 root 胜出(`packages/preset/agent-presets/src/index.ts`),而 `copy()` 也拒绝任何 root 已提供的 id。在 `ptc` 中,副本要同时删掉工具行和引擎行的 `disabled`,因为 `tool-ralph` 注入 `ctx.workflowEngine`。
 
@@ -32,7 +32,7 @@ Status: implemented
 
 **连同 goal 工具一起降级。** 两者都推迟了独立评估,用同一把尺子量结果相同。goal 工具是受支持的长时间工作路径——`ralph` 的描述本身就把普通的长时间工作指向它们——并且带有产品界面,因此一起降级会让长时间工作失去受支持的运行方式。
 
-**在 `ptc` 中保留 `workflow-worker-thread` 启用。** 这样在复制出的 `ptc` preset 中删掉 `tool-ralph` 的 `disabled` 只需改一处而不是两处。它会在随附组合里留下一个没有消费方的提供方,而 `packages/AGENTS.md` 拒绝这种做法;改由重述后的注释点明这层依赖。
+**在 `ptc` 中保留 `workflow-ptc` 启用。** 这样在复制出的 `ptc` preset 中删掉 `tool-ralph` 的 `disabled` 只需改一处而不是两处。它会在随附组合里留下一个没有消费方的提供方,而 `packages/AGENTS.md` 拒绝这种做法;改由重述后的注释点明这层依赖。
 
 **把选择启用的方法写进 `docs/`。** 指南页面能触达从不打开组合文件的用户。各平面的恢复方法不同,但每种都只有三行,因此由行内注释在使用点承载。
 
@@ -40,10 +40,10 @@ Status: implemented
 
 默认的 Web、headless、sdk、acp 或自定义基于 base 的会话不再提供 `ralph`,`standard`、`ptc`、`cordis` 三个 preset 也不再提供。要恢复它,用户需要修改组合,因此该能力变成显式选择加入,而不只是被劝阻使用。已经记录了 `ralph` 调用的既有会话仍可回放和渲染:工具包仍然安装,其事件类型未变。
 
-`ptc` 模式同时失去该引擎。复制出的 `ptc` preset 若只恢复 `tool-ralph` 而不恢复 `workflow-worker-thread`,该工具行会留下未解析的注入,这正是两行都在注释里点明这层依赖的原因。
+`ptc` 模式同时失去该引擎。复制出的 `ptc` preset 若只恢复 `tool-ralph` 而不恢复 `workflow-ptc`,该工具行会留下未解析的注入,这正是两行都在注释里点明这层依赖的原因。
 
 ## 验证
 
-`packages/preset/agent-presets/tests/shipped-root.spec.ts` 固定了四点:每个带 `tool-ralph` 的 preset 都禁用它;`minimal` 清单不含该行;`ptc` 禁用 `workflow-worker-thread`;`standard` 与 `cordis` 为各自的 `workflow` 工具保留引擎启用。`apps/cli/tests/web-agent-presets.e2e.ts` 与 `apps/web/tests/shipped-composition.e2e.ts` 固定默认与 PTC 的确切工具目录,因此某一行不再贡献会直接导致测试失败,而不是让列表悄悄变短。`scripts/verify-cordis-config.ts` 的平面隔离检查在禁用行存在的情况下继续通过。
+`packages/preset/agent-presets/tests/shipped-root.spec.ts` 固定了四点:每个带 `tool-ralph` 的 preset 都禁用它;`minimal` 清单不含该行;`ptc` 禁用 `workflow-ptc`;`standard` 与 `cordis` 为各自的 `workflow` 工具保留引擎启用。`apps/cli/tests/web-agent-presets.e2e.ts` 与 `apps/web/tests/shipped-composition.e2e.ts` 固定默认与 PTC 的确切工具目录,因此某一行不再贡献会直接导致测试失败,而不是让列表悄悄变短。`scripts/verify-cordis-config.ts` 的平面隔离检查在禁用行存在的情况下继续通过。
 
 多数受影响的录制会话旁挂文件由 `DSH_SNAPSHOT=refresh pnpm run test:snapshot` 重新生成,并由 `pnpm run test:snapshot` 回放整个语料。另有六个旁挂文件是人工整理,因为本机没有任何一次运行会产出它们。其中四个属于 `pwsh-tool-turn` 与 `persistent-pwsh-tool-turn`:本机缺少 `pwsh`,这两个场景在本地被跳过;它们删掉的文本与刷新在别处删掉的文本逐字节相同。剩下两个是 `snapshots/web/schedule-catalog`,没有任何在执行中的测试写入或读取它们:`schedule-after.e2e.ts` 只读该目录的 `catalog.expected.md` 与 `session.v3.jsonl`,`assertFixtureInventory` 也只检查那四个文件存在,因此它 `snapshot.yml` 里的 `header.pin: true` 并未被强制执行。`snapshots/session/ralph-loop` 在它自己的组合补丁下通过,这证明这次降级把该行移出了默认组合,却没有移除对该工具的覆盖。

+ 2 - 2
.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.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-23-installed-python-wheel-black-box-ci.md
-2026-08-23-installed-python-wheel-black-box-ci.md: 8dfc6d7e344837b9663c1dbe5cff940731f53166
-2026-08-23-installed-python-wheel-black-box-ci.zh.md: 05972560816193ff0b93323e6dcbea3ad5020215
+2026-08-23-installed-python-wheel-black-box-ci.md: a23f7ea1b0157ba4ec050069b9fa9a4ea705bfa7
+2026-08-23-installed-python-wheel-black-box-ci.zh.md: 1751430445c6f42cf37fdf9b6164c19b04f01536

+ 1 - 1
.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md

@@ -18,7 +18,7 @@ The black-box harness rejects a non-venv process, repository-relative working di
 
 ### Keyless behavior
 
-Every target runs the complete packaged-runtime scenario set after installation. A local SSE model keeps outputs deterministic while the public SDK exercises the default SDK profile, ordered patch overlays, external bundle installation through `dsh plugin`, persistent PTY and editor behavior, worker-thread code and workflow execution, ripgrep-backed search, external stdio MCP discovery and execution, model-visible and durable snapshots, Zstandard persistence, direct JSON-RPC, and shutdown. A restart snapshot launches two complete SDK runtime processes against one persistence root and pins their isolated model histories, high-level results, and separate durable logs. The installed run replaces the source-SDK pre-wheel run; the executable and wheel are tested together once rather than maintaining two behavior inventories.
+Every target runs the complete packaged-runtime scenario set after installation. A local SSE model keeps outputs deterministic while the public SDK exercises the default SDK profile, ordered patch overlays, external bundle installation through `dsh plugin`, persistent PTY and editor behavior, PTC Node program and workflow execution, ripgrep-backed search, external stdio MCP discovery and execution, model-visible and durable snapshots, Zstandard persistence, direct JSON-RPC, and shutdown. A restart snapshot launches two complete SDK runtime processes against one persistence root and pins their isolated model histories, high-level results, and separate durable logs. The installed run replaces the source-SDK pre-wheel run; the executable and wheel are tested together once rather than maintaining two behavior inventories.
 
 Linux additionally retains its manylinux 2.28 clean-install smoke and GLIBC checks. macOS retains deployment-target and native helper checks. These platform constraints supplement the common black-box behavior rather than substituting for it.
 

+ 1 - 1
.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.zh.md

@@ -18,7 +18,7 @@ Python SDK 单元测试驱动 fake peer,而打包运行时工作流可以在
 
 ### Keyless 行为
 
-每个目标都会在安装后运行完整的打包运行时场景。一个本地 SSE mock 模型提供确定性输出,公开 SDK 则覆盖默认 SDK profile、有序 patch overlay、通过 `dsh plugin` 安装外部 bundle、持久 PTY 与 editor 行为、worker thread 代码与 workflow 执行、基于 ripgrep 的搜索、外部 stdio MCP 发现与执行、模型可见及持久化快照、Zstandard 持久化、直接 JSON-RPC 与关闭。Restart 快照针对同一持久化根目录启动两个完整 SDK 运行时进程,并固定其彼此隔离的模型历史、高层结果与独立持久日志。安装后运行取代 wheel 构建前的源码 SDK 运行,因此可执行文件与 wheel 包共同接受一次验证,而不是维护两套行为清单。
+每个目标都会在安装后运行完整的打包运行时场景。一个本地 SSE mock 模型提供确定性输出,公开 SDK 则覆盖默认 SDK profile、有序 patch overlay、通过 `dsh plugin` 安装外部 bundle、持久 PTY 与 editor 行为、PTC Node 程序与 workflow 执行、基于 ripgrep 的搜索、外部 stdio MCP 发现与执行、模型可见及持久化快照、Zstandard 持久化、直接 JSON-RPC 与关闭。Restart 快照针对同一持久化根目录启动两个完整 SDK 运行时进程,并固定其彼此隔离的模型历史、高层结果与独立持久日志。安装后运行取代 wheel 构建前的源码 SDK 运行,因此可执行文件与 wheel 包共同接受一次验证,而不是维护两套行为清单。
 
 Linux 另外保留 manylinux 2.28 干净安装冒烟测试与 GLIBC 检查。macOS 保留部署目标与原生 helper 检查。这些平台约束补充共同黑盒行为,不能替代它。
 

+ 4 - 1
.github/workflows/ci.yml

@@ -436,6 +436,9 @@ jobs:
       - name: Install (immutable)
         run: pnpm install --frozen-lockfile
 
+      - name: Prepare bubblewrap (unrestrict userns)
+        run: bash scripts/prepare-ci-bubblewrap.sh
+
       - name: Run compatibility smokes
         env:
           DSH_BUILD_CLIENT_PROFILE: official
@@ -651,7 +654,7 @@ jobs:
           --no-file-parallelism
           --testTimeout 90000
           packages/shell/tool-pwsh/tests/loader.spec.ts
-          packages/workflow/workflow-worker-thread/tests/workflow-worker-thread.spec.ts
+          packages/workflow/workflow-ptc/tests/workflow-ptc.spec.ts
           packages/workflow/tool-ralph/tests/integration.spec.ts
           packages/subprocess/subprocess-local/tests/process-exit.spec.ts
 

+ 6 - 3
apps/cli/composition.md

@@ -136,8 +136,10 @@ flowchart LR
   cfg --> plugin_dsh_base_tool_subagent
   plugin_dsh_base_tool_subagent_fork["tool-subagent-fork<br/>@deepseek-ai/dsh-tool-subagent"]
   cfg --> plugin_dsh_base_tool_subagent_fork
-  plugin_dsh_base_workflow_worker_thread["workflow-worker-thread<br/>@deepseek-ai/dsh-workflow-worker-thread"]
-  cfg --> plugin_dsh_base_workflow_worker_thread
+  plugin_dsh_base_ptc_runtime["ptc-runtime<br/>@deepseek-ai/dsh-ptc-runtime-node"]
+  cfg --> plugin_dsh_base_ptc_runtime
+  plugin_dsh_base_workflow_ptc["workflow-ptc<br/>@deepseek-ai/dsh-workflow-ptc"]
+  cfg --> plugin_dsh_base_workflow_ptc
   plugin_dsh_base_tool_workflow["tool-workflow<br/>@deepseek-ai/dsh-tool-workflow"]
   cfg --> plugin_dsh_base_tool_workflow
   plugin_dsh_base_timeout_policy["timeout-policy<br/>@deepseek-ai/dsh-tool-call-timeout-policy"]
@@ -246,7 +248,8 @@ flowchart LR
 | `tool-subagent-list-agents` | `@deepseek-ai/dsh-tool-subagent-control/list-agents` |
 | `tool-subagent` | `@deepseek-ai/dsh-tool-subagent` |
 | `tool-subagent-fork` | `@deepseek-ai/dsh-tool-subagent` |
-| `workflow-worker-thread` | `@deepseek-ai/dsh-workflow-worker-thread` |
+| `ptc-runtime` | `@deepseek-ai/dsh-ptc-runtime-node` |
+| `workflow-ptc` | `@deepseek-ai/dsh-workflow-ptc` |
 | `tool-workflow` | `@deepseek-ai/dsh-tool-workflow` |
 | `timeout-policy` | `@deepseek-ai/dsh-tool-call-timeout-policy` |
 | `spill-local` | `@deepseek-ai/dsh-spill-local` |

+ 1 - 1
apps/cli/package.json

@@ -94,7 +94,7 @@
     "@deepseek-ai/dsh-web-app": "workspace:^",
     "@deepseek-ai/dsh-webhook": "workspace:^",
     "@deepseek-ai/dsh-webhook-github": "workspace:^",
-    "@deepseek-ai/dsh-workflow-worker-thread": "workspace:^",
+    "@deepseek-ai/dsh-workflow-ptc": "workspace:^",
     "@deepseek-ai/schemastery": "workspace:^",
     "commander": "^15.0.0",
     "js-yaml": "^4.2.0",

+ 2 - 2
docs/capability-seams.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/capability-seams.md
-capability-seams.md: 35f73dfc61da5ffdef2548c26ce828db3dc4acd1
-capability-seams.zh.md: 4bbb93b36288b0ed21eabb2dd6f190d2a6ea4e7e
+capability-seams.md: acc2947f49a915ba27428d869f2404832b41e6e6
+capability-seams.zh.md: 019061bf17f00af8d091104ac15936051f080a1c

+ 5 - 4
docs/capability-seams.md

@@ -177,6 +177,7 @@ flowchart LR
   svc_ptcRuntime["ctx.ptcRuntime<br/>PTC execution seam"]
   pkg_ptc_runtime_node["ptc-runtime-node"]
   pkg_experimental_ptc_runtime_python["experimental-ptc-runtime-python"]
+  pkg_workflow_ptc["workflow-ptc"]
   pkg_fs["fs"]
   svc_fs["ctx.fs<br/>Filesystem provider seam"]
   pkg_fs_local["fs-local"]
@@ -221,7 +222,6 @@ flowchart LR
   svc_clientModules["ctx.clientModules<br/>Client plugin graph host"]
   pkg_workflow["workflow"]
   svc_workflowEngine["ctx.workflowEngine<br/>Workflow script engine"]
-  pkg_workflow_worker_thread["workflow-worker-thread"]
   pkg_tool_workflow["tool-workflow"]
   pkg_webhook["webhook"]
   svc_webhookRuntime["ctx.webhookRuntime<br/>Webhook rule runtime"]
@@ -355,7 +355,7 @@ flowchart LR
   pkg_web_search_perplexity --> svc_web
   pkg_webhook --> svc_webhookRuntime
   pkg_workflow --> svc_workflowEngine
-  pkg_workflow_worker_thread --> svc_workflowEngine
+  pkg_workflow_ptc --> svc_workflowEngine
   pkg_workspace --> svc_workspaceRegistry
   svc_agentDefaultModel --> pkg_api_session_controller
   svc_agentDefaultModel --> pkg_headless
@@ -401,6 +401,7 @@ flowchart LR
   svc_lsp --> pkg_tool_lsp
   svc_mcpResources --> pkg_mcp_resources
   svc_ptcRuntime --> pkg_tools
+  svc_ptcRuntime --> pkg_workflow_ptc
   svc_sandbox --> pkg_bash_sandbox
   svc_sandbox --> pkg_terminal_bash
   svc_sandboxPolicy --> pkg_bash_sandbox
@@ -549,7 +550,7 @@ flowchart LR
 | `ctx.sandboxPolicy` | `core` | [`sandbox-policy`](../packages/sandbox/sandbox-policy) | - | [`bash-sandbox`](../packages/shell/bash-sandbox), [`fs-sandbox`](../packages/fs/fs-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash) | - | The one home for the deployment default mode + workspace root; only the sandboxed executor and provider read the service (the tool layers use the pure `sandbox/mode` fold it also exports). Both enforcing families read it so bash and fs cannot confine to different roots. |
 | `ctx.approval` | `seam` | [`user-approval`](../packages/interaction/user-approval) | - | [`tools`](../packages/core/tools), [`tool-bash`](../packages/shell/tool-bash), [`acp`](../packages/acp/acp) | - | One-shot permission decisions dispatched over the `approval/request` waterfall; answerers are listeners (the ACP bridge for its own agents), absence fails closed to `unavailable`. |
 | `ctx.permissionPresets` | `core` | [`permission-presets`](../packages/interaction/permission-presets) | - | - | - | User-facing preset table (`workspace-write`/`danger-full-access`) bundling the sandbox-mode and approval-policy knobs; a switch writes one `permission/preset` event through to both knob events. |
-| `ctx.ptcRuntime` | `seam` | [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime) | [`ptc-runtime-node`](../packages/ptc-runtime/ptc-runtime-node), [`experimental-ptc-runtime-python`](../packages/experimental/ptc-runtime-python) | [`tools`](../packages/core/tools) | - | Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the tool registry consumes it for PTC mode). |
+| `ctx.ptcRuntime` | `seam` | [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime) | [`ptc-runtime-node`](../packages/ptc-runtime/ptc-runtime-node), [`experimental-ptc-runtime-python`](../packages/experimental/ptc-runtime-python) | [`tools`](../packages/core/tools), [`workflow-ptc`](../packages/workflow/workflow-ptc) | - | Runs programs against host-provided async bindings; tools owns PTC presentation and workflow-ptc owns workflow orchestration. |
 | `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`fs-ssh`](../packages/ssh/fs-ssh) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | tool-fs executes read/write/edit through ctx.fs; fs-sandbox fences mutations by the shared sandbox mode; fs-observation-policy contributes observed-state checks through the fs/* event gate. |
 | `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | The basic backend consumes post-step pressure and request-error recovery events; there is no model-facing compact tool. |
 | `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/workflow/tool-ralph) | - | Providers implement transports; the service also owns optional Activation-based continuation orchestration, tool-subagent selects one-shot or continuable delegation, tool-subagent-control delivers follow-ups, and tool-ralph requires one fresh structured-output route. |
@@ -561,7 +562,7 @@ flowchart LR
 | `ctx.directoryPicker` | `seam` | [`host-directory-picker`](../packages/host/directory-picker) | [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse) | [`api-workspace-controller`](../packages/api/workspace-controller) | - | Discriminated interaction capability: the native backend opens one OS chooser on the host display, the browse backend serves listing/creation primitives for the in-app browser; dual-face backends fill ui-workspace directory-flow slots from their browser halves (no wire advertisement). |
 | `ctx.webServer` | `core` | [`host-webserver`](../packages/host/webserver) | - | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`client-hmr`](../packages/client/hmr) | - | Plain node:http carrier: named-route registry, index transform taps, and the static dist fallback; web-transport plugins register their own routes. |
 | `ctx.clientModules` | `core` | [`client-modules`](../packages/client/modules) | - | [`client-hmr`](../packages/client/hmr) | - | Composes the __DSH_BOOT__ entry graph from an incremental dsh.client scan, serves plugin bundles, and notifies rebuilt/graph-changed subscribers. |
-| `ctx.workflowEngine` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-worker-thread`](../packages/workflow/workflow-worker-thread) | [`tool-workflow`](../packages/workflow/tool-workflow), [`tool-ralph`](../packages/workflow/tool-ralph) | - | One engine per context, as in bash, with no named-provider registry; the general workflow and fixed Ralph consumers start runs whose agent() calls fan out through ctx.subagents. |
+| `ctx.workflowEngine` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-ptc`](../packages/workflow/workflow-ptc) | [`tool-workflow`](../packages/workflow/tool-workflow), [`tool-ralph`](../packages/workflow/tool-ralph) | - | One engine per context, as in bash, with no named-provider registry; the general workflow and fixed Ralph consumers start runs whose agent() calls fan out through ctx.subagents. |
 | `ctx.webhookRuntime` | `core` | [`webhook`](../packages/webhook/webhook) | - | [`webhook-github`](../packages/webhook/webhook-github) | - | Provider adapters dispatch authenticated deliveries; trusted plugins register independent process-local rules, and the runtime turns non-null results into ordinary Workspace-backed Sessions without delivery or completion state. |
 | `ctx.lsp` | `seam` | [`lsp`](../packages/lsp/lsp) | [`lsp-stdio`](../packages/lsp/lsp-stdio) | [`tool-lsp`](../packages/lsp/tool-lsp) | - | Provider registration and selection plus normalized query execution over exactly four operations; the seam offers no protocol escape hatch, so a backend translates into the normalized request and result. |
 | `ctx.dynamicCordisRunner` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | Owns the in-memory definition registry, the vm sandbox for host halves, and the request-run round trip; browser pages reach the same service over the wire through its remote namespace. |

+ 5 - 4
docs/capability-seams.zh.md

@@ -179,6 +179,7 @@ flowchart LR
   svc_ptcRuntime["ctx.ptcRuntime<br/>PTC execution seam"]
   pkg_ptc_runtime_node["ptc-runtime-node"]
   pkg_experimental_ptc_runtime_python["experimental-ptc-runtime-python"]
+  pkg_workflow_ptc["workflow-ptc"]
   pkg_fs["fs"]
   svc_fs["ctx.fs<br/>Filesystem provider seam"]
   pkg_fs_local["fs-local"]
@@ -223,7 +224,6 @@ flowchart LR
   svc_clientModules["ctx.clientModules<br/>Client plugin graph host"]
   pkg_workflow["workflow"]
   svc_workflowEngine["ctx.workflowEngine<br/>Workflow script engine"]
-  pkg_workflow_worker_thread["workflow-worker-thread"]
   pkg_tool_workflow["tool-workflow"]
   pkg_webhook["webhook"]
   svc_webhookRuntime["ctx.webhookRuntime<br/>Webhook rule runtime"]
@@ -357,7 +357,7 @@ flowchart LR
   pkg_web_search_perplexity --> svc_web
   pkg_webhook --> svc_webhookRuntime
   pkg_workflow --> svc_workflowEngine
-  pkg_workflow_worker_thread --> svc_workflowEngine
+  pkg_workflow_ptc --> svc_workflowEngine
   pkg_workspace --> svc_workspaceRegistry
   svc_agentDefaultModel --> pkg_api_session_controller
   svc_agentDefaultModel --> pkg_headless
@@ -403,6 +403,7 @@ flowchart LR
   svc_lsp --> pkg_tool_lsp
   svc_mcpResources --> pkg_mcp_resources
   svc_ptcRuntime --> pkg_tools
+  svc_ptcRuntime --> pkg_workflow_ptc
   svc_sandbox --> pkg_bash_sandbox
   svc_sandbox --> pkg_terminal_bash
   svc_sandboxPolicy --> pkg_bash_sandbox
@@ -551,7 +552,7 @@ flowchart LR
 | `ctx.sandboxPolicy` | `core` | [`sandbox-policy`](../packages/sandbox/sandbox-policy) | - | [`bash-sandbox`](../packages/shell/bash-sandbox), [`fs-sandbox`](../packages/fs/fs-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash) | - | 统一保存部署默认模式和工作区根目录;只有沙箱执行器和提供方读取该服务(工具层使用它同时导出的纯 `sandbox/mode` 折叠区)。两类强制执行组件都读取该服务,因此 bash 与 fs 不会限制到不同的根目录。 |
 | `ctx.approval` | `seam` | [`user-approval`](../packages/interaction/user-approval) | - | [`tools`](../packages/core/tools), [`tool-bash`](../packages/shell/tool-bash), [`acp`](../packages/acp/acp) | - | 一次性权限决策通过 `approval/request` waterfall(瀑布式事件)分派;回答方是监听器(即 ACP 为自身 agent 提供的桥接),没有回答方时以 `unavailable` 关闭失败。 |
 | `ctx.permissionPresets` | `core` | [`permission-presets`](../packages/interaction/permission-presets) | - | - | - | 面向用户的预设表(`workspace-write`/`danger-full-access`),将沙箱模式与审批策略选项组合在一起;一次切换会写入一个 `permission/preset` 事件,并贯通到两个选项事件。 |
-| `ctx.ptcRuntime` | `seam` | [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime) | [`ptc-runtime-node`](../packages/ptc-runtime/ptc-runtime-node), [`experimental-ptc-runtime-python`](../packages/experimental/ptc-runtime-python) | [`tools`](../packages/core/tools) | - | 使用 Host 提供的异步绑定运行一段由模型编写的程序;各后端采用不同的基础环境和语言(工具注册表在 PTC mode 下消费该服务)。 |
+| `ctx.ptcRuntime` | `seam` | [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime) | [`ptc-runtime-node`](../packages/ptc-runtime/ptc-runtime-node), [`experimental-ptc-runtime-python`](../packages/experimental/ptc-runtime-python) | [`tools`](../packages/core/tools), [`workflow-ptc`](../packages/workflow/workflow-ptc) | - | 使用 Host 提供的异步绑定运行程序;tools 负责 PTC 呈现,workflow-ptc 负责工作流编排。 |
 | `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`fs-ssh`](../packages/ssh/fs-ssh) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | tool-fs 通过 ctx.fs 执行读取/写入/编辑;fs-sandbox 按共享沙箱模式限制变更;fs-observation-policy 通过 fs/* 事件门禁贡献基于观测状态的检查。 |
 | `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 基础后端消费步骤后的压力事件和请求错误恢复事件;不存在面向模型的压缩工具。 |
 | `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/workflow/tool-ralph) | - | 提供方实现传输;该服务还负责可选的、基于 Activation 的延续编排,tool-subagent 选择一次性或可延续委派,tool-subagent-control 传递后续消息,而 tool-ralph 要求一条全新的结构化输出路由。 |
@@ -563,7 +564,7 @@ flowchart LR
 | `ctx.directoryPicker` | `seam` | [`host-directory-picker`](../packages/host/directory-picker) | [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse) | [`api-workspace-controller`](../packages/api/workspace-controller) | - | 带判别标记的交互能力:原生后端在 Host 显示设备上打开一个操作系统选择器,浏览后端为应用内浏览器提供列表与创建原语;双端后端通过其浏览器侧填充 ui-workspace 目录流程的 slot(不通过协议发布)。 |
 | `ctx.webServer` | `core` | [`host-webserver`](../packages/host/webserver) | - | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`client-hmr`](../packages/client/hmr) | - | 普通的 node:http 载体:具名路由注册表、索引转换 tap,以及静态 dist 回退;Web 传输插件注册自己的路由。 |
 | `ctx.clientModules` | `core` | [`client-modules`](../packages/client/modules) | - | [`client-hmr`](../packages/client/hmr) | - | 通过增量 `dsh.client` 扫描组合 __DSH_BOOT__ 入口图,提供插件组合包,并通知重建/图变更订阅方。 |
-| `ctx.workflowEngine` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-worker-thread`](../packages/workflow/workflow-worker-thread) | [`tool-workflow`](../packages/workflow/tool-workflow), [`tool-ralph`](../packages/workflow/tool-ralph) | - | 每个上下文使用一个引擎,与 bash 相同,且没有具名提供方注册表;通用工作流与固定 Ralph 消费方启动运行,其中的 agent() 调用通过 ctx.subagents 扇出。 |
+| `ctx.workflowEngine` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-ptc`](../packages/workflow/workflow-ptc) | [`tool-workflow`](../packages/workflow/tool-workflow), [`tool-ralph`](../packages/workflow/tool-ralph) | - | 每个上下文使用一个引擎,与 bash 相同,且没有具名提供方注册表;通用工作流与固定 Ralph 消费方启动运行,其中的 agent() 调用通过 ctx.subagents 扇出。 |
 | `ctx.webhookRuntime` | `core` | [`webhook`](../packages/webhook/webhook) | - | [`webhook-github`](../packages/webhook/webhook-github) | - | 提供方适配器分派已认证交付;可信插件注册独立的进程本地规则,runtime 把非 null 结果转换为普通的 Workspace-backed Session,不保留交付或完成状态。 |
 | `ctx.lsp` | `seam` | [`lsp`](../packages/lsp/lsp) | [`lsp-stdio`](../packages/lsp/lsp-stdio) | [`tool-lsp`](../packages/lsp/tool-lsp) | - | 提供方注册与选择,加上恰好四种操作的标准化查询执行;该 seam 不提供协议逃生口,后端必须转换为标准化请求和结果。 |
 | `ctx.dynamicCordisRunner` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | 拥有内存定义注册表、Host 半的 vm 沙箱和 request-run 往返流程;浏览器页面通过其 Remote 命名空间在线访问同一服务。 |

+ 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: b7564da17dda7ace339eff60a46b8e54762e1d6d
-config-catalog.zh.md: 362c3ff87f6a37ed5020dab4ae64671e29cdcd08
+config-catalog.md: b1cb9db56757db61bd6cdd05ac43dc1c6b7378f4
+config-catalog.zh.md: e1bd6411114e0f82c229d1df9c5844a6ee309a51

+ 6 - 12
docs/config-catalog.md

@@ -1691,7 +1691,7 @@ Requires: `fs` · `subprocess` · `sandbox` · `sandboxPolicy`
 export interface Config extends LaunchConfig {
   /** Default elapsed deadline, including nested tool and approval waits. */
   timeoutMs?: number
-  /** Maximum elapsed deadline accepted by resolve. */
+  /** Maximum numeric elapsed budget accepted by resolve. */
   maxTimeoutMs?: number
   /** Combined serialized logs, completion and diagnostic byte cap. */
   maxOutputBytes?: number
@@ -3448,11 +3448,11 @@ export interface Config {
 
 Source: [`packages/webhook/webhook-github/src/index.ts:17`](../packages/webhook/webhook-github/src/index.ts)
 
-<a id="deepseek-aidsh-workflow-worker-thread"></a>
+<a id="deepseek-aidsh-workflow-ptc"></a>
 
-## `@deepseek-ai/dsh-workflow-worker-thread`
+## `@deepseek-ai/dsh-workflow-ptc`
 
-Requires: `subagents`
+Requires: `subagents` · `ptcRuntime` · `sandboxPolicy`
 
 ```ts config-catalog
 /** Plugin config (all optional — `static Config` supplies the defaults). */
@@ -3465,18 +3465,12 @@ export interface Config {
   maxTotalAgents?: number
   /** Items accepted by a single `parallel()`/`pipeline()` call (default 4096). */
   maxItemsPerCall?: number
-  /** vm timeout for the script's initial synchronous slice, inside the worker (default 5000 ms). */
+  /** VM timeout for the script's initial synchronous slice (default 5000 ms). */
   syncTimeoutMs?: number
-  /**
-   * How long after a cancellation an unsettled script may keep running before
-   * the run force-settles `cancelled` and its worker is TERMINATED (default
-   * 5000 ms); also bounds `dispose()`.
-   */
-  disposeGraceMs?: number
 }
 ```
 
-Source: [`packages/workflow/workflow-worker-thread/src/index.ts:32`](../packages/workflow/workflow-worker-thread/src/index.ts)
+Source: [`packages/workflow/workflow-ptc/src/index.ts:32`](../packages/workflow/workflow-ptc/src/index.ts)
 
 ## Loadable plugins with no config
 

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

@@ -1693,7 +1693,7 @@ export interface Config {
 export interface Config extends LaunchConfig {
   /** Default elapsed deadline, including nested tool and approval waits. */
   timeoutMs?: number
-  /** Maximum elapsed deadline accepted by resolve. */
+  /** Maximum numeric elapsed budget accepted by resolve. */
   maxTimeoutMs?: number
   /** Combined serialized logs, completion and diagnostic byte cap. */
   maxOutputBytes?: number
@@ -1716,7 +1716,7 @@ export interface LaunchConfig {
 }
 ```
 
-来源:[`packages/ptc-runtime/ptc-runtime-node/src/index.ts:26`](../packages/ptc-runtime/ptc-runtime-node/src/index.ts)
+来源: [`packages/ptc-runtime/ptc-runtime-node/src/index.ts:26`](../packages/ptc-runtime/ptc-runtime-node/src/index.ts)
 
 <a id="deepseek-aidsh-pwsh-local"></a>
 
@@ -3450,11 +3450,11 @@ export interface Config {
 
 来源:[`packages/webhook/webhook-github/src/index.ts:17`](../packages/webhook/webhook-github/src/index.ts)
 
-<a id="deepseek-aidsh-workflow-worker-thread"></a>
+<a id="deepseek-aidsh-workflow-ptc"></a>
 
-## `@deepseek-ai/dsh-workflow-worker-thread`
+## `@deepseek-ai/dsh-workflow-ptc`
 
-需要:`subagents`
+需要: `subagents` · `ptcRuntime` · `sandboxPolicy`
 
 ```ts config-catalog
 /** Plugin config (all optional — `static Config` supplies the defaults). */
@@ -3467,18 +3467,12 @@ export interface Config {
   maxTotalAgents?: number
   /** Items accepted by a single `parallel()`/`pipeline()` call (default 4096). */
   maxItemsPerCall?: number
-  /** vm timeout for the script's initial synchronous slice, inside the worker (default 5000 ms). */
+  /** VM timeout for the script's initial synchronous slice (default 5000 ms). */
   syncTimeoutMs?: number
-  /**
-   * How long after a cancellation an unsettled script may keep running before
-   * the run force-settles `cancelled` and its worker is TERMINATED (default
-   * 5000 ms); also bounds `dispose()`.
-   */
-  disposeGraceMs?: number
 }
 ```
 
-来源:[`packages/workflow/workflow-worker-thread/src/index.ts:32`](../packages/workflow/workflow-worker-thread/src/index.ts)
+来源: [`packages/workflow/workflow-ptc/src/index.ts:32`](../packages/workflow/workflow-ptc/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: a92b3f1ff5dbbdde717aea812b65a729f10d44c4
-extension-cookbook.zh.md: 23d21084c8e9ac375a482e151edd4f89f1f0c7ea
+extension-cookbook.md: 40d91dbe698a2f2801503a5278c885d6b7a39ed5
+extension-cookbook.zh.md: 149817dec36c5a625e417e9cc1e2879a012682d6

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

@@ -106,7 +106,7 @@ Every product feature maps to a listener on a documented extension point — the
 | Hook system (user + project level) | listeners on `agent/session-start`, `agent/pre-step`, `agent/request`, `tools/pre-execute`, `tools/post-execute`, and `agent/turn-stopping`; the waterfalls return typed decisions, while `agent/turn-stopping` may steer another step; the `dsh-hooks-claude-code` / `dsh-hooks-codex` bridges map hook config files onto these extension points |
 | `/goal` | `ctx.goals` owns durable state, `dsh-goal-round-driver` schedules same-session rounds through the public `Agent`, and separate command/tool producers expose human/model control |
 | `/loop` | on the `turn/end` session event, `followup()` the next iteration; or force-continue |
-| Dynamic workflow | `ctx.workflowEngine` + the worker-thread engine + the `workflow` tool; structured in-process children enforce output with scoped prompt/tool registrations, a monotonic tool guard, final `tools/result` commit (including enclosing `run_code`), and the structured-output execution's monotonic `concludeTurn()` marker |
+| Dynamic workflow | `ctx.workflowEngine` + the PTC workflow engine + the `workflow` tool; structured in-process children enforce output with scoped prompt/tool registrations, a monotonic tool guard, final `tools/result` commit (including enclosing `run_code`), and the structured-output execution's monotonic `concludeTurn()` marker |
 | Queued + steering messages | core `Agent.followup()` / `Agent.steer()` |
 | Context compaction (auto + manual) | the `ctx.compaction` seam + `dsh-compaction-basic`; automatic pressure runs on serial `agent/pre-step`, canonical overflow recovery runs on `agent/request-error`, and manual callers use the same compact service ([compaction Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md)) |
 | System prompt configurability | `ctx.systemPrompt.section()` with ordering and scope-local shadowing |

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

@@ -110,7 +110,7 @@ export function apply(ctx: Context) {
 | 钩子系统(用户级 + 项目级) | `agent/session-start`、`agent/pre-step`、`agent/request`、`tools/pre-execute`、`tools/post-execute` 和 `agent/turn-stopping` 上的监听器;waterfall 返回类型化决策,`agent/turn-stopping` 则可通过 steering(中途引导)触发下一步;`dsh-hooks-claude-code` / `dsh-hooks-codex` 桥接器将钩子配置文件映射到这些扩展点上 |
 | `/goal` | `ctx.goals` 管理持久状态,`dsh-goal-round-driver` 通过公共 `Agent` 调度同会话 Round,独立的命令/工具生产方分别提供人类/模型控制 |
 | `/loop` | 在 `turn/end` 会话事件上 `followup()` 下一次迭代;或强制继续 |
-| 动态工作流 | `ctx.workflowEngine` + worker-thread 引擎 + `workflow` 工具;结构化的进程内子任务通过作用域化的提示词/工具注册、单调工具守卫、最终 `tools/result` 提交(包括外层 `run_code`)和结构化输出执行的单调 `concludeTurn()` 标记来强制输出 |
+| 动态工作流 | `ctx.workflowEngine` + PTC 工作流引擎 + `workflow` 工具;结构化的进程内子任务通过作用域化的提示词/工具注册、单调工具守卫、最终 `tools/result` 提交(包括外层 `run_code`)和结构化输出执行的单调 `concludeTurn()` 标记来强制输出 |
 | 排队消息 + steering | 核心 `Agent.followup()` / `Agent.steer()` |
 | 上下文压缩(context compaction)(自动 + 手动) | `ctx.compaction` seam + `dsh-compaction-basic`;自动压力检查运行在串行 `agent/pre-step`,标准的溢出恢复机制运行在 `agent/request-error`,手动调用方使用同一个压缩服务([压缩 Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.zh.md)) |
 | 系统提示词可配置性 | `ctx.systemPrompt.section()`,支持排序与作用域局部覆盖 |

+ 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: b189e217beb5e9f60cb9e00237dc13f9fa5596ff
-module-graph.zh.md: b2d479ddf52ad07f322badf105f7a253a6df544b
+module-graph.md: ee9b15ebe1576e689cd11d76b296125a47597000
+module-graph.zh.md: 80d6777d4d79fd41afa66575c3af038d8033133d

+ 11 - 8
docs/module-graph.md

@@ -378,7 +378,7 @@ flowchart TD
     pkg_tool_ralph["tool-ralph"]
     pkg_tool_workflow["tool-workflow"]
     pkg_workflow["workflow"]
-    pkg_workflow_worker_thread["workflow-worker-thread"]
+    pkg_workflow_ptc["workflow-ptc"]
   end
   subgraph group_workspace["packages/workspace"]
     pkg_workspace["workspace"]
@@ -1168,12 +1168,15 @@ flowchart TD
   pkg_tool_ralph --> pkg_system_prompt
   pkg_tool_ralph --> pkg_tools
   pkg_tool_ralph --> pkg_workflow
-  pkg_workflow_worker_thread --> pkg_agent
-  pkg_workflow_worker_thread --> pkg_llm
-  pkg_workflow_worker_thread --> pkg_session
-  pkg_workflow_worker_thread --> pkg_subagent
-  pkg_workflow_worker_thread --> pkg_tools
-  pkg_workflow_worker_thread --> pkg_workflow
+  pkg_workflow_ptc --> pkg_agent
+  pkg_workflow_ptc --> pkg_llm
+  pkg_workflow_ptc --> pkg_ptc_runtime
+  pkg_workflow_ptc --> pkg_sandbox
+  pkg_workflow_ptc --> pkg_sandbox_policy
+  pkg_workflow_ptc --> pkg_session
+  pkg_workflow_ptc --> pkg_subagent
+  pkg_workflow_ptc --> pkg_tools
+  pkg_workflow_ptc --> pkg_workflow
   pkg_subagent_fork_in_process --> pkg_agent
   pkg_subagent_fork_in_process --> pkg_session
   pkg_subagent_fork_in_process --> pkg_subagent
@@ -1501,7 +1504,7 @@ flowchart TD
 | [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) |
 | [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
 | [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
-| [`workflow-worker-thread`](../packages/workflow/workflow-worker-thread) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
+| [`workflow-ptc`](../packages/workflow/workflow-ptc) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
 | [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process) | `subagent` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) |
 | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process) | `subagent` | [`subagent`](../packages/subagent/subagent), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) |
 | [`experimental-client-ui-agent-team`](../packages/experimental/client-ui-agent-team) | `experimental` | [`api-remotes`](../packages/api/remotes), [`api-session-controller`](../packages/api/session-controller), [`client-locale`](../packages/client/locale), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-slots`](../packages/client/ui-slots), [`experimental-agent-team`](../packages/experimental/agent-team), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) |

+ 11 - 8
docs/module-graph.zh.md

@@ -380,7 +380,7 @@ flowchart TD
     pkg_tool_ralph["tool-ralph"]
     pkg_tool_workflow["tool-workflow"]
     pkg_workflow["workflow"]
-    pkg_workflow_worker_thread["workflow-worker-thread"]
+    pkg_workflow_ptc["workflow-ptc"]
   end
   subgraph group_workspace["packages/workspace"]
     pkg_workspace["workspace"]
@@ -1170,12 +1170,15 @@ flowchart TD
   pkg_tool_ralph --> pkg_system_prompt
   pkg_tool_ralph --> pkg_tools
   pkg_tool_ralph --> pkg_workflow
-  pkg_workflow_worker_thread --> pkg_agent
-  pkg_workflow_worker_thread --> pkg_llm
-  pkg_workflow_worker_thread --> pkg_session
-  pkg_workflow_worker_thread --> pkg_subagent
-  pkg_workflow_worker_thread --> pkg_tools
-  pkg_workflow_worker_thread --> pkg_workflow
+  pkg_workflow_ptc --> pkg_agent
+  pkg_workflow_ptc --> pkg_llm
+  pkg_workflow_ptc --> pkg_ptc_runtime
+  pkg_workflow_ptc --> pkg_sandbox
+  pkg_workflow_ptc --> pkg_sandbox_policy
+  pkg_workflow_ptc --> pkg_session
+  pkg_workflow_ptc --> pkg_subagent
+  pkg_workflow_ptc --> pkg_tools
+  pkg_workflow_ptc --> pkg_workflow
   pkg_subagent_fork_in_process --> pkg_agent
   pkg_subagent_fork_in_process --> pkg_session
   pkg_subagent_fork_in_process --> pkg_subagent
@@ -1503,7 +1506,7 @@ flowchart TD
 | [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) |
 | [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
 | [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
-| [`workflow-worker-thread`](../packages/workflow/workflow-worker-thread) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
+| [`workflow-ptc`](../packages/workflow/workflow-ptc) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
 | [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process) | `subagent` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) |
 | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process) | `subagent` | [`subagent`](../packages/subagent/subagent), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) |
 | [`experimental-client-ui-agent-team`](../packages/experimental/client-ui-agent-team) | `experimental` | [`api-remotes`](../packages/api/remotes), [`api-session-controller`](../packages/api/session-controller), [`client-locale`](../packages/client/locale), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-slots`](../packages/client/ui-slots), [`experimental-agent-team`](../packages/experimental/agent-team), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) |

+ 2 - 2
docs/subsystems/ptc-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 docs/subsystems/ptc-runtime.md
-ptc-runtime.md: 98032c564954bdae3f8f940b6d6246d36560ca0c
-ptc-runtime.zh.md: 15e64cace278b9ddea541f5d821785aba2c1ace1
+ptc-runtime.md: efab5bcefa0aaec50c29c73f981f6a6b717cd087
+ptc-runtime.zh.md: f35c5bd84d79ddaccc40653707d792f5672e73d9

+ 9 - 6
docs/subsystems/ptc-runtime.md

@@ -8,7 +8,7 @@ Source: [`packages/ptc-runtime/ptc-runtime/src/types.ts`](../../packages/ptc-run
 
 ## The run: request in, result out
 
-`PtcRunRequest` contains the program, bindings, cancellation and optional execution choices. The provider's `resolve` validates supported choices and applies its deployment defaults; `run` receives a `PtcRunSpec` with an explicit directory and deadline. A provider that cannot enforce a requested policy rejects it before program execution:
+`PtcRunRequest` contains the program, bindings, cancellation and optional execution choices. The provider's `resolve` validates supported choices and applies its deployment defaults; `run` receives a `PtcRunSpec` with an explicit directory and deadline choice. An omitted timeout uses provider defaults, a number requests a capped elapsed budget, and `null` requests no elapsed deadline. Providers reject unsupported choices before execution:
 
 ```ts type-equiv
 /**
@@ -27,8 +27,11 @@ interface PtcRunRequest {
   bindings: PtcBindingNamespace[]
   /** Working directory in the mounted filesystem and subprocess execution world. */
   cwd?: string
-  /** Requested elapsed execution time; the provider's resolver validates and caps it. */
-  timeoutMs?: number
+  /**
+   * Elapsed execution budget in milliseconds. Omission uses provider defaults;
+   * null requests no deadline. Providers validate and cap numeric budgets or reject unsupported choices.
+   */
+  timeoutMs?: number | null
   /** Resolved authority for this execution. Providers without confinement reject an explicit policy. */
   sandboxPolicy?: SandboxExecutionPolicy
   /**
@@ -41,12 +44,12 @@ interface PtcRunRequest {
 ```
 
 ```ts type-equiv
-/** Fully resolved execution inputs; run never supplies a missing directory or timeout. */
+/** Fully resolved execution inputs; run never supplies a missing directory or deadline choice. */
 interface PtcRunSpec extends PtcRunRequest {
   /** Absolute directory in the provider's execution world. */
   cwd: string
-  /** Positive finite execution deadline in milliseconds, after provider capping. */
-  timeoutMs: number
+  /** Positive finite elapsed budget in milliseconds after provider capping, or null for no deadline. */
+  timeoutMs: number | null
 }
 ```
 

+ 9 - 6
docs/subsystems/ptc-runtime.zh.md

@@ -8,7 +8,7 @@ PTC 执行[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-
 
 ## 运行:请求进,结果出
 
-`PtcRunRequest` 包含程序、绑定、取消和可选执行选择。提供方的 `resolve` 验证支持的选择并应用部署默认值;`run` 接收目录与截止时间明确的 `PtcRunSpec`。无法强制执行所请求策略的提供方在程序执行前拒绝请求
+`PtcRunRequest` 包含程序、绑定、取消和可选执行选择。提供方的 `resolve` 验证支持的选择并应用部署默认值;`run` 接收目录与截止选择明确的 `PtcRunSpec`。省略 timeout 使用提供方默认值,数值请求封顶的经过时间预算,`null` 请求不设经过时间截止。提供方在执行前拒绝不支持的选择
 
 ```ts type-equiv
 /**
@@ -27,8 +27,11 @@ interface PtcRunRequest {
   bindings: PtcBindingNamespace[]
   /** Working directory in the mounted filesystem and subprocess execution world. */
   cwd?: string
-  /** Requested elapsed execution time; the provider's resolver validates and caps it. */
-  timeoutMs?: number
+  /**
+   * Elapsed execution budget in milliseconds. Omission uses provider defaults;
+   * null requests no deadline. Providers validate and cap numeric budgets or reject unsupported choices.
+   */
+  timeoutMs?: number | null
   /** Resolved authority for this execution. Providers without confinement reject an explicit policy. */
   sandboxPolicy?: SandboxExecutionPolicy
   /**
@@ -41,12 +44,12 @@ interface PtcRunRequest {
 ```
 
 ```ts type-equiv
-/** Fully resolved execution inputs; run never supplies a missing directory or timeout. */
+/** Fully resolved execution inputs; run never supplies a missing directory or deadline choice. */
 interface PtcRunSpec extends PtcRunRequest {
   /** Absolute directory in the provider's execution world. */
   cwd: string
-  /** Positive finite execution deadline in milliseconds, after provider capping. */
-  timeoutMs: number
+  /** Positive finite elapsed budget in milliseconds after provider capping, or null for no deadline. */
+  timeoutMs: number | null
 }
 ```
 

+ 2 - 2
docs/subsystems/workflow.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/subsystems/workflow.md
-workflow.md: b608efab2300b20b06a3aa09041c3419fbc2145b
-workflow.zh.md: 47186df724b10138f277302384b3e96a0667be46
+workflow.md: 8f56774699eebd5f0a287aee3fcd08eef82ec045
+workflow.zh.md: dd24f920f6e04ba9c8721235919962ce15f84a82

+ 8 - 8
docs/subsystems/workflow.md

@@ -4,7 +4,7 @@ English | [中文](workflow.zh.md)
 
 The workflow seam lets an agent run a model-written orchestration SCRIPT that starts subagents. Like [subagent](subagent.md) it is **one optional capability**, not part of the agent loop, so its types and operations live here rather than in [core.md](core.md). Like bash, it permits ONE engine implementation per context to provide `ctx.workflowEngine`; there is no named-provider registry (a second engine replaces the first through plugin configuration rather than running beside it).
 
-Service Definition: [dsh-workflow](../../packages/workflow/workflow) (`ctx.workflowEngine` + the vocabulary below). The Service Provider is [dsh-workflow-worker-thread](../../packages/workflow/workflow-worker-thread) (a `node:worker_threads` engine — one worker per run, the script's vm context inside it); the model-facing Consumer is [dsh-tool-workflow](../../packages/workflow/tool-workflow). The proposal and rationale: [the dynamic-workflows Agent Note](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md).
+Service Definition: [dsh-workflow](../../packages/workflow/workflow) (`ctx.workflowEngine` and the vocabulary below). [dsh-workflow-ptc](../../packages/workflow/workflow-ptc) executes the VM and helpers through the shared Node PTC process runtime under the calling Session's file policy. The consumers are [dsh-tool-workflow](../../packages/workflow/tool-workflow) and the opt-in [dsh-tool-ralph](../../packages/workflow/tool-ralph). [Workflow sandbox reuse](../../.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.md) owns execution choices; the [dynamic-workflows decision](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md) owns script semantics.
 
 Sources: browser-safe vocabulary in [`packages/workflow/workflow/src/types.ts`](../../packages/workflow/workflow/src/types.ts), Host request and live-run handles in [`runtime-types.ts`](../../packages/workflow/workflow/src/runtime-types.ts).
 
@@ -82,8 +82,8 @@ interface WorkflowResult {
   /**
    * How many `agent()` calls the run accepted over its whole lifetime. On a
    * graceful settlement this is the script-side count (calls still queued for
-   * a concurrency slot included); on a termination path (grace force-settle,
-   * worker death) it degrades to the host-observed count — calls queued
+   * a concurrency slot included); on a termination path (cancellation or
+   * process failure) it degrades to the host-observed count — calls queued
    * inside a terminated script are unknowable then.
    */
   agentsStarted: number
@@ -92,7 +92,7 @@ interface WorkflowResult {
 
 ## A live run: `WorkflowRun`
 
-The handle the consumer holds while a script executes. The consumer awaits `result`, may `cancel` mid-flight, and MUST `dispose` on every path. `result` does NOT reject — a script failure resolves with `stopReason: 'error'` — and once the run is cancelled it SETTLES within the engine's bounded grace even if the script itself never settles (the engine force-settles `cancelled`; the worker-thread engine then terminates the script's worker), so a consumer awaiting `result` is never wedged past a cancellation. `dispose()` = cancel + that bounded settle + child quiescence; it never hangs on a stuck script.
+The consumer awaits `result`, may `cancel` during execution, and must `dispose` on every path. `result` never rejects: script failure resolves with `stopReason: 'error'`, and cancellation with `'cancelled'`. The PTC engine has no overall elapsed deadline; it immediately aborts the managed process when cancelled. Disposal awaits process and child cleanup under their provider contracts, without an independent workflow cleanup deadline.
 
 ```ts type-equiv
 /**
@@ -106,7 +106,7 @@ interface WorkflowRun {
   readonly result: Promise<WorkflowResult>
   /** Cancel the run and its children. */
   cancel(reason?: string): void
-  /** Cancel if needed and await bounded settlement and cleanup. */
+  /** Cancel if needed and await script and child cleanup. */
   dispose(): Promise<void>
 }
 ```
@@ -139,7 +139,7 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp
 
 ### `ctx.workflowEngine` — `WorkflowEngine` (abstract seam)
 
-Workflow Service Definition contract. Invalid requests throw before publication; a live run is holder-owned, its result never rejects, cancellation and disposal are bounded, and disposal waits for child cleanup within that bound. Lifecycle listener failures are contained, and `workflow/end` fires exactly once as the result settles.
+Workflow Service Definition contract. Invalid requests throw before publication; a live run is holder-owned, its result never rejects, and disposal waits for script and child cleanup. Lifecycle listener failures are contained, and `workflow/end` fires exactly once as the result settles.
 
 ```ts cordis-catalog
 /**
@@ -161,14 +161,14 @@ Source: [`packages/workflow/workflow/src/index.ts`](../../packages/workflow/work
 
 #### `workflow/agent-end` — emit
 
-One `agent()` call settled (clean result, child failure, or run cancellation). Paired with Events['workflow/agent-start'] by `agent.seq`, exactly once per started call on every stop path — on an engine termination path (a worker killed past its grace) the end is engine-synthesized with outcome `'cancelled'`.
+One `agent()` call settled (clean result, child failure, or run cancellation). Paired with Events['workflow/agent-start'] by `agent.seq`, exactly once per started call on every stop path — on an engine termination path the end is engine-synthesized with outcome `'cancelled'`.
 
 ```ts cordis-catalog
 /**
  * One `agent()` call settled (clean result, child failure, or run
  * cancellation). Paired with {@link Events['workflow/agent-start']} by
  * `agent.seq`, exactly once per started call on every stop path — on an
- * engine termination path (a worker killed past its grace) the end is
+ * engine termination path the end is
  * engine-synthesized with outcome `'cancelled'`.
  * @param info - the run's identity snapshot.
  * @param agent - the call identity plus its outcome.

+ 8 - 8
docs/subsystems/workflow.zh.md

@@ -4,7 +4,7 @@
 
 工作流 seam 允许 agent(智能体)运行由模型编写、会启动 subagent 的编排脚本。与 [subagent](subagent.zh.md) 一样,它是**一项可选能力**,不属于 agent loop,因此其类型和操作记录在此处,而非 [core.md](core.zh.md)。与 bash 一样,每个上下文只允许一个引擎实现提供 `ctx.workflowEngine`;没有命名提供方注册表(第二个引擎通过插件配置替换第一个,而不与它同时运行)。
 
-Service Definition:[dsh-workflow](../../packages/workflow/workflow)(`ctx.workflowEngine` + 下文词汇)。Service Provider 是 [dsh-workflow-worker-thread](../../packages/workflow/workflow-worker-thread)(一个 `node:worker_threads` 引擎——每个 run 一个 worker,脚本的 vm 上下文位于其中);面向模型的 Consumer 是 [dsh-tool-workflow](../../packages/workflow/tool-workflow)。提案与设计理由见 [dynamic-workflows Agent Note](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.zh.md)。
+Service Definition:[dsh-workflow](../../packages/workflow/workflow)(`ctx.workflowEngine` 和下文词汇)。[dsh-workflow-ptc](../../packages/workflow/workflow-ptc)通过共享 Node PTC 进程运行时按调用 Session 的文件策略执行 VM 与辅助函数。消费方为 [dsh-tool-workflow](../../packages/workflow/tool-workflow) 和需显式启用的 [dsh-tool-ralph](../../packages/workflow/tool-ralph)。[工作流沙箱复用](../../.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.zh.md)负责执行选择;[动态工作流决策](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.zh.md)负责脚本语义
 
 源码:浏览器安全词汇位于 [`packages/workflow/workflow/src/types.ts`](../../packages/workflow/workflow/src/types.ts),Host 请求与活跃运行句柄位于 [`runtime-types.ts`](../../packages/workflow/workflow/src/runtime-types.ts)。
 
@@ -82,8 +82,8 @@ interface WorkflowResult {
   /**
    * How many `agent()` calls the run accepted over its whole lifetime. On a
    * graceful settlement this is the script-side count (calls still queued for
-   * a concurrency slot included); on a termination path (grace force-settle,
-   * worker death) it degrades to the host-observed count — calls queued
+   * a concurrency slot included); on a termination path (cancellation or
+   * process failure) it degrades to the host-observed count — calls queued
    * inside a terminated script are unknowable then.
    */
   agentsStarted: number
@@ -92,7 +92,7 @@ interface WorkflowResult {
 
 ## 活跃运行:`WorkflowRun`
 
-脚本执行期间消费方持有的句柄。消费方会等待 `result`,可以在运行期间调用 `cancel`,并且必须在每条路径上调用 `dispose`(资源释放)。`result` 不会被拒绝:脚本失败会兑现为 `stopReason: 'error'`。运行被取消后,即使脚本本身永不结算,结果也会在引擎规定的有界宽限期内结算;引擎会强制将其结算为 `cancelled`,随后 worker-thread 引擎会终止脚本所在的 worker。因此,等待 `result` 的消费方不会在取消后无限期挂起。`dispose()` 会执行取消、等待有界结算并等待子 agent 完全停稳,不会因脚本卡死而挂起
+消费方等待 `result`,可以在执行期间调用 `cancel`,且必须在每条路径上调用 `dispose`(资源释放)。`result` 绝不拒绝:脚本失败以 `stopReason: 'error'` 兑现,取消以 `'cancelled'` 兑现。PTC 引擎没有整体经过时间截止;取消时立即中止受管进程。资源释放按照各提供方约定等待进程与子 agent 清理,不另设工作流清理截止
 
 ```ts type-equiv
 /**
@@ -106,7 +106,7 @@ interface WorkflowRun {
   readonly result: Promise<WorkflowResult>
   /** Cancel the run and its children. */
   cancel(reason?: string): void
-  /** Cancel if needed and await bounded settlement and cleanup. */
+  /** Cancel if needed and await script and child cleanup. */
   dispose(): Promise<void>
 }
 ```
@@ -139,7 +139,7 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp
 
 ### `ctx.workflowEngine` — `WorkflowEngine` (abstract seam)
 
-Workflow Service Definition contract. Invalid requests throw before publication; a live run is holder-owned, its result never rejects, cancellation and disposal are bounded, and disposal waits for child cleanup within that bound. Lifecycle listener failures are contained, and `workflow/end` fires exactly once as the result settles.
+Workflow Service Definition contract. Invalid requests throw before publication; a live run is holder-owned, its result never rejects, and disposal waits for script and child cleanup. Lifecycle listener failures are contained, and `workflow/end` fires exactly once as the result settles.
 
 ```ts cordis-catalog
 /**
@@ -161,14 +161,14 @@ Source: [`packages/workflow/workflow/src/index.ts`](../../packages/workflow/work
 
 #### `workflow/agent-end` — emit
 
-One `agent()` call settled (clean result, child failure, or run cancellation). Paired with Events['workflow/agent-start'] by `agent.seq`, exactly once per started call on every stop path — on an engine termination path (a worker killed past its grace) the end is engine-synthesized with outcome `'cancelled'`.
+One `agent()` call settled (clean result, child failure, or run cancellation). Paired with Events['workflow/agent-start'] by `agent.seq`, exactly once per started call on every stop path — on an engine termination path the end is engine-synthesized with outcome `'cancelled'`.
 
 ```ts cordis-catalog
 /**
  * One `agent()` call settled (clean result, child failure, or run
  * cancellation). Paired with {@link Events['workflow/agent-start']} by
  * `agent.seq`, exactly once per started call on every stop path — on an
- * engine termination path (a worker killed past its grace) the end is
+ * engine termination path the end is
  * engine-synthesized with outcome `'cancelled'`.
  * @param info - the run's identity snapshot.
  * @param agent - the call identity plus its outcome.

+ 2 - 0
package.json

@@ -153,6 +153,8 @@
     "verify-client-catalog": "tsx scripts/gen-client-catalog.ts --check",
     "verify-export-jsdoc": "tsx scripts/verify-export-jsdoc.ts",
     "gen-tool-catalog": "tsx scripts/gen-tool-catalog.ts",
+    "gen-workflow-guest": "tsx scripts/gen-workflow-guest.ts",
+    "verify-workflow-guest": "tsx scripts/gen-workflow-guest.ts --check",
     "verify-tool-catalog": "tsx scripts/gen-tool-catalog.ts --check",
     "gen-config-catalog": "tsx scripts/gen-config-catalog.ts",
     "verify-config-catalog": "tsx scripts/gen-config-catalog.ts --check",

+ 2 - 2
packages/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/README.md
-README.md: b15d9b11822772702b92fac9be24afdbdd885785
-README.zh.md: 1f572953fffc2bbd7a55a9417ee179e43b64f300
+README.md: 433911f1dceaa9acccd99d0082acde20f73b15c9
+README.zh.md: 96d37732b1e658b07955a60ab470bd6596b3079e

+ 1 - 1
packages/README.md

@@ -51,7 +51,7 @@ Every package lives in exactly one group; new packages join existing groups, and
 | [`subagent/`](subagent/README.md) | Subagent capability family: provider-registry contract and model-facing delegation tools |
 | [`jobs/`](jobs/README.md) | Generic background-job runtime and model-facing job control tools |
 | [`experimental/`](experimental/README.md) | Pre-stable prototypes with explicit private exceptions |
-| [`workflow/`](workflow/README.md) | Workflow seam, worker-thread engine, and model-facing `workflow`/`ralph` tools |
+| [`workflow/`](workflow/README.md) | Workflow seam, PTC process engine, and model-facing `workflow`/`ralph` tools |
 | [`webhook/`](webhook/README.md) | Verified external events, trusted rules, and fire-and-forget Workspace Sessions |
 | [`web/`](web/README.md) | Web capability family: seam, search/fetch providers, model-facing web tools |
 | [`attachment/`](attachment/README.md) | Durable attachment identity, validation, local content-addressed storage |

+ 1 - 1
packages/README.zh.md

@@ -51,7 +51,7 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会
 | [`subagent/`](subagent/README.zh.md) | subagent 能力系列:提供方注册表约定和面向模型的委托工具 |
 | [`jobs/`](jobs/README.zh.md) | 通用后台任务运行时和面向模型的作业控制工具 |
 | [`experimental/`](experimental/README.zh.md) | 预稳定原型,包含显式私有例外 |
-| [`workflow/`](workflow/README.zh.md) | 工作流 seam、worker 线程引擎、面向模型的 `workflow`/`ralph` 工具 |
+| [`workflow/`](workflow/README.zh.md) | 工作流 seam、PTC 进程引擎、面向模型的 `workflow`/`ralph` 工具 |
 | [`webhook/`](webhook/README.zh.md) | 已验证外部事件、受信规则与即发即弃 Workspace 会话 |
 | [`web/`](web/README.zh.md) | Web 能力系列:seam、搜索/获取提供方、面向模型的 Web 工具 |
 | [`attachment/`](attachment/README.zh.md) | 持久附件标识、校验、本地内容寻址存储 |

+ 5 - 2
packages/bundle/base/cordis.patch.yml

@@ -366,8 +366,11 @@
         toolName: subagent_fork
         backgroundMode: one-shot
 
-    - id: workflow-worker-thread
-      name: '@deepseek-ai/dsh-workflow-worker-thread'
+    - id: ptc-runtime
+      name: '@deepseek-ai/dsh-ptc-runtime-node'
+
+    - id: workflow-ptc
+      name: '@deepseek-ai/dsh-workflow-ptc'
       config:
         provider: spawn
 

+ 3 - 2
packages/bundle/base/package.json

@@ -117,8 +117,9 @@
     "@deepseek-ai/dsh-web": "workspace:^",
     "@deepseek-ai/dsh-web-fetch-http": "workspace:^",
     "@deepseek-ai/dsh-web-search-deepseek": "workspace:^",
-    "@deepseek-ai/dsh-workflow-worker-thread": "workspace:^",
-    "@deepseek-ai/dsh-agent-instructions": "workspace:^"
+    "@deepseek-ai/dsh-workflow-ptc": "workspace:^",
+    "@deepseek-ai/dsh-agent-instructions": "workspace:^",
+    "@deepseek-ai/dsh-ptc-runtime-node": "workspace:^"
   },
   "peerDependencies": {
     "@deepseek-ai/cordis": "workspace:^"

+ 2 - 2
packages/bundle/headless/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/bundle/headless/README.md
-README.md: 731c7c987fa3b9e2768dad4f3960cf1d4e5d080c
-README.zh.md: f4a222ea2c75948a4497e29f74121844c8044656
+README.md: d3eb8054e5a4e597b71855f9718ca57252d926da
+README.zh.md: b3f4d445132f8a59f8924f2668ad2eb2041f6a3f

+ 1 - 1
packages/bundle/headless/README.md

@@ -81,7 +81,7 @@ The runner awaits the complete application (`ctx.get('loader')?.await()`) so the
 
 ### Patch surface over base
 
-The patch rides over `dsh-base`: it inherits the projection cache, sets the coding persona prefix and separate cwd suffix on the base `system-prompt` row, keeps the same temporary process-wide PTC mode opt-in (`DSH_TOOLS_MODE`) as the Web surface, disables the shared HMR row, inserts PTC mode's PTC runtime as a core execution capability, and mounts the startup provider and the runner. The cache checkpoints each persisted one-shot session for later consumers; its durability barrier flushes each covered log prefix before publishing the cache row and may split otherwise coalesced JSONL runs. The startup provider ([`src/startup.ts`](src/startup.ts)) injects `ctx.cmdlineArgs` ([`dsh-cmdline`](../../boot/cmdline/README.md)), reads the positional argument and the `--session-id`/`--json` options, prints the app's `--help`, and provides `headlessStartup`; the runner injects that service and reads its task and run options from lazy config.
+The patch rides over `dsh-base`: it inherits the projection cache and shared PTC runtime, sets the coding persona prefix and separate cwd suffix on the base `system-prompt` row, keeps the same temporary process-wide PTC mode opt-in (`DSH_TOOLS_MODE`) as the Web surface, disables the shared HMR row, and mounts the startup provider and the runner. The cache checkpoints each persisted one-shot session for later consumers; its durability barrier flushes each covered log prefix before publishing the cache row and may split otherwise coalesced JSONL runs. The startup provider ([`src/startup.ts`](src/startup.ts)) injects `ctx.cmdlineArgs` ([`dsh-cmdline`](../../boot/cmdline/README.md)), reads the positional argument and the `--session-id`/`--json` options, prints the app's `--help`, and provides `headlessStartup`; the runner injects that service and reads its task and run options from lazy config.
 
 ### Exit mapping
 

+ 1 - 1
packages/bundle/headless/README.zh.md

@@ -81,7 +81,7 @@ runner 等待整个应用结算(`ctx.get('loader')?.await()`),确保已组
 
 ### 基于 base 的 patch 内容
 
-patch 叠加在 `dsh-base` 之上:继承投影缓存,在基础 `system-prompt` 行上设置编码 persona 前缀与独立的 cwd 后缀,保留与 Web 表层相同的临时进程级 PTC mode 开关(`DSH_TOOLS_MODE`),禁用共享的 HMR(热模块替换)行,把 PTC mode 的 PTC 运行时作为核心执行能力插入,并挂载启动提供方与 runner。缓存为每个已持久化的一次性会话写入检查点,供后续消费方使用;其持久性屏障会在发布缓存行前 flush 所覆盖的日志前缀,因此可能拆分原本会合并的 JSONL 连续段。启动提供方([`src/startup.ts`](src/startup.ts))注入 `ctx.cmdlineArgs`([`dsh-cmdline`](../../boot/cmdline/README.zh.md)),读取位置参数与 `--session-id`/`--json` 选项、打印应用自己的 `--help`,并提供 `headlessStartup`;runner 注入该服务,再从惰性配置中读取任务与运行选项。
+patch 叠加在 `dsh-base` 之上:继承投影缓存与共享 PTC 运行时,在基础 `system-prompt` 行上设置编码 persona 前缀与独立的 cwd 后缀,保留与 Web 表层相同的临时进程级 PTC mode 开关(`DSH_TOOLS_MODE`),禁用共享的 HMR(热模块替换)行,并挂载启动提供方与 runner。缓存为每个已持久化的一次性会话写入检查点,供后续消费方使用;其持久性屏障会在发布缓存行前 flush 所覆盖的日志前缀,因此可能拆分原本会合并的 JSONL 连续段。启动提供方([`src/startup.ts`](src/startup.ts))注入 `ctx.cmdlineArgs`([`dsh-cmdline`](../../boot/cmdline/README.zh.md)),读取位置参数与 `--session-id`/`--json` 选项、打印应用自己的 `--help`,并提供 `headlessStartup`;runner 注入该服务,再从惰性配置中读取任务与运行选项。
 
 ### 退出映射
 

+ 0 - 4
packages/bundle/headless/cordis.patch.yml

@@ -18,10 +18,6 @@
     mode: !!js process.env.DSH_TOOLS_MODE
 
 - insert:
-    # PTC mode is a core execution capability, not a Web component.
-    - id: ptc-runtime
-      name: '@deepseek-ai/dsh-ptc-runtime-node'
-
     - id: headless-startup
       name: '@deepseek-ai/dsh-headless/startup'
 

+ 0 - 1
packages/bundle/headless/package.json

@@ -42,7 +42,6 @@
   "dependencies": {
     "@deepseek-ai/dsh-brand": "workspace:^",
     "@deepseek-ai/dsh-cmdline": "workspace:^",
-    "@deepseek-ai/dsh-ptc-runtime-node": "workspace:^",
     "@deepseek-ai/dsh-util-values": "workspace:^",
     "@deepseek-ai/schemastery": "workspace:^",
     "commander": "^15.0.0"

+ 1 - 4
packages/bundle/web-app/cordis.patch.yml

@@ -47,9 +47,6 @@
     - id: subagent-model-selection-settings
       name: '@deepseek-ai/dsh-tool-subagent/model-selection-settings'
 
-    - id: ptc-runtime
-      name: '@deepseek-ai/dsh-ptc-runtime-node'
-
     - id: message-feedback
       name: '@deepseek-ai/dsh-message-feedback'
       config:
@@ -452,7 +449,7 @@
 - id: tool-subagent-fork
   disabled: true
 
-- id: workflow-worker-thread
+- id: workflow-ptc
   disabled: true
 
 - id: tool-workflow

+ 0 - 1
packages/bundle/web-app/package.json

@@ -94,7 +94,6 @@
     "@deepseek-ai/dsh-client-ui-workflow-run": "workspace:^",
     "@deepseek-ai/dsh-client-ui-workspace": "workspace:^",
     "@deepseek-ai/dsh-cmdline": "workspace:^",
-    "@deepseek-ai/dsh-ptc-runtime-node": "workspace:^",
     "@deepseek-ai/dsh-cordis-client-runner": "workspace:^",
     "@deepseek-ai/dsh-cordis-host-runner": "workspace:^",
     "@deepseek-ai/dsh-file-reference": "workspace:^",

+ 1 - 1
packages/core/tools/tests/ptc.spec.ts

@@ -2228,7 +2228,7 @@ describe('per-program execution controls', () => {
     } finally { await python.ctx.fiber.dispose() }
   })
 
-  it.each([0, -1, Number.NaN, Number.POSITIVE_INFINITY, '1000'])('rejects invalid timeout %s before runtime execution', async (timeoutMs) => {
+  it.each([0, -1, Number.NaN, Number.POSITIVE_INFINITY, '1000', null])('rejects invalid timeout %s before runtime execution', async (timeoutMs) => {
     const { ctx, runtime, execute } = await controlledSetup()
     try {
       expect((await execute({ timeoutMs })).isError).toBe(true)

+ 2 - 2
packages/experimental/ptc-runtime-python/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/experimental/ptc-runtime-python/README.md
-README.md: 879fef9cbc9f9bded5aabb2860a13b0d9586367f
-README.zh.md: 4f5a9e602e7264b7f906ce55d10ce763f2cbf8cc
+README.md: 287617991c5da9c13775e40c325877c231dbc312
+README.zh.md: f0c64a535cbac53241a807cc9901074f9ddd843d

+ 1 - 0
packages/experimental/ptc-runtime-python/README.md

@@ -117,6 +117,7 @@ These limits define what the package does and does not cover; they are current p
 - **A `log` frame that arrives after settlement is dropped** — once the run has settled, host-side capture is closed; a late fd-3 `log` frame (from a thread that outlived the done frame) is discarded rather than appended to `logs`.
 - **A binding REPLY value has no seam-level byte or depth cap** — `maxValueBytes` meters only the done frame's completion value; a wide binding reply is rebuilt host-side (`snapshotJsonValue` traversal) and encoded whole, bounded on both sides only by process memory (like a binding argument, which has no child-side budget either).
 - **No shipped profile mounts this provider** — the keyless `ptc-python-turn` snapshot replaces the headless PTC runtime through the real Loader; released profiles use the sandboxed Node process backend.
+- **Workflow execution requires Node** — compositions using this Python provider disable `workflow-ptc`, `tool-workflow`, and `tool-ralph`; the workflow provider rejects an incompatible runtime when loaded.
 - **Cross-channel log interleaving is backend-dependent** — Python stdout, stderr, and fd-3 log frames travel independently; each channel preserves its own order, while their total order in `result.logs` may differ.
 - **CPython 3.10 or newer is required** — the configured executable is resolved and version-probed at load; unsupported interpreters fail before `ctx.ptcRuntime` is registered.
 - **Diagnostic and temporary-directory prefixes omit the package's experimental qualifier** — the marker `[dsh-ptc-runtime-python] log capture truncated at <N> bytes` and the `dsh-ptc-runtime-python-` directory prefix identify this provider independently of its npm package name. The protocol mirror verifies identical marker bytes in TypeScript and Python.

+ 1 - 0
packages/experimental/ptc-runtime-python/README.zh.md

@@ -117,6 +117,7 @@ kind: "package-reference"
 - **结算后到达的 `log` 帧被丢弃**——运行一旦结算,宿主侧捕获即关闭;迟到的 fd-3 `log` 帧(来自比 done 帧存活更久的线程)会被丢弃,而不是追加到 `logs`。
 - **binding 回复值没有 seam 级字节或深度上限**——`maxValueBytes` 只计量 done 帧的完成值;宽 binding 回复在宿主侧重建(`snapshotJsonValue` 遍历)并整帧编码,两侧都只受进程内存约束(与没有子进程侧预算的 binding 实参一样)。
 - **已发布 profile 均不挂载本提供方**——keyless `ptc-python-turn` 快照通过真实 Loader 替换 headless PTC 运行时;已发布 profile 使用沙箱 Node 进程后端。
+- **工作流执行需要 Node**——使用本 Python 提供方的组合禁用 `workflow-ptc`、`tool-workflow` 和 `tool-ralph`;工作流提供方在加载时拒绝不兼容的运行时。
 - **跨通道日志交错由后端决定**——Python stdout、stderr 与 fd-3 日志帧彼此独立传输;每个通道保留自身顺序,但它们在 `result.logs` 中的总顺序可能不同。
 - **需要 CPython 3.10 或更高版本**——配置的可执行文件会在加载期完成解析与版本探测;不受支持的解释器会在 `ctx.ptcRuntime` 注册前失败。
 - **诊断与临时目录前缀省略包名中的 experimental 限定词**——标记 `[dsh-ptc-runtime-python] log capture truncated at <N> bytes` 与 `dsh-ptc-runtime-python-` 目录前缀独立于 npm 包名来标识本提供方。协议镜像检查 TypeScript 与 Python 的标记字节完全相同。

+ 2 - 0
packages/experimental/ptc-runtime-python/tests/runtime.spec.ts

@@ -115,10 +115,12 @@ describe('PythonPtcRuntime — seam descriptors and misuse', () => {
       expect(spec.cwd).toBe(cwd)
       expect(() => runtime.resolve({ ...request, cwd: 'relative' })).toThrow('cwd must be absolute')
       expect(() => runtime.resolve({ ...request, timeoutMs: 1 })).toThrow('per-call timeout is unsupported')
+      expect(() => runtime.resolve({ ...request, timeoutMs: null })).toThrow('per-call timeout is unsupported')
       const sandboxPolicy = { mode: 'danger-full-access' as const, workspaceRoot: cwd }
       expect(() => runtime.resolve({ ...request, sandboxPolicy })).toThrow('sandbox policy is unsupported')
       await expect(runtime.run({ ...spec, sandboxPolicy })).rejects.toThrow('unsupported execution policy or timeout')
       await expect(runtime.run({ ...spec, timeoutMs: 1 })).rejects.toThrow('unsupported execution policy or timeout')
+      await expect(runtime.run({ ...spec, timeoutMs: null })).rejects.toThrow('unsupported execution policy or timeout')
       const result = await runtime.run(runtime.resolve({ ...request, cwd, program: 'import os\nreturn os.getcwd()' }))
       expect(result.error).toBeUndefined()
       expect(result.value).toBe(realpathSync(cwd))

+ 4 - 4
packages/extensions/tool-cordis/src/api-catalog.ts

@@ -2902,7 +2902,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
   {
     key: 'workflowEngine',
     summary: 'Workflow Service Definition contract.',
-    description: 'Workflow Service Definition contract. Invalid requests throw before publication; a live run is holder-owned, its result never rejects, cancellation and disposal are bounded, and disposal waits for child cleanup within that bound. Lifecycle listener failures are contained, and `workflow/end` fires exactly once as the result settles.',
+    description: 'Workflow Service Definition contract. Invalid requests throw before publication; a live run is holder-owned, its result never rejects, and disposal waits for script and child cleanup. Lifecycle listener failures are contained, and `workflow/end` fires exactly once as the result settles.',
     methods: [
       {
         signature: 'abstract start(request: WorkflowStartRequest): WorkflowRun',
@@ -3572,7 +3572,7 @@ export const EVENT_API: readonly EventApiEntry[] = [
     mode: 'emit',
     signature: '\'workflow/agent-end\'(info: WorkflowRunInfo, agent: WorkflowAgentEndInfo): void',
     summary: 'One `agent()` call settled (clean result, child failure, or run cancellation).',
-    description: 'One `agent()` call settled (clean result, child failure, or run cancellation). Paired with Events[\'workflow/agent-start\'] by `agent.seq`, exactly once per started call on every stop path — on an engine termination path (a worker killed past its grace) the end is engine-synthesized with outcome `\'cancelled\'`.',
+    description: 'One `agent()` call settled (clean result, child failure, or run cancellation). Paired with Events[\'workflow/agent-start\'] by `agent.seq`, exactly once per started call on every stop path — on an engine termination path the end is engine-synthesized with outcome `\'cancelled\'`.',
     parameters: [{ name: 'info', description: 'the run\'s identity snapshot.' }, { name: 'agent', description: 'the call identity plus its outcome.' }],
   },
   {
@@ -4917,7 +4917,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
   },
   {
     name: 'PtcRunRequest',
-    declaration: 'export interface PtcRunRequest {\n    program: string;\n    bindings: PtcBindingNamespace[];\n    cwd?: string;\n    timeoutMs?: number;\n    sandboxPolicy?: SandboxExecutionPolicy;\n    signal?: AbortSignal;\n}',
+    declaration: 'export interface PtcRunRequest {\n    program: string;\n    bindings: PtcBindingNamespace[];\n    cwd?: string;\n    timeoutMs?: number | null;\n    sandboxPolicy?: SandboxExecutionPolicy;\n    signal?: AbortSignal;\n}',
   },
   {
     name: 'PtcRunResult',
@@ -4929,7 +4929,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
   },
   {
     name: 'PtcRunSpec',
-    declaration: 'export interface PtcRunSpec extends PtcRunRequest {\n    cwd: string;\n    timeoutMs: number;\n}',
+    declaration: 'export interface PtcRunSpec extends PtcRunRequest {\n    cwd: string;\n    timeoutMs: number | null;\n}',
   },
   {
     name: 'ReadFileLine',

+ 1 - 2
packages/host/directory-picker-native/tests/built-worker.e2e.ts

@@ -1,6 +1,5 @@
 /**
- * Keyless built-artifact guard (the `dsh-workflow-worker-thread` built-worker
- * shape): plain `node` runs `lib/worker.cjs` and the bundle reaches its
+ * Keyless built-artifact guard: plain `node` runs `lib/worker.cjs` and the bundle reaches its
  * real koffi requires. POSIX hosts prove the load path end to end through
  * the deterministic ole32 rejection; win32 skips (a real dialog would
  * open), where the win32-only smoke in win32-dialog.spec.ts covers the

+ 2 - 2
packages/host/directory-picker-native/tsdown.config.ts

@@ -1,8 +1,8 @@
 import { defineConfig } from 'tsdown'
 
 /**
- * Node-only backend. The Win32 dialog worker builds as its own CJS entry
- * (mirroring dsh-workflow-worker-thread's worker): path-loaded by the driver,
+ * Node-only backend. The Win32 dialog worker builds as its own CJS entry,
+ * path-loaded by the driver,
  * inlining the dialog logic while koffi stays an external native require.
  */
 export default defineConfig([

+ 2 - 2
packages/preset/agent-presets/presets/cordis/agent.cordis.yml

@@ -207,8 +207,8 @@
         backgroundMode: one-shot
         maxDepth: provider-managed
 
-    - id: workflow-worker-thread
-      name: '@deepseek-ai/dsh-workflow-worker-thread'
+    - id: workflow-ptc
+      name: '@deepseek-ai/dsh-workflow-ptc'
       config:
         provider: spawn
 

+ 2 - 2
packages/preset/agent-presets/presets/cordis/skills/editing-cordis-compositions/SKILL.md

@@ -88,8 +88,8 @@ When a preset genuinely owns a service, wrap the provider **and every consumer t
   isolate:
     workflows: true
   config:
-    - id: workflow-worker-thread
-      name: '@deepseek-ai/dsh-workflow-worker-thread'
+    - id: workflow-ptc
+      name: '@deepseek-ai/dsh-workflow-ptc'
       config:
         provider: spawn
     - id: tool-workflow

+ 2 - 2
packages/preset/agent-presets/presets/ptc/agent.cordis.yml

@@ -226,8 +226,8 @@
         backgroundMode: one-shot
         maxDepth: provider-managed
 
-    - id: workflow-worker-thread
-      name: '@deepseek-ai/dsh-workflow-worker-thread'
+    - id: workflow-ptc
+      name: '@deepseek-ai/dsh-workflow-ptc'
       # No consumer here: `tool-workflow` is off in PTC mode and `tool-ralph`
       # below is off by default. Restore this row together with `tool-ralph`.
       disabled: true

+ 2 - 2
packages/preset/agent-presets/presets/standard/agent.cordis.yml

@@ -219,8 +219,8 @@
         backgroundMode: one-shot
         maxDepth: provider-managed
 
-    - id: workflow-worker-thread
-      name: '@deepseek-ai/dsh-workflow-worker-thread'
+    - id: workflow-ptc
+      name: '@deepseek-ai/dsh-workflow-ptc'
       config:
         provider: spawn
 

+ 2 - 2
packages/preset/agent-presets/tests/shipped-root.spec.ts

@@ -144,12 +144,12 @@ describe('the shipped preset root', () => {
   it('omits the general workflow tool and its unused engine only from PTC', async () => {
     const ptc = await shippedEntries('ptc')
     expect(findEntry(ptc, 'tool-workflow')?.disabled).toBe(true)
-    expect(findEntry(ptc, 'workflow-worker-thread')?.disabled).toBe(true)
+    expect(findEntry(ptc, 'workflow-ptc')?.disabled).toBe(true)
 
     for (const id of ['standard', 'cordis']) {
       const entries = await shippedEntries(id)
       expect(findEntry(entries, 'tool-workflow')?.disabled, id).not.toBe(true)
-      expect(findEntry(entries, 'workflow-worker-thread')?.disabled, id).not.toBe(true)
+      expect(findEntry(entries, 'workflow-ptc')?.disabled, id).not.toBe(true)
     }
   })
 

+ 2 - 2
packages/ptc-runtime/ptc-runtime-node/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/ptc-runtime/ptc-runtime-node/README.md
-README.md: 3f8cdee44d863a8f7539b0975a95695692da4e58
-README.zh.md: 76f975bc752ee89e1b2b56ad30b118787e45e0b4
+README.md: f8f7b5535ba8ed1594b898d410df04e24f5dc8ef
+README.zh.md: dd4e33e70f05ae3917f128e33bf26af5d1a915c4

+ 5 - 5
packages/ptc-runtime/ptc-runtime-node/README.md

@@ -55,7 +55,7 @@ Configure the provider row after its required services are available:
 | `nodeExecutable` | Current Node executable | Executable resolved in the subprocess execution world |
 | `bootstrapPath` | Package bootstrap | Optional absolute path to a preinstalled built bootstrap in that world |
 
-The [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-ptc-runtime-node) defines accepted config fields. `resolve(request)` supplies cwd, the capped timeout and the execution policy; `run(spec)` accepts those resolved inputs and does not fill missing values.
+The [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-ptc-runtime-node) defines accepted config fields. `resolve(request)` supplies cwd, the numeric or null deadline choice and the execution policy; `run(spec)` accepts those resolved inputs and does not fill missing values.
 
 ### Execution and results
 
@@ -67,7 +67,7 @@ Direct filesystem, network and subprocess operations remain Node operations, sub
 
 The PTC consumer exposes per-call timeout and approved sandbox choices as described in [dsh-tools](../../core/tools/README.md#ptc-mode). The runtime's readonly `timeout` descriptor reports its effective default and maximum to that consumer. Its `executionInstructions` describes fresh Node state, direct Node APIs, the empty program environment and file policy in the model-visible schema.
 
-The elapsed deadline covers runtime setup and execution, including time awaiting nested tools or approval. It is not a CPU meter. Timeout or cancellation stops a synchronous loop through the host's managed process owner; successful completion also cleans that managed range. The timer stops when an outcome is selected, before cleanup, so the returned call can take longer than its execution deadline while cleanup settles.
+Omitting `timeoutMs` uses the configured elapsed default; numeric requests are validated and capped. Service callers can explicitly pass `timeoutMs: null` to omit the elapsed timer, as the workflow adapter does; `run_code` continues to accept only positive numeric overrides. An enabled deadline covers runtime setup and execution, including time awaiting nested tools or approval. It is not a CPU meter. Timeout or cancellation stops a synchronous loop through the host's managed process owner; successful completion also cleans that managed range. The timer stops when an outcome is selected, before cleanup, so the returned call can take longer than its execution deadline while cleanup settles.
 
 ### Failures
 
@@ -87,7 +87,7 @@ The host owns policy, deadlines, binding lookup and process cleanup. The child o
 
 The host strips erasable types, resolves the executable and bootstrap in the configured execution world, awaits argv confinement through `ctx.sandbox`, then spawns through `ctx.subprocess`. Cancellation is checked again after confinement, so a provider returning after cancellation cannot start the program. After adopting the inherited control channel, the child retains only executable-search, Windows system, and temporary paths in its OS environment and replaces the program-visible `process.env` with an empty dictionary. Windows ACL setup receives the parent's distinct `TEMP` and `TMP` values for shared grant locks, then replaces both with its private directory before starting the program. These native paths keep nested process creation and native temporary-file APIs functional. The heap limit uses Node argv or a provider-created `NODE_OPTIONS` value for packaged executables; ambient loader and inspector flags are discarded.
 
-Length-framed JSON travels separately from stdout/stderr. The host bounds frames and queued writes, validates call identity and declared binding names before dispatch, and refuses invalid traffic. Output capture meters serialized logs plus the completion or diagnostic; fixed result-envelope fields and sandbox metadata are outside that ledger.
+Length-framed JSON travels separately from stdout/stderr. The host bounds frames and queued writes, validates call identity and declared binding names before dispatch, and refuses invalid traffic. The child flushes its terminal frame and keeps the control channel open until the host closes it. After submitting that frame, it ignores later binding replies and sends no further program control messages. Output capture meters serialized logs plus the completion or diagnostic; fixed result-envelope fields and sandbox metadata are outside that ledger.
 
 ### Source and built bootstraps
 
@@ -124,7 +124,7 @@ Read the service contract before using the provider directly; the decisions expl
 <a id="model-experience"></a>
 ## Model Experience
 
-Indirectly, through PTC mode in `dsh-tools`, which returns captured logs and the completion value or a failure with sandbox facts. Intermediate binding traffic stays outside model history; the outer result follows the ordinary tool spill policy.
+Indirectly, through PTC mode in `dsh-tools` and `dsh-workflow-ptc`, which present program outcomes through their own tool results. Intermediate binding traffic stays outside model history; the outer result follows the ordinary tool spill policy.
 
 #### KV Cache effect
 
@@ -150,6 +150,6 @@ These limits qualify the execution guarantees and retained output.
 <details>
 <summary>Working context for maintainers — click to expand</summary>
 
-The [timeout discussion](../../../.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.md#deferred-timeout-design) records open choices about yielding, total lifetime, approval wait accounting and process-tree CPU/RSS limits. Those choices do not change the configured elapsed deadline.
+The [timeout discussion](../../../.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.md#deferred-timeout-design) records open choices about yielding, total lifetime, approval wait accounting and process-tree CPU/RSS limits. Those choices do not alter numeric deadline defaults or the explicit no-deadline service option.
 
 </details>

+ 5 - 5
packages/ptc-runtime/ptc-runtime-node/README.zh.md

@@ -55,7 +55,7 @@ kind: "package-reference"
 | `nodeExecutable` | 当前 Node 可执行文件 | 在子进程执行世界中解析的可执行文件 |
 | `bootstrapPath` | 包内 bootstrap | 该执行世界中预先安装的构建后 bootstrap 的可选绝对路径 |
 
-[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-ptc-runtime-node)定义可接受的配置字段。`resolve(request)` 补全 cwd、封顶后的 timeout 与执行策略;`run(spec)` 接受这些已解析输入,不补缺省值。
+[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-ptc-runtime-node)定义可接受的配置字段。`resolve(request)` 补全 cwd、数值或 null 截止选择与执行策略;`run(spec)` 接受这些已解析输入,不补缺省值。
 
 ### 执行与结果
 
@@ -67,7 +67,7 @@ kind: "package-reference"
 
 PTC 消费方按 [dsh-tools](../../core/tools/README.zh.md#ptc-mode) 的说明公开逐次超时与经审批的沙箱选择。运行时只读 `timeout` 描述符向该消费方报告有效默认值与上限。 其 `executionInstructions` 在面向模型的 schema 中说明全新 Node 状态、直接 Node API、空程序环境和文件策略。
 
-经过时间截止覆盖运行时准备和执行,包括等待嵌套工具或审批的时间。它不是 CPU 计量器。超时或取消通过 Host 的受管进程所有者停止同步循环;成功完成也会清理该受管范围。选择结果后、清理前停止计时器,因此调用可能要在执行截止之后等待清理结算才返回。
+省略 `timeoutMs` 使用配置的经过时间默认值;数值请求经过验证并封顶。服务调用方可以显式传入 `timeoutMs: null` 来省略经过时间定时器,工作流适配器即如此;`run_code` 仍只接受正数覆盖值。启用的截止覆盖运行时准备和执行,包括等待嵌套工具或审批的时间。它不是 CPU 计量器。超时或取消通过 Host 的受管进程所有者停止同步循环;成功完成也会清理该受管范围。选择结果后、清理前停止计时器,因此调用可能要在执行截止之后等待清理结算才返回。
 
 ### 失败
 
@@ -87,7 +87,7 @@ Host 负责策略、截止时间、绑定查找和进程清理。子进程负责
 
 Host 擦除可擦除类型,在配置的执行世界中解析可执行文件与 bootstrap,通过 `ctx.sandbox` 等待 argv 限制准备完成,再通过 `ctx.subprocess` 启动。限制准备完成后会再次检查取消状态,因此提供方在取消后返回也无法启动程序。接管继承的控制通道后,子进程在 OS 环境中只保留可执行文件搜索路径、Windows 系统路径和临时路径,并将程序可见的 `process.env` 替换为空字典。Windows ACL 初始化接收父进程各自的 `TEMP` 和 `TMP` 值以使用共享授权锁,然后在启动程序前将二者替换为私有目录。这些原生路径使嵌套进程创建和原生临时文件 API 仍可正常工作。堆上限通过 Node argv 或为打包可执行文件由提供方构造的 `NODE_OPTIONS` 值传递;环境中的加载器和调试器标志会被丢弃。
 
-带长度分帧的 JSON 与 stdout/stderr 分开传输。Host 限制帧与排队写入,在分派前验证调用身份和已声明的绑定名,并拒绝无效通信。输出捕获计量序列化日志加完成值或诊断;固定结果信封字段与沙箱元数据不计入该账本。
+带长度分帧的 JSON 与 stdout/stderr 分开传输。Host 限制帧与排队写入,在分派前验证调用身份和已声明的绑定名,并拒绝无效通信。子进程刷新终态帧后仍保持控制通道打开,直到 Host 关闭通道。提交终态帧后,子进程忽略后续绑定回复,不再发送程序控制消息。输出捕获计量序列化日志加完成值或诊断;固定结果信封字段与沙箱元数据不计入该账本。
 
 ### 源代码与构建后 bootstrap
 
@@ -124,7 +124,7 @@ Host 擦除可擦除类型,在配置的执行世界中解析可执行文件与
 <a id="model-experience"></a>
 ## 模型体验
 
-通过 `dsh-tools` 的 PTC 模式间接提供,返回捕获日志与完成值,或带沙箱事实的失败。中间绑定通信不进入模型历史;外层结果遵循普通工具溢出策略。
+通过 `dsh-tools` 的 PTC 模式与 `dsh-workflow-ptc` 间接提供;它们通过各自的工具结果呈现程序结果。中间绑定通信不进入模型历史;外层结果遵循普通工具溢出策略。
 
 #### KV Cache effect
 
@@ -150,6 +150,6 @@ Host 擦除可擦除类型,在配置的执行世界中解析可执行文件与
 <details>
 <summary>维护者工作上下文——点击展开</summary>
 
-[timeout 讨论](../../../.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.zh.md#deferred-timeout-design)记录 yield、总生命周期、审批等待计时和进程树 CPU/RSS 上限的开放选择。这些选择不改变已配置的经过时间截止
+[timeout 讨论](../../../.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.zh.md#deferred-timeout-design)记录 yield、总生命周期、审批等待计时和进程树 CPU/RSS 上限的开放选择。这些选择不改变数值截止的默认值或显式的不设截止服务选项
 
 </details>

+ 6 - 5
packages/ptc-runtime/ptc-runtime-node/src/index.ts

@@ -26,7 +26,7 @@ import type { ProgramBootData } from './protocol.ts'
 export interface Config extends LaunchConfig {
   /** Default elapsed deadline, including nested tool and approval waits. */
   timeoutMs?: number
-  /** Maximum elapsed deadline accepted by resolve. */
+  /** Maximum numeric elapsed budget accepted by resolve. */
   maxTimeoutMs?: number
   /** Combined serialized logs, completion and diagnostic byte cap. */
   maxOutputBytes?: number
@@ -103,7 +103,7 @@ export class NodePtcRuntime extends PtcRuntime {
   /**
    * Resolve an execution under explicit or deployment policy.
    * @param request - Program, bindings, optional cwd/deadline, and resolved authority.
-   * @returns Complete execution inputs with a capped deadline.
+   * @returns Complete execution inputs with a capped numeric budget or an explicit null deadline.
    */
   resolve(request: PtcRunRequest): PtcRunSpec {
     if (this.disposed) throw new Error('ptc-runtime-node: resolve after disposal')
@@ -113,7 +113,7 @@ export class NodePtcRuntime extends PtcRuntime {
     return {
       ...request,
       cwd,
-      timeoutMs: clampTimeout(request.timeoutMs, this.config.timeoutMs, this.config.maxTimeoutMs, 'ptc-runtime-node: timeoutMs'),
+      timeoutMs: request.timeoutMs === null ? null : clampTimeout(request.timeoutMs, this.config.timeoutMs, this.config.maxTimeoutMs, 'ptc-runtime-node: timeoutMs'),
       sandboxPolicy,
     }
   }
@@ -126,7 +126,7 @@ export class NodePtcRuntime extends PtcRuntime {
   async run(spec: PtcRunSpec): Promise<PtcRunResult> {
     if (this.disposed) throw new Error('ptc-runtime-node: run after disposal')
     if (spec.sandboxPolicy === undefined) throw new Error('ptc-runtime-node: run requires a resolved sandbox policy')
-    if (!isAbsolute(spec.cwd) || !Number.isFinite(spec.timeoutMs) || spec.timeoutMs <= 0 || spec.timeoutMs > this.config.maxTimeoutMs) throw new Error('ptc-runtime-node: run requires resolved cwd and timeout')
+    if (!isAbsolute(spec.cwd) || (spec.timeoutMs !== null && (!Number.isFinite(spec.timeoutMs) || spec.timeoutMs <= 0 || spec.timeoutMs > this.config.maxTimeoutMs))) throw new Error('ptc-runtime-node: run requires resolved cwd and timeout')
     const bindings = validateBindings(spec)
     const controller = new AbortController()
     const completion = Promise.withResolvers<void>()
@@ -160,7 +160,8 @@ export class NodePtcRuntime extends PtcRuntime {
     let overflowResult: PtcRunResult | undefined
     let stderr = ''
     let parsing = true
-    const wallTimer = setTimeout(() => { timedOut = true; controller.abort('execution deadline reached') }, spec.timeoutMs)
+    const wallTimer = spec.timeoutMs === null ? undefined
+      : setTimeout(() => { timedOut = true; controller.abort('execution deadline reached') }, spec.timeoutMs)
     const finish = (failure?: PtcRunFailure, value?: PtcJsonValue): void => {
       if (settled) return
       settled = true

+ 20 - 6
packages/ptc-runtime/ptc-runtime-node/src/process.ts

@@ -19,7 +19,7 @@ export interface ProgramProcess {
  * @param stream - Inherited, already-adopted control endpoint.
  * @param maxMessageBytes - Host-validated maximum frame and queued-write bytes.
  * @param processState - Environment, output streams and exit status of this Node child.
- * @returns After the program has settled and its control output has flushed.
+ * @returns After control output flushes and host shutdown is observed, or transport failure closes the channel.
  */
 export async function runNodeMain(stream: Duplex, maxMessageBytes: number, processState: ProgramProcess): Promise<void> {
   if (!Number.isSafeInteger(maxMessageBytes) || maxMessageBytes <= 0 || maxMessageBytes > 0xffff_ffff) throw new Error('invalid control message limit')
@@ -32,6 +32,10 @@ export async function runNodeMain(stream: Duplex, maxMessageBytes: number, proce
   const listeners: Array<(message: ReplyMessage) => void> = []
   let started = false
   let failed = false
+  let terminalSent = false
+  const hostClosed = Promise.withResolvers<void>()
+  const onClose = (): void => { hostClosed.resolve() }
+  stream.once('close', onClose)
   const channel = new JsonChannel(stream, maxMessageBytes, (raw) => {
     if (!started) {
       started = true
@@ -42,19 +46,26 @@ export async function runNodeMain(stream: Duplex, maxMessageBytes: number, proce
       boot.resolve((raw as { data: ProgramBootData }).data)
       return
     }
+    if (terminalSent) return
     for (const listener of listeners) listener(raw as ReplyMessage)
-  }, (error) => {
-    failed = true
-    boot.reject(error)
-    channel.close()
-    processState.exitCode = 1
+  }, (error, kind) => {
+    if (!terminalSent || kind === 'protocol') {
+      failed = true
+      boot.reject(error)
+      channel.close()
+      processState.exitCode = 1
+    }
+    hostClosed.resolve()
   })
   const pending = new Set<Promise<void>>()
   const send = (message: ProgramToHost): void => {
+    if (terminalSent) return
+    if (message.type === 'done') terminalSent = true
     const task = channel.send(message).catch(() => {
       failed = true
       channel.close()
       processState.exitCode = 1
+      hostClosed.resolve()
     }).finally(() => { pending.delete(task) })
     pending.add(task)
   }
@@ -67,7 +78,10 @@ export async function runNodeMain(stream: Duplex, maxMessageBytes: number, proce
     }, data, { stdout: processState.stdout, stderr: processState.stderr })
     while (pending.size > 0) await Promise.all(pending)
     await channel.drain()
+    // Host binding replies can race the terminal frame; the host owns channel shutdown.
+    await hostClosed.promise
   } finally {
+    stream.off('close', onClose)
     channel.close()
     // Transport callbacks can set failed while the awaited program executes.
     // oxlint-disable-next-line typescript/no-unnecessary-condition

+ 26 - 0
packages/ptc-runtime/ptc-runtime-node/tests/host-failures.spec.ts

@@ -200,6 +200,32 @@ describe('Node runtime host failures', () => {
     expect(h.resolveExecutable).not.toHaveBeenCalled()
   })
 
+  it('keeps a null-deadline run active past the numeric ceiling until cancellation', async () => {
+    const h = await setup({ timeoutMs: 20, maxTimeoutMs: 40 })
+    const booted = Promise.withResolvers<undefined>()
+    h.onBoot(() => { booted.resolve(undefined) })
+    const controller = new AbortController()
+    let active: ReturnType<typeof h.start> | undefined
+    vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
+    try {
+      active = h.start({ ...request, timeoutMs: null, signal: controller.signal })
+      const settled = vi.fn()
+      void active.then(settled)
+      await booted.promise
+      await vi.advanceTimersByTimeAsync(1000)
+      expect(settled).not.toHaveBeenCalled()
+      expect(h.terminate).not.toHaveBeenCalled()
+      controller.abort('stop unlimited run')
+      expect((await active).error).toEqual({ kind: 'abort', message: 'stop unlimited run' })
+      expect(h.terminate).toHaveBeenCalledOnce()
+      expect(h.waitForExit).toHaveBeenCalledOnce()
+    } finally {
+      controller.abort('test cleanup')
+      vi.useRealTimers()
+      await active
+    }
+  })
+
   it('does not launch after cancellation races executable lookup completion', async () => {
     const h = await setup()
     const controller = new AbortController()

+ 69 - 0
packages/ptc-runtime/ptc-runtime-node/tests/process-main.spec.ts

@@ -1,4 +1,5 @@
 import { Duplex, PassThrough } from 'node:stream'
+import { setImmediate as nextTurn } from 'node:timers/promises'
 import { expect, it, onTestFinished } from 'vitest'
 import { JsonChannel } from '../src/channel.ts'
 import { runNodeMain } from '../src/process.ts'
@@ -31,6 +32,7 @@ it('clears process environment, dispatches a binding reply and flushes the termi
     messages.push(message)
     if (message.type === 'ready') void peer.send({ type: 'boot', data: { code: 'console.log("ready"); return await tools.echo({});', namespaces: [{ global: 'tools', names: ['echo'] }], maxOutputBytes: 1024 } })
     if (message.type === 'call') void peer.send({ type: 'reply', id: message.id, ok: true, value: encodePtcJsonWire(42) })
+    if (message.type === 'done') host.end()
   }, () => {})
   onTestFinished(() => { peer.close() })
   await runNodeMain(child, 4096, state)
@@ -42,6 +44,39 @@ it('clears process environment, dispatches a binding reply and flushes the termi
   expect(decodePtcJsonWire(messages.find(message => message.type === 'done')?.value)).toBe(42)
 })
 
+it('keeps the control pipe open for a late binding reply until the host closes it', async () => {
+  const { child, host } = endpoints()
+  const state = processState()
+  const terminal = Promise.withResolvers<unknown>()
+  const messages: string[] = []
+  let callId: unknown
+  const peer = new JsonChannel(host, 4096, (raw) => {
+    const message = raw as Record<string, unknown>
+    messages.push(String(message.type))
+    if (message.type === 'ready') void peer.send({ type: 'boot', data: {
+      code: 'void tools.echo({}).then(() => { throw new Error("late reply resumed the program") }); setImmediate(() => console.log("late timer")); return 42;',
+      namespaces: [{ global: 'tools', names: ['echo'] }],
+      maxOutputBytes: 1024,
+    } })
+    if (message.type === 'call') callId = message.id
+    if (message.type === 'done') terminal.resolve(decodePtcJsonWire(message.value))
+  }, (error) => { terminal.reject(error) })
+  const main = runNodeMain(child, 4096, state)
+  try {
+    expect(await terminal.promise).toBe(42)
+    // Settle the terminal write callbacks while the host still owns the open pipe.
+    await nextTurn()
+    expect(child.destroyed).toBe(false)
+    await peer.send({ type: 'reply', id: callId, ok: true, value: encodePtcJsonWire(42) })
+    await nextTurn()
+    expect(messages).toEqual(['ready', 'call', 'done'])
+  } finally {
+    peer.close()
+    await main
+  }
+  expect(state.exitCode).toBeUndefined()
+})
+
 it.each([0, -1, 1.5, 4294967296])('rejects an invalid bootstrap frame limit %i', async (limit) => {
   const { child } = endpoints()
   await expect(runNodeMain(child, limit, processState())).rejects.toThrow('invalid control message limit')
@@ -72,3 +107,37 @@ it('contains program writes that exceed queued control output', async () => {
   await runNodeMain(child, 1024, state)
   expect(state.exitCode).toBe(1)
 })
+
+it('records a terminal frame that cannot fit the control write budget as failure', async () => {
+  const { child, host } = endpoints()
+  const state = processState()
+  const peer = new JsonChannel(host, 1024, (raw) => {
+    if ((raw as { type: string }).type === 'ready') void peer.send({ type: 'boot', data: {
+      code: 'return "x".repeat(2000)', namespaces: [], maxOutputBytes: 8000,
+    } })
+  }, () => {})
+  onTestFinished(() => { peer.close() })
+  await runNodeMain(child, 1024, state)
+  expect(state.exitCode).toBe(1)
+})
+
+it('retains a malformed host frame failure after the terminal frame', async () => {
+  const { child, host } = endpoints()
+  const state = processState()
+  const terminal = Promise.withResolvers<undefined>()
+  const peer = new JsonChannel(host, 1024, (raw) => {
+    const message = raw as { type: string }
+    if (message.type === 'ready') void peer.send({ type: 'boot', data: {
+      code: 'return 42', namespaces: [], maxOutputBytes: 1000,
+    } })
+    if (message.type === 'done') terminal.resolve(undefined)
+  }, () => {})
+  onTestFinished(() => { peer.close() })
+  const main = runNodeMain(child, 1024, state)
+  try {
+    await terminal.promise
+    host.write(Buffer.from([0, 0, 0, 1, 0xff]))
+    await main
+    expect(state.exitCode).toBe(1)
+  } finally { peer.close(); await main }
+})

+ 1 - 0
packages/ptc-runtime/ptc-runtime-node/tests/process.spec.ts

@@ -45,5 +45,6 @@ it('boots an unbuilt source closure outside the workspace and exchanges tool rep
   }, (error) => { completed.reject(error) })
   onTestFinished(async () => { channel.close(); child.kill(); await finished })
   expect(await completed.promise).toEqual({ answer: 42, env: {} })
+  channel.close()
   await finished
 })

+ 5 - 4
packages/ptc-runtime/ptc-runtime-node/tests/runtime.spec.ts

@@ -136,6 +136,7 @@ describe('Node program process', () => {
     expect(runtime.executionInstructions).toBe('Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution\'s sandbox policy.')
     expect(runtime.resolve({ program: '', bindings: [] }).timeoutMs).toBe(120_000)
     expect(runtime.resolve({ program: '', bindings: [], timeoutMs: 900_000 }).timeoutMs).toBe(600_000)
+    expect(runtime.resolve({ program: '', bindings: [], timeoutMs: null }).timeoutMs).toBeNull()
     for (const timeoutMs of [0, -1, NaN, Infinity]) expect(() => runtime.resolve({ program: '', bindings: [], timeoutMs })).toThrow()
     await expect(runtime.run({ program: '', bindings: [], cwd: process.cwd(), timeoutMs: 1000 })).rejects.toThrow('sandbox policy')
   })
@@ -164,11 +165,11 @@ describe('Node program process', () => {
     expect(result.error?.kind).toBe('timeout')
   })
 
-  it('cancels a live program and closes its managed process', async () => {
+  it.each([{}, { timeoutMs: null }] as const)('cancels a live program and closes its managed process with %j', async (timing) => {
     const { run } = await setup()
     const entered = Promise.withResolvers<undefined>()
     const controller = new AbortController()
-    const active = run({ program: 'await tools.enter({}); for (;;) {}', signal: controller.signal, bindings: bindings({ enter: async () => { entered.resolve(undefined); return null } }) })
+    const active = run({ ...timing, program: 'void tools.enter({}); for (;;) {}', signal: controller.signal, bindings: bindings({ enter: async () => { entered.resolve(undefined); return null } }) })
     await entered.promise
     controller.abort('stop')
     expect((await active).error).toEqual({ kind: 'abort', message: 'stop' })
@@ -220,11 +221,11 @@ describe('Node program process', () => {
     expect(Number(limits[1]) - Number(limits[0])).toBe(32 * 1024 * 1024)
   })
 
-  it('disposes active programs and rejects later execution', async () => {
+  it.each([{}, { timeoutMs: null }] as const)('disposes active programs and rejects later execution with %j', async (timing) => {
     const { ctx, run, runtime } = await setup()
     const spec = runtime.resolve({ program: '', bindings: [] })
     const entered = Promise.withResolvers<undefined>()
-    const active = run({ program: 'await tools.enter({}); await new Promise(() => {})', bindings: bindings({ enter: async () => { entered.resolve(undefined); return null } }) })
+    const active = run({ ...timing, program: 'await tools.enter({}); await new Promise(() => {})', bindings: bindings({ enter: async () => { entered.resolve(undefined); return null } }) })
     await entered.promise
     await ctx.fiber.dispose()
     expect((await active).error?.kind).toBe('abort')

+ 2 - 2
packages/ptc-runtime/ptc-runtime/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/ptc-runtime/ptc-runtime/README.md
-README.md: e02e38a614ba50fc22db8fe357419b3c2a8e1708
-README.zh.md: f02fd601d3654a2e7f572921909733c0468d190c
+README.md: 658697a73f126722474c3280e74c1a73ec0c8320
+README.zh.md: 70c2315e9ab7ed8597f031fee95f2ca847a90a77

+ 5 - 5
packages/ptc-runtime/ptc-runtime/README.md

@@ -25,7 +25,7 @@ Use `dsh-ptc-runtime` to run one model-written program against host-provided asy
 <a id="use-this-package"></a>
 ## Use this package
 
-Choose this package when you compose a deployment that executes model-written programs, consume `ctx.ptcRuntime` directly, or build a backend that runs programs. In the shipped composition, PTC mode in `dsh-tools` is the consumer: only what the program printed and returned re-enters the conversation.
+Choose this package when you compose a deployment that executes model-written programs, consume `ctx.ptcRuntime` directly, or build a backend that runs programs. PTC mode in `dsh-tools` uses it for tool programs, and `dsh-workflow-ptc` uses it for workflow orchestration. Each consumer owns the content returned to its model.
 
 ### Run a program
 
@@ -64,11 +64,11 @@ This section explains the design behind the seam; observable behavior is fully c
 
 ### Design concept
 
-The package is the Service Definition role of the PTC execution capability seam ([capability seams](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md)): an abstract `PtcRuntime extends Service` registered as `ctx.ptcRuntime`, plus the vocabulary both backends and the consumer share. Providers subclass `PtcRuntime`, implement `resolve` and `run`, and register the service; the consumer (PTC mode in `dsh-tools`) generates the model-facing SDK and bridges tool dispatch. The runtime stays ignorant of tools and sessions by contract: it receives a program, named async bindings and resolved execution options, then returns captured output, the outcome and applicable sandbox facts.
+The package is the Service Definition role of the PTC execution capability seam ([capability seams](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md)): an abstract `PtcRuntime extends Service` registered as `ctx.ptcRuntime`, plus the vocabulary providers and consumers share. Providers subclass `PtcRuntime`, implement `resolve` and `run`, and register the service. PTC mode in `dsh-tools` owns tool bindings, while `dsh-workflow-ptc` owns workflow hooks and child agents. The runtime stays ignorant of tools and sessions by contract: it receives a program, named async bindings and resolved execution options, then returns captured output, the outcome and applicable sandbox facts.
 
 ### Service API
 
-The readonly `timeout` descriptor exposes `{ defaultMs, maxMs }` for a provider supporting per-call overrides; `undefined` means that the consumer must omit the field. It reports presentation values, while `resolve` remains the validation and clamping authority.
+The readonly `timeout` descriptor exposes numeric `{ defaultMs, maxMs }` for consumer presentation; an absent descriptor means numeric overrides are unsupported. An omitted `timeoutMs` selects the provider default; a number requests a validated, capped elapsed budget; explicit `null` requests no elapsed deadline. A provider rejects choices it does not support. The Node workflow adapter requests `null`, while the model-facing `run_code` tool accepts only positive numeric overrides.
 
 `executionInstructions` supplies provider-owned usage guidance, or an empty string when none is needed. Consumers can include it in their program presentation without identifying the provider from its language or isolation descriptor; PTC includes it in the logged `run_code` schema.
 
@@ -78,7 +78,7 @@ The exhaustive semantics live in the [PTC runtime subsystem reference](../../../
 
 ### Vocabulary
 
-`PtcRunRequest` carries the program, host bindings, cancellation and optional execution choices. `PtcRunSpec` requires the resolved cwd and elapsed deadline. `PtcBindingNamespace` declares program globals and optional typed rejection constructors. `PtcRunResult` separates logs/value, failure and `PtcRunSandbox` facts; exact fields and provider obligations live in [`src/types.ts`](src/types.ts).
+`PtcRunRequest` carries the program, host bindings, cancellation and optional execution choices. `PtcRunSpec` requires the resolved cwd and an explicit numeric or null deadline choice. `PtcBindingNamespace` declares program globals and optional typed rejection constructors. `PtcRunResult` separates logs/value, failure and `PtcRunSandbox` facts; exact fields and provider obligations live in [`src/types.ts`](src/types.ts).
 
 ### Portable identifiers
 
@@ -112,7 +112,7 @@ Read these when the package-level contract is not enough. They move from the PTC
 <a id="model-experience"></a>
 ## Model Experience
 
-Indirectly, through PTC mode in `dsh-tools`, which exposes `run_code` and returns program logs, values, or failures as retained tool-result tokens.
+Indirectly, through PTC mode in `dsh-tools` and the workflow adapter, which present program outcomes through their own tool results.
 
 #### KV Cache effect
 

+ 5 - 5
packages/ptc-runtime/ptc-runtime/README.zh.md

@@ -25,7 +25,7 @@ kind: "package-reference"
 <a id="use-this-package"></a>
 ## 使用本包
 
-当你要组合一个执行模型程序的部署、直接消费 `ctx.ptcRuntime`,或构建运行程序的后端时,选择本包。在已发布的组合中,`dsh-tools` 里的 PTC mode 是消费方:只有程序打印和返回的内容重新进入对话
+当你要组合一个执行模型程序的部署、直接消费 `ctx.ptcRuntime`,或构建运行程序的后端时,选择本包。`dsh-tools` 中的 PTC mode 用它执行工具程序,`dsh-workflow-ptc` 用它编排工作流。每个消费方负责返回给模型的内容
 
 ### 运行一个程序
 
@@ -64,11 +64,11 @@ binding-global 与 error-class 名称是语言可移植的:必须匹配 `[A-Za
 
 ### 设计理念
 
-本包是 PTC 执行能力 seam 的 Service Definition 角色([能力 seam](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md)):一个注册为 `ctx.ptcRuntime` 的抽象 `PtcRuntime extends Service`,加上两个后端与消费方共享的词汇。提供方继承 `PtcRuntime`、实现 `resolve` 和 `run` 并注册服务;消费方(`dsh-tools` 中的 PTC mode)生成面向模型的 SDK 并桥接工具分发。按约定,运行时不了解工具与会话:它接收程序、具名异步绑定和已解析执行选项,然后返回捕获输出、执行结果与适用的沙箱事实。
+本包是 PTC 执行能力 seam 的 Service Definition 角色([能力 seam](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md)):一个注册为 `ctx.ptcRuntime` 的抽象 `PtcRuntime extends Service`,加上提供方与消费方共享的词汇。提供方继承 `PtcRuntime`、实现 `resolve` 和 `run` 并注册服务。`dsh-tools` 中的 PTC mode 负责工具绑定,`dsh-workflow-ptc` 负责工作流钩子与子 agent。按约定,运行时不了解工具与会话:它接收程序、具名异步绑定和已解析执行选项,然后返回捕获输出、执行结果与适用的沙箱事实。
 
 ### 服务 API
 
-只读 `timeout` 描述符为支持逐次覆盖的提供方公开 `{ defaultMs, maxMs }`;`undefined` 表示消费方必须省略该字段。它报告呈现值,验证与截断仍由 `resolve` 负责
+只读 `timeout` 描述符公开数值型 `{ defaultMs, maxMs }`,供消费方呈现;描述符缺省表示不支持数值覆盖。省略 `timeoutMs` 使用提供方默认值;数值请求经过验证和封顶的经过时间预算;显式 `null` 请求不设经过时间截止。提供方拒绝不支持的选择。Node 工作流适配器请求 `null`,而面向模型的 `run_code` 工具只接受正数覆盖值
 
 `executionInstructions` 提供由运行时拥有的使用说明;不需要说明时返回空字符串。消费方可将其纳入程序呈现,无需根据语言或隔离描述符识别提供方;PTC 将它纳入已记录的 `run_code` schema。
 
@@ -78,7 +78,7 @@ binding-global 与 error-class 名称是语言可移植的:必须匹配 `[A-Za
 
 ### 词汇
 
-`PtcRunRequest` 携带程序、Host 绑定、取消和可选执行选择。`PtcRunSpec` 要求已解析的 cwd 与经过时间截止。`PtcBindingNamespace` 声明程序全局对象与可选的类型化拒绝构造器。`PtcRunResult` 将日志/值、失败与 `PtcRunSandbox` 事实分开;确切字段与提供方义务见 [`src/types.ts`](src/types.ts)。
+`PtcRunRequest` 携带程序、Host 绑定、取消和可选执行选择。`PtcRunSpec` 要求已解析的 cwd 和明确的数值或 null 截止选择。`PtcBindingNamespace` 声明程序全局对象与可选的类型化拒绝构造器。`PtcRunResult` 将日志/值、失败与 `PtcRunSandbox` 事实分开;确切字段与提供方义务见 [`src/types.ts`](src/types.ts)。
 
 ### 可移植标识符
 
@@ -112,7 +112,7 @@ binding-global 与 error-class 名称是语言可移植的:必须匹配标识
 <a id="model-experience"></a>
 ## 模型体验
 
-通过 `dsh-tools` 中的 PTC mode 间接提供;后者公开 `run_code`,并将程序日志、值或失败作为保留的工具结果 token 返回
+通过 `dsh-tools` 中的 PTC mode 与工作流适配器间接提供;它们通过各自的工具结果呈现程序结果
 
 #### KV Cache 影响
 

+ 1 - 1
packages/ptc-runtime/ptc-runtime/src/index.ts

@@ -127,7 +127,7 @@ export abstract class PtcRuntime extends Service {
   /** Deployment file-policy mode, or undefined for a provider without confinement support. */
   get sandboxMode(): SandboxMode | undefined { return undefined }
 
-  /** Configured elapsed-time defaults and cap, or undefined when per-call overrides are unsupported. */
+  /** Configured numeric elapsed-time defaults and cap, or undefined when per-call overrides are unsupported. */
   get timeout(): { defaultMs: number; maxMs: number } | undefined { return undefined }
 
   constructor(ctx: Context) {

+ 8 - 5
packages/ptc-runtime/ptc-runtime/src/types.ts

@@ -82,8 +82,11 @@ export interface PtcRunRequest {
   bindings: PtcBindingNamespace[]
   /** Working directory in the mounted filesystem and subprocess execution world. */
   cwd?: string
-  /** Requested elapsed execution time; the provider's resolver validates and caps it. */
-  timeoutMs?: number
+  /**
+   * Elapsed execution budget in milliseconds. Omission uses provider defaults;
+   * null requests no deadline. Providers validate and cap numeric budgets or reject unsupported choices.
+   */
+  timeoutMs?: number | null
   /** Resolved authority for this execution. Providers without confinement reject an explicit policy. */
   sandboxPolicy?: SandboxExecutionPolicy
   /**
@@ -94,12 +97,12 @@ export interface PtcRunRequest {
   signal?: AbortSignal
 }
 
-/** Fully resolved execution inputs; run never supplies a missing directory or timeout. */
+/** Fully resolved execution inputs; run never supplies a missing directory or deadline choice. */
 export interface PtcRunSpec extends PtcRunRequest {
   /** Absolute directory in the provider's execution world. */
   cwd: string
-  /** Positive finite execution deadline in milliseconds, after provider capping. */
-  timeoutMs: number
+  /** Positive finite elapsed budget in milliseconds after provider capping, or null for no deadline. */
+  timeoutMs: number | null
 }
 
 /** File confinement applied to a program, independently of its terminal outcome. */

+ 2 - 2
packages/workflow/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/workflow/README.md
-README.md: c213c59386be610724bd52fa488f5c21e3a53d5d
-README.zh.md: 403d87401cac5401e11382ba4794973b5faa3c02
+README.md: 2eb78c2b8fce87aeadad5fab4f3d5a5ea983e514
+README.zh.md: 33f590e36b01d138438a491b4521e27b82da9f10

+ 3 - 3
packages/workflow/README.md

@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
 
 ## Summary
 
-The workflow group lets an agent run a model-authored orchestration script that fans work out across many subagents and returns a final value. The `workflow` package provides the run service, the worker-thread package executes scripts in isolated threads, and two model-facing tools expose orchestration: the general `workflow` tool for scripted fan-out and the fixed `ralph` tool for fresh-agent iterative loops. The script coordinates agents with hooks while the agents do the actual work. The engine keeps a script's synchronous work off the host event loop but is containment, not a security boundary.
+The workflow group lets an agent run orchestration scripts that delegate work to subagents and return a final value. The `workflow` tool supports scripted fan-out; the opt-in `ralph` tool runs a fixed sequence of fresh agents. Scripts use the shared PTC Node process runtime under the calling Session's file policy. Workflow hooks and child lifecycle remain owned by the workflow engine.
 
 ## Table of Contents
 
@@ -25,7 +25,7 @@ The workflow group lets an agent run a model-authored orchestration script that
 | Package | Role | ctx key |
 |---|---|---|
 | [`workflow`](workflow/README.md) | Runs a model-written orchestration script that fans out subagents | `ctx.workflowEngine` |
-| [`workflow-worker-thread`](workflow-worker-thread/README.md) | Executes each workflow script in its own worker thread, off the host event loop | registers on `ctx.workflowEngine` |
+| [`workflow-ptc`](workflow-ptc/README.md) | Runs workflow scripts through the shared sandboxed PTC Node process runtime | registers on `ctx.workflowEngine` |
 | [`tool-workflow`](tool-workflow/README.md) | Gives the model the `workflow` tool for scripted multi-agent orchestration | registers on `ctx.tools` |
 | [`tool-ralph`](tool-ralph/README.md) | Gives the model the `ralph` tool for fresh-agent iterative loops | registers on `ctx.tools` |
 
@@ -37,7 +37,7 @@ The workflow group lets an agent run a model-authored orchestration script that
 - [Workflow subsystem](../../docs/subsystems/workflow.md) — the seam's types, start request, and `workflow/*` events.
 - [Generated tool catalog](../../docs/tool-catalog.md#deepseek-aidsh-tool-workflow) — the `workflow` tool schema the model receives.
 - [Generated tool catalog](../../docs/tool-catalog.md#deepseek-aidsh-tool-ralph) — the `ralph` tool schema the model receives.
-- [Generated configuration catalog](../../docs/config-catalog.md#deepseek-aidsh-workflow-worker-thread) — every accepted engine config field.
+- [Generated configuration catalog](../../docs/config-catalog.md#deepseek-aidsh-workflow-ptc) — every accepted engine config field.
 - [Dynamic workflows Agent Note](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md) — the seam design and its decisions.
 - [Harness-level goal-based execution Agent Note](../../.agents/notes/implemented/feature/2026-07-16-harness-level-loop.md) — the fixed fresh-agent loop design and deferred work.
 

+ 3 - 3
packages/workflow/README.zh.md

@@ -9,7 +9,7 @@ kind: "package-group"
 
 ## 概述
 
-workflow 组让 agent(智能体)可以运行一段由模型编写的编排脚本,把工作扇出到多个 subagent 并返回最终值。`workflow` 包提供运行服务,worker-thread 包在隔离线程中执行脚本,两个面向模型的工具公开编排能力:通用的 `workflow` 工具用于脚本化扇出,固定的 `ralph` 工具用于全新 agent 迭代循环。脚本用钩子协调 agent,实际工作由 agent 完成。引擎把脚本的同步工作移出宿主事件循环,但这只是隔离,不是安全边界
+workflow 组让 agent(智能体)可以运行编排脚本,将工作委派给 subagent 并返回最终值。`workflow` 工具支持脚本化扇出;需显式启用的 `ralph` 工具运行固定的全新 agent 序列。脚本使用共享 PTC Node 进程运行时,遵守调用 Session 的文件策略。工作流钩子和子 agent 生命周期仍由工作流引擎负责
 
 ## 目录
 
@@ -25,7 +25,7 @@ workflow 组让 agent(智能体)可以运行一段由模型编写的编排
 | 包 | 职责 | ctx 键 |
 |---|---|---|
 | [`workflow`](workflow/README.zh.md) | 运行由模型编写的、扇出 subagent 的编排脚本 | `ctx.workflowEngine` |
-| [`workflow-worker-thread`](workflow-worker-thread/README.zh.md) | 在独立 worker thread 中执行每个工作流脚本,移出宿主事件循环 | 注册到 `ctx.workflowEngine` |
+| [`workflow-ptc`](workflow-ptc/README.zh.md) | 通过共享的沙箱化 PTC Node 进程运行时执行工作流脚本 | 注册到 `ctx.workflowEngine` |
 | [`tool-workflow`](tool-workflow/README.zh.md) | 把 `workflow` 工具交给模型,用于脚本化多 agent 编排 | 注册到 `ctx.tools` |
 | [`tool-ralph`](tool-ralph/README.zh.md) | 把 `ralph` 工具交给模型,用于全新 agent 迭代循环 | 注册到 `ctx.tools` |
 
@@ -37,7 +37,7 @@ workflow 组让 agent(智能体)可以运行一段由模型编写的编排
 - [工作流子系统](../../docs/subsystems/workflow.zh.md)——seam 的类型、启动请求与 `workflow/*` 事件。
 - [生成的工具目录](../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-workflow)——模型接收的 `workflow` 工具 schema。
 - [生成的工具目录](../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-ralph)——模型接收的 `ralph` 工具 schema。
-- [生成的配置目录](../../docs/config-catalog.zh.md#deepseek-aidsh-workflow-worker-thread)——每个受支持的引擎配置字段。
+- [生成的配置目录](../../docs/config-catalog.zh.md#deepseek-aidsh-workflow-ptc)——每个受支持的引擎配置字段。
 - [动态工作流 Agent Note](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.zh.md)——seam 设计及其决策。
 - [Harness 层目标式执行 Agent Note](../../.agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md)——固定全新 agent 循环的设计与暂缓事项。
 

+ 2 - 2
packages/workflow/tool-ralph/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/workflow/tool-ralph/README.md
-README.md: 7f9b45727d0f86b93e0caa72b42aa7f465cae465
-README.zh.md: 5114e3f327a19794af98f47e70b9174b1c1dda6b
+README.md: d94d3a6ca9b74e65a386b3ca43c8dd34ce8781a0
+README.zh.md: bb7b3bd81d1b72a501a687dc92e5f2ce49ac7969

+ 2 - 2
packages/workflow/tool-ralph/README.md

@@ -70,7 +70,7 @@ Status-specific semantics and the serialized `maxHandoffChars` ceiling are valid
 
 ### Lifecycle and cancellation
 
-The caller's agent is the parent of every fresh child, preserving cwd and lineage without copying its conversation. `exec.signal` enters the workflow engine and is also bridged to `run.cancel()` for implementation independence. The tool awaits `run.result` and calls `run.dispose()` in `finally`, so a cancelled parent step waits for the engine's bounded termination and child quiescence before returning.
+The caller's agent is the parent of every fresh child, preserving cwd and lineage without copying its conversation. `exec.signal` enters the workflow engine and is also bridged to `run.cancel()` for implementation independence. The tool awaits `run.result` and calls `run.dispose()` in `finally`, so a cancelled parent step waits for process and child cleanup before returning. The PTC engine sets no overall workflow elapsed deadline; caller cancellation still applies.
 
 ### Render intent
 
@@ -94,7 +94,7 @@ Read these pages when the tool-level contract is not enough. They move from the
 
 - [Workflow subsystem](../../../docs/subsystems/workflow.md) — the seam contract behind the fixed loop.
 - [Workflow seam](../workflow/README.md) — the run and result vocabulary.
-- [Worker-thread engine](../workflow-worker-thread/README.md) — the engine that executes the fixed script.
+- [PTC workflow engine](../workflow-ptc/README.md) — the engine that executes the fixed script.
 - [subagent seam](../../subagent/subagent/README.md) — the fresh-child provider contract.
 - [Goal group](../../goal/goal/README.md) — same-session goal tools for ordinary long-running objectives.
 - [Harness-level goal-based execution Agent Note](../../../.agents/notes/implemented/feature/2026-07-16-harness-level-loop.md) — the policy, provider requirements, and deferred work.

+ 2 - 2
packages/workflow/tool-ralph/README.zh.md

@@ -70,7 +70,7 @@ kind: "package-reference"
 
 ### 生命周期与取消
 
-调用方 agent 是每个全新子 agent 的父级,因此会保留 cwd 与谱系,但不会复制其对话。`exec.signal` 进入工作流引擎,同时也桥接到 `run.cancel()`,以便不依赖具体实现。工具等待 `run.result` 并在 `finally` 中调用 `run.dispose()`,因此被取消的父级步骤会等到引擎完成有界终止且子 agent 完全停稳后才返回
+调用方 agent 是每个全新子 agent 的父级,因此会保留 cwd 与谱系,但不会复制其对话。`exec.signal` 进入工作流引擎,同时也桥接到 `run.cancel()`,以便不依赖具体实现。工具等待 `run.result` 并在 `finally` 中调用 `run.dispose()`,因此被取消的父级步骤会等到进程与子 agent 清理完成后才返回。PTC 引擎不设整体工作流经过时间截止;调用方取消仍然生效
 
 ### 渲染意图
 
@@ -94,7 +94,7 @@ kind: "package-reference"
 
 - [工作流子系统](../../../docs/subsystems/workflow.zh.md)——固定循环背后的 seam 约定。
 - [工作流 seam](../workflow/README.zh.md)——运行与结果词汇。
-- [worker-thread 引擎](../workflow-worker-thread/README.zh.md)——执行固定脚本的引擎。
+- [PTC 工作流引擎](../workflow-ptc/README.zh.md)——执行固定脚本的引擎。
 - [subagent seam](../../subagent/subagent/README.zh.md)——全新子 agent 的提供方约定。
 - [goal 组](../../goal/goal/README.zh.md)——面向普通长期目标的同会话 goal 工具。
 - [Harness 层目标式执行 Agent Note](../../../.agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md)——策略、提供方要求与暂缓事项。

+ 1 - 1
packages/workflow/tool-ralph/package.json

@@ -51,7 +51,7 @@
     "@deepseek-ai/dsh-system-prompt": "workspace:^",
     "@deepseek-ai/dsh-tools": "workspace:^",
     "@deepseek-ai/dsh-workflow": "workspace:^",
-    "@deepseek-ai/dsh-workflow-worker-thread": "workspace:^",
+    "@deepseek-ai/dsh-workflow-ptc": "workspace:^",
     "@deepseek-ai/cordis": "workspace:^",
     "@deepseek-ai/dsh-session-projection": "workspace:^"
   }

+ 25 - 9
packages/workflow/tool-ralph/tests/integration.spec.ts

@@ -1,4 +1,7 @@
-import { describe, expect, it } from 'vitest'
+import { describe, expect, it, onTestFinished } from 'vitest'
+import { mkdtemp, rm } from 'node:fs/promises'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
 import { Context } from '@deepseek-ai/cordis'
 import type { Agent } from '@deepseek-ai/dsh-agent'
 import AgentLoop from '@deepseek-ai/dsh-agent-loop'
@@ -8,33 +11,45 @@ import { SessionId } from '@deepseek-ai/dsh-session'
 import SubagentRuntime from '@deepseek-ai/dsh-subagent'
 import { STRUCTURED_OUTPUT_TOOL } from '@deepseek-ai/dsh-subagent-in-process-driver'
 import * as spawn from '@deepseek-ai/dsh-subagent-spawn-in-process'
-import WorkerThreadWorkflowEngine from '@deepseek-ai/dsh-workflow-worker-thread'
+import PtcWorkflowEngine from '@deepseek-ai/dsh-workflow-ptc'
 import { MockAdapter, maxTokensResponse, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
 import * as toolRalph from '../src/index.ts'
+import { mountWorkflowRuntime } from '../../workflow-ptc/tests/setup.ts'
 
 type MockScript = ConstructorParameters<typeof MockAdapter>[0]
 const testToolSignal = new AbortController().signal
 
+async function mountExecution(ctx: Context): Promise<string> {
+  const cwd = await mkdtemp(join(tmpdir(), 'dsh-ralph-'))
+  onTestFinished(async () => {
+    await ctx.fiber.dispose()
+    await rm(cwd, { recursive: true, force: true })
+  })
+  await mountWorkflowRuntime(ctx, { cwd })
+  return cwd
+}
+
 /** Mount the shipped Ralph execution stack around one keyless model script. */
 async function mountRalph(script: MockScript, config: toolRalph.Config) {
   const ctx = new Context()
   const adapter = new MockAdapter(script)
   await mountAgentLoopTestDependencies(ctx)
+  const cwd = await mountExecution(ctx)
   await ctx.plugin(AgentLoop, { agents: [] })
   await ctx.plugin(SubagentRuntime)
   await ctx.plugin(spawn, { providerName: 'spawn' })
-  await ctx.plugin(WorkerThreadWorkflowEngine, {})
+  await ctx.plugin(PtcWorkflowEngine, {})
   await ctx.plugin(toolRalph, config)
   ctx.llm.registerAdapter(['mock'], adapter)
   const parentHandle = await ctx.agents.create({
     sessionId: SessionId('ralph-parent'),
-    meta: { cwd: '/tmp/ralph-shared-workspace' },
+    meta: { cwd },
     agentOptions: { provider: 'mock', model: 'mock' },
   })
   return { ctx, adapter, parentHandle, parent: parentHandle.agent }
 }
 
-describe('dsh-tool-ralph over the real spawn and worker-thread stack', () => {
+describe('dsh-tool-ralph over the real spawn and sandboxed PTC stack', () => {
   it('uses distinct empty-seed children, shared cwd, and only the prior bounded handoff', { timeout: 90_000 }, async () => {
     const firstReport = {
       status: 'continue',
@@ -57,16 +72,17 @@ describe('dsh-tool-ralph over the real spawn and worker-thread stack', () => {
       toolCallResponse('round-2', STRUCTURED_OUTPUT_TOOL, finalReport),
     ])
     await mountAgentLoopTestDependencies(ctx)
+    const cwd = await mountExecution(ctx)
     await ctx.plugin(AgentLoop, { agents: [] })
     await ctx.plugin(SubagentRuntime)
     await ctx.plugin(spawn, { providerName: 'spawn' })
-    await ctx.plugin(WorkerThreadWorkflowEngine, {})
+    await ctx.plugin(PtcWorkflowEngine, {})
     await ctx.plugin(toolRalph, { maxRounds: 2 })
     ctx.llm.registerAdapter(['mock'], adapter)
 
     const parentHandle = await ctx.agents.create({
       sessionId: SessionId('ralph-parent'),
-      meta: { cwd: '/tmp/ralph-shared-workspace' },
+      meta: { cwd },
       agentOptions: { provider: 'mock', model: 'mock' },
     })
     const parent = parentHandle.agent
@@ -96,7 +112,7 @@ describe('dsh-tool-ralph over the real spawn and worker-thread stack', () => {
     expect(children).toHaveLength(2)
     expect(new Set(children.map(child => child.id)).size).toBe(2)
     for (const child of children) {
-      expect(child.session.header.cwd).toBe('/tmp/ralph-shared-workspace')
+      expect(child.session.header.cwd).toBe(cwd)
       expect(child.session.header.parentSession).toBe(parent.session.header.id)
       expect(child.session.header.isSeeded).toBe(false)
       expect(child.session.inheritedEventCount).toBe(0)
@@ -236,7 +252,7 @@ describe('dsh-tool-ralph over the real spawn and worker-thread stack', () => {
     await parentHandle.dispose()
   })
 
-  it('cancels the real worker and fresh child to quiescence', { timeout: 90_000 }, async () => {
+  it('cancels the sandboxed process and fresh child to quiescence', { timeout: 90_000 }, async () => {
     const { ctx, parent, parentHandle } = await mountRalph(['hang'], { maxRounds: 2 })
     const children: Agent[] = []
     const outcomes: string[] = []

+ 2 - 2
packages/workflow/tool-workflow/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/workflow/tool-workflow/README.md
-README.md: 8c93fa2658703f2252b3300a4c55cec4ae5fa0cf
-README.zh.md: 354502a49f0157af2fb3e6c2109e303184400a44
+README.md: 4cf16a233637da3fb2384ba80cdba8adb0298e5a
+README.zh.md: cfbbdd74f14c1c6f8c8c97b2bb84366ff61bb4d1

+ 2 - 2
packages/workflow/tool-workflow/README.md

@@ -58,7 +58,7 @@ This section explains how the consumer is split from the engine and how the run
 
 ### Design concept
 
-The consumer owns the model-facing schema, the `tool:<toolName>` system-prompt guidance, and the result envelope; script parsing, execution, caps, and cancellation live behind `ctx.workflowEngine`, so a hardened engine swaps in without changing what the model sees. Usage guidance ships with the tool plugin as a prompt section, never in the deployment persona.
+The consumer owns the model-facing schema, the `tool:<toolName>` system-prompt guidance, and the result envelope; script parsing, execution, caps, and cancellation live behind `ctx.workflowEngine`, while the PTC engine shares Node process confinement with `run_code`. Usage guidance ships with the tool plugin as a prompt section, never in the deployment persona.
 
 ### Run lifecycle
 
@@ -91,7 +91,7 @@ Read these pages when the tool-level contract is not enough. They move from the
 
 - [Workflow subsystem](../../../docs/subsystems/workflow.md) — the seam contract, start request, and event payloads.
 - [Workflow seam](../workflow/README.md) — the run and result vocabulary behind the tool.
-- [Worker-thread engine](../workflow-worker-thread/README.md) — the engine that executes the scripts.
+- [PTC workflow engine](../workflow-ptc/README.md) — the engine that executes the scripts.
 - [subagent tool](../../subagent/tool-subagent/README.md) — the plain-delegation alternative for one or two children.
 - [Group map](../README.md) — the workflow capability family and its packages.
 - [Dynamic workflows Agent Note](../../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md) — the seam design and its decisions.

+ 2 - 2
packages/workflow/tool-workflow/README.zh.md

@@ -58,7 +58,7 @@ kind: "package-reference"
 
 ### 设计理念
 
-消费方拥有模型侧 schema、`tool:<toolName>` 系统提示词指导与结果包络;脚本解析、执行、上限与取消位于 `ctx.workflowEngine` 之后,因此更坚固的引擎可以无缝替换,而不改变模型看到的内容。使用指导以提示词段的形式随工具插件交付,绝不放入部署 persona。
+消费方拥有模型侧 schema、`tool:<toolName>` 系统提示词指导与结果包络;脚本解析、执行、上限与取消位于 `ctx.workflowEngine` 之后,PTC 引擎与 `run_code` 共享 Node 进程约束。使用指导以提示词段的形式随工具插件交付,绝不放入部署 persona。
 
 ### 运行生命周期
 
@@ -91,7 +91,7 @@ kind: "package-reference"
 
 - [工作流子系统](../../../docs/subsystems/workflow.zh.md)——seam 约定、启动请求与事件载荷。
 - [工作流 seam](../workflow/README.zh.md)——工具背后的运行与结果词汇。
-- [worker-thread 引擎](../workflow-worker-thread/README.zh.md)——执行脚本的引擎。
+- [PTC 工作流引擎](../workflow-ptc/README.zh.md)——执行脚本的引擎。
 - [subagent 工具](../../subagent/tool-subagent/README.zh.md)——一两项委派时的普通委派替代方案。
 - [组地图](../README.zh.md)——工作流能力家族及其包。
 - [动态工作流 Agent Note](../../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.zh.md)——seam 设计及其决策。

+ 1 - 1
packages/workflow/tool-workflow/package.json

@@ -58,7 +58,7 @@
     "@deepseek-ai/dsh-system-prompt": "workspace:^",
     "@deepseek-ai/dsh-tools": "workspace:^",
     "@deepseek-ai/dsh-workflow": "workspace:^",
-    "@deepseek-ai/dsh-workflow-worker-thread": "workspace:^",
+    "@deepseek-ai/dsh-workflow-ptc": "workspace:^",
     "@deepseek-ai/cordis": "workspace:^",
     "@deepseek-ai/dsh-session-projection": "workspace:^"
   }

Деякі файли не було показано, через те що забагато файлів було змінено