Replies: 3 comments 2 replies
|
Confirmed against master c291e79 (= 0.1.5-rc.2) and the rc.2 dist: the behavior is real and ACP-specific. session/resume rejects any session whose header carries parentSession, OR-ed with the true subagent marker (packages/acp/acp/src/index.ts:249-250; your :1215/:1266 line numbers match the dist verbatim); session/list hides them (:304-313); both predicates test key-presence only, and the behavior is pinned by the ACP's own tests (bridge.spec.ts:373-399). The two signals are separate fields: origin === 'subagent' is the real marker (core/session/src/types.ts:106), and plain forks write parentSession (core/session/src/index.ts:1203-1218). Outside ACP this does not reproduce: Web/API resume rejects only subagent origin (session-controller/src/agent.ts:400-434), and core has resume coverage for forked sessions (agent-loop/tests/resume.spec.ts:907-934). Nuance: README says session/list returns "root sessions" (acp/README.md:67-68), but session/resume has no such restriction (README.md:12), so the resume-side predicate looks over-broad - present since 5111816 (2026-08-22), unfixed on master. |
|
核实结论:已确认,两处谓词在 master @ 你的"四处分判、一处并判"分析完全成立:fork 路径只写 建议修法我认同:判据收窄到
顺带确认: |
|
@yamingmou — thanks for the careful reproduction; that data closes the seeded-replay gap better than anything I could assert from source alone. Extra shapes. Both dimensions you named are worth it, plus a third:
Synthesized v3 header shapes are perfect — paste them here or on the PR, whichever you prefer. PR. I'll prepare it as one three-part change: narrow the predicate, update One nuance on the controller side. Your summary of my cross-reference was a degree too strong: History check. I re-ran Also noted your authorship caveat — the n=2 replay stands on its own evidence; nothing to correct from my side. |
Uh oh!
There was an error while loading. Please reload this page.
Summary.
@deepseek-ai/dsh-acp@0.1.5-rc.2refuses to list or resume any session whose header carriesparentSession, OR-ing it together with the real subagent marker:The two fields mean different things, and this is the only place in the release that conflates them.
originis the subagent marker;parentSessionis lineage. A session created by the official fork path hasparentSessionand noorigin, so it disappears fromsession/listand is rejected bysession/resume— even though its log is intact and opens fine.Related but distinct. This is not the same gap as #6322 (missing
available_commands_update), #6324 or #6393 (nosession/load/ resume does not replay history). Those are about an ACP client rebuilding a transcript. This one is about which sessions ACP is willing to address at all: a session can be perfectly readable and still be excluded before any transcript question arises.Where the two fields are kept apart elsewhere. Four sites in the same release:
dsh-session— the fork API writesparentSessionand nothing else:dsh-subagent— the delegation path is the only writer oforigin, and it writes all three fields together:parentSessionalone:record.header.parentSession === parentSessionId && record.header.origin === "subagent".dsh-api-session-controller— subagent authorization also requires both:header.origin !== "subagent" || header.parentSession !== address.parentSessionId.parentSession?: SessionIdandorigin?: 'subagent'— andoriginadmits no value other than"subagent".One site merges what four sites separate.
On documentation.
dsh-acp's README lists fork among unsupported operations ("deletion, forks, transcript replay, and additional directories are unsupported"), and thesession/listrow says "persisted, resumable root sessions". So a "root only" reading oflistis arguable. But thesession/resumerow states no such restriction — it says "A persisted inactive session whose canonical workspace is verified before composition" — and nothing in the README says a session with lineage metadata cannot be resumed. The:1215condition exists only in the code, and the refusal message (session is not resumable: <id>) carries no reason.Controlled experiment (4 headers, one field apart). Four synthetic v0 headers served by the shipped ACP server (
dsh --profile acp):initialize→session/list→session/resume.originparentSessiondelegationDepthsession/listsession/resumesubagentparentSessionalone — resume goes fromokto refused. One variable, one flip.delegationDepthalone — identical outcome, so depth is not consulted.Invalid params: session is not resumable: <id>.Two further checks on real artifacts (108 MB and 80 MB of JSONL, both fork-derived,
delegationDepth: 0, noorigin):parentSessionfrom the header, everything else byte-identical, flips them to openable:session/listshows them,session/resumereturns ok, and the log migrates to v3.parentSessionat a session that does not exist changes nothing — same message. The branch does not resolve the pointer; it only tests presence.Measured blast radius (our store, read-only scan). Snapshot: 279 session directories, 248 of which carry
parentSession.origin: "subagent"(depth 1/2/3)origin, depth 0The middle row is the problem: 26 ordinary forked main-line sessions. They are not delegated work, nothing owns them, and their logs are intact — but under 0.1.5 an ACP client cannot see them and cannot continue them. For a user that reads as "my sessions disappeared and will not reopen", which is hard to distinguish from data loss.
Scope. This is the ACP surface only —
dsh-api-session-controllerand the client paths never treatparentSessionas a refusal, so desktop/web is unaffected. But ACP is what external ACP clients speak, anddsh-subagent-acpis the repository's own ACP client for out-of-process delegation.Suggested fix. Narrow the predicate to the subagent marker — refuse on
origin === "subagent"and keepparentSessionas lineage metadata, matching the four sites above. Failing that, at least let a fork be resumed when its parent is resolvable; and if the ban is deliberate, state it in the README next to thesession/resumerow rather than only in the code.Repro. Four header lines differing in one field each, placed under an isolated
DSH_HOME, theninitialize→session/list→session/resume. Script and raw output available on request; no session content, user name, or filesystem path is included here.Authorship note: reproduced and measured by me against my own store; root-cause tracing and drafting assisted by AI; verification and publication by me. I have no engineering background — if any technical claim reads wrong, please call it out; I will re-verify against the toolchain and correct.
Reported by the OfferKuai Team — Founder: Zhaofeng (Yaming). Website: https://www.offerkuai.com/ | Contact: contact@offerkuai.com
中文版
摘要。
@deepseek-ai/dsh-acp@0.1.5-rc.2会拒绝 list / resume 任何 header 里带parentSession的会话,把它和真正的子代理标记用||并在一起:这两个字段含义不同,而整个版本里只有这一处把它们混在一起。
origin是子代理标记;parentSession只是世系来源。由官方 fork 路径产生的会话有parentSession、没有origin,于是它从session/list里消失、被session/resume拒绝 —— 而它的日志完好、本来打得开。与相邻几条不同题。 这条不是 #6322(缺
available_commands_update),也不是 #6324 / #6393(没有session/load、resume 不重放历史)—— 那几条讲的是 ACP 客户端如何重建一份 transcript。这一条讲的是 ACP愿意寻址哪些会话:一个会话可以完全可读,却在任何 transcript 问题出现之前就被排除在外了。官方在另外四处都是分开判的:
dsh-session—— fork API 只写parentSession,别的都不写:dsh-subagent—— 派生路径是origin全仓唯一的写入点,三个字段一起写:parentSession === X && origin === "subagent"。dsh-api-session-controller—— 子代理鉴权同样要两个:header.origin !== "subagent" || header.parentSession !== address.parentSessionId。parentSession?: SessionId、origin?: 'subagent',且origin的取值域只有"subagent"。四个位点分开判,一个位点合并判。
关于文档。 README 把 fork 列在"不支持的操作"里("deletion, forks, transcript replay, and additional directories are unsupported"),
session/list一行写的是 "persisted, resumable root sessions" —— 所以 list 侧的"只列 root"是可以争辩为已文档化的。但session/resume一行没有任何此类限定(原文只说"A persisted inactive session whose canonical workspace is verified before composition"),README 里也没有任何一句说"带世系元数据的会话不可 resume"。:1215这个条件只存在于代码里,而拒绝信息(session is not resumable: <id>)不说明理由。对照实验(4 个 header,彼此只差一个字段)。 用官方发布的服务端(
dsh --profile acp):initialize→session/list→session/resume。originparentSessiondelegationDepthsession/listsession/resumesubagentparentSession:resume 从ok变为拒绝。单变量、单次翻转。delegationDepth:结果完全相同 ⇒ 该字段不参与判定。Invalid params: session is not resumable: <id>。另在两例真实产物上复核(108 MB 与 80 MB 的 JSONL,均为 fork 派生、
delegationDepth: 0、无origin):parentSession一个键、其余逐字节不动,两例立刻变为可打开:session/list可见、session/resume返回 ok、日志迁移落 v3。parentSession指向一个不存在的会话,什么都没变 —— 报的还是同一句。说明该分支不解析指针,只测"键在不在"。实测影响面(我方语料,只读扫描)。 快照:279 个会话目录,其中 248 个带
parentSession。origin: "subagent"(depth 1/2/3)origin、depth 0问题就在中间这一行:26 个普通的 fork 主线会话。它们不是被派出去干的活、没有谁"占用"它们、日志也完好 —— 但在 0.1.5 下 ACP 客户端看不到、也接不回去。对用户来说,这就是"我的会话不见了、而且打不开",与实际数据损坏难以区分。
范围。 这道门只在 ACP 面(
dsh-api-session-controller与客户端各路径都不把parentSession当作拒绝依据),所以桌面/网页主界面不受影响。但 ACP 正是外部 ACP 客户端所讲的那套协议,而dsh-subagent-acp是本仓自带的进程外委派 ACP 客户端。建议修法。 把判据收窄到子代理标记 —— 以
origin === "subagent"为准,parentSession只作世系元数据,与上面四处保持一致。退一步讲,至少允许父可解析的 fork 被 resume;如果这个拦截是有意为之,请把它写进 README 的session/resume那一行旁边,而不是只存在于代码里。复现。 四个只在单个字段上有差异的 header 行,放进一个隔离的
DSH_HOME,然后走initialize→session/list→session/resume。脚本与原始输出可按需提供;本文不含任何会话内容、用户名或文件系统路径。声明:本次实测与统计由我针对自己的数据完成;根因追查与文稿撰写由 AI 辅助;核验与发布由我本人负责。我没有工程背景 —— 若任何技术表述有误,请直接指出,我会对照工具链重新核实并更正。
本报告由 OfferKuai(Offer快)团队提交 —— 创始人:Zhaofeng(Yaming)。官网:https://www.offerkuai.com/ | 联系:contact@offerkuai.com
All reactions