[Bug][0.1.5-rc.x] Fork 出的会话发送新消息时重放源会话旧 prompt(A),新 prompt(B/C)永久滞留队列不执行 #6314
Replies: 5 comments
源级复核:你观测到的行为与我读码得到的机制链一致,§5 的推测方向(fork 继承了待消费输入)基本正确
1. 完整机制链(每环都有源文件 + 行号)
2. 为什么它长这样:两处 fork 语义不对称,且其中一处被测试钉死
3. 报告里没有的三点
4. P0 的两种修法(我更倾向乙,因为它不动被钉死的契约)
我认为真正的决策点是"被 fork 继承的 pending 该不该随子会话自动派发",而不是切点本身 —— 这需要维护者拍板,所以我把它作为讨论留在这里,而不是断言某个补丁对。 5. 插件可挂载面判定(我的插件线配额)结论:缺口落在 core 面,插件只能做受害者侧规避,修不了。
6. 边界
EN TL;DR — Your §5 hypothesis is essentially right, and here is the exact chain on |
|
一样的问题 |
补充:服务器侧独立复现 + 真实日志量化 + 本地已验证的最小补丁
1. 机制(与 @argszero 的源码链一致,这里补上「被中断那一轮」为何必中)fork() 取种子时: const boundary = ... // atSeq 之后第一个 turn/end,即用户点「分支」的那一轮
let cut = SessionLogOffset(boundary.seq + 1)
while (cut < source.events.length && source.events[cut]?.type !== "turn/start") cut = SessionLogOffset(cut + 1)
seed = source.events.slice(0, cut)该 while 把 boundary 之后、下一个 turn/start 之前的全部事件扫进种子,其中包括下一轮 prompt 的 agent/inbox/spliced 插入事件。DSH 每条用户消息都是「先入 inbox → turn/start → consume(inbox)」,而 consume 在 turn/start 之后,不在种子里。子会话重放种子即重建出一个非空待发队列 → 第一次提交先消费它。 被中断那一轮正是高发场景:分支点在上一轮的 turn/end,被中断轮的 prompt 插入紧贴在它的 turn/start 之前,必然被扫入。 2. 真实日志证据(本机 Information 工作区)父 session-f4859ede-f7a1-456b-bdc6-647973622446 → 子 session-65dcab50-6954-4113-bd2d-6b0ea804893e
3. 量化:不是偶发,是普遍现象对本机全部 61 个会话日志(~/.dsh/sessions///session.v3.jsonl.zstd)扫描每个 turn/end 锚点:
⇒「在非末轮 fork 必带上下一轮 prompt」几乎每次发生。 4. 本地已验证补丁(最小改动,只改种子截断点) let cut = SessionLogOffset(boundary.seq + 1);
- while (cut < source.events.length && source.events[cut]?.type !== "turn/start") cut = SessionLogOffset(cut + 1);
+ // 种子同样必须在 agent/inbox/spliced 之前截断,而不仅仅在下一个 turn/start 之前
+ while (cut < source.events.length && source.events[cut]?.type !== "turn/start"
+ && source.events[cut]?.type !== "agent/inbox/spliced") cut = SessionLogOffset(cut + 1);理由:种子本应是 completed-turn prefix,boundary 之后的事件不该进入分支;其中只有 agent/inbox/spliced 是活状态(turn 配对 / compaction 括号 / model/selection 等区间事件保留,避免误伤)。无待发 inbox 变更时与官方行为逐字节一致。 验证:同一条 atSeq → 旧 cut=780 得继承队列 [A, B];新 cut=776 得 [];node --check 通过。 5. 一点备注@joker-charles 的 fix-session-fork-cut.tar.gz 我在服务器侧下载超时未能核对;欢迎比对切点是否一致。若官方修复了此处,本补丁会撤下以免双重修复冲突。 (本地运维记录:PATCH-record-20260913-fork-seed-inbox.md) |
|
A correction to one line of the earlier source-level note, now that I have re-checked it against the published artifacts. "上游 master 的
So for anyone still on the 0.1.5 line the note's analysis and patch remain exactly right; on What the decision deliberately leaves alone, from the note itself: "Events already inside the selected prefix retain their ordinary replay semantics; this decision does not redefine pending input inserted before the selected closing event." Input already pending inside the selected turn — a I wrote the full per-line picture up on #7118 (same mechanism, reported against |
Uh oh!
There was an error while loading. Please reload this page.
1. 摘要(TL;DR)
从进行中的会话 fork 出新会话后,在新会话中提交任何新 prompt:
结果:fork 功能无法用于"基于历史继续对话"——第一次提交必被旧 prompt 劫持,用户自己的 prompt 永远到不了模型;且每次重放都会向会话历史写入一条重复的 A 消息(上下文污染、token 浪费)。
2. 环境
3. 复现步骤与观察
实际观察
期望行为
4. 行为规律(三次提交、两个 fork 代际)
5. 疑似方向(未验证,仅供参考)
会话日志中用户输入以
agent/inbox/spliced(target: next-turn)入队、turn 开始消费后以removedCount移除。怀疑 fork 复制历史时把源会话的 待消费输入队列 / inbox 状态 一并继承:A 在会话 1 提交时的入队记录成为 fork 会话中的滞留种子,此后:6. 后果
7. 建议修复方向(供参考)
[Bug][0.1.5-rc.x] A forked session replays the source session's old prompt (A) on new submissions; new prompts (B/C) are stuck in the input queue forever
1. Summary (TL;DR)
After forking an in-progress session into a new one, submitting any new prompt in the forked session results in:
Consequence: forking cannot be used to continue a conversation from its history — the first submission is hijacked by the stale prompt, the user's own prompt never reaches the model, and every replay writes a duplicate A message into the session history (context pollution, wasted tokens).
2. Environment
3. Reproduction and observations
Observed
Expected
4. Behavioral patterns (three submissions, two fork generations)
5. Suspected direction (unverified, for reference only)
In the session log, user input is queued via
agent/inbox/spliced(target: next-turn) and removed withremovedCountonce the turn consumes it. The suspicion is that forking inherits the source session's pending-input queue / inbox state: A's enqueue record from session 1 becomes a stale seed inside the forked session, after which:6. Impact
7. Suggested fix directions (for reference)
All reactions