Node PTC execution modes and in-memory state
Node PTC 执行模式与内存状态
Interactive automation retains application objects, imported modules and helper functions between model calls. Recreating every object for every program prevents consumers from using ordinary JavaScript references across observations and actions.
English
Problem
Interactive automation retains application objects, imported modules and helper functions between model calls. Recreating every object for every program prevents consumers from using ordinary JavaScript references across observations and actions.
Decision
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 persistent-in-memory mode keeps a 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 owns per-call confinement; the PTC foundation 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; persistent-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. The retained-process permission decision owns confinement, policy changes and temporary escalation.
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. programFormat describes source evaluation independently of retention: a REPL need not retain state. The runtime owns syntax, completion and retention instructions through executionInstructions; tools own call arguments, SDK declarations and image attachment. Workflow uses programFormat to wrap its program and refuses restricted file policies when the selected runtime cannot enforce them.
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.
Cancellation, deadline expiry, protocol or output-limit failure, owner disposal and process exit discard the environment. Ordinary exceptions retain earlier mutations. A retained function uses the active evaluation's host bindings; asynchronous work from an earlier evaluation cannot acquire a later evaluation's tool authority or output sink.
Alternatives considered
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. Process ownership alone does not provide a file sandbox or multi-tenant isolation.
Consequences
Selecting persistent-in-memory retains execution state; the permission decision defines its file restrictions. Nested tools keep 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. Tool schemas and program guidance follow the selected mode.
中文
问题
交互式自动化需要在模型调用之间保留应用对象、导入模块和辅助函数。如果每次执行都重新创建对象,消费者就无法在观察与操作之间继续使用普通 JavaScript 引用。
决策
现有 @deepseek-ai/dsh-ptc-runtime-node 插件通过 executionMode 选择执行模式。默认的 per-call 模式按配置的文件策略启动新进程。显式启用的 persistent-in-memory 模式为每个以对象身份标识的状态所有者保留一个 Node REPL 进程。两种模式都提供同一个 ctx.ptcRuntime 服务。PTC 使用存活的 Agent 作为所有者,在其上下文销毁时等待释放。修改配置会重载提供方并释放其进程。Node 沙箱决策负责逐次执行的文件约束,PTC 基础决策负责工具分派与模型请求日志。
模式名称描述状态保留方式,不绑定进程实现或代码语法。per-call 只为一次调用保留状态;persistent-in-memory 跨调用保留状态,直到进程或所有者结束。durable 用于命名未来跨重启保存与恢复执行状态的能力。它不是可接受的配置值:实现必须先明确恢复点、可恢复的值以及外部资源的处理方式,才能公开这个模式。常驻进程权限决策负责文件约束、权限切换与临时提权。
一个 npm 包和一个插件入口保持现有安装与加载路径稳定。通信、绑定代理、JSON 编码和输出限制继续作为包内模块复用。Node 提供方将保留进程的所有权交给内部执行对象,该对象不注册额外的 Cordis 服务。逐次执行和内存保留各自管理不同的生命周期。programFormat 描述代码求值形式,与状态保留独立:REPL 不必跨调用保留状态。运行时通过 executionInstructions 负责语法、完成值和状态保留指引;工具层负责调用参数、SDK 声明和图片附加。工作流根据 programFormat 包装程序;所选运行时无法执行受限文件策略时,工作流拒绝执行。
Session 记录提交的程序、嵌套工具调用和返回输出。它不序列化 REPL 堆,也不在重启、恢复或派生后重新执行旧程序,因为重放自动化可能重复外部操作。缺失变量产生普通程序错误,模型指引要求重新创建变量并获取资源。提供方没有引入日志之外的模型输入,因此不需要环境标识或新的持久 Session 事件。
取消、截止时间到期、协议失败、输出超限、所有者销毁和进程退出都会丢弃环境。普通异常保留此前发生的修改。保留的函数使用当前执行的宿主绑定;较早执行中的异步任务不能获取后续执行的工具权限或向其输出。
考虑过的替代方案
将提供方拆成独立 npm 包并增加共享库,可以分别分发,但需要维护额外的包、依赖声明和共享实现的跨包导出。两种 Node 模式目前没有不同发布节奏或独立依赖的需求。第二个插件子路径允许独立加载,但要求部署配置通过替换插件入口来选择执行策略。配置字段直接表达这个选择,并保留已有入口。需要独立分发或出现明显不同的依赖时,可以重新评估拆包。
为公共辅助代码新增 Cordis 服务,会增加注册和注入依赖,却没有提供独立替换的运行能力。内部函数和类已经能够完成复用。共同的执行基类还会把新进程清理与按所有者保留进程的生命周期耦合起来,因此提供方共用处理机制,各自管理生命周期。
序列化任意 JavaScript 对象无法保留原生资源和闭包。重放旧程序可能重复写入和输入操作。这两种做法都会暗示内存保留模式没有提供的恢复保证。
Harness 进程内的独立执行上下文与模型代码共享进程生命周期,无法单独终止同步死循环。托管子进程可以在正常使用时保留导入和变量,同时支持取消与资源清理。仅有进程所有权不能提供文件沙箱或多租户隔离。
影响
选择 persistent-in-memory 会保留执行状态;权限决策定义其文件限制。嵌套工具仍遵循原有授权和日志规则。即使 Harness 没有重启,内存和原生句柄也可能丢失。模型需要从引用或资源错误中恢复,运行时不会自动重试程序。
空闲所有者保留一个没有过期计时器的进程。调用方显式释放所有者,没有所有者的服务调用使用临时进程。程序使用 REPL 语句和末尾表达式,不支持顶层 return;同一保留所有者的调用不能重叠。工具 schema 和程序指引跟随所选模式。