Skills
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 - Return a
task_idby default for tracking background vectorization
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
Imports return a task_id by default. Query the Task API and search the skill after the task reaches completed.
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"
}
}'
# 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(data=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(data=mcp_tool)
print(f"Added: {result['uri']}")
# Approach 3: Add from local SKILL.md file
result = client.add_skill(data="./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(data="./skills/code-runner/")
print(f"Added: {result['uri']}")
print(f"Auxiliary files: {result['auxiliary_files']}")
# Check the status of the previous import task
print(client.get_task(result["task_id"]))TypeScript SDK
const result = await client.addSkill("./my-skill");
console.log(result.task_id);Go SDK
result, err := client.AddSkill(ctx, "./skills/my-skill/", nil)
if err != nil {
return err
}
fmt.Println(result["task_id"])CLI
ov add-skill and ov skills add share the same options and import flow. The CLI clones Git repositories or GitHub tree directories locally, then packages and uploads the selected skills. The server API still receives inline content or temporary uploads. Supporting files such as references/ and scripts/ are included. Use --list to inspect a collection and --skill to select skills. Batch imports require confirmation unless --yes is set. By default, the command waits for downloading, uploading, server-side parsing, overview generation, and file writes, but not vectorization. --wait additionally waits for vectorization.
# Import one skill from the standalone skills branch; ov skills add also works
ov add-skill https://github.com/volcengine/OpenViking/tree/skills/llm-wiki --wait
# Inspect a local collection, then select skills to import
ov add-skill ./examples/compile/ov-compile-skills --list
ov add-skill ./examples/compile/ov-compile-skills --skill llm-wiki daily-report --yes
# Add a skill from a local file or directory
ov add-skill ./skills/my-skill.json
ov add-skill ./skills/search-web/SKILL.md
ov add-skill ./skills/code-runner/
# Check progress using the task_id returned by submission
ov task status TASK_ID
# Use JSON output format
ov add-skill ./skills/my-skill/ -o jsonResponse 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,
"task_id": "uuid-xxx"
},
"telemetry": {
"operation_id": "550e8400-e29b-41d4-a716-446655440000"
},
"time": 0.1
}CLI response (default table format):
Note: Skill processing may continue in the background.
Use 'ov task status <task_id>' to check progress, or 'ov task list' to see all tasks.
status success
root_uri viking://user/alice/skills/my-skill
uri viking://user/alice/skills/my-skill
name my-skill
auxiliary_files 2
task_id uuid-xxxCLI 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,
"task_id": "uuid-xxx"
}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 |
task_id | string | Returned in the default asynchronous mode; query the Task API for the background processing task's final status |
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)
_ = skillsHTTP 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(
skill_name="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),
})
_ = skillHTTP 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(query="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,
})
_ = resultsHTTP 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(data={"name": "search-web", "description": "..."})
updated = client.update_skill(
skill_name="search-web",
data="./skills/search-web",
)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", nil)
_, _ = validated, updatedHTTP 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."
}
}'Delete Skills
Python SDK
client.delete_skill(skill_name="old-skill")TypeScript SDK
await client.deleteSkill("my-skill");Go SDK
deleted, err := client.DeleteSkill(ctx, "old-skill")
_ = deletedHTTP 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",
# Additional skill fields
}
# Less helpful - too vague
skill = {
"name": "search",
"description": "Search",
# Additional skill fields
}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
