Skip to content

Commit e1cccba

Browse files
phernandezclaude
andcommitted
docs: add metadata search reference
Document the full structured metadata filter system — operators, MCP tools, tag shortcuts, and CLI flags — which previously had no dedicated documentation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 6ff3907 commit e1cccba

1 file changed

Lines changed: 294 additions & 0 deletions

File tree

docs/metadata-search.md

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
# Metadata Search Reference
2+
3+
Basic Memory automatically indexes custom frontmatter fields so you can query them with structured filters. Any YAML key in a note's frontmatter beyond the standard set (`title`, `type`, `tags`, `permalink`, `schema`) is stored as `entity_metadata` and becomes searchable.
4+
5+
## Two Ways to Query
6+
7+
| Tool | Use When |
8+
|------|----------|
9+
| `search_by_metadata` | You only need metadata filters (no text query) |
10+
| `search_notes` | You want to combine a text query with metadata filters |
11+
12+
Both tools accept the same filter syntax.
13+
14+
## Filter Syntax
15+
16+
Filters are a JSON dictionary where each key targets a frontmatter field and the value specifies the match condition. Multiple keys combine with **AND** logic — every filter must match.
17+
18+
### Equality
19+
20+
Match a single value exactly.
21+
22+
```json
23+
{"status": "active"}
24+
```
25+
26+
Finds notes whose frontmatter contains `status: active`.
27+
28+
### Array Contains (all)
29+
30+
Pass a list to require **all** listed values to be present in the field.
31+
32+
```json
33+
{"tags": ["security", "oauth"]}
34+
```
35+
36+
Finds notes tagged with both `security` and `oauth`.
37+
38+
### `$in` (any of)
39+
40+
Match if the field equals **any** value in the list.
41+
42+
```json
43+
{"priority": {"$in": ["high", "critical"]}}
44+
```
45+
46+
### `$gt`, `$gte`, `$lt`, `$lte`
47+
48+
Numeric and text comparisons. Numeric values use numeric comparison; strings use lexicographic comparison.
49+
50+
```json
51+
{"confidence": {"$gt": 0.7}}
52+
{"score": {"$lte": 100}}
53+
```
54+
55+
### `$between`
56+
57+
Range filter (inclusive). Takes a `[min, max]` pair.
58+
59+
```json
60+
{"score": {"$between": [0.3, 0.8]}}
61+
```
62+
63+
### Nested Access (dot notation)
64+
65+
Access nested frontmatter values using dots.
66+
67+
```json
68+
{"schema.version": "2"}
69+
```
70+
71+
This queries the `version` key inside a `schema` object in frontmatter.
72+
73+
### Summary Table
74+
75+
| Operator | Syntax | Example |
76+
|----------|--------|---------|
77+
| Equality | `{"field": "value"}` | `{"status": "active"}` |
78+
| Array contains (all) | `{"field": ["a", "b"]}` | `{"tags": ["security", "oauth"]}` |
79+
| `$in` (any of) | `{"field": {"$in": [...]}}` | `{"priority": {"$in": ["high", "critical"]}}` |
80+
| `$gt` / `$gte` | `{"field": {"$gt": N}}` | `{"confidence": {"$gt": 0.7}}` |
81+
| `$lt` / `$lte` | `{"field": {"$lt": N}}` | `{"score": {"$lt": 0.5}}` |
82+
| `$between` | `{"field": {"$between": [min, max]}}` | `{"score": {"$between": [0.3, 0.8]}}` |
83+
| Nested access | `{"a.b": "value"}` | `{"schema.version": "2"}` |
84+
85+
**Key rules:**
86+
- Filter keys must match `[A-Za-z0-9_-]+` (dots separate nesting levels).
87+
- Each operator dict must contain exactly one operator.
88+
- `$in` and array-contains require non-empty lists.
89+
- `$between` requires exactly two values `[min, max]`.
90+
91+
## MCP Tools
92+
93+
### `search_by_metadata` — metadata-only search
94+
95+
Searches entities by structured frontmatter metadata without a text query. Results are scoped to entity-level items.
96+
97+
**Parameters:**
98+
99+
| Parameter | Type | Required | Description |
100+
|-----------|------|----------|-------------|
101+
| `filters` | dict | Yes | Metadata filter dictionary (see syntax above) |
102+
| `project` | string | No | Project to search in (uses default if omitted) |
103+
| `limit` | int | No | Max results (default 20) |
104+
| `offset` | int | No | Skip N results for pagination (default 0) |
105+
106+
**Example:**
107+
108+
```python
109+
# Find all notes with status "in-progress"
110+
await search_by_metadata({"status": "in-progress"})
111+
112+
# Find high-priority specs in the research project
113+
await search_by_metadata(
114+
{"type": "spec", "priority": {"$in": ["high", "critical"]}},
115+
project="research",
116+
limit=10,
117+
)
118+
```
119+
120+
### `search_notes` with metadata — combined text + metadata
121+
122+
The `search_notes` tool accepts `metadata_filters`, `tags`, and `status` parameters alongside the text `query`. This lets you combine full-text search with structured filtering.
123+
124+
**Relevant parameters:**
125+
126+
| Parameter | Type | Description |
127+
|-----------|------|-------------|
128+
| `query` | string | Text search query (can be empty when using only filters) |
129+
| `metadata_filters` | dict | Structured filter dict (same syntax as `search_by_metadata`) |
130+
| `tags` | list[str] | Convenience shorthand — merged into `metadata_filters["tags"]` |
131+
| `status` | string | Convenience shorthand — merged into `metadata_filters["status"]` |
132+
133+
**Merging rules:** `tags` and `status` are convenience shortcuts. They are merged into `metadata_filters` using `setdefault` — if the same key already exists in `metadata_filters`, the explicit filter wins.
134+
135+
**Examples:**
136+
137+
```python
138+
# Text search filtered by metadata
139+
await search_notes("authentication", metadata_filters={"status": "draft"})
140+
141+
# Filter-only search (empty query)
142+
await search_notes("", metadata_filters={"type": "spec"})
143+
144+
# Combine text, tags shortcut, and metadata
145+
await search_notes(
146+
"oauth flow",
147+
tags=["security"],
148+
metadata_filters={"confidence": {"$gt": 0.7}},
149+
)
150+
151+
# Convenience shortcuts
152+
await search_notes("planning", status="active")
153+
await search_notes("", tags=["tier1", "alpha"])
154+
```
155+
156+
## Tag Search Shortcuts
157+
158+
The `tag:` prefix in a search query is a shorthand for tag-based metadata filtering. When `search_notes` receives a query starting with `tag:`, it converts the query into a `tags` filter and clears the text query.
159+
160+
```python
161+
# These are equivalent:
162+
await search_notes("tag:tier1")
163+
await search_notes("", tags=["tier1"])
164+
165+
# Multiple tags (comma or space separated) — all must be present:
166+
await search_notes("tag:tier1,alpha")
167+
await search_notes("tag:tier1 alpha")
168+
```
169+
170+
## CLI Access
171+
172+
The `bm tool search-notes` command exposes metadata filtering via `--meta` and `--filter` flags.
173+
174+
### `--meta` — simple key=value filters
175+
176+
Repeatable flag for equality filters on frontmatter fields.
177+
178+
```bash
179+
# Single filter
180+
bm tool search-notes "my query" --meta status=draft
181+
182+
# Multiple filters (AND logic)
183+
bm tool search-notes "" --meta status=active --meta priority=high
184+
```
185+
186+
### `--filter` — advanced JSON filters
187+
188+
Pass a full JSON filter dictionary for operator-based queries.
189+
190+
```bash
191+
# Range filter
192+
bm tool search-notes "" --filter '{"score": {"$between": [0.3, 0.8]}}'
193+
194+
# $in filter
195+
bm tool search-notes "" --filter '{"priority": {"$in": ["high", "critical"]}}'
196+
```
197+
198+
### `--tag` and `--status` — convenience shortcuts
199+
200+
```bash
201+
bm tool search-notes "query" --tag security --tag oauth
202+
bm tool search-notes "" --status draft
203+
```
204+
205+
### Combined example
206+
207+
```bash
208+
bm tool search-notes "authentication" --tag security --meta status=draft --type spec
209+
```
210+
211+
## Practical Examples
212+
213+
### Example notes with custom frontmatter
214+
215+
**`specs/auth-design.md`:**
216+
217+
```markdown
218+
---
219+
title: Auth Design
220+
type: spec
221+
tags: [security, oauth]
222+
status: in-progress
223+
priority: high
224+
confidence: 0.85
225+
---
226+
227+
# Auth Design
228+
229+
## Observations
230+
- [decision] Use OAuth 2.1 with PKCE for all client types #security
231+
- [requirement] Token refresh must be transparent to the user
232+
233+
## Relations
234+
- implements [[Security Requirements]]
235+
```
236+
237+
**`specs/search-redesign.md`:**
238+
239+
```markdown
240+
---
241+
title: Search Redesign
242+
type: spec
243+
tags: [search, performance]
244+
status: draft
245+
priority: medium
246+
confidence: 0.6
247+
---
248+
249+
# Search Redesign
250+
251+
## Observations
252+
- [goal] Sub-100ms search response times #performance
253+
- [approach] Hybrid FTS + vector retrieval
254+
255+
## Relations
256+
- depends_on [[Database Schema]]
257+
```
258+
259+
### Queries that find them
260+
261+
```python
262+
# Find all in-progress specs
263+
await search_by_metadata({"status": "in-progress", "type": "spec"})
264+
# → Auth Design
265+
266+
# Find high-confidence specs
267+
await search_by_metadata({"confidence": {"$gt": 0.7}})
268+
# → Auth Design (confidence: 0.85)
269+
270+
# Find specs with priority high or medium
271+
await search_by_metadata({"priority": {"$in": ["high", "medium"]}})
272+
# → Auth Design, Search Redesign
273+
274+
# Find specs in a confidence range
275+
await search_by_metadata({"confidence": {"$between": [0.5, 0.9]}})
276+
# → Auth Design (0.85), Search Redesign (0.6)
277+
278+
# Find notes tagged with security
279+
await search_notes("tag:security")
280+
# → Auth Design
281+
282+
# Combined: text search + metadata filter
283+
await search_notes("OAuth", metadata_filters={"status": "in-progress"})
284+
# → Auth Design
285+
```
286+
287+
### CLI equivalents
288+
289+
```bash
290+
bm tool search-notes "" --meta status=in-progress --type spec
291+
bm tool search-notes "" --filter '{"confidence": {"$gt": 0.7}}'
292+
bm tool search-notes "OAuth" --meta status=in-progress
293+
bm tool search-notes --tag security
294+
```

0 commit comments

Comments
 (0)