sr-query은(는) 무엇을 하나요?
sr-query는 stockroom 웨어하우스 — Cursor + Claude Code 기록이 담긴 단일 파일 DuckDB 데이터베이스 — 에 대해 읽기 전용 SQL을 실행합니다. 표면은 설계상 읽기 전용입니다. DuckDB가 이를 통한 모든 쓰기를 거부하므로 질의로 데이터를 손상시킬 수 없습니다.
sr-query를 사용해야 할 때
질문이 알려진 형태를 가지고 있으며, 답변이 이름을 지정할 수 있는 필드에 대한 정확하거나 구조화된 조회일 때 sr-query를 사용하십시오.
- ID로 특정 행 (
WHERE message_id = '…',WHERE session_id = '…') - 필터, 개수, 롤업 (
COUNT(*),GROUP BY harness,WHERE harness = 'claude', 토큰 합계, 날짜 범위) - VIEW
session_token_usage를 통한 세션별 토큰 롤업 (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"
stdin에서 -를 사용하여 SQL을 파이프로 전달할 수도 있습니다:
echo "SELECT count(*) FROM messages" | stockroom query -
command -v stockroom 명령이 실패하면, 시스템이 아직 설정되지 않은 것입니다: 사용자에게 sr-initialize 스킬을 실행하라고 알리고, 다른 호출을 시도하지 마십시오.
출력 규율: --format 및 --detail
기본값은 이미 에이전트에게 안전합니다 — 기본 호출은 범위가 제한되고 파싱 가능한 출력을 제공합니다 — 따라서 상황이 요구할 때만 플래그를 사용하십시오.
--format — 출력 형태, 기본값 tsv
| 값 | 형태 | 사용 시기 |
|---|---|---|
tsv (기본값) | 헤더 행 + 탭으로 구분된 행, 개수 표시 없음 | 기본값. 스트림에 적합하며 유닉스 파이프(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열)를 절대 실행하지 마십시오. 대신: 명시적인 열 목록과LIMIT으로--detail compact/snippet으로 좁게 스캔하고, 원하는 행을 식별한 다음, 해당 행의 넓은 필드만 ID에 대한WHERE로 다시 가져오십시오. 단일SELECT text FROM messages WHERE message_id = '…'은 저렴합니다.SELECT * FROM messages --detail full은 컨텍스트 폭탄입니다. 정확한 공백(마크다운 테이블, 코드 블록)이 필요하면--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(프로젝트 디렉터리 슬러그 그대로 — 그룹화 키; Cursor CLI 채팅의 경우 채팅 해시 디렉터리),cwd(실제 경로,NULL일 수 있음),workspace_key(하니스 간 경로 롤업,NULL일 수 있음),entrypoint(알려진 경우 하니스의 표면 출처;NULL일 수 있음 — 리터럴을 추측하지 말고SELECT DISTINCT entrypoint사용),models(VARCHAR[], Cursor만 해당),title,git_branch,is_subagent,parent_session_id,started_at,ended_at,source_mtime(마지막 수집 시점의 소스 트랜스크립트 수정 시간), 그리고 네 개의 nullable 세션 단위 토큰BIGINT(input_tokens,output_tokens,cache_creation_tokens,cache_read_tokens— 하니스가 대화 수준 사용량을 보고하는 경우에만 채워짐; Claude/Cursor는NULL로 남김). PK(harness, session_id).messages— 턴당 한 행.message_id = '{session_id}#{ordinal}'(하니스 전체에서 균일),parent_id,ordinal,role('user'|'assistant'),text(전체; 생각은 캡처되지 않음),model(메시지당, Claude만 해당),ts,first_seen_at(stockroom이 메시지를 처음 관찰한 시점), 그리고 네 개의 토큰BIGINT:input_tokens,output_tokens,cache_creation_tokens,cache_read_tokens(Claude 메시지 사용량; Cursor는NULL). PK(harness, session_id, message_id).session_token_usage— sessions + 메시지 토큰 합계에 대한 VIEW. 대화 롤업에는 이것을 선호하십시오. 열:*_from_messages(SUM),*_native(세션 열),*_total(COALESCE(native, from_messages)),token_grain('session'|'message'|'none'). 합계는 보고된 필드의 웨어하우스 롤업이며, 공급업체 청구서가 아닙니다.*_total위에 메시지 토큰을 추가로SUM하지 마십시오.tool_calls— 도구 입력만 (출력은 절대 없음).message_id(이를 방출한 턴),ordinal,tool_name,tool_input(이질적인JSON, 전체 저장됨 — 가드레일 참조). PK(harness, session_id, message_id, ordinal).embeddings— 의미 검색을 위한 청크당 벡터 (owner_table,owner_id,chunk_index,embed_model,vector FLOAT[384]). 직접 질의하는 경우는 드뭅니다 —sr-semantic스킬을 사용하십시오._sync_state— 수집 워터마크 기록; 질의할 가치가 없습니다.
조인을 작성하기 전에 알아야 할 한 가지 정체성 규칙: 항상 균일한 message_id / (harness, session_id)로 조인하고, source_* 출처 열로는 절대 조인하지 마십시오. 하니스당 한 단위로만 존재하는 값은 다른 쪽에서 정직하게 NULL입니다(예: messages.model은 Claude 전용; sessions.models은 Cursor 전용; 동일한 이중 단위 정직성이 토큰에도 적용됨).
작업 예제
모두 실제 웨어하우스에서 검증되었습니다.
# 어떤 하니스가 있는가? (tsv 기본값)
stockroom query "SELECT DISTINCT harness FROM sessions ORDER BY 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"
# 세션별 토큰 롤업 (VIEW 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"
더 많은 변형(활동 창이 있는 무제한 도구, 일/하니스별 토큰 롤업, 하니스별 스킬 사용 SQL): Cookbook을 참조하십시오.
쿡북
까다로운 시작 SQL: references/cookbook/. 인덱스를 열고 필요한 레시피만 선택하십시오.
사람에게 전달
당신은 도구의 운영자이지, 디스플레이가 아닙니다. SQL을 실행하고, 결과를 읽은 후 사용자에게 자연어로 답변하십시오. 사용자가 보길 요청하지 않는 한, 사람에게 원시 tsv를 붙여넣지 마십시오. 사용자가 원시 출력 — 또는 직접 실행할 명령을 원할 때는 --format table(읽기용) 또는 --format json(처리용) 변형을 제공하십시오.
시스템 이해
이러한 계약이 왜 그런 형태를 가지는지 이해하려면 — 패키징, 토치 계약, 설계상 읽기 전용, 잘림 원칙, 정체성/출처 — 공유 시스템 모델을 읽으십시오: ../sr-search/references/system-model.md.