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)。
recall、retain、reflect全部在单个 bank 内运行,系统不存在跨 bank 查询。所以"这两个东西该不该共享一个 bank"实际等价于"A 存入的记忆,B 是否应该能召回?" - 硬隔离边界(租户、客户、不可信上下文)用独立 bank;软分区(同一个信任域内有时想过滤、有时想交叉引用的分组)用同一 bank 里的 tags。
- bank 是首次使用时惰性创建的,一个新的
bank_id字符串就是一个全新的空记忆。这正是大多数碎片化的根源:每个会话一个 bank,意味着每个会话都从空白开始。 - 多个集成把这一策略直接暴露为配置:静态
bankId,或由user、project、agent等上下文字段组合而成的dynamicBankId。 - 策略可以事后调整,但召回历史被限定在 bank 内,所以前期选对能省下一次数据回填。
一个核心概念:bank 是召回边界
在 Hindsight 中,一个 bank 是一套完整、隔离的存储:从会话中保留的记忆(memories)、为检索而索引的文档、从中抽取的实体、连接这些实体的知识图谱,以及 bank 自身的 disposition 与 directives。bank 之间彼此隔离,没有内置的跨 bank 查询。每一次recall、retain、reflect调用都只点名一个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-agent | Agent 角色 | 角色分明、需要各自经验的 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为例,其解析顺序为:
directoryBankMap:显式的目录 → bank 映射,优先级最高;- 静态模式(
dynamicBankId=false):使用bankId配置(默认"claude-code"); - 动态模式(
dynamicBankId=true):按dynamicBankGranularity字段列表组合 id,默认["agent", "project"]。
动态模式下各字段的取值来源(见同文件field_map):
| 字段 | 取值来源 |
|---|---|
agent | 配置的agentName(默认"claude-code") |
project | 由 cwd 推导;开启resolveWorktrees(默认)时通过git rev-parse --git-common-dir解析到主仓库名,使同一仓库的所有 worktree 共享同一 bank |
session | hook 输入的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。注意session和user的缺省值分别是"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:web、project:billing、team: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 中的bankId、bankIdPrefix配置)。
从上下文推导 bank的集成则把决定权交给字段组合:
- Claude Code:
dynamicBankId开关 +dynamicBankGranularity列表,从agent、project、session、channel、user中选择要组合进 id 的字段。设为["user"]得到 per-user banks;设为["agent", "project"]得到每 agent 每项目一个 bank。配置与解析见 hindsight-integrations/claude-code/settings.json 与 hindsight-integrations/claude-code/scripts/lib/config.py。 - Paperclip:
bankGranularity默认["company", "agent"],记忆按"Agent 在公司中的角色"而非单次运行来限定;可选粒度含"user"(README 提到这对 GDPR 合规有用的按用户隔离)。实现见 hindsight-integrations/paperclip/src/bank.ts。 - omo(oh-my-pi 示例):把选择显式拆成三种命名模式——
global、per-project、per-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。
一份决策清单
按顺序把每条边界过一遍:
- 这是租户或安全边界吗?(不同客户、不同不可信来源)→永远独立 bank。不要用 tags 来谈判这件事。
- A 的记忆需要被 B 触达吗?不需要 → 独立 bank。需要 → 继续往下。
- 同一个信任域,只是组织性问题?(同一租户内的项目、主题、团队)→一个 bank + tags。
- 这些 Agent 应该共享上下文协作吗?→一个 shared bank,全部指向它。
- 无论你选了什么,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),仅供参考