Repository navigation
Replies: 2 comments
补充证据(Discussion #8204):触发条件的源码级候选已定位
1. 边界已精确到「注册层」,不是按功能分对同一次 15 段全部是工具插件的 prompt 贡献,且它们与丢失的 26 个工具同源。证据: export function applyReadTool(ctx: Context, caps: ReadToolCaps): void {
ctx.systemPrompt.section({ name: 'tool:read', order: ..., text: 'Use the read tool — not shell commands like cat …' })
ctx.tools.register(defineTool({ name: 'read', ... }))
}同进同出。所以丢失的单位是「preset 层挂载的那些插件在 同一张
⇒ 不是 2. 候选机制:挂载被剪枝,而 agent 的 scope parent 还指着它
const mounts = new Set<PresetMount>() // :138
function pruneDisposedMounts(): void { // :156
for (const mount of mounts) {
if (mount.fiber.uid === null) mounts.delete(mount) // :158 已销毁 ⇒ 删除
}
}
export function livePresetMounts(within?: Fiber): PresetMount[] {
pruneDisposedMounts() // :174 读取路径上剪枝
const all = [...mounts]
return within === undefined ? all : all.filter(mount => withinFiber(mount.fiber, within))
}
export function standingMountFor(agentCtx: Context): JoinedPresetMount | undefined {
const agentKey = scopeOf(agentCtx)
if (agentKey === undefined) return undefined
const standingKey = scopeParentOf(agentKey) // :246 agent 指向的那一代
if (standingKey === undefined) return undefined
return livePresetMounts().find( // :248 只在【已剪枝】的集合里找
(candidate): candidate is JoinedPresetMount => candidate.key === standingKey,
)
}因果链:
为什么值得盯这一处:
即:剪枝只在读路径上发生,而生产环境唯一的读路径承担者(invariant 伴生)在发货态根本不加载。 这与「缺陷只在发货态静默」是同一个成因。 与观测的吻合:该机制天然解释可逆性——重新挂载后 parent 又能对上(实测 3. 已排除的假设(都试过,都被数据推翻)诚实列出,免得重复走:
结论:现象与边界已确证;触发「挂载为何被销毁」的上游条件仍未确证,§2 只是把机制缩到一个具体函数与其注释自陈的脆弱点。 4. 建议的下一步(可验证)
5. 复现脚本(纯只读)
样本会话: |
补充证据(二)· Discussion #8204:因果链已闭合,并附可运行的受控复现
1. 结论速览根因不是「preset 被过滤」,而是「preset 那层注册的挂载记录被剪掉后,没有任何东西把 agent 重新接回去」, 五环因果链(每环都有源码位置):
第 5 环是使缺陷「无限期 + 间歇 + 可逆」的那一环,也是前文完全没提到的。 2. 为什么 resume 是放大器
于是:一代死后,此后每一个 resume 出来的会话都被绑到一个谁也不服务的 key 上,而 3. 阳性受控复现(本次新增,可直接运行)单测文件(放在检出里即可跑,不改产品代码):
签名与线上逐项吻合:工具与提示段同灭、会话存活、零报错。 4. 关键佐证:检出器存在,但发货态不加载包自己的 invariant 伴生就是为本缺陷写的,且措辞逐字对得上
复现中它确实抛了
「测试里响亮、发货态静默」不是猜测,是两侧源码与实测共同印证的。 5. 对前文的更正与新增排除5.1 更正:
|
| # | 假设 | 证伪判据 |
|---|---|---|
| 1 | s2s 唤醒 | 反例 + 同一会话两次 s2s 结果相反 |
| 2 | 紧跟 end-seed 重启 |
52 次跟重启却正常、2 次缺陷无重启 |
| 3 | 进程冷启动 | 缺陷组仅 2/5 在空档后;正常组 17 个在空档后 |
| 4 | 组合文件 stamp 变化 | 三个 preset 文件 mtime 全为 2026-09-03 |
| 5 | 换代本身让已加入 agent 失工具 | mount.spec.ts:700-701 断言相反 |
| 6 | live patch 层热重载(本次) | 它重建整个运行时、agent 一并消失,与「存活且被剥光」不符(§5.2) |
6. 建议修法(按性价比)
ensureStanding校验存活(一行级,直击第 5 环):缓存命中后不仅比 stamp,还确认该代的 scope fiber 仍活着
(u !== null/ 未 dispose);不活则重建。这一条就能把缺陷从「无限期」压成「最多影响一代」。standingMountFor返回undefined且context.agent存在时补可归因日志:含agentKey与它期望的
standingKey。当前该状态完全静默,这正是它能长期存在的原因。- 把剪枝从「读路径」改成「有 orphan 引用的可观测状态」:
pruneDisposedMounts删除前先核对是否仍有 agent 指向该代。 composeFrom/mount在 parent key 已死时报错,或在dsh-scope补销毁钩子(scopeParents目前无失效通知)。
7. 复现脚本一览(全部纯只读,不改产品代码)
| 文件 | 作用 | 结果 |
|---|---|---|
probe-8204-teardown.spec.ts |
机制逐环:销毁 standing scope 后 agent 静默失层 | 6/6 |
probe-8204-resume.spec.ts |
resume 路径复现 fleet 签名 | 1/1 |
probe-8204-scope.spec.ts |
反证 patch 热重载(整树重建) | 1/1 |
仍未定:触发第 1 环的那个外部部署侧事件(谁先销毁 standing scope)。它需要线上日志;
但机制、持久化、复现与排除项均已确证,修法 1 也不依赖它。
Uh oh!
There was an error while loading. Please reload this page.
resume 后部分会话丢失全部 preset 工具且无报错
部分会话 resume 后只剩宿主层插件工具(32 个),preset 工具(26 个)及其提示段全部消失。
复现、预期与验收
复现步骤:
agent-presets)的部署里正常使用一个会话若干轮(此时每次装配 58 个工具)。resume)。request/header事件的tools数组与system长度。实际结果:工具 58 → 32,丢失的恰好是 preset 注册的那 26 个;
system9893 → 4673 字符。丢失的 26 个逐名如下(跨工作区、跨三天完全一致):即整个 preset 工具面:
tool-fs(read/write/edit) ·tool-fs-search(glob/grep) ·tool-pwsh·tool-jobs·tool-skill·tool-goal·tool-subagent(-control/list/fork/codex/claude-code)·tool-workflow·tool-ralph·tool-ask-user·tool-todo·tool-web。保留的 32 个全是宿主层插件工具(
memory_*·board_*·s2s_*·session_*·calendar_*·rename_session)——丢失集里没有一个插件工具,保留集里没有一个 preset 工具,分界是完美二分。system丢掉的 15 段全部是工具使用说明(read / write / edit / glob / grep / job / web_search / web_fetch / goal / workflow / ralph / subagent / subagent_fork 各自的指引),与那 26 个工具逐项对应;而身份段(persona)逐字仍在 ⇒ preset 身份没丢,丢的是它的工具与提示贡献。预期结果:resume 后仍为 58。同一 preset、同一份组成文件,且文件从未改动(三个 preset 的
agent.cordis.ymlmtime 全部是 2026-09-03,早于全部事件)。环境:Windows 10.0.26200 · DSH
master76fda72979· web profile ·agentPreset: standard· providerdsh-api· 模型deepseek-v4.1-flash验收条件:resume 装配后工具集与
initial装配一致;若确实无法保持一致,则该状态必须可归因(见下)。全机普查(98 会话 / 274 次装配,脚本附后):
方向可逆 ⇒ 是竞态,不是持久损坏。
session-162425ee同一会话先丢后自愈:为何静默(这是我认为最该先修的一点):
packages/preset/agent-presets/src/invariant.ts:46-68已经检查了「agent 未加入任何 preset」并在system-prompt/assemble处fail(),其注释写明后果:但
.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.md:45明确:该 invariant 只在加载了dsh-invariants的组合里生效,发货态不加载;AgentPresets侧的告警是 advisory。⇒ 发货部署里这个状态没有任何输出,用户只能看到模型回unknown tool。可疑点(未确证):
packages/preset/agent-presets/src/mount.ts:243-251的standingMountFor()通过scopeParentOf(agentKey)在当前存活的livePresetMounts()里按 key 相等查找,对不上即返回undefined。我试过并已排除「组成文件 stamp 变化导致换代」(index.ts:757/:766)——文件 mtime 未变,无从换代。所以触发条件仍未定位,上述只是把范围缩到该函数及其调用链。建议:
standingMountFor返回undefined而context.agent存在时打一行可归因日志(含 agentKey 与期望的 standingKey)。当前该状态在发货态完全静默,这正是它能长期存在的原因。AgentPresets那条 advisory 告警是否覆盖「曾加入、后来对不上」的情形(不只是「发布时未加入」)。resume路径上 agent scope parent 的重建时序——本机 7/7 次转移都发生在resume。证据脚本(纯只读,扫会话日志按「装配带的是哪一层工具」分类每次装配):
probe-preset-tools-census.mjs—— 复现上表全部数字session-04743b35(丢)·session-87cf7656(丢)·session-162425ee(丢后自愈)附:普查脚本(
probe-preset-tools-census.mjs)All reactions