refactor(session_search): tighten schema description to spec

The tool-description prose had accumulated playbook-style guidance over
the course of development (pre-flight rules, mode-picking policy,
multi-anchor recipe, anti-pattern teaching, reading-order advice).
That material now lives in the session-recall skill where it can be
loaded on demand rather than shipping in every system prompt.

Schema description now covers only what the tool IS: what each mode
returns, default-mode resolution, anchor contract, FTS5 syntax, and a
one-paragraph 'when to use'. Mode enum description shrunk to three
one-line entries. Cost claims generalised — no fixed dollar figures
since aux-LLM cost depends on the user's configured aux model.

Net: ~9.5 KB -> ~3 KB of description prose. One schema-content
assertion in tests updated to match the new phrasing while keeping the
same intent (cross-session language exists; no current-session nudge).
This commit is contained in:
yoniebans
2026-05-15 16:10:38 +02:00
parent af1ea1f4ed
commit ef10d2e7c9
2 changed files with 50 additions and 104 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ class TestHiddenSessionSources:
class TestSessionSearchSchema:
def test_keeps_cross_session_recall_guidance_without_current_session_nudge(self):
description = SESSION_SEARCH_SCHEMA["description"]
assert "past conversations" in description
assert "past session" in description
assert "recent turns of the current session" not in description
+49 -103
View File
@@ -1166,102 +1166,52 @@ def check_session_search_requirements() -> bool:
SESSION_SEARCH_SCHEMA = {
"name": "session_search",
"description": (
"Search your long-term memory of past conversations, browse recent sessions, or drill "
"into a specific session. This is your recall -- every past session is searchable.\n\n"
"USER-CONFIGURED DEFAULT (check this FIRST before picking a mode):\n"
" The user can configure their preferred default via "
"``auxiliary.session_search.default_mode`` in ~/.hermes/config.yaml. Accepted values: "
"``fast`` | ``summary``. **When the user has set a default, it is binding on your "
"first call.** Use that mode, no matter what the question shape looks like. The user "
"expressed a deliberate cost/quality preference — they pay the bill, they get to "
"choose. Don't second-guess based on the question's shape; the user already "
"considered the shape when they set the default. After the first call, you may pick "
"subsequent modes freely based on what the first call returned (e.g. ``guided`` to "
"drill into anchors a ``fast`` call surfaced).\n"
" When no default is configured, fall through to the DEFAULT RECALL PATH below.\n\n"
"DEFAULT RECALL PATH (when the user hasn't configured a preference): fast → guided.\n"
" • mode='fast' (default starting move) — FTS5 snippets + 1 message of context, no LLM "
"call. ~10ms, ~1 KB per session. Use this for ANY recall question — discovery AND state "
"reconstruction. Returns session_id + match_message_id anchors you then drill into.\n"
" • mode='guided' (standard follow-up) — given (session_id, message_id) anchors from a "
"prior fast call, returns a window of raw messages around each anchor plus session "
"bookends (first/last few user+assistant messages, when they don't already overlap the "
"window). No LLM, no truncation, ~ms latency. This is how you actually read what "
"happened — fast finds the sessions, guided reads the transcript with start-and-end "
"context guaranteed. Tool messages around the anchor are filtered (anchor itself "
"preserved) so payload stays signal-dense. **Never invent anchors or guess session_ids — "
"guided will reject pairs that don't match real messages.**\n"
" • mode='summary' — LLM-generated recap across matched sessions. ~30s, ~$2/call in aux "
"LLM cost. Trades latency + cost for prose synthesis. Reach for it when you genuinely "
"need cross-session synthesis in one shot AND a fast→guided walk would be too many "
"round-trips. Legitimate when the user explicitly asks for it or has configured it as "
"their default — not legitimate as a reflexive choice when you haven't thought about "
"the trade.\n\n"
"PICKING A MODE WHEN UNSET: if the user hasn't configured a default and hasn't asked "
"for a specific mode, prefer fast. Don't reflexively pick summary for 'catch me up' "
"or 'what did we decide' questions — fast→guided answers those cheaper and shows you "
"the actual messages instead of an LLM recap of them.\n\n"
"MULTI-SESSION CATCH-UP: when a topic spans multiple sessions (e.g. 'where did we get to "
"with X' over several days, or active work that's been touched in several recent "
"sessions), do NOT drill only the top fast hit. Pass the top 23 hits as a multi-anchor "
"guided call: ``mode='guided', anchors=[{session_id, around_message_id}, ...]``. Each "
"anchor returns its own window + bookends in one call, so you see the full arc instead "
"of one session's slice. A single-anchor drill is fine when the topic is contained to "
"one session.\n\n"
"WHEN FAST SNIPPETS LOOK NOISY: if fast's snippets all read like the same keywords "
"echoing (because the searched topic IS the subject of those sessions — e.g. searching "
"for 'session_search' in sessions about session_search), the snippets are decorative, "
"not signal. The signal is the (session_id, match_message_id) pair. Do NOT pivot to "
"filesystem/SQL/grep — that's the same shape failure as reflexive summary, just with "
"manual archaeology instead of LLM telephone. Drill the top 23 hits with guided; "
"bookend_end carries the session's prose resolution that snippets routinely miss.\n\n"
"READING GUIDED RESPONSES: every guided window has three slices — ``bookend_start`` "
"(opening prose of that session, may be empty when the window already covers the "
"start), ``messages`` (the anchored window itself, the FTS5 hit + its neighbours), and "
"``bookend_end`` (closing prose of that session). Read all three — the resolution lives "
"in bookend_end, the goal in bookend_start. Skipping them leaves you anchored on the "
"FTS5 hit alone and missing what came before/after.\n\n"
"LINEAGE AWARENESS: sessions can be split by context compaction — when this happens, "
"the child session's first messages are a post-compaction handoff, NOT the original "
"arc opener. Spot it by ``parent_session_id`` on a fast hit (or in the guided response's "
"session_meta). If ``bookend_start`` reads like a summary-of-prior-work rather than a "
"user kickoff, the real opener is in the parent — fast-search again scoped to the "
"parent if you need it. Most sessions are not compacted; this only matters on long "
"multi-day arcs.\n\n"
"TEMPORAL DIRECTION (fast mode only): by default fast ranks by FTS5 relevance with no "
"time signal — fine for keyword discovery but ties are arbitrary, and same-keyword hits "
"from years ago can outrank fresh ones. Pass ``sort='newest'`` when the question is "
"shaped by recency (\"where did we leave X\", \"latest thinking on Y\", \"current status of "
"Z\") and ``sort='oldest'`` when it's shaped by origin (\"how did X start\", \"what was the "
"original take on Y\", \"first time we discussed Z\"). When the question is exploratory "
"(\"what do we know about X\") leave sort unset and let relevance lead. sort is silently "
"ignored in summary / guided / recent modes — for temporal narrative across multiple "
"sessions, drive the temporal selection from a fast call with sort set, then drill the "
"right anchors with guided.\n\n"
"Browsing recent sessions: call with NO arguments to see what was worked on recently. "
"Returns titles, previews, timestamps. Zero LLM cost, instant. Start here when the user "
"asks 'what were we working on' or 'what did we do recently'.\n\n"
"(See USER-CONFIGURED DEFAULT at the top of this description for how the per-user "
"default_mode setting interacts with the guidance above.)\n\n"
"USE THIS PROACTIVELY when:\n"
"- **BEFORE reaching for `gh`, GitHub API, web search, or file inspection**: if the user "
"asks about the status of any project, branch, PR, design, or topic that's been worked on "
"before, call session_search FIRST. The session DB carries what was DISCUSSED and DECIDED; "
"external tools only show the current world state. Use session_search to find context, "
"then external tools to verify reality.\n"
"- The user says 'we did this before', 'remember when', 'last time', 'as I mentioned'\n"
"- The user asks about a topic you worked on before but don't have in current context\n"
"- The user references a project, person, or concept that seems familiar but isn't in memory\n"
"- You want to check if you've solved a similar problem before\n"
"- The user asks 'what did we do about X?' or 'how did we fix Y?'\n\n"
"Don't hesitate to search when it is actually cross-session — fast mode is ~10ms and free. "
"Better to search and confirm than to guess or ask the user to repeat themselves.\n\n"
"Search syntax (modes 'fast' and 'summary'): keywords joined with OR for broad recall "
"(elevenlabs OR baseten OR funding), phrases for exact match (\"docker networking\"), "
"boolean (python NOT java), prefix (deploy*). "
"IMPORTANT: Use OR between keywords for best results — FTS5 defaults to AND which misses "
"sessions that only mention some terms. If a broad OR query returns nothing, try individual "
"keyword searches in parallel."
"Search past sessions stored in the local session DB. Three modes plus a default "
"browsing mode when no arguments are passed. All three modes operate on the same "
"FTS5-indexed message store; they differ in what they return and at what cost.\n\n"
"MODES\n\n"
" • mode='fast' — FTS5 snippets across matched sessions. No LLM call. Returns one "
"entry per matched session with session_id, match_message_id, a one-message context "
"window, and metadata. Use this as the starting move for any recall question — "
"discovery and state reconstruction both. The match_message_id is the anchor you "
"pass to guided.\n\n"
" • mode='guided' — REQUIRES anchors from a prior fast call. Returns a window of "
"raw messages around each anchor plus session bookends (bookend_start, bookend_end). "
"No LLM call, no truncation. Single or multi-anchor: pass "
"``anchors=[{session_id, around_message_id}, ...]``. Each anchor returns its own "
"window in the response's ``windows`` array. Bookends are the first/last "
"user+assistant messages of the session, empty when the window already overlaps "
"the session head/tail. Tool messages are filtered from the window (the anchor "
"itself is preserved even if role='tool').\n\n"
" • mode='summary' — LLM-generated prose synthesis across matched sessions. Issues "
"one auxiliary-model call per session in the hit list, so cost scales with whatever "
"auxiliary model (or main model fallback) is configured. Returns aux token usage in "
"the response (``aux_usage`` per call, ``aux_usage_total`` per batch). Reach for "
"this when you genuinely need cross-session prose synthesis in one shot.\n\n"
" • No query, no mode — browses recent sessions chronologically. Returns titles, "
"previews, timestamps. No LLM call.\n\n"
"DEFAULT MODE\n\n"
" When ``mode=`` is unset, the resolver checks ``auxiliary.session_search.default_mode`` "
"in ~/.hermes/config.yaml (accepted values: ``fast`` | ``summary``). If the user "
"has set a default, honour it on the first call. With no config, the default is "
"``fast``. An explicit ``mode=`` argument always wins.\n\n"
"ANCHOR CONTRACT\n\n"
" An anchor is the pair (session_id, around_message_id). The session_id MUST be "
"the raw owning session of around_message_id — guided rejects anchors where the "
"message_id does not exist in the named session. Fast results return both "
"session_id (raw owning) and parent_session_id (when different, for display "
"context only). Pair session_id with match_message_id from the same fast hit; do "
"not substitute parent_session_id.\n\n"
"FTS5 SYNTAX\n\n"
" FTS5 defaults to AND across terms — multi-word queries require all terms to "
"match. Use OR explicitly for broader recall (``alpha OR beta OR gamma``), quoted "
"phrases for exact match (``\"docker networking\"``), boolean (``python NOT java``), "
"or prefix wildcards (``deploy*``).\n\n"
"WHEN TO USE\n\n"
" Before reaching for ``gh``, web search, or filesystem inspection on questions "
"about prior work — what was discussed, what was decided, where an artefact was "
"created. The session DB carries what was said when; external tools show current "
"world state."
),
"parameters": {
"type": "object",
@@ -1283,13 +1233,9 @@ SESSION_SEARCH_SCHEMA = {
"type": "string",
"enum": ["fast", "summary", "guided"],
"description": (
"fast (default) — FTS5 snippets, no LLM, ~10ms. Use for any recall. "
"guided — REQUIRES anchors from a prior fast call; returns raw message "
"window around each anchor. summary — LLM recap, ~30s, ~$2/call; opt-in "
"cross-session synthesis (respect user config if they set it, but don't "
"reflexively pick it). If you want to drill but have no anchors yet, call "
"fast first and use its match_message_id values. Never invent anchors. "
"See the tool description for when to use which."
"fast — FTS5 snippets, no LLM. Default. "
"guided — requires anchors from a prior fast call; returns raw message window per anchor. "
"summary — LLM synthesis across matched sessions; opt-in, costs per aux-model call."
),
"default": "fast",
},