@deepseek-ai/dsh-workflow-worker-thread 0.0.1-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +124 -0
- package/README.zh.md +124 -0
- package/lib/index.js +896 -0
- package/lib/invariant.js +23 -0
- package/lib/types/host.d.ts +151 -0
- package/lib/types/index.d.ts +58 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/meta.d.ts +19 -0
- package/lib/types/protocol.d.ts +132 -0
- package/lib/types/realm.d.ts +39 -0
- package/lib/types/runtime.d.ts +109 -0
- package/lib/types/session.d.ts +33 -0
- package/lib/types/types.d.ts +88 -0
- package/lib/types/worker.d.ts +8 -0
- package/lib/worker.cjs +783 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, DeepSeek
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/workflow/workflow-worker-thread/README.md
|
|
5
|
+
README.md: ba32c9dab4e870dd52c8f8acba3cfa627fa78000
|
|
6
|
+
README.zh.md: 99107aec40a45c8460f10082f2b75b908e3e8692
|
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-workflow-worker-thread
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
This package implements `WorkflowEngine` with one Node worker thread per run. The worker executes the orchestration script; child agents remain on the host and are reached through `ctx.subagents` over a typed host/worker protocol.
|
|
6
|
+
|
|
7
|
+
The package root exports the default engine plugin and its `Config`; the worker protocol, runtime, and session modules stay private to the implementation. The operational `./worker` entry remains the engine's spawn target.
|
|
8
|
+
|
|
9
|
+
The split has one primary purpose: a synchronous script loop cannot block the harness event loop, and a script that ignores cancellation can be terminated with its worker. It is not a security sandbox.
|
|
10
|
+
|
|
11
|
+
## Trust and isolation boundary
|
|
12
|
+
|
|
13
|
+
Workflow scripts are model-written and have the same trust premise as the model's existing bash access. `node:vm` inside a worker is an API-shaping mechanism, not a security boundary: an escaped script can recover Node capabilities with the host process's privileges.
|
|
14
|
+
|
|
15
|
+
The worker still provides useful containment:
|
|
16
|
+
|
|
17
|
+
- Script CPU work and synchronous spins stay off the host event loop.
|
|
18
|
+
- `worker.terminate()` gives disposal a real final stop.
|
|
19
|
+
- The worker starts with an empty environment, except unbuilt loader plumbing, so ambient credentials do not cross through `process.env`.
|
|
20
|
+
- Host/worker messages use structured-clone data, with plain-JSON validation at the script boundary.
|
|
21
|
+
|
|
22
|
+
A genuinely untrusted-script sandbox would require a different engine behind the same workflow seam.
|
|
23
|
+
|
|
24
|
+
## Script contract
|
|
25
|
+
|
|
26
|
+
The workflow's `meta` is host-provided data, not evaluated script text. The engine validates its required `name` and `description`, rejects unknown fields, and parse-checks the body before returning a run.
|
|
27
|
+
|
|
28
|
+
Inside the worker, the script receives `args` and these hooks:
|
|
29
|
+
|
|
30
|
+
- `agent(prompt, { label, phase, schema, model })` starts one host-side subagent. With a schema it returns the structured value; otherwise it returns final text. An ordinary failed child yields `null`.
|
|
31
|
+
- `parallel(thunks)` runs thunks under the configured concurrency limit.
|
|
32
|
+
- `pipeline(items, ...stages)` passes `(previous, item, index)` without a cross-stage barrier.
|
|
33
|
+
- `phase(title)` and `log(message)` emit observer narration.
|
|
34
|
+
|
|
35
|
+
Unknown options, malformed arguments, unsupported schemas, tripped caps, provider-start failures, and infrastructure result failures are fatal workflow errors. No timers, filesystem API, or Node globals are intentionally injected, though the trust caveat above still applies.
|
|
36
|
+
|
|
37
|
+
## Run sequence
|
|
38
|
+
|
|
39
|
+
`start()` validates meta, parses the body, resolves a registered normalized provider route, and resolves any per-run total-child cap before creating a worker or publishing `workflow/start`. A requested `maxTotalAgents` must be a positive safe integer no greater than the engine's configured deployment ceiling. Source mode installs TypeScript transforms through a data-URL bootstrap; built mode passes sibling `lib/worker.cjs` as a filesystem path because pkg's VFS hook expects CommonJS. Both work under ordinary Node. A ready/go handshake prevents a start-signal cancellation racing worker boot from executing the script's initial synchronous slice.
|
|
40
|
+
|
|
41
|
+
For each `agent()` call:
|
|
42
|
+
|
|
43
|
+
1. The worker sends `child-start` with a plain-data prompt and options.
|
|
44
|
+
2. The host calls the start request's provider override, or otherwise the configured provider, through async `SubagentRuntime.start`, passing the workflow's parent and one canonical per-run abort signal. Provider choice applies to every child in that run and is not visible to the script.
|
|
45
|
+
3. If start rejects, the host sends `child-start-error`; provider startup has already reached quiescence and no child lifecycle event is emitted.
|
|
46
|
+
4. If start fulfills while the workflow still admits work, the host records the run, observes `result`, then sends `child-started`. Even an already-settled result is forwarded afterward, preserving start-before-result order.
|
|
47
|
+
5. The worker emits paired `workflow/agent-start` and `workflow/agent-end` narration and requests child disposal after collection.
|
|
48
|
+
|
|
49
|
+
Provider starts are tracked separately from published children. If cancellation, worker death, or normal workflow settlement closes admission while a start is pending, the shared signal aborts it. A provider that nevertheless fulfills after closure is disposed by the host and never announced to the worker.
|
|
50
|
+
|
|
51
|
+
## Value boundary
|
|
52
|
+
|
|
53
|
+
Values leaving the script pass through `materializeFromRealm`, which accepts plain, lossless JSON data and rejects exotic prototypes, functions, symbols, cycles, sparse arrays, non-finite numbers, and nested `undefined`. The walk runs in the worker, and defines object keys as data properties so `__proto__` cannot mutate a prototype.
|
|
54
|
+
|
|
55
|
+
Child results are projected and snapshotted before crossing from the host to the worker. This is a real process-like serialization boundary; it is deliberately different from trusted same-process workflow and subagent event payloads, which are borrowed immutable values.
|
|
56
|
+
|
|
57
|
+
## Cancellation and disposal
|
|
58
|
+
|
|
59
|
+
`WorkflowRun.cancel()` records the first reason, tells the worker to cancel, aborts the one signal shared by every pending and published child, and arms the `disposeGraceMs` timer. Worker hooks then throw `CANCELLED` at their next await. If the run remains unsettled at the deadline, the host resolves it as cancelled, pairs stranded child lifecycle events, and terminates the worker.
|
|
60
|
+
|
|
61
|
+
The subagent seam has one cancellation channel: the request signal. There is no separate child-cancel RPC. Published child teardown uses `run.dispose()`; pending provider starts remain provider-owned until their promise rejects or fulfills.
|
|
62
|
+
|
|
63
|
+
Normal settlement also aborts pending starts and begins disposing any published fire-and-forget children before the result becomes externally settled. The host's quiescence condition includes both pending starts and published child disposals, so cleanup does not forget an async startup transaction.
|
|
64
|
+
|
|
65
|
+
`dispose()` is idempotent. It cancels the run, starts host-driven disposal immediately, waits for result plus child quiescence up to the same grace, terminates the worker unconditionally, and performs a final survivor sweep. Per-child disposal is memoized so worker RPC, host cancellation, death cleanup, and public disposal all join one operation.
|
|
66
|
+
|
|
67
|
+
## Outcome and event guarantees
|
|
68
|
+
|
|
69
|
+
Terminal outcome is first-wins at host claim points. An accepted external cancellation overrides a later non-cancelled worker result; a result or worker death that claims first cannot be rewritten by reentrant cleanup callbacks.
|
|
70
|
+
|
|
71
|
+
Worker error, message failure, or premature exit closes message admission before cleanup, then resolves `error` unless cancellation already owns the run. Late queued messages cannot create children or narrate after that logical boundary.
|
|
72
|
+
|
|
73
|
+
The host keeps a ledger of forwarded child starts. A graceful worker supplies their ends; death or force termination synthesizes any missing end as cancelled. Every forwarded `workflow/agent-start` is therefore paired exactly once, although cleanup after an already-arrived workflow result may complete afterward.
|
|
74
|
+
|
|
75
|
+
## Config
|
|
76
|
+
|
|
77
|
+
| Key | Default | Meaning |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `provider` | `spawn` | Host-side subagent provider used by `agent()`. |
|
|
80
|
+
| `maxConcurrentAgents` | `0` | Concurrent `agent()` ceiling; `0` resolves from available CPU parallelism. |
|
|
81
|
+
| `maxTotalAgents` | `1000` | Total `agent()` calls in one run. |
|
|
82
|
+
| `maxItemsPerCall` | `4096` | Items accepted by one `parallel()` or `pipeline()` call. |
|
|
83
|
+
| `syncTimeoutMs` | `5000` | VM timeout for the script's initial synchronous slice. |
|
|
84
|
+
| `disposeGraceMs` | `5000` | Bound before force-settlement/termination and for public disposal. |
|
|
85
|
+
|
|
86
|
+
An owning consumer may set `WorkflowStartRequest.subagentProvider` and `WorkflowStartRequest.maxTotalAgents` for one run. These are engine-level policy, not script hooks or model-facing options; the ordinary `workflow` tool leaves both unset. A per-run total-child cap may lower but never raise the configured `maxTotalAgents` ceiling.
|
|
87
|
+
|
|
88
|
+
## Model Experience
|
|
89
|
+
|
|
90
|
+
### Child-agent requests
|
|
91
|
+
|
|
92
|
+
#### What the model sees
|
|
93
|
+
|
|
94
|
+
Every script `agent()` call sends its prompt verbatim and optional model or structured-output schema to a subagent provider. Each child sees that provider's own context; phase and log narration stays on observer events.
|
|
95
|
+
|
|
96
|
+
#### Token effect
|
|
97
|
+
|
|
98
|
+
Potentially many independent child contexts are paid, bounded by `maxConcurrentAgents`, `maxTotalAgents`, and `maxItemsPerCall`; they never join the parent history directly.
|
|
99
|
+
|
|
100
|
+
#### KV Cache effect
|
|
101
|
+
|
|
102
|
+
Independent of the parent request cache and of sibling children. Each child can reuse only a byte-identical prefix under its own provider, model, prompt, and schema; its later history grows append-only.
|
|
103
|
+
|
|
104
|
+
### Parent tool result, indirectly
|
|
105
|
+
|
|
106
|
+
#### What the model sees
|
|
107
|
+
|
|
108
|
+
Through [`dsh-tool-workflow`](../tool-workflow/README.md), success exposes only the materialized final JSON value and child count in that consumer's wrapper. This engine supplies stable errors including `workflow script does not parse: <error>`, `invalid meta: <violations>`, `agent() requires a non-empty prompt string`, `agent() could not start a child: <error>`, `child agent run failed: <error>`, and its exact `parallel()`, `pipeline()`, `phase()`, option, schema, and JSON-boundary validation messages. Intermediate child outputs are available to the script but not the parent model.
|
|
109
|
+
|
|
110
|
+
#### Token effect
|
|
111
|
+
|
|
112
|
+
Zero direct parent tokens from this engine. Final result size is capped by the tool consumer and retained until compaction.
|
|
113
|
+
|
|
114
|
+
#### KV Cache effect
|
|
115
|
+
|
|
116
|
+
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
|
|
117
|
+
|
|
118
|
+
## Known Limitations and Deferred Work
|
|
119
|
+
|
|
120
|
+
- **The worker/vm is not a security boundary** — model-written code can escape `node:vm` and reach the worker's process authority; a hostile-code deployment needs a separate-process or container engine.
|
|
121
|
+
- **One worker thread is paid per run** — there is no pool, warm runtime, or cross-run script cache.
|
|
122
|
+
- **No ambient timers, filesystem, or network are injected, but escaped code can still reach Node** — the missing globals are portability API, not containment.
|
|
123
|
+
- **Termination can only report host-observed starts** — `agentsStarted` excludes worker-side calls still queued behind concurrency when a forced termination makes them unknowable.
|
|
124
|
+
- **Cross-realm errors fail `instanceof Error` inside scripts** — workflow authors must branch on stable fields such as `name` and `code`.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-workflow-worker-thread
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
本包为 `WorkflowEngine` 提供实现,每次运行使用一个 Node worker thread。worker 执行编排脚本;子 agent(智能体)留在宿主上,脚本通过带类型的宿主/worker 协议经由 `ctx.subagents` 访问它们。
|
|
6
|
+
|
|
7
|
+
包根目录默认导出引擎插件及其 `Config`;worker 协议、运行时和会话模块均为实现私有。操作入口 `./worker` 仍是引擎的 spawn 目标。
|
|
8
|
+
|
|
9
|
+
这种拆分只有一个主要目的:同步脚本循环不能阻塞 harness 事件循环,忽略取消的脚本可以连同其 worker 一起终止。它不是安全沙箱。
|
|
10
|
+
|
|
11
|
+
## 信任与隔离边界
|
|
12
|
+
|
|
13
|
+
工作流脚本由模型编写,信任前提与模型已有的 bash 访问相同。worker 内的 `node:vm` 是塑造 API 的机制,不是安全边界:逃逸的脚本可以用宿主进程权限重新取得 Node 能力。
|
|
14
|
+
|
|
15
|
+
worker 仍提供实用的隔离:
|
|
16
|
+
|
|
17
|
+
- 脚本 CPU 工作和同步自旋不会占用宿主事件循环;
|
|
18
|
+
- `worker.terminate()` 为 dispose(资源释放)提供真实的最终停止手段;
|
|
19
|
+
- 除未构建 loader 所需的衔接配置外,worker 以空环境启动,因此环境凭据不会通过 `process.env` 跨越边界;
|
|
20
|
+
- 宿主/worker 消息使用结构化克隆数据,并在脚本边界执行普通 JSON 校验。
|
|
21
|
+
|
|
22
|
+
真正的不可信脚本沙箱需要在同一工作流 seam 背后采用不同引擎。
|
|
23
|
+
|
|
24
|
+
## 脚本约定
|
|
25
|
+
|
|
26
|
+
工作流的 `meta` 是宿主提供的数据,而不是待求值的脚本文本。引擎会校验必需的 `name` 和 `description`、拒绝未知字段,并在返回运行前检查脚本正文能否解析。
|
|
27
|
+
|
|
28
|
+
在 worker 内,脚本会收到 `args` 以及以下钩子:
|
|
29
|
+
|
|
30
|
+
- `agent(prompt, { label, phase, schema, model })` 启动一个宿主侧 subagent。提供 schema 时返回结构化值,否则返回最终文本。普通子 agent 失败会产生 `null`;
|
|
31
|
+
- `parallel(thunks)` 在已配置的并发限制下运行 thunk;
|
|
32
|
+
- `pipeline(items, ...stages)` 在没有跨阶段屏障的情况下传递 `(previous, item, index)`;
|
|
33
|
+
- `phase(title)` 和 `log(message)` 发出观察器叙述。
|
|
34
|
+
|
|
35
|
+
未知选项、格式错误的参数、不支持的 schema、超出上限、提供方启动失败和基础设施结果失败都属于致命工作流错误。有意不注入 timer、文件系统 API 或 Node 全局变量,但上述信任注意事项仍然适用。
|
|
36
|
+
|
|
37
|
+
## 运行顺序
|
|
38
|
+
|
|
39
|
+
`start()` 会校验 meta、解析脚本正文、解析一个已注册且规范化的提供方路由,并解析每次运行的子 agent 总数上限,然后才创建 worker 或发布 `workflow/start`。请求的 `maxTotalAgents` 必须是正安全整数,且不能超过引擎配置的部署上限。源代码模式通过 data URL bootstrap 安装 TypeScript 转换;构建模式把同级 `lib/worker.cjs` 作为文件系统路径传入,因为 pkg 的虚拟文件系统(VFS)钩子要求 CommonJS。两者都能在普通 Node 下运行。ready/go 握手可以避免启动信号取消与 worker 启动发生竞态,导致脚本最初的同步片段被执行。
|
|
40
|
+
|
|
41
|
+
对于每次 `agent()` 调用:
|
|
42
|
+
|
|
43
|
+
1. worker 发送 `child-start`,其中包含普通数据提示词和选项。
|
|
44
|
+
2. 宿主通过异步 `SubagentRuntime.start` 调用启动请求中指定的提供方,否则调用已配置的提供方;调用会传入工作流父级和该次运行共用的唯一中止信号。提供方选择应用于该次运行的每个子 agent,对脚本不可见。
|
|
45
|
+
3. 如果启动被拒绝,宿主会发送 `child-start-error`;提供方启动已经完全停稳,不会发出子 agent 生命周期事件。
|
|
46
|
+
4. 如果启动兑现时工作流仍接纳工作,宿主会记录该运行、观察 `result`,然后发送 `child-started`。即使结果已经结算,也只会随后转发,以保持先启动、后结果的顺序。
|
|
47
|
+
5. worker 发出成对的 `workflow/agent-start` 和 `workflow/agent-end` 叙述,并在收集后请求 dispose 子 agent。
|
|
48
|
+
|
|
49
|
+
提供方启动与已发布子 agent 分开跟踪。如果启动仍在等待,而取消、worker 死亡或正常工作流结算关闭了接纳,共享信号会中止该启动。即便提供方随后兑现,宿主也会 dispose 它,且绝不向 worker 通知。
|
|
50
|
+
|
|
51
|
+
## 值边界
|
|
52
|
+
|
|
53
|
+
离开脚本的值会经过 `materializeFromRealm`;该函数接受普通的无损 JSON 数据,并拒绝特殊原型、函数、symbol、循环、稀疏数组、非有限数和嵌套 `undefined`。遍历在 worker 内执行,并把对象键定义为数据属性,使 `__proto__` 无法改变原型。
|
|
54
|
+
|
|
55
|
+
子 agent 结果从宿主跨越到 worker 之前,会先投影并制作快照。这是真正近似进程的序列化边界;它有意不同于可信的同进程工作流和 subagent 事件 payload,后者以不可变方式借用值。
|
|
56
|
+
|
|
57
|
+
## 取消与 dispose
|
|
58
|
+
|
|
59
|
+
`WorkflowRun.cancel()` 会记录第一个原因、通知 worker 取消、中止每个待处理及已发布子 agent 共享的唯一信号,并启动 `disposeGraceMs` 定时器。worker 钩子会在下次 await 时抛出 `CANCELLED`。如果运行到期限仍未结算,宿主会将其以已取消状态兑现、为悬空的子 agent 生命周期事件配对,并终止 worker。
|
|
60
|
+
|
|
61
|
+
subagent seam 只有一个取消通道:请求信号。不存在单独的子 agent 取消 RPC。已发布子 agent 使用 `run.dispose()` 清理;待处理的提供方启动在其 promise 拒绝或兑现前仍由提供方负责。
|
|
62
|
+
|
|
63
|
+
正常结算也会中止待处理启动,并在结果对外结算前开始 dispose 所有已发布但无需等待的子 agent。宿主的完全停稳条件同时包括待处理启动和已发布子 agent 的 dispose,因此清理不会遗漏异步启动事务。
|
|
64
|
+
|
|
65
|
+
`dispose()` 是幂等的。它会取消运行、立即启动宿主驱动的 dispose、在同一宽限时间内等待结果和子 agent 完全停稳、无条件终止 worker,并执行最后一次幸存项扫描。每个子 agent 的 dispose 都会记忆化,使 worker RPC、宿主取消、死亡清理和公开 dispose 都汇入同一操作。
|
|
66
|
+
|
|
67
|
+
## 结果与事件保证
|
|
68
|
+
|
|
69
|
+
在宿主的结果确认点,终态结果遵循先到者胜。已接受的外部取消会覆盖后到的非取消 worker 结果;先完成确认的结果或 worker 死亡不能被可重入清理回调改写。
|
|
70
|
+
|
|
71
|
+
worker 错误、消息失败或提前退出会在清理前关闭消息接纳,然后以 `error` 兑现;如果取消已经接管该运行,则不覆盖取消。后到的排队消息无法在该逻辑边界后创建子 agent 或发出叙述。
|
|
72
|
+
|
|
73
|
+
宿主会维护已转发子 agent 启动的台账。优雅退出的 worker 会提供对应的结束事件;死亡或强制终止会把缺失的结束事件合成为已取消。因此,每个已转发的 `workflow/agent-start` 都会且只会配对一次,不过已经到达的工作流结果之后的清理可能稍后才完成。
|
|
74
|
+
|
|
75
|
+
## 配置
|
|
76
|
+
|
|
77
|
+
| 键 | 默认值 | 含义 |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `provider` | `spawn` | `agent()` 使用的宿主侧 subagent 提供方。 |
|
|
80
|
+
| `maxConcurrentAgents` | `0` | 并发 `agent()` 上限;`0` 会根据可用 CPU 并行度解析。 |
|
|
81
|
+
| `maxTotalAgents` | `1000` | 一次运行中的 `agent()` 调用总数。 |
|
|
82
|
+
| `maxItemsPerCall` | `4096` | 一次 `parallel()` 或 `pipeline()` 调用接受的条目数。 |
|
|
83
|
+
| `syncTimeoutMs` | `5000` | 脚本最初同步片段的 VM 超时时间。 |
|
|
84
|
+
| `disposeGraceMs` | `5000` | 强制结算/终止之前的期限,也是公开 dispose 的期限。 |
|
|
85
|
+
|
|
86
|
+
负责该引擎的消费方可以为一次运行设置 `WorkflowStartRequest.subagentProvider` 和 `WorkflowStartRequest.maxTotalAgents`。它们属于引擎级策略,不是脚本钩子或面向模型的选项;普通 `workflow` 工具不会设置两者。每次运行的子 agent 总数上限可以降低、但绝不能提高已配置的 `maxTotalAgents` 上限。
|
|
87
|
+
|
|
88
|
+
## 模型体验
|
|
89
|
+
|
|
90
|
+
### 子 agent 请求
|
|
91
|
+
|
|
92
|
+
#### 模型看到的内容
|
|
93
|
+
|
|
94
|
+
脚本每次调用 `agent()`,都会把提示词原样发送给 subagent 提供方,并附带可选模型或结构化输出 schema。每个子 agent 看到该提供方自己的上下文;phase 和 log 叙述只留在观察器事件中。
|
|
95
|
+
|
|
96
|
+
#### Token 影响
|
|
97
|
+
|
|
98
|
+
可能需要为许多独立子 agent 上下文支付 token 成本,数量受 `maxConcurrentAgents`、`maxTotalAgents` 和 `maxItemsPerCall` 限制;这些上下文绝不会直接加入父级历史。
|
|
99
|
+
|
|
100
|
+
#### KV Cache 影响
|
|
101
|
+
|
|
102
|
+
与父级请求缓存和同级子 agent 缓存相互独立。每个子 agent 只能在其自身提供方、模型、提示词和 schema 下复用逐字节相同的前缀;其后续历史仅追加增长。
|
|
103
|
+
|
|
104
|
+
### 父级工具结果(间接)
|
|
105
|
+
|
|
106
|
+
#### 模型看到的内容
|
|
107
|
+
|
|
108
|
+
通过 [`dsh-tool-workflow`](../tool-workflow/README.md),成功结果只会在该消费方的包装层中公开实体化的最终 JSON 值和子 agent 数量。本引擎提供稳定错误,包括 `workflow script does not parse: <error>`、`invalid meta: <violations>`、`agent() requires a non-empty prompt string`、`agent() could not start a child: <error>`、`child agent run failed: <error>`,以及其精确的 `parallel()`、`pipeline()`、`phase()`、选项、schema 和 JSON 边界校验消息。中间子 agent 输出可供脚本使用,但不提供给父模型。
|
|
109
|
+
|
|
110
|
+
#### Token 影响
|
|
111
|
+
|
|
112
|
+
本引擎不会直接向父级添加 token。最终结果大小由工具消费方限制,并保留到压缩(compaction)为止。
|
|
113
|
+
|
|
114
|
+
#### KV Cache 影响
|
|
115
|
+
|
|
116
|
+
仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
|
|
117
|
+
|
|
118
|
+
## 已知限制与暂缓事项
|
|
119
|
+
|
|
120
|
+
- **worker/vm 不是安全边界**:模型编写的代码可以逃逸 `node:vm` 并取得 worker 的进程权限;不可信代码部署需要独立进程或容器引擎。
|
|
121
|
+
- **每次运行都要支付一个 worker thread 的成本**:没有池、预热运行时或跨运行脚本缓存。
|
|
122
|
+
- **不注入默认可用的定时器、文件系统或网络,但逃逸代码仍可访问 Node**:这些缺失的全局变量属于可移植性 API 设计,而非隔离措施。
|
|
123
|
+
- **终止只能报告宿主观察到的启动**:`agentsStarted` 不包括因并发限制仍在 worker 侧排队、且在强制终止后无法得知的调用。
|
|
124
|
+
- **跨 realm 错误在脚本内无法通过 `instanceof Error`**:工作流作者必须根据 `name` 和 `code` 等稳定字段分支。
|