Files
hermes-agent/docs/2026-09-10_074105-session-search-project-scope-v4.md

23 KiB
Raw Permalink Blame History

项目内历史对话召回与语义检索完整方案 V4

For Hermes: Use subagent-driven-development when implementation is authorized. This document is a plan, not authorization to modify business code, migrate user data, commit, push, deploy, or send historical conversations to an external service.

Goal: 会话具有独立的项目归属属性;用户移动会话只修改归属;模型能够按需通过关键词与语义混合检索找回同项目历史原文。

Architecture: 分两阶段交付:A 完成 V3 的 project_id、创建/移动/列表与项目内关键词召回;B 在其上建立可重建的原文片段向量索引,并扩展现有 session_search 为项目内混合检索。原始消息和会话项目归属是事实来源,向量是派生数据。工作目录独立,不生成项目或会话摘要,不自动注入历史上下文。

Tech Stack: 现有 Python、SQLite/FTS5、SessionDB、TUI Gateway、Desktop TypeScript。语义层首选同项目片段的精确相似度计算;embedding 模型及必要依赖由小规模实测后确定,不预先引入独立向量服务。

状态与版本: 完整独立文件,包含 V3 基础能力和语义层,不需要同时阅读 V3 才能实施。替代此前版本作为后续设计依据,原文件保留。本文件是实施方案,不是功能已经完成或测试已经通过的证明。


1. 范围与交付目标

1.1 包含

  1. sessions 保存可空 project_id,和 cwd/git_repo_root 分离。
  2. 新建、草稿落库、恢复、压缩延续、独立分支正确保存或继承归属。
  3. 显式移动会话改变整条逻辑对话的项目归属,不移动消息、不改变工作目录。
  4. 项目会话列表和 session_search 按持久化归属工作。
  5. 项目内关键词、语义及混合检索,返回原文和消息锚点。
  6. 原文片段向量增量更新、删除/修改处理、中断恢复、模型切换后的重建。
  7. 索引未配置、未完成或不可用时,项目内关键词检索仍可用。

1.2 不包含

  • 项目摘要、会话摘要、结论蒸馏、自动每回合召回注入。
  • 新的核心工具、独立记忆管理平台、MemoryProvider 槽位占用。
  • 项目级权限撤销协议、项目版本平台、消息级项目授权、操作系统沙箱。
  • 全局 MEMORY/USER、技能、其他插件的项目隔离改造。
  • 旧会话自动按目录归类或复杂批量迁移审批系统。
  • 独立向量数据库服务、常驻队列平台或未验证必要性的近似索引。

2. 业务合同

操作 历史所属项目 工作目录
在项目中创建对话 保存选定 Project.id 使用既有默认或用户选择
未选择项目创建 NULL 按现有逻辑
恢复对话 读取已存 project_id 读取已存目录
更改目录 不变 更新 cwd 与 git 元数据
移动对话到项目 更新整条逻辑对话的 project_id 不变
移出项目 更新为 NULL 不变
修改项目名称或 folders 已有归属不变 不自动修改
搜索历史 使用当前会话持久归属 不参与过滤

项目归属是业务分类。移动前已经开始的检索可以完成,移动完成后发起的新查询按新归属执行;不取消在途工具、不要求先停止 Agent。已返回到其他会话的内容不会被追溯撤回。

无归属会话不是共同项目:不搜索其他 NULL 会话,但保留当前逻辑对话离开上下文后的压缩历史恢复能力。

同 profile 的显式项目 ID 才是归属值。相同路径不代表跨 profile 同项目,工具不通过任意 profile 参数或链接自动扩大检索范围。全局历史管理 UI 不因此变成权限系统。

3. 现有代码与改动落点

以下是已定位的现有文件;新字段、模块和测试均是拟新增设计项,不冒称已有 API。

职责 现有位置 预期改动
表结构及升级 hermes_state_common.py、hermes_state_schema.py project_id、索引、增量迁移
会话创建/更新/浏览 hermes_state_sessions.py 保存归属、移动、继承、项目过滤
搜索路径 hermes_state_search.py FTS/CJK/LIKE/回退的项目过滤
工具 tools/session_search_tool.py 项目内所有形状、混合检索接线
Agent 实际入口 agent/inline_tool_executors.py 当前会话上下文及检索参数透传
项目及目录 RPC tui_gateway/methods_session.py、methods_projects.py、methods_config.py 创建和移动归属、区分目录操作
项目列表 tui_gateway/project_tree.py 会话成员按 project_id 分组
桌面状态 apps/desktop/src/store/projects.ts 创建/移动请求、刷新与错误状态
项目存储 hermes_cli/projects_db.py 复用真实 Project 对象和校验路径
配置 hermes_cli/config_defaults.py 及实际设置/加载路径 embedding 配置及启用说明

会话插入/upsert、压缩延续、branch/new/reset 的具体调用者在实施前追踪后纳入。不得仅修改 facade 而漏掉实际 sibling 绑定;新逻辑过大时使用专题 sibling,不做无关拆分。

4. 阶段 A:项目归属与关键词召回

4.1 数据模型

sessions 增加 project_id TEXT NULL。在现有迁移机制中追加字段和必要索引,旧记录保留 NULL,原始消息不变。索引由实际 WHERE/排序和 EXPLAIN 决定,不预设多个冗余索引。

projects.db 与 state.db 是现有分库,不能直接声称有跨库 SQLite 外键。创建/移动入口校验目标项目存在且属于当前 profile;NULL 表示移出项目。归档保留归属;硬删除仍有绑定会话的项目时拒绝并提示先移动,避免孤儿归属。

upsert 必须区分“未提供 project_id”和“显式传入 NULL”,恢复或重复保存不得清空已有归属。

4.2 逻辑对话与移动

正常移动只更新属性。若用户可见的一条对话由多个历史压缩 session 记录组成,在同一个现有 SessionDB 写事务内更新其 project_id:

  • 原地压缩无需移动消息。
  • 压缩延续继承同一归属,和逻辑对话一起移动。
  • 独立 branch 创建时继承项目,之后独立移动。
  • reset/new 的新对话初始化项目,但不跟随旧对话后续移动。
  • 委派记录维持现有隐藏规则。

不能使用沿所有 parent 边走根的宽泛 helper 代替逻辑对话判定。现有代码区分 branch/reset/压缩,需复用其准确语义并测试。

移动与压缩延续创建采用现有数据库串行写事务;延续创建在写事务中读取最新来源归属,不从长期缓存复制旧 project_id。这是数据一致性要求,不新增项目锁服务或移动禁用机制。

4.3 旧会话与自动仓库入口

旧会话为 NULL,用户直接通过“移动到项目”归类即可。不做目录推断自动回填。

自动发现的 repo 路径不是 Project.id。该入口用于创建项目内对话或成为移动目标时,复用已有项目创建/查重能力取得稳定 ID。仅浏览仓库不必批量创建项目;不要把路径写进 project_id。

4.4 前后端接线

项目内新建与草稿第一次持久化都传入归属。恢复从 DB 读取,不按全局当前项目或 cwd 重算。

提供一个窄职责的设置会话项目 RPC,方法名在核实注册表后确定,入参使用已有会话标识和可空 project_id,返回实际写入结果。前端失败时不改成成功状态;成功后刷新源项目、目标项目和会话信息。

session.cwd.set 和 session.workspace.move 保留目录操作语义,不隐式改归属;“移动到项目”不能继续只调用 workspace.move。项目成员按 project_id 展示,repo/lane 和目录信息尽量沿用现有布局,不重写整个侧栏。

4.5 项目内检索

Agent 每次调用从可信当前 session/profile 读取当前归属,不长期缓存移动前的 ID。默认当前项目,无须添加只有 project 一个取值的 scope 参数。

查询时在 sessions 的 JOIN/WHERE 中施加 project_id,发生在候选 LIMIT 之前。不做全库先截再筛、不建立全量成员表、不将历史集合截为 900/2000 条。

关键词、标题、browse、read、scroll、首尾片段、窗口、lineage 展开和 profile fallback 均遵守项目范围;内部 SessionDB 可保留未过滤查询供其他可信调用者使用,但 Agent 不能把 NULL/读取错误当成不过滤。

保留既有角色、隐藏来源、压缩/撤销可见性和 bounded browse 行为。NULL 情况只恢复自身可靠压缩延续历史,不把 branch/reset/delegation 全树当作自身。

原文 session_id 与 message_id 必须配对,保留链接。不改系统提示词或已发送历史消息,只用工具结果进入上下文。

5. 阶段 B:语义与混合检索

5.1 技术路线与前置小实验

首版选用原文片段 embedding + 项目内精确余弦相似度 + 排名融合。先验证精确扫描的正确性、中文效果和实际规模耗时;不因“语义检索”直接引入远程向量服务或近似索引。

向量存储优先在现有 profile 的 state.db 中新增派生表,便于直接 JOIN sessions/messages 实现前置过滤。大 BLOB 对现有备份、数据库大小和读写锁的影响必须实测。若该选择不能通过规模验证,更新语义层存储选择,不绕过项目前置过滤,不阻塞阶段 A 的独立交付。

相似度可使用已验证可用的数值实现;仓库某些 optional extra 中出现 numpy 不表示基础安装已有该依赖。新增依赖必须明确声明、设置版本上界并更新锁文件。不要先写出项目里不存在的 embedding client/import。

5.2 模型配置与数据发送

正式回填前确定:embedding provider、模型及版本、向量维度、输入上限、批大小、超时,以及本地或远程运行方式。通过现有配置与 setup 路径设置,不增加非秘密 HERMES_* 环境变量,不读取或打印密钥。

建议拟用独立 session_search.semantic 配置节承载 enabled、模型路由与索引预算;最终字段在核实现有配置模式后确定。语义功能默认未配置/关闭时阶段 A 正常工作。

本方案保存不代表用户授权把历史发送给第三方。先使用合成测试对话验证候选模型;远程历史回填需确认服务方和项目范围,界面/设置说明后续新消息也会送该 embedding 服务。没有可用路由时不安装大型本地模型或猜用聊天模型代替。

5.3 原文切片与派生数据

按单条原始消息稳定切片,长消息分段并有有限重叠。保持消息 ID 和字符范围;命中后利用既有窗口读取相邻对话,不先总结再 embedding,也不把完整长会话当一个向量。

片段大小依据实际模型输入上限和 tokenizer 确定,明确 token/字符单位,不能混用。默认索引 user/assistant 文本;工具正文只有在用户启用相应索引范围后处理,其余显式 tool-role 查询仍走现有关键词路径并标明语义覆盖不足。索引未解析附件二进制,不把 base64 送入 embedding。

拟新增派生表使用清晰字段:session_id、message_id、片段起止位置、原文内容标识、切片策略版本、embedding 模型指纹/维度、向量、处理状态和必要时间戳。具体表名在实现时确定,原文仍由 messages 读取,不保存另一份权威消息。

模型指纹包含端点/提供方、模型身份、维度、归一化与切片版本等影响兼容性的非秘密信息,不能含 token。不同指纹的查询向量和索引向量不可混用。

不保存用于授权的 project_id 副本。向量通过 message/session 关联当前 sessions.project_id。片段标识稳定,唯一键保证重试不产生重复有效片段。

5.4 增量索引与中断恢复

初次索引按用户选择的项目分批执行;之后对已启用范围的新建/修改消息增量处理。先定位已有消息写入、更新、删除和可见性修改路径,复用实际持久化边界,不新造通用事件平台。

最低实现为派生行状态和内容标识:pending、ready、failed 可恢复。正文变化使旧片段失效并重建;仅记录最大 message_id 不能识别旧消息编辑。写入端不要做 embedding 网络调用。

使用嵌入宿主的有界后台批处理,明确启动、唤醒、关闭和每批预算;不建设独立常驻服务。进程退出后从持久化 pending/failed 恢复,不依赖 Agent 子进程存活。若多个进程同时执行,用现有 SQLite 事务做最小任务领取/幂等写入,不新增调度平台。

开始 embedding 前读取原文标识,结果落库前再检查原文仍存在且未变化;旧网络结果不能覆盖已编辑或已删除消息。网络期间不持有长写事务。首次回填可重试、可中断、可继续;失败记录原因,不无限重试阻塞用户回复。

查询端同时检查原消息当前可见性和内容标识,尚未物理清理的失效向量不能贡献结果。删除/撤销立即通过原文 JOIN 和可见性条件排除,后台清理派生行;压缩归档按既有历史规则保留,不能一律排除 active=0。分支副本与原消息各自保留正确来源。

移动会话只修改 project_id,不重算已有向量。若目标项目已启用语义但该会话尚未索引,正常增量批处理补齐缺片段;若目标未启用,不因为历史已有向量就绕过配置调用远程模型。

切换模型后逐步生成新指纹索引,旧指纹不可参与新模型检索。新索引部分覆盖时可返回部分语义结果并明确状态,关键词保持覆盖;按项目过滤后不会为了补足结果搜索其他项目。

5.5 查询与融合

仅 query discovery 启用语义;browse/read/scroll 继续直接读取,不产生无意义的 embedding 调用。

查询顺序:

  1. 从 DB 解析当前会话项目及现有角色/可见性限制。
  2. 在该范围执行既有关键词检索。
  3. 配置可用时生成查询向量;SQL 先 JOIN 当前 sessions/messages,过滤 project_id、有效原文、模型指纹和角色,再计算该集合的向量相似度。
  4. 精确扫描可分批读取整个允许集合,用固定大小候选堆保存最相关结果,不先按时间 LIMIT 一批向量,也不在全库 Top-K 之后才过滤项目。
  5. 关键词与语义按匹配消息锚点归并,以 Reciprocal Rank Fusion(基于排名而非原始分数相加)生成混合候选,再执行既有会话去重与结果展开。
  6. 对命中消息读取原文窗口,返回实际 session_id/message_id/link。窗口仍遵守阶段 A 的项目和可见性规则。

建议给现有 session_search 增加 search_mode=hybrid|keyword|semantic,默认 hybrid;函数、schema、registry 和 inline 映射同步。模式仅控制算法,不改变项目范围。keyword 便于精确符号检索和对照;semantic 用于用户明确的相似问题追溯及效果测试。

hybrid 在语义不可用时返回关键词结果并注明实际模式与原因。显式 semantic 失败应报告不可用,不冒称结果来自语义;用户或模型可改用 keyword。全程共用调用截止时间,不串行无限等待 embedding。

现有 sort=newest/oldest 保留为项目内结果的时间偏好,在两路候选融合后使用一致的已文档化顺序;不是承诺搜遍所有命中。确切排名参数通过固定测试集验证后在代码/配置中明确,不混用可变随机参数。

5.6 覆盖与降级状态

工具结果最少携带 requested_mode、used_mode、semantic_status(disabled/ready/partial/unavailable)、必要的截断/超时提示和匹配来源(keyword/semantic/both)。不要回传向量 BLOB。

“零命中”与“语义未索引完整”分开。已处理到最大 ID 不等于索引覆盖完整;覆盖统计依据已选范围的有效消息/片段状态,无法准确计算时报告 unknown/partial,不编造百分比。

失败不改变项目过滤,关键词 FTS/CJK/LIKE 的现有回退继续可用。原始历史与关键词检索不以语义服务成功为前提。

6. 实施阶段与退出条件

A1:归属字段与数据行为

主要文件:hermes_state_common.py、hermes_state_schema.py、hermes_state_sessions.py 及实际创建/压缩调用点。

拟新增测试 tests/test_session_project_assignment.py:旧库升级、NULL 与省略区别、移动不改 cwd/消息、压缩延续共同移动、branch/reset 不级联、移动与延续创建的事务顺序。

先写失败测试,再实现最小字段和事务更新,再跑绿色回归。退出条件:数据属性正确,不触碰用户实际迁移。

A2:界面与 RPC

修改第 3 节对应 TUI/桌面文件,接通项目内新建、草稿、恢复、移动、列表刷新及自动 repo 转显式项目。拟新增 tests/tui_gateway/test_session_project_assignment.py;扩展 projects.test.ts、现有项目树与 profile 测试。

退出条件:更改目录不换项目,移动项目不改目录;失败不误报成功,刷新恢复一致。

A3:关键词项目召回

修改 tools/session_search_tool.py、agent/inline_tool_executors.py、hermes_state_search.py 和必要会话读取函数。拟新增 tests/agent/test_session_search_project_assignment.py,扩展 tests/tools/test_session_search.py。

退出条件:真实内联/registry 和全部搜索/读取形状按项目工作;其他项目同词不返回;移动后新查询使用新归属;原文链接可定位。

A4:阶段 A 独立交付

真实可见 Desktop 建测试项目 P/Q:P 留历史,P 新对话能召回;cwd 改到 Q 目录仍属于 P;移动后 Q 可查、P 后续不可查;旧 NULL 会话手动移动后可查。相关 Python/TS 测试、独立代码复审通过后阶段 A 可单独交付,不等待 embedding 选型。

B0:语义可行性实验

先检查当前仓库可复用模型配置、client、依赖和消息持久化钩子。可用则复用,不假定 memory 插件中的能力天然能用于 SessionDB。

用合成且明确标注的中文项目对话实验,验证候选模型的同义改写、符号查询、项目先过滤、精确扫描耗时/内存、向量表对 DB/备份影响。生成真实实验记录,包含模型、维度、实际规模、批大小、耗时、排名结果;不得用模拟 embedding 冒充真实效果。

阶段 A 不因 B0 网络/模型未配置而阻塞;但 B0 未通过,不宣称语义层可交付。正式历史回填前确认本地/远程服务及项目范围。

B1:派生表、切片与增量任务

拟新增专题模块:hermes_state_semantic.py(派生存储/查询)、agent/session_semantic_index.py(切片与增量处理)。仅在不存在可复用同职责模块时创建,避免 facade 继续膨胀。

按第 5 节设计创建派生 schema、原文切片、模型指纹、状态恢复,接通实际消息写入/编辑/删除路径。拟新增 tests/test_session_semantic_index.py。

退出条件:重复处理幂等,中断可续,编辑/删除后旧结果不可见,网络晚到结果不会写回旧内容,原地压缩与旧式压缩历史符合可见性合同。单元测试可用确定性向量测试算法,不将其当模型实测。

B2:混合检索与配置

拟新增 tools/session_search_semantic.py 承载算法辅助;现有工具入口调用它,不新增工具。修改 schema、inline/registry 参数传递、配置及用户文档。拟新增 tests/tools/test_session_search_semantic.py,补充真实内联测试。

退出条件:过滤先于向量候选截断,RRF 融合去重正确,关键词/语义模式含义明确,移动无需 embedding 重算,故障/部分覆盖状态准确,关键词独立可用。

B3:效果验证与最终验收

使用事先固定且与调参样例分开的评估集:中文同义改写、精确函数名/错误码、多个会话相似话题、其他项目干扰、旧历史、无匹配查询。人工标注目标消息与可接受原文范围,不以模型自己说“相关”代替标准。

记录 keyword、semantic、hybrid 的 Top-K 目标命中、相关性和耗时;报告实际数据,不预先伪造指标。退出要求:同义改写能找回词面不同的目标;符号查询由关键词保底,不因融合明显退化;所有返回的原文属于当前项目;失败回退真实有效。B0 形成固定规模/延迟预算,B3 按该预算验收而非边跑边放宽。

可见 Desktop 用真实模型主动调用 hybrid,点击原文链接;移动已索引会话后再次检索,并通过索引记录/embedding 请求计数证明移动未触发已有片段重算。测试关闭语义服务时关键词仍能工作。

7. 测试命令与测试边界

Python 一律使用 scripts/run_tests.sh。基础文件存在后执行:

scripts/run_tests.sh tests/test_session_project_assignment.py tests/tui_gateway/test_session_project_assignment.py tests/agent/test_session_search_project_assignment.py
scripts/run_tests.sh tests/tools/test_session_search.py tests/tui_gateway/test_project_tree.py tests/hermes_cli/test_profiles_sidebar_scope.py
scripts/run_tests.sh tests/test_session_semantic_index.py tests/tools/test_session_search_semantic.py
scripts/run_tests.sh tests/tools/ tests/tui_gateway/ tests/hermes_cli/ tests/agent/ tests/test_hermes_state.py

新测试路径是拟新增;实施时将实际发现的根目录 state/search/压缩专题测试加入,不将目录测试误当全部覆盖。TS 测试/typecheck/lint 使用实际 package.json 脚本。新增依赖后按仓库要求更新锁文件。

测试均使用临时 HERMES_HOME、真实 SessionDB 和隔离项目数据;禁止改用户其他 profile 或实际历史。行为单测与真实 embedding 网络实测分开标注。既有失败记录基线,候选造成的失败必须修复。

8. 风险、恢复和边界

  • 本地/远程模型尚未选定: B0 后冻结;无配置时保持关键词可用,不猜用聊天模型。
  • 远程隐私和费用: 仅授权范围回填,设置批量/输入/超时预算;不自动发送所有 profile 历史。
  • 精确扫描规模: SQL 项目过滤后流式扫描;实际超预算再评估索引,不提前引入外部平台。
  • 派生表增长: 不复制完整正文,记录向量/锚点;实测 DB/备份开销,支持只清理派生索引再重建。
  • 模型切换: 不混用指纹,新模型逐步重建,partial 期间关键词保底。
  • 停止或崩溃: 持久化状态恢复;原文不依赖 worker,索引结果落库前校验原文标识。
  • 回滚: 关闭 semantic 即恢复阶段 A,不删除历史或 project_id。退回不识别 project_id 的旧代码可能恢复旧宽检索/目录分组,不能当作满足新要求的安全回退。
  • 移动语义: 后续查询按新归属,已开始的查询可以完成;不撤销已复制的历史,不建设版本撤销协议。

9. 评审结论与实施前确认

设计自审:V3 的简单项目属性模型完整保留;语义层只增加可重建的检索索引,不承担项目归属权威,不包含任何摘要。取消 V1/V2 的额外权限/归属扫描设计。

实施前确认项仅限语义技术落地:模型运行位置/服务、向量维度与输入限制、正式回填范围、实测性能预算。不是重新选择业务方案,也不阻断阶段 A。

本文件作为完整方案保存。架构方向已在会话中评审认可;向量性能与模型效果尚未实测,后续代码必须经过测试和独立复审。文档写入成功不代表实施通过。