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:
@@ -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
@@ -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 2–3 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 2–3 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",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user