docs/en/api/04-skills.md
Skills are callable capabilities that agents can invoke. This module provides skill addition and management functionality.
OpenViking supports multiple skill definition formats:
Skills are stored at viking://agent/skills/:
viking://agent/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
+-- ...
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 |
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:
inputSchema.propertiesinputSchema.requiredConversion 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.
"""
}
Add a skill to the knowledge base.
Skills are a special type of resource that define actions or tools agents can perform.
Processing Flow:
viking://agent/skills/ pathwait=true, wait for vectorization to completeCode Entry Points:
openviking/client/local.py:LocalClient.add_skill - SDK entry point (embedded)openviking_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 handlerParameters
| 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:
SKILL.md files or directories directly. In HTTP mode they automatically upload before calling the server API.dataSKILL.md content in dataPOST /api/v1/resources/temp_upload to upload a local SKILL.md file/zip directory, then call POST /api/v1/skills with temp_file_idPOST /api/v1/skills does not accept direct host filesystem paths in data.Supported data formats:
name, description, content, etc.name, description, inputSchema, auto-detected and convertedSKILL.md file, or directory containing SKILL.md (auxiliary files included)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()
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://agent/skills/my-skill/",
"uri": "viking://agent/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://agent/skills/my-skill
uri viking://agent/skills/my-skill
name my-skill
auxiliary_files 2
CLI response (JSON format, using -o json):
{
"status": "success",
"root_uri": "viking://agent/skills/my-skill",
"uri": "viking://agent/skills/my-skill",
"name": "my-skill",
"auxiliary_files": 2
}
Field Description:
| Field | Type | Description |
|---|---|---|
status | string | Processing status: success or error |
root_uri | string | Final URI of the skill in OpenViking (same as uri) |
uri | string | 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 |
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.
Python SDK
# List all skills
skills = client.ls("viking://agent/skills/")
for skill in skills:
print(f"{skill['name']}")
# Simple list (names only
names = client.ls("viking://agent/skills/", simple=True)
print(names)
HTTP API
curl -X GET "http://localhost:1933/api/v1/fs/ls?uri=viking://agent/skills/" \
-H "X-API-Key: your-key"
Python SDK
uri = "viking://agent/skills/search-web/"
# L0: Brief description
abstract = client.abstract(uri)
print(f"Abstract: {abstract}")
# L1: Parameters and usage overview
overview = client.overview(uri)
print(f"Overview: {overview}")
# L2: Full skill documentation
content = client.read(uri)
print(f"Content: {content}")
HTTP API
# L0: Brief description
curl -X GET "http://localhost:1933/api/v1/content/abstract?uri=viking://agent/skills/search-web/" \
-H "X-API-Key: your-key"
# L1: Parameters and usage overview
curl -X GET "http://localhost:1933/api/v1/content/overview?uri=viking://agent/skills/search-web/" \
-H "X-API-Key: your-key"
# L2: Full skill documentation
curl -X GET "http://localhost:1933/api/v1/content/read?uri=viking://agent/skills/search-web/" \
-H "X-API-Key: your-key"
Python SDK
# Semantic search for skills
results = client.find(
"search the internet",
target_uri="viking://agent/skills/",
limit=5
)
for ctx in results.skills:
print(f"Skill: {ctx.uri}")
print(f"Score: {ctx.score:.3f}")
print(f"Description: {ctx.abstract}")
HTTP API
curl -X POST http://localhost:1933/api/v1/search/find \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"query": "search the internet",
"target_uri": "viking://agent/skills/",
"limit": 5
}'
Python SDK
client.rm("viking://agent/skills/old-skill/", recursive=True)
HTTP API
curl -X DELETE "http://localhost:1933/api/v1/fs?uri=viking://agent/skills/old-skill/&recursive=true" \
-H "X-API-Key: your-key"
# 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",
...
}
Include in your skill content:
Use kebab-case for skill names:
search-web (recommended)searchWeb (avoid)search_web (avoid)