Skip to main content
In addition to the built-in Skills, you can create custom Skills to extend Kubiya’s capabilities with organization-specific integrations, tools, or workflows.

Overview

Custom Skills are loaded from the .kubiya/skills/ directory in your home directory or project workspace. They allow you to:
  • Integrate with proprietary APIs and services
  • Create organization-specific tools
  • Package reusable workflow patterns
  • Extend Kubiya without modifying core code

Directory Structure

Create custom Skills in one of these locations (in order of priority):
  1. Project Workspace: .kubiya/skills/ (in your project directory)
  2. User Global: ~/.kubiya/skills/ (in your home directory)
Example Directory Layout:
~/.kubiya/skills/
├── slack-notifier/
│   ├── skill.yaml           # Skill manifest (required)
│   └── agno_impl.py        # Python implementation
├── aws-helper/
│   ├── skill.yaml
│   └── implementation.py
└── database-query/
    ├── skill.yaml
    ├── agno_impl.py
    └── requirements.txt

Creating a Custom Skill

Step 1: Create the Skill Directory

mkdir -p ~/.kubiya/skills/my-custom-skill
cd ~/.kubiya/skills/my-custom-skill

Step 2: Create the Skill Manifest (skill.yaml)

The skill.yaml file defines your Skill’s metadata, configuration schema, and dependencies. Minimal Example:
apiVersion: kubiya.ai/v1
kind: Skill
metadata:
  name: my-custom-skill
  version: 1.0.0
  description: A custom skill for my organization
  author: Your Name
  tags: ["custom", "integration"]

spec:
  type: custom

  # Runtime implementations
  implementations:
    agno:
      module: agno_impl       # Python module name (agno_impl.py)
      class: MyCustomSkill    # Class name to instantiate
    default:
      module: agno_impl
      class: MyCustomSkill

  # Configuration schema (JSON Schema)
  configuration:
    type: object
    properties:
      api_key:
        type: string
        description: API key for external service
      timeout:
        type: integer
        default: 30
        description: Request timeout in seconds
    required:
      - api_key
Full Example with All Options:
apiVersion: kubiya.ai/v1
kind: Skill
metadata:
  name: slack-integration
  version: 2.0.1
  description: Send notifications and query Slack
  author: DevOps Team
  license: MIT
  tags: ["slack", "notification", "custom"]

spec:
  type: custom

  implementations:
    agno:
      module: slack_impl
      class: SlackSkill
    claude_code:
      builtin: true
      tools: ["Bash", "Read", "Write"]
    default:
      module: slack_impl
      class: SlackSkill

  # Python package dependencies
  pythonDependencies:
    - slack-sdk>=3.19.0
    - requests>=2.28.0

  # Required environment variables
  environmentVariables:
    - name: SLACK_BOT_TOKEN
      required: true
      description: Slack bot token for authentication
    - name: SLACK_WORKSPACE_ID
      required: false
      description: Optional workspace ID override

  # Configuration schema
  configuration:
    type: object
    properties:
      default_channel:
        type: string
        description: Default channel for notifications
        default: "#general"
      allowed_channels:
        type: array
        items:
          type: string
        description: List of channels this skill can post to
      timeout:
        type: integer
        minimum: 5
        maximum: 300
        default: 30
      enable_threads:
        type: boolean
        default: true
      max_message_length:
        type: integer
        default: 4000
    required:
      - default_channel

Step 3: Create the Python Implementation

Create a Python file (e.g., agno_impl.py) that implements your Skill’s functionality. Example Implementation:
# agno_impl.py
from agno.tools import Toolkit

class MyCustomSkill(Toolkit):
    """Custom skill for organization-specific operations"""

    def __init__(self, api_key: str, timeout: int = 30, **kwargs):
        """Initialize the skill with configuration

        Args:
            api_key: API key for external service (from configuration)
            timeout: Request timeout in seconds (from configuration)
            **kwargs: Additional configuration options
        """
        super().__init__(name="my-custom-skill")
        self.api_key = api_key
        self.timeout = timeout

    def send_notification(self, message: str, recipient: str) -> str:
        """Send a notification via the external service

        Args:
            message: The notification message to send
            recipient: The recipient identifier

        Returns:
            Status message indicating success or failure
        """
        # Implementation here
        # Use self.api_key to authenticate with external service
        return f"Notification sent to {recipient}: {message}"

    def query_data(self, query: str) -> dict:
        """Query data from the external service

        Args:
            query: The query string

        Returns:
            Dictionary containing query results
        """
        # Implementation here
        return {"results": [], "count": 0}

Step 4: (Optional) Add Dependencies

If your Skill requires additional Python packages, create a requirements.txt:
requests>=2.28.0
slack-sdk>=3.19.0
pydantic>=2.0.0

Environment Variables

Custom Skills can require environment variables for API keys, credentials, or configuration:
  1. Define in skill.yaml:
environmentVariables:
  - name: API_KEY
    required: true
    description: API key for service
  - name: API_ENDPOINT
    required: false
    default: "https://api.example.com"
  1. Set on the Worker:
export API_KEY="your-secret-key"
export API_ENDPOINT="https://custom.api.com"
  1. Access in Python:
import os

class MySkill(Toolkit):
    def __init__(self, **kwargs):
        self.api_key = os.getenv("API_KEY")
        self.endpoint = os.getenv("API_ENDPOINT", "https://api.example.com")

Skill Discovery and Loading

Important: Custom Skills AvailabilityCustom skills must be available in both locations:
  1. Worker Process: The worker that runs the agent/team must have access to the skill files in .kubiya/skills/
  2. Control Plane API Server: The control plane must also have the skill files to register and manage the skill
Ensure you deploy your custom skills to both the worker nodes and the control plane server, or use a shared filesystem/volume mounted at both locations.
Custom Skills are automatically discovered when:
  1. Control Plane starts: Skills in .kubiya/skills/ are scanned at startup
  2. Worker initializes: Skills are loaded when a worker starts
  3. Dynamic loading: Controlled by KUBIYA_ENABLE_DYNAMIC_SKILLS environment variable (default: true)
Configuration Environment Variables:
# Primary skills directory (default: ~/.kubiya/skills)
export KUBIYA_SKILLS_TEMPLATES_PATH="/path/to/custom/skills"

# Additional paths (colon-separated on Unix, semicolon on Windows)
export KUBIYA_SKILLS_EXTRA_PATHS="/path1:/path2"

# Enable/disable dynamic skill loading (default: true)
export KUBIYA_ENABLE_DYNAMIC_SKILLS=true

Using Custom Skills

Once loaded, custom Skills appear in the Skills dashboard and CLI alongside built-in Skills: UI:
  1. Navigate to Skills > New Skill
  2. Select your custom Skill type from the dropdown
  3. Configure the Skill instance
  4. Assign to agents, teams, or environments
CLI:
# List all Skills (including custom)
kubiya skill definitions

# Create an instance of your custom Skill
kubiya skill create --name "Prod Slack" --type slack-integration --config-json '{"default_channel":"#alerts","timeout":60}'

# Assign to an agent
kubiya skill associate agent <agent-id> <skill-id>

Custom Skill Best Practices

  1. Schema Validation: Define comprehensive JSON Schema in your skill.yaml to validate configuration
  2. Error Handling: Implement robust error handling in your Python code
  3. Documentation: Include detailed descriptions in your skill.yaml metadata
  4. Versioning: Use semantic versioning for your custom Skills
  5. Dependencies: Keep pythonDependencies minimal and specify version ranges
  6. Security: Never hardcode credentials; use environment variables
  7. Testing: Test your Skills locally before deploying to production workers

Troubleshooting Custom Skills

Skill not appearing in dashboard:
  • Verify skill.yaml is valid YAML format
  • Check that apiVersion and kind are correct
  • Ensure file is in .kubiya/skills/<skill-name>/skill.yaml
  • Check worker logs for validation errors
Skill fails to load:
  • Verify Python module name matches implementations.agno.module
  • Check that class name matches implementations.agno.class
  • Ensure all pythonDependencies are installed on the worker
  • Verify environment variables are set if marked as required
Skill works locally but not on worker:
  • Check that worker has same Python version
  • Verify all dependencies are installed on worker
  • Ensure environment variables are set on worker
  • Check worker logs for import or runtime errors

Example: Complete Custom Skill

Here’s a complete example of a custom Skill that integrates with an external API: ~/.kubiya/skills/github-helper/skill.yaml:
apiVersion: kubiya.ai/v1
kind: Skill
metadata:
  name: github-helper
  version: 1.0.0
  description: GitHub API integration for repository operations
  author: DevOps Team

spec:
  type: custom

  implementations:
    agno:
      module: github_impl
      class: GitHubSkill
    default:
      module: github_impl
      class: GitHubSkill

  pythonDependencies:
    - PyGithub>=2.1.1

  environmentVariables:
    - name: GITHUB_TOKEN
      required: true
      description: GitHub personal access token

  configuration:
    type: object
    properties:
      default_org:
        type: string
        description: Default GitHub organization
      allowed_repos:
        type: array
        items:
          type: string
        description: List of repository names this skill can access
      timeout:
        type: integer
        default: 30
    required:
      - default_org
~/.kubiya/skills/github-helper/github_impl.py:
from agno.tools import Toolkit
from github import Github
import os

class GitHubSkill(Toolkit):
    """GitHub integration skill"""

    def __init__(self, default_org: str, allowed_repos: list = None, timeout: int = 30, **kwargs):
        super().__init__(name="github-helper")
        self.gh = Github(os.getenv("GITHUB_TOKEN"), timeout=timeout)
        self.default_org = default_org
        self.allowed_repos = allowed_repos or []

    def list_repositories(self, org: str = None) -> list:
        """List repositories in an organization

        Args:
            org: Organization name (uses default if not specified)

        Returns:
            List of repository names
        """
        org_name = org or self.default_org
        org_obj = self.gh.get_organization(org_name)
        repos = org_obj.get_repos()

        if self.allowed_repos:
            # Filter to allowed repos
            return [r.name for r in repos if r.name in self.allowed_repos]
        return [r.name for r in repos]

    def create_issue(self, repo: str, title: str, body: str) -> dict:
        """Create an issue in a repository

        Args:
            repo: Repository name
            title: Issue title
            body: Issue description

        Returns:
            Dictionary with issue details
        """
        if self.allowed_repos and repo not in self.allowed_repos:
            raise PermissionError(f"Access to repository '{repo}' not allowed")

        full_repo = f"{self.default_org}/{repo}"
        repository = self.gh.get_repo(full_repo)
        issue = repository.create_issue(title=title, body=body)

        return {
            "number": issue.number,
            "url": issue.html_url,
            "state": issue.state
        }
Usage:
# Create the skill instance
kubiya skill create --name "GitHub Prod" --type github-helper --config-json '{"default_org":"my-company","allowed_repos":["backend","frontend"]}'

# Assign to agent
kubiya skill associate agent <agent-id> <skill-id>
Now your agents can use this custom Skill to interact with GitHub repositories!