Replies: 4 comments
|
Thanks for the measurement — 14 out of 154 archived sessions is a much clearer signal than a hand-made repro, and the window you identified is exactly right. I read the claim path and both halves of a repair turn out to be public API, so this can be closed from outside the tree without an upstream change:
So the message does not have to be merely reported. Starting from the claim event, the guard waits for the turn to close, proves the input never became durable, and puts the same message object back. I have published that as a plugin:
npm install @argszero/cordis-plugin-inbox-claim-guard- insert:
- id: inbox-claim-guard
name: '@argszero/cordis-plugin-inbox-claim-guard'
config:
mode: restore # restore | report | off
wakeup: true # may a restored message wake the driver
maxWakesPerMessage: 2 # wake budget per message id
restoreOnAbort: false # cancel() drops pending input on purposeThree details that decided the implementation, in case they are useful for the core fix: 1. Not every closed claim is a loss. A 2. The boundary is recoverable, so it does not have to be guessed. A claimed batch is 3. Restore at the Two things I deliberately did not do, to avoid buying durability by changing the log:
Version note: the exact boundary directory above (the |
|
装的那版(dsh 0.1.5-rc.1,产物 0.1.5-rc.2)我逐条对过了:claim 与 两处要补,都是我的问题。正文里「日志里连痕迹都没有」说过头了 —— claim 写了 还没装。装要重启宿主,本机有别的会话在跑;而且 peer 只钉了 cordis,版本表全在 README 里,装错线 npm 不会响 —— 你把 |
|
同补版本口径:壳 0.1.5-rc.1 只是 CLI 层,运行时是 0.1.5-rc.2(= npm latest,09-10 就发了),行号请按 rc.2 产物看; |
|
把落点收到最小一格;另一个更大的问题(事前余额检查)另说,不混在这条里。 真正会丢内容的只有一处:第一次 最小落地:只给第一次 attempt 加一条回滚 —— 抛错时把已 claim 的消息放回 inbox( 稳定复现口(与内容和 provider 无关):把会话模型钉成一个不存在的 id → 第一次 判据(盯代码,不用等回音): 改完之后,那行 durable append 不该再由 |
Uh oh!
There was an error while loading. Please reload this page.
先钉版本:我本机装的是
@deepseek-ai/dsh0.1.5-rc.1,跑起来的内部包是 0.1.5-rc.2(dsh-agent-loop/dsh-api-session-controller/dsh-client-ui-chat),Windows 11,web profile。每条都给两处行号:我本机安装目录里的编译产物(0.1.5-rc.2),和 2026-09-17 的 master 源码。行号会漂,标识名和事件形状为准。一句话
我在一轮里发的消息,会先进 pending inbox,等下一个 step 边界被 claim;而它被写成 durable
user/message的那一次 append,排在step()里prepareRequest()之后。于是prepareRequest一抛错,这条消息就既不在 inbox、也不在日志里 —— 我在自己的 154 份会话存档里量到 14 例,合计 6379 字符,最长一条 5746。事件形状(脱敏,取自那 14 例之一)
代码位置
packages/core/agent-loop/src/agent.ts::245在 pre-step 里this.inbox.claim(target, position.turn);那条user/message直到:376才this.session.append('user/message', …),中间隔着:361的await this.prepareRequest(turn, step, signal)。claim 自己只写一条agent/inbox/spliced(masterpackages/core/agent-loop/src/inbox.ts:109-113),不写那条user/message。本机 0.1.5-rc.2 对应dsh-agent-loop/lib/index.js:104-107与:1028。prepareRequest会抛什么。 master 的prepareRequest里有三处:agent/requestwaterfall 抛的、if (!proposedConfig.provider || !proposedConfig.model)那条守卫、以及await this.loopCtx.llm.prepareCall(proposedConfig, signal)。后者在本机是dsh-llm-pi-ai/lib/index.js:1824的prepareCall→modelInfo→modelOf,会抛两类配置类错误:模型不在目录里时UNKNOWN_MODEL(:1779)、profile 记了modelErrors/catalog 失败时INVALID_CONFIG(:1776)。顺带一句:同一段代码把NO_ADAPTER显式吞掉继续走(catch (error) { if (… code !== 'NO_ADAPTER') throw error; … }),所以它不是这条路径的入口;UNKNOWN_MODEL/INVALID_CONFIG是。cancel会把整队摘掉。 masteragent.ts:151/ 0.1.5-rc.2lib/index.js:799:keepInbox不为真时inbox.clear()。宿主侧 Stop 带的是keepInbox: true,但 disposal 那条路(0.1.5-rc.2lib/index.js:1656的cancel({ kind: 'disposed' }))没带。我这边另有 12 例outcome:"canceled"之后再没出现过的输入。实测
$DATA_HOME/sessions/<workspace>/<session>/session*.jsonl.zstd,我扫了 154 份(扫描时点 2026-09-17)。agent/inbox/spliced折叠出 inbox 投影,跟 durableuser/message的 id 集合比对(必须先扫全档再折叠,否则那些随后就被正常 append 的条目会被误判)。outcome:"canceled"之后再没出现。复现
不需要任何特别的内容或端点,任选一个让
prepareRequest抛错的入口即可:INVALID_CONFIG的条目);turn/end判UNKNOWN_MODEL(或INVALID_CONFIG),而这条消息的user/message永远不出现。说明一句我在自己机器上的诚实边界:那 14 例的现场是同一个
prepareRequest里另一个抛错(我当时一份畸形的模型条目,Cannot read properties of undefined (reading 'length')),UNKNOWN_MODEL这条入口我是读调用链确认的,没有为它单独构造现场 —— 那要动正在跑的宿主的模型配置。建议(按成本排)
appenddurable 再消费 inbox,要么在 catch 里把 claimed 的消息放回去。现在这一步失败,我打进去的字就没了,日志里连痕迹都没有。inbox.clear()别静默:cancel/disposal 摘掉了哪些消息,至少留一条记录,产品事后才有机会告诉我「那句话没发出去」。这些是我给自己做会话体检、解存档数事件的时候量出来的;跟模型、provider 和对话内容都无关。
All reactions