sr-query
sr-query exécute du SQL en lecture seule sur l’entrepôt stockroom — la base de données DuckDB monofichier de votre historique capturé de Cursor et Claude Code. La surface est en lecture seule par construction : DuckDB rejette toute écriture tentée via cette interface, vous ne pouvez donc rien corrompre en interrogeant.
Quand utiliser sr-query
Utilisez sr-query lorsque la question a une forme connue et que la réponse est une recherche exacte ou structurée sur des champs que vous pouvez nommer :
- Une ligne spécifique par id (
WHERE message_id = '…',WHERE session_id = '…'). - Filtres, comptages et regroupements (
COUNT(*),GROUP BY harness,WHERE harness = 'claude', sommes de tokens, plages de dates). - Regroupements de tokens par session via la VUE
session_token_usage(préférez cette vue à unSUMmanuel surmessages). - Jointures entre
sessions/messages/tool_calls. - "Combien", "quelles sessions", "liste les appels d'outils", "quels modèles" — tout ce qui peut s'exprimer en SQL sur des colonnes nommées.
N'utilisez pas sr-query pour une recherche basée sur le sens ("trouve les conversations sur les tests flaky"). SQL ILIKE est une correspondance textuelle littérale, pas une recherche sémantique — pour le sens, utilisez la compétence sr-semantic. Quand vous n'êtes pas sûr de la bonne compétence, le jugement appartient à la compétence sr-search.
Comment invoquer le moteur
stockroom query "SELECT DISTINCT harness FROM sessions ORDER BY harness"
Vous pouvez aussi envoyer du SQL depuis l'entrée standard avec - :
echo "SELECT count(*) FROM messages" | stockroom query -
Si command -v stockroom échoue, la machine n'est pas configurée : dites à l'utilisateur d'exécuter la compétence sr-initialize, et n'essayez aucune autre invocation.
Discipline de sortie : --format et --detail
Les valeurs par défaut sont déjà sûres pour un agent — un appel simple donne une sortie bornée et analysable — donc utilisez les drapeaux seulement quand la situation le nécessite.
--format — forme de la sortie, défaut tsv
| Valeur | Forme | Utilisez-le quand |
|---|---|---|
tsv (défaut) | Ligne d'en-tête + lignes séparées par des tabulations, pas de compteur de lignes | Par défaut. Adapté au flux pour vous et pour les pipes Unix (cut, awk, wc -l). |
json | Un seul objet {"columns": [...], "rows": [...]} | Un utilisateur demande une sortie structurée/machine, ou vous voulez la passer à jq. |
table | Colonnes alignées en ASCII avec une ligne (N rows) | Un utilisateur demande quelque chose de lisible / une commande copier-coller à regarder. |
Commencez par le défaut tsv. Proposez --format table ou --format json quand l'utilisateur le demande pour une sortie lisible ou structurée — "ces autres formes existent si vous les voulez."
--detail — largeur par champ, défaut snippet
Les champs chaîne larges (notamment messages.text et tool_calls.tool_input) sont tronqués à la lecture pour qu'une colonne volumineuse n'inonde pas votre contexte. La troncation est purement d'affichage — le texte complet est toujours accessible.
| Valeur | Budget par champ | Utilisez-le quand |
|---|---|---|
compact | ~40 caractères | Parcourir de nombreuses lignes candidates à bas coût avant d'en choisir une. |
snippet (défaut) | ~120 caractères | Par défaut. Assez pour reconnaître une ligne sans la décharger entièrement. |
full | illimité, sur une seule ligne | Vous avez besoin de toute la longueur du champ, et le repli sur une ligne convient (tableaux/TSV restent sûrs). |
raw | illimité, espaces exacts | Vous avez besoin du texte tel que stocké — retours à la ligne et espaces internes intacts. À privilégier avec --format json. |
Un champ dépassant le budget est élagué avec un marqueur indiquant combien de caractères ont été cachés, par exemple …(+2284). Ce marqueur est votre signal que plus de contenu existe : si vous en avez besoin, récupérez uniquement ce champ pour la ligne spécifique à --detail full (longueur) ou --format json --detail raw (espaces exacts) — voir les garde-fous.
Garde-fous
Ce sont les modes de défaillance que cette compétence existe pour empêcher :
- N'inondez pas votre contexte. Ne faites jamais
SELECT *(ou une large colonnetext) à--detail full/rawsur de nombreuses lignes. Au lieu de cela : scannez étroitement à--detail compact/snippetavec une liste de colonnes explicite et unLIMIT, identifiez la ligne que vous voulez, puis récupérez uniquement le champ large de cette ligne avec unWHEREsur son id. Un simpleSELECT text FROM messages WHERE message_id = '…'est bon marché ;SELECT * FROM messages --detail fullest une bombe de contexte. Quand vous avez besoin exactement des espaces (tableaux markdown, blocs de code), utilisez--format json --detail raw. - C'est en lecture seule — n'essayez jamais d'écrire.
INSERT/UPDATE/DELETE/CREATEéchouent tous (query failed: …). Ne réessayez pas une écriture avec une formulation différente ; l'interface ne fait qu'interroger. tool_inputest un JSON hétérogène. Ses clés varient selon l'outil (Shellacommand,Readapath, …), donc untool_input->>'key'naïf peut lever une erreur de cast. Extrayez en toute sécurité en filtrant d'abordtool_namedans une sous-requête/CTE et en utilisant la fonction explicite :
stockroom query "SELECT json_extract_string(tool_input, '\$.command') AS cmd
FROM (SELECT tool_input FROM tool_calls WHERE tool_name = 'Shell') LIMIT 5"
Gérez les erreurs sans vous acharnier
Chaque échec est un message propre sur stderr + un code de sortie — lisez-le et prenez l'action correspondante, ne bouclez pas :
| Message | Sortie | Ce que cela signifie / action suivante |
|---|---|---|
error: empty query (…) | 2 | Aucun SQL n'a été passé. Fournissez une requête. |
error: no warehouse found at … — run \stockroom ingest` first` | 1 | L'entrepôt n'a pas été construit. Dites à l'utilisateur d'exécuter stockroom ingest (ou sr-initialize si la machine n'a jamais été configurée) ; ne relancez pas la requête. |
query failed: … | 1 | SQL invalide, ou une écriture a été rejetée. Corrigez la requête (lisez le message DuckDB) — ne réexécutez pas le même SQL. |
Ce qui se trouve dans l'entrepôt
Découvrez d'abord le schéma en direct — cela reste correct à mesure que le schéma évolue :
stockroom query "SELECT table_name, column_name, data_type
FROM information_schema.columns ORDER BY table_name, ordinal_position"
Référence rapide des colonnes importantes depuis les migrations 0001–0008 (confirmez avec la requête d'introspection ci-dessus) :
sessions— une ligne par conversation.harness('cursor'|'claude'),session_id,project_id(slug du répertoire projet — la clé de regroupement ; pour les chats Cursor CLI, c'est le répertoire de hachage des chats),cwd(chemin réel, peut êtreNULL),workspace_key(regroupement de chemins multi-harnais, peut êtreNULL),entrypoint(provenance de surface du harnais quand connue ; peut êtreNULL— utilisezSELECT DISTINCT entrypointplutôt que de deviner des littéraux),models(VARCHAR[], Cursor uniquement),title,git_branch,is_subagent,parent_session_id,started_at,ended_at,source_mtime(dernière modification de la transcription source à la dernière ingestion), et quatre champsBIGINTde tokens granulaires pour la session (input_tokens,output_tokens,cache_creation_tokens,cache_read_tokens— remplis uniquement quand un harnais rapporte l'utilisation au niveau de la conversation ; Claude/Cursor les laissentNULL). PK(harness, session_id).messages— une ligne par tour.message_id = '{session_id}#{ordinal}'(uniforme entre les harnais),parent_id,ordinal,role('user'|'assistant'),text(complet ; la pensée n'est pas capturée),model(par message, Claude uniquement),ts,first_seen_at(première observation par stockroom), et quatre champsBIGINTde tokens :input_tokens,output_tokens,cache_creation_tokens,cache_read_tokens(utilisation des messages Claude ; CursorNULL). PK(harness, session_id, message_id).session_token_usage— VUE sur les sessions + sommes de tokens des messages. Préférez-la pour les regroupements de conversation. Colonnes :*_from_messages(SUM),*_native(colonnes de session),*_total(COALESCE(native, from_messages)),token_grain('session'|'message'|'none'). Les totaux sont des regroupements dans l'entrepôt des champs rapportés, pas des factures de fournisseur. N'ajoutez pas non plus deSUMsur les tokens de message par-dessus*_total.tool_calls— entrées d'outils uniquement (jamais les sorties).message_id(le tour qui l'a émise),ordinal,tool_name,tool_input(JSON hétérogène, stocké intégralement — voir le garde-fou). PK(harness, session_id, message_id, ordinal).embeddings— vecteurs par chunk pour la recherche sémantique (owner_table,owner_id,chunk_index,embed_model,vector FLOAT[384]). Vous interrogez rarement cela directement — utilisez la compétencesr-semantic._sync_state— comptabilité de la marque d'ingestion ; pas intéressant à interroger.
Une règle d'identité à connaître avant d'écrire une jointure : joignez toujours sur le message_id / (harness, session_id) uniforme, jamais sur les colonnes de provenance source_*. Une valeur qui n'existe qu'à une granularité par harnais est honnêtement NULL pour l'autre (par exemple, messages.model est Claude uniquement ; sessions.models est Cursor uniquement ; la même honnêteté de double granularité s'applique aux tokens).
Exemples concrets
Tous vérifiés contre un entrepôt réel.
# Quels harnais sont présents ? (tsv par défaut)
stockroom query "SELECT DISTINCT harness FROM sessions ORDER BY harness"
# Nombre de sessions par harnais, lisible pour l'utilisateur :
stockroom query --format table "SELECT harness, count(*) AS n FROM sessions GROUP BY harness ORDER BY n DESC"
# Outils les plus actifs :
stockroom query "SELECT tool_name, count(*) AS calls FROM tool_calls GROUP BY tool_name ORDER BY calls DESC LIMIT 5"
# Parcourir les messages candidats à bas coût, puis récupérer le message choisi en entier :
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>'"
# Sortie structurée pour un utilisateur/outil :
stockroom query --format json "SELECT harness, session_id, title FROM sessions WHERE title IS NOT NULL LIMIT 5"
# Regroupements de tokens par session (VUE 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"
Plus de variantes (outils sans limite avec fenêtre d'activité, regroupements de tokens par jour/harnais, SQL d'utilisation de compétence par harnais) : voir le Cahier de recettes.
Cahier de recettes
SQL de démarrage coriace : references/cookbook/. Ouvrez l'index, puis seulement la recette dont vous avez besoin.
Transmission à un humain
Vous êtes l'opérateur de l'outil, pas son affichage. Exécutez le SQL, lisez le résultat, et répondez à l'utilisateur en langage naturel. Ne collez pas du tsv brut à un humain sauf s'il a demandé à le voir. Quand il veut la sortie brute — ou une commande à exécuter lui-même — donnez-lui une variante --format table (pour lire) ou --format json (pour traiter).
Comprendre le système
Pour comprendre pourquoi ces contrats sont comme ils sont — le packaging, le contrat de torche, la construction en lecture seule, la doctrine de troncature, l'identité/la provenance — lisez le modèle système partagé : ../sr-search/references/system-model.md.