Model-facing session query tools
面向模型的会话查询工具
The unified `ctx.sessionQuery` service exposes exact reads, filters, relationship traces, and full-text search over live-preferred session logs, but models cannot use that service directly. Giving a model the provider request types would also expose unstable pagination cursors, trusted corpus scope, storage-shaped time values, and result records that are more convenient for programmatic consumers than for reasoning.
English
Problem
The unified ctx.sessionQuery service exposes exact reads, filters, relationship traces, and full-text search over live-preferred session logs, but models cannot use that service directly. Giving a model the provider request types would also expose unstable pagination cursors, trusted corpus scope, storage-shaped time values, and result records that are more convenient for programmatic consumers than for reasoning. Large traces and event payloads introduce a separate output-size concern, but solving that concern inside this consumer would duplicate the harness-wide spill mechanism and make session-query tools disagree with other tools.
Decision
@deepseek-ai/dsh-tool-session-query is the model-facing consumer of ctx.sessionQuery. It registers five narrow read-only tools: session_search, session_event_search, session_trace, session_event_trace, and session_event_read. The package imports the interface rather than the SQLite implementation, owns model argument validation and readable text rendering, and contributes one concise prompt section that teaches the prior-history search and search-to-trace/read workflow.
The package entrypoint is only the public composition root for configuration, prompt registration, and tool registration. Its internal modules follow the execution boundary: input.ts owns model schemas, normalization, and filter construction; service-boundary.ts contains provider calls and model-safe error translation; workspace-access.ts owns caller identity, workspace authorization, title access, and lineage projection; operations.ts orchestrates the five service workflows; and presentation.ts renders tool results and call cards. This keeps policy in its owning layer without changing the package contract.
session_search groups full-text matches by session and exposes typed session and event metadata filters. session_event_search searches one session, defaulting to the caller's current session. session_trace returns the complete authorized ancestor chain and recursive descendant trees. session_event_trace returns every known positional replacement and direct cited source-event relationship for one event. session_event_read returns the exact target event as unabridged JSON and optionally summarizes a bounded raw-event window; omitted before and after values mean target-only.
Model-facing filters use flat snake-case fields. Timestamps are timezone-qualified ISO 8601 strings at the tool boundary, convert to inclusive epoch-millisecond ranges for the service, and render as UTC ISO 8601. List values are ORed inside one filter while separate filters are ANDed. Requested parent ids are deduplicated and authority-filtered before FTS, so only parents in the caller workspace enter the provider clause; missing and cross-workspace guesses behave identically, while the root-session marker remains independently ORed into that clause. Event type strings remain open because SessionEventMap is merge-extensible; availability and event surface use closed values.
Workspace authority
Every executor derives its caller from immutable ToolExecution.exec.agent identity and never accepts a model-supplied workspace. A target is authorized only when its observed cwd exactly equals the caller session's cwd. Cross-session search always adds that workspace filter. Direct operations preflight the target and then validate the header returned from the same service observation as every event-search page, event trace, event read, lineage target, or folded title before rendering its payload. This prevents a live or persisted target replacement between the check and use from crossing the workspace boundary. Lineage rendering stops at an unauthorized ancestor or descendant subtree without revealing the hidden session id. A caller whose session has no cwd can inspect only its own session; missing agent identity fails closed.
Every trusted ctx.sessionQuery call crosses one model-boundary sanitizer. It checks the execution signal first, preserving caller cancellation exactly. For other failures it records the available corpus or provider diagnostic chain in the internal log on a best-effort basis, substituting a fixed placeholder when the value cannot be safely inspected. Diagnostic formatting and error classification are independently guarded, so an unprintable nested cause cannot escape or prevent a safely classified outer error, while unsafe classification or logging returns the fixed generic SESSION_QUERY_TOOL_FAILED code and message. Per-title failures use the same sanitizer before becoming unavailable markers. Tool-owned input-validation and authorization errors remain precise because they are created outside this service boundary.
The search tools expose prior work rather than the operation that is performing the search. session_search omits the caller's session. When session_event_search targets the caller's session, it intersects the requested sequence range with the event immediately before the current step/start, excluding the current assistant message and tool call as well as the query arguments indexed from that call.
Cursor-free results and spill
Neither search tool exposes a cursor, offset, page size, or model-controlled result limit. One execution follows provider cursors while the observed generation remains valid and collects up to the configured maxSearchResults, which defaults to 100. A capped result tells the model to narrow its query or filters; a generation change reports that the whole search must be retried. Search execution carries a configurable searchTimeoutMs, defaulting to 30 seconds, through the tool deadline and the service abort signal. Because internal pages share generation-bound cursors, both search tools are exclusive in the agent-loop scheduler; the exact trace and read tools opt into parallel sibling execution because their observations tolerate intervening commits.
Trace and read tools likewise expose no lineage or character pagination. Canonical results are plain text and remain complete within the service's existing event-window and search-count resource bounds. Each exact executor passes its unchanged tool-execution signal through target authorization and the service trace or read. Within service resolution, known-live event traces, event reads, and title reads remain persistence-free while honoring pre-abort. Session lineage tracing passes the signal to whole-corpus persistence listing; persisted event tracing and reading pass it to target listing and inspection. Each started backend call is awaited for cleanup before the exact abort reason is preserved, even when that backend ignored cancellation. The generic tools/post-execute spill policy owns inline byte retention: when a configured deployment receives oversized text, it replaces that text with a bounded preview plus an opaque locator and retrieval hint while preserving the complete result in its spill store. The session-query consumer neither imports ctx.spillStore nor implements a second truncation format.
Session-level results include the latest folded title when available. Each tool execution batches its unique title ids through one live-preferred corpus observation with at most the service's configured persistedInspectConcurrency workers, which defaults to four, and passes the exact tool-execution signal through persisted listing and inspection. Live sources fold directly; each persisted worker folds its completed source to a detached header/title observation and releases the full log before dequeuing another id, so the batch retains only small projected values. For the search tools, the execution signal carries the configured search deadline. Cancellation starts no queued title inspections and rejects the complete tool execution after already-started inspections settle; a missing, malformed, or operationally failed title remains isolated to that id, preserves the base result, renders an unavailable marker, and logs the underlying error, while an authorization mismatch fails closed. Search results include the strongest matching event and provider excerpt, traces include complete authorized relationships, and event reads keep neighbor presentation readable while reserving exact JSON for the requested target.
Host composition
The consumer is an opt-in plugin. Shipped host compositions do not mount it: the shipped TUI, Web, and headless surfaces keep the ctx.sessionQuery index (the SQLite service behind /resume and the Web content search) but not the model-facing consumer, so their default requests carry neither the query prompt nor the five schemas; the automation-only ACP composition also mounts neither (session-search-not-shipped-default). These compositions also supply the generic timeout and spill policies. The dedicated ACP snapshot fixture mounts the consumer and both policies explicitly, with private local spill storage. Generic tool presentation requires no session-query-specific client plugin.
Alternatives considered
- Expose provider cursors to the model — rejected because recording a tool result or starting the next model step changes the relevant session or global generation, so a cursor is usually stale before the model can reuse it.
- Add tool-local truncation, offsets, or spill files — rejected because the post-execute spill policy already owns complete-result retention and retrieval across tools.
- Allow every persisted session or model-supplied workspace filters — rejected because
ctx.sessionQueryis a trusted service and the model-facing consumer must enforce the caller's authority boundary. - Combine search, tracing, and exact reads into one operation selector — rejected because narrow names give the model clearer schemas, defaults, presentation intents, and follow-up choices.
- Return only one lineage hop — rejected because spill removes the inline-size motivation while one-hop output would omit relationships with no continuation path.
Verification
Package tests pin argument validation, filter translation, timestamp normalization, exact-workspace authorization, parent-filter preauthorization and oracle resistance, changed-observation rejection, service-diagnostic redaction for ordinary and adversarial unknown values, best-effort cyclic-cause logging, logger-failure containment, missing-identity behavior, hidden-boundary pruning, current-step exclusion, internal provider paging, exclusive search and parallel exact-read classification, count caps, exact-signal forwarding, abort-reason preservation, persistence cleanup quiescence, one-scan bounded batch title enrichment, projection-before-dequeue ordering, queued-work suppression, started-worker quiescence, per-header validation, title fallbacks, representative search/trace/read rendering, generic presentation, and disposable registration. Integration coverage uses the real SQLite FTS provider over live and persisted sessions. Shipped configuration and the TUI/Web composition tests prove that the model-facing consumer is absent from the TUI, Web, and headless surfaces, while assembled ACP request-header snapshots prove that the automation surface omits it by default. A package-owned Loader smoke and dedicated keyless ACP snapshot explicitly mount the consumer with timeout and spill support, pinning its prompt guidance, schemas, and path-independent exact event-read retention behavior.
Consequences
Models gain provider-independent access to prior session work without receiving storage authority or continuation state. Search has a finite per-call work bound and may require a narrower query to reach matches beyond the first 100; search calls cannot overlap siblings, while exact observations retain parallel scheduling. Complete traces and event payloads may become spill references instead of inline text. Exact string cwd equality favors a conservative security boundary over resolving symlink-equivalent paths. Custom compositions may mount the tool without spill, but then they explicitly accept complete inline trace and read results.
中文
问题
统一的 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)。这些组合还提供通用的超时与 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 的情况下使用该工具,但这表示它们明确接受完整追踪与读取结果直接出现在行内。