# How to Filter Semantic Search Results by Wing and Room in MemPalace

> Learn to filter semantic search results in MemPalace by wing and room. Enhance your searches by passing optional parameters to search_memories() or the CLI to refine ChromaDB queries.

- Repository: [MemPalace/mempalace](https://github.com/MemPalace/mempalace)
- Tags: how-to-guide
- Published: 2026-06-06

---

**To filter semantic search results by wing and room in MemPalace, pass the optional `wing` and `room` parameters to the `search_memories()` function or the CLI, which constructs a ChromaDB `where` filter that constrains the vector search to matching metadata before re-ranking occurs.**

MemPalace organizes user data using a hierarchical metadata system where every stored item (called a *drawer*) is tagged with a **wing** (top-level category) and **room** (sub-category). When you perform semantic search, you can scope results to specific wings, rooms, or combinations of both by leveraging the filter builder in [`mempalace/searcher.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/searcher.py). This guide explains how the filtering mechanism works and how to use it across the Python API and command-line interface.

## Understanding Wing and Room Metadata

In MemPalace, the **wing** represents a high-level container such as a project, person, or topic, while the **room** groups related drawers within that wing. These tags are embedded into every drawer's metadata during the mining process and stored in ChromaDB. When you query the palace, the system can pre-filter the candidate pool using these metadata fields before applying semantic similarity or hybrid re-ranking.

## How the Where Filter Works Under the Hood

The core filtering logic resides in [`mempalace/searcher.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/searcher.py) within the `build_where_filter` function. This helper constructs ChromaDB-compatible query filters based on which parameters you provide:

```python

# mempalace/searcher.py – building the filter

def build_where_filter(wing: str = None, room: str = None) -> dict:
    """Build ChromaDB where filter for wing/room filtering."""
    if wing and room:
        return {"$and": [{"wing": wing}, {"room": room}]}
    elif wing:
        return {"wing": wing}
    elif room:
        return {"room": room}
    return {}

```

When both `wing` and `room` are specified, the function returns a `$and` logical operator ensuring both conditions must match. If only one parameter is provided, it returns a simple key-value filter for that field. An empty dictionary (no filter) allows all drawers to be considered.

This filter is injected into the retrieval stage at two entry points in [`mempalace/searcher.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/searcher.py):
- Lines 20-22 in the `search()` function (CLI entry point)
- Lines 81-82 in the `search_memories()` function (programmatic API)

Because filtering happens at the **retrieval** stage within ChromaDB (`col.query`), the subsequent hybrid re-ranking step (`_hybrid_rank`) operates only on the pre-filtered candidate pool. This guarantees that final relevance scores reflect only drawers belonging to your specified wing and/or room.

## Filtering via the Python API

Use the `search_memories` function to programmatically filter results. Import the function from `mempalace.searcher` and provide the `wing` and/or `room` arguments alongside your query.

```python
from mempalace.searcher import search_memories

results = search_memories(
    query="how did I greet the new client",
    palace_path="/path/to/palace",
    wing="wing_user",          # ← filter on wing

    room="room_profile",       # ← filter on room (optional)

    n_results=5,
)

print(results["results"])

```

If the underlying ChromaDB collection supports native `where` clauses, the filter executes within the database. Otherwise, MemPalace falls back to `_query_drawers_with_filter_fallback`, which fetches a superset and applies the filter on the Python side to maintain consistent behavior.

## Filtering via the CLI

The `mempalace` command-line tool exposes the same filtering capabilities through `--wing` and `--room` flags. These arguments parse into the same `build_where_filter` call used by the Python API.

Filter by wing only:

```bash

# Show up to 10 matches from the “wing_project” wing, any room

mempalace search \
    --query "refactor authentication flow" \
    --wing wing_project \
    --n-results 10

```

Filter by both wing and room:

```bash

# Show matches from a specific room inside a wing

mempalace search \
    --query "daily stand-up notes" \
    --wing wing_team \
    --room room_meeting

```

Both commands pass the parsed arguments to the `search()` function in [`mempalace/searcher.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/searcher.py), ensuring the filter logic remains consistent across interfaces.

## Filter Propagation Through the Architecture

Understanding how the filter travels through the codebase helps debug issues and extend functionality:

- **[`mempalace/searcher.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/searcher.py)**: Contains `build_where_filter`, the public `search()` function for CLI usage, and `search_memories()` for programmatic access.
- **[`mempalace/mcp_server.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/mcp_server.py)**: Exposes `search_memories` as the `mempalace_search` tool for MCP-compatible agents, demonstrating filter propagation to the Model Context Protocol layer.
- **[`mempalace/palace.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/palace.py)**: Provides `_open_collection_or_explain` and low-level collection helpers that ultimately invoke `col.query`.
- **[`mempalace/backends/chroma.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/backends/chroma.py)**: Implements the `where` clause handling specific to ChromaDB's query syntax.

This architecture ensures that **CLI → API → Backend** all respect the same wing/room constraints, creating a unified filtering experience regardless of how you interact with MemPalace.

## Summary

- **Metadata structure**: Wings are top-level categories; rooms are sub-categories; both are stored in ChromaDB metadata.
- **Filter construction**: The `build_where_filter` function in [`mempalace/searcher.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/searcher.py) translates Python arguments into ChromaDB `where` clauses, using `$and` logic when both wing and room are specified.
- **Retrieval-stage filtering**: Filters apply before hybrid re-ranking, ensuring only relevant drawers are scored.
- **Multiple interfaces**: Use `search_memories()` in Python or `--wing`/`--room` flags in the CLI; both leverage the same underlying filter builder.
- **Fallback support**: If the backend lacks native filtering, `_query_drawers_with_filter_fallback` handles filtering in Python.

## Frequently Asked Questions

### Can I filter by room without specifying a wing?

Yes. The `build_where_filter` function in [`mempalace/searcher.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/searcher.py) supports filtering by `room` independently. Pass only the `room` parameter to the API or CLI, and the function returns `{"room": "your_room_value"}`, which matches drawers across all wings that reside in that specific room.

### What happens if no drawers match the wing and room filters?

If the ChromaDB `where` clause returns no matches, the `col.query` call yields an empty result set. The hybrid re-ranking step receives no candidates, and both the Python API and CLI return empty results. No error is raised; the response simply contains zero matches.

### Does filtering affect the semantic similarity scores?

Filtering occurs **before** the hybrid re-ranking stage (`_hybrid_rank`). The scores reflect only the semantic similarity and keyword overlap of drawers within your filtered subset. This means scores are calculated relative to the constrained candidate pool, not the entire palace.

### How do I search across multiple wings or rooms simultaneously?

The current implementation in [`mempalace/searcher.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/searcher.py) supports single-value equality matching for wing and room. To search across multiple wings, you would need to execute separate queries for each wing (or room) and merge results manually, as the `build_where_filter` does not generate `$or` operators or list-based `$in` clauses by default.