Skip to content

fix(mcp): close post-#30 catalog, re-tick, steer, and watermark residuals - #31

Merged
linkerlin merged 2 commits into
masterfrom
cursor/close-post-30-residuals-8747
Nov 3, 2026
Merged

linkerlin merged 2 commits into
masterfrom
cursor/close-post-30-residuals-8747

Conversation

@linkerlin

@linkerlin linkerlin commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

#30 之后还剩四件事:宿主目录经常看不到 swarm_doctor、只认 gate_soft_continue=true 的宿主在未完成软缺口上会再 tick、软固化引导仍把 bench freeze / bench baseline 列成当前步骤、以及 test_identical_rewrite_is_noop 在部分文件系统上失败。这一支把四件都收进同一份改动。

Four residuals left after #30 are closed together: the host catalog lag on swarm_doctor, soft-continue-only hosts re-ticking an unfinished soft gap, freeze/baseline still offered as the current solidify step, and the material watermark treating an identical rewrite as skipped.

CI follow-up

notifications/tools/list_changed 没有 id。e2e 的 recv 把握手后的下一行当成 JSON-RPC 响应,于是 tools/call 和 prompts/get 在 ["result"] 上 KeyError(run 41606679556:3.13 十处、slow 两处、Windows 同一形态)。协议测试客户端本来就会跳过无 id 的 notifications/*。e2e 客户端现在同样跳过,并按请求 id 配对。test_00_list_changed_does_not_desync_the_next_response 钉住:通知被收进侧账,下一次 tools/list 与 prompts/get 仍带 result。本地 uv run pytest tests/e2e/test_swarm_full_e2e.py -q --tb=short -m "not llm" 为 12 passed;协议 / usability / mcp_server 48 passed。服务端仍在 initialized 之后发这条通知,没有改成不发。

What changed per residual

1. DEBUG #61 — swarm_doctor 在宿主目录里消失

服务端 tools/list 本来就在同一页列出只读 swarm_doctor(探测:29 个工具、无 nextCursor、载荷约 25KB、doctor 在中部、带 cursor 再要一页仍然在)。注册没有漏,也没有按名字过滤。握手用默认 NotificationOptions(),tools.listChanged 为 false。把「列表永不变」当成可以跨重启留目录的宿主,调用打到新进程,目录仍停在旧快照上。

现在初始化广告 tools.listChanged=true(prompts / resources 一并标可变),并在 notifications/initialized 之后发一次 notifications/tools/list_changed。不另注册一个假工具。仍忽略该通知、也不在新会话重读 tools/list 的宿主,目录仍旧;这是仓外行为,DEBUG #76 写明了。

2. 只看 gate_soft_continue=true 的宿主会再 tick

#30 把这个键写回第一次未完成软缺口 tick 和保险丝,软缺口重新可见,但只在该键为严格 true 时再 tick 的宿主又会空转。写成 false 会重开 DEBUG #67(严格 false 被当成整段会话失败)。

这次只在 tick 与保险丝上 去掉 这个键,绝不写成 false。同时写结构字段 soft_gap:present=true、advisory=true、tick=false,有缺口码时带 code。boot / status 仍写 gate_soft_continue=true,且不带 soft_gap。doctor 仍不写 gate_soft_continue、session_not_failed、soft_gap。保险丝仍是 ok=false、没有 error、loop_fuse=pending_redirect_loop、session_not_failed=true,preserve 三元组不动。

3. 软固化引导上的 freeze / baseline

_commands_without_current_doctor_fix 现在同时丢掉精确的 evolver doctor --fix、evolver bench freeze、evolver bench baseline。next_steps 里点名 freeze 或 baseline 的句子改成「先 swarm_solidify,不要现在跑这两条」。作用面是未完成 run 的 boot / status / doctor / 第一次 tick / 保险丝。没有未完成 run 时这三条仍在。本路径不执行 --fix,不冻结包,不写 Parent baseline,不放宽固化门。

4. 水印 test_identical_rewrite_is_noop

快跳过原先只比 mtime_ns 和 size。本环境的文件系统在 write() 时把 mtime 拨回创建时刻,os.utime 会被下一次写入盖掉,于是相同字节重写被记成 skipped 而不是 rewrite_noop。GitHub 上 master 的 ext4 会推进 mtime,所以 CI 是绿的;同一套只看 mtime 的快跳过,在 mtime 被还原时会把同长度的真实改写静默跳过。现在快跳过还要比全文哈希。相同字节且 mtime 变了仍是 rewrite_noop。同长度、mtime 被拨回去、字节不同则走全量重扫。

How to test

uv run pytest tests/e2e/test_swarm_full_e2e.py -q --tb=short -m "not llm"
uv run pytest tests/test_mcp_protocol.py tests/test_mcp_host_usability_pending_smoke.py tests/test_mcp_server.py -q --tb=line
uv run pytest tests/gep/test_material.py tests/test_mcp_protocol.py \
  tests/test_mcp_host_usability_pending_smoke.py \
  tests/test_mcp_host_usability_p0.py tests/test_mcp_host_usability_p1.py \
  tests/test_mcp_server.py tests/test_open_box_setup_doctor.py tests/test_swarm.py
uv run ruff check src tests
uv run ruff format --check src tests
uv run mypy src

本地结果:e2e(不含 llm)12 passed;协议 / usability / mcp_server 48 passed;早先相关套件 167 passed;ruff 与 mypy 通过。未设置 DEEPSEEK_API_KEY,live-LLM 三条用 -m "not llm" 跳过。

Risk

Medium。改的是 MCP 宿主契约和物料摄取,不是固化门、Parent baseline 或评分器。

  • 只在 gate_soft_continue is True 时才 tick 的宿主,在未完成软缺口的 tick / 保险丝上会停,并应去 swarm_solidify。boot / status 上这个键仍为 true,那些宿主仍可能从 boot 再进一次 tick;tick 信封本身不再用 true 邀请下一次。
  • 把缺席当成 false 的宿主不会把这次省略读成会话失败:值不是 false。
  • 忽略 notifications/tools/list_changed 的宿主仍要新开一次会话才能看到 swarm_doctor。
  • 水印快跳过多一次全文哈希。相同字节的重写仍不追加记录。
  • e2e 修复只动测试客户端的读循环,不改变服务端何时发送目录通知。

How we fixed each residual

可直接贴回聊天:

  1. DEBUG #61。 根因在本仓库的握手,不在工具注册。同一进程的 tools/list 已经包含只读 swarm_doctor(一整页、无 nextCursor)。默认广告是 tools.listChanged=false,宿主因此把目录当成永不变,重启后的新进程调用打得到、目录仍是旧的。修复是广告 listChanged=true,并在 notifications/initialized 之后发送 notifications/tools/list_changed。没有造假工具。不听这个通知的宿主仍须新开会话;这一点写在 DEBUG #76 和协议测试里。

  2. 只认 soft-continue 的宿主再 tick。 未完成软缺口的第一次 swarm_tick 和 loop_fuse=pending_redirect_loop 省略 gate_soft_continue,从不写成 false。新增 soft_gap.advisory=true 与 soft_gap.tick=false,让「缺口还在」和「不要再 tick」分开。boot / status 继续写 gate_soft_continue=true,让需要看见软缺口的宿主不受影响。doctor 仍不写这个键、不写 session_not_failed、不写 soft_gap。保险丝保持 ok=false + session_not_failed + loop_fuse=pending_redirect_loop,没有 error 字段。preserve 三元组仍在。

  3. freeze / baseline 瘦身。 未完成 run 的软固化引导(boot、status、doctor、第一次 tick、保险丝)从 commands / fixes 去掉精确的 evolver bench freeze 和 evolver bench baseline,next_steps 改成先固化。没有未完成 run 时这两条和 evolver doctor --fix 仍列出。不执行 --fix,不写 Parent baseline,不放宽固化门。

  4. 水印。 ingest_file 的快跳过改为 (mtime_ns, size, full_hash) 三者都相符才 skipped。相同字节的重写仍是 rewrite_noop,不追加记录。同长度但内容变了、mtime 又被文件系统拨回去时走全量重扫。测试在写入之后再 utime,这样在会还原 mtime 的文件系统上也能走到 memo 路径;另有一条同长度、还原 mtime、字节不同的用例钉住重扫。没有删测试,也没有 skip。

  5. CI。 上述目录通知没有 id。e2e 客户端把它读成下一次调用的响应。recv 现在跳过 notifications/*,直到匹配请求 id 的帧。回归测试确认 tools/list 与 prompts/get 仍有 result,通知记在侧账里。

Harness/evaluator governance

Upstream governance surface: MCP swarm host catalog and unfinished-run envelopes (swarm_tick / swarm_boot / swarm_status / swarm_doctor); material watermark ingest. Not GEP scoring, solidify accept, or Parent baseline.
Downstream EvoX impact: hosts that tick only while gate_soft_continue is strictly true stop on the unfinished soft-gap tick and fuse. Boot and status still expose that bit. No bridge contract change.
Rollout-local scope: protocol and ingest behavior on this branch; no cohort flag.
Promotion boundary: merge to master. No separate rollout switch.
Evaluator mismatch sets: N/A -- observation envelopes and file ingest only.
Non-regression evidence: 167 related tests passed locally, including pending-smoke host simulators, MCP protocol list/notification, doctor key absence, and both watermark cases. e2e stdio suite 12 passed with live-LLM tests deselected. ruff and mypy clean.
Fix-severity review: medium
Owner approval: swarm / MCP host contract reviewer
Security boundary: no new secrets, network, or tool. Catalog notification asks the host to re-list tools the server already implements. Soft steer still does not run evolver doctor --fix or write a Parent baseline.
Rollback: revert this commit. Omitting gate_soft_continue is absence, not false, so reverting restores the #30 true bit on tick and fuse.
Live promotion: no
Autonomous evaluator self-editing: no

Self-check

  • No new runtime dependencies without a clear justification in "What changed".
  • Tests added or updated to cover the new behavior; gates pass locally: uv run pytest (related suites, 167 passed; e2e without llm, 12 passed), uv run ruff check src tests, uv run ruff format --check src tests, uv run mypy src.
Open in Web Open in Cursor 

…uals

Advertise tools.listChanged and send tools/list_changed after initialize
so hosts refresh a catalog that already lists swarm_doctor. Omit
gate_soft_continue on unfinished soft-gap tick and fuse envelopes
(never write false) and set soft_gap.tick=false. Drop bench freeze and
baseline from those solidify steers. Fast-skip a watermark only when
the full hash matches, so a same-size rewrite is not a silent skip.

Co-authored-by: Steper Lin <linker.lin@live.com>
Copilot AI balanced review requested due to automatic review settings November 3, 2026 08:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

The catalog refresh notification has no id. The e2e recv treated the
next stdout line as the JSON-RPC result, so tools/call and prompts/get
raised KeyError on result. Read until the matching id, and pin that
pairing with a regression.

Co-authored-by: Steper Lin <linker.lin@live.com>
@linkerlin
linkerlin merged commit bec1f26 into master Nov 3, 2026
5 checks passed
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.

3 participants