Skip to content

fix(sandbox): 主智能体编排期沙盒 idle 超时被误杀,加主动 keepalive 续命 - #1005

Closed
zgpnuaa wants to merge 2 commits into
xerrors:mainfrom
zgpnuaa:feat/sandbox-keepalive
Closed

fix(sandbox): 主智能体编排期沙盒 idle 超时被误杀,加主动 keepalive 续命#1005
zgpnuaa wants to merge 2 commits into
xerrors:mainfrom
zgpnuaa:feat/sandbox-keepalive

Conversation

@zgpnuaa

@zgpnuaa zgpnuaa commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

现象

编排型主智能体(如 deep-research)在调度子智能体期间,可能几分钟不执行任何沙盒命令。此时沙盒被 provisioner 按 idle 超时清理,之后主智能体再想用沙盒时 create_if_missing=False 无法重建,任务报 sandbox is unavailable 直接失败。

机理

原实现是「惰性 touch」——只在真正要执行命令前才 touch 续命。但主智能体编排期根本不碰沙盒,惰性 touch 永远不触发,沙盒就「安静地」到了 idle 超时被回收。

改进方法

ProvisionerSandboxProvider 里加一个后台 daemon 线程,按 SANDBOX_KEEPALIVE_INTERVAL_SECONDS(默认 30s)周期遍历所有活跃沙盒连接,对到期的执行 touch 续命;touch 发现沙盒已被清理则移除连接;shutdown()stop_event 优雅停线程。

效果

主智能体即使长时间不执行命令,沙盒也不会被误回收;编排结束后沙盒仍可用,任务不再因 sandbox is unavailable 中断。

验证

新增 4 个单测:keepalive touch 报活并更新时间戳、tick 移除已清理沙盒、tick 保留存活沙盒、tick 跳过未到期沙盒。

@xerrors

xerrors commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Codex Review:

补充核查(修正此前预 review):当前主路径已经启用 create_if_missing=True,PR 所述“因 False 无法重建”的根因尚未成立。

在本 PR head 0b36338bf447 中,ProvisionerSandboxBackend.__init__ 默认 create_if_missing=TrueSandboxBackendScope.create_backend 也显式传入 True;_get_connection 将此值传给 provider。provider 发现原实例不存在后会进入 create 分支。因此仅凭“编排等待期间 idle 回收”不能推导出下一次调用一定报 sandbox unavailable;请先给出实际传入 False 的调用路径或可复现故障。

两种机制解决不同问题:按需重建可以重新提供环境,并重新挂载持久 Workdir/UserWorkspace;主动 keepalive 则能避免原容器的 /tmp、运行中进程及未持久化环境状态因 idle 回收而丢失。只有当前任务确实依赖这些状态跨编排等待存续时,才有独立的续命需求。根 Agent 和子 Agent 共享 runtime scope,不能仅根据“主 Agent 没有执行命令”认定整个 sandbox 无活动。

建议先不按目前的 bug-fix 理由合并:若验收只要求回收后仍能执行命令并读取持久产物,优先验证现有按需重建即可;若要求同一执行树内临时 runtime 状态连续存续,请明确该契约,再证明主动续命与 Run 生命周期绑定。当前实现遍历所有缓存连接,缓存存在本身不等于 Run 仍在执行。

此前建议“恢复惰性 touch 后应因 sandbox 不可用失败”不够准确,应更正为:先建立实际缺陷的独立复现;若缺陷是临时状态丢失,负向测试应验证该状态丢失,而不是假定不能重建。合并前按最终目标补充真实 worker/provisioner 验证及 tracked decision。

已有验证:在该 PR head 独立源码快照运行 PYTHONPATH=package:server python -m pytest test/unit/backends/test_sandbox_backends.py -q --disable-warnings --tb=short,87 passed。上述调用链为静态核查,未运行该 head 的真实 idle 回收/重建 E2E;该组 unit 不能证明本 PR 的必要性。

@xerrors xerrors closed this Sep 10, 2026
@zgpnuaa

zgpnuaa commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

你说得对,本 PR 的根因不成立。撤回

核实结论

你的判断完全正确,我逐条验证后确认:

  1. create_if_missingFalse 无关ProvisionerSandboxBackend.__init__ 自该文件存在起就是 create_if_missing=True(最早版本在 _get_connection 里硬编码 True,后来重构成参数,默认值仍是 True)。全仓库检索 create_if_missingtools.pycomposite.pykbs/tools.pyinstall_skill.py 全部显式传 True没有任何调用方传 False
  2. sandbox is unavailable 不可达ProvisionerSandboxProvider.get()create_if_missing=True 时要么 create 成功返回连接,要么 create 抛异常——不存在返回 None 的路径,而 RuntimeError("sandbox is unavailable ...") 只在 discover 返回 None(即 create_if_missing=False)时抛出。
  3. 更糟的是:False 这个说法本来就出自我自己写进 provider.py 的注释,不是代码事实。我把自己写错的注释当成了依据。

关于 keepalive 本身

你在 [P1] 里指出的另一点也成立,而且我的实现确实有这个缺陷:「缓存里存在连接」不等于「Run 仍在执行」。现有实现遍历所有缓存连接续命,会让已经结束但尚未 release 的 Run 的沙盒一直不进入 idle 回收,与 provisioner 的 idle 回收设计相冲突。

你提出的两种机制的区别我认同:按需重建解决的是「回收后仍能执行命令并读取持久产物」,主动 keepalive 只对「同一执行树内临时 runtime 状态(/tmp、运行中进程)必须跨编排等待连续存续」这一契约有意义。而我没有为该契约提供任何可复现证据,因此不应按 bug-fix 合并。

处置

关闭本 PR。分支保留(fix/sandbox-keepalive),便于需要时重开。

如果后续确有「临时 runtime 状态需跨编排等待存续」的需求,我会按你的建议重做:先给出可复现证据,再把续命绑定到 Run 生命周期(只对仍在执行、且声明该契约的 Run 续命),并补 tracked decision——而不是像现在这样遍历全部缓存连接。

感谢你两次纠正,这次的核查让我把「注释当事实」这个错误暴露出来了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants