# 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: 1. **Structured skill data**: Dictionary with name, description, content, etc. 2. **SKILL.md files**: Markdown files with YAML frontmatter 3. **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: ```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** | 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**: 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 **Conversion Example**: 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. """ } ``` ## 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**: 1. Receive skill data or uploaded temporary file 2. Detect data format (structured data, SKILL.md content, MCP format) 3. Parse skill definition 4. Store to the current user's `viking://user/{user_id}/skills/` path 5. 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 router - `openviking/service/resource_service.py:ResourceService.add_skill` - Core service implementation - `crates/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.md` files 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.md` content in `data` - First call `POST /api/v1/resources/temp_upload` to upload a local `SKILL.md` file/zip directory, then call `POST /api/v1/skills` with `temp_file_id` - `temp_upload` defaults to local temporary storage; pass `upload_mode=shared` only when you explicitly need distributed shared temporary uploads. Python HTTP clients can set `upload.mode = "shared"` in `ovcli.conf`; the Rust `ov` CLI instead uses `OPENVIKING_UPLOAD_MODE=shared` - `POST /api/v1/skills` does not accept direct host filesystem paths in `data`. - **Targeting**: - Skills are always user-scoped. `add_skill` does not accept `to`, `parent`, or `root_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. - **Supported data formats**: 1. **Dict (Skill format)**: Includes `name`, `description`, `content`, etc. 2. **Dict (MCP Tool format)**: Includes `name`, `description`, `inputSchema`, auto-detected and converted 3. **String (SKILL.md content)**: Complete SKILL.md content 4. **Path (file or directory)**: Path to `SKILL.md` file, or directory containing `SKILL.md` (auxiliary files included) #### 3. Usage Examples **HTTP API** ``` POST /api/v1/skills Content-Type: application/json ``` ```bash # 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** ```python 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** ```typescript await client.addSkill("./my-skill", { wait: true }); ``` **Go SDK** ```go result, err := client.AddSkill(ctx, "./skills/my-skill/", &openviking.AddSkillOptions{ Wait: true, }) if err != nil { return err } fmt.Println(result["uri"]) ``` **CLI** ```bash # 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)**: ```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 2 ``` **CLI response (JSON format, using -o json)**: ```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: ```json { "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** ```python skills = client.list_skills(node_limit=1000) for skill in skills["skills"]: print(skill["name"]) ``` **TypeScript SDK** ```typescript console.log(await client.listSkills()); ``` **Go SDK** ```go skills, err := client.ListSkills(ctx, nil) _ = skills ``` **HTTP API** ```bash curl -X GET "http://localhost:1933/api/v1/skills?node_limit=1000" \ -H "X-API-Key: your-key" ``` ### Read Skill **Python SDK** ```python skill = client.get_skill("search-web", include_content=True, include_files=True) print(skill["name"]) print(skill.get("content")) ``` **TypeScript SDK** ```typescript console.log(await client.getSkill("my-skill")); ``` **Go SDK** ```go skill, err := client.GetSkill(ctx, "search-web", &openviking.GetSkillOptions{ IncludeContent: openviking.Bool(true), IncludeFiles: openviking.Bool(true), }) _ = skill ``` **HTTP API** ```bash 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** ```python results = client.find_skills("search the internet", limit=5) for skill in results["skills"]: print(skill["name"], skill["score"]) ``` **TypeScript SDK** ```typescript console.log(await client.findSkills("database migration")); ``` **Go SDK** ```go results, err := client.FindSkills(ctx, "search the internet", &openviking.FindSkillsOptions{ Limit: 5, }) _ = results ``` **HTTP API** ```bash 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** ```python validated = client.validate_skill({"name": "search-web", "description": "..."}) updated = client.update_skill("search-web", "./skills/search-web", wait=True) ``` **TypeScript SDK** ```typescript 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** ```go 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, updated ``` **HTTP API** ```bash # 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** ```python client.delete_skill("old-skill") ``` **TypeScript SDK** ```typescript await client.deleteSkill("my-skill"); ``` **Go SDK** ```go deleted, err := client.DeleteSkill(ctx, "old-skill") _ = deleted ``` **HTTP API** ```bash 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`. ```json { "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`: ```json { "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: ```json { "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 ```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` (recommended) - `searchWeb` (avoid) - `search_web` (avoid) ## Related Documentation - [Resource Management](02-resources.md) - Resource addition and management - [File System](03-filesystem.md) - File and directory operations - [Context Types](../concepts/02-context-types.md) - Skill concept - [Retrieval](06-retrieval.md) - Finding skills - [Sessions](05-sessions.md) - Tracking skill usage