关于 helix-memory-system
面向构建 Helix Agent 记忆系统的架构技能,涵盖按租户隔离的图关系、向量相似度和 BM25 检索。它覆盖记忆从摄取、生成、去重和版本管理到遗忘及混合召回的生命周期,并明确区分 Helix 能力与应用侧工作。
主要功能
- 01设计按租户隔离的记忆模型
- 02结合图关系与向量、BM25 检索
- 03规划记忆去重、更新和版本管理
- 04支持分类、画像维护与遗忘
- 05区分数据库能力和应用侧任务
适用场景
- 01设计 Agent 长期记忆数据模型
- 02构建语义、关键词与图混合召回
- 03实现记忆纠正、过期和删除
- 04规划文档分块与来源追踪
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ helixdb/ skills --skill helix-memory-system安装后在 Claude Code 中输入/helix-memory-system确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
EXAMPLES.mdEXAMPLES.rust.mdREFERENCE.md详细介绍
Helix 记忆系统
在 Helix 上构建持久化的多租户 Agent 记忆平台,在一个数据库中整合图关系、向量相似度和 BM25 全文检索。本技能涵盖完整的记忆生命周期:原始上下文摄取、提取、记忆生成、去重、更新/版本管理、删除/遗忘、分类、档案维护和混合检索。
Helix 是存储和检索引擎。完整的记忆产品还需要应用工作进程来处理提取、分块、嵌入、关系分类、重排序、连接器同步和档案摘要。
适用场景
在以下任务中使用本技能:
- 设计 Agent 记忆、长期记忆、用户档案、文档/分块 RAG 或“记住用户告诉我的内容”功能的数据模型
- 编写用于创建、去重、强化、整合、版本管理、纠正、过期、遗忘、分类或检索记忆的查询
- 判断给定的记忆操作应使用哪种 Helix 功能(属性索引、图边、向量索引、BM25 文本索引)
- 构建融合语义 + 关键词 + 图关系 + 档案上下文的混合记忆召回
- 实现高级记忆组件,例如源文档、分块、连接器、提取出的事实、持续演变的档案、关系感知召回和遗忘
不要将本技能用于通用查询语法问题。有关构建器/方法的详细信息,请参阅 helix-query-typescript(默认 DSL)、helix-query-rust 或 helix-query-json-dynamic。本技能假设你已了解这些内容,重点介绍基于 Helix 构建记忆架构。
第一步
- 检查目标代码库中现有的标签、边、属性、索引和路由风格。如果已有内容,请沿用其准确的大小写形式。
- 默认使用 TypeScript v3 DSL(
@helix-db/[email protected]),以便应用将查询生成逻辑放在服务代码附近。仅当运行环境为 Rust,或团队明确使用 Rust 查询时,才使用EXAMPLES.rust.md。 - 在建模之前,先确定租户边界。规范的租户属性是
tenant_id,因为 Helix 当前要求租户分区文本索引使用此名称。为每个租户所有的节点和边添加tenant_id。 - 将记忆可见性边界与租户边界分开确定。在大多数应用中,
tenant_id用于划分索引,而userId、containerId、projectId或应用 ACL 决定哪些记忆可以被召回。默认示例使用userId作为第二级作用域。 - 在发明标签之前,先沿用以下规范模型。调整名称,而不是模型结构。
- 确认嵌入的生成方式。生产环境和可基准测试的记忆系统默认使用 OpenAI
text-embedding-3-small:1536个维度,以F32数组存储。应用在客户端计算嵌入,并传入数值数组(param.array(param.f32()))。Helix 不会在动态查询路径上嵌入文本;当前 DSL 中没有Embed()/SearchV。每个向量索引都应固定嵌入模型和维度。确定性哈希嵌入仅适用于本地 UI 演示或冒烟测试,不适用于质量基准测试。 - 确定 Helix 之外的应用工作进程:提取器、分块器、嵌入器、记忆写入器、关系分类器、衰减/过期清理器、档案摘要器、可选的查询改写器、可选的重排序器,以及连接器同步任务。
一览记忆模型
核心标签:Tenant、User、UserProfile、SourceDocument、Chunk、Memory、Category、Entity、Session,以及可选的 Connector 和 IngestionJob。
核心边:OWNS(Tenant/User→Memory)、HAS_PROFILE(User→UserProfile)、HAS_CHUNK(SourceDocument→Chunk)、EXTRACTED_FROM(Memory→Chunk 或 SourceDocument)、IN_CATEGORY(Memory→Category)、MENTIONS(Memory→Entity)、UPDATES(新 Memory→旧 Memory)、EXTENDS(Memory→Memory 增补)、DERIVES(推断出的 Memory→支持它的 Memory)、RELATES_TO(Memory→Memory 关联)、DERIVED_FROM(Memory→Session),以及可选的 PARENT_OF(Category→Category)。
快速且安全的字段:
- 在每个由租户拥有的节点和边上设置
tenant_id,并在用作锚点时为其建立相等性索引 - 在用户或容器专属的记忆、源文档和分块上设置
userId或等效的作用域键;只有明确设计为共享的记录才应覆盖整个租户 - 稳定 ID,例如
memoryId、documentId、chunkId、categoryKey、entityKey、sessionId和profileId - 使用
Memory.isLatest、validFrom、validTo、expiresAt和deletedAt筛选记录生命周期 - 当记忆涉及有日期的事件或事实时,可选用现实世界时间字段,例如
observedAt、eventStartAt、eventEndAt、temporalText和timezone - 在
Memory.embedding/Memory.content上建立按租户分区的向量/文本索引,也可选择在Chunk.embedding/Chunk.content上建立;所有索引均按tenant_id分区
完整规范、类型和索引初始化见 REFERENCE.md。
模态选择规则
根据要回答的问题选择机制,并有意组合使用:
| 需求 | 使用 | 原因 |
|---|---|---|
租户隔离(tenant_id)、精确身份、生命周期标记(deletedAt、expiresAt、validTo、isLatest)、排序/筛选(createdAt、salience) | 属性 + 相等性/范围索引 | 缩小锚点范围并安全筛选。租户作用域不可妥协。 |
| 分类、实体、来源、配置文件所有权、更新/增补/推导、关联簇、分类体系 | 图边 | 这些都是关系;应遍历并聚合。 |
| 去重、释义召回、相似记忆、相似分块 | 向量搜索 | 语义相似度;能够容忍措辞变化。 |
| 精确名称、ID、罕见词元、命令、文件路径、产品术语 | BM25 文本搜索 | 嵌入会模糊精确词元;BM25 能保留这些词元。 |
| 模型应始终了解的广泛用户上下文 | UserProfile 节点 + 汇总器工作进程 | 避免为稳定的身份/偏好/近期关注点执行多次搜索。 |
| 原始文档和引文 | SourceDocument + Chunk 节点 | 记忆事实不能替代基于来源的 RAG。 |
经验法则:绝不要将记忆系统简化为仅使用向量。 向量会遗漏精确名称,也不具备所有权、时效性、矛盾、来源、配置文件状态或分类的概念。
始终使用 tenantValue = tenant_id 限定向量/BM25 搜索范围。租户作用域是必要条件,但不总是充分条件:默认的用户记忆召回应通过 userId 或应用中等效的容器/ACL 进行筛选,除非记录明确在整个租户范围内共享。每条召回路径都必须筛除已忘记/过时的记录:deletedAt IsNull、isLatest = true、validTo IsNull,且 expiresAt 不存在或在未来。应在执行限定遍历范围的向量或 BM25 搜索之前构建这些候选项,以确保语义召回和关键词召回都严格遵守可见性成员资格。如果某条路径无法表达完整的可见性规则,应在应用代码中强制执行其余规则。
产品层
Helix 提供图谱和搜索原语。一个完整的智能记忆系统还需要:
| 层 | 职责 |
|---|---|
| 摄取 API | 接收文本、聊天记录、文件、URL、连接器事件和直接记忆写入。 |
| 提取器 | 将 PDF、文档、HTML、图像/OCR、音频/视频转录文本、代码和结构化数据转换为文本。 |
| 分块器 | 按语义章节、消息轮次、文档标题、代码 AST 边界或转录片段拆分原始上下文。 |
| 嵌入工作器 | 在写入 Helix 之前,为记忆和分块生成 text-embedding-3-small 1536 维 F32 嵌入,除非应用已明确统一使用其他模型。 |
| 记忆生成器 | 使用当前轮次及近期上下文、活跃实体、召回的记忆和当前日期,从对话/文档中提取以实体为中心的原子候选事实。 |
| 关系分类器 | 判定每个候选项属于 UPDATES、EXTENDS、DERIVES、重复项,还是独立项。 |
| 个人资料摘要器 | 根据最新记忆维护 UserProfile.staticSummary 和 dynamicSummary。 |
| 遗忘任务 | 执行过期、衰减、个人资料过时和连接器删除清理任务。 |
| 检索服务 | 改写查询,对记忆/分块执行向量搜索和 BM25,融合结果、重新排序、扩展图谱,并附带引用打包上下文。 |
| 评估 | 衡量召回质量、过时记忆抑制、租户隔离、延迟和令牌效率。 |
不要暗示 Helix 会自动执行提取、分块、嵌入、关系分类、个人资料生成、连接器同步、重新排序或 TTL。这些都属于应用职责,除非用户使用的是提供这些功能的托管服务。
记忆生命周期
每个步骤都链接到 EXAMPLES.md(TypeScript)和 EXAMPLES.rust.md(Rust)中的完整示例。
1. 摄取与生成
- 将原始上下文作为
SourceDocument、对话/会话、直接记忆写入或连接器更新接收。 - 如果输入尚未是原子记忆,则在应用侧进行提取和分块。
- 默认在应用侧使用 OpenAI
text-embedding-3-small为每个候选记忆/分块生成嵌入。存储/传递长度为 1536 的F32向量。 - 提取原子、完整且独立的候选记忆。优先采用以实体为中心的事实:使用“Alex 偏好上午开会”,而非“偏好上午开会”。
- 对候选项分类:
fact、preference、episode、procedure或应用特定的等效类型。 - 写入前进行去重。相似度阈值无法作为批处理条件,因此语义去重应采用先读后写,精确重复项则使用幂等 upsert。
- 写入包含
tenant_id、memoryId、content、embedding、kind、salience、isLatest: true和生命周期时间戳的Memory;关联所有权和溯源边。 - 立即分类并关联实体。
上下文提取规则
不要孤立地从最新用户消息中提取信息。提取工作器应接收:
- 当前用户消息
- 上一条助手消息,因为它通常会定义简短回答的含义
- 有界的近期对话窗口
- 回忆出的活跃记忆和活跃实体
- 用于解析相对时间短语的当前日期/时间
- 记忆范围(
tenant_id加上userId、containerId、projectId或 ACL 上下文)
在决定是否存储记忆之前,先解析代词、省略表达和简短的后续回答。如果助手提出了一个会产生记忆的后续问题,而用户简短作答,则将回答转换为完整、自包含的记忆。
提取器的输出应具有足够的结构,以便确定性地写入:shouldStore、自包含的 content、kind、confidence、salience、entities、source 指针、scope、可选的时间字段,以及关系判定(new、duplicate、EXTENDS、UPDATES 或 DERIVES)。不要让单个向量距离阈值决定是否更新;应通过向量检索和 BM25 检索候选项,并在应用代码中判定是完全重复、更新还是扩展。
示例:
Existing memory: User is planning a trip to Japan with Maya.
Assistant: When are you going?
User: next April
Extract: User is planning a trip to Japan with Maya next April.
Relationship: EXTENDS the existing Japan trip memory; MENTIONS Maya and Japan.
Assistant: What do you want to do there?
User: mostly food, temples, and trains
Extract: User wants their Japan trip with Maya to focus on food, temples, and trains.
Relationship: EXTENDS the existing Japan trip memory; categorise as travel/preferences.
User later: actually we're going in May instead
Extract: User is planning a trip to Japan with Maya in May.
Relationship: UPDATES the previous next-April timing memory and invalidates the older version.
2. 更新与版本控制
- **访问时强化:**递增
accessCount、lastAccessedAt,并在限定范围内提高salience。 - **更新/更正:**创建一条新记忆,关联
new -UPDATES-> old,将旧记忆的isLatest设为false并设置validTo;如果希望它不再出现在常规召回中,还可以设置deletedAt。 - **扩展:**当新事实丰富了旧事实但没有取代它时,关联
new -EXTENDS-> existing。 - **推导:**为推断出的事实添加指向支持它的记忆的
DERIVES边,并通过置信度元数据将其标记为推断内容。 - 如果
content发生变化,应重新生成嵌入,并在同一次写入中更新embedding。内容与向量绝不能不一致。 - 将生命周期有效性(
validFrom、validTo、deletedAt)与现实世界事件时间(eventStartAt、eventEndAt、temporalText)分开。因事实发生变化而更新记忆时,即使新旧事实都指向未来或过去的日期,也应使旧记录失效。 - **写入时等待持久化。**更新/版本控制采用先读后写,并且常常会在多个会话中并发运行,因此 HTTP 409 写入冲突的可能性更高。写入时等待持久化以减少冲突(TypeScript 中使用
.shouldAwaitDurability(true),Rust 中使用.should_await_durability(true),Go 中使用helix.AwaitDurability(true))。应根据稳定的transaction_conflict代码识别真实冲突(远程调用时对应 HTTP 409),绝不要通过解析诊断消息来判断。重新加载权威记忆状态后再重建变更;只有在操作具备幂等性或其他方面安全时,才进行有界退避重试。请参阅对应语言的 skill 和../../docs/error-handling.md。
3. 删除 / 遗忘
Helix 没有原生 TTL 或衰减机制。遗忘通过应用运行的显式写入查询实现。
- 软删除(首选):设置
deletedAt = Expr.datetime(),并在读取时过滤掉该记录。可撤销且便于审计。 - **版本失效:**当某条记忆被取代时,将
isLatest设为false,并将validTo设为Expr.datetime()。 - **过期清理:**隐藏或硬删除
expiresAt < now的记忆。 - **衰减清理:**隐藏较弱、过时且很少访问的情景记忆。
- **硬删除:**仅在策略要求物理删除时使用
drop()。drop()会删除节点及其关联边;对于对多重图敏感的路径,请使用dropEdgeById进行精确的边清理。
4. 分类与实体链接
- 将显示分类存储为
Category节点,并按tenant_id隔离;使用唯一的categoryKey,例如${tenant_id}:${normalisedName}。 - 将实体存储为
Entity节点,并按tenant_id隔离;使用唯一的entityKey,例如${tenant_id}:${normalisedName}。 - 如果需要按标签/实体遍历、聚合或回忆,优先使用边,而不是数组。
- 对于无需扩展图谱的显示/审计字段,使用嵌套对象元数据。将经常用于筛选的字段放在顶层;如果需要按标签/实体遍历、聚合或回忆,则优先使用边。
5. 资料维护
- 为每个用户/容器维护一个
UserProfile,包含profileId、tenant_id、userId、staticSummary、dynamicSummary和updatedAt。 - 静态资料:身份、稳定偏好、长期背景。
- 动态资料:当前项目、近期上下文、临时目标、未解决的任务。
- 在写入和删除记忆后异步更新资料;确保资料生成过程足够确定,以便进行测试。
检索
运行多个召回路径,并在应用端融合:
- 获取
UserProfile,用于始终提供的上下文。 - 对当前
Memory节点运行向量检索和 BM25,并按租户、用户/容器范围及新鲜度进行筛选。 - 可选:对
Chunk节点运行向量检索和 BM25,以进行基于源材料的 RAG 并提供引用;除非文档明确为共享文档,否则采用相同的所有者/范围策略。 - 在应用端使用 RRF 融合,然后根据显著性、近期程度、关系类型和可选的交叉编码器分数重新排序。
- 通过
MENTIONS、IN_CATEGORY、EXTENDS、UPDATES和RELATES_TO扩展排名靠前的记忆,并限制深度和租户范围。 - 整理上下文时不包含嵌入;如有来源/引用元数据,则一并包含。
反模式
请勿:
- 在新的动态/TS/Rust DSL 工作中使用已弃用的
.hx方言(Embed()、SearchV、SearchBM25、AddV) - 将
userId用作文本索引的租户属性;对于按租户分区的文本/向量索引,请使用tenant_id - 认为仅凭
tenant_id就能为组织/团队租户建立安全的召回边界;请通过userId、containerId、项目 ACL 或明确的共享记忆标记进行筛选 - 只为
Memory附加tenant_id;每个归租户所有的节点和边都需要它 - 在未同时检查
tenant_id的情况下,通过memoryId修改、删除、分类或强化记忆 - 因为召回只检查了
deletedAt,就返回已被取代、遗忘或过期的记忆 - 将生命周期时间戳(
validTo、deletedAt)与现实世界的事件日期混用;对于涉及旅行、截止日期、预约或历史事实的记忆,请使用单独的时间字段 - 构建仅支持向量的存储,并将其称为记忆
- 在生产环境的召回或基准测试声明中使用玩具哈希嵌入;除非应用有更好的标准模型,否则默认使用
text-embedding-3-small - 仅凭向量阈值决定去重/更新/扩展;应结合精确检查、BM25 候选项、向量候选项,以及应用/LLM 判定
- 只从最新的用户消息中提取记忆,因而遗漏“明年四月”或“主要是美食、寺庙和火车”等上下文相关的后续信息
- 在解析上下文前,因为简短的后续回答缺乏独立完整性而将其丢弃
- 写入没有所有者或范围字段的用户专属分块/文档,之后又在整个租户范围内召回
- 期望 Helix 自动提取文件、分块文档、生成嵌入、分类更新、构建资料、重新排序、同步连接器或运行 TTL 作业
- 在
out/in/both步骤之后读取$distance或$score;搜索后应立即投影排名元数据 - 试图将相似度阈值去重表达为
BatchCondition;它只能检查变量是否为空/其大小 - 更新
content后不重新生成嵌入 - 在 API 响应中返回
embedding数组,除非明确要求 - 在多租户记忆应用中按显示名称将
Category或Entity设为全局节点
验证清单
完成前:
- 确认
readBatch()与writeBatch()使用正确 - 每个租户所有的节点和边都有
tenant_id - 向量索引/文本索引使用
tenant_property = "tenant_id",且搜索传入tenantValue = tenant_id - 向量检索和 BM25 检索在遍历范围限定的搜索之前,先构建用户/ACL 和生命周期候选项
- 每次记忆读取都筛选
tenant_id、用户/容器可见性、deletedAt IsNull、当前/最新状态和有效期 - 每条写入路径都接受并按
tenant_id筛选 - 用于 upsert 的 ID 要么全局唯一,要么包含租户标识(
categoryKey、entityKey等) - 特定于用户/容器的文档和分块都携带检索使用的相同所有者/范围字段,或通过应用策略明确标记为共享
- 生命周期有效性字段不与事件时间字段混用;带日期的事实使用
observedAt、eventStartAt、eventEndAt、temporalText或应用中的等效字段 - 嵌入模型为
openai:text-embedding-3-small,且每个向量都是 1536 维F32,除非应用明确统一采用其他固定模型/维度 - 内容编辑与重新生成嵌入在同一次写入中完成
- 生成过程进行语义去重,且完全相同的重复内容具有幂等性
- 在决定存储什么之前,抽取过程会查看上一轮助手对话、近期对话窗口、检索到的有效记忆/实体以及当前日期
- 抽取过程会输出结构化的关系/范围/来源/时间决策,以便进行确定性测试
- 如果功能承诺提供引用或对原始上下文执行 RAG,则必须存在源文档/分块
- 如果功能承诺始终提供个性化体验,则必须存在用户资料更新任务
- 评估涵盖租户隔离、用户/容器隔离、过时记忆抑制、上下文追问抽取、精确令牌检索、时间更正、删除、资料重建、延迟和令牌预算
- 时间戳采用统一约定;此技能使用通过
Expr.datetime()和param.dateTime()设置的类型化 DateTime - 除非明确要求,否则投影输出不包含
embedding - 最多一个值的响应字段允许为
null;集合/折叠/变更的空值保留为[],标量0或false保持为标量 - 标签/边/属性与现有代码库的大小写格式一致
参考文件
REFERENCE.md— 完整的数据模型规范、租户规则、索引、模态速查表、嵌入指南、融合/重排公式,以及 TypeScript ↔ Rust API 映射。EXAMPLES.md— 生命周期场景的@helix-db/[email protected]TypeScript 代码片段。默认使用。EXAMPLES.rust.md— 使用 Rust DSL 编写的相同场景。- 相邻技能:
helix-query-typescript、helix-query-rust、helix-query-json-dynamic、helix-query-optimize。