🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

持久记忆 tluoluo/dsh-persist

DeepSeek Harness
tluoluo80 天前有提交已提供中文文档扫描:含敏感能力

给每个对话装上不会失忆的长期记忆

28.2/100
综合分
2
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/9/19 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add tluoluo/dsh-persist
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么
dsh 原生插件 · chat
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 80 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 74 处,展示前 56 条)
  • 读取密钥环境变量
    src/embedder.ts:18
    const apiKey = process.env.SILICONFLOW_API_KEY ?? ''
  • 发起网络请求
    src/embedder.ts:52
    const res = await fetch(ENDPOINT, {
  • 写入本地文件
    src/index.ts:532
    writeFileSync(vaultFile, md, { mode: 0o600 })
  • 读取本地文件
    src/index.ts:541
    if (!existsSync(vaultFile)) return { ok: false, error: `vault.md not found at ${vaultFile} — run vault export first` }
  • 读取本地文件
    src/index.ts:542
    const text = readFileSync(vaultFile, 'utf8')
  • 读取本地文件
    src/memory-store.ts:45
    if (!existsSync(dirname(this.file))) {
  • 写入本地文件
    src/memory-store.ts:46
    mkdirSync(dirname(this.file), { recursive: true, mode: 0o700 })
  • 读取本地文件
    src/memory-store.ts:53
    const raw = readFileSync(this.file, 'utf8')
  • 写入本地文件
    src/memory-store.ts:63
    renameSync(this.file, backup)
  • 写入本地文件
    src/memory-store.ts:85
    writeFileSync(tmp, JSON.stringify(map, null, 2), { mode: 0o600 })
  • 写入本地文件
    src/memory-store.ts:87
    renameSync(tmp, this.file)
  • 删除本地文件
    src/memory-store.ts:89
    try { rmSync(tmp, { force: true }) } catch { }
  • 读取本地文件
    src/profile-store.ts:71
    if (!existsSync(file)) {
  • 读取本地文件
    src/profile-store.ts:74
    if (!existsSync(this.root)) mkdirSync(this.root, { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/profile-store.ts:74
    if (!existsSync(this.root)) mkdirSync(this.root, { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/profile-store.ts:75
    writeFileSync(file, template, { mode: 0o600 })
  • 读取本地文件
    src/profile-store.ts:79
    return readFileSync(file, 'utf8')
  • 读取本地文件
    src/profile-store.ts:88
    if (!existsSync(this.root)) mkdirSync(this.root, { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/profile-store.ts:88
    if (!existsSync(this.root)) mkdirSync(this.root, { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/profile-store.ts:89
    writeFileSync(this.fileFor(kind), content, { mode: 0o600 })
  • 读取本地文件
    src/session-store.ts:190
    if (!existsSync(this.root)) mkdirSync(this.root, { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/session-store.ts:190
    if (!existsSync(this.root)) mkdirSync(this.root, { recursive: true, mode: 0o700 })
  • 读取本地文件
    src/session-store.ts:227
    return readFileSync(this.memoryFile(sessionId), 'utf8')
  • 写入本地文件
    src/session-store.ts:236
    mkdirSync(dirname(file), { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/session-store.ts:247
    mkdirSync(dirname(file), { recursive: true, mode: 0o700 })
  • 读取本地文件
    src/session-store.ts:262
    const parsed = JSON.parse(readFileSync(this.injectFile(sessionId), 'utf8')) as Record<string, unknown>
  • 读取本地文件
    src/session-store.ts:288
    const backups = readdirSync(dirname(file)).filter(n => n.startsWith('inject.json.corrupt-'))
  • 写入本地文件
    src/session-store.ts:289
    if (backups.length === 0) renameSync(file, `${file}.corrupt-${Date.now()}`)
  • 写入本地文件
    src/session-store.ts:301
    mkdirSync(dirname(file), { recursive: true, mode: 0o700 })
  • 读取本地文件
    src/session-store.ts:354
    return readFileSync(this.projectMemoryFile(projectKey), 'utf8')
  • 写入本地文件
    src/session-store.ts:363
    mkdirSync(dirname(file), { recursive: true, mode: 0o700 })
  • 读取本地文件
    src/session-store.ts:391
    if (!existsSync(file)) {
  • 删除本地文件递归
    src/session-store.ts:506
    rmSync(dir, { recursive: true, force: true })
  • 删除本地文件
    src/session-store.ts:560
    try { rmSync(tmp, { force: true }) } catch { }
  • 删除本地文件递归
    src/smoke-semantic.ts:32
    rmSync(dir, { recursive: true, force: true })
  • 删除本地文件递归
    src/smoke-semantic.ts:73
    rmSync(dir, { recursive: true, force: true })
  • 读取本地文件
    src/smoke.ts:51
    check('builtin: default USER.md materialized', existsSync(join(dir, 'USER.md')), true)
  • 读取本地文件
    src/smoke.ts:69
    check('builtin: MEMORY.md default materialized', existsSync(join(dir, 'MEMORY.md')), true)
  • 删除本地文件递归
    src/smoke.ts:90
    rmSync(dir, { recursive: true, force: true })
  • 删除本地文件递归
    src/smoke.ts:161
    rmSync(vaultDir, { recursive: true, force: true })
  • 写入本地文件
    src/smoke.ts:411
    mkdirSync(corruptDir, { recursive: true })
  • 写入本地文件
    src/smoke.ts:412
    writeFileSync(join(corruptDir, 'inject.json'), '{ not json')
  • 读取本地文件
    src/smoke.ts:415
    !readdirSync(corruptDir).some(n => n.startsWith('inject.json.corrupt-')), true)
  • 读取本地文件
    src/smoke.ts:418
    readdirSync(corruptDir).some(n => n.startsWith('inject.json.corrupt-')), true)
  • 删除本地文件递归
    src/smoke.ts:420
    rmSync(sessionDir, { recursive: true, force: true })
  • 写入本地文件
    src/vault-store.ts:51
    if (!existsSync(base)) mkdirSync(base, { recursive: true, mode: 0o700 })
  • 读取本地文件
    src/vault-store.ts:51
    if (!existsSync(base)) mkdirSync(base, { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/vault-store.ts:63
    try { chmodSync(join(base, 'vault.db'), 0o600) } catch { }
  • 写入本地文件
    src/vault-store.ts:323
    writeFileSync(this.vaultMdPath(), md, { mode: 0o600 })
  • 读取本地文件
    src/webui.ts:38
    if (!existsSync(base)) mkdirSync(base, { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/webui.ts:38
    if (!existsSync(base)) mkdirSync(base, { recursive: true, mode: 0o700 })
  • 读取本地文件
    src/webui.ts:56
    if (!existsSync(file)) return ''
  • 读取本地文件
    src/webui.ts:58
    return readFileSync(file, 'utf8')
  • 写入本地文件
    src/webui.ts:68
    writeFileSync(join(base, name), content, { mode: 0o600 })
  • 读取本地文件
    src/webui.ts:661
    const text = existsSync(vaultFile) ? readFileSync(vaultFile, 'utf8') : ''
  • 读取本地文件
    src/webui.ts:661
    const text = existsSync(vaultFile) ? readFileSync(vaultFile, 'utf8') : ''
扫描于 2026/11/3(1 天前) · 范围 全包扫描(包内未声明入口) · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-agent@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-llm@deepseek-ai/dsh-session@deepseek-ai/dsh-tools
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 按会话记笔记
  • 选择性注入记忆
  • 语义搜索与自动检索

为 DeepSeek Harness 提供持久记忆:按会话记笔记、选择性注入、项目记忆、语义 Vault 搜索加自动检索。

README

由 DeepSeek 最新模型翻译生成
dsh-persist 给 DeepSeek Harness 的 agent 装上"不会失忆"的长期记忆。 Persistent memory for DeepSeek Harness agents. 它解决什么问题 默认情况下 DSH 的 agent 换对话就失忆——上下文一关,什么都不记得。dsh-persist 把记忆做成文件系统上的分层结构,每个对话按需注入: | 层 | 存储 | 对标 | 怎么被读取 | | 用户画像 + 长期记忆 | USER.md / MEMORY.md | 长期记忆(LTM) | 每对话可选注入 | | 关键记忆 | memory.json | 速查卡片 | 每对话可选注入;工具读写 | | 对话记忆 | sessions//memory.md | 工作记忆 | 只注入本对话 | | 项目记忆 | projects//memory.md | PARA 的 Project 层 | 按项目勾选注入 | | Vault 语义记忆 | vault.md + vault.db | Zettelkasten 卡片盒 | 按需语义召回(工具或「自动检索」开关,不静态注入) | 特性 - 对话独有记忆——每个对话一本"不会丢的笔记本",其他对话看不到、不注入 - 选择性注入——勾选什么才注入什么;新对话默认零注入,不占上下文
展开完整文档(README 剩余 1 节)
dsh-persist

给 DeepSeek Harness 的 agent 装上"不会失忆"的长期记忆。
Persistent memory for DeepSeek Harness agents.

它解决什么问题

默认情况下 DSH 的 agent 换对话就失忆——上下文一关,什么都不记得。dsh-persist 把记忆做成文件系统上的分层结构,每个对话按需注入:

| 层 | 存储 | 对标 | 怎么被读取 |
| 用户画像 + 长期记忆 | USER.md / MEMORY.md | 长期记忆(LTM) | 每对话可选注入 |
| 关键记忆 | memory.json | 速查卡片 | 每对话可选注入;工具读写 |
| 对话记忆 | sessions//memory.md | 工作记忆 | 只注入本对话 |
| 项目记忆 | projects//memory.md | PARA 的 Project 层 | 按项目勾选注入 |
| Vault 语义记忆 | vault.md + vault.db | Zettelkasten 卡片盒 | 按需语义召回(工具或「自动检索」开关,不静态注入) |

特性

- 对话独有记忆——每个对话一本"不会丢的笔记本",其他对话看不到、不注入
- 选择性注入——勾选什么才注入什么;新对话默认零注入,不占上下文
- 项目记忆互通——同一工作目录共享项目经验;命名项目可把同目录的多项目分开
- Vault 语义检索——配一个免费 key 就能"按意思"找记忆;不配自动降级为关键词搜索
- 自动召回(三态模式)——记忆 tab 里选择自动检索模式:关闭(默认)/ 智能(纯本地规则过滤闲聊,提到历史/项目/主题才查,零外发)/ LLM 判断(调用模型判断是否需要检索,更准但会把消息发给模型提供商,超时/失败自动降级为智能)
- 记忆 tab——对话顶部可视化编辑 + 注入配置 + 实时预览(所见即所得);tab 右上角的「打开记忆管理页」在新标签打开完整管理页(全部会话/项目/Vault/全局文件)
- 全部纯文本——~/.dsh-memory/ 下每个文件人类可读可改,随时备份、迁移、导出

快速上手

dsh plugin --profile web add dsh-persist # 1. 安装
2. 重启 DSH(dsh --profile web)
3. 打开对话 → 顶部「记忆」tab → 勾选要注入的块

想让 agent 记住什么,直接对它说"记住这个",它会自动写入本对话记忆。
要开语义搜索:在 硅基流动免费申请 key,设置环境变量
SILICONFLOW_API_KEY=... 后重启即可(不配也能用,自动降级为关键词搜索)。

怎么装?

曾用名 dsh-memory(npm 名已被占用,故改名 dsh-persist)。存储路径 ~/.dsh-memory/、路由 /dsh-memory/ 不变,旧数据无需迁移。

环境要求:Node.js >= 22.6(推荐 24+;测试用 --experimental-strip-types 自 22.6 起可用)。Vault 层用内置 node:sqlite:23.4+ 默认可用,22.6–23.3 需 --experimental-sqlite 标志;更低版本插件照常加载,仅 Vault 工具自动降级禁用(记忆/注入/UI 不受影响)。宿主为 DeepSeek Harness(需提供 tools / systemPrompt / webServer / sessions / agents 服务与 conversation.view UI 槽位)。

dsh-persist 是一个标准 DSH 组合包(bundle):声明了 dsh.bundle,通过 dsh plugin 装进任意 profile。@deepseek-ai/ 是 optional peer 依赖,由 DSH 宿主在运行时提供,npm 不会、也不需要安装它们。

从 npm 安装(自带构建好的 lib/,无需构建):

装进你的 web profile(首选)
dsh plugin --profile web add dsh-persist
或装进别的 profile
dsh plugin --profile demo add dsh-persist

安装后重启(dsh --profile web),插件即生效。

从 GitHub 安装(会拉源码并跑 prepare 构建,见下文):

dsh plugin --profile web add github:tluoluo/dsh-persist

从 git 安装时 pnpm 在首次 add 后可能需要你授权运行 prepare 构建脚本:把 dsh 打印的包键复制进 profile 的 pnpm-workspace.yaml 的 allowBuilds,再重跑 add(详见 DSH 官方文档 publish.md)。

可选环境变量(全部非必需,装完就能用):

- SILICONFLOW_API_KEY=... → 可选,开启"语义搜索"。bge-m3 会把记忆转成向量、按意思找(比纯关键词更懂你)。不配也能用:Vault 记忆自动降级成关键词搜索,功能不受影响。在硅基流动免费拿 key,然后设这个环境变量即可。它是本插件唯一可选的"外挂大脑",用来让搜索更聪明,但不是必需。
- DSH_MEMORY_INJECT=0 → 关闭自动注入(默认开启)
- DSH_MEMORY_ALLOW_REMOTE=1 → 允许非本机(非 loopback)访问记忆 API(默认一律 403;仅当 DSH web server 绑定 0.0.0.0 时需要,请自行评估隐私风险)
- DSH_MEMORY_AUTO_VAULT=0 → 强制关闭所有对话的自动语义检索;=1 → 强制开启(不设置则按每个对话记忆 tab 里的「自动检索」模式,默认关。注意:=1 且未显式设置 GATE 时一律按智能门控,会话 tab 里选的 LLM 档不生效)
- DSH_MEMORY_AUTO_VAULT_GATE=heuristic|llm|off → 全局覆盖每个对话的自动检索模式(heuristic 智能门控:纯本地规则过滤闲聊,零成本、零外发、零延迟;llm 调用模型判断(模型见 DSH_MEMORY_JUDGE_MODEL),任何失败自动降级为 heuristic;off 关闭门控——每回合都检索)。不设置则按各对话记忆 tab 的选择——注意 tab 的「关闭」= 不检索,与这里的 off 含义不同:off 只在显式设置该变量时生效。词表是启发式的,存在已知边界(如无触发词的短消息不查、长闲聊可能漏网),属设计取舍
- DSH_MEMORY_JUDGE_MODEL=provider/model → LLM 判断模式用的模型(默认 deepseek-official/deepseek-chat,便宜快速;格式 provider/model)
- DSH_MEMORY_JUDGE_TIMEOUT_MS=5000 → LLM 判断超时(默认 5000ms,超时降级为智能模式)
- DSH_MEMORY_AUTO_VAULT_NAMESPACES=user,dsh-persist → 限制自动检索只查这些 namespace(默认查全部 namespace,含项目归档)

隐私提示:开启语义搜索(SILICONFLOW_API_KEY)后,每条用户消息和记忆内容都会发送给硅基流动(SiliconFlow)做向量化;自动检索同样如此。LLM 判断模式还会把当前消息发送给 DSH_MEMORY_JUDGE_MODEL 指定的模型提供商做"是否需要检索"的判断。介意请勿配置 key,或设 DSH_MEMORY_AUTO_VAULT=0 关闭自动检索(手动 vault search 仍可用)。

一句话:装完 dsh plugin add 重启就有记忆功能;想要更聪明的语义搜索,再去硅基流动拿个免费 key 配上。

装完怎么用(新手三步)

1. 重启 DSH(dsh --profile web,别用还在跑的旧进程),插件即生效。
2. 打开 Web 界面,进入任一对话,会话顶部(轨迹 tab 右边)会出现一个 「记忆」tab——这里就是你本对话的记忆和注入开关。
3. 想让 agent 记住什么,就在对话里直接说,agent 会自动通过 memory 工具写入本对话记忆;或你在记忆 tab 里手动编辑。默认不注入任何记忆(不占上下文),你在记忆 tab 勾选后才把对应记忆每轮放进上下文。

可选:想用语义搜索,先在硅基流动拿到免费 key,设置环境变量 SILICONFLOW_API_KEY=... 再重启,Vault 层就从关键词搜索升级为语义搜索(README 不替你存 key,请放在 DSH 宿主能读到的环境里)。

安全提示:/dsh-memory/api/ 只允许 loopback 访问(非本机请求返回 403,除非显式设置 DSH_MEMORY_ALLOW_REMOTE=1)。记忆内容包含个人身份信息,请勿在共享网络中开放。

开发构建

使用者不需要构建——发布包自带 lib/,dsh plugin add 直接装。

外部开发者在自己环境 clone 后,npm install 会自动运行 prepare(tsdown 纯转译,不依赖 @deepseek-ai 类型即可产出 lib/),因此能自包含地构建出可用的产物:

npm install # 自动跑 prepare → lib/index.js + lib/client.js
npm run prepare # 显式重建自包含产物(host 用 tsdown.host.config.ts,client 用 tsdown.config.ts)
npm test # smoke 测试(node --experimental-strip-types src/smoke.ts)

prepare 只做转译(不 type-check):它把源码里对 @deepseek-ai/ 的 import type 全部擦除,产物运行时只保留对宿主提供的两个 import(@deepseek-ai/dsh-tools.defineTool 与 @deepseek-ai/dsh-llm.createUserMessage)——所以无宿主类型也能构建,产物由 DSH 宿主持有并加载。

维护者(在 DSH 宿主树内、junction 到宿主依赖以获得 @deepseek-ai 类型的场景)可跑全量构建,额外产出 .d.ts 并做完整类型检查:

npm run build # typecheck + typecheck:client + build:host + bundle + dts

语义检索的端到端测试在 src/smoke-semantic.ts(需要真实 SILICONFLOW_API_KEY),不包含在 npm test 里——需要时手动运行:
- PowerShell:$env:SILICONFLOW_API_KEY=...; node --experimental-strip-types src/smoke-semantic.ts
- bash:SILICONFLOW_API_KEY=... node --experimental-strip-types src/smoke-semantic.ts

注意:lib/ 已 gitignore;运行中的 DSH 需重启才加载新 host 代码(client bundle 刷新页面即可)。

怎么用?

工具动作(model 调用)

memory 工具的 scope 参数决定写/读到哪:

- scope=conversation(默认)→ 本对话记忆:add 追加一条笔记,list/search 读全文
- scope=project → 当前工作目录的项目记忆(同目录对话互通)
- scope=global → 全局 keyed 记忆:add/get/search/delete(按 key)

| 动作 | 参数 | 作用 |
| add | scope、content(global 还需 key) | 存一条记忆 |
| get / search / delete | scope、key | global 的 keyed 操作 |
| list | scope | 读对话/项目记忆全文 |
| profile | profileKind、profileOp、content | 读写全局用户画像(USER.md/MEMORY.md) |
| vault | vaultOp、content/query、namespace | 语义存取:add 存、search 查、list 列、export 导出、import 导回(带 namespace 隔离) |

记忆文件(全部人类可读可改)

| 文件 | 内容 | 怎么改 |
| ~/.dsh-memory/sessions//memory.md | 本对话记忆 | 记忆 tab 里编辑,或 memory add |
| ~/.dsh-memory/sessions//inject.json | 本对话注入配置 | 记忆 tab 里勾选 |
| 所有历史会话 | 各对话记忆 + 注入配置 | /dsh-memory/ 管理页「会话记忆」tab:浏览 / 编辑 / 删除(记忆 tab 只能编辑当前对话) |
| ~/.dsh-memory/projects//memory.md | 项目经验 | memory add(scope=project),或直接编辑 |
| ~/.dsh-memory/USER.md / MEMORY.md | 全局池 | /dsh-memory/ 页面或直接编辑 |
| ~/.dsh-memory/memory.json | 全局 keyed | 直接编辑 JSON |
| ~/.dsh-memory/vault.md | Vault 语义记忆 | 工具增删(memory(vaultOp="add"/"delete"))自动同步此文件;手改文件后 memory(vaultOp="import")(或管理页「Vault 同步」)写回数据库 |

例子

对话 A(工作目录 /work/projA):
- 记忆 tab 勾选:用户画像 + 本对话记忆 + 项目记忆(projA)
- agent 每轮自动注入这三块;对话 B 看不到对话 A 的记忆

agent: 用户说他喜欢用空格缩进 agent: memory(action="add", content="用户偏好空格缩进") # 写入对话 A 的记忆

agent: 用户问"你记得我喜欢怎么缩进吗"(同一对话) agent: memory(action="list") → 读回对话 A 的记忆

agent: 记录项目经验 agent: memory(action="add", scope="project", content="构建脚本在 build.ps1") 写入 /work/projA 的项目记忆,同目录其他对话也能勾选注入

agent: 全局 keyed 记忆(跨对话共享) agent: memory(action="add", scope="global", key="user-name", content="小明")

技术设计(对应 DSH 课程)

| 部分 | 实现 | 课程模块 |
| 存储层 | MemoryStore / ProfileStore / SessionMemoryStore,文件 + 原子写入 | 模块 ⑤ 方案 A |
| Vault 层 | VaultStore + embedder(SQLite + bge-m3) | 模块 ⑤ 方案 C |
| 工具层 | ctx.tools.register(defineTool({...})),scope 三态 | 模块 ③④ |
| 选择性注入 | ctx.systemPrompt.context() 按会话读 inject.json 渲染(子 agent 沿 parentSession 继承所属对话) | 模块 ⑥ |
| 多 agent 隔离 | namespace + 对话/项目两级隔离 | 模块 ⑦ |
| Client UI | conversation.view 槽位 id memory order 20(轨迹右边),tsdown 自包含 bundle | 模块 ⑧ |
| 混合检索 | 有向量走语义、无向量走关键词,合并排序 | 模块 ⑤ |
| 插件结构 | name / inject / apply 三件套 | 模块 ② |

注意:改代码后需要重启 DSH 才会加载新 host 代码(lib/ 更新了,但运行中的进程持有旧模块;client bundle 刷新页面即可)。

故障排查

| 现象 | 处理 |
| 记忆文件损坏(memory.json / inject.json 无法解析) | 插件会自动把损坏文件备份为同目录下 .corrupt- 并重置为空,控制台会打印备份路径——从备份找回内容即可 |
| 记忆 API 返回 403 | loopback 守卫生效(默认只允许本机)。确认 DSH 绑定 127.0.0.1;确实需要远程时设置 DSH_MEMORY_ALLOW_REMOTE=1(自行评估隐私风险) |
| 想完全关闭自动注入 | DSH_MEMORY_INJECT=0 后重启 DSH |
| 改 host 代码不生效 | DSH 进程持有旧模块,需要重启 DSH(client bundle 刷新页面即可) |
| 注入内容过长 | 静态记忆块(USER/MEMORY/对话/项目)有 100 行截断,超出的部分不会注入——请在 /dsh-memory/ 页面精简对应文件。Vault 无静态注入:只经「自动检索」topK=3 或工具按需召回,不受行数限制 |
| 对话/项目记忆文件越来越大 | 注入有 100 行截断,但磁盘上的 memory.md 会持续增长——建议定期(如每个里程碑)用 memory 工具或直接编辑精简,过时条目移入 Vault 归档 |
| 手改 vault.md 后内容丢失 | 工具增删(memory(vaultOp="add"/"delete"))会全量重写 vault.md(自动同步镜像)。手改请在无工具操作的间隙进行,改完立即 memory(vaultOp="import") 或管理页「Vault 同步」写回数据库 |

贡献
- 代码结构:src/ 顶层 = 宿主逻辑 + 公共纯函数,src/client/ = 浏览器侧;两套产物独立构建(host 用 tsdown.host.config.ts,client 用 tsdown.config.ts),类型声明(.d.ts)由 tsc 生成
- 开发流程:改代码 → npm run build(typecheck + host + client + d.ts 全量)→ npm test 全绿;只改 client 时可单独 npm run bundle 快速迭代
- 测试永远用临时目录(src/smoke.ts 已如此),绝不直接读写 ~/.dsh-memory/ 真数据
- 提交前跑 npm pack --dry-run 确认发布内容(prepack 钩子会自动构建)

路线图

- [x] v1 基础:memory 工具 + 文件存储
- [x] Builtin 层:用户画像 + 自动注入(context())
- [x] Vault 层:向量语义检索(bge-m3,含关键词降级)
- [x] 对话层:每对话独有记忆 + 注入配置 + 项目(cwd)记忆
- [x] Client UI:记忆 tab(轨迹右边)+ 编辑/注入配置面板
- [x] 真正的自动向量注入:agent/pre-step 按当前消息检索 Vault topK=3 注入(记忆 tab 勾选「自动检索」,默认关;DSH_MEMORY_AUTO_VAULT=0/1 可全局强制)

License

MIT
预览分享卡片长什么样 ↓tluoluo/dsh-persist
分享 · DeepSeek Harness Hub
tluoluo/dsh-persist

给每个对话装上不会失忆的长期记忆

这是你的仓库?认领条目

回到顶部