How OpenViking Implements Hierarchical Retrieval with Directory Recursion
OpenViking performs hierarchical retrieval with directory recursion using a score-driven, breadth-first algorithm that traverses directory structures via a priority queue, propagating relevance scores from parent to child nodes with configurable alpha blending and early stopping based on result convergence.
OpenViking, the open-source knowledge base system developed by Volcengine, implements sophisticated hierarchical retrieval with directory recursion to navigate complex, nested knowledge structures. The core algorithm resides in openviking/retrieve/hierarchical_retriever.py and combines vector similarity search with intelligent directory traversal to surface the most relevant documents across multiple levels of a virtual file system.
Core Algorithm: Score-Driven Breadth-First Search
The hierarchical retriever treats the knowledge base as a tree where directories are intermediate nodes and documents are leaves. Rather than performing a flat search across all vectors, the system uses a min-heap priority queue (dir_queue) to explore the highest-scoring directories first.
The queue stores tuples of (-score, uri), ensuring that directories with the highest relevance scores are popped first. This breadth-first approach prioritizes promising branches of the knowledge tree before descending into lower-scoring subtrees.
Initialization and Entry Points
Global Root Discovery
The retrieval process begins by establishing entry points into the hierarchy. If the user specifies target_directories, these become the root nodes. Otherwise, the retriever invokes _global_vector_search to query the vector store for global root candidates across the entire knowledge base.
Merging Starting Points
The _merge_starting_points function consolidates these entry points into a unified list of (uri, seed_score) pairs. This deduplication step ensures that overlapping directory specifications do not create redundant search branches. The initialization logic appears in the retrieve() method between lines 31-48 and 122-131 of hierarchical_retriever.py.
Recursive Directory Traversal
Priority Queue Management
Once initialized, the algorithm enters its main loop. While dir_queue contains entries, the retriever pops the highest-scoring directory and fetches its children via search_children_in_tenant. This pre-filter stage (lines 89-98) retrieves all immediate children of the current directory in a single vector store call.
Score Propagation Across Levels
Child nodes inherit relevance from their parents through score propagation. The final score calculation applies a configurable alpha blend:
final_score = α * child_score + (1 - α) * parent_score
Where α is defined by the constant SCORE_PROPAGATION_ALPHA (lines 20-22). This ensures that documents in highly relevant directories receive boosted scores, even if their direct vector similarity is modest.
Threshold Filtering and Deduplication
After propagation, candidates must satisfy passes_threshold, which respects the score_gte flag to implement greater-than-or-equal versus strict greater-than comparisons (lines 23-27). The collected_by_uri dictionary maintains only the highest-scoring entry for each unique URI, preventing duplicate results across different traversal paths (lines 29-34).
Directory-Only Recursion
The recursion logic explicitly checks if r.get("level", 2) != 2 before re-inserting a child into the priority queue (lines 40-42). In OpenViking's schema, level 2 represents files, while levels 0 and 1 represent directories and subdirectories. This check ensures that the algorithm only recurses into directory nodes, treating files as terminal leaves.
Convergence-Based Early Stopping
To prevent infinite deep scans in large knowledge bases, the retriever implements convergence detection. After each queue pop, the algorithm builds the current top-K list (current_topk). If this set remains unchanged for MAX_CONVERGENCE_ROUNDS (defaulting to 3 iterations), the walk terminates early (lines 52-57).
This optimization recognizes that once the highest-scoring documents stabilize, deeper exploration of lower-scoring branches is unlikely to improve results, saving significant compute resources in deeply nested hierarchies.
Result Enrichment and Post-Processing
After traversal completes, _convert_to_matched_contexts transforms the raw candidates into MatchedContext objects. This stage enriches results with hotness scores (calculated from access frequency and recency) and resolves relations via VikingFS.get_relations to include connected knowledge entities (lines 68-84).
The final output blends semantic relevance, directory hierarchy, temporal hotness, and relational context into a comprehensive ranked list of knowledge resources.
Practical Implementation Examples
Basic Async Retrieval
The following example demonstrates initializing the retriever and executing a hierarchical search:
import asyncio
from openviking.retrieve.hierarchical_retriever import HierarchicalRetriever, RetrieverMode
from openviking_cli.utils.config import RerankConfig
from openviking.storage import VikingVectorIndexBackend
from openviking_cli.retrieve.types import TypedQuery
async def demo():
# Initialise storage backend (example: Milvus‑like implementation)
vector_store = VikingVectorIndexBackend(...)
# Optional embedder (e.g., OpenAI‑based)
embedder = ...
retriever = HierarchicalRetriever(
storage=vector_store,
embedder=embedder,
rerank_config=RerankConfig(enabled=False) # no external rerank service
)
query = TypedQuery(
query="How to configure the OpenViking agent?",
context_type=None,
target_directories=[],
)
result = await retriever.retrieve(
query=query,
ctx=..., # RequestContext with user/tenant info
limit=5,
mode=RetrieverMode.THINKING,
)
for ctx in result.matched_contexts:
print(f"URI: {ctx.uri}\nScore: {ctx.score:.4f}\nAbstract: {ctx.abstract[:120]}…\n")
asyncio.run(demo())
This implementation automatically handles directory recursion, score propagation, and hotness-boosted ranking without manual traversal logic.
Shallow Non-Recursive Search
To constrain the search to specific directories without deep recursion, specify target_directories and use RetrieverMode.QUICK:
result = await retriever.retrieve(
query=query,
ctx=ctx,
limit=3,
mode=RetrieverMode.QUICK,
target_dirs=["/knowledge/agents/config"]
)
While QUICK mode skips the external rerank step, the retriever still respects the directory boundaries to prevent unwanted deep traversal.
Debugging the Retrieval Queue
Enable internal logging to observe the hierarchical traversal in real-time:
import logging
logging.basicConfig(level=logging.INFO) # enables the logger.info calls inside the retriever
This configuration emits diagnostic messages during execution:
[RecursiveSearch] Entering URI: /knowledge/agents
[RecursiveSearch] Updated URI: /knowledge/agents/intro.md candidate score to 0.8421
...
Key Source Files
| File | Role |
|---|---|
openviking/retrieve/hierarchical_retriever.py |
Main implementation of the recursive, priority‑queue based retrieval algorithm. |
openviking/storage/viking_fs.py |
Provides get_relations and read_batch used to enrich results with related contexts and hotness scores. |
openviking/utils/time_utils.py |
Parses ISO timestamps for hotness calculation. |
openviking/models/embedder/base.py |
Defines EmbedResult used by the retriever to obtain dense & sparse vectors. |
openviking_cli/retrieve/types.py |
Data‑class definitions (TypedQuery, QueryResult, MatchedContext) consumed by the retriever. |
openviking_cli/utils/config.py |
Holds RerankConfig; the retriever checks its availability to decide whether to invoke an external reranker. |
These files collectively enable OpenViking to traverse its virtual file system, propagate relevance scores across directory levels, and return the most contextually appropriate resources to an AI agent.
Summary
- Score-driven breadth-first traversal: OpenViking uses a min-heap priority queue to explore the highest-scoring directories first, ensuring optimal resource allocation during hierarchical retrieval with directory recursion.
- Intelligent score propagation: Child nodes inherit relevance through alpha blending (
SCORE_PROPAGATION_ALPHA), combining local vector similarity with parent directory scores to surface contextually relevant documents. - Directory-only recursion: The algorithm explicitly checks node levels (
level != 2) to ensure recursion stops at file boundaries, preventing unnecessary deep traversal into document contents. - Convergence-based early stopping: The retriever monitors top-K stability across
MAX_CONVERGENCE_ROUNDSiterations to terminate exploration when results plateau, optimizing performance in deeply nested hierarchies. - Rich result enrichment: Final results integrate hotness scores (access frequency/recency) and relational context via
VikingFS.get_relationsto provide comprehensive knowledge context beyond pure semantic similarity.
Frequently Asked Questions
How does OpenViking prevent infinite recursion in deep directory structures?
OpenViking prevents infinite recursion through convergence detection and level-based filtering. The algorithm tracks the top-K results across iterations and terminates early if the result set remains unchanged for MAX_CONVERGENCE_ROUNDS (default 3). Additionally, the recursion logic explicitly checks if r.get("level", 2) != 2 before re-inserting nodes into the priority queue, ensuring only directories (levels 0 and 1) trigger further traversal while files (level 2) are treated as terminal leaves.
What is the purpose of score propagation in hierarchical retrieval?
Score propagation ensures that documents inherit contextual relevance from their parent directories, preventing highly relevant folders from being overlooked due to mediocre individual document embeddings. The algorithm applies the formula final_score = α * child_score + (1 - α) * parent_score, where α is defined by SCORE_PROPAGATION_ALPHA. This alpha blending boosts documents located in highly scored directories while preserving the influence of individual vector similarity, creating a balanced ranking that respects both semantic content and structural context.
Can I disable directory recursion and search only specific folders?
Yes, you can constrain the search to specific directories without deep recursion by utilizing the target_directories parameter in your TypedQuery. When you specify exact folder paths in target_dirs, the retriever treats these as the sole entry points. While RetrieverMode.QUICK skips external reranking, the traversal still respects directory boundaries. To achieve a truly shallow search, provide the specific leaf directory you wish to search and avoid specifying parent directories that would trigger recursive descent into subfolders.
How does the convergence detection mechanism improve performance?
The convergence detection mechanism significantly reduces computational overhead in large, deeply nested knowledge bases by terminating the search when additional traversal is unlikely to improve results. After each iteration of the priority queue, the algorithm compares the current top-K URI set against previous iterations. If this set remains stable for MAX_CONVERGENCE_ROUNDS consecutive iterations (defaulting to 3), the retriever concludes that the ranking has plateaued and exits early. This prevents wasteful deep scans of low-scoring directory branches while maintaining result quality, particularly effective in hierarchies where high-relevance content clusters in specific subtrees.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →