Skills
August 21, 2026 ยท View on GitHub
Skills are callable capabilities that agents can invoke. This module provides skill addition and management functionality.
Core Concepts
Skill Types
OpenViking supports multiple skill definition formats:
- Structured skill data: Dictionary with name, description, content, etc.
- SKILL.md files: Markdown files with YAML frontmatter
- MCP Tool format: Automatically detected and converted to OpenViking skill format
Skill Storage Structure
Skills are stored under the current user's skills root. The home alias
viking://~/skills/ expands to viking://user/{user_id}/skills/ for the
authenticated request (the uid-less spelling viking://user/skills/ is no longer
accepted):
viking://user/{user_id}/skills/
+-- search-web/
| +-- .abstract.md # L0: Brief description
| +-- .overview.md # L1: Parameters and usage overview
| +-- SKILL.md # L2: Full documentation
| +-- [auxiliary files] # Any additional files
+-- calculator/
| +-- .abstract.md
| +-- .overview.md
| +-- SKILL.md
+-- ...
SKILL.md Format
Skills can be defined using SKILL.md files with YAML frontmatter:
---
name: skill-name
description: Brief description of the skill
allowed_tools:
- Tool1
- Tool2
tags:
- tag1
- tag2
---
# Skill Name
Full skill documentation in Markdown format.
## Parameters
- **param1** (type, required): Description
- **param2** (type, optional): Description
## Usage
When and how to use this skill.
## Examples
Concrete examples of skill invocation.
Required Fields
| Field | Type | Description |
|---|---|---|
| name | str | Skill name (kebab-case recommended) |
| description | str | Brief description |
Optional Fields
| Field | Type | Description |
|---|---|---|
| allowed_tools | List[str] | Tools this skill can use |
| tags | List[str] | Tags for categorization |
MCP Format Automatic Conversion
OpenViking automatically detects and converts MCP tool definitions to skill format.
Detection Rule: A dictionary is treated as MCP format if it contains an inputSchema field.
Conversion Process:
- Name is converted to kebab-case
- Description is preserved
- Parameters are extracted from
inputSchema.properties - Required fields are marked from
inputSchema.required - Markdown content is generated
Conversion Example:
Input (MCP format):
{
"name": "search_web",
"description": "Search the web",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query"
},
"limit": {
"type": "integer",
"description": "Max results"
}
},
"required": ["query"]
}
}
Output (Skill format):
{
"name": "search-web",
"description": "Search the web",
"content": """---
name: search-web
description: Search the web
---
# search-web
Search the web
## Parameters
- **query** (string) (required): Search query
- **limit** (integer) (optional): Max results
## Usage
This tool wraps the MCP tool `search-web`. Call this when the user needs functionality matching the description above.
"""
}
API Reference
add_skill
Add a skill to the knowledge base.
1. API Implementation Overview
Skills are a special type of resource that define actions or tools agents can perform.
Processing Flow:
- Receive skill data or uploaded temporary file
- Detect data format (structured data, SKILL.md content, MCP format)
- Parse skill definition
- Store to the current user's
viking://user/{user_id}/skills/path - If
wait=true, wait for vectorization to complete
Code Entry Points:
sdk/python/openviking_sdk/client.py:AsyncHTTPClient.add_skill- Python SDK entry pointopenviking_cli/client/http.py:AsyncHTTPClient.add_skill- SDK entry point (HTTP)openviking/server/routers/resources.py:add_skill- HTTP routeropenviking/service/resource_service.py:ResourceService.add_skill- Core service implementationcrates/ov_cli/src/handlers.rs:handle_add_skill- CLI handler
2. Interface and Parameters
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| data | Any | No | - | Inline skill content or structured data. Mutually exclusive with temp_file_id |
| temp_file_id | str | No | - | Temporary upload file ID (from temp_upload). Mutually exclusive with data |
| wait | bool | No | False | Wait for skill processing to complete |
| timeout | float | No | None | Timeout in seconds, only effective when wait=true |
| telemetry | TelemetryRequest | No | False | Whether to return telemetry data |
Additional Notes:
-
Local file handling:
- Python SDK and CLI accept local
SKILL.mdfiles or directories directly. In HTTP mode they automatically upload before calling the server API. - Raw HTTP callers should either:
- Send structured skill data directly in
data - Send raw
SKILL.mdcontent indata - First call
POST /api/v1/resources/temp_uploadto upload a localSKILL.mdfile/zip directory, then callPOST /api/v1/skillswithtemp_file_id temp_uploaddefaults to local temporary storage; passupload_mode=sharedonly when you explicitly need distributed shared temporary uploads. Python HTTP clients can setupload.mode = "shared"inovcli.conf; the RustovCLI instead usesOPENVIKING_UPLOAD_MODE=shared
- Send structured skill data directly in
POST /api/v1/skillsdoes not accept direct host filesystem paths indata.
- Python SDK and CLI accept local
-
Targeting:
- Skills are always user-scoped.
add_skilldoes not acceptto,parent, orroot_uri. - Peer-scoped skill roots are not supported; actor peer filtering only applies to peer memories/resources, not peer skills.
- Use the home alias
viking://~/skills/...to address your own skills when listing, reading, deleting, or searching. The uid-lessviking://user/skills/...spelling returns an error with a corrective hint.
- Skills are always user-scoped.
-
Supported data formats:
- Dict (Skill format): Includes
name,description,content, etc. - Dict (MCP Tool format): Includes
name,description,inputSchema, auto-detected and converted - String (SKILL.md content): Complete SKILL.md content
- Path (file or directory): Path to
SKILL.mdfile, or directory containingSKILL.md(auxiliary files included)
- Dict (Skill format): Includes
3. Usage Examples
HTTP API
POST /api/v1/skills
Content-Type: application/json
# Using inline structured data
curl -X POST http://localhost:1933/api/v1/skills \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"data": {
"name": "search-web",
"description": "Search the web for current information",
"content": "# search-web\n\nSearch the web for current information.\n\n## Parameters\n- **query** (string, required): Search query\n- **limit** (integer, optional): Max results, default 10"
},
"wait": true
}'
# Using inline SKILL.md content
curl -X POST http://localhost:1933/api/v1/skills \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"data": "---\nname: my-skill\ndescription: My custom skill\n---\n\n# My Skill\n\nSkill content here."
}'
# Using MCP Tool format (auto-detected and converted
curl -X POST http://localhost:1933/api/v1/skills \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"data": {
"name": "calculator",
"description": "Perform mathematical calculations",
"inputSchema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression to evaluate"
}
},
"required": ["expression"]
}
}
}'
# Using local file (first use temp_upload)
TEMP_FILE_ID=$(
curl -s -X POST http://localhost:1933/api/v1/resources/temp_upload \
-H "X-API-Key: your-key" \
-F "file=@./skills/my-skill.json" \
| jq -r '.result.temp_file_id'
)
curl -X POST http://localhost:1933/api/v1/skills \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d "{
\"temp_file_id\": \"$TEMP_FILE_ID\"
}"
Python SDK
import openviking as ov
client = ov.SyncHTTPClient(url="http://localhost:1933", api_key="your-key")
client.initialize()
# Approach 1: Using structured skill data
skill = {
"name": "search-web",
"description": "Search the web for current information",
"content": """# search-web
Search the web for current information.
## Parameters
- **query** (string, required): Search query
- **limit** (integer, optional): Max results, default 10
"""
}
result = client.add_skill(skill)
print(f"Added: {result['root_uri']}")
# Approach 2: Using MCP Tool format (auto-detected and converted
mcp_tool = {
"name": "calculator",
"description": "Perform mathematical calculations",
"inputSchema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression to evaluate"
}
},
"required": ["expression"]
}
}
result = client.add_skill(mcp_tool)
print(f"Added: {result['uri']}")
# Approach 3: Add from local SKILL.md file
result = client.add_skill("./skills/search-web/SKILL.md")
print(f"Added: {result['uri']}")
# Approach 4: Add from directory containing SKILL.md (auxiliary files included
result = client.add_skill("./skills/code-runner/")
print(f"Added: {result['uri']}")
print(f"Auxiliary files: {result['auxiliary_files']}")
# Wait for processing completion
result = client.add_skill("./skills/my-skill/", wait=True)
client.wait_processed()
TypeScript SDK
await client.addSkill("./my-skill", { wait: true });
Go SDK
result, err := client.AddSkill(ctx, "./skills/my-skill/", &openviking.AddSkillOptions{
Wait: true,
})
if err != nil {
return err
}
fmt.Println(result["uri"])
CLI
# Add skill (from file or directory
ov add-skill ./skills/my-skill.json
ov add-skill ./skills/search-web/SKILL.md
ov add-skill ./skills/code-runner/
# Wait for processing completion
ov add-skill ./skills/my-skill/ --wait
# Use JSON output format
ov add-skill ./skills/my-skill/ -o json
Response Examples
HTTP API response (JSON):
{
"status": "ok",
"result": {
"status": "success",
"root_uri": "viking://user/alice/skills/my-skill",
"uri": "viking://user/alice/skills/my-skill",
"name": "my-skill",
"auxiliary_files": 2,
"queue_status": {
"pending": 0,
"processing": 0,
"completed": 1
}
},
"telemetry": {
"operation_id": "550e8400-e29b-41d4-a716-446655440000"
},
"time": 0.1
}
CLI response (default table format):
Note: Skill is being processed in the background.
Use 'ov wait' to wait for completion, or 'ov observer queue' to check status.
status success
root_uri viking://user/alice/skills/my-skill
uri viking://user/alice/skills/my-skill
name my-skill
auxiliary_files 2
CLI response (JSON format, using -o json):
{
"status": "success",
"root_uri": "viking://user/alice/skills/my-skill",
"uri": "viking://user/alice/skills/my-skill",
"name": "my-skill",
"auxiliary_files": 2
}
Field Description:
| Field | Type | Description |
|---|---|---|
status | string | Processing status: success or error |
root_uri | string | Canonical final URI of the skill in OpenViking (same as uri) |
uri | string | Canonical final URI of the skill in OpenViking (same as root_uri) |
name | string | Skill name |
auxiliary_files | number | Number of auxiliary files included with the skill |
queue_status | object | (Optional, only when wait=true) Queue processing status with pending, processing, completed counts |
4. Error Handling
Synchronous Processing Errors:
If skill parsing or processing fails synchronously, raw HTTP returns the standard error envelope with a non-2xx HTTP status code:
{
"status": "error",
"error": {
"code": "PROCESSING_ERROR",
"message": "Skill parse error: invalid skill metadata"
}
}
The Python HTTP SDK raises the corresponding mapped exception for this response.
Skill Management Operations
The Python HTTP SDK and Go SDK expose dedicated skill management methods:
list_skills, find_skills, validate_skill, get_skill, update_skill,
and delete_skill in Python; ListSkills, FindSkills, ValidateSkill,
GetSkill, UpdateSkill, and DeleteSkill in Go. The general
filesystem/content/retrieval methods still work for URI-level access.
List Skills
Python SDK
skills = client.list_skills(node_limit=1000)
for skill in skills["skills"]:
print(skill["name"])
TypeScript SDK
console.log(await client.listSkills());
Go SDK
skills, err := client.ListSkills(ctx, nil)
_ = skills
HTTP API
curl -X GET "http://localhost:1933/api/v1/skills?node_limit=1000" \
-H "X-API-Key: your-key"
Read Skill
Python SDK
skill = client.get_skill("search-web", include_content=True, include_files=True)
print(skill["name"])
print(skill.get("content"))
TypeScript SDK
console.log(await client.getSkill("my-skill"));
Go SDK
skill, err := client.GetSkill(ctx, "search-web", &openviking.GetSkillOptions{
IncludeContent: openviking.Bool(true),
IncludeFiles: openviking.Bool(true),
})
_ = skill
HTTP API
curl -X GET "http://localhost:1933/api/v1/skills/search-web?include_content=true&include_files=true" \
-H "X-API-Key: your-key"
Search Skills
Python SDK
results = client.find_skills("search the internet", limit=5)
for skill in results["skills"]:
print(skill["name"], skill["score"])
TypeScript SDK
console.log(await client.findSkills("database migration"));
Go SDK
results, err := client.FindSkills(ctx, "search the internet", &openviking.FindSkillsOptions{
Limit: 5,
})
_ = results
HTTP API
curl -X POST http://localhost:1933/api/v1/skills/find \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"query": "search the internet",
"limit": 5
}'
Validate and Update Skills
Python SDK
validated = client.validate_skill({"name": "search-web", "description": "..."})
updated = client.update_skill("search-web", "./skills/search-web", wait=True)
TypeScript SDK
console.log(await client.validateSkill({
name: "search-web",
description: "Search the web for current information",
content: "# search-web\n\nSearch the web for current information.",
}));
Go SDK
validated, err := client.ValidateSkill(ctx, map[string]any{
"name": "search-web",
"description": "...",
}, nil)
updated, err := client.UpdateSkill(ctx, "search-web", "./skills/search-web", &openviking.UpdateSkillOptions{
Wait: true,
})
_, _ = validated, updated
HTTP API
# Validate skill data
curl -X POST http://localhost:1933/api/v1/skills/validate \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{"data": {"name": "search-web", "description": "..."}}'
# Replace an existing skill with new content
curl -X PUT http://localhost:1933/api/v1/skills/search-web \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"data": {
"name": "search-web",
"description": "Search the web for current information",
"content": "# search-web\n\nUpdated instructions."
},
"wait": true
}'
Delete Skills
Python SDK
client.delete_skill("old-skill")
TypeScript SDK
await client.deleteSkill("my-skill");
Go SDK
deleted, err := client.DeleteSkill(ctx, "old-skill")
_ = deleted
HTTP API
curl -X DELETE "http://localhost:1933/api/v1/skills/old-skill" \
-H "X-API-Key: your-key"
Skill Management Responses
List and search return a skills array and total. Without target_uri, root_uris identifies the private user and shared Agent roots; with a target, the response contains a single root_uri.
{
"status": "ok",
"result": {
"root_uris": [
"viking://user/default/skills",
"viking://agent/skills"
],
"skills": [
{
"type": "skill",
"name": "search-web",
"uri": "viking://user/default/skills/search-web",
"root_uri": "viking://user/default/skills/search-web",
"skill_md_uri": "viking://user/default/skills/search-web/SKILL.md",
"description": "Search the web for current information",
"tags": [],
"allowed_tools": [],
"score": 0.87,
"match_reason": "semantic",
"level": 0
}
],
"total": 1
}
}
Reading one skill returns the metadata above and conditionally adds abstract, overview, content, files, and source according to level and the include_* parameters.
Validation returns valid, strict, normalized metadata, body_lines, errors, and warnings. Invalid input still uses a successful response envelope with valid=false:
{
"status": "ok",
"result": {
"valid": false,
"strict": false,
"name": "search-web",
"description": "",
"tags": [],
"allowed_tools": [],
"body_lines": 0,
"errors": [
{
"rule": "description_required",
"message": "description is required",
"field": "description"
}
],
"warnings": []
}
}
A successful update returns the same processing result as add_skill with an additional "action": "update". A successful delete returns:
{
"status": "ok",
"result": {
"name": "old-skill",
"uri": "viking://user/default/skills/old-skill",
"root_uri": "viking://user/default/skills/old-skill",
"estimated_deleted_count": 4,
"privacy_deleted": false
}
}
estimated_deleted_count appears only when the filesystem can estimate the number of deleted entries.
Best Practices
Clear Descriptions
# Good - specific and actionable
skill = {
"name": "search-web",
"description": "Search the web for current information using Google",
...
}
# Less helpful - too vague
skill = {
"name": "search",
"description": "Search",
...
}
Comprehensive Content
Include in your skill content:
- Clear parameter descriptions with types
- When to use the skill
- Concrete examples
- Edge cases and limitations
Consistent Naming
Use kebab-case for skill names:
search-web(recommended)searchWeb(avoid)search_web(avoid)
Related Documentation
- Resource Management - Resource addition and management
- File System - File and directory operations
- Context Types - Skill concept
- Retrieval - Finding skills
- Sessions - Tracking skill usage