# How to Implement Three-Tier Context Loading (L0/L1/L2) in Custom OpenViking Agents

> Learn to implement three-tier context loading L0 L1 L2 in custom OpenViking agents using VikingFS abstraction and OVFileTool with this technical guide.

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

---

**OpenViking implements three-tier context loading through the VikingFS abstraction, where L0 ([`.abstract.md`](https://github.com/volcengine/OpenViking/blob/main/.abstract.md)) provides ~100-token summaries, L1 ([`.overview.md`](https://github.com/volcengine/OpenViking/blob/main/.overview.md)) provides ~300-token outlines, and L2 delivers full content, accessible via the `OVFileTool` with `level` parameters "abstract", "overview", or "read".**

The OpenViking framework structures resources as directories containing hierarchical context files to optimize token usage and retrieval performance. Understanding how to implement **three-tier context loading** is essential for building efficient custom agents that balance cost and comprehensiveness. This guide explains the L0/L1/L2 architecture and provides implementation patterns using the core VikingFS API and agent tools according to the volcengine/OpenViking source code.

## Understanding the Three-Tier Context Model

OpenViking stores every resource as a directory that contains three special files representing different granularity levels. This design minimizes token costs during initial retrieval while preserving access to full content when necessary.

### L0 Abstract: High-Level Summaries

The **L0 (abstract)** tier resides in [`.abstract.md`](https://github.com/volcengine/OpenViking/blob/main/.abstract.md) files containing approximately 100 tokens. These ultra-condensed summaries serve two critical functions: they populate the vector index for semantic search and enable rapid relevance filtering without loading heavy content. When your agent needs to scan large resource collections, requesting L0 abstracts keeps latency and token consumption minimal.

### L1 Overview: Structured Outlines

The **L1 (overview)** tier exists in [`.overview.md`](https://github.com/volcengine/OpenViking/blob/main/.overview.md) files with roughly 300 tokens. This level provides more detailed structural information about the resource, typically shown when an agent returns a list of candidates to the user. The overview contains enough context for users to determine if they want the full document, bridging the gap between abstract summaries and complete content.

### L2 Detail: Full Content Retrieval

The **L2 (detail)** tier corresponds to the original content file itself (for example, [`doc.md`](https://github.com/volcengine/OpenViking/blob/main/doc.md), `image.png`, or other binary payloads). This level contains the full, unabridged resource and should only be loaded when the user explicitly requests specific details. Loading L2 content incurs the highest token and bandwidth costs, making the three-tier hierarchy essential for scalable agent architectures.

## Core Implementation: VikingFS and ContextLevel

The three-tier loading pattern is built into the core file-system abstraction **VikingFS** ([`openviking/storage/viking_fs.py`](https://github.com/volcengine/OpenViking/blob/main/openviking/storage/viking_fs.py)). The system enforces these levels through the **`ContextLevel` enum** defined in [`openviking/core/context.py`](https://github.com/volcengine/OpenViking/blob/main/openviking/core/context.py) (lines 33-38), which standardizes the tier definitions across the entire stack.

The public API surface exposes three distinct methods:

```python
await viking_fs.abstract(uri)          # → L0

await viking_fs.overview(uri)          # → L1  

await viking_fs.read_file(uri)         # → L2 (default) or explicit level="read"

```

In [`openviking/storage/viking_fs.py`](https://github.com/volcengine/OpenViking/blob/main/openviking/storage/viking_fs.py), the `abstract` and `overview` methods (lines 511-540) read the special hidden files and return clean strings through `_handle_agfs_content`. The `read_file` method (lines 1235-1247) handles L2 retrieval and accepts an optional `level` argument, enabling unified access patterns across all tiers.

The vector indexer respects this hierarchy through `ALLOWED_CONTEXT_TYPES` in [`viking_vector_index_backend.py`](https://github.com/volcengine/OpenViking/blob/main/viking_vector_index_backend.py), ensuring that L0 abstracts are stored as the default indexed payload for efficient similarity search.

## Implementing Three-Tier Loading in Custom Agents

For agent development, the preferred retrieval method is the **OVFileTool** ([`bot/vikingbot/agent/tools/ov_file.py`](https://github.com/volcengine/OpenViking/blob/main/bot/vikingbot/agent/tools/ov_file.py)). This tool exposes a `level` argument in its JSON schema with three allowed values:

```python
{
    "type": "object",
    "properties": {
        "uri": {"type": "string"},
        "level": {
            "type": "string",
            "description": "Reading level: 'abstract' (L0 summary), 'overview' (L1 overview), or 'read' (L2 full content)",
            "enum": ["abstract", "overview", "read"],
            "default": "abstract"
        }
    },
    "required": ["uri"]
}

```

To implement three-tier loading in your custom agent:

1. **Add the `openviking_read` tool** to your agent's toolset (available in the default `vikingbot` package)
2. **Request L0 abstracts** when generating candidate lists or performing initial similarity searches
3. **Upgrade to L1 overviews** when the user asks for more context or file outlines
4. **Fetch L2 full content** only upon explicit user request for the complete document

The tool forwards requests to `VikingClient.read_content`, meaning you do not need to manage file paths or AGFS details manually. The client handles URI-to-path conversion, access checks, and vector-store synchronization automatically.

## Code Examples

### Using OVFileTool for Tiered Access

The `VikingReadTool` class in [`bot/vikingbot/agent/tools/ov_file.py`](https://github.com/volcengine/OpenViking/blob/main/bot/vikingbot/agent/tools/ov_file.py) provides the cleanest interface for tiered access. The `execute` method (lines 58-60) forwards to `client.read_content`:

```python
from vikingbot.agent.tools.ov_file import VikingReadTool

async def get_resource_summary(uri: str, tool_context):
    """L0 - Fast, cheap retrieval for indexing and filtering"""
    read_tool = VikingReadTool()
    abstract = await read_tool.execute(tool_context, uri=uri, level="abstract")
    return abstract

async def get_resource_outline(uri: str, tool_context):
    """L1 - Detailed outline for candidate presentation"""
    read_tool = VikingReadTool()
    overview = await read_tool.execute(tool_context, uri=uri, level="overview")
    return overview

async def get_resource_full(uri: str, tool_context):
    """L2 - Complete content for deep analysis"""
    read_tool = VikingReadTool()
    content = await read_tool.execute(tool_context, uri=uri, level="read")
    return content

```

### Direct VikingClient Integration

For non-tool code or custom services, use `VikingClient` directly from [`bot/vikingbot/openviking_mount/ov_server.py`](https://github.com/volcengine/OpenViking/blob/main/bot/vikingbot/openviking_mount/ov_server.py). The `read_content` method (lines 116-130) wraps the three-tier API:

```python
from bot.vikingbot.openviking_mount.ov_server import VikingClient

async def read_l0(uri: str):
    client = await VikingClient.create(workspace_id="default")
    return await client.read_content(uri, level="abstract")   # L0

async def read_l1(uri: str):
    client = await VikingClient.create(workspace_id="default")
    return await client.read_content(uri, level="overview")  # L1

async def read_l2(uri: str):
    client = await VikingClient.create(workspace_id="default")
    return await client.read_content(uri, level="read")      # L2

```

### Batch Processing in Context Builders

For advanced implementations, integrate tiered loading into your `ContextBuilder` using VikingFS batch methods. The `_batch_fetch_abstracts` method (lines 555-585) already performs parallel L0 fetches:

```python
async def build_system_prompt(self, session_key, current_message, history):
    # Stage 1: Load only abstracts for candidate resources (L0)

    abstracts = await self.viking_fs._batch_fetch_abstracts(
        candidate_entries, abs_limit=256
    )
    
    # Stage 2: Fetch L1 overviews when user requests outlines

    if "outline" in current_message.intent:
        outlines = await self.viking_fs._batch_fetch_overviews(
            candidate_uris
        )
    
    # Stage 3: Retrieve L2 full content on explicit demand

    if "full_document" in current_message.intent:
        full_content = await self.viking_fs.read_file(
            target_uri, level="read"
        )

```

## Summary

- **L0 Abstract** ([`.abstract.md`](https://github.com/volcengine/OpenViking/blob/main/.abstract.md)): ~100 tokens, ideal for vector indexing and initial filtering via `viking_fs.abstract()`
- **L1 Overview** ([`.overview.md`](https://github.com/volcengine/OpenViking/blob/main/.overview.md)): ~300 tokens, suitable for candidate lists and outlines via `viking_fs.overview()`
- **L2 Detail** (original file): Full content, loaded only on explicit request via `viking_fs.read_file()`
- **Implementation**: Use `VikingReadTool` with `level="abstract"`, `"overview"`, or `"read"` to access tiers without managing file paths
- **Source references**: `ContextLevel` enum in [`openviking/core/context.py`](https://github.com/volcengine/OpenViking/blob/main/openviking/core/context.py), VikingFS implementation in [`openviking/storage/viking_fs.py`](https://github.com/volcengine/OpenViking/blob/main/openviking/storage/viking_fs.py), and tool schema in [`bot/vikingbot/agent/tools/ov_file.py`](https://github.com/volcengine/OpenViking/blob/main/bot/vikingbot/agent/tools/ov_file.py)

## Frequently Asked Questions

### What is the typical token count for each OpenViking context level?

According to the source implementation, **L0 abstracts** contain approximately **100 tokens**, **L1 overviews** contain roughly **300 tokens**, and **L2** represents the full, unbounded content of the original file. These limits ensure that vector indexing and initial retrieval remain cost-effective while preserving detailed access when necessary.

### How does the vector indexer handle the three-tier context model?

The vector indexer uses the `ContextLevel` enum defined in [`openviking/core/context.py`](https://github.com/volcengine/OpenViking/blob/main/openviking/core/context.py) and respects `ALLOWED_CONTEXT_TYPES` in [`viking_vector_index_backend.py`](https://github.com/volcengine/OpenViking/blob/main/viking_vector_index_backend.py). By default, the system indexes **L0 abstracts** as the vector payload, enabling efficient semantic search without storing expensive full-text embeddings. L1 and L2 content remains accessible through the file system but is not embedded in the vector store.

### Can I customize the context loading behavior for specific resource types?

While the **OVFileTool** schema enforces the three standard levels ("abstract", "overview", "read"), you can implement custom logic in your agent's context builder to determine when to escalate from L0 to L1 or L2. The underlying `VikingFS` methods in [`openviking/storage/viking_fs.py`](https://github.com/volcengine/OpenViking/blob/main/openviking/storage/viking_fs.py) support direct file access if you need to bypass the standard tiering for specialized resource types, though this requires manual URI-to-path resolution.

### Where is the ContextLevel enum defined in the OpenViking source?

The `ContextLevel` enum is defined in [`openviking/core/context.py`](https://github.com/volcengine/OpenViking/blob/main/openviking/core/context.py) at lines 33-38. This enumeration standardizes the L0/L1/L2 designations across the framework, ensuring that components like the vector backend, VikingFS, and agent tools maintain consistent tier semantics throughout the retrieval pipeline.