session query 提供什么
session-query 家族提供独立于压缩之外、对实时与持久会话日志的授权检索。它回答代理或人类可能想要的两类事情:
- 搜索——通过全文搜索按会话(跨语料)或按事件(会话内)查找先前的工作。
- 追踪与读取——跟随谱系(某会话的父级与后代)、检查确切事件、读取 surface,并导出原始日志。
这一切的事实来源是每个会话的会话日志,由 packages/session/session-persistence-jsonl 持久化为持久的原始历史。查询是这条日志的消费者(ctx.sessions 实时加上动态挂载的 ctx.sessionPersistence);它从不写入日志。
包家族
| 包 | 角色 | ctx key |
|---|---|---|
session-query | Service Definition:可信读取、关系查询、过滤、搜索契约 | ctx.sessionQuery |
session-query-sqlite | 具体 provider:SQLite FTS5 全文搜索、对账、分页 | ctx.sessionQuery |
tool-session-query | 面向模型、受工作区授权的搜索/追踪/读取工具 | 注册到 ctx.tools |
session-log-export | Web /export 命令、共享下载状态、结果弹窗 | ctx.sessionLogDownload |
session/session-persistence-jsonl | 事实来源的 JSONL 持久日志后端 | ctx.sessionPersistence |
服务契约(dsh-session-query)
SessionQueryEngine 是组合后的抽象 ctx.sessionQuery 契约。它实现精确的会话历史检索、关系追踪与 provider 无关的过滤;具体后端实现它的两个全文方法。匹配的 id 产生一条记录——实时事件取胜——而 live 与 persisted 同时报告两种来源的可用性;冲突的不可变 header 以 SESSION_QUERY_SOURCE_CONFLICT 失败。
读取操作
| 读取 | 用途 |
|---|---|
listSessions(signal?) | 克隆合并后的逻辑语料,最新在前。 |
readSession(sessionId) | 一条完整的分离原始日志,采用与 resume 相同的核心重放校验;绝不进入实时存储。 |
filterSessions(filters, signal?) | 对克隆语料应用会话元数据谓词。 |
filterEvents(sessionId, filters) | 提取第一方语义文档并按升序 seq 进行过滤。 |
listEvents(sessionId) | 把每个事件分类为 current、shadowed 或 log-only。 |
readSurface(sessionId) | 克隆 header + 以模型历史顺序折叠完成的当前 surface。 |
readEvent(request, signal?) | 克隆 header + 完整目标事件 + 有界的原始 seq 窗口。 |
traceSession(sessionId) | 从内到外的祖先加上确定性的递归后代树。 |
traceEvent(request) | 克隆 source header,带直接的位置替换与被引的 source 链接。 |
readTitleSnapshots(sessionIds) / readTitle(sessionId) | 面向 UI 的逐会话标题观测。 |
listSessions() 保持轻量——它不加载日志也不索引标题。持久化是可选的,可以动态挂载/卸载;针对已知实时会话的标题/事件/trace 读取不会咨询持久化,因此持久后端的健康问题不会让当前内存状态不可读。
过滤器
SessionResultFilter 覆盖 id、可空 cwd、创建时间范围、可空父级与来源可用性。SessionEventResultFilter 覆盖 seq/时间范围、事件类型、surface 与语义文本。过滤器数组内部按 AND 合并;单一条款内的值按 OR 合并;空列表值不匹配任何东西;格式错误的范围以 SESSION_QUERY_INVALID_FILTER 失败。文本条款刻意是一种字面语义文本扫描,而非全文查询:调用者文本被转义成一个 Unicode、大小写不敏感的正则,其中每个空白段匹配一个或多个空白字符。
两个抽象搜索方法
searchSessions(request, exec?) 按最强匹配事件对逻辑语料分组;searchEvents(request, exec?) 搜索单个逻辑会话。两者都返回其续接为一个自有的 branded SessionSearchCursor 的页,接受可选取消,并暴露不带 provider 专属数字分数的片段。搜索请求只接受元数据事件过滤器。
SessionQueryError.code 是一个封闭联合:SESSION_QUERY_ABORTED、…_CORRUPT_SESSION、…_EVENT_NOT_FOUND、…_INDEX_FAILED、…_INVALID_CONFIG、…_INVALID_CURSOR、…_INVALID_FILTER、…_INVALID_LIMIT、…_INVALID_QUERY、…_INVALID_LINEAGE、…_INVALID_SURFACE、…_INVALID_WINDOW、…_PERSISTENCE_FAILED、…_SEARCH_DISABLED、…_SESSION_NOT_FOUND、…_STALE_CURSOR、…_SOURCE_CONFLICT。
| 配置 | 默认 | 契约 |
|---|---|---|
readWindowMax | 50 | before/after 原始事件计数上限。 |
persistedInspectConcurrency | 4 | 一次批量读取中并发持久日志检查的上限。 |
SQLite provider(dsh-session-query-sqlite)
SqliteSessionQueryEngine 继承确切的读取、追踪与过滤器,并以 SQLite FTS5 实现两个全文方法。查询是必填、经 trim、空白归一化的字面短语——OR、NEAR、* 等 FTS5 语法被当作数据,而非可执行的 MATCH 语法。元数据过滤器是在排序之前应用的参数化 SQL 谓词。
相关性在持久表与 TEMP 表之间可比较:实际 FTS5 高亮匹配跨度数降序,然后是存储文档码点长度升序;事件时间、会话 id 与 seq 打破平局。片段是空白归一化的纯文本,以 Unicode 码点限界(snippetChars,默认 240)。游标绑定到归一化后的请求与服务实例,并在相关代变化时失败。
一个串行化的状态机比较带来源限定的轻量持久快照修订,非变更性地只检查新增/已变日志,提取共享语义文档,事务性地对账,然后执行查询。持久 FTS 行位于专用派生数据库中;连接本地 TEMP 表持有实时行,为同一会话遮蔽持久基表。
| 配置 | 默认 | 契约 |
|---|---|---|
path | 必填 | 专用派生索引 SQLite 路径;支持 :memory:。 |
openAt | startup | startup / first-search / never。 |
journalMode | wal | wal / delete / truncate / persist。 |
defaultLimit | 20 | 请求省略 limit 时的页大小。 |
maxLimit | 100 | 接受的最大请求页大小。 |
snippetChars | 240 | 片段的码点长度上限。 |
readWindowMax | 50 / persistedInspectConcurrency | 4——继承的读取配置。 |
分词器是 FTS5 unicode61:这是token 识别(token recall),不是任意子串识别——AI 不匹配 token BRAID;需要字面扫描时请用带 text 条款的 filterEvents()。Node 的同步 DatabaseSync 在 MATCH 执行期间会阻塞 JavaScript 线程,且无法中断正在运行的语句。
面向模型的工具(dsh-tool-session-query)
这个可选包注册 session_search、session_event_search、session_trace、session_event_trace 与 session_event_read。它不默认挂载到发货的宿主组合中。
| 配置 | 默认 | 含义 |
|---|---|---|
maxSearchResults | 100 | 跨内部 provider 页收集的授权非自身命中上限。 |
searchTimeoutMs | 30000 | 附加到两个全文搜索工具的协作式截止时间。 |
调用者只来自 ToolExecution.exec.agent。跨会话访问要求目标会话与调用者会话的 cwd 值精确相等;没有 cwd 的调用者只能检查自身。搜索从不暴露 provider 游标或模型可控的限制。session_search 总是省略调用者会话;当前会话的 session_event_search 在调用它的 step 之前停止,因此活跃的 assistant 输出与被记录的 tool call 不能匹配自身。谱系输出会把未经授权的祖先/后代边界替换为不含任何隐藏会话 id 的标记。该包刻意不做任何字节或字符截断,也不导入 spill 后端;需要约束内联输出的部署自行挂载 @deepseek-ai/dsh-spill-policy。
日志导出(dsh-session-log-export)
Web /export 命令与 Session log 头部动作通过宿主流式端点 GET /api/session.export?sessionId=<id>&includeDescendants=true 把原始会话日志下载为 ZIP。宿主半部(在 packages/host/apiproxy 中)拥有 ZIP 生成、原始 JSONL/zstd 读取、后代、附件、背压与 HTTP 错误语义;这个 Web 包拥有按钮、一个下载控制器、共享弹窗以及触发浏览器下载的 command/executed 确认。
/export 记录一条人类命令生命周期;/export <path> 返回错误(浏览器下载通过浏览器普通的下载行为选择目的地)。宿主端点在 readRaw 之前刷新一个实时的根会话,因此由斜杠命令触发的 ZIP 包含其确认触发下载的那一对 command/run + command/done。
JSONL 事实来源(session-persistence-jsonl)
每个会话拥有的持久日志默认是一个 Zstandard 压缩的 JSONL 文件,布局为 <root>/--<normalized-cwd>--/<encoded-id>/session.jsonl.zstd。第一逻辑行是不可变的 SessionHeader(带 type: 'session',且 seq 保持连续——events[i].seq === i);每个后续行是一条存储记录,或针对一段 ≥3 个同块 assistant/chunk 增量的打包分块行(packChunks 默认 true)。存储是追加式的且具备崩溃修复:不完整的尾部帧会被截断,并以合成的 tool/step/turn 闭合符重新开放。查询引擎通过一个 SessionPersistence 接缝读取同一持久语料,因此搜索、追踪与导出一致地看到同一事实来源。
已知限制
- 服务内部无调用者授权——
ctx.sessionQuery是可信的上下文级基础设施;只有工具(exec.agent+cwd相等)与 Web 动作限制访问。 - token 识别,而非子串——字面扫描请用
filterEvents()。 - 单一拥有者的派生索引——每个 SQLite
path必须只由某个服务/进程拥有。 - 搜索限制在部署上限——没有续接 token;工具会请模型收窄查询。
延伸阅读
- 子代理 与 工作流与 Ralph——兄弟家族;
session_query工具让它们的代理恢复先前的工作。 - 术语表——
turn、step、round界定了 query 事件的含义。 - 仓库内的子系统参考:
docs/subsystems/session-query.md。 - README:
packages/session-query/session-query/README.md、packages/session-query/session-query-sqlite/README.md、packages/session-query/tool-session-query/README.md、packages/session-query/session-log-export/README.md。 - 持久日志格式:
packages/session/session-persistence-jsonl/README.md与src/format.ts。 - Agent Note:
.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.md与2026-07-13-session-query-tracing.md。