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 25e3f265bb..8760266b9c 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: 97555414342a02d7340febf68caa03545e168a60
-2026-07-31-code-runtime-python-settlement-fixes.zh.md: 8e536b438adfa2c4c756d67d76dd936f97c987da
+2026-07-31-code-runtime-python-settlement-fixes.md: 97768a63f3192b90d89cc2b935cb962844e91d04
+2026-07-31-code-runtime-python-settlement-fixes.zh.md: ae2cb4949a2c8c20a1ce12c046d6da11a5f1dcb1
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 9755541434..97768a63f3 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
@@ -74,7 +74,7 @@ One residual write-path copy is fixed alongside, independent of the config gate:
### The completion value and error are pre-encoded at their validation point
-In [`py/bootstrap.py`](../../../../packages/code-runtime/code-runtime-python/py/bootstrap.py), `_done_with_value` now returns the whole terminal frame as a PRE-ENCODED JSON string on the success path: the admitted value is serialized once here, at the validation point inside `_run`'s `try`, as `'{"type": "done", "value": ' + _encode_json_plain(value) + "}"`. The program can keep mutating a returned list/dict from a daemon thread or signal handler after it returns, so a second traversal held at a later point would be a TOCTOU — a concurrent mutation into a non-JSON type would let that later encode throw outside the settlement handler and downgrade a settled run host-side to `worker-exit`. Serializing once, inside the `try` that wraps this call, closes the window: if a concurrent mutation makes the encode throw, the exception handler classifies it as an `exception`, and once the string is produced the frame is written verbatim with no further touching of the live value.
+In [`py/bootstrap.py`](../../../../packages/code-runtime/code-runtime-python/py/bootstrap.py), `_done_with_value` now returns the whole terminal frame as a PRE-ENCODED JSON string on the success path: the admitted value is serialized once here, at the validation point inside `_run`'s `try`, as `'{"type": "done", "value": ' + _encode_json_plain(value) + "}"`. The program can keep mutating a returned list/dict from a daemon thread or signal handler after it returns, so a second traversal held at a later point would be a TOCTOU — a concurrent mutation into a non-JSON type would let that later encode throw outside the settlement handler and downgrade a settled run host-side to `worker-exit`. Serializing once, inside the `try` that wraps this call, closes the window: if a concurrent mutation makes the encode throw, the exception handler classifies it as an `exception`, and once the string is produced the frame is written verbatim with no further touching of the live value. `_done_with_value` also binds `_check_done_value` and `_encode_json_plain` as DEF-TIME default arguments, so a `__main__` rebind after model execution cannot rewrite a legitimate success into an `exception`.
`send_done` (a local function inside `_run`) writes the pre-encoded string through a BOUND `channel.write_encoded`, and encodes a dict error frame through a bound `_encode_json_plain` before writing it — it never calls `channel.send_sync`, whose body re-resolves `self.write_encoded` and the module-level `_encode_json_plain` at call time. `_encode_json_plain` and `channel.write_encoded` are bound into locals before the program runs, for the same reason `flush_out`/`flush_err`/`safe_model_traceback` are: the program runs as `__main__`, so `import __main__; __main__.ProtocolChannel.send_sync = boom` or `__main__._encode_json_plain = boom` would otherwise re-resolve the send/encode to a rebranded callable at call time and, when that replacement raises, skip the `done` frame and downgrade a settled verdict to a host-side `worker-exit`.
@@ -92,13 +92,13 @@ In [`py/bootstrap.py`](../../../../packages/code-runtime/code-runtime-python/py/
`_clamped` also lowers a clamped RLIMIT_CPU soft limit that EQUALS the hard by one unit (when the hard is at least 2). A `ulimit -t N` sets both, and with soft == hard the kernel checks the hard limit in the same tick and SIGKILLs a busy loop directly, so SIGXCPU is never delivered — and the host classifies a CPU overrun ONLY on `signal === 'SIGXCPU'`, so a definite budget exhaustion would be misreported as a `worker-exit`. Lowering the soft one unit gives SIGXCPU a window to fire, so the overrun is reported as a timeout. This is scoped to RLIMIT_CPU (a one-byte soft differential on RLIMIT_AS would only misalign the child's applied limit with the host budget gate, with no signal to preserve). The `hard >= 2` guard leaves a `hard == 1` blind spot — a 1-second dual limit cannot lower the soft to 0, so a definite overrun there is still reported as `worker-exit`.
-`send_done` wraps its encode+write in a try and, on any throw from a rebound transitive name (`_dump_scalar`/`os`), writes a fixed pre-encoded done frame via the `_run`-local bound `_os_write`/`_memoryview`/`_FALLBACK_DONE_FRAME` — so a settled `exception` verdict is never downgraded to a `worker-exit`, and the host still gets a verdict. The reply queue's head-cursor drain clears each consumed slot so a wide written payload is released immediately, bounding host memory to the current backlog under sustained fd-3 backpressure. The exception classes the settlement-path `except` clauses catch are likewise bound before any model code runs: `_BaseException` is a `_run` LOCAL and a closure cell in `_make_failure_reporter`; `_RuntimeError`, `_BindingRejection`, `str`, and `bool` are DEF-TIME default arguments of `_pump_replies` (a body-local `X = X` binding is too late — the model's top-level statements run before the pump's first step). A rebind of `__main__.BaseException` or `__main__.RuntimeError` cannot make a program exception escape the handler and lose the `done` frame.
+`send_done` wraps its encode+write in a try and, on any throw from a rebound transitive name (`_dump_scalar`/`os`), writes a fixed pre-encoded done frame via the `_run`-local bound `_os_write`/`_memoryview`/`_FALLBACK_DONE_FRAME` — so a settled `exception` verdict is never downgraded to a `worker-exit`, and the host still gets a verdict. The reply queue's head-cursor drain clears each consumed slot so a wide written payload is released immediately, bounding host memory to the current backlog under sustained fd-3 backpressure. The exception classes the settlement-path `except` clauses catch are likewise bound before any model code runs: `_BaseException` is a `_run` LOCAL and a closure cell in `_make_failure_reporter`; `_RuntimeError`, `_BindingRejection`, `str`, and `bool` are DEF-TIME default arguments of `_pump_replies` (a body-local `X = X` binding is too late — the model's top-level statements run before the pump's first step). A rebind of `__main__.BaseException` cannot make a program exception escape the handler and lose the `done` frame; a rebind of `__main__.RuntimeError` (or `_BindingRejection`/`str`/`bool`) cannot make a closed-loop scheduling failure escape the pump catch and strand every later reply to the wall clock.
## Testing
- `tests/boot-write-failure.spec.ts` mocks `spawn` so the fd-3 pipe throws on the boot write — the one path a real subprocess cannot be coerced into — and asserts `run()` resolves a `worker-exit` rather than rejecting. A sibling case makes the mocked `spawn` throw SYNCHRONOUSLY and asserts `run()` still resolves a `worker-exit` and removes its staging directory, keyed off the exact bootstrap path the mocked `spawn` received in its argv so a sibling worker's concurrent staging cannot flake it. Both are isolated in this spec so the real-subprocess suite is untouched.
- `tests/residual-detach.spec.ts` unit-tests `detachResidual`: the carried copy equals the residual, owns a backing store sized to its own length (fixture kept above Node's Buffer pool threshold), and does not share the source frame's `ArrayBuffer`.
-- `tests/runtime.spec.ts` — the output-cap case asserts the `ceiling - envelope` bound (268435392) and its message. A daemon-thread case drives four threads emitting unterminated writes through settlement's flush. A native-write case writes 200 KiB with no newline via `os.write` under a raised `maxLogBytes` and asserts it reads back as EXACTLY one log entry (proving stray output is aggregated by line, not split at pipe-chunk boundaries); a companion writes `b"one\ntwo\nthree"` and asserts three entries (proving real newlines still delimit). A newline-free-flood case writes 2 MiB under a 4 KiB `maxLogBytes` and asserts the capture ends at the truncation marker and stays under budget (proving the residual is bounded by the ledger, not buffered whole); a NUL-flood companion writes 4000 newline-free NULs under the same budget and asserts truncation (proving the residual is charged by SERIALIZED cost, ~6× raw, measured without allocating the escaped copy); an illegal-UTF-8 case paces single-byte `\xff` writes under a 3072-byte budget with `Buffer.concat` wrapped to measure the peak merged buffer, asserting it stays under 2048 (charged at the U+FFFD width 3 the residual flushes near 1024 raw bytes; a raw-byte undercount would let it reach ~3072, so the bound discriminates); a CESU-8/overlong case paces the structurally-well-formed but illegal `ED A0 80` one byte at a time and asserts the same peak bound (charged at the true 9 per sequence it flushes early; charging the structural width 3 triples the peak, so reverting the per-lead range check turns it red); a broken-multibyte case writes a 3-byte lead then a fresh ASCII byte in separate chunks and asserts both a captured `A` and a U+FFFD (exercising `accrueStrayCost`'s cross-chunk broken-sequence branch); a post-truncation case writes a 108-byte payload (under the smallest PIPE_BUF, so one atomic write) whose first line exhausts a 64-byte budget and asserts the second line is dropped (exercising the post-truncation admit no-op in one `data` callback, no v8-ignore); a short-escape case writes a line mixing a tab, quote, backslash, a `\uXXXX` control, a multibyte character, and ASCII, asserting it round-trips verbatim (exercising every branch of `jsonStringCostUpTo`); a reassembly case writes a payload spanning every valid multibyte lead class (E0-range, plain 3-byte, F0, and F4) past the pipe buffer and asserts it round-trips with no U+FFFD (exercising `accrueStrayCost`'s per-lead ranges and cross-chunk reassembly); a lone-surrogate case forges an fd-3 `log` frame flooding 1000 `\ud800` escapes under a 4 KiB budget and asserts truncation (the count sits in the window where charging 3 bytes would admit and 6 bytes truncates, proving the surrogate is charged its full escaped width); a stray-sealing case paces 60000 single-byte newline-free `os.write(1, …)` calls under a raised budget with `Buffer.concat` wrapped to measure copy volume, asserting the trickle coalesces to one entry and the cumulative copy stays under a measured 256 KiB threshold (the sealed shape copies ~120 KB, the re-merge shape ~538 KB, so reverting the seal to a re-merge turns the assertion red — proving the fragment list seals into blocks past `MAX_PENDING_CHUNKS`). A closeDeadline-flush case has the leader write a newline-free diagnostic then spawn a `setsid` orphan holding the pipes open, and asserts the diagnostic survives in `logs` (proving the residual is flushed before the deadline destroys the streams). The same-group reap case spawns a SIGTERM-ignoring same-group descendant that releases the pipes and bumps a heartbeat file; the test asserts the heartbeat STOPS after the grace-window SIGKILL — an assertion robust whether the killed descendant is reaped or lingers as a zombie, so it holds where PID 1 does not wait() orphans. A dispose-after-resolve case asserts `dispose()` of a completed run with a same-group survivor returns only after the survivor stops executing (proving the run stays in `live` until its group is reaped), with an `expect(afterDispose).toBeGreaterThan(0)` guard so the frozen-heartbeat assertion cannot pass vacuously when the file was never written. A deadline case busy-blocks the event loop past both timers and asserts the survivor's heartbeat freezes (proving the poll's deadline arm sends SIGKILL itself rather than cancelling the unfired escalation). The cross-loop case runs a binding from a worker thread's own `asyncio.run` loop while the main coroutine yields with `await asyncio.sleep`, asserting the reply round-trips instead of timing out; a companion case abandons a thread's call so its loop closes, then answers it before a later binding — asserting the pump survives the closed-loop `call_soon_threadsafe` (host-gated ordering makes it deterministic, fail-before hangs the later binding to the wall clock). The inherited-soft-limit case runs the interpreter through a `ulimit -S -t` wrapper that sets a CPU soft limit below `cpuSeconds` and asserts the applied `RLIMIT_CPU` soft is the inherited value, not the configured one (CPU rather than address space, since macOS ignores `ulimit -v`); a companion inherits a 1 s CPU soft, has the program trap SIGXCPU and busy-loop past it, and asserts the settlement recheck reports a timeout — proving the recheck uses the effective soft, not the configured `cpuSeconds`. A control-heavy-diagnostic case raises a NUL-flood exception under a small `maxValueBytes` and asserts the serialized frame fits (proving the diagnostic is metered by serialized cost). A tail-copy case (`maxLogBytes: 256`, `addressSpaceMb: 384`) has the program build a tail in a variable and write `"\n" + tail` where `tail` is 150 MiB — construction peaks at ~2× (~300 MiB, within the address space, so the model's own allocation succeeds and any OOM belongs to the defect path), and the pre-fix whole-tail re-buffer added a third ~150 MiB copy past 384 MiB; the sliced prefix lets the run truncate and complete (Linux-only RLIMIT_AS repro, macOS happy path — the fixture's own construction must fit the address space, a general rule for these RLIMIT_AS cases). An output-budget/address-space case asserts a `maxLogBytes` of 50 MB AND a `maxValueBytes` of 50 MB each reject at load against a 256 MiB `addressSpaceMb` (past the room left after the interpreter baseline when multiplied by the worst-case 12) while the default caps against 512 MiB load, gating both budgets symmetrically; a discriminating case asserts a 48 MiB `maxLogBytes` against a 512 MiB `addressSpaceMb` rejects — 48×8 = 384 MiB fits the 448 MiB budgetable (the old 8× multiple wrongly admitted it) but 48×12 = 576 MiB does not. The ~12× peak the multiple covers is the NEWLINE path's single near-budget write — the caller's own string, the line slice, and the encode copy live at once; the settlement flush is no longer the binding case, because `flush_line` drops the pending chunks before its push and so holds two copies rather than three. An inherited-RLIMIT_AS case runs the interpreter through a `ulimit -v 131072` wrapper with a 32 MiB `maxLogBytes` the configured 512 MiB `addressSpaceMb` admits, and asserts the boot re-check rejects it as an `exception` whose message names the inherited RLIMIT_AS (the 128 MiB inherited limit leaves too little after the baseline; Linux-only, macOS ignores `ulimit -v` and the run proceeds). A non-integer-budget case asserts a fractional `maxLogBytes`/`maxValueBytes` rejects at load. A combined-peak case (`maxLogBytes: 32 MiB`, `maxValueBytes: 32 MiB`, `addressSpaceMb: 512` — each budget admitted alone at 12×) writes ~33M newline-free astral characters (buffered, unflushed) then returns ~33M astral characters, and asserts the run settles as `output-limit` (the value is itself over its 32 MiB budget); pre-fix the unflushed log pending plus the value's build-and-encode peak added past the 512 MiB address space and OOM'd, so flushing the logs before framing the value is what lets the value check complete (Linux-only RLIMIT_AS repro; on macOS the over-budget value reports output-limit under both orders). A wide-value case (`maxValueBytes: 20 MiB`, `addressSpaceMb: 384`) returns `[0] * 6_000_000` — ~12 MB of JSON, under the 20 MiB budget, so it must round-trip; pre-fix the O(width) walk allocated ~400 MB of per-element traversal tuples and encoder stack entries (~28× the serialized size, past the 12× the gate reserves) and OOM'd on a value the meter admitted, while the O(depth) cursor keeps the only width-proportional allocation the output string itself (Linux-only RLIMIT_AS repro; the fixture stays within the address space so it is honest on macOS too). A wide-BINDING-ARGUMENT case (`addressSpaceMb: 384`) calls a binding with `[0] * 6_000_000` and asserts the length echoes back: `_lossless_json_violation` runs on model-built arguments that no child-side budget bounds first, and its per-member tuples measured 459.1 MiB against 0.0 MiB for the cursor. A backtracking case returns a 4 MiB string from a binding and asserts it round-trips: the old scalar regex retained engine state proportional to the string's width (146 MiB at 1 MiB, 557.8 MiB at 4 MiB, past the default 512 MiB), which raised MemoryError inside `_pump_replies` and stranded the call to the wall clock. A control-heavy metering case returns 8M NULs under a 16 MiB `maxValueBytes` and asserts `output-limit`, not `exception`: charging by counting instead of materializing the escaped form measured 19.1 MiB against 228.9 MiB for an identical byte count. An addressSpaceMb-baseline case asserts 64 MiB and 32 MiB reject at load with a message naming `addressSpaceMb`, rather than the budget loop's negative admissible limit. A process-identity case asserts the leader's start time reads stably on Linux and reports undefined on Darwin, the guard that keeps a recycled pgid from receiving this run's SIGTERM. A paced-replies case resolves eight 4 MiB values in one `asyncio.gather` round and asserts the frames round-trip; the peak the fix removes (32.0 MiB buffered → 0.0 MiB) lives in the host's fd-3 writable buffer, invisible through the seam, so this case pins the round-trip and the no-regression match but is measured for its peak only out-of-tree. A late-drop case settles the run on `maxWallMs` and resolves the pending binding afterwards, asserting a `timeout` result, an undefined value, and that the late path actually ran — the three assertions all hold pre-fix because `sendReply` already dropped after-settlement values, just later, so the case pins the ordering, not a seam-observable behavior. A binding-all-names case (`rebinds every name the failure path uses`) asserts a real `ValueError` survives send-done binding — a tested fix pinned by a case that rebinds `__main__.ProtocolChannel.send_sync`, `__main__.ProtocolChannel.write_encoded`, and `__main__._encode_json_plain` — the three names the shipped `send_done` would resolve late if it looked them up at call time — and pins the `done` frame against that call-time look-up skipping it; the done-value TOCTOU pre-encoding, the stray-UTF-8 budget-flush retention, and the late-rejection settled guard are counted among the ten no-fail-before fixes (reasons in the Problem section), not pinned by a fail-before test. A fragment-cap drip case writes 200 000 single-character newline-free `sys.stdout.write` calls and asserts the run completes with a truncation marker rather than a MemoryError (no-fail-before: the 25 M-scale OOM is not deterministically constructible in CI). A dual-limit CPU case runs the interpreter through a `ulimit -t 2` wrapper and busy-loops past it, asserting a `timeout` (the soft limit is lowered to 1 so SIGXCPU fires, not a `worker-exit`). A transitive-name rebind case rebinds `__main__._dump_scalar`, `__main__.os`, `__main__._os_write`, `__main__._memoryview`, and `__main__._FALLBACK_DONE_FRAME` and asserts a done frame still lands as an `exception`, not a `worker-exit` (the real message is replaced by the fixed fallback literal). A BaseException-rebind case rebinds `__main__.BaseException` to `RuntimeError` and raises `ValueError`, asserting the run still reports an `exception`, not a `worker-exit` (the catch uses a pre-program local exception class).
+- `tests/runtime.spec.ts` — the output-cap case asserts the `ceiling - envelope` bound (268435392) and its message. A daemon-thread case drives four threads emitting unterminated writes through settlement's flush. A native-write case writes 200 KiB with no newline via `os.write` under a raised `maxLogBytes` and asserts it reads back as EXACTLY one log entry (proving stray output is aggregated by line, not split at pipe-chunk boundaries); a companion writes `b"one\ntwo\nthree"` and asserts three entries (proving real newlines still delimit). A newline-free-flood case writes 2 MiB under a 4 KiB `maxLogBytes` and asserts the capture ends at the truncation marker and stays under budget (proving the residual is bounded by the ledger, not buffered whole); a NUL-flood companion writes 4000 newline-free NULs under the same budget and asserts truncation (proving the residual is charged by SERIALIZED cost, ~6× raw, measured without allocating the escaped copy); an illegal-UTF-8 case paces single-byte `\xff` writes under a 3072-byte budget with `Buffer.concat` wrapped to measure the peak merged buffer, asserting it stays under 2048 (charged at the U+FFFD width 3 the residual flushes near 1024 raw bytes; a raw-byte undercount would let it reach ~3072, so the bound discriminates); a CESU-8/overlong case paces the structurally-well-formed but illegal `ED A0 80` one byte at a time and asserts the same peak bound (charged at the true 9 per sequence it flushes early; charging the structural width 3 triples the peak, so reverting the per-lead range check turns it red); a broken-multibyte case writes a 3-byte lead then a fresh ASCII byte in separate chunks and asserts both a captured `A` and a U+FFFD (exercising `accrueStrayCost`'s cross-chunk broken-sequence branch); a post-truncation case writes a 108-byte payload (under the smallest PIPE_BUF, so one atomic write) whose first line exhausts a 64-byte budget and asserts the second line is dropped (exercising the post-truncation admit no-op in one `data` callback, no v8-ignore); a short-escape case writes a line mixing a tab, quote, backslash, a `\uXXXX` control, a multibyte character, and ASCII, asserting it round-trips verbatim (exercising every branch of `jsonStringCostUpTo`); a reassembly case writes a payload spanning every valid multibyte lead class (E0-range, plain 3-byte, F0, and F4) past the pipe buffer and asserts it round-trips with no U+FFFD (exercising `accrueStrayCost`'s per-lead ranges and cross-chunk reassembly); a lone-surrogate case forges an fd-3 `log` frame flooding 1000 `\ud800` escapes under a 4 KiB budget and asserts truncation (the count sits in the window where charging 3 bytes would admit and 6 bytes truncates, proving the surrogate is charged its full escaped width); a stray-sealing case paces 60000 single-byte newline-free `os.write(1, …)` calls under a raised budget with `Buffer.concat` wrapped to measure copy volume, asserting the trickle coalesces to one entry and the cumulative copy stays under a measured 256 KiB threshold (the sealed shape copies ~120 KB, the re-merge shape ~538 KB, so reverting the seal to a re-merge turns the assertion red — proving the fragment list seals into blocks past `MAX_PENDING_CHUNKS`). A closeDeadline-flush case has the leader write a newline-free diagnostic then spawn a `setsid` orphan holding the pipes open, and asserts the diagnostic survives in `logs` (proving the residual is flushed before the deadline destroys the streams). The same-group reap case spawns a SIGTERM-ignoring same-group descendant that releases the pipes and bumps a heartbeat file; the test asserts the heartbeat STOPS after the grace-window SIGKILL — an assertion robust whether the killed descendant is reaped or lingers as a zombie, so it holds where PID 1 does not wait() orphans. A dispose-after-resolve case asserts `dispose()` of a completed run with a same-group survivor returns only after the survivor stops executing (proving the run stays in `live` until its group is reaped), with an `expect(afterDispose).toBeGreaterThan(0)` guard so the frozen-heartbeat assertion cannot pass vacuously when the file was never written. A deadline case busy-blocks the event loop past both timers and asserts the survivor's heartbeat freezes (proving the poll's deadline arm sends SIGKILL itself rather than cancelling the unfired escalation). The cross-loop case runs a binding from a worker thread's own `asyncio.run` loop while the main coroutine yields with `await asyncio.sleep`, asserting the reply round-trips instead of timing out; a companion case abandons a thread's call so its loop closes, then answers it before a later binding — asserting the pump survives the closed-loop `call_soon_threadsafe` (host-gated ordering makes it deterministic, fail-before hangs the later binding to the wall clock). The inherited-soft-limit case runs the interpreter through a `ulimit -S -t` wrapper that sets a CPU soft limit below `cpuSeconds` and asserts the applied `RLIMIT_CPU` soft is the inherited value, not the configured one (CPU rather than address space, since macOS ignores `ulimit -v`); a companion inherits a 1 s CPU soft, has the program trap SIGXCPU and busy-loop past it, and asserts the settlement recheck reports a timeout — proving the recheck uses the effective soft, not the configured `cpuSeconds`. A control-heavy-diagnostic case raises a NUL-flood exception under a small `maxValueBytes` and asserts the serialized frame fits (proving the diagnostic is metered by serialized cost). A tail-copy case (`maxLogBytes: 256`, `addressSpaceMb: 384`) has the program build a tail in a variable and write `"\n" + tail` where `tail` is 150 MiB — construction peaks at ~2× (~300 MiB, within the address space, so the model's own allocation succeeds and any OOM belongs to the defect path), and the pre-fix whole-tail re-buffer added a third ~150 MiB copy past 384 MiB; the sliced prefix lets the run truncate and complete (Linux-only RLIMIT_AS repro, macOS happy path — the fixture's own construction must fit the address space, a general rule for these RLIMIT_AS cases). An output-budget/address-space case asserts a `maxLogBytes` of 50 MB AND a `maxValueBytes` of 50 MB each reject at load against a 256 MiB `addressSpaceMb` (past the room left after the interpreter baseline when multiplied by the worst-case 12) while the default caps against 512 MiB load, gating both budgets symmetrically; a discriminating case asserts a 48 MiB `maxLogBytes` against a 512 MiB `addressSpaceMb` rejects — 48×8 = 384 MiB fits the 448 MiB budgetable (the old 8× multiple wrongly admitted it) but 48×12 = 576 MiB does not. The ~12× peak the multiple covers is the NEWLINE path's single near-budget write — the caller's own string, the line slice, and the encode copy live at once; the settlement flush is no longer the binding case, because `flush_line` drops the pending chunks before its push and so holds two copies rather than three. An inherited-RLIMIT_AS case runs the interpreter through a `ulimit -v 131072` wrapper with a 32 MiB `maxLogBytes` the configured 512 MiB `addressSpaceMb` admits, and asserts the boot re-check rejects it as an `exception` whose message names the inherited RLIMIT_AS (the 128 MiB inherited limit leaves too little after the baseline; Linux-only, macOS ignores `ulimit -v` and the run proceeds). A non-integer-budget case asserts a fractional `maxLogBytes`/`maxValueBytes` rejects at load. A combined-peak case (`maxLogBytes: 32 MiB`, `maxValueBytes: 32 MiB`, `addressSpaceMb: 512` — each budget admitted alone at 12×) writes ~33M newline-free astral characters (buffered, unflushed) then returns ~33M astral characters, and asserts the run settles as `output-limit` (the value is itself over its 32 MiB budget); pre-fix the unflushed log pending plus the value's build-and-encode peak added past the 512 MiB address space and OOM'd, so flushing the logs before framing the value is what lets the value check complete (Linux-only RLIMIT_AS repro; on macOS the over-budget value reports output-limit under both orders). A wide-value case (`maxValueBytes: 20 MiB`, `addressSpaceMb: 384`) returns `[0] * 6_000_000` — ~12 MB of JSON, under the 20 MiB budget, so it must round-trip; pre-fix the O(width) walk allocated ~400 MB of per-element traversal tuples and encoder stack entries (~28× the serialized size, past the 12× the gate reserves) and OOM'd on a value the meter admitted, while the O(depth) cursor keeps the only width-proportional allocation the output string itself (Linux-only RLIMIT_AS repro; the fixture stays within the address space so it is honest on macOS too). A wide-BINDING-ARGUMENT case (`addressSpaceMb: 384`) calls a binding with `[0] * 6_000_000` and asserts the length echoes back: `_lossless_json_violation` runs on model-built arguments that no child-side budget bounds first, and its per-member tuples measured 459.1 MiB against 0.0 MiB for the cursor. A backtracking case returns a 4 MiB string from a binding and asserts it round-trips: the old scalar regex retained engine state proportional to the string's width (146 MiB at 1 MiB, 557.8 MiB at 4 MiB, past the default 512 MiB), which raised MemoryError inside `_pump_replies` and stranded the call to the wall clock. A control-heavy metering case returns 8M NULs under a 16 MiB `maxValueBytes` and asserts `output-limit`, not `exception`: charging by counting instead of materializing the escaped form measured 19.1 MiB against 228.9 MiB for an identical byte count. An addressSpaceMb-baseline case asserts 64 MiB and 32 MiB reject at load with a message naming `addressSpaceMb`, rather than the budget loop's negative admissible limit. A process-identity case asserts the leader's start time reads stably on Linux and reports undefined on Darwin, the guard that keeps a recycled pgid from receiving this run's SIGTERM. A paced-replies case resolves eight 4 MiB values in one `asyncio.gather` round and asserts the frames round-trip; the peak the fix removes (32.0 MiB buffered → 0.0 MiB) lives in the host's fd-3 writable buffer, invisible through the seam, so this case pins the round-trip and the no-regression match but is measured for its peak only out-of-tree. A late-drop case settles the run on `maxWallMs` and resolves the pending binding afterwards, asserting a `timeout` result, an undefined value, and that the late path actually ran — the three assertions all hold pre-fix because `sendReply` already dropped after-settlement values, just later, so the case pins the ordering, not a seam-observable behavior. A binding-all-names case (`rebinds every name the failure path uses`) asserts a real `ValueError` survives send-done binding — a tested fix pinned by a case that rebinds `__main__.ProtocolChannel.send_sync`, `__main__.ProtocolChannel.write_encoded`, and `__main__._encode_json_plain` — the three names the shipped `send_done` would resolve late if it looked them up at call time — and pins the `done` frame against that call-time look-up skipping it; the done-value TOCTOU pre-encoding, the stray-UTF-8 budget-flush retention, and the late-rejection settled guard are counted among the ten no-fail-before fixes (reasons in the Problem section), not pinned by a fail-before test. A fragment-cap drip case writes 200 000 single-character newline-free `sys.stdout.write` calls and asserts the run completes with a truncation marker rather than a MemoryError (no-fail-before: the 25 M-scale OOM is not deterministically constructible in CI). A dual-limit CPU case runs the interpreter through a `ulimit -t 2` wrapper and busy-loops past it, asserting a `timeout` (the soft limit is lowered to 1 so SIGXCPU fires, not a `worker-exit`). A transitive-name rebind case rebinds `__main__._dump_scalar`, `__main__.os`, `__main__._os_write`, `__main__._memoryview`, and `__main__._FALLBACK_DONE_FRAME` and asserts a done frame still lands as an `exception`, not a `worker-exit` (the real message is replaced by the fixed fallback literal). A BaseException-rebind case rebinds `__main__.BaseException` to `RuntimeError` and raises `ValueError`, asserting the run still reports an `exception`, not a `worker-exit` (the catch uses a pre-program local exception class). A RuntimeError-rebind closed-loop case rebinds `__main__.RuntimeError` to `ValueError` as the first program statement and drives the closed-loop worker pattern, asserting the pump survives the dead-loop reply and delivers the later binding (the pump's `_RuntimeError` is a def-time default argument, so it captures the original before the rebind).
## Alternatives considered
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 8e536b438a..ae2cb4949a 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
@@ -74,7 +74,7 @@ Status: implemented
### 完成值与错误在其校验点处预编码
-在 [`py/bootstrap.py`](../../../../packages/code-runtime/code-runtime-python/py/bootstrap.py) 中,`_done_with_value` 现在会在成功路径上把整个终止帧作为一个已预编码的 JSON 字符串返回:被准入的值在这里、在 `_run` 的 `try` 之内的校验点处恰好序列化一次,即 `'{"type": "done", "value": ' + _encode_json_plain(value) + "}"`。程序在返回之后仍可能从 daemon 线程或信号处理器继续变异它返回的 list/dict,因此在一个更晚的点上做第二次遍历会构成一次 TOCTOU——如果一次变异让一个并发变异的后续编码在结算处理器之外抛出,就会把一次已结算的运行在宿主侧降级成 `worker-exit`。在这里、在包裹该调用的 `try` 之内恰好序列化一次,就关上了这个窗口:如果一次并发变异导致编码抛出,异常处理器会把它如实分类为 `exception`;一旦字符串产生出来,该帧就会被逐字写走、不再触碰任何活对象。
+在 [`py/bootstrap.py`](../../../../packages/code-runtime/code-runtime-python/py/bootstrap.py) 中,`_done_with_value` 现在会在成功路径上把整个终止帧作为一个已预编码的 JSON 字符串返回:被准入的值在这里、在 `_run` 的 `try` 之内的校验点处恰好序列化一次,即 `'{"type": "done", "value": ' + _encode_json_plain(value) + "}"`。程序在返回之后仍可能从 daemon 线程或信号处理器继续变异它返回的 list/dict,因此在一个更晚的点上做第二次遍历会构成一次 TOCTOU——如果一次变异让一个并发变异的后续编码在结算处理器之外抛出,就会把一次已结算的运行在宿主侧降级成 `worker-exit`。在这里、在包裹该调用的 `try` 之内恰好序列化一次,就关上了这个窗口:如果一次并发变异导致编码抛出,异常处理器会把它如实分类为 `exception`;一旦字符串产生出来,该帧就会被逐字写走、不再触碰任何活对象。`_done_with_value` 还把 `_check_done_value` 与 `_encode_json_plain` 绑定为 def 期默认参数,因此模型执行后的 `__main__` 重绑无法把一个合法成功改写为 `exception`。
`send_done`(`_run` 内部的一个局部函数)通过绑定的 `channel.write_encoded` 写出已预编码的字符串,并在写之前用绑定的 `_encode_json_plain` 编码一个 dict 错误帧——它绝不经 `channel.send_sync`,因为后者的函数体会在调用时刻重新解析 `self.write_encoded` 和模块级的 `_encode_json_plain`。`_encode_json_plain` 与 `channel.write_encoded` 在程序运行前就被绑定进局部变量,理由与 `flush_out`/`flush_err`/`safe_model_traceback` 被绑定相同:程序以 `__main__` 运行,因此 `import __main__; __main__.ProtocolChannel.send_sync = boom` 或 `__main__._encode_json_plain = boom` 本会在调用时刻把发送/编码重新解析成被替换的可调用对象,当该替换抛出时跳过 `done` 帧、把已结算的结论降级成宿主侧的 `worker-exit`。
@@ -92,13 +92,13 @@ Status: implemented
`_clamped` 还会把钳制出的、与硬限制相等的 RLIMIT_CPU 软限制降低一个单位(当硬限制至少为 2 时)。`ulimit -t N` 会同时设置两者,而当 soft == hard 时,内核会在同一 tick 检查硬限制并直接 SIGKILL 一个忙循环,因此 SIGXCPU 永远不会送达——而宿主只在 `signal === 'SIGXCPU'` 时把 CPU 超限分类为超时,所以一次确定的预算耗尽会被误报为 `worker-exit`。把软限制降低一个单位给 SIGXCPU 一个触发窗口,因此超限会被报告为超时。这仅限定于 RLIMIT_CPU(在 RLIMIT_AS 上的一字节软差异只会让子进程实际应用的限制与宿主预算门失步,没有需要保留的信号)。`hard >= 2` 守卫留下了 `hard == 1` 盲区——一个 1 秒的双限制无法把软限制降到 0,因此那里的确定超限仍被报告为 `worker-exit`。
-`send_done` 将其 encode+write 包进 try,任何来自被重绑的传递名(`_dump_scalar`/`os`)的抛出都会写入一条固定的预编码 done 帧,经由 `_run` 局部绑定的 `_os_write`/`_memoryview`/`_FALLBACK_DONE_FRAME`——因此一个已结算的 `exception` 判决绝不会被降级为 `worker-exit`,宿主仍会拿到一个判决。回复队列的头游标排空会清除每个已消费槽位,因此一个已写出的宽 payload 会被立即释放,把宿主内存限制在持续的 fd-3 背压下的当前积压量。 结算路径各 `except` 子句所捕获的异常类同样在任何模型代码运行之前绑定:`_BaseException` 是 `_run` 的局部与 `_make_failure_reporter` 的闭包单元;`_RuntimeError`、`_BindingRejection`、`str` 与 `bool` 是 `_pump_replies` 的 def 期默认参数(函数体内的 `X = X` 绑定太晚——模型顶层语句会先于泵体首步执行)。重绑 `__main__.BaseException` 或 `__main__.RuntimeError` 无法让程序异常逃出处理器、丢失 `done` 帧。
+`send_done` 将其 encode+write 包进 try,任何来自被重绑的传递名(`_dump_scalar`/`os`)的抛出都会写入一条固定的预编码 done 帧,经由 `_run` 局部绑定的 `_os_write`/`_memoryview`/`_FALLBACK_DONE_FRAME`——因此一个已结算的 `exception` 判决绝不会被降级为 `worker-exit`,宿主仍会拿到一个判决。回复队列的头游标排空会清除每个已消费槽位,因此一个已写出的宽 payload 会被立即释放,把宿主内存限制在持续的 fd-3 背压下的当前积压量。结算路径各 `except` 子句所捕获的异常类同样在任何模型代码运行之前绑定:`_BaseException` 是 `_run` 的局部与 `_make_failure_reporter` 的闭包单元;`_RuntimeError`、`_BindingRejection`、`str` 与 `bool` 是 `_pump_replies` 的 def 期默认参数(函数体内的 `X = X` 绑定太晚——模型顶层语句会先于泵体首步执行)。重绑 `__main__.BaseException` 无法让程序异常逃出处理器、丢失 `done` 帧;重绑 `__main__.RuntimeError`(或 `_BindingRejection`/`str`/`bool`)无法让闭环调度失败逃出泵的捕获、把每条后续回复搁浅到墙钟超时。
## Testing
- `tests/boot-write-failure.spec.ts` 对 `spawn` 做 mock,使 fd-3 管道在引导写入时抛出异常(这是真实子进程无法被迫进入的唯一路径),并断言 `run()` resolve 出一个 `worker-exit` 而非 reject。一个同级用例让被 mock 的 `spawn` 同步抛出,并断言 `run()` 仍然 resolve 出一个 `worker-exit`,且会移除它的暂存目录——以被 mock 的 `spawn` 在其 argv 中收到的确切引导路径为准,因此一个同级 worker 的并发暂存不会让它变得不稳定。两者都被隔离在这个 spec 中,因此真实子进程测试套件不受影响。
- `tests/residual-detach.spec.ts` 对 `detachResidual` 做单元测试:向前传递的副本与残余数据相等、拥有一个大小与其自身长度一致的底层存储(fixture 保持在 Node 的 Buffer 池阈值之上),并且不与源帧的 `ArrayBuffer` 共享。
-- `tests/runtime.spec.ts`:output-cap 用例断言 `ceiling - envelope` 上界(268435392)及其消息。一个 daemon 线程用例驱动四个线程穿过结算的 flush 发出未结束的写入。一个 native-write 用例在抬高后的 `maxLogBytes` 之下,通过 `os.write` 写入 200 KiB 且不含换行符,断言它回读时恰好是一条日志条目(证明散逸输出是按行聚合的,而不是在管道分片边界处被切开);一个配套用例写入 `b"one\ntwo\nthree"`,断言得到三条条目(证明真正的换行符仍然起分隔作用)。一个 newline-free-flood 用例在一个 4 KiB 的 `maxLogBytes` 之下写入 2 MiB,断言捕获终止于截断标记且保持在预算之内(证明残余数据受账本约束,而不是被整体缓冲);一个 NUL-flood 配套用例在同一预算之下写入 4000 个不含换行符的 NUL,断言发生截断(证明残余数据是按序列化开销计费的,约为原始的 6 倍,且在度量时不分配转义后的副本);一个 illegal-UTF-8 用例在一个 3072 字节的预算之下控速发出单字节 `\xff` 写入,并对 `Buffer.concat` 做包装以度量峰值合并缓冲区,断言它保持在 2048 之下(按 U+FFFD 宽度 3 计费时残余数据在约 1024 原始字节处冲刷;一次原始字节的少计会让它达到约 3072,因此该界限具有区分力);一个 CESU-8/overlong 用例把结构良构但非法的 `ED A0 80` 一次一个字节地控速发出,断言同样的峰值界限(按每序列真实的 9 计费时它提前冲刷;按结构宽度 3 计费会使峰值增至三倍,因此把逐前导字节范围检查回退会使它变红);一个 broken-multibyte 用例在分开的分片里先写入一个 3 字节的前导字节、再写入一个新的 ASCII 字节,断言同时捕获到一个 `A` 和一个 U+FFFD(覆盖 `accrueStrayCost` 的跨分片断裂序列分支);一个 post-truncation 用例写入一个 108 字节的载荷(小于最小的 PIPE_BUF,因此是一次原子写入),其首行耗尽一个 64 字节的预算,断言第二行被丢弃(覆盖单次 `data` 回调中的截断后准入空操作,无需 v8-ignore);一个 short-escape 用例写入一行混合了制表符、引号、反斜杠、一个 `\uXXXX` 控制字符、一个多字节字符和 ASCII 的内容,断言它原样完成往返(覆盖 `jsonStringCostUpTo` 的每一条分支);一个 reassembly 用例写入一个跨越每个合法多字节前导字节类别(E0 范围、普通 3 字节、F0 和 F4)、越过管道缓冲区的载荷,断言它原样完成往返且不含 U+FFFD(覆盖 `accrueStrayCost` 的逐前导字节范围与跨分片重组);一个 lone-surrogate 用例在一个 4 KiB 预算之下伪造一个以 1000 个 `\ud800` 转义洪泛的 fd-3 `log` 帧,断言发生截断(该计数正落在计 3 字节会放行、计 6 字节则截断的窗口内,证明该代理项是按其完整转义宽度计费的);一个 stray-sealing 用例在抬高后的预算之下控速发出 60000 次单字节、不含换行符的 `os.write(1, …)` 调用,并对 `Buffer.concat` 做包装以度量复制量,断言这股细流合并为一条条目、且累积复制量保持在一个实测的 256 KiB 阈值之下(封存后的形态复制约 120 KB,重新合并的形态复制约 538 KB,因此把封存回退成重新合并会使该断言变红——证明分片列表在越过 `MAX_PENDING_CHUNKS` 后封存为块)。一个 closeDeadline-flush 用例让 leader 写入一段不含换行符的诊断,随后 spawn 一个持有管道不放的 `setsid` 孤儿进程,断言该诊断在 `logs` 中存留下来(证明残余数据在截止时间销毁流之前被冲刷)。same-group 回收用例 spawn 一个忽略 SIGTERM 的同进程组后代,它释放管道并递增一个心跳文件;该测试断言在宽限窗口的 SIGKILL 之后心跳停止:无论被杀死的后代是被回收还是作为僵尸进程滞留,这个断言都成立,因此它在 PID 1 不 wait() 孤儿进程的环境下同样成立。一个 dispose-after-resolve 用例断言,对一个已完成、且存在同进程组存活者的运行调用 `dispose()`,只有在该存活者停止执行之后才返回(证明该运行会一直留在 `live` 中,直到它的进程组被回收),并带有一个 `expect(afterDispose).toBeGreaterThan(0)` 守卫,使得当心跳文件从未被写入时,冻结心跳的断言不会被空洞地通过。一个 deadline 用例忙阻塞事件循环越过两个定时器,断言该存活者的心跳冻结(证明轮询的截止时间分支自身发送 SIGKILL,而不是取消尚未触发的升级)。cross-loop 用例在主协程通过 `await asyncio.sleep` 让出时,从一个工作线程自己的 `asyncio.run` 事件循环运行一个绑定,断言该回复完成往返而不是超时;一个配套用例放弃某个线程的调用,使其事件循环关闭,随后在一个后续绑定之前回答它——断言 pump 在关闭事件循环上的 `call_soon_threadsafe` 之后仍然存活(由宿主门控的顺序使其具有确定性,未修复时会把后续绑定拖到墙钟上挂起)。inherited-soft-limit 用例通过一个 `ulimit -S -t` 包装脚本运行解释器,将 CPU 软限制设为低于 `cpuSeconds`,并断言实际应用的 `RLIMIT_CPU` 软限制是继承来的值,而不是配置的值(用 CPU 而非地址空间,因为 macOS 忽略 `ulimit -v`)。一个配套用例继承 1 秒的 CPU 软限制,让程序捕获 SIGXCPU 并忙循环越过它,断言结算复查报告 timeout——证明复查用的是实际生效的软限制,而不是配置的 `cpuSeconds`。一个 control-heavy-diagnostic 用例在一个较小的 `maxValueBytes` 之下抛出一个 NUL 洪泛异常,断言序列化后的帧能放得下(证明该诊断是按序列化开销计量的)。一个 tail-copy 用例(`maxLogBytes: 256`、`addressSpaceMb: 384`)让程序在一个变量里构建一个尾部并写入 `"\n" + tail`,其中 `tail` 为 150 MiB——构建峰值约 2 倍(约 300 MiB,落在地址空间之内,因此模型自身的分配会成功,任何 OOM 都属于缺陷路径),而修复前的整尾重新缓冲会加上第三份约 150 MiB 的副本、越过 384 MiB;切片后的前缀让该次运行得以截断并完成(仅 Linux 的 RLIMIT_AS 复现,macOS 走顺利路径——fixture 自身的构建必须放进地址空间,这是这些 RLIMIT_AS 用例的一条通用规则)。一个 output-budget/address-space 用例断言一个 50 MB 的 `maxLogBytes` 和一个 50 MB 的 `maxValueBytes` 各自对照一个 256 MiB 的 `addressSpaceMb` 在加载期被拒绝(乘以最坏情况的 12 之后超过解释器基线之后剩下的余量),而默认的各项上限对照 512 MiB 则加载成功,对两项预算对称地门控;一个具区分力的用例断言一个 48 MiB 的 `maxLogBytes` 对照一个 512 MiB 的 `addressSpaceMb` 被拒绝——48×8 = 384 MiB 放得进 448 MiB 的可预算余量(旧的 8× 倍数会错误放行),但 48×12 = 576 MiB 放不进。该倍数覆盖的约 12× 峰值来自换行路径上一次接近预算的写入——调用方自己的字符串、行切片与 encode 副本同时存活;结算期 flush 已不再是承重者,因为 `flush_line` 在 push 之前就丢弃了 pending 分块,因此只持有两份副本而非三份。一个 inherited-RLIMIT_AS 用例通过一个 `ulimit -v 131072` 包装层运行解释器,配以一个配置的 512 MiB `addressSpaceMb` 所允许的 32 MiB `maxLogBytes`,断言引导期的重新检查把它作为 `exception` 拒绝、且其消息点名了继承的 RLIMIT_AS(128 MiB 的继承限制在基线之后剩下的太少;仅 Linux,macOS 忽略 `ulimit -v`,该次运行会继续)。一个 non-integer-budget 用例断言一个小数的 `maxLogBytes`/`maxValueBytes` 在加载期被拒绝。一个 combined-peak 用例(`maxLogBytes: 32 MiB`、`maxValueBytes: 32 MiB`、`addressSpaceMb: 512`——每项预算单独都被 12× 门放行)写入约 33M 个不含换行符的星芒面字符(缓冲、未冲刷)后返回约 33M 个星芒面字符,断言该次运行以 `output-limit` 结算(该值本身就超过它 32 MiB 的预算);修复前未冲刷的日志 pending 加上值的构建加编码峰值会一起越过 512 MiB 地址空间而 OOM,因此在分帧值之前先冲刷日志正是让值检查得以完成的原因(仅 Linux 的 RLIMIT_AS 复现;在 macOS 上超预算的值在两种顺序下都报 output-limit)。一个宽完成值用例(`maxValueBytes: 20 MiB`、`addressSpaceMb: 384`)返回 `[0] * 6_000_000`——JSON 约 12 MB、低于预算,因此必须成功往返;修复前 O(width) 的遍历为每个元素分配遍历元组与编码器栈项(约为序列化尺寸的 28×,超出门保留的 12×),在一个计量器已放行的值上 OOM,而 O(depth) 游标使唯一按宽度分配的只剩输出字符串本身。一个宽 binding 实参用例(`addressSpaceMb: 384`)以 `[0] * 6_000_000` 调用 binding 并断言长度回传:`_lossless_json_violation` 运行在模型构造的实参上,子进程侧没有任何字节预算先行约束,其逐元素元组实测 459.1 MiB,而游标为 0.0 MiB。一个回溯用例从 binding 返回一个 4 MiB 字符串并断言其成功往返:旧的标量正则保留的引擎状态与字符串宽度成正比(1 MiB 时 146 MiB,4 MiB 时 557.8 MiB,超过默认的 512 MiB),会在 `_pump_replies` 内抛出 MemoryError 并把该次调用搁置到墙钟。一个 control-heavy 计费用例在 16 MiB 的 `maxValueBytes` 之下返回 8M 个 NUL,断言得到 `output-limit` 而非 `exception`:以计数替代物化转义形式来计费,在字节数完全相同的前提下实测 19.1 MiB 对 228.9 MiB。一个 addressSpaceMb 下界用例断言 64 MiB 与 32 MiB 在加载期被拒绝,且消息点名 `addressSpaceMb`,而不是预算循环给出的负数上限。一个进程身份用例断言 leader 的启动时刻在 Linux 上可稳定读取、在 Darwin 上报告 undefined,这正是使被复用的 pgid 不会收到本次运行 SIGTERM 的那道守卫。一个 paced-replies 用例在一轮 `asyncio.gather` 中 resolve 八条 4 MiB 的值,断言这些帧能够往返;本修复移除的峰值(32.0 MiB 缓冲 → 0.0 MiB)位于宿主 fd-3 可写缓冲内部、透过 seam 不可见,因此该用例钉住的是往返与无回归匹配,其峰值只能在树外度量。一个 late-drop 用例让该次运行在 `maxWallMs` 上结算,随后才 resolve 那个 pending 的 binding,断言得到一个 `timeout` 结果、一个 undefined 值、且迟到路径确实被执行过——这三条断言在修复前也全部成立,因为 `sendReply` 本就丢弃结算之后的值、只是更晚,因此该用例钉住的是顺序,而非一个透过 seam 可观测的行为。一个"绑定全部用名"用例(`rebinds every name the failure path uses`)断言一个真实的 `ValueError` 在 send-done 绑定之后仍然存活——这是一个有测修复,由一个逐名重绑 `__main__.ProtocolChannel.send_sync`、`__main__.ProtocolChannel.write_encoded` 与 `__main__._encode_json_plain` 的用例钉住——这三个名字正是 shipped 的 `send_done` 若做调用时刻查找时会迟解析的那三个——并钉住 `done` 帧不被一次调用时刻的查找跳过;而完成值的 TOCTOU 预编码、stray UTF-8 预算冲刷的扣留与结算后到达的迟到拒绝的 settled 先查,都被计入那十处无 fail-before 修复(理由见 Problem 段),不由 fail-before 测试钉住。 一个 fragment-cap 滴灌用例写入 200 000 次单字符无换行的 `sys.stdout.write` 调用,断言该次运行以截断标记完成而非 MemoryError(no-fail-before:25 M 规模的 OOM 无法在 CI 中确定性构造)。一个 dual-limit CPU 用例通过一个 `ulimit -t 2` 包装脚本运行解释器并忙循环越过它,断言得到 `timeout`(软限制被降到 1,因此 SIGXCPU 触发,而非 `worker-exit`)。一个传递名重绑用例重绑 `__main__._dump_scalar`、`__main__.os`、`__main__._os_write`、`__main__._memoryview` 与 `__main__._FALLBACK_DONE_FRAME`,断言仍有一帧 done 以 `exception` 落地,而非 `worker-exit`(真实消息被固定兜底字面量替换)。 一个 BaseException 重绑用例把 `__main__.BaseException` 重绑为 `RuntimeError` 并抛出 `ValueError`,断言该次运行仍报告 `exception`,而非 `worker-exit`(catch 用的是程序运行前的局部异常类)。
+- `tests/runtime.spec.ts`:output-cap 用例断言 `ceiling - envelope` 上界(268435392)及其消息。一个 daemon 线程用例驱动四个线程穿过结算的 flush 发出未结束的写入。一个 native-write 用例在抬高后的 `maxLogBytes` 之下,通过 `os.write` 写入 200 KiB 且不含换行符,断言它回读时恰好是一条日志条目(证明散逸输出是按行聚合的,而不是在管道分片边界处被切开);一个配套用例写入 `b"one\ntwo\nthree"`,断言得到三条条目(证明真正的换行符仍然起分隔作用)。一个 newline-free-flood 用例在一个 4 KiB 的 `maxLogBytes` 之下写入 2 MiB,断言捕获终止于截断标记且保持在预算之内(证明残余数据受账本约束,而不是被整体缓冲);一个 NUL-flood 配套用例在同一预算之下写入 4000 个不含换行符的 NUL,断言发生截断(证明残余数据是按序列化开销计费的,约为原始的 6 倍,且在度量时不分配转义后的副本);一个 illegal-UTF-8 用例在一个 3072 字节的预算之下控速发出单字节 `\xff` 写入,并对 `Buffer.concat` 做包装以度量峰值合并缓冲区,断言它保持在 2048 之下(按 U+FFFD 宽度 3 计费时残余数据在约 1024 原始字节处冲刷;一次原始字节的少计会让它达到约 3072,因此该界限具有区分力);一个 CESU-8/overlong 用例把结构良构但非法的 `ED A0 80` 一次一个字节地控速发出,断言同样的峰值界限(按每序列真实的 9 计费时它提前冲刷;按结构宽度 3 计费会使峰值增至三倍,因此把逐前导字节范围检查回退会使它变红);一个 broken-multibyte 用例在分开的分片里先写入一个 3 字节的前导字节、再写入一个新的 ASCII 字节,断言同时捕获到一个 `A` 和一个 U+FFFD(覆盖 `accrueStrayCost` 的跨分片断裂序列分支);一个 post-truncation 用例写入一个 108 字节的载荷(小于最小的 PIPE_BUF,因此是一次原子写入),其首行耗尽一个 64 字节的预算,断言第二行被丢弃(覆盖单次 `data` 回调中的截断后准入空操作,无需 v8-ignore);一个 short-escape 用例写入一行混合了制表符、引号、反斜杠、一个 `\uXXXX` 控制字符、一个多字节字符和 ASCII 的内容,断言它原样完成往返(覆盖 `jsonStringCostUpTo` 的每一条分支);一个 reassembly 用例写入一个跨越每个合法多字节前导字节类别(E0 范围、普通 3 字节、F0 和 F4)、越过管道缓冲区的载荷,断言它原样完成往返且不含 U+FFFD(覆盖 `accrueStrayCost` 的逐前导字节范围与跨分片重组);一个 lone-surrogate 用例在一个 4 KiB 预算之下伪造一个以 1000 个 `\ud800` 转义洪泛的 fd-3 `log` 帧,断言发生截断(该计数正落在计 3 字节会放行、计 6 字节则截断的窗口内,证明该代理项是按其完整转义宽度计费的);一个 stray-sealing 用例在抬高后的预算之下控速发出 60000 次单字节、不含换行符的 `os.write(1, …)` 调用,并对 `Buffer.concat` 做包装以度量复制量,断言这股细流合并为一条条目、且累积复制量保持在一个实测的 256 KiB 阈值之下(封存后的形态复制约 120 KB,重新合并的形态复制约 538 KB,因此把封存回退成重新合并会使该断言变红——证明分片列表在越过 `MAX_PENDING_CHUNKS` 后封存为块)。一个 closeDeadline-flush 用例让 leader 写入一段不含换行符的诊断,随后 spawn 一个持有管道不放的 `setsid` 孤儿进程,断言该诊断在 `logs` 中存留下来(证明残余数据在截止时间销毁流之前被冲刷)。same-group 回收用例 spawn 一个忽略 SIGTERM 的同进程组后代,它释放管道并递增一个心跳文件;该测试断言在宽限窗口的 SIGKILL 之后心跳停止:无论被杀死的后代是被回收还是作为僵尸进程滞留,这个断言都成立,因此它在 PID 1 不 wait() 孤儿进程的环境下同样成立。一个 dispose-after-resolve 用例断言,对一个已完成、且存在同进程组存活者的运行调用 `dispose()`,只有在该存活者停止执行之后才返回(证明该运行会一直留在 `live` 中,直到它的进程组被回收),并带有一个 `expect(afterDispose).toBeGreaterThan(0)` 守卫,使得当心跳文件从未被写入时,冻结心跳的断言不会被空洞地通过。一个 deadline 用例忙阻塞事件循环越过两个定时器,断言该存活者的心跳冻结(证明轮询的截止时间分支自身发送 SIGKILL,而不是取消尚未触发的升级)。cross-loop 用例在主协程通过 `await asyncio.sleep` 让出时,从一个工作线程自己的 `asyncio.run` 事件循环运行一个绑定,断言该回复完成往返而不是超时;一个配套用例放弃某个线程的调用,使其事件循环关闭,随后在一个后续绑定之前回答它——断言 pump 在关闭事件循环上的 `call_soon_threadsafe` 之后仍然存活(由宿主门控的顺序使其具有确定性,未修复时会把后续绑定拖到墙钟上挂起)。inherited-soft-limit 用例通过一个 `ulimit -S -t` 包装脚本运行解释器,将 CPU 软限制设为低于 `cpuSeconds`,并断言实际应用的 `RLIMIT_CPU` 软限制是继承来的值,而不是配置的值(用 CPU 而非地址空间,因为 macOS 忽略 `ulimit -v`)。一个配套用例继承 1 秒的 CPU 软限制,让程序捕获 SIGXCPU 并忙循环越过它,断言结算复查报告 timeout——证明复查用的是实际生效的软限制,而不是配置的 `cpuSeconds`。一个 control-heavy-diagnostic 用例在一个较小的 `maxValueBytes` 之下抛出一个 NUL 洪泛异常,断言序列化后的帧能放得下(证明该诊断是按序列化开销计量的)。一个 tail-copy 用例(`maxLogBytes: 256`、`addressSpaceMb: 384`)让程序在一个变量里构建一个尾部并写入 `"\n" + tail`,其中 `tail` 为 150 MiB——构建峰值约 2 倍(约 300 MiB,落在地址空间之内,因此模型自身的分配会成功,任何 OOM 都属于缺陷路径),而修复前的整尾重新缓冲会加上第三份约 150 MiB 的副本、越过 384 MiB;切片后的前缀让该次运行得以截断并完成(仅 Linux 的 RLIMIT_AS 复现,macOS 走顺利路径——fixture 自身的构建必须放进地址空间,这是这些 RLIMIT_AS 用例的一条通用规则)。一个 output-budget/address-space 用例断言一个 50 MB 的 `maxLogBytes` 和一个 50 MB 的 `maxValueBytes` 各自对照一个 256 MiB 的 `addressSpaceMb` 在加载期被拒绝(乘以最坏情况的 12 之后超过解释器基线之后剩下的余量),而默认的各项上限对照 512 MiB 则加载成功,对两项预算对称地门控;一个具区分力的用例断言一个 48 MiB 的 `maxLogBytes` 对照一个 512 MiB 的 `addressSpaceMb` 被拒绝——48×8 = 384 MiB 放得进 448 MiB 的可预算余量(旧的 8× 倍数会错误放行),但 48×12 = 576 MiB 放不进。该倍数覆盖的约 12× 峰值来自换行路径上一次接近预算的写入——调用方自己的字符串、行切片与 encode 副本同时存活;结算期 flush 已不再是承重者,因为 `flush_line` 在 push 之前就丢弃了 pending 分块,因此只持有两份副本而非三份。一个 inherited-RLIMIT_AS 用例通过一个 `ulimit -v 131072` 包装层运行解释器,配以一个配置的 512 MiB `addressSpaceMb` 所允许的 32 MiB `maxLogBytes`,断言引导期的重新检查把它作为 `exception` 拒绝、且其消息点名了继承的 RLIMIT_AS(128 MiB 的继承限制在基线之后剩下的太少;仅 Linux,macOS 忽略 `ulimit -v`,该次运行会继续)。一个 non-integer-budget 用例断言一个小数的 `maxLogBytes`/`maxValueBytes` 在加载期被拒绝。一个 combined-peak 用例(`maxLogBytes: 32 MiB`、`maxValueBytes: 32 MiB`、`addressSpaceMb: 512`——每项预算单独都被 12× 门放行)写入约 33M 个不含换行符的星芒面字符(缓冲、未冲刷)后返回约 33M 个星芒面字符,断言该次运行以 `output-limit` 结算(该值本身就超过它 32 MiB 的预算);修复前未冲刷的日志 pending 加上值的构建加编码峰值会一起越过 512 MiB 地址空间而 OOM,因此在分帧值之前先冲刷日志正是让值检查得以完成的原因(仅 Linux 的 RLIMIT_AS 复现;在 macOS 上超预算的值在两种顺序下都报 output-limit)。一个宽完成值用例(`maxValueBytes: 20 MiB`、`addressSpaceMb: 384`)返回 `[0] * 6_000_000`——JSON 约 12 MB、低于预算,因此必须成功往返;修复前 O(width) 的遍历为每个元素分配遍历元组与编码器栈项(约为序列化尺寸的 28×,超出门保留的 12×),在一个计量器已放行的值上 OOM,而 O(depth) 游标使唯一按宽度分配的只剩输出字符串本身。一个宽 binding 实参用例(`addressSpaceMb: 384`)以 `[0] * 6_000_000` 调用 binding 并断言长度回传:`_lossless_json_violation` 运行在模型构造的实参上,子进程侧没有任何字节预算先行约束,其逐元素元组实测 459.1 MiB,而游标为 0.0 MiB。一个回溯用例从 binding 返回一个 4 MiB 字符串并断言其成功往返:旧的标量正则保留的引擎状态与字符串宽度成正比(1 MiB 时 146 MiB,4 MiB 时 557.8 MiB,超过默认的 512 MiB),会在 `_pump_replies` 内抛出 MemoryError 并把该次调用搁置到墙钟。一个 control-heavy 计费用例在 16 MiB 的 `maxValueBytes` 之下返回 8M 个 NUL,断言得到 `output-limit` 而非 `exception`:以计数替代物化转义形式来计费,在字节数完全相同的前提下实测 19.1 MiB 对 228.9 MiB。一个 addressSpaceMb 下界用例断言 64 MiB 与 32 MiB 在加载期被拒绝,且消息点名 `addressSpaceMb`,而不是预算循环给出的负数上限。一个进程身份用例断言 leader 的启动时刻在 Linux 上可稳定读取、在 Darwin 上报告 undefined,这正是使被复用的 pgid 不会收到本次运行 SIGTERM 的那道守卫。一个 paced-replies 用例在一轮 `asyncio.gather` 中 resolve 八条 4 MiB 的值,断言这些帧能够往返;本修复移除的峰值(32.0 MiB 缓冲 → 0.0 MiB)位于宿主 fd-3 可写缓冲内部、透过 seam 不可见,因此该用例钉住的是往返与无回归匹配,其峰值只能在树外度量。一个 late-drop 用例让该次运行在 `maxWallMs` 上结算,随后才 resolve 那个 pending 的 binding,断言得到一个 `timeout` 结果、一个 undefined 值、且迟到路径确实被执行过——这三条断言在修复前也全部成立,因为 `sendReply` 本就丢弃结算之后的值、只是更晚,因此该用例钉住的是顺序,而非一个透过 seam 可观测的行为。一个"绑定全部用名"用例(`rebinds every name the failure path uses`)断言一个真实的 `ValueError` 在 send-done 绑定之后仍然存活——这是一个有测修复,由一个逐名重绑 `__main__.ProtocolChannel.send_sync`、`__main__.ProtocolChannel.write_encoded` 与 `__main__._encode_json_plain` 的用例钉住——这三个名字正是 shipped 的 `send_done` 若做调用时刻查找时会迟解析的那三个——并钉住 `done` 帧不被一次调用时刻的查找跳过;而完成值的 TOCTOU 预编码、stray UTF-8 预算冲刷的扣留与结算后到达的迟到拒绝的 settled 先查,都被计入那十处无 fail-before 修复(理由见 Problem 段),不由 fail-before 测试钉住。 一个 fragment-cap 滴灌用例写入 200 000 次单字符无换行的 `sys.stdout.write` 调用,断言该次运行以截断标记完成而非 MemoryError(no-fail-before:25 M 规模的 OOM 无法在 CI 中确定性构造)。一个 dual-limit CPU 用例通过一个 `ulimit -t 2` 包装脚本运行解释器并忙循环越过它,断言得到 `timeout`(软限制被降到 1,因此 SIGXCPU 触发,而非 `worker-exit`)。一个传递名重绑用例重绑 `__main__._dump_scalar`、`__main__.os`、`__main__._os_write`、`__main__._memoryview` 与 `__main__._FALLBACK_DONE_FRAME`,断言仍有一帧 done 以 `exception` 落地,而非 `worker-exit`(真实消息被固定兜底字面量替换)。 一个 BaseException 重绑用例把 `__main__.BaseException` 重绑为 `RuntimeError` 并抛出 `ValueError`,断言该次运行仍报告 `exception`,而非 `worker-exit`(catch 用的是程序运行前的局部异常类)。一个 RuntimeError 重绑闭环用例把 `__main__.RuntimeError` 重绑为 `ValueError` 作为程序首条语句,并驱动闭环 worker 模式,断言泵存活于死循环回复、投递后续 binding(泵的 `_RuntimeError` 是 def 期默认参数,因此在重绑前捕获原始值)。
## Alternatives considered
diff --git a/packages/code-runtime/code-runtime-python/README.i18n.yaml b/packages/code-runtime/code-runtime-python/README.i18n.yaml
index efcbb3ac0c..4f7837caa6 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: 2841bc6ccec9ac7e59bbe302dac83e6e9c3d89b5
-README.zh.md: 953ab46ca5196589a21311573c1a344f95a95ad1
+README.md: b29121c892a820258f905f68f2ab8fbe78003198
+README.zh.md: 8e772c2dd5a09cc64769a4f1175f7c7bc334e3c5
diff --git a/packages/code-runtime/code-runtime-python/README.md b/packages/code-runtime/code-runtime-python/README.md
index 2841bc6cce..b29121c892 100644
--- a/packages/code-runtime/code-runtime-python/README.md
+++ b/packages/code-runtime/code-runtime-python/README.md
@@ -39,4 +39,4 @@ 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 1-second dual-limit `ulimit -t 1` CPU overrun is reported as `worker-exit`, not a timeout.** When the host starts under a hard CPU limit equal to the soft (`ulimit -t N` sets both) and that limit is 1, `_clamped` cannot lower the soft to 0, so the kernel SIGKILLs the busy loop in the same tick and SIGXCPU is never delivered. The host classifies a CPU overrun only on `signal === 'SIGXCPU'`, so the overrun is reported as `worker-exit`. For a dual limit of 2 or more the soft is lowered by one unit, SIGXCPU fires, and the run is a timeout. Containment holds in both cases; only the 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.
+- **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. A cross-thread binding that the program joins with a synchronous `t.join()` can also deadlock: the joining thread blocks the main coroutine while the binding's reply still needs the pump to deliver it, so `await` never resumes until the wall clock. 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.
diff --git a/packages/code-runtime/code-runtime-python/README.zh.md b/packages/code-runtime/code-runtime-python/README.zh.md
index 953ab46ca5..8e772c2dd5 100644
--- a/packages/code-runtime/code-runtime-python/README.zh.md
+++ b/packages/code-runtime/code-runtime-python/README.zh.md
@@ -39,4 +39,4 @@ 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 仍然成立,只是失败分类失真。
- **1 秒双限 `ulimit -t 1` 下的 CPU 超限会被报告为 `worker-exit`,而非超时。** 当宿主在一个硬 CPU 限制等于软限制(`ulimit -t N` 同时设置两者)且该限制为 1 的环境下启动时,`_clamped` 无法把软限制降到 0,因此内核在同一 tick 直接 SIGKILL 忙循环,SIGXCPU 永不送达。宿主只在 `signal === 'SIGXCPU'` 时把 CPU 超限分类为超时,因此该超限被报告为 `worker-exit`。当双限为 2 或更大时,软限制会被降低一个单位,SIGXCPU 触发,该次运行成为超时。两种情况 containment 都成立;只是分类被降级。
-- **宽 binding 回复会按成员展开宿主侧状态。** 回复经由 [`@deepseek-ai/dsh-session`](../../core/session/README.md) 的 `snapshotJsonValue` 穿越,其 `walkJsonValue` 为每个成员压入一个任务帧,而 binding 回复在 seam 层没有字节上限。因此一个数百万元素的合法回复可以耗尽宿主堆。该性质属于那个共享遍历,而不属于本后端——worker-thread 后端消费同一个函数——所以修复应落在 `packages/core/session`,让所有消费方一并受益。
+- **宽 binding 回复会按成员展开宿主侧状态。** 回复经由 [`@deepseek-ai/dsh-session`](../../core/session/README.zh.md) 的 `snapshotJsonValue` 穿越,其 `walkJsonValue` 为每个成员压入一个任务帧,而 binding 回复在 seam 层没有字节上限。因此一个数百万元素的合法回复可以耗尽宿主堆。程序用同步的 `t.join()` 连接一个跨线程 binding 也会死锁:该线程阻塞主协程,而 binding 的回复仍需泵来投递,因此 `await` 直到墙钟才会恢复。该性质属于那个共享遍历,而不属于本后端——worker-thread 后端消费同一个函数——所以修复应落在 `packages/core/session`,让所有消费方一并受益。
diff --git a/packages/code-runtime/code-runtime/README.i18n.yaml b/packages/code-runtime/code-runtime/README.i18n.yaml
index c8913de7c0..83723e2d05 100644
--- a/packages/code-runtime/code-runtime/README.i18n.yaml
+++ b/packages/code-runtime/code-runtime/README.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/code-runtime/code-runtime/README.md
-README.md: b16a8c81e80e07665b7ac868e4cb643529938055
-README.zh.md: ad5ec96fc8df00c0f3c1b1771bc5efbf148de054
+README.md: 56decf8fc86c37f08e0ee266006efabaf6436712
+README.zh.md: 46fd55891c84d971ed453791f44af831919b8bca
diff --git a/packages/code-runtime/code-runtime/README.md b/packages/code-runtime/code-runtime/README.md
index b16a8c81e8..56decf8fc8 100644
--- a/packages/code-runtime/code-runtime/README.md
+++ b/packages/code-runtime/code-runtime/README.md
@@ -121,9 +121,9 @@ No direct invalidation; the named consumer owns any request-prefix changes.
These limits define what the seam cannot do; they are current package constraints, not a task backlog.
- **`run()` is one-shot** — `logs` arrive only on the resolved `CodeRunResult`; the seam exposes no streaming-log or progress API for a live program's output.
-- **No state survives between runs** — every request runs against a fresh world; a persistent REPL-style kernel is deferred until a backend brings its own logging story.
-- **Only the worker-thread backend ships** — `'process'` and `'container'` are declared well-known `isolation` values with no implementation; a hard security boundary awaits a container backend.
-- **Intermediate binding values have no byte cap** — implementations remain subject to structured-clone cost and process memory, while a provider may already impose its own acquisition bound.
+- **A persistent REPL-style kernel is recorded future work** — the no-state-between-runs contract stands until a persistent-kernel backend brings its own logging story ([Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.md)).
+- **The worker-thread and Python (process) backends ship; `'container'` is future work** — `'process'` is implemented by the `dsh-code-runtime-python` backend, while `'container'` remains a declared well-known `isolation` value with no implementation; a hard security boundary awaits a container backend.
+- **Intermediate binding values have no byte cap** — implementations remain subject to structured-clone cost and process memory, while a provider or executor may already have imposed its own acquisition bound.
### Dev Note
diff --git a/packages/code-runtime/code-runtime/README.zh.md b/packages/code-runtime/code-runtime/README.zh.md
index ad5ec96fc8..46fd55891c 100644
--- a/packages/code-runtime/code-runtime/README.zh.md
+++ b/packages/code-runtime/code-runtime/README.zh.md
@@ -121,9 +121,9 @@ binding-global 与 error-class 名称是语言可移植的:必须匹配标识
这些限制说明 seam 不能做什么;它们是当前包约束,不是任务积压。
- **`run()` 是一次性的**——`logs` 只有在 `CodeRunResult` resolve 后才能获得;seam 不提供正在运行的程序所产生输出的流式日志或进度接口。
-- **运行之间不保留状态**——每次请求都在全新环境中运行;持久 REPL 风格内核在某个后端带来自己的日志方案之前保持延期。
-- **目前只发布 worker 线程后端**——`'process'` 与 `'container'` 是已经声明但没有实现的已知 `isolation` 值;强安全边界需要等待容器后端。
-- **中间绑定值没有字节上限**——实现仍受 structured-clone 成本与进程内存约束,而提供方可能已经应用自己的获取上限。
+- **持久 REPL 风格内核已记录为未来工作**——在持久内核后端带来自己的日志方案前,运行之间不保留状态的约定继续有效(参见 [Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md))。
+- **目前提供 worker 线程与 Python(process)后端;`'container'` 是未来工作**——`'process'` 由 `dsh-code-runtime-python` 后端实现,而 `'container'` 仍是已声明但没有实现的已知 `isolation` 值;强安全边界需要等待容器后端。
+- **中间绑定值没有字节上限**——实现仍受 structured-clone 成本与进程内存约束,而提供方或执行器可能已经应用自己的获取上限。
### 开发备注