news 2026/9/13 21:20:35

Hindsight 银行(Bank)策略完全指南:如何为 Agent 记忆划定隔离边界

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Hindsight 银行(Bank)策略完全指南:如何为 Agent 记忆划定隔离边界

Hindsight 银行(Bank)策略完全指南:如何为 Agent 记忆划定隔离边界

【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight

导读:Hindsight 的所有集成文档都会告诉你设置一个bank_id,却几乎不告诉你该如何决定"一个 bank 应该代表什么"。这个看似微不足道的决定,实际决定了你的 Agent 到底能回忆起什么:范围划得过宽,一个用户的记忆会渗入另一个用户;划得过窄,Agent 又够不到它需要的内容,因为那些内容住在它看不见的另一个 bank 里。本文以 Hindsight 官方博客《One Bank or Many? A Field Guide to Structuring Agent Memory》为骨架,结合 hindsight-api-slim 的源码与 hindsight-integrations 中各集成的真实配置,系统讲解 bank 的本质、tag 的正确用法、五类经典划分策略、性能真相与反模式清单,并给出一份可直接照做的决策清单。

TL;DR

  • bank 是召回边界(recall boundary)recallretainreflect全部在单个 bank 内运行,系统不存在跨 bank 查询。所以"这两个东西该不该共享一个 bank"实际等价于"A 存入的记忆,B 是否应该能召回?"
  • 硬隔离边界(租户、客户、不可信上下文)用独立 bank软分区(同一个信任域内有时想过滤、有时想交叉引用的分组)用同一 bank 里的 tags
  • bank 是首次使用时惰性创建的,一个新的bank_id字符串就是一个全新的空记忆。这正是大多数碎片化的根源:每个会话一个 bank,意味着每个会话都从空白开始。
  • 多个集成把这一策略直接暴露为配置:静态bankId,或由userprojectagent等上下文字段组合而成的dynamicBankId
  • 策略可以事后调整,但召回历史被限定在 bank 内,所以前期选对能省下一次数据回填

一个核心概念:bank 是召回边界

在 Hindsight 中,一个 bank 是一套完整、隔离的存储:从会话中保留的记忆(memories)、为检索而索引的文档、从中抽取的实体、连接这些实体的知识图谱,以及 bank 自身的 disposition 与 directives。bank 之间彼此隔离,没有内置的跨 bank 查询。每一次recallretainreflect调用都只点名一个bank_id并始终停留在该 bank 之内。

这一个事实就是全部的设计工具。与其问"我该如何组织记忆",不如对每条边界只问一个问题:

如果 A 保留了一条记忆,B 应该能召回它吗?

答案是yes,A 和 B 就属于同一个 bank;答案是no,它们就该分属不同 bank。下文所有模式都只是把这个问题套用到不同的 A 和 B 上:两个用户、两个项目、两个 Agent、两个渠道。

还有一个关键的机械细节:你不需要预创建 bank。第一次使用某个bank_id时,Hindsight 会用默认设置创建它。这里有一个真实的好处(无需 provisioning 步骤),也有一个真实的陷阱:一个你从未写入过的bank_id就是一片空白,一个拼写错误或不稳定的 id 会静默地给你一份全新的空记忆,而不是一个报错。Bank 身份(identity)是承重的(load-bearing)。

主轴:一个 bank 代表什么?

以下是常见策略,每一条都用召回边界问题来框定,并给出它会在哪里"咬人":

策略每 bank 代表适合哪里会咬人
Global一切单用户工具、个人开发助手第二个用户一出现,记忆就混在一起
Per-user终端用户SaaS 产品、个人助手一个用户有多个项目时,全部混为一谈
Per-project / per-repo代码库或工作区编码 Agent、项目工作用户的跨项目上下文不会跟随他
Per-agentAgent 角色角色分明、需要各自经验的 multi-agent 系统本应共享上下文的 Agent 无法共享
Shared / team一个群体(有意为之)跨多个界面或团队成员共享一份记忆需要显式 opt-in,它不是默认值

这些策略没有哪个在抽象意义上是"正确"的。正确的选择取决于你的 A 和 B 是谁:

  • 消费者助手——每个人的记忆绝不能碰到别人:per-user。用户就是隔离边界,所以也就是 bank 边界。
  • 编码 Agent——真正有用的记忆是关于这个代码库的(它的约定、它的决策、它的布局):per-repo。这正是 Aider 集成默认做的事:bank 默认取 git 仓库名(见 hindsight-integrations/aider/README.md),让所有操作同一仓库的编辑器共享一份项目记忆。
  • 一组各司其职的 Agent(规划者、研究员、审查者)——各自保留经验:per-agent。但一旦你希望它们共享上下文协同工作,就让它们指向同一个sharedbank。

源码印证:Claude Code 的dynamicBankId如何推导 bank

以 hindsight-integrations/claude-code/scripts/lib/bank.py 中的derive_bank_id为例,其解析顺序为:

  1. directoryBankMap:显式的目录 → bank 映射,优先级最高;
  2. 静态模式dynamicBankId=false):使用bankId配置(默认"claude-code");
  3. 动态模式dynamicBankId=true):按dynamicBankGranularity字段列表组合 id,默认["agent", "project"]

动态模式下各字段的取值来源(见同文件field_map):

字段取值来源
agent配置的agentName(默认"claude-code"
project由 cwd 推导;开启resolveWorktrees(默认)时通过git rev-parse --git-common-dir解析到主仓库名,使同一仓库的所有 worktree 共享同一 bank
sessionhook 输入的session_id(缺失则为"unknown"
channel环境变量HINDSIGHT_CHANNEL_ID(用于 Telegram/Discord 等渠道 Agent,缺失则"default"
user环境变量HINDSIGHT_USER_ID(多用户 Agent,缺失则"anonymous"

最终 id 用"::"连接各段,例如["agent", "project"]会得到形如claude-code::myproject的 bank id。注意sessionuser的缺省值分别是"unknown""anonymous"——如果启用了含这两个字段的粒度却未正确配置来源,多个真实不同的会话/用户会静默地共享同一个 bank,这与"不稳定 id 导致碎片化"是同一个问题的反面,值得警惕。

大多数人忽略的第二条轴:bank 内的 tags

最常见的错误是:每当你想要在同一个信任域内分离两种类型的记忆,就去开一个新的 bank。你不需要为此开新 bank,你需要的是tags

Hindsight 允许你在 retain 记忆时附加tags,并在 recall / reflect 时按 tags 过滤。Tags 是bank 内的分区,在查询时生效,因此同一个 bank 可以容纳许多被标记的切片,由你在每次查询时决定看哪些切片:

# retain 时带上作用域标签 client.retain(bank_id="acme", content="We deploy on Fridays only in emergencies", tags=["project:web"]) # 只在该切片内召回 client.recall(bank_id="acme", query="deploy policy?", tags=["project:web"], tags_match="all")

tags_match模式是最值得知道的细节,因为它的默认值比人们预期的更"友好":

  • any(默认):OR 匹配,且包含未打标签的记忆。适合 tags 只是提示(hints)而非围墙(walls)的场景。
  • all:AND 匹配,仍包含未打标签的记忆。
  • any_strict/all_strict:匹配语义同上,但排除未打标签的记忆
  • exact:记忆的标签集合必须与查询的完全相等。

源码印证:五种匹配模式的 SQL 语义

上述五种模式在 hindsight-api-slim/hindsight_api/engine/search/tags.py 中有精确定义(TagsMatch = Literal["any", "all", "any_strict", "all_strict", "exact"]),核心是build_tags_where_clause

  • any/any_strict使用 Postgres 数组重叠操作符&&
  • all/all_strict使用包含操作符@>
  • exact@> AND <@实现集合相等(顺序无关);
  • 含 untagged 的模式(any/all)会生成tags IS NULL OR tags = '{}' OR ...的析取子句;strict 模式则显式加tags IS NOT NULL AND tags != '{}'

一个值得注意的边界:exact模式下空查询标签集[]None)不是"不过滤",而是"只匹配未打标签的记忆"——这是观察(observation)作用域过滤的语义(见该文件头部注释),与其他模式把空标签当作"不过滤"的行为相反。

默认值any正是判断 tags 是否是错误工具的试金石:因为any会包含未打标签的记忆,tags 是软分区——方便组织,但不是安全控制。如果一条记忆绝对不能在错误上下文中浮现,不要依赖某个可能会忘记传的标签过滤器,把它放进自己的 bank。

所以真正的决策是两层:

  • 硬隔离(租户、客户、不可信来源、任何泄露即 bug 的场景):独立 bank。隔离由存储边界强制,而不是靠记得过滤。
  • 软分区(把同一信任域组织成项目、主题或用户,有时想过滤、有时想合并查看):一个 bank + tags

一个工作示例:服务单家公司的单租户内部助手可以放在一个 bank 里,用project:webproject:billingteam:sre这样的 tags,让"账单相关的问题"可以精确限域,也可以放开调取所有内容。但多租户 SaaS 中每个客户是不同公司,就必须给每个客户独立的 bank。Tags 负责组织,banks 负责隔离。

除了 tags,recall 还能按事实types和按时间过滤(created_after/created_before,以及用于问"截至某日期我们知道了什么"的query_timestamp),所以单个 bank 可以在不止一个维度上保持可查询。但 tags 是组织"什么住在一起"的主要旋钮。

你不需要手搓这些策略

上述策略不只是需要你手动实现的概念。多个集成直接把 bank 划分暴露为配置,读它们是怎么做的,是内化这个模型最快的方式。

静态 bank id的集成把整个"shared bank"模式浓缩成一行:在"一份记忆、三个界面"的设定里,OpenClaw 被固定到一个静态bankId,Vapi webhook 用同一个bank_id构造,语音通话和编码会话读写的是同一个存储(见 hindsight-integrations/openclaw/README.md 中的bankIdbankIdPrefix配置)。

从上下文推导 bank的集成则把决定权交给字段组合:

  • Claude CodedynamicBankId开关 +dynamicBankGranularity列表,从agentprojectsessionchanneluser中选择要组合进 id 的字段。设为["user"]得到 per-user banks;设为["agent", "project"]得到每 agent 每项目一个 bank。配置与解析见 hindsight-integrations/claude-code/settings.json 与 hindsight-integrations/claude-code/scripts/lib/config.py。
  • PaperclipbankGranularity默认["company", "agent"],记忆按"Agent 在公司中的角色"而非单次运行来限定;可选粒度含"user"(README 提到这对 GDPR 合规有用的按用户隔离)。实现见 hindsight-integrations/paperclip/src/bank.ts。
  • omo(oh-my-pi 示例):把选择显式拆成三种命名模式——globalper-projectper-project-tagged,最后一种正是"一个 bank + 项目 tags"。开启dynamicBankId后会产生omo::myproject这样的 bank,并且支持在查询时附带额外的 bank(见 hindsight-integrations/omo/README.md)。

per-project-tagged值得单独停下来看,因为它是两条轴合成一条建议:一个信任域一个 bank,域内项目用 tags。当你不确定时,这通常就是你想要的形态。

bank 数量影响性能吗?

远小于人们的直觉,所以它很少是值得优化的目标。所有记忆都住在一张表里,bank_id只是每一行上的一个列,而不是每个 bank 一张表或一个数据库。拆成很多 bank 不增加 provisioning,全塞进一个 bank 也不会形成什么巨石结构。

从源码看,这条设计在 DDL 层面同样成立:初始 schema 在memory_units表上创建的是普通的idx_memory_units_bank_id索引(见 hindsight-api-slim/hindsight_api/alembic/versions/5a366d414dce_initial_schema.py),而非按 bank 分表。

召回由bank_id过滤限定范围,而在默认的 Postgres 后端上,每个 bank 拥有自己的向量索引(per-bank partial vector index,见 hindsight-api-slim/hindsight_api/_vector_index.py 中should_create_per_bank_indexes与"达到多少行才建索引"的策略),所以一个 bank 的搜索在很大程度上不受另一个 bank 数据量的影响。

因此,bank 的数量通常不是决定召回快慢的杠杆。"一个大 bank 好扩展"和"很多小 bank 各自快"都是站不住脚的结构理由。基于正确性来决定——谁该召回什么——把性能当作一个独立问题。

反模式(Anti-patterns)

把"每会话一个 bank"当作实际策略。某些集成在没有其他配置时会回退到会话级 bank。作为默认值这没问题,作为策略就很糟:因为 bank 首次使用时才创建,每个新会话都会解析到一个全新的空 bank,Agent 从此再也记不住跨会话的东西。过去的记忆并没有被删除,只是不可达了——没有任何东西指回那个会话的 id。如果你的 Agent"每次会话之间全忘光",十有八九就是这个原因:去查 bank id 实际解析成了什么。

多租户应用里的一个全局 bank。经典的泄露。它在一个用户的 demo 里完美运行,在第二个用户出现时变成事故。租户边界就是 bank 边界,没有例外。

过度碎片化。相反方向的失败。每(用户 × 项目 × Agent × 会话)一个 bank 看起来很整洁,却饿死了召回:每个 bank 的内容太少,Agent 几乎没有足够上下文变得有用。召回的质量只取决于与它共享 bank 的东西。不确定时,宁可 bank 少一些,多依赖 tags。

不稳定的 bank id。因为新 id 就是新空 bank,用一些不该变却会变的东西来推导 id(每台机器都不同的绝对路径、会话令牌、会被编辑的显示名)会静默地把一份记忆切成许多份。从稳定身份推导 bank id:用户 id、仓库名、租户 id。这正是 hindsight-integrations/claude-code/scripts/lib/bank.py 里_resolve_project_name花力气解析 git common dir 的原因——把 worktree 统一到主仓库名,防止同一仓库因路径不同而裂成多个 bank。

一份决策清单

按顺序把每条边界过一遍:

  1. 这是租户或安全边界吗?(不同客户、不同不可信来源)→永远独立 bank。不要用 tags 来谈判这件事。
  2. A 的记忆需要被 B 触达吗?不需要 → 独立 bank。需要 → 继续往下。
  3. 同一个信任域,只是组织性问题?(同一租户内的项目、主题、团队)→一个 bank + tags
  4. 这些 Agent 应该共享上下文协作吗?一个 shared bank,全部指向它。
  5. 无论你选了什么,bank id 稳定吗?从持久身份推导,不要从偶然的东西推导。

你可以改变主意,但代价存在

Bank 策略不是单向门,但也不是免费可逆的。因为召回被限定在 bank 内,把一个 bank 拆成多个(或把多个合并成一个)意味着在 bank 之间搬移记忆、在你需要的地方重建历史,而不是翻转一个配置开关。这完全可行,而且在积累一年记忆之前做,远比之后做容易。现在就花十分钟过一遍上面的清单。

延伸阅读

  • Inside retain():每次写入 bank 时实际存储了什么。
  • One memory for every AI tool:让多个 Agent 指向一个共享 bank。
  • Give every agent you run in Omnigent a persistent memory:每 Agent 一个 bank,带会话级回退。
  • 想深入 API 层,可阅读 hindsight-api-slim/hindsight_api/engine/search/tags.py(tags 匹配的 SQL 实现)与 hindsight-api-slim/hindsight_api/_vector_index.py(per-bank 向量索引策略);想看集成层推导,可对比 hindsight-integrations/claude-code/scripts/lib/bank.py、hindsight-integrations/paperclip/src/bank.ts 与 hindsight-integrations/aider/hindsight_aider/bank.py 三种不同风格的 bank 解析。

【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 21:20:29

用MATLAB实现PQ分解法潮流计算:从IEEE 14节点到N-1分析

简介&#xff1a;IEEE标准14节点PQ分解法MATLAB程序&#xff08;.m&#xff09;是一份面向电力系统专业学生、研究人员与工程初学者的仿真算法源码&#xff0c;用于在14节点标准算例上进行快速潮流计算与稳态分析。程序基于PQ分解思想&#xff0c;将潮流方程组拆分为P-θ与Q-V两…

作者头像 李华
网站建设 2026/9/13 21:17:19

烧录地址的本质:芯片启动时CPU取指令的物理映射逻辑

1. 烧录地址不是“乱填的数字”&#xff0c;而是芯片启动逻辑的物理指纹你第一次在Keil里点“Download”时&#xff0c;烧录器弹出窗口让你选起始地址&#xff0c;手一抖填了0x08000000——结果板子不跑&#xff1b;改成0&#xff0c;程序能跑但串口没反应&#xff1b;再试0x60…

作者头像 李华
网站建设 2026/9/13 21:17:17

FSK解调性能仿真陷阱与工程级MATLAB实现

简介&#xff1a;本资源是一份面向通信工程专业本科生、研究生及MATLAB初学者的FSK调制解调实践代码包&#xff0c;聚焦数字通信系统中相干与非相干解调原理对比及误码率性能分析这一核心教学难点。压缩包仅含1个MATLAB脚本文件&#xff08;.m&#xff09;&#xff0c;体积精简…

作者头像 李华
网站建设 2026/9/13 21:15:25

STM32图书馆环境监测系统:原理图+仿真+可打板设计

1. 项目概述&#xff1a;一个真正能落地的图书馆环境监测系统长什么样&#xff1f;STM32项目开源&#xff1a;图书馆环境监测系统&#xff08;代码原理图仿真&#xff09;——这个标题里藏着三个硬核关键词&#xff1a;STM32、原理图、仿真。它不是那种“点亮LED”级别的入门De…

作者头像 李华
网站建设 2026/9/13 21:15:20

墨水屏HAT与NB-IoT/GPRS模组整合:从硬件原理到Demo Code实战解析

简介&#xff1a;面向嵌入式与物联网开发者的电子纸/NB-IoT/GPRS HAT 扩展板示例代码包&#xff0c;聚焦电子纸显示、NB-IoT/GPRS 通信与树莓派 HAT 标准集成&#xff0c;适合需要快速上手低功耗远程可视化终端的初学者和做原型验证的工程师。压缩包内共 151 个文件&#xff0c…

作者头像 李华