sr-query
sr-query 针对 stockroom 仓库运行只读 SQL——这是一个单文件 DuckDB 数据库,存储了你捕获的 Cursor + Claude Code 历史记录。该表面层本身是只读的:DuckDB 会拒绝任何通过它尝试的写入操作,因此查询不会损坏任何数据。
何时使用 sr-query
当问题具有已知模式,并且答案是对你能命名的字段进行精确或结构化查找时,请使用 sr-query:
- 按 ID 查找特定行(
WHERE message_id = '…',WHERE session_id = '…')。 - 过滤器、计数和汇总(
COUNT(*),GROUP BY harness,WHERE harness = 'claude',token 求和,日期范围)。 - 通过视图
session_token_usage进行每个会话的 token 汇总(优先使用此视图,而非手动对messages进行SUM)。 - 跨
sessions/messages/tool_calls表的联接。 - “有多少”、“哪些会话”、“列出工具调用”、“什么模型”——任何你可以通过命名列用 SQL 表达的内容。
不要将 sr-query 用于基于含义的回忆(如“查找关于不稳定测试的对话”)。SQL 的 ILIKE 是字面子串匹配,而非语义搜索——对于含义搜索,请使用 sr-semantic 技能。当你不确定哪个合适时,应由 sr-search 技能来判断。
如何调用引擎
stockroom query "SELECT DISTINCT harness FROM sessions ORDER BY harness"
你也可以通过标准输入使用 - 来传递 SQL:
echo "SELECT count(*) FROM messages" | stockroom query -
如果 command -v stockroom 失败,则表示机器尚未设置:告诉用户运行 sr-initialize 技能,并且不要尝试其他调用方式。
输出规范:--format 和 --detail
默认设置对代理来说已经是安全的——裸调用会提供有限且可解析的输出——因此仅在特定情况下使用这些标志。
--format — 输出形状,默认为 tsv
| 值 | 形状 | 使用场景 |
|---|---|---|
tsv (默认) | 标题行 + 制表符分隔的行,无计数尾部 | 默认。对流友好,对 Unix 管道(cut,awk,wc -l)也友好。 |
json | 单个 {"columns": [...], "rows": [...]} 对象 | 用户要求结构化/机器输出,或者你想将其传递给 jq。 |
table | 列对齐的 ASCII,带 (N rows) 尾部 | 用户要求人类可读的输出/用于查看的复制粘贴命令。 |
优先使用默认的 tsv。当用户要求人类可读或结构化输出时,再提供 --format table 或 --format json——“如果你需要,还有其他形状可用。”
--detail — 每字段宽度,默认为 snippet
宽字符串字段(特别是 messages.text 和 tool_calls.tool_input)在读取时会被截断,以防止一个大的列充斥你的上下文。截断仅影响显示——完整文本始终可检索。
| 值 | 每字段预算 | 使用场景 |
|---|---|---|
compact | ~40 字符 | 在挑选一行之前,廉价地扫描许多候选行。 |
snippet (默认) | ~120 字符 | 默认。足以识别一行,而无需转储其全部内容。 |
full | 无限制,单行 | 你需要整个字段长度,并且单行折叠是可以接受的(表格/TSV 保持安全)。 |
raw | 无限制,精确空白 | 你需要原样存储的文本——保留换行符和内部空格。优先与 --format json 一起使用。 |
超出预算的字段会用一个标记进行省略,报告隐藏了多少字符,例如 …(+2284)。这个标记表明还有更多内容:如果你确实需要它,请以 --detail full(长度)或 --format json --detail raw(精确空白)重新获取特定行的那个字段——请参阅防护措施。
防护措施
以下是此技能旨在防止的故障模式:
- 不要撑爆你的上下文。 永远不要在
--detail full/raw下对多行执行SELECT *(或一个宽的text列)。相反:在--detail compact/snippet下使用明确的列列表和LIMIT进行窄范围扫描,确定你需要的行,然后只使用该行的 ID 和WHERE子句重新获取其宽字段。单个SELECT text FROM messages WHERE message_id = '…'是廉价的;而SELECT * FROM messages --detail full则是一个上下文炸弹。当你需要精确空白时(markdown 表格,代码块),请使用--format json --detail raw。 - 它是只读的——永远不要尝试写入。
INSERT/UPDATE/DELETE/CREATE均会失败(query failed: …)。不要通过不同的措辞重试写入;该表面层仅用于查询。 tool_input是异构 JSON。 其键因工具而异(Shell有command,Read有path,……),因此简单的tool_input->>'key'可能引发类型转换错误。通过在子查询/CTE 中首先按tool_name过滤,然后使用明确的函数来安全地提取:
stockroom query "SELECT json_extract_string(tool_input, '\$.command') AS cmd
FROM (SELECT tool_input FROM tool_calls WHERE tool_name = 'Shell') LIMIT 5"
无抖动处理错误
每个故障都会输出清晰的 stderr 消息和退出码——读取它并执行相应的操作,不要循环重试:
| 消息 | 退出码 | 含义 / 下一步操作 |
|---|---|---|
error: empty query (…) | 2 | 未传递 SQL。提供一个语句。 |
error: no warehouse found at … — run \stockroom ingest` first` | 1 | 仓库尚未构建。告诉用户运行 stockroom ingest(如果机器从未设置过,则运行 sr-initialize);不要重试查询。 |
query failed: … | 1 | SQL 无效,或写入被拒绝。修复语句(阅读 DuckDB 的消息)——不要重新运行相同的 SQL。 |
仓库中包含什么
首先发现实时模式——这能确保你获取的是最新的模式:
stockroom query "SELECT table_name, column_name, data_type
FROM information_schema.columns ORDER BY table_name, ordinal_position"
截至迁移 0001–0008 的关键列快速参考(请通过上述内省查询确认):
sessions— 每个会话一行。harness('cursor'|'claude'),session_id,project_id(项目目录的原始 slug——分组键;对于 Cursor CLI 聊天,这是聊天的哈希目录),cwd(真实路径,可能为NULL),workspace_key(跨 harness 的路径汇总,可能为NULL),entrypoint(已知时来自 harness 的表面来源;可能为NULL——使用SELECT DISTINCT entrypoint而非猜测字面值),models(VARCHAR[],仅 Cursor),title,git_branch,is_subagent,parent_session_id,started_at,ended_at,source_mtime(上次摄取时源记录的修改时间),以及四个可为空的会话级 tokenBIGINT字段(input_tokens,output_tokens,cache_creation_tokens,cache_read_tokens——仅当 harness 报告会话级使用量时才填充;Claude/Cursor 将其留为NULL)。主键(harness, session_id)。messages— 每轮对话一行。message_id = '{session_id}#{ordinal}'(在所有 harness 中一致),parent_id,ordinal,role('user'|'assistant'),text(完整内容;不捕获思考过程),model(每个消息,仅 Claude),ts,first_seen_at(stockroom 首次观察到该消息的时间),以及四个 tokenBIGINT字段:input_tokens,output_tokens,cache_creation_tokens,cache_read_tokens(Claude 消息使用量;Cursor 为NULL)。主键(harness, session_id, message_id)。session_token_usage— 基于 sessions 和消息 token 总和的视图。优先用于会话汇总。列:*_from_messages(SUM),*_native(会话列),*_total(COALESCE(native, from_messages)),token_grain('session'|'message'|'none')。总数是报告字段的仓库汇总,而非供应商发票。不要在*_total之上再次对消息 token 执行SUM。tool_calls— 仅工具输入(绝不包含输出)。message_id(发出该调用的轮次),ordinal,tool_name,tool_input(异构JSON,整体存储——请参阅防护措施)。主键(harness, session_id, message_id, ordinal)。embeddings— 用于语义搜索的每个分块向量(owner_table,owner_id,chunk_index,embed_model,vector FLOAT[384])。你很少直接查询此表——请使用sr-semantic技能。_sync_state— 摄取水位线簿记;对查询不感兴趣。
在编写联接之前,有一个值得了解的 identity 规则:始终在统一的 message_id / (harness, session_id) 上进行联接,而永远不要在 source_* 来源列上联接。某个值如果仅在每个 harness 的一个粒度上存在,那么在另一个粒度上则诚实地为 NULL(例如 messages.model 仅在 Claude 中;sessions.models 仅在 Cursor 中;token 字段也同样诚实地遵循这种双粒度方式)。
实际示例
所有示例均已针对真实仓库验证。
# 存在哪些 harness?(tsv 默认)
stockroom query "SELECT DISTINCT harness FROM sessions ORDER BY harness"
# 每个 harness 的会话计数,为人类用户提供可读格式:
stockroom query --format table "SELECT harness, count(*) AS n FROM sessions GROUP BY harness ORDER BY n DESC"
# 最繁忙的工具:
stockroom query "SELECT tool_name, count(*) AS calls FROM tool_calls GROUP BY tool_name ORDER BY calls DESC LIMIT 5"
# 廉价扫描候选消息,然后完整重新获取所需的那个:
stockroom query --detail compact "SELECT message_id, text FROM messages WHERE text ILIKE '%flaky test%' LIMIT 10"
stockroom query --format json --detail raw \
"SELECT text FROM messages WHERE message_id = '<id-from-the-scan>'"
# 为用户/工具提供结构化输出:
stockroom query --format json "SELECT harness, session_id, title FROM sessions WHERE title IS NOT NULL LIMIT 5"
# 每个会话的 token 汇总(视图 session_token_usage):
stockroom query --format table \
"SELECT harness, session_id, input_tokens_total, output_tokens_total, token_grain
FROM session_token_usage
ORDER BY input_tokens_total DESC NULLS LAST
LIMIT 10"
更多变体(具有活动窗口的无界工具、天粒度/harness token 汇总、每个 harness 的技能使用 SQL):请参阅 食谱。
食谱
复杂的入门 SQL:references/cookbook/。打开索引,然后只查找你需要的配方。
向人类呈现
你是工具的操作者,而非其显示器。运行 SQL,读取结果,然后用自然语言回答用户。除非用户要求查看原始 tsv,否则不要将原始 tsv 粘贴给人类。当用户确实想要原始输出——或一个他们可以自己运行的命令时,请提供 --format table(用于阅读)或 --format json(用于处理)的变体。
理解系统
要理解为什么这些约定是这样的——打包方法、torch 约定、构造上的只读、截断原则、identity/来源——请阅读共享的系统模型:../sr-search/references/system-model.md。