dsh-session-index: A DSH full-text indexing plugin enabling agents to retrieve historical sessions

前言

用 DSH 开发智能体,很快会碰到一个具体问题:会话之间是互相隔开的。新开一轮对话,agent 查不到上一次说过什么、调用过哪些工具。想自己写插件订阅 session/event 攒一份记录也不够——session/event 是”提交后追加”的事件流,恢复(resume)的会话不会重放 seed 历史事件,旧会话的内容拿不到。

dsh-session-index 补的就是这个缺口:把所有会话内容建成一份跨会话的全文索引,并暴露成模型可以直接调用的工具。下面介绍它的定位、工作方式和安装步骤。

这是什么

dsh-session-index 是 DeepSeek Harness 的会话全文索引插件,以组合包(dsh.bundle)形式分发,作者是 longyu065,当前版本 0.1.0,MIT 许可。

它监听 session/event 事件流,把会话内容抽取成可检索文档,构建跨会话倒排索引,再通过 session_search / session_index_stats 两个工具暴露给模型,让 agent 能检索之前任何一次对话里说过什么、做过什么。

索引的内容包括:user/messageassistant/message 的 text(reasoning 可选)、tool/call(工具名 + 参数)、tool/resultsession/title

核心功能

实时索引与历史回填

每个 session/event 追加立即入索引,按 sessionId#seq 幂等去重,回填与实时事件重叠时不会重复计数。

针对恢复会话不重放历史事件的缺口,插件启动时做两步回填:

  1. 先用 ctx.sessions.list() 索引内存中的活动会话;
  2. 再用 ctx.sessionPersistence.list() 配合 readFrom(id, watermark+1),按水位线增量补全落盘的旧会话。

水位线同时存在内存和持久化索引文件里,重启后只补增量。

混合检索

preferFts 开启(默认)时,FTS5 与内存索引并跑、按会话去重合并:

  • 框架的 ctx.sessionQuery(SQLite FTS5)负责英文/词级召回;
  • 内置的二元组索引补足中文召回。FTS5 的 unicode61 分词器不做中文切词,整串 CJK 是单个 token,”可以跨会话搜索历史了”匹配不到”跨会话搜索”,二元组召回正好互补。

engine 字段报告实际使用的检索引擎:memory / fts5 / hybrid

富卡片

检索结果会投影成 Web UI 的 card:'search' 卡片:presentCall / presentResult 配合 output.presentationMeta,把命中按会话分组渲染成可展开列表,组头是会话 id 和标题,组内是命中片段。卡片数据随 tool/result 事件持久化,可回放。

持久化与清理

索引文档按 JSONL 追加到 $DSH_HOME/session-index/<sessionId>.jsonl,重启只补增量。会话销毁时(session/disposed),同步移除该会话的全部文档与索引文件。

隐私开关

两个配置项控制索引范围:

  • indexReasoning:默认关,不索引 CoT 思考文本;
  • indexToolResults:默认开,控制是否索引工具返回结果。

安装与启用

源码在 GitHub 仓库(链接见文末),本地拿到仓库后分三步安装:

# ① 打包(在仓库根目录)
pnpm pack          # 产出 dsh-session-index-0.1.0.tgz
# ② 装进 profile(web = 桌面端用的 profile)
dsh plugin --profile web add ./dsh-session-index-0.1.0.tgz
# ③ 重启桌面应用 / dsh web

为什么用 tarball 而不是 add ./目录:pnpm 对 link: 协议的本地目录包不会安装它的 dependencies(实测);pnpm pack 出的 tarball 是普通包,依赖会正常装进 profile 的 .pnpm 子树,bundle 内的 @deepseek-ai/* 导入才能解析。

组合包自带一份 cordis.patch.yml,做两件事:挂载插件本身;把框架自带的 FTS5(session-query-sqlite)从默认的 openAt: never 改成 openAt: first-search,持久化路径设为 $DSH_HOME/session-query.sqlite。这样 session_search 会自动进入混合检索。不想用这层覆盖,就在自己 profile 的 cordis.patch.yml 里再覆盖该行——patch 按层后写覆盖前写。

配置项

默认值 说明
dataDir ''(自动 = $DSH_HOME/session-index 索引落盘目录,填自定义路径可改位置
maxResults 20 session_search 默认最大命中数
maxSnippetChars 240 摘要片段最大字符数(Unicode 码点)
maxDocChars 4000 单条文档索引的最大字符数,截断防工具结果膨胀
indexReasoning false 是否索引 assistant 的 reasoning 思考文本
indexToolResults true 是否索引工具返回结果
preferFts true 是否优先用框架 FTS5 做混合检索,false 则纯内存索引

开发与测试

插件源码是单文件可擦除 TS(src/session-index.ts),Node 22.18+ 原生类型剥离即可加载。运行依赖 @deepseek-ai/cordis ^4.0.1@deepseek-ai/dsh-tools ^0.1.0-rc.6@deepseek-ai/schemastery ^3.18.1,随 tarball 正常装进 profile。

本地开发时,@deepseek-ai/* 依赖需要先做个符号链接:

mkdir -p node_modules && ln -sfn <dsh安装目录>/node_modules/@deepseek-ai node_modules/@deepseek-ai

<dsh安装目录> 通常是 ~/.npm/_npx/<hash>/,桌面端与 dsh web 共用同一份。tsc 类型检查还需要 @types/node

测试和构建:

node test-index.mjs    # 引擎独立测试,不启动 dsh,65 项断言
pnpm run build         # tsc 编译 src → dist/ → index.js

适用场景与注意

适合的场景:希望 agent 能”回忆”之前任何一次对话内容的 DSH 用户——查上次的结论、翻之前的工具调用记录、跨会话延续上下文,都靠这份索引。

使用前注意几点已知限制:

  • 索引目录应由单个 dsh 进程独占,多进程共享同一 dataDir 未做并发保护;
  • 中文按二元组召回,单字查询(如”插”)只能命中孤立单字文档,建议至少输入两字;
  • 框架 FTS5 不索引 session/titlekind='title' 过滤走内存索引;
  • 卡片内暂无”跳转原会话”交互(框架 wire format 无 link/action 块),卡片携带会话 id 和标题,配合侧边栏定位;
  • session/disposed 只清理插件自己的内存与 JSONL 文件,不影响原始会话日志。

另外,插件以当前 dsh 进程的权限运行。它的源码是单文件,安装前把 src/session-index.ts 过一遍、确认 MIT 许可符合预期,成本不高。

结尾

dsh-session-index 做的事不复杂:把所有会话内容建成一份可检索的索引,暴露成两个工具,agent 因此能查到之前任何一次对话里说过什么、做过什么。索引、回填、混合检索、卡片展示都在包内完成,MIT 许可,源码单文件可审。

  • GitHub:https://github.com/longyu065/dsh-session-index
  • 社区目录页:https://www.skillhub.cn/plugins/longyu065/dsh-session-index

社区目录是独立站点,与 DeepSeek / 幻方无官方从属关系,仅作插件索引使用。

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye