From d4773ce35c52ecac04e5bbca0a41865b8b402e00 Mon Sep 17 00:00:00 2001 From: yangjianxin4 Date: Wed, 22 Jul 2026 17:58:55 +0800 Subject: [PATCH] =?UTF-8?q?docs(memory):=20=E6=A0=91=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E8=AE=B0=E5=BF=86=E6=9E=84=E5=BB=BA=EF=BC=8CTime=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E7=BB=B4=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/design/architecture.md | 210 +++++--- docs/design/vision.md | 68 +-- docs/features/common/F01-memory-layer.md | 21 +- docs/features/common/F07-memory-tree.md | 621 +++++++++++++++++++++++ docs/specs/S01-ingest-access.md | 68 ++- docs/specs/S02-memory-api.md | 185 +++++-- docs/specs/S03-control.md | 137 ++++- docs/specs/S04-retrieval.md | 248 +++++++-- docs/specs/S05-construction.md | 230 +++++++-- docs/specs/S06-storage.md | 59 ++- docs/specs/S07-common.md | 117 ++++- 11 files changed, 1678 insertions(+), 286 deletions(-) create mode 100644 docs/features/common/F07-memory-tree.md diff --git a/docs/design/architecture.md b/docs/design/architecture.md index 532d3b8e..d1e8e565 100644 --- a/docs/design/architecture.md +++ b/docs/design/architecture.md @@ -1,22 +1,22 @@ # agent-memory架构设计(Architecture) > 文档性质:总体架构设计(概念、分层、组件与依赖方向) -> 版本:v0.2 | 日期:2026-08-06 -> 关联文档:[愿景 VISION](./VISION.md) | [统一 Storage](../features/storage/F05-unified-storage-design.md) | [Storage 检索 Pipeline](../features/retrieval/F05-storage-retrieval-pipelines.md) | [Benchmark 调研](./memory_benchmarks.md) -> 说明:本文描述系统级架构方向;精确接口契约以 `docs/specs/` 为准,特性取舍与首版实现边界以 `docs/features/` 为准。 +> 版本:v0.2 | 日期:2026-08-11 +> 关联文档:[愿景](./vision.md) | [统一 Storage](../features/storage/F05-unified-storage-design.md) | [Storage 检索 Pipeline](../features/retrieval/F05-storage-retrieval-pipelines.md) | [多模态记忆](../features/construction/F05-construction-spec-multimodal-design.md) | [MemoryUnit 树结构(目标)](../features/common/F07-memory-tree.md) | [Memory API](../specs/S02-memory-api.md) | [记忆控制](../specs/S03-control.md) | [检索](../specs/S04-retrieval.md) | [构建](../specs/S05-construction.md) | [存储](../specs/S06-storage.md) | [公共数据](../specs/S07-common.md) | [配置](../specs/S08-config.md) | [Benchmark 调研](./memory_benchmarks.md) +> 说明:本文描述系统级架构方向;精确接口契约以 `docs/specs/` 为准,特性取舍与首版实现边界以 `docs/features/` 为准。Hierarchy TIME 是目标设计,尚未实现。 --- ## 1. 架构目标与约束 -承接 [VISION](./VISION.md),架构需同时满足: +承接 [愿景](./vision.md),架构需同时满足: | 目标 | 架构含义 | | ----------------------------------- | --------------------------------- | | 框架无关、可独立提供、可嵌入可服务 | 内核与接入层解耦;可作为独立服务/产品交付,也可作为库嵌入;接入层是内核的薄封装 | | 多形态调用(CLI/Skill/SDK·Python/API/MCP) | 记忆接口层为唯一入口,各形态映射到它 | -| 不止向量:分层 + 多索引 + 结构化关联 | 记忆按抽象粒度分层;索引阶段**支持**文档/关键词/向量/图多形式索引,**按配置启用**、混合检索 | +| 不止向量:四轴分层 + 多索引 + 结构化关联 | 同 unit 披露、多模态构建、认知抽象、跨 unit 树结构彼此正交;多形式索引按配置启用、混合检索 | | 多模态数据输入 | 多模态来源在接入层规约:保留原模态资产(或引用)+ 派生可治理文本/结构投影 | | 可配置的记忆真源形态(文档 / 结构化) | 真源唯一,承载形态可插拔;其上各粒度记忆与索引可重建 | | 记忆自演进 | 自演进闭环持续构建/维护分层记忆,在线/离线双通道 | @@ -25,7 +25,7 @@ | 透明可治理 | 可检视/编辑/审计/回溯/遗忘为一等公民 | -**核心架构信条**:**唯一真源 = 原始数据(用户及 Agent 记忆数据);从中提取相关信息,经抽象与精炼、关联分析,挖掘出不同抽象粒度的记忆(此为记忆结构本身);并在索引阶段按配置构建文档/关键词/向量/图等多形式索引(结构化关联/图为可选索引形式,非固有结构)——全部可从原始数据重建。** +**核心架构信条**:**KV/文档中的权威输入(原始数据与明确写入的权威叶)是重建边界;认知演进产物保留 `provenance`,父摘要与父子树可从权威叶重建,内容/关系索引可从 `MemoryUnit` 真源重建。同 unit 披露、多模态构建、认知抽象和跨 unit 树结构彼此正交,图关系仍是可选索引而非父子结构。** --- @@ -36,15 +36,15 @@ │ A. 调用与数据接入层 CLI·Skill·SDK(Python)·HTTP/gRPC·MCP │ │ Access & Ingest + 多模态信息源(对话/文档/代码/工具轨迹/图像/音视频)接入│ │ B. (记忆接口) Memory API write(同步/异步) · recall · get · update · │ -│ delete · evolve · admin(形态无关) │ +│ delete · evolve · admin · expand(目标) │ ├──────────────────────────────────────────────────────────────────────────┤ -│ C. 记忆管理层 Manage 生命周期 · 治理(检视/编辑/审计/遗忘) · 权限 · 配置/策略 │ +│ C. 记忆管理层 Manage 生命周期 · 层级治理(目标) · 审计 · 权限 · 配置/策略 │ ├──────────────────────────────────────────────────────────────────────────┤ -│ D. 记忆检索层 Retrieve 查询解析 · Storage 检索内核适配 · 重排 · 渐进披露│ +│ D. 记忆检索层 Retrieve Storage 检索内核适配 · 重排 · 单 unit 渐进披露 · 父优先/展开(目标)│ ├──────────────────────────────────────────────────────────────────────────┤ -│ E. 记忆构建层 Build 分层记忆结构(皆可从原始数据重建): │ -│ (Layered Memory) 从原始数据提取 → 抽象精炼/关联分析 → 多抽象粒度 │ -│ 记忆 + 多形式索引(文档·关键词·向量·图); │ +│ E. 记忆构建层 Build 四轴分层:披露 · 多模态 · 认知抽象 · 树结构 │ +│ (Layered Memory) 权威叶 → 父摘要;父与子各自支持 L0/L1/L2 │ +│ + 多形式索引(文档·关键词·向量·图); │ │ 由记忆自演进(§9.3)持续构建与维护 │ ├──────────────────────────────────────────────────────────────────────────┤ │ F. 记忆存储层 Storage 统一领域操作·能力发现·安全边界·检索适配入口 │ @@ -55,7 +55,7 @@ 横切:端/云/端云协同部署(§11) · 可观测(检索轨迹) · 多租户隔离 · 安全合规 ``` -> 从下往上看:**数据层(G)持久化原始数据 → 记忆构建层(E)从原始数据提取、经抽象精炼/关联分析挖掘多粒度记忆并构建多形式索引(皆可重建) → 记忆检索层(D)通过统一 Storage 选择检索内核并完成重排与披露 → 记忆管理层(C)做生命周期/治理/权限/配置 → 记忆接口层(B)→ 调用与数据接入层(A)**。记忆存储层(F)以统一 `Storage` 契约屏蔽物理装配,同时按能力暴露标准底层端口;端/云/端云协同为部署维度(§11)。 +> 从下往上看:**数据层(G)持久化原始数据与权威叶 → 记忆构建层(E)从原始数据提取、经抽象精炼/关联分析挖掘多粒度记忆,维护同 unit 内容层、目标父子树及多形式索引(皆可重建) → 记忆检索层(D)通过统一 Storage 选择检索内核、重排与单 unit 披露;目标状态可先召回父节点再按需展开子证据 → 记忆管理层(C)做生命周期、层级治理、权限与配置 → 记忆接口层(B)→ 调用与数据接入层(A)**。记忆存储层(F)以统一 `Storage` 契约屏蔽物理装配,同时按能力暴露标准底层端口;端/云/端云协同为部署维度(§11)。 > > **记忆管理层(C)总览**:C 层是管理面,负责生命周期、权限、治理、调度与运行时策略的统一编排;职责总览见 §7。 @@ -72,20 +72,25 @@ MemoryUnit ├── id Scope 内唯一 id ├── scope 归属:{ org, space, user, agent, session }(多维,用于隔离/共享) ├── tier 认知角色(记忆分类维度之一):working/core/episodic/semantic/procedural/archival -├── content 内容(可治理文本/结构化字段;多模态来源在接入时规约为此投影) -├── assets 原模态资产引用(图像/音频/视频等,或对象存储路径;文本投影的来源) -├── source 来源:对话/工具轨迹/文档/图像/音视频/外部导入 + 原始引用 +├── layers ContentLayers:l0 概要 / l1 片段;L2 是 content 合并视图 +├── segments[] Segment:每段含 content + assets[] + source +├── content segments[].content 的只读换行合并视图 +├── assets segments[].assets 的只读扁平合并视图 +├── source 首段 source 的只读主模态视图 +├── source_ref RawPayload / 会话等来源引用 ├── temporal 时间:t_event(发生) / t_ingest(摄入) / t_valid / t_invalid ├── provenance 演进血缘(多→一合成):由哪些 unit 抽取/升华/合并而来(来源可仍有效) ├── supersedes 版本链(一→一更替):本版取代的上一版 id(update SUPERSEDE 模式产生;空=首版) +├── hierarchy HierarchyRef:跨 unit 树结构(设计目标,尚未实现) ├── tags/metadata 标签、命名空间、置信度、重要度等 └── lifecycle 状态:active / superseded(被取代) / archived / forgotten ``` - `temporal` 借鉴 Zep **双时间模型**,支持有效期与时间点回溯。 -- **`provenance` 与 `supersedes` 分离**:`provenance` 是「多→一」演进血缘(抽取/升华/合并;来源 unit 可仍有效),支撑「派生可重建 + 可审计回溯」与 `trace` 回溯;`supersedes` 是「一→一」版本链,支撑按 `as_of` 的版本回溯(`get` 沿链返回当时有效版本)。两者不再共用一个字段。 +- **当前内容真相是 `segments[]`**:`content/assets/source` 均为折叠后的只读兼容视图,不是与 `segments[]` 并列写入的第二份数据。`ContentLayers.l0/l1` 已实现,L2 不重复存储,直接取 `MemoryUnit.content`。 +- **`provenance`、`supersedes`、`hierarchy` 三分**:`provenance` 回答“由哪些 unit 抽取或合成”,供 `trace` 回溯;`supersedes` 回答“本版本取代谁”,供 `as_of` 版本回溯;目标 `HierarchyRef` 回答“结构上包含谁、隶属于谁”,供父子树构建与 `expand`。三者生命周期、遍历方向和治理动作互不替代。 - `lifecycle` 用「标记失效」而非物理删除(非破坏式更新)。`update` 默认 **SUPERSEDE**(生成新 id 版本、旧版标记 superseded、新版 `supersedes` 记链),亦可 **OVERWRITE**(同 id 原地覆写,旧内容仅留审计——非破坏式原则的有意例外)。 -- **多模态**:`assets` 保留原模态资产(或引用)作为真源的一部分;`content` 是其**可治理文本/结构投影**(转录/OCR/caption/描述),由接入层生成。下游索引与检索统一作用在 `content` 投影上(详见 §5.1)。 +- **多模态**:每个 `Segment` 把可治理文本/结构投影、原模态资产引用与来源模态放在一起;下游索引与检索统一作用于各段合并后的 `content` 视图(详见 §5.1)。 ### 3.2 作用域与多租户(Scope) @@ -110,37 +115,38 @@ scope 的前缀”。这样同一套 `Scope` 字段既能表达 `user -> agent` --- -## 4. 分层记忆结构(Layered Memory by Abstraction Granularity) +## 4. 分层记忆结构:四条正交轴 -本节只定义记忆结构的 **What**:系统区分**短时记忆**(工作/会话态,易失)与**长时记忆**(可持久、可治理、可重建的分层结构)。长时记忆按抽象粒度组织,索引是建在记忆之上的可配置检索结构,不是记忆本体。 +本节定义记忆结构的 **What**。系统仍区分短时工作态与可跨会话复用的长时记忆,但“分层”必须拆成四个不能互相推导的轴: + +| 轴 | 表达载体 | 回答的问题 | 当前状态 | +| --- | --- | --- | --- | +| **同 unit 渐进披露** | `ContentLayers` + `DisclosureLevel` | 同一条记忆用 L0 概要、L1 片段还是 L2 全文进入上下文? | F01 已实现基线 | +| **多模态构建** | `metadata.memory_level` + `provenance` | 同一媒体源产出的 CLM/ELM 等不同构建粒度 unit? | F05 已有设计/实现基线 | +| **认知抽象** | `MemoryTier` + evolve | 这条记忆扮演工作、情景、语义、程序性、核心还是归档角色? | 已有基线 | +| **跨 unit 树结构** | `HierarchyRef` | 哪个父摘要包含哪些可按需展开的子证据? | F07 设计目标,尚未实现 | ``` - ┌────────────────────────────────────────────────────────────────────────────┐ - │ 多形式索引 Indexes:文档索引 · 关键词索引 · 向量索引 · 图索引(主要建于长时记忆)│ - │ (检索时由记忆检索层 §8 做融合召回 + 重排) │ - └────────────────────────────────────────────────────────────────────────────┘ - ▲ 在各抽象粒度记忆上构建索引 - ┌────────────────────┐ 升华 ┌──────────────────────────────────────────────────┐ - │ 短时记忆 Short-term │ /沉淀 │ 长时记忆 Long-term(分层记忆结构,按抽象粒度) │ - │ 工作记忆/会话上下文 │ ─────▶ │ 高抽象(精炼/概括) 画像 · 长期偏好 · 习得技能/模式 │ - │ 近期缓冲/临时状态 │ │ 中抽象(关联/组织) 事件 · 实体关系 · 主题聚类 │ - │ 易失、快速读写 │ │ 低抽象(贴近原始) 抽取的事实/片段 │ - └────────────────────┘ └──────────────────────────────────────────────────┘ - ▲ 缓冲/沉淀 ▲ 构建算子生成不同抽象粒度(见 §9.1) - ┌────────────────────────────────────────────────────────────────────────────┐ - │ 数据层 原始数据(唯一真源,承载形态可配:文档 / 结构化) │ - └────────────────────────────────────────────────────────────────────────────┘ - ▲ 长时记忆与索引皆可从原始数据重建(短时记忆为易失工作态,不必持久重建) +ContentLayers / DisclosureLevel CLM/ELM metadata+provenance MemoryTier / evolve HierarchyRef +同一 unit 的压缩度 单媒体源构建粒度 认知角色与演进 跨 unit 的结构包含 +L0 ── L1 ── L2 clm / elm working…archival 父摘要 + └─ 子证据 +四轴可同时出现在同一节点上;tier、披露级别、媒体构建粒度和树深之间不存在固定映射。 ``` -- **短时记忆 vs 长时记忆**:短时记忆承载当前工作/会话上下文(近期缓冲、临时状态,易失、快速读写);长时记忆承载可跨会话复用的事实、事件、主题关系、画像、偏好、技能与模式。 -- **抽象粒度**:低抽象贴近原始事实/片段,中抽象组织事件、实体关系与主题,高抽象沉淀画像、长期偏好、可复用技能/模式。具体构建算子见 §9.1。 -- **多形式索引(按配置启用)**:索引建立在记忆之上,支持文档 / 关键词 / 向量 / 图等形态,具体启用哪些由配置决定(§13),检索融合见 §8。 -- **真源唯一、皆可重建**:原始数据是唯一权威来源;各粒度记忆与索引均可从它重算(推广 memSearch「删索引不丢数据」理念到整个记忆构建层)。本架构主信条。 -- **认知角色作为分类维度**:「认知角色(working/core/episodic/semantic/procedural/archival)」作为记忆的**一个分类维度**存在(决定常驻上下文 or 按需检索),不单列为独立轴。 -- **由自演进持续维护**:这些结构不是一次性产物,而是由记忆自演进(§9.3)按触发时机持续维护。 +- **短时记忆 vs 长时记忆**:短时记忆承载当前工作/会话上下文;长时记忆承载跨会话复用的事实、事件、画像、偏好、技能与模式。短时沉淀为长时属于认知抽象与演进,不是披露升级或树遍历。 +- **披露轴边界**:L0/L1/L2 永远只描述同一 `MemoryUnit`。父节点与子节点各自都可以有 L0/L1/L2。 +- **多模态构建轴边界**:视频的 CLM(片段级)与 ELM(事件级)是从单一媒体源构建出的不同 `MemoryUnit`,用 `provenance` 和 `metadata` 表达来源与粒度;它们不是同 unit 的 `ContentLayers`,也不使用目标 `HierarchyRef`。CLM/ELM 可先完成多模态检索,再作为叶节点进入目标 TIME 树。 +- **树结构轴边界**:`HierarchyKind.TIME|TOPIC|DIRECTORY|CLUSTER|CUSTOM` 表示结构维度;role 可为 `snapshot/time_span/scene/event/profile/root/node`。role 是树位,不等于 `MemoryTier`。 +- **叶权威、父可重建**:普通写入产生的权威叶不会因父节点重建而被删除;父摘要、父子引用及其索引是可重建派生物。 +- **单 kind 遍历**:首期同一 kind 是严格树,展开必须指定单一 kind,不隐式跨 kind,也不把图关系当作包含关系。 +- **结构状态与生命周期分离**:`HierarchyStatus` 必填且默认 `ACTIVE`,只允许 `ACTIVE/DISMISSED/PENDING_CONFIRM`;归档、遗忘和版本失效归 `LifecycleState`。 +- **结构区间规则**:TIME 节点必须声明有效 span;非 TIME 节点可选,声明后同样满足成对、有效和父覆盖直接子的约束。 +- **时间概念分离**:`MemoryUnit.temporal` 保存事件/摄入/有效期双时间;`RecallChannel.TEMPORAL` 是叠加在召回上的时间过滤通道;`HierarchyKind.TIME` 是 TIME 树。三者互不替代。 +- **多形式索引(按配置启用)**:文档、关键词、向量与图索引建立在记忆之上,具体启用项由 §13 决定;图索引表达实体、因果、引用等非包含关系。 +- **由不同机制维护**:同 unit 内容层由 `LayerAnnotator` 生成,多模态 CLM/ELM 由视频构建管线维护,认知抽象由 evolve 维护,树结构由目标 `evolve(HIERARCHY)` 或专用构建管线维护。 -> 注:「演进产物与真源的关系(append-only?演进产物是否回写真源?)」为**设计阶段开放问题**,见 §17。 +详细决策见 [F07 — MemoryUnit 树结构](../features/common/F07-memory-tree.md);公开契约以 [S07 公共数据](../specs/S07-common.md) 及 §6 所列相关规约为准。 --- @@ -205,10 +211,12 @@ scope 的前缀”。这样同一套 `Scope` 字段既能表达 `user -> agent` | `write_async(…)`(协程,签名/返回值同 `write`) | **异步**写入记忆:直通引擎的异步 write,供事件循环/高并发接入形态(HTTP/MCP)非阻塞调用 | | `recall(query, context, *, identity, filters=…, as_of=…, top_k=…, disclosure=…, with_trajectory=…) -> RetrievalResult` | 混合检索召回;`context.scope` 为目标范围,`filters` 为结构化 `FilterExpr`(叶子谓词 + AND/OR/NOT 树),`as_of` **时间点回溯(valid-time)**;结果包含命中项、可选轨迹和始终可见的通道错误 | | `list(scope, *, identity, offset=0, limit=100, memory_types=None, extensions=None, filters=None) -> MemoryListResult` | 列出 scope 下已建索引记忆;支持类型/结构化过滤、自定义透传与分页,返回当前页 `items` 和分页前匹配总数 `count`;只返回 `/memory/` 真源记录 | +| `recall(…, hierarchy_kind=…, hierarchy_role=…, expand_depth=0, …)`(设计目标) | 可选按 kind/role/span 等结构条件筛选;指定父侧 role 即形成父优先召回,默认不展开后代 | +| `expand(unit_id, scope, *, identity, kind, depth=…, budget_tokens=…)`(设计目标) | 沿指定单一 kind 从父节点展开有序子树切片;深度和树级逻辑节点准入预算显式受控,返回节点与截断轨迹 | | `get(unit_id, scope, *, identity, as_of=None) -> MemoryUnit` | 按 id 读取记忆单元;`as_of` 非空时沿 `supersedes` 版本链返回当时有效版本;不存在抛 `NotFoundError` | | `update(unit_id, scope, patch: MemoryPatch, *, identity) -> MemoryUnit` | 修正记忆(仅非 None 字段生效):`patch.mode` = **SUPERSEDE**(默认、非破坏式:生成新 id 版本、旧版标记 superseded、新版 `supersedes` 记链)/ **OVERWRITE**(同 id 原地覆写、旧内容仅留审计);返回结果记忆单元 | | `delete(selector: DeleteSelector, *, identity) -> list[str]` | 删除:按选择器(id / scope / 标签 / 时间,条件取「与」)批量执行;`mode` = forget 遗忘 / archive 归档 / downweight 降权(均非破坏式)/ **purge 完全删除**(物理删除真源与全部派生索引,合规删除、不可恢复、仅留审计记录);返回命中的 id | -| `evolve(scope, mode, channel=BACKGROUND, *, identity) -> job_id` | 触发演进(mode:extract / associate / consolidate / forget),经控制层 Scheduler 双通道调度,返回任务 id;索引维护不在此(随数据面操作自动跟进) | +| `evolve(scope, mode, channel=BACKGROUND, *, identity) -> job_id` | 当前触发 extract / associate / consolidate / forget;目标增加 `HIERARCHY` 作为显式/后台建树与重建入口。经控制层 Scheduler 双通道调度,返回任务 id;索引维护不在此(随数据面操作自动跟进) | | `job_status(job_id, *, identity) -> JobInfo` / `job_cancel(job_id, *, identity)` | 查询 / 取消演进任务(委托 Scheduler) | | `inspect(unit_ids, scope, *, identity) -> list[MemoryUnit]` | 治理·检视:读取完整内容与治理字段(含已失效版本,委托 Governor) | | `trace(unit_id, scope, *, identity) -> list[MemoryUnit]` | 治理·血缘回溯:沿 `provenance` 追溯演进来源链(委托 Governor) | @@ -235,6 +243,8 @@ scope 的前缀”。这样同一套 `Scope` 字段既能表达 `user -> agent` > - **统一异常契约**:错误由 `common/errors`(根 `AgentMemoryError`)的类型承载——`NotFoundError`/`ConflictError`/`PermissionDeniedError`/`ValidationError`/`PolicyError`/`HealthCheckError`/`BackendError`,调用方跨后端/跨层用同一套捕获,不依赖具体实现自带异常。 > - **控制模式**:`evolve` 与自动触发对应 §9.3 的 `agent_control / static_control / both`。 > - **不设 `link` 接口**:记忆/实体关联不对外暴露为接口语义,由构建层 Associator 在演进(§9.3)中自动维护(`Relation` 结构供图索引内部使用)。 +> - **`trace` 不等于 `expand`**:`trace` 沿 `provenance` 回溯“如何派生”,`expand` 沿 `HierarchyRef` 下钻“结构上包含哪些证据”。版本回溯则由 `supersedes` + `as_of` 承担。 +> - 本表中不带“设计目标”的 F01 能力为当前基线;hierarchy 参数、`expand` 与 `EvolveMode.HIERARCHY` 的目标 specs 已完整同步,尚待设计评审与实现。 --- @@ -247,14 +257,17 @@ scope 的前缀”。这样同一套 `Scope` 字段既能表达 `user -> agent` | 生命周期 | 维护 memory 的 active / superseded / archived / forgotten 状态,并管理 space 的创建、冻结、归档、删除与 offboarding | 数据模型 §3.1;scope 模型 §3.2;自演进触发 §9.3;接口 `delete/update/inspect/delete_space` §6 | | 权限与隔离 | 基于 `org + space` 硬边界、space 级 `principal_path` 与 identity 做访问控制,跨 space 共享必须显式授权 | scope 模型 §3.2;接口层 PEP §6 | | 治理与审计 | 支持检视、编辑、血缘回溯、space 级审计查询、导出、用量统计与可观测轨迹 | 横切关注点 §12;接口 `inspect/trace/audit/export_space/space_usage` §6 | +| 树结构治理(目标) | 校验同 org+space、单 kind 单父、无环、父子双向一致与稳定顺序;细粒度 scope 可按 profile 放宽;剪边、重建、空父处理均可审计,且不得误删权威叶 | 数据模型 §3.1;结构轴 §4;构建 §9;接口 `expand/evolve(HIERARCHY)` §6 | | 调度与策略 | 管理 hot/background 任务、演进阶段、索引开关与运行时可变策略 | 自演进控制 §9.3;配置体系 §13 | -管理层只编排和约束这些动作,不定义新的记忆结构。分层记忆的内容结构见 §4;构建算子见 §9.1/§9.2;演进触发时机与控制模式见 §9.3。 +管理层只编排和约束这些动作,不把 hierarchy 混入 lifecycle、`provenance` 或 `supersedes`。四轴内容结构见 §4;构建算子见 §9.1/§9.2;演进触发时机与控制模式见 §9.3。 --- ## 8. 记忆检索层(Retrieve) +### 8.1 当前基线:六条内容层召回路 + 可选图召回 + ``` query ─▶ ① QueryParser:查询理解/去噪(清洗为空则短路返回) ─▶ ② 合并 scope/标签/时间等系统谓词与用户过滤 @@ -283,6 +296,26 @@ scope 的前缀”。这样同一套 `Scope` 字段既能表达 `user -> agent` - **两条时间轴**:`as_of` 是系统相信时间(valid-time,回溯「T 时刻哪个版本有效」),与从 query 文本解析出的事件时间约束(event-time,`time_from/time_to`,过滤 `t_event`)分开,互不折叠。 - **通道↔Store 非 1:1**:`RecallChannel` 是逻辑召回路,到物理 Store 的映射由 Storage 装配内部决定(一路对一 Store,多路也可合到 FusionStore 一次召回;TEMPORAL 多为叠加在其他通道上的时间过滤)。未指定通道表示调用全部已配置通道,显式空列表是无效输入。 +### 8.2 目标层级检索:按父侧 role 优先召回、子证据按需展开 + +```text +hierarchy filter(kind/父侧 role/span) + → 父节点走现有六路内容召回(可选图召回仍独立) + → 融合/重排 + → 可选 Expand(单 kind、显式 depth) + → 分数传播(首期 MaxP;top-M/阈值收敛) + → 树级逻辑预算分配(节点准入 + 主披露级) + → 对每个选中 unit 调用现有 Discloser + → 结果 + 父命中/展开/预算截断轨迹 +``` + +- **父优先**:显式指定父侧 `hierarchy_role` 时只召回该父角色,且默认不附带子全文; + 省略 role 时同 kind 下所有活动角色均可参与。叶命中向父上卷是可选策略,默认关闭。 +- **展开边界**:只沿请求指定的一个 `HierarchyKind` 和有序 `child_ids` 行走;depth、节点数与逻辑 token 预算共同限制返回子树切片。 +- **分数与预算分层**:跨节点分数传播和树级预算先决定选哪些节点及主 `DisclosureLevel`;现有 Discloser 最后只负责每个节点内部的 L0/L1/L2。由于 `RetrievedItem` 始终返回 abstract/overview/content 全字段,响应可超过该逻辑上下文注入预算;严格 wire-size 上限不在当前契约内。 +- **轨迹可审计**:trajectory 区分父层命中与 Expand,记录根节点、kind、实际深度、返回节点数、分数传播策略和预算截断原因。 +- **时间三分**:`RecallChannel.TEMPORAL` 继续表示时间过滤通道,消费 `MemoryUnit.temporal`;`HierarchyKind.TIME` 只表示 TIME 树中的父子包含。启用其中一个不会隐式启用另一个。 + --- ## 9. 记忆构建层详解:构建算子(自演进触发见 §9.3) @@ -302,8 +335,9 @@ scope 的前缀”。这样同一套 `Scope` 字段既能表达 `user -> agent` | **抽象与精炼** | 情景→语义、经验→技能/模式,概括出高抽象记忆 | 升华出画像、长期偏好、可复用技能/模式 | | **关联分析** | 实体共指、因果/引用链、跨会话/跨 Agent 关联 | 支持多跳推理、「连点成线」;构成中抽象的关系/主题结构 | | **多维分类** | 按主题/认知角色/来源/重要度等多维度归类 | 认知角色(working/core/episodic/semantic/procedural/archival)是其中一维,决定常驻上下文 or 按需检索 | -| **分层披露标注** | 标注 L0 摘要 / L1 片段 / L2 全文等披露层级 | 检索层按需加载,吸收 OpenViking 分层加载以节省 token | -| **时序组织** | 有效期、时间点、事件先后 | 双时间模型,支持历史回溯与非破坏式更新 | +| **同 unit 内容层标注** | `LayerAnnotator` 生成 L0 概要 / L1 片段;L2 复用全文 | 当前已接 EXTRACT/CONSOLIDATE 派生 unit,必须在其持久化与索引前完成;普通 write 尚未接入 | +| **树结构构建(目标)** | `HierarchyBuilder` 从权威叶生成父摘要与双向父子引用 | 所有 kind 共享树校验、叶权威与单 kind 遍历约束 | +| **TIME 树管线(目标)** | `TimeHierarchyPipeline` 按区间构建 snapshot/time_span/scene/event | `MemoryUnit.temporal` 提供叶事件时间,`HierarchyRef.span_*` 表示父覆盖区间 | ### 9.2 多形式索引:文档 / 关键词 / 向量 / 图 @@ -319,7 +353,11 @@ scope 的前缀”。这样同一套 `Scope` 字段既能表达 `user -> agent` | 标签/命名空间 | scope 过滤、多租户隔离 | `org + space` 硬隔离,`agent/user/session` 按 space 内主体路径过滤 | -> 各粒度记忆与各形式索引本身都是可重建派生物,落在记忆存储层的对应后端中。 +> 设计目标中,各粒度记忆与各形式索引都是可重建派生物,落在记忆存储层的对应后端中。 +> +> 目标父节点必须先由 `LayerAnnotator` 生成或安全降级同 unit 内容层,再持久化并建立内容索引;否则父摘要索引会与 KV 真源不一致。`replace_in_span` 只替换相交的派生父节点及索引,保留全部权威叶,并一致更新直接子节点的 `parent_id`。 +> +> `IndexBuilder.rebuild()` 接口已经声明,但当前 vector/fulltext 实现仍是 no-op;因此“索引可从真源重建”尚不是当前实现保证。层级 metadata 的目标重建必须先实现基于 KV `scopes()` + `list(scope)` 的真实枚举与全量重建。 ### 9.3 记忆自演进(Evolution):触发时机与控制模式 @@ -334,13 +372,14 @@ scope 的前缀”。这样同一套 `Scope` 字段既能表达 `user -> agent` ``` - **写入触发**:新内容写入后,hot path 做低时延落盘与轻量索引;需要重推理的抽取、关联、升华与冲突消解进入 background。 +- **普通 write 保持轻量**:默认只写权威叶或调用方明确给出的单节点,不同步自动构建整棵父子树;建树由显式或后台 `evolve(HIERARCHY)` 承担。 - **周期触发**:按策略对过期、低价值或长期未访问记忆做降权、归档或遗忘(非破坏式,保留血缘)。 - **显式触发**:调用方可通过 `evolve(scope, mode, channel, *, identity)` 触发指定阶段。 - **双通道**: - **Hot path(在线)**:低时延的即时记忆写入与轻量更新。 - **Background(离线)**:异步做重的抽取/升华/重索引,不阻塞主链路。 - **控制模式**:`agent_control`(Agent 自主调用记忆工具)/ `static_control`(开发者/管线控制)/ `both`。 -- **演进阶段(EvolveMode)= 内容演进四阶段**:`extract / associate / consolidate / forget`。**索引维护不是演进模式**——它随数据面操作(write/update/delete)由 IndexBuilder 增量跟进(build/update/remove),从真源全量重建走 `IndexBuilder.rebuild()` 维护路径(上述 background「重索引」即指此类维护工作,由数据面/维护触发,而非 `evolve(mode=…)`)。 +- **演进阶段(EvolveMode)**:当前为 `extract / associate / consolidate / forget`;目标增加 `HIERARCHY`,只调度父子树创建/重建并写 `HierarchyRef`,不暗写 `provenance` 或 `supersedes`。**索引维护仍不是演进模式**——它随数据面操作由 IndexBuilder 增量跟进,全量重建走独立维护路径。 --- @@ -364,7 +403,7 @@ scope 的前缀”。这样同一套 `Scope` 字段既能表达 `user -> agent` | ---------------------------- | --------------------- | | 编码 Agent / git 化 / 可审计 / 跨工具 | 文档形式(Markdown + 影子索引) | | 高并发个性化 / 大规模多租户 | 结构化形式(向量 + 结构化关联) | -| 时序敏感 / 企业知识 | 结构化 + 时序图 | +| 时间过滤敏感 / 企业知识 | 结构化 + 双时间与图索引 | | 端侧隐私 / 轻量 | 文档形式 或 SQLite | @@ -410,6 +449,7 @@ scope 的前缀”。这样同一套 `Scope` 字段既能表达 `user -> agent` > `IntegratedStorage` 是已定义的实现方向,尚未提供仓内实现。精确契约见 > [S06-storage.md](../specs/S06-storage.md),设计取舍见 > [F05-unified-storage-design.md](../features/storage/F05-unified-storage-design.md)。 +> **hierarchy 存储边界(目标)**:首期 `HierarchyRef` 内嵌于 KV 真源的 `MemoryUnit`;kind/role/span 等字段投影到全文/向量索引 metadata 供前置过滤,目标索引可从 KV 重建(当前 rebuild 实现缺口见 §9.2)。GraphStore 只表达实体、因果、引用等非包含关系,不作为首期父子树真源;当同 kind 多父、丰富边属性或跨 kind 组合查询成为主路径时,再评估独立边存储。 --- @@ -474,8 +514,9 @@ scope 的前缀”。这样同一套 `Scope` 字段既能表达 `user -> agent` | **真源形态**(§10.1) | 文档 / 结构化 | 按 profile | 切轻量真源降存储与运维 | | **索引类型**(§9.2) | 文档 / 关键词 / 向量 / 图 各自开关 | 关键词+向量(图/文档按需启用) | 关图/向量大幅降写入与存储成本 | | **检索策略**(§8) | Storage 首选 pipeline、召回通道、重排 on/off、渐进披露层级、`as_of` | 按 Storage 实现选择 + 混合召回 | 关重排/单通道降时延 | +| **树结构**(§4/§8) | `hierarchy.enabled`、启用 kinds、`hierarchy.auto_derive`、`hierarchy.ensure_on_recall`、`hierarchy.score_propagation`、`hierarchy.expand_default_depth` | 默认关闭;启用时单 kind、显式建树、MaxP;公开 recall `expand_depth=0`,显式 expand/内部策略默认 depth=1 | 关闭建树/展开可保持 F01 基线成本;关闭 recall 时补建可避免读延迟抖动 | | **自演进**(§9.3) | 总开关、阶段(extract/associate/consolidate/forget)、hot/background、控制模式 | 全闭环+双通道 | 仅 extract 或纯离线,降在线时延与 LLM 成本 | -| **双时间**(§3.1) | 启用 / 关闭(仅留最新版本) | 启用 | 关闭省时序维护,适合无回溯需求 | +| **双时间**(§3.1) | 启用 / 关闭(仅留最新版本) | 启用 | 关闭可省去历史时间维护,适合无回溯需求 | | **多模态规约**(§5.1) | 启用的规约器、是否留原模态资产、投影粒度 | 文本+按需图像 | 仅文本,去掉 ASR/OCR/caption 依赖 | | **scope / 共享**(§3.2) | `org + space` 隔离粒度、space 级 `principal_path`、共享池、跨 scope 授权策略 | space 隔离 + `user_agent` 默认 | 单租户简化 | | **存储后端**(§10.2) | Storage 实现、KV/向量/全文/图/融合/FS 端口选型(extras 选装) | CompositeStorage;端 SQLite / 云 PG+专用库 | 一体化 Storage 或端侧精简能力 | @@ -504,12 +545,12 @@ scope 的前缀”。这样同一套 `Scope` 字段既能表达 `user -> agent` > ⚠️ **下表的配置取值仅为示意举例**,用于说明「同一套能力如何按场景裁剪组合」,并非推荐值或最终默认;具体每项取值以立项后的 design/spec 与实测调优为准。 -| 场景 Profile | 真源 | 索引 | 自演进 | 双时间 | 多模态 | 部署 | 备注 | -| --- | --- | --- | --- | --- | --- | --- | --- | -| **编码 Agent** | 文档(Markdown) | 文档+关键词+向量 | 轻(extract,离线为主) | 可选 | 文本/代码 | 端侧/本地 | git 化、可审计、低开销 | -| **个人助手** | 结构化/SQLite | 向量+关键词(+图) | 全闭环 | 启用 | 文本+图像 | 端云协同 | 跨设备同步、隐私留端 | -| **企业多 Agent** | 结构化(向量+图) | 全开(含图) | 全闭环+重 background | 启用 | 按需 | 云侧 | 多租户 scope+共享池、强审计 | -| **端侧轻量** | 文档/SQLite | 关键词+轻向量 | 降级(规则/小模型或延迟到云) | 可关 | 仅文本(音视频延迟到云) | 纯端 | 低资源、离线优先 | +| 场景 Profile | 真源 | 索引 | 树结构(示意) | 自演进 | 双时间 | 多模态 | 部署 | 备注 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| **编码 Agent** | 文档(Markdown) | 文档+关键词+向量 | `hierarchy.enabled=true`;DIRECTORY;`hierarchy.auto_derive=false`;`hierarchy.ensure_on_recall=false`;MaxP;`hierarchy.expand_default_depth=1` | 轻(extract,离线为主) | 可选 | 文本/代码 | 端侧/本地 | git 化、可审计、低开销 | +| **个人助手** | 结构化/SQLite | 向量+关键词(+图) | `hierarchy.enabled=true`;TIME/TOPIC;`hierarchy.auto_derive=true`;`hierarchy.ensure_on_recall=false`;MaxP;`hierarchy.expand_default_depth=1` | 全闭环 | 启用 | 文本+图像 | 端云协同 | 跨设备同步、隐私留端 | +| **企业多 Agent** | 结构化(向量+图) | 全开(含图) | `hierarchy.enabled=true`;TOPIC/DIRECTORY/CUSTOM;`hierarchy.auto_derive=false`;`hierarchy.ensure_on_recall=false`;MaxP;`hierarchy.expand_default_depth=2` | 全闭环+重 background | 启用 | 按需 | 云侧 | 多租户 scope+共享池、强审计 | +| **端侧轻量** | 文档/SQLite | 关键词+轻向量 | `hierarchy.enabled=false`;kinds=空;MaxP;`hierarchy.expand_default_depth=1` | 降级(规则/小模型或延迟到云) | 可关 | 仅文本(音视频延迟到云) | 纯端 | 低资源、离线优先 | > 「既能全、也能轻」:关闭图/向量/演进/双时间后,可退化为接近 memSearch 的轻量形态(低写入开销);全开则对标 Mem0/Zep 的完整能力。配置裁剪是把「广度」转化为「按场景的合适成本」的关键手段。 @@ -551,6 +592,30 @@ recall() → 查询理解/去噪 → 空查询短路 → 合并过滤 evolve()/触发器 → 读取原始数据 → LLM 提取/抽象升华 → 关联/冲突消解(标记失效) → 写演进产物 → 重建多粒度记忆与索引 ``` +**树结构构建路径(Hierarchy Build,目标)** + +```text +evolve(HIERARCHY)/后台触发 + → 按 scope + kind + span 读取权威叶 + → HierarchyBuilder(TIME 使用 TimeHierarchyPipeline)切分并生成父摘要 + → LayerAnnotator 标注父节点 L0/L1 + → replace_in_span 断旧边、替换派生父节点(保留权威叶) + → 一致回写 parent_id/child_ids + → 持久化父节点并构建索引 + → 审计 +``` + +**层级展开读取路径(Expand Read,目标)** + +```text +recall(hierarchy filter) → 父摘要命中 + → [可选] expand(root, kind, depth, token_budget) + → 单 kind 遍历有序 child_ids + → 分数传播 + 树级逻辑预算选节点/主披露级 + → 每个节点交给 Discloser + → 父→子证据切片 + 展开轨迹 +``` + **同步路径(Sync, 端云协同)** ``` @@ -566,12 +631,12 @@ evolve()/触发器 → 读取原始数据 → LLM 提取/抽象升华 → 关联 | ------------ | -------------------------------- | ------------------------------------- | | Mem0 | 抽取+更新双阶段流水线、可插拔后端、多租户 scope、混合检索 | §9.3 自演进、§10.2 存储抽象、§3.2 scope、§8 检索 | | OpenViking | 统一上下文、L0/L1/L2 分层加载、检索轨迹、记忆自迭代 | §8 渐进式披露+轨迹、§9.3 演进、§10.1 文档真源 | -| Zep/Graphiti | 双时间模型、有效期、非破坏式更新 | §3.1 temporal、§9.1 时序组织、§14 同步冲突解决 | +| Zep/Graphiti | 双时间模型、有效期、非破坏式更新 | §3.1 temporal、§9.1 TIME 树与时间字段、§14 同步冲突解决 | | MemOS | 技能记忆、memory cube 共享、异步调度 | §9.3 升华(经验→技能)、§3.2 共享、§9.3 background 通道 | | memSearch | 文档为真源 + 影子索引可重建 | §10.1 文档真源、核心信条「派生可重建」 | -**差异化目标**:以上各家多为单点强项,本架构尝试以「**唯一真源(形态可配) + 多抽象粒度的分层记忆 + 多形式索引 + 多形态接入 + 端云三态**」统一整合,且接口/检索体验一致。该目标是否成立,需要在公开基准、端侧资源占用、检索延迟与部署迁移成本上验证。 +**差异化目标**:以上各家多为单点强项,本架构尝试以「**权威输入(承载形态可配) + 四轴分层记忆 + 父摘要到子证据 + 多形式索引 + 多形态接入 + 端云三态**」统一整合,且接口/检索体验一致。该目标是否成立,需要在公开基准、树级 token 预算、端侧资源占用、检索延迟与部署迁移成本上验证。 --- @@ -588,9 +653,9 @@ agent-memory/ │ ├── docs/ # 文档 │ ├── design/ # 设计文档(VISION / ARCHITECTURE / 调研) -│ ├── specs/ # 跨模块接口规约(S01-S07) +│ ├── specs/ # 跨模块接口规约(S01-S08) │ ├── features/ # 特性文档 -│ └── RULES.md +│ └── AGENTS.md # 文档归档规约 │ ├── agent_plugin/ # Agent 插件接入(依赖内核的封装) │ ├── JiwenSwarm/ @@ -617,11 +682,12 @@ agent-memory/ ├── config/ # 配置:真源形态 / 索引策略 / 部署 profile(不可变/重型配置) │ ├── common/ # 跨层共享:能力插件 + 通用结构体 + 异常 + 横切组件 - │ ├── base.py # Plugin 插件契约(pluginType/health)+ PluginType 枚举 + │ ├── base.py # Plugin 插件契约(plugin_type()/health)+ PluginType 枚举 │ ├── errors.py # 异常类型:AgentMemoryError 根 + NotFound/Conflict/PermissionDenied/Validation/Policy/HealthCheck/Backend │ ├── type_def/ # 通用结构体:Scope / MemoryUnit / FilterExpr / RawPayload / AuditEvent; │ │ # Storage 与 Retrieval 共用的 ParsedQuery / ScoredUnit / ScoredMemoryUnit / │ │ # RecallBatch / ChannelError / CandidateFuser + │ │ # [planned] HierarchyRef / HierarchyKind(目标树结构) │ ├── tokenizer/ # 分词:构建建倒排 ↔ 检索 query 分词(须同一分词器) │ ├── chunker/ # 切分:写入切 chunk ↔ 重索引按同一规则重切 │ ├── embedder/ # 向量化:chunk 向量 ↔ query 向量(须同模型同维度) @@ -647,8 +713,12 @@ agent-memory/ │ ├── abstractor.py # 抽象与精炼/升华:→ 高抽象 unit(provenance 记血缘) │ ├── associator.py # 关联分析:→ Relation(供图索引) │ ├── classifier.py # 多维分类:tier / 主题 / 重要度 - │ ├── index_builder.py # 多形式索引构建:build / update / remove / rebuild(记录落 unit.scope) - │ └── evolver.py # 自演进:EvolveMode(extract/associate/consolidate/forget;索引维护非演进模式) + │ ├── layer_annotator.py # 当前:同 unit L0/L1 内容层标注 + │ ├── index_builder.py # 多形式索引构建:build / update / remove / rebuild(scope 显式传给 Store) + │ ├── evolver.py # 自演进:EvolveMode(extract/associate/consolidate/forget;索引维护非演进模式) + │ ├── hierarchy_builder.py # [planned] 通用父子树创建/重建、replace_in_span + │ └── hierarchy_pipeline_impl/ + │ └── time_hierarchy_pipeline.py # [planned] TIME 树构建管线 │ ├── retrieval/ # D 记忆检索层(§8):查询解析→Storage pipeline→Reranker→阈值→披露 │ ├── base.py # RetrievalOperator 算子契约 @@ -659,6 +729,8 @@ agent-memory/ │ ├── discloser.py # 渐进式披露:disclose(query, …) L0 摘要 → L1 片段 → L2 全文 │ ├── retriever.py # 检索入口接口:retrieve(scope, query) │ └── retriever_impl/ # PipelineRetriever:按 Storage 首选值编排三条 pipeline + │ ├── hierarchy_expander.py # [planned] 单 kind、按深度展开父子树 + │ └── tree_budget.py # [planned] 跨节点分数传播与树级 token 预算 │ ├── storage/ # F 记忆存储层(§10):统一 Storage 门面 + 六类标准 Store │ ├── storage.py # Storage:MemoryUnit 领域操作、能力发现、端口与检索适配入口 @@ -681,6 +753,7 @@ agent-memory/ │ # 写链路仅异步 async write,返回插入的 MemoryUnit 列表) ├── lifecycle.py # 生命周期:transition / sweep(非破坏式标记) ├── governance.py # 治理:inspect 检视 / trace 血缘回溯 / audit 审计查询 + ├── hierarchy_governance.py # [planned] 父子双向一致、剪边、空父与重建治理 ├── permission.py # 权限:grant / revoke / check(跨 scope 显式授权) ├── scheduler.py # 演进调度:submit / status / cancel(双通道驱动构建层) └── policy.py # 运行时可变策略(§13.4 admin 落点) @@ -716,7 +789,7 @@ agent-memory/ > 当前状态:主要接口与默认实现已存在。本轮统一 Storage 首版已完成 Retriever 接入; > Construction/Control 迁移和一体化 `Storage` 实现仍待后续迭代。实际签名与行为以 -> `src/`、`docs/specs/` 和对应 features 文档为准。 +> `src/`、`docs/specs/` 和对应 features 文档为准。标记 `[planned]` 的 hierarchy 条目仅表示目标落点,不声称代码已存在。 --- @@ -731,6 +804,9 @@ agent-memory/ 5. **多模态规约的保真边界**:§5.1 已定(保留原模态资产 + 文本投影、检索走投影),但仍待细化——投影的保真度/成本权衡、是否保留多份不同粒度投影、原模态资产的生命周期与遗忘策略。 6. **审计后端缺口**:`common/audit` 已定义横切的 `AuditLogger` 记录接口与 `AuditEvent` 结构,但 `src/storage` 的六种 Store 中没有审计持久化后端——是新增 `AuditStore`(append-only 写入 + 按条件查询,供 `Governor.audit()` 消费),还是复用 KV/Fulltext 存储审计流水?涉及合规保留期限与查询能力的权衡。 7. **数值元数据的类型**:`FilterClause` 已支持数值/时间范围算子(gt/lt 等),但 `MemoryUnit.metadata` 仍为 `dict[str,str]`、重要度/置信度按字符串存放。范围比较与 `LifecycleManager.sweep` 的「低价值」判断要可靠生效,需让数值类元数据以可比较类型入库,或把 `importance`/`confidence` 提升为 `MemoryUnit` 的显式数值字段;本轮暂不改,留待实现/调优阶段定。 +8. **多父与独立边存储阈值**:同一 kind 多父、边属性或跨 kind 组合查询增长到什么规模时,内嵌 `HierarchyRef` 应迁移为独立边存储?需要以查询频率、双写成本和一致性故障率量化。 +9. **父子双向更新并发语义**:并发 write、Expand、剪边与 `replace_in_span` 下,`parent_id`/`child_ids` 如何原子更新、失败回滚和修复,需结合具体后端验证。 +10. **profile 的结构位置**:`profile` 应默认进入 TOPIC/CUSTOM 树、作为独立根,还是仅由认知 tier 与标签管理;它不进入 TIME 主树,也不得把 TIME 节点(snapshot/time_span/scene/event)挂为 `profile` 的结构子节点,但最终放置仍需数据验证。 > 注:原「召回通道 ↔ 存储后端一一对应」一项已结论——`RecallChannel` 是逻辑召回路,到物理 Store 的映射由检索层装配内部决定(非 1:1,详见 §8)。 @@ -738,7 +814,7 @@ agent-memory/ ## 18. 后续(Next) -- 本架构与 [VISION](./VISION.md) 一致,作为 `/opsx-propose` 立项输入。 +- 本架构与 [愿景](./vision.md) 一致;层级目标契约已同步到相关 specs,当前待设计评审与实现。 - 建议的首批 change 切分:① 记忆接口层 + 数据模型;② 记忆存储层/真源抽象(先文档 + SQLite);③ 混合检索引擎;④ 自演进(写入流水线优先);⑤ 调用与数据接入层(SDK Python + CLI + MCP 优先);⑥ 配置体系与场景 Preset(§13,贯穿各 change);⑦ 端云同步(后置)。 -> 本文为设计阶段架构,不含实现代码与排期;具体技术选型与接口签名以立项后的 design/spec 为准。 +> 本文为设计阶段架构,不含实现代码与排期;层级接口签名以已同步的相关 specs 为准,具体后端技术选型仍由后续实现验证。 diff --git a/docs/design/vision.md b/docs/design/vision.md index bab31c6d..5c723809 100644 --- a/docs/design/vision.md +++ b/docs/design/vision.md @@ -1,14 +1,14 @@ # agent-memory 愿景(Vision) > 文档性质:方向性愿景(探索阶段产出,非实现承诺) -> 版本:v0.1 | 日期:2026-05 -> 关联文档:[竞品调研](./competitor_analysis.md) | [Benchmark 调研](./memory_benchmarks.md) +> 版本:v0.2 | 日期:2026-07-16 +> 关联文档:[竞品调研](./competitor_analysis.md) | [Benchmark 调研](./memory_benchmarks.md) | [MemoryUnit 树结构](../features/common/F07-memory-tree.md) --- ## 0. 一句话愿景 -> **agent-memory是一个可独立提供、可嵌入、多形态调用的通用智能体记忆系统——它不只是向量检索,而是融合「分层 + 结构化关联」的多索引记忆底座,能自我演进,能力可按场景灵活配置(既能全、也能轻),并原生支持端、云及端云协同的单/多 Agent 场景。** +> **agent-memory是一个可独立提供、可嵌入、多形态调用的通用智能体记忆系统——它不只是向量检索,而是融合「同 unit 渐进披露 + 多模态构建 + 认知抽象 + 跨 unit 树结构 + 结构化关联」的多索引记忆底座,能自我演进,能力可按场景灵活配置(既能全、也能轻),并原生支持端、云及端云协同的单/多 Agent 场景。** 为 Agent 提供一套「装上即可拥有长期记忆」的统一底座:无论上层是聊天助手、编码 Agent 还是自主 Agent,无论跑在云端集群还是端侧设备,都能通过 CLI / Skill / SDK / API / MCP 等方式接入同一套记忆能力。 @@ -53,7 +53,7 @@ agent-memory 致力于成为智能体世界的「记忆底座」: | **框架无关(Framework-agnostic)** | 不绑定任何单一 Agent 框架;通过开放接口与协议接入,可与 LangGraph、AgentScope、OpenClaw、自研 Agent 等共存。 | | **可嵌入 + 可服务(Embeddable & Service)** | 既能作为库(in-process)嵌入,也能作为独立服务(out-of-process)被远程调用。 | | **多形态调用(Multi-surface)** | CLI、Skill、SDK(以 Python 为主)、HTTP/gRPC API、MCP Server 等统一映射到同一记忆内核。 | -| **分层而非扁平(Layered, not flat)** | 记忆按抽象粒度分层(贴近原始 → 关联/组织 → 精炼/概括)+ 多形式索引(文档/关键词/向量/图),而非单一向量库。 | +| **分层而非扁平(Layered, not flat)** | 四条正交轴共同避免扁平化:`ContentLayers`/`DisclosureLevel` 表达同 unit 渐进披露,CLM/ELM(`metadata`+`provenance`)表达多模态构建粒度,`MemoryTier`/evolve 表达认知抽象,`HierarchyRef` 表达跨 unit 树结构;其上再按需构建文档/关键词/向量/图索引。 | | **多模态数据输入(Multimodal Input)** | 数据输入支持多模态:对话/文档/代码/图像/音频/视频等均可作为记忆来源;既保留原模态资产(或引用),又在接入时沉淀可治理的文本/结构化投影(转录/OCR/caption/描述),下游索引与检索基于统一投影进行。 | | **可配置的表示形态(Configurable representation)** | 记忆的「真源形态」可按场景配置——既支持**文档形式**(Markdown/文件为真源 + 派生影子索引,透明可 git),也支持**结构化形式**(向量/图为真源,高并发可规模化);对上层暴露的记忆 API 保持不变。 | | **场景化可配置(Scenario-configurable)** | 索引类型、自演进阶段、双时间、多模态、部署形态、模型等关键能力均可**按 Agent 场景裁剪与组合**,并提供开箱即用预设(编码 Agent / 个人助手 / 企业多 Agent / 端侧轻量)——**既能全、也能轻**,把能力广度转化为按场景的合适成本。 | @@ -73,8 +73,8 @@ agent-memory 致力于成为智能体世界的「记忆底座」: └─────────────────────────────────────────────┘ ┌───────────┬─────────────┬────────────┬────────────┬─────────────┐ ▼ ▼ ▼ ▼ ▼ - ① 框架无关 + ② 分层记忆结构 ③ 记忆自演进 ④ 端云协同 ⑤ 单/多 Agent - 多形态调用 多粒度+多形式索引 (Self-Evolving) 部署形态 隔离与共享 + ① 框架无关 + ② 四轴分层结构 ③ 记忆自演进 ④ 端云协同 ⑤ 单/多 Agent + 多形态调用 披露·多模态·抽象·树结构 (Self-Evolving) 部署形态 隔离与共享 ``` ### 支柱一:框架无关、可嵌入、多形态调用 @@ -99,43 +99,47 @@ agent-memory 致力于成为智能体世界的「记忆底座」: - **API**:HTTP/gRPC 远程服务,供任意语言/分布式系统调用。 - **MCP Server**:即插即用接入 Cursor、Codex、Claude Code 等支持 MCP 的宿主。 -### 支柱二:分层记忆结构(多抽象粒度 + 多形式索引,不止向量检索) +### 支柱二:四轴分层记忆结构(披露 + 多模态 + 抽象 + 树结构,不止向量检索) -核心心智模型:**记忆系统只有一个真源——原始数据;从中提取相关信息,经抽象与精炼、关联分析,挖掘出不同抽象粒度的记忆,并在其上构建多种形式的索引。** 所有抽象粒度的记忆与索引皆可从原始数据重建。 +核心心智模型:**记忆系统从权威叶与原始数据出发,经演进形成不同认知角色的记忆;每条记忆可按预算渐进披露;单媒体源可构建不同粒度的多模态记忆;跨记忆再以可重建的树结构提供“父摘要 → 子证据”的结构化下钻。** 四条轴彼此正交,多形式索引建立在这些记忆之上。 ``` ┌──────────────────────────────────────────────────────────────────────┐ │ 多形式索引 Indexes:文档索引 · 关键词索引 · 向量索引 · 图索引 │ │ (检索时混合召回 + 重排) │ └──────────────────────────────────────────────────────────────────────┘ - ▲ 在各抽象粒度记忆上构建索引 + ▲ 在各类 MemoryUnit 上构建索引 ┌──────────────────────────────────────────────────────────────────────┐ -│ 长时记忆 · 分层记忆结构 Layered Memory(按抽象粒度分层) │ -│ 高抽象(精炼/概括) 画像 · 长期偏好 · 习得技能/模式 │ -│ 中抽象(关联/组织) 事件 · 实体关系 · 主题聚类 │ -│ 低抽象(贴近原始) 抽取的事实/片段 │ +│ 长时记忆 · 四条正交轴 │ +│ 同 unit 披露:ContentLayers + DisclosureLevel L0 概要/L1 片段/L2 全文 │ +│ 多模态构建:CLM/ELM(metadata + provenance) 单媒体源 → 多粒度 unit │ +│ 认知抽象:MemoryTier + evolve 工作/情景/语义/程序性/核心/归档 │ +│ 跨 unit 树结构:HierarchyRef 父摘要 ──按深度/预算展开──▶ 子证据 │ └──────────────────────────────────────────────────────────────────────┘ - ▲ 提取 · 抽象与精炼 · 关联分析 + ▲ 提取 · 抽象精炼 · 关联分析 · 显式/后台建树 ┌──────────────────────────────────────────────────────────────────────┐ -│ 原始数据 Raw Data(唯一真源 / source of truth) │ +│ 原始数据与权威叶 Raw Data & Authoritative Leaves │ │ 对话 · 事件 · 事实 · 偏好 · 工具调用轨迹 · 图像/音频/视频(或引用) ... │ │ 承载形态可配置:文档(Markdown/文件) 或 结构化记录(DB/向量库) │ └──────────────────────────────────────────────────────────────────────┘ - ▲ 自下而上构建,皆可从原始数据重建;上层只面向统一记忆接口,真源形态对其透明 + ▲ 父节点与索引可重建,权威叶不因父树重算而删除;真源形态对上层透明 ``` -- **短时记忆与长时记忆**:系统同时维护**短时记忆**(工作/会话上下文,易失)与**长时记忆**(上图沉淀的分层记忆结构);短时记忆经升华/沉淀转入长时记忆。(细分见 ARCHITECTURE §4) -- **从原始数据到多粒度记忆**:从原始数据**提取**相关信息,基于**抽象与精炼**(情景→语义、经验→技能/模式)、**关联分析**(实体、因果、引用、跨会话),挖掘出**不同抽象粒度**的记忆,让 Agent 既能取用细节,也能取用高层概括。 +- **短时记忆与长时记忆**:短时记忆由 `MemoryTier.WORKING` 等工作/会话态承载,易失且快速读写;长时记忆是可跨会话复用的各类 `MemoryUnit`。从短时到长时是认知抽象与演进,不等于 L0→L2 披露,也不等于树深变化。 +- **同 unit 渐进披露轴(已实现基线)**:`ContentLayers.l0/l1` 与 `DisclosureLevel` 让同一 `MemoryUnit` 以 L0 概要、L1 片段或 L2 全文进入上下文。L0/L1/L2 只表示同一 unit 的压缩度。 +- **多模态构建轴**:同一媒体源可产出 CLM/ELM 等不同粒度 `MemoryUnit`,用 `metadata.memory_level` 与 `provenance` 表达;不是同 unit 披露,也不用 `HierarchyRef` 表达同视频内 ELM⊃CLM。 +- **认知抽象轴**:`MemoryTier` 描述工作、情景、语义、程序性、核心与归档等认知角色;evolve 负责抽取、关联、巩固与遗忘。它回答“这是什么认知角色”,不编码树位。 +- **跨 unit 树结构轴(设计目标)**:`HierarchyRef` 以 `HierarchyKind.TIME|TOPIC|DIRECTORY|CLUSTER|CUSTOM` 组织树结构;节点可取 `snapshot/time_span/scene/event/profile/root/node` 等角色。调用方显式筛选父侧 role 时先返回父摘要,再按指定 kind、展开深度和树级 token 预算读取子证据;省略 role 时同 kind 下各活动角色均可参与。叶保持权威,高层可重建。 +- **四轴不可互推**:每个父节点仍可有自己的 `MemoryTier`、L0/L1/L2 与(若适用)多模态粒度标注;role 不等于 tier,树深不等于披露级别或 CLM/ELM。`MemoryUnit.temporal` 记录双时间,`RecallChannel.TEMPORAL` 表示时间过滤通道,`HierarchyKind.TIME` 才表示 TIME 树的结构包含。 - **多形式索引(按配置启用)**:索引是建立在记忆之上、**可配置的检索结构,并非记忆本身的固有结构**。在索引阶段**支持**多种互补索引——**文档索引**(面向文档形态记忆的路径/章节式定位)、**关键词索引**(全文/BM25)、**向量索引**(语义相似)、**图索引**(实体-关系、因果/引用链,支持多跳推理与「连点成线」);**具体启用哪些由配置决定**,检索时对启用的索引混合召回 + 重排。 - **多模态数据输入**:原始数据可来自多模态来源——对话/文档/代码/图像/音频/视频/工具轨迹等。系统既保留**原模态资产**(或其引用/对象存储路径),又在接入与抽象时生成**可治理的文本/结构化投影**(转录、OCR、caption、视觉描述)。原模态资产作为真源、文本投影作为派生,二者皆可治理、可重建;下游各形式索引基于统一的文本/结构化投影构建。 -- **真源唯一、分层可重建**:原始数据是唯一权威来源;各抽象粒度的记忆与索引均可从它重算(推广了 memSearch「删索引不丢数据」的理念)。 +- **真源与可重建边界**:原始数据和权威叶不可因父层重算而删除;派生父节点、内容标注与索引可从权威输入重建。 - **真源承载形态可配置**: - **文档形式**(Markdown/文件为真源 + 影子索引)——人可读、可编辑、可 git、可审计,适合编码 Agent / 跨工具复用 / 端侧轻量。参照 memSearch、OpenViking、Claude Code、Codex。 - **结构化形式**(DB/向量/图为真源)——高并发、强检索、可规模化、多租户弹性。参照 Mem0、Zep。 -> **相对单一打法的差异化目标**:memSearch/OpenViking 偏文档真源、Mem0 偏结构化真源;agent-memory 计划以**唯一原始数据真源(形态可配) + 多抽象粒度的分层记忆 + 多形式索引**统一两派,上层接口与检索体验保持一致。该目标需要通过不同真源形态下的重建成本、检索一致性与运维复杂度验证。 -> -> 场景建议:编码 Agent / 需 git 化审计 → 文档真源;高并发个性化 / 大规模多租户 → 结构化真源;时序敏感 / 企业知识 → 结构化 + 时序图;端侧隐私 / 轻量 → 文档或 SQLite,视资源而定。 +> **相对单一打法的差异化目标**:memSearch/OpenViking 偏文档真源、Mem0 偏结构化真源;agent-memory 计划以**可配置真源 + 四轴分层记忆 + 多形式索引**统一两派,并以父摘要召回、子证据展开和树级预算把“浏览概要”与“精确取证”连接起来。该目标需要通过重建成本、检索一致性、树展开质量与运维复杂度验证。> +> 场景建议:编码 Agent / 需 git 化审计 → 文档真源;高并发个性化 / 大规模多租户 → 结构化真源;时间过滤敏感 / 企业知识 → 结构化 + 双时间与图索引;端侧隐私 / 轻量 → 文档或 SQLite,视资源而定。 ### 支柱三:记忆自演进(Self-Evolving Memory) @@ -209,19 +213,19 @@ agent-memory 致力于成为智能体世界的「记忆底座」: ``` ┌──────────────────────────────────────────────────────────────────────────┐ │ 调用与数据接入层 CLI·Skill·SDK(Python)·HTTP/gRPC·MCP + 多模态信息源接入 │ -│ (记忆接口) write · recall · update · delete · link · evolve │ +│ (记忆接口) write · recall · expand · update · delete · evolve │ ├──────────────────────────────────────────────────────────────────────────┤ │ 记忆管理层 生命周期 · 治理(检视/编辑/审计/遗忘) · 权限 · 配置/策略 │ ├──────────────────────────────────────────────────────────────────────────┤ -│ 记忆检索层 混合召回 · 重排 · 渐进式披露(消费下方记忆构建层) │ +│ 记忆检索层 混合召回 · 按父侧 role 优先 · 子证据展开 · 单 unit 披露 │ ├──────────────────────────────────────────────────────────────────────────┤ -│ 记忆构建层 分层记忆结构:从原始数据提取→抽象精炼/关联分析→多抽象粒度 │ -│ (Layered Memory) 记忆 + 多形式索引(文档·关键词·向量·图);记忆自演进驱动 │ +│ 记忆构建层 四轴结构:披露 · 多模态构建 · 认知抽象 · 跨 unit 树结构 │ +│ (Layered Memory) 父摘要→子证据 + 多形式索引;记忆自演进与显式建树驱动 │ ├──────────────────────────────────────────────────────────────────────────┤ │ 记忆存储层 可配置真源: 文档(Markdown,影子索引) / 结构化(向量·图·KV) │ │ 后端: 向量库 │ 图库 │ KV/全文 │ 文件系统 │ 审计日志 │ ├──────────────────────────────────────────────────────────────────────────┤ -│ 数据层 Data 用户记忆数据 · Agent 记忆数据(原始数据,唯一真源) │ +│ 数据层 Data 用户/Agent 原始数据与明确写入的权威叶(重建边界) │ └──────────────────────────────────────────────────────────────────────────┘ 横切:端/云/端云协同部署 · 可观测 · 可治理 · 多租户隔离 · 安全合规 ``` @@ -240,7 +244,7 @@ agent-memory 致力于成为智能体世界的「记忆底座」: - **可插拔向量后端 + 多租户 scope(user/session/agent)**:成熟、生态广。 - **混合检索(向量 + BM25 + 实体)**与「省 token / 低时延」的工程口径。 -> **差异化目标**:Mem0 的图谱能力(Mem0g)常为可选/付费、以文本事实为主、写入为异步存在延迟。agent-memory 计划把**图/结构化关联与时序作为内建、可按配置启用的索引能力**,并提供**端侧低延迟写入 + 端云协同**。这些目标需要在图/时序召回质量、写入可见延迟、端侧资源占用等指标上验证。 +> **差异化目标**:Mem0 的图谱能力(Mem0g)常为可选/付费、以文本事实为主、写入为异步存在延迟。agent-memory 计划把**图/结构化关联与时间过滤作为内建、可按配置启用的索引能力**,并提供**端侧低延迟写入 + 端云协同**。这些目标需要在图/时间召回质量、写入可见延迟、端侧资源占用等指标上验证。 ### 6.2 吸收 OpenViking 的优点 @@ -248,7 +252,7 @@ agent-memory 致力于成为智能体世界的「记忆底座」: - **检索可观测**:检索轨迹(trajectory)可见、可调试,而非黑盒。 - **记忆自迭代**:会话结束触发抽取,自动更新 user/agent 记忆。 -> **差异化目标**:OpenViking 以文件系统路径浏览 + 目录递归检索为主、多模态较弱、偏自托管单形态。agent-memory 在保留「可浏览/可观测」透明性的同时,计划叠加**向量 + 结构化关联 + 时序的混合检索**(不止路径浏览),并把部署扩展为**端 / 云 / 端云协同三态**与**单/多 Agent**统一。该目标需要在检索质量、轨迹可解释性、部署迁移成本上验证。 +> **差异化目标**:OpenViking 以文件系统路径浏览 + 目录递归检索为主、多模态较弱、偏自托管单形态。agent-memory 在保留「可浏览/可观测」透明性的同时,计划叠加**向量 + 结构化关联 + 时间过滤的混合检索**(不止路径浏览),并把部署扩展为**端 / 云 / 端云协同三态**与**单/多 Agent**统一。该目标需要在检索质量、轨迹可解释性、部署迁移成本上验证。 ### 6.3 综合差异化定位 @@ -257,10 +261,11 @@ agent-memory 致力于成为智能体世界的「记忆底座」: | --------- | ------------- | ------------- | ---------------------------------------------------- | | 框架/宿主绑定 | 中立(SDK) | 偏 OpenClaw 等 | **框架无关**:SDK 进程内嵌入 + CLI/Skill/MCP/API 进程外接入,不绑定单一框架 | | 调用方式 | 以 SDK/API 为主 | REST + 文件协议 | **CLI + Skill + SDK(Python) + API + MCP 全覆盖**,同一内核 | -| 记忆索引 | 向量为主,图谱可选/付费 | 文件系统 + 目录递归 | **索引阶段支持多种索引(关键词/向量/图/全文/时序),按配置启用、混合检索** | +| 记忆索引 | 向量为主,图谱可选/付费 | 文件系统 + 目录递归 | **索引阶段支持多种索引(关键词/向量/图/全文)并叠加时间过滤,按配置启用、混合检索** | +| 记忆层次 | 事实与关系为主 | 同内容分层加载 | **同 unit 披露、多模态构建、认知抽象、跨 unit 树结构四轴正交;父摘要命中后按深度与树预算展开子证据** | | 真源形态 | 结构化(向量/图) | 文档/文件(单一) | **文档 与 结构化双真源,按场景可配,API 一致** | | 数据输入模态 | 偏文本事实 | 多模态较弱 | **多模态数据输入**:图像/音频/视频等可作来源,原模态资产 + 可治理文本投影 | -| 时序能力 | 弱 | 弱 | **双时间 + 有效期 + 非破坏式更新**(吸收 Zep 理念) | +| 时间能力 | 弱 | 弱 | **双时间 + 有效期 + 非破坏式更新**(吸收 Zep 理念) | | 自演进 | 抽取+冲突消解 | 会话级自迭代 | **抽取→关联→消解→升华→遗忘 体系化闭环** | | 端云部署 | 偏云(可自托管) | 偏自托管单形态 | **端 / 云 / 端云协同 三态统一**,按场景选型、可平滑演进 | | 单/多 Agent | scope 隔离(共享弱) | user/agent 记忆 | **隔离 + 共享记忆池统一**,与部署正交,单/多 Agent 皆可 | @@ -278,6 +283,7 @@ agent-memory 致力于成为智能体世界的「记忆底座」: - 能力诊断:MemoryAgentBench(检索/学习/理解/更新)、MemBench(事实 vs 反思)。 - 多源/端侧:LifeBench 覆盖多源数字痕迹(贴合端侧、多模态信息源定位)。 - **工程指标**:除准确率外,报告检索 token 量、p50/p95 时延、记忆容量、端侧资源占用。 +- **层级检索信号**:报告父摘要召回后定位到相关子证据的准确率、不同展开深度下的收益/时延曲线、树级 token 预算命中率与截断原因,以及权威叶在父树重建后的完整性。 - **生态指标**:支持接入主流框架, star与fork数量。 --- diff --git a/docs/features/common/F01-memory-layer.md b/docs/features/common/F01-memory-layer.md index 0a4656d8..8c8c4323 100644 --- a/docs/features/common/F01-memory-layer.md +++ b/docs/features/common/F01-memory-layer.md @@ -4,7 +4,7 @@ | 项 | 值 | |---|---| -| 日期 | 2026-07-07(初版)/ 2026-07-03(落地修订) | +| 日期 | 2026-07-16 | | 影响范围 | src/common/type_def/memory.py、src/common/type_def/memory_codec.py、src/construction/layer_annotator.py、src/construction/layer_annotator_impl/、src/construction/evolver_impl/orchestrating_evolver.py、src/construction/index_builder_impl/、src/construction/base.py、src/construction/bootstrap.py、docs/specs/S05-construction.md、docs/specs/S07-common.md | | 测试基线 | `tests/unit/construction/test_layer_annotator.py`(10 passed)、`tests/unit/construction/test_layers_index.py`(10 passed)、全量 `tests/` 426 passed / 54 skipped | | Refs | — | @@ -21,7 +21,7 @@ architecture §4 定义了长时记忆的纵向抽象分层:低抽象事实/ 落地前的状态(已克服):`MemoryUnit` 只有 `segments`/`content` 合并视图,L0/L1 不在数据结构中;构建管线不产出 L0/L1;索引只基于 `unit.content`。 -本特性已落地 **MemoryUnit 内建内容层 + 构建层标注 + 层级索引记录**,召回/披露端消费尚未接入(见已知遗留)。纵向抽象级别不在本特性中建模。 +本特性已落地 **MemoryUnit 内建内容层 + 构建层标注 + 分层索引记录 + 分层召回与披露**。纵向抽象级别不在本特性中建模;跨 `MemoryUnit` 的树结构也不属于本特性。 ## 决策 @@ -353,14 +353,9 @@ L0/L1/L2 分别落独立 Milvus collection,共用同一维度与 COSINE 度量 - `loads` 读回构造 `ContentLayers`,缺失取空串(老数据无迁移读出)。 - 未知字段继续忽略,保持向前兼容。 -## 后续扩展:层级展开 +## 后续扩展:树结构 -本特性只处理同一 unit 内的 L0/L1/L2 内容层。若后续要支持目录、主题、聚类等父子结构,需要另立 feature: - -1. 引入 `parent_id` / `parent_uri` 或主题节点。 -2. 支持父节点摘要命中后展开子节点。 -3. 支持父子分数传播和收敛策略。 -4. 支持按预算从父级概要升级到子级全文。 +本特性只处理同一 unit 内由 `DisclosureLevel` 表达的 L0/L1/L2 压缩度;它不表示跨 unit 的父子关系。跨 `MemoryUnit` 的树结构、父命中后展开、分数传播与树级 token 预算已在 [F07-memory-tree.md](F07-memory-tree.md) 中完成设计,目前尚未实现。 ## 拒绝的方案 @@ -376,9 +371,9 @@ L0/L1/L2 分别落独立 Milvus collection,共用同一维度与 COSINE 度量 拒绝。L2 等于 `unit.content`,重复存储会造成 segments 与 layers.l2 的一致性问题。 -### 方案 D:本次引入父子层级展开 +### 方案 D:本次引入树结构展开 -拒绝作为本次范围。父子层级展开需要新增主题/目录节点、父子关系、分数传播和展开策略,超出同一 `MemoryUnit` 内容层的边界。 +拒绝作为本次范围。该决定保留为 F01 的历史范围取舍:树结构展开超出同一 `MemoryUnit` 内容层的边界。树结构现已由 [F07-memory-tree.md](F07-memory-tree.md) 独立完成设计,但仍未实现。 ## 验证 @@ -395,7 +390,7 @@ L0/L1/L2 分别落独立 Milvus collection,共用同一维度与 COSINE 度量 - [ ] delete/lifecycle 转换不修改 layers,PURGE 删除真源与索引(未落地) - [x] VectorIndexBuilder 为 L0/L1/L2 建层级记录,metadata 含 `content_layer`(分表、store None 跳过) - [x] FulltextIndexBuilder 为 L0/L1/L2 建层级文档,metadata 含 `content_layer`(分表、store None 跳过) -- [x] Recaller 聚合层级命中到 `unit_id`,多路多层级经 Fuser RRF 聚合(已实施,§6) +- [x] Recaller 聚合层级命中到 `unit_id`,同通道多层命中按 MaxP 聚合(已实施,§6) - [x] TruncatingDiscloser 优先读 layers,空值回退原截断逻辑(已实施,§6) - [x] StructuredDiscloser 优先读 layers,空值回退原结构化逻辑(已实施,§6) - [x] RetrievedItem 三层一次性填充(abstract/overview/content,已实施,§6) @@ -407,7 +402,7 @@ L0/L1/L2 分别落独立 Milvus collection,共用同一维度与 COSINE 度量 原始 unit 无 layers。后续接 write 路径时再实现。 2. **update/delete 路径未接**:update 不会自动重标注(layers 随版本语义继承/清空);delete 不 修改 layers。后续接 update 路径时实现 patch 触发重标注。 -3. **父子层级展开未实现**:本次只做同一 unit 的 L0/L1/L2 内容层,不做目录/主题节点展开。 +3. **树结构展开未实现**:本次只做同一 unit 的 L0/L1/L2 内容层;跨 unit 的结构设计见 [F07-memory-tree.md](F07-memory-tree.md)。 4. **KeywordLayerAnnotator 质量有限**:规则标注无法保证 LLM 级语义浓缩,hot path 接受该折衷。 5. **抽象粒度字段未建模**:低/中/高抽象分层仍由 `tier/tags/metadata/provenance` 间接表达;如需 一等字段,应另立纵向抽象分层 feature。 diff --git a/docs/features/common/F07-memory-tree.md b/docs/features/common/F07-memory-tree.md new file mode 100644 index 00000000..b1b22e37 --- /dev/null +++ b/docs/features/common/F07-memory-tree.md @@ -0,0 +1,621 @@ +# F07 — MemoryUnit 树结构 + +## 元信息 + +| 项 | 值 | +|---|---| +| 日期 | 2026-08-11(修订:编号顺延为 F07;统一「树结构」命名;四轴背景与边界;结构边 scope=同 org+space 非五维全等) | +| 影响范围 | src/api/、src/common/、src/construction/、src/control/、src/ingest/、src/retrieval/、src/storage/;docs/specs/S01–S07;关联 [`F05-construction-spec-multimodal-design`](../construction/F05-construction-spec-multimodal-design.md) | +| 测试基线 | 目标设计与 specs 已同步完成,待设计评审;树结构代码尚未实现,无 pytest 结果 | +| Refs | — | + +## 背景 + +现有记忆模型已经覆盖三轴,彼此独立、互不推导: + +1. **同 unit 披露轴**:`ContentLayers` 与 `DisclosureLevel` 决定一条 + `MemoryUnit` 以 L0 概要、L1 片段还是 L2 全文进入上下文。L0/L1/L2 只表示 + same-unit compression,不表示节点之间的关系。 +2. **多模态构建轴**:多模态构建(F05)对**一条原始媒体源**(首期视频)产出 + CLM/ELM 等多条不同概括粒度的 `MemoryUnit`,用 `metadata.memory_level` + + `provenance` 表达单媒体源构建粒度,不表示跨源的结构包含。 +3. **认知抽象轴**:`MemoryTier` 与既有演进模式区分工作记忆、情景、语义、 + 程序性、核心与归档等认知角色。 + +以上三轴仍不能单独回答“一段时间窗口内、跨多条原始源(文本/视频/图片…)的结构 +包含与下钻”。因此本特性引入第四轴: + +4. **树结构轴**:由 `HierarchyRef` 表达跨 `MemoryUnit` 的包含关系, + 以父节点作为可检索概要,以子节点作为可按需展开的证据。 + +四轴可在同一节点上共存,且互不推导: + +| 轴 | 载体 | 作用域 | +|---|---|-----------------------| +| 同 unit 披露 | `ContentLayers` | 单 unit | +| 多模态构建 | CLM/ELM metadata + provenance | 单媒体源 → 多 unit(见 F05) | +| 认知抽象 | `MemoryTier` | 单 unit 认知角色,unit 可以演进 | +| 树结构 | `HierarchyRef`(本文,首期 TIME) | 跨 unit / 跨源 | + +## 架构裁决:树结构轴与其他三轴的边界 + +树结构轴与既有三轴正交。任意一轴的值都不能推导另外一轴;同一节点可同时携带 +四轴信息。 + +### 与同 unit 披露轴 + +1. `ContentLayers` / `DisclosureLevel` 只描述**同一** `MemoryUnit` 的压缩披露, + 不表达跨 unit 的父子包含。 +2. 父节点与子节点各自可以有独立的 L0/L1/L2;树展开选择的是**哪个节点**进入 + 上下文,披露级别选择的是该节点**以何种压缩度**呈现。 +3. **禁止**用 L0/L1/L2 或把子节点正文塞进父节点 `layers` 来模拟树结构。 + +### 与多模态构建轴 + +1. **禁止**用 `HierarchyRef` 表达同视频内 ELM⊃CLM——只用 F05 的 provenance/metadata。 +2. TIME 建树的叶可以是文本直写 unit,也可以是 F05 产出的 CLM/ELM(或其它模态记忆)。 +3. **建议叶粒度**:默认以 CLM(及文本叶)作为细粒度权威叶;ELM 可作为并行候选, + 首期**不要**自动把 ELM 写成 TIME 父节点(父由 `HierarchyBuilder` 生成)。 +4. `evolve(HIERARCHY)` **不**调用、不替代视频理解流水线;缺 F05 时对视频源可降级或跳过。 +5. 未来若将单视频 CLM/ELM 升为 `HierarchyKind.MEDIA`,单独立项 RFC,不在首期混用。 + +### 与认知抽象轴 + +1. `MemoryTier` 表示认知角色(工作/情景/语义等),不是树位;`HierarchyRef.role` + (如 snapshot/scene/event)表示结构树位,不等于 tier。 +2. 父子节点可各自选择不同 tier;**禁止**用 `MemoryTier` 枚举或 evolve 模式映射 + 代替树深、父子边或 `HierarchyKind`。 +3. 既有非 `HIERARCHY` 演进模式不暗改 `HierarchyRef`;`evolve(HIERARCHY)` 只维护 + 树结构边与派生父。 + +## 决策 + +### 1. 首期采用内嵌 `HierarchyRef` + +首期把结构引用内嵌到 `MemoryUnit`,不新增独立边存储。主要原因是: + +- KV 中的 `MemoryUnit` 继续作为完整真源,目标索引可从真源重建; +- 父命中后的常用读取是按有序 `child_ids` 点读,首期无需额外 join; +- 缺少 `hierarchy` 的旧数据可以按“非层级节点”兼容读取; +- 可先验证单 kind 严格树、重建与展开语义,再决定是否承担多父图的复杂度。 + +这是一项首期边界,不是否认独立边存储的长期价值。当同一节点必须在同一种 kind +下拥有多个父节点,或边属性、跨 kind 组合查询成为主路径时,再评估迁移。 + +公开数据结构、序列化兼容和错误语义已落入 +[S07-common.md](../../specs/S07-common.md);本文只记录选择理由和设计约束。 + +### 2. `HierarchyRef` 的字段职责 + +`HierarchyRef` 用 `kind/role` 标识结构维度与树位,用 `parent_id/child_ids` 保存直接且 +有序的双向边,用 `span_start/span_end` 表示覆盖区间,并用 `ordinal/status` 表示稳定 +顺序与结构修正状态。`status` 是必填字段,默认 `HierarchyStatus.ACTIVE`,取值只允许 +`ACTIVE/DISMISSED`;归档、遗忘和版本失效完全由 `LifecycleState` +管理,不进入结构状态。 + +TIME 节点必须声明有效 span,非 TIME 节点可选;任何已声明的 span 都必须成对、有效且 +满足父覆盖直接子。首期仍坚持**同 org+space**、单 kind 严格树、双向一致、无环、稳定 +顺序和叶权威;`user`/`agent`/`session` 可按 build profile 放宽(见决策 2a),默认展开 +不隐式跨 kind。具体字段类型、默认值、完整不变量与错误语义见 +[S07-common.md](../../specs/S07-common.md)、[S03-control.md](../../specs/S03-control.md) +和 [S04-retrieval.md](../../specs/S04-retrieval.md)。 + +### 2a. 树结构边的 scope 规则(非五维全等) + +父子节点之间的 scope ,不是 `Scope(org, space, user, agent, session)` 五维 +全等,会挡住 TIME 的核心场景:跨多个 session 概括、乃至同租户下跨多个 user 概括。 +首期的目标契约是: + +| 维度 | 规则 | +|---|-----------------------------------------------------------| +| `org` + `space` | **硬边界**:父子必须相同;跨 space / 跨 org 的树边一律拒绝。(与 F03 租户隔离一致) | +| `session` | **默认可跨**:同 user(或 profile 允许的主体)下连续多 session 可挂同一 TIME 树 | +| `user` / `agent` | **默认不可跨**;同 org+space 跨 user/agent 建树须 build profile 显式开启 | + +父节点通常写在 build 请求的 **tree home scope**(例如清空 `session` 的用户级归属,或 +策略开启时的 space 级归属);权威叶可仍驻留在更细的 session scope。因 id 只在完整 +Scope 内唯一,跨细粒度 scope 的边必须携带 `child_scopes` / `parent_scope`(见 S07), +缺省时仍表示与持有边的 unit 完整 Scope 相同。 + +跨 space 的「共享记忆」继续走 F03 的 grant / shared space,**不允许**用 `HierarchyRef` 穿越 +租户硬边界。 + +### 3. 血缘、版本与结构三分 + +三种引用表达不同事实,必须分离: + +| 载体 | 回答的问题 | 生命周期 | +|---|---|---| +| `provenance` | 这条记忆由哪些记忆抽取、升华或合成而来? | 随演进与可追溯性管理 | +| `supersedes` | 这个版本取代了哪个旧版本? | 随版本链和 valid-time 管理 | +| `HierarchyRef` | 这个节点结构上包含谁、隶属于谁? | 随建树、剪枝、重建与展开管理 | + +`evolve(HIERARCHY)` 可以作为建树调度入口,但其产物关系仍只写 +`HierarchyRef`。建树不意味着生成 `provenance`,结构重建不意味着 +`supersedes`,沿血缘追溯也不承担树展开。 + +### 4. 丰富实体复用 `MemoryUnit` 槽位 + +不同角色不新增各自的实体表。每个节点仍是一条完整 `MemoryUnit`,领域信息按语义 +进入既有或新增槽位: + +| 信息 | 槽位 | +|---|---| +| 节点身份 | `MemoryUnit.id` | +| 正文、叙述、父摘要 | `segments` 及其 `content` 合并视图 | +| 同 unit 压缩表示 | `ContentLayers.l0/l1`;L2 仍是 `MemoryUnit.content` | +| 认知角色 | `MemoryUnit.tier` | +| 结构身份、父子边、区间、顺序、状态 | `HierarchyRef` | +| 叶事件时间和双时间语义 | `MemoryUnit.temporal` | +| 设备、应用、标题、路径、模板、置信度、价值分等领域字段 | `metadata` | +| 主题分类 | `tags` | +| 原模态证据 | `segments[].assets` | +| 抽取或合成来源 | `provenance`,仅用于真实演进血缘 | +| 版本替换 | `supersedes` | + +这使丰富角色可以共享存储、索引、生命周期和披露能力,又不把领域字段提升为所有 +kind 都必须理解的核心类型。 + +首期推荐统一使用小写 snake_case metadata 键,TIME 叶可使用 `device_id`、`app`、 +`window_title`,`event` 父可使用 `event_type`、`template_id`、`confidence`, +DIRECTORY 节点可使用 `path`。这些键是领域投影,不是 `MemoryUnit` 一级字段; +construction 可以提供 pack/unpack 辅助,但不得让 common 类型依赖某一种 kind。 + +### 5. `HierarchyRole` 与 `MemoryTier` 只提供指导映射 + +role 表示树位,tier 表示认知角色,两者不做硬编码等价。默认建议如下: + +| role | 建议 tier | 理由 | +|---|---|---| +| `snapshot` | `EPISODIC` | 权威事件叶 | +| `time_span` | `EPISODIC` | 连续活动片段 | +| `scene` | `EPISODIC`;稳定抽象后可为 `SEMANTIC` | 场景回顾仍以情节为主 | +| `event` | `PROCEDURAL` | 表达任务流程或可复用模式 | +| `profile` | `CORE` | 稳定画像;不进入 TIME 主树,也不挂 TIME 节点为结构子 | +| `root` | `SEMANTIC` 或 `CORE` | 结构入口 | +| `node` | 由内容决定 | 通用 kind 不预设认知角色 | + +构建器可以按领域策略覆盖建议值,但不得用 tier 代替 role。 + +### 6. 写叶与构建父节点分离 + +普通 `write` 继续负责写入权威叶或调用方明确提供的单节点,不同步构建整棵树。 +叶可以没有 `HierarchyRef`,也可以显式标记为某个 kind 的叶角色。 + +父节点及父子边由显式或后台的 `evolve(HIERARCHY)` 构建。构建过程读取目标范围内 +的权威叶,生成父节点正文与可选 `ContentLayers`,写入有序子引用,并回写子节点 +的直接父引用。默认写路径保持轻量,也让父节点能够按区间重新推导。 + +公开的 write/evolve 参数、调度与返回结构分别由 +[S02-memory-api.md](../../specs/S02-memory-api.md)、 +[S03-control.md](../../specs/S03-control.md) 和 +[S05-construction.md](../../specs/S05-construction.md) 定义;本文不复制目标签名。 + +### 7. 演进模式不隐式混写 hierarchy + +普通 `write` 与 `EXTRACT/ASSOCIATE/CONSOLIDATE` 不因产生 unit、血缘或图关系而自动 +挂树;`HIERARCHY` 才负责创建或重建父节点和双向直接边。`FORGET` 必须同时断开遗忘 +节点的直接父边和全部直接子边:从父 `child_ids` 移除该节点、清空该节点 +`parent_id`,并清空其 `child_ids` 及所有直接子的对应 `parent_id`;这些节点保留且 +不发生级联删除。逐模式字段行为和删除顺序以 +[S05-construction.md](../../specs/S05-construction.md) 与 +[S03-control.md](../../specs/S03-control.md) 为准。 + +### 8. `replace_in_span` 以叶权威为边界 + +TIME 父节点会因切分策略、修正或新增叶而重算。`replace_in_span` 只替换与目标区间 +相交的派生父层及其索引,完整断旧边并一致挂新边,所有权威叶及其内容保持不变。 +区间边界不得留下半断开的双向引用。存储仍提供通用 CRUD,具体替换步骤、失败修复与 +事务边界由 +[S05-construction.md](../../specs/S05-construction.md)、 +[S03-control.md](../../specs/S03-control.md) 与 +[S06-storage.md](../../specs/S06-storage.md)。 + +替换区间若切过一个旧父节点中部,构建器只能扩大替换集至该旧父的完整覆盖范围, +或者在任何写入前拒绝请求;不得保留“半个旧父”。断开旧边后尚未重挂的子节点只清空 +`parent_id` 成为未挂接节点,仍是可检索、可再次建树的权威节点,不进入 FORGOTTEN, +也不因空父回收而被删除。 + +### 9. 检索支持按父侧 role 优先召回,再按需展开 + +层级检索分成两个阶段: + +1. **父层召回**:调用方按 kind、父侧 role、区间等结构条件筛选父节点,走现有混合 + 召回、融合、重排和阈值链路。`expand_depth=0` 只返回直接命中的父节点,不自动附带 + 子全文;省略 role 时同 kind 下所有活动角色均可参与,不再称为“只召回父节点”。 +2. **子树展开**:调用方或检索编排依据深度与预算,沿父节点有序 `child_ids` 点读 + 子节点;展开默认不跨 kind。 + +父优先使粗粒度摘要成为稳定入口,同时保留“先看概要、再取证据”的交互方式。 +叶命中向父上卷是可选策略,默认关闭,避免单个噪声叶把整棵父树带入候选。 +检索轨迹必须区分父层命中与子树展开阶段,并记录根节点、展开深度、返回节点数和预算 +截断原因,使父→子的证据路径可审计。 + +`RetrievedItem` 保持扁平,不嵌套 `child_ids` 或树容器。调用方可以在同一次 recall +中通过非零 `expand_depth` 展开,也可以先消费父结果,再独立调用 `expand()`。 +`rollup` 只把后代相关性传播到目标父角色,默认不展开后代;“父命中”“分数上卷” +和“内容展开”是三个可独立启用的动作。 + +层级过滤、展开和结果结构的精确公开契约已写入 +[S02-memory-api.md](../../specs/S02-memory-api.md) 与 +[S04-retrieval.md](../../specs/S04-retrieval.md)。 + +### 10. 分数传播与树级 token 预算独立于现有 Discloser + +父子结构新增两类跨节点决策: + +- **分数传播**:首期采用 MaxP,把父自身得分与相关子节点最高分合并;同一父下应有 + top-M 或阈值收敛,防止候选爆炸。其他传播算法留待后续基准验证。 +- **树级 token 预算**:作为逻辑上下文注入/节点准入预算,决定展开哪些子节点及每个 + 节点的主 `DisclosureLevel`;它不是严格的序列化响应大小上限。 + +现有 Discloser 的职责是对**单个 unit**选择或塑形 L0/L1/L2 内容。它不负责选择 +父子节点、跨节点分配预算或遍历子树。因此树预算分配器应先确定节点配额和披露级别, +再调用现有 Discloser;不能把跨节点行为伪装成同 unit 的 `ADAPTIVE` 披露。 +由于 `RetrievedItem` 始终返回 abstract/overview/content 全字段,实际响应可超过该逻辑 +预算;严格 wire-size 投影不在当前契约内。 + +### 11. TIME 是结构 kind,不是时间字段或召回通道 + +`HierarchyKind.TIME` 用树结构组织时间维度的多粒度记忆,典型角色顺序是: + +```text +event(可选森林根) + └─ scene + └─ time_span + └─ snapshot +``` + +TIME 的主要约束是: + +- `snapshot` 通常是权威叶,事件时刻使用 `MemoryUnit.temporal.t_event`; +- 区间父节点使用 `HierarchyRef.span_start/span_end` 表示覆盖范围; +- 直接子节点按时间稳定排序; +- `profile` 属于画像或主题组织,不进入 TIME 主树,也不得把 TIME 节点挂为结构子; +- 高层可重建,叶不可因父层重建被清除。 + +`MemoryUnit.temporal` 是双时间字段,`RecallChannel.TEMPORAL` 是检索中的时间过滤 +通道,二者都不等于 `HierarchyKind.TIME`。TIME 负责“谁在时间结构上包含谁”, +时间字段负责“何时发生、摄入、生效或失效”,通道负责“如何按时间约束召回”。 + +### 12. 多 kind 复用协议,避免新增结构轴 + +同一套父子协议还可表达: + +- `HierarchyKind.DIRECTORY`:`root`/`node` 组成路径浏览树; +- `HierarchyKind.TOPIC`:主题根与主题节点组织相关记忆; +- `HierarchyKind.CLUSTER`:聚类父节点包含成员节点; +- `HierarchyKind.CUSTOM`:由调用方或插件定义的包含结构。 + +每种 kind 可以拥有自己的构建策略和排序规则,但共享树校验、父优先召回、展开、 +分数传播与预算机制。首期默认单 kind 遍历;同一节点的多 kind、多父或图关系不做 +隐式合并,非包含关系继续由 GraphStore 表达。 + +### 13. 模块分解与实现顺序 + +树结构横跨七个内核模块,但每层只承担一种职责: + +| 模块 | 本特性职责 | 不承担的职责 | +|---|---|---| +| `common` | 公共枚举、`HierarchyRef`、codec、无副作用树校验 | 建树和存储事务 | +| `storage` | KV 真源、索引 metadata、scope 隔离 CRUD | 解释父子业务语义或级联 | +| `construction` | `HierarchyBuilder`、kind pipeline、父内容生成、索引更新 | 鉴权和召回 | +| `retrieval` | 结构过滤、Expander、MaxP、树预算和轨迹 | 建树和修复 | +| `control` | 策略闸门、任务调度、结构事务、ensure、生命周期联动 | kind 专属切分算法 | +| `api` | 参数装配、PEP、错误透传 | 数据面编排 | +| `ingest` | 把可信来源提示映射为无边叶身份 | 建父、查父或回写边 | + +实现依赖顺序固定为: + +```text +common → storage → construction → retrieval → control → api + ↑ ↑ + ingest --------------------┘ +``` + +这里表示类型和能力依赖,不表示所有代码必须串行开发。construction 不得反向依赖 +control 或 Scheduler;control 负责提交任务,construction 只执行构建请求。 +ingest 与 api 可以在公共契约稳定后并行实现。bootstrap 和 agent plugin 只做薄适配, +不承载内核建树算法。 + +结构事务的业务编排归 control:它负责 scope/kind/span 并发闸门、任务终态,以及 +update/delete/FORGET/SUPERSEDE 路径。construction 的 `HierarchyBuilder` 负责生成并 +校验候选子树,并通过不含鉴权和 Policy 的提交端口完成 KV/索引写入。control 调度 +evolve/replace 并以 `HierarchyBuildResult.complete` 判断终态;Builder 不读取运行时 +Policy,也不自行提交后台任务。 + +### 14. 构建层采用统一 Builder 加 kind pipeline + +`HierarchyBuilder` 是跨 kind 的统一构建入口,负责请求校验、pipeline 选择、 +结构校验、持久化和修复报告;kind 专属算法由可替换 pipeline 承担: + +```text +HierarchyBuilder +├─ TimeHierarchyPipeline +│ ├─ TimeSpanMerger +│ ├─ SceneSegmenter +│ └─ EventBuilder +├─ TopicHierarchyPipeline(后置) +├─ DirectoryHierarchyPipeline(后置) +└─ HierarchyMaintainer +``` + +每个 stage 接收同 kind、稳定排序、且满足决策 2b scope 规则的叶或中间节点集合,以及 +构建 span 和不可变构建选项;输出候选父 `MemoryUnit` 与待应用的直接边变更。stage 不直接 +鉴权、调度或提交存储事务,因而可以用内存输入做确定性单测。`HierarchyBuilder` 在所有 +stage 完成后统一验证整棵候选子树,再决定提交或返回错误。 + +`EvolveMode.HIERARCHY` 直接委托 `HierarchyBuilder`,不进入 EXTRACT/CONSOLIDATE 的 +Dedup 主路径。父摘要的内容去重可以作为以后独立策略加入,但不得让相似性判定改变 +树的单父、区间覆盖和稳定顺序。 + +`HierarchyMaintainer` 处理 dismiss、剪边、空父回收和显式修复。节点正文或 metadata +修改仍走既有 update,不新增“重命名”旁路。Maintainer 可以复用 Builder 的校验与 +提交器,但不重新执行内容派生算法;空父默认保留,只有明确策略才能退役,且永不级联 +删除权威叶。 + +精确的请求、结果和算子签名由 +[S05-construction.md](../../specs/S05-construction.md) 单点定义,本文只确定组件边界。 + +### 15. TIME 派生链及各 stage 逻辑 + +TIME pipeline 的输入是指定 span 内、`role=snapshot`、生命周期和结构状态均可用的权威叶。 +输入先按 `span_start`、`temporal.t_event`、请求中的稳定顺序排序;重复 id、跨 org/space、 +跨 kind 或区间非法在进入 stage 前拒绝。profile 未允许的跨 user/agent 同样拒绝。 + +```text +snapshot → TimeSpanMerger → time_span + → SceneSegmenter → scene + → EventBuilder → event +``` + +| stage | 输入 | 边界判定 | 输出内容 | +|---|---|---|---| +| `TimeSpanMerger` | 连续 snapshot | 设备/会话硬边界、配置的上下文键变化、事件间隔超过阈值时切断;其余相邻叶合并 | 一个连续活动片段,子为 snapshots | +| `SceneSegmenter` | 有序 time_spans | 明确上下文切换为硬边界;主题/任务相似度、最大持续时间和显式结束信号形成软边界 | 一个可回顾场景,子为 time_spans | +| `EventBuilder` | 有序 scenes | 按任务目标、动作序列和实体重合聚合;不得为了相似度打乱时间顺序或让 scene 多父 | 一个任务流程或可复用模式,子为 scenes | + +硬边界优先于任何语义相似度;软边界的阈值和特征组合属于 build profile,不写死在 +公共类型。算法必须确定性消费已排序输入:同样的输入、profile 和模型版本应产生相同 +分段顺序。LLM 可用于命名和摘要,但不能绕过硬边界或直接提交结构边。 + +各层字段生成遵循以下规则: + +| role | span | content / layers | tier | +|---|---|---|---| +| `snapshot` | 事件点可表示为起止相同 | 保留权威内容;不由 pipeline 改写 | 通常 `EPISODIC` | +| `time_span` | 直接 snapshot 区间并集 | 连续活动摘要,保留关键应用/标题等 metadata | 通常 `EPISODIC` | +| `scene` | 直接 time_span 区间并集 | 目标、关键动作、结果和证据摘要 | 通常 `EPISODIC`,稳定抽象后可为 `SEMANTIC` | +| `event` | 直接 scene 区间并集 | 任务模式、步骤和结果;可写 `event` 领域 metadata | 通常 `PROCEDURAL` | + +父 span 默认取直接子 span 的最小起点和最大终点,不得缩小到遗漏直接子。父正文先由 +stage 生成 segments,再由 `LayerAnnotator` best-effort 生成 `layers.l0/l1`。子 +`parent_id` 回写时不改子内容、tier、temporal、provenance 或 lifecycle。 + +`profile` 不进入 TIME 主链,也不得把 snapshot/time_span/scene/event 挂为 `profile` 的 +结构子节点。稳定画像应作为 `MemoryTier.CORE` 的独立 unit,或进入 TOPIC 结构;它与 +TIME 证据只通过 metadata 或真实演进来源弱连接。把 profile 设为 TIME 根或 TIME 父会把 +无界、持续更新的画像强行变成一个时间区间父,破坏 span 和局部重建语义。 + +首个可交付构建切片 P1 只要求 snapshot→time_span;scene 在 P2 加入,event 在 P3 +加入;pipeline 协议从一开始允许缺省后续 stage。 + +### 16. 字段填充、校验与持久化顺序 + +Builder 创建父节点时按下列顺序处理: + +```text +1. stage 生成候选父的 id、scope、role、span、segments、tier 和领域 metadata +2. LayerAnnotator best-effort 生成 l0/l1;失败保留空 layers +3. 组装候选 parent_id/child_ids 和对子节点的边变更 +4. 对完整候选子树校验 scope、kind、单父、无环、排序和 span 覆盖 +5. 写入新父 KV,并在同一结构提交中回写子 parent_id/旧父 child_ids +6. KV 成功后 build/update 内容层索引及 hierarchy metadata +7. 返回 created/updated/replaced/repair_required/complete +``` + +父节点 id 必须新生成;结构派生不写 `provenance`,除非该父正文确实通过既有演进算子 +由来源 unit 合成,且这条血缘在脱离层级关系后仍然成立。`metadata` 只接收该 kind +约定的领域键(见决策 4),不得覆盖 id、scope、temporal、lifecycle 或 hierarchy。 + +`replace_in_span` 在步骤 1 前先读取所有相交旧派生父并扩大替换边界,然后计算“旧边 +断开、旧父退役、新父写入、新边挂接”的完整变更集。只有新树整体可验证时才开始写。 +索引始终后于 KV;索引失败不会把索引提升为真源,但操作必须返回不完整状态并进入修复。 + +支持事务的 KV 后端应原子提交全部受影响 unit。不支持事务的后端采用可恢复顺序: +先持久化无活动边的新父,再按稳定顺序切换子边,最后退役旧父;任何中断返回 +`complete=false` 和 `repair_required`,任务不得标记成功。修复以 KV 中可见 unit +重新计算双向边和索引,不从旧索引反推真源。 + +### 17. 运行时 Policy 与不可变 build profile 分离 + +运行时 Policy 控制“是否执行”,build profile 决定“如何构建”: + +| 分类 | 内容 | 变更语义 | +|---|---|---| +| 运行时 Policy | 总开关、auto derive、ensure、MaxP、内部/接入形态默认展开深度、top-M | 可以治理时调整;不回写已有树 | +| build profile | kind、leaf role、parent role 序列、stage 启用、硬边界键、阈值、模型/提示版本 | 装配期固定;变更后通过显式 rebuild 生效 | + +首期所有运行时能力默认关闭:普通 write 和 recall 行为不变。`auto_derive` 只在叶成功 +写入且 profile 能确定有界 span 时提交 BACKGROUND 任务,不阻塞 hot path。 +`ensure_on_recall` 只服务显式 kind+有界 span 的召回,并阻塞等待构建终态,避免调用方 +请求“确保后召回”却拿到静默的无结构结果。 + +build profile 至少定义 `leaf_role`、从近叶到远叶的 `parent_roles` 和每个 stage 的 +算法配置。role 序列不放入可随时修改的 PolicyManager,避免运行中改变树形导致同一 +scope 出现两套半成品结构。profile 缺失时 ensure 抛 `PolicyError`,auto derive 记录 +跳过原因;两者都不得猜测默认 role 序列。 + +`hierarchy.expand_default_depth` 只供未显式给 depth 的内部或接入形态使用;公开 +recall 的默认值始终是 `expand_depth=0`,Policy 不得隐式改写该公开默认。 + +Policy 键、默认值和校验由 +[S03-control.md](../../specs/S03-control.md) 定义;构建请求字段由 +[S05-construction.md](../../specs/S05-construction.md) 定义。 + +### 18. 失败、降级与并发决策 + +| 场景 | 决策 | +|---|---| +| 父摘要或 layers 生成失败 | 保留结构候选,父 content 使用确定性规则摘要或最低可用拼接,layers 为空;记录诊断 | +| HIERARCHY 部分写入失败 | `complete=false` 并返回逐项 `repair_required`;任务状态不得为 SUCCEEDED | +| `replace_in_span` 中断 | 不删除权威叶;根据 KV 重算未完成边,修复前不宣称替换完成 | +| expand 遇到缺子、跨 kind、环或不可见节点 | 跳过该分支、记录 issue、`complete=false`;不让一个坏分支使所有有效结果失败 | +| ensure 任务失败、取消或超时 | recall 抛 `BackendError`,不降级为普通无层级召回 | +| auto derive 提交失败 | 不回滚已成功写入的叶;记录任务和审计错误 | + +同一 `scope + kind` 下存在重叠 span 的 build、replace、update、FORGET 或 PURGE 必须 +串行化,或者由后端乐观版本条件检测冲突。并发 write 可以先完成叶写入;若其 span 与 +正在替换区间相交,当前 replace 不能悄悄吸收未参与初始快照的叶,必须冲突重试或由 +后续增量任务补建。这样保证一次构建的输入快照和结果可解释。 + +`HierarchyRepair` 只报告结构差异,不借用 provenance trace。修复器重读当前 KV、 +重建期望双向边并重建派生索引;无法确定唯一父时停止并返回冲突,不凭 id 顺序猜测。 + +### 19. 分阶段落地与兼容边界 + +| 阶段 | 交付范围 | 进入下一阶段的条件 | +|---|---|---| +| P0 | 公共类型、codec、纯函数校验、索引 metadata | 旧数据兼容;环、跨 org/space、非法跨 user/agent、重复子和单 kind 多父被拒绝 | +| P1 | snapshot→time_span、结构提交器、`replace_in_span` | 可重复重建且叶内容零变化 | +| P2 | snapshot→time_span→scene、父侧召回、`expand(depth=1)`、MaxP | 默认 depth=0 不返回子全文;预算和轨迹确定 | +| P3 | event、ensure/auto derive、修复任务 | 失败状态和后台任务可观测 | +| P4 | 至少一种非 TIME kind | 复用同一校验、存储与展开协议 | +| P5 | Maintainer 修正流(dismiss/剪边/空父回收/修复)和性能优化 | 并发冲突与展开性能达到已设基线 | + +每个阶段都必须满足:`hierarchy.enabled=false` 时既有 write/evolve/recall 结果和错误语义 +不变;没有 `hierarchy` 的历史 `_v=2` 数据无需迁移即可读取;目标接口未启用时不得改变 +现有插件装配和 Store 抽象。 + +## 拒绝的方案 + +### 1. 用 `ContentLayers` 表示父子节点 + +拒绝。L0/L1/L2 是同一条 unit 的压缩表示,没有独立身份、生命周期或子证据集合。 +把结构角色映射为披露级别会破坏 F01 已确立的 same-unit compression 语义。 + +### 2. 用 `MemoryTier` 表示 `snapshot`、`scene` 等树位 + +拒绝。tier 表示认知角色,同一个 role 可以因内容不同选择不同 tier;父子节点的 tier +也可以不同。绑定两者会使目录、主题和聚类结构无法复用。 + +### 3. 扩展 `provenance` 承载父子关系 + +拒绝。演进来源与结构包含有不同的遍历方向、重建时机和治理语义。混用后,血缘追溯、 +版本治理、删除与展开都无法判断边的真实含义。 + +### 4. 首期直接采用独立边存储 + +拒绝作为首期默认。它能更自然地支持多父、多 kind 共节点和丰富边属性,但会增加 +新 Store、双写一致性与查询 join。在严格树 MVP 尚未验证前,这些成本没有足够收益。 + +### 5. 每个角色建立专用实体和存储 + +拒绝。专用表会复制 scope、生命周期、索引、披露和序列化能力,并把通用层级协议 +绑定到单一领域。丰富字段应优先复用 `MemoryUnit` 的结构化槽位。 + +### 6. 普通 write 同步自动建完整父树 + +拒绝作为默认。建树可能涉及区间读取、切分、聚类、摘要和多次写入,会扩大 hot path +时延,也使局部写入与全局重算耦合。显式或后台 `evolve(HIERARCHY)` 更符合父可重建、 +叶权威的边界。 + +### 7. 召回父节点时自动返回整棵子树 + +拒绝。无界展开会放大延迟与 token 消耗,也让调用方无法先看概要再决定是否取证。 +调用方显式按父侧 role 召回时,默认不展开;深度和预算必须显式控制。 + +### 8. 直接扩展现有 Discloser 负责整棵树预算 + +拒绝。Discloser 已有清晰的单 unit 披露职责。树遍历、节点选择与跨节点预算是独立 +问题,应在调用 Discloser 之前完成。 + +## 验证 + +目标设计和 specs 同步已完成,代码尚未实现,设计评审待完成。不得用现有 pytest +结果替代本特性的实现验证。分阶段验收如下: + +设计验收之后,实施验收依次对应决策 19 的 P0–P5:阶段 1 对应 P0,阶段 2 对应 P1, +阶段 3 对应 P2,阶段 4 对应 P3,阶段 5 对应 P4,阶段 6 对应 P5。每阶段只以本阶段 +及此前已经交付的能力作为门禁。 + +### 阶段 0:设计验收 + +- [x] 四轴术语在 features、specs 与总体设计中一致,无披露级、多模态粒度、tier、时间字段和结构 kind 混用。 +- [x] `HierarchyRef` 字段、兼容读取、错误语义和公开契约进入对应 specs。 +- [x] 明确首期单 kind 严格树边界,以及迁移到独立边存储的触发条件。 +- [ ] 完成设计评审。 + +### 阶段 1:模型与树一致性 + +- [ ] 旧数据缺少 hierarchy 时兼容读取为空结构。 +- [ ] 拒绝跨 org/space、profile 未允许的跨 user/agent、重复子、环和单 kind 多父。 +- [ ] 跨 session(及显式允许的跨 user)边携带可解析 `child_scopes`/`parent_scope`。 +- [ ] 父子双向引用、稳定排序与区间覆盖校验通过。 +- [ ] 索引可以从 KV 真源重建结构过滤 metadata。 + +### 阶段 2:构建与重建 + +- [ ] 普通 write 不自动构建父树,显式叶写入仍可工作。 +- [ ] `evolve(HIERARCHY)` 能建立最小父子树,其他演进模式不暗改结构字段。 +- [ ] `replace_in_span` 只替换相交派生父节点,不删除权威叶。 +- [ ] 父节点内容层在落盘和建索引前按 best-effort 策略生成或安全降级。 + +### 阶段 3:P2 构建、检索与预算 + +- [ ] snapshot→time_span→scene 可构建、可重复重建,且权威叶内容零变化。 +- [ ] 默认父层召回不自动包含子全文。 +- [ ] 展开按顺序、深度、kind 与 scope 约束返回子树切片。 +- [ ] 检索轨迹分别记录父层命中与展开阶段,并能解释展开深度和预算截断。 +- [ ] MaxP 与 top-M 收敛策略有确定性测试。 +- [ ] 树级预算先选节点与主 `level`,再由 Discloser 处理各节点的同 unit 披露。 +- [ ] `MemoryUnit.temporal`、`RecallChannel.TEMPORAL` 和 `HierarchyKind.TIME` 的过滤行为互不替代。 + +### 阶段 4:调度、策略与修复 + +- [ ] ensure 阻塞等待任务终态;失败、取消或超时抛 `BackendError`,不静默降级。 +- [ ] auto derive 不阻塞 write,提交失败不回滚已成功写入的叶。 +- [ ] `complete=false` 或存在 `repair_required` 时任务为 FAILED,修复项可观测。 +- [ ] build profile 变更只通过显式重建生效,不产生两套半成品 role 序列。 + +### 阶段 5:多 kind 与回归 + +- [ ] 至少一种非 TIME kind 复用相同树校验与展开协议。 +- [ ] hierarchy 关闭或字段为空时,既有 write/evolve/recall 行为保持兼容。 +- [ ] 完成相关单元、集成、序列化兼容、索引重建与性能基线测试。 + +### 阶段 6:修正流与性能 + +- [ ] dismiss、剪边、空父回收和显式修复不级联删除权威叶。 +- [ ] 重叠 span 并发冲突和展开性能达到已设基线。 +- [ ] `profile` 不进入 TIME 主树,也不把 TIME 节点挂为结构子。 + +### 实现测试矩阵 + +| 测试层 | 必测内容 | +|---|---| +| common 单测 | codec 缺字段/未知字段;空结构;无环、单父、双向一致、span 覆盖 | +| construction 单测 | 各 TIME stage 的确定性边界;LayerAnnotator 失败降级;replace 不触叶;repair 路径 | +| control 单测 | attach/detach/SUPERSEDE/FORGET 的结构事务;ensure 终态;Policy 关闭 | +| retrieval 单测 | depth=0;稳定展开顺序;MaxP/top-M;预算截断;坏分支 issue | +| storage 单测 | hierarchy metadata 投影、区间过滤、从 KV 重建索引 | +| 集成测试 | P2 起:write snapshots → HIERARCHY → recall scene → expand time_span/snapshot → replace span | +| 回归测试 | hierarchy 关闭、空结构和旧 codec 数据下既有路径零行为变化 | + +存储测试使用 in-memory Store;stage 算法使用固定 fixture 和规则 stub,不依赖在线 LLM。 +模型参与的命名、摘要和语义切分质量另设离线评测,不把非确定外部调用混入单元测试。 + +## 已知遗留 + +1. **独立边存储迁移阈值**:多父、多 kind 共节点和边属性复杂度达到何种规模时迁移, + 需要以真实查询与一致性成本评估。 +2. **并发一致性**:父子双向更新、`replace_in_span` 与并发 write 的后端事务能力和 + 故障恢复仍需实现验证。 +3. **父摘要生成质量**:不同 kind 的摘要器、失败降级与幂等性仍需实现阶段验证。 +4. **树预算策略**:多父候选间的公平性与深度偏置需要基准评测。 +5. **TIME 切分算法**:time_span、scene、event 的边界与置信度策略需要数据集和人工评审。 +6. **多 kind 交叉查询**:首期只保证单 kind 遍历,跨 kind 联合过滤与结果合并后置。 +7. **扩展分数传播**:衰减和等非 MaxP 算法需在真实浏览场景中验证后再进入契约。 + +F01 的同 unit 披露设计与实现历史见 +[F01-memory-layer.md](F01-memory-layer.md)。 diff --git a/docs/specs/S01-ingest-access.md b/docs/specs/S01-ingest-access.md index 725f34fd..e195b37a 100644 --- a/docs/specs/S01-ingest-access.md +++ b/docs/specs/S01-ingest-access.md @@ -5,9 +5,9 @@ | 项 | 值 | |---|---| | 关联模块 | src/ingest/ | -| 最近一次修订日期 | 2026-07-27 | +| 最近一次修订日期 | 2026-08-11 | -| 关联特性文档 | docs/features/F01-system-spec-design.md | +| 关联特性文档 | docs/features/F01-system-spec-design.md,docs/features/common/F07-memory-tree.md | ## 范围 / 边界 **管什么**: @@ -20,6 +20,7 @@ - 不负责落盘(真源写入由构建层调用 `src/storage` 完成) - 不做 LLM 调用(如需 caption/ASR 等,由注入的 Normalizer 插件内部调用) - 不做分类/索引/演进(由构建层负责) +- 不构建父节点,不维护任何既有父节点的 `child_ids` - 不做鉴权(由 `src/api` 层在入口执行) ## 不变量 @@ -29,10 +30,15 @@ 3. **接口与实现严格分离**:顶层 `.py` 是纯抽象,不 import `*_impl/`。`*_impl/` 通过 Producer 工厂被外部装配消费。 4. **所有算子必须实现 `operator_type()` 和 `health()`**:继承自 `IngestOperator`,自描述 + 存活探测。 5. **Source 只拉不规约**:Source 只产出 `RawPayload`,不做格式转换——规约归 Normalizer。 +6. **接入仅做转换**:默认产出的 `MemoryUnit.hierarchy` 为空。接入层不得创建父节点、 + 查询父节点或回写子列表。 +7. **scope 不被提示覆盖**:树结构叶提示只能修饰当前 payload 转换出的 unit,unit 的 + scope 始终来自 `RawPayload.scope`;接入层不得创建或回写任何 `HierarchyRef` 结构边 + (含跨 session/user 边);跨 org/space 引用尤其禁止。 ## 接口契约 -### IngestOperator(基类,`base.py`) +### IngestOperator(基类) ```python class IngestOperatorType(str, Enum): @@ -43,7 +49,7 @@ class IngestOperator(ABC): def health(self) -> None # 存活探测:健康返回 None,否则抛异常 ``` -### Source(`source.py`) +### Source 信息源连接器,对接一类外部数据源,把源数据拉取为统一的 `RawPayload`。 @@ -68,9 +74,41 @@ Source.fetch() → list[RawPayload] → 返回 list[MemoryUnit] ``` +### 层级叶提示(目标契约,尚未实现) + +Source adapter 可以在 `RawPayload.metadata` 中提供以下保留键: + +| 键 | 类型 | 语义 | +|---|---|---| +| `hierarchy_kind` | str | `time` / `topic` / `directory` / `cluster` / `custom` | +| `hierarchy_role` | str | 接入允许的叶角色:TIME 为 `snapshot`,其他 kind 为 `node`;完整枚举见 S07 | +| `hierarchy_span_start` | ISO 8601 str | 可选覆盖区间起点 | +| `hierarchy_span_end` | ISO 8601 str | 可选覆盖区间终点 | + +Ingestor 只允许把一组完整且有效的提示映射到当前 unit 的叶安全字段: +`kind`、`role`、`span_start`、`span_end`。映射后的 `parent_id` 必须为空, +`child_ids` 必须为空,`status` 使用 `ACTIVE`。未提供任何保留键时, +`hierarchy` 保持默认空结构。 + +校验是确定性的: + +1. kind/role 必须同时提供;区间必须同时提供或同时缺省。 +2. 枚举值必须精确匹配,时间必须可按 ISO 8601 解析,且起点不得晚于终点。 +3. `HierarchyKind.TIME` 必须提供区间;其他 kind 可省略区间。 +4. 接入提示只接受叶角色:TIME 只接受 `snapshot`;DIRECTORY、TOPIC、CLUSTER、CUSTOM + 只接受 `node`。`time_span`、`scene`、`event`、`profile`、`root` 等父侧 + 角色必须由构建层创建。 +5. `hierarchy_parent_id`、`hierarchy_child_ids` 或其他试图建立边的保留前缀键一律以 + `ValidationError` 拒绝,不作为普通 metadata 静默保留。 +6. 任一叶提示无效时拒绝该 payload 的转换,不产出半有效 `HierarchyRef`;非 + `hierarchy_` 前缀的 metadata 继续原样透传。 + +这些提示只是来源对当前 unit 结构身份的声明,不证明边存在。父子边只能由构建或控制 +契约在持久化阶段校验并维护。 + ## 数据结构 -### RawPayload(`common/type_def/raw.py`) +### RawPayload | 字段 | 类型 | 语义 | |------|------|------| @@ -82,26 +120,18 @@ Source.fetch() → list[RawPayload] | `metadata` | dict[str, Any] | 附加元数据;JSON 标量原生类型由接入链路透传 | | `occurred_at` | datetime \| None | 事件发生时间 | -### Modality(`common/type_def/memory.py`) +### Modality ``` TEXT / IMAGE / AUDIO / VIDEO / CODE / DOCUMENT ``` -## 实现注册机制 - -``` -src/ingest/source_impl/ - __init__.py # 重导出实现类 - .py # 具体实现 + 尾部 @SourceProducer.register("name") -``` - - ## 与其它 spec 的关系 | 关联 spec | 关系 | |-----------|------| -| S02-memory_api | MemoryAPI.write 触发控制层→本层的 Ingestor.ingest | -| S05-construction | 构建层接收本层产出的 MemoryUnit 做落盘+索引+演进 | -| S07-common | Normalizer/Tokenizer 等共享插件由本层消费 | -| architecture.md §10 | 多模态信息源接入与规约投影 | +| [S02-memory-api.md](S02-memory-api.md) | MemoryAPI.write 触发控制层→本层的 Ingestor.ingest | +| [S05-construction.md](S05-construction.md) | 构建层接收本层产出的 MemoryUnit 做落盘+索引+演进 | +| [S07-common.md](S07-common.md) | 定义 Normalizer、MemoryUnit 与层级叶提示映射后的公共类型 | +| [F07-memory-tree.md](../features/common/F07-memory-tree.md) | 接入只声明叶身份、不建立结构边的决策来源 | +| [architecture.md](../design/architecture.md) §5 | 多模态信息源接入与规约投影 | diff --git a/docs/specs/S02-memory-api.md b/docs/specs/S02-memory-api.md index 2f13339f..fced3bbd 100644 --- a/docs/specs/S02-memory-api.md +++ b/docs/specs/S02-memory-api.md @@ -5,22 +5,13 @@ | 项 | 值 | |---|---| | 关联模块 | src/api/ | -| 最近一次修订日期 | 2026-08-07 | -| 关联特性文档 | docs/features/F01-system-spec-design.md,docs/features/api/F01-memory-api-impl-design.md,docs/features/api/F02-write-infer-extract.md,docs/features/api/F03-batch-write-api.md,docs/features/construction/F02-dynamic-extraction-consolidation.md,docs/features/construction/F04-cc-memory-compat.md,docs/features/common/F03-scope-space-isolation.md,docs/features/retrieval/F03-metadata-filtering.md,docs/features/control/F04-permission-context-routing.md,docs/features/control/F05-cloud-engine-design.md,docs/features/config/F01-config-source.md | +| 最近一次修订日期 | 2026-08-11 | +| 关联特性文档 | docs/features/F01-system-spec-design.md,docs/features/api/F01-memory-api-impl-design.md,docs/features/api/F02-write-infer-extract.md,docs/features/api/F03-batch-write-api.md,docs/features/construction/F02-dynamic-extraction-consolidation.md,docs/features/construction/F04-cc-memory-compat.md,docs/features/construction/F05-construction-spec-multimodal-design.md,docs/features/common/F01-memory-layer.md,docs/features/common/F07-memory-tree.md,docs/features/common/F03-scope-space-isolation.md,docs/features/retrieval/F03-metadata-filtering.md,docs/features/control/F04-permission-context-routing.md,docs/features/control/F05-cloud-engine-design.md,docs/features/config/F01-config-source.md | ## 范围 / 边界 -**管什么**: -- 统一对外 Core API(形态无关):所有接入形态(SDK/CLI/Skill/MCP/HTTP·gRPC)最终映射到 `MemoryAPI` -- 鉴权执行点(PEP):调用 `PermissionManager.check(identity, scope, action)` 做入口鉴权 -- 入口审计:写审计事件到 `AuditLogger` -- 参数装配:将调用侧参数装配为控制层可消费的内部结构 -- 同步/异步桥接:为同步形态桥接引擎异步协程 +本规约定义形态无关的 `MemoryAPI`、入口鉴权、参数装配、返回类型和错误语义。树结构相关内容是**目标契约,尚未实现**;未使用目标参数时,现有调用保持兼容。 -**不管什么**: -- 不做编排逻辑(全部委托 `src/control`) -- 不直接操作存储 -- 不调用 LLM / 构建 / 检索 -- 不做 admin 策略存储(直达 PolicyManager) +API 层是薄封装和策略执行点(PEP),不编排检索、建树、删除修复或存储事务。数据面委托 `MemoryEngine`,治理面委托 `Governor`,任务面委托 `Scheduler`,策略面直达 `PolicyManager`。 ## 不变量 @@ -40,12 +31,112 @@ 12. **list 按实际资源二次鉴权**:请求显式给出的 `memory_types` 先做类型级鉴权;Engine 再以当前分页实际命中的 MemoryUnit 真源元数据返回权限上下文,API 逐条 READ 鉴权,全部通过后才返回内容。参与权限路由的 extensions 值必须作为系统过滤条件回注。 13. **list 过滤和计数在 KV 内完成**:API 复制 `extensions`、规范化 `filters` 后完整下推;返回 `MemoryListResult.items` 当前页和分页前精确 `count`,不以 `len(items)` 代替总数。 14. **六类动态配置不走业务入参**:能力开关、prompt 全文、LLM/Embedder/Reranker 的 model/api_key/url、Store 连接或 `*.active` 等由 `ConfigSource.fetch` 提供(见 S08);`write`/`recall`/`evolve`/`list` 不得把上述值解释为配置写入。调用侧可传 prompt **key**、`memory_type`/pipeline 等业务选择子。 +15. **层级能力默认关闭(目标)**:普通 `write` 默认不建父树;只由显式 `evolve(..., mode=HIERARCHY, hierarchy_options=...)` 或启用的后台策略触发。显式层级请求在 `hierarchy.enabled=false` 时抛 `PolicyError`,不带层级参数的既有操作保持语义。 +16. **三类遍历严格分离(目标)**:`trace` 只沿 `provenance`,`expand` 只沿 `HierarchyRef`,`get(as_of)` 只沿 `supersedes`/valid-time;L0/L1/L2 仅表示同一 unit 的披露层。 +17. **内容、媒体与 TIME 结构正交**:`ContentLayers` 的 L0/L1 只表示同一 `MemoryUnit` 的披露;多模态 CLM/ELM 是以 `provenance` 和 `metadata` 标记的媒体构建产物,不使用 `HierarchyRef`;目标 TIME 树只表达跨 unit 结构包含。视频 CLM/ELM 可先完成多模态召回,再作为 TIME 树叶节点;这些目标能力不得改变现有 `FilterExpr`、五维 scope、`space_id`、批量写入、`ConfigSource` 或统一 Storage 契约。 ## 接口契约 -### MemoryAPI(`memory_api.py`) +### 数据面 -#### 数据面(委托 MemoryEngine) +| 方法 | 签名 | 权限与语义 | +|---|---|---| +| `write` | `(content, scope, source=TEXT, *, identity, assets=None, tags=None, metadata=None, occurred_at=None) -> list[MemoryUnit]` | `WRITE`;同步桥接 hot path。默认不建父树 | +| `write_async` | `async (同 write 参数) -> list[MemoryUnit]` | `WRITE`;异步语义与 write 相同 | +| `recall` | 见下文完整签名 | `READ`;装配 `RetrievalQuery` 并委托 Engine | +| `expand`(目标) | 见下文完整签名 | `READ`;对一个已知 root 做单 kind 结构展开 | +| `get` | `(unit_id, scope, *, identity, as_of=None) -> MemoryUnit` | `READ`;点读或按 valid-time 取版本 | +| `update` | `(unit_id, scope, patch: MemoryPatch, *, identity) -> MemoryUnit` | `UPDATE`;内容/版本修正及受控层级 patch | +| `delete` | `(selector: DeleteSelector, *, identity) -> list[str]` | `DELETE`;生命周期操作或物理删除 | +| `evolve` | 见下文完整签名 | `WRITE`;提交演进或层级构建任务 | + +### recall + +```python +def recall( + query: str, + context: Context, + *, + identity: Scope, + filters: FilterExpr | list[FilterClause] | None = None, + as_of: datetime | None = None, + top_k: int = 10, + disclosure: DisclosureLevel = DisclosureLevel.L0, + with_trajectory: bool = False, + hierarchy_kind: HierarchyKind | None = None, + hierarchy_role: HierarchyRole | None = None, + span_start: datetime | None = None, + span_end: datetime | None = None, + expand_depth: int = 0, + expand_budget_tokens: int | None = None, + rollup: bool = False, +) -> RetrievalResult: ... +``` + +`filters/as_of/top_k/disclosure/with_trajectory` 和 `context.extensions["max_tokens"]` 的既有处理不变。新增参数原样装配到 `RetrievalQuery`。`expand_depth=0`、`rollup=false` 保证默认只返回直接召回命中的节点,不展开后代、不把后代分数上卷;调用方通过 `hierarchy_role` 指定父侧角色时,即形成父节点优先召回。省略 role 时,同 kind 下所有活动角色均可参与召回。span 是 `HierarchyRef` 结构区间,不替代查询文本解析得到的 event-time。 + +校验和闭区间相交语义以 [S04-retrieval.md](S04-retrieval.md) 为准。任一显式 hierarchy 参数、非零展开深度或 rollup 都构成层级请求;功能关闭时抛 `PolicyError`。普通 recall 不因 hierarchy 关闭而失败。 + +### expand(目标契约,尚未实现) + +```python +def expand( + unit_id: str, + scope: Scope, + *, + identity: Scope, + kind: HierarchyKind, + depth: int = 1, + disclosure: DisclosureLevel = DisclosureLevel.L1, + budget_tokens: int | None = None, + with_trajectory: bool = True, +) -> ExpandResult: ... +``` + +`identity` 是调用方身份,必须以 `Action.READ` 对 target `scope` 做 PEP 校验;通过后仅把 scope 与 `ExpandRequest` 下沉。root 不存在或实际属于 scope 外时统一抛 `NotFoundError`,不得暴露跨 scope 存在性。`depth >= 1`,非空 `budget_tokens > 0`。展开只沿指定 `kind`,默认不跨 kind、不返回 root;预算是节点准入与主披露级的逻辑上下文注入预算,不是严格响应大小上限。完整顺序、issue、预算和轨迹语义见 S04。 + +S04 的 `ExpandRequest.include_archived` 与 `query` 是检索编排内部字段,不扩展本公开 +方法:独立 `MemoryAPI.expand` 固定装配 `include_archived=false`、`query=None`。 +带 query 的计分展开只由 `recall` 内部产生,不能通过公开 expand 伪造相关性分数。 + +### evolve + +```python +def evolve( + scope: Scope, + mode: EvolveMode, + channel: Channel = Channel.BACKGROUND, + *, + identity: Scope, + hierarchy_options: HierarchyBuildOptions | None = None, +) -> str: ... +``` + +所有 evolve 模式要求 `Action.WRITE`。`EvolveMode.HIERARCHY` 是目标新增,必须提供 +[S05-construction.md](S05-construction.md) 定义的 `HierarchyBuildOptions`。S05 是该 +类型字段与默认值的唯一契约来源,API 层不复制定义。 + +仅 HIERARCHY 接受 `hierarchy_options`;其他模式提供 options 时抛 `ValidationError`。 +HIERARCHY 缺 options 或 options 违反 S05 的 span、role 序列、`replace_existing` +约束时抛 `ValidationError`。功能关闭时抛 `PolicyError`。调用成功返回 job id, +不表示任务已经完成。 + +### update 与层级 patch(目标契约,尚未实现) + +`MemoryPatch` 的既有非空字段为 `content/tier/tags/metadata/t_valid/t_invalid/mode`,目标增加: + +```python +hierarchy: HierarchyPatch | None = None +``` + +`HierarchyPatch` 的精确类型由 S03 定义。它只允许修改指定 kind 的结构状态、span、 +受控子边和稳定顺序;不得接受未校验的完整 `HierarchyRef`、裸 `parent_id` 或任意 +`child_ids` 覆写。API 仅做形状校验,Engine 必须按 S03 验证同 org+space、无环、单父、 +双向一致和稳定顺序后原子应用。 + +`SUPERSEDE` 若目标已挂树,Engine 必须把结构位置从旧 id 一致迁移到新版本 id,再把旧版本设为 `SUPERSEDED`;不得留下指向旧版本的活动结构边。`OVERWRITE` 保持 id,但层级 patch 仍须经过相同校验。 + +### 任务、治理、授权和策略 | 方法 | 签名 | 语义 | |------|------|------| @@ -53,12 +144,13 @@ | `write_async` | `async (同签名) -> list[MemoryUnit]` | 异步写入:直通 Engine 协程,供事件循环形态使用 | | `batch_write` | `(items: list[BatchWriteItem], scope=None, source=TEXT, *, identity, tags, metadata, occurred_at, stream_id="", continue_on_error=True) -> BatchWriteResult` | 同步桥接批量写入;逐项归一化、WRITE 鉴权、space 校验与审计,结果始终按输入索引对齐 | | `batch_write_async` | `async (同签名) -> BatchWriteResult` | 串行保序批量写入;默认归集单项错误,`continue_on_error=False` 时后续项为 `Skipped` | -| `recall` | `(query, context: Context, *, identity, filters, as_of, top_k, disclosure, with_trajectory) -> RetrievalResult` | 混合检索:鉴权 READ→拆 Context→装配 RetrievalQuery→委托 Engine | +| `recall` | 见上文完整签名 | 混合检索:鉴权 READ→拆 Context→装配 RetrievalQuery→委托 Engine;层级字段为目标、默认关闭 | +| `expand`(目标) | 见上文完整签名 | 只沿指定 kind 的父子结构展开 | | `list` | `(scope, *, identity, offset=0, limit=100, memory_types=None, extensions=None, filters=None) -> MemoryListResult` | 列出已建索引记忆:支持类型/FilterExpr 过滤、自定义参数透传和分页前精确总数;只返回 `/memory/` 真源记录 | | `get` | `(unit_id, scope, *, identity, as_of=None) -> MemoryUnit` | 真源点读:鉴权 READ→委托 Engine | | `update` | `(unit_id, scope, patch: MemoryPatch, *, identity) -> MemoryUnit` | 修正记忆:鉴权 UPDATE→委托 Engine | | `delete` | `(selector: DeleteSelector, *, identity) -> list[str]` | 删除/归档/降权:鉴权 DELETE→委托 Engine | -| `evolve` | `(scope, mode: EvolveMode, channel=BACKGROUND, *, identity) -> str` | 触发演进:鉴权→委托 Engine→返回 job_id | +| `evolve` | `(scope, mode: EvolveMode, channel=BACKGROUND, *, identity, hierarchy_options=None) -> str` | 触发演进:鉴权→委托 Engine→返回 job_id;仅目标 `HIERARCHY` 接受 options | | `job_status` | `(job_id, *, identity) -> JobInfo` | 查询任务状态(委托 Scheduler) | | `job_cancel` | `(job_id, *, identity) -> None` | 取消任务(委托 Scheduler) | | `admin_get` | `(key, *, identity) -> str` | 读策略(直达 PolicyManager) | @@ -161,28 +253,31 @@ ## 数据结构 -### Scope —— 目标范围 vs 调用方身份(`common/type_def`) +### Scope 与 Context `org > space > user/agent > session` 五维归属,同时支撑隔离与共享。各维默认 `""`。API 里 `Scope` 出现在两个**不同语义**的位置(均为 `Scope` 类型,勿混淆): +target scope 表示操作对象,公开 PEP 参数统一为 `identity`。审计数据结构 `AuditEvent` 的字段仍可命名为 `actor`,但它不是公开 API 参数名。`Context.scope` 是 recall target;`Context.extensions` 只承载字符串值。`max_tokens` 解析成功后从透传 extensions 移除。 -- **目标范围(target)**:操作作用于「谁的」记忆——`scope` 参数(或 `Context.scope` / `DeleteSelector.scope`)。 -- **调用方身份(identity)**:「谁」在发起调用——`identity` 参数(必填 keyword-only)。 +### MemoryUnit / Segment `space` 是全局唯一的逻辑隔离标识,`org` 表示其归属组织并继续参与权限边界;不同 org 不能创建相同的非空 space id。空 `space` 只表示兼容旧数据/单租户默认域,不参与 Space 资源注册,也不表示跨全部 space。`space` 为 keyword-only 字段,旧四段位置参数顺序仍是 `org/user/agent/session`。 -### Context(`common/type_def/context.py`) +API 输入输出直接复用 S07 的 `MemoryUnit`、`Segment`、`ContentLayers` 和目标 +`HierarchyRef`,不在本 spec 复制字段表。API 必须保持 +`MemoryUnit.content/assets/source` 的既有折叠只读视图;`layers.l0/l1` 不是 +`segments`,也不是父子节点,`MemoryUnit.temporal` 也不是 TIME 层级。精确字段、 +默认值与编解码契约见 [S07-common.md](S07-common.md)。 -| 字段 | 类型 | 默认 | 语义 | -|------|------|------|------| -| `scope` | Scope | 空 Scope | 检索目标范围(多租户隔离) | -| `extensions` | dict[str, str] | `{}` | 调用方自定义透传配置,值须为传输安全的 str;约定 key `"max_tokens"` 表示自适应披露 token 预算,由 API 边界解析为 `RetrievalQuery.max_tokens` | +### 检索返回 -> `extensions["max_tokens"]` 是 API 边界解释的约定 key,解析后从透传 extensions 中移除;无此 key 或空串时披露阶段使用默认策略。 +`RetrievedItem` 的当前精确字段是: -### MemoryUnit / Segment(读取类方法返回,`common/type_def/memory.py`) +```text +unit_id, score, abstract, overview, content, level +``` | 字段 | 类型 | 语义 | |------|------|------| @@ -198,7 +293,9 @@ | `metadata` | dict[str, Any] | 业务元数据;保留 JSON 标量原生类型,也可使用字符串数组 | | `lifecycle` | LifecycleState | 生命周期状态 | -`Segment`:`content`(可治理文本/结构投影,索引与检索对象)、`assets`(本段原模态资产引用)、`source`(本段来源 Modality)。便捷只读折叠属性:`unit.content`(各段换行连接)、`unit.assets`(各段扁平合并)、`unit.source`(首段模态)——返回新对象,勿就地 `append`。 +`abstract/overview/content` 分别承载同 unit 的 L0/L1/L2 表示。层级展开返回独立 `ExpandResult.items: list[RetrievedItem]`,不修改 `RetrievedItem` 结构。 + +### 其他公共类型 写入和更新边界对 metadata 执行以下约束: @@ -209,9 +306,20 @@ ### MemoryPatch / UpdateMode(update,`control/types.py`) -`MemoryPatch` 仅**非 None** 字段生效:`content` / `tier` / `tags`(整体替换)/ `metadata`(合并)/ `t_valid` / `t_invalid` / `mode`。 +- `DeleteSelector` 条件取 AND,至少包含 `unit_ids/scope/tags/before` 之一;模式为 FORGET/ARCHIVE/DOWNWEIGHT/PURGE。 +- `FilterExpr` 支持 `FilterClause` / `FilterGroup` 的递归 AND/OR/NOT;旧 `list[FilterClause]` 兼容为隐式 AND,scope 不进入 filters。 +- `DisclosureLevel` 为 L0/L1/L2/ADAPTIVE;ADAPTIVE 的实际行为见 S04。 +- `EvolveMode` 既有 EXTRACT/ASSOCIATE/CONSOLIDATE/FORGET,目标增加 HIERARCHY。 +- `HierarchyKind`、`HierarchyRole` 与 `HierarchyStatus` 的精确成员由 S07 单点定义。 + +## 鉴权与审计 -`UpdateMode`:`SUPERSEDE`(默认、非破坏式,新 id 新版本 + 旧版标 superseded)/ `OVERWRITE`(原地覆写、同 id,旧内容仅留审计)。 +```text +MemoryAPI.method(target_scope, identity=caller_identity) +→ PermissionManager.check(actor=caller_identity, target=target_scope, action=required_action) +→ 通过:仅下沉 target scope;拒绝:PermissionDeniedError +→ 记录入口审计事件 +``` ### DeleteSelector / DeleteMode(delete,`control/types.py`) @@ -291,17 +399,17 @@ scope 不走 filters。metadata 比较严格保留类型:number、string、boo `id` / `actor`(操作者 Scope)/ `target`(目标 Scope)/ `action` / `target_id` / `layer`(产生事件的层)/ `occurred_at` / `detail`。`detail` 常见约定包括 `permission_check`、`permission_reason`、`job_id`、`before_unit_id` / `after_unit_id`、`before_unit_ids` / `after_unit_ids`;其中 `before_unit_*` / `after_unit_*` 仅表示记忆单元 id,不用于调度任务 id。审计查询支持 `actor_*` 与 `target_*` scope 字段过滤。 -## 错误语义 +`recall/get/expand/inspect/trace` 使用 READ,`write/evolve` 使用 WRITE,`update` 使用 UPDATE,`delete` 使用 DELETE,`grant/revoke` 使用 SHARE。未限定 target scope 的 delete 和全局管理操作退到根 scope 闸门。 -均继承自 `common.errors.AgentMemoryError`: +## 错误语义 | 异常 | 触发场景 | |------|----------| | `PermissionDeniedError` | 鉴权不通过(identity 对 target scope 无相应 Action 权限) | -| `NotFoundError` | `get` 等按 id 读取但记忆不存在 | -| `ValidationError` | 入参非法(如 `recall` 的 `top_k <= 0`;`write`/`batch_write` 的 `content` 非 `str`、空串或纯空白) | -| `PolicyError` | `admin_set` 的键未知或为不可变配置 | -| `ConflictError` | 写入冲突(如 id 重复) | +| `NotFoundError` | `get`、`update` 或目标 `expand` root 在已鉴权 scope 内不可见 | +| `ValidationError` | 入参非法(如 `recall` 的 `top_k <= 0`;`write`/`batch_write` 的 `content` 非 `str`、空串或纯空白;目标层级的 span、深度、预算、options 或 patch 形状非法) | +| `PolicyError` | `admin_set` 的键未知或为不可变配置;层级功能关闭时发起显式目标层级操作 | +| `ConflictError` | 写入冲突(如 id 重复)或目标结构前置条件与当前状态冲突 | | `BackendError` / `HealthCheckError` | 后端故障 / 健康探测失败 | ## 鉴权流程 @@ -322,6 +430,8 @@ src/api/memory_api_impl/ __init__.py # 重导出实现类 ``` +参数默认值不得隐式打开层级能力:`recall` 默认不展开、不 rollup;`write` 默认不建树;普通 evolve 不携带 hierarchy options。 + ## 与其它 spec 的关系 | 关联 spec | 关系 | @@ -329,5 +439,8 @@ src/api/memory_api_impl/ | S01-ingest_access | write 路径中 Engine 内部调用 Ingestor | | S03-control | 数据面委托 MemoryEngine,治理/授权/调度面委托对应算子 | | S04-retrieval | recall 路径中 Engine 委托 Retriever | +| S05-construction | 目标 HIERARCHY options 与构建结果 | +| S07-common | `MemoryUnit`、`HierarchyRef`、枚举和错误类型 | | S08-config | 六类动态配置经 ConfigSource;不经本层业务入参写入 | +| F07-memory-tree | 树结构公开接口的决策来源 | | architecture.md §9 | 记忆接口层语义定义 | diff --git a/docs/specs/S03-control.md b/docs/specs/S03-control.md index f5557ed6..b14c1f81 100644 --- a/docs/specs/S03-control.md +++ b/docs/specs/S03-control.md @@ -5,8 +5,8 @@ | 项 | 值 | |---|---| | 关联模块 | src/control/ | -| 最近一次修订日期 | 2026-08-05 | -| 关联特性文档 | docs/features/F01-system-spec-design.md,docs/features/api/F01-memory-api-impl-design.md,docs/features/api/F02-write-infer-extract.md,docs/features/api/F03-batch-write-api.md,docs/features/construction/F02-dynamic-extraction-consolidation.md,docs/features/construction/F04-cc-memory-compat.md,docs/features/control/F02-control-isolation-and-audit.md,docs/features/control/F03-control-pipeline-routing.md,docs/features/control/F04-permission-context-routing.md,docs/features/control/F05-cloud-engine-design.md,docs/features/common/F03-scope-space-isolation.md,docs/features/retrieval/F03-metadata-filtering.md,docs/features/config/F01-config-source.md | +| 最近一次修订日期 | 2026-08-11 | +| 关联特性文档 | docs/features/F01-system-spec-design.md,docs/features/api/F01-memory-api-impl-design.md,docs/features/api/F02-write-infer-extract.md,docs/features/api/F03-batch-write-api.md,docs/features/construction/F02-dynamic-extraction-consolidation.md,docs/features/construction/F04-cc-memory-compat.md,docs/features/control/F02-control-isolation-and-audit.md,docs/features/control/F03-control-pipeline-routing.md,docs/features/control/F04-permission-context-routing.md,docs/features/control/F05-cloud-engine-design.md,docs/features/common/F01-memory-layer.md,docs/features/common/F07-memory-tree.md,docs/features/common/F03-scope-space-isolation.md,docs/features/retrieval/F03-metadata-filtering.md,docs/features/config/F01-config-source.md | ## 范围 / 边界 **管什么**: @@ -26,6 +26,7 @@ - 不生产记忆(由 `src/ingest` + `src/construction` 负责) - 不执行检索(由 `src/retrieval` 负责) - 不管不可变/重型配置(由 `src/config` 在实例初始化时确定) +- 不实现建树算法、检索评分算法或存储后端;树结构相关内容是**目标契约,尚未实现** ## 不变量 @@ -49,6 +50,10 @@ 系统过滤谓词;routing fallback 必须是最小权限策略,不得使用 `allow_all`。 16. **目标操作使用完整 Scope**:MemoryUnit id 仅在 Scope 内唯一。LifecycleManager、Governor 与 IndexBuilder 的目标修改/读取/删除不得依赖全局 `id -> scope` 猜测,调用方必须显式提供 Scope 或携带 Scope 的 MemoryUnit。 17. **Engine 部署边界明确**:`InMemoryEngine` 只接受空 `space` 兼容域;具名非空 space 的数据面操作使用 `CloudEngine`。`CloudEngine` 仍兼容空 space,但生产多租户配置应开启 `scope.require_space=true`。 +18. **普通 write 默认不建树(目标)**:`hierarchy.auto_derive` 是独立、默认关闭的后台策略;显式层级 recall/expand/evolve/update 在 `hierarchy.enabled=false` 时抛 `PolicyError`,普通非层级行为兼容。 +19. **树结构一致性(目标)**:同一 kind 的父子边必须同 `org+space`、无环、单父、双向一致且顺序稳定;`user`/`agent`/`session` 可按 build profile 放宽(跨细粒度 scope 时边须可解析定位);`HierarchyStatus` 只允许 ACTIVE/DISMISSED/PENDING_CONFIRM,且与 `LifecycleState` 分离。 +20. **结构与生命周期事务(目标)**:`provenance`、`supersedes` 与 `hierarchy` 分别表示演进来源、版本替换和父子包含;FORGET/PURGE 不级联删除后代内容。 +21. **重叠 span 串行化(目标)**:同一 `scope + kind` 下 span 相交的 HIERARCHY build/replace、层级 update、FORGET 和 PURGE 必须串行化,或以乐观版本条件在提交前检测冲突;replace 不得吸收未参与初始输入快照的并发叶写入。 ## 接口契约 @@ -73,14 +78,15 @@ class ControlOperator(ABC): | `batch_write` | `async (items: list[BatchWriteItem], *, continue_on_error=True) -> BatchWriteResult` | 只接收 API 已归一化并完成鉴权/space 前置校验的项;按输入顺序复用 `write`,归集领域异常及非领域异常(后者为 `InternalError`);fail-fast 时填充 `Skipped` outcomes | | `recall` | `async (scope, query: RetrievalQuery) -> RetrievalResult` | 委托 Retriever 完整检索链路 | | `list` | `async (scope, *, offset=0, limit=100, memory_types=None, extensions=None, filters=None) -> MemoryListResult` | 校验分页参数并完整委托 `KVStore.list`;返回当前页和分页前匹配总数 | +| `expand`(目标) | `async (scope: Scope, request: ExpandRequest) -> ExpandResult` | 校验 scope/策略后委托 Expander;不接受身份参数,身份已由 API PEP 消费 | | `permission_context_for_unit` | `async (unit_id, scope) -> PermissionContext` | 读取已有记忆的权限上下文,只返回 memory_type/tags/metadata 等鉴权元数据,不返回 content/assets | | `list_with_permission_contexts` | `async (同 list 参数) -> tuple[MemoryListResult, list[PermissionContext]]` | 从同一次 KV 查询的当前页构造逐项真源权限上下文,items/count/context 不做二次读取 | | `permission_contexts_for_delete` | `async (selector: DeleteSelector) -> list[PermissionContext]` | 解析 delete selector 命中的候选 unit 权限上下文,供 API 层逐条鉴权 | | `get` | `async (unit_id, scope, as_of=None) -> MemoryUnit` | 真源点读;`as_of` 非空沿 supersedes 链返回当时有效版本 | -| `update` | `async (unit_id, scope, patch: MemoryPatch) -> MemoryUnit` | SUPERSEDE 新 id 记版本链 / OVERWRITE 原地覆写 | -| `delete` | `async (selector: DeleteSelector) -> list[str]` | PURGE 物理删 / 其他委托 LifecycleManager 非破坏式流转 | +| `update` | `async (unit_id, scope, patch: MemoryPatch) -> MemoryUnit` | SUPERSEDE 新 id 记版本链 / OVERWRITE 原地覆写;目标层级 patch 走结构事务 | +| `delete` | `async (selector: DeleteSelector) -> list[str]` | PURGE 物理删 / 其他委托 LifecycleManager 非破坏式流转;目标需维护受影响层级边 | | `purge_space` | `async (org: str, space: str) -> list[str]` | 物理删除该 Space 全部 user/agent/session 子 Scope 的 MemoryUnit 真源与索引,供 offboarding 调用 | -| `evolve` | `async (scope, mode: EvolveMode, channel=BACKGROUND) -> str` | 提交演进任务到 Scheduler;执行逻辑由构建层 Evolver 完成,返回 job_id | +| `evolve` | `async (scope, mode: EvolveMode, channel=BACKGROUND, *, hierarchy_options=None) -> str` | 提交演进任务到 Scheduler;执行逻辑由构建层 Evolver 完成,返回 job_id;仅目标 HIERARCHY 接受 options | | `admin_get/set/all` | — | 管理面语义由 API 层直达 PolicyManager,Engine 不承载策略存储 | **write 路径**: @@ -102,6 +108,76 @@ Ingestor.ingest([RawPayload]) → list[MemoryUnit] 返回 units ``` + +### 树结构目标扩展(尚未实现) + +普通 write 默认不建父树。`hierarchy.auto_derive=false` 时不提交任何层级任务。启用后,write 在不可变构建配置已有 build profile 且本批叶可确定有界 span 时,必须在成功返回后向 BACKGROUND 通道提交 HIERARCHY 任务,并固定组装 `replace_existing=true`;条件不足时不提交,并记录跳过原因。提交失败只记录任务/审计错误,不回滚已经成功的权威叶写入。auto derive 不得改成阻塞 hot path,也不得推断未配置的 kind、role 或无界 span。 + +#### ensure_hierarchy + +策略名统一为 `hierarchy.ensure_on_recall`,默认 `false`。只有同时满足以下条件才允许 ensure: + +1. `hierarchy.enabled=true`; +2. `hierarchy.ensure_on_recall=true`; +3. recall 显式提供一个 `hierarchy_kind`; +4. `span_start` 与 `span_end` 成对、有效且有界。 + +每个可 ensure 的 kind 还必须在不可变构建配置中存在 S05 定义的 `HierarchyBuildProfile`。profile 按 kind 唯一查找,提供 `leaf_role/parent_roles` 和 stage options;请求提供 kind+span,Engine 据 profile 组装完整 `HierarchyBuildOptions`,并固定 `replace_existing=true`。缺少 profile 时抛 `PolicyError`。 + +本规约选择**阻塞式 ensure**:Engine 在父层召回前同步提交对应 kind+span 的 HIERARCHY 构建,并等待任务进入终态。SUCCEEDED 且 `complete=true` 后才执行 recall;FAILED、CANCELLED、修复未完成或超过调度器配置的等待期限均抛 `BackendError`。功能关闭时显式层级请求抛 `PolicyError`,不得悄悄退化为无层级结果。该行为只针对明确的有界层级 recall;普通 recall、无 span 的层级 recall 和独立 `expand` 从不触发 ensure。 + +当 `ensure_on_recall=false` 时,显式层级 recall 只查询当前已有结构,不自动构建;合法的空结果仍返回空结果。 + +#### HierarchyPatch + +```python +class HierarchyEdgeOp(str, Enum): + ATTACH = "attach" + DETACH = "detach" + +@dataclass +class HierarchyEdgeChange: + op: HierarchyEdgeOp + child_id: str + expected_parent_id: str = "" + +@dataclass +class HierarchySpanPatch: + span_start: datetime | None + span_end: datetime | None + +@dataclass +class HierarchyPatch: + kind: HierarchyKind + status: HierarchyStatus | None = None + span: HierarchySpanPatch | None = None + edge_changes: list[HierarchyEdgeChange] = field(default_factory=list) + child_order: list[str] | None = None +``` + +patch 以被 update 的 `unit_id` 为父节点。`ATTACH` 把 child 挂到该父;若 child 已有父,`expected_parent_id` 必须精确匹配旧父,Engine 才能在同一事务中从旧父移除并改挂。`DETACH` 要求 child 当前父为该 unit;`expected_parent_id` 为空或等于该 unit,否则 `ConflictError`。 + +`child_order` 若提供,必须恰好是应用全部 edge_changes 后的完整直接子集合,无重复、无缺失。未提供时保留未变子节点的相对顺序,DETACH 删除原位置,ATTACH 按请求顺序追加。TIME 结构最终按 span 起点、事件时间和稳定次序校验;其他 kind 按 `ordinal` 和领域稳定顺序校验。 + +`span=None` 表示不修改区间;`HierarchySpanPatch(None, None)` 表示清除区间,仅非 TIME kind 允许;其他组合必须同时给出起止且起点不晚于终点。 + +Engine 在写入前必须一次性加载所有受影响 unit 并验证:同 org+space、同 kind、单父、无环、双向一致、span 有效且 TIME 必填;跨细粒度 scope 时 `child_scopes`/`parent_scope` 可解析。调用方不得通过 `metadata`、完整 `HierarchyRef`、裸 `parent_id` 或裸 `child_ids` 绕过该接口。校验失败不写任何 unit。 + +`UpdateMode.SUPERSEDE` 对已挂树 unit 生成新 id 后,必须在同一结构事务中把父列表中的旧 id 替换为新 id,并把直接子的 `parent_id` 改为新 id,位置不变;随后旧 unit 进入 SUPERSEDED。`OVERWRITE` 保持 id。 + +#### delete 与层级边 + +删除选择器先解析完整命中集,再按稳定 id 顺序处理: + +| DeleteMode | 生命周期/存储 | 层级边 | +|---|---|---| +| DOWNWEIGHT | lifecycle 保持 ACTIVE,仅降权 | 不改边 | +| ARCHIVE | lifecycle 设 ARCHIVED | 保留边;默认召回和展开按 lifecycle 排除,显式 include_archived 可见 | +| FORGET | lifecycle 设 FORGOTTEN | 提交前双向断开所有直接父边和子边 | +| PURGE | 物理删除真源与索引 | 先双向断边,再删除目标 | + +删除父节点时,直接子仅清空指向该父的 `parent_id`,成为未挂接节点;不删除、归档或遗忘子。删除子节点时,从父 `child_ids` 移除并保持其余顺序。一个 selector 同时命中父子时先计算最终存活边,再一次提交。空父默认保留;任何 PURGE 都不得沿 hierarchy 级联删除权威叶。 + ### MemoryPipeline(`pipeline.py`) 控制层的跨构建/查询 profile 编排抽象。Pipeline 不实现具体能力,只返回一组已装配的组件绑定。 @@ -170,7 +246,9 @@ pipeline: |------|------|------| | `transition` | `(scope: Scope, unit_ids: list[str], target: LifecycleState) -> None` | 在指定 Scope 内批量非破坏式状态标记 | | `supersede` | `(scope: Scope, unit_id: str, invalid_at: datetime) -> MemoryUnit` | 在指定 Scope 内将旧版本标记 SUPERSEDED,并把 valid-time 失效边界设为 `invalid_at` | -| `sweep` | `() -> list[str]` | 扫描到期(`t_invalid` 已过)的 active 单元,标记 FORGOTTEN | +| `sweep` | `() -> list[str]` | 扫描到期(`t_invalid` 已过)的 active 单元,标记 FORGOTTEN;目标挂树 unit 必须走与 FORGET 相同的断边编排 | + +LifecycleManager 不修改 `HierarchyStatus`。结构 status 的 ACTIVE 不会覆盖 ARCHIVED/FORGOTTEN 等生命周期过滤。 **状态机**: ``` @@ -240,12 +318,14 @@ recall 完成权限检查后,API 读取 `PermissionManager.routing_fields()` | 方法 | 签名 | 语义 | |------|------|------| -| `submit` | `(scope: Scope, mode: EvolveMode, channel: Channel) -> str` | 提交演进任务,返回 job_id | +| `submit` | `(scope: Scope, mode: EvolveMode, channel: Channel, *, hierarchy_options: HierarchyBuildOptions | None = None) -> str` | 提交演进任务,返回 job_id;仅 HIERARCHY 接受 options(目标) | | `status` | `(job_id: str) -> JobInfo` | 查询任务状态 | | `cancel` | `(job_id: str) -> None` | 取消尚未完成的任务(幂等) | **双通道**:HOT(在线低时延:write 返回前完成的轻量索引);BACKGROUND(离线异步:重的抽取/升华/重索引)。 +目标 HIERARCHY 任务必须在 `JobInfo.detail` 中提供稳定字符串键:`hierarchy_kind`、`span_start`/`span_end`、`trigger`(`explicit`/`ensure_on_recall`/`auto_derive`)、`created_parent_count`、`updated_child_count`、`replaced_parent_count`、`repair_required_count`、`complete`、`error`。`detail["repair_required_count"]` 等于 `HierarchyBuildResult.repair_required` 的元素数量。目标 `JobInfo` 增加 `result: EvolveResult | None = None`(结构见 S05);`complete=false` 或非空 `repair_required` 不得标记 SUCCEEDED。ensure 等待终态;auto derive 不等待。 + ### PolicyManager(`policy.py`) | 方法 | 签名 | 语义 | @@ -277,6 +357,19 @@ space 元数据、space policy、成员、用量与 offboarding 状态管理。 | `add_member` | `(org: str, space: str, member: SpaceMember) -> None` | 添加或更新成员角色;成员 scope 的 org/space 归一为目标 space | | `remove_member` | `(org: str, space: str, member: Scope) -> None` | 移除成员 | +目标层级策略键: + +| 键 | 类型与默认 | 语义 | +|---|---|---| +| `hierarchy.enabled` | bool,`false` | 层级总开关 | +| `hierarchy.auto_derive` | bool,`false` | write 后是否后台派生 | +| `hierarchy.ensure_on_recall` | bool,`false` | 是否对显式有界层级 recall 阻塞确保结构 | +| `hierarchy.score_propagation` | str,默认 `maxp` | rollup 算法;当前仅接受 `maxp` | +| `hierarchy.expand_default_depth` | int,`1` | 仅供未显式给 depth 的内部/接入形态;公开 recall 默认仍为 0 | +| `hierarchy.expand_top_m` | int | None,`None` | 每个父最多保留的直接子数;必须 > 0 | + +`enabled=false` 优先于其他层级键。修改策略不回写已有 unit,不触发隐式重建。未知值或越界值抛 `PolicyError`。 + ## 数据结构 ### 控制层数据类型(`types.py`) @@ -288,10 +381,10 @@ space 元数据、space policy、成员、用量与 offboarding 状态管理。 | `Grant` | dataclass | grantor(Scope) / grantee(Scope) / actions(list[Action]) / expires_at | | `Channel` | 枚举 | HOT / BACKGROUND | | `JobStatus` | 枚举 | PENDING / RUNNING / SUCCEEDED / FAILED / CANCELLED | -| `JobInfo` | dataclass | id / channel / mode / scope / status / detail | +| `JobInfo` | dataclass | id / channel / mode / scope / status / detail;目标增加 result | | `MemoryListResult` | dataclass | items: list[MemoryUnit] / count: int(分页前匹配总数) | | `UpdateMode` | 枚举 | SUPERSEDE(默认,新 id)/ OVERWRITE(同 id) | -| `MemoryPatch` | dataclass | content(修正后的文本投影,应用时更新对应 Segment 内容) / tier / tags / metadata / t_valid / t_invalid / mode(UpdateMode) | +| `MemoryPatch` | dataclass | content(修正后的文本投影,应用时更新对应 Segment 内容) / tier / tags / metadata / t_valid / t_invalid / mode(UpdateMode);目标增加 hierarchy | | `DeleteMode` | 枚举 | FORGET / ARCHIVE / DOWNWEIGHT / PURGE | | `DeleteSelector` | dataclass | unit_ids / scope / tags / before / mode(DeleteMode) | | `PrincipalPath` | 枚举 | USER_AGENT / AGENT_USER | @@ -304,6 +397,18 @@ space 元数据、space policy、成员、用量与 offboarding 状态管理。 | `SpaceUsage` | dataclass | org / space / memory_count / message_count / index_count / storage_bytes / audit_count | | `SpaceDeleteResult` | dataclass | org / space / deleted_counts / status / audit_event_id | +`DeleteSelector` 条件取 AND 且至少一项非空。`MemoryPatch` 仅非 `None` 字段生效;目标 `HierarchyPatch.edge_changes` 的空列表表示无边变更。 + +### 三种状态/引用边界(目标) + +| 结构 | 允许值或作用 | +|---|---| +| `HierarchyStatus` | 结构节点修正状态;精确枚举见 S07 | +| `LifecycleState` | unit 生命周期;精确枚举见 S07 | +| `provenance` | 演进来源 | +| `supersedes` | 版本替换 | +| `HierarchyRef.parent_id/child_ids` | 直接父子包含 | + ### 生命周期状态映射(delete 路径) | DeleteMode | 目标 LifecycleState | @@ -325,14 +430,26 @@ src/control/<算子>_impl/ 自注册模式:Producer 定义在对应顶层接口文件中;实现文件尾部 `@XxxProducer.register("name")` 绑定构建函数,`__init__.py` 导入实现文件触发注册,`control.bootstrap.register_controllers()` 统一 import 各 `*_impl` 包。装配层通过 Producer 按配置选取实现。 +## 错误语义(层级目标扩展) + +| 异常 | 场景 | +|---|---| +| `ValidationError` | 空 selector、非法 options/patch/span/顺序、跨 kind 组合 | +| `ConflictError` | expected_parent_id 不匹配、并发版本变化或单父冲突 | +| `NotFoundError` | 已鉴权 scope 内的目标或边端点不存在 | +| `PolicyError` | 层级功能关闭、策略非法、ensure 前提不满足 | +| `BackendError` | 层级事务、调度或阻塞 ensure 失败 | + ## 与其它 spec 的关系 | 关联 spec | 关系 | |-----------|------| | S02-memory_api | 数据面委托 MemoryEngine;治理/授权/调度/策略/space 管理面直达控制算子 | +| S04-retrieval | Retriever/目标 Expander、父优先链路和树预算 | | S05-construction | Engine/Scheduler 驱动构建层 IndexBuilder/Evolver;演进逻辑由构建层执行 | | S06-storage | 控制层通过 KVStore 读写真源;LifecycleManager/Governor 的目标操作按显式 Scope 点查或枚举,只有 sweep/offboarding 这类全局管理任务使用 `kv.scopes()` | -| S07-common | 控制层消费 `MemoryUnit`、`AuditEvent`、错误类型等公共结构 | +| S07-common | 控制层消费 `MemoryUnit`、目标 `HierarchyRef`、`AuditEvent`、错误类型等公共结构 | +| F07-memory-tree | 树结构编排与生命周期决策来源 | | architecture.md §3.1 | MemoryUnit 数据模型(lifecycle / temporal / supersedes / provenance)由 `common/type_def` 定义,控制层消费 | | architecture.md §8 | 演进调度(EvolveMode / Channel)映射到 Scheduler 双通道 + Evolver 四阶段 | | architecture.md §9 | `src/api/MemoryAPI` 是控制层的薄封装 + PEP;数据面委托 Engine,管理面直达各算子 | diff --git a/docs/specs/S04-retrieval.md b/docs/specs/S04-retrieval.md index b320ee47..977f85f6 100644 --- a/docs/specs/S04-retrieval.md +++ b/docs/specs/S04-retrieval.md @@ -5,8 +5,8 @@ | 项 | 值 | |---|---| | 关联模块 | src/retrieval/ | -| 最近一次修订日期 | 2026-08-07 | -| 关联特性文档 | docs/features/F01-system-spec-design.md、docs/features/construction/F04-cc-memory-compat.md、docs/features/retrieval/F02-retrieval-threshold-topk-design.md、docs/features/retrieval/F03-metadata-filtering.md、docs/features/retrieval/F04-score-max-fusion.md、docs/features/retrieval/F05-storage-retrieval-pipelines.md | +| 最近一次修订日期 | 2026-08-11 | +| 关联特性文档 | docs/features/F01-system-spec-design.md、docs/features/construction/F04-cc-memory-compat.md、docs/features/construction/F05-construction-spec-multimodal-design.md、docs/features/retrieval/F02-retrieval-threshold-topk-design.md、docs/features/retrieval/F03-metadata-filtering.md、docs/features/retrieval/F04-score-max-fusion.md、docs/features/retrieval/F05-storage-retrieval-pipelines.md、docs/features/common/F01-memory-layer.md、docs/features/common/F07-memory-tree.md | ## 范围 / 边界 @@ -20,11 +20,9 @@ - 渐进式披露:L0 摘要/L1 片段/L2 全文 按需加载 - 检索轨迹:可观测的非黑盒调试信息 -**不管什么**: -- 不做鉴权(由 `src/api` 层负责) -- 不做记忆写入/演进/落盘 -- 不直接操作存储写入(只做存储读取/检索) -- 不实现 Embedder/Tokenizer/Reranker 等共享插件(消费 `src/common` 注入的实例) +树结构过滤、展开、分数传播和树级预算是**目标契约,尚未实现**;未提出层级请求时,既有检索语义保持不变。 + +本层不做鉴权、写入、建树或层级修复。层级真源是各节点驻留 Scope 下的 `MemoryUnit.hierarchy`;检索层按边携带的子 Scope(缺省则与父 unit 完整 Scope 相同)点读并按确定规则遍历。跨 org/space 的边不可见。 ## 不变量 @@ -36,7 +34,7 @@ 6. **所有算子必须实现 `operator_type()` 和 `health()`**:继承自 `RetrievalOperator`。 7. **scalar_filters 与软召回信号分离**:ParsedQuery 中 `scalar_filters`(硬前置过滤)与 `tokens/keywords/entities/vector`(软召回信号)不能互相折叠。 8. **双时间轴独立**:`as_of`(valid-time 回溯点)与 `time_from/time_to`(event-time 范围)是两条独立时间轴。 -9. **召回分数高分优先**:chunk→unit MaxP、分层归并与融合排序统一按「分越大越相关」处理;向量 Recaller 不接受 L2 等 lower-is-better 度量。 +9. **召回分数高分优先**:生产链路以 vector/keyword 分别查询 content/L0/L1 的六路内容索引;各路先按 chunk→unit MaxP、同通道跨层 MaxP 归并,再融合排序,统一按「分越大越相关」处理。向量 Recaller 不接受 L2 等 lower-is-better 度量。 10. **生产过滤先于 top-k**:Milvus / Elasticsearch / pgvector 必须在 `limit/top_k` 前完整下推 `FilterExpr`;UnitReader 的真源复核只做纵深防御, 不能补回已被截断的候选。 @@ -46,27 +44,71 @@ 独立阶段,不下沉到 Storage 的 retrieve 入口。 13. **部分失败显式返回**:部分召回入口失败时继续处理成功候选并返回 `ChannelError`;全部选中 入口失败抛 `StorageRetrievalError`。显式空 channels 是无效输入。 +14. **披露与结构轴分离**:`ContentLayers`/`DisclosureLevel` 是同一 unit 的 L0/L1/L2 披露轴;`HierarchyRef` 是跨 unit 的父子结构轴,两者不得互相推导。 +15. **时间概念分离**:`MemoryUnit.temporal` 保存事件、摄入和有效时间;`RecallChannel.TEMPORAL` 表示时间过滤;`HierarchyKind.TIME` 表示时间维度上的父子包含。三者互不替代。 +16. **单 kind 层级请求**:一次层级请求只处理一个 `HierarchyKind.TIME|TOPIC|DIRECTORY|CLUSTER|CUSTOM`,不隐式跨 kind。 +17. **层级默认保守**:`expand_depth=0`、`rollup=false`;只返回直接召回命中的节点,不遍历子节点,也不传播后代分数;父优先由显式 `hierarchy_role` 父侧角色过滤实现。 +18. **展开顺序与隔离**:Expander 只沿直接 `child_ids` 向下,且必须保持父节点声明的稳定顺序;跨 org/space 引用不可见;同租户内跨 session/user 的子节点按 `child_scopes`(或缺省父 Scope)解析。 +19. **树预算独立**:树级 token 预算是逻辑上下文注入/节点准入预算,负责选择跨 unit 的节点及其主披露级;Discloser 只负责单个 unit 的内容塑形。两种预算不可合并为一个隐式行为。`span_start/span_end` 是结构覆盖区间,与 `as_of` 的 valid-time 回溯及 `time_from/time_to` 的 event-time 范围独立。 +20. **层级与多模态正交**:CLM/ELM 等多模态检索保留独立召回通道及 provenance;层级过滤、父优先召回、展开和分数上卷不替代 `FilterExpr`,也不改变既有多模态候选的过滤与溯源语义。 ## 接口契约 -### RetrievalOperator(基类,`base.py`) +### Retriever -```python -class RetrievalOperatorType(str, Enum): - QUERY_PARSER / RECALLER / FUSER / DISCLOSER / RETRIEVER +| 方法 | 签名 | 语义 | +|---|---|---| +| `retrieve` | `(scope: Scope, query: RetrievalQuery) -> RetrievalResult` | 在 scope 内执行完整检索链路;层级字段为空时执行既有链路 | + +目标父优先链路固定为: + +```text +QueryParser +→ hierarchy kind/role/span 硬过滤 +→ 既有 L0/L1/L2 内容层多路召回 +→ fusion → rerank → threshold → top_k +→ 可选 Expand(expand_depth > 0) +→ tree score / convergence / tree token budget +→ 对每个保留 unit 调用 Discloser +→ RetrievalResult +``` + +`hierarchy_kind`、`hierarchy_role` 与结构 span 先于内容层召回生效;显式层级召回只接受 +`HierarchyStatus.ACTIVE` 的节点。生命周期过滤与普通 recall 相同: +FORGOTTEN/SUPERSEDED 不可见,ARCHIVED 仅在 `include_archived=true` 时可见。 +指定父侧 `hierarchy_role` 时,召回集合只包含该父角色;省略 role 时,同 kind 下所有 +可见活动角色均可参与。过滤后的候选仍走既有融合、重排和阈值链路,因此层级父节点 +不是一条绕过相关性判断的特殊结果通道。默认不展开。 -class RetrievalOperator(ABC): - def operator_type(self) -> RetrievalOperatorType # 自描述 - def health(self) -> None # 存活探测 +### QueryParser / Recaller / Fuser + +| 接口 | 签名 | 语义 | +|---|---|---| +| `QueryParser.parse` | `(query: RetrievalQuery) -> ParsedQuery` | 产生规范化文本、软召回信号、硬过滤条件和时间条件;完整保留层级查询字段 | +| `Recaller.recall` | `(scope: Scope, query: ParsedQuery, top_k: int) -> list[ScoredUnit]` | 在 scope 和硬过滤约束内执行单路召回 | +| `Fuser.fuse` | `(query: ParsedQuery, candidates: list[list[ScoredUnit]]) -> list[ScoredUnit]` | 按 unit_id 融合多路、多内容层候选并稳定排序 | + +`RecallChannel.TEMPORAL` 仅应用 event-time/valid-time 条件,不创建、过滤或展开 `HierarchyKind.TIME` 树。TIME 层级过滤必须来自明确的 hierarchy 字段。 + +### Expander(目标契约,尚未实现) + +```python +class Expander(RetrievalOperator): + def expand(self, scope: Scope, request: ExpandRequest) -> ExpandResult: ... ``` -### Retriever(`retriever.py`) +Expander 先校验 root,再按深度从浅到深遍历;同一父的子顺序与 `child_ids` 一致,同层父分组沿上一层结果顺序。返回项使用相同顺序,不包含 root,只包含实际选中的后代。深度 1 表示直接子节点,深度 N 最多遍历 N 条父子边。 -检索层入口,编排完整链路。 +边界规则: -| 方法 | 签名 | 语义 | -|------|------|------| -| `retrieve` | `(scope: Scope, query: RetrievalQuery) -> RetrievalResult` | 在 scope 范围内执行完整检索链路,返回结果项与轨迹 | +- root 不存在或不属于传入 `scope`:抛 `NotFoundError`,不得泄漏其他 scope 是否存在同 id。 +- root 的 kind 与请求 kind 不同或 root 为空层级:抛 `ValidationError`。 +- 子 id 在其驻留 Scope(`child_scopes[i]` 或父 unit 完整 Scope)缺失:记录 `ExpandIssue(code="missing_child")`,跳过该分支并置 `complete=false`。 +- 子节点 kind 不同:记录 `kind_mismatch` 并跳过;不得转入另一 kind。 +- 检测到自环、祖先环或重复到达:记录 `cycle`,首次出现之后不再访问该节点;结果中每个 id 至多一次。 +- 子引用解析到其他 scope:按 `missing_child` 处理,不返回或描述外部对象。 +- `HierarchyStatus` 非 ACTIVE:记录 `status_excluded` 并跳过该分支。FORGOTTEN/SUPERSEDED 同样不可展开;ARCHIVED 仅在 recall 的 `include_archived=true` 时可见,公开 `expand` 默认不可见。生命周期排除记录 `lifecycle_excluded`。 +- 达到深度不是截断;预算、top-M 或节点上限导致未遍历完才是截断。 **retrieve 路径**: ``` @@ -81,17 +123,48 @@ QueryParser.parse(query) → ParsedQuery → 组装 RetrievalResult(items + trajectory + errors) ``` -### QueryParser(`query_parser.py`) +独立公开 `MemoryAPI.expand` 按 S02 固定装配 `include_archived=false`、`query=None`。 +`ExpandRequest` 的这两个字段保留给 recall 内部编排:前者继承检索查询的生命周期 +可见性,后者为 MaxP 和树预算提供已经规范化的 query;它们不是额外公开参数。 +recall 内部触发展开时,必须把父 `RetrievalQuery.include_archived` 和 QueryParser +产出的 `ParsedQuery` 原样装配进 `ExpandRequest`。 + +### 分数传播、收敛与树预算(目标契约,尚未实现) + +父层召回的默认分数保持不变。`rollup=true` 时,检索层增加一条同 query、kind、span +和 lifecycle 可见性约束下的后代节点召回,不套用目标父角色过滤;命中后沿 +`parent_id` 上卷: +`hierarchy_role` 非空时取满足该 role 的最近祖先,role 为空时只取直接父节点,再与 +父节点自身召回分融合。该路径不改变输出展开深度,也不把后代自动加入结果。 +默认传播算法是 MaxP: + +```text +parent_score = max(parent_recall_score, selected_descendant_scores) +``` + +后代相关性分数使用与父召回相同的规范化 query 评分口径;仅结构点读且没有 query 的公开 `expand` 调用,其 `RetrievedItem.score` 固定为 `0.0`,不做伪相关性估计。传播只在请求 kind 内进行,不改变子项自身分数。 + +每个父节点最多保留策略 `hierarchy.expand_top_m` 指定的高分直接子节点;同分按 `child_ids` 顺序。某层最高剩余分不超过检索阈值时停止向下,形成确定性收敛。top-M 为空表示不额外裁剪,但仍受深度和预算约束。 + +`expand_budget_tokens` 是后代树独立的逻辑上下文注入/节点准入预算,不包含父结果的 +`max_tokens`。分配顺序为父命中顺序、深度从浅到深、同父 `child_ids` 顺序;预算估算 +决定某节点是否入选以及其主 `level`。不足时停止后续选择,`truncated=true`、 +`complete=false`,并记录 `budget_exhausted`。选定节点与披露级别后,逐 unit 调用 +Discloser;Expander 不把子 id 塞入父 `RetrievedItem`。 + +`RetrievedItem` 始终返回 `abstract/overview/content` 全字段,因此这些字段的完整序列化 +大小可能超过上述逻辑预算。当前契约不提供严格 wire-size/token-size 投影或上限保证。 + +### Discloser | 方法 | 签名 | 语义 | -|------|------|------| -| `parse` | `(query: RetrievalQuery) -> ParsedQuery` | 将检索请求解析为结构化查询表示 | +|---|---|---| +| `disclose` | `(query, candidates, units, level, max_tokens=None) -> list[RetrievedItem]` | 为已选中的单个 unit 候选填充 L0/L1/L2 内容和实际主披露级 | -**产出**:raw/rewritten/intent/tokens/keywords/entities/vector/scalar_filters/as_of/time_from/time_to/channels/extensions。 +`RetrievedItem` 始终一次性具有 `abstract`、`overview`、`content` 三个字段;`level` 表示本次主披露级。已有行为必须准确区分: -`raw` 表示进入检索链路的规范化 query 文本,不要求逐字等于调用方传入的 -`RetrievalQuery.text`。默认 `simple` 实现会先剥除上游包装噪声(如 UTC 时间戳、 -`Sender (untrusted metadata)` 元数据行),再基于清洗后的文本产生分词、向量和时间窗。 +- `StructuredDiscloser` 的 `ADAPTIVE` 会先给所有候选 L0;无 `max_tokens` 时尝试把首项提升到 L1;有预算时按预算尝试首项 L1、满足置信差时首项 L2,再依次提升其余项到 L1。 +- 默认 `TruncatingDiscloser` 不实现自适应升级;收到 `ADAPTIVE` 时确定性降为 L0。其 `max_tokens` 不改变该行为。 ### 过滤表达式 @@ -141,25 +214,36 @@ metadata 比较保留 JSON 原生类型。查询侧不做 string / number / bool - `level: DisclosureLevel` — L0/L1/L2/ADAPTIVE - `max_tokens: int | None` — 自适应披露预算 +以上是单 unit 披露行为,不承担选子、遍历或树预算。 + ## 数据结构 -### RetrievalQuery(`types.py`) +### RetrievalQuery + +既有字段保持兼容,目标新增字段标为“目标”: | 字段 | 类型 | 默认 | 语义 | |------|------|------|------| -| `text` | str | "" | 自然语言查询 | -| `filters` | FilterExpr \| None | None | 标签/元数据硬过滤;支持 AND / OR / NOT 树 | -| `as_of` | datetime \| None | None | valid-time 回溯点 | -| `top_k` | int | 10 | 返回条数上限(经相关性阈值后实际可少于此数) | -| `disclosure` | DisclosureLevel | L0 | 结果披露层级 | -| `max_tokens` | int \| None | None | 自适应披露预算 | -| `with_trajectory` | bool | False | 是否返回检索轨迹 | -| `channels` | list[RecallChannel] \| None | None | 覆盖启用的召回通道 | -| `rerank` | bool \| None | None | 覆盖重排开关 | -| `include_archived` | bool | False | 是否纳入 archived 记忆 | -| `extensions` | dict[str, str] | {} | 调用方自定义透传配置 | - -### ParsedQuery(`types.py`) +| `text` | str | `""` | 自然语言查询 | +| `filters` | FilterExpr \| None | `None` | scope 之外的硬过滤;支持 AND / OR / NOT 树 | +| `as_of` | datetime \| None | `None` | valid-time 回溯点 | +| `top_k` | int | `10` | 父层结果上限 | +| `disclosure` | DisclosureLevel | `L0` | 父结果及后代的请求披露级 | +| `max_tokens` | int \| None | `None` | 既有单 unit 自适应披露预算 | +| `with_trajectory` | bool | `False` | 是否返回轨迹 | +| `channels` | list[RecallChannel] \| None | `None` | 覆盖召回通道 | +| `rerank` | bool \| None | `None` | 覆盖重排开关 | +| `include_archived` | bool | `False` | 是否纳入归档 unit | +| `extensions` | dict[str, str] | `{}` | 调用级透传配置 | +| `hierarchy_kind`(目标) | HierarchyKind \| None | `None` | 单一结构 kind | +| `hierarchy_role`(目标) | HierarchyRole \| None | `None` | 父层角色过滤 | +| `span_start`(目标) | datetime \| None | `None` | 结构区间起点 | +| `span_end`(目标) | datetime \| None | `None` | 结构区间终点 | +| `expand_depth`(目标) | int | `0` | 后代最大边深度;0 不展开 | +| `expand_budget_tokens`(目标) | int \| None | `None` | 后代树的逻辑节点准入与主披露级预算 | +| `rollup`(目标) | bool | `False` | 是否启用后代分数向父传播 | + +校验规则: | 字段 | 类型 | 语义 | |------|------|------| @@ -179,7 +263,12 @@ metadata 比较保留 JSON 原生类型。查询侧不做 string / number / bool | `include_archived` | bool | 当前态真源复核是否允许 archived | | `extensions` | dict[str, str] | 透传配置 | -### 结果结构 +1. `top_k > 0`,`expand_depth >= 0`;非空 `max_tokens` 与 `expand_budget_tokens` 必须大于 0。 +2. `hierarchy_role`、任一 span、`expand_depth > 0`、非空 `expand_budget_tokens` 或 `rollup=true` 都要求显式 `hierarchy_kind`。 +3. span 必须成对出现且 `span_start <= span_end`。 +4. 区间采用闭区间相交:节点满足 `node.span_start <= query.span_end AND node.span_end >= query.span_start`;端点相等算相交。没有 span 的节点不匹配有 span 的查询。 +5. `hierarchy_kind=HierarchyKind.TIME` 的查询可以省略 query span,此时查询已有 TIME 结构的全部范围;但每个匹配节点自身必须具有有效 span。阻塞 ensure 仍要求 query span 有界。这不改变对 `MemoryUnit.temporal.t_event` 的普通时间过滤。 +6. hierarchy 功能关闭时,任何显式层级字段、非零展开深度或 `rollup=true` 都抛 `PolicyError`;没有层级请求的召回不受影响。 | 类型 | 关键字段 | |------|----------| @@ -191,23 +280,73 @@ metadata 比较保留 JSON 原生类型。查询侧不做 string / number / bool | `ChannelError` | channel / source / error_type / message | | `RetrievalResult` | items / trajectory / errors: list[ChannelError] | -### 枚举 +### ParsedQuery -| 枚举 | 值 | -|------|------| -| `DisclosureLevel` | L0 / L1 / L2 / ADAPTIVE | -| `RecallChannel` | DOCUMENT / KEYWORD / VECTOR / GRAPH / TEMPORAL | +`ParsedQuery` 保留既有 `raw/rewritten/intent/tokens/keywords/entities/vector/scalar_filters/as_of/time_from/time_to/channels/extensions`,目标增加与 `RetrievalQuery` 同名的 hierarchy 字段。Parser 不把 hierarchy span 改写成 event-time,也不从 TEMPORAL 通道推导 TIME kind。 -## 实现注册机制 +### ExpandRequest / ExpandIssue / ExpandResult(目标契约,尚未实现) +```python +@dataclass +class ExpandRequest: + root_id: str + kind: HierarchyKind + depth: int = 1 + disclosure: DisclosureLevel = DisclosureLevel.L1 + budget_tokens: int | None = None + with_trajectory: bool = True + include_archived: bool = False + query: ParsedQuery | None = None + +@dataclass +class ExpandIssue: + unit_id: str + code: str + message: str + +@dataclass +class ExpandResult: + root_id: str + kind: HierarchyKind + items: list[RetrievedItem] + actual_depth: int + truncated: bool + complete: bool + issues: list[ExpandIssue] + trajectory: list[TrajectoryStep] ``` -src/retrieval/<算子>_impl/ - __init__.py # 重导出实现类 - .py # 具体实现 + 尾部 @XxxProducer.register("name") -``` -各 Producer:`QueryParserProducer` / `RecallerProducer` / `FuserProducer` / `DiscloserProducer` / `RetrieverProducer`。 -注册由 `retrieval.bootstrap.register_operators` 统一触发。 +`depth >= 1`,非空 `budget_tokens > 0`。`actual_depth` 是返回项中离 root 的最大边数;空结果为 0。`complete=true` 当且仅当请求深度内所有可见、同 kind、有效的后代都完成处理,且没有 issue 或预算/top-M/节点上限截断。issues 按首次遇到顺序稳定排列。 + +### 既有结果结构 + +| 类型 | 精确字段 | +|---|---| +| `ScoredUnit` | `unit_id` / `score` / `channel` / `evidence` | +| `ChannelEvidence` | `channel` / `rank` / `score` / `weight` / `contribution` | +| `RetrievedItem` | `unit_id` / `score` / `abstract` / `overview` / `content` / `level` | +| `TrajectoryStep` | `stage` / `channel` / `candidate_count` / `cost_ms` / `detail` | +| `RetrievalResult` | `items` / `trajectory` | + +不得向既有 `RetrievedItem` 增加 `child_ids` 或把 `content` 改作树容器。 + +### 轨迹 + +普通链路沿用 `parse/recall/fuse/rerank/threshold/disclose`。层级召回额外使用: + +- `parent_recall`:`detail` 至少记录 `kind`、`role`、span、父候选数。 +- `expand`:每个 root 一步,`detail` 至少记录 `root_id`、`kind`、`requested_depth`、`actual_depth`、`item_count`、`truncated` 和截断原因。 + +`with_trajectory=false` 时 `RetrievalResult.trajectory=[]`;公开 `expand` 的 `with_trajectory` 独立控制 `ExpandResult.trajectory`。 + +## 错误语义 + +| 异常 | 场景 | +|---|---| +| `ValidationError` | 深度、预算、span 或 kind/role 组合非法;root kind 不匹配 | +| `NotFoundError` | 展开 root 不存在或不在请求 scope | +| `PolicyError` | 显式层级召回或展开在 hierarchy 关闭时发起 | +| `BackendError` | 召回或点读后端失败,且不能按 issue 规则局部处理 | ## 与其它 spec 的关系 @@ -220,3 +359,4 @@ src/retrieval/<算子>_impl/ | S07-common | 复用 Tokenizer/Embedder/FeatureExtractor/LLM/Reranker | | S08-config | 能力开关与 rerank/embedder 晚绑定经 ConfigSource | | architecture.md §8 | 检索链路设计 | +| [F07-memory-tree.md](../features/common/F07-memory-tree.md) | 父优先召回与按需展开的目标设计来源 | diff --git a/docs/specs/S05-construction.md b/docs/specs/S05-construction.md index 0518a2d2..8f6c1481 100644 --- a/docs/specs/S05-construction.md +++ b/docs/specs/S05-construction.md @@ -5,8 +5,8 @@ | 项 | 值 | |---|---| | 关联模块 | src/construction/ | -| 最近一次修订日期 | 2026-08-04 | -| 关联特性文档 | docs/features/F01-system-spec-design.md, docs/features/construction/F01-construction-spec-design.md, docs/features/construction/F02-dynamic-extraction-consolidation.md, docs/features/construction/F03-extraction-layer-integrity.md, docs/features/construction/F04-cc-memory-compat.md, docs/features/common/F01-memory-layer.md, docs/features/common/F03-scope-space-isolation.md, docs/features/retrieval/F03-metadata-filtering.md | +| 最近一次修订日期 | 2026-08-11 | +| 关联特性文档 | docs/features/F01-system-spec-design.md, docs/features/construction/F01-construction-spec-design.md, docs/features/construction/F02-dynamic-extraction-consolidation.md, docs/features/construction/F03-extraction-layer-integrity.md, docs/features/construction/F04-cc-memory-compat.md, docs/features/common/F01-memory-layer.md, docs/features/common/F03-scope-space-isolation.md, docs/features/common/F07-memory-tree.md, docs/features/retrieval/F03-metadata-filtering.md | ## 范围 / 边界 @@ -19,6 +19,7 @@ - 候选落盘前巩固(ADD/UPDATE/SUPERSEDE/NOOP) - 多形式索引构建(文档/关键词/向量/图,按配置启用) - 记忆自演进(抽取 → 关联 → 冲突消解 → 升华 → 遗忘/降权) +- 树结构派生、区间重建与双向边维护(目标契约,尚未实现) **不管什么**: - 不做鉴权(由 `src/api` 层负责) @@ -29,7 +30,8 @@ ## 不变量 1. **落盘由本层负责**:接入层产出 MemoryUnit 后,真源写入由本层调用 KVStore 完成。 -2. **索引是可重建派生**:索引全部可从真源重建,IndexBuilder.rebuild() 是非破坏式保障。 +2. **索引是目标可重建派生**:`IndexBuilder.rebuild()` 接口已经声明,但当前 vector/fulltext + 实现是 no-op,尚不构成实现保证。目标实现必须枚举 KV 真源并非破坏式重建全部索引。 3. **provenance 回指来源**:派生记忆单元的 `provenance` 字段记录由哪些 unit 演进而来。 4. **接口与实现严格分离**:顶层 `.py` 是纯抽象,不 import `*_impl/`。 5. **所有算子必须实现 `operator_type()` 和 `health()`**:继承自 `ConstructionOperator`。 @@ -52,14 +54,21 @@ `MemoryUnit.metadata`,再用系统真源字段覆盖保留 key;时间投影为 epoch 毫秒, `t_invalid=None` 仅在索引中写为 `T_INVALID_OPEN`,不改写真源。 14. **索引删除按 MemoryUnit 定位**:`IndexBuilder.remove` 接收带 Scope 的 MemoryUnit,禁止维护仅按 unit id 的单值 Scope 缓存;同一逻辑 id 在不同 Scope 的索引互不影响。 +15. **叶权威、父可重建**(目标契约,尚未实现):普通写入或来源转换产生的叶是权威事实; + `HierarchyBuilder` 生成的父节点是派生物。重建父层不得删除、改写或归档权威叶内容。 +16. **层级边双向一致**(目标契约,尚未实现):父 `child_ids` 与子 `parent_id` 必须在同一构建操作中维护, + 并在写索引前通过同 org+space、无环、单 kind 单父、区间覆盖校验(跨细粒度 scope 时边可解析)。 +17. **父标注先于持久化和索引**(目标契约,尚未实现):新派生父节点先经 `LayerAnnotator` best-effort 生成 L0/L1, + 再写 KV 和索引。标注失败保留空 layers 并继续,不得因摘要失败丢失结构结果。 ## 接口契约 -### ConstructionOperator(基类,`base.py`) +### ConstructionOperator(基类) ```python class OperatorType(str, Enum): EXTRACTOR / ABSTRACTOR / ASSOCIATOR / CLASSIFIER / INDEX_BUILDER / EVOLVER / LAYER_ANNOTATOR + # 目标新增:HIERARCHY_BUILDER class ConstructionOperator(ABC): def operator_type(self) -> OperatorType # 自描述 @@ -68,7 +77,7 @@ class ConstructionOperator(ABC): > `OperatorType` 枚举无独立 DEDUP 值——`Dedup` 实现复用 `OperatorType.EVOLVER`(去重召回服务于 evolver)。`DynamicEvolver` 是 `OrchestratingEvolver` 的子类,同样返回 `OperatorType.EVOLVER`——它是 evolver 的动态 prompt 变体,通过覆盖 `_evolve_extract` 切换 EXTRACT 路径。 -### Extractor(`extractor.py`) +### Extractor 信息提取,产出低抽象粒度的派生记忆单元。 @@ -131,7 +140,7 @@ Extractor。 |------|------|------| | `abstract` | `(units: list[MemoryUnit]) -> list[MemoryUnit]` | 对一批记忆单元做抽象与精炼,产出高抽象粒度的新记忆单元 | -### Associator(`associator.py`) +### Associator 关联分析,发现记忆间的关联关系。 @@ -141,7 +150,7 @@ Extractor。 产出的 `Relation` 交由 IndexBuilder 写入图索引。 -### Classifier(`classifier.py`) +### Classifier 多维分类,为记忆单元打上分类标签。 @@ -149,7 +158,7 @@ Extractor。 |------|------|------| | `classify` | `(units: list[MemoryUnit]) -> list[MemoryUnit]` | 为一批记忆单元打上 tier/主题/重要度等分类标签,返回更新后的单元 | -### LayerAnnotator(`layer_annotator.py`) +### LayerAnnotator 分层披露标注,给已有 `MemoryUnit` 写 `layers.l0`/`layers.l1`(不产出新记忆)。 @@ -163,7 +172,7 @@ Extractor。 条目单独跳过,其余条目在结构校验完成后写入。Evolver 在 EXTRACT/CONSOLIDATE 抽取 (升华)后、去重落盘前调用。 -### IndexBuilder(`index_builder.py`) +### IndexBuilder 多形式索引构建与维护。 @@ -172,53 +181,200 @@ Extractor。 | `build` | `(units: list[MemoryUnit]) -> None` | 为一批记忆单元构建已启用的各形式索引 | | `update` | `(units: list[MemoryUnit]) -> None` | 记忆变更后增量更新对应索引条目 | | `remove` | `(units: list[MemoryUnit]) -> None` | 按每个 MemoryUnit 自带 Scope 删除对应索引条目(幂等) | -| `rebuild` | `() -> None` | 从真源全量重建索引(删索引不丢数据的保障) | +| `rebuild` | `() -> None` | 声明从真源全量重建索引;当前 vector/fulltext 实现为 no-op | + +目标 hierarchy metadata 重建必须实现真实的 KV `scopes()` + `list(scope)` 枚举,解码 +每个 `MemoryUnit` 后重新生成内容层与 hierarchy metadata;不得从旧索引反推。该能力 +落地前,不得把 `rebuild()` 接口存在视为“删索引不丢数据”的当前保证。 **build 路径**(按配置启用的索引类型,各实现独立构建): ``` MemoryUnit -├─ 关键词路(FulltextIndexBuilder):unit.content 整篇不切片 -│ → Document(id=unit.id, text=unit.content, metadata={tier,tags,source}) +├─ 关键词路:unit.content 整篇不切片 +│ → Document(id=unit.id, text=unit.content, +│ metadata={unit_id,tier,lifecycle,tags,source,content_layer="l2",...hierarchy}) │ → FulltextStore.insert -├─ 向量路(VectorIndexBuilder):Chunker 切片 +├─ 向量路:Chunker 切片 │ → Chunker.chunk(unit.content) → chunks -│ → Embedder.embed(chunks) → VectorRecord(id={unit.id}-{chunk.id}, vector, metadata={unit_id,tier}) +│ → Embedder.embed(chunks) +│ → VectorRecord(id={unit.id}-{chunk.id}, vector, +│ metadata={unit_id,tier,lifecycle,seq,content_layer="l2",...hierarchy}) │ → VectorStore.insert + KVStore 维护 chunk_id 跟踪(供 update/remove 读旧 chunk) -├─ L0/L1 分层路(FulltextIndexBuilder + VectorIndexBuilder 扩展): +├─ L0/L1 分层路: │ → unit.layers.l0/l1 非空且对应 store 已注入 → 整段不切片 -│ → Document/VectorRecord(id={unit.id}-l0/-l1, text/vector=layers.l0/l1, metadata={unit_id,layer}) +│ → VectorRecord.id={unit.id}-layer-l0/-layer-l1 +│ → Document.id={unit.id}:l0/:l1 +│ → metadata 保留 content_layer,并复制同一 unit 的 hierarchy metadata │ → 写独立 FulltextStore/VectorStore 实例(不同 collection/index = 分表,与 content 物理隔离) │ → store 为 None 跳过该层(向后兼容 + 配置降级);update 先删后建,remove 幂等删 ├─ 图路(Evolver ASSOCIATE 模式编排): │ → FeatureExtractor → Node → GraphStore.insert │ → Associator.associate → Edge → GraphStore.insert -└─ HybridIndexBuilder:组合 fulltext + vector 两个子 builder(默认实现) +└─ 混合路:组合 fulltext + vector 两种投影 ``` > 注:文档索引(path → unit_id 映射)与 FusionStore 融合索引不属于本文已固化的构建接口契约,属设计预留。 > L0/L1 分层索引的召回接入未落地(为披露层预留),详见 F01。 -### Evolver(`evolver.py`) +`layers_index_enabled` 默认 `true`;对应 L0/L1 store 未配置时仅跳过该层。L0/L1/L2 +记录均以 `unit_id` 指向同一真源 unit。记录到 unit 的折叠由单路 recaller 完成; +不同 recaller 的结果再由融合阶段按 `unit_id` 累加贡献,IndexBuilder 不负责召回聚合。 + +目标 hierarchy metadata 的精确键、空值和区间表示由 +[S06-storage.md](S06-storage.md) 单点定义。IndexBuilder 必须把同一 unit 的结构 +metadata 一致投影到已启用的 L0/L1/L2 索引记录;索引是派生物,必须可从 KV 中的 +`MemoryUnit` 重建。 + +### HierarchyBuilder(目标契约,尚未实现) + +```python +@dataclass(frozen=True) +class HierarchyBuildProfile: + kind: HierarchyKind + leaf_role: HierarchyRole + parent_roles: tuple[HierarchyRole, ...] + stage_options: dict[str, dict[str, str]] = field(default_factory=dict) + +@dataclass +class HierarchyBuildOptions: + kind: HierarchyKind + leaf_role: HierarchyRole + parent_roles: list[HierarchyRole] + span_start: datetime | None = None + span_end: datetime | None = None + replace_existing: bool = False + metadata: dict[str, str] = field(default_factory=dict) + +@dataclass +class HierarchyBuildRequest: + scope: Scope + leaf_ids: list[str] + options: HierarchyBuildOptions + +@dataclass +class HierarchyRepair: + unit_id: str + issue: str + expected_parent_id: str = "" + observed_parent_id: str = "" + +@dataclass +class HierarchyBuildResult: + created_parent_ids: list[str] = field(default_factory=list) + updated_child_ids: list[str] = field(default_factory=list) + replaced_parent_ids: list[str] = field(default_factory=list) + repair_required: list[HierarchyRepair] = field(default_factory=list) + complete: bool = True + +class HierarchyBuilder(ConstructionOperator): + def build(self, request: HierarchyBuildRequest) -> HierarchyBuildResult: ... + def replace_in_span(self, request: HierarchyBuildRequest) -> HierarchyBuildResult: ... +``` + +`HierarchyBuildProfile` 是装配期不可变配置,按 `kind` 唯一注册;重复 kind、 +空 `parent_roles`、重复 role、`leaf_role` 出现在父序列中、未注册 stage 或 kind +不支持该 role 序列时拒绝装配。`stage_options` 的外层键是稳定 stage 名,内层值只允许 +字符串配置;运行时 Policy 不修改 profile。 + +`leaf_ids` 必须非空、无重复并全部解析到 `request.scope`;其顺序是输入稳定顺序。 +`parent_roles` 必须非空,是从近叶到远叶的待构建父角色序列;不得重复,也不得包含 +`leaf_role`。 +kind/role 必须使用 S07 定义的枚举。TIME 请求必须给出成对且有效的 span;非 TIME +可省略。`metadata` 只复制到新派生父节点,不得覆盖 id、scope、tier、temporal、 +provenance、supersedes、lifecycle 或 hierarchy 等核心字段。 + +调用方按 `replace_existing` 确定唯一分派:`false` 调用 `build`,发现冲突旧父时整体 +失败;`true` 调用 `replace_in_span`,并要求请求具有成对且有界的 span。显式 +`evolve(HIERARCHY)` 使用调用方给出的值;S03 的 ensure 和 auto derive 固定组装为 +`replace_existing=true`,使同一区间的重复任务成为受控重建,而不是产生第二套父层。 + +`build` 读取权威叶,在请求 span 内创建指定父角色,写入父的有序 `child_ids` 并回写 +直接子的 `parent_id`。它不得隐式替换 span 外的父节点;发现已有冲突父边时返回校验 +错误,不做部分挂接。父节点的 tier 由内容决定,不得从 role 硬推导;角色与 tier 的 +设计指导映射由 [F07-memory-tree.md](../features/common/F07-memory-tree.md) +记录,不构成本接口的枚举等价约束。 + +TIME 构建以 `MemoryUnit.temporal.t_event` 作为叶事件时间,以 +`HierarchyRef.span_start/span_end` 作为结构覆盖区间。父区间覆盖所有直接子区间, +直接子按区间起点、事件时间和输入稳定顺序排序。`HierarchyKind.TIME` 不替代 +`MemoryUnit.temporal`,也不替代 `RecallChannel.TEMPORAL`。 + +新父正文和 segments 先构造,再调用 `LayerAnnotator`;无 annotator 或标注失败时以空 +layers 降级。只有通过结构校验后,才按“KV 真源 → 内容索引”顺序持久化父与被改写的子。 + +`replace_in_span` 仅选择与请求 span 相交、kind 匹配且角色位于 `parent_roles` 的旧派生 +父节点。它必须先计算完整替换集并验证新树,然后: + +1. 从旧父 `child_ids` 移除边,并清空仍指向旧父的直接子 `parent_id`; +2. 将相交旧派生父默认转为 `LifecycleState.ARCHIVED`,并从活动内容索引移除; + `replace_in_span` 本身不物理 PURGE 真源,物理回收必须走 S03 的显式生命周期策略; +3. 保留全部权威叶及其 segments、temporal、provenance 和生命周期; +4. 写入新父,回挂双向边,再更新受影响索引; +5. 边界切过旧父时扩大替换范围到完整旧父,或拒绝请求,不留下半父节点。 + +期望的原子边界是同 org+space 下“旧边断开、新父写入、新边挂接、旧父退役”的一次提交 +(子叶可驻留不同 session/user Scope,由边定位)。 +支持事务的 KV 后端必须原子提交;不支持事务时必须先暂存并验证新父,按可恢复顺序写入, +具体顺序是“写入尚未挂活动边的新父 → 按稳定顺序切换子边 → 归档旧父 → 更新索引”。 +失败后返回 `complete=false` 和逐项 `repair_required`,且不得删除权威叶。调用方不得把 +带 repair 项的结果当作成功;construction/control 负责重试或一致性修复。 + +### Evolver 记忆自演进,持续驱动演进闭环。两个实现:`OrchestratingEvolver`(注册名 `orchestrating`,legacy)与 `DynamicEvolver`(注册名 `dynamic`,子类,EXTRACT 走动态 prompt 四步)。`evolve` 按模式分派到 `_evolve_extract` / `_evolve_consolidate` / `_evolve_associate` / `_evolve_forget` 四个可覆盖方法。 | 方法 | 签名 | 语义 | |------|------|------| -| `evolve` | `(units: list[MemoryUnit], mode: EvolveMode) -> EvolveResult` | 对一批记忆单元执行指定阶段的演进,返回变更结果 | +| `evolve` | `(request: EvolveRequest) -> EvolveResult` | 对一批记忆单元执行指定阶段的演进,返回变更结果 | **EvolveMode**: - `EXTRACT` — 信息提取 - `ASSOCIATE` — 关联分析 - `CONSOLIDATE` — 冲突消解(近重复融合/矛盾标记失效) - `FORGET` — 遗忘/降权(过期/低价值记忆归档) +- `HIERARCHY` — 显式创建或重建父节点及双向包含边(目标新增) + +```python +@dataclass +class EvolveRequest: + units: list[MemoryUnit] + mode: EvolveMode + metadata: dict[str, str] = field(default_factory=dict) + hierarchy_options: HierarchyBuildOptions | None = None +``` + +`metadata` 承载 correlation id、触发来源等请求级透传信息,不写回 unit 核心字段。 +仅 `HIERARCHY` 接受 `hierarchy_options`,且必须提供 kind、leaf_role、parent_roles 与 +TIME 所需 span;其他 mode 提供该 options 时拒绝。实现迁移期间可以保留 +`evolve(units, mode)` 作为兼容入口,其语义等价于构造不带 metadata/options 的请求; +该入口不能触发 HIERARCHY。 **EvolveResult**: -- `created_ids: list[str]` — 新增记忆单元 id -- `updated_ids: list[str]` — 更新记忆单元 id -- `superseded_ids: list[str]` — 被取代记忆单元 id -- `forgotten_ids: list[str]` — 被遗忘记忆单元 id -### Dedup(`dedup.py`) +```python +@dataclass +class EvolveResult: + created_ids: list[str] = field(default_factory=list) + updated_ids: list[str] = field(default_factory=list) + superseded_ids: list[str] = field(default_factory=list) + forgotten_ids: list[str] = field(default_factory=list) + hierarchy_result: HierarchyBuildResult | None = None +``` + +`hierarchy_result` 只在 HIERARCHY 模式返回结构结果与修复报告,其他 mode 为 `None`。 + +各模式对 hierarchy 的行为: + +| 路径/模式 | hierarchy 契约 | +|---|---| +| 普通 `write` | 默认空;调用方提供经校验的叶字段时可保留 kind/role/span,但不得写父或子边 | +| `EXTRACT` | 既有节点不变;新派生节点默认空,`provenance` 来源不自动成为父 | +| `ASSOCIATE` | hierarchy 不变;关系只写 GraphStore,不写 `parent_id` | +| `CONSOLIDATE` | 既有节点不变;新合成节点默认空,需单独建树 | +| `FORGET` | 不改其他节点 kind/role/span;断开直接父边和全部直接子边,不级联删除父或任何子孙 | +| `HIERARCHY` | 委托 HierarchyBuilder 创建/替换父节点并一致回写直接子边 | + +### Dedup 去重召回,由 Evolver 实现(`OrchestratingEvolver._dedup_batch` / `DynamicEvolver._consolidate_step`)及 infer 上下文收集调用。召回 + 阈值过滤 + 加载 + 聚合取 max 全在实现内完成;判定与落盘动作归调用方(evolver)。 @@ -228,10 +384,6 @@ MemoryUnit **score 量纲 0~1**:向量路=cosine,倒排路=词重叠率,阈值统一复用。 -**两个实现**(装配按 `vector_enabled` 选): -- `VectorDedup`(`vector`)— Embedder → VectorStore.search,cosine;record_id 为 `{unit_id}-{chunk_id}` 需解析 -- `KeywordDedup`(`keyword`)— FulltextStore.search,词重叠率;Document.id = unit.id 恒等无需解析 - **降级契约**:实现内部任何异常(Embedder/Store 失败)都吞掉并返回空列表——去重是尽力而为,不可阻断演进。 ## 数据结构 @@ -252,9 +404,11 @@ MemoryUnit | `metadata` | dict[str, Any] | 元数据(保留 JSON 标量原生类型) | | `lifecycle` | LifecycleState | 生命周期状态 | -**注**:`MemoryUnit.content` / `assets` / `source` 是基于 segments 的只读合并视图,非独立字段。 +构建层直接消费 S07 定义的 `HierarchyRef` 和层级枚举。目标父节点复用既有 +segments、layers、tier 和 metadata 槽位,结构边只写 hierarchy;`content/assets/source` +仍是基于 segments 的只读合并视图。精确类型与默认值见 [S07-common.md](S07-common.md)。 -### Relation(`common/type_def/feature.py`) +### Relation | 字段 | 类型 | 语义 | |------|------|------| @@ -264,7 +418,7 @@ MemoryUnit | `score` | float | 关联置信度 | | `metadata` | dict[str, Any] | 附加信息 | -### Segment(`common/type_def/memory.py`) +### Segment | 字段 | 类型 | 语义 | |------|------|------| @@ -274,19 +428,6 @@ MemoryUnit `MemoryUnit.content` 是所有段 `content` 以换行连接的只读合并视图。 -## 实现注册机制 - -``` -src/construction/<算子>_impl/ - __init__.py # 重导出实现类 - .py # 具体实现 + 尾部 @XxxProducer.register("name") -``` - -各 Producer:`ExtractorProducer` / `AbstractorProducer` / `AssociatorProducer` / `ClassifierProducer` / `IndexBuilderProducer` / `DedupProducer` / `EvolverProducer`。 -注册由 `construction.bootstrap.register_constructors` 统一触发。 - -> 当前有哪些实现、文件职责、行为铁律归 [`src/construction/AGENTS.md`](../../src/construction/AGENTS.md),本 spec 只列契约。 - ## 与其它 spec 的关系 | 关联 spec | 关系 | @@ -298,3 +439,4 @@ src/construction/<算子>_impl/ | S07-common | 本层消费 Chunker/Tokenizer/Embedder/FeatureExtractor/LLM/Reranker 共享插件 | | S08-config | Prompt 文本与模型晚绑定经 ConfigSource;业务入参只传 prompt key | | architecture.md §4/§6/§8 | 分层记忆结构 / 多形式索引 / 记忆自演进 | +| [F07-memory-tree.md](../features/common/F07-memory-tree.md) | 树结构构建的目标设计来源 | diff --git a/docs/specs/S06-storage.md b/docs/specs/S06-storage.md index 563c7602..3274ed43 100644 --- a/docs/specs/S06-storage.md +++ b/docs/specs/S06-storage.md @@ -5,8 +5,8 @@ | 项 | 值 | |---|---| | 关联模块 | src/storage/ | -| 最近一次修订日期 | 2026-08-07 | -| 关联特性文档 | docs/features/F01-system-spec-design.md,docs/features/api/F01-memory-api-impl-design.md,docs/features/control/F02-control-isolation-and-audit.md,docs/features/control/F05-cloud-engine-design.md,docs/features/retrieval/F03-metadata-filtering.md,docs/features/retrieval/F05-storage-retrieval-pipelines.md,docs/features/common/F03-scope-space-isolation.md,docs/features/common/F04-security-interfaces-and-encryption.md,docs/features/storage/F02-encrypted-storage.md,docs/features/storage/F03-postgres-backend.md,docs/features/storage/F04-storage-ssl.md,docs/features/storage/F05-unified-storage-design.md | +| 最近一次修订日期 | 2026-08-11 | +| 关联特性文档 | docs/features/F01-system-spec-design.md,docs/features/api/F01-memory-api-impl-design.md,docs/features/control/F02-control-isolation-and-audit.md,docs/features/control/F05-cloud-engine-design.md,docs/features/construction/F05-construction-spec-multimodal-design.md,docs/features/retrieval/F03-metadata-filtering.md,docs/features/retrieval/F05-storage-retrieval-pipelines.md,docs/features/common/F03-scope-space-isolation.md,docs/features/common/F07-memory-tree.md,docs/features/common/F04-security-interfaces-and-encryption.md,docs/features/storage/F02-encrypted-storage.md,docs/features/storage/F03-postgres-backend.md,docs/features/storage/F04-storage-ssl.md,docs/features/storage/F05-unified-storage-design.md | ## 范围 / 边界 **管什么**: @@ -26,6 +26,7 @@ - 不做检索编排(由 `src/retrieval` 层负责) - 不做索引构建逻辑(由 `src/construction` 层负责) - 不实现具体后端(实现在 `*_impl/` 下,通过 Producer 注册) +- 不解释或维护父子业务语义;通用 CRUD 不执行 hierarchy 级联 ## 不变量 @@ -52,14 +53,24 @@ 16. **space 是 scope 的硬分区维度**:`scope_segments(scope)` 使用 `org/space/user/agent/session` 五段;`scope_dims(scope)` 在 `org` 非空时即使 `space==""` 也下推 `space == ""`,避免空 space 查询跨到非空 space。 17. **标识唯一性分层**:非空 Space id 在 Space 资源注册表中全局唯一;MemoryUnit 与各 Store 记录 id 只要求在完整 Scope 内唯一。 18. **SSL 声明即生效**:接外部后端的实现统一接受 `ssl_verify` / `ssl_ca_cert` 两个装配参数(默认关闭)。`ssl_verify` 只表示**是否校验服务端证书**,不负责开启加密——加密开关落在连接串上(`rediss://` / `https://` / `sslmode=`)。开启后不得静默降级:缺证书、连接串仍为明文、或连接串自带会覆盖本设置的 TLS 参数,一律在**装配阶段**报错。 -19. **Storage capability 唯一来源**:能力集合只包含 KV/VECTOR/FULLTEXT/GRAPH/FUSION/FS; +19. **KV 是层级真源**(目标契约,尚未实现):序列化 `MemoryUnit.hierarchy` 与 unit + 一同存入 KV。当前契约不新增 hierarchy Store,也不把父子包含边双写到 GraphStore; + 若未来迁移到独立边存储,必须先修订本 spec 和 S07 的数据模型契约。 +20. **层级索引是派生物**:VectorRecord/Document 的 hierarchy metadata 必须能够从 KV + 中的 `MemoryUnit` 全量重建;索引丢失或不一致时以 KV 为准。 +21. **GraphStore 边界明确**:GraphStore 表示关联和多跳关系,不表示 hierarchy containment; + `HierarchyRef.parent_id/child_ids` 不投影为图边。 +22. **CRUD 不级联层级关系**:KVStore 的 insert/update/delete 只作用于指定 key。删除父或子 + 不会自动改写其他 unit;父子双向边维护、剪枝与修复由 construction/control 调用显式 + CRUD 完成。GraphStore 删除节点时清理关联图边的既有语义不适用于 hierarchy。 +23. **Storage capability 唯一来源**:能力集合只包含 KV/VECTOR/FULLTEXT/GRAPH/FUSION/FS; `has_*()` 由集合推导,未声明端口访问抛 `UnsupportedStorageCapabilityError`。 -20. **命名端口仍受 Storage 管控**:`has_vector_port(name)` 与 `vector_port(name)` 等成对使用; +24. **命名端口仍受 Storage 管控**:`has_vector_port(name)` 与 `vector_port(name)` 等成对使用; 默认端口名为 `default`,分层索引可使用 `layers_l0` / `layers_l1`,上层不得绕过 StorageProducer 直接解析 Store 具名实例。 -21. **检索路径独立于 capability**:Storage 提供 recall/recall_and_get/retrieve,并以全局稳定的 +25. **检索路径独立于 capability**:Storage 提供 recall/recall_and_get/retrieve,并以全局稳定的 `preferred_retrieval_pipeline()` 选择首选入口;路径值不加入 capability。 -22. **统一授权不可绕过**:MemoryUnit 领域接口和 Storage 暴露的 Store 代理端口都先执行 +26. **统一授权不可绕过**:MemoryUnit 领域接口和 Storage 暴露的 Store 代理端口都先执行 `StorageSecurity.authorize`;默认 AllowAll 可省略 access。Store 自身 `security` 表示数据保护。 ## 接口契约 @@ -170,6 +181,9 @@ AAD 版本当前为 `1`,绑定 `scope(org/space/user/agent/session)`、KV `key | `get` | `(scope, node_ids: list[str]) -> list[Node]` | 在 scope 内按 id 点查节点;缺失的 id 从结果中省略 | | `search` | `(scope, query: GraphQuery) -> list[Node]` | 在 scope 内从 query.start_id 出发扩展邻域/子图(多跳遍历) | +GraphStore 只承载 `ASSOCIATE` 等路径产生的语义关联、共指、因果或引用关系。父子包含 +关系的读取与遍历以 KV 中 `MemoryUnit.hierarchy` 为准,不通过 GraphStore 搜索或修复。 + ### FusionStore(`fusion.py`) 向量·倒排·正排融合存储。 @@ -209,6 +223,20 @@ AAD 版本当前为 `1`,绑定 `scope(org/space/user/agent/session)`、KV `key | `VectorRecord` | id / vector: list[float] / metadata | | `VectorQuery` | vector: list[float] / top_k / filters: FilterExpr \| None | +目标层级索引 metadata 在既有 `unit_id`、`content_layer`、`tier`、`lifecycle`、`seq` +基础上增加: + +| 键 | 表示 | +|---|---| +| `hierarchy_kind` | kind 的字符串值;空 hierarchy 时缺省 | +| `hierarchy_role` | role 的字符串值;空 hierarchy 时缺省 | +| `parent_id` | 直接父 id;根或未挂接时为空串 | +| `span_start` | ISO 8601 区间起点;未声明区间时缺省 | +| `span_end` | ISO 8601 区间终点;未声明区间时缺省 | + +同一 unit 的 L0/L1/L2 VectorRecord 必须携带相同的 hierarchy metadata;现有记录 id +格式保持不变。 + ### 全文(`types.py`) | 类型 | 关键字段 | @@ -216,6 +244,24 @@ AAD 版本当前为 `1`,绑定 `scope(org/space/user/agent/session)`、KV `key | `Document` | id / text / metadata | | `TextQuery` | text / top_k / filters: FilterExpr \| None | +Document 使用与 VectorRecord 相同的五个 hierarchy metadata 键,并保留既有 +`content_layer`。L0/L1/L2 文档的当前 id 规则保持不变;增加 metadata 不改变主键。 + +### 层级过滤与区间表示(目标契约,尚未实现) + +层级过滤继续使用现有 `FilterClause(field, op, value)`,不新增查询结构: + +- kind/role/parent 精确过滤使用 `EQ`,例如 + `field="hierarchy_kind"`、`field="parent_id"`。 +- 区间相交 `[query_start, query_end]` 表示为 + `span_start <= query_end AND span_end >= query_start`,即分别使用 `LTE` 与 `GTE`。 +- 时间值统一写为 ISO 8601 字符串;同一索引内必须规范到可按时间顺序比较的统一时区格式。 +- filters 只承载 scope 之外的谓词,scope 仍是 Store 方法的显式第一参数。 + +后端若不能原生执行区间谓词,可以在同 scope 候选上做等价后过滤,但不得放宽结果语义。 +索引重建必须枚举 KV 真源的 MemoryUnit,重新生成内容层与 hierarchy metadata;不得从 +旧索引反推 hierarchy。 + ### 图(`types.py`) | 类型 | 关键字段 | @@ -268,5 +314,6 @@ Store 抽象、跨后端不变量与注册机制。 | S03-control | Engine 通过 KVStore 读写真源;目标生命周期/治理操作按显式 Scope 定位,全局 sweep/offboarding 才跨 Scope 枚举 | | S04-retrieval | Retriever 经 StorageProducer 获取统一 Storage;CompositeStorage 的兼容 Recaller 在检索装配期绑定 | | S05-construction | 构建层通过本层抽象做真源与索引持久化 | +| S07-common | 定义 `MemoryUnit.hierarchy`、`HierarchyKind`、`HierarchyRole` 与 `FilterClause` | | S08-config | Store 连接参数与 `*.active` 可由 ConfigSource 晚绑定;切换后端不包含数据迁移 | | architecture.md §5 | 可配置真源形态(文档/结构化)与多后端 | diff --git a/docs/specs/S07-common.md b/docs/specs/S07-common.md index 9538d860..865eedaf 100644 --- a/docs/specs/S07-common.md +++ b/docs/specs/S07-common.md @@ -5,8 +5,8 @@ | 项 | 值 | |---|-------------| | 关联模块 | src/common/ | -| 最近一次修订日期 | 2026-08-05 | -| 关联特性文档 | docs/features/F01-system-spec-design.md,docs/features/api/F01-memory-api-impl-design.md,docs/features/construction/F04-cc-memory-compat.md,docs/features/common/F01-memory-layer.md,docs/features/common/F02-dashscope-llm-provider.md,docs/features/common/F03-scope-space-isolation.md,docs/features/common/F04-security-interfaces-and-encryption.md,docs/features/control/F02-control-isolation-and-audit.md,docs/features/retrieval/F03-metadata-filtering.md,docs/features/common/F05-model-service-ssl.md,docs/features/common/F06-distributed-lock.md,docs/features/config/F01-config-source.md | +| 最近一次修订日期 | 2026-08-11 | +| 关联特性文档 | docs/features/F01-system-spec-design.md,docs/features/api/F01-memory-api-impl-design.md,docs/features/construction/F04-cc-memory-compat.md,docs/features/common/F01-memory-layer.md,docs/features/common/F02-dashscope-llm-provider.md,docs/features/common/F03-scope-space-isolation.md,docs/features/common/F04-security-interfaces-and-encryption.md,docs/features/common/F07-memory-tree.md,docs/features/control/F02-control-isolation-and-audit.md,docs/features/retrieval/F03-metadata-filtering.md,docs/features/common/F05-model-service-ssl.md,docs/features/common/F06-distributed-lock.md,docs/features/config/F01-config-source.md | ## 范围 / 边界 @@ -31,7 +31,8 @@ 1. **共享插件必须双侧同一**:Embedder/Tokenizer/FeatureExtractor 等必须在构建侧与检索侧使用同一实现/同一配置,保证同词表/同向量空间。 2. **接口与实现严格分离**:顶层 `.py` 是纯抽象,不 import `*_impl/`。 3. **所有插件必须实现 `plugin_type()` 和 `health()`**:继承自 `Plugin` 基类。 -4. **types.py 零依赖其他文件**:纯数据定义,被全局共享依赖。 +4. **公共类型保持低依赖**:`type_def` 只依赖标准库及同目录基础类型,不依赖业务编排、 + 插件实现或存储后端,可被各层共同引用。 5. **工厂注册发生在 import 时**:实现文件尾部 `@XxxProducer.register("name")` 绑定构建函数,`__init__.py` 导入实现文件触发注册。 6. **LLM Provider 参数不上浮到业务层**:厂商专属请求字段只能由对应 Adapter 生成;消费 `LLM` 的算子只传递通用生成选项。 7. **SecurityProvider 是字节级横切接口**:调用方在持久化字节写入前加密、读取后解密;接口不绑定 `MemoryUnit` 或存储后端,是否启用由装配配置决定。 @@ -49,6 +50,25 @@ 第二道防线(幂等键、唯一约束、乐观并发控制)。重入以 `asyncio.current_task()` 为身份 边界,`create_task` 派生的子任务不视为重入;重入记账与租约有效性正交,持有权状态一律 以 `LockHandle.lost` 为准。后端不可用时 fail-closed 抛 `BackendError`,不静默降级为无锁。 +12. **四条层次轴正交**:`ContentLayers`/`DisclosureLevel` 表示同一 unit 的 L0/L1/L2 披露; + 多模态 CLM/ELM(`metadata.memory_level` + `provenance`)表示单媒体源构建粒度; + `MemoryTier` 表示认知角色;`HierarchyRef` 表示跨 unit 的树结构包含。任一轴不得推导 + 或代替另外三轴。 +13. **树结构引用一致**(目标契约,尚未实现):`kind` 与 `role` 必须同时设置或同时缺省; + 空 `HierarchyRef` 等价于未启用树结构。非空结构**不得成环**;同一 kind 下采用单父 + 严格树;父 `child_ids` 与子 `parent_id` 双向一致且子列表不得重复。 + **结构边的 scope 规则(非五维全等)**: + - **硬边界**:相连节点必须同 `org` 且同 `space`;禁止跨 org / 跨 space 的结构边 + (与 F03 租户隔离一致;跨 space 共享走 grant / shared space,不走树边)。 + - **细粒度可放宽**:`user` / `agent` / `session` **不要求**与父节点五维全等。 + 因此「同 scope 连接」**不是**要求 `Scope(org, space, user, agent, session)` 全部一致。 + - **典型允许**:同 user 跨多个 session 建 TIME 树;同 org+space 下跨多个 user + (及各自 session)建树——后者须 build profile / 策略显式开启,默认关闭。 + - **引用可解析**:因 `MemoryUnit.id` 仅在完整 Scope 内唯一,当子(或父)与持有边的 + unit 完整 Scope 不完全相同时,边必须携带可定位的子/父 Scope(见下方 + `child_scopes` / `parent_scope`);二者皆缺省时退化为「与本 unit 完整 Scope 相同」。 +14. **层级区间有效**(目标契约,尚未实现):`span_start`/`span_end` 必须同时为空或同时存在,存在时 `span_start <= span_end`,父区间覆盖直接子区间;`HierarchyKind.TIME` 的所有节点必须有区间。 +15. **引用语义分离**:`provenance` 只表示演进来源,`supersedes` 只表示版本替换,`hierarchy` 只表示结构包含;生命周期归 `LifecycleState`,结构修正状态归 `HierarchyStatus`。 ## 接口契约 @@ -182,7 +202,7 @@ DashScope Adapter 的 `params.enable_thinking` 由 Adapter 转换为 | 类型 | 关键字段 | 语义 | |------|----------|------| -| `MemoryUnit` | id / scope / tier / layers / segments / source / temporal / provenance / supersedes / tags / metadata / lifecycle | 记忆单元;id 在完整 Scope 内唯一 | +| `MemoryUnit` | id / scope / tier / layers / segments / source / temporal / provenance / supersedes / tags / metadata / lifecycle / hierarchy | 记忆单元;id 在完整 Scope 内唯一 | | `ContentLayers` | l0 / l1 | 分层披露标注(l0=50-100 字概要、l1=200-500 字要点 overview);默认空串,extractor 对超阈 content 产出 | | `Segment` | type / content / asset_ref / metadata | 内容段 | | `Temporal` | t_event / t_ingest / t_valid / t_invalid | 时间字段 | @@ -208,14 +228,97 @@ DashScope Adapter 的 `params.enable_thinking` 由 Adapter 转换为 | `Modality` | TEXT / IMAGE / AUDIO / VIDEO / CODE / DOCUMENT | | `LifecycleState` | ACTIVE / SUPERSEDED / ARCHIVED / FORGOTTEN | +目标新增的 `HierarchyKind`、`HierarchyRole` 和 `HierarchyStatus` 只在下节定义一次, +避免摘要表与精确枚举并存后发生漂移。 + +### 树结构(目标契约,尚未实现) + +```python +class HierarchyKind(str, Enum): + TIME = "time" + TOPIC = "topic" + DIRECTORY = "directory" + CLUSTER = "cluster" + CUSTOM = "custom" + +class HierarchyRole(str, Enum): + SNAPSHOT = "snapshot" + TIME_SPAN = "time_span" + SCENE = "scene" + EVENT = "event" + PROFILE = "profile" + ROOT = "root" + NODE = "node" + +class HierarchyStatus(str, Enum): + ACTIVE = "active" + DISMISSED = "dismissed" + +@dataclass +class HierarchyRef: + kind: HierarchyKind | None = None + role: HierarchyRole | None = None + parent_id: str = "" + child_ids: list[str] = field(default_factory=list) + # 与 child_ids 等长;空列表表示全部子节点与本 unit 完整 Scope 相同(兼容旧语义)。 + # 非空时 child_scopes[i] 为 child_ids[i] 的驻留 Scope,且必须与本 unit 同 org+space。 + child_scopes: list[Scope] = field(default_factory=list) + # None 表示 parent 与本 unit 完整 Scope 相同;非空时必须同 org+space。 + parent_scope: Scope | None = None + span_start: datetime | None = None + span_end: datetime | None = None + ordinal: int = 0 + status: HierarchyStatus = HierarchyStatus.ACTIVE + +# MemoryUnit 的目标增量字段;其余既有字段保持不变 +hierarchy: HierarchyRef = field(default_factory=HierarchyRef) +``` + +`HierarchyStatus` 只描述结构节点是否有效、被结构修正排除或等待确认,不包含 +`ARCHIVED`/`FORGOTTEN`;归档、遗忘与版本失效继续由 `LifecycleState` 表达。 +`parent_id=""` 表示根或尚未挂接,`child_ids` 是直接子节点的稳定有序列表, +`ordinal` 是同一父节点下的排序提示。非 TIME kind 可以不声明区间;一旦声明,仍须满足 +成对、顺序和父覆盖约束。TIME 的区间是结构覆盖范围,不替代 +`MemoryUnit.temporal` 的双时间,也不替代 `RecallChannel.TEMPORAL` 的召回过滤。 + +目标实现必须满足以下校验不变量: + +- 所有父子引用必须解析到**同 org + 同 space** 的 `MemoryUnit`;禁止跨 org / 跨 space + 结构边;禁止自环、祖先环。 +- `user` / `agent` / `session` 允许按 build profile 与本 unit 不同;此时必须用 + `child_scopes` / `parent_scope` 唯一定位,不得只靠裸 id 在错误命名空间里点读。 +- `child_scopes` 为空时,每个 `child_ids[i]` 在本 unit 的完整 Scope 下解析;非空时 + `len(child_scopes) == len(child_ids)`,且每个 `child_scopes[i]` 与本 unit 同 org+space。 +- 同一 kind 下每个节点最多一个非空 `parent_id`;当前契约不支持同 kind 多父。 +- 对每条边 `P -> C`,`C.parent_id == P.id` 当且仅当 `C.id` 在 `P.child_ids` 中;若使用 + scope 覆盖,则 `C` 侧 `parent_scope`(或缺省的本 unit scope)必须与 `P` 的驻留 Scope 一致。 +- `child_ids` 不重复;TIME 按区间起点或事件时间稳定排序,其他 kind 按 `ordinal` + 与领域稳定顺序排序。 +- 空结构定义为 `kind is None and role is None`;此时 `parent_id=""`、`child_ids=[]`、 + `child_scopes=[]`、`parent_scope is None`,且不得携带非空父子 id 或 span。旧数据没有 + `hierarchy` 时读取为空结构。 +- `provenance`、`supersedes`、`hierarchy` 不互相回填;披露级、多模态粒度、tier 与 + hierarchy 也不互相推导。 + +单父限制是本 spec 当前有效的目标契约。未来若引入同 kind 多父,必须先修订本契约和 +编解码/存储模型,再按 +[F07-memory-tree.md](../features/common/F07-memory-tree.md) 的独立边存储 +迁移条件更新实现;在此之前,多父输入必须被拒绝。 + ### MemoryUnit 编解码(`type_def/memory_codec.py`) 真源 KVStore 存**字节**,`MemoryUnit` 对象只在写入(`dumps`)与产出结果(`loads`)两处出现。编解码与 `MemoryUnit` 同住 `common/type_def`,纯函数、无存储后端依赖。 -- `dumps(unit) -> bytes`:`MemoryUnit` → JSON 字节,带 `_v` 版本号、枚举取 `.value`、时间取 isoformat。字段含 `layers`(`{l0, l1}`)。 +- `dumps(unit) -> bytes`:`MemoryUnit` → JSON 字节,带 `_v` 版本号、枚举取 `.value`、时间取 isoformat。字段含 `segments`、`layers`(`{l0, l1}`)。 - `loads(raw) -> MemoryUnit | None`:逆 `dumps`;非 dict 返回 `None`(KVStore 中混有索引/跟踪等非 unit 记录,靠此过滤)。 - **容错演进**:未知字段忽略、缺失字段取默认。加字段是兼容演进(老数据缺省读出,不升 `_v`);改字段含义/结构才升 `_v` 并在 `loads` 按 `_v` 分支。当前 `_v=3`(`_v=2` 为 segments 列表化;`_v=3` 把 scope 从 `org/user/agent/session` 扩展为 `org/space/user/agent/session`,老数据读为空 `space`)。 - `layers` 字段缺失时 `loads` 取空串 `ContentLayers()`——老数据无迁移读出。 +- `hierarchy` 缺失、非对象、字段缺失或包含未知扩展字段时安全读取:缺失/非对象读为空 + `HierarchyRef`,未知字段忽略。未知 kind/role/status 不得构造半有效结构,应把该 + hierarchy 降级为空并留下可观测诊断;写出侧只允许已定义枚举值。 +- 写入和接入路径对非法枚举或半有效结构执行严格拒绝;上述降级只用于兼容读取已经 + 存在的异常或未来版本数据。 +- 只有字段语义或结构发生破坏性变化时才提升 `_v`;增加可选枚举成员或可缺省字段不单独升版。 ### 工厂注册机制(`factory/factory.py`) @@ -292,12 +395,14 @@ def _build(config: ComponentConfig) -> Embedder: - `reset_all()` 清空缓存(隔离多次装配 / 测试隔离) 各 Producer 继承 `Factory`: -- `EmbedderProducer` / `ChunkerProducer` / `TokenizerProducer` / `NormalizerProducer` / `FeatureExtractorProducer` / `LlmProducer` / `RerankerProducer` / `AuditProducer` / `SecurityProducer` +- `EmbedderProducer` / `ChunkerProducer` / `TokenizerProducer` / `NormalizerProducer` / `FeatureExtractorProducer` / `LlmProducer` / `RerankerProducer` / `AuditProducer` / `SecurityProducer` / `LockProducer` ## 错误类型(`errors.py` / `security.py` / `lock.py`) | 异常 | 含义 | |------|------| +| `AgentMemoryError` | 所有项目自定义异常的根类型,供跨层统一捕获 | +| `ValidationError` | 输入参数、配置或数据结构违反契约 | | `ConflictError` | 资源冲突(id 已存在) | | `NotFoundError` | 资源不存在 | | `PermissionDeniedError` | 鉴权失败 |