Skip to content

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

ParameterTypeRequiredDefaultDescription
dataAnyYes-Skill data. Raw HTTP accepts structured data or raw SKILL.md content, not direct host paths
temp_file_idstrNoNoneUpload ID returned by POST /api/v1/resources/temp_upload for raw HTTP local file ingestion
waitboolNoFalseWait for vectorization to complete
timeoutfloatNoNoneTimeout in seconds

How local skill files work

  • Python SDK and CLI accept local SKILL.md files 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.md content in data
    • upload a local SKILL.md file first with POST /api/v1/resources/temp_upload, then call POST /api/v1/skills with temp_file_id
    • zip a local skill directory first, upload the .zip file, then call POST /api/v1/skills with temp_file_id
  • POST /api/v1/skills does not accept direct host filesystem paths in data.

Supported Data Formats

  1. Dict (Skill format):
python
{
    "name": "skill-name",
    "description": "Skill description",
    "content": "Full markdown content",
    "allowed_tools": ["Tool1", "Tool2"],  # optional
    "tags": ["tag1", "tag2"]  # optional
}
  1. Dict (MCP Tool format) - Auto-detected and converted:
python
{
    "name": "tool_name",
    "description": "Tool description",
    "inputSchema": {
        "type": "object",
        "properties": {...},
        "required": [...]
    }
}
  1. String (SKILL.md content):
python
"""---
name: skill-name
description: Skill description
---

# Skill Content
"""
  1. Path (file or directory):
    • Single file: Path to SKILL.md file
    • Directory: Path to directory containing SKILL.md (auxiliary files included)

Python SDK (Embedded / HTTP)

python
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/skills
bash
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"
    }
  }'

CLI

bash
openviking add-skill ./my-skill/ [--wait]

Response

json
{
  "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:

json
{
  "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)

python
# 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

bash
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)

python
# 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

bash
# 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

markdown
---
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

FieldTypeDescription
namestrSkill name (kebab-case recommended)
descriptionstrBrief description

Optional Fields

FieldTypeDescription
allowed-toolsList[str]Tools this skill can use
tagsList[str]Tags for categorization

Managing Skills

List Skills

Python SDK (Embedded / HTTP)

python
# 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

bash
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)

python
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

bash
# 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)

python
# 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

bash
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)

python
client.rm("viking://agent/skills/old-skill/", recursive=True)

HTTP API

bash
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:

python
if "inputSchema" in data:
    # Convert to skill format
    skill = mcp_to_skill(data)

Conversion Process

  1. Name is converted to kebab-case
  2. Description is preserved
  3. Parameters are extracted from inputSchema.properties
  4. Required fields are marked from inputSchema.required
  5. Markdown content is generated

Example Conversion

Input (MCP format):

python
{
    "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):

python
{
    "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

python
# 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)

Released under the Apache-2.0 License.