Replies: 2 comments 2 replies
|
Your diagnosis is correct, and the cause is one line of code — which also means this is already fixed upstream. Details below, in the order that makes the fix obvious. 1. The mechanism, one line
let cut = SessionLogOffset(boundary.seq + 1)
while (cut < source.events.length && source.events[cut]?.type !== 'turn/start') {
cut = SessionLogOffset(cut + 1)
}
The tail copy exists because between-turn state (title, model selection) was meant to travel with the fork. But a pending input is durable state committed to the log before the Your correction to your own wording is also right, and for the same reason: the loop only ever looks at event types. It cannot know whether the input was consumed in the source, so "whether it had already run" genuinely does not matter — the insertion is copied either way. "Unconsumed queue" is the wrong description; "the cut advances past the selected 2. Why "don't copy the queue" is the right conclusion but the wrong placementUpstream fixed it at the cut, not at the queue: const cut = SessionLogOffset(boundary.seq + 1)Your suggestion 1 states the outcome this produces; the placement is what matters, and the design note records why the two queue-side shapes were rejected:
So the rule became: no event after the selected closing event belongs to the seed — queued input, titles and model settings alike. 3. It is already fixed, and your build is the last one before the fixThree commits on 2026-09-11 (Dudu-0223), merged as PR #4001 (
Design record: Tag containment, verified per tag:
Your build was cut the day before the fix. The regression vector added with it is your scenario almost literally — 4. What to do, and what is honestly still openUpgrade to a
5. Related threads
One note on your "相关" section: for the moderation-400 case you cite as making fork the only way out, the recovery no longer has to go through fork. |
|
0.1.7-alpha.2 依然存在 |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
[Bug] fork 会继承源会话 fork 点的下一条输入,新会话第一次输入执行的是它
现象
从会话中间 fork 出新分支后,源会话 fork 点的下一条输入会被新会话一起继承,并在新会话第一次输入时被执行 —— 我看到的不是自己刚发的那句话对应的回答,而是「先跑了另一段 prompt」。我真正发的那条被挤到它后面,进入排队队列。
触发条件只有一条:fork 点不在最后一条消息上。 与源会话是否出错、是否卡死,以及那条输入在源会话里是否已经执行过,全都无关(后两点我都在桌面端实测确认过)。
我被迫补发说明、或直接终止该轮才能把会话拉回来;而 fork 最需要被用到的场景恰恰是最容易落在会话中间的场景。
实际经过(含事件证据)
事件序号 / 时间轴来自两个会话的
session.v3.jsonl.zstd。源会话(
0a77ac23的父会话):turn/endturn 52(completed)session/end-seedagent/inbox/splicedtarget=next-turnturn/start)session/end-seedinherited:truefork 出来的新会话(
0a77ac23,即「dsh 基础介绍与用法」):agent/inbox/splicedtarget=next-turnstart=1turn/startturn 53agent/inbox/spliced移除 1 条user/messageagent/inbox/splicedtarget=next-stepuser/message两条消息的
rpcId完全相同 ——seq=2301的user/message与源会话seq=2294的agent/inbox/spliced都是9b475cc4-b958-4917-8a98-acbc3a727ec1。这不是我重复发送,而是同一条输入被跨会话执行了。机制
fork 会把源会话在 fork 点上的输入队列一起复制,其中包含 fork 点之后加入的那条输入 —— 无论它在源会话里是否已经被消费。新会话第一次输入触发轮次启动时,队列里那条先于新输入被取出执行,新输入则要再等一次队列处理才落定(上表 seq 2299 → 2307)。
所以这不是「残留未消费队列」的问题。 上表那条输入在源会话里确实没被执行过,但我复测发现,即使一句输入在源会话里已经正常执行完,fork 之后它仍会被带过去。按「未消费」来描述是错的,准确的说法是:fork 复制输入队列时,把 fork 点之后的输入一并复制了。
影响
建议
我这边可以做的
我不是 DSH 的开发者,只是碰到这个问题的用户。但如果方向被认可,我可以尝试改一下 fork 的输入队列复制逻辑(也就是让 fork 时不再把 fork 点之后的队列条目复制进新会话)。
我能定位到相关代码路径并给出草案,但需要熟悉这块实现的人确认设计意图 —— 因为"fork 应该继承多少状态"可能是有意为之的取舍,我不想凭空猜。
按官方贡献指南,目前不接受外部 PR,所以我会把这些以补丁 / 思路的形式发在 Discussions 里,直接提交 PR 就不必了。需要的话我可以先交一份最小验证(怎么稳定复现、以及改动前后的事件对比)。不保证效果。
相关
以上三条覆盖的都是「队列结算」,没有一条覆盖 fork 继承 —— 本 issue 的新意是「输入队列被跨会话复制」。
All reactions