Files
deepseek-harness/packages/code-runtime/code-runtime-python/README.zh.md
T
Chinesezjc c4fe0320fb test(code-runtime-python): drive the inherited SIGXCPU path with a wrapper; fix the zh outer wire sentence
The review's two follow-ups on the inherited-SIGXCPU fix: (1) a discriminating
case — pythonBin points at a wrapper that ignores SIGXCPU before exec'ing
python3, so the child genuinely inherits the ignore; with cpuSeconds: 1 the
busy loop must end as timeout (the bootstrap reset restored SIG_DFL), and
reverting the reset leaves it running to the wall — verified red. (2) The zh
README's OUTER wire section now carries the truncation-exception sentence
(the previous commit had duplicated it in the inner section instead); the
duplicate is removed, and the settlement note registers the inherited-SIGXCPU
reset.
2026-08-31 15:05:23 +08:00

12 KiB
Raw Blame History

description, kind
description kind
CPython 子进程代码 runtime:为 Python 模型代码实现 dsh-code-runtime seam,及其使用的 fd-3 wire 协议。 package-reference

@deepseek-ai/dsh-code-runtime-python

English | 中文

概述

dsh-code-runtime-python 交付 PythonCodeRuntime——dsh-code-runtime seam 的 CPython 子进程实现:它以 language: 'python'isolation: 'process' 注册为 codeRuntime,每次 run() 启动一个全新的 python3 -I 子进程,把程序作为 async 函数体执行,通过子进程 fd 3 上的无版本 JSON-lines 协议通信(stdout/stderr 留给程序自己的输出)。宿主侧(src/protocol.ts)把每条入站帧都视为敌意并逐字段重建后才读取;Python 侧(py/protocol.py)镜像消息词汇。隔离(不是安全边界——模型代码与 bash 同等的信任)来自空环境、RLIMIT_CPU/RLIMIT_AS、墙钟上限与 SIGTERM→宽限→SIGKILL 进程组拆卸,所有上限都在插件加载期校验。

目录


使用本包

在需要通过 code-runtime seam 运行 Python 模型代码时选择本包:向 dsh-tools 注册 PythonCodeRuntimerun() 就在全新的 python3 -I 子进程中执行每个程序,成功时以 result.value resolve、失败时以 result.error resolve(正交的 CodeRunFailure.kind 分类涵盖解析失败、抛出异常、无效完成值、输出溢出、预算到期、中止与执行基底终止);只有 seam 误用才 reject——绑定命名空间畸形,或已释放后仍调用。配置在加载期被拒绝:非 Unix 平台、非正或非整数的预算、低于截断标记下限(64)的 maxLogBytessetTimeout 会收敛的定时器值、超过单个 fd-3 帧可承载的预算,以及最坏峰值会突破 RLIMIT_ASaddressSpaceMb/输出预算组合。

你得到什么

包的默认导出是 PythonCodeRuntime 插件。其公开面还重新导出宿主侧协议词汇:validateChildFrame(重建每条入站帧)、无损 JSON codec 与计量器(encodeJsonPlaincheckDoneValuehasUnsafeIntegerTokenhasNonLosslessNumber)以及 logTruncationMarker(共享截断标记文本)。每个上限都是带默认值并经校验的 Config 字段:cpuSeconds60)、maxWallMs600000)、addressSpaceMb512Darwin 上不生效)、maxLogBytes65536)、maxValueBytes32768)、graceMs3000)与 pythonBinpython3,在子进程以空环境启动前对照 PATH 解析)。

wire

帧在子进程 fd 3 上以 JSON-lines 传输——每行一个对象——因此 stdout/stderr 留给程序自己的输出。子进程 → 宿主:boot-ackcalllogdone。宿主 → 子进程:boot(首帧,携带全部上限与命名空间声明)、runboot-ack 之后,只携带程序体)与每个 call 一个 reply。伪造帧可在 done 上同时携带 valueerror,因此消费方必须先检查 error,在它存在时忽略 valuelog 帧的 open 标志标记由显式 flush 提交的未结束行:宿主把下一个 log 帧追加到同一条目,因此 print('a', end='', flush=True); print('b') 读回为一条 'ab' 条目而不是假换行。合并的唯一例外是截断:当后续超预算帧触发账本时,已计费的前缀作为独立条目先提交,截断 marker 跟在后面(marker 保持末位,无重复计费)。

可能出错的地方

宿主侧校验在不抛异常的情况下丢弃垃圾,因此畸形或伪造帧永远不会让宿主进程崩溃:validateChildFrame 对任何不能干净重建的内容返回 undefined,非数字的 call id 永远不会被回显进 reply,伪造的额外字段永远不会被带走。非无损 JSON 或超过配置字节预算的完成值会被显式拒绝(non-losslessover-budget),而不是被静默取整或截断。原始长度超过 64 MiB 的 fd-3 帧会让本次运行以 worker-exit 结算(接收路径在 toString/JSON.parse 之前限制原始帧,紧凑宽帧不能解码出远超其线上字节的宿主内存)。


理解实现

实现内部——点击展开

本节解释后端背后的设计;可观察行为在使用本包中完整覆盖。

设计概念

单向信任:宿主把每条入站帧都视为敌意(模型代码可以在 fd 3 上伪造任何内容)并逐字段重建后才读取;Python 侧信任宿主回复。bootstrappy/bootstrap.py)把程序作为 async 函数体执行,因此顶层 awaitreturn 都可用;binding 调用经 fd 3 以 JSON-lines 往返,回复在 pump 中限速,以免大量大回复钉住宿主的 fd-3 可写缓冲。

wire 契约

帧为 bootrun(宿主 → 子进程)与 boot-ackcalllogdone 加每个 call 一个 reply(子进程 → 宿主)。log 帧的 truncated 标志标记的就是子进程账本自己的截断标记帧,因此宿主在与子进程相同的点停止捕获,而不是从自己的预算推断。log 帧的 open 标志标记由显式 flush 提交的未结束行:宿主把下一个 log 帧合并进同一条目,因此 print('a', end='', flush=True); print('b') 读回为一条 'ab' 条目而不是假换行(拆分计费算术在 fd-3 协议 Agent Note 的 wire-contract 段)。合并的唯一例外是截断:当后续超预算帧触发账本时,已计费的前缀作为独立条目先提交,截断 marker 跟在后面(marker 保持末位,无重复计费)。done.error.kindexceptioninvalid-outputoutput-limit 之一;墙钟/CPU 预算、中止与基底死亡在宿主侧观察,不以帧形式携带。

无损 JSON 跨越

完成值与 binding 实参以精确 JSON 跨越:值无递归序列化,因此低于字节预算的深层载荷存活,而不会死在 JSON.stringify 的栈上限;超出安全范围的整型 double 以精确数字跨越,而不是被静默取整的 token;src/protocol.ts 中的计量器在任何其他代码读取载荷之前强制字节预算与数字无损性。

镜像对齐

tests/protocol-mirror.e2e.ts 启动真实 python3,对照 src/protocol.ts 断言 PROTOCOL_FD/截断标记文本以及 py/protocol.py 中每个 TypedDict 的必填/可选 wire 字段集,因此字段改名、删除或一侧把另一侧必填的字段变成可选都会使测试失败。字段类型不跨语言边界比较;该残留由评审加后端的真实子进程套件(tests/runtime.spec.ts)负责。

源码地图

文件 职责
src/index.ts 插件入口:PythonCodeRuntime——spawn、帧 pump、预算、隔离、拆卸;重新导出协议词汇
src/protocol.ts 宿主侧:帧 codec、敌意帧校验器、无损 JSON 计量器、共享标记文本
py/bootstrap.py 子进程侧:fd-3 通道、程序执行、binding 分发、账本与结算
py/protocol.py Python 侧:PROTOCOL_FDTypedDict 帧镜像、log_truncation_marker
tests/runtime.spec.ts 真实子进程套件:预算、隔离、敌意帧、名称重绑
tests/protocol-mirror.e2e.ts 对照真实 python3 的跨语言镜像测试
src/invariant.ts 不变式伴生(无运行时不变式;本包不注册可变数据关系)

进一步探索

当 runtime 契约不够时阅读这些。它们从 seam 定义走向设计记录与配套后端。


模型体验

间接地,通过 dsh-tools 中的 Code Mode,它把程序的完成值或失败渲染成保留的 run_code 结果。

KV Cache 效应

无直接失效;指定的消费方拥有任何请求前缀变化。

已知限制与延期工作

这些限制定义本包覆盖与不覆盖的内容;它们是当前包约束,不是任务积压。

  • 跨语言 guard 覆盖执行的表面与帧字段形状,而非字段类型——mirror e2e 比较必填/可选字段集,而非 cpuSeconds 在两侧是否都是 int;类型级漂移由评审加后端的真实子进程套件捕获。
  • setsid() 逃出子进程组后代不被组拆卸回收——kill(-pid) 够不到它;运行仍按 done 帧决定的值结算,若该孤儿持有管道,close 截止兜底会强制结算,但孤儿本身在自行退出前一直存活到 fiber 之外。
  • 结算后到达的 log 帧被丢弃——运行一旦结算,宿主侧捕获即关闭;迟到的 fd-3 log 帧(来自比 done 帧存活更久的线程)会被丢弃,而不是追加到 logs
  • binding 回复值没有 seam 级字节或深度上限——maxValueBytes 只计量 done 帧的完成值;宽 binding 回复在宿主侧重建(snapshotJsonValue 遍历)并整帧编码,两侧都只受进程内存约束(与没有子进程侧预算的 binding 实参一样)。
  • run() 是一次性的——logs 只有在 CodeRunResult resolve 后才能获得;没有为运行中程序产生的输出提供流式日志或进度接口。
  • 运行之间不保留状态——每次请求都在全新子进程中执行;持久 REPL 风格内核在某个后端带来自己的日志方案之前保持延期。
  • 原始长度超过 64 MiB 的 fd-3 帧会让本次运行以 worker-exit 结算——maxLogBytes/maxValueBytes 在加载期被限制到同一解析器上限,因此诚实子进程的帧总能放得下;模型构造的超过 64 MiB 的 binding 实参(一个在 seam 层没有预算的值)会触发同一上限——这是该 OOM 防护的已接受残余。
  • 组合日志与值的峰值不被加载门建模——持续写入的模型 daemon 线程与完成值计量、分帧相加的峰值没有任何门会放行或拒绝;运行以 worker-exit 告终,隔离成立,只有失败分类降级。
  • open 续接帧计费为零并占用一个宿主槽位——伪造的 {"type":"log","text":"","open":true} 洪泛在不触碰 logBudget 的情况下增长持有的片段数组(每个空片段的计费为 max(cost - 2, 0) = 0)。接受的残余:每帧的宿主成本远低于其约 30 字节的 fd-3 线上成本,洪泛受管道吞吐限界,且模型代码的信任级别与 bash 相同。
  • 1 秒双限 ulimit -t 1 CPU 超限被报告为 worker-exit 而非 timeout——当宿主在一个与软限相等的硬 CPU 限下启动且该限为 1 时,_clamped 无法下调软限,内核在同一 tick SIGKILL 忙循环,SIGXCPU 永远不会送达;隔离成立,只有分类降级。
  • 中间 binding 值没有字节上限——实现仍受无损 JSON 序列化成本与进程内存约束,提供方或执行器可能应用自己的获取上限。

开发备注

维护者的工作上下文——点击展开

无。