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

跨会话记忆 ddtcorex/dsh-maestro-memory

DeepSeek Harnessv3.0.0
ddtcorex7 天前有提交已提供中文文档扫描:含敏感能力

为 DeepSeek HarnessDSH提供持久记忆和待办 —— 原地保留 /.dsh/memories。

30.6/100
综合分
1
GitHub 星标
239
npm 周下载
—/5
用户评分

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

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add @ddtcorex/dsh-maestro-memory
npm 源 · @ddtcorex/dsh-maestro-memory 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么[目录]—
dsh 原生插件 · dev
暂无时间戳·判据 →
装得上吗[实装验证]—
曾在 dsh 0.2.1-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.2-rc.1,本站尚未在新版下复验
L5 · 运行时冒烟等级说明 →
数据截至 2026/10/28·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/04·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 7 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

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

验证判据与等级说明 →

可信度证据

安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包@ddtcorex/dsh-maestro-memory @ 3.0.0
✓Node 引擎要求 ^22.19.0 || >=24.0.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/12 11:39:06

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

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

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 137 处,展示前 105 条)
  • 读取本地文件
    lib/index.js:103
    if (!existsSync(flagFile)) {
  • 写入本地文件
    lib/index.js:110
    mkdirSync(maestroMetaDir(root), { recursive: true });
  • 写入本地文件
    lib/index.js:111
    writeFileSync(flagFile, JSON.stringify({ at: new Date().toISOString(), report }), 'utf8');
  • 发起网络请求
    lib/index.js:735
    const fetchRes = await syncService.fetch(cwd);
  • 发起网络请求
    lib/index.js:748
    const res = await syncService.fetch(cwd);
  • 读取本地文件
    lib/memory/store.js:237
    days = readdirSync(dir)
  • 读取本地文件
    lib/memory/store.js:638
    if (!existsSync(filePath)) {
  • 读取本地文件
    lib/memory/store.js:646
    const raw = readFileSync(filePath, 'utf8');
  • 读取本地文件
    lib/memory/repair-runner.js:26
    if (existsSync(p) && statSync(p).isFile())
  • 读取本地文件
    lib/memory/repair-runner.js:26
    if (existsSync(p) && statSync(p).isFile())
  • 读取本地文件
    lib/memory/repair-runner.js:37
    for (const name of readdirSync(daily))
  • 读取本地文件
    lib/memory/repair-runner.js:48
    for (const hash of readdirSync(projects)) {
  • 读取本地文件
    lib/memory/repair-runner.js:68
    raw = readFileSync(path, 'utf8');
  • 读取本地文件
    lib/todo/store.js:69
    return readFileSync(this.pathFor(target, cwd, date), 'utf8');
  • 读取本地文件
    lib/todo/store.js:107
    names = readdirSync(join(this.root(), 'daily'));
  • 写入本地文件
    lib/todo/store.js:131
    mkdirSync(dir, { recursive: true });
  • 写入本地文件
    lib/todo/store.js:136
    writeFileSync(tmp, text, 'utf8');
  • 写入本地文件
    lib/todo/store.js:137
    renameSync(tmp, p);
  • 读取本地文件
    lib/review/queue.js:31
    const text = readFileSync(this.file, 'utf8');
  • 写入本地文件
    lib/review/queue.js:57
    mkdirSync(dir, { recursive: true });
  • 写入本地文件
    lib/review/queue.js:61
    writeFileSync(tmp, content, 'utf8');
  • 写入本地文件
    lib/review/queue.js:62
    renameSync(tmp, this.file);
  • 写入本地文件
    lib/review/queue.js:68
    mkdirSync(dir, { recursive: true });
  • 写入本地文件
    lib/review/queue.js:100
    mkdirSync(dir, { recursive: true });
  • 读取本地文件
    lib/review/queue.js:115
    const text = readFileSync(this.file, 'utf8');
  • 写入本地文件
    lib/review/queue.js:138
    mkdirSync(dir, { recursive: true });
  • 读取本地文件
    lib/storage/atomic-store.js:151
    const info = statSync(lockPath);
  • 读取本地文件
    lib/storage/atomic-store.js:153
    const owner = JSON.parse(readFileSync(lockPath, 'utf8'));
  • 读取本地文件
    lib/storage/atomic-store.js:176
    const info = await stat(lockPath);
  • 读取本地文件
    lib/storage/atomic-store.js:178
    const owner = JSON.parse(await readFile(lockPath, 'utf8'));
  • 写入本地文件
    lib/storage/atomic-store.js:215
    mkdirSync(dir, { recursive: true });
  • 写入本地文件
    lib/storage/atomic-store.js:222
    writeFileSync(lockPath, lockJson());
  • 删除本地文件
    lib/storage/atomic-store.js:237
    rmSync(lockPath, { force: true });
  • 删除本地文件
    lib/storage/atomic-store.js:248
    rmSync(lockPath, { force: true });
  • 写入本地文件
    lib/storage/atomic-store.js:262
    await mkdir(dir, { recursive: true });
  • 删除本地文件
    lib/storage/atomic-store.js:284
    await rm(lockPath, { force: true });
  • 删除本地文件
    lib/storage/atomic-store.js:295
    await rm(lockPath, { force: true });
  • 读取本地文件
    lib/storage/atomic-store.js:308
    if (!existsSync(filePath))
  • 读取本地文件
    lib/storage/atomic-store.js:311
    const data = readFileSync(filePath);
  • 写入本地文件
    lib/storage/atomic-store.js:312
    writeFileSync(backupPath, data);
  • 写入本地文件
    lib/storage/atomic-store.js:325
    await writeFile(backupPath, data, { flag: 'wx', mode: 0o600 });
  • 写入本地文件
    lib/storage/atomic-store.js:371
    await mkdir(dir, { recursive: true });
  • 删除本地文件
    lib/storage/atomic-store.js:391
    await rm(tmp, { force: true });
  • 删除本地文件
    lib/storage/atomic-store.js:422
    rmSync(tmp, { force: true });
  • 读取本地文件
    lib/sync/service.js:54
    text = readFileSync(p, 'utf8');
  • 读取本地文件
    lib/sync/service.js:145
    if (!existsSync(p))
  • 读取本地文件
    lib/sync/service.js:148
    const txt = readFileSync(p, 'utf8');
  • 写入本地文件
    lib/sync/service.js:163
    mkdirSync(dirname(p), { recursive: true });
  • 写入本地文件
    lib/sync/service.js:165
    writeFileSync(p, lines ? lines + '\n' : '', 'utf8');
  • 发起网络请求
    lib/sync/service.js:186
    async fetch(cwd) {
  • 发起网络请求
    lib/sync/service.js:193
    const fetchRes = await this.git.fetch(cfg.remoteUrl, cfg.branch);
  • 发起网络请求
    lib/sync/service.js:238
    const fetchRes = await this.git.fetch(cfg.remoteUrl, cfg.branch);
  • 发起网络请求
    lib/sync/service.js:305
    const fetchRes = await this.git.fetch(cfg.remoteUrl, cfg.branch);
  • 写入本地文件
    lib/sync/service.js:342
    mkdirSync(dirname(p), { recursive: true });
  • 写入本地文件
    lib/sync/service.js:344
    writeFileSync(p, out, 'utf8');
  • 写入本地文件
    lib/sync/service.js:404
    writeFileSync(p, `${TODO_HEADER}${entries.length ? `\n§\n${entries.join(ENTRY_DELIMITER)}\n` : ''}`, 'utf8');
  • 写入本地文件
    lib/sync/service.js:410
    writeFileSync(p, `${TODO_HEADER}${entries.length ? `\n§\n${entries.join(ENTRY_DELIMITER)}\n` : ''}`, 'utf8');
  • 发起网络请求
    lib/sync/git.js:20
    async fetch(remoteUrl, branch) {
  • 发起网络请求
    lib/sync/git.js:83
    async fetch(remoteUrl, branch) {
  • 执行外部命令固定命令
    lib/sync/git.js:86
    const res = spawnSync('git', ['ls-remote', remoteUrl, `refs/heads/${branch}`], { encoding: 'utf8', timeout: 5000, env: { ...process.env, GIT_TERMINAL_
  • 读取本地文件
    lib/sync/git.js:125
    if (existsSync(url))
  • 执行外部命令固定命令
    lib/sync/git.js:138
    const res = spawnSync('git', ['--git-dir', barePath, 'show', `${branch}:${f}`], { encoding: 'utf8', timeout: 5000 });
  • 执行外部命令固定命令
    lib/sync/git.js:154
    const ls = spawnSync('git', ['--git-dir', barePath, 'rev-parse', '--verify', `refs/heads/${branch}`], { encoding: 'utf8' });
  • 执行外部命令固定命令
    lib/sync/git.js:168
    let r = spawnSync('git', ['init'], { cwd: tmp, encoding: 'utf8' });
  • 执行外部命令固定命令
    lib/sync/git.js:171
    r = spawnSync('git', ['remote', 'add', 'origin', barePath], { cwd: tmp, encoding: 'utf8' });
  • 执行外部命令固定命令
    lib/sync/git.js:175
    spawnSync('git', ['fetch', 'origin', branch], { cwd: tmp, encoding: 'utf8', env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } });
  • 写入本地文件
    lib/sync/git.js:189
    mkdirSync(join(p, '..'), { recursive: true });
  • 写入本地文件
    lib/sync/git.js:190
    writeFileSync(p, content, 'utf8');
  • 删除本地文件递归
    lib/sync/git.js:205
    rmSync(tmp, { recursive: true, force: true });
  • 读取本地文件
    lib/sync/git.js:225
    if (existsSync(p))
  • 读取本地文件
    lib/sync/git.js:226
    files[f] = readFileSync(p, 'utf8');
  • 删除本地文件递归
    lib/sync/git.js:232
    rmSync(tmp, { recursive: true, force: true });
  • 写入本地文件
    lib/sync/git.js:244
    mkdirSync(repo, { recursive: true });
  • 写入本地文件
    lib/sync/git.js:261
    writeFileSync(join(repo, name), content, 'utf8');
  • 删除本地文件递归
    lib/sync/git.js:275
    rmSync(tmp, { recursive: true, force: true });
  • 读取本地文件
    lib/skills-browser.js:92
    entries = readdirSync(skillsDir);
  • 读取本地文件
    lib/skills-browser.js:102
    st = lstatSync(skillFolder);
  • 读取本地文件
    lib/skills-browser.js:113
    const follow = statSync(skillFolder);
  • 读取本地文件
    lib/skills-browser.js:122
    const fileSt = statSync(skillFilePath);
  • 读取本地文件
    lib/skills-browser.js:131
    rawContent = readFileSync(skillFilePath, 'utf8');
  • 读取本地文件
    lib/skills-browser.js:182
    if (existsSync(c))
  • 读取本地文件
    lib/prompt/snapshot.js:125
    if (!existsSync(userFile) || readFileSync(userFile, 'utf8').trim() === '') {
  • 读取本地文件
    lib/prompt/snapshot.js:125
    if (!existsSync(userFile) || readFileSync(userFile, 'utf8').trim() === '') {
  • 读取本地文件
    lib/prompt/snapshot.js:167
    if (existsSync(refPath)) {
  • 读取本地文件
    lib/prompt/snapshot.js:168
    const refContent = readFileSync(refPath, 'utf8');
  • 读取本地文件
    lib/sync/config.js:6
    if (!existsSync(p))
  • 读取本地文件
    lib/sync/config.js:9
    const data = JSON.parse(readFileSync(p, 'utf8'));
  • 写入本地文件
    lib/sync/config.js:22
    mkdirSync(dirname(p), { recursive: true });
  • 写入本地文件
    lib/sync/config.js:23
    writeFileSync(p, JSON.stringify(cfg, null, 2), 'utf8');
  • 删除本地文件递归
    lib/sync/config.js:28
    rmSync(dir, { recursive: true, force: true });
  • 读取本地文件
    lib/sync/config.js:34
    if (!existsSync(p))
  • 读取本地文件
    lib/sync/config.js:37
    return JSON.parse(readFileSync(p, 'utf8'));
  • 写入本地文件
    lib/sync/config.js:45
    mkdirSync(dirname(p), { recursive: true });
  • 写入本地文件
    lib/sync/config.js:46
    writeFileSync(p, JSON.stringify(meta, null, 2), 'utf8');
  • 读取本地文件
    src/host/index.ts:132
    if (!existsSync(flagFile)) {
  • 写入本地文件
    src/host/index.ts:138
    mkdirSync(maestroMetaDir(root), { recursive: true })
  • 写入本地文件
    src/host/index.ts:139
    writeFileSync(flagFile, JSON.stringify({ at: new Date().toISOString(), report }), 'utf8')
  • 发起网络请求
    src/host/index.ts:714
    const fetchRes = await syncService.fetch(cwd)
  • 发起网络请求
    src/host/index.ts:725
    const res = await syncService.fetch(cwd)
  • 读取本地文件
    src/host/skills-browser.ts:106
    entries = readdirSync(skillsDir)
  • 读取本地文件
    src/host/skills-browser.ts:115
    st = lstatSync(skillFolder)
  • 读取本地文件
    src/host/skills-browser.ts:124
    const follow = statSync(skillFolder)
  • 读取本地文件
    src/host/skills-browser.ts:131
    const fileSt = statSync(skillFilePath)
  • 读取本地文件
    src/host/skills-browser.ts:138
    rawContent = readFileSync(skillFilePath, 'utf8')
  • 读取本地文件
    src/host/skills-browser.ts:190
    if (existsSync(c)) return c
扫描于 2026/11/4(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

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

社区实装还没有人打卡 · 装过的第一个就是你
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-tools@deepseek-ai/cordis@deepseek-ai/dsh-client-ui-slots@deepseek-ai/dsh-client-ui-conversation@deepseek-ai/dsh-client-locale
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 跨会话持久记忆
  • 待办管理
  • 五条记忆轨道

DSH 插件,提供跨会话的持久记忆与待办——五条轨道(全局/用户/项目/关键/每日),写入需确认,Git 同步,可就地接管 ~/.dsh/memories。

v3.0.12026-10-29
2026-10-28 设计审计的 Memory 视图修复(19 项发现,在 DSH Web 0.2.2-alpha.2 上实测;报告见 maestro-harness/docs/reports/2026-10-28-dsh-maestro-memory-design-audit.md)。 · 已修复 · Memory 标签页上的队列徽标。计数是追加到宿主标签按钮上的 span,而该按钮是 display: block,标签放在内层 span 里,于是徽标掉到第二行,把标签条从 35px 撑到 52px,还压到激活态下划线上。现在标签盒固定为单行内联。 · 焦点环。键盘焦点原先用 --dsw-alias-interactive-bg-active 绘制,这是个半透明表面 token,在浅色主题下合成后对比度只有 1.20:1,深色下 1.51:1,等于面板把宿主原本可见的焦点环换成了看不见的。现在改用平台焦点环(深色 17.45:1,浅色 18.90:1),聚焦字段保留 label-secondary 边框(5.80:1 / 9.25:1)。 · 徽标计数。深色下白字配原始 error token 实测 3.29:1;现在填充色向黑色混合 22%(深色 5.09:1,浅色 6.75:1),并保留原始 token 作兜底。它的 reduced-motion 否决规则从面板样式表移到了整个会话都挂载的样式表里。 · Health 区块报了它没有的数字。硬编码的「1.5 avg / session」和真实的零并排出现,空项目还会显示绿色的「100.0% PASS」,因为 total === 0 时宿主返回覆盖率 100。现在这种情况显示 n/a,数值放在带底色的 chip 上,文字用 label-primary(原始状态色作为浅色表面上的文字实测只有 2.28:1 和 2.15:1)。 · 操作结果没有播报。原先只有一个区块发布了 live region,其余四个把失败渲染成灰色小字。现在一个 role=status 的 MemStatus 组件服务全部五个区块,失败用 error 颜色(深色 4.77:1,浅色 4.83:1)。 · Skills 区块把空状态消息打印了两遍,还在 12px 行里列出绝对文件系统路径;现在路径折叠为最后两段,完整路径放进 title。 · 移动端区块导航栏。它原本是 303px 盒子里的 517px 滚动区,滚动条还隐藏了,Skills 和 Health 跑到屏幕外且没有任何提示。现在低于 1024px 时改为换行网格,胶囊/下拉/搜索保持 44px 触控下限,空状态是 520px 的提示块而不是整宽虚线板,todo 编辑器默认折叠,队列卡片只显示一次原因,编辑框收在开关后面。 · 浅色主题没有表面层级。bg-base、bg-layer-1、bg-layer-2 在那里都解析成 #fff,卡片、表单和空状态没有边界;现在它们搭配 border-l2 和柔和阴影。 · 宿主界面规则没有限定作用域。面板样式表一挂载就重设所有会话视图的样式;现在只保留 :has(.memx) 限定的那组规则。 · 已变更 · 区块标签页的行为像标签页了。roving tabindex 加方向键/Home/End,面板本身也可聚焦。 · 徽标计数不再算进标签页名称。它设为 aria-hidden,并用 aria-describedby 描述(「N pending suggestions」),标签页读作「Memory」。 · 每个区块只刷新一次。只轮询队列计数的头部图标已移除;Health 的刷新从底部移到了工具栏。 · 测试。新增徽标位置、无障碍 token、审计后续项以及新的 src/client/format.ts 辅助函数的测试套件;共 428 个测试。 · 变更内容 · docs(memory):把规范引用指向重命名后的文件 · fix(memory):关闭 Memory 视图设计审计发现的问题 · chore(release):3.0.1 完整变更日志:
v3.0.02026-10-10
· 移除 · 一次性旧布局迁移。不再提供从旧 ~/.dsh/memories 布局就地迁移:migration.* RPC 端点、scripts/migrate.mjs 和迁移服务都已删除。仍在使用旧布局的人,必须用最后一个包含该迁移的版本(2.2.1)完成迁移。 · write-block.json 写入拦截。已经没有任何代码写这个标志,所以记忆存储、待办存储和同步服务不再检查它,status RPC 不再报告 blocked,isWriteBlocked 也已删除。 · 未使用的布局辅助函数。删除迁移后,journalPath、backupsDir、backupManifestPath、backupFilesDirPath、schemaPath 和 allMetadataPaths 已无调用方。 · 记忆存储里重复的 todayStamp。现在改为从待办存储导入导出的那份。 · 修复 · 连接行注入 webStartup。DSH 0.2.1-alpha.x 移除了 webRuntime 服务;连接条目仍在注入它,就会等待一个永远不会挂载的服务,导致 dsh web 永远起不来。 · 变更内容 · docs(memory):记录要求、版本锁定和随附的连接补丁 · chore(memory):移除旧布局迁移和死代码 · chore(memory):启用 noUnusedParameters · 兼容 DSH 0.2.1:在连接行注入 webStartup · chore(release):3.0.0 完整变更日志:
v2.2.12026-10-07
· 已修复 · RPC 行在 DSH 0.2.x 上启用。bundle 补丁在连接条目上声明 webServer,因此调用 rpc.handle 的行不再因缺少 inject 而报 cannot get property "webServer",其通道也不再返回 405。 · 对等版本范围现在截止到 <0.3.0-0,因此 memory 在 dsh 0.2.x 上不再被跳过(已发布的 2.2.0 声明的是 <0.2.0)。 · 变更内容 · chore:目标改为 DSH 0.1.7-alpha.2 · fix(memory):给 dsh 对等版本范围加上 -0 上限后缀 · fix(memory):从 rpc.handle 去掉被忽略的 authority 参数 · docs(memory):在 memory 工具 schema 中写明 replace 约定 · fix(memory):把 Memory 视图的 todo 标签页标为 'Todo' · fix(memory):把 dsh 对等版本上限重新锚定到 0.3.0-0 · chore:把 dsh devDependencies 对齐到 0.2.0-rc.2,并去掉已失效的 client-runtime inject 依赖边 · docs(memory):让 README、AGENTS 和架构文档与代码保持一致 · ci:用新仓库名调用可复用工作流 · docs(build):客户端 bundle 按包名命名,不再沿用已废弃的 fork 名 · docs(memory):指向改名后的 SYNC-DESIGN 文档 · fix(memory):在连接条目上声明 webServer · chore(release):2.2.1 完整变更日志:
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
dsh-maestro-memory 为 DeepSeek Harness(DSH)提供持久记忆和待办 —— 原地保留 ~/.dsh/memories。 给 AI 跨会话的持久记忆和待办 —— 你用得越多,它越懂你。 - 包名: @ddtcorex/dsh-maestro-memory(cordis.patch.yml id 为 maestro-memory) - 版本: 见 package.json · 更新日志: CHANGELOG.md 环境要求 - Node.js ^22.19.0 || >=24.0.0,pnpm 11+(见 package.json 里的 packageManager) - DSH 版本需落在 package.json 声明的 @deepseek-ai/dsh-client- peer 范围内 安装 pnpm install pnpm run build # -> lib/ pnpm test # vitest run DSH 配置档(运维): dsh plugin --profile web add link:<workspace-root>/packages/dsh-maestro-memory
展开完整文档(README 剩余 1 节)
dsh-maestro-memory

为 DeepSeek Harness(DSH)提供持久记忆和待办 —— 原地保留 ~/.dsh/memories。

给 AI 跨会话的持久记忆和待办 —— 你用得越多,它越懂你。

- 包名: @ddtcorex/dsh-maestro-memory(cordis.patch.yml id 为 maestro-memory)
- 版本: 见 package.json · 更新日志: CHANGELOG.md

环境要求

- Node.js ^22.19.0 || >=24.0.0,pnpm 11+(见 package.json 里的 packageManager)
- DSH 版本需落在 package.json 声明的 @deepseek-ai/dsh-client- peer 范围内

安装

pnpm install
pnpm run build # -> lib/
pnpm test # vitest run

DSH 配置档(运维):

dsh plugin --profile web add link:<workspace-root>/packages/dsh-maestro-memory
production: dsh plugin --profile web add github:ddtcorex/dsh-maestro-memory#<tag-or-sha>

cordis.patch.yml 随包一起发布,里面有一条 connection 条目,声明了 webServer(DSH 0.2.x 需要它,rpc.handle 才能注册 channel)。不要在配置档里重复写。从 npm 安装时用 @ddtcorex/dsh-maestro-memory@<version> 锁定版本;每次对 link: 检出的代码执行 git pull 后,都要跑一遍 pnpm install && pnpm build。

- insert:
- id: maestro-memory
name: '@ddtcorex/dsh-maestro-memory'
config:
memoryDir: null # -> ~/.dsh/memories
snapshotOrder: 500
autoMemory: { enabled: false, userMessage: true, desensitize: true } # opt-in
writeGuard: { enabled: false, threshold: 2 } # per-turn write watchdog, opt-in

writeGuard 只在 apply() 时读取一次 —— 改它需要编辑配置档并重启宿主。threshold 统计连续多少轮有效人类对话没有写入 daily/project;任何小于 1 的值都按 1 处理。

工具

| 工具 | 用途 |
| memory | 五条轨道 memory/user/project/key/daily + 归档/展开。key 需经 memory_suggest 放行。 |
| maestro_todo | 跨会话持久待办存储(四条轨道 life/work/project/daily,带 id,智能视图最多 8 条)。命名上区别于 harness 自带的会话内任务列表 todo_write。 |
| memory_suggest | 受控提案写入 SUGGESTIONS.jsonl —— 需要人工批准。 |

memory 会清洗敏感片段([Filtered:API key/password/token/ID/phone],纯凭据 → content filtered)。

系统提示词快照

memory:snapshot(order 500)注入有界、确定性的上下文:

USER + MEMORY + KEY (branch-filtered) + Project Context (auto-recall top-4, 600 chars each, cap 1024) + Recent Daily (last 2 days, 512) + header + discipline note

上限:memory 2048 / user 4096 / key 6144 / recentDaily 512 / autoRecall 1024,外加各分区的条目预算 memory 8 / user 8 / key 12 / recentDaily 2 / autoRecall 4。

上限在两个维度上都是硬性的。最新条目始终保留 —— 当它带 summary 标签时,压缩为 head + [summary:…]。未打标签的条目若大于超过自身字节上限两倍的内容会被截断,并带上明确的 …[truncated] 标记,而不是整条保留:在这条规则之前,单条 2,293 字节的全局条目就占满了 2,048 字节的区段,把其他条目全都挤出了提示词。

成本上报。 renderSnapshotWithStats(memory:snapshot 背后的渲染器)会报告每个区段的字节数、条目数和被排除的条目数。宿主在内存中保留最近 50 次渲染结果,并通过 /dsh-maestro-memory-health 以 cost 返回:samples、last、medianTotalBytes、maxTotalBytes、medianBySection。条目正文从不保留——只保留字节数。

写入看门狗。 开启 writeGuard.enabled 后,宿主会统计连续多少个人类回合里发生了工作、却没有写入 daily/project 条目(agent/turn-stopping)。一个回合要计入,必须同时满足三件事:

1. 它由直接的人类提示词开启——目标续跑回合(source.kind === 'goal')和注入的上下文('plugin',比如唤醒通知)不承担每回合的义务;
2. 它不是子代理会话;
3. 它至少派发了一次工具调用。只回答问题的回合属于对话,不是工作,因此不累积欠账——否则看门狗会逼着模型写凑数条目,而这正是纪律提示词明令禁止的。

一旦缺口达到 threshold,快照就会在纪律提示词之前紧挨着多出一个 # ⚠️ Memory Write Backlog 区段,并一直保留到某次写入成功为止。
只有真正记录下内容的写入才算数——去重后未新增的写入不算。告警文本是静态的(只含阈值,从不含实时计数),所以一个未闭合的缺口最多产生两次尾部快照:出现,然后消失。

子代理会话(session.header.origin === 'subagent')改用克制的按成果节奏,而不是每回合义务,并且永远看不到积压告警。共享记忆上下文(MEMORY / USER / KEY / Project Context)仍会为它们注入。

渲染出的快照会把两个及以上的连续花括号折叠成一个:DSH 会插值每个提示词上下文,遇到名字格式错误或未注册的 {{name}} 分组就会让整个回合失败,所以自由形式的记忆文本永远不会以模板语法到达那里。

UI 与 RPC

一个 conversation.view 插槽(maestro-memory,顺序 40),标签页为 Memory / Queue / Todo / Skills / Health。Health 显示 coverage、daily last 7d、longest 以及 5 维评分 S/R/J/C/Safety(综合分 min0.4+mean0.6)。

Memory 标签页以只读方式列出每条轨道,并带一个手动添加编辑器:选一条轨道,输入条目,按 Add。它会以 action: 'add' 发送 memory.mutate,因此不需要模型往返;草稿按轨道分别保存,条目为空或 key/project 没有 cwd 时按钮保持禁用。模型侧的 key 门禁在宿主上按 exec.agent 作用域生效,所以它依然有效——你在这里写 key,等同于批准一条排队中的建议。
RPC:/dsh-maestro-memory + 回环 /dsh-maestro-memory-health + /dsh-maestro-memory-propose。

/dsh-maestro-memory 上的维护端点(两者都是先预览:dryRun 默认为 true,真正写入需要 confirm: true):

| 端点 | 作用 |
| memory.repair | 拆分缺少 § 分隔符而粘连在一起的条目,删除完全重复的条目,并把结尾的 [summary:…] 移到规范的头部位置,覆盖每个存储文件。报告 files / changed / split / deduped / relocated。 |
| memory.maintenance | 规划(并可选执行)memory/user/key/project 中最旧条目的归档,超出 DEFAULT_ARCHIVE_POLICY(保留字节数 + 最长时限)的部分。超量条目移到对应轨道的 -archive.md;它们永远不会被丢弃。 |

归档与双机同步。 sync/ 把 MEMORY.md、KEY.md 和 KEY-archive.md 按并集合并——它从不丢弃任何版本,而项目/全局条目不带 [id:…],所以一台机器上的删除和「另一台机器还没看到它」无法区分。归档前值得了解几个后果:

- 只在 ONE 台机器上归档并不稳定:下一次并集合并会把归档的条目拉回活动文件,因为对端仍然把它们放在那里。要在每台机器上归档(同一策略),这样两边一致,并集就是空操作;或者归档时关闭同步。
- MEMORY-archive.md 不在合并集合里(只有 KEY-archive.md 在),所以项目轨道的归档留在本地。这对活动文件是安全的,但也意味着两台机器的归档文件可以合理地不同。
- 归档不改变提示词显示的内容:# Project Key Memory 上限为 6,144 B,# Project Context 上限为最新的 4 条,两者都远低于归档策略的保留预算。已在活动存储上验证:渲染在运行前后逐字节一致。

启动时运行一次修复,由 <root>/.maestro-memory/(maestroMetaDir)下的标志文件控制:delimiter-repaired-v3 覆盖每个存储文件,并把运行报告写入该标志。(v1 是仅针对 KEY 的一遍,一直是静默空操作——它被用错误的 cwd 调用——v2 覆盖了存储,但没有拆分游离的分隔符;带版本的标志让已有机器恰好一次地采用新的修复规则。)

维护

维护通过上文 UI & RPC 中描述的 memory.repair 和 memory.maintenance RPC 端点运行(先预览,confirm: true 才写入)。没有独立的维护脚本;scripts/ 只存放构建辅助工具。
预览分享卡片长什么样 ↓ddtcorex/dsh-maestro-memory
分享 · DeepSeek Harness Hub
ddtcorex/dsh-maestro-memory

为 DeepSeek HarnessDSH提供持久记忆和待办 —— 原地保留 /.dsh/memories。

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部