diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.i18n.yaml
index 27f0e00a1b..a70c099a8f 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.i18n.yaml
+++ b/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.md
-2026-07-31-code-runtime-python-settlement-fixes.md: b5568d9f4db8bfb34b00a1badcf697dbe2cc6b69
-2026-07-31-code-runtime-python-settlement-fixes.zh.md: eec8a8a5104c83620d890e5805d5e259487be88e
+2026-07-31-code-runtime-python-settlement-fixes.md: ed0532b1792fa9d99f3b13bf12ecf20f73a9102c
+2026-07-31-code-runtime-python-settlement-fixes.zh.md: 70106c28bbbbc65243d3693d7fbd5ad81415fa96
diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.md b/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.md
index b5568d9f4d..ed0532b179 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.md
+++ b/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.md
@@ -42,6 +42,12 @@ The reap poll also handles a host event loop BLOCKED past both timers. If a sync
In [`py/bootstrap.py`](../../../../packages/code-runtime/code-runtime-python/py/bootstrap.py) `_clamped` bounded a requested `(soft, hard)` rlimit pair by the inherited HARD limit alone. A deployment that inherited a soft limit below the requested one — say inherited `(100, 200)`, requested `(150, 160)` — got back `(150, 160)`, RAISING the effective soft from 100 to 150: for `RLIMIT_AS` that loosens the memory ceiling, for `RLIMIT_CPU` it defers SIGXCPU, both violating "strictest of configured and inherited". `_clamped` now clamps each side against its own inherited counterpart (`RLIM_INFINITY` imposing no ceiling), then pins soft under hard so `setrlimit` never sees an inverted pair. The settlement-time CPU recheck (`die_if_cpu_exhausted`) follows the same rule: it compares spent CPU against the EFFECTIVE clamped `cpu_soft`, not the configured `cpuSeconds`, so a program that traps SIGXCPU and burns past a stricter inherited soft before returning is reported as a timeout rather than a false success. The SIGXCPU diagnostic no longer names the configured `cpuSeconds` as the effective budget — under a stricter inherited soft that number is wrong — and instead reports that CPU time was exhausted at "at most the configured N seconds", which holds whichever limit fired.
+### Concurrent binding replies are paced against fd 3
+
+`sendReply` ignored `proto.write`'s `false` return, so a program resolving several large values in one `asyncio.gather` round encoded every reply in the same turn and queued all of them in fd 3's writable buffer. Binding resolution carries no seam-level byte cap to bound that, and the failure kills the HOST process rather than failing the run: measured on a 64 KiB-highWaterMark pipe, eight 4 MiB replies buffered 32.0 MiB at once. Replies now go through a queue that encodes and writes one frame at a time, awaiting `drain` when the pipe is full, which measured a 0.0 MiB peak for the same shape. The encode happens inside the loop so a queued reply the run no longer needs is dropped by the `settled` check without ever being serialized.
+
+Pacing changes nothing the model can observe. The child matches each reply to its `call` by id from a pump that reads fd 3 continuously, so arrival order was never observable, and the bindings themselves still run concurrently -- only the host's peak memory and the flush timing change. That is also why serializing is not a narrowing of the seam's concurrency contract, which was the reason this was first deferred; that reasoning was wrong.
+
### Binding replies complete on the calling loop's thread
Also in `py/bootstrap.py`, a binding reply Future is created on the loop that ran `dispatch`. When the model calls a binding from a worker THREAD via `asyncio.run(tools.x(...))`, that Future belongs to the thread's loop, not the main loop where `_pump_replies` reads the reply. `asyncio.Future` is not thread-safe: completing it from another thread does not wake its own loop, so the direct `set_result`/`set_exception` left the awaiting thread stranded and the run degraded to a wall-clock timeout. Each pending entry now records its Future's loop alongside the Future, and `_pump_replies` completes it via that loop's `call_soon_threadsafe`. The shared `pending`/`next_id` state is guarded by a `threading.Lock` held across the id claim, the fd-3 write, and the counter advance, so concurrent callers cannot interleave frames out of the id order the host requires. `call_soon_threadsafe` onto a loop that has already CLOSED (the worker thread finished and abandoned its call before the reply arrived) raises `RuntimeError`; that schedule is wrapped so the moot reply is dropped rather than letting the exception end the pump task and strand every later reply.
diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.zh.md b/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.zh.md
index eec8a8a510..70106c28bb 100644
--- a/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.zh.md
+++ b/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.zh.md
@@ -42,6 +42,12 @@ Status: implemented
在 [`py/bootstrap.py`](../../../../packages/code-runtime/code-runtime-python/py/bootstrap.py) 中,`_clamped` 仅用继承而来的 HARD 限制来约束一个请求的 `(soft, hard)` rlimit 对。一个继承了低于请求值的软限制的部署——比如继承 `(100, 200)`、请求 `(150, 160)`——会拿回 `(150, 160)`,把有效软限制从 100 抬高到 150:对 `RLIMIT_AS` 而言这放松了内存上限,对 `RLIMIT_CPU` 而言它推迟了 SIGXCPU,两者都违反了"取配置值与继承值中最严格者"。现在 `_clamped` 用每一侧各自继承而来的对应值来约束该侧(`RLIM_INFINITY` 不施加任何上限),随后把 soft 钉在 hard 之下,因此 `setrlimit` 绝不会看到一个倒置的对。结算时的 CPU 复查(`die_if_cpu_exhausted`)遵循同一规则:它把已消耗的 CPU 与实际生效的、被夹紧的 `cpu_soft` 比较,而不是与配置的 `cpuSeconds` 比较,因此一个捕获 SIGXCPU、在返回前消耗超过更严格的继承软限制的程序会被报告为 timeout,而非误判为成功。SIGXCPU 诊断不再把配置的 `cpuSeconds` 说成实际生效的预算——在一个更严格的继承软限制之下那个数字是错的——而是报告 CPU 时间是在"至多配置的 N 秒"处被耗尽,这一表述无论哪个限制先触发都成立。
+### 并发 binding 回复对 fd 3 做节流
+
+`sendReply` 忽略了 `proto.write` 的 `false` 返回值,因此一个在一轮 `asyncio.gather` 中解析多个大值的程序,会把每条回复都在同一个 turn 内编码、并全部排入 fd 3 的可写缓冲。binding 回复在 seam 层没有字节上限可以约束它,而且这个失败杀掉的是**宿主进程**而不是让本次运行失败:在 highWaterMark 为 64 KiB 的管道上实测,八条 4 MiB 回复会同时缓冲 32.0 MiB。现在回复走一个队列,一次编码并写出一帧,管道写满时等待 `drain`——同样形状实测峰值为 0.0 MiB。编码放在循环内部,因此一条运行已不再需要的排队回复会被 `settled` 检查丢弃,根本不会被序列化。
+
+节流不改变任何模型可观测的行为。子进程通过一个持续读取 fd 3 的 pump、按 id 把每条回复匹配到它自己的 `call`,因此到达顺序从来不可观测,而各 binding 本身仍然并发执行——只有宿主的峰值内存与冲刷时延改变。这也正是为什么串行化并不构成对 seam 并发契约的收窄,而那恰是最初推迟此项的理由;那个理由是错的。
+
### Binding replies complete on the calling loop's thread
同样在 `py/bootstrap.py` 中,一个绑定回复 Future 是在运行 `dispatch` 的那个事件循环上创建的。当模型通过 `asyncio.run(tools.x(...))` 从一个工作线程调用某个绑定时,该 Future 属于该线程的事件循环,而不是 `_pump_replies` 读取回复的主事件循环。`asyncio.Future` 不是线程安全的:从另一个线程完成它并不会唤醒它自己的事件循环,因此直接的 `set_result`/`set_exception` 会让那个正在等待的线程被搁置,该次运行退化为墙钟超时。现在每个待处理条目都会在记录 Future 的同时记录其 Future 所属的事件循环,`_pump_replies` 通过该事件循环的 `call_soon_threadsafe` 来完成它。共享的 `pending`/`next_id` 状态由一把 `threading.Lock` 保护,该锁跨越 id 认领、fd-3 写入和计数器推进这三步持有,因此并发调用方无法以违反宿主所要求的 id 顺序来交错帧。对一个已经关闭的事件循环(工作线程已结束、在回复到达前放弃了它的调用)调用 `call_soon_threadsafe` 会抛出 `RuntimeError`;该调度被包裹起来,使这个已无意义的回复被丢弃,而不是让异常终结 pump 任务并搁置此后的每一个回复。
diff --git a/docs/config-catalog.i18n.yaml b/docs/config-catalog.i18n.yaml
index 49cb7bb2c7..503ffa970a 100644
--- a/docs/config-catalog.i18n.yaml
+++ b/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: ec077edd10962f324db242698b1d652563c3ac2f
-config-catalog.zh.md: d349575cb2884bd2e80097c8345db8d0227107c7
+config-catalog.md: f49a76e01e2fceac0e306eeb1e714024d4006ff0
+config-catalog.zh.md: 51ef121bc31f1bbe52013b4ce218f0fcf9f06ac7
diff --git a/docs/config-catalog.md b/docs/config-catalog.md
index b64905ce00..f49a76e01e 100644
--- a/docs/config-catalog.md
+++ b/docs/config-catalog.md
@@ -413,7 +413,7 @@ export interface Config {
}
```
-Source: [`packages/code-runtime/code-runtime-python/src/index.ts:43`](../packages/code-runtime/code-runtime-python/src/index.ts)
+Source: [`packages/code-runtime/code-runtime-python/src/index.ts:44`](../packages/code-runtime/code-runtime-python/src/index.ts)
diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md
index d349575cb2..51ef121bc3 100644
--- a/docs/config-catalog.zh.md
+++ b/docs/config-catalog.zh.md
@@ -358,6 +358,65 @@ export interface Config {
来源:[`packages/client/hmr/src/index.ts:31`](../packages/client/hmr/src/index.ts)
+
+
+## `@deepseek-ai/dsh-code-runtime-python`
+
+```ts config-catalog
+/** Plugin config: every cap, changeable from `cordis.yml` (no hardcoded tunables). */
+export interface Config {
+ /**
+ * RLIMIT_CPU in whole seconds (a positive integer — `setrlimit` in the child
+ * rejects a float). The child sets the soft limit to `cpuSeconds` and the
+ * hard limit to `cpuSeconds + 1`: the kernel delivers SIGXCPU at the soft
+ * limit, which the host classifies as a `timeout`; the +1s hard limit is a
+ * SIGKILL backstop for a program that traps SIGXCPU. Granularity is seconds —
+ * a coarser counterpart to the worker backend's millisecond `computeMs`.
+ */
+ cpuSeconds?: number
+ /** Wall-clock ceiling in milliseconds; backstops CPU time for programs awaiting a promise nobody resolves. */
+ maxWallMs?: number
+ /**
+ * RLIMIT_AS in mebibytes; caps address space so a runaway allocation fails
+ * cleanly. Not applied on Darwin, where the dyld shared cache mapped into
+ * every process at exec exceeds any practical cap and the kernel rejects
+ * the call; `cpuSeconds` and `maxWallMs` still bound the run there. Bounds
+ * `maxLogBytes`/`maxValueBytes` at load on EVERY platform (this static check
+ * runs on Darwin too, where only the runtime `setrlimit` is skipped): each
+ * budget times a worst-case Unicode expansion must fit this byte count minus a
+ * fixed interpreter baseline, so a near-budget output cannot breach the address
+ * space during the child's build-and-encode.
+ */
+ addressSpaceMb?: number
+ /**
+ * Shared byte budget for captured log text (host-side ledger). Bounded at load
+ * against `addressSpaceMb`: the child builds and encodes a near-budget entry
+ * under RLIMIT_AS with several copies live at once, so this cap times the
+ * worst-case Unicode expansion must fit the address space left after the
+ * interpreter baseline (see `addressSpaceMb`) — a load-time rejection, not a
+ * runtime clamp.
+ */
+ maxLogBytes?: number
+ /**
+ * Byte cap for the completion value. Bounded at load against `addressSpaceMb`
+ * the same way `maxLogBytes` is: the child builds and encodes a near-budget
+ * value under RLIMIT_AS with several copies live at once, so this cap times the
+ * worst-case Unicode expansion must fit the address space left after the
+ * interpreter baseline.
+ */
+ maxValueBytes?: number
+ /** SIGTERM→SIGKILL grace period on kill, matching bash-local's default. */
+ graceMs?: number
+ /**
+ * Absolute path or basename of the CPython interpreter to spawn. Resolved
+ * through `PATH` when a basename is given.
+ */
+ pythonBin?: string
+}
+```
+
+来源:[`packages/code-runtime/code-runtime-python/src/index.ts:43`](../packages/code-runtime/code-runtime-python/src/index.ts)
+
## `@deepseek-ai/dsh-code-runtime-worker-thread`
diff --git a/packages/code-runtime/code-runtime-python/README.i18n.yaml b/packages/code-runtime/code-runtime-python/README.i18n.yaml
index 2f4a05fe65..c13bb31a48 100644
--- a/packages/code-runtime/code-runtime-python/README.i18n.yaml
+++ b/packages/code-runtime/code-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/code-runtime/code-runtime-python/README.md
-README.md: e6f78893e32e03760a9d62bae701eb0e93776fd1
-README.zh.md: f4876cf4e13719de4e446bdd28ba58ae041be0a3
+README.md: 3a719c875a39cd2f19b481b8087b20a5b4c884a7
+README.zh.md: 3423763a04c5c18ef02ca52c1cc5aa1bce9ab586
diff --git a/packages/code-runtime/code-runtime-python/README.md b/packages/code-runtime/code-runtime-python/README.md
index e6f78893e3..3a719c875a 100644
--- a/packages/code-runtime/code-runtime-python/README.md
+++ b/packages/code-runtime/code-runtime-python/README.md
@@ -39,4 +39,3 @@ No direct invalidation; the named consumer owns any request-prefix changes.
- **A descendant that calls `setsid()` / `start_new_session=True` escapes teardown.** Termination signals the child's process group with `kill(-pid)`; a descendant that moves itself into a fresh session is no longer in that group and no signal reaches it. If it also releases the inherited stdout/stderr/fd-3 pipes, the leader's `close` still settles the run, and after the `closeDeadline` bound the fiber goes quiescent while that orphan keeps running. This is the containment boundary, not a security one — model code has bash-equivalent trust, and a bash tool can `setsid` away just the same. Reaching such an orphan would require tracking every descendant pid (as the bash-local backend's process-inspector does) and is deferred; the process-group teardown reaps everything that stays in the group.
- **A combined log-and-value peak is not modelled by the load gate.** Each budget is checked against `addressSpaceMb` on its own. A model daemon thread that keeps writing while the completion value is metered and framed can refill the log pending toward `maxLogBytes` during that window, so the two peaks add in a way no gate admits or rejects. A gate over `(maxLogBytes + maxValueBytes)` was considered and deferred: its discriminating case cannot be scheduled deterministically under `RLIMIT_AS`, so the gate would only prove its own arithmetic. When the combined peak is reached the run dies as `worker-exit` -- containment holds and only the failure classification is degraded.
- **A wide binding REPLY expands host-side state per member.** Resolutions cross through `snapshotJsonValue` in [`@deepseek-ai/dsh-session`](../../core/session/README.md), whose `walkJsonValue` pushes one task frame per member, and binding resolution carries no seam-level byte cap. A legitimate reply of several million elements can therefore exhaust the host heap. The property belongs to that shared walk, not to this backend -- the worker-thread backend consumes the same function -- so the fix belongs in `packages/core/session` where every consumer benefits.
-- **Concurrent binding replies are not paced against fd 3.** `proto.write` returns `false` once the pipe's buffer is full and this backend does not wait for `drain`, so several bindings resolving large values in one `asyncio.gather` round encode and queue together in host memory. Serializing the replies would bound it, at the cost of changing the concurrency the seam currently allows; the sibling worker-thread backend has no equivalent (it posts structured clones, which carry no stream backpressure), so there is no in-repo precedent to copy.
diff --git a/packages/code-runtime/code-runtime-python/README.zh.md b/packages/code-runtime/code-runtime-python/README.zh.md
index f4876cf4e1..3423763a04 100644
--- a/packages/code-runtime/code-runtime-python/README.zh.md
+++ b/packages/code-runtime/code-runtime-python/README.zh.md
@@ -39,4 +39,3 @@ host 与 CPython 子进程在子进程的 fd 3 上交换一个无版本号的 JS
- **调用 `setsid()` / `start_new_session=True` 的后代会逃出 teardown。** 终止是用 `kill(-pid)` 向子进程的进程组发信号;一个把自己移入新会话的后代已不在该进程组内,任何信号都到不了它。若它同时释放了继承而来的 stdout/stderr/fd-3 管道,leader 的 `close` 仍会结算该次运行,在 `closeDeadline` 到界之后 fiber 变为完全停稳,而那个孤儿仍在运行。这是 containment 边界,而非安全边界——模型代码具有等同 bash 的信任级别,一个 bash 工具同样能 `setsid` 逃逸。要够到这样的孤儿需要追踪每一个后代 pid(如 bash-local 后端的 process-inspector 所做),此项已推迟;进程组 teardown 会回收所有留在组内的进程。
- **日志与完成值的叠加峰值未被加载门建模。** 每项预算都是各自对照 `addressSpaceMb` 检查的。模型的 daemon 线程可以在完成值被计量并分帧的窗口内持续写入、把日志 pending 重填到接近 `maxLogBytes`,于是两个峰值以任何门都不曾放行也不曾拒绝的方式相加。对 `(maxLogBytes + maxValueBytes)` 设门的方案经评估后推迟:它的判别用例无法在 `RLIMIT_AS` 之下确定性地构造出来,因此该门只能证明自己的算术。叠加峰值被触及时该次运行死为 `worker-exit`——containment 仍然成立,只是失败分类失真。
- **宽 binding 回复会按成员展开宿主侧状态。** 回复经由 [`@deepseek-ai/dsh-session`](../../core/session/README.md) 的 `snapshotJsonValue` 穿越,其 `walkJsonValue` 为每个成员压入一个任务帧,而 binding 回复在 seam 层没有字节上限。因此一个数百万元素的合法回复可以耗尽宿主堆。该性质属于那个共享遍历,而不属于本后端——worker-thread 后端消费同一个函数——所以修复应落在 `packages/core/session`,让所有消费方一并受益。
-- **并发 binding 回复没有对 fd 3 做节流。** 管道缓冲写满后 `proto.write` 返回 `false`,而本后端不等待 `drain`,因此在一轮 `asyncio.gather` 中多个 binding 同时返回大值时,它们会一起编码并排入宿主内存。把回复串行化可以给它设界,代价是改变 seam 当前允许的并发度;同类的 worker-thread 后端没有等价物(它投递结构化克隆,不存在流背压),因此仓库内没有可照抄的先例。
diff --git a/packages/code-runtime/code-runtime-python/src/index.ts b/packages/code-runtime/code-runtime-python/src/index.ts
index 89843b4962..096a0da2c1 100644
--- a/packages/code-runtime/code-runtime-python/src/index.ts
+++ b/packages/code-runtime/code-runtime-python/src/index.ts
@@ -13,6 +13,7 @@
*/
import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'
+import { once } from 'node:events'
import { accessSync, copyFileSync, constants as fsConstants, mkdtempSync, readFileSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { delimiter, dirname, isAbsolute, join } from 'node:path'
@@ -1382,14 +1383,47 @@ export class PythonCodeRuntime extends CodeRuntime {
// strings/numbers), which is encodeJsonPlain's precondition. A closed
// pipe (child already gone) is swallowed since the close path settles
// the run.
+ //
+ // Replies are encoded and written ONE AT A TIME, waiting for `drain`
+ // whenever fd 3's buffer is full. Binding resolution carries no
+ // seam-level byte cap, so a program that resolves several large values in
+ // one `asyncio.gather` round would otherwise encode them all in the same
+ // turn and queue every frame in the writable stream's buffer -- measured
+ // to exhaust a 256 MiB Node heap, which kills the whole host process
+ // rather than failing this one run. Pacing changes no model-visible
+ // behavior: the child matches each reply to its `call` by id from a pump
+ // that reads fd 3 continuously, so arrival order was never observable,
+ // and the bindings themselves still run concurrently. Only the host's peak
+ // memory and the flush timing change.
+ const replyQueue: ReplyMessage[] = []
+ let draining = false
+ const drainReplies = async (): Promise => {
+ if (draining) return
+ draining = true
+ try {
+ while (replyQueue.length > 0) {
+ if (settled) break
+ const payload = replyQueue.shift() as ReplyMessage
+ // Encode inside the loop, not up front: a queued reply the run no
+ // longer needs is dropped by the `settled` check above without ever
+ // being serialized.
+ if (!proto.write(`${encodeJsonPlain(payload)}\n`)) {
+ await once(proto, 'drain')
+ }
+ }
+ } catch {
+ // Pipe closed under us (child exited), or `drain` never arrives because
+ // the child died. The close path settles the run either way.
+ } finally {
+ draining = false
+ replyQueue.length = 0
+ }
+ }
const sendReply = (payload: ReplyMessage): void => {
/* v8 ignore next -- `settled` covers a race where the child exits between decision and write. */
if (settled) return
- try {
- proto.write(`${encodeJsonPlain(payload)}\n`)
- } catch {
- // Pipe closed under us (child exited). The close path finishes the run.
- }
+ replyQueue.push(payload)
+ void drainReplies()
}
// Escalate SIGTERM → grace → SIGKILL on the entire process group. Idempotent
diff --git a/packages/code-runtime/code-runtime-python/tests/runtime.spec.ts b/packages/code-runtime/code-runtime-python/tests/runtime.spec.ts
index a47e5d6196..acb8a61ff3 100644
--- a/packages/code-runtime/code-runtime-python/tests/runtime.spec.ts
+++ b/packages/code-runtime/code-runtime-python/tests/runtime.spec.ts
@@ -3863,6 +3863,35 @@ describe('PythonCodeRuntime — hostile peer', () => {
expect(resolvedLate).toBe(true)
}, 90_000)
+ it('paces concurrent binding replies instead of queueing every frame at once', async () => {
+ // Binding resolution carries no seam-level byte cap. Before pacing, a program
+ // resolving several large values in one `asyncio.gather` round encoded them
+ // all in the same turn and queued every frame in fd 3's writable buffer,
+ // which exhausted the host heap and killed the whole process rather than
+ // failing the run. Replies are now encoded one at a time, waiting for
+ // `drain` when the pipe is full.
+ //
+ // Eight concurrent 4 MiB replies (32 MiB of frames) must all round-trip. The
+ // program sums the lengths, so the assertion proves every reply arrived and
+ // was matched to its own call -- pacing must not drop or misroute any. What
+ // this case cannot show is the peak itself, which lives in the stream's
+ // buffer: measured directly on a 64 KiB-highWaterMark pipe with this same
+ // 8x4 MiB shape, the unpaced writes buffered 32.0 MiB while the paced ones
+ // peaked at 0.0 MiB.
+ const chunk = 'A'.repeat(4 * 1024 * 1024)
+ const { runtime } = await setup({ maxWallMs: 60_000 })
+ const result = await runtime.run({
+ program: [
+ 'import asyncio',
+ 'parts = await asyncio.gather(*[tools.chunk({}) for _ in range(8)])',
+ 'return sum(len(p) for p in parts)',
+ ].join('\n'),
+ bindings: [{ global: 'tools', functions: { chunk: async () => chunk } }],
+ })
+ expect(result.error).toBeUndefined()
+ expect(result.value).toBe(8 * chunk.length)
+ }, 90_000)
+
it('bounds a flood of zero-byte log lines through the per-entry separator charge', async () => {
// Blank print() lines carry zero content bytes; without the +1 separator
// charge they would bypass maxLogBytes entirely and grow the retained