How do Weaviate’s vector and inverted indexes work together?

Short answer: Each shard keeps HNSW for meaning and an inverted index for terms and property filters—so pre-filtering and hybrid fusion stay efficient on the same objects.

Agent memory queries are rarely pure poetry or pure tokens. A user asks about an overnight proofing rule and also names dough-bench-3—meaning and exact strings both matter. A vector index answers what is close in meaning; an inverted index answers which objects contain terms or satisfy property conditions. This chapter explains how pre-filtering uses the inverted index to guide HNSW safely, how ACORN-style strategies help when filters correlate poorly with the query vector, and how parallel vector and BM25 paths fuse into one ranking (ranked fusion or relative score fusion). For agent memory, user, group, and property boundaries become structured eligibility—not hope that top semantic hits belong to the right person. Weaviate Engram’s hybrid retrieval sits on that dual-index design; your job is clear memory text, waiting on runs when freshness matters, and a retrieval config that matches the question shape.

Agent memory queries are rarely pure poetry or pure tokens. A user asks about “the overnight proofing rule” and also names dough-bench-3. Meaning and exact strings both matter. Weaviate keeps a vector index and an inverted index beside the same objects in each shard so those needs share one engine. This chapter explains what each index contributes, how pre-filtering uses the inverted index to guide HNSW safely, how hybrid search runs both paths in parallel and fuses scores, and how Weaviate Engram’s hybrid retrieval sits on that dual-index design for everyday memory recall.

Why Does One Shard Need Two Different Indexes?

A vector index answers “what is close in meaning.” Embeddings place similar sentences near each other even when wording differs. An inverted index answers “which objects contain these terms or satisfy these property conditions.” It maps words and values to object ids the way a traditional search engine does. Agent memory needs both. Paraphrases change constantly. Codes, bench ids, and product names must still hit exactly.

Weaviate stores objects, an inverted index, and a vector index together per shard. Import builds both structures. That co-location is not a convenience. It is what makes filtered vector search and hybrid search first-class instead of client-side stitching across two systems. When Engram commits a memory, the text becomes searchable by BM25 and by vector similarity without a second write pipeline in your app.

Understanding each structure separately makes their cooperation clearer.

What Does the Vector Index Do That Keywords Cannot?

Weaviate’s default vector index is a custom HNSW graph. Search walks layered neighbor links to find approximate nearest neighbors quickly. The implementation supports inserts while querying, updates, deletes with tombstone cleanup, and persistence through write-ahead logs. That mutability matters for memory. Facts change. Indexes cannot freeze after the first bulk load.

Vector search shines when the query never shares tokens with the stored sentence. “Keep the dough cool overnight” can retrieve a memory about retarded fermentation even if those exact words never appear. Pure keyword search would miss it. Pure vectors struggle with rare identifiers. That weakness is why the inverted index sits next door.

The inverted index is more than a backup keyword tool. It also builds the allow-lists that keep filtered ANN honest.

How Does Pre-Filtering Join the Inverted Index to HNSW?

Naïve filtered vector search often post-filters. It fetches nearest neighbors first, then drops ones that fail a condition. Restrictive filters can wipe the whole candidate set. You cannot predict how many results remain. Weaviate uses pre-filtering instead. The inverted index builds an allow-list of eligible object ids. That list is passed into HNSW. Graph traversal still follows edges normally, but only allow-listed ids enter the result set. Exit conditions stay the same as an unfiltered search once enough good candidates accumulate.

Because the allow-list is a compact id set, it can grow large without forcing a brute-force scan of every vector. ACORN-style filter strategies further help when filters correlate poorly with the query vector. For agent memory, this pattern protects scoped recall. User, group, and property boundaries become structured eligibility, not a hope that the top semantic hits happen to belong to the right person.

Hybrid search uses the same two indexes in a second, complementary way.

How Do Parallel Vector and BM25 Paths Become One Ranking?

Hybrid search runs vector search and BM25 keyword search in parallel on the same query string. BM25 uses the inverted index and term statistics. Vector search uses the HNSW graph and the query embedding. A fusion step then merges the two ranked lists. Weaviate supports ranked fusion and relative score fusion. Relative score fusion normalizes each side’s scores and combines them so large gaps on one side still influence the final order. That preserves more signal than rank-only fusion.

An alpha weight can lean the mix toward keywords or toward vectors when you tune Weaviate directly. Engram exposes the same underlying idea as retrieval types. Choose vector when you only care about meaning. Choose BM25 when you only care about exact terms. Choose hybrid for the default path. Hybrid is recommended for most Engram searches because agent questions mix paraphrase and identifiers in the same sentence.

Here is a bakery assistant writing a bench rule, then retrieving it with hybrid search so both the semantic proofing idea and the exact bench id can contribute.

import os
from engram import EngramClient
from engram.types import HybridRetrieval

client = EngramClient(api_key=os.environ["ENGRAM_API_KEY"])
user_id = "baker-soren"
group = "bakery_lab"

run = client.memories.add(
    "Dough bench 3 (dough-bench-3): Soren holds country loaves at 4C for a "
    "twelve-hour retard before scoring. Never dust that bench with rye flour "
    "after a gluten-free tray—cross-contact rule from March.",
    user_id=user_id,
    group=group,
    properties={"bench_id": "dough-bench-3"},
)
client.runs.wait(run.run_id)

results = client.memories.search(
    query="Overnight cold proof rules for dough bench 3",
    user_id=user_id,
    group=group,
    properties={"bench_id": "dough-bench-3"},
    retrieval_config=HybridRetrieval(limit=5),
)
for memory in results:
    print(memory.content)

The query says “overnight cold proof” in human language and “dough bench 3” as a hard token. Vector similarity can connect cold proof to the stored retard note. BM25 can lock onto the bench id string. Hybrid fusion keeps whichever side is strong without forcing you to run two client searches and merge by hand.

Day-to-day product choices follow from that mechanics picture.

When Should You Lean on One Index More Than the Other?

Use hybrid as the default in Weaviate Engram. Switch to BM25 when operators search for ticket ids, SKUs strings, or error codes that embeddings dilute. Switch to vector when the user speaks vaguely and you want conceptual neighbors. Keep scopes tight either way. Property filters and user ids still rely on structured isolation even when ranking is hybrid.

Avoid reinventing dual-index logic in application code. Engram already routes search into Weaviate’s combined machinery. Your job is to store clear memory text, wait on runs when freshness matters, and pick a retrieval config that matches the question shape. The indexes working together is infrastructure. The agent only sees ranked memories that feel both precise and understanding.

Weaviate’s strength for agent memory is not choosing vectors or keywords. It is keeping HNSW and the inverted index on the same objects so pre-filtering and hybrid fusion stay efficient. Weaviate Engram makes that dual path the default recall style. Our next chapter, What is native hybrid search in Weaviate?, goes deeper into hybrid parameters, fusion behavior, and how to tune the blend for memory workloads.