Skip to content

[Feature] 记忆落盘形态与目录归属:镜像、导出、工作区投影的统一与迁出 ~/.dsh #278

Closed

Description

@heptaspirit

@modusensus

使用场景

顺着之前的提案顺下来,当我们能让 mneme 能导出记忆为 markdown 后,我认为这意味着用户可以将 mneme 的导出当「能长期维护的笔记」用:一个记忆库导出成一批 markdown,交给编辑器或别的工具索引、浏览、跨机器带走。但这会引入新的问题,用久了自然会串成一条链:整理记忆 → 落盘文件变多 → 需要一个统一的落点来管它们 → 这个落点该不该继续待在 ~/.dsh 下 → 将来要不要把每个工作区的记忆也投影一份到仓库里。

这条链的第一环已经在做了(#231 的整理接口),后面几环到现在没有落点。它们不是四个独立功能:共用同一套渲染逻辑,只差落在哪、以及能不能改回来。所以想一次把形态与归属议清楚,而不是一个 PR 改一处、每个落点各自发明一套。

已查现状

现在有三个落点,行为各不相同:

  • ~/.dsh/memory/*.md——磁盘镜像,按 type 分五个文件;有回流:/import 与磁盘镜像的合并走 mirror-digest 加 更新时间 的三方合并,只吃 title 与 content。
  • /api/dsh-mneme/export——只读导出,format=json 给整表 JSON,format=markdown 按类型分节拼成一个文件,与镜像共用同一条渲染路径。
  • <workspace>/.mneme/——工作区投影,还不存在。

工作区投影这件事,是回调一条更早的设计:#17 的 §五「workspace 投影(备份性质,可选)」写过它的一份完整草稿——定位是单向投影、不是权威源;可选、默认关;把 workspace_scope = 当前 workspace 的记忆渲染到 <workspace>/.mneme/,按 type 分文件,frontmatter 带 memory id / WorkspaceId / 导出时间;agent 维度写进 frontmatter 而不建子目录(避免笛卡尔积);价值是「编辑器里直接读项目记忆」与「DB 损坏时的明文兜底」,恢复走显式命令、靠 (type, title, scope) 去重保幂等。

但那是一版随 issue 搁置的草稿,不是生效中的设计:它没有随那一批落地,也没有排期。你关闭 #17 时列的偏差里就有它——「mirror 分域路径(第 6 条)……未随本批,作为已知边界跟踪(见 #168 的 PR 描述边界节)」,同一个 issue 的 M3 清单也列了它。也就是说它现在只存在于一个已关闭的 issue 和一个 PR 的描述里;代码里没有任何实现(src/ 下写记忆文件的只有 mirror.js 一处)。

所以下面拿它当回调看:它的价值在于「当时有人把该带哪些 frontmatter 字段列出来了」,而不在于「它已经定了」。

另外两条现状,都影响怎么选:

  • 目录:记忆库现在是 ~/.dsh/memory(memoryDir 的默认值,放 memory.db 与那五个镜像文件),运行时重载荷在 ~/.dsh/mneme/。而 src/runtime/layout.js 里有一句注释写着这些「都挂在 ~/.dsh/mneme/ 下,便于用户整个删掉重来」——所以迁出这件事与你当初的意图是冲突的,要先对口径。
  • 硬约束:同一个 scope 设计里,sensitivity=1 的记忆「不参与跨库检索、不进 mirror 导出、不入跨项目 dream 提炼」。任何新落点都必须继承这条。

期望的行为变化

同一份记忆,无论落在哪个位置,读到的形态是同一套;并且哪个落点允许改回库、哪个只读是明确定义的,不靠使用者的印象。

具体到四件事:

  1. 格式:落盘文件带 frontmatter,字段能直接当查询信号(来源、生成者、时间、状态)。我这边看的是 Open Knowledge Format v0.2——一个 concept 一个文件、type 必填,sources / generated / status 可选。它与我们已有的列重叠度不低,但要不要采用、还是只借它的键名,我不预设。
  2. 回流:现在只有全局镜像能改回库,而且只吃 title 与 content——标签、重要性、来源、状态从文件改不回来。要不要扩到元数据,得先定义字段的权威归属(比如 type 算不算身份锚、status: deprecated 对应归档时是不是单向的)。
  3. 目录:记忆库是否从 ~/.dsh 迁到 ~/.mneme 这类用户目录下、按用途分子目录。理由不是整洁:落盘文件一旦是给人读、给人改的,它放在宿主的私有目录里就等于在暗示用户别碰。
  4. 投影:工作区投影要不要重新排期。它跟第 3 条互相牵制——投影目录本身就长在用户仓库里,先把库的位置定下来,投影的位置才有意义。

想先确认的

  • [Feature Request] 按 agent 与 workspace 隔离记忆:专属记忆 + 全局共享记忆并存 #17 §五 那版草稿只当参考,不当依据。 里面「单向、无回流」这条尤其想重看:当时给的理由是双向同步要解决 git checkout 冲突、双写向量、digest 冲突;但今天全局镜像已经是双向的(/import + mirror-digest 三方合并),而 <workspace>/.mneme/ 在用户的 git 仓库里,读回会跟 git checkout 打架。两个落点的策略本来就可以不同、不必统一——这一条想听你怎么看。
  • 迁出 ~/.dsh 与「便于用户整个删掉重来」怎么取舍;存量安装要不要迁移(memoryDir 是用户可配置键)。
  • 这一串拆成几个 PR、按什么顺序落。

可能的做法(仅供讨论,不是方案)

摆在桌上的只有这几条,我不预设:只给 /export 加一个新出口;镜像文件本身加 frontmatter;彻底改成一条记忆一个文件。粒度的代价可以量化,顺带一提:现在 5 个文件,一条记忆一个文件就是数千个,Windows 上的写入与扫描代价需要实测一次再决定——这条数据可能比格式本身更能定方向。

Activity

  1. heptaspirit commented on Sep 21, 2026

    @heptaspirit
    CollaboratorAuthor

    @modusensus 补一条同一议题里的关联:说「能读也能写」时,读回环其实今天已经在(/export ↔ /import、磁盘镜像 ↔ syncMirror),真正缺的是元数据也能写。

    现状(读码所得):/import → parseHumanEdits → mergeHumanEdits,这条链只消费 title 与 content(src/api.js 的注释自己写明「只吃 title/content」),parseHumanEdits 返回的也只有 id/title/content 加两个版本 token(更新时间、mirror-digest)。也就是说标签、重要性、来源、归档状态,今天从文件里改不了。

    所以如果 frontmatter 要成为正本的一部分,得先约定字段的权威归属——OKF 只规定键名,没规定谁说了算。我这边列几个需要落定的问题,供讨论、不定方案:

    • type 是身份锚吗?改它等于换了一条记忆,还是直接拒绝?
    • title / 正文 / tags / importance 允许从文件改回库,这些是无损的;
    • status: deprecated 对应归档:方向是单向的吗(能归档,能不能取消归档)?
    • source 只能追加还是可改?它在 OKF 里对应两个不同的键——「谁生成的」(generated.by)与「从哪来」(sources),我们现在只有一个单值字符串列;
    • 人工改动与机器写入的判定锚现在是每条一个 mirror-digest,换成 frontmatter 之后它放哪。

    这些定下来,「能读也能写」才有确定语义。定不下来我倾向先只做读(导出)方向,写回等粒度定了再说。

    另外一处与粒度相关的成本,可能比格式本身更能定方向:一条记忆一个文件意味着文件数从 5 个变成数千个,Windows 上的镜像写入与扫描代价需要实测一次。

  2. changed the title [-][Feature] 导出格式与 OKF v0.2 对齐:frontmatter 与概念粒度[/-] [+][Feature] 记忆落盘形态与目录归属:镜像、导出、工作区投影的统一与迁出 ~/.dsh[/+] on Sep 21, 2026
  3. modusensus commented on Sep 21, 2026

    @modusensus
    Member

    @heptaspirit 这份把形态与归属议清楚了,值得一次议定。先核对你的现状清单——大部分属实,但有一处必须修正,它影响整条链的假设。

    一处现状修正:sensitivity 那条不是「硬约束」

    你写「sensitivity=1 的记忆『不参与跨库检索、不进 mirror 导出、不入跨项目 dream 提炼』……任何新落点都必须继承这条」——这条约束在代码里不存在。我逐处核过 main:

    • src/mirror.js、src/export.js:没有任何 sensitivity 过滤,渲染路径全量写出;
    • src/dream.js / src/dream/*:无 sensitivity 判断;
    • src/service.js:105 明说「sensitivity 是标签不参与可见性判定」;检索侧的 scope 加权/过滤只认 agent_scope / workspace_scope(strictScope 也只收窄这两维,README 原话「真正的物理隔离请用 sensitivity」是建议,不是实现)。

    也就是说 sensitivity 目前真实的语义只有两个:去重键第三维(跨 sensitivity 不合并)+ 标签展示。你引的那三条是我方文档里的建议口径,不是生效代码。所以新落点不是「继承」一条硬约束,而是第一次落地它——这反而是好事:约束怎么写、过滤规则是什么,可以在这次一起定,而不是背着一条没实现的口径往前走。建议:frontmatter 带 sensitivity 字段(信息展示),过滤语义并进「存储生命周期」issue 的可见性部分一起定。

    其余现状核对(属实)

    逐条拍板

    1. 格式:只借 OKF 键名,不整体采纳。 OKF 的 one-concept-per-file 与我们的 type 分文件镜像形态冲突,且一条记忆一个文件 = 数千文件,Windows 写入/扫描成本未实测——你这条数据本身可能比格式更能定方向,我同意先实测。字段集以 #17 §五 草稿为底:memory id / type / scope(agent+workspace)/ sensitivity / updated / status / generated.by,键名对齐 OKF(sources / generated / status 可借),值域用我们的。

    2. 回流:第一批只做读方向。 title/content 回流保持现状(mirror-digest 三方合并已稳),元数据写回不做——字段权威归属不定,写回就是拍脑袋。归属表先定:type 是身份锚(去重键成员,文件不可改写);title/content/tags/importance 可写回;status: deprecated 单向(对应归档,不回退);source 追加只读。这张表并进「存储生命周期」issue(我方欠的一个立项),定稿后元数据写回作为第二批。

    3. 目录:不迁 ~/.dsh。 理由:layout.js 那句「便于整个删掉重来」是我们的原意,迁出与它直接冲突,且收益是感受层面的;memoryDir 已是用户可配置键,想要用户目录的自己配。「放宿主私有目录 = 暗示用户别碰」用文档明示「镜像可编辑」解决,不动目录。记忆库留在 ~/.dsh/memory。

    4. 投影:重新排期,放第一批之后。 两个落点策略不必统一——同意你,而且理由比你说的更硬:全局镜像能双向,是因为 mirror-digest 是我们自己定义的文件状态(三方合并语义由我们定);工作区投影长在用户的 git 仓库里,git checkout 会覆盖文件、digest 语义直接失效。所以:全局镜像双向(现状)、工作区投影单向只读(#17 §五 原案:按 type 分文件、frontmatter 带 id/WorkspaceId/导出时间、agent 进 frontmatter 不建子目录、恢复走显式命令、按 (type,title,scope) 去重保幂等)。投影目录 <workspace>/.mneme/ 在用户仓库内,与记忆库位置无关,不互相牵制——第 3 条不阻塞它。

    拆分与顺序

    批 内容 依赖
    前置 一条记忆一文件的 Windows 写入/扫描成本实测 无
    第一批 frontmatter 格式 + 只读导出(export 与镜像共用渲染路径加字段) 实测结果(决定格式粒度)
    第二批 工作区投影(单向、默认关、opt-in) 第一批的渲染路径
    第三批 元数据写回 字段权威归属表(存储生命周期 issue)

    第一批就是「只读方向」:所有落点读到的形态同一套、能改回库的只有 title/content、sensitivity 过滤语义随存储生命周期定。投影从「不存在的第四件事」变成「已排期的第二批」,价值不变、风险可控。

    关键判断:#278 拍板 = sensitivity 约束实为建议口径(首次落地而非继承);只借 OKF 键名;第一批只做只读导出、元数据写回等归属表;不迁 ~/.dsh;投影单向只读排第二批;前置先测 Windows 文件成本。

  4. heptaspirit commented on Sep 21, 2026

    @heptaspirit
    CollaboratorAuthor

    补一条更正,四批拆分收下

    先更正:sensitivity 那三条隔离口径是我引错了,你说得对,代码里没有。照你指的位置我逐处复核了 upstream/main,全部属实,且比你说的还干净:src/mirror.js 与 src/export.js 的渲染路径无过滤,src/dream.js 与 src/dream/* 无判断,src/service.js:105 明写「sensitivity 是标签不参与可见性判定」。它今天真实的语义只有两条:去重键第三维(service.js:1154/:1165、document.js:117 的 supersede 匹配键)与标签展示(tools.js:37/:72)。我把设计文档里的建议口径当成了生效实现,这个错误随正文一起传给了你。

    四条拍板都收下。其中「不迁 ~/.dsh」我一开始想拧,看完理由认了:memoryDir 本来就是可配置键,我这边真正要的是「用户知道能配」,那是文档的活,不是目录的活。投影那条理由我也认,而且确实比我原来给的硬:mirror-digest 是自己定义的文件状态,投影长在别人的 git 仓库里,git checkout 一覆盖 digest 就失效。

    前置那条实测我这边做,没有依赖,三档粒度一起量:5 个 type 文件 / 一条一文件 / 按簇分桶,每组都测全量重写、单条增量写、扫描与读取。

    顺手先摆一个规模事实,免得「数千文件」后面被当成既定量级:今天的镜像只覆盖 store.list({ limit: 500, includeForgotten: false }),而 list 默认 includeArchived: false(store.js:1234/:1310),归档根本不进镜像。「数千」要同时满足「含归档」与「一条一文件」才出现,眼下活跃集是 260 条。那个 500 的硬顶本身像是另一个独立问题:活跃数长过 500 之后镜像会静默截断。

    借这条再提一件相邻的事:配置说明缺一份

    这次能发现自己忽略了 memoryDir 可配,是靠你在回复里顺手指出。往前追一步,是我们参与开发这个项目的人自己就说不清一部分配置:上面那条引错的约束也是同一个来源,手边没有一处能查「这个键干什么、默认是什么、改了会怎样」,于是拿设计文档里的建议口径当了生效实现。我这边的开关状态一多半是「名字看着有用就开着」,答不上具体改变了什么行为。

    用户侧现在能查的只有 README 的「Quick config (optional)」一节;dsh-mneme/docs/ 下五份(ENTITIES / LOCAL_MODEL / MIGRATION / SEMANTIC / SLEEP)讲的都是机制,不是怎么配。而配置实际分在两处,改法还不一样:src/config.js 的 137 个键属宿主挂载时的插件配置,src/settings.js:177 的 39 个功能开关存在库里的 user_settings 表(settings.js:7/:286/:292),没有文件可改。

    从这一脚往外看,需要的可能是单独一份配置说明,要满足几点:新增功能时跟着过一遍;用户能读到(面板里能打开?);agent 也能读到,好按用户的需求解释或者直接配。它和 #249 是一件事的两面——#249 管模型怎么用记忆,这一面管用户或 agent 怎么配记忆;同一个开关,两边说的应该是同一件事。形态、位置、要不要单独立项,你来定,我们只是希望它别排得太靠后:这一轮的混淆就是它缺位的直接后果。

  5. heptaspirit commented on Sep 22, 2026

    @heptaspirit
    CollaboratorAuthor

    前置实测:三档粒度的写入与扫描成本

    环境 NTFS / C 盘 / node v26;内容用真渲染器 renderMirrorText 生成,数据形状取本机库(活跃 282 条均 387 字符、归档 2638 条均 262);三次取中位数。

    规模 粒度 文件数 全量重写 单条增量 扫描(读全) 整目录删除
    活跃 282 按 type 5 4.8 ms 0.7 ms 1.5 ms 1.9 ms
    活跃 282 一条一文件 282 206 ms 0.6 ms 44 ms 81 ms
    活跃 282 按簇(25) 12 9.8 ms 0.7 ms 2.5 ms 4.9 ms
    活跃+归档 2920 按 type 5 6.1 ms 1.1 ms 4.1 ms 2.6 ms
    活跃+归档 2920 一条一文件 2920 1998 ms 0.6 ms 396 ms 948 ms
    活跃+归档 2920 按簇(25) 117 76 ms 0.7 ms 18 ms 42 ms

    三条读数:

    1. 代价集中在文件数。同一份 3 MB,5 个文件重写 6 ms,2920 个文件 1998 ms,约 330 倍;每个文件约 0.6–0.7 ms 固定开销。
    2. 「一条一文件」换不来更快的单条增量:三档都在 0.6–1.1 ms。今天的增量本就是重写所属 type 文件,热缓存下 3 MB 只要 1 ms。
    3. 按簇分桶是唯一的中间档:2920 条时全量 76 ms,活跃规模下几乎与现状无差别。

    建议直接排除「一条一文件」。 除了这个倍数,数千个小文件的维护面(重命名、手工编辑、diff、将来进了 git 仓库的投影)也会跟着涨,而它换来的东西在这张表里看不到。粒度不动的话现状够用;真要按主题切,按簇分桶是可选项。

    表里的数只够支撑方向判断:缓存热、单进程、顺序执行,内容长度取均值,「簇」是顺序切 25 条而非真语义聚类(真聚类后桶大小不匀,只会更差一点)。

    顺带撞见一个比粒度更早的问题

    本机活跃库有 9 个 type,TYPE_FILE(src/mirror.js)只有 5 个,两处落点都按它过滤:

    • 镜像 mirror.js:187 只渲命中的 type,renderMirrorText 对未知 type 返回 undefined;
    • /export api.js:1004-1006 先按 TYPE_FILE[row.type] 丢行,:1008-1010 再只遍历那 5 个键出节。

    本机分布是 pitfall 69、constraint 51、rejected_solution 16、pattern 7:282 条活跃里 143 条从不进镜像,导出的 markdown 同样少一半,没有任何提示。constraint 与 pitfall 还是插件自己新增的 type,现在整类没有落点。

    第一批说「所有落点读到的形态同一套」,得先让所有 type 有落点。这条要不要并进第一批,你定。

    索引这一层

    文件化之后,跨 type 的「按主题找」没有入口,5 个文件就是全部。tag 的现状我核了:字段有、memory_save 收、镜像里渲成 - **标签**:;但它在 service.js 与 src/search/ 里只出现在去重、质量信号与叙事标签几处,没进检索权重。缺的更像是一个索引产物,tag 字段本身够用;而 summary 这个 type 已经是一个空着的索引位(TYPE_FILE.summary → summary.md)。

    这一层算第一批的一部分,还是并进第二批的投影?另外 tag 现在落在块内一行,要拿它建索引得先解析它;若第一批上了 frontmatter,它就落到文件头,外部工具与 agent 能直接聚合。

    两处小修正

    • src/export.js 不存在(src/ 下只有 mirror.js),/export 在 api.js:977-1011。
    • 镜像的行数上限 store.list({ limit: 500, ... })(service.js:1815)在活跃超过 500 时静默截断,本机 282 尚未触顶。
  6. modusensus commented on Sep 22, 2026

    @modusensus
    Member

    @heptaspirit 实测收下——这张表把「数千文件」从猜想变成了读数,当初「成本未实测」的悬念可以关掉了。

    拍板

    1. 粒度:维持按 type 分文件,正式排除「一条一文件」。 330 倍的全量重写差距(6 ms → 1998 ms)、增量写毫无收益(三档都是 0.6–1.1 ms——现状的增量本就是重写所属 type 文件,热缓存下 3 MB 只要 1 ms)、再算上数千小文件在 diff / 手工编辑 / 将来投影里的维护面:它在这张表里换不到任何东西。按簇分桶记为后备选项,只在将来出现「按主题做文件级操作」的真实需求时再启用。你这份数据建议原样带进第一批的 PR 说明,省得后人重测。

    2. 9-type 盲区:并进第一批,作为前置项。 你的判断是对的——「所有落点读到的形态同一套」的前提是所有 type 有落点,否则第一批做完,镜像与导出对一半活跃记忆仍然是盲的(你本机 143/282 这个数字本身就是验收理由)。constraint 和 pitfall 还是 v0.7.13 我们自己新增的 type,加的时候没接落点,这笔账该还。落点两处都要修:mirror 的渲染过滤(mirror.js:187 一带)与 /export 的 byType 丢弃 + 节遍历(api.js:1004-1010);新增 type 的文件名按现有命名风格定,readHumanEdits 走 Object.keys(TYPE_FILE) 表扩了自动跟上,但要补对应 type 的解析回归测试。顺带认领你两处修正里的第一处:src/export.js 是我上一楼的引用笔误,/export 逻辑在 api.js,以你核的位置为准。

    3. limit: 500 截断:一并修。 镜像宣称覆盖活跃集就不能带静默截断,这和盲区是同一类问题(落点存在但覆盖不完整、无提示),放进第一批顺手处理,不单开。

    4. tag / 索引层:拆两半。 格式层进第一批——frontmatter 定下来时 tag 落文件头,外部工具与 agent 直接可聚合,这属于格式约定本身;检索权重化不进第一批——那是 search/ 侧的改动,有自己的验收线(检索质量回归基准),混进落盘批次会把两个本可独立回滚的改动绑死,第二批再议。TYPE_FILE.summary 那个空索引位的观察很好,第一批定文件名时一并考虑去留。

    5. 配置说明:认账,我们来立项。 你描述的状态是真实的:user_settings 的 39 个开关存在库里没有文件可改,137 个 config 键没有一处汇总可查——这一轮 sensitivity 的混淆(你引错的建议口径、我引错的不存在文件,两端都中招)就是它缺位的直接后果。形态按你提的三点要求来:随功能更新维护、用户与 agent 都能读、与 #249 对同一开关说同一件事。单独立项,我们这边开单不让你背,开出来会链回这条线。第一批不与它耦合。

    第一批的验收范围因此更新为:落盘形态(含 frontmatter / tag 落头)+ 9-type 盲区 + 500 截断,粒度不动。你可以按这个范围出第一批的具体方案了。

  7. heptaspirit commented on Sep 22, 2026

    @heptaspirit
    CollaboratorAuthor

    @modusensus

    第一批实施方案

    范围按你的拍板:落盘形态(frontmatter + tag)+ 9-type 盲区 + 500 截断,粒度不动。

    需要你拍四点

    1. tag 落哪一层:一个 type 文件装 N 条,tag 落不到文件头。建议条目级照旧,文件头放本文件标签的并集(纯派生,供聚合)。
    2. 归档进不进镜像:store.list 默认不含归档,修好截断后归档仍不出现。建议不进,文件头写「活跃 N 条,不含归档」。
    3. 条目标签换不换英文键:建议不改(存量文件会整文件级 diff),只把名字收成单点定义。
    4. 文件头字段集:type / generated.by / generated.at / covered / coverage / tags。

    实施

    frontmatter 只能文件级,落两层:文件头放上列键;条目块沿用 ## <title> + - **ID**: 锚,按需补 scope / sensitivity / status。逐条 YAML 会打断 parseHumanEdits 的锚点契约(mirror.js:74-82),不在这批。

    9-type 盲区:TYPE_FILE 补 pitfalls.md / constraints.md / rejected-solutions.md / patterns.md(旧 5 个不动,改了会让磁盘上已有文件与 digest 失配);两处过滤(mirror.js:174/:187、service.js:1818、api.js:1004-1010)照表走;加断言「TYPE_FILE 覆盖 store 全部 type」防复发,新 type 各补解析往返测试。

    500 截断:service.js:1816 的 limit: 500 去掉,文件头写清覆盖范围。

    要先解决的风险:parseHumanEdits(mirror.js:111-113)的白名单是手写正则,渲染标签来自 lang.js 的 mirrorLabel(zh/en)。今天两处逐字一致(已核,无脏数据),但加字段要改三处,漏一处该行就随 /import 写进 content。改成常量表单点定义 + 往返测试。

    不做

    粒度;目录层级(继续平铺 memoryDir);第二个文件写入器(多一个可编辑落点=多一个 digest 合并面);元数据写回;tag 检索权重化。

  8. heptaspirit commented on Sep 22, 2026

    @heptaspirit
    CollaboratorAuthor

    @modusensus 按这个范围做完了,PR:#288。三件事都在,粒度没动。

    9-type 盲区:TYPE_FILE 补 pitfalls.md / constraints.md / rejected-solutions.md / patterns.md(旧 5 个名不动),两处过滤随表扩自动收敛。document 指针行显式排除(MIRROR_EXCLUDED_TYPES):镜像块渲染的字段里没有 doc_path,落一个 documents.md 会得到「看着完整、其实找不到文件」的视图,它的落点归 #230 那条线;TYPES \ TYPE_FILE 必须恰好等于排除集,测试双向钉住。

    文件头 frontmatter:type / generated.by / generated.at / covered / coverage / tags。键名逐个对过 OKF v0.2:type 是 §4.1 必填,generated.* 是 §5.2 的 trust 族(generated.by 按 §7 的 actor 约定写 dsh-mneme/<版本>),author 只在 sources[] 里当可信度信号、不是顶层键,updated 在 v0.2 里不存在(§13.1 已用 generated.at 取代 v0.1 的 timestamp)。covered / coverage 是我们自己的扩展。条目级元数据仍在块内,并按你拍的清单补齐:ID / 类型 / 重要性 / 标签 / 更新时间 / 来源 / 作用域(agent / workspace) / 敏感度(后两者有值才渲染);status 没有对应列:OKF 的 status: deprecated 对应归档,属写回语义,跟第三批的字段权威归属表一起定。块内逐条 YAML 会打断 import 必须吃回 export 的锚点契约,所以不走那条路。sensitivity 按讨论的结论只做信息展示,过滤语义不在这里落地。

    500 截断:改 store.all() + 同一套 !forgotten && !archived(不能只去掉 limit,store.list 的默认值是 50)。代价是全表读落在最热路径上,要压得换按 type 分页,属另一批。

    自查与一次单盲审查各抓出两条,都已修:① 无条件重写会让 generated.at 在每次业务写后改掉全部镜像文件的字节(编辑器会提示「被外部修改」,进行中的手工编辑有被冲掉的风险),现在内容没变就不落盘;② markdown 导出原先是 9 段文件头串在一个 .md 里,只有第一段是真 frontmatter、其余 type:/tags: 会变成正文段落,所以改成一份文档级 frontmatter(coverage: all,如实声明含 archived/forgotten)+ 分节。

    summary 那个位的去留一并答了:留着。它装的是 dream 生成的叙述条与状态摘要,有真实内容;删掉它就得进排除集,那才是真开盲区。

    读数:node --test 1292 / 1291 pass / 0 fail;check-sync src↔lib 一致(44 文件)。另有一处同类盲区想请教:service.js 的 INJECT_TYPES、summarize.js 的 VALID、tools.js 的 enum 仍是 5-type 字面量,本批范围只钉了 mirror + /export,所以没动。要不要另开一批,你定。

  9. heptaspirit commented on Sep 22, 2026

    @heptaspirit
    CollaboratorAuthor

    @modusensus 第一批的范围之外,想补一个「第二类落点」的问题:document 型记忆的全文放哪。

    现状:注册只校验「绝对路径 + 文件存在非空」(src/document.js:52-76),没有落点约定,也没有对应的配置键(目录相关的键只有 memoryDir)。也就是说 doc_path 是一个自由字符串,放工作区、放临时目录、放哪都收。文档一多,库里是一堆摘要行、文件系统里是一堆 md,两边都靠记忆找。

    本地实测(我在自己的实例上把一份整理稿按 document 型注册,再搬家一次):

    • 搬家 = 移动文件 + 同标题重注册,一次调用完成:返回 action: superseded,旧行归档并追加 [superseded by <新行 id>],旧摘要进 content_history(document.js:132-133 的同路径/同标题匹配)。
    • 反过来(改标题又换路径)会被 C2 门拒掉(DOCUMENT_MIN_SIM = 0.92,:139-159),按设计是防「搬家变成留两份互相漂移的指针」。这条我赞成,别绕过。

    会怎么乱(按后果排):①路径散落,每次都要重新决定放哪;②把工作区路径写进 doc_path,仓库改名/移动后指针全废,而且注册之后再没有任何地方读那个文件(全库只有 :73 一次 statSync),所以文件缺失与摘要漂移都没有检测点;③没有索引,文档多了无法浏览;④supersede 匹配带 scopeKeyOf,跨 scope 永不互判,同一份文档换工作区重注册会留两份指针行。

    一个提案(供讨论,不是方案):加 documentDir(默认 <memoryDir>/documents/,跟 memoryDir 可配置),配两级规则:

    • managed:路径在该目录下,允许建目录、校验命名(建议 <YYYY-MM-DD>-<slug>.md,按 workspace 再分一层);
    • external:路径在别处(项目自带的报告、别人给的文档),只做指针、一律不碰文件,即现状行为原样保留。
    • 每个根一份 index.md(按 OKF §8 的渐进式披露列 title / doc_path / 摘要首句 / updated),让文档多起来仍可按需读取。
    • 悬挂指针与摘要漂移不放在读路径(热路径不引 fs I/O),给一个批量体检入口更合适。

    想请你拍四点:①要不要加 documentDir 这个键;②managed 目录要不要 mneme 来建(今天是「管线对文件零读零写零改」,建目录算不算越界);③index.md 谁来维护(一碰自动写文件就回到写入权分离的边界);④文档要不要在镜像/工作区投影里有一个只读视图(今天 document 行被 MIRROR_EXCLUDED_TYPES 排除,镜像里看不到任何文档)。

    要不要单独开 issue、或并进哪个批次,你定;我这边先把实验记录留在本地。

  10. modusensus commented on Sep 23, 2026

    @modusensus
    Member

    @heptaspirit 单开 issue,跟 #230 那条线走——第一批已经做完在 #288,不再往里塞。四点先给方向,开单后正式拍:

    1. documentDir 加。 路径散落、每次重新决定放哪是真问题;默认 <memoryDir>/documents/ 跟 memoryDir 可配置的结构也对。
    2. 目录 mneme 建不算越界。 「管线对文件零读零写」的边界说的是记忆内容文件;managed 目录与 index.md 和镜像 md 同权(机器所有产物)。真正的红线是不碰 external 路径的文件,以及 managed 文件的正文内容。
    3. index.md 整文件机器所有。 不给手工编辑面,「谁维护」的问题就不存在;要人工批注,进记忆库,不进 index。
    4. 镜像只读视图倾向给(指针行 + 摘要首句,不含全文)。feat(mirror): 补落盘盲区、文件头 frontmatter 与无上限覆盖(#278 第一批) #288 把 document 排除是因为镜像块渲染字段缺 doc_path;等 [Feature] document 型记忆:agent 产出的长文档入库——注册校验、摘要+指针落库、supersede 记账 #230 线把 doc_path 纳入渲染字段后随 MIRROR_EXCLUDED_TYPES 复审——有测试钉着,去留都是一行的事。

    supersede 跨 scope 不互判那条,连同你「别绕过 C2 门」的立场一起带进单里,那条设计是对的。

  11. added a commit that references this issue on Sep 23, 2026
  12. modusensus commented on Oct 20, 2026

    @modusensus
    Member

    现状(2026-10-21 对照 main 实测)

    正文没有勾选框,是纯设计帖。实测:目录形态与镜像已落地,~/.dsh 迁出未做。

    已落地

    未落地

    • ~/.dsh 迁出:migrateOut / exportMirror 均 0 命中。当前默认仍在 ~/.dsh/mneme(注意:那是 embedding 模型缓存与 onnxruntime,不是记忆库;记忆库在 ~/.dsh/memory/memory.db)。
    • 统一导出格式:没有独立的导出器(memory_list/memory_search 有 include_archived,但没有「整库导出」入口)。

    还缺什么

    1. 迁出路径:把记忆库与镜像从 ~/.dsh 下挪到可配位置,并提供迁移工具(含回滚);
    2. 统一导出:一次导出「记忆表 + 镜像 + 审计」为单一格式(JSONL 或 SQLite),可被外部工具读;
    3. 目录归属文档:现在「哪个目录是什么」散在注释里,新人容易把 ~/.dsh/mneme(模型缓存)当成记忆库——这个坑我自己踩过。

    接手建议

    这条的三个子项互相独立,建议拆开。最容易先做且收益最高的是第 3 条(目录归属文档),因为它防的是真实发生过的误解。

  13. modusensus commented on Nov 11, 2026

    @modusensus
    Member

    按 2026-10-21 盘点的三个子项收口:

    已落地:

    剩余两环拆出:

    • 迁出 ~/.dsh(迁移工具含回滚 + 默认值口径)→ #NNNA
    • 统一导出(记忆表 + 镜像 + 审计,外部工具可读)→ #NNNB

    维持搁置: 工作区投影(<workspace>/.mneme/,#17 §五的回调草稿)——它与迁出互相牵制,库的位置定下来再议;若立项单开 issue,frontmatter 口径以 #NNNB 为准。

    本 issue 关闭留作设计帖索引;原正文对 Open Knowledge Format、回流权威归属的分析在两条新 issue 里各有承接。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions