DeepSeek Harness session_search 超时:中文搜不到与一个坏会话拖垮全库
DeepSeek Harness(DSH)的会话检索看起来是一个「工具」,实际背后是一个可选的 SQLite 检索后端;它坏起来会同时给出三种完全不像同一件事的症状:session_search 一律恰好 30 秒超时(换关键词无效、重启无效、日志干净);中文子串搜不到(搜「刑事」找不到含「请分析这个刑事案件」的会话);升级之后侧边栏已经恢复,但 session_search 依旧 30 秒超时。 这三种表现分别对应三个机制:索引是内存态冷建的(path: ':memory:' + 单事务末尾提交,工具超时的中止信号一到就整次回滚),FTS5 默认分词器不切汉字(unicode61 把连续汉字当整 token),以及历史语料 revision 随任一写入整体变更(一个当前会话写入就让全部历史被重读重写)。前两条修起来很直接(落盘 + 调超时、换分词器重建索引),第三条要把迁移与建索引一次性做完(#9695、#9064、#8783)。
先纠一个常见前提:这套检索是可选的实验性 bundle(
dsh-experimental-session-search/optional-session-query-sqlite一类),不是所有安装的默认行为。看到 30 秒超时时,先确认你确实启用了它。
DeepSeek Harness 现象一:session_search 一律 30 秒整超时
一句话结论:这不是「语料太大所以慢」,而是「每次搜索都在从零冷建索引、而每次都建不完」——一个必然失败的结构,与查什么词无关。 症状特征很干净(#9695):
- 报错文本固定为
tool call timed out after 30000ms,时间总是 30 秒整; - 换任何查询词都一样——因为失败发生在建索引阶段,还没到匹配阶段;
- 重启无效、
err.log干净——中止是设计内的信号,不产生崩溃栈。
DeepSeek Harness 机制:内存 FTS + 单事务 + 中止即回滚
据报告者与社区核对源码,链路如下(#9695):
- 该检索后端被配成
path: ':memory:',即索引只活在内存里; - 每次搜索都要把整个语料冷建进内存 FTS,并且构建过程包在一个事务里、在末尾才提交;
- 工具的 30 秒预算一到就 abort,中止信号直达后端,返回
SESSION_QUERY_ABORTED并回滚; - 回滚之后索引等于不存在,下一次搜索又从零开始——只要一次建不完,就永远建不完。
DeepSeek Harness 编号修复:两处同改,缺一不可
只改一处没用:只调大超时会让每次搜索都慢到 30 秒以上;只落盘而不调超时,首次冷建仍可能被 30 秒掐断。步骤如下:
- 找到该检索插件的配置,把索引路径从内存改成磁盘上的持久文件:
# 在该插件(session-query-sqlite 类)的配置行里
path: "C:/Users/<你>/.dsh/storages/session-query/index.db"
# 原来是 ':memory:'
- 同时把搜索超时调大,给首次冷建留够时间:
searchTimeoutMs: 120000
# 默认与工具预算同为 30000 量级,首次建索引时不够用
- 重启 DSH,让配置生效。
- 验证:在会话里让 agent 调一次
session_search,预期第一次耗时较长(在冷建索引)、之后明显变快;再跑第二次同样查询,预期稳定在秒级而不是 30 秒整。 - 观察日志里是否还出现
SESSION_QUERY_ABORTED;不再出现即修复成功(#9695)。
有一份实测记录值得参考:有用户在后续版本上完成「落盘 + 调大超时」两处修改并复现修复,说明这条路是通的;关键是两处同改(#9695)。
DeepSeek Harness 现象二:中文(CJK)子串搜不到
一句话结论:FTS5 的默认分词器把连续汉字当成一个 token,所以「刑事」这种子串永远匹配不到「请分析这个刑事案件」。 这是分词问题,不是索引损坏(#9064)。
DeepSeek Harness 三步确认与修复
- 做一次对照:用同一段文本里的完整词去搜(如完整的四个字),预期能命中;再用其中的两字子串去搜,预期命中不了。这个差异是分词问题的指纹(#9064)。
- 换分词器:把 FTS5 的分词从
unicode61换成基于 trigram 的分词(或 ICU 分词)。trigram 会把文本切成三字符窗口,从而支持中文子串匹配;讨论中提到社区提供了面向中文的检索后端dsh-session-query-sqlite-cjk可直接替换使用(#9064)。 - 重建索引:分词器换了必须重建,否则旧索引仍是按旧分词建的——这一步是最容易漏的。重建时建议先落盘(见上一节的
path配置),避免重建过程又被 30 秒掐断。 - 验证:重跑第 1 步的两字子串查询,预期这次能命中;再确认原有的完整词查询仍然正常。
讨论中也给了不换分词器的临时办法(插入保留非字符 / 自定义分隔符后重建一次索引),但它属于「改数据形状」的绕过,不如换分词器干净(#9064)。
DeepSeek Harness 附带一条:一个不可读会话会让整次搜索失败
同一份讨论里还有第二层机制:读取器遇到不可读会话时不是跳过它,而是让整次查询失败。 报错是 SESSION_QUERY_PERSISTENCE_FAILED(而不是「搜索未启用」那类码),表现为「搜索功能整体坏掉」,实际原因是「库里有一条会话读不出来」(#9064)。
处置步骤:
- 先做区分,不要直接怀疑检索插件:用离线会话检查工具逐个看会话的健康状态,把不可读的那条(或那几条)找出来。
- 隔离或修好它,然后重试搜索;预期搜索恢复。
- 长期诉求:上游应当把不可读会话显式跳过并在结果里报告,而不是让一次查询整体失败——这也是讨论中提出的修复方向(#9064)。
DeepSeek Harness 现象三:新会话一写入,全部历史索引就失效
一句话结论:历史语料的 revision 被算成了「整个 store 的哈希」,所以哪怕历史成员一个没变,只要有任何一个当前会话写入,全部历史 revision 就会同时变化,检索于是每次都重读重写全部 legacy 会话。 这解释了「升级后侧边栏恢复了、但 session_search 依旧 30 秒超时」这个矛盾(#8783)。
DeepSeek Harness 编号恢复:迁移 + 一次性建索引 + 持久 path
- 确认是这一条:症状是侧边栏正常、
session_search每次 30 秒整超时,且报SESSION_QUERY_PERSISTENCE_FAILED;同时你在近期写入过任何会话(几乎必然成立)(#8783)。 - 完成历史会话的 v4 迁移(这一步是「一次性」的,别在迁移未完成的状态下反复搜索)。
- 一次性建好索引,并给它配持久的落盘
path(同现象一的配置);这可以避免「每次搜索都在重建」的循环。 - 验证:建好之后连做两次搜索——第一次允许较慢,第二次预期秒级返回;再写入一个新会话后重试,预期仍然秒级(如果又变慢,说明 revision churn 这一层仍在起作用,需等核心侧修复)(#8783)。
- 可复现的特征值得记下来:同一进程内对历史语料后缀取哈希是一致的,而跨进程不一致——这是判断「revision 计算口径错了」的旁证(#8783)。
DSH插件 排查注意事项
三条都落在同一个检索后端上,排查顺序建议是:先确认装没装、再看超时形状、再看搜索词语言、最后才怀疑数据。 八条要点:
- 先确认这是可选的实验 bundle,不是所有安装都有;别在没启用的环境里找问题(#9695)。
- 「30 秒整」是关键指纹:任何「恰好等于工具预算」的超时,都优先怀疑「一次做不完、每次重来」的结构,而不是性能不足(#9695)。
path与searchTimeoutMs必须同改,只改一个基本没用(#9695)。- 换分词器必须重建索引,否则改了等于没改(#9064)。
- 中文搜不到别当索引坏了:先用「完整词能中、子串不中」的对照确认是分词问题(#9064)。
- 一个坏会话会拖垮整库搜索:看到
SESSION_QUERY_PERSISTENCE_FAILED先从会话健康度查起(#9064)。 - 侧边栏好 ≠ 索引好:这两条路径的修复进度不同步,别用侧边栏正常来排除检索问题(#8783)。
- 修复状态如实:三条在讨论所指版本里都未修;落盘、调超时、换分词器、隔离坏会话都是用户侧处置,核心侧的 revision 计算口径与「不可读会话跳过」仍未改。
排查这类「工具莫名超时」的问题,很容易先怀疑插件冲突。如果你想先把插件变量排掉,与其在各处手敲命令,不如用 DSH Plugin Hub 这类可视化插件市场——已安装列表、装卸历史与系统日志都在一个面板里,确认「这套检索 bundle 到底装没装、最近动过什么」会快很多。

常见问题
DeepSeek Harness 的 session_search 每次都 30 秒超时,是因为索引在内存态冷建、每次搜索都从零开始。这个后端被配成 path: ':memory:',检索时把整个会话语料冷建进内存 FTS 并在单事务末尾才提交,而工具的 30 秒预算一到就 abort 到后端,触发 SESSION_QUERY_ABORTED 回滚,于是下次又从头开始,形成必然死锁。修法是把 path 落到磁盘并同时调大 searchTimeoutMs,两处都要改,首次冷建成功后之后都是增量。
DeepSeek Harness 搜中文搜不到,是因为 FTS5 默认使用 unicode61 分词器,它把连续的汉字当成一个整 token,所以子串匹配不到。可行的做法是换成基于 trigram 的分词或 ICU 分词器,并按 trigram 重建一次索引;讨论中提到社区提供了面向中文的检索后端可作为替代。注意分词器换掉后需要重建索引,否则旧索引仍然是按旧分词建的。
DeepSeek Harness 的这个报错,是因为读取器在遇到不可读会话时会直接失败整次查询,而不是跳过它。先做区分:用离线会话检查工具逐个看健康状态,把不可读的那条找出来;如果确实存在,就隔离或修好它,搜索随即恢复。这也是为什么这类故障看起来像「搜索坏了」而实际是「库里有一条会话读不出来」。
DeepSeek Harness 升级后仍 30 秒超时,是另一条机制:历史语料 revision 随任一写入整体变更。据社区逐行核对源码,迁移所需的历史会话 revision 被并进了整个 store 的哈希,所以任何一个当前会话写入都会让全部历史 revision 同时变化,导致每次搜索都重读重写全部 legacy 会话。恢复路径是完成官方 v4 迁移、一次性建好索引,并给该检索插件配一个持久的落盘 path。
相关术语
- session_search
- session_search 是 DeepSeek Harness 提供的会话检索工具,让 agent 在历史会话里做全文搜索。它由一个可选的 SQLite 检索后端(session-query-sqlite)支撑,属于实验性 bundle,不是所有安装都默认启用。— https://github.com/deepseek-ai/deepseek-harness/discussions/9695
- SESSION_QUERY_ABORTED
- SESSION_QUERY_ABORTED 是会话检索后端在收到中止信号后回滚当前事务并返回的错误码。当检索把全部语料放在一个事务里冷建索引时,工具超时触发的中止会让整次构建作废,形成「每次搜索都从零开始」的循环。— https://github.com/deepseek-ai/deepseek-harness/discussions/9695
- FTS5 unicode61 分词
- unicode61 是 SQLite FTS5 的默认分词器,按 Unicode 规则切分单词边界。它对拉丁文本有效,但把连续的汉字视为单个 token,因此「刑事」这类子串查询无法命中「请分析这个刑事案件」。— https://github.com/deepseek-ai/deepseek-harness/discussions/9064
- historicalCorpusRevision
- historicalCorpusRevision 是会话检索后端用来判断历史语料是否变化的一个 revision 值,理想上它应只反映历史会话的成员与内容变化。当前实现把整个 store 的哈希并了进来,导致任一当前会话写入都会让全部历史 revision 一起变化。— https://github.com/deepseek-ai/deepseek-harness/discussions/8783
来源
- #9695 — [Bug] session_search 永久 30s 超时::memory: FTS 索引 + 单事务对账 + 工具超时中止,大语料必然死锁· deepseek-ai(GitHub Discussions)
- #9064 — [Bug] session-query-sqlite: CJK substrings are not searchable, and one unreadable Session fails every search· deepseek-ai(GitHub Discussions)
- #8783 — [Bug] Session indexing still fails on 0.2.1-alpha.2: historical revision churn + isolated in-memory Agent index· deepseek-ai(GitHub Discussions)