sr-semantic
sr-semantic runs pure vector search against the stockroom warehouse — the single-file DuckDB database of your captured agentic coding harness history. It embeds your natural-language query and returns a ranked list of the most semantically similar messages. The surface is read-only by construction: you cannot corrupt anything by searching.
When to use sr-semantic
Reach for sr-semantic when the question is about meaning — content you can describe but not name exactly:
- "Find conversations about flaky test debugging."
- "Where did we work through the warehouse locking design?"
- Paraphrased or conceptual recall — the stored text won't contain your words verbatim, but it's about what you're asking.
Do not use sr-semantic for exact or structured lookups — a known session_id/message_id, filters, counts, GROUP BY, joins, date ranges. Those have a known shape and belong to the sr-query skill (raw read-only SQL). When you are not sure which is right, that judgement belongs to the sr-search skill.
Phrase the query as a description of the content you want, in natural language: a short phrase or sentence naming the topic, activity, or concept ("incremental re-embed of new content", "fixing the REUSE licensing layout"). Do not add any instruction preamble — the model's query prefix is applied automatically; hand-adding it would double it.
How to invoke the engine
stockroom semantic "how does the warehouse locking work"
If command -v stockroom fails, the machine isn't set up yet: tell the user to run the sr-initialize skill, and don't attempt any other invocation.
One runtime note: the model loader may print a Hugging Face hub notice / weight-loading progress to stderr — stdout stays clean for pipes; ignore the noise.
Output discipline: -k, --format, and --detail
The defaults are already safe for an agent, so reach for the flags only when a situation calls for it.
-k / --limit — result count, default 10
Lower it (-k 3) when you expect one obvious winner; raise it when you're casting wide before narrowing. Must be a positive integer.
--format — output shape, default tsv
| Value | Shape | Use it when |
|---|---|---|
tsv (default) |
Header rank score harness role preview + tab-separated rows, no count trailer |
Default. Stream-friendly for you and for unix pipes. |
json |
A single {"results": [...]} object — each result additionally carries session_id and message_id and a numeric score |
You need the hit's ids (e.g. for the full-text handoff below), a user asks for structured output, or you want jq. |
table |
Column-aligned ASCII with a (N results) trailer |
A user asks for something human-readable / a copy-paste command to look at. |
Lead with the default tsv. Offer --format table or --format json when the user asks for human-readable or structured output.
score is cosine similarity (higher = closer, ~0–1). Scores are relative quality within this corpus and query — read the previews to judge relevance; don't threshold on an absolute score.
--detail — preview width, default snippet
The preview/text field is truncated at read time so ranked previews can't flood your context. Truncation is display-only — full text is always retrievable.
| Value | Budget | Use it when |
|---|---|---|
compact |
~40 chars | Scanning many candidates cheaply before picking one. |
snippet (default) |
~120 chars | Default. Enough to recognize a hit without dumping it. |
full |
unbounded, single-line | Almost never here — prefer the sr-query handoff below for whole text. |
raw |
unbounded, exact whitespace | Exact stored text (newlines intact). Prefer with --format json; almost never on a large -k. |
An over-budget preview is elided with a marker reporting how many characters were hidden, e.g. …(+2539). That marker is your signal that more exists.
Guardrails
These are the failure modes this skill exists to prevent:
- Don't blow out your context. Never combine
--detail full/rawwith a large-k— ten untruncated messages can be tens of thousands of characters. Scan at the defaultsnippet(orcompact), pick the hit you want, then fetch just that one message's whole text via thesr-queryhandoff: re-run with--format jsonto get the hit'smessage_id, then
stockroom query --format json --detail raw \
"SELECT text FROM messages WHERE message_id = '<message_id-from-the-json>'"
- It is read-only — never attempt writes. The surface only searches.
- Weak results for recent work usually mean stale embeddings, not absence. Semantic search only sees what has been embedded, and embeddings can lag ingestion. Before re-phrasing the same query in a loop, check coverage — and if it lags, suggest the user run the (incremental) embed pass:
stockroom query "SELECT
(SELECT count(DISTINCT owner_id) FROM embeddings WHERE owner_table = 'messages') AS embedded_messages,
(SELECT count(*) FROM messages) AS total_messages"
# lagging badly? -> stockroom embed (incremental; needs torch)
- Re-phrase, don't repeat. If results miss, one reworded query (different vocabulary for the same concept) is reasonable; more than that means the content likely isn't there or isn't embedded — switch strategy (
sr-querykeywordILIKE, or the coverage check above) instead of thrashing.
Handle errors without thrashing
Each failure is a clean stderr message + exit code — read it and take the matching action, don't loop:
| Message | Exit | What it means / next action |
|---|---|---|
error: empty query (…) |
2 | No search text was passed. Provide a query. |
error: --limit must be a positive integer |
2 | Bad -k. Fix the number. |
error: no warehouse found at … — run \stockroom ingest` first` |
1 | The warehouse hasn't been built. Tell the user to run stockroom ingest (or sr-initialize if the machine was never set up); don't retry. |
ModuleNotFoundError: No module named 'torch' |
— | Environment problem, not a query problem (this surface needs torch at query time). Don't retry; tell the user to re-run sr-initialize to re-provision torch. |
Worked examples
All verified against a real warehouse.
# Default: top 10, bounded previews (tsv)
stockroom semantic "how does the warehouse locking work"
# Expecting one obvious winner — keep it tight:
stockroom semantic -k 3 "incremental re-embed of new content"
# Need the ids (for the full-text handoff), or structured output:
stockroom semantic --format json -k 2 "flock sidecar lock"
# Human-readable, terse previews, for a user to eyeball:
stockroom semantic --format table --detail compact -k 3 "REUSE licensing layout"
And the full-text handoff pair (scan semantically, then fetch one whole message with sr-query):
stockroom semantic --format json -k 5 "flock sidecar lock"
# ...pick the winning message_id from the json, then:
stockroom query --format json --detail raw \
"SELECT text FROM messages WHERE message_id = 'fcf35cbe-…#51'"
Relaying to a human
You are the tool's operator, not its display. Run the search, read the previews, and answer the user in natural language — cite the relevant hit(s), fetch full text only when the answer needs it. Don't paste raw tsv at a human unless they asked to see it. When they do want the raw output — or a command to run themselves — hand them a --format table (to read) or --format json (to process) variant.
Understanding the system
To understand why these contracts look the way they do — the torch contract, the embedding pipeline and its staleness model, read-only-by-construction, the truncation doctrine — read the shared system model: ../sr-search/references/system-model.md.