LifeOS Principal Hot-Layer Memory 深度解析:从模板文件到自治记忆系统的完整链路
2026/9/14 11:07:57 网站建设 项目流程

LifeOS Principal Hot-Layer Memory 深度解析:从模板文件到自治记忆系统的完整链路

【免费下载链接】LifeOS⛰️ The Life Operating System — an intent engineering platform that moves you from your current state to your ideal state, in life and work.项目地址: https://gitcode.com/GitHub_Trending/pe/LifeOS

导读

PRINCIPAL_MEMORY.md是 LifeOS 中"用户(Principal)热层记忆"的唯一事实载体:它平时是一个空的模板文件,由后台的 Memory Reviewer 在每次会话结束后自动将关于你的耐久事实写入其中,并在每一个后续提示词中自动加载,让 Digital Assistant(DA)始终带着关于你的最新认知与你协作。本文基于该模板的完整规格与仓库源码,从文件结构、条目规范、写入链路、触发时机到恢复机制,逐层拆解这套"无需手动维护、自动策展、自动遗忘"的自治记忆系统的实现原理与配置方式。

一、模板文件定位:为什么它"应该保持为空"

在 LifeOS/install/USER/PRINCIPAL/PRINCIPAL_MEMORY.md 中,文件首部的引言明确给出了它的定位:

SAMPLE TEMPLATE — auto-curated hot-layer memory about you. It starts empty and the memory reviewer writes durable facts here as you work with your DA. Nothing to fill in manually. Pulse's memory panel reads this file.

翻译过来就是:这是"关于你的、由系统自动策展的热层记忆",初始为空,记忆评审器(memory reviewer)会在你与 DA 协作的过程中把耐久事实写入这里,无需手动填写。这正是本模板与普通笔记模板的本质区别——它不是给人填写的内容容器,而是一个"写入面(write surface)",由后台自治循环独占写入。

因此,新安装的 LifeOS 上该文件为空是预期状态(模板注释原文:"Empty on a fresh install — this is expected"),而不是安装故障。与之对称的还有一份关于 DA 自身的记忆文件 DA_MEMORY.md,两者共同构成"双热层记忆"。

二、文件结构与 schema 规格逐字段拆解

2.1 frontmatter:记忆文件的元数据契约

模板顶部的 YAML frontmatter 定义了该文件的完整规格,各字段含义如下:

--- provenance: template # 来源标记:当前文件来自安装模板 schema_version: 1 # schema 版本号,供解析器做兼容判断 cap_entries: 48 # 条目数上限:最多 48 条 cap_chars_per_entry: 256 # 单条目字符上限:256 字符(含前缀与来源标记) last_updated: 2026-01-01 # 最近更新时间,由写入器自动刷新 last_updated_by: bootstrap-template # 最近更新者,写入时会被记录为调用方 convention: pai-freshness-v1 # 遵循的约定:主记忆"新鲜度 v1"(Principal AI freshness) ---

其中cap_entries: 48cap_chars_per_entry: 256不是装饰性配置,而是被底层代码硬编码执行的边界——在 MemoryWriter.ts 中可以看到对应的常量MAX_ENTRIES = 48MAX_CHARS_PER_ENTRY = 256,二者必须保持一致,否则会出现"frontmatter 说可以写、写入器却拒绝"的失配。从源码结构看,frontmatter 中的cap_*主要用于让各消费方(如状态栏、Pulse 面板、健康检查)在不解析代码的情况下也能知道容量语义。

2.2 marker 对:条目的物理边界

<!-- BEGIN ENTRIES --> <!-- END ENTRIES -->

这是文件中唯一"必须保持不变"的结构:所有记忆条目只会出现在这一对注释标记之间。模板注释明确要求 "keep both markers in place"(保持两个标记在位)。这两个标记字符串在 MemoryWriter.ts 中被导出为BEGIN_MARKER/END_MARKER常量,全仓库的解析器(LoadMemory 钩子、Pulse 面板、健康检查、恢复工具)都通过它们定位条目区。

值得强调的是实现中的一个防御细节:条目中不允许包含 marker 子串。解析器只在"整行等于 marker"时将其视为结构行,而写入验证器(validateAndDedup)会拒绝任何包含BEGIN/END ENTRIES子串的条目,防止恶意或意外内容污染条目区块(见 MemoryWriter.ts)。

2.3 条目前缀:五种受支持的事实类型

写入该文件的每一条记忆都必须是"前缀 + 事实"的形式,且前缀只允许以下五种(大小写敏感、精确匹配、后接冒号空格):

前缀语义示例
NAME:姓名类事实NAME: 张伟
ROLE:角色类事实ROLE: 某公司技术负责人
RELATION:关系类事实RELATION: 与李娜是大学同学
PREFERENCE:偏好类事实PREFERENCE: 偏好简洁直接的回复 ~explicit
RULE:规则类事实RULE: 部署到生产前必须确认 ~explicit

前缀白名单在源码中由正则PREFIX_PATTERN = /^(NAME|ROLE|RELATION|PREFERENCE|RULE): /强制(MemoryWriter.ts),不符合前缀的条目会被静默丢弃(silent-drop),不会进入文件。这保证了热层记忆永远只包含结构化的五类事实,便于后续 BM25 检索与上下文注入的稳定解析。

2.4 来源标记(provenance tag):事实的可信度分级

每条条目的末尾可附带来源标记,表示该事实是如何被获知的:

  • ~explicit—— 用户明确陈述的事实(默认,未标注即视为 explicit);
  • ~deduced—— 从用户陈述中逻辑推断出的事实;
  • ~inferred—— 观察到的行为模式中推断出的事实。

评审提示词中的记忆策展规则对此有严格要求(见 MemoryReviewer.ts):只写陈述性事实,不写指令。例如应写PREFERENCE: prefers terse responses ~explicit(偏好简洁回复),而不是RULE: Always be terse(总是要简洁)——后者在未来重新加载时会被误读为命令。这一设计把"关于用户的描述"与"对 DA 的指令"严格分离。

三、条目规范与边界约束(评审提示词中的硬性规则)

在 MemoryReviewer.ts 内置的评审系统提示词中,定义了这套文件必须遵守的完整策展契约:

  • 容量红线:整个文件最多48 条、每条最多256 字符(含前缀与来源标记)。一旦当前列表达到 39 条(约 80%),评审器必须先合并(CONSOLIDATE)再新增——合并相关条目、删除最无用/最过时的条目。任何一条超限都会导致整批写入被拒绝
  • 取代而非叠加(SUPERSEDE, don't stack):用户陈述了新事实(如"在 A 公司工作"→"在 B 公司工作"),必须删除旧条目、写入新条目,绝不两者共存
  • 无法溯源的冲突不裁决:若两条既有条目互相矛盾而本次对话无法定论,评审器不得二选一(那只是猜测,且写成~explicit会被当作"用户亲口所说")。正确做法是保留最新条目、标记为~inferred,并在 rationale 中说明待确认。
  • 合并重复:表达同一件事的三条条目应折叠为一条。
  • 保持未来价值:保留仍能减少未来"方向性引导"的条目,丢弃已过时的。
  • 永不保存的内容:会话级临时信息、环境依赖的故障、一次性任务叙述、负面工具结论("X 工具未安装")、任务进度/TODO、commit SHA/PR 号/分支名、任何 7 天内会过时的内容、以及"这次对话里发生了什么"。

四、写入链路:从会话到记忆文件的完整调用链

热层记忆不是被某个工具直接写入的,而是经过一条完整的自治管道。以 MemoryReviewer.ts 的review()为骨架,链路如下:

4.1 步骤 1:定位并提取会话转写(transcript)

MemoryReviewer 在~/.claude/projects/下寻找最近修改的.jsonl会话转写findMostRecentTranscript),或在收到钩子传入的--input <path>时直接使用指定转写。随后extractRecentExchanges解析转写,只保留最近的 N 轮 user→assistant 配对(默认DEFAULT_TURNS = 20),过滤掉 tool_use/tool_result/图片等非文本块,并对每条消息做 2000 字符上限截断,防止单条巨型消息撑爆推理预算(MemoryReviewer.ts)。

4.2 步骤 2:注入当前记忆快照,进行"策展式"评审

关键设计在于:评审器不是简单地"追加新发现",而是拿着当前文件的完整条目列表readCurrentMemorySnapshot读取两份热层文件),让模型以op:"set"返回它想要的完整下一状态。提示词原文(MemoryReviewer.ts):

You return, via op:"set", the FULL desired list for that file — the next state you want. The system REPLACES the file with your list. Whatever you omit is forgotten.

即:你返回完整的目标列表,系统用你的列表整体替换文件;你省略掉的条目即被遗忘。这就是热层记忆"活着"的方式——新增、合并、取代、删除全部发生在同一次评审中。遗忘(forgetting)是结构性的:省略即驱逐(eviction is omission)。

4.3 步骤 3:模型输出解析与校验

模型输出必须是一个{items:[...]}的 JSON 信封。parseReviewerOutput具备容错能力:容忍前后空白与 markdown 代码围栏(```json … ```);对超过 256 字符的合并条目做确定性截断(在词边界截断并保留尾部来源标记);任何未知类型、字段越界或注入风险(frontmatter 注入、注释注入、控制字符、YAML 歧义语法)都会整批拒绝(见sanitizeTypedItemForPersistence,MemorySystem.ts)。校验失败时还有一次修正性重试:把精确的校验错误回喂给模型,要求仅修复错误指出的问题后重发完整 JSON。

4.4 步骤 4:按类型路由分发

每一条目通过 MemorySystem.ts 的add()按类型路由(该路由表由 MemoryTypes.ts 中冻结的TYPE_REGISTRY定义):

类型存储位置加载时机变更层级写入方式
memoryPRINCIPAL_MEMORY.md/DA_MEMORY.mdalways(每个提示词)Tier Aset-overwrite(整体替换)
ideaMEMORY/KNOWLEDGE/Ideas/<slug>.mdon-relevance(相关性加载)Tier Bappend(追加)
knowledgeMEMORY/KNOWLEDGE/{People,Companies,Research}/<slug>.mdon-relevanceTier Bappend(追加)
proposalMEMORY/OBSERVABILITY/pending-proposals.jsonlsurface-only(仅面板展示)Tier Cqueue(队列)

注意memory类型的load_timing: "always"write_mode: "set-overwrite"正是本模板文件的两个关键语义:每轮提示词都加载、每次写入都是整体替换set-overwrite相较增量增删的四大优势在 MemoryWriter.ts 中有明确说明:无竞争面(每次评审单次原子写)、幂等(相同输入产生相同文件)、驱逐是结构性的、心智模型简单("这就是我想要的最终状态")。

4.5 步骤 5:原子写入与安全护栏

MemoryWriter.setEntries在写入前做四重验证(MemoryWriter.ts):前缀校验(静默丢弃畸形条目)、长度校验(超 256 字符丢弃)、重复校验(大小写敏感的字符串去重)、容量校验(超 48 条返回结构化EAT_CAP错误供模型重提)。随后以"<file>.lock排他锁 → 写<file>.tmp→ fsync → 原子 rename"的方式落盘(MemoryWriter.ts),并对目标路径做白名单校验——仅允许PRINCIPAL_MEMORY.mdDA_MEMORY.md两个文件,任何其他路径返回EINVAL_PATH

写入前还会执行两道防丢失护栏(MemoryWriter.ts):

  • ESUSPECT_SHRINK(灾难性收缩):若现有条目 ≥10 条,而新列表 <3 条,或"删除超半数且零新增",判定为疑似幻觉输出,拒绝写入;
  • ESUSPECT_EROSION(缓慢侵蚀):单次写入净删除 ≥2 条(删除数 − 新增数)即被拦截——这是针对 LLM 重转录时"每轮悄悄少几条"的慢性数据丢失模式设计的护栏。合法的深度整合可通过allowDrastic: true放行。

同时,每次 Tier A 写入前都会把旧文件内容快照MEMORY/OBSERVABILITY/memory-snapshots/环形缓冲区(每个文件保留最近 30 份),使每一次自治写入都可经由 MemoryRestore.ts 单独回滚。

五、触发机制:谁、何时启动记忆评审

5.1 MemoryReviewFire 钩子:评审节奏的唯一拥有者

触发评审的是 MemoryReviewFire.hook.ts,这是一个 Stop 钩子——每次主会话(primary session)停止时执行。其节奏逻辑(见 MemoryReviewFire.hook.ts):

  1. 本会话turn_count_since_last_review加一,记录last_message_at
  2. 本会话轮次 ≥turn_threshold距全局last_review_atmin_minutes_between分钟时,分离式(detached)启动bun MemoryReviewer.ts review,并重置本会话计数、打上全局时间戳。

这里有个刻意的不对称设计:轮次阈值按会话计(问的是"这段对话是否足够有内容值得评审"),时间间隔阈值全局计(是推理量护栏,防止 N 个并发会话每窗口跑 N 次评审)。轮次计数存储在 per-session 状态文件MEMORY/STATE/memory-review/<session_id>.json,修复了此前全局计数被并发会话互相清零的问题(public issue #1711)。

5.2 评审节奏配置:memory-review.json

节奏参数集中在 memory-review.json:

{ "schema_version": 1, "turn_threshold": 8, "min_minutes_between": 30, "idle_threshold": 2, "confidence_threshold": 0.70, "notes": "Cadence for the autonomic memory reviewer: it considers writing durable memory only after >= turn_threshold turns, >= min_minutes_between minutes since the last run, and >= idle_threshold idle turns. Tier-C proposals auto-apply at >= confidence_threshold. Conservative defaults; tune after use." }
参数默认值含义
turn_threshold8单会话至少 8 轮对话才考虑评审
min_minutes_between30距上次评审至少 30 分钟(全局护栏)
idle_threshold2空闲轮次阈值
confidence_threshold0.70proposal 自动应用的最低置信度(见下文)

钩子默认回退值(turn_threshold: 8min_minutes_between: 30)在 MemoryReviewFire.hook.ts 中与配置文件一致;confidence_threshold的默认回退0.70则在 MemoryReviewer.ts 中加载。

5.3 钩子会剥离凭证

值得一提的安全细节:spawnReviewer在派生评审进程时显式删除ANTHROPIC_API_KEYANTHROPIC_AUTH_TOKENCLAUDE_CODE等环境变量(MemoryReviewFire.hook.ts),并以stdio: "ignore"detached: trueunref()方式运行——评审进程完全独立于会话生命周期,且不携带宿主会话的密钥。

六、加载链路:为什么每一轮对话都"记得"你

6.1 LoadMemory 钩子:每轮注入

写入只是闭环的一半。读取侧由 LoadMemory.hook.ts 承担,它是一个UserPromptSubmit 钩子:在每一个提示词提交前,把两份热层文件渲染为<lifeos-memory>上下文块注入:

<lifeos-memory> ## PRINCIPAL MEMORY [12/48 entries · 2140/12288 chars] NAME: 张伟 ROLE: 某公司技术负责人 PREFERENCE: 偏好简洁直接的回复 ~explicit ... ## DA MEMORY [3/48 entries · 430/12288 chars] ROLE: LifeOS 数字助理 ... </lifeos-memory>

这里可以看到容量计算的另一种形式:48 条 × 256 字符 ≈ 12288 字符/文件(capChars = 12288),两份合计约 24K 字符上限。该钩子是热路径钩子,必须廉价:只渲染条目、不含帮助注释;任何错误只写 stderr 并返回空,绝不阻塞提示词。子代理进程(subagent)通过环境标记被识别并跳过——每轮记忆循环只服务于主用户会话(LoadMemory.hook.ts)。

6.2 共享解析器:读写永不分歧

LoadMemory 钩子与写入器共用同一个解析器parseMemoryContent(MemoryWriter.ts),其注释强调:"Every consumer (LoadMemory hook, Pulse memory panel, MemoryHealthCheck, MemoryRestore) imports it — reader and writer can never diverge again. Never write a second marker-parsing implementation."(所有消费者都导入它——读写永不分歧,永远不要写第二套 marker 解析实现。)解析器采用"整行识别 marker、宽容解析"模型:marker 只在作为完整修剪行时才被当作结构行,任何前缀合法的行(无论是否在 marker 区块内)都被视为条目,从而即使文件历史上出现过ENDBEGIN之前的错乱,也能完整恢复条目而不会静默丢失记忆。

6.3 检索支持

除了全量注入,热层文件还参与 BM25 相关性检索——MemorySystem.ts 的find()将两份_MEMORY.md与知识笔记一并纳入语料,供按需召回(MemoryRetriever.ts)。

七、状态可见性:Pulse 面板与状态栏

该文件的实时状态有多个可见面:

  • Pulse 记忆面板:Pulse 的 memory.ts 模块通过GET /api/memory提供完整快照(状态、上次运行、健康、两文件内容、proposals、近期运行记录),内部同样复用parseMemoryContent解析文件内容(memory.ts);
  • 状态栏LIFEOS_StatusLine.sh与 MemoryStatus.ts 提供 🧠 记忆状态行;评审的全局镜像状态MEMORY/OBSERVABILITY/review-state.json持续被各消费方读取;
  • 健康检查:MemoryHealthCheck.ts 依据 48/256 容量与写入日志评估记忆健康度,评审运行摘要记录于reviewer-runs.jsonl,每次写入事件记录于memory-writes.jsonl(含 prior_count/new_count、逐条 evictions/additions,为防侵蚀审计提供完整证据)。

八、CLI 操作与验证手段

仓库中的记忆子系统提供一组可直接运行的命令行接口,均以 bun 执行:

# 读取当前热层记忆(含容量统计与非法条目报告) bun MemoryWriter.ts read ~/.claude/LIFEOS/USER/PRINCIPAL/PRINCIPAL_MEMORY.md # 以 stdin 逐行条目写入(set-overwrite) printf 'NAME: 张伟\nPREFERENCE: 偏好简洁回复 ~explicit\n' | \ bun MemoryWriter.ts set ~/.claude/LIFEOS/USER/PRINCIPAL/PRINCIPAL_MEMORY.md # 手动触发一次记忆评审(最近 N 轮对话) bun MemoryReviewer.ts review --turns 20 # 对指定转写评审(不写盘,仅提取与构造提示词) bun MemoryReviewer.ts review --input <transcript.jsonl> --dry-run # 查看类型注册表与存储路径解析 bun MemoryTypes.ts list bun MemoryTypes.ts resolve memory '{"actor":"principal"}' # 内置冒烟测试(注意:MemoryWriter 的 test 不触碰真实记忆文件) bun MemoryWriter.ts test bun MemorySystem.ts test

需要特别提示:bun MemoryWriter.ts test的设计原则是"不触碰真实记忆文件"(其冒烟测试曾因在真实文件上清理时误清空记忆而重构为纯内存夹具,见 MemoryWriter.ts 的注释),对已装载记忆的安装环境是安全的。

九、故障自愈与边界防护小结

综合以上源码分析,PRINCIPAL_MEMORY.md所在的记忆子系统围绕"不能丢、不能错、不能静默损坏"设计了多层防御:

  1. 路径白名单——写入器只认两份热层文件(EINVAL_PATH);
  2. 条目 schema 强制——五前缀、256 字符、48 条上限、marker 隔离(畸形静默丢弃);
  3. 写入竞争防护——排他锁 + 原子 rename + fsync;锁支持带 pid/host 时间戳的陈旧锁恢复(MemorySystem.ts 的staleLockReason决策矩阵:本机 pid 消失立即破锁,无法验证的持有者按 5 分钟 TTL 兜底);
  4. 防丢失护栏——灾难性收缩(ESUSPECT_SHRINK)与缓慢侵蚀(ESUSPECT_EROSION)双重拦截;
  5. 可回滚——每次写入前快照到 30 份环形缓冲,MemoryRestore.ts 可单独恢复;
  6. 读写统一解析——单解析器模型杜绝"写进去读不出"的分歧;
  7. 完整可观测——memory-writes.jsonlreviewer-runs.jsonlreviewer-fires.jsonlmemory-locks.jsonl构成审计证据链。

十、总结

PRINCIPAL_MEMORY.md表面看是一个近乎空白的模板文件,实际上它是 LifeOS 自治记忆系统的"热层出口":前端由MemoryReviewFire钩子按节奏触发MemoryReviewer,中端由MemoryTypes的冻结注册表完成类型路由、MemorySystem统一入口调度,后端由MemoryWriter以"set-overwrite + 原子写 + 防侵蚀护栏"落盘,最终由LoadMemory钩子在每个提示词中全量回灌。这套设计把"记住用户"从手工笔记变成了一个有容量边界、有策展规则、有遗忘机制、有安全护栏的自治闭环——空文件不是终点,而是记忆生命周期的起点。若要深入,建议依次阅读 MemoryWriter.ts、MemorySystem.ts、MemoryReviewer.ts 与 MemoryTypes.ts,再对照 LoadMemory.hook.ts 与 MemoryReviewFire.hook.ts 两个钩子理解闭环两端。

【免费下载链接】LifeOS⛰️ The Life Operating System — an intent engineering platform that moves you from your current state to your ideal state, in life and work.项目地址: https://gitcode.com/GitHub_Trending/pe/LifeOS

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询