memU 的 Skill 一等公民化:从 Memory Item/Category 到按 track 划分的 RecallFile/RecallEntry 存储(ADR 0006 详解) memU 的 Skill 一等公民化从 Memory Item/Category 到按 track 划分的 RecallFile/RecallEntry 存储ADR 0006 详解【免费下载链接】memUPersonal memory across agents项目地址: https://gitcode.com/GitHub_Trending/mem/memU本文围绕 memU 仓库中的架构决策记录 ADR 0006docs/adr/0006-from-memory-item-category-to-tracked-workspace-memorization.md展开讲清 memU 如何将skill技能从导出期才合成的磁盘产物升级为与 memory 对等的一等数据通过RecallFile.track列实现轨道隔离在memorize_workspace工作流内逐文件生成技能并在读侧提供免 LLM 的 track 感知检索。读完后你将理解 memU 双轨道memory/skill数据模型的来龙去脉、迁移与隔离机制以及memu list-files、commit 等命令背后的 track 语义便于你复用用一条track列在单一存储里划分多条记忆线的设计思路。背景skill 曾经是只活在磁盘上的合成产物memU 的前置决策 ADR 0004docs/adr/0004-workspace-memorize-and-memory-file-system.md落地了memorize_workspace与 markdown memory file system并刻意让skill/目录树只在导出期合成——它来自每个 source 共享的 description 主干既不持久化也不从抽取出的条目派生。按 ADR 0006 的 Context 描述skill 只存在于磁盘上MemoryFilesBuilder.build通过MemorySynthesizer合成一份slug - body映射导出器写出skill/slug/SKILL.md增量更新时再从磁盘读回这些文件read_skills作为下一轮合成中existing skills已有技能的一半上下文。ADR 0006 指出这种设计随着使用变得尴尬共三个问题skill 不是一等数据无法按user_data作用域过滤、无法查询、无法与结构化存储并列推理markdown 树本身就是数据库增量合成上下文靠反解析给增量合成提供已有技能的上下文靠的是把 markdown 从磁盘再解析回来而不是读一张表memory 与 skill 两条轨道不对称memory category 是RecallFile行确定性渲染进memory/而 skill 是一个瞬时的合成产物在导出器里走一条平行但不同的代码路径。与此同时memory 轨道在memorize_workspace的逐文件循环里已经在做 skill 想要的同一形状的工作取预处理内容经 LLM patch 折叠进持久的RecallFile.summary当时的列名。ADR 的结论是skill 应当复用这套机制而不是另起炉灶。域词汇先行MemoryItem → RecallEntry、MemoryCategory → RecallFile该 ADR 的第一次提交是一次全域纯重命名无行为变化奠定了后续工作使用的词汇旧名称新名称说明MemoryItemRecallEntry可检索条目MemoryCategoryRecallFile分类文件现在承载 memory 与 skill 两类内容CategoryItemRecallFileEntry文件下的条目对应 repositoriesRecallFile/RecallEntry系 repository同步重命名在当前仓库中可以看到这套词汇已经全面落地src/memu/database/models.py 中定义了RecallFile与RecallFileSegmentsrc/memu/database/repositories/recall_file.py 定义了RecallFileRepo协议契约list_recall_files、get_or_create_recall_file、update_recall_file等SQLite/Postgres/in-memory 三套后端各自实现如 src/memu/database/sqlite/repositories/recall_file_repo.py。数据模型track列、summary→content重命名与键控ADR 0006 的核心决定是让 skill 成为一等RecallFile用新的track列区分它与 memory category并在 memorize 工作流内部workspace 路径生成 skill而不是在导出期临时生成。具体数据模型变化有三点新增RecallFile.track: str取值memory | skill默认memory存量行回填为memory。重命名RecallFile.summary→RecallFile.content。summary这个名字早于MemoryCategory→RecallFile的重命名该列实际存放文件正文content才是正确命名。注意RecallEntry.summary保持不变——只重命名文件级列。由于添加track列本身就必须做一次迁移两个改动合并进同一次迁移发布。get_or_create_category的键改为(name, track, *scope)Postgres 带作用域的唯一索引同样变为(name, track, *scope)——于是 skill 与 memory category 即使同名也不会冲突。当前仓库的实现与 ADR 描述一致。领域模型后端无关的 Pydantic 记录在 src/memu/database/models.pyclass RecallFile(BaseRecord): name: str # Which track this file belongs to: memory (a memory file) or skill # (a synthesized skill). Defaults to memory so existing rows backfill correctly. track: str memory description: str embedding: list[float] | None None content: str | None NoneSQLite 后端把默认值做成了数据库层约束保证存量回填与插入一致性src/memu/database/sqlite/models.pytrack: str Field(defaultmemory, sa_columnColumn(String, nullableFalse, server_defaultmemory))仓库契约则精确体现了按(name, track, 用户作用域)匹配或插入的键控以及空值回填backfill语义src/memu/database/repositories/recall_file.pydef get_or_create_recall_file( self, *, name: str, description: str, embedding: list[float], user_data: dict[str, Any], track: str memory, ) - RecallFile: Match on (name, track, user scope), or insert a new file. ...轨道隔离所有读路径通过where按 track 过滤ADR 没有引入 ad-hoc 的后置过滤而是让每个RecallFile读路径都通过既有的where过滤器按 track 作用域化memory/categorize/retrieve/CRUD 路径以及导出器渲染memory/时读trackmemoryskill 生成步骤与未来的skill 渲染读trackskill。由此 skill 被排除在 memory 检索/RAG 打分之外也排除在memory/渲染之外。这一点在当前代码中依然可见例如commit_results在解析待提交文件时按 track 分组一次性列出既有行src/memu/app/agentic.py 中对store.recall_file_repo.list_recall_files(where{**user_data, track: track})的调用检索侧同样把 track 作为普通列谓词下推见下文RetrieveFileConfig.tracks。一个值得注意的衍生设计RecallFileSegmentADR 0007 引入的文件切片/L2 检索单元也把track反范化了一份——从 src/memu/database/models.py 的注释看切片在文件被重新切片时是丢弃重建的track在其生命周期内不可变因此检索可以直接用列谓词按 track 过滤而不需要 join 回文件表。工作流内生成 skillmemorize_workspace与逐文件 skill 步骤ADR 0006 决定不重载共享的memorize管线单文件memorize()保持不变呼应 ADR 0004 不动memorize 的立场而是让_memorize_onememorize_workspace的逐文件路径运行一条新工作流memorize_workspace既有memorize步骤 一个 skill 生成步骤。该新步骤的行为逐文件执行位于 memory persist 步骤之后输入是preprocessed_resources与 memory categorize/persist 的输出无数据依赖从数据库读既有trackskill的RecallFile作为已有技能上下文——不再是解析磁盘 markdown——于是第N个文件能看到第1..N-1个文件创建的技能复用既有的技能合成 prompt/算法处理后内容 已有技能 → 要新增/替换的技能但输出升级为(name, description, body)三元组直接将每个技能持久化为RecallFile(trackskill, contentbody)embedding 使用name description完全绕开RecallEntry平面受memory_files_config.synthesize门控。当前仓库中workspace 记忆的落地形态保留了这套轨道子目录结构src/memu/app/memorize/lifecycle.py 中MemorizeWorkspace按TRACK_DIRS暴露memory/与skill/两个子目录manifest 快照/差异snapshot_tracked/diff_tracked以轨道子目录为单位追踪变更文件src/memu/hosts/bridging/layout.py。CLI 侧memu memorize prepare/commit命令驱动这条 developer self-evolve 路径src/memu/cli.pycommit 后的输出即带轨道前缀- {track}/{name}见 _cmd_memorize_commit 的打印逻辑——每个提交的 recall file 都会以memory/name或skill/name的形式列出这正是skill 成为可按 track 查询的一等行在用户可见层面的直接体现。导出镜像skill/slug.md 合成/索引化的SKILL.md总览ADR 0006 让导出器与 memory 轨道完全镜像导出器把每个trackskill的RecallFile确定性渲染为skill/slug.md与memory/slug.md由 memory 轨道文件渲染完全对称二者共用同一个与 track 无关的渲染器合成器的 skill 职责被削减为只产出单一总览SKILL.md正文MEMORY.md的镜像由各 skill 文件的正文合成而不再是逐 skill 的正文旧的基于 description 的技能合成、RecallEntryskill 旁路、skill/slug/SKILL.md目录布局全部移除read_skills被read_skill_body取代对应read_memory_body的 SKILL.md 总览版本synthesizeFalse时SKILL.md回退为确定性的链接索引镜像MEMORY.md的行为。也就是说磁盘上的 markdown 树从skill 的唯一住所降级为确定性投影单一事实来源是数据库中按 track 划分的RecallFile行。读侧消费轨道免 LLM 的单次向量检索与tracks过滤ADR 0006 指出track列同时打开了读侧新增retrieve_workspace入口——memorize_workspace的读侧对偶与路由繁重的retrieve相比是简单版——执行单次、免 LLM的检索查询只 embed 一次按向量相似度对 file/entry/resource 各层排序不含意图路由、充分性检查或摘要生成文件层通过RetrieveFileConfig.tracks按 track 作用域化因此 skill 轨道的RecallFile可以独立于 memory 轨道被检索或被排除——这是上文写侧轨道隔离在读侧的镜像。该路径由新的RetrieveWorkspaceConfig驱动。在当前仓库中这条读侧路径体现为progressive_retrievesrc/memu/app/agentic.py 中query 只 embed 一次、file-segment 与 resource 层各自按向量相似度排序、无路由/充分性/摘要的实现其配置类与 ADR 描述的形态对应class RetrieveFileConfig(BaseModel): enabled: bool Field(defaultTrue, descriptionWhether to enable file retrieval.) top_k: int Field(default5, descriptionTotal number of files to retrieve.) tracks: list[str] | None Field( defaultNone, descriptionOptional file tracks (e.g. [memory, skill]) to filter on. None means all tracks., ) class ProgressiveRetrieveConfig(BaseModel): Configure the single-shot, LLM-free retrieval (progressive_retrieve). ... file: RetrieveFileConfig Field(defaultRetrieveFileConfig()) resource: RetrieveResourceConfig Field(defaultRetrieveResourceConfig())见 src/memu/app/settings.py。tracksNone表示不过滤检索全部轨道设置[skill]即可只召回技能文件。CLI 的memu retrieve ...命令即走这条免 LLM 路径src/memu/cli.pyhelp 文案即Single-shot embedding retrieval over segments/files/resources (LLM-free, fast)。轨道在切片层同样生效从 src/memu/app/agentic.py 的_commit_segment_texts_for_file看segment 文本按 track 差异化生成——skill轨道整文件只生成一条name: ...\ndescription: ...切片技能名与描述是检索入口memory轨道则逐行生成切片跳过空行与 markdown 标题、保序去重。测试侧tests/test_segment_vector_search.py、tests/test_agentic.py 等用例对 track 相关行为有大量断言可作为验证入口。该 ADR 的提交范围四个提交 一个无关修复ADR 明确圈定了自己在分支上的落点共四个提交第五个——把新 category 创建门控在allow_new_categories之后——是既有分类法路径的无关修复域重命名基础。MemoryItem→RecallEntry、MemoryCategory→RecallFile、CategoryItem→RecallFileEntry及对应 repository。纯重命名无行为变化确立后续工作依赖的词汇。Skill 轨道 工作流。Schema 基础track列、summary→content、(name, track)键控、迁移、轨道隔离memory 轨道读路径过滤trackmemory、以及带逐文件 skill 生成步骤的新memorize_workspace工作流。导出镜像。skill/slug.md从 DB 确定性渲染 合成/索引化的SKILL.md总览与memory/MEMORY.md对称。Workspace 检索。免 LLM 的retrieve_workspace路径把轨道读回来。遗留问题、代价与后果遗留问题ADR 明确 deferredskill 的陈旧性/删除溯源。因为绕开了RecallEntry平面skill 的RecallFile没有指回产生它的 resource的链接。memory 轨道能正确失效靠的是RecallEntry.resource_id → RecallFileEntry → category级联_cascade_delete_by_urlsskill 没有对应机制源文件删除后派生的技能会滞留。ADR 给出的候选方案是技能文件到贡献 resource 的溯源链接或diff.has_removals时整体重建 skill 轨道并声明在那之前 skill 轨道只做追加/合并删除不会使其收缩。成本/延迟。技能合成从每次 workspace 同步约 1 次合并 LLM 调用变为每个新增/变更文件 1 次调用与逐文件 memory 摘要的既有做法一致且受synthesize门控外加每次导出 1 次SKILL.md总览合成MEMORY.md合成的镜像。ADR 认为可接受但明确记录在案。正面后果skill 成为一等、可作用域化、可查询的行markdown 树不再是 skill 的唯一住所增量生成的已有技能上下文是一次表读取而非 markdown 反解析memory 与 skill 轨道对称同为RecallFile行以track区分两条分叉的代码路径收敛单文件memorize()不受影响skill 步骤只存在于 workspace 路径skill 成为一等行后读侧可按track把技能读回来workspace 因此获得对称的 memorize/retrieve 对无需为 skill 专门开读路径。负面后果需要一次 schema 迁移加tracksummary→content既有 Postgres/SQLite 部署必须应用新库由create_all直接获得每个RecallFile读路径现在都必须 track-aware这是一个贯穿where的横切关注点skill 的删除/溯源是已知的、被推迟的缺口。小结如何在 memU 中复用这套双轨道模型把 ADR 0006 抽象成可复用模式可以归纳为四步单一存储 track列用一列枚举memory | skill在既有表内划分数据轨道键控与唯一索引带上track使不同轨道可同名写侧在工作流内生成新轨道的生产放进逐文件工作流新步骤挂在新工作流上而非重载共享管线既有内容上下文一律从库内读取保证第N次处理可见前N-1次的产物导出是确定性投影磁盘文件全部由 DB 行渲染合成层只负责总览MEMORY.md/SKILL.md关闭合成时回退为链接索引读侧用普通列谓词消费轨道检索配置暴露tracks白名单None 全部配合把track反范化到切片表避免 join。在 memU 中实操时常用入口是 src/memu/cli.py 暴露的memu retrieve单次向量检索、memu list-filesList every recall file across the memory and skill tracks输出track/name: description见 list 命令实现与memu memorize prepare/commitdeveloper self-evolve 提交workspace 位于~/.memu/developer本地模式通过--db指定 SQLite默认./data/memu.sqlite3。理解以上链路后你可以直接以track 感知的RecallFile行为对象去扩展自己的记忆轨道而不必再维护第二套磁盘状态。【免费下载链接】memUPersonal memory across agents项目地址: https://gitcode.com/GitHub_Trending/mem/memU创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考