Skills
Skills are callable capabilities that agents can invoke. This guide covers how to add and manage skills.
API Reference
add_skill()
Add a skill to the knowledge base.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| data | Any | Yes | - | Skill data. Raw HTTP accepts structured data or raw SKILL.md content, not direct host paths |
| temp_file_id | str | No | None | Upload ID returned by POST /api/v1/resources/temp_upload for raw HTTP local file ingestion |
| wait | bool | No | False | Wait for vectorization to complete |
| timeout | float | No | None | Timeout in seconds |
How local skill files work
- Python SDK and CLI accept local
SKILL.mdfiles or directories directly. In HTTP mode they automatically upload local files before calling the server API. - Raw HTTP callers should either:
- send structured skill data directly in
data - send raw
SKILL.mdcontent indata - upload a local
SKILL.mdfile first withPOST /api/v1/resources/temp_upload, then callPOST /api/v1/skillswithtemp_file_id - zip a local skill directory first, upload the
.zipfile, then callPOST /api/v1/skillswithtemp_file_id
- send structured skill data directly in
POST /api/v1/skillsdoes not accept direct host filesystem paths indata.
Supported Data Formats
- Dict (Skill format):
{
"name": "skill-name",
"description": "Skill description",
"content": "Full markdown content",
"allowed_tools": ["Tool1", "Tool2"], # optional
"tags": ["tag1", "tag2"] # optional
}- Dict (MCP Tool format) - Auto-detected and converted:
{
"name": "tool_name",
"description": "Tool description",
"inputSchema": {
"type": "object",
"properties": {...},
"required": [...]
}
}- String (SKILL.md content):
"""---
name: skill-name
description: Skill description
---
# Skill Content
"""- Path (file or directory):
- Single file: Path to
SKILL.mdfile - Directory: Path to directory containing
SKILL.md(auxiliary files included)
- Single file: Path to
Python SDK (Embedded / HTTP)
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']}")HTTP API
POST /api/v1/skillscurl -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"
}
}'CLI
openviking add-skill ./my-skill/ [--wait]Response
{
"status": "ok",
"result": {
"status": "success",
"root_uri": "viking://agent/skills/search-web/",
"uri": "viking://agent/skills/search-web/",
"name": "search-web",
"auxiliary_files": 0
},
"time": 0.1
}Synchronous Processing Error
If skill parsing or processing fails synchronously, raw HTTP returns the standard error envelope with a non-2xx HTTP status:
{
"status": "error",
"error": {
"code": "PROCESSING_ERROR",
"message": "Skill parse error: invalid skill metadata"
}
}The Python HTTP SDKs raise the mapped exception for this response.
Example: Add from MCP Tool
Python SDK (Embedded / HTTP)
# MCP tool format is 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']}")HTTP API
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"]
}
}
}'Example: Add from SKILL.md File
Python SDK (Embedded / HTTP)
# Add from file path
result = client.add_skill("./skills/search-web/SKILL.md")
print(f"Added: {result['uri']}")
# Add from directory (includes auxiliary files)
result = client.add_skill("./skills/code-runner/")
print(f"Added: {result['uri']}")
print(f"Auxiliary files: {result['auxiliary_files']}")HTTP API
# Step 1: upload the local SKILL.md file
TEMP_FILE_ID=$(
curl -sS -X POST http://localhost:1933/api/v1/resources/temp_upload \
-H "X-API-Key: your-key" \
-F 'file=@./skills/search-web/SKILL.md' \
| jq -r '.result.temp_file_id'
)
# Step 2: add the uploaded skill
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\"
}"For a local skill directory, zip the directory first, upload the .zip file, then call the same POST /api/v1/skills request with the returned temp_file_id.
SKILL.md Format
Skills can be defined using SKILL.md files with YAML frontmatter.
Structure
---
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 |
Managing Skills
List Skills
Python SDK (Embedded / HTTP)
# 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"Read Skill Content
Python SDK (Embedded / HTTP)
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"Search Skills
Python SDK (Embedded / HTTP)
# 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
}'Remove Skills
Python SDK (Embedded / HTTP)
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"MCP Conversion
OpenViking automatically detects and converts MCP tool definitions to skill format.
Detection
A dict is treated as MCP format if it contains an inputSchema field:
if "inputSchema" in data:
# Convert to skill format
skill = mcp_to_skill(data)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
Example Conversion
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.
"""
}Skill Storage Structure
Skills are stored at viking://agent/skills/:
viking://agent/skills/
+-- search-web/
| +-- .abstract.md # L0: Brief description
| +-- .overview.md # L1: Parameters and usage
| +-- SKILL.md # L2: Full documentation
| +-- [auxiliary files] # Any additional files
+-- calculator/
| +-- .abstract.md
| +-- .overview.md
| +-- SKILL.md
+-- ...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(good)searchWeb(avoid)search_web(avoid)
Related Documentation
- Context Types - Skill concept
- Retrieval - Finding skills
- Sessions - Tracking skill usage
