Skills
Skills are task instructions and supporting resources for agents to read. OpenViking stores, retrieves, and manages them; the consuming Agent/Harness handles activation, tool policies, and execution. This page follows the HTTP API, SDKs, and ov CLI in the current repository.
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: Converted into Skill documentation; does not connect to an MCP server or register executable tools
- Skill packages and collections: Directories or ZIPs containing
SKILL.mdand auxiliary files, or Git repository / GitHub tree URLs
Skill Storage Structure
Skills support the current user's private root viking://user/{user_id}/skills/ and the account-shared root viking://agent/skills/. The add target resolves in this order: request target_uri → user add_targets.skill_uri → server user_config_defaults.add_targets.skill_uri → current user's private root. Access remains subject to the current identity and permissions.
The home alias viking://~/skills/ always resolves to the authenticated user's private root, independently of the configured add target. The uid-less viking://user/skills/ and peer-scoped skills are unsupported. The private package layout below also applies under the shared root:
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
+-- ...Package Processing and Indexing
New Skills process the entire package by default, without an additional flag. Root L0 still comes from Skill metadata, and root L1 still uses only SKILL.md. Subdirectories use resource directory summaries. Directory L0/L1 and supported L2 files are indexed; nested SKILL.md files remain ordinary attachments.
Auxiliary files now enter the existing summary and embedding pipeline, subject to the current hidden-file filters, input limits, and media settings. Images use supported image or text inputs; audio and video use text summaries with the existing filename fallback. wait=true waits for the whole package. Upgrades do not automatically rebuild existing Skills: use semantic_and_vectors to populate missing summaries; rebuilding vectors alone does not create them.
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: Read Bash(python3 *)
tags:
- tag1
- tag2
metadata:
author: example-team
vikingbot:
requires:
bins: [python3]
---
# 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 | Nonempty; at most 64 ASCII letters, digits, underscores, or hyphens; kebab-case recommended |
| description | str | Brief description |
Optional Fields
| Field | Type | Description |
|---|---|---|
| allowed-tools | str / List[str] | Space-separated tool declarations or a compatible string list; parentheses can contain spaces; the consumer interprets and enforces policies |
| tags | List[str] | Tags for categorization |
| metadata | object | Preserved extensions such as metadata.vikingbot.requires; OpenViking does not install these dependencies |
Use hyphenated allowed-tools in SKILL.md; parsed structured data and API summaries use allowed_tools. Do not substitute the underscore spelling in frontmatter. An omitted declaration and an explicit empty declaration can have different Harness permissions; a summary with allowed_tools: [] cannot distinguish them. Read the full definition before execution. See VikingBot Skills for instruction and metadata handling.
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:
- Underscores in the name are replaced with hyphens (camelCase is not converted automatically)
- 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 (Python dictionary):
{
"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
This endpoint stores Skill instructions and supporting files, then generates summaries and indexes for retrieval. It does not execute the Skill or register tools.
Processing Flow:
- Receive skill data or uploaded temporary file
- Detect data format (structured data, SKILL.md content, MCP format)
- Parse skill definition
- Store the package under the selected private or account-shared skills root
- 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- Compatibility import forwarding to the Python SDKopenviking/server/routers/resources.py:add_skill- HTTP routeropenviking/server/mcp_endpoint.py:add_skill- MCP toolopenviking/server/skill_ingest.py:install_skills- Install implementation shared by the HTTP router, the MCP tool, and signed skill uploadsopenviking/service/resource_service.py:ResourceService.add_skill- Core service implementationopenviking/server/routers/skills.py- List, find, read, validate, update, and delete endpointscrates/ov_cli/src/commands/skills.rs- CLI Skill command handlers
2. Interface and Parameters
HTTP body parameters (POST /api/v1/skills)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| data | Any | No | - | Inline SKILL.md, structured/MCP data, or Git URL; choose this or temp_file_id |
| temp_file_id | str | No | - | Temporary upload file ID (from temp_upload); choose this or data |
| wait | bool | No | False | Wait for skill processing to complete |
| telemetry | TelemetryRequest | No | False | Whether to return telemetry data |
| target_uri | str | No | Target resolution order | Skills root, such as viking://~/skills or viking://agent/skills |
| skills | List[str] | No | [] | Select directory names from a directory/Git collection; empty or ["*"] selects all |
| list_only | bool | No | False | Inspect source Skills without installing; requires a resolvable file, directory, or Git source, not an inline dictionary |
| source_metadata | object | No | Generated automatically | Source tracking saved in .source.json; distinct from frontmatter metadata |
Supply at least one of data or temp_file_id. The current server gives uploads precedence if both are present; clients should send only one to avoid ambiguity. Unknown body fields are rejected.
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 - Send a Git repository or GitHub tree URL for server-side source resolution
- First call
POST /api/v1/resources/temp_uploadto upload a localSKILL.mdor directory ZIP, then callPOST /api/v1/skillswithtemp_file_id
- Send structured skill data directly in
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.POST /api/v1/skillsdoes not accept direct host filesystem paths indata.- MCP clients use the
add_skilltool:datatakes SKILL.md text, andpathtakes a Git URL or a local path. For a local path the tool returns a one-time signedtemp_uploadURL; after the client POSTs the SKILL.md or ZIP there, the server installs it with the token-boundtarget_uri,skills, andlist_only, and the upload response carries the install result.
- Python SDK and CLI accept local
Targeting:
- Add requests use
target_urifor the skills root;to,parent, androot_uriare not HTTP request fields. CLI-p/--parent-auto-createmaps totarget_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.
- Add requests use
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 (automatically uploaded by SDK/CLI): SKILL.md file, Skill directory/collection, or ZIP; a single Markdown file does not include sibling resources
- Git URL: Repository or Skill subdirectory. Collection
skillsselectors use directory names, not frontmatter names. A discoveredSKILL.mdowns its subtree; nestedSKILL.mdfiles remain attachments rather than separate discovered Skills
- Dict (Skill format): Includes
Local .json files are not automatically decoded into structured Skills. Parse JSON and send the object in data, or use a frontmatter-based SKILL.md instead.
3. Usage Examples
Single-Skill imports return a task_id by default. Query the Task API and search after the task reaches completed. With wait=true, the call waits for queue processing and returns queue_status. Multiple imports return an installed array with independent task information per result; the batch is not atomic. list_only=true returns source skills and total without creating import tasks.
HTTP fields and SDK parameters do not have identical names or nesting. Python add_skill(data, wait=False, options=None) / update_skill(skill_name, data, ...) take target_uri and telemetry inside options. Put fields such as skills, list_only, source_metadata (add), and from_source (update) in options["extra"]. TypeScript uses targetUri and extra; Go uses TargetURI and Extra. TypeScript local path uploads require Node.js.
# Preview a local collection without writing to OpenViking
listing = client.add_skill(
"./skills",
options={"extra": {"list_only": True}},
)
print(listing["skills"])
# Select directory names and install in the shared root (requires permission)
result = client.add_skill(
"./skills",
wait=True,
options={
"target_uri": "viking://agent/skills",
"extra": {"skills": ["search-web", "calculator"]},
},
)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/SKILL.md" \
| 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_sdk 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. Use --list to inspect a collection and --skill to select skills. Batch imports require confirmation unless --yes is set.
# 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
# Inspect a local collection, then select skills to import
ov add-skill ./openviking/system_assets/compile/example-skills --list
ov add-skill ./openviking/system_assets/compile/example-skills --skill llm-wiki daily-report --yes
# Add a skill from a local file or directory
ov add-skill ./skills/search-web/SKILL.md
ov add-skill ./skills/code-runner/
# Target the shared root and wait for processing
ov skills add ./skills/code-runner/ -p viking://agent/skills --wait
# Check progress using the task_id returned by submission
ov task status TASK_ID
# Use JSON output format
ov -o json add-skill ./skills/my-skill/Response 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"
}
}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):
{
"ok": true,
"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"
}
}Field Description:
| Field | Type | Description |
|---|---|---|
status | string | Successful import results use success; synchronous failures use HTTP error responses |
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 | With wait=true, queue names such as Semantic / Embedding map to processed, requeue_count, error_count, and errors |
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.
Error codes depend on the failure stage: invalid names/targets use INVALID_ARGUMENT / INVALID_URI, missing Skills use NOT_FOUND, oversized integrity manifests use RESOURCE_EXHAUSTED. Success with wait=false only confirms the synchronous import stage; inspect task status for background failures. Standard responses may include optional telemetry / profile, but have no generic time field. SDKs normally return the result object directly.
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
GET /api/v1/skills accepts query parameters node_limit=1000 and optional target_uri. Without a target it merges private and shared roots, retaining same-name Skills distinguished by root_uri. The current server fixes directory scanning at node_limit=1000 per root and does not forward the requested node_limit to that scan; do not rely on it as a global result limit or pagination parameter.
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)
if err != nil {
return err
}
fmt.Println(skills)HTTP API
curl -X GET "http://localhost:1933/api/v1/skills?node_limit=1000" \
-H "X-API-Key: your-key"Read Skill
GET /api/v1/skills/{skill_name}:
| Query parameter | Default | Meaning |
|---|---|---|
target_uri | Unspecified | Skills root to try first |
level | Unspecified | 0 returns abstract, 1 overview, 2 SKILL.md; omitted returns all three levels |
include_content | Unspecified | true adds content; false disables content when level is omitted; level=2 always returns content |
include_files | true | File and directory manifest, without hashes by default |
include_integrity | false | Read a snapshot under a tree lock; requires include_files=true to produce file hashes and revision |
include_source | false | Include .source.json tracking; absent tracking returns source.tracked=false |
Read, update, and delete share name resolution: by default, try the private user Skill, then the shared Skill. An explicit target_uri is tried first, but a miss still falls back to private/shared roots. For these operations, target_uri currently does not guarantee exclusive resolution within that root. Read and inspect the returned root_uri when managing same-name Skills.
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),
})
if err != nil {
return err
}
fmt.Println(skill)HTTP API
curl -X GET "http://localhost:1933/api/v1/skills/search-web?include_content=true&include_files=true" \
-H "X-API-Key: your-key"Integrity Manifests and Remote Use
A remote Harness can request an integrity manifest before downloading scripts or binary resources:
skill = client.get_skill(
"search-web",
target_uri="viking://~/skills",
include_content=True,
include_files=True,
include_integrity=True,
)
print(skill["root_uri"], skill["revision"], skill["content_sha256"])
for entry in skill["files"]:
if not entry["is_dir"]:
print(entry["path"], entry["uri"], entry["size"], entry["sha256"])The HTTP equivalent is GET /api/v1/skills/search-web?include_content=true&include_files=true&include_integrity=true. The current Python SDK exposes include_integrity. TypeScript/Go getSkill / GetSkill options and ov skills show do not yet expose it; use HTTP or the Python SDK when needed.
| Response field | Meaning |
|---|---|
content_sha256 | SHA-256 of the returned SKILL.md text; present whenever content is returned, even without integrity mode |
revision | Version derived from the sorted package manifest: paths, directory flags, sizes, and hashes; not a Git commit or metadata.version |
files[].name/path/uri | Entry name, package-relative path, and canonical URI |
files[].is_dir/kind | Directory flag; kind is definition, summary, auxiliary, or directory |
files[].size/sha256 | Byte size and SHA-256 for non-directory entries in integrity mode; absent from ordinary manifests |
The manifest includes SKILL.md, summaries, and auxiliary files/directories, but excludes .source.json. Integrity limits are 512 entries (including directories), 16 MiB per file, and 64 MiB total file bytes, with read concurrency 8. Exceeding a limit returns RESOURCE_EXHAUSTED. Content, manifest, and revision are obtained under one tree lock, but later downloads can encounter updates; consumers should verify file hashes and recheck revision.
Search and get_skill return content and manifests without executing scripts or installing files in an Agent sandbox. A Harness can read text remotely and download resources when a tool requires local paths. See VikingBot Skills for the complete consumer workflow. MCP clients reach the same package-level behavior through the find tool with context_type="skill"; see MCP Integration.
Search Skills
POST /api/v1/skills/find body:
| Parameter | Default | Meaning |
|---|---|---|
query | Required | Search text |
limit | 10 | Maximum distinct Skills returned after merging private and shared scopes |
score_threshold | null | Minimum score; omission uses underlying retrieval defaults |
level | null | Levels allowed to match, such as [0]; omission allows all levels, unlike the single integer on get |
target_uri | null | Search scope; omitted searches private and shared roots separately |
telemetry | false | Telemetry configuration |
Package hits are grouped by their full Skill root URI, ranked by their highest final score, then limited to limit Skills. Same-named Skills in different scopes remain distinct. total is the returned array length, not a count of all possible matches.
Each Skill is represented by its highest-scoring package hit. The existing uri, level, score, and abstract fields come directly from that hit, without additional response fields. Package grouping and pagination apply to the dedicated skills/find endpoint and to the MCP find tool called with context_type="skill" alone; REST find / search continue to return individual hits. MCP search retrieves individual hits too, and only collapses them per package when rendering its answer.
| Response field | Meaning |
|---|---|
uri / level | Actual hit URI and level: L0 points to .abstract.md, L1 to .overview.md, and L2 to the matching file |
score | The hit's final score, also used to rank its Skill |
abstract | The hit record's existing summary, following the original search rules |
name / description / tags / allowed_tools | Metadata read separately from the Skill root by the dedicated skills/find endpoint |
root_uri / skill_md_uri | The Skill root and main SKILL.md addresses returned by the dedicated skills/find endpoint |
The dedicated skills/find endpoint now returns the actual hit URI instead of replacing it with the package root; the MCP find tool differs and rewrites every Skill hit to <package root>/SKILL.md. List and get-by-name responses are unchanged. level=[2] restricts matching to files and returns level=2 with the URI of the highest-scoring file in each package.
General search with read_content=true continues to read the returned URI — for MCP find with context_type="skill" that URI is the package's SKILL.md, not the file that matched. The dedicated skills/find endpoint does not accept this option.
The URI rules above apply to semantic search. A filter-only general find retains the stored record URI and returns score=0, without adding summary-file suffixes for L0 or L1; MCP find keeps such a query on that generic path, since package retrieval requires query text, but still rewrites each skill hit to its SKILL.md.
Scope, level, and permission filters apply before grouping; the Skill root must also be accessible.
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,
})
if err != nil {
return err
}
fmt.Println(results)HTTP 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
POST /api/v1/skills/validate accepts required data (structured data or complete SKILL.md text), strict=false, source_path=null, skill_dir_name=null, and target_uri=null. source_path is report information, not an instruction to read a server-local file. The current endpoint accepts but does not use target_uri for validation. SDK validate_skill does not upload paths automatically; read the file text first.
Missing names/descriptions and invalid YAML produce errors. Directory/name mismatch, invalid name length/characters, and descriptions over 1024 characters produce warnings normally and errors with strict=true. A body exceeding 500 lines remains only a warning in strict mode. Validation does not install Skills or check registered tools and executable dependencies; it does not replace import or Harness checks.
PUT /api/v1/skills/{skill_name} replaces the entire package and accepts:
| Parameter | Default | Meaning |
|---|---|---|
data / temp_file_id | Unspecified | New content or uploaded package, as for add |
from_source | false | Refresh from recorded Git source; cannot be combined with data/temp_file_id |
target_uri | Unspecified | Skills root to try first; see read resolution fallback |
source_metadata | Generated automatically | Updated source tracking |
wait | false | Wait for background processing |
telemetry | false | Telemetry configuration |
Supply new content/an upload, or set from_source=true. The new name must match skill_name in the URL; update is neither rename nor a partial patch. The server parses and checks the new package before backing up and replacing the old one, and attempts restoration on synchronous failure. Include all auxiliary files that should remain in the replacement package.
Background processing starts after the update's files, privacy configuration, and task setup are ready. When an update fails or is cancelled while waiting, it cancels that update's summary and indexing work, waits for started writes to exit, and restores the original files, index, and privacy configuration. Restoration failures are reported. Background failures after a successful wait=false response do not trigger restoration.
Python SDK
validated = client.validate_skill(data={"name": "search-web", "description": "..."})
print(validated["valid"], validated["errors"], validated["warnings"])
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)
if err != nil {
return err
}
fmt.Println(validated)
updated, err := client.UpdateSkill(ctx, "search-web", "./skills/search-web", nil)
if err != nil {
return err
}
fmt.Println(updated)HTTP 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."
}
}'Source refresh requires a recorded Git source and fetches the recorded repository, ref, and subdirectory again. A local upload has no Git source to pull automatically:
updated = client.update_skill(
"search-web",
data=None,
wait=True,
options={"extra": {"from_source": True}},
)curl -X PUT http://localhost:1933/api/v1/skills/search-web \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{"from_source": true, "wait": true}'Delete Skills
DELETE /api/v1/skills/{skill_name} accepts optional query parameter target_uri. It recursively removes the resolved package and handles its associated Skill privacy configuration. Target fallback is the same as for read.
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")
if err != nil {
return err
}
fmt.Println(deleted)HTTP API
curl -X DELETE "http://localhost:1933/api/v1/skills/old-skill" \
-H "X-API-Key: your-key"CLI Management Commands
ov skills list -p viking://~/skills
ov skills find "search the internet" --level 0 --limit 5 -p viking://~/skills
ov skills show search-web --level 2 --files --source -p viking://~/skills
ov skills validate ./skills/search-web --strict
ov skills update search-web -p viking://~/skills --wait --yes
ov skills remove old-skill -p viking://~/skills --yesThe long form of -p is --uri for list/find/show and --parent-auto-create for add/update/remove. show returns all text levels by default; request files with --files and tracking with --source. validate runs locally without installation. update refreshes recorded Git sources; for non-Git sources, interactive use can prompt for a path/URL.
Without names, ov skills update attempts all installed Skills and reports unsuccessful entries in skipped. remove without names opens interactive selection; only --all selects everything. Update and remove require confirmation; use --yes explicitly in scripts.
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. The following search response uses an L0 hit. A list response uses the package URI and does not include search scores.
{
"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/.abstract.md",
"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
}Execution Conditions and Expected Results
State when the Skill applies, the tools and dependencies it needs, its inputs, and the expected output. Give one concrete example and a way to check the result. Explain what the Agent should do when a dependency is missing or a step fails. OpenViking stores these instructions; the consuming Harness determines which tools can actually run.
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
- VikingBot Skills - Local/remote activation, metadata, and execution