策略完全指南:如何为 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;DRbank 是召回边界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 应该能召回它吗答案是yesA 和 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为例其解析顺序为directoryBankMap显式的目录 → bank 映射优先级最高静态模式dynamicBankIdfalse使用bankId配置默认claude-code动态模式dynamicBankIdtrue按dynamicBankGranularity字段列表组合 id默认[agent, project]。动态模式下各字段的取值来源见同文件field_map字段取值来源agent配置的agentName默认claude-codeproject由 cwd 推导开启resolveWorktrees默认时通过git rev-parse --git-common-dir解析到主仓库名使同一仓库的所有 worktree 共享同一 banksessionhook 输入的session_id缺失则为unknownchannel环境变量HINDSIGHT_CHANNEL_ID用于 Telegram/Discord 等渠道 Agent缺失则defaultuser环境变量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_idacme, contentWe deploy on Fridays only in emergencies, tags[project:web]) # 只在该切片内召回 client.recall(bank_idacme, querydeploy policy?, tags[project:web], tags_matchall)tags_match模式是最值得知道的细节因为它的默认值比人们预期的更友好any默认OR 匹配且包含未打标签的记忆。适合 tags 只是提示hints而非围墙walls的场景。allAND 匹配仍包含未打标签的记忆。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_clauseany/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 负责隔离。除了 tagsrecall 还能按事实types和按时间过滤created_after/created_before以及用于问截至某日期我们知道了什么的query_timestamp所以单个 bank 可以在不止一个维度上保持可查询。但 tags 是组织什么住在一起的主要旋钮。你不需要手搓这些策略上述策略不只是需要你手动实现的概念。多个集成直接把 bank 划分暴露为配置读它们是怎么做的是内化这个模型最快的方式。静态 bank id的集成把整个shared bank模式浓缩成一行在一份记忆、三个界面的设定里OpenClaw 被固定到一个静态bankIdVapi webhook 用同一个bank_id构造语音通话和编码会话读写的是同一个存储见 hindsight-integrations/openclaw/README.md 中的bankId、bankIdPrefix配置。从上下文推导 bank的集成则把决定权交给字段组合Claude CodedynamicBankId开关 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。PaperclipbankGranularity默认[company, agent]记忆按Agent 在公司中的角色而非单次运行来限定可选粒度含userREADME 提到这对 GDPR 合规有用的按用户隔离。实现见 hindsight-integrations/paperclip/src/bank.ts。omooh-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 首次使用时才创建每个新会话都会解析到一个全新的空 bankAgent 从此再也记不住跨会话的东西。过去的记忆并没有被删除只是不可达了——没有任何东西指回那个会话的 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.pytags 匹配的 SQL 实现与 hindsight-api-slim/hindsight_api/_vector_index.pyper-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),仅供参考