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.5.tar.gz (22.9 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.5-py3-none-any.whl (21.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pyskills-0.0.5.tar.gz
  • Upload date:
  • Size: 22.9 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.5.tar.gz
Algorithm Hash digest
SHA256 fc6a4a84ce5d0329cdae47ecc194b91cac36315ead12c7ea1703f59f2dbd1d1a
MD5 f5afbf7b56f0119b9eaaa0020f03aa01
BLAKE2b-256 2791c1796071234c0723bd47ddd373e99cd60e3b7d951673befebc38bfcea867

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pyskills-0.0.5-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.5-py3-none-any.whl
Algorithm Hash digest
SHA256 f24f4bc49a8afd48f794079548ba799ca1cffbc0700caf922e5561d8053cfb9b
MD5 ff0ebee285c8a76a22667c5426f20427
BLAKE2b-256 33a51b59cecaa3abe389570afab61081572c602b2c5c8d5aa56ab57279bbef7d

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