|
| 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