Skip to main content

Quick start

dagstack/plugin-system is a generic plugin registry for any application that needs pluggable extension points. Typical scenarios: data sources, event handlers, notification channels, authentication providers, pipeline steps, integrations with external systems. Plugins are discovered in a directory by their manifests, loaded into a registry, and called through one of five dispatch classes (singleton, broadcast-collect, broadcast-notify, chain, capability).

:::info Release status

  • Python: dagstack-plugin-system is being prepared for PyPI; today it ships from an internal repository.
  • TypeScript: @dagstack/plugin-system 0.1.0-rc.2 ships spec-emitted types only (VERSION, ToolV1, OrchestratorV1); the full runtime lands in Phase 1.
  • Go: go.dagstack.dev/plugin-system v0.1.0-rc.1 is published as a release candidate (Phase 1 in-process surface). :::

Installation

:::warning Snippets show the planned high-level API The TabItem snippets across this site illustrate the language-agnostic contract from ADR-0001..0006. Concrete bindings ship the same contract under slightly different names: Python today exposes discover(registry, path) rather than the convenience discover(path) -> Registry shown here, and Go uses Discover() returning []ManifestEntry plus a separate NewRegistry() + RegisterManifest() loop. For exact, today-runnable signatures see the per-binding API reference. :::

pip install dagstack-plugin-system

Your first plugin

A minimal working plugin consists of two files in a dedicated folder: a dagstack.toml manifest and an implementation module.

Plugin manifest

The manifest declares the kind, name, runtime, the compatible core version (core_version, PEP 440 specifier), and the entry_point that the loader resolves to a Python class.

plugins/echo/dagstack.toml
[plugin]
schema_version = "1"
name = "echo"
kind = "tool"
kind_api_version = "1"
core_version = ">=0.1.0,<1.0.0"
runtime = "in_process"
license = "Apache-2.0"
entry_point = "plugin:EchoTool"

The entry_point resolves to plugins/echo/plugin.py and the class EchoTool inside it (ADR-0006 §2 — modules load into the isolated dagstack._discovered.<name>.<module> namespace).

Plugin implementation

plugins/echo/plugin.py
from dagstack.plugin_system import PluginContext


class EchoPlugin:
"""Returns the input message wrapped under the `echoed` key.

Mirrors the canonical `examples/echo_plugin.EchoTool` shipped
with the binding. Both sync and async `setup` / `teardown` are
accepted by the registry; the published example uses sync.
"""

def setup(self, context: PluginContext) -> None:
self._ctx = context

def execute(self, args: dict) -> dict:
return {"echoed": args["msg"]}

def teardown(self) -> None:
self._ctx = None

Discover and load the registry

Construct a PluginRegistry, then call registry.discover(path) to walk a directory recursively, find every folder that contains a dagstack.toml, validate the manifests, and load the plugins into the registry. The setup_all(ctx) method initialises each plugin while honouring inter-plugin dependencies (topological sort).

import asyncio
import logging
from dagstack.plugin_system import PluginContext, PluginRegistry


async def main() -> None:
registry = PluginRegistry()
registry.discover("plugins/")

ctx = PluginContext(
config={},
logger=logging.getLogger("app"),
registry=registry,
)
await registry.setup_all(ctx)

for manifest in registry.list_manifests():
print(manifest.kind, manifest.name)

await registry.teardown_all()


asyncio.run(main())

Calling a plugin

Plugins are retrieved from the registry by the kind + name pair. The returned object is a proxy that applies the dispatch rules, injects resources, and runs contract checks.

echo = registry.get_plugin("tool", name="echo")
result = echo.execute({"msg": "hello"})
assert result == {"echoed": "hello"}

What's next

Concepts — how the main components are wired:

Guides — how to solve typical tasks:

Reference — exact tables for quick lookup:

Specification — normative architecture decisions:

API reference (generation deferred until the bindings reach stable):