Hindsight Mental Models 深度解析:Agent 记忆学习顶层的持久化表征、Delta 刷新与标签匹配策略 📅 发布时间:2026/9/13 11:27:09 👁 浏览次数: Hindsight Mental Models 深度解析Agent 记忆学习顶层的持久化表征、Delta 刷新与标签匹配策略【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight本篇技术指南以 Hindsight 的官方深潜博客 2026-06-05-mental-models-deep-dive.md 为主体骨架结合仓库内 hindsight-api-slim 的源码实现完整讲解 Mental Models心智模型从数据表结构、两级刷新模式、自动刷新钩子到标签匹配策略的全部细节。读完你将对 Hindsight 学习层次结构的顶层机制形成体系化认知并掌握创建、刷新、排查与维护 Mental Model 的完整实战方案——包括那些安静地咬人的all_strict标签默认策略和detail级别的性能陷阱。为什么需要 Mental Models三层学习层次结构的顶层Hindsight 的 Agent 记忆体系按学习深度分为三个阶段Raw Facts原始事实——说了什么是从 Agent 对话中逐条抽取出的个体记忆世界事实与经历。Observations观察——注意到了什么是 Hindsight 跨大量事实自动合并出的模式与结论。Mental Models心智模型——理解了什么是稳定、具名、会随银行memory bank新证据持续演化的文档位于检索层次的最顶端因为学习就发生在这里。对于 Agent 每次会话都会询问的问题——这位用户的偏好是什么、这个项目的约定是什么——如果每一轮都从原始事实重新推导答案既浪费又脆弱每次 reflect 调用都从零开始综合输出会随会话漂移session-to-session drift。Mental Models 用 Hindsight 一直在持续精炼的持久化表征取代了这种重复推导。概念核心Mental Models 是保存下来的 reflect 响应由你为记忆银行策展。创建 Mental Model 时Hindsight 会以你的 source query 运行一次 reflect 操作并存储结果在后续的 reflect 调用中这些预计算的摘要会被优先检查从而提供更快、更一致的答案。技术上一个 Mental Model 就是mental_models表中的一行包含source_query要问什么content字段综合出的答案trigger配置何时刷新可选的tags哪些记忆喂养它以及谁能看到它Reflect 的三层检索层次Reflect agent 收到的系统提示明确写出了这个层次结构。源码实现在 prompts.py当银行存在 Mental Models 时提示词会生成如下层级### 1. MENTAL MODELS (search_mental_models) - Try First - User-curated summaries about specific topics - HIGHEST quality - manually created and maintained - If a relevant mental model exists and is FRESH, it may fully answer the question - Check is_stale field - if stale, also verify with lower levels ### 2. OBSERVATIONS (search_observations) - Second Priority - Auto-consolidated knowledge from memories - Check is_stale field - if stale, ALSO use recall() to verify ### 3. RAW FACTS (recall) - Ground Truth - Individual memories (world facts and experiences)Mental Models 位于该层次的最顶端。Agent 先查看它们然后逐级下降到观察、原始事实。层级越低需要做的综合工作越多响应也就越慢。源码中has_mental_models标志控制该层级是否出现在提示词中且 recall 的兜底措辞会随前置工具动态调整——提示词只会提及实际暴露给 LLM 的工具避免弱模型幻觉出被禁用的工具调用见 prompts.py 的注释与 issue #1724。数据表结构mental_models表mental_models表值得了解的列如下与官方文档一致并可由 alembic 迁移目录 中的多个版本佐证其演进例如v7q8r9s0t1u2_add_max_tokens_to_mental_models.py、b3w4x5y6z7a8_add_structured_content_to_mental_models.py、a2v3w4x5y6z7_add_last_refreshed_source_query.pyColumnNotesidbank_id复合主键——文本 ID按银行bank作用域隔离source_query刷新时经由 reflect 运行的自然语言查询content综合后的文本——Agent 拉取模型时读到的东西triggerJSONB 配置mode、refresh_after_consolidation、fact_types、tags_match等tagsJSONB 数组——既门控哪些记忆喂养模型也门控谁能读取它max_tokens单模型综合输出的上限默认2048structured_contentJSONB 的解析内容 AST——delta 模式刷新的关键武器last_refreshed_source_query追踪两次刷新之间的查询变化用于检测主题漂移reflect_response最近一次刷新的完整 reflect 载荷包括based_on溯源last_refreshed_at最近一次成功刷新的 ISO 时间戳history先前内容版本的 JSONB 数组embeddingname content的 384 维向量用于图谱集成复合主键(id, bank_id)意味着每个银行的 Mental Models 都生活在自己的命名空间中。你可以在bank-alice和bank-bob中各有user-preferences它们是完全独立的记录——跨银行不会发生id冲突。值得一提的是表上还维护了last_memory_seen_at水印列见迁移e7c3a91f4b62_add_mental_model_last_memory_seen_at.py与 memory_engine.py 中的_mental_model_stale_scope陈旧性判断问的是数据而非时钟——自该文档所依据的最新记忆被写入以来作用域内是否有新记忆因此比较基准是last_memory_seen_at而非墙钟时间last_refreshed_at单纯刷新模型本身不会让模型显得新鲜。两种刷新模式full与delta这是创建 Mental Model 时最重要的选择。两种模式本质上是对同一工作把新证据融入模型已有的理解的两种姿态full从当前证据重新推导一切。delta保留先前的理解把新证据折叠进去。请选择与你主题实际演化方式相匹配的模式。trigger.mode字段是Literal[full, delta]源码类型别名RefreshMode定义在 mental_model_refresh.py。完整的 trigger schemaclass MentalModelTriggerOutput(BaseModel): mode: Literal[full, delta] Field(defaultfull, ...) refresh_after_consolidation: bool Field(defaultFalse, ...) fact_types: list[Literal[world, experience, observation]] | None ... exclude_mental_models: bool ... exclude_mental_model_ids: list[str] | None ... tags_match: TagsMatch | None ... tag_groups: list[TagGroup] | None ... include_chunks: bool | None ... recall_max_tokens: int | None ... recall_chunks_max_tokens: int | None ...full—— 从头重新生成默认默认模式。每次刷新都针对银行运行完整的 reflect 流水线从零综合出一份新文档并覆盖之前的content。简单、可预测适合短文档或最终形态可能随时间漂移的模型。delta—— 对现有文档做外科手术式编辑Delta 模式更有意思。官方参考文档刷新会发出类型化操作add section、append bullet、replace block、remove stale paragraph。未被操作命中的章节按字节原样复制——没有改写、没有空白漂移、没有列表样式归一化。它适合长寿的playbook式模型——希望增量演化、又不希望 LLM 重写无需改动部分的文档。refresh_mental_model的决策逻辑解析trigger.mode。如果是delta检查现有content是否为非空且不是占位符Generating content...该占位符常量MENTAL_MODEL_PENDING_CONTENT定义在 memory_engine.py。检查source_query自last_refreshed_source_query以来是否变化。两项检查都通过则走 delta 路径否则回退到 full 综合。回退是自动且静默的——你无法对尚无基线的模型做 delta 编辑也无法对刚改过 source query 的模型做 delta 编辑因为先前的内容可能已不再相关。源码中用ModeFallbackReason枚举精确区分了这些回退原因no_baseline_content、source_query_changed、structured_doc_unreadable、delta_ops_failed、delta_ops_all_skipped见 mental_model_refresh.py。其中前两者是在 delta 真正运行之前就关闭 delta 的合法全量重建其余则属于失败类目_delta_failure_reason的映射逻辑见 memory_engine.py。Delta 刷新只看新事实delta 路径运行时recall 调用会做时间窗口限定刷新向 reflect 调用附加created_afterlast_refreshed_at因此 agentic 循环只会检索自上次刷新以来到达的记忆。LLM 随后通过 add/replace 操作把这些新事实混入现有结构化内容。模型中已表征的一切无需重读也绝不会由旧事实重写。刷新路径中的内联注释直言不讳so the agentic loop only retrieves genuinely new information.只检索真正的新信息。源码层面的细节更有意思created_after尽管名字带 created实际约束的是记忆的updated_at——窗口语义是在此窗口内发生过变化的记忆因此一条被编辑过的记忆会重新进入窗口这正是 delta 刷新从水印开始追踪的行为见 memory_engine.py 的参数文档。刷新的时间窗口模型定义在 mental_model_refresh.pycreated_after下界仅 delta 模式设置值为模型的last_memory_seen_atcreated_before上界数据库时间快照保证快照之后写入的记忆保持比持久化水印更新从而被下一次刷新捕获watermark真实刷新会持久化的last_memory_seen_at——快照时刻可见的最新作用域内记忆而不是now()。字节级一致byte-identical保证delta 模式下只有被操作命中的章节才会被重写其余内容从前一个structured_contentAST逐字复制。这比听起来更重要如果你的 Mental Model 含有一份团队审阅过的清单或代码块delta 模式保证 LLM 不会悄悄改写它。这一保证在架构上是结构性的而非靠提示词约束delta 操作引擎 delta_ops.py 的模块文档明确说明——未被任何操作提及的章节与块被物理复制通过不存在 LLM 对未改文本的重新发射环节因此散文漂移在结构上不可能发生。设计者还解释了为什么用操作而不是输出新结构化文档输出新文档仍要求 LLM生成每个章节的块包括它本不想改的给了它同样的漂移机会操作让无变化情形机械化零操作 → 文档逐字节相同操作可审计每次刷新产生一份精确变更日志便于调试 LLM 行为与解释 diff。并且块按id 而非下标寻址issue #3273下标需要模型去数差一仍然在范围内会静默覆盖无关块且被记为成功id 是复制来的错误的 id 无法解析、被跳过并上报。空内容保护与模式无关刷新有绝不用空内容覆盖的保护如果 LLM 调用失败或返回空内容现有内容被保留——刷新绝不会用空内容覆盖已填充的文档。如果 reflect 空手而归——没有匹配记忆、LLM 失败、标签不匹配——现有内容保持不变。刷新失败被记录在审计追踪中reflect_response.refresh_skipped empty_candidate但 Agent 读到的文档仍是最后一个已知良好版本。源码中刷新结果枚举完整刻画了这一行为content_written、content_unchanged、content_preserved_no_new_facts、refresh_failed_empty_candidate、refresh_failed_delta_not_applied见 mental_model_refresh.py而持久化路径上的操作记录还会追加refresh_failed_structured_output与refresh_failed_error以区分拒绝写入与运行中断。合并周期上的自动刷新refresh_after_consolidationtrigger配置的另一半是refresh_after_consolidation。设为true后模型会作为 Hindsight 合并consolidation周期的一部分自动刷新。钩子函数的 docstring 明确写出策略源码位于 consolidator.pyasync def _trigger_mental_model_refreshes( memory_engine: MemoryEngine, bank_id: str, request_context: RequestContext, consolidated_tags: list[str] | None None, perf: ConsolidationPerfLog | None None, ) - int: Trigger refreshes for mental models with refresh_after_consolidationtrue. SECURITY: Only triggers refresh for mental models whose tags overlap with the consolidated memory tags, preventing unnecessary refreshes across security boundaries. 所以合并不会在每次运行后刷新所有模型——它只刷新标签与本次合并触及的记忆标签重叠的模型。project:alice的 Mental Model 只在合并器处理了打上project:alice标签的记忆时才刷新。这既是性能优化避免无意义工作也是安全属性不跨租户边界泄露刷新信号。SQL 预过滤在标签交集上做廉价剪枝随后compute_mental_model_is_stale再按模型解析后的作用域验证自上次刷新以来确实有新记忆被摄入consolidator.py。刷新本身通过memory_engine.submit_async_refresh_mental_model()异步执行合并周期从不等待刷新完成且提交带skip_if_in_flight——合并链每轮都会触发此钩子、同一银行上可能并发运行多个合并因此仍在挂起/处理中的模型绝不会被二次入队issue #3411对应测试 test_mental_model_refresh_pending_dedupe_3487.py。标签匹配的地雷all_strict默认策略Mental Model 上的标签做两件事你必须同时考虑控制刷新路径生成内容时读取哪些记忆控制哪些 reflect / recall 调用能看到该模型第 1 点的默认策略是all_strict——刷新期间模型只能看到携带其全部标签的记忆。解析逻辑memory_engine.pydef _resolve_refresh_tag_filtering(model_tags, trigger_data): trigger_tags_match trigger_data.get(tags_match) tags_match: TagsMatch ( trigger_tags_match if trigger_tags_match else (all_strict if model_tags else any) ) return RefreshTagFiltering( tagsmodel_tags, tags_matchtags_match, tag_groupsNone, )官方参考文档点明了其含义Mental model 标签[user:alice]刷新期间它读取✅Alice prefers async communication—— 有user:alice✅Team uses Slack for announcements—— 有user:alice外加其他标签❌Company policy: no meetings on Fridays—— 无标签被排除❌Bob dislikes long meetings—— 没有user:alice标签以及随之而来的警告给 Mental Model 加标签会收窄其刷新可读取的源记忆池。如果还没有记忆携带这些标签刷新将返回空内容例如I cannot find any information…即使对同一查询直接 reflect 是正常的。最常见的踩坑方式你给 Mental Model 打了银行里没人携带的标签。刷新返回空。空内容保护启动现有很可能为空的文档保持为空然后你花一个小时纳闷为什么模型一直没生成。解决办法二选一在首次刷新前把标签回填到源记忆上或通过trigger.tags_match覆盖默认值例如any允许 OR 匹配any_strict也做 OR 匹配但仍排除无标签项。具体模式当你为每个项目构建project:name标签的记忆银行时务必确保 retain 流水线在你播种依赖它的 Mental Model 之前就已经附加项目标签。先播种、后打标签的 retain中间每一次刷新都对着空的源池运行——空内容保护会悄悄保住占位文本而你还在纳闷为什么什么都没生成。相关测试见 test_mental_model_trigger_flags.py 与 test_mental_model_trigger_patch_3687.py。合成提示词让 LLM 正确地写 Mental Model刷新运行时会以context参数调用reflect_async告诉 LLM 具体如何写 Mental Modelrefresh_context ( fYou are writing a document called {mm_name}. fONLY include content that directly answers the topic query. fDiscard observations that are tangential or off-topic — retrieval may return floosely related content that does not belong in this document.\n\n fQuality guidelines:\n f- Preserve concrete examples, before/after pairs, and sample sentences ffrom the observations. These teach more than abstract rules.\n f- If observations contain illustrative examples (e.g. ✅/❌ pairs, frewrites, sample phrases), include them in your answer.\n f- Structure the document around the topic, not around the sources. )该提示词中有两个值得关注的设计选择Discard observations that are tangential.丢弃离题的观察reflect agent 会浮现出比紧凑 Mental Model 所需更宽泛的记忆集。提示词指示 LLM过滤而非仅汇总。Structure the document around the topic, not around the sources.围绕主题而非来源组织文档没有这句模型输出往往读起来像记忆 A 说 X。记忆 B 说 Y。而不是一份连贯文档。这一行提示词的引导作用远超其篇幅。Delta 模式的结构化操作提示词delta 模式下由额外的系统提示词STRUCTURED_DELTA_SYSTEM_PROMPT驱动类型化操作的 LLM 调用决定要添加、编辑或删除什么完整定义见 prompts.py。模型返回DeltaOperationList随后被应用到现有 AST——这正是未改动章节字节级一致保证的来源。该提示词允许的八种操作每种都有精确的 JSON 形态append_block——在既有章节末尾添加块insert_block——在after_block_id之后插入块null表示置于章节开头replace_block——按block_id替换一个块的文本保留位置与 idremove_block——移除一个块add_section——添加新章节含标题、级别、块列表remove_section——移除整个章节replace_section_blocks——重写整个章节的块列表rename_section——重命名章节标题值得注意的硬性规则包括操作按section_id/block_id寻址必须逐字复制提示词中出现的 id杜撰的 id 会被丢弃缺失不是矛盾——SUPPORTING FACTS 里没有提及某实体/数量绝不等于它错了或被取代只有显式反驳或同一侧面状态、数量、属主、位置的更新陈述才构成覆盖的理由禁止仅改写未变内容的操作禁止归一化格式编号→圆点、大小写、段落→列表等每个操作必须由一条具体事实证成无变化时输出{operations: []}。提示词还通过build_structured_delta_promptprompts.py对超大的文档 JSON、新信息综合稿与支撑事实三块做预算拟合默认max_input_tokens为 24,000见_STRUCTURED_DELTA_DEFAULT_MAX_INPUT_TOKENS并对超长文档给出文档预算超限提示引导模型优先用替换而非追加来回收空间——因为 delta 只增不减长寿页面每轮都会长一点。撤回Retraction通道delta 刷新还配套了独立的撤回通道STRUCTURED_RETRACTION_SYSTEM_PROMPT与build_structured_retraction_promptprompts.py处理文档引用了但银行里已不存在的事实——事实被删除后不会出现在任何 recall、工具调用或支撑事实列表中是唯一对刷新不可见的输入。该通道只允许remove_block/remove_section/replace_block/replace_section_blocks四种删减操作规则是拿不准就保留删除不可恢复并对照 STILL-SUPPORTED FACTS 区分重摄取换了新 id内容未撤回与真撤回。这一逻辑对应的模型定义见 mental_model_refresh.py测试见 test_mental_model_retractions.py 与 test_delta_retain_orphan_observations.py。详情级别按需付费list 和 get 端点支持detail参数有三个级别在 http.py 中定义为Literal[metadata, content, full]get 端点默认fulllist 端点默认metadataLevelIncludesUse casemetadataid、bank_id、name、tags、last_refreshed_at、created_at这个银行里有哪些模型contentmetadatasource_query、content、max_tokens、triggerAgent 启动——把实际文本加载进提示词fullcontentreflect_response溯源深度检查或审计文档对何时用哪种态度坚决面向 Agent 的启动流程请使用detailcontent。它包含 Agent 所需的一切且避开了沉重的reflect_response溯源链——对于含大量模型的银行后者可能超过 200KB。如果你在启动路径中调用list_mental_models来渲染缓存块请请求content并跳过每个模型 200KB 的溯源载荷。如果你在调试为什么模型生成了奇怪输出请请求full并检查 reflect 实际返回了什么。Clear vs Refresh两个看起来相似的操作refresh_mental_model—— 用当前记忆重新生成content。遵循modedelta 或 full。clear_mental_model—— 把content置空使下一次刷新没有可供 delta 编辑的基线从而强制回退到 full 综合。文档解释了为什么这很重要对于长寿的 delta 模式 Mental Model考虑安排周期性的 clear refresh例如每 48 小时一次在享受两次之间增量 delta 更新的同时保持内容准确。该模式是依赖 delta 模式刷新来处理合并触发的廉价、频繁、低流失更新周期性clearrefresh从零重建文档重置大量小编辑累积的漂移。对应端点实现见 http.pyhistory 端点与相关测试。完整实战示例下面是一个完整闭环使用 Python 客户端构建一个用户偏好模型每当合并器处理新记忆时自动刷新from hindsight_client import Hindsight client Hindsight(base_urlhttps://api.hindsight.vectorize.io, api_keyhsk_...) # 创建模型 mm client.create_mental_model( bank_idmy-app, nameUser Preferences, source_query( What does the user prefer in coding style, tooling, communication, and review? Capture only durable preferences expressed across sessions, not one-off requests. ), max_tokens600, trigger{ mode: delta, refresh_after_consolidation: True, }, ) print(fcreated {mm[id]}) # 手动触发首次刷新创建是异步的但首次刷新若不做则会等合并 client.refresh_mental_model(bank_idmy-app, mental_model_idmm[id]) # 之后——在 Agent 启动时把模型加载进系统提示词 model client.get_mental_model(bank_idmy-app, mental_model_idmm[id]) prompt_block fuser_preferences\n{model[content]}\n/user_preferences几个会话后下一次合并针对my-app运行时模型经由refresh_after_consolidation钩子自动刷新。因为mode: delta只有需要变更的文档部分被重写其余部分按字节保留。每周左右运行一次clear_mental_model从零重建client.clear_mental_model(bank_idmy-app, mental_model_idmm[id]) client.refresh_mental_model(bank_idmy-app, mental_model_idmm[id])平时用 delta、偶尔清空重建这一模式正是官方文档对长寿模型明确推荐的。若想在应用clear之前确认刷新会产生什么MentalModelDryRunRefreshResultmental_model_refresh.py描述的 dry-run 预览机制可以不动任何持久化状态地运行完整流水线返回effective_mode、mode_fallback_reason、outcome、would_persist、检索/使用事实数、unified diff 与完整执行追踪。REST 与客户端表面MethodEndpointPython clientPOST/v1/default/banks/{bank_id}/mental-modelscreate_mental_model()GET/v1/default/banks/{bank_id}/mental-modelslist_mental_models()GET/v1/default/banks/{bank_id}/mental-models/{id}get_mental_model()PATCH/v1/default/banks/{bank_id}/mental-models/{id}update_mental_model()DELETE/v1/default/banks/{bank_id}/mental-models/{id}delete_mental_model()POST/v1/default/banks/{bank_id}/mental-models/{id}/refreshrefresh_mental_model()POST/v1/default/banks/{bank_id}/mental-models/{id}/clearclear_mental_model()GET/v1/default/banks/{bank_id}/mental-models/{id}/historyget_mental_model_history()端点定义可在 http.py 中直接核对。所有客户端方法都有 asynca*前缀变体。MCP 服务器暴露相同的操作作为工具create_mental_model、refresh_mental_model等因此通过 MCP 与 Hindsight 对话的 Claude / Cursor / Codex agent 可以直接创建与刷新模型。版本演进Mental Models 跨版本持续演化。主要里程碑与博客正文一致v0.4.02026-01-28—— Mental models 发布见 学习能力发布博客。v0.5.02026-04-07—— Bank Template Hub。Mental models 可在可移植模板清单中定义并在导入时按id匹配。v0.5.22026-04-15—— trigger API 上的 recall 控制逐模型调优fact_types、tags_match、include_chunks、recall_max_tokens。v0.5.32026-04-17——Delta 模式发布。刷新发出结构化操作而非从头重新生成见 v0.5.3 发布博客。v0.7.02026-05-27——clear_mental_model端点history 上限防止 JSONB 溢出full 刷新正确重设挂起的 delta 基线见 v0.7.0 发布博客。实践中常见的五个坑all_strict标签默认值。如果你的模型带标签刷新路径只看到携带全部标签的记忆。如果还没有记忆携带这些标签刷新返回空。主题漂移回退到 full。修改source_query会使 delta 基线失效下一次刷新从零重新综合。Delta 随时间漂移。大量小 delta 刷新会累积小误差。周期性clearrefresh重建干净基线。热路径中使用detailcontent。full级别每个模型可能返回 200KB——检查用没问题每次 Agent 启动都调用则是灾难。银行作用域 ID。Mental Model 是(id, bank_id)——两个银行中的相同id是两个不同记录。设计 ID 约定时要规划这一点。核心思想Mental Models 是 Hindsight学习Agent 反复询问之事的方式。原始事实捕获说了什么观察捕获 Hindsight 在它们之间注意到什么Mental Models 捕获 Hindsight 最终理解了什么——与前两层不同它们稳定、具名并随新证据落地而增量精炼。性能收益——即时检索、无需每轮综合——只是副产品。真正的收益在于你的 Agent 不再每会话重新推导对稳定主题的理解而是从 Hindsight 一路持续精炼的表征开始工作。延伸阅读Entity Labels自动给记忆打标签——受控词表标签往往决定了你的 Mental Models 能看到什么Hindsight 学习能力发布v0.4.0——Mental Models 的诞生背景Recall vs Reflect 深度对比——理解 Mental Models 赖以工作的 reflect 流水线内存合并机制——refresh_after_consolidation所依赖的合并周期原理核心实现源码mental_model_refresh.py、prompts.py、delta_ops.py、consolidator.py相关测试test_mental_models.py、test_mental_model_consolidation_refresh_scope.py、test_mental_model_scheduled_refresh.py、test_delta_retain.py【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考