Repository navigation
Description
Pre-submission checklist | 提交前检查
- I have searched existing issues and this hasn't been mentioned before | 我已搜索现有问题,确认此问题尚未被提及
- I have read the project documentation and confirmed this issue doesn't already exist | 我已阅读项目文档并确认此问题尚未存在
- This issue is specific to MemOS and not a general software issue | 该问题是针对 MemOS 的,而不是一般软件问题
Environment | 环境
| Item | Value |
|---|---|
| Plugin | @memtensor/memos-cloud-dsh-plugin@0.1.1 (npm latest as of 2026-09-25) |
| Host | DeepSeek Harness 0.1.7-rc.2, profile desktop |
| Host protocol | session format v4 (hostProtocolVersion: 4) |
| OS / runtime | Windows 11 x64, Node 24.21.0 |
Bug Description | 问题描述
With the DSH plugin enabled, every turn that triggers recall fails. The run aborts with:
SessionFormatError: format v4 message requires a producer-owned source kind
DSH surfaces this to the user only as a failed turn ("本轮运行失败"); no assistant reply is produced.
Note this is not intermittent: MemOS' search/memory treats conversation_id as a relevance signal rather than a hard filter, so once the user_id namespace contains anything, recall returns hits for essentially any query and the plugin becomes unusable while active.
Steps to reproduce | 复现步骤
-
Install and enable the plugin as a DSH profile bundle, e.g.
dsh plugin --profile desktop add @memtensor/memos-cloud-dsh-plugin -
Set
MEMOS_API_KEY(and optionallyMEMOS_USER_ID) in~/.dsh/.env, and add the profile patch row:- id: memos-cloud name: "@memtensor/memos-cloud-dsh-plugin" config: apiKeyEnv: MEMOS_API_KEY
-
Ensure the namespace holds at least one memory. Easiest: complete one turn with recall disabled, so the write-back path populates it — or use a namespace that another MemOS client already filled.
-
Restart DSH and send any message.
Expected: recalled memories are injected as a <memos-recall> block and the turn completes.
Actual: the turn aborts with the error above.
The session log ends immediately after inbox consumption — no step/start follows:
{"type":"turn/start","seq":564,"data":{"turn":7}}
{"type":"agent/inbox/spliced","seq":565,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}Root cause | 根因
createRecallMessage() builds the injected recall message with the retired shared plugin source kind (published lib/index.js:897):
return createUserMessage({
source: {
kind: "plugin", // ← retired in the v4 session format
plugin: "memos-cloud",
form: "recall"
},
content: [{ type: "text", text }]
});That message is injected by installMemosLifecycle's agent/pre-step handler (step 1) via insertRecallBeforeDirectUser, so it enters the session as agent/inbox/spliced.inserted.
DSH's format-v4 admission then refuses the row:
// @deepseek-ai/dsh-session → lib/types/message-sources.js
function source(message) {
const value = message["source"];
if (!isSessionFormatJsonObject(value) || typeof value["kind"] !== "string"
|| value["kind"].length === 0 || value["kind"] === "plugin")
throw new SessionFormatError("format v4 message requires a producer-owned source kind");
}It is called by assertV4MessageSources for every declared durable message slot, and by assertV4SourceRowAdmission specifically for agent/inbox/spliced.inserted.
In v4 a plugin must attribute its own durable messages with a producer-owned kind. DSH's own v3→v4 converter encodes exactly that rule — unknown third-party plugin names become plugin:<name>, first-party ones keep their name:
function producerKind(plugin, role) {
if (plugin === "@deepseek-ai/dsh-system-prompt" && role === "system") return "system-prompt";
const renamed = Object.hasOwn(RENAMED_PRODUCERS, plugin) ? RENAMED_PRODUCERS[plugin] : void 0;
if (renamed !== void 0) return renamed;
if (RELEASED_SAME_NAME_PRODUCERS.has(plugin)) return plugin;
return `plugin:${plugin}`;
}Other third-party DSH plugins already follow this convention (e.g. dsh-session-sync writes with its own kind).
Because the row is rejected before it can be persisted, the error leaves no trace in the session log — only the failed turn is visible, which makes this hard to diagnose from the user side. A diagnostic hint on stderr would help.
Suggested fix | 建议修复
One line:
return createUserMessage({
source: {
- kind: "plugin",
+ kind: "memos-cloud",
plugin: "memos-cloud",
form: "recall"
},"plugin:memos-cloud" also passes admission, but a plugin's own producer kind is the convention.
Verified locally: with only that change, the recall message is admitted, persisted, and the turn completes. The injected row then reads:
{"type":"user/message","seq":433,"data":{"source":{"kind":"memos-cloud","plugin":"memos-cloud","form":"recall"}}}Workaround for users on DSH ≥ 0.1.7 | 用户侧临时方案
- Set
recallEnabled: falsein thememos-cloudconfig. The write-back path (/add/message) creates no session messages and keeps working, so memories still accumulate — only recall is lost. - Or apply the one-line patch above locally; note that reinstalling or upgrading the plugin overwrites it.
Additional notes | 补充
peerDependenciescurrently pin@deepseek-ai/dsh-session: ^0.1.0-rc.6 || ^0.1.2-rc.1. pnpm reportsIssues with peer dependencies foundat install time. DSH's own compatibility pre-check still lets the bundle load (it evaluates prerelease versions withincludePrerelease, and0.1.7-rc.2falls inside^0.1.2-rc.1), so the skew is silent until the runtime failure. Widening the range will not fix this bug by itself, but it would make the mismatch visible.- The write-back (
captureTurn//add/message) path is unaffected and does not need changes.
中文摘要
在 DeepSeek Harness 0.1.7-rc.2(会话格式 v4)上启用本插件后,每一轮都会失败,报 format v4 message requires a producer-owned source kind,用户侧只看到"本轮运行失败"。
根因:createRecallMessage() 注入召回消息时用的是已废弃的共享 source.kind = "plugin"(lib/index.js:897)。该消息经 agent/pre-step 钩子以 agent/inbox/spliced.inserted 进入会话,被 DSH v4 的持久消息准入直接拒绝。v4 要求插件用自己的 producer kind,DSH 自身的 v3→v4 迁移逻辑对第三方插件生成的就是 plugin:<name>,社区插件也都用自己的名字。
修复只需一行:kind: "plugin" → kind: "memos-cloud"。已验证改完后同一条召回消息可正常写入日志、回合正常完成。
临时方案:把配置里的 recallEnabled 设为 false,可保留记忆回写、仅失去召回。另外 peerDependencies 落后于当前 DSH 版本线,安装时 pnpm 已有 peer 警告。
Activity
Adamaik commentedon Sep 30, 2026
Confirming the source-kind diagnosis — and adding two more v4 mismatches, one of which contradicts the note above that the write-back path is unaffected.
The write-back path does need changes.
captureTurnreads the tool call identity from the releasedtool-resultcontent wrapper:v4 no longer emits that wrapper: the id rides the message itself (
message.toolCallId, mirrored onmessage.source.callId) andcontentholds plain blocks. SoresultCallIdsstays empty — and becausetoolCalls()emits a call only once its result has been correlated, tool-call-only assistant messages are dropped as well.Measured against a real DSH
0.2.0-rc.2session, ten completed turns withincludeToolMemory: true:So under the suggested
recallEnabled: falseworkaround memories do keep accumulating, but only bare user/assistant text: no tool calls and no tool results reach MemOS.Agent preset.
lifecycle.tstags writes and recall filters withsession.header.agentPreset, which holds only the initial value; current DSH keeps the live preset in theagentPresetsession projection, andSessionexposes no projection accessor. A session that switches preset mid-flight therefore tags every later memory with a stale preset.Fix: #176 addresses all three — the source kind, the tool-result shape, and the preset — while keeping the released shapes working, so a single build serves both runtime lines. It uses
plugin:memos-cloudrather than a bare kind only because that is exactly what DSH's own v3→v4 migration assigns this plugin id; happy to switch to the bare form if you prefer.pnpm test168 passed / 4 skipped,tsc --noEmitandpnpm buildclean. The pinned@deepseek-ai/dsh-*@0.1.2-rc.1devDependencies are the last line where both retired shapes were still valid, which is why the suite stayed green; the new tests spell out the current shapes directly instead of bumping them.补充确认了本 issue 的根因,另外还有两处 v4 不兼容(其中一处与上面"回写路径不受影响"的结论相反):工具的 call id 在 v4 里挂在消息本身(
message.toolCallId),不再包在tool-result内容块里,导致工具记忆实际抓到 0 条(真实会话十个回合实测,修复后 170 条),并且"只有工具调用没有文本"的 assistant 消息会被整条丢弃;另外 agent preset 应从agentPresetsession projection 读取,而不是 session header(header 只是初始值)。PR #176 一并修复这三处,并兼容旧格式。fix(dsh): read the current session format for recall sources and tool…
EF1874 commentedon Oct 30, 2026
Fixed on
mainby #176 and #180, which reachedmainin #185.#176 changes the recall source to
plugin:memos-cloudand reads both the old and current tool-result shapes. #180 widens the DSH peer range through 0.2.x. #176 usedRefs #174, so GitHub did not close this automatically.