Skip to content

fix(dsh): read the current session format for recall sources and tool results - #176

Merged
EF1874 merged 1 commit into
MemTensor:testfrom
Adamaik:fix/dsh-session-format-v4-compat
Oct 27, 2026
Merged

EF1874 merged 1 commit into
MemTensor:testfrom
Adamaik:fix/dsh-session-format-v4-compat

Conversation

@Adamaik

@Adamaik Adamaik commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

What this fixes

Three ways the plugin predates the current DSH session format (v4). The first is a hard failure; the other two are silent.

1. Recall injection is refused at admission — every turn fails

source: { kind: 'plugin', plugin: 'memos-cloud', form: 'recall' } is the retired wrapper. v4 refuses it (format v4 message requires a producer-owned source kind), and because the message is injected at step 1 of every turn, no turn completes. This is #174.

The plugin now names its own producer kind, plugin:memos-cloud, registered through the platform's merge-extensible MessageSourceMap. That value is what the platform's own v3→v4 migration already produces for this plugin id (producerKind('memos-cloud') → plugin:memos-cloud for any plugin outside its first-party allowlists), so newly written recall rows and rows migrated from older logs agree.

plugin:memos-cloud is accepted by both runtimes: dsh-llm@0.1.2-rc.1's createMessage does not validate source at all, and dsh-session@0.1.2-rc.1 only requires a non-empty kind string.

2. Tool memory silently collected nothing

captureTurn read the call identity from the released tool-result content wrapper. v4 retired that wrapper: the id now rides the message (message.toolCallId, mirrored on message.source.callId) and content holds plain blocks. So resultCallIds stayed empty, and since toolCalls() emits a call only once its result has been correlated, tool-call-only assistant messages were dropped as well.

Measured against a real 0.2.0-rc.2 session (ten completed turns, includeToolMemory: true):

tool messages captured
before 0 — every turn reduced to one user and one assistant message
after 170

The reader now accepts either shape, so the released wrapper keeps working.

3. Agent preset came from the header

lifecycle.ts tagged writes and recall filters with session.header.agentPreset, which holds only the initial value; current DSH keeps the live preset in the agentPreset session projection (Session exposes no projection accessor). A session that switched preset mid-flight tagged every later memory with the stale preset.

The preset is now read from the projection when the runtime exposes one, falling back to the header for runtimes that do not — and a projection that reports no preset leaves it absent rather than reviving the header.

Tests

  • pnpm test in packages/dsh: 168 passed, 4 skipped. Two existing assertions encoded the retired shapes and were updated; everything else passes unchanged, including the released-wrapper tool-result coverage.
  • tsc --noEmit clean; pnpm build clean.
  • New coverage: the current tool-result shape (call id on the message, plain content blocks), the projection preset winning over the header, and a projection reporting no preset not reviving the header.

Notes for reviewers

  • Kind choice. bug: DSH recall uses the retired "plugin" source kind — every turn fails on DeepSeek Harness 0.1.7 #174 suggests the bare memos-cloud; plugin:memos-cloud also passes admission. I picked the prefixed form because it is exactly what the platform's own migration assigns this plugin, so migrated history and new writes stay identical. Happy to switch to a bare producer kind if you prefer.
  • Pinned platform devDependencies. @deepseek-ai/dsh-* is pinned to 0.1.2-rc.1, the last line where both retired shapes were valid, so the suite cannot exercise the current format. This PR does not bump them (that would likely require rewriting much of the suite); the new tests spell out the current shapes directly instead. Bumping them — and widening the peerDependencies range past ^0.1.2-rc.1 — would surface the skew at install time. Happy to take that on in a follow-up if you want it in scope.

Refs #174

… results

Session format v4 admits only producer-owned message source kinds and refuses
the released `{ kind: 'plugin', plugin }` wrapper. The injected recall message
therefore failed admission, so every turn that reached recall ended before its
first step with:

  format v4 message requires a producer-owned source kind

The same format carries a tool result's call identity on the message itself and
no longer wraps it in a `tool-result` content block. Tool memory read only the
wrapper, so it collected no result ids at all — which also dropped
tool-call-only assistant messages, because a call is emitted only once its
result has been correlated. Measured against a real v4 session of ten completed
turns with `includeToolMemory` enabled: zero tool messages, each turn reduced to
one user and one assistant message.

The current agent preset also moved out of the session header into the
`agentPreset` session projection, leaving the header with only its initial
value. A session that switched preset mid-flight tagged every later memory with
the stale preset.

Read whichever tool-result shape the runtime carries, name the plugin's own
source kind through the platform's merge-extensible source map, and take the
preset from the projection when the runtime exposes one, falling back to the
header otherwise.

Refs MemTensor#174
@Adamaik

Adamaik commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

Since workflows from a fork need approval before they run, here is the dsh-compat matrix replicated locally against both legs.

current (@deepseek-ai/dsh-*@0.1.2-rc.1, the pinned devDependencies):

pnpm --filter @memtensor/memos-cloud-dsh-plugin test
→ Test Files 9 passed (9) | Tests 168 passed | 4 skipped (172)

legacy-0.1.0-rc.6 (same suite, DSH_TEST_RUNTIME pointed at a standalone install of the packages listed in dsh-compat.yml):

DSH_TEST_RUNTIME=/tmp/dsh-legacy-runtime pnpm --filter @memtensor/memos-cloud-dsh-plugin test
→ Test Files 9 passed (9) | Tests 168 passed | 4 skipped (172)

Also clean locally: tsc --noEmit -p packages/dsh/tsconfig.json and pnpm --filter @memtensor/memos-cloud-dsh-plugin build.

The legacy leg is the reason both readers keep the released shapes: readToolResult still accepts the tool-result wrapper, and agentPresetOf falls back to the header whenever the runtime exposes no sessionProjections service.

@EF1874
EF1874 merged commit 0e9580b into MemTensor:test Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants