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, xdir() for listing a module or class’s public symbols, 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.
Install the latest from pypi
$ pip install pyskillsfrom pyskills import *Discover what pyskills are available. This works without importing any pyskill modules:
list_pyskills(){'pyskills.edit': 'Functions for creating, viewing, and modifying files. Each editing operation returns unified diffs showing what changed. Where the `exhash` pyskill is available, prefer it for editing: its hash-verified addressing fails loudly on stale context instead of editing nearby text.',
'pyskills.ipynb': 'Functions for view/modifying ipynb file notebook cells. Each operation returns unified diffs showing what changed. Where `exhash` is available, prefer its hash-verified editing for cell source changes.',
'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.',
'tracefunc.skill': "Trace a Python function's execution at AST-line level: per-line hit counts and live variable values, via `sys.monitoring`. Use when debugging *why* code takes a branch, loops, recurses, or computes a wrong value, without editing the code under investigation or using an interactive debugger.",
'dialoghelper.solveitskill': 'Read, search, edit, and manage Solveit dialogs using dialoghelper.core, including dialog/message addressing, line-numbered inspection, targeted message edits, add/update/delete/copy/paste workflows, and safe editing patterns.',
'dialoghelper.termskill': 'Read and edit Solveit dialog (or Jupyter) .ipynb files from a CLI / script. Solveit is an online notebook application (like Jupyter with AI integration) where each notebook is called a "dialog" and is stored as an `.ipynb` file containing `code`, `note` (markdown), and `prompt` (markdown with a special delimiter) messages (aka "cells"). The `dialoghelper` package provides tools for reading, searching, adding, updating, and deleting those messages.',
'ghapi.skill': 'GitHub REST API access via `GhApi`, plus local git operations via `fastgit.Git`. Use this for day-to-day GitHub work: reading/creating issues and PRs, checking CI status, managing releases/branches/gists, and repo-local git operations -- all from Python, no shelling out to `gh`/`git` needed.',
'rgapi.skill': 'Fast and flexible file discovery and search for Python. Use this when code needs `fd`-style file finding or `rg`-style searching.',
'exhash.skill': 'Universal hash-verified text editing for local files. Use this when an LLM needs one safe editing interface for reading, previewing, and modifying text files.',
'cordslite.skill': 'Load this skill when an agent needs to search, summarize, or find information in Discord using cordslite. It covers read-only workflows for connecting to Discord, opening a guild, orienting through channels, searching messages, reading threads, and fetching attachments.',
'bgtmux.skill': 'Use tmux-backed background terminal sessions from Solveit. Useful to have a persistent terminal session that both you and the user can inspect and edit, and that you can send input to from Solveit.',
'clikernel.skill': 'Use the persistent `clikernel` MCP session as the default workspace for any task advanced through live Python execution -- stateful inspection, file-editing workflows, debugging, experiments, API probes, data transforms, or notebook-style work. Read this before writing, running, or debugging Python code in a session with `clikernel` connected.'}
Once you’ve found a pyskill you want to use, import its module using standard python syntax:
import pyskills.skillUse 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, submodules, and any allow() calls:
print(doc(pyskills.skill)[-500:])ills.skill.SkillTestClass)
doc(pyskills.skill.skill_test_func)
## Creating pyskills
`from pyskills import createskill; doc(createskill)` for how to build and register your own pyskill modules.
"""
## types:
- class SkillTestClass(str): … # Some class.
## functions:
- async def async_skill_test_func(x: int = 0) -> str: … # A test function…
- def skill_test_func(x: int = 0) -> str: … # A test function…
## submodules:
pyskills.createskill: … # How to create a pyskills pyskill module.
For a function, doc renders the full signature with parameter comments (docments):
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:
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 callables are trusted to perform otherwise-denied operations (filesystem writes, subprocesses, network access). safepyrun uses fastaudit to deny those effects unless they happen inside a callable registered in the __pytools__ registry. The allow() function is how pyskills register their trusted callables.
You can allow individual functions, methods, all public methods of a type, or one specific callable instance:
# Allow specific methods on a type
allow({str: ['zfill']})
# Allow all public methods on a type
allow({list: ...})
# Allow a function
allow(list_pyskills)
# Allow one specific callable instance (e.g. a dynamically-generated client op)
allow(client.images.create_image)An object can also define __allow__, returning a list of items to register in its place; allow recurses into the result, so a container (such as a fastspec client or op group) can register all its callables at once.
Skill modules typically call allow() at module level, so permissions are registered automatically when the pyskill is imported. When safepyrun’s RunPython executes LLM-generated code, pure computation just runs; an operation with side effects is only permitted when it happens inside a registered callable.
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 createskillprint(doc(createskill)[:300])# module pyskills.createskill:
"""How to create a pyskills pyskill 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
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.
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.