Перейти к основному содержимому

Реестр плагинов

PluginRegistry — центральный объект системы. Он хранит набор загруженных плагинов, проверяет совместимость их манифестов с версией ядра, рассчитывает порядок инициализации с учётом зависимостей и предоставляет доступ к плагинам по сочетанию вид + имя.

Получение реестра

Единственный рекомендуемый путь получить реестр — вызвать discover(path). Функция рекурсивно обходит указанный каталог, находит папки с файлом dagstack.toml, валидирует манифесты и возвращает готовый реестр.

from dagstack.plugin_system import PluginRegistry

registry = PluginRegistry()
registry.discover("plugins/")

Прямое создание реестра через конструктор PluginRegistry(...) в обычном коде не нужно и не рекомендуется — обход каталогов выполняет discover, который одновременно с обнаружением проводит валидацию.

Жизненный цикл

Каждый загруженный плагин проходит через три фазы:

  1. Регистрацияdiscover читает манифест, создаёт экземпляр плагина, добавляет в реестр. На этом шаге работают только проверки манифеста и импорта.
  2. Инициализацияsetup_all() вычисляет порядок инициализации через топологическую сортировку зависимостей (topo_sort) и вызывает setup(context) у каждого плагина.
  3. Завершениеteardown_all() вызывает teardown() в обратном порядке. Ошибки в отдельных плагинах не прерывают цикл, а собираются в TeardownErrors.
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())

Получение плагина

Доступ к плагину — по сочетанию kind + name. Метод возвращает прокси-объект, который применяет правила диспетчеризации и инъекцию ресурсов.

llm = registry.get_plugin("llm", name="openai_compatible")
response = llm.complete(prompt="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() поймал исключения в нескольких плагинах.
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

Инварианты

Реестр поддерживает следующие инварианты поверх жизненного цикла плагинов:

  • Идемпотентность 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 возвращает один и тот же экземпляр плагина.