# Agent Note: 面向模型的会话查询工具

Status: implemented

[English](2026-07-24-model-facing-session-query-tools.md) | 中文

## 问题

统一的 `ctx.sessionQuery` 服务对优先使用实时数据的会话日志提供精确读取、过滤、关系追踪与全文搜索，但模型无法直接使用该服务。若把提供方请求类型交给模型，还会暴露不稳定的分页游标、受信任的语料范围、存储形态的时间值，以及更适合程序化消费者而非模型推理的结果记录。大型追踪与事件负载另有输出大小问题，但若在该消费者内部解决，就会重复 harness 的通用 spill 机制，并使会话查询工具与其他工具的行为不一致。

## 决策

`@deepseek-ai/dsh-tool-session-query` 是 `ctx.sessionQuery` 面向模型的消费者。它注册五个职责单一的只读工具：`session_search`、`session_event_search`、`session_trace`、`session_event_trace` 和 `session_event_read`。该包依赖接口而非 SQLite 实现，负责模型参数校验与易读文本渲染，并贡献一个精简的提示词段，说明历史搜索以及从搜索转向追踪／读取的工作流。

该包入口仅作为配置、提示词注册与工具注册的公开组合根。内部模块沿执行边界划分：`input.ts` 负责模型 schema、规范化与过滤条件构造；`service-boundary.ts` 包含提供方调用与面向模型的安全错误转换；`workspace-access.ts` 负责调用者身份、工作区授权、标题访问与谱系投影；`operations.ts` 编排五个服务工作流；`presentation.ts` 渲染工具结果与调用卡片。这样可让策略留在其所属层，同时不改变包契约。

`session_search` 按会话聚合全文匹配，并公开带类型的会话与事件元数据过滤条件。`session_event_search` 搜索一个会话，默认目标为调用者的当前会话。`session_trace` 返回完整的已授权祖先链与递归后代树。`session_event_trace` 返回一个事件所有已知的位置替换关系与直接来源关系。`session_event_read` 以未删节 JSON 返回准确的目标事件，并可选择汇总一个有界的原始事件窗口；省略 `before` 与 `after` 时只返回目标。

面向模型的过滤条件使用扁平的 snake-case 字段。工具边界上的时间戳采用带时区的 ISO 8601 字符串，转换为服务使用的闭区间毫秒时间戳，并以 UTC ISO 8601 渲染。同一个过滤条件中的列表值按 OR 组合，不同过滤条件按 AND 组合。请求的父会话 id 会在 FTS 之前去重并按权限过滤，因此只有调用者工作区中的父会话会进入提供方条件；缺失与跨工作区的猜测具有相同行为，而根会话标记仍会独立按 OR 加入该条件。由于 `SessionEventMap` 可通过声明合并扩展，事件类型字符串保持开放；可用状态与事件表层使用封闭取值。

## 工作区权限

每个执行器都从不可变的 `ToolExecution.exec.agent` 身份推导调用者，绝不接受模型提供的工作区。只有当目标观测中的 `cwd` 与调用者会话的 `cwd` 完全相同时，目标才获授权。跨会话搜索始终附加该工作区过滤条件。直接操作先预检目标，然后在渲染负载前，校验与每一页事件搜索结果、事件追踪、事件读取、谱系目标或折叠标题来自同一服务观测的会话头。这样，即使实时或持久化目标在检查与使用之间被替换，也无法跨越工作区边界。谱系渲染在遇到未授权的祖先或后代子树时停止，且不泄露被隐藏的会话 id。调用者会话没有 `cwd` 时只能检查自身会话；缺少 agent 身份时按失败关闭处理。

每个受信任的 `ctx.sessionQuery` 调用都会经过同一个模型边界净化器。它首先检查执行信号，准确保留调用者取消。对于其他失败，它会尽力把可获得的语料或提供方诊断链写入内部日志；当值无法安全检查时，则改用固定占位符。诊断格式化与错误分类各自受到保护，因此无法打印的嵌套 cause 既不会逃逸，也不会阻止对外层错误进行安全分类；分类不安全或日志记录失败时，则返回固定的通用错误码 `SESSION_QUERY_TOOL_FAILED` 及其消息。逐标题失败也会先经过同一个净化器，再转为不可用标记。工具自身的输入校验与授权错误在该服务边界之外创建，因此仍保留精确消息。

搜索工具公开的是既往工作，而不是正在执行搜索的操作本身。`session_search` 排除调用者会话。`session_event_search` 以调用者会话为目标时，会把请求的序号范围与当前 `step/start` 之前的最后一个事件取交集，从而排除当前 assistant 消息、工具调用，以及从该次调用中建立索引的查询参数。

## 无游标结果与 spill

两个搜索工具都不向模型公开游标、偏移量、页大小或模型可控的结果限制。一次执行会在观察到的代保持有效时持续跟随提供方游标，并收集不超过配置项 `maxSearchResults` 的结果，其默认值为 100。达到上限的结果会要求模型缩小查询或过滤范围；代发生变化时会报告必须重试完整搜索。搜索执行通过工具截止时间与服务中止信号传递可配置的 `searchTimeoutMs`，默认值为 30 秒。由于内部页面共享与代绑定的游标，两个搜索工具在 agent loop 调度器中都以独占方式执行；精确追踪与读取工具则允许和兄弟工具并行执行，因为其观测可以容忍期间发生的提交。

追踪与读取工具同样不公开谱系分页或字符分页。规范结果采用纯文本，并在服务已有的事件窗口与搜索数量资源边界内保持完整。每个精确执行器都会将未经替换的工具执行信号传递给目标授权与服务追踪或读取。在服务解析过程中，已知实时事件追踪、事件读取与标题读取在遵循预中止的同时仍不访问持久化。会话谱系追踪会将该信号传递给全语料持久化列表；持久化事件追踪与读取则将其传递给目标列表和检查。每个已启动的后端调用都会等待清理完成后再保留准确的中止原因，即使该后端忽略了取消也不例外。通用的 `tools/post-execute` spill 策略负责行内字节保留：当已配置的部署收到过大的文本时，该策略会用有界预览、不可透明推导的定位符与读取提示替换文本，同时在 spill 存储中保留完整结果。会话查询消费者既不导入 `ctx.spillStore`，也不实现第二套截断格式。

会话级结果在可用时包含最新折叠标题。每次工具执行都会通过一次优先使用实时数据的语料观测批量读取唯一标题 id，最多使用服务通过 `persistedInspectConcurrency` 配置的持久化检查 worker，其默认值为 4，并将准确的工具执行信号传递给持久化列表与检查操作。实时来源会直接折叠；每个持久化 worker 都会把已完成的来源折叠为分离的会话头／标题观测，并在取出下一个 id 前释放完整日志，因此批次只保留小型投影值。对于搜索工具，该执行信号携带已配置的搜索截止时间。取消不会启动排队中的标题检查，并会在已经启动的检查全部完成后拒绝完整的工具执行；标题缺失、格式错误或发生操作性失败时，错误只影响对应 id，同时保留基础结果、渲染不可用标记并记录底层错误，而授权不匹配则按失败关闭处理。搜索结果包含最强匹配事件与提供方摘录，追踪包含完整的已授权关系，事件读取保持邻近事件表现易读，同时只为被请求的目标保留精确 JSON。

## 宿主组合

该消费方是一个需显式启用的插件。已交付的宿主组合不挂载它：已交付的 TUI、Web 与无头界面保留 `ctx.sessionQuery` 索引（即 `/resume` 与 Web 内容搜索背后的 SQLite 服务），但不挂载面向模型的消费方，因此其默认请求既不携带查询提示词，也不携带五个 schema；仅用于自动化的 ACP 组合也两者均不挂载（[session-search-not-shipped-default](2026-08-02-session-search-not-shipped-default.md)）。这些组合还提供通用的超时与 spill 策略。专用的 ACP 快照 fixture（测试前置数据）显式挂载该消费方与这两项策略，并使用私有的本地 spill 存储。通用工具表现无需会话查询专用客户端插件。

## 考虑过的替代方案

- **向模型公开提供方游标**：不予采纳，因为记录工具结果或开始下一个模型步骤会改变相关会话或全局代，导致游标通常在模型能够复用前就已过期。
- **增加工具本地截断、偏移量或 spill 文件**：不予采纳，因为执行后 spill 策略已经统一负责各工具的完整结果保留与读取。
- **允许访问所有持久化会话或由模型提供工作区过滤条件**：不予采纳，因为 `ctx.sessionQuery` 是受信任服务，面向模型的消费者必须执行调用者权限边界。
- **把搜索、追踪与精确读取合并为一个带操作选择器的工具**：不予采纳，因为职责单一的名称能为模型提供更清晰的 schema、默认值、表现意图与后续选择。
- **只返回一层谱系**：不予采纳，因为 spill 已消除行内大小方面的理由，而单层输出会遗漏关系且没有继续读取路径。

## 验证

包级测试固定参数校验、过滤条件转换、时间戳规范化、精确工作区授权、父级过滤预授权与抵御预言机探测、变更观测拒绝、普通值与对抗性未知值的服务诊断脱敏、尽力记录循环 cause、日志失败隔离、身份缺失行为、隐藏边界裁剪、当前步骤排除、内部提供方翻页、搜索独占与精确读取并行分类、数量上限、精确信号传递、中止原因保留、持久化清理静止、单次扫描且并发有界的批量标题扩充、先投影再取出下一个任务的顺序、抑制排队工作、等待已启动 worker 静止、逐会话头校验、标题回退、代表性搜索／追踪／读取渲染、通用表现与可释放注册。集成覆盖使用真实 SQLite FTS 提供方查询实时与持久化会话。发布配置与 TUI/Web 组合测试证明面向模型的消费方不存在于 TUI、Web 与无头界面，而组装后的 ACP 请求头快照证明自动化界面默认不包含它。包自身的 Loader 冒烟测试与专用无密钥 ACP 快照显式挂载该消费方，并配套启用超时与 spill 支持，固定其提示词指引、schema 以及与路径无关的精确事件读取保留行为。

## 后果

模型无需获得存储权限或继续状态，即可通过与提供方无关的方式访问既往会话工作。搜索具有有限的单次调用工作边界，若要命中前 100 条以后的结果，可能需要缩小查询；搜索调用不能与兄弟工具重叠执行，而精确观测仍可并行调度。完整追踪与事件负载可能表现为 spill 引用而不是行内文本。严格的 `cwd` 字符串相等选择了保守安全边界，而不解析通过符号链接等价的路径。自定义组合可以在不挂载 spill 的情况下使用该工具，但这表示它们明确接受完整追踪与读取结果直接出现在行内。
