跳到主要内容

MOSCore:记忆操作系统的内核

30 秒导读: MemOS 把"记忆"当成一种操作系统资源来管。MOSCore 就是这个 OS 的内核——它自己不实现记忆算法,而是像内核调度进程一样,编排多个用户多个 MemCube(记忆容器)上的记忆操作与对话。你调用 add/search/chat,内核负责鉴权、路由到对的 Cube、把检索到的记忆拼进 prompt、再把结果回写。真正"记忆怎么抽取、怎么组织、怎么检索"由后续各章的组件完成。

本章讲内核如何编排,不深入记忆内部算法(那委托给 MemReader图谱组织检索管线调度器)。想先建立"三类记忆 + MemCube 容器"的心智模型,请先读 第 1 章


1. 这是什么(零基础也能懂)

一句话定义: MOSCore(Memory Operating System Core)是一个记忆的操作系统内核——统一入口,管理若干 MemCube(记忆立方,装记忆的容器),并在多用户场景下提供增删查改与"带记忆的对话"。

它解决什么问题? 假设你在做一个能"记住用户"的 AI 助手。你会立刻遇到一堆脏活:

  • 记忆存在哪个容器里?一个用户可能有多个 Cube,一个 Cube 可能被多个用户共享。
  • 谁能读谁的记忆?多租户下必须鉴权,不能让 A 搜到 B 的私人记忆。
  • 对话时怎么把"相关记忆"喂给大模型?检索、拼 prompt、回写历史,每次都要做。

MOSCore 把这些编排逻辑收拢到一处。你的应用只跟内核打交道,不用自己拼装记忆管线。

它对外能做什么(公共 API): 记忆的 CRUD + 对话 + 用户/Cube 治理。核心就这几组动词:

动词方法干什么
add把消息/文档/纯文本变成记忆,存进某个 Cube
search / get / get_all按 query 检索、按 id 取、取全部
改删update / delete / delete_all改一条、删一条、清空一个 Cube
chat带着检索到的记忆跟 LLM 对话
治理create_user / register_mem_cube / share_cube_with_user建用户、挂载 Cube、共享 Cube

用起来什么样: 一个最小真实用法(来自 src/memos/mem_os/main.py:79MOS.simple() docstring 示例):

# 示意,非源码(改写自 main.py:94-104 的 docstring)
memory = MOS.simple() # 读环境变量自动配置,并自动挂载一个默认 Cube
memory.add("Hello world!") # 记一句话
response = memory.chat("What did I just say?") # 带着记忆回答

一句话直觉:MOSCore操作系统内核MemCube进程/文件UserManager权限系统。内核自己不干活,它调度资源、做鉴权、串流程。


2. 顶层全景(内核大概怎么转)

内核由三块协作:公共 API 面(对外动词)、治理层(用户与 Cube 的鉴权、路由)、执行层(把请求落到具体 MemCube 的记忆组件上)。

怎么读下面这张图: 从上到下是一次调用的穿透顺序——先过 API,再过治理鉴权,最后落到某个 Cube 的记忆组件。

┌─────────────────────────────────────────┐
你的应用 ──▶ │ MOSCore 公共 API 面 │
│ add / search / get / update / delete │
│ / chat / register_mem_cube ... │
└───────────────────┬─────────────────────┘
│ ① 鉴权 + 路由

┌─────────────────────────────────────────┐
│ 治理层 UserManager (SQLite) │
│ 校验 user 存在? 有权访问这个 cube? │
│ 一个 user ⇄ 多个 cube(多对多) │
└───────────────────┬─────────────────────┘
│ ② 拿到可访问的 cube_id

┌─────────────────────────────────────────┐
│ 执行层 self.mem_cubes[cube_id] │
│ .text_mem / .act_mem / .pref_mem │
│ (真正的记忆抽取/组织/检索在这些组件) │
└─────────────────────────────────────────┘

③ 可选:把请求异步抛给 MemScheduler(见第 6 章)

部件一句话职责:

部件干什么在哪
MOSCore内核本体,编排一切src/memos/mem_os/core.py:38
self.mem_cubescube_id → GeneralMemCube 的字典(多用户时用线程安全字典)core.py:53
UserManager用户/Cube 的持久化与鉴权(SQLAlchemy + SQLite)src/memos/mem_user/user_manager.py:97
self.chat_history_manageruser_id → ChatHistory,逐用户维护对话历史core.py:51
MOS(子类)面向易用性的增强:自动配置 + CoT 分解式检索src/memos/mem_os/main.py:24
mem_scheduler可选的异步调度器,接线开关core.py:82@property

主线走一遍(高层): 应用调 chat("...") → 内核查该用户可访问的 Cube → 在每个 Cube 的 text_mem 上检索记忆 → 把记忆拼进 system prompt → 连同对话历史喂给 LLM → 回写这轮问答到历史。下一节展开这条链。


3. 核心原理(逐个机制)

3.1 chat 主链:一次"带记忆的对话"是怎么串起来的

它要解决的小问题: 光有 LLM 不会"记事"。要让回答带上用户的历史与偏好,就得在每次对话前临时检索相关记忆并注入 prompt——但不能让模型觉得自己在"读数据库"。

思路: chat 不是简单转发。它是一条固定管线:检索 → 拼 prompt → 生成 → 回写

怎么读这张流程图: 从左到右是 MOSCore.chat 的时间顺序,中间三步是内核自己做的编排。

query ─▶ ① 查该 user 可访问的 cube
(user_manager.get_user_cubes)


② 逐 cube 在 text_mem 上检索
(cube.text_mem.search,top_k=config.top_k)
│ memories_all

③ 拼 system prompt
(_build_system_prompt:把记忆编号成 1. 2. 3. ...)


④ [system] + 历史对话 + [user query] ─▶ chat_llm.generate
│ response

⑤ 回写这轮问答到 chat_history
(+ 可选:把 query/answer 抛给 scheduler)

原理演示: 把这条链的骨架用简化代码演出来。

# 示意,非源码:还原 core.py:251 chat 的主干
def chat(self, query, user_id=None):
target = user_id or self.user_id
cubes = self.user_manager.get_user_cubes(target) # ① 鉴权+路由
history = self.chat_history_manager[target]

memories = []
for cube_id, cube in self.mem_cubes.items():
if cube_id in {c.cube_id for c in cubes} and cube.text_mem:
memories += cube.text_mem.search(query, top_k=self.config.top_k) # ② 检索

system = self._build_system_prompt(memories) # ③ 拼 prompt
messages = [{"role": "system", "content": system},
*history.chat_history,
{"role": "user", "content": query}] # ④ 组消息
response = self.chat_llm.generate(messages) # ④ 生成
history.chat_history += [{"role": "user", "content": query},
{"role": "assistant", "content": response}] # ⑤ 回写
return response

真实实现: 主体在 core.py:251chat。检索段在 core.py:273-303if self.config.enable_textual_memory and self.mem_cubes: 里逐 Cube 调 mem_cube.text_mem.search);消息组装在 core.py:306-310;回写在 core.py:334-336

关键细节 / 坑:

  • 记忆如何变成文本?_build_system_promptcore.py:354):把每条记忆编号成 1. …\n2. …,若 base_prompt 里含 {memories} 占位符就 .format 填进去,否则追加一个 ## Memories: 段(core.py:382-387)。展示用的格式化另有 _str_memoriescore.py:390)。
  • 激活记忆(KV-cache) 只在 huggingface 后端生效:chat 会取第一个 Cube 的 act_mempast_key_values 传给 generatecore.py:313-330),其它后端直接跳过并告警。
  • 一个真实小 bug(inferred): 回写那行是 self.chat_history_manager[user_id] = chat_historycore.py:336),用的是原始入参 user_id 而非 target_user_id。当调用方不传 user_idNone)时,历史会被写到键 None 下,而读取用的是 target_user_id——两者不一致。这是内核里值得注意的一处不对称。

3.2 多用户与多 Cube 治理:鉴权与路由

它要解决的小问题: 多租户下,"这条 add 该落到哪个 Cube""这个 user 能不能 search 那个 Cube"必须有人拍板。内核在每个动词入口都先过一遍鉴权。

两个私有守卫(内核的"门禁"):

守卫作用位置
_validate_user_existsuser 不存在或未激活 → 抛 ValueErrorcore.py:200
_validate_cube_access先验 user,再验 user 是否有权访问该 cubecore.py:214

_validate_cube_access 内部委托给 UserManager.validate_user_cube_accessuser_manager.py:305):owner 直接放行,否则查多对多关联表core.py:228user_manager.py:328-332)。

数据模型:user 与 cube 是多对多。 治理数据不放内存,落在 SQLite(SQLAlchemy)。

users user_cube_association cubes
┌───────────┐ ┌──────────────────┐ ┌────────────┐
│ user_id PK│◀──────▶│ user_id (FK) │◀──────▶│ cube_id PK │
│ user_name │ │ cube_id (FK) │ │ owner_id FK│
│ role │ └──────────────────┘ │ cube_path │
│ is_active │ (多对多关联表) │ is_active │
└───────────┘ └────────────┘
│ owned_cubes(一对多:owner) ▲
└──────────────────────────────────────────────────┘
  • 关联表定义:user_cube_associationuser_manager.py:47)。
  • User / Cube 模型与角色枚举:user_manager.py:56 / user_manager.py:76 / UserRoleuser_manager.py:37ROOT/ADMIN/USER/GUEST)。

治理动词一览(内核 → UserManager):

内核方法做什么委托到
create_usercore.py:406建用户user_manager.create_useruser_manager.py:149
register_mem_cubecore.py:460把 Cube 挂载进内存 mem_cubes 并在库里建/关联create_cube / add_user_to_cube
unregister_mem_cubecore.py:534从内存 mem_cubes 移除(不动数据库)
share_cube_with_usercore.py:1162把 Cube 授权给另一个 useruser_manager.add_user_to_cubeuser_manager.py:356

register_mem_cube 的三种来源(core.py:485-496): 传进来的既可以是已构造的 GeneralMemCube 对象,也可以是本地目录路径init_from_dir),还可以是远程仓库名init_from_remote_repo)。挂载后还会检查 Cube 的 embedder 与 MOSConfig 是否一致,不一致仅告警并以 Cube 的为准(core.py:501-508)。

关键细节 / 坑:

  • 默认 Cube = 最近创建的那个。 add/get/... 在不指定 mem_cube_id 时,取 get_user_cubes(...)[0]core.py:724 等,注释还标着 # TODO not only first)。而 get_user_cubescreated_at 倒序排(user_manager.py:352),所以"默认"其实是"最新"。
  • share_cube_with_user 参数顺序可疑(inferred): 它调 self._validate_cube_access(cube_id, target_user_id)core.py:1173),而 _validate_cube_access 的签名是 (user_id, cube_id)core.py:214)——两个实参位置对调了。看起来是一处未被测试覆盖的错位。
  • unregistercreate/register 不对称: unregister 只删内存字典、不软删数据库;而 register 会同时写库。

3.3 MOS 子类:自动配置 + CoT 分解式检索

MOSmain.py:24)继承 MOSCore,加两样"糖",内核语义不变。

① 自动配置 simple()main.py:79): 无参构造时读环境变量(OPENAI_API_KEY 等)走 _auto_configuremain.py:57),并自动挂载一个默认 Cubemain.py:51-55)。这就是 §1 那个三行示例能跑起来的原因。

② CoT(Chain-of-Thought)分解式检索: 只在 PRO_MODE 打开时启用(self.enable_cotmain.py:44)。它把"一个复杂问题"拆成子问题、各自检索作答、再合成。

怎么读这张图: 从上到下是 _chat_with_cot_enhancementmain.py:131)的判断与分支;任何一步不满足就回退到父类 super().chat

query

▼ cot_decompose(query) —— LLM 判断"是否复杂"并拆子问题 (main.py:349)

├─ is_complex == False ──────────────▶ 回退 super().chat(标准链)

▼ is_complex == True
get_sub_answers(sub_questions, ...) —— 每个子问题并行检索+作答 (main.py:401)

▼ _generate_enhanced_response_with_context —— 把 Q/A 拼进 SYNTHESIS_PROMPT 合成终答
│ (main.py:256)
▼ 回写历史(与标准链一致)
  • 拆解 cot_decomposemain.py:349@classmethod):用 COT_DECOMPOSE_PROMPTtemplates/mos_prompts.py:1)让 LLM 返回 {"is_complex": bool, "sub_questions": [...]},并对非法 JSON 做正则兜底(main.py:383-396)。
  • 子问题作答 get_sub_answersmain.py:401):_search_with_enginemain.py:514)用线程池并行检索每个子问题(保序),再并行让 LLM 逐题作答。
  • 合成 _generate_enhanced_response_with_contextmain.py:256):把 Q1/A1、Q2/A2… 拼进 SYNTHESIS_PROMPTmos_prompts.py:52)接在 system prompt 后。
  • 兜底哲学: 全程被一个大 try/except 包住,任何异常都退回标准 chatmain.py:227-230),保证"增强失败也能答"。

关键细节: chat 本身只做一个开关判断——enable_cot 关就 super().chat,开就走增强链(main.py:124-129)。增强链会复用父类的 _build_system_promptmain.py:296),保证 prompt 组装规则一致。


4. 调度器开关:内核只做接线

内核对 mem_scheduler 的态度是**"只接线,不管细节"**。启停就是几个薄方法,异步摄取/激活刷新的真正逻辑在 第 6 章

方法作用位置
enable_mem_scheduler从 config 读的总开关core.py:72
_initialize_mem_scheduler首次访问时按 config 造实例并 start()core.py:111
mem_scheduler_on / off手动启停core.py:141 / core.py:153
mem_scheduler(property)懒加载并把 mem_cubes 注入调度器core.py:82

接线点在哪: add/chat/search 里凡是 if self.enable_mem_scheduler and self.mem_scheduler is not None: 的分支,都是把一个 ScheduleMessageItem 抛进调度器(如 core.py:283core.py:342core.py:773),带上 QUERY_TASK_LABEL / ANSWER_TASK_LABEL / ADD_TASK_LABEL 等标签。内核只负责"投递消息",怎么消费是调度器的事。

内核里还留着一组 reorganizer 开关(mem_reorganizer_off/_waitcore.py:172/core.py:181),把"关闭/等待记忆重组"转发到各 Cube 的 text_mem.memory_manager——同样是接线,重组细节见 第 4 章


5. 产品入口:/product FastAPI 路由(薄封装,但另起一套)

诚实提示: 任务设想 /product 路由是"薄封装到 MOSCore"。但按当前源码,/product 走的是一套独立的、基于 handler 类的架构,并不直接调用 MOSCore——它操作一个共享的 naive_mem_cube 与"多 Cube 视图"。二者是平行的两条代码路径MOSCore 是库内核(Python 直接用),/product 是产品服务器(HTTP 用)。它们复用底层同一批记忆组件,但编排入口不同。

路由确实很薄。 端点只做"转发给 handler",无业务逻辑:

# 示意,非源码:还原 server_router.py:112 / :128 的薄转发
@router.post("/search") # server_router.py:111,prefix="/product"(:68)
def search_memories(search_req):
return search_handler.handle_search_memories(search_req) # :118

@router.post("/add") # server_router.py:127
def add_memories(add_req):
return add_handler.handle_add_memories(add_req) # :134

真正干活的是 handler + Cube 视图:

POST /product/search

▼ SearchHandler.handle_search_memories (search_handler.py:67)
│ _build_cube_view(req) ─▶ Single/CompositeCubeView
│ cube_view.search_memories(req)
│ → 去重(sim/mmr) → 相关度过滤 → rerank (search_handler.py:100-120)

SearchResponse

POST /product/add

▼ AddHandler.handle_add_memories (add_handler.py:42)
│ _build_cube_view(req) ─▶ cube_view.add_memories(req) (add_handler.py:109)

MemoryResponse
  • Handler 靠依赖注入拿到共享组件:HandlerDependenciesbase_handler.py:18)持有 naive_mem_cube / mem_reader / mem_scheduler / reranker 等,在 server_router.py:74-95 一次性初始化并注入所有 handler。
  • 与内核不同:/product 的 search 做了内核 search 没有的产品化后处理——去重(dedup="sim"/"mmr",会先把 top_k 放大 3 倍再收敛,search_handler.py:86-87)、相关度阈值过滤、以及 rerank(rerank_knowledge_memsearch_handler.py:114)。
  • 记忆写入统一经 cube_view.add_memoriesadd_handler.py:109),Cube 视图分 SingleCubeView / CompositeCubeViewadd_handler.py:130-159),按请求解析出的 cube_id 数量决定单 Cube 还是组合 Cube。

一句话对照:

维度MOSCore(库内核)/product 路由(产品服务器)
入口Python 方法 add/search/chatHTTP POST /product/*
记忆容器self.mem_cubes[cube_id](多 Cube 字典)共享 naive_mem_cube + CubeView
鉴权UserManager 多对多请求里带 cube_ids,由 handler 解析
检索后处理无(直接返回 text_mem/pref_mem去重 + 阈值 + rerank + hook

6. 巧妙之处(可借鉴)

  • "操作系统"抽象本身。 内核不存记忆、不实现算法,只做鉴权 + 路由 + prompt 编排,把三类记忆组件(text/act/pref)当可插拔资源。换记忆实现不动内核。见 MOSCore 类 docstring(core.py:38-43)。
  • 每个动词入口统一鉴权。 add/get/update/delete/... 都先 _validate_cube_accesscore.py:214),把"多租户安全"收敛到一个守卫,不散落在各处。
  • prompt 组装单点化。 标准链与 CoT 增强链共用 _build_system_promptcore.py:354),保证"记忆怎么进 prompt"只有一处真相,占位符 {memories} 让调用方可自定义模板而不破坏格式。
  • 增强永远可回退。 CoT 用大 try/except 包住并在每个失败点 super().chatmain.py:227),把"高级特性"做成纯增量,坏了不影响基础对话。
  • 多用户下的线程安全字典。 产品/服务器场景 mem_cubesOptimizedThreadSafeDict,单用户场景退化为普通 dict(core.py:53-55),按需付费。

7. 边界与局限(诚实)

  • "默认 Cube = 最新 Cube" 是隐式约定。 多处 accessible_cubes[0] + # TODO not only first(如 core.py:724)意味着多 Cube 时的默认选择不精确,依赖创建时间倒序。
  • update 不支持 tree_text 后端。 tree_text 记忆调 update 只告警不改(core.py:1028-1034)。
  • 激活记忆仅限 huggingface 后端。 其它后端直接跳过并 error 日志(core.py:313-317)。
  • 两处疑似 bug(inferred,见 §3.1 / §3.2): chat 回写用错 user_idcore.py:336);share_cube_with_user_validate_cube_access 实参顺序对调(core.py:1173)。
  • 内核与 /product 是两套编排。 想要产品化的去重/rerank,得走 /product;直接用 MOSCore.search 拿到的是未后处理的原始结果。

8. 代码地图(导航索引)

主题文件符号
内核类 / 初始化src/memos/mem_os/core.pyMOSCoreMOSCore.__init__
chat 主链src/memos/mem_os/core.pyMOSCore.chat
prompt 组装src/memos/mem_os/core.pyMOSCore._build_system_promptMOSCore._str_memories
记忆 CRUDsrc/memos/mem_os/core.pyMOSCore.addsearchgetget_allupdatedeletedelete_all
鉴权守卫src/memos/mem_os/core.pyMOSCore._validate_user_exists_validate_cube_access
Cube 挂载/共享src/memos/mem_os/core.pyMOSCore.register_mem_cubeunregister_mem_cubeshare_cube_with_user
对话历史src/memos/mem_os/core.pyMOSCore._register_chat_historychat_history_manager
query 改写src/memos/mem_os/core.pyMOSCore.get_query_rewrite
调度器接线src/memos/mem_os/core.pyMOSCore.mem_scheduler_initialize_mem_schedulermem_scheduler_on/off
MOS 子类 / 自动配置src/memos/mem_os/main.pyMOSMOS.simpleMOS._auto_configure
CoT 分解式检索src/memos/mem_os/main.pyMOS._chat_with_cot_enhancementcot_decomposeget_sub_answers_search_with_engine
用户/Cube 治理src/memos/mem_user/user_manager.pyUserManagercreate_uservalidate_user_cube_accessget_user_cubesadd_user_to_cubeuser_cube_association
产品路由(薄转发)src/memos/api/routers/server_router.pysearch_memoriesadd_memoriesrouter(prefix /product)
产品 handlersrc/memos/api/handlers/search_handler.pyadd_handler.pybase_handler.pySearchHandler.handle_search_memoriesAddHandler.handle_add_memoriesHandlerDependencies
prompt 模板src/memos/templates/mos_prompts.pyCOT_DECOMPOSE_PROMPTSYNTHESIS_PROMPTQUERY_REWRITING_PROMPT