Viking URI: How OpenViking Identifies and Organizes AI Agent Context
Purpose
This post explains the Viking URI specification and how it enables deterministic context access in OpenViking.
Problem
When I first started using OpenViking, I kept getting confused about how to reference specific pieces of context. I’d use find() for everything, even when I knew exactly where the resource was.
Here’s what my code looked like:
# I knew the file was at resources/docs/auth/oauth.md# But I still used semantic searchresults = client.find("oauth authentication guide")for result in results: if "oauth" in result.path: content = result.content breakThis felt wrong. I knew the exact location—why was I searching? It was like using Google every time I wanted to open a file on my own computer.
Then I discovered Viking URIs.
What is a Viking URI?
A Viking URI is OpenViking’s unified resource identifier format. Every piece of context—documents, memories, skills, sessions—has a unique URI that determines its location in the virtual filesystem.
The format is simple:
viking://[context_type]/[namespace]/[path]From the OpenViking documentation:
“Each context is assigned a unique viking:// URI, enabling precise location and access to resources stored in different locations.”
This transforms vague semantic matching into precise, traceable operations.
Environment
- Python 3.10+
- OpenViking SDK
- Basic understanding of filesystem navigation
URI Components
Let me break down each component:
viking://resources/github.com/owner/repo/docs/api.md│ │ │ ││ │ │ └── Path: hierarchical location│ │ └── Namespace: logical grouping│ └── Context Type: what kind of resource└── Protocol: always "viking://"Context Types
OpenViking uses four main context types:
resources/ -> External knowledge (docs, PDFs, web pages)user/ -> User-specific memoriesagent/ -> Agent-learned memories and skillssession/ -> Conversation sessionsI made a quick reference table:
| Type | URI Prefix | Purpose |
|---|---|---|
| Resource | viking://resources/ | External knowledge from URLs or files |
| User Memory | viking://user/memories/ | User preferences and facts |
| Agent Memory | viking://agent/memories/ | Agent task experiences |
| Skill | viking://agent/skills/ | Callable capabilities |
| Session | viking://session/ | Conversation history |
URI in Action
Resource URIs
For external resources, I use URL-based namespaces:
from openviking import OpenViking
client = OpenViking()
# Add a resource from GitHubclient.add_resource( url="https://github.com/volcengine/OpenViking/blob/main/README.md", path="viking://resources/github.com/volcengine/OpenViking/README.md")
# Later, access it directly without searchcontent = client.read("viking://resources/github.com/volcengine/OpenViking/README.md")No semantic search needed. I knew the URI, so I accessed it directly.
Memory URIs
User memories follow a predictable structure:
# User profile memoryprofile = client.read("viking://user/memories/profile.md")
# User preferencesprefs = client.read("viking://user/memories/preferences/coding_style.md")
# Entity memories (project, person, company)entity = client.read("viking://user/memories/entities/project_alpha.md")Agent memories for task experiences:
# Case memories - specific debugging scenarioscase = client.read("viking://agent/memories/cases/debug_auth_issue.md")
# Pattern memories - reusable solutionspattern = client.read("viking://agent/memories/patterns/api_error_handling.md")Skill URIs
Skills have their own namespace:
# List available skillsskills = client.ls("viking://agent/skills/")# Returns: ['search_web/', 'analyze_code/', 'send_email/']
# Read a skill definitionskill_def = client.read("viking://agent/skills/search_web/SKILL.md")Operations with URIs
List and Tree
I use ls and tree to explore:
# List contents of a directoryentries = client.ls("viking://resources/my_project/")# Returns: ['docs/', 'src/', 'README.md']
# View hierarchical structuretree = client.tree("viking://resources/my_project", depth=3)The tree output shows the hierarchy:
viking://resources/my_project├── docs/│ ├── api/│ │ ├── authentication.md│ │ └── endpoints.md│ └── getting-started.md├── src/│ └── main.py└── README.mdRead
Direct access when I know the URI:
content = client.read("viking://resources/docs/api/authentication.md")print(content)Search Within a URI
I can combine search with URI paths for scoped retrieval:
# Search only within docs/api/results = client.find( "authentication", target_uri="viking://resources/docs/api/")
for ctx in results.resources: print(f"URI: {ctx.uri}") print(f"Abstract: {ctx.abstract}") print(f"Score: {ctx.score}")This is powerful: semantic search within a known directory.
Grep
Search for patterns within files:
# Find all TODO comments in a projectmatches = client.grep( "TODO", uri="viking://resources/my_project/")Special Files: Abstracts and Overviews
Every directory can contain metadata files that support OpenViking’s tiered loading:
viking://resources/docs/auth/├── .abstract.md # L0: ~100 token summary (always loaded)├── .overview.md # L1: ~2k token overview (on demand)├── .relations.json # Links to related contexts├── oauth.md # L2: Full content (when needed)└── jwt.mdI read these directly:
# Get the abstract (L0)abstract = client.read("viking://resources/docs/auth/.abstract.md")
# Get the overview (L1)overview = client.read("viking://resources/docs/auth/.overview.md")
# Get relations (linked contexts)relations = client.read("viking://resources/docs/auth/.relations.json")Or use the convenience methods:
# These methods handle the special files automaticallyabstract = client.abstract("viking://resources/docs/auth/")overview = client.overview("viking://resources/docs/auth/")Namespace Conventions
URL-based Namespaces
For web resources, I mirror the URL structure:
viking://resources/github.com/owner/repo/...viking://resources/docs.python.org/3/library/...viking://resources/web/example.com/articles/...This makes it easy to trace back to the original source.
Custom Namespaces
For local or custom resources:
viking://resources/my_project/...viking://resources/company_docs/...viking://resources/knowledge_base/...URI in Session Messages
I can reference context in conversations using URIs:
from openviking.message import TextPart, ContextPart
session.add_message("assistant", [ TextPart("Here's the authentication guide:"), ContextPart( uri="viking://resources/docs/auth/oauth.md", abstract="OAuth 2.0 guide for API authentication" )])The agent can then load the full content if needed.
Real-World Example: Debugging Retrieval
Last week, my agent was retrieving the wrong OAuth documentation. I traced the problem using URIs.
First, I checked what was stored:
# List all OAuth-related resourcesresults = client.find("oauth", target_uri="viking://resources/")
for r in results.resources: print(f"URI: {r.uri}") print(f"Abstract: {r.abstract[:100]}")I found two OAuth documents:
URI: viking://resources/docs/auth/oauth.mdAbstract: OAuth 2.0 implementation for our API...
URI: viking://resources/legacy/old_oauth.htmlAbstract: Deprecated OAuth 1.0 documentation...The agent was picking up the deprecated OAuth 1.0 docs. I fixed this by:
- Removing the legacy doc:
client.remove("viking://resources/legacy/old_oauth.html") - Or scoping the search:
client.find("oauth", target_uri="viking://resources/docs/")
Without URIs, I would have struggled to identify the problem. URIs gave me a clear view of what was stored and where.
URI vs Search: When to Use Which
I learned this distinction through trial and error:
Use URIs when:
- You know the exact location
- You need deterministic access
- You’re debugging retrieval
- You want to scope search to a directory
Use search when:
- You don’t know the location
- You want semantic matching
- You’re exploring unfamiliar content
# URI: Deterministic, fastcontent = client.read("viking://resources/docs/auth/oauth.md")
# Search: Flexible, semanticresults = client.find("how do I authenticate with oauth")Best Practices
After working with Viking URIs for a few weeks, I settled on these practices:
- Consistent naming: Use clear, hierarchical paths that mirror your mental model
# Good: Clear hierarchyclient.add_resource(url, "viking://resources/project/docs/api/endpoints.md")
# Bad: Flat structureclient.add_resource(url, "viking://resources/project_api_endpoints.md")- Logical grouping: Keep related resources in the same namespace
viking://resources/my_project/├── docs/├── src/└── tests/- Document structure: Add README files for major directories
client.write( "viking://resources/my_project/README.md", "# My Project Resources\n\nThis directory contains...")- Use relations: Link related contexts via
.relations.json
{ "related": [ "viking://resources/docs/api/authentication.md", "viking://agent/skills/oauth_handler/SKILL.md" ]}Summary
In this post, I explained the Viking URI specification and how it enables deterministic context access in OpenViking. The key insight is that every piece of context has a unique hierarchical identifier, turning vague semantic matching into precise, traceable operations.
Viking URIs solve a fundamental problem: knowing where something is but having to search for it anyway. With URIs, I can list, read, and grep context just like a filesystem, while still having semantic search when I need it. This combination of deterministic access and flexible search makes OpenViking practical for real-world agent development.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments