dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Cross-session memory for DeepSeek Harness: layered notes, a 600-char always-injected core card, social/multi-person records, session-scoped suppression, and a built-in backup/restore and setup doctor.
⚠️ main 分支是开发中的版本,可能比 Release 新。当前最新发布为0.32.0(2026-10-24,兼容 DSH 0.2.1-rc.3),新增值班台。历史稳定版见 Release 列表。开发过程记录在过程/目录。
原名 dsh-memory,2026-10-20 更名。DSH(DeepSeek Harness)的跨会话记忆插件:三层记忆打底,外加常驻核心卡、三线状态、多人多群、会话级记忆管理。一句话:智能体是提线木偶,记忆是箱子里的旧剧本——上台前翻出本子,照着念,接着演;但剧本太多会压垮台子,所以还配了一张 600 字的常驻小卡,和一套「脏页可以当场撕掉」的机制。
同目录还有三份偏人读的材料,按需要看:《给人类的说明书》(怎么装)、《怎么用-给大肥鱼》(怎么用)、《前辈经验》(可看可不看的踩坑清单:本地小模型路由、怎么让 agent 真看见画面、发布避坑)。
能力 · 一句话 三层记忆 · 行为(人设/关系/笔记/日记/用户档案/便签)、深层(常言/树状日记/引导)、阵列(会话索引,可回读原文) 核心记忆 core.md · 600 字常驻小卡,每步缺就补地注入,优先级最高;字数从总预算里先扣,不撑爆上下文 三线状态 · 工作 / 陪伴 / 学习:词表检测 + 半衰期衰减 + 话题锚点;只给定性,不给数字 多人多群 · 人/群实体、身份归一(合并必须人确认)、四分区可见性、按场景注入,跨群默认不串台 会话记忆管理 · 脏片段可在当前会话屏蔽(数据不删)或删掉(先归档),也可整会话暂停注入 自动摘要 / 事实候选 · 压缩后/会话结束压摘要;事实只提案,accept 才写记忆 小模型路由 · 摘要/提炼可用独立 provider/model(本地 Ollama 等),失败自动回退主会话模型 值班台 · 侧栏一屏看三样心跳:QQ 观察器 / B站值班 / 记忆(每 30 秒自动刷新,只读文件、不花模型钱) 可移植性 · 默认备份到家目录、目录不可写自动回退、memory_doctor 一键自检
定位从「三层记忆」变成「三层 + 一张常驻卡 + 一套状态与隔离机制」:三层管「存在哪」,其余管「怎么用、不滥用、不串台」。
icon.png(512×512),构成主义风格:深蓝圆徽章里一块斜切的夜海,一条没有帆也没有桅杆的小船停在米白的海面上,船身印白色小冠徽记(取自头饰标志,裁掉缎带、缩小到 70%);画面上方是几枚若隐若现的小光点——看起来像星星,其实是深海光团透出水面的光。
设计约定:木偶只是名字里的比喻,图标不画木偶,只画「船在海上走,光在底下亮」。
session-start 登记会话;pre-step 注入行为记忆摘要(人设 / 关系 / 最近笔记 / 最近日记,默认 ≤1600 字符,内容不变不重复注入,子代理会话跳过;上限由 config.inject.maxChars 调)。注入频率可切换:默认每步注入;切到 first-step 后只在会话里注入一次(用 memory_control 的 action=inject 改)。关系条目超出注入预算时,保留最新的完整条目,被裁掉的旧条目自动转存到 deep/guide.jsonl(带日期标注,存完整释义,可用 memory_search layer=deep 找回);标了 important 的日记在注入时优先占位;turn-stopping 把本回合压成一条日记 + 一条阵列索引(不调用模型);disposed 会话结束时同步备份。tags 分类标签(见下文「标签」),注入时显示成 〔第一次·关系〕。user)和便签(memo)不进注入:不常用的资料放 user,跨会话交接/临时草稿放 memo,需要时 memory_search 按层查;memory_clear 可清空便签(清空前自动归档)。~/.dsh/memory/
├── behavior/ 第一层:当前台口的台词
│ ├── persona.md 人设(Markdown)
│ ├── core.md 核心记忆:600 字预算,每次注入(v0.22)
│ ├── relations.jsonl 关系:关键词 + 释义 + 使用次数(每轮注入,常用的放这里)
│ ├── user.jsonl 用户档案:兴趣爱好/习惯/背景资料(不注入,按需检索)
│ ├── memo.jsonl 便签:工作交接/临时草稿(不注入,可清空)
│ ├── archive/ 清空便签前的归档
│ ├── notes.jsonl 临时笔记
│ ├── sources.jsonl 出处:媒体资产的「谁说的/在哪看到的」(一行一条,可多出处)
│ └── diary/YYYY-MM.jsonl 日记:每回合压缩后的关键词/摘要/时间
├── deep/ 第二层:旧剧本细页
│ ├── sayings.jsonl 常言:关键词 → 细节解释
│ ├── guide.jsonl 引导:事件/关键词/时间 → 位置(不含内容)
│ └── tree/ 树状日记(按会话/月份归档)
├── array/ 第三层:整箱底稿
│ ├── index.jsonl 事件索引(时间/会话/类型/关键词)
│ └── sessions.jsonl 会话清单 → 原始 session.v4 文件路径(memory_recall 可按需解码读取)
└── state/ 三线状态(v0.23):工作 / 陪伴 / 学习的权重与轨迹
├── lines.json 当前三线权重 + baseline + 最近信号
├── lines-history.jsonl 轨迹:每条用户发言一条(谁把它推上去的)
└── lines-anchors.jsonl 话题锚点:相似话题回来时恢复当时权重
媒体不单独成层:写记忆时用 `images`/`audios` 带上本地图片或音频路径,文件按 sha256 复制进
`assets/`,条目里只记引用(图片 `"images": ["assets/<hash>.png"]`、音频 `"audios": [{"ref":
"assets/<hash>.mp3", "type": "audio"}]`)。同一份内容重复写入只存一份;`assets/index.json` 里
每个文件带 `type`(`image` / `audio`),老版本数据没有 `type` 的按图片处理。文字记忆会随复述漂移,图片不会。所以人物设定、截图、示意图这类「细节比描述重要」的东西, 可以让图片跟着条目一起存:
// memory_write 调用示例
{ "layer": "relation", "keyword": "大肥鱼两代形象", "meaning": "...", "images": ["D:\\Pictures\\v4.png", "D:\\Pictures\\v4pro.jpg"] }~/.dsh/memory/assets/,按 sha256 命名(去重),原文件删了也不影响;memory_search 命中带图条目时,会直接列出图片的绝对路径,方便打开查看;memory_stats 会显示图片/音频的数量与占用空间;assets/ 在快照里)。有些东西是声音本身,转成文字就丢了——语气、笑声、一段环境音、一句语音。写记忆时可以把音频 跟着条目一起存:
// 用 images 参数直接带音频(图片和音频可以混着传)
{ "layer": "relation", "keyword": "鲸鱼娘的第一次哼唱", "meaning": "...", "images": ["D:\\Voice\\hum.mp3"], "imageNames": ["鲸鱼娘哼唱"] }
// 也可以用专用参数:audios + audioNames + transcripts(转写与音频一一对应)
{ "layer": "note", "text": "深夜电台片段", "audios": ["D:\\Voice\\radio.m4a"], "audioNames": ["深夜电台"], "transcripts": ["……这里是深夜电台,今晚聊聊记忆。"] }~/.dsh/memory/assets/,按 sha256 命名(去重),原文件删了也不影响;条目里记在audios 字段(旧的 images 字段名不变);
transcripts(与音频一一对应);不传就自动读音频同目录同名 .txt(如 voice.mp3 → voice.txt)。转写存进条目的 audios[].transcript,会参与 memory_search;
memory_search 命中带音频条目时,会列出音频文件名、绝对路径和转写摘要([音1: …]);memory_stats 分开显示「N 张图片、M 段音频」和总体积;memory_image action=list 也按图片/音频分组列出,action=rename 同样支持音频;
assets/ 在快照里)。记一件事,除了「是什么」,还要记「谁说的、在哪看到的」——不然几个月后翻回来,只剩一句无从核对的话。 出处分两条路走:
1. 文字条目:直接用条目的 source + quote 两个字段,不新建文件:
// 「UP 主在视频里说过的话」:作者@平台ID + 原文/转述标记
{ "layer": "relation", "keyword": "炸盆子", "meaning": "…", "source": "蒙德砍壬@BV15X3n6mEWy", "quote": "verbatim" }
// 实在找不到出处、只能自己概括:标 paraphrase,或者干脆不写出处
{ "layer": "note", "text": "…", "source": "", "quote": "paraphrase" }source:一行短串「作者@平台 ID」(如 蒙德砍壬@BV15X3n6mEWy),也可以直接给 URL / 书名;quote:verbatim = 原文照抄、paraphrase = 我自己的转述(中英文都收,原文/转述 也会归一);paraphrase,不能把转述当原话存;relation / saying / user / memo / note / diary 都支持,更新同关键词条目时旧出处保留。2. 媒体资产(图片/音频/视频截图):登记在 behavior/sources.jsonl,一行一条,同一资产可挂多个出处:
// memory_source action=add
{ "action": "add", "asset": "大肥鱼炸盆子-1a2b3c4d.png", "author": "蒙德砍壬", "id": "BV15X3n6mEWy", "platform": "bilibili", "quote": "verbatim", "note": "视频 0:07 的截图" }
// 挂到某条记忆上(等价的写法:hash 给条目 hash)
{ "action": "add", "hash": "ac3e8ce05689de95", "author": "宫本", "id": "BV1wnnw6FENE" }platform,视频平台 → type: video);quote 标记只登记一次(返回「这条出处已经登记过」);memory_write 带 images/audios 且带 source 时,会自动把这条出处挂到每个资产上,不用再单独调一次;memory_image action=rename 会同步更新 sources.jsonl 里的资产名(和记忆条目引用一起);action=list(可按 platform / asset / author / quote 筛,时间倒序)、action=find(按作者/BV 号/URL/标题/资产名搜)、action=stats(条数、视频数、原文/转述占比、哪些资产还没有出处)。显示:memory_search 结果行尾和 memory_locate 详情里自动带出处标签,视频来源前面标 ▶:
behavior/relations.jsonl:29 {"kind":"relation", ...} #4e1dfe3548d3a008 〔▶视频出处:蒙德砍壬@BV15X3n6mEWy · 原文〕memory_image action=list 每个资产行尾也会显示已登记的出处;memory_stats 多一行「出处:N 条(视频 x;原文 y / 转述 z);资产有出处 a/b」。 出处不参与注入、不占注入预算——它是「核对信息」时才看的东西。
标签是人工给条目的分类,比自动关键词稳定,用来按主题捞东西。只接受固定词表(防同义词碎片):
称呼 身份 关系 工作 背景 记忆 插件 备份 会话 形象 图标 语言 常言 性格 第一次 视频 表情包 梗 生活 金钱 学习 待办 交接
memory_write 的 tags 参数:数组,或 "第一次, 关系" 这样的字符串(按逗号/顿号/分号/空格切);最多 6 个、每个 ≤16 字,自动去空白去重;relation / user / memo / note / saying / diary 支持;persona 是 Markdown 文件,不打标签;relation / saying 用同关键词更新时,旧标签自动保留并合并(新标签在前,合并结果也要过词表);tags 里写 -旧标签(或 !旧标签),更新时只删不加;〔第一次·关系〕关键词=释义,最近日记也带 〔…〕 前缀;memory_search 传 tag 按标签过滤(子串匹配):可以只给 tag,也可以和 query 一起取交集;useCount(最多折 +2.5)+ 标签命中(+2)+ important(+2);同分按时间新的在前;memory_stats 会列一条「标签:N 条带标签;第一次(3)、关系(2)…」,并附「词表外 x 个」审计(老数据可能残留);important: true 标重要事件:注入时优先占位(显示成 [10-14★]),不会被后写的日常挤掉。// 例子:给「第一次见面」打两级标签
{ "layer": "relation", "keyword": "初次见面(2026-10-14)", "meaning": "...", "tags": ["第一次", "关系"] }
// 例子:重要日记
{ "layer": "diary", "text": "第一次见面:2026-10-14 深夜,用户开口「我们能聊聊吗?」", "tags": ["第一次"], "important": true }人类记忆不是靠关键词查出来的,是被情境勾出来的。给条目加 cue(可选,relation / saying / diary 支持):
{
"layer": "relation",
"keyword": "初次见面(2026-10-14)",
"meaning": "...",
"tags": ["第一次", "关系"],
"cue": { "scene": "深夜在会话窗口第一次搭话", "mood": "好奇、拘谨", "when": "2026-10-14 23:53", "co": "MOSS、流浪地球3" }
}scene(情境:在哪、和谁、做什么)、mood(情绪)、when(时间锚)、co(共现:同一场景里还提到谁/哪件事),各截到 60 字;relation 是每轮都要看的台词,有些信息其实不用每轮看——那就放进两个不注入的抽屉:
层 · 存什么 · 例子 · 清空 user · 用户的长期资料:兴趣爱好、习惯、偏好、背景 · 「喜欢玩游戏」 · 不自动清空 memo · 跨会话交接、临时草稿、做到一半的活 · 「上次会话改了什么、还剩什么」 · memory_clear 清空(清空前归档到 behavior/archive/)
// 用户档案:不注入,需要时按层查
{ "layer": "user", "text": "喜欢玩游戏,平时对话用不上,聊到游戏时再提", "tags": ["背景", "游戏"] }
// 便签:交接用,做完可清
{ "layer": "memo", "text": "上一次会话:标签功能已做完,等重启验证", "tags": ["交接"] }
// 查:memory_search({ "layer": "user", "query": "游戏" })DSH 把原始会话存在 ~/.dsh/sessions/,本插件不复制、只建索引;要翻旧账时用 memory_recall 按需解码:
{ "session": "list" } // 列最近会话
{ "session": "f98c…", "query": "华硕", "limit": 20 } // 在那次会话里找关键词
{ "session": "f98c…", "limit": 30 } // 取最近 30 条对话会话文件 session.v4.jsonl.zstd 是多帧 zstd 拼接,memory_recall 按帧魔数切开逐帧解压,取用户/助手文本;不会把整个文件塞进上下文。
触发点只有三个,它们都只是「排队」,真正落盘受频率限制:
compaction/end / compaction/summary 事件,或 compress 工具调用;另有「上下文骤降」兜底);disposed);memory_backup(不受频率限制,随时能压一份)。频率与清理(config.backup):
minIntervalHours(默认 24):自动备份距上次不足这个间隔就跳过,所以一天最多一份;手动备份不受限。keepDays(默认 30):每次备份成功后删掉 30 天前的 snapshot-*(按快照名里的时间戳判断,不靠 mtime)。behavior(默认 true):行为记忆也进快照——以前只备 deep/array,guide.jsonl 里 300+ 条指针指向 behavior/ 却全裸,恢复时等于断链。config(默认 true):config.json + state/runtime.json 一起放进快照的 config/。sessions(默认 false 时跳过):原始会话文件体积最大,关掉能省很多空间。备份目录默认 ~/.dsh/memory-backups/snapshot-YYYYMMDDHHmmss/(v0.25 起;配置可改,目录不可写会自动回退到 /backups 并提示),结构:deep/ array/ behavior/ assets/ config/ sessions/。
Web UI → 设置下有两个独立页面(settings.section 槽,排在「表情包」后面):
页面 · 里面有什么 记忆备份(order 26) · 当前状态、备份策略(自动备份开关 / 最小间隔 / 保留天数 / 备份内容 / 目录)、上下文压缩、待确认事实;保存 / 立即备份一次 / 刷新 吸在页面顶部,不用滚到底 自动摘要(order 27) · 自动摘要设置(总开关 / 触发条件 / 保留上限 / 规模限制)+ 已有摘要列表;保存自动摘要设置 / 刷新 同样在顶部
拆页原因:原来一页塞了备份、压缩、自动摘要、事实候选,越加越长,设置按钮被挤到最下面。settings.section 本身就是「一个 list 条目 = 一个设置页」,所以拆页不需要改壳层、也不需要新路由。
面板和 memory_backup / memory_control 改的是同一份 config.json.backup,没有第二套状态。数据走宿主本机回环接口 /dsh-puppet-memory-api(GET ?action=status|autosummaries|compaction|fact-candidates、POST ?action=save|backup|delete-autosummary|accept-fact|reject-fact,非回环请求一律 403),不经过模型、不进上下文;宿主端逐项校验,非法值整次拒绝、不写半份配置。
面板里还有一张「上下文压缩」卡片(v0.14 起):累计压缩次数、最近一次时间、最近一次触发来源、自动摘要次数;v0.18 起加「最近压缩记录」列表(时间、手动 /compact 还是自动、压掉几条、省了多少 token、摘要模型)。**记录只来自官方 compaction/* 会话事件,按 compactionId 去重——重启、会话重放、第三方压缩插件都不影响计数,也不会重复计数(v0.17 之前靠扫描 `compress` 工具调用,重启后 `seenEvents` 清空会把历史再数一遍,已知 bug,v0.18 已移除该路径)。压缩本身由官方 `@deepseek-ai/dsh-compaction-basic` 引擎完成(2026-10-21 之前也可能是 `billion-context` 的 `compress` 工具),木偶记忆不参与压缩、不改会话历史**。
第三方压缩控制面(可选):装了 dsh-compact-suite 这类负责「阈值滑块 / 摘要模型改派 / 一键压缩」的插件时,压缩卡片会标注「阈值/摘要模型由 dsh-compact-suite 控制」;它卸载或停更只影响这些控制功能,压缩记录与历史不受影响(记录层只认官方事件)。v0.18 不自己实现阈值滑块——那要改 preset 里 isolate 的引擎实例配置,属于 hack,留给第三方控制面插件;引擎本身归 DSH 官方。
只读契约(给别的插件/面板消费):GET /dsh-puppet-memory-api?action=compaction
{
"ok": true,
"provider": "官方 compaction 引擎(deepseek-account/deepseek-flash)",
"count": 3, "lastAt": "2026-10-21T14:20:27.197Z", "lastSession": "session-…",
"summaries": 2, "lastSummaryAt": "…", "lastSummaryTurns": 3,
"recordsTotal": 3,
"recent": [
{ "id": "215ecbd7-…", "at": "2026-10-21T13:49:25.894Z", "session": "session-…",
"trigger": "command", "commandId": "cmd-…", "items": 5, "savedTokens": 1782,
"provider": "deepseek-account", "model": "deepseek-flash", "maxTokens": 65536,
"inputTokens": 394, "outputTokens": 1086 }
],
"suite": "dsh-compact-suite"
}trigger 取 command(人手动发 /compact)或 auto(引擎按阈值自动压);suite 为空串表示没探测到第三方控制面。字段全部来自 compaction/summary 的 shadowedSeqs / shadowedTokenCount / provider / model / usage,不猜、不估算。
下面是「自动摘要」卡片(v0.16 起):列出最近的 kind:"auto-summary" 日记条目(时间、触发原因、覆盖回合数、会话尾号、正文前 400 字),每条可单独删除。删除只动 behavior/diary/*.jsonl 里的这一条 + deep/guide.jsonl 里同 hash 的索引行,不碰原始会话、不碰手写日记;删掉的回合会重新变成「未摘要」,下次触发摘要时会再摘一遍。列表最多 100 条(默认 20)。
再下面是「待确认事实」卡片(v0.19 起):三期提炼出来的候选,逐条显示层(用户档案 / 关系记忆 / 常言)、关键词、正文、理由和提炼时间,每条两个按钮——采纳(写进对应记忆层)或拒绝(只改状态、什么都不写)。没有候选时卡片会提示去跑 memory_facts action=extract。这是三期的“人自己点”入口;不点就什么都不会写。
API 对应三个动作:GET ?action=fact-candidates&limit=(列表 + pending/accepted/rejected 统计)、POST ?action=accept-fact {id}、POST ?action=reject-fact {id};缺 id → 400,找不到该候选 → 404,非回环 → 403。
实现形态是标准的 Client bundle:package.json 里 exports["./client"] + dsh.client(platform: "web"),浏览器侧 window.__ModuleLoader__.load({ id: "dsh-puppet-memory", factory }) 只负责登记 settings.section,样式/监听放在 apply 里用 ctx.effect 回收。改完 client.js 需要重启 DSH 才会生效。
每个回合已经有机械压缩的 kind:"turn" 日记(user/assistant 各截 240 字)。自动摘要(默认关闭)在下面三个时机,把还没摘过的回合压成一条 kind:"auto-summary" 日记。默认用当前会话的模型;v0.26 起可以在 autoSummary.model / facts.model 指定独立路由(比如本地 Ollama 的小模型),调用失败会自动回退主会话模型:
turn-stopping 里本回合是否有原生 compaction/end / compaction/summary 事件(官方 dsh-compaction-basic 引擎的做法,2026-10-21 起是主力)、或调用了 compress 工具(bili 时代的做法,它不会 append 原生事件 —— 实测 16 个会话日志里 compaction 事件 0 条);session/event 的 compaction/end 监听作兼容备用;另有「上下文骤降 ≥35%、且原先超过 3 万字」兜底。各条路都会打一行 diag,方便事后核对;disposed);memory_control action=summarize,force 跑一次,不受 enabled 影响。防止污染记忆的红线: