Skip to main content

Python-native skills system

Project description

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyskills-0.0.6.tar.gz (22.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyskills-0.0.6-py3-none-any.whl (21.9 kB view details)

Uploaded Python 3

File details

Details for the file pyskills-0.0.6.tar.gz.

File metadata

  • Download URL: pyskills-0.0.6.tar.gz
  • Upload date:
  • Size: 22.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.0

File hashes

Hashes for pyskills-0.0.6.tar.gz
Algorithm Hash digest
SHA256 edd6790dea554a4c768e4ee8827c23003163877a76b2a1a5a7235b84e81af443
MD5 a2c9e907d3d0fa05327cf519313bb6c5
BLAKE2b-256 c5d6c4b5492fc6d68995836e3fe38965c6a1fd1c6e73ae2e98ad5ce7078b263d

See more details on using hashes here.

File details

Details for the file pyskills-0.0.6-py3-none-any.whl.

File metadata

  • Download URL: pyskills-0.0.6-py3-none-any.whl
  • Upload date:
  • Size: 21.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.0

File hashes

Hashes for pyskills-0.0.6-py3-none-any.whl
Algorithm Hash digest
SHA256 5fde4ba0be0e0d39350c107b81c168437456c2d96bcfb4d7caf68db022812ae7
MD5 70fbcebf280c66465dea01fb496853e8
BLAKE2b-256 132d1c26173b92b5dcf951cc4d8b7b8eac64bd27c1a1cd3ead9127a4f64d812c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page