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 short URI viking://user/skills/ resolves to viking://user/{user_id}/skills/ for the authenticated request:
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:
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 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. In Python HTTP client / CLI flows, this can also be driven byovcli.confviaupload.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
viking://user/skills/...as current-user shorthand when listing, reading, deleting, or searching skills.
- 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 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,
"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 2CLI 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)
_ = 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("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("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({"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, 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."
},
"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")
_ = 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",
...
}
# 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
