Repository navigation
[Agent Core] 让子 Agent 继承父 Run 的截止时间与终止语义 - #168
Merged
rojim666 merged 1 commit intoSep 23, 2026
Merged
Conversation
子 Agent 此前从配置里拿一份全新的完整墙钟预算,与父 Run 的剩余时间无关: 子的 deadline = spawn 时刻 + 完整预算,必然越过父的 deadline,越过量等于 spawn 时父已耗掉的时间。同时 AgentLoop._wait_for_background 是无界 gather, 一次成功的父收尾会被仍在运行的后台子 Agent 拖出预算。 - ExecutionContext 新增 inherited_deadline_at 入口;start() 在继承 deadline 与 自身 max_wall_clock_s 之间取更早者,任一方都不能放宽另一方 - SpawnAgentTool 派生子 Agent 时传入父的 deadline_at 与 clock(必须同时传, 否则绝对 deadline 与时钟源不一致) - _wait_for_background 的 gather 加 Run 剩余时间边界,deadline 到达时取消 后台子 Agent 并等其收尾 - 子 Agent 撞上 Run deadline 时终态归 CANCELLED 并带具体原因,不再误判为 FAILED;reason 负责区分"为什么",status 只表达"不是错误失败" - 前台派生路径的 subagent.finished status 归一化进协议词汇 ("success" | "failed"),不再吐出协议外的 "interrupted" - 修 _spawn_then_loop_provider 夹具:原先父子共享一个 spawned 标志,导致子 自行 repeated_error 失败,"父中断 → 取消 child"路径从未被执行 Refs rojim666#69 Signed-off-by: Ace <[email protected]>
rojim666
approved these changes
Sep 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
关联 Issue #69。PR-01 至 PR-06 已分别覆盖 Run Deadline 基础、LLM hard cutoff、
Provider timeout/retry、工具调用、权限等待和上下文压缩;本 PR 处理剩下的一跳——
Parent → Subagent。
此前有两个问题:
subagent/tool.py用self._budget.max_wall_clock_s构造子 context,因此子的deadline =
spawn 时刻 + 完整预算,必然越过父的 deadline,越过量等于 spawn 时父已耗掉的时间。
AgentLoop._wait_for_background是无界asyncio.gather。实测(探针,在未改动的基线上):父预算 2 秒、1 秒后派生后台 child 再
end_turn,elapsed=3.359s—— 超预算 68%,且一次本应success的收尾被拖成了interrupted。本 PR 是 Issue #69 分阶段实现中的 Subagent 阶段,只使用
Relates to #69,不在本 PR 合并时关闭父 Issue。
Behavior
ExecutionContext新增inherited_deadline_at入口字段;start()在继承 deadline与自身
max_wall_clock_s之间取更早者——父 Run 的截止时刻与子自己的预算互相都不能放宽对方(与 provider / 工具 / 权限各处
min(自身超时, remaining)同一口径)。SpawnAgentTool派生子 Agent 时传入父的deadline_at与clock。两者必须同时传:只传绝对 deadline 不传 clock,会因时钟源不一致而算出无意义的值。
_wait_for_background的 gather 由父 Run 剩余时间界定;deadline 在等待期间到达时取消后台子 Agent 并等其收尾,不把无界等待留给父 Run。
CANCELLED并带具体原因,不再误判为FAILED。取舍:
status只表达"不是错误失败","为什么"由reason区分(
max_wall_clock_exceeded/parent_cancelled等)。subagent.finishedstatus 归一化进协议词汇(
"success" | "failed"),不再吐出协议外的"interrupted"。/compact)行为不变。Validation
218 passed(test_subagent_cancellation.py/test_subagent_registry.py/test_subagent_prompts.py/test_loop.py/test_runner.py/test_compactor.py/test_session_manager.py/test_budget_admission.py/test_long_compaction.py/tests/integration/test_deadline_foundation.py)。test_deadline_shrinks_monotonically_across_nesting已核验在未改动的基线上失败(
assert 9.921 <= 3.0),另两个分别是锁定"自有预算耗尽也归 cancelled"这一取舍、以及补充"迟到终态不改变父唯一终态"的覆盖(该机制本就存在,缺的只是覆盖)。
context.py+loop.py+subagent/tool.py):Success: no issues found in 3 source files。All checks passed!。compileall:通过。git -c core.whitespace=cr-at-eol diff --check:通过。桌面端或构建配置。
Baseline failures fixed by a fixture change
tests/unit/test_subagent_cancellation.py有两个测试在未改动的origin/main上就稳定失败(3/3 复现,非抖动):
test_max_steps_interrupt_cancels_blocking_childtest_interrupt_event_order_and_uniqueness根因是测试夹具坏了,不是生产代码:
_spawn_then_loop_provider()的state["spawned"]标志在父子间共享(SpawnAgentTool把同一个 provider 对象传给子Agent),子的第一次调用就走到
unknown_tool分支并以repeated_error自行失败,被测的"父中断 → 取消 child"路径从未被执行。诊断实测
child status=failed reason=repeated_error。本 PR 给该夹具加
parent_run_id参数、按run_id分派,使子真正阻塞到被取消。换成正确夹具后实测生产代码行为本就是正确的(
child status=cancelled、subagent.finished status=cancelled),因此修的是夹具,不是生产代码;两个测试的断言一字未改。
Known limitations
Node and TypeScript checks、Build Linux packages),已核验为基线失败,非本 PR 引入:仓库只有
desktop-linux.yml、typescript-ci.yml、website.yml三个 workflow,没有 Python CI,本 PR 只改Python;两个 workflow 在
main上近期运行全部失败(含 PR-06 的 merge commit),失败 job 日志根因均为
npm cache is not found。SubagentFinishedEvent只有status,没有 reason 字段(
bus/events.py:336-341;协议packages/protocol/src/index.ts:301同样如此),子的具体终止原因无法在该事件上体现,只能经
agent_result的 reason 文本观察。本 PR 未改协议。
"cancelled":若子阻塞在一个不遵守remaining_s的 provider 调用里,它无法自己算出 deadline,只能被父强制取消。遵守
remaining_s的正常 provider 走的是已修好的路径。runner.py收尾对非 success 终态仍不取消挂起压缩(PR-06 已记录的同一条遗留项),与本 PR 无关,未在本次范围内改动。
Checklist
Signed-off-by(DCO)Relates
#69