diff --git a/tests/tools/test_session_search.py b/tests/tools/test_session_search.py index 3e6c3b26d6..d3acea96ed 100644 --- a/tests/tools/test_session_search.py +++ b/tests/tools/test_session_search.py @@ -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 diff --git a/tools/session_search_tool.py b/tools/session_search_tool.py index f0354e2b07..a10d9525e6 100644 --- a/tools/session_search_tool.py +++ b/tools/session_search_tool.py @@ -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", },