diff --git a/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.i18n.yaml
index 22c7dbe890..9699976dbf 100644
--- a/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.i18n.yaml
+++ b/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.i18n.yaml
@@ -9,8 +9,8 @@
en: 66ac3d7ce7eb8e15
zh: abb9d41b71900bbf
/agent-note-sandboxed-node-execution-for-ptc/decision:
- en: cf072f53de09faca
- zh: 9edd5c4a2bfaae5a
+ en: d6b9c22603a4f933
+ zh: f324b91830078d9a
/agent-note-sandboxed-node-execution-for-ptc/decision/resolved-inputs-and-policy:
en: e93df995efab569a
zh: 850daf02d1207028
@@ -18,8 +18,8 @@
en: 2c7a694b04951772
zh: 133b95b0477b127f
/agent-note-sandboxed-node-execution-for-ptc/decision/resource-limits:
- en: b8e87a87cbcc4fa6
- zh: 5613a8d69c84c9f3
+ en: afda56ffba40d34b
+ zh: 92a8e4684138aed2
/agent-note-sandboxed-node-execution-for-ptc/alternatives-considered:
en: 8ffcc2b32a2243d7
zh: 81d48d38947abc76
diff --git a/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.md b/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.md
index 7efe06e8a4..22e50e5d08 100644
--- a/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.md
+++ b/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.md
@@ -12,7 +12,7 @@ The [PTC foundation](../feature/2026-06-15-ptc.md) remains responsible for regis
## Decision
-`dsh-ptc-runtime-node` runs each program in one fresh Node process. The host resolves execution choices, confines the launch through the same `ctx.sandbox` provider as Bash, and gives process lifetime to `ctx.subprocess`. The child evaluates erasable TypeScript with direct Node APIs, an empty model environment and host-provided asynchronous bindings. No worker or persistent kernel remains inside this provider.
+`dsh-ptc-runtime-node` in its default `per-call` mode runs each program in one fresh Node process. The host resolves execution choices, confines the launch through the same `ctx.sandbox` provider as Bash, and gives process lifetime to `ctx.subprocess`. The child evaluates erasable TypeScript with direct Node APIs, an empty model environment and host-provided asynchronous bindings. No worker or persistent kernel remains after a per-call execution.
The host preserves `ELECTRON_RUN_AS_NODE` for child startup; the bootstrap removes it from the native environment before evaluation, and model-visible `process.env` stays empty. Nested Electron launches require their own explicit Node-mode selection. Desktop uses Electron as its Node executable; removing this selector launches Electron's application path instead of the PTC bootstrap. Sandbox permission changes cannot repair that launch mismatch. The macOS Desktop regression uses real Electron to verify binding writes, direct workspace writes, and rejection of writes outside the workspace under restricted policy. It requires an installed Electron binary; ordinary runtime tests cover environment filtering without that dependency.
@@ -34,7 +34,7 @@ Program completion, timeout, cancellation and protocol failure all close executi
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.
-The separate [best-effort persistent provider](2026-09-23-best-effort-persistent-ptc.md) retains disposable memory under explicit full access; this decision continues to own the default confined provider.
+The opt-in [in-memory execution mode](2026-09-23-best-effort-persistent-ptc.md) retains disposable memory under explicit full access; this decision continues to own the default confined execution mode.
## Alternatives considered
diff --git a/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.zh.md b/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.zh.md
index 2382b02247..f7a81e6f94 100644
--- a/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.zh.md
+++ b/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.zh.md
@@ -12,7 +12,7 @@ Node worker 隔离 JavaScript 状态,但不应用调用 Session 的 OS 沙箱
## 决策
-`dsh-ptc-runtime-node` 在一个全新 Node 进程中运行每个程序。Host 解析执行选择,通过与 Bash 相同的 `ctx.sandbox` 提供方约束启动,并将进程生命周期交给 `ctx.subprocess`。子进程以直接 Node API、空模型环境和 Host 提供的异步绑定求值可擦除 TypeScript。本提供方不保留 worker 或持久内核。
+`dsh-ptc-runtime-node` 的默认 `per-call` 模式在一个全新 Node 进程中运行每个程序。Host 解析执行选择,通过与 Bash 相同的 `ctx.sandbox` 提供方约束启动,并将进程生命周期交给 `ctx.subprocess`。子进程以直接 Node API、空模型环境和 Host 提供的异步绑定求值可擦除 TypeScript。逐次执行结束后不保留 worker 或持久内核。
Host 在子进程启动时保留 `ELECTRON_RUN_AS_NODE`;bootstrap 在求值前将其从原生环境中删除,模型可见的 `process.env` 仍为空。嵌套启动 Electron 需要自行显式选择 Node 模式。桌面端使用 Electron 作为 Node 可执行文件;删除此选择变量会启动 Electron 应用路径,而不是 PTC bootstrap。更改沙箱权限无法修复这一启动模式不匹配。macOS 桌面端回归测试使用真实 Electron 验证绑定写入、直接工作区写入以及受限策略对工作区外写入的拒绝。该测试需要已安装的 Electron 二进制文件;普通运行时测试无需此依赖即可覆盖环境过滤。
@@ -34,7 +34,7 @@ Host 在子进程启动时保留 `ELECTRON_RUN_AS_NODE`;bootstrap 在求值前
默认经过时间截止为 120 秒,默认上限为 600 秒。可信服务消费方可以请求 `timeoutMs: null` 来禁用该定时器;[工作流沙箱复用](2026-09-13-workflow-ptc-sandbox-reuse.zh.md)负责这种由调用方控制的生命周期。省略 timeout 或使用数值的请求(包括面向模型的 `run_code`)保留数值默认值与上限。启用的截止包括运行时准备以及嵌套工具或审批等待。V8 老生代内存、序列化外层输出与控制通信具有独立配置的上限。堆限制不包含原生分配和后代内存,经过时间也不是进程树 CPU 预算。
-独立的[尽力持久提供方](2026-09-23-best-effort-persistent-ptc.zh.md)在显式完全访问下保留可丢失的内存;本决定继续负责默认的受约束提供方。
+显式启用的[内存保留执行模式](2026-09-23-best-effort-persistent-ptc.zh.md)在显式完全访问下保留可丢失的内存;本决定继续负责默认的受约束执行模式。
## 考虑过的替代方案
diff --git a/.agents/notes/implemented/architecture/2026-09-23-best-effort-persistent-ptc.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-23-best-effort-persistent-ptc.i18n.yaml
index 2206de0b42..797f1044e5 100644
--- a/.agents/notes/implemented/architecture/2026-09-23-best-effort-persistent-ptc.i18n.yaml
+++ b/.agents/notes/implemented/architecture/2026-09-23-best-effort-persistent-ptc.i18n.yaml
@@ -2,18 +2,18 @@
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
# After editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-23-best-effort-persistent-ptc.md
-/agent-note-best-effort-persistent-ptc-memory:
- en: 002038f955af77c1
- zh: 4a49c3f5a2b7cb43
-/agent-note-best-effort-persistent-ptc-memory/problem:
+/agent-note-node-ptc-execution-modes-and-in-memory-state:
+ en: 078a89753b06cd6a
+ zh: 4004e5e4bb42f2cc
+/agent-note-node-ptc-execution-modes-and-in-memory-state/problem:
en: e42db40b879fdbf4
- zh: 3018bd35363e7051
-/agent-note-best-effort-persistent-ptc-memory/decision:
- en: 3d3f7d0ff75dfe1c
- zh: 10c12e18dc348896
-/agent-note-best-effort-persistent-ptc-memory/alternatives-considered:
- en: 20e0ec55331dbcf6
- zh: e9eb7e0339c9a545
-/agent-note-best-effort-persistent-ptc-memory/consequences:
- en: 544f61693ab010d5
- zh: 4c24fbd7fe849c0f
+ zh: 6017da9c4da17835
+/agent-note-node-ptc-execution-modes-and-in-memory-state/decision:
+ en: c2c41c4566fd4f8f
+ zh: 780f2cb168f537e7
+/agent-note-node-ptc-execution-modes-and-in-memory-state/alternatives-considered:
+ en: d20d210ecfa615e4
+ zh: 0136bc91993ce518
+/agent-note-node-ptc-execution-modes-and-in-memory-state/consequences:
+ en: 70b2aa046aae8324
+ zh: cd051dff8b7f4c06
diff --git a/.agents/notes/implemented/architecture/2026-09-23-best-effort-persistent-ptc.md b/.agents/notes/implemented/architecture/2026-09-23-best-effort-persistent-ptc.md
index ab35a716ff..60398ef146 100644
--- a/.agents/notes/implemented/architecture/2026-09-23-best-effort-persistent-ptc.md
+++ b/.agents/notes/implemented/architecture/2026-09-23-best-effort-persistent-ptc.md
@@ -1,4 +1,4 @@
-# Agent Note: Best-effort persistent PTC memory
+# Agent Note: Node PTC execution modes and in-memory state
Status: implemented
@@ -10,7 +10,11 @@ Interactive automation retains application objects, imported modules and helper
## Decision
-The optional `dsh-ptc-runtime-node/persistent` provider keeps an unsandboxed Node REPL process per explicit, object-identity state owner. PTC uses the live Agent as owner and awaits release during its context disposal. The default [sandboxed provider](2026-09-11-sandboxed-node-ptc-runtime.md) retains its fresh-process behavior. This extends the [PTC foundation](../feature/2026-06-15-ptc.md) with disposable memory, while preserving registry dispatch and logged model requests.
+The existing `@deepseek-ai/dsh-ptc-runtime-node` plugin selects its execution mode through `executionMode`. The default `per-call` mode starts a fresh process under the configured file policy. Opt-in `in-memory` mode keeps an unsandboxed Node REPL process per explicit, object-identity state owner. Both modes provide the same `ctx.ptcRuntime` service. PTC uses the live Agent as owner and awaits release during its context disposal. Changing the configuration reloads the provider and releases its processes. The [sandboxed Node decision](2026-09-11-sandboxed-node-ptc-runtime.md) owns per-call confinement; the [PTC foundation](../feature/2026-06-15-ptc.md) owns tool dispatch and logged model requests.
+
+Mode names describe state retention rather than a process implementation or source syntax. `per-call` retains state for one invocation; `in-memory` retains it across invocations until the process or owner ends. `durable` names the deferred ability to save and restore execution state across restarts. It is not an accepted configuration value: a future implementation must define its recovery point, recoverable values and external-resource handling before exposing that mode. File confinement remains a separate property; in-memory execution currently rejects explicit sandbox policies.
+
+One npm package and one plugin entry keep existing installation and loading paths stable. Communication, binding proxies, JSON encoding and output limits remain package-local modules. The Node provider delegates retained process ownership to an internal execution object, which registers no additional Cordis service. Per-call and in-memory execution keep their distinct lifecycle rules.
The Session records submitted programs, nested tool calls and returned output. It does not serialize the REPL heap or rerun earlier programs after restart, resume or fork: replaying automation could repeat external side effects. A missing variable is a normal program error, and the model-visible instructions require recreating variables and reacquiring resources. No environment identifier or new durable Session event is required because the provider exposes no unlogged model input.
@@ -18,12 +22,16 @@ Cancellation, deadline expiry, protocol or output-limit failure, owner disposal
## Alternatives considered
-Serializing arbitrary JavaScript objects cannot preserve native resources and closures. Replaying prior programs can repeat writes and input actions. Both would imply recovery guarantees that this provider does not offer.
+Separate npm providers plus a shared library allow independent distribution, at the cost of another package, dependency declarations and cross-package exports for shared implementation details. The two Node modes have no current need for separate release schedules or independent dependencies. A second plugin subpath permits separate loading but asks deployments to replace plugin entries to select an execution policy. A configuration field expresses that selection while preserving the existing entry. Independent distribution or substantially different dependencies can justify revisiting this choice.
+
+A new Cordis service for common helpers would require another registration and injected dependency without adding an independently replaceable runtime capability. Internal functions and classes provide the required reuse. A common execution superclass would also couple fresh-process cleanup with owner-retained lifetimes; the provider shares mechanisms without merging those lifecycles.
+
+Serializing arbitrary JavaScript objects cannot preserve native resources and closures. Replaying prior programs can repeat writes and input actions. Both would imply recovery guarantees that in-memory execution does not offer.
A realm inside the Harness process shares process lifetime with model code and cannot terminate a synchronous loop independently. A managed child process permits cancellation and resource cleanup while retaining imports and variables during healthy use. This process is not a file sandbox or a multi-tenant security boundary.
## Consequences
-Enabling the provider grants direct Node code full host access. It is opt-in, rejects explicit sandbox policies, and leaves nested tools under their normal authorization and logging. Memory and native handles may disappear without a Harness restart. The model must recover from reference or resource errors; the runtime never retries a program automatically.
+Selecting `in-memory` grants direct Node code full host access. It is opt-in, rejects explicit sandbox policies, and leaves nested tools under their normal authorization and logging. Memory and native handles may disappear without a Harness restart. The model must recover from reference or resource errors; the runtime never retries a program automatically.
-An idle owner retains a process with no expiration timer. Callers release owners explicitly; ownerless service calls use temporary processes. Programs use REPL statements and a final expression rather than top-level return, and calls for one retained owner cannot overlap.
+An idle owner retains a process with no expiration timer. Callers release owners explicitly; ownerless service calls use temporary processes. Programs use REPL statements and a final expression rather than top-level return, and calls for one retained owner cannot overlap. Tool schemas and program guidance follow the selected mode.
diff --git a/.agents/notes/implemented/architecture/2026-09-23-best-effort-persistent-ptc.zh.md b/.agents/notes/implemented/architecture/2026-09-23-best-effort-persistent-ptc.zh.md
index 0ec7ae1f7c..16208f3a21 100644
--- a/.agents/notes/implemented/architecture/2026-09-23-best-effort-persistent-ptc.zh.md
+++ b/.agents/notes/implemented/architecture/2026-09-23-best-effort-persistent-ptc.zh.md
@@ -1,29 +1,37 @@
-# Agent Note: 尽力保留的持久 PTC 内存
+# Agent Note: Node PTC 执行模式与内存状态
Status: implemented
[English](2026-09-23-best-effort-persistent-ptc.md) | 中文
-## Problem
+## 问题
-交互式自动化需要在模型调用之间保留应用对象、导入模块和辅助函数。每个程序都重新创建对象,会使消费者无法在观察和操作之间使用普通 JavaScript 引用。
+交互式自动化需要在模型调用之间保留应用对象、导入模块和辅助函数。如果每次执行都重新创建对象,消费者就无法在观察与操作之间继续使用普通 JavaScript 引用。
-## Decision
+## 决策
-可选提供方 `dsh-ptc-runtime-node/persistent` 为每个以对象身份标识的显式状态所有者保留无沙盒 Node REPL 进程。PTC 使用存活 Agent 作为所有者,并在其上下文销毁时等待释放。默认的[沙盒提供方](2026-09-11-sandboxed-node-ptc-runtime.zh.md)保留每次启动新进程的行为。此决定为 [PTC 基础](../feature/2026-06-15-ptc.zh.md)增加可丢失的内存,同时保留注册表分派和模型请求日志。
+现有 `@deepseek-ai/dsh-ptc-runtime-node` 插件通过 `executionMode` 选择执行模式。默认的 `per-call` 模式按配置的文件策略启动新进程。显式启用的 `in-memory` 模式为每个以对象身份标识的状态所有者保留一个无沙箱 Node REPL 进程。两种模式都提供同一个 `ctx.ptcRuntime` 服务。PTC 使用存活的 Agent 作为所有者,在其上下文销毁时等待释放。修改配置会重载提供方并释放其进程。[Node 沙箱决策](2026-09-11-sandboxed-node-ptc-runtime.zh.md)负责逐次执行的文件约束,[PTC 基础决策](../feature/2026-06-15-ptc.zh.md)负责工具分派与模型请求日志。
-Session 记录提交的程序、嵌套工具调用和返回输出。它不序列化 REPL 堆,也不在重启、恢复或派生后重跑以前的程序,因为回放自动化可能重复外部副作用。缺失变量是普通程序错误,模型可见说明要求重新创建变量和获取资源。提供方没有暴露未记录的模型输入,因此不需要环境标识符或新的持久 Session 事件。
+模式名称描述状态保留方式,不绑定进程实现或代码语法。`per-call` 只为一次调用保留状态;`in-memory` 跨调用保留状态,直到进程或所有者结束。`durable` 用于命名未来跨重启保存与恢复执行状态的能力。它不是可接受的配置值:实现必须先明确恢复点、可恢复的值以及外部资源的处理方式,才能公开这个模式。文件约束是独立属性;内存保留模式目前拒绝显式沙箱策略。
-取消、截止到期、协议失败、输出超限、所有者销毁和进程退出会丢弃环境。普通异常保留此前发生的修改。保留的函数使用当前执行的宿主绑定;较早执行产生的异步工作不能取得后续执行的工具权限或输出接收器。
+一个 npm 包和一个插件入口保持现有安装与加载路径稳定。通信、绑定代理、JSON 编码和输出限制继续作为包内模块复用。Node 提供方将保留进程的所有权交给内部执行对象,该对象不注册额外的 Cordis 服务。逐次执行和内存保留各自管理不同的生命周期。
-## Alternatives considered
+Session 记录提交的程序、嵌套工具调用和返回输出。它不序列化 REPL 堆,也不在重启、恢复或派生后重新执行旧程序,因为重放自动化可能重复外部操作。缺失变量产生普通程序错误,模型指引要求重新创建变量并获取资源。提供方没有引入日志之外的模型输入,因此不需要环境标识或新的持久 Session 事件。
-序列化任意 JavaScript 对象无法保留原生资源和闭包。回放以前的程序可能重复写入和输入操作。这两种方案都会隐含本提供方没有承诺的恢复保证。
+取消、截止时间到期、协议失败、输出超限、所有者销毁和进程退出都会丢弃环境。普通异常保留此前发生的修改。保留的函数使用当前执行的宿主绑定;较早执行中的异步任务不能获取后续执行的工具权限或向其输出。
-Harness 进程内的执行域与模型代码共享进程寿命,无法单独终止同步循环。受管子进程允许取消和资源清理,同时在正常使用时保留导入模块和变量。该进程不提供文件沙盒或多租户安全隔离。
+## 考虑过的替代方案
-## Consequences
+将提供方拆成独立 npm 包并增加共享库,可以分别分发,但需要维护额外的包、依赖声明和共享实现的跨包导出。两种 Node 模式目前没有不同发布节奏或独立依赖的需求。第二个插件子路径允许独立加载,但要求部署配置通过替换插件入口来选择执行策略。配置字段直接表达这个选择,并保留已有入口。需要独立分发或出现明显不同的依赖时,可以重新评估拆包。
-启用提供方会允许直接 Node 代码完全访问宿主。它需要显式启用,拒绝显式沙盒策略,嵌套工具继续遵循原有授权和日志规则。即使 Harness 没有重启,内存和原生句柄也可能丢失。模型必须从引用错误或资源错误中恢复;运行时不会自动重试程序。
+为公共辅助代码新增 Cordis 服务,会增加注册和注入依赖,却没有提供独立替换的运行能力。内部函数和类已经能够完成复用。共同的执行基类还会把新进程清理与按所有者保留进程的生命周期耦合起来,因此提供方共用处理机制,各自管理生命周期。
-空闲所有者保留进程,没有过期计时器。调用方显式释放所有者;没有所有者的服务调用使用临时进程。程序使用 REPL 语句和末尾表达式,不能使用顶层 return;同一保留环境的调用不能重叠。
+序列化任意 JavaScript 对象无法保留原生资源和闭包。重放旧程序可能重复写入和输入操作。这两种做法都会暗示内存保留模式没有提供的恢复保证。
+
+Harness 进程内的独立执行上下文与模型代码共享进程生命周期,无法单独终止同步死循环。托管子进程可以在正常使用时保留导入和变量,同时支持取消与资源清理。这个进程不提供文件沙箱,也不构成多租户安全隔离。
+
+## 影响
+
+选择 `in-memory` 会赋予直接 Node 代码完整的宿主访问权限。该模式需要显式启用,拒绝显式沙箱策略;嵌套工具仍遵循原有授权和日志规则。即使 Harness 没有重启,内存和原生句柄也可能丢失。模型需要从引用或资源错误中恢复,运行时不会自动重试程序。
+
+空闲所有者保留一个没有过期计时器的进程。调用方显式释放所有者,没有所有者的服务调用使用临时进程。程序使用 REPL 语句和末尾表达式,不支持顶层 return;同一保留所有者的调用不能重叠。工具 schema 和程序指引跟随所选模式。
diff --git a/.agents/notes/implemented/feature/2026-06-15-ptc.i18n.yaml b/.agents/notes/implemented/feature/2026-06-15-ptc.i18n.yaml
index c85af1bf90..ebf3580bd4 100644
--- a/.agents/notes/implemented/feature/2026-06-15-ptc.i18n.yaml
+++ b/.agents/notes/implemented/feature/2026-06-15-ptc.i18n.yaml
@@ -39,8 +39,8 @@
en: 761eb6c1aeb7af18
zh: 5434456a3d0a1b65
/agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/testing:
- en: a8bd8cbe4b344fb8
- zh: 82ba8b452fae7f4d
+ en: 6e637b0970682265
+ zh: 7fed6e98f87b5f1a
/agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/alternatives-considered:
en: 90110f91f6f6130f
zh: 25d28433840f91a4
diff --git a/.agents/notes/implemented/feature/2026-06-15-ptc.md b/.agents/notes/implemented/feature/2026-06-15-ptc.md
index 220f8d27fa..ba215d1e46 100644
--- a/.agents/notes/implemented/feature/2026-06-15-ptc.md
+++ b/.agents/notes/implemented/feature/2026-06-15-ptc.md
@@ -101,7 +101,7 @@ Deployments switching to `'ptc'` must update any native-only `toolOrder`. Assemb
- **With-key e2e:** A real model composes two bash calls in one program; another discovers nested workspace instructions through a PTC mode fs dispatch. The tests verify collapsed request headers, correlated dispatch events, resulting files, deferred context, and model behavior.
- **Snapshot:** The `ptc-turn`, `both-mode-turn`, and `ptc-workspace-context` fixtures pin SDK text, header tool lists, dispatch events, deferred context, and result cards. The TypeScript SDK PTC scenario mounts its Node runtime through an explicit test-owned profile patch and pins Session events and JSON-RPC notifications; its expected response and completed-turn checks run before refresh writes.
-The optional [best-effort persistent provider](../architecture/2026-09-23-best-effort-persistent-ptc.md) permits disposable cross-call memory without restoring objects or replaying programs after restart; the default provider stays stateless.
+The optional [Node in-memory execution mode](../architecture/2026-09-23-best-effort-persistent-ptc.md) permits disposable cross-call memory without restoring objects or replaying programs after restart; the default execution mode stays stateless.
## Alternatives considered
diff --git a/.agents/notes/implemented/feature/2026-06-15-ptc.zh.md b/.agents/notes/implemented/feature/2026-06-15-ptc.zh.md
index e244d0173e..7207892a9b 100644
--- a/.agents/notes/implemented/feature/2026-06-15-ptc.zh.md
+++ b/.agents/notes/implemented/feature/2026-06-15-ptc.zh.md
@@ -101,7 +101,7 @@ SDK 指示模型编写一个所加载运行时语言的异步函数体(默认
- **带密钥 e2e:** 真实模型在一个程序中组合两次 bash 调用;另一个模型通过 PTC mode fs 分发发现嵌套的工作区指令。测试验证折叠的请求头、关联的分发事件、结果文件、延后上下文和模型行为。
- **快照:** `ptc-turn`、`both-mode-turn` 和 `ptc-workspace-context` fixture(测试前置数据)固定 SDK 文本、请求头工具列表、分发事件、延后上下文和结果卡片。TypeScript SDK PTC 场景通过测试拥有的显式 profile patch 挂载 Node 运行时,并固定 Session 事件与 JSON-RPC 通知;预期回复和完成轮次检查在 refresh 写入前执行。
-可选的[尽力持久提供方](../architecture/2026-09-23-best-effort-persistent-ptc.zh.md)允许跨调用保留可丢失的内存,不在重启后恢复对象或回放程序;默认提供方保持无状态。
+可选的[Node 内存保留执行模式](../architecture/2026-09-23-best-effort-persistent-ptc.zh.md)允许跨调用保留可丢失的内存,不在重启后恢复对象或回放程序;默认执行模式保持无状态。
## 曾考虑的替代方案
diff --git a/docs/config-catalog.md b/docs/config-catalog.md
index b5b85518c6..7c602c88ec 100644
--- a/docs/config-catalog.md
+++ b/docs/config-catalog.md
@@ -2283,6 +2283,8 @@ export interface Config {
```ts config-catalog
/** Deployment-varying runtime bounds and launch choices. */
export interface Config extends LaunchConfig {
+ /** per-call starts fresh confined processes; in-memory retains an unsandboxed REPL per owner. */
+ executionMode?: 'per-call' | 'in-memory'
/** Default elapsed deadline, including nested tool and approval waits. */
timeoutMs?: number
/** Maximum numeric elapsed budget accepted by resolve. */
diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md
index 6256541d31..59ae7416df 100644
--- a/docs/config-catalog.zh.md
+++ b/docs/config-catalog.zh.md
@@ -2285,6 +2285,8 @@ export interface Config {
```ts config-catalog
/** Deployment-varying runtime bounds and launch choices. */
export interface Config extends LaunchConfig {
+ /** per-call starts fresh confined processes; in-memory retains an unsandboxed REPL per owner. */
+ executionMode?: 'per-call' | 'in-memory'
/** Default elapsed deadline, including nested tool and approval waits. */
timeoutMs?: number
/** Maximum numeric elapsed budget accepted by resolve. */
diff --git a/packages/ptc-runtime/README.i18n.yaml b/packages/ptc-runtime/README.i18n.yaml
index 4209e1b984..2b287b6181 100644
--- a/packages/ptc-runtime/README.i18n.yaml
+++ b/packages/ptc-runtime/README.i18n.yaml
@@ -9,8 +9,8 @@
en: d487c8e1301e1d5a
zh: a03fbc7abe89f9f8
/ptc-runtime-ptc-execution-capability-family/summary:
- en: 0c36d4c8f5d82c51
- zh: a8b49c7d177b34e6
+ en: 9e9be99cf6a59343
+ zh: 4afb790d3aa8a8b8
/ptc-runtime-ptc-execution-capability-family/table-of-contents:
en: 1eb35b015a6c423f
zh: a8a3dceee2c9e943
diff --git a/packages/ptc-runtime/README.md b/packages/ptc-runtime/README.md
index 48af8ef445..ce70f22fad 100644
--- a/packages/ptc-runtime/README.md
+++ b/packages/ptc-runtime/README.md
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
## Summary
-The `ptc-runtime/` group executes model programs against host-provided async functions. The default Node backend starts a fresh process under the configured sandbox policy. Its optional persistent entry keeps a separate unsandboxed REPL for each explicit state owner; memory ends with the process or owner. The experimental Python backend uses a fresh CPython process. Failures return as results for callers to diagnose or present to the model.
+The `ptc-runtime/` group executes model programs against host-provided async functions. The default Node backend starts a fresh process under the configured sandbox policy. Its opt-in `executionMode: in-memory` keeps a separate unsandboxed REPL for each explicit state owner; memory ends with the process or owner. The experimental Python backend uses a fresh CPython process. Failures return as results for callers to diagnose or present to the model.
## Table of Contents
diff --git a/packages/ptc-runtime/README.zh.md b/packages/ptc-runtime/README.zh.md
index 5ace3e3175..9133206036 100644
--- a/packages/ptc-runtime/README.zh.md
+++ b/packages/ptc-runtime/README.zh.md
@@ -9,7 +9,7 @@ kind: "package-group"
## 概述
-`ptc-runtime/` 分组执行调用宿主异步函数的模型程序。默认 Node 后端按配置的沙盒策略启动新进程。可选的持久入口为每个显式状态所有者保留独立且无沙盒的 REPL;进程或所有者结束时内存消失。实验性 Python 后端使用新的 CPython 进程。失败作为结果返回,供调用方诊断或交给模型处理。
+`ptc-runtime/` 分组执行调用宿主异步函数的模型程序。默认 Node 后端按配置的沙盒策略启动新进程。显式启用的 `executionMode: in-memory` 模式为每个显式状态所有者保留独立且无沙盒的 REPL;进程或所有者结束时内存消失。实验性 Python 后端使用新的 CPython 进程。失败作为结果返回,供调用方诊断或交给模型处理。
## 目录
diff --git a/packages/ptc-runtime/ptc-runtime-node/README.i18n.yaml b/packages/ptc-runtime/ptc-runtime-node/README.i18n.yaml
index 32deacde26..24cb54cd12 100644
--- a/packages/ptc-runtime/ptc-runtime-node/README.i18n.yaml
+++ b/packages/ptc-runtime/ptc-runtime-node/README.i18n.yaml
@@ -9,8 +9,8 @@
en: 7e1a8657a212c70b
zh: afb7a8c6a3afd718
/deepseek-ai-dsh-ptc-runtime-node/summary:
- en: 4d6be30d9289a58e
- zh: b1b2b4d773d32029
+ en: 6a63a229e4fbf441
+ zh: 193340193cd5ee1d
/deepseek-ai-dsh-ptc-runtime-node/table-of-contents:
en: d152484eb41ac6b4
zh: 725b47765ee8f30b
@@ -18,17 +18,17 @@
en: 246231cf2a529550
zh: 2b4ed96194acc639
/deepseek-ai-dsh-ptc-runtime-node/use-this-package/configuration:
- en: e5bca585d02f08ca
- zh: a2e9a32fe4052d90
+ en: 82aa42dafb974cec
+ zh: a1253f32d64f7023
/deepseek-ai-dsh-ptc-runtime-node/use-this-package/execution-and-results:
- en: ac391fe3027c4aa7
- zh: dfc756add81abbe1
+ en: c79baab0c0a49ca7
+ zh: b90b78d0d75280fb
/deepseek-ai-dsh-ptc-runtime-node/use-this-package/persistent-repl:
- en: aadcb7054d6ca604
- zh: be8f8d3f946f1254
+ en: cf58c5bd3ff65aa0
+ zh: 4f8b020c21ca527b
/deepseek-ai-dsh-ptc-runtime-node/use-this-package/deadlines-and-cancellation:
- en: 6eb28e8dad077627
- zh: a3af2c84b1801291
+ en: 74825f65e6b59013
+ zh: 928b56f87309b8db
/deepseek-ai-dsh-ptc-runtime-node/use-this-package/failures:
en: 7682e50f9dcfa25c
zh: ddcb3502e12be06f
@@ -36,14 +36,14 @@
en: 9020b491a39e4bf5
zh: bcd1b2b1a3bde7f3
/deepseek-ai-dsh-ptc-runtime-node/understand-the-implementation/launch-and-control:
- en: 45527dad94dd9362
+ en: 894cf1c1fdbe0092
zh: c1e1179391e89a3f
/deepseek-ai-dsh-ptc-runtime-node/understand-the-implementation/source-and-built-bootstraps:
en: 7b1a3dabfce0da43
zh: 8955956722262cff
/deepseek-ai-dsh-ptc-runtime-node/understand-the-implementation/source-map:
- en: d7f257861e360564
- zh: aecdb3901685b667
+ en: 23deeb308a596ff1
+ zh: fbbe56b248602c38
/deepseek-ai-dsh-ptc-runtime-node/further-exploration:
en: ac2e2ae13d3f1394
zh: 5d08b9c658fcf83a
@@ -54,8 +54,8 @@
en: 40294c1c31b74824
zh: d91e7b78c0619c7e
/deepseek-ai-dsh-ptc-runtime-node/known-limitations-and-deferred-work:
- en: 363be8c96ec82d7a
- zh: 3d9f6f9049e8d7e5
+ en: 9442283bba290644
+ zh: 0a5d34099555c9be
/deepseek-ai-dsh-ptc-runtime-node/known-limitations-and-deferred-work/dev-note:
en: 6373aba4e08f7205
zh: 2a8c33d1f2839df4
diff --git a/packages/ptc-runtime/ptc-runtime-node/README.md b/packages/ptc-runtime/ptc-runtime-node/README.md
index fb058e4906..879038c1e3 100644
--- a/packages/ptc-runtime/ptc-runtime-node/README.md
+++ b/packages/ptc-runtime/ptc-runtime-node/README.md
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
## Summary
-Execute model-written TypeScript under the same platform sandbox policy as Bash, with host-provided functions available as async bindings. Each call starts a fresh Node process and returns captured logs, an exact JSON value, or a structured failure. Direct Node APIs remain available within the selected restrictions. Elapsed deadlines, output bounds and a V8 heap limit constrain execution; cancellation and completion terminate the managed process range. A requested restricted mode fails when its sandbox backend is unavailable.
+Execute model-written TypeScript with host-provided async functions. The default `per-call` mode starts a fresh Node process under the same platform sandbox policy as Bash. Opt-in `in-memory` mode retains an unsandboxed REPL across calls for each Agent. Both return captured logs, an exact JSON value, or a structured failure. Direct Node APIs remain available within the selected restrictions. Elapsed deadlines, output bounds and a V8 heap limit constrain execution; cancellation and per-call completion terminate the managed process range. A requested restricted mode fails when its sandbox backend is unavailable.
## Table of Contents
@@ -34,6 +34,7 @@ Configure the provider row after its required services are available:
```yaml
- name: '@deepseek-ai/dsh-ptc-runtime-node'
config:
+ executionMode: per-call
timeoutMs: 120000
maxTimeoutMs: 600000
maxOutputBytes: 67108864
@@ -45,6 +46,7 @@ Configure the provider row after its required services are available:
| Field | Default | Meaning |
|---|---|---|
+| `executionMode` | `per-call` | `per-call` starts fresh processes under file policy; `in-memory` retains an unsandboxed REPL per owner |
| `timeoutMs` | `120,000` | Default elapsed execution deadline, including nested tool and approval waits |
| `maxTimeoutMs` | `600,000` | Elapsed deadline ceiling applied by the resolver |
| `maxOutputBytes` | `67,108,864` | Combined serialized logs and completion or diagnostic budget |
@@ -59,24 +61,22 @@ The [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-ptc-r
### Execution and results
-Programs are async function bodies: top-level `await` and `return` work, and only erasable TypeScript is accepted. A successful call returns its lossless-JSON value as `result.value` and captured text as `result.logs`. `result.sandbox` reports the selected mode, observed denial and the backend's full or partial enforcement independently of the program outcome.
+In `per-call` mode, programs are async function bodies: top-level `await` and `return` work, and only erasable TypeScript is accepted. A successful call returns its lossless-JSON value as `result.value` and captured text as `result.logs`. `result.sandbox` reports the selected mode, observed denial and the backend's full or partial enforcement independently of the program outcome.
Direct filesystem, network and subprocess operations remain Node operations, subject to the selected OS sandbox. Nested host bindings cross the control channel; PTC tool calls retain the registry's visibility, ordering, logging and approval rules. Running a program does not change the Session's standing policy or automatically replay it after a denial.
### Persistent REPL
-The optional `@deepseek-ai/dsh-ptc-runtime-node/persistent` entry replaces the default provider. It requires `fs` and `subprocess`, uses the same configuration fields, and does not create a file sandbox. Enable it deliberately with a profile patch; it is not a shipped default:
+Set `executionMode: in-memory` on the existing `@deepseek-ai/dsh-ptc-runtime-node` plugin to retain state. Both modes require `fs`, `subprocess`, `sandbox` and `sandboxPolicy`; in-memory execution does not apply a file sandbox and rejects explicit sandbox policies. Enable it deliberately with a profile patch:
```yaml
- id: ptc-runtime
- disabled: true
-- insert:
- - id: ptc-runtime-persistent
- name: '@deepseek-ai/dsh-ptc-runtime-node/persistent'
+ config:
+ executionMode: in-memory
```
-Save the patch and pass its path to `dsh headless --patch ` in a composition with PTC tool presentation. Node applications still launch through a named `dsh` profile. Packaged single-executable hosts require `nodeExecutable` pointing to Node and `bootstrapPath` pointing to this package's built `persistent-process.js`.
+Save the patch and pass its path to `dsh headless --patch ` in a composition with PTC tool presentation. The default `per-call` mode retains no program memory between calls. Changing the configuration reloads the provider and releases its execution resources. Packaged single-executable hosts require `nodeExecutable` pointing to Node and `bootstrapPath` pointing to this package's built `persistent-process.js` for in-memory execution.
Each live Agent keeps its own process and top-level variables, functions, and imported modules across `run_code` calls and turns. Use `await import(...)`, `console.log(...)`, and a final JSON expression; top-level `return` is not supported. Assignments to undeclared names create retained globals under Node REPL’s non-strict evaluation. Node REPL declaration rules apply, including errors on redeclaring existing `let`, `const`, or `class` names; assign to a mutable binding or choose a new name. A leading `{ ... }` is a statement block and can contain top-level await and temporary declarations; parenthesize an object expression to return it. Retained functions and saved tool methods call the current evaluation's bindings. All tool calls must be awaited; callbacks from a completed evaluation cannot invoke tools in a later evaluation.
@@ -86,9 +86,9 @@ Service consumers pass an object as `stateOwner` to reuse its environment and aw
### Deadlines and cancellation
-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. The default provider’s `executionInstructions` describes fresh Node state, direct Node APIs, the empty program environment and file policy in the model-visible schema.
+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. The per-call mode’s `executionInstructions` describes fresh Node state, direct Node APIs, the empty program environment and file policy in the model-visible schema.
-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.
+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 per-call 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
@@ -106,7 +106,7 @@ The host owns policy, deadlines, binding lookup and process cleanup. The child o
### Launch and control
-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 host preserves `ELECTRON_RUN_AS_NODE` only for child startup so the Desktop executable runs the Node bootstrap; the bootstrap removes the selector before evaluating model code. The heap limit uses Node argv or a provider-created `NODE_OPTIONS` value for packaged executables; ambient loader and inspector flags are discarded.
+For per-call execution, 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 host preserves `ELECTRON_RUN_AS_NODE` only for child startup so the Desktop executable runs the Node bootstrap; the bootstrap removes the selector before evaluating model code. 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. 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.
@@ -118,9 +118,9 @@ Source execution loads an erasable-only bootstrap closure without relying on sib
| File | Role |
|---|---|
-| [`src/index.ts`](src/index.ts) | Default provider: resolution, policy, bindings and managed execution |
-| [`src/config.ts`](src/config.ts) | Shared configuration, validation and defaults |
-| [`src/persistent.ts`](src/persistent.ts) | Retained process ownership, evaluation deadlines and cleanup |
+| [`src/index.ts`](src/index.ts) | Node provider: mode selection, per-call resolution, policy and managed execution |
+| [`src/config.ts`](src/config.ts) | Execution mode, validated limits and defaults |
+| [`src/persistent.ts`](src/persistent.ts) | In-memory process ownership, evaluation deadlines and cleanup |
| [`src/persistent-process.ts`](src/persistent-process.ts) | Child REPL, retained declarations and current binding dispatch |
| [`src/output-stream.ts`](src/output-stream.ts) | Bounded stream capture and process-output draining |
| [`src/launch.ts`](src/launch.ts) | Executable/bootstrap arguments and execution-world asset mapping |
@@ -164,8 +164,8 @@ These limits qualify the execution guarantees and retained output.
- **Confinement inherits the selected backend's limits** — full and partial enforcement are reported separately; sandbox policy and managed-process containment are distinct guarantees.
- **The heap cap is not a process-tree memory limit** — native allocations and descendant memory are outside the V8 old-generation bound. No process-tree CPU meter is supplied.
- **Cleanup inherits subprocess observability** — escaped descendants on a fallback platform may remain outside the managed range; see the subprocess provider's stated limits.
-- **Default execution is one-shot** — retained state requires the optional persistent provider; neither provider offers a yield/wait API.
-- **Persistent environments retain one process per live owner** — idle owners keep their process and heap until explicit release, owner disposal or failure. Fan-out can retain many processes; idle expiration remains deferred.
+- **Default execution is one-shot** — retained state requires `executionMode: in-memory`; neither mode offers a yield/wait API.
+- **In-memory environments retain one process per live owner** — idle owners keep their process and heap until explicit release, owner disposal or failure. Fan-out can retain many processes; idle expiration remains deferred.
- **Output caps reject rather than retain every byte** — spill can preserve only the bounded result delivered by this provider.
- **Bindings are bounded at transport admission** — control limits do not bound the memory a host binding allocates while producing its result.
- **The console shim has five methods** — `log`, `info`, `warn`, `error` and `debug`.
diff --git a/packages/ptc-runtime/ptc-runtime-node/README.zh.md b/packages/ptc-runtime/ptc-runtime-node/README.zh.md
index be03ff034c..bac0a90f1c 100644
--- a/packages/ptc-runtime/ptc-runtime-node/README.zh.md
+++ b/packages/ptc-runtime/ptc-runtime-node/README.zh.md
@@ -9,7 +9,7 @@ kind: "package-reference"
## 概述
-在与 Bash 相同的平台沙箱策略下执行模型编写的 TypeScript,并通过异步绑定调用 Host 提供的函数。每次调用启动一个全新的 Node 进程,返回捕获日志、精确 JSON 值或结构化失败。直接 Node API 在所选限制内仍可使用。经过时间截止、输出上限和 V8 堆限制约束执行;取消和完成都会终止受管进程范围。请求受限模式但沙箱后端不可用时,执行失败。
+执行模型编写的 TypeScript,并提供宿主异步函数。默认的 `per-call` 模式每次启动新 Node 进程,使用与 Bash 相同的平台沙箱策略。显式启用的 `in-memory` 模式为每个 Agent 跨调用保留无沙箱 REPL。两种模式都返回捕获的日志、精确 JSON 值或结构化失败。直接 Node API 受所选模式的限制。执行时限、输出上限和 V8 堆上限约束运行;取消与逐次执行完成后清理托管进程范围。请求受限模式而沙箱后端不可用时,执行失败。
## 目录
@@ -34,6 +34,7 @@ kind: "package-reference"
```yaml
- name: '@deepseek-ai/dsh-ptc-runtime-node'
config:
+ executionMode: per-call
timeoutMs: 120000
maxTimeoutMs: 600000
maxOutputBytes: 67108864
@@ -45,6 +46,7 @@ kind: "package-reference"
| 字段 | 默认值 | 含义 |
|---|---|---|
+| `executionMode` | `per-call` | `per-call` 每次按文件策略启动新进程;`in-memory` 为每个所有者保留无沙箱 REPL |
| `timeoutMs` | `120,000` | 默认经过时间截止,包括嵌套工具与审批等待 |
| `maxTimeoutMs` | `600,000` | 解析器应用的经过时间截止上限 |
| `maxOutputBytes` | `67,108,864` | 序列化日志与完成值或诊断的合计预算 |
@@ -59,24 +61,22 @@ kind: "package-reference"
### 执行与结果
-程序是异步函数体:支持顶层 `await` 与 `return`,且只接受可擦除 TypeScript。成功调用以 `result.value` 返回无损 JSON 值,以 `result.logs` 返回捕获文本。`result.sandbox` 独立于程序结果报告所选模式、观察到的拒绝,以及后端完整或部分的强制能力。
+`per-call` 模式中的程序是异步函数体:支持顶层 `await` 与 `return`,且只接受可擦除 TypeScript。成功调用以 `result.value` 返回无损 JSON 值,以 `result.logs` 返回捕获文本。`result.sandbox` 独立于程序结果报告所选模式、观察到的拒绝,以及后端完整或部分的强制能力。
直接文件系统、网络与子进程操作仍是 Node 操作,受所选 OS 沙箱约束。嵌套 Host 绑定通过控制通道调用;PTC 工具调用保留注册表的可见性、排序、日志和审批规则。运行程序不会改变 Session 的常设策略,也不会在拒绝后自动重放程序。
### 持久 REPL
-可选入口 `@deepseek-ai/dsh-ptc-runtime-node/persistent` 替换默认提供方。它需要 `fs` 和 `subprocess`,使用同一组配置字段,不创建文件沙盒。通过配置档补丁显式启用;默认配置不会启用它:
+在现有 `@deepseek-ai/dsh-ptc-runtime-node` 插件上设置 `executionMode: in-memory` 即可保留状态。两种模式都需要 `fs`、`subprocess`、`sandbox` 和 `sandboxPolicy`;内存保留模式不应用文件沙箱,并拒绝显式沙箱策略。通过配置档补丁显式启用:
```yaml
- id: ptc-runtime
- disabled: true
-- insert:
- - id: ptc-runtime-persistent
- name: '@deepseek-ai/dsh-ptc-runtime-node/persistent'
+ config:
+ executionMode: in-memory
```
-保存补丁,在启用 PTC 工具呈现的组合中使用 `dsh headless --patch ` 传入路径。Node 应用仍通过具名 `dsh` 配置档启动。打包为单个可执行文件的宿主需要将 `nodeExecutable` 指向 Node,将 `bootstrapPath` 指向本包构建的 `persistent-process.js`。
+保存补丁,在使用 PTC 工具展示的配置中通过 `dsh headless --patch ` 加载。默认的 `per-call` 模式不跨调用保留程序内存。修改配置会重载提供方并释放其执行资源。单文件打包宿主使用内存保留模式时,需要将 `nodeExecutable` 指向 Node,并将 `bootstrapPath` 指向本包构建出的 `persistent-process.js`。
每个存活 Agent 保留独立进程,跨 `run_code` 调用和轮次保留顶层变量、函数及导入模块。使用 `await import(...)`、`console.log(...)` 和末尾 JSON 表达式;不支持顶层 `return`。Node REPL 使用非严格模式,给未声明的名称赋值会创建保留的全局变量。声明遵循 Node REPL 规则,包括重复声明已有 `let`、`const` 或 `class` 名称时报错;应给可变绑定赋值或选择新名称。开头的 `{ ... }` 按语句块执行,可以包含顶层 await 和临时声明;需要返回对象表达式时,用圆括号包裹。保留的函数和工具方法引用调用当前执行的绑定。必须等待所有工具调用;已完成执行的回调不能在后续执行中调用工具。
@@ -86,9 +86,9 @@ kind: "package-reference"
### 截止时间与取消
-PTC 消费方按 [dsh-tools](../../core/tools/README.zh.md#ptc-mode) 的说明公开逐次超时与经审批的沙箱选择。运行时只读 `timeout` 描述符向该消费方报告有效默认值与上限。默认提供方的 `executionInstructions` 在面向模型的 schema 中说明全新 Node 状态、直接 Node API、空程序环境和文件策略。
+PTC 消费方按 [dsh-tools](../../core/tools/README.zh.md#ptc-mode) 的说明公开逐次超时与经审批的沙箱选择。运行时只读 `timeout` 描述符向该消费方报告有效默认值与上限。逐次执行模式的 `executionInstructions` 在面向模型的 schema 中说明全新 Node 状态、直接 Node API、空程序环境和文件策略。
-省略 `timeoutMs` 使用配置的经过时间默认值;数值请求经过验证并封顶。服务调用方可以显式传入 `timeoutMs: null` 来省略经过时间定时器,工作流适配器即如此;`run_code` 仍只接受正数覆盖值。启用的截止覆盖运行时准备和执行,包括等待嵌套工具或审批的时间。它不是 CPU 计量器。超时或取消通过 Host 的受管进程所有者停止同步循环;成功完成也会清理该受管范围。选择结果后、清理前停止计时器,因此调用可能要在执行截止之后等待清理结算才返回。
+省略 `timeoutMs` 使用配置的经过时间默认值;数值请求经过验证并封顶。服务调用方可以显式传入 `timeoutMs: null` 来省略经过时间定时器,工作流适配器即如此;`run_code` 仍只接受正数覆盖值。启用的截止覆盖运行时准备和执行,包括等待嵌套工具或审批的时间。它不是 CPU 计量器。超时或取消通过 Host 的受管进程所有者停止同步循环;逐次执行成功完成也会清理该受管范围。选择结果后、清理前停止计时器,因此调用可能要在执行截止之后等待清理结算才返回。
### 失败
@@ -119,8 +119,8 @@ Host 擦除可擦除类型,在配置的执行世界中解析可执行文件与
| 文件 | 职责 |
|---|---|
| [`src/index.ts`](src/index.ts) | 默认提供方的解析、策略、绑定与受管执行 |
-| [`src/config.ts`](src/config.ts) | 共享配置、校验与默认值 |
-| [`src/persistent.ts`](src/persistent.ts) | 保留进程的所有权、执行截止与清理 |
+| [`src/config.ts`](src/config.ts) | 执行模式、限制校验与默认值 |
+| [`src/persistent.ts`](src/persistent.ts) | 内存保留模式的进程所有权、执行截止与清理 |
| [`src/persistent-process.ts`](src/persistent-process.ts) | 子进程 REPL、保留声明与当前绑定分派 |
| [`src/output-stream.ts`](src/output-stream.ts) | 有界流捕获与进程输出排空 |
| [`src/launch.ts`](src/launch.ts) | 可执行文件/bootstrap 参数与执行世界资源映射 |
@@ -164,8 +164,8 @@ Host 擦除可擦除类型,在配置的执行世界中解析可执行文件与
- **约束继承所选后端的限制**——完整与部分强制能力分开报告;沙箱策略与受管进程约束是不同保证。
- **堆上限不是进程树内存限制**——原生分配与后代进程内存不属于 V8 老生代上限。不提供进程树 CPU 计量器。
- **清理继承子进程的可观测范围**——使用 fallback 的平台上,逃逸的后代可能仍在受管范围之外;参见子进程提供方声明的限制。
-- **默认执行是一次性的**——保留状态需要可选的持久提供方;两个提供方都不提供 yield/wait API。
-- **持久环境为每个存活所有者保留一个进程**——空闲所有者保留进程及堆内存,直到显式释放、所有者销毁或执行失败。并行创建多个所有者会保留多个进程;空闲过期机制留待后续处理。
+- **默认执行是一次性的**:保留状态需要设置 `executionMode: in-memory`;两种模式都不提供 yield/wait API。
+- **内存保留环境为每个存活所有者保留一个进程**——空闲所有者保留进程及堆内存,直到显式释放、所有者销毁或执行失败。并行创建多个所有者会保留多个进程;空闲过期机制留待后续处理。
- **输出上限拒绝超量内容,而不保留每个字节**——溢出只能保存本提供方交付的有界结果。
- **绑定在传输接纳时受限**——控制限制不约束 Host 绑定生成结果期间分配的内存。
- **console shim 有五个方法**——`log`、`info`、`warn`、`error` 与 `debug`。
diff --git a/packages/ptc-runtime/ptc-runtime-node/package.json b/packages/ptc-runtime/ptc-runtime-node/package.json
index fafa73a46e..d3d188ba5c 100644
--- a/packages/ptc-runtime/ptc-runtime-node/package.json
+++ b/packages/ptc-runtime/ptc-runtime-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@deepseek-ai/dsh-ptc-runtime-node",
- "description": "Sandboxed Node process implementation of the DeepSeek Harness PTC execution capability",
+ "description": "Node PTC execution with per-call sandboxed processes or in-memory REPL state",
"version": "0.1.7-rc.2",
"publishConfig": {
"access": "public"
@@ -22,16 +22,11 @@
"./process": {
"types": "./lib/types/process-entry.d.ts",
"default": "./lib/process.js"
- },
- "./persistent": {
- "types": "./lib/types/persistent.d.ts",
- "default": "./lib/persistent.js"
}
},
"files": [
"lib/index.js",
"lib/process.js",
- "lib/persistent.js",
"lib/persistent-process.js",
"lib/types/**/*.d.ts"
],
diff --git a/packages/ptc-runtime/ptc-runtime-node/src/config.ts b/packages/ptc-runtime/ptc-runtime-node/src/config.ts
index 51b60662ba..db3f47c8f0 100644
--- a/packages/ptc-runtime/ptc-runtime-node/src/config.ts
+++ b/packages/ptc-runtime/ptc-runtime-node/src/config.ts
@@ -1,4 +1,4 @@
-/** Shared configuration for the isolated and persistent Node providers. */
+/** Execution mode and resource limits for the Node PTC provider. */
import { isAbsolute } from 'node:path'
import z from '@deepseek-ai/schemastery'
import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
@@ -6,6 +6,8 @@ import type { LaunchConfig } from './launch.ts'
/** Deployment-varying runtime bounds and launch choices. */
export interface Config extends LaunchConfig {
+ /** per-call starts fresh confined processes; in-memory retains an unsandboxed REPL per owner. */
+ executionMode?: 'per-call' | 'in-memory'
/** Default elapsed deadline, including nested tool and approval waits. */
timeoutMs?: number
/** Maximum numeric elapsed budget accepted by resolve. */
@@ -24,8 +26,9 @@ export interface Config extends LaunchConfig {
/** Configuration after Loader defaults and executable selection. */
export type ResolvedConfig = Required> & Pick
-/** Loader defaults shared by both Node providers. */
+/** Loader defaults for the Node PTC provider. */
export const NodeConfig: z = z.object({
+ executionMode: z.union([z.const('per-call'), z.const('in-memory')]).default('per-call'),
timeoutMs: z.number().default(120_000),
maxTimeoutMs: z.number().default(600_000),
maxOutputBytes: z.number().default(67_108_864),
diff --git a/packages/ptc-runtime/ptc-runtime-node/src/index.ts b/packages/ptc-runtime/ptc-runtime-node/src/index.ts
index a940f89ce8..0c202162dc 100644
--- a/packages/ptc-runtime/ptc-runtime-node/src/index.ts
+++ b/packages/ptc-runtime/ptc-runtime-node/src/index.ts
@@ -1,4 +1,4 @@
-/** Confined Node programs with host-owned bindings, output limits, and managed process cleanup. */
+/** Node PTC execution with configurable per-call processes or retained in-memory REPL state. */
import { stripTypeScriptTypes } from 'node:module'
import { isAbsolute } from 'node:path'
import { Context } from '@deepseek-ai/cordis'
@@ -14,6 +14,7 @@ import { replyToBinding, validateBindings } from './bindings.ts'
import { JsonChannel } from './channel.ts'
import { bootstrapArgs } from './launch.ts'
import { NodeConfig, validateConfig } from './config.ts'
+import { InMemoryExecution } from './persistent.ts'
import type { Config, ResolvedConfig } from './config.ts'
export type { Config } from './config.ts'
import { OutputLedger } from './output-ledger.ts'
@@ -29,13 +30,17 @@ const STRIP_SUFFIX = '\n}'
function messageOf(error: unknown): string { return error instanceof Error ? error.message : String(error) }
function record(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value) }
-/** Node provider; direct file effects use the same sandbox service as Bash. */
+/** Node provider; per-call execution uses file policy, while in-memory execution has full host access. */
export class NodePtcRuntime extends PtcRuntime {
static inject = ['fs', 'subprocess', 'sandbox', 'sandboxPolicy']
static Config = NodeConfig
readonly language = 'typescript'
readonly isolation = 'process'
+ private readonly inMemory?: InMemoryExecution
+
+ override get programFormat(): 'function-body' | 'repl' { return this.inMemory ? 'repl' : 'function-body' }
override get executionInstructions(): string {
+ if (this.inMemory) return this.inMemory.executionInstructions
return '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.'
}
private readonly config: ResolvedConfig
@@ -46,6 +51,10 @@ export class NodePtcRuntime extends PtcRuntime {
super(ctx)
this.config = { ...config, nodeExecutable: config.nodeExecutable ?? process.execPath } as ResolvedConfig
validateConfig(this.config)
+ if (this.config.executionMode === 'in-memory') {
+ this.inMemory = new InMemoryExecution(ctx, this.config)
+ return
+ }
ctx.effect(() => async () => {
this.disposed = true
const active = [...this.live]
@@ -54,18 +63,23 @@ export class NodePtcRuntime extends PtcRuntime {
}, 'Node ptc-runtime cleanup')
}
- override get sandboxMode(): SandboxMode { return this.ctx.sandboxPolicy.defaultMode }
+ override get sandboxMode(): SandboxMode | undefined { return this.inMemory ? undefined : this.ctx.sandboxPolicy.defaultMode }
+
+ override release(owner: object): Promise {
+ return this.inMemory ? this.inMemory.release(owner) : super.release(owner)
+ }
override get timeout(): { defaultMs: number; maxMs: number } {
return { defaultMs: Math.min(this.config.timeoutMs, this.config.maxTimeoutMs), maxMs: this.config.maxTimeoutMs }
}
/**
- * Resolve an execution under explicit or deployment policy.
+ * Resolve the selected execution mode, applying file policy only to per-call processes.
* @param request - Program, bindings, optional cwd/deadline, and resolved authority.
* @returns Complete execution inputs with a capped numeric budget or an explicit null deadline.
*/
resolve(request: PtcRunRequest): PtcRunSpec {
+ if (this.inMemory) return this.inMemory.resolve(request)
if (this.disposed) throw new Error('ptc-runtime-node: resolve after disposal')
const sandboxPolicy = request.sandboxPolicy ?? this.ctx.sandboxPolicy.resolve()
const cwd = request.cwd ?? sandboxPolicy.workspaceRoot
@@ -79,11 +93,12 @@ export class NodePtcRuntime extends PtcRuntime {
}
/**
- * Run a resolved program in a fresh managed and confined Node process.
- * @param spec - Inputs returned by resolve; missing authority is caller misuse.
- * @returns Output and file-confinement facts after managed cleanup.
+ * Run a resolved program under the configured execution mode.
+ * @param spec - Inputs returned by resolve for the configured mode.
+ * @returns Logs, JSON completion or failure; retained processes remain until release or invalidation.
*/
async run(spec: PtcRunSpec): Promise {
+ if (this.inMemory) return this.inMemory.run(spec)
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) || (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')
diff --git a/packages/ptc-runtime/ptc-runtime-node/src/persistent.ts b/packages/ptc-runtime/ptc-runtime-node/src/persistent.ts
index 81731b3498..82ef8a9c7c 100644
--- a/packages/ptc-runtime/ptc-runtime-node/src/persistent.ts
+++ b/packages/ptc-runtime/ptc-runtime-node/src/persistent.ts
@@ -1,13 +1,11 @@
-/** Optional, unsandboxed Node REPL provider with memory scoped to caller-owned objects. */
+/** Node PTC in-memory execution with retained processes scoped to caller-owned objects. */
import { stripTypeScriptTypes } from 'node:module'
import { isAbsolute } from 'node:path'
import { Context } from '@deepseek-ai/cordis'
-import { PtcRuntime } from '@deepseek-ai/dsh-ptc-runtime'
import type { PtcRunRequest, PtcRunSpec, PtcRunResult, PtcRunFailure, PtcBindingNamespace, PtcJsonValue } from '@deepseek-ai/dsh-ptc-runtime'
import { clampTimeout } from '@deepseek-ai/dsh-timeout'
import type { SubprocessHandle, SubprocessOutcome } from '@deepseek-ai/dsh-subprocess'
-import { NodeConfig, validateConfig } from './config.ts'
-import type { Config, ResolvedConfig } from './config.ts'
+import type { ResolvedConfig } from './config.ts'
import { replyToBinding, validateBindings } from './bindings.ts'
import { JsonChannel } from './channel.ts'
import { bootstrapArgs } from './launch.ts'
@@ -17,7 +15,6 @@ import { OutputLedger } from './output-ledger.ts'
import { drainOutput, terminateProcess } from './output-stream.ts'
import type { ProgramBootData } from './protocol.ts'
-export type { Config } from './config.ts'
const messageOf = (error: unknown): string => error instanceof Error ? error.message : String(error)
const record = (value: unknown): value is Record => typeof value === 'object' && value !== null && !Array.isArray(value)
@@ -241,27 +238,24 @@ class Kernel {
}
/** Unsandboxed, best-effort retained state; restart, disposal, and interrupted execution clear it. */
-export class PersistentNodePtcRuntime extends PtcRuntime {
- static inject = ['fs', 'subprocess']
- static Config = NodeConfig
- readonly language = 'typescript'
- readonly isolation = 'process'
- override get programFormat(): 'repl' { return 'repl' }
- override get executionInstructions(): string {
+export class InMemoryExecution {
+ /**
+ * Model-facing rules for retained variables, authority, output, and recovery.
+ * @returns Usage guidance appended to the run_code schema.
+ */
+ get executionInstructions(): string {
return 'Variables, functions, and imported modules remain in memory for this Agent across calls. Use await import(...) for modules. This environment has full host access without a file sandbox; process.env starts empty. Restart, Agent disposal, cancellation, timeout, output overflow, or process failure clears the environment. If a variable is undefined, recreate it and reacquire its resources. State is not restored from the conversation, and failed evaluations can leave earlier mutations in memory. Redeclaring an existing let, const, or class name fails; assign to a mutable binding or use a new name. Await all tool calls; background callbacks cannot call tools after their evaluation ends. Await child-process completion before ending an evaluation; raw subprocess output belongs to whichever evaluation is active when it arrives.'
}
- override get timeout(): { defaultMs: number; maxMs: number } {
- return { defaultMs: Math.min(this.config.timeoutMs, this.config.maxTimeoutMs), maxMs: this.config.maxTimeoutMs }
- }
- private readonly config: ResolvedConfig
private readonly owners = new WeakMap