深度解析 LifeOS Cortex:不部署任何服务,让本地记忆库可检索、可验证、可拒写
【免费下载链接】LifeOS⛰️ LifeOS — The universal AI Harness designed to move you from Current to Ideal state in both life and work.项目地址: https://gitcode.com/GitHub_Trending/pe/LifeOS
假设现在是凌晨两点,你要回答一个业务问题,但线索可能散落在几个月前写下的几百条本地记忆里。最顺手的做法是起一个向量数据库、拉一个守护进程、等索引建完——而 LifeOS 的 Cortex 组件给出了另一条路:一条基于 Bun 的本地 CLI,直接读文件、算排名、吐 JSON,进程退出即结束。它不提供任何常驻能力,却把"检索结果可解释、写入受授权、边界越界即拒绝"这三件事做到了可在终端里逐条验证的程度。下面以 Cortex.ts 的实现为底,拆解这个本地记忆检索工具的调用约定、数据流与安全设计。
设计定位:提供什么,刻意不提供什么
Cortex v1 的定位可以用一张"有/无"对照表说清楚。它是构建在既有文件型记忆系统之上的命令行工具,而不是一套新的存储栈:
| 提供的 | 刻意不提供的 |
|---|---|
| 本地 BM25 词法检索与时间线查询(卡片式,渐进披露正文) | MCP 服务器、HTTP 端点、任何守护进程 |
| 显式 ID 的整读与只读导出 | Chroma、CMEM、嵌入、向量索引、SQLite FTS |
| 逐次授予的写入通道(委托给既有治理层) | 跨设备/云端同步、远程变更 |
| 确定性重建证明(rebuild 摘要比对) | 对原生 harness 转录的清洗 |
随系统发布的进程内适配器 CortexAdapter.ts 为claude、hermes、codex、subagent四种受识别身份提供工厂方法。注意它的设计意图:createCortexAdapter返回的对象被Object.freeze冻结,status/search/timeline/get/export天然是只读的;而remember/propose虽在接口上,权限却是逐次调用判定的——writePermission会检查传入选项的键集,只要出现任何allowWrite之外的键,或allowWrite不是布尔值,就返回null并整体拒绝,只有显式传入allowWrite: true才向底层追加--allow-write。写权限因此不是身份级的特权,而是每次调用都要重新出示的凭证。
一次调用的生命周期:从参数解析到响应信封
所有命令的入口形态是bun LifeOS/install/LIFEOS/TOOLS/Cortex.ts <command> [arguments] [options]。一次调用的数据流大致分四站,每一站都有明确的失败出口。
第一站:入口解析。parseArguments 维护每张命令的白名单选项表(ALLOWED),未知选项、重复选项、缺值、把不相关的选项带进不相关的命令,都会在进入任何业务逻辑之前被拒。这种"拒绝而非忽略"的策略是刻意的:CLI 参数拼错时,静默降级比报错更危险,因为调用方拿到的会是一个"看起来成功"的假结果。
第二站:规范根解析与完整性校验。根目录按四级回退解析,在 Cortex.ts#L283 可以直接验证顺序:运行时注入的memoryRoot、--memory-root选项、环境变量CORTEX_MEMORY_ROOT、默认值~/.claude/LIFEOS/MEMORY。这里有个不直觉的细节——pinCanonicalRoot会对根做一次realpath解析并"钉住"结果,也就是说顶层的符号链接别名是被容忍的(默认的LIFEOS/MEMORY别名指向私有数据仓库正是靠这个机制工作),但钉住根之下的一切符号链接都触发IntegrityError:checkedRealpath 对每个遍历到的路径做lstat判链、再校验realpath是否逃逸出根。重复记录 ID、坏 JSONL、updated早于created、valid_from >= valid_until、socket 之类非常规模块,统统归入同一类失败。在读取任何正文之前,还有一层语料级闸门(CANONICAL_CORPUS_LIMITS):最多 10,000 个文件、单文件 8 MiB、总量 128 MiB、记录 50,000 条。设计逻辑是:与其让一个异常语料把检索拖进不可预期的资源消耗,不如在边界上整体失败。
第三站:检索与读取路径。语料来自既有的KNOWLEDGE/树(canonicalFiles 按KNOWLEDGE→MEMORY/KNOWLEDGE→ 根本身三级回退),排除下划线与点号前缀路径,并纳入根级*_MEMORY.md热记忆。排序是本地 BM25:rankBM25 中逆文档频率用了带 0.5 平滑的对数公式来避免零除,词频饱和系数 2.5,长度归一化取b = 0.25 + 0.75·len/avgdl,同分按 ID 字典序稳定。--type/--source/--session是精确匹配,--from/--to对created做闭区间过滤;--recency只对updated加权(源码里就是score + recency·Date.parse(updated)/1e13),它调整顺序但不替代词法相关性。关键的输出约束在 toCortexCard:卡片只带id/type/created/updated/provenance/score/est_tokens,其中est_tokens = ceil(正文字符数 / 4),没有任何正文内容——调用方先拿卡片决策,再用get取全文,注入成本因此被显式拆分。图扩展(--expand)从给定 ID 沿规范related做有界 BFS,默认 10 节点 / 2,000 token 预算,上限 100 / 50,000,它不碰任何持久化图数据库。
第四站:响应构造。每条命令向 stdout 恰好写一个五字段 JSON 对象:
{"schema":"lifeos-cortex/v1","ok":true,"command":"status","data":{},"error":null}失败时保持同样五个顶层字段,ok:false、data:null、error:{code,message}。这不是约定俗成,而是库内建自校验:validateCortexEnvelope 检查字段集恰好为command/data/error/ok/schema、ok与error/data的一致性,每次ok(...)/fail(...)构造后都先过一遍再返回。退出码与信封是两条独立的通道:0 成功、1 内部失败(internal_error)、3 显式 ID 未找到(not_found)、4 非法输入或完整性问题(invalid_input/integrity_error)、5 写授权缺失或治理拒绝(write_refused/governance_refused)。所以调用方必须同时消费进程退出码和信封——能解析出 JSON 只说明格式合法,不说明事情办成了。
写路径:双重绑定的授权模型
八个子命令里只有remember与propose会改变状态,它们的授权是两道独立的锁。
第一道:身份与授权分离。必须同时出现受识别的--adapter和--allow-write(见 runCortex 开头的守卫)。只声明身份不构成写意图——--adapter claude本身不授予任何权限,缺少--allow-write直接以退出码 5 的write_refused结束。
第二道:命令与条目判别器绑定。remember只接受type为memory、idea、knowledge的条目,propose只接受type:"proposal",不匹配在委托给MemorySystem.add()之前就被拦下。payload 恰好一个、上限 262,144 字节、必须是合法 JSON。通过后条目进入既有的变更治理——分层(mutation tiers)、目标钉住、提案审批、审计、快照与收缩守卫都保持权威地位;治理拒绝时是退出码 5 的governance_refused,不存在部分成功:要么整个条目按既有规则落库,要么什么都没发生。
把这两道锁合起来看,设计回答的问题是"一条写请求凭什么可信":谁来写(身份)、谁授权(逐次 flag)、写什么类型(判别器)、写到哪(下游治理)。四个答案必须同时成立,任何一环缺失都在最便宜的位置失败。
隐私边界与完整性:数据在哪被净化,越界时发生什么
隐私边界的核心是一类类 HTML 标签的显式私有 span,比如public <private>never persist or export this</private> public。实现集中在 CaptureEnvelope.ts#L17 的stripPrivateContent,它的匹配语义按数据流排开是这样的:
- 数据从哪来:reviewer 输入、reviewer 调试/错误产物、进入
MemorySystem.add()的类型化条目、以及规范读取(Markdown/JSONL 解析时经sanitizeContent二次净化)。 - 在哪被净化:规范化后以 NFKC 归一、去控制字符与空白、转小写的候选标签,只要"看起来像 private 开头"但格式不良(NUL/控制字符插入、全角 Unicode、丢失右尖括号),就视为不可信开头,从该位置起抑制字符串剩余部分——不尝试宽松的 HTML 恢复,因为宽松恢复本身就是绕过通道。正常路径上,匹配大小写不敏感、容忍无害空白与属性;嵌套 span 用深度计数整体移除;孤儿闭合标签作为控制标记删掉、两侧公开文本保留;未闭合的开头标签触发同一套"抑制剩余"逻辑。
- 哪些环节强制校验:边界先于 reviewer 推断、先于类型化路由、先于词法排序、图扩展、get/export/rebuild 生效;类型化条目的净化是递归的,覆盖 content 及承载持久化语义的元数据(标题、名称、rationale、session provenance、entries、related slugs),剥离后变空的必填字段会被拒绝。
- 越界时发生什么:语料级 fail-closed 上限(前文所列四项)与逐条完整性检查(唯一 ID、合法时间戳、控制字符黑名单、UTF-8 fatal 解码)都抛
IntegrityError,统一映射为退出码 4 的integrity_error——整个语料不可用,而不是悄悄跳过坏记录。
源中立的摄取助手ingestCaptureEnvelope(input, consumer)把"先净化、再交给 consumer"固化成唯一入口,fixture 覆盖 Claude、Hermes、Codex、子代理与一个消息通道。这里要保留一份诚实的边界声明:助手能表示这些来源不等于这些来源都已被迁移;另外,原生 harness 转录在其文档所述 30 天保留期内可能仍含<private>内容,Cortex 不触碰转录字节,私有标签是它的持久化与处理边界,不是对上游日志的清洗承诺。有效期窗口遵循同一哲学:valid_from含边界、valid_until不含、缺失视为开放,而Date.parse产生 NaN 的边界直接判"不生效"(isEnvelopeValidAt)——坏数据不能证明自己有效。
如何证明它在正常工作:基准测量与运营健康
组件的可验证性分两条链路,共同遵循一个原则:缺失的证据不产生绿灯。
检索基准由 CortexBenchmark.ts 执行,标签集(cortex-retrieval-v1.jsonl)是操作者在自己语料上手工编写的本地资产,不随系统发布;每行标签必须携带溯源头,证明期望 ID 来自真实live-cortex-cli执行加人工核验。方法学上最关键的一点:基准导入生产代码(activeCortexRecords、rankBM25、toCortexCard与摘要函数),而不是自带一份基准专用排序器。每条带标签查询跑 25 次,每次只生产排序一遍,同一份排序在两种披露测量间共享:bm25-baseline序列化完整 top-5 记录,progressive只序列化卡片加第一条被选中记录的全文。这样被比较的是披露与注入成本,而不是两个检索算法。报告 schema 为lifeos-cortex-benchmark/v1,除 Recall@5、MRR、时序成对排序、假阳性召回、注入 token、p95 延迟、磁盘增长、峰值 RSS 外,还记录分词次数与排序运行次数——防止"卡片优先"的比较掩盖重复的检索工作。持久化输出必须落在解析后的MEMORY/BENCHMARKS/之下、使用版本化文件名且不覆盖已有报告。由此推出向量索引的采用门槛:vector_config当前为null;要采纳向量或混合索引,需要一份带标签报告证明相对渐进式 BM25 的质量提升,且索引可规范重建、边界单独文档化。单纯省 token 不构成证据。
运营健康来自另一个工具:bun LifeOS/install/LIFEOS/TOOLS/MemoryHealthCheck.ts --json,其评估逻辑在 CortexHealth.ts。机器可读报告给出overall、实测证据、生效阈值、findings,健康退出码按 ok/warn/critical 映射为 0/1/2。阈值与判定规则可以列成一串要点:
- Reviewer 成功新鲜度超 7 天为 WARN;最新一次 reviewer 运行失败、解析失败、超时、格式错误均为 CRITICAL——最新证据优先于历史成功;
- 进行中的新运行目录 10 分钟宽限后仍无终行即判超时;
- 检索证据(最新一行有效的
memory-retrievals.jsonl)缺失或超 24 小时为 WARN; - 待审提案只统计状态恰为
pending的行,积压大于 10 为 WARN; - 可观测性证据递归测量
MEMORY/OBSERVABILITY/下的.jsonl与.log,字节超 256 MiB、最老日志超 30 天均为 WARN; - 格式错误的 JSONL 会被暴露而非静默跳回上次成功;未来时间戳不能证明新鲜度;
- 索引侧:随系统发布的 CORTEX_INDEX_POLICY.json 是
lifeos-cortex-index-policy/v1的肯定性no-index-v1标记——存在标记且无清单时,BM25 直读规范文件,rebuild不创建任何东西,健康检查把这一明确状态报为健康的no-index-v1,不遍历也不哈希整个语料;标记与清单双缺时仅告警index-evidence-missing,标记格式错误则是 critical。若存在合法的已采用清单(lifeos-cortex-index/v1,含规范 SHA-256、索引路径与哈希、indexed_at),它优先于 no-index 标记,实际字节会被逐字节验证。
阈值覆盖只接受有限正数,非法值产生 critical 的cortex-threshold-invalid而不是让比较失效;运营覆盖变量为CORTEX_RETRIEVAL_STALE_MS、CORTEX_PROPOSAL_BACKLOG、CORTEX_OBSERVABILITY_MAX_BYTES、CORTEX_OBSERVABILITY_MAX_AGE_MS,另有CORTEX_HEALTH_ROOT、CORTEX_HEALTH_NOW等测试路径变量。
上手:三条命令跑通最小闭环
前置条件是 Bun 运行时与已部署的规范根(默认~/.claude/LIFEOS/MEMORY,可用CORTEX_MEMORY_ROOT覆盖)。克隆仓库后,入口是LifeOS/install/LIFEOS/TOOLS/Cortex.ts:
# 1. 确认契约可用性与语料形状(只读,报告钉住的根与记录数) bun LifeOS/install/LIFEOS/TOOLS/Cortex.ts status # 2. 查询记忆库,拿到分页卡片(不含正文) bun LifeOS/install/LIFEOS/TOOLS/Cortex.ts search "cortex 隐私边界" \ --page-size 5 # 3. 依据卡片中的 id 取完整记录(任一 id 缺失则整条命令以退出码 3 失败) bun LifeOS/install/LIFEOS/TOOLS/Cortex.ts get <id1> <id2>解读输出时记住三件事:先看进程退出码(0 才算成功),再看信封的ok字段,最后才读data。列表响应的total是精确过滤后的总数,分页默认第 1 页每页 10 条、页大小上限 100;timeline --anchor <ID或日期>的--before/--after默认各 5、可为 0、上限 100;rebuild --from-canonical对规范视图与重建视图各算一次 SHA-256 并报告equivalent——摘要相等证明的是记录视图可被确定性重建,不是源文件被逐字节重写。
设计取舍:为什么不做那些看起来方便的事
- 不做向量索引与常驻守护进程。BM25 直读规范文件意味着检索行为完全由文件内容决定、随时可复算;索引一旦成为事实源,就会出现"文件是 A、检索结果像 B"的漂移。代价是语料必须受限于 128 MiB 量级——对文件型个人记忆系统,这是用容量换可验证性,方向不亏。
- 不做自动注入全文。卡片不含正文,全文只经显式
get获取。自动注入省一步调用,却让每次查询的 token 成本不可预算;渐进拆分后,"看 10 张卡片"和"读 3 条全文"是两个成本显式的动作。 - 不做顶层别名之外的符号链接容忍。钉住根下逐路径
realpath校验看似苛刻,但它堵的是"合法记忆树里混入一个指向别处的软链"这类静默逃逸;整体报integrity_error让问题在下次调用就暴露,而不是在某次检索里悄悄少一条记录。 - 不做模糊参数容错。选项表白名单 + 重复/缺值全拒,牺牲了敲命令的宽容度,换来"输出即真相":调用方永远不用猜哪个拼错的 flag 被忽略了。
- 不对上游做清洗承诺。转录、终端回显、提供方日志里的
<private>内容不在 Cortex 能力圈内,文档把这层负空间写清楚,比给出一个含糊的"已脱敏"暗示在安全上更诚实。
延伸阅读(均在仓库内):
- CortexContract.md — 完整命令契约与边界声明
- MemorySystem.md — 记忆架构、策展分层与写者清单
- ObservabilitySystem.md — 健康证据与本地可观测性管线
- lifeos-cortex-v1.schema.json — 发布版响应信封 Schema
【免费下载链接】LifeOS⛰️ LifeOS — The universal AI Harness designed to move you from Current to Ideal state in both life and work.项目地址: https://gitcode.com/GitHub_Trending/pe/LifeOS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考