fix(guidance): carry the runtime memory snapshot as this plugin's own message - #107
Merged
Merged
Conversation
… message The snapshot was contributed through systemPrompt.context(), so the host merged it into the shared dsh-system-prompt runtime-context projection. It therefore rendered as 'Context injection - @deepseek-ai/dsh-system-prompt' rather than as dsh-mnemon, and because DSH joins every context contribution into one string and diffs the whole, a memory write re-emitted sandbox:policy and approval:policy with it. At session start that projection is also composed ahead of skill-catalog, so across sessions a memory write invalidated roughly a thousand tokens of byte-stable catalog that had not changed. The snapshot now travels as a dsh-mnemon message appended last in the batch, and the pre-step listener is registered with prepend so it observes the fully assembled batch. The block remains a complete state superseding its predecessor; only the carrier changed. Supersede is keyed on the rendered text, which already carries the revision digest. The static mnemon:runtime-memory-protocol section is untouched.
This was referenced Aug 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
摘要 / Summary
mnemon:runtime-memorywas contributed throughsystemPrompt.context(), so the host merged it into the shared@deepseek-ai/dsh-system-promptruntime-context projection. Two consequences followed, and this changes only the carrier to fix both.Attribution. The snapshot rendered as
Context injection · @deepseek-ai/dsh-system-prompt, as though the harness produced it. Mnemon's own block carried only the 275-character[MNEMON]routing line. The snapshot is now adsh-mnemonmessage and is attributed correctly.Cache. DSH joins every
.context()contribution into one string and diffs the whole (RuntimeContext.project()overjoinContextSections(sections)), so a memory write re-emittedsandbox:policyandapproval:policyalong with it. At session start that projection is also composed ahead ofskill-catalog, so across sessions a memory write invalidated roughly a thousand tokens of byte-stable catalog that had not changed.The block remains a complete state superseding its predecessor — only the carrier changed. Supersede is keyed on the rendered text, which already carries the revision digest.
关联 Issue 或背景 / Related Issue or Context
Closes #111.
#111 is a verbatim refile of #106, which the template enforcer auto-closed nine seconds after submission: the body used level-2 (
##) headings while.github/workflows/issue-template-enforcer.ymlmatches sections only at level 3 (###), so every required section read as empty. The content is unchanged.Adjacent and complementary, not overlapping:
Why the ordering half is included
dsh-tool-skillships inside@deepseek-ai/dsh-baseand registers itsagent/pre-steplistener beforedsh-mnemondoes. Neither usedprepend, so both are pushed onto the listener list in load order. Cordis runs waterfall listeners outermost-first, so the innermost returns first and appends first — meaning the later-loading plugin lands earlier in the batch. Mnemon therefore sat ahead ofskill-catalog, and moving the snapshot to an own message without changing that would leave the catalog still behind it.The existing lifecycle listener is now registered with
prepend: truerather than adding a second listener. A second listener looked cleaner but the test harness keys listeners by event name (agentListeners.set(name, listener)), so a secondagent/pre-stepregistration silently replaces the first; a one-listener change avoids a harness rewrite. Running outermost also meansrecordTurnMessagesobserves the complete batch rather than a partial one.Why this matters disproportionately for local inference
Prefix caching is strictly positional: one changed token invalidates everything downstream, so a single memory write forfeits every byte behind it in the next session. On a hosted API that is a discounted line item. On local inference it is wall-clock time to first token, on hardware the user is also running the model on.
Measured on this setup, DSH against a local oMLX server (
DeepSeek-V4-Flash-8bit, Apple silicon):skill-catalogalone is ~1,073 tokens, so it straddles roughly a whole block that is reusable in principle and lost in practice whenever memory changed since the previous session.涉及区域 / Affected Areas
PR 类型 / PR Type
最新代码确认 / Latest Codebase Confirmation
main分支开发,或在提交前已 rebase 或合并最新main。 / I developed from the latestmain, or rebased or merged the latestmainbefore submitting.Branched from
e118094(Merge pull request #104, v0.3.4).同步命令 / Sync command:
git fetch origin && git rebase origin/mainAI 编码披露 / AI Coding Disclosure
使用的 AI 模型 / AI model used:
Claude Opus 5
使用的编码 Agent 工具 / Coding Agent tool used:
Claude Code
仓库规范检查 / Repository Rules
@deepseek-ai/*NPM 契约。 / I did not modify DSH source or point tsconfig at a DSH source checkout, and used only published@deepseek-ai/*NPM contracts.src/shared/contracts.ts为准,没有在两侧重复定义 wire DTO。 / The Client and Host boundary still usessrc/shared/contracts.tsas the single source for wire DTOs. This change touches only the Host-sidesrc/contracts.tsand adds no Client or wire DTO.lib/文件。 / I did not commit tokens, credentials, private memory, unredacted logs, or generatedlib/files.兼容性与数据安全 / Compatibility and Data Safety
No persistence format, storage path, RPC authority, or Provider credential is touched, and no configuration key is added or renamed.
HostAgentContext['on']andHostContextShape['on']gain an optional thirdoptionsparameter. Omitting it keeps the previous two-argument behaviour exactly, so this is source- and behaviour-compatible in both directions.applyAgentMemoryViewWakenow filters any inheritedmnemon:runtime-memorycontribution out ofassembly.contexts, so a profile upgraded in place stops emitting it through the shared projection rather than emitting it twice.memoryPromptTextis exported so the same{{}}neutralization applies to the injected text; interpolation is still never parsed.registerRuntimeMemoryContextandregisterAgentRuntimeMemoryContextare left as they are. Neither is wired in production — onlyregisterAgentMemoryViewContextis, atlifecycle.ts— and their tests still pass, so the diff stays minimal. Happy to migrate or remove them if you would rather they not diverge.mnemon:runtime-memory-protocolsection is unchanged and stays in the system prompt.本地验证 / Local Validation
执行的命令 / Commands run:
结果摘要 / Result summary:
All checks pass: typecheck clean; Vitest 511 passed, 1 skipped, 0 failed; deterministic double builds verified across 106 files; Headless profile activation verified with 35 total tools and 5 representative Mnemon tools; package contents verified (113 files); 10 Node-compatible public entries imported on v24.20.0;
publint --strictandattw --pack . --profile esm-onlyboth pass. Nothing skipped or failed.TZ=UTCis needed only becausetests/pack.spec.tsfails 11 tests on unmodifiedmainin timezones behind UTC, unrelated to this change (filed separately as #112):src/pack.tspinsmtime: new Date('1980-01-01T00:00:00.000Z')for reproducible archives, and fflate validates against local date parts, so in any negative-UTC-offset timezone that reads as 1979 and tripsdate not in range 1980-2099. Reproduced on unmodifiedmainate118094;TZ=UTCpasses 12/12. Details and a suggested one-line fix are in #112.测试改动 / Test changes
Existing assertions were updated where they pinned the old carrier or the old message count, and the pinning test now observes the Wake through the injected message instead of the context callback — covering both directions of supersede: unchanged text on a later step is not re-injected, and a new turn's Wake is.