ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Hindsight Knowledge Pages 完全指南:让记忆库自维护的「活文档」知识库 API

Hindsight Knowledge Pages 完全指南:让记忆库自维护的「活文档」知识库 API Hindsight Knowledge Pages 完全指南让记忆库自维护的「活文档」知识库 API【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight本指南围绕 Hindsight 的Knowledge Pages知识页面展开这是一种由记忆库bank自己撰写、并随着记忆持续学习而不断自我改写的活文档以文件夹树组织、可浏览、可搜索还能映射为磁盘上的普通 Markdown 文件。读完本文你将掌握知识库的全部 API 端点树查询、增删改查、混合搜索、导出、页面与文件夹的创建参数、tags过滤与默认 trigger 的底层语义、is_stale过期标记的判定规则以及如何用 Python / Node.js / CLI 客户端在真实项目中落地一套答案来自记忆、结构像 Wiki的知识管理系统。什么是 Knowledge Pages形状是 Wiki引擎是记忆Hindsight 官方概念文档developer/knowledge-pages.md对知识页面的定义是记忆库写给自己的活文档。每个页面回答一个问题——这里有哪些组件我们的错误处理约定是什么——并在记忆库学到更多知识时改写自己。关键在于其底层模型A knowledge pageisa mental model.一个知识页面本质上就是一个 mental model心智模型拥有相同的合成机制、相同的后台刷新、相同的事实来源provenance。两者的区别只在于使用门槛心智模型需要使用者理解合成范围synthesis scope与刷新触发器refresh trigger这些机制细节而维护 Wiki 不应该要求任何人思考这些。因此页面把决策预先做好了只从观察observations构建读取的是观察中已合并、去重、有证据支撑的信念而不是原始会话噪音增量刷新每次合并consolidation在页面作用域内产生新知识时才重写文档且采用增量编辑而非整体重新生成不读其他页面页面之间不会互相引用避免错误声明在知识库中形成反馈循环更大的内容预算因为它是文档而不是一次回答。从源码结构看这套页面层只拥有树结构、内容全部落在背书的心智模型上的设计也体现在存储层API 文档的 Storage 一节明确指出树结构存放在knowledge_pages表而页面正文、source query、tags、token 预算、trigger 与刷新历史全部存放在mental_models表。迁移脚本 a9b8c7d6e5f4_add_knowledge_pages.py 即为该表的建立依据。所有端点都相对于一个记忆库基础路径为/v1/default/banks/{bank_id}/knowledge-base组织得像 Wiki文件夹树与页面生命周期页面存在于文件夹树中嵌套层级任意、页面名在其所在文件夹内唯一、删除文件夹会级联删除整个子树。这就是全部结构模型——一个层级结构正如任何人手工整理文档的方式。Architecture/、Runbooks/、Decisions/每个目录下的页面各自保持最新这正是知识库可导航而非一堆合成的 blob的原因。一个页面的生命周期分为五个阶段见 API 文档Create创建——页面以占位内容存储并提交一个后台刷新调用立即返回operation_idFirst build首次构建——因为没有既有文档可编辑首次构建是一次完整生成Consolidation合并——新记忆被保留并合并为观察Staleness check过期检查——要求检查的页面按其自身作用域tags 与fact_types均生效被检查Delta refresh增量刷新——过期的页面被重写仅用新增观察编辑既有文档。观察是页面构建自的材料但页面仍可检视其下的证据刷新 agent 可以把一条记忆展开到其原始 chunk 或文档除非银行禁用了store_document_text若启用HINDSIGHT_API_REFLECT_SOURCE_FACTS_MAX_TOKENS默认关闭观察搜索还会额外返回每条观察的 grounding facts。获取整棵树GET /knowledge-base/treeGET /knowledge-base/tree将整个知识库以嵌套的文件夹/页面树返回。页面正文不包含在树响应中——读取内容需单独获取页面。树的每个节点带kindfolder或page、mental_model_id仅页面指向背书的心智模型、description页面的 source query——即重建它的那个问题、timestamp页面为最近刷新时间、文件夹为最近更新时间、is_stale与managed等字段。三种客户端调用方式# Fetch the whole knowledge base as a nested folder/page tree (no page bodies) tree client.get_knowledge_base_tree(BANK_ID) for root in tree.roots: print(f{root.kind}: {root.name}) for child in root.children: print(f {child.kind}: {child.name} (stale: {child.is_stale}))// Fetch the whole knowledge base as a nested folder/page tree (no page bodies) const tree await client.getKnowledgeBaseTree(BANK_ID); for (const root of tree.roots) { console.log(${root.kind}: ${root.name}); for (const child of root.children ?? []) { console.log( ${child.kind}: ${child.name} (stale: ${child.is_stale})); } }# Show the folder/page tree (no page bodies) hindsight knowledge-base tree $BANK_ID响应 JSON 结构示例API 文档{ roots: [ { id: kf-9f2c..., kind: folder, name: Operations, parent_id: null, mental_model_id: null, managed: false, description: null, tags: [], timestamp: 2026-08-01T11:04:0200:00, is_stale: null, children: [ { id: kp-2e85..., kind: page, name: Deploying the API, parent_id: kf-9f2c..., mental_model_id: mm-77ab..., managed: false, description: How is the API deployed?, tags: [ops], timestamp: 2026-08-03T09:12:4400:00, is_stale: true, children: [] } ] } ] }字段速查表FieldDescriptionkindfolder或pagemental_model_id背书的心智模型仅页面description页面的 source query——重建它的那个问题timestamp页面为最近刷新时间文件夹为最近更新时间is_stale仅页面当该页面作用域内的一条记忆自页面上次读取记忆以来被写入时为true详见下文managed节点被标记为系统拥有而非人工编写时为trueis_stale 是如何判定的每个页面都按其自身作用域——它的 tags 和fact_types——回答过期判定问题使用的是与心智模型调度刷新时完全相同的 staleness 检查。被标记的页面是一次刷新会真正重写它的页面未被标记的则是刷新会放它一马的页面。银行其他区域的活动不会标记一个自身作用域安静的页面。两个关键实现事实整棵树一次查询无论银行有 3 个页面还是 300 个页面过期标记的代价相同。测试 test_tree_asks_once_for_the_whole_tree 直接验证了这一点与单模型读取完全一致GET /mental-models/{id}对页面的背书模型返回完全相同的值由测试 test_tree_agrees_with_the_exact_per_model_check 佐证。它看不到的一件事是删除deletions检查只问自页面上次读取记忆以来作用域内写入了什么而删除一条作用域内记忆不会留下任何写入痕迹——因此一个引用了被删事实的页面会持续把自己报告为最新。相关测试包括 test_tree_staleness_ignores_writes_outside_a_page_scope 与 test_tree_flags_the_page_whose_own_scope_changed。创建页面POST /knowledge-base/pages创建页面会以占位内容存储它并在后台调度首次构建。调用立即返回operation_id需通过 operations API 轮询该操作以得知内容何时就绪。# Create a page — content is generated in the background page client.create_knowledge_page( BANK_ID, nameDeploying the API, source_queryHow is the API deployed?, parent_idfolder.id, tags[ops, type:runbook], ) # Poll the operation to know when the first build has finished print(fPage ID: {page.page_id}, operation: {page.operation_id})// Create a page — content is generated in the background const page await client.createKnowledgePage( BANK_ID, Deploying the API, How is the API deployed?, { parentId: folder.id, tags: [ops, type:runbook] }, ); // Poll the operation to know when the first build has finished console.log(Page ID: ${page.page_id}, operation: ${page.operation_id});# Create a page — content is generated in the background hindsight knowledge-base create-page $BANK_ID \ Deploying the API \ How is the API deployed? \ --parent-id $FOLDER_ID \ --tags ops,type:runbook响应示例{ page_id: kp-2e85..., mental_model_id: mm-77ab..., operation_id: op-1d0f... }参数表ParameterTypeRequiredDescriptionnamestringYes页面名。在其所在文件夹内必须唯一大小写不敏感——重名返回409。仅 PostgreSQL 上强制。source_querystringYes页面回答的问题每次刷新都会重新提问。parent_idstringNo页面所在的文件夹。null或省略则创建在根下。tagslistNo限定页面从哪些记忆中构建的 tags——见下文 Tags 是过滤器。type:x标签同时设置页面的渲染类型且仍计入过滤器。max_tokensintNo内容预算。默认4096普通心智模型默认2048。triggerobjectNo刷新配置——见下文。客户端实现可对照 hindsight_client.py 中create_knowledge_page约 L1747 起确认各参数的实际传递方式。重要陷阱Tags 是过滤器不是标签tags不是页面的标签而是页面构建自的作用域一个带标签的页面默认以all_strict模式匹配记忆——一条记忆必须携带页面的每一个标签且完全排除未打标签的记忆。例如这样创建的页面{ name: Homelab Infrastructure, source_query: NAS, ThinkPad, docker containers, jellyfin, tags: [type:runbook, homelab, infrastructure] }只会由同时带type:runbook且homelab且infrastructure的记忆构建。如果你的记忆在被保留时没有携带这些精确的标签——而通常情况正是如此这些标签是在创建页面时才发明出来描述主题的——那么页面将匹配不到任何东西生成结果会是I dont have information about this.而同一查询的直接 recall 仍能返回全部内容因为 recall 没有被施加同样的过滤器。type:x标签特别容易踩坑文档说它设置页面的渲染类型但它和任何标签一样会收窄检索范围。三种正确的处理方式You wantDo this页面从整个银行构建省略tags或传[]用 tags 限定作用域但未打标签的记忆仍被包含保留tags并加trigger: {tags_match: all}只匹配携带每一个标签的记忆严格隔离例如按用户隔离页面保留tags与all_strict默认值若要修复一个已经生成空内容的页面用{tags: []}或放宽的tags_match对它做PATCH然后刷新即可——因为 tags 存储在背书的心智模型上而非烧录进内容里。完整语义any、all、any_strict、all_strict、exact见 recall 的 tags 参考。默认 Trigger文档导向的配置省略trigger时页面以如下文档导向配置创建{ mode: delta, fact_types: [observation], exclude_mental_models: true, refresh_after_consolidation: true }这使页面成为一份活文档只从已合并的观察构建每当合并在其作用域内产生新知识时增量刷新且永不受其他页面影响。SettingWhyfact_types: [observation]页面读取合并后的信念而非其下原始的会话噪音。观察已经过去重且有证据支撑所以页面读起来像一份定稿的文档而非逐字记录。这是结构性强制作用域内只有observation时刷新 agent 根本不会拿到原始记忆的 recall 工具。exclude_mental_models: true页面从不反思兄弟页面。否则页面会互相引用并漂移成反馈循环——一条错误声明会传遍整个知识库。mode: delta每次刷新用自上次刷新以来的新内容编辑既有文档而非重新生成因此手工调整的结构与措辞得以保留。详见 Refresh Mode。refresh_after_consolidation: true每当合并在其作用域内产生新知识时页面改写自己——与任何心智模型一样受同一 staleness check 门控因此无关的银行活动不会触发重建。测试 test_default_trigger_and_max_tokens 与 test_client_trigger_and_max_tokens_override_defaults 验证了默认值与覆盖行为。补充要点提供的trigger是一个补丁patch只有你真正发送的字段被应用其余保持上述默认值。例如发送{trigger: {tags_match: all}}会放宽标签过滤器同时保持mode、fact_types、exclude_mental_models、refresh_after_consolidation不变。唯一的例外是两个刷新触发器互斥设置refresh_cron会清除refresh_after_consolidation反之亦然所有心智模型 trigger 设置在此都可用——包括用refresh_cron做定时重建代替合并驱动以及用tag_groups做复合标签作用域。创建文件夹POST /knowledge-base/folders# Create a folder (omit parent_id, or pass None, to create it at the root) folder client.create_knowledge_folder(BANK_ID, nameOperations) print(fFolder ID: {folder.id})// Create a folder (omit parentId, or pass null, to create it at the root) const folder await client.createKnowledgeFolder(BANK_ID, Operations); console.log(Folder ID: ${folder.id});# Create a folder (omit --parent-id to create it at the root) hindsight knowledge-base create-folder $BANK_ID OperationsParameterTypeRequiredDescriptionnamestringYes文件夹名parent_idstringNo父文件夹。省略或传null表示根目录。一个不存在的parent_id或指向页面而非文件夹的parent_id返回400。测试 test_create_folder_bad_parent、test_create_page_missing_parent_rolls_back_mental_model 等还验证了创建失败时的心智模型回滚与重复页面409语义。读取页面GET /knowledge-base/pages/{page_id}返回页面渲染为 Markdown 文档的结果# Read a page as a markdown document document client.get_knowledge_page(BANK_ID, page.page_id) print(document.type) # runbook — from the type:runbook tag print(document.body) # the synthesized markdown body print(document.markdown) # YAML frontmatter body// Read a page as a markdown document const document await client.getKnowledgePage(BANK_ID, page.page_id); console.log(document.type); // runbook — from the type:runbook tag console.log(document.body); // the synthesized markdown body console.log(document.markdown); // YAML frontmatter body# Read a page as a markdown document hindsight knowledge-base get-page $BANK_ID $PAGE_ID响应示例API 文档{ id: kp-2e85..., name: Deploying the API, type: runbook, description: How is the API deployed?, tags: [ops], timestamp: 2026-08-03T09:12:4400:00, body: # Deploying the API\n\n..., markdown: ---\nid: \kp-2e85...\\ntype: \runbook\\n...\n---\n\n# Deploying the API\n\n... }body是单独的合成 Markdown 正文markdown是完整文档YAML frontmatter 块id、type、title、description、tags、timestamp加正文type来自type:x标签默认knowledge-page。返回的tags中会移除type:标签。页面正文的 Markdown 渲染细节frontmatter 生成等在测试 test_page_markdown.py 中有覆盖。搜索页面GET /knowledge-base/search这是文档级混合搜索全文BM25匹配与向量相似度匹配用 Reciprocal Rank FusionRRF融合。没有重排序步骤因此足够快可以成为 agent 的第一次调用。注意它与 recall 的区别页面搜索返回的是整页文档recall 搜索的是单条记忆。# Hybrid search (full-text vector) over whole pages results client.search_knowledge_base(BANK_ID, qhow do we deploy, limit5) for hit in results.results: print(f{hit.score:.3f} {hit.name}: {hit.snippet})// Hybrid search (full-text vector) over whole pages const results await client.searchKnowledgeBase(BANK_ID, how do we deploy, { limit: 5 }); for (const hit of results.results) { console.log(${hit.score.toFixed(3)} ${hit.name}: ${hit.snippet}); }# Hybrid search (full-text vector) over whole pages hindsight knowledge-base search $BANK_ID how do we deploy --limit 5响应示例{ results: [ { id: kp-2e85..., name: Deploying the API, mental_model_id: mm-77ab..., snippet: The API is deployed via ..., score: 0.032, updated_at: 2026-08-03T09:12:4400:00 } ], total: 1 }ParameterTypeDefaultDescriptionqstring—必填。搜索查询最小长度 1。limitint10最大结果数1–50。BM25 检索臂的实现细节在 postgresql.py 中按后端原生tsvector、pgroonga、pg_search/ParadeDB、pg_textsearch、vchord分支测试 test_knowledge_bm25_dispatch.py 逐条断言了各后端生成的 SQLtest_knowledge_search_text_search_disabled.py 覆盖了禁用全文搜索时的降级路径。排名相关行为相关页面排第一、排除文件夹、自然语言问题仍能匹配 BM25 等见 test_knowledge_base.py 中的test_ranks_relevant_page_first等用例。更新或移动节点PATCH /knowledge-base/nodes/{node_id}一次PATCH即可重命名节点、移动节点和/或更新页面的选项。每个字段仅在出现在请求体中时生效# Rename a node, move it, and/or update a pages options. # Changing source_query rebuilds the page against the new question. client.update_knowledge_node( BANK_ID, page.page_id, nameDeploying the API (v2), tags[ops, type:runbook, reviewed], )// Rename a node, move it, and/or update a pages options. // Changing sourceQuery rebuilds the page against the new question. await client.updateKnowledgeNode(BANK_ID, page.page_id, { name: Deploying the API (v2), tags: [ops, type:runbook, reviewed], });# Rename a node, move it, and/or update a pages options. # Changing --source-query rebuilds the page against the new question. hindsight knowledge-base update $BANK_ID $PAGE_ID \ --name Deploying the API (v2) \ --tags ops,type:runbook,reviewedParameterTypeApplies toDescriptionnamestringBoth新名称parent_idstring | nullBoth新的父文件夹。显式传null移动到根目录。source_querystringPages新问题。更改它会使页面按新问题重建。tagslistPages替换页面的 tags从而替换它构建自的作用域。传[]清除它们并让页面从整个银行重建。max_tokensintPages新的内容预算triggerobjectPages要修改的刷新设置以补丁方式应用。{tags_match: all}保留页面 tags 但不再排除未打标签的记忆。发送空请求体返回400未知节点返回404。仓库测试对这一端点覆盖很深改名与移动失败时的回滚test_patch_rolls_back_the_rename_when_the_move_fails、移动成环被拒绝test_move_cycle_rejected、并发反向移动被串行化test_move_serializes_behind_a_concurrent_delete以及 trigger 补丁语义test_update_patches_the_trigger_instead_of_replacing_it。删除节点DELETE /knowledge-base/nodes/{node_id}删除一个文件夹或页面及其整个子树。背书被删页面的心智模型也会被移除。# Delete a folder or page — deleting a folder removes its whole subtree client.delete_knowledge_node(BANK_ID, folder.id)// Delete a folder or page — deleting a folder removes its whole subtree await client.deleteKnowledgeNode(BANK_ID, folder.id);# Delete a folder or page — deleting a folder removes its whole subtree hindsight knowledge-base delete $BANK_ID $FOLDER_ID -y响应{status: deleted}。级联行为见测试 test_delete_folder_cascades。导出为 Markdown BundleGET /knowledge-base/export返回整个知识库为一组可移植的 Markdown 文件一个嵌套的index.md、每个页面一个page-id.md以及被重建过页面的page-id.log.md刷新历史。# Export the knowledge base as a portable markdown bundle bundle client.export_knowledge_base(BANK_ID) for file in bundle.files: print(file.path) # index.md, page-id.md, page-id.log.md// Export the knowledge base as a portable markdown bundle const bundle await client.exportKnowledgeBase(BANK_ID); for (const file of bundle.files) { console.log(file.path); // index.md, page-id.md, page-id.log.md }# Export the knowledge base as a portable markdown bundle hindsight knowledge-base export $BANK_ID响应示例{ files: [ {path: index.md, content: ---\ntype: \index\\n...}, {path: kp-2e85....md, content: ---\nid: \kp-2e85...\\n...}, {path: kp-2e85....log.md, content: ---\ntype: \log\\n...} ] }映射到磁盘hindsight fs mount --bank my-bank通过后台刷新循环把本地文件夹与这个 bundle 保持同步——这样ls、grep、rg和你的编辑器都能直接操作真实文件。CLI 侧的实现位于 fs/client.rs其中fetch knowledge-base tree调用即负责拉取树结构并与页面内容做联结。存储结构两表分工TableHoldsknowledge_pages树文件夹与页面、它们的名字、父节点与排序。页面行引用其背书的心智模型文件夹行没有。mental_models内容文档正文、source query、tags、token 预算、trigger 与刷新历史。页面层只拥有树结构——关于内容的一切都存在于背书的心智模型上这正是为什么每一条心智模型能力都能原封不动地作用于页面。端点速查表MethodPathDescriptionGET/knowledge-base/tree带过期标记的嵌套文件夹/页面树POST/knowledge-base/folders创建文件夹POST/knowledge-base/pages创建页面异步首次构建GET/knowledge-base/pages/{page_id}以 Markdown 读取页面GET/knowledge-base/search页面的混合搜索PATCH/knowledge-base/nodes/{node_id}重命名、移动或重配置节点DELETE/knowledge-base/nodes/{node_id}删除节点及其子树GET/knowledge-base/export以 Markdown 文件导出整个知识库所有端点在 HTTP 层api/http.pyL6727 起与客户端Python 的 knowledge_base_api.py均有完整对应实现。MCP 与 agent SDK 侧还暴露了agent_knowledge_*工具见 mcp_tools.py让 agent 能在会话中列出、读取、创建与更新自己的页面。为什么不用普通文件投影视图而非存储回答如果答案是文档树我们是否重新发明了文件这个质疑官方文档给出了清晰的分界文件是信息老化之地——最后写它的人赢了矛盾悄然积累没人去调和。而知识页面是处理过记忆之上的投影视图projected view正如数据库视图不是表在页面被写出之前Hindsight 已经完成了文件自身做不到的工作——从原始会话、提交和文档中抽取事实、去重、并通过合并调和矛盾。因此当团队先决定 X、后又修订为 Y 时页面写的是 Y——并且能说出为什么——而不是在一段之隔的地方同时保留二者。你的原始文档仍是说了什么的真相来源页面是什么成立的调和真相。这也是页面自我痊愈而非腐烂的原因它们不是存储而是渲染。删除一个页面什么都不丢失——它会从记忆重新投影。不妨试试删掉一个 Wiki 再把它找回来。工作方式一览见概念文档控制平面——Knowledge Base 视图渲染树、页面内容以及哪些页面已落后HTTP API——即本文讲解的完整端点面CLI——hindsight fs把一个银行镜像为本地 Markdown 文件文件夹Agent 工具——agent SDK 暴露agent_knowledge_*工具agent 可在会话期间列出、读取、创建和更新自己的页面。【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进