What this means in practice
This includes:- semantic search over recent messages (STM)
- scoped message visibility by channel
- time-range filtering for recent continuity
- knowledge-base-aware query behavior
- client/channel isolation rules (enforced server-side)
crystal_search_messages — Semantic message search
Search short-term memory (recent messages) by semantic similarity. Complementscrystal_recall (which searches long-term memories).
Parameters
Returns
Examples
Basic message search:Channel filtering
What channels do
- Isolate conversations — messages in one channel are not visible to queries scoped to another channel
- Enable cross-client scenarios — the same user can have separate channels for different integrations (e.g., Slack vs Telegram)
- Preserve context boundaries — memory recall respects channel scope by default
Using channels
If you omitchannel, searches return results from all channels the user has access to. To narrow results:
- Provide the channel name — Pass the channel identifier (e.g.,
”slack”,”telegram”,”claude-code”) - Verify visibility — Server-side rules enforce that you only see messages within accessible channels
- Combine with sinceMs — Time range + channel gives fine-grained control
Example: Multi-channel scenario
A user interacts with Memory Crystal via both Slack and Telegram. Each has its own channel. To search only Slack messages:Time-range filtering with sinceMs
UsesinceMs to retrieve messages after a specific Unix timestamp (milliseconds):
Examples
Messages from today onwards:When to use vs alternatives
crystal_search_messages (recent, exact context)
Use when: You need to find recent messages by semantic meaning, preserving context and timestamps. Best for: Session continuity, recent decision tracking, finding exact phrasing from recent conversations. Example: “What did we just discuss about authentication?”crystal_recall (long-term, high-confidence memories)
Use when: You want to find durable, extracted facts and decisions. Better for understanding patterns and history. Best for: Getting the gist of a topic without reading full conversation history. Example: “What have we decided about authentication?”Decision guide
Scoping and isolation
Memory Crystal enforces isolation at multiple levels:- User-level isolation — Users never see each other’s messages or memories (enforced by
userId) - Channel-level isolation — Messages in one channel don’t appear in queries scoped to another (enforced by channel filtering)
- Knowledge-base isolation — KB queries only return chunks from the specified knowledge base
- Age-based visibility — Archived memories and expired STM messages are hidden by default
Error handling
How it actually works
Key repo surfaces:mcp-server/src/tools/search-messages.ts— tool definition and HTTP handlerconvex/crystal/messages.ts— message storage and search logicconvex/crystal/mcp.ts— API endpoint routingconvex/crystal/recall.ts— scoping and filtering rules
- Tool validates query, limit, channel, and sinceMs parameters
- Query is embedded using OpenAI’s
text-embedding-3-small - ConvexClient POSTs to
/api/mcp/search-messageswith embedding - Backend applies channel and time filters
- Vector search retrieves top-K matching messages
- Results are returned with scores and timestamps
Common mistakes
- Treating search and recall as if they are identical (they’re designed for different purposes)
- Forgetting that scoped channels affect results (all searches are channel-aware)
- Not using sinceMs for time-range queries (just relying on default recency)
- Assuming cross-channel visibility when channels are set (isolation is enforced)
- Using
crystal_search_messagesfor long-term patterns (usecrystal_recallinstead)
Source of truth
Primary files behind this page:mcp-server/src/tools/search-messages.ts— tool definitionconvex/crystal/messages.ts— backend logic and filteringconvex/crystal/recall.ts— scoping rulesconvex/crystal/knowledgeBases.ts— KB visibility logicconvex/crystal/mcp.ts— HTTP endpoint routing