Nebula searches over a vector graph of entities and relationships, not raw text chunks. You send a natural language query; Nebula traverses the graph and returns structured memory: semantics (facts and inferences), procedures (preferences and habits), episodes (temporal event clusters), and sources (the original text).
Basic Search
from nebula import Nebula, Memory
nebula = Nebula()
results = nebula.search(
query="What has Sarah worked on?",
collection_ids=["engineering"]
)
# Semantics: structured subject-predicate-value triples
for fact in results.semantics:
print(f"{fact['subject']} → {fact['predicate']} → {fact['value']}")
print(f" Score: {fact['activation_score']:.2f}")
# Procedures: user preferences and habits
for proc in results.procedures:
print(f"{proc['statement']} (confidence: {proc['confidence']:.2f})")
# Sources: original source text grounding the semantics
for source in results.sources:
print(f"[{source.get('speaker', 'unknown')}]: {source['text']}")
Understanding Results
Search returns a MemoryResponse with four layers of memory:
| Layer | Contains | Example |
|---|
| Semantics | Subject-predicate-value triples (facts, inferences, tasks) | Sarah Chen → led → Aurora migration |
| Procedures | User preferences and behavioral patterns | Prefers PostgreSQL over MySQL |
| Episodes | Temporally clustered events | Q4 database migration project |
| Sources | Original source text that grounds the semantics | "Sarah led the migration from PostgreSQL to Aurora last quarter" |
Each item has an activation_score (0-1) reflecting its relevance to the query. Semantics items link back to their source text, giving you a full provenance chain from structured assertions down to original content.
The vector graph is built automatically when you store memories. You don’t need to define entities or relationships - Nebula extracts them.
Search Effort
The effort parameter is the primary control for search. It determines how deeply Nebula traverses the vector graph.
| Effort | Depth | Use Case |
|---|
auto | Adapts to query | Default - good for most queries |
low | 2 hops, narrow | Fast lookups, simple factual queries |
medium | 2 hops, wider | Broader exploration across more relationships |
high | 3 hops, widest | Deep multi-hop reasoning across the graph |
# Quick factual lookup
results = nebula.search(query="Sarah's role", effort="low")
# Deep exploration
results = nebula.search(query="How are the Q4 projects connected?", effort="high")
Start with the default (auto). Only increase effort if you need the graph to discover connections further from the query.
Scoping with Collections
Use collection_ids to scope which part of the vector graph is searched.
# Search specific collections
results = nebula.search(
query="customer feedback",
collection_ids=["support-2024", "support-2023", "surveys"]
)
# Search all accessible collections (omit collection_ids)
results = nebula.search(query="product roadmap")
Searching all collections is slower than targeting specific ones.
Authority Scores
Authority is a store-time parameter on conversation messages that tells Nebula how much to trust a piece of content. It’s stored per-message, not per-memory.
# Verified answer from support agent
conv_id = nebula.store_memory(
Memory(
collection_id="support",
content="Here is the verified answer...",
role="assistant",
authority=0.9
)
)
# Uncertain user message
nebula.store_memory(
Memory(
memory_id=conv_id,
collection_id="support",
content="I'm not sure, maybe try this?",
role="user",
authority=0.4
)
)
| Score | Use Case |
|---|
0.9-1.0 | Verified facts, official sources |
0.5-0.7 | General content (default: 0.5) |
0.0-0.3 | Low confidence or uncertain |
Advanced Options
Hybrid Search Weights
Control the balance between semantic and full-text matching for seed discovery (how the graph traversal finds its starting points):
results = nebula.search(
query="neural networks",
collection_ids=["research"],
search_settings={
"semantic_weight": 0.7,
"fulltext_weight": 0.3
}
)
Defaults (0.8 semantic, 0.2 fulltext) work well for most cases. Lower semantic weight if you need exact keyword matching.
Optionally narrow the search scope using metadata constraints. Filters restrict which part of the graph is entered - the graph still handles discovery within that scope.
results = nebula.search(
query="project updates",
collection_ids=["work"],
filters={
"status": "active",
"team": "engineering"
}
)
Operators: $eq, $ne, $in, $nin, $gt, $gte, $lt, $lte, $like, $ilike, $overlap, $contains, $and, $or
See Metadata Filtering for the full reference.
Next Steps