# How OpenViking Implements Hierarchical Retrieval with Directory Recursion

> Discover how OpenViking implements hierarchical retrieval with directory recursion. Learn about its score-driven, breadth-first algorithm and priority queue traversal.

- Repository: [Volcengine/OpenViking](https://github.com/volcengine/OpenViking)
- Tags: internals
- Published: 2026-03-08

---

**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`](https://github.com/volcengine/OpenViking/blob/main/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`](https://github.com/volcengine/OpenViking/blob/main/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:

```python
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:

```python
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`:

```python
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:

```python
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`](https://github.com/volcengine/OpenViking/blob/main/openviking/retrieve/hierarchical_retriever.py) | Main implementation of the recursive, priority‑queue based retrieval algorithm. |
| [`openviking/storage/viking_fs.py`](https://github.com/volcengine/OpenViking/blob/main/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`](https://github.com/volcengine/OpenViking/blob/main/openviking/utils/time_utils.py) | Parses ISO timestamps for hotness calculation. |
| [`openviking/models/embedder/base.py`](https://github.com/volcengine/OpenViking/blob/main/openviking/models/embedder/base.py) | Defines `EmbedResult` used by the retriever to obtain dense & sparse vectors. |
| [`openviking_cli/retrieve/types.py`](https://github.com/volcengine/OpenViking/blob/main/openviking_cli/retrieve/types.py) | Data‑class definitions (`TypedQuery`, `QueryResult`, `MatchedContext`) consumed by the retriever. |
| [`openviking_cli/utils/config.py`](https://github.com/volcengine/OpenViking/blob/main/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_ROUNDS` iterations 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_relations` to 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.