Skip to main content

Conversation Search

Overview

Conversation search lets you (or an agent) find earlier chat turns by keyword. Every message in a chat session is persisted twice: as the durable JSONL session log on disk, and as a searchable row in a full-text index. The search runs over that index and returns the matching messages ranked by relevance, each with its source session id and timestamp. It answers the question “what did we discuss — or decide — before?” without you having to remember which session, or even which agent, it happened in. This is distinct from the Crew Journal recall surface, which searches the workspace-wide event memory (runs, peer conversations, escalations). Conversation search reads the chat transcripts themselves.

Scope and isolation

A search runs in one of two scopes, and both are decided by the server:
  • Agent scope — you name an agent. The query is filtered to that agent’s agent_id, and the API verifies the agent belongs to your workspace first. This is the only scope an agent-initiated search can ever have: the agent identity comes from the runtime, so one agent can never read another’s chats through this surface.
  • Workspace scope — you name none. The API reads the agents in your workspace — from your session, never from the request body — and runs one ranked query across them. This is what ⌘K in the dashboard sends, and what the CLI does by default. It spans at most 400 agents (the most recently created ones).
In both scopes the search backend only filters; the API decides which agent ids it may ever see, and drops any hit outside that set. Two limits apply to both scopes:
  • Search-from-now-on. Only conversations recorded after this feature shipped are indexed. There is no backfill of older history — the index fills as new turns land.
  • Keyword (BM25) only. This first release ranks by BM25 full-text relevance. There is no semantic / embedding re-rank yet.

CLI

Use crewship conversation search (alias: crewship conv search):
--format yaml and --format ndjson are honoured too, like every other read command — see Output Formats. Arguments and flags: Each hit shows its timestamp, the message role (user / assistant), the agent that said it, the source session_id, and a snippet of the matched content. The agent-first positional form this command shipped with (crewship conversation search backend-bot "deploy pipeline") still works: with no --agent and several arguments, the first is read as an agent.

Dashboard (⌘K)

The command palette carries a Conversations group. Unlike the entity groups, which are fetched once when the palette opens and filtered in the browser, this one asks the server as you type (debounced, and each keystroke cancels the request before it), because messages are the largest thing in a workspace and a match is a phrase rather than a name. A row shows the matched snippet, the agent that said it and how long ago; selecting it opens that thread at /chat/<agent-slug>?session=<chat-id> — the same deep link a chat notification uses. Two keystrokes are the minimum before anything is sent. If the endpoint is not configured on the deployment, or the search fails, the group simply does not appear: nobody explicitly ran this search, so it never raises an error.

API

POST /api/v1/conversations/search Request body:
Response:
Status codes:

Agent tool

conversation.search is not advertised to agents today. The memory MCP server’s tools/list publishes memory.read, memory.write, memory.search and memory.append_daily only, so a model never sees the tool — and its backend is the host database, which the in-container dispatcher cannot reach. Search conversations from the HTTP API or the CLI above.
The tool’s schema and handler ship, ready for a transport that can reach the mirror from a sandbox. It is agent-scoped by construction — the agent identity comes from the runtime, never from the model, so an agent could only ever search its own history — and where the mirror is not reachable it returns a recoverable “not available” result rather than failing the run.

How it works

When a chat turn is persisted, the conversation store writes the JSONL line (the durable source of truth) and, when a database mirror is configured, dual-writes a row into a conversation_messages table backed by an SQLite FTS5 external-content index. Search joins the index, filters by the agent set the API resolved, and orders by BM25. The JSONL files remain authoritative; the index is a rebuildable mirror, so a transient write failure costs at most the searchability of a single turn, never the turn itself.