feat(mesh): run the dispatch loop inside the host session, unattended - #11263
feat(mesh): run the dispatch loop inside the host session, unattended#11263yiliang114 wants to merge 1 commit into
Conversation
Nothing ticked the dispatcher: dispatchOnce and startMeshHostSessionOwner were referenced only by their tests, and the first live slice was driven by hand. The loop now runs inside the hidden host session. The launcher and the background-agent registry live in that process, so ticking there makes inspect a local registry read and a start a local call; a daemon-side loop would have paid a process boundary per candidate per tick for nothing. The daemon's whole job is to keep the host resident, which it does for any trusted workspace whose roster is non-empty. The supervisor dispatches only while its session still holds the workspace's host claim, so a stale duplicate the reaper did not reach cannot give one agent two bodies. Ticks coalesce rather than overlap, and a tick requested mid-pass joins that pass and receives its outcome. It polls rather than waiting for notifications because posts written by the daemon's REST route land in the store, not in this process. Two stale assertions are aligned with changes that landed after they were written: thread tool names now live in ToolNames, and the prompt window is committed when the runtime accepts the turn rather than at booking.
|
Thanks for the PR! Template looks good ✓ — every required section is filled in, including the OS table and Risk & Scope. One nit: the template's Problem: real, and verifiable rather than theoretical. Direction: aligned. This is step 6 of the committed mesh implementation plan, stacked on the still-open parent #11206, so nothing here reaches Size: core paths are touched ( Approach: scope feels right — one new 141-line module, two wiring edits, and two stale assertions realigned. No drive-by refactors. Two things I'd want answered before this merges, both about how faithfully the daemon side mirrors the pattern it copies:
Risk: Stage 1e matched a high-risk path — Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ —— 各必填部分都写了,包括操作系统表格和 Risk & Scope。一个小问题:模板要求的 **问题:**真实存在,且可验证,不是理论性加固。在本 PR 之前 **方向:**对齐。这是已提交的 mesh 实施计划里的 step 6,叠在仍未合并的父 PR #11206 之上,因此目前不会影响 **规模:**触及核心路径( **方案:**范围合理——一个新模块 141 行、两处接线改动、两条过期断言的对齐,没有顺手重构。合并前我想先确认两点,都是关于 daemon 侧对其所复制模式的还原是否完整:
**风险:**Stage 1e 命中高风险路径—— 进入代码审查 🔍 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
Code reviewThe core of this PR is good. The claim guard is the right safety property, tick coalescing via My independent read before opening the diff was that the loop belonged in the existing What I could not confirm is the lifecycle wiring. Four findings, all in the part the description itself flags as untested. 1. The damage is contained, and contained by your own guard: the orphan reads 2. 3. The mesh host is not wired to workspace add/drain. The mirrored block also parks 4. The deleted watermark assertion points at coverage that does not exist. The removal in Two smaller things: On the two realigned assertions: both check out. The six thread tools really are in sequenceDiagram
participant P1 as Daemon server.ts roster timer
participant P2 as MeshHostSessionOwner
participant P3 as ACP bridge
participant P4 as Host child acpAgent.ts
participant P5 as MeshSupervisor
participant P6 as dispatchOnce plus port
P1->>P1: every keepalive, readMeshAgents
P1->>P2: ensureResident() when roster non-empty
P2->>P3: spawnOrAttach(sourceType mesh host)
P3->>P4: new session with mesh host source type
P4->>P5: startMeshSupervisor(projectRoot, sessionId, port)
loop every 2 s
P5->>P5: readMeshWorkspace, claim guard
P5->>P6: dispatchOnce(projectRoot, port)
P6->>P6: selectCandidates, inspect, claimRun
P6-->>P5: DispatchRecord list
end
P4->>P5: stop() on discardStoredSessionIfCurrent
Files changed (10)
TestingThis is an unattended CI run, so I executed nothing from this PR — no build, no test, no The load-bearing fact: the repository's real gates do not run on this PR at all. That means there is currently no automated evidence that Not verified: that a booked run actually starts unattended; that the claim guard holds against a real stale duplicate rather than a mocked one; that
The two in-progress checks are TUI parity and a no-flicker gate. Neither touches the mesh, so green results there would say nothing about this PR's claim. Sandboxed verification would settle this, and you have write access so both lanes are available: Real-scenario testing (2c): N/A — unattended CI run, which never drives the product locally. 中文说明代码审查 PR 的核心部分是好的。claim guard 是正确的安全属性,通过 我在看 diff 之前的独立判断是:循环应该放进已有的 我无法确认的是生命周期接线。四条发现,都落在描述自己承认未测试的那部分。 1. 影响是被控制的,而且是被你自己的 guard 控制的:孤儿读到 2. 3. mesh host 没有接入 workspace 的新增/摘除生命周期。 被模仿的代码块还挂了 4. 被删除的 watermark 断言指向了并不存在的覆盖。 另外两件小事: 关于两条被对齐的断言:都核实过。六个 thread 工具现在确实在 测试 这是无人值守的 CI 运行,因此我没有执行本 PR 的任何东西——没有构建、没有测试、没有 关键事实:本仓库真正的门禁在这个 PR 上根本没有运行。 这意味着目前没有任何自动化证据表明 未验证:booked run 是否真的无人值守地启动;claim guard 面对真实的过期副本(而非 mock)是否成立; 两个进行中的检查是 TUI parity 和 no-flicker gate,都不触及 mesh,因此它们变绿也说明不了本 PR 的主张。 沙箱验证可以定论这件事,而且你有写权限,两条通道都可用: 真实场景测试(2c):N/A —— 无人值守 CI 运行,从不在本地驱动产品。 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Confidence: 3/5 — the supervisor itself is clean and the placement decision is better than the one I would have made; the daemon-side lifecycle wiring is incomplete in three specific places, and nothing on this commit can tell us whether the loop actually runs. ⏸️ Deferring to @yiliang114 — not approving. You own this roadmap line, so the call is yours, but I don't think this should merge as-is, and I want to be explicit about why rather than wave it through because the author is a maintainer. Stepping back: the problem is real, and I verified it rather than taking the description's word —
Where I stop is the wiring, and it stops for a reason the description already half-concedes: the daemon side has no automated test on this branch. All three findings live in that untested gap, and all three are the same mistake — the block copies the scheduled-task keepalive's startup loop but not its lifecycle. Finding 4 is smaller but I'd still fix the comment: the deleted watermark assertion is right to delete, and it points the next reader at On evidence, I want to be blunt about how thin it is. Two ways to close that, both available to you: I'm not requesting changes, because the feature's direction and its core module are right and this is stacked work on an unmerged branch where you may well prefer to fold the fixes into step 8's restart-and-stall pass. But I'm not approving either: three concrete lifecycle defects plus zero automated evidence on the commit is not a state I'd attest to. 中文说明信心度:3/5 —— supervisor 本身很干净,放置决策比我原本会做的更好;但 daemon 侧的生命周期接线有三处具体缺失,而且此 commit 上没有任何东西能告诉我们这个循环是否真的跑起来了。 ⏸️ 转交 @yiliang114,不予批准。 这条 roadmap 归你,所以决定权在你。但我不认为它应该按现状合并,我也想明确说清原因,而不是因为作者是维护者就放行。 退一步看:问题是真实的,而且是我自己核实的,不是照抄描述——在本 PR 之前
我停下来的地方是接线,而停下来的理由描述里已经承认了一半:daemon 侧在本分支上没有自动化测试。三条发现全部落在这个未测试的缺口里,而且是同一个错误——这段代码照搬了 scheduled-task keepalive 的启动循环,却没照搬它的生命周期。 发现 4 更小,但我仍会改那条注释:删掉 watermark 断言是对的,而它把下一个读者指向 关于证据,我想直说它有多薄。 有两条路可以补上,你都有权限: 我没有提交 request changes,因为功能方向和核心模块是对的,而且这是叠在未合并分支上的工作,你完全可能更愿意把这些修复并入 step 8 的重启与卡死恢复那一轮。但我也不批准:三处具体的生命周期缺陷,加上此 commit 上零自动化证据,不是一个我愿意背书的状态。 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Closing: superseded by work already on the mesh branch. |
What this PR does
Closes step 6 and makes step 7 runnable without a human driving it.
supervisor.tsticksdispatchOnceinside the hidden host session;acpAgent.tsstarts it for any session whose source type is the mesh host and stops it when that session is discarded;server.tskeeps one host session resident per trusted workspace whenever the roster is non-empty. Two stale assertions from earlier steps are aligned with #11252.Why it's needed
Nothing ticked the dispatcher.
dispatchOnceandstartMeshHostSessionOwnerwere referenced only by their tests, and the first live slice recorded in §0.2 was driven by hand. Without a loop there is no unattended demo.Where the loop runs is a decision, and it is now made: in the host session, not the daemon. The launcher and the background-agent registry live in that session's process, so ticking there makes
inspecta local registry read and a start a local call. A daemon-side loop would have paid an ACP round trip per candidate per tick for nothing. The daemon's whole job is to keep the host resident. Recorded in the design's §5.2 step 6 as decision (a), with (b) and why it was rejected.Two guards make it safe unattended. The supervisor dispatches only while its session still holds the workspace's host claim, so a stale duplicate the reaper did not reach cannot give one agent two bodies. Ticks coalesce rather than overlap: a tick requested mid-pass joins that pass and receives its outcome, so a caller asking for "the state after the next pass" always gets one and the port is never entered twice for the same queued runs.
It polls on purpose. Posts written by the daemon's REST route land in the store, not in this process, and the design treats in-process notifications as hints rather than truth. Two seconds of latency is the price of never missing a durable trigger.
Reviewer Test Plan
How to verify
Then the live recipe from the acceptance doc:
qwen serve,POST /mesh/agentsfor alice and bob,POST /mesh/threadsassigned to alice. Within one keepalive interval the daemon spawns the host; within 2 s the supervisor launches alice.qwen serve --debugshowsMESH_SUPERVISORticks.Evidence (Before & After)
Before: booked runs stayed
queueduntil someone calleddispatchOnceby hand. After: they start on their own, only from the claimed host.Tested on
Environment (optional)
Named Vitest files only. Observed:
supervisor.test.ts3 tests;src/agents/mesh/14 files / 139 tests.mesh-host-session.test.tscould not load on the review machine (itsnode_modulesresolves an older@qwen-code/acp-bridgewithout./sessionSource); CI is the gate for it, once #11261 unbreaks the build.Risk & Scope
server.ts) has no automated test on this branch; it mirrors the scheduled-task keepalive block line for line and is exercised only by the live recipe. The child-side wiring is oneifon the session's source type.queueExternalInput(false)rebook, and cancellation remain step 8.MeshTickOutcomeis new;dispatchOnceandcreateMeshDispatchPortare now exported from core for the ACP child.Linked Issues
Parent delivery PR: #11206. Depends on #11261 for a green build. Pairs with #11260 for the visible demo.