feat(core): keep background agents resident between turns - #7353
feat(core): keep background agents resident between turns#7353DragonnZhang wants to merge 14 commits into
Conversation
0b84454 to
4765ce6
Compare
|
Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. 中文请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。 |
d8adc1a to
92397fb
Compare
4765ce6 to
134216e
Compare
92397fb to
92b7528
Compare
134216e to
42e3421
Compare
ce6f41b to
d3c3afc
Compare
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
f71a28c to
51aa7a6
Compare
51aa7a6 to
7d488d7
Compare
|
Thanks for the PR! Template looks good ✓ Problem: this is a well-specified feature request with a linked issue (#7410) that clearly describes the performance cost of reconstructing runtimes from JSONL on every continuation, and the finishing-window race where a message can land after the final drain but before the task transitions to completed. The problem is observed and documented, not theoretical. Direction: aligned. Background agent continuity is on the roadmap ( Size: this PR touches core paths. Production logic: 1875 lines (additions + deletions, excluding test and docs). Test: 1839 lines. Docs: 160 lines. Since production lines exceed 500, flagging for maintainer awareness per the core-module policy. The 1000+ advisory also applies — this is a large PR, though the scope feels justified given the feature's concurrency and lifecycle complexity. Approach: the scope feels right for what it's solving. The key design decisions — identity-guarded residency, Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:这是一个规格清晰的 feature request,关联了 issue #7410,明确描述了每次 continuation 从 JSONL 重建 runtime 的性能开销,以及消息在最后一次 drain 之后、任务转为 completed 之前到达的收尾窗口竞态。问题是已观测到的,不是理论性的。 方向:对齐。后台 Agent 连续性在路线图上( 规模:触及核心路径。生产逻辑 1875 行(不含测试和文档)。测试 1839 行。文档 160 行。生产行数超过 500,按核心模块策略标记为维护者知悉。1000+ 大 PR 建议也适用——不过考虑到并发和生命周期管理的复杂度,范围是合理的。 方案:范围合理。关键设计决策——identity-guarded residency、 进入代码审查 🔍 — Qwen Code · qwen3.7-max Reviewed at |
Code ReviewIndependent proposal: I would have added a resident controller map to Findings — no critical blockers. The architecture is clean: The launch path refactoring in The resident controller closures in The One minor note: Files changed (23 of 23 shown)
Unit TestsAll PR-related test files pass (the 3 Real-Scenario TestingBackground agent resident continuation verified via tmux with the dev build ( The follow-up 中文说明代码审查独立方案: 我会在 发现——无关键阻塞项。 架构清晰: 单元测试所有 PR 相关测试文件通过(3 个 真实场景测试通过 tmux 验证了后台 Agent resident 续接:初始 prompt 得到 — Qwen Code · qwen3.7-max Reviewed at |
|
Confidence: 3/5 — clean review across every stage, but the Stage 0 maintainer-awareness cap (1500 production lines on core infrastructure) needs a maintainer's sign-off before approval. This is a well-executed PR. The design doc is thorough, the architecture is clean (resident handle separated from serializable task state, identity-guarded cleanup), and the implementation matches the stated goals. The launch-path refactoring also fixes a real double-construction leak as a side benefit. Tests are comprehensive — 600+ new test lines covering lifecycle, concurrency, identity guards, eviction, and disposal ordering. The tmux run confirms the headline behavior: a follow-up Going back to my independent proposal: the PR matches it closely and extends it in sensible ways (model pinning, provider view isolation, Non-blocking observations for the maintainer:
Deferring to a maintainer for the final call on a core change of this size. 中文说明置信度:3/5——每个阶段审查都很干净,但 Stage 0 维护者关注上限(核心基础设施 1500 行生产代码)需要维护者签字才能批准。 这是一个执行良好的 PR。设计文档详尽,架构清晰(resident 句柄与可序列化任务状态分离,identity-guarded cleanup),实现与目标一致。启动路径重构还顺带修复了真实的双重构造泄漏。测试全面——600+ 行新测试覆盖生命周期、并发、identity guard、淘汰和 disposal 排序。tmux 运行确认了核心行为:follow-up 与我的独立方案对比:PR 高度匹配并以合理方式扩展。我没有找到更简路径——生命周期、并发和资源管理关注点确实相互交织。 非阻塞观察:resident controller 闭包在两个路径中共享类似逻辑; 因核心变更规模,转交维护者做最终决定。 — Qwen Code · qwen3.7-max Reviewed at |
|
⏸️ Deferring to @tanzhenxin @ecchanger — this PR touches core infrastructure at scale (~1500 production logic lines across |
d92bc06 to
7301740
Compare
Pull request was converted to draft
7301740 to
188e529
Compare
188e529 to
e39f1f9
Compare
What this PR does
This PR keeps compatible completed background-agent runtimes resident within the parent session. A follow-up
send_messagecontinues the same chat, prepared tool surface, provider-side cache state, task row, task ID, and launch model. Transcript reconstruction remains the fallback when no compatible resident runtime is available.Inputs arriving while a resident turn is finishing are atomically claimed after the final asynchronous hook or cleanup boundary and processed on the same runtime before completion is published. Resident resources are identity-guarded and released on failure, cancellation, reset, replacement, terminal-entry eviction, working-directory changes, explicit ACP close, and parent-session disposal. Asynchronous tool-registry and headless-runtime cleanup is tracked and awaited even when a resident is already idle. ACP teardown blocks new create/load/resume/close operations, waits for in-flight lifecycle work before taking SessionEnd hook snapshots, coalesces concurrent disposal, and retains the session writer lease until resident runtimes have drained. A quarantined same-ID owner is fully shut down before replacement Config initialization begins, and an ambiguous post-quarantine close failure makes the bridge discard the child channel.
Session boundaries cannot inherit resident state accidentally:
/branchrefuses to switch while background work is still running and clears terminal resident state only after branch initialization succeeds, before committing the UI switch. Cold revival also pins the model recorded for the original launch, while inherited resident runtimes receive a dedicated launch-time provider view so later parent model or auth changes cannot silently reroute an existing task.Why it's needed
Previously, every message sent to a completed background agent reconstructed a new runtime from JSONL. The transcript preserved most model-visible history, but the live chat, prepared tools, per-agent registries, provider cache, and related process resources were recreated for every continuation.
There was also a finishing-window race: a message could arrive after the final in-loop drain but before the task became completed. The caller would receive a successful queued response, yet no later turn was guaranteed to consume that input. Keeping a resident runtime and closing the terminal transition atomically removes both the reconstruction overhead and that delivery gap while explicit session and model ownership prevents reuse across incompatible boundaries.
Reviewer Test Plan
How to verify
task_id. Confirm the follow-up remembers the nonce, reuses one runtime/chat/tool preparation, and emits a new running-to-completed lifecycle for the same task row./branchwith running background work and confirm the switch is refused; after work is terminal, branch and confirm the old terminal resident state is cleared before the new session starts.Local verification passed:
/branchtests: 3 files, 723 tests passed.npm run build,npm run typecheck,npm run lint, andgit diff --checkpassed.Evidence (Before & After)
N/A — this is lifecycle, concurrency, and resource-management behavior covered by unit tests; it does not introduce a new visual surface.
Tested on
Environment (optional)
Local macOS checkout with Node.js 22+, package-local Vitest runs, and no sandbox.
Risk & Scope
/branchrefuses to switch while background work is running.Linked Issues
Related to #7300 and #7352.
中文说明
这个 PR 做了什么
这个 PR 会让兼容的已完成后台 Agent runtime 在父 session 内保持 resident。后续
send_message会继续使用同一个 chat、已准备的工具面、provider 侧缓存状态、任务行、任务 ID 与启动模型。当没有兼容的 resident runtime 时,仍会回退到 transcript 重建。如果输入在 resident turn 的收尾阶段到达,系统会在最后一个异步 hook 或 cleanup 边界之后原子认领这些输入,并在发布完成状态前用同一个 runtime 处理它们。resident 资源带有 identity guard,并会在失败、取消、reset、替换、terminal entry 淘汰、工作目录变化、显式 ACP close 与父 session disposal 时释放。即使 resident 已经 idle,异步 tool registry 与 headless runtime cleanup 也会被跟踪并等待。ACP teardown 会阻止新的 create/load/resume/close,在获取 SessionEnd hook 快照前等待在途 lifecycle 工作,合并并发 disposal,并在 resident runtime drain 完成前保留 session writer lease。相同 ID 的 quarantined owner 会在 replacement Config 开始初始化前完整 shutdown;如果 quarantine 后的 close 结果不确定,bridge 会丢弃 child channel。
Session 边界不会意外继承 resident 状态:
/branch会在后台工作仍在运行时拒绝切换,并且只在 branch 初始化成功后、提交 UI 切换前清理 terminal resident state。Cold revival 也会固定原始启动记录中的模型;inherited resident runtime 会获得独立的启动时 provider view,因此父 Agent 后续切换模型或认证时不会把既有任务静默重路由。为什么需要它
此前,每次向已完成的后台 Agent 发送消息,都会从 JSONL 重建一个新 runtime。transcript 能保留大部分模型可见历史,但实时 chat、已准备工具、每个 Agent 的 registry、provider 缓存和相关进程资源都会在每次 continuation 时重新创建。
此外还存在一个收尾窗口竞态:消息可能在最后一次循环内 drain 之后、任务变为 completed 之前到达。调用方会收到成功 queued 的响应,但不保证之后会有新的 turn 消费该输入。保留 resident runtime 并原子关闭 terminal transition,可以同时消除重建开销和这段消息交付缺口;显式 session 与模型 ownership 则可以阻止跨不兼容边界复用。
Reviewer 测试计划
如何验证
task_id发送 follow-up。确认 follow-up 记得该 nonce,只复用一个 runtime/chat/工具准备过程,并为同一个任务行发出新的 running-to-completed 生命周期。/branch,确认切换被拒绝;工作进入 terminal 后再 branch,确认旧 terminal resident state 会在新 session 启动前清理。本地验证已通过:
/branch相关测试:3 个文件、723 个测试通过。npm run build、npm run typecheck、npm run lint与git diff --check均通过。证据(修改前与修改后)
N/A——这是由单元测试覆盖的生命周期、并发与资源管理行为,没有引入新的可视化界面。
测试平台
环境(可选)
本地 macOS checkout,Node.js 22+,在对应 package 内运行 Vitest,未使用 sandbox。
风险与范围
/branch会在后台工作运行时拒绝切换。关联问题
相关:#7300、#7352。