Skip to content

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 -e after 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 ValidationError from pydantic during load_manifest will 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 metadata schema incompatibly, or alter execute semantics 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 metadata of 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:

  1. Monorepo (current) — plugins live in packages/ and are installed via the workspace. uv sync handles everything.
  2. 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.
  3. 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.toml and manifest.yaml.
  • [ ] kernel_version covers 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.