OpenHuman Archivist 后台知识馆员:会话归档、经验提炼与 MEMORY.md 记忆沉淀机制全解析
【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman
OpenHuman 的Archivist(知识馆员)是一个在会话结束后于后台运行的专用 Agent,负责把每一轮对话沉淀为可检索的记忆资产:回合索引写入 FTS5 情景记忆表、经验教训抽取与分类、知识库文件 MEMORY.md 增量更新。本文以 Archivist 的系统提示词(prompt.md)为骨架,结合其 Agent 配置(agent.toml)、提示词装配器(prompt.rs)以及后端 Hook 实现(hook_impl.rs、lifecycle.rs),深入剖析 OpenHuman 的「会话 → 记忆」流水线,帮助你理解其记忆系统如何做到低开销、可检索、防泄露、不重复。
一、Archivist 是谁:后台知识馆员的角色定位
在 OpenHuman 的 Agent 注册表中,Archivist 被定义为一个典型的「后台 + 轻量」Agent。它的when_to_use字段一句话点明使命:
Background librarian — extracts lessons from a completed session, updates MEMORY.md, and indexes to FTS5. Runs cheap and slow.(后台知识馆员——从已完成的会话中提取经验,更新 MEMORY.md,并索引到 FTS5。以低成本、低速度运行。)
这段描述(见 agent.toml)揭示了三个关键设计意图:
- 后台触发:
background = true,不占用前台交互路径; - 低成本:
temperature = 0.4(低随机性、输出稳定)、max_iterations = 3(最多 3 轮工具循环,防止无限迭代); - 知识归档:产出物横跨两类存储——结构化数据库(FTS5 情景记忆)与人类可读的 Markdown 知识库(MEMORY.md)。
系统提示词把 Archivist 的全部职责收敛为三条明确指令:
- Index turns— 将每一轮对话记录到情景记忆(FTS5)中,供未来检索召回;
- Extract lessons— 识别可复用的模式、需要规避的错误以及用户偏好;
- Update MEMORY.md— 将重要的学习成果追加到工作区知识库。
从源码结构看,这三条职责分别对应了三条独立但联动的实现路径:回合索引走ArchivistHook::on_turn_complete(hook_impl.rs),经验提炼走分段 recap + 事件启发式提取(lifecycle.rs),知识库更新则通过白名单工具update_memory_md完成(update_memory_md.rs)。
二、系统提示词如何装配:prompt.md → 完整提示词
Archivist 的提示词并非一段写死的字符串,而是由 prompt.rs 中的build()函数在运行时动态拼装而成:
- 通过
include_str!("prompt.md")把本文的 archetype(角色原型)嵌入二进制; - 依次追加
render_user_files(ctx)(用户可见文件)、render_tools(ctx)(可用工具清单)与render_workspace(ctx)(工作区上下文)。
这正是 OpenHuman「提示词即产物」的设计理念——build()的输出就是 LLM 最终看到的内容,Runner 不做任何二次加工(见 prompt.rs 模块注释)。这意味着 prompt.md 中每条规则都会原样生效,且工具清单由运行时上下文决定而非硬编码。
对应的单元测试 prompt_tests.rs 验证了build()在最小上下文(空工具集、空工作区)下也能产出非空提示词,保证该 Agent 在任何场景下都能被加载。
三、agent.toml 运行参数详解
agent.toml 是 Archivist 的完整运行配置,理解这些参数是自定义 Agent 的最佳范本:
| 配置项 | 值 | 含义与影响 |
|---|---|---|
id | archivist | 注册表中的唯一标识 |
delegate_name | archive_session | 编排器调用该子 Agent 时使用的委托名 |
temperature | 0.4 | 低随机性,保证归档内容稳定、可复现 |
max_iterations | 3 | 最多 3 轮工具调用循环,防止后台任务失控 |
max_result_chars | 8000 | 归档摘要回流到编排器的字数上限(约 2000 tokens),与普通子 Agent 上限一致,防止冗长的「记忆写入确认」撑爆编排器上下文(issue #4099) |
sandbox_mode | none | 无需沙箱,因为其工具集(写 MEMORY.md / 写数据库)本就受白名单约束 |
background | true | 后台运行,不阻塞主会话 |
omit_identity/omit_memory_context/omit_safety_preamble | true | 跳过身份、记忆上下文与安全前言,进一步压缩上下文开销 |
model.hint | local | 优先使用本地模型,契合「便宜、慢速、后台」的定位 |
tools.named | update_memory_md、insert_sql_record、memory_store | 仅暴露三个写工具,权限面最小化 |
值得强调的是tools.named的最小权限设计:Archivist 只能通过update_memory_md写 Markdown 知识库、通过insert_sql_record写 FTS5 表格、通过memory_store访问记忆存储——它没有文件系统通用读写权限,从机制上杜绝了后台 Agent 越权操作工作区。
四、职责一:Index turns — 回合索引与情景记忆(FTS5)
Archivist 的后端实现是一个PostTurnHook(回合后钩子),核心入口是ArchivistHook::on_turn_complete(hook_impl.rs)。每轮对话结束后,它依次执行:
- 写入用户回合:把用户消息作为一条
EpisodicTurn(role=user)插入 FTS5 情景记忆表,insert_turn返回数据库分配的自增 ID; - 写入助手回合:把助手回复作为第二条记录写入,时间戳加
0.001毫秒偏移,确保同一轮内「用户在前、助手在后」的排序稳定;工具调用摘要(tool_calls_json)随助手记录一并落库; - 轻量教训提取:若本轮存在失败的工具调用,则调用
extract_lesson_from_tools生成一条纯启发式教训(不消耗 LLM),格式为Tools that failed in this turn: xxx, yyy(helpers.rs); - 双写 md 归档:同时把回合写入
<workspace>/memory_tree/content/episodic/<session_id>/<seq:06>.md的 md 备份存储(最佳努力模式,写失败不阻断回合),为 FTS5 → md 的存储迁移做并行验证(hook_impl.rs)。
会话分段:把长对话切成「知识单元」
原始回合是零散的时间序列,Archivist 通过**会话分段(conversation segmentation)**把同主题的回合聚合成 Segment,为后续 recap 提供边界。分段判定由 boundary.rs 的detect_boundary()完成,按「从便宜到昂贵」的顺序执行四重检查(boundary.rs):
| 检查项 | 判定条件 | 默认阈值 |
|---|---|---|
TurnCountExceeded | 段内回合数超上限 | max_turns_per_segment = 20 |
TimeGap | 相邻回合间隔过长 | max_time_gap_secs = 600(10 分钟) |
ExplicitMarker | 回合以话题切换短语开头(如now let's、switching to、by the way,等) | 内置 15 个英文短语表 |
EmbeddingDrift | 回合向量与段质心的余弦相似度低于阈值 | min_cosine_similarity = 0.4 |
边界判定产出BoundaryDecision::Continue(继续积累)或Boundary::Boundary(reason)(关闭当前段、以当前回合为起点新建段,段 ID 形如seg-{uuid})。这个设计的精妙之处在于「便宜检查先跑、贵检查后跑」——一个回合若命中前三条任一规则,就永远不会支付 embedding 比较的成本。
五、职责二:Extract lessons — 经验提炼的三层漏斗
Archivist 的教训提取不是单一机制,而是「无 LLM 启发式 → 启发式事件 → LLM recap」的三层漏斗,按成本从低到高排列:
第一层:工具失败教训(零成本)
extract_lesson_from_tools纯规则实现:遍历本轮工具调用记录,只要存在success = false的调用,就生成一条「哪些工具失败」的教训。这是唯一逐轮执行的教训提取,因为它不消耗任何推理资源。
第二层:事件启发式提取(segment 关闭时)
在段关闭(on_segment_closed)时,Archivist 对段内所有用户消息做句子切分(按.!?换行符),再与四组模式表做子串匹配(events_heuristic.rs):
- Decision(决策):如
i decided、going with等模式; - Commitment(承诺):如
i will、i'll make sure等; - Preference(偏好):如
i prefer、i like、i don't like等; - Fact(事实):如
my name is、i work at、i live in、my timezone等。
每个事件被写入EpisodicEvent表(confidence 固定 0.6),其中Preference 与 Fact 事件还会二次写入用户画像(Profile Facets):通过extract_profile_key(取内容前 4 个有意义的词生成键)与upsert_provider_facet合并,置信度低的重复观察不会覆盖更强的旧记录(lifecycle.rs)。
第三层:LLM 段摘要(recap,可回退)
每个被关闭的 Segment 都会产出一段摘要(recap),默认使用"summarization"角色的推理模型生成(见 lifecycle.rs 中RECAP_INFERENCE_ROLE常量)。整个流程遵循软回退契约:
- 若
with_config阶段探测到无法构建summarization角色的模型,则回退到启发式摘要; on_segment_closed永不返回 Err,所有失败只记日志不中断回合;- recap 生成后:持久化段摘要 → 调用 embedder 生成向量并写入
segment_embeddings(段关闭时是唯一写入点,空摘要直接跳过以避免上游 embedding API 400)→ 提取事件 → 更新画像。
这里有一个重要的**「证据 vs 解读」数据策略**:在config.learning.chat_to_tree_enabled = true时,Archivist 会把该段的原始散文回合(用户 + 助手消息,剥离工具调用 JSON)以source_id="conversations:agent"整批灌入记忆树,而绝不把 LLM recap 喂给记忆树——树必须基于原始证据自己归纳,否则就成了「对摘要再做摘要」(lifecycle.rs)。
六、职责三:Update MEMORY.md — 知识库的并发安全写入
update_memory_md是 Archivist 更新工作区知识库的唯一通道(update_memory_md.rs),其实现体现了 OpenHuman 对「后台并发写文件」这一危险场景的完整防护:
- 白名单约束:工具只能修改
MEMORY.md与SKILL.md(ALLOWED_FILES常量),其他工作区文件一概拒绝; - 进程内互斥:每个工作区目录对应一把全局 async 互斥锁(以 canonicalize 后的路径为键),并发跑(并行 fork、cron)的归档写操作排队执行而非互相覆盖(issue #4458);
- 跨进程文件锁:由于 cron 通过独立子进程启动,进程内互斥锁无法覆盖,因此再对工作区下的哨兵文件
.memory-write.lock做flock独占锁(阻塞式、运行在线程池);同时拒绝 symlink 锁文件,并用O_NOFOLLOW关闭 TOCTOU 窗口,防止符号链接把锁重定向到工作区之外; - 原子写入:临时文件 + 原子重命名,进程被 kill 也不会留下截断文件。
这套机制与 prompt.md 中的Deduplicate规则形成「机制 + 智能」的双保险:锁保证写入不互相破坏,LLM 负责在追加前比对已有 MEMORY.md 内容、避免重复条目。
七、质量规则:五条提示词纪律
prompt.md 的 Rules 部分是 Archivist 输出质量的灵魂,逐条展开:
| 规则 | 含义 | 落地方式 |
|---|---|---|
| Be concise | 教训浓缩为一两句,密集而非啰嗦 | max_result_chars = 8000从物理上限制回流体量 |
| Be selective | 并非每轮都有教训,只沉淀真正有用的观察 | 默认只对工具失败做逐轮启发式提取,其余留给段级 recap |
| Never log secrets | 脱敏 API 密钥、令牌、密码与 PII | 由提示词纪律约束,配合 log_redaction 等安全机制 |
| Use categories | 按类型打标签:pattern(模式)、mistake(错误)、preference(偏好)、fact(事实) | 与事件启发式的 Decision/Commitment/Preference/Fact 分类一一呼应 |
| Deduplicate | 追加前先查 MEMORY.md,避免重复 | 配合update_memory_md的读-改-写加锁流程 |
八、可配置开关:learning 配置族
Archivist 的行为并非硬编码,而是由 learning.rs 中的学习配置族统一开关,以下参数均可通过learning配置节或环境变量覆盖:
| 配置项 | 默认值 | 作用 | 环境变量覆盖 |
|---|---|---|---|
episodic_capture_enabled | true | 情景捕获总开关;即使learning.enabled关闭也保持活动(情景记忆是对话回合的系统事实来源) | OPENHUMAN_LEARNING_EPISODIC_CAPTURE_ENABLED=0\|1 |
chat_to_tree_enabled | true | 是否把对话原始回合灌入记忆树(conversations:agent) | — |
goals_enrichment_enabled | true | 段关闭后是否后台触发goals_agent刷新长期目标清单(MEMORY_GOALS.md) | OPENHUMAN_LEARNING_GOALS_ENRICHMENT_ENABLED=0\|1 |
stm_recall_enabled | true | 是否在会话开始注入跨会话的近期情景召回(FTS5 关键词 + cosine 段摘要) | OPENHUMAN_LEARNING_STM_RECALL_ENABLED=0\|1 |
explicit_preferences_enabled | true | 显式固定偏好是否注入系统提示词 | OPENHUMAN_LEARNING_EXPLICIT_PREFERENCES_ENABLED=0\|1 |
注意learning.enabled默认是false,而episodic_capture_enabled默认true——这体现了「情景记忆是独立于推理学习栈的基础设施」这一架构判断:即使关闭反射/稳定性检测等推理组件,对话归档依然持续运转。
九、会话收尾:flush 保证最后一段不丢失
一个容易被忽略的边界情况是:会话结束时往往没有一个触发边界的回合,导致最后一个 Segment 永远处于 open 状态、得不到 recap。Archivist 通过flush_open_segment解决(lifecycle.rs):在会话结束(Agent::spawn_session_memory_extraction)时强制关闭残留的 open segment,并走与正常段关闭完全相同的路径——recap + embedding + 事件提取 + 树灌入。该操作幂等(段只会从open → closed迁移一次),可安全重复调用。
十、小结:一条完整的「会话 → 知识」流水线
将以上机制串联,一次会话在 OpenHuman 中的记忆沉淀路径是:
- 逐轮:
on_turn_complete将用户/助手回合写入 FTS5 情景表,双写 md 归档,工具失败生成零成本教训; - 分段:
detect_boundary按回合数、时间间隔、话题短语、向量漂移四重检查切分 Segment; - 段关闭:LLM recap(或启发式回退)→ 段摘要与 embedding 落库 → 启发式事件提取 → 画像 Facet 合并 → 原始散文灌入记忆树 → 后台刷新长期目标;
- 知识库:
update_memory_md以白名单 + 进程内锁 + 跨进程 flock + 原子重命名的方式安全追加 MEMORY.md/SKILL.md; - 会话结束:
flush_open_segment兜底收尾最后一个开放段。
Archivist 的设计核心可以概括为三句话:用便宜机制做逐轮工作,用昂贵机制做段级工作,用并发安全机制做文件写入。如果你想在 OpenHuman 中自定义类似的后台记忆 Agent,agent.toml 的「background + 最小工具集 + 低 temperature + max_result_chars 限流」组合,加上 prompt.md 的「职责 + 规则」双层结构,是一份可以直接复用的模板。
【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考