Реестр плагинов
PluginRegistry — центральный объект системы. Он хранит набор загруженных плагинов, проверяет совместимость их манифестов с версией ядра, рассчитывает порядок инициализации с учётом зависимостей и предоставляет доступ к плагинам по сочетанию вид + имя.
Получение реестра
Единственный рекомендуемый путь получить реестр — вызвать discover(path). Функция рекурсивно обходит указанный каталог, находит папки с файлом dagstack.toml, валидирует манифесты и возвращает готовый реестр.
- Python
- TypeScript
- Go
from dagstack.plugin_system import PluginRegistry
registry = PluginRegistry()
registry.discover("plugins/")
:::warning TypeScript runtime ships in Phase 1
@dagstack/plugin-system@0.1.0-rc.2 exports only the spec-emitted types — VERSION, ToolV1, OrchestratorV1. The runtime (PluginRegistry, discover, dispatchers, contract suite) lands in Phase 1. Today: implement the kind contract against the published types, then host plugins through Python over mcp_stdio or wait for the Phase 1 release. See the TypeScript API reference for the planned shape.
:::
entries, err := pluginsystem.Discover("plugins/")
if err != nil {
return err
}
reg := pluginsystem.NewRegistry()
for _, entry := range entries {
plugin := buildPlugin(entry.Manifest) // user-defined factory
if err := reg.RegisterManifest(entry.Manifest, plugin); err != nil {
return err
}
}
Прямое создание реестра через конструктор PluginRegistry(...) в обычном коде не нужно и не рекомендуется — обход каталогов выполняет discover, который одновременно с обнаружением проводит валидацию.
Жизненный цикл
Каждый загруженный плагин проходит через три фазы:
- Регистрация —
discoverчитает манифест, создаёт экземпляр плагина, добавляет в реестр. На этом шаге работают только проверки манифеста и импорта. - Инициализация —
setup_all()вычисляет порядок инициализации через топологическую сортировку зависимостей (topo_sort) и вызываетsetup(context)у каждого плагина. - Завершение —
teardown_all()вызываетteardown()в обратном порядке. Ошибки в отдельных плагинах не прерывают цикл, а собираются вTeardownErrors.
- Python
- TypeScript
- Go
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)
try:
...
finally:
await registry.teardown_all()
asyncio.run(main())
:::warning TypeScript runtime ships in Phase 1
@dagstack/plugin-system@0.1.0-rc.2 exports only the spec-emitted types — VERSION, ToolV1, OrchestratorV1. The runtime (PluginRegistry, discover, dispatchers, contract suite) lands in Phase 1. Today: implement the kind contract against the published types, then host plugins through Python over mcp_stdio or wait for the Phase 1 release. See the TypeScript API reference for the planned shape.
:::
ctx := context.Background()
entries, _ := pluginsystem.Discover("plugins/")
reg := pluginsystem.NewRegistry()
for _, entry := range entries {
_ = reg.RegisterManifest(entry.Manifest, buildPlugin(entry.Manifest))
}
pluginCtx := &pluginsystem.PluginContext{
Logger: slog.Default(),
Registry: reg,
}
if err := reg.SetupAll(ctx, pluginCtx); err != nil {
return err
}
defer reg.TeardownAll(ctx)
Получение плагина
Доступ к плагину — по сочетанию kind + name. Метод возвращает прокси-объект, который применяет правила диспетчеризации и инъекцию ресурсов.
- Python
- TypeScript
- Go
llm = registry.get_plugin("llm", name="openai_compatible")
response = llm.complete(prompt="Explain dagstack in one paragraph.")
:::warning TypeScript runtime ships in Phase 1
@dagstack/plugin-system@0.1.0-rc.2 exports only the spec-emitted types — VERSION, ToolV1, OrchestratorV1. The runtime (PluginRegistry, discover, dispatchers, contract suite) lands in Phase 1. Today: implement the kind contract against the published types, then host plugins through Python over mcp_stdio or wait for the Phase 1 release. See the TypeScript API reference for the planned shape.
:::
p, err := reg.Resolve("openai_compatible")
if err != nil {
return err
}
llm := p.Unwrap().(LLMClient)
resp, _ := llm.Complete(ctx, "Explain dagstack in one paragraph.")
Если плагин не найден, выбрасывается KindUnknown (вид не зарегистрирован) или AmbiguousPlugin (несколько плагинов с одним kind+name).
Ошибки реестра
Все ошибки наследуются от PluginRegistryError:
| Класс | Когда возникает |
|---|---|
ManifestInvalid | Манифест не соответствует схеме (отсутствует обязательное поле, неверный тип). |
AmbiguousPlugin | Два плагина зарегистрированы с одинаковой парой kind+name. |
VersionIncompatible | Плагин объявляет core_version, несовместимый с установленной версией ядра. |
KindUnknown | Вид плагина не зарегистрирован в реестре. |
RuntimeNotSupported | Плагин не поддерживает ни одну из доступных исполняющих сред (in_process, mcp_stdio, mcp_http). |
DependencyCycle | Топологическая сортировка обнаружила цикл в декларированных зависимостях между плагинами. |
TeardownErrors | Агрегированная ошибка, если teardown_all() поймал исключения в нескольких плагинах. |
- Python
- TypeScript
- Go
from dagstack.plugin_system import PluginRegistry
from dagstack.plugin_system import ManifestInvalid, PluginRegistryError
registry = PluginRegistry()
try:
registry.discover("plugins/")
except ManifestInvalid as exc:
print(f"Invalid manifest: {exc}")
raise
except PluginRegistryError:
raise
:::warning TypeScript runtime ships in Phase 1
@dagstack/plugin-system@0.1.0-rc.2 exports only the spec-emitted types — VERSION, ToolV1, OrchestratorV1. The runtime (PluginRegistry, discover, dispatchers, contract suite) lands in Phase 1. Today: implement the kind contract against the published types, then host plugins through Python over mcp_stdio or wait for the Phase 1 release. See the TypeScript API reference for the planned shape.
:::
entries, err := pluginsystem.Discover("plugins/")
if err != nil {
switch {
case errors.Is(err, pluginsystem.ErrManifestInvalid):
slog.Error("invalid manifest", "err", err)
case errors.Is(err, pluginsystem.ErrAmbiguousPlugin):
slog.Error("ambiguous plugin", "err", err)
case errors.Is(err, pluginsystem.ErrDependencyCycle):
slog.Error("dependency cycle", "err", err)
default:
slog.Error("registry error", "err", err)
}
return err
}
_ = entries
Инварианты
Реестр поддерживает следующие инварианты поверх жизненного цикла плагинов:
- Идемпотентность
setup_all(). Повторный вызов без предварительногоteardown_all()— no-op (плагины уже инициализированы). - Порядок teardown — обратный setup. Если
setup_all()инициализировал плагины в порядкеA → B → C, тоteardown_all()вызоветteardownв порядкеC → B → A. - Partial failure в setup. Если
setupодного плагина выбрасывает исключение, уже инициализированные плагины получаютteardown, и исходное исключение пробрасывается наверх. - Безопасность повторных
get. Один и тот жеkind+nameвозвращает один и тот же экземпляр плагина.