Metadata-Version: 2.4
Name: agent_skills
Version: 0.1.2
Project-URL: Home, https://github.com/datalayer/agent-skills
Author-email: Datalayer <info@datalayer.io>
License: BSD 3-Clause License
        
        Copyright (c) 2025, Datalayer
        All rights reserved.
        
        Redistribution and use in source and binary forms, with or without
        modification, are permitted provided that the following conditions are met:
        
        1. Redistributions of source code must retain the above copyright notice, this
           list of conditions and the following disclaimer.
        
        2. Redistributions in binary form must reproduce the above copyright notice,
           this list of conditions and the following disclaimer in the documentation
           and/or other materials provided with the distribution.
        
        3. Neither the name of the copyright holder nor the names of its
           contributors may be used to endorse or promote products derived from
           this software without specific prior written permission.
        
        THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
        AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
        IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
        DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
        FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
        DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
        SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
        CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
        OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
        OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License-File: LICENSE
Keywords: AI,Agents,Jupyter,MCP,PydanticAI,Skills
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: BSD License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Requires-Dist: code-sandboxes
Requires-Dist: mcp[cli]>=1.0
Requires-Dist: pydantic-ai-slim
Requires-Dist: pydantic-graph
Requires-Dist: pydantic>=2.0
Requires-Dist: pyyaml>=6.0
Provides-Extra: lint
Requires-Dist: mdformat-gfm>=0.3.5; extra == 'lint'
Requires-Dist: mdformat>0.7; extra == 'lint'
Requires-Dist: ruff; extra == 'lint'
Provides-Extra: skills
Requires-Dist: beautifulsoup4>=4.12; extra == 'skills'
Requires-Dist: httpx>=0.27; extra == 'skills'
Requires-Dist: pdf2image>=1.17; extra == 'skills'
Requires-Dist: pillow>=10.0; extra == 'skills'
Requires-Dist: pypdf>=4.0; extra == 'skills'
Provides-Extra: skills-crawl
Requires-Dist: beautifulsoup4>=4.12; extra == 'skills-crawl'
Requires-Dist: httpx>=0.27; extra == 'skills-crawl'
Provides-Extra: skills-github
Requires-Dist: httpx>=0.27; extra == 'skills-github'
Provides-Extra: skills-pdf
Requires-Dist: pdf2image>=1.17; extra == 'skills-pdf'
Requires-Dist: pillow>=10.0; extra == 'skills-pdf'
Requires-Dist: pypdf>=4.0; extra == 'skills-pdf'
Provides-Extra: test
Requires-Dist: ipykernel; extra == 'test'
Requires-Dist: jupyter-server<3,>=1.6; extra == 'test'
Requires-Dist: pytest-asyncio>=0.21; extra == 'test'
Requires-Dist: pytest>=7.0; extra == 'test'
Provides-Extra: typing
Requires-Dist: mypy>=0.990; extra == 'typing'
Description-Content-Type: text/markdown

<!--
  ~ Copyright (c) 2025-2026 Datalayer, Inc.
  ~
  ~ BSD 3-Clause License
-->

[![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io)

[![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer)

# 🧰 Agent Skills

[![PyPI - Version](https://img.shields.io/pypi/v/agent-skills)](https://pypi.org/project/agent-skills)

**Reusable Agent Skills**: Create, manage, and execute reusable code-based tool compositions for AI agents.

For more information, see the [Agent Skills community website](https://agentskills.io), the [specification](https://agentskills.io/specification), and the [integration guide](https://agentskills.io/integrate-skills).

## Overview

Agent Skills provides a simple and powerful way for AI agents to build their own toolbox. Skills are Python files that compose MCP tools and other skills to accomplish specific tasks.

Agent Codemode consumes skills from this package. If you are using agent-codemode, import skill utilities from `agent_skills`.

### How It Works

1. **Skills are code files**: Python files in a `skills/` directory with async functions
2. **Agents discover skills**: By listing the skills directory and reading file contents
3. **Agents create skills**: By writing Python files to the skills directory
4. **Agents execute skills**: By importing and calling them in executed code
5. **Agents compose skills**: By importing multiple skills together

This pattern allows agents to evolve their own toolbox over time, saving useful compositions as reusable skills.

## Installation

```bash
pip install agent-skills
```

## Examples

See the runnable examples in [examples/README.md](examples/README.md).

```bash
python examples/skills_example.py
```

For Pydantic AI integration (requires the skills feature branch):

```bash
pip install "pydantic-ai @ git+https://github.com/DougTrajano/pydantic-ai.git@DEV-1099"
```

## Quick Start: Pydantic AI SkillsToolset (Recommended)

The recommended pattern for using agent-skills with Pydantic AI agents is via `AgentSkillsToolset`.
Skills can be loaded in two ways that work independently or together.

### Path-based loading

Point the toolset at a local directory tree.  Every sub-directory that contains
a `SKILL.md` file is discovered automatically at first use.

Use this when skills are checked into the same repository or mounted at a
well-known path at runtime:

```python
from pydantic_ai import Agent
from agent_skills import AgentSkillsToolset, SandboxExecutor
from code_sandboxes.eval_sandbox import EvalSandbox

toolset = AgentSkillsToolset(
    directories=["./skills"],           # scanned recursively for SKILL.md
    executor=SandboxExecutor(EvalSandbox()),
)

agent = Agent(model='openai:gpt-4o', toolsets=[toolset])
# Agent gets: list_skills, load_skill, read_skill_resource, run_skill_script
```

### Module-based loading

Load skills that are packaged inside an installed Python library using
`AgentSkill.from_module()`.  This handles both regular packages and namespace
packages (directories without `__init__.py`).

Use this when skills are distributed as part of a pip-installable package
(such as `agent-skills` itself):

```python
from pydantic_ai import Agent
from agent_skills import AgentSkill, AgentSkillsToolset, SandboxExecutor
from code_sandboxes.eval_sandbox import EvalSandbox

toolset = AgentSkillsToolset(
    skills=[
        AgentSkill.from_module("agent_skills.skills.crawl"),
        AgentSkill.from_module("agent_skills.skills.github"),
        AgentSkill.from_module("agent_skills.skills.pdf"),
        AgentSkill.from_module("agent_skills.skills.events"),
    ],
    executor=SandboxExecutor(EvalSandbox()),
)

agent = Agent(model='openai:gpt-4o', toolsets=[toolset])
```

### Combining both

The two approaches stack freely — path-based directories and module-based
skills are merged into a single skill registry:

```python
toolset = AgentSkillsToolset(
    directories=["./skills"],           # local / custom skills
    skills=[
        AgentSkill.from_module("agent_skills.skills.crawl"),
        AgentSkill.from_module("agent_skills.skills.github"),
    ],
    executor=SandboxExecutor(EvalSandbox()),
)
```

### Programmatic Skills

Define skills in Python code with decorators:

```python
from agent_skills import AgentSkill, AgentSkillsToolset

# Create a skill
skill = AgentSkill(
    name="data-analyzer",
    description="Analyzes datasets and provides insights",
    content="Use this skill to analyze CSV and JSON data files.",
)

# Add a script via decorator
@skill.script
async def analyze(ctx, file_path: str) -> str:
    """Analyze a data file."""
    # Access dependencies via ctx.deps
    data = await ctx.deps.filesystem.read(file_path)
    return f"Analyzed {len(data)} bytes"

# Add a resource
@skill.resource
def get_reference() -> str:
    return "Reference documentation..."

# Use with agent
toolset = AgentSkillsToolset(skills=[skill])
```

### SKILL.md Format

Skills on disk use YAML frontmatter in a `SKILL.md` file:

```markdown
---
name: pdf-extractor
description: Extract text and tables from PDF documents
version: "1.0.0"
allowed-tools: filesystem__read_file filesystem__write_file
denied-tools: network__fetch
tags:
  - pdf
  - extraction
---

# PDF Extractor Skill

Instructions for extracting content from PDF files...

## Usage

1. Use the `extract` script with a PDF path
2. Review the extracted content
```

With optional directories:
- `resources/`: Reference documents, templates, examples
- `references/`: Additional documentation loaded on demand (spec)
- `assets/`: Static resources like templates or data files (spec)
- `scripts/`: Executable Python scripts

Tool access policies in the frontmatter are surfaced in the skill summary and can be used by callers to enforce allow/deny lists.
Optional fields like `license`, `compatibility`, and `metadata` are supported per the Agent Skills specification.

## Quick Start: Skills as Code Files

The primary pattern for agent skills is simple: skills are just Python files.

### Setting Up the Skills Directory

```python
from agent_skills import setup_skills_directory

# Initialize the skills directory
skills = setup_skills_directory("./workspace/skills")
```

### Creating a Skill

Create a Python file in the skills directory:

```python
# skills/analyze_csv.py
#!/usr/bin/env python3
"""Analyze a CSV file and return statistics."""

async def analyze_csv(file_path: str) -> dict:
    """Analyze a CSV file.
    
    Args:
        file_path: Path to the CSV file.
    
    Returns:
        Statistics about the file.
    """
    from generated.mcp.filesystem import read_file
    
    content = await read_file({"path": file_path})
    lines = content.split("\n")
    headers = lines[0].split(",") if lines else []
    
    return {
        "rows": len(lines) - 1,
        "columns": len(headers),
        "headers": headers,
    }


# Optional: CLI support for direct execution
if __name__ == "__main__":
    import asyncio
    import sys
    
    result = asyncio.run(analyze_csv(sys.argv[1]))
    import json
    print(json.dumps(result, indent=2))
```

Or use the API:

```python
skills.create(
    name="analyze_csv",
    code='''
async def analyze_csv(file_path: str) -> dict:
    from generated.mcp.filesystem import read_file
    
    content = await read_file({"path": file_path})
    lines = content.split("\\n")
    headers = lines[0].split(",") if lines else []
    
    return {
        "rows": len(lines) - 1,
        "columns": len(headers),
        "headers": headers,
    }
''',
    description="Analyze a CSV file and return statistics",
)
```

### Using a Skill

In executed code, import and call the skill:

```python
from skills.analyze_csv import analyze_csv

result = await analyze_csv("/data/sales.csv")
print(f"Found {result['rows']} rows with columns: {result['headers']}")
```

### Discovering Skills

```python
from agent_skills import SkillDirectory

skills = SkillDirectory("./workspace/skills")

# List all skills
for skill in skills.list():
    print(f"{skill.name}: {skill.description}")
    print(f"  Functions: {', '.join(skill.functions)}")

# Search for relevant skills
matches = skills.search("data analysis")
for skill in matches:
    print(f"Found: {skill.name}")
```

### Composing Skills

Skills can import and use other skills:

```python
# skills/batch_analyze.py
"""Process and analyze multiple files."""

async def batch_analyze(directory: str) -> list:
    """Analyze all CSV files in a directory.
    
    Args:
        directory: Directory containing CSV files.
    
    Returns:
        List of analysis results.
    """
    from skills.analyze_csv import analyze_csv
    from generated.mcp.filesystem import list_directory
    
    entries = await list_directory({"path": directory})
    results = []
    
    for entry in entries.get("entries", []):
        if entry.endswith(".csv"):
            result = await analyze_csv(f"{directory}/{entry}")
            results.append({"file": entry, **result})
    
    return results
```

## API Reference

### SkillDirectory

The main interface for working with skills as code files.

```python
from agent_skills import SkillDirectory

skills = SkillDirectory("./workspace/skills")
```

#### Methods

- **`list() -> list[SkillFile]`**: List all skills in the directory
- **`get(name: str) -> SkillFile`**: Get a skill by name
- **`search(query: str, limit: int = 10) -> list[SkillFile]`**: Search for skills
- **`create(name, code, description, make_executable) -> SkillFile`**: Create a new skill
- **`delete(name: str) -> bool`**: Delete a skill
- **`add_to_sys_path()`**: Add skills directory to Python path for imports

### SkillFile

Represents a skill file with metadata and callable functions.

```python
skill = skills.get("analyze_csv")
print(skill.name)         # "analyze_csv"
print(skill.description)  # From module docstring
print(skill.functions)    # ["analyze_csv"]

# Load and call the function
func = skill.get_function()
result = await func("/data/file.csv")
```

### setup_skills_directory

Convenience function that creates a SkillDirectory and adds it to sys.path:

```python
from agent_skills import setup_skills_directory

# Call during sandbox initialization
skills = setup_skills_directory("./workspace/skills")

# Now executed code can do:
# from skills.my_skill import my_function
```

## Skill File Format

A skill file is a Python file with:

1. **Module docstring**: Description of what the skill does
2. **Async functions**: The skill's callable functions
3. **Optional CLI support**: For running the skill directly

```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Short description of the skill.

Longer description with more details about what the skill does,
what inputs it expects, and what outputs it produces.
"""

async def main_function(param1: str, param2: int = 10) -> dict:
    """Main function description.
    
    Args:
        param1: First parameter description.
        param2: Second parameter with default.
    
    Returns:
        Dictionary with results.
    """
    # Import tools and other skills
    from generated.mcp.filesystem import read_file
    from skills.helper_skill import helper_function
    
    # Do the work
    content = await read_file({"path": param1})
    processed = await helper_function(content)
    
    return {"result": processed, "count": param2}


# CLI support (optional but recommended)
if __name__ == "__main__":
    import asyncio
    import sys
    import json
    
    if len(sys.argv) < 2:
        print(f"Usage: python {sys.argv[0]} <param1> [param2]")
        sys.exit(1)
    
    param1 = sys.argv[1]
    param2 = int(sys.argv[2]) if len(sys.argv) > 2 else 10
    
    result = asyncio.run(main_function(param1, param2))
    print(json.dumps(result, indent=2))
```

## Best Practices

1. **One main function per skill**: Name it the same as the file
2. **Include docstrings**: Document what the skill does and its parameters
3. **Add CLI support**: Makes skills runnable standalone for testing
4. **Keep skills focused**: Do one thing well
5. **Compose skills**: Build complex workflows from simple skills
6. **Use type hints**: For better documentation and IDE support
7. **Handle errors gracefully**: Return meaningful error information

## Advanced: Managed Skills (Optional)

For advanced use cases like versioning, database storage, or skill registries,
you can use the SkillManager and MCP server:

```python
from agent_skills import SkillManager, skills_server, configure_server

# Create a skill manager for database-backed storage
manager = SkillManager("./skills")

# Discover SKILL.md format skills
skills = manager.discover()

# Search with ranking
result = manager.search("data processing", limit=5)

# Configure and run MCP server for agent integration
configure_server(skills_path="./skills")
skills_server.run()
```

See the full documentation for details on the managed skills API.

## Integration with MCP Codemode

Agent Skills works seamlessly with the `agent-codemode` package:

```python
from agent_codemode import CodemodeClient
from agent_skills import setup_skills_directory

# Set up the skills directory
skills = setup_skills_directory("./workspace/skills")

# Create codemode client
client = CodemodeClient()

# Skills are available in executed code
result = await client.execute_code('''
from skills.analyze_csv import analyze_csv

# Call the skill
data = await analyze_csv("/data/sales.csv")
print(f"Analyzed {data['rows']} rows")
''')
```

## CI Workflows

This repository uses a reusable GitHub Actions workflow at `.github/workflows/reusable-python.yml`.

The following workflows call it:

- `.github/workflows/build.yml`
- `.github/workflows/py-tests.yml`
- `.github/workflows/py-code-style.yml`
- `.github/workflows/py-typing.yml`

Reusable workflow inputs:

- `python-version`: Python version to run.
- `install-system-deps`: Install Linux dependencies and unlock keyring.
- `install-extras`: Extras from `pyproject.toml` (for example `test,typing`).
- `extra-packages`: Additional packages installed with `uv pip install`.
- `run-tests`: Enable test execution.
- `test-command`: Command used for tests.
- `run-mypy`: Enable mypy.
- `mypy-target`: Package or module passed to mypy.
- `run-pre-commit`: Enable pre-commit checks.

## License

BSD 3-Clause License - see [LICENSE](LICENSE) for details.
