ep = entry_points()
es = first(ep.select(group='pyskills', name='pyskills.skill'))
esEntryPoint(name='pyskills.skill', value='pyskills.skill', group='pyskills')
allow
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.
Packages register pyskills under the group pyskills:
The value is a module path (no :attribute needed). The module’s docstring first paragraph serves as the pyskill description.
A pyskill module MUST have:
A pyskill module MAY have:
__all__ — the available symbols imported.Returns {name: description} for all registered pyskills, using find_spec + AST parsing — no imports.
Standard python native import
Standard python native docs.
The host (e.g. solveit, claude code, codex, …) would:
list_pyskills() at startup to build a pyskill catalogueimport {module} followed by doc({module}) for chosen pyskillsEntryPoint(name='pyskills.skill', value='pyskills.skill', group='pyskills')
First paragraph of docstring for entry point ep, without importing it
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.
Returns {module: description} for all pyskills. To load a module, use import {module} then view `doc({module}). NB: pyskills are THE critical way to extend functionality. ALWAYS check for pyskills to help with tasks. If unsure whether a particular pyskill might help, load it and grabs its docs to see!
{'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 skill.'}
allowAdd all items in c to __pytools__, optionally constrained by allow_policy
__pytools__ is a defaultdict(set) mapping classes/modules to their allowed method/function names (or ... for all public methods). Values can be plain strings or (name, AllowPolicy) tuples for allow-checked methods. allow registers entries — callables are added under their module, dicts go directly into __pytools__.
def _test_fn(): pass
_test_fn.__module__ = '__main__'
_test_fn.__name__ = 'my_test_func'
allow(_test_fn)
assert 'my_test_func' in __pytools__[sys.modules['__main__']]
allow({str: ['zfill']})
assert 'zfill' in __pytools__[str]
allow({list: ...})
assert ... in __pytools__[list]
__pytools__[sys.modules['__main__']].discard('my_test_func')
allow(collections.Counter.most_common)
assert 'most_common' in __pytools__[collections.Counter]
__pytools__[collections.Counter].discard('most_common')Call self as a function.
chk_dest resolves a path and verifies it falls under one of the allowed destination prefixes. Raises PermissionError if not. Used by all AllowPolicy subclasses.
Correctly blocked /etc/passwd
Check open() only when mode is writable
Check resolved Path self, optionally also target args
Check positional/keyword arg is an allowed destination
Base for allow destination policies
Three AllowPolicy subclasses handle different allow-checking patterns.
pp = PosAllowPolicy(1, 'dst')
pp(None, ['src', '/tmp/ok'], {}, ['/tmp'])
try: pp(None, ['src', '/root/bad'], {}, ['/tmp'])
except PermissionError: print("PosAllowPolicy blocked /root/bad")
pwp = PathWritePolicy()
pwp(Path('/tmp/f.txt'), [], {}, ['/tmp'])
try: pwp(Path('/etc/f.txt'), [], {}, ['/tmp'])
except PermissionError: print("PathWritePolicy blocked /etc/f.txt")
owp = OpenWritePolicy()
owp(None, ['/tmp/f.txt', 'w'], {}, ['/tmp'])
owp(None, ['/etc/passwd', 'r'], {}, ['/tmp'])
try: owp(None, ['/root/f.txt', 'w'], {}, ['/tmp'])
except PermissionError: print("OpenWritePolicy blocked write to /root/f.txt")PosAllowPolicy blocked /root/bad
PathWritePolicy blocked /etc/f.txt
OpenWritePolicy blocked write to /root/f.txt
Allow policies are stored as (name, AllowPolicy) tuples directly inside __pytools__ sets.
Docs for sym. modules: classes/functions with 1st docstring line; classes: method sigs; functions: sig+docments
For a function, doc renders the docstring and full signature with docments (parameter comments):
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/properties with their first docstring line:
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
For a module, doc shows the docstring, all public classes and functions with their signatures and first docstring line, plus any allow() calls:
## 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)
Pyskills can be added as standard modules with pyproject entrypoints. But for convenience, they can also be added to a custom pyskills XDG directory, which is automatically added to sys.path.
Create xdg pyskills dir and .pth file if needed
Directory for user pyskills
pyskills_dir returns the XDG data home path for user pyskills. ensure_pyskills_dir creates that directory if needed and writes a .pth file into site-packages so Python automatically adds it to sys.path. You can drop pyskill modules there without manual path configuration and which can be available across venvs.
Clear modules starting with prefix from python caches
clear_mod purges all cached modules matching a prefix from sys.modules and invalidates import caches, ensuring a fresh import on next access. Used after enabling/disabling pyskills so changes take effect immediately.
Register a pyskill module name in the xdg pyskills dir
Enable pyskill name by creating its dist-info entry point
enable_pyskill creates a minimal dist-info directory with an entry point so the pyskill is listed. register_pyskill also writes an actual module file (with docstring and code) into the pyskills directory, and creates any needed __init__.py files for nested packages.
This lets you programmatically create and register a pyskill without a full package install.
Disable pyskill name by removing its dist-info entry point
disable_pyskill removes the dist-info directory for a pyskill, so it no longer appears in entry point discovery. It also clears the module cache so the pyskill is fully unloaded.
Delete pyskill name module files and dist-info