Skip to content

AnswerDotAI/pyskills

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pyskills

pyskills is a plugin system that lets Python packages register “skills” (units of LLM-usable functionality) via standard entry points. An LLM host (e.g. solveit) discovers available pyskills without importing them, reads lightweight descriptions via AST inspection, and selectively loads chosen pyskills into context using standard imports.

It includes list_pyskills() for discovery, doc() for rendering module/class/function documentation in LLM-friendly format, and an allow() system for registering safe callable access in sandboxed environments. Skills can be installed as regular packages with entry points, or dropped into an XDG data directory for quick local use.

pyskills shares the progressive disclosure philosophy of the Agent Skills specification: both separate lightweight discovery metadata from full instructions loaded on demand. However, where Agent Skills uses a file-system convention (SKILL.md with YAML frontmatter, scripts/, references/ directories), pyskills takes a Python-native approach: pyskills are regular Python modules discovered via standard entry points, documented with docstrings, and loaded with import. This means pyskills are directly executable, come with auto-generated structured documentation via doc(), and include a sandboxing layer via allow() for safe execution. This makes pyskills a superset that covers discovery, documentation, execution, and security in one system.

Usage

Installation

Install the latest from pypi

$ pip install pyskills

How to use

from pyskills import *

Discover what pyskills are available. This works without importing any pyskill modules:

list_pyskills()
{'pyskills.skill': 'Pyskills is a plugin system allowing Python packages to register "skills" — units of LLM-usable functionality — via standard Python entry points. An LLM host (e.g. solveit) discovers available pyskills without importing them, reads lightweight descriptions via AST inspection, and selectively loads chosen pyskills into context using standard imports.',
 'test.skill': 'A test pyskill.'}

The doc function

Once you’ve found a pyskill you want to use, import its module using standard python syntax:

import pyskills.skill

Use doc() to read its full documentation. doc() works on modules, classes, and functions, rendering LLM-friendly output in each case.

For a module, doc shows all public classes and functions with their signatures and first docstring line:

print(doc(pyskills.skill)[-500:])
illTestClass)
    doc(pyskills.skill.skill_test_func)

## Creating pyskills

`from pyskills import createskill; doc(createskill)` for how to build and register your own pyskill modules, including the allow/policy system."""

def allow(*c, allow_policy=None): ...  # Add all items in `c` to `__pytools__`, optionally constrained by `allow_policy`
class SkillTestClass(str): ...  # Some class.
def skill_test_func(x: int = 0) -> str: ...  # A test function

allows:
- allow(skill_test_func, SkillTestClass)

For a function, doc renders the full signature with parameter comments (docments):

print(doc(pyskills.skill.skill_test_func))
def skill_test_func(
    x:int=0, # the input
)->str: # the output
"""A test function"""

For a class, doc shows the class hierarchy, docstring, __init__ signature, and all public methods with their first docstring line:

print(doc(pyskills.skill.SkillTestClass))
class SkillTestClass(str):
    """Some class.
    More info about it."""
    def __init__(self): ...
    def f(self, x: int = 0) -> str: ...  # A test method
    @property
    def g(self) -> str: ...  # A test prop

The allow system

When pyskills run in a sandboxed environment like safepyrun, they need to declare which functions and methods are safe for an LLM to call. safepyrun uses RestrictedPython to intercept every attribute access and function call, checking each one against an allowlist stored in the __pytools__ registry. The allow() function is how pyskills register their safe callables into that registry.

You can allow individual functions, all public methods of a type, or specific methods:

# Allow specific methods on a type
allow({str: ['zfill']})

# Allow all public methods on a type
allow({list: ...})

# Allow a callable function
allow(list_pyskills)

Skill modules typically call allow() at module level, so permissions are registered automatically when the pyskill is imported. For instance, when safepyrun’s RunPython executes LLM-generated code, it checks every call against __pytools__. If a function isn’t registered, the call is blocked.

The pyskills.skill module used in the examples above is itself registered as a pyskill entry point. It ships with pyskills both as a working sample and as a self-documenting pyskill that explains the system itself. Its docstring’s “Creating pyskills” section cross-references pyskills.createskill, a companion module (not registered as an entry point, so not shown in list_pyskills()) that documents how to build your own pyskills:

from pyskills import createskill
print(doc(createskill)[:300])
module pyskills.createskill:
"""How to create a pyskills module.

A pyskill is a standard Python module that registers itself via entry points so LLM hosts can discover and load it.

## 1. Create your module

Your module needs:
- A docstring — first paragraph is the short description shown durin

Creating pyskills

A pyskill is a standard Python module that registers itself via entry points so LLM hosts can discover and load it. Your module needs three things:

  • A docstring: the first paragraph is the short description shown during discovery via list_pyskills(); the rest is the detailed documentation the LLM reads after loading.
  • __all__: lists the symbols available to the LLM.
  • allow() calls: declares what the LLM is permitted to call in sandboxed environments.

Here’s a minimal example:

'''Short description for discovery.

Detailed docs read by the LLM after import.'''

from pyskills.core import allow

__all__ = ['my_func', 'MyClass']

def my_func(x: int) -> str:
    "Does something useful"
    ...

class MyClass:
    "A useful class"
    def method(self) -> str:
        "Does something"
        ...

allow(my_func, {MyClass: ...})

To register your module as a discoverable pyskill, add an entry point in your pyproject.toml:

[project.entry-points.pyskills]
my_skill = "mypackage.mymodule"

The key is an arbitrary name; the value is the module path. After installing the package, list_pyskills() will include your pyskill automatically.

For full details on creating pyskills, including allow policies for write-guarded operations, see doc(createskill) after importing it as shown above.

Local pyskills without packaging

The entry point approach above requires installing a package. But sometimes you want to create pyskills quickly without a full package: personal utility pyskills, or pyskills shared across multiple projects that each use isolated environments (like uv venvs).

pyskills provides an XDG-based pyskills directory for this. When you first import pyskills, it creates a directory at your platform’s XDG data home (typically ~/.local/share/pyskills/) and writes a .pth file into site-packages. This .pth file tells Python to add the pyskills directory to sys.path on startup, so any modules placed there are importable as standard Python modules without any special import machinery. This works across all Python environments on your system, even separate uv projects with isolated venvs.

You can check where this directory is (although you can use the functions below without needing to know):

pyskills_dir()
Path('/Users/jhoward/.local/share/pyskills')

You can drop pyskill modules directly into this directory, or use register_pyskill to create one programmatically:

register_pyskill('my_local.skill', docstr='A quick local pyskill.', code='''
from pyskills.core import allow

__all__ = ['hello']

def hello(name: str) -> str:
    "Greet someone"
    return f"Hello, {name}!"

allow(hello)
''')

This writes the module file into the XDG pyskills directory and creates a minimal entry point, so the pyskill immediately appears in list_pyskills().

You can also manage pyskills with enable_pyskill and disable_pyskill to toggle their visibility without deleting files, or use disable_pyskill to remove one entirely.