Skip to content

Commit d34beeb

Browse files
murrayjujgpruitt
authored andcommitted
Use hybrid search for CLI positional queries
1 parent 63fd383 commit d34beeb

8 files changed

Lines changed: 41 additions & 29 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ me login
3333
# Store a memory
3434
me memory create "Auth uses bcrypt with cost 12" --tree design.auth
3535

36-
# Search by meaning
36+
# Search by meaning + keywords
3737
me memory search "how does authentication work"
3838

3939
# Connect to your AI tools (Claude Code, Gemini, Codex, OpenCode)

docs/cli/me-memory.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ me memory search [query] [options]
7070

7171
| Argument | Required | Description |
7272
|----------|----------|-------------|
73-
| `query` | no | Semantic search query (shorthand for `--semantic`). |
73+
| `query` | no | Hybrid search query (uses both semantic and fulltext search). |
7474

7575
| Option | Description |
7676
|--------|-------------|
@@ -89,12 +89,12 @@ me memory search [query] [options]
8989
| `--weight-fulltext <w>` | Fulltext weight, 0-1. |
9090
| `--order-by <dir>` | Sort direction: `asc` or `desc`. |
9191

92-
At least one search criterion is required. When both `--semantic` and `--fulltext` are provided, results are ranked using Reciprocal Rank Fusion (hybrid mode).
92+
At least one search criterion is required. A positional `query` runs hybrid search by sending the same text to semantic and fulltext ranking. Use `--semantic` for pure vector search, `--fulltext` for pure keyword search, or both flags to provide different text for each mode.
9393

9494
### Examples
9595

9696
```bash
97-
# Semantic search
97+
# Hybrid search (recommended default)
9898
me memory search "how does authentication work"
9999

100100
# Keyword search

docs/concepts.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,10 @@ Memory Engine supports three search modes. Quick guide:
169169
Find memories by meaning. Uses vector embeddings and cosine similarity.
170170

171171
```bash
172-
me memory search "how does authentication work"
172+
me memory search --semantic "how does authentication work"
173173
```
174174

175-
Good for finding conceptually related content even when the exact words differ.
175+
Good for finding conceptually related content even when the exact words differ. For short literal terms, identifiers, and exact words, prefer fulltext or hybrid search; semantic-only rankings are not lexical and can return unrelated short memories.
176176

177177
### Fulltext search
178178

@@ -189,10 +189,12 @@ Good for finding memories with specific terms, names, or identifiers.
189189
Combine both modes. Results are ranked using Reciprocal Rank Fusion (RRF), which merges the two ranked lists into a single result set.
190190

191191
```bash
192+
me memory search "embedding performance"
193+
# or provide different text for each ranker:
192194
me memory search --semantic "embedding performance" --fulltext "nomic ollama"
193195
```
194196

195-
Good when you want both meaning-based and keyword-based relevance.
197+
Good when you want both meaning-based and keyword-based relevance. The positional CLI query uses hybrid search and is the recommended default.
196198

197199
### Filters
198200

docs/getting-started.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ me memory create "PostgreSQL 18 supports native UUIDv7 generation." \
3636
## Search
3737

3838
```bash
39-
# Semantic search (by meaning)
39+
# Hybrid search (recommended default: meaning + keywords)
4040
me memory search "UUID generation in Postgres"
4141

4242
# Keyword search
4343
me memory search --fulltext "UUIDv7"
4444

45-
# Hybrid (both combined)
46-
me memory search --semantic "UUID generation" --fulltext "PostgreSQL 18"
45+
# Pure semantic search (by meaning only)
46+
me memory search --semantic "database-generated identifiers"
4747
```
4848

4949
## Browse the tree

docs/mcp-integration.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ Search memory proactively:
174174

175175
## Search Examples
176176

177+
# Hybrid search (recommended: meaning + keywords)
178+
me_memory_search({semantic: "database-generated identifiers", fulltext: "database-generated identifiers"})
179+
177180
# Semantic search (by meaning)
178181
me_memory_search({semantic: "how does authentication work"})
179182

docs/mcp/me_memory_search.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Search and browse memories using text matching and/or filters.
44

5-
Supports three search modes: **semantic** (meaning-based), **fulltext** (keyword-based via BM25), or **hybrid** (both combined via Reciprocal Rank Fusion). Combine any search mode with tree, meta, and temporal filters.
5+
Supports three search modes: **semantic** (meaning-based), **fulltext** (keyword-based via BM25), or **hybrid** (both combined via Reciprocal Rank Fusion). Combine any search mode with tree, meta, and temporal filters. For ordinary user queries, short terms, identifiers, or exact words, prefer hybrid by setting both `semantic` and `fulltext` to the query text.
66

77
## Parameters
88

@@ -84,8 +84,17 @@ See [Tree filter syntax](../concepts.md#tree-filter-syntax) for the full referen
8484
```json
8585
{
8686
"semantic": "how does authentication work",
87-
"limit": 10,
88-
"semanticThreshold": 0.7
87+
"limit": 10
88+
}
89+
```
90+
91+
### Hybrid search (recommended default)
92+
93+
```json
94+
{
95+
"semantic": "panics",
96+
"fulltext": "panics",
97+
"limit": 10
8998
}
9099
```
91100

@@ -115,6 +124,6 @@ See [Tree filter syntax](../concepts.md#tree-filter-syntax) for the full referen
115124

116125
- Provide at least one of `semantic`, `fulltext`, or a filter (`tree`, `meta`, `temporal`, `grep`) -- otherwise the search has no criteria.
117126
- Optional parameters may be omitted or explicitly passed as `null` — both are treated as "no value".
118-
- When both `semantic` and `fulltext` are provided, results are ranked using Reciprocal Rank Fusion (hybrid mode).
127+
- When both `semantic` and `fulltext` are provided, results are ranked using Reciprocal Rank Fusion (hybrid mode). This is recommended for single-word or literal searches because semantic-only ranking does not guarantee exact-word matches rank first.
119128
- `order_by` only applies to filter-only searches (no `semantic`/`fulltext`). Ranked searches are always sorted by score.
120129
- `score` ranges from 0 to 1, where 1 is the best match.

packages/cli/commands/memory.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ function createMemoryGetCommand(): Command {
209209
function createMemorySearchCommand(): Command {
210210
return new Command("search")
211211
.description("search memories")
212-
.argument("[query]", "semantic search query (shorthand for --semantic)")
212+
.argument("[query]", "hybrid search query (semantic + fulltext)")
213213
.option("--semantic <text>", "semantic (vector) search")
214214
.option("--fulltext <text>", "BM25 keyword search")
215215
.option(
@@ -234,24 +234,20 @@ function createMemorySearchCommand(): Command {
234234
requireSession(creds, fmt);
235235
requireEngine(creds, fmt);
236236

237-
// Resolve semantic query
238-
const semantic = query ?? opts.semantic ?? null;
239-
if (query && opts.semantic) {
237+
// Resolve search text. A positional query runs hybrid search
238+
if (query && opts.semantic && opts.fulltext) {
239+
const error =
240+
"Positional query is masked by --semantic and --fulltext flags.";
240241
if (fmt === "text") {
241-
clack.log.error(
242-
"Cannot use both positional query and --semantic flag.",
243-
);
242+
clack.log.error(error);
244243
} else {
245-
output(
246-
{ error: "Cannot use both positional query and --semantic" },
247-
fmt,
248-
() => {},
249-
);
244+
output({ error }, fmt, () => {});
250245
}
251246
process.exit(1);
252247
}
253248

254-
const fulltext = opts.fulltext ?? null;
249+
const semantic = opts.semantic ?? query ?? null;
250+
const fulltext = opts.fulltext ?? query ?? null;
255251
const tree = opts.tree ?? null;
256252
const meta = opts.meta ? parseMeta(opts.meta) : null;
257253

packages/cli/mcp/server.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,17 @@ Docs: ${docUrl("me_memory_create")}`,
126126
title: "Search Memories",
127127
description: `Search and browse memories using text matching and/or filters.
128128
129-
Search modes: semantic (meaning), fulltext (keywords), or both (hybrid). Combine with tree, meta, and temporal filters. Results scored 0-1.
129+
Search modes: semantic (meaning), fulltext (keywords), or both (hybrid). For ordinary queries, short terms, identifiers, or exact words, prefer hybrid by setting both semantic and fulltext to the query text. Combine with tree, meta, and temporal filters. Results scored 0-1.
130130
131131
Docs: ${docUrl("me_memory_search")}`,
132132
inputSchema: {
133133
semantic: z
134134
.string()
135135
.optional()
136136
.nullable()
137-
.describe("Natural language query for semantic/meaning search"),
137+
.describe(
138+
"Natural language query for semantic/meaning search. For short or literal queries, also set fulltext to the same value.",
139+
),
138140
fulltext: z
139141
.string()
140142
.optional()

0 commit comments

Comments
 (0)