Publishing a plugin¶
How to register, install, and version-bump a plugin so the runtime picks it up.
Local development¶
While iterating, install the plugin into the workspace as editable:
uv pip install -e packages/<your_plugin>/
If your plugin lives inside this monorepo, add it to the workspace by listing it under
packages = [...] in the root pyproject.toml. uv sync --all-extras --dev (run by
make install) then sets up the editable install for you.
Entry-point registration¶
The runtime's kernel.plugin_registry.discovery.discover_plugins scans Python entry points in
three groups:
| Plugin type | Entry-point group |
|---|---|
| Connector | knowledge_os.connectors |
| Skill | knowledge_os.skills |
| Domain Pack | knowledge_os.domain_packs |
Register your class in pyproject.toml:
# Connector
[project.entry-points."knowledge_os.connectors"]
my = "connector_my:MyConnector"
# Skill
[project.entry-points."knowledge_os.skills"]
pr_review = "skill_pr_review:PRReviewSkill"
# Domain Pack
[project.entry-points."knowledge_os.domain_packs"]
pack_python_web = "pack_python_web:PythonWebPack"
The string left of the = is the entry-point name (acts as a fallback id). The string on the
right is <module>:<attribute>. The attribute is usually a class but can be any importable.
Verifying registration¶
After installing your plugin into the active environment, confirm discovery:
from kernel.plugin_registry.discovery import discover_plugins
for plugin in discover_plugins():
print(plugin.kind, plugin.id, plugin.entry_point.value)
cls = plugin.load() # actually imports + returns the class
print(" ->", cls.__name__)
Or filter by kind:
discover_plugins(kind="connector")
discover_plugins(kind="skill")
discover_plugins(kind="domain_pack")
If your plugin is missing:
- Did you
uv pip install -eafter adding it to the workspace? - Is the entry-point group spelled exactly right? (
knowledge_os.connectors, plural, underscores.) - Did the manifest validate at import time? A
ValidationErrorfrom pydantic duringload_manifestwill surface as the entry point failing to load. Try importing the class directly:python -c "import connector_my".
Versioning¶
Plugins follow SemVer 2.0.
- Bump the patch version for bug fixes and internal changes that do not affect the manifest, the Protocol surface, or stored entities.
- Bump the minor version when you add backward-compatible features (e.g. a new capability
in
provides.sources[].capabilities, a new optional config field). - Bump the major version when you break the manifest, change a stored
metadataschema incompatibly, or alterexecutesemantics in a way that existing consumers cannot tolerate.
Set the version in two places — keep them in sync:
# pyproject.toml
[project]
version = "0.2.0"
# manifest.yaml
plugin:
id: connector_my
type: connector
version: "0.2.0"
kernel_version: ">=0.1.0,<2.0"
Declaring kernel_version¶
kernel_version is a SemVer range expression. The runtime refuses to register a plugin whose
range does not include the currently-installed kernel. Recommended defaults:
| Situation | kernel_version |
|---|---|
| Stable plugin against the current major | ">=0.1.0,<2.0" |
| Pre-1.0 plugin pinned to a specific minor | ">=0.2.0,<0.3.0" |
| Plugin that uses only the most fundamental Protocols | ">=0.0.1" |
Bump the range when you start using a new kernel feature so old kernels are caught at install time, not at first run.
Plugin migrations¶
If a new major version of your plugin changes the on-disk shape of:
- The plugin's stored
config, or - The
metadataof entities your plugin owns,
you must ship a migration script alongside the new code:
class Migration_2_0_0:
"""Migrate plugin config from v1.x to v2.0."""
def migrate_config(self, old: dict) -> dict:
...
def migrate_entities(self, ctx) -> None:
"""Optional: batch-update existing entities owned by this plugin."""
The plugin registry runs migrations on upgrade. Without a migration script, a schema-incompatible upgrade is blocked and the project admin must intervene manually.
Local install for a downstream user¶
For an outside consumer (an internal team that does not check into this monorepo) the install flow is:
# Until a private index exists, ship a wheel from this repo:
uv build packages/connector_my
# -> dist/connector_my-0.1.0-py3-none-any.whl
# Then on the consumer side:
uv pip install ./connector_my-0.1.0-py3-none-any.whl
After install, the entry-points are picked up automatically; no extra registration step.
Publishing roadmap¶
Today the platform runs first-party-only. The publishing flow evolves in three stages:
- Monorepo (current) — plugins live in
packages/and are installed via the workspace.uv synchandles everything. - Internal index (planned) — wheels published to a private index (e.g. a self-hosted
PyPI mirror or a GitLab Package Registry). Deployments pin versions in their root
pyproject.toml. - Public marketplace (future) — third-party plugins behind sandboxing + capability-based
permissions + resource quotas. Out of scope for v1; see
doc-platform-rewrite/02_plugin_contracts.md, section 7, for the long-term shape.
Pre-release checklist¶
Before tagging a plugin release:
- [ ] Version is bumped in both
pyproject.tomlandmanifest.yaml. - [ ]
kernel_versioncovers the current kernel + any intentional forward-compatibility window. - [ ] All tests pass (
uv run pytest packages/<your_plugin>). - [ ]
SkillContractTest(skills only) is green. - [ ] No imports from
kernel.*in plugin code (grep -rn "from kernel" packages/<your_plugin>/src). - [ ] No secrets, no API keys, no log dumps committed in tests.
- [ ] Manifest loads cleanly (
python -c "from connector_my import MyConnector; print(MyConnector.manifest.plugin.version)"). - [ ] Migration script attached if the schema changes incompatibly.
- [ ] CHANGELOG (if you keep one) lists user-visible changes.