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

ADR-0001 · Архитектура ядра

Статус: accepted v1.0 (2026-04-16) · Полный нормативный текст

Зачем спецификация единой архитектуры​

dagstack/plugin-system строится для приложений, где множество точек расширения — источники данных, модели, препроцессоры, шаги пайплайна, интеграции — меняются чаще, чем само ядро. Чтобы одни и те же плагины работали в Python-ядре и TypeScript-ядре одинаково, нужна общая карта: что входит в любую реализацию независимо от языка, а что — идиоматическая деталь реализации.

ADR-0001 отвечает на этот вопрос: шесть обязательных компонентов, которые должен содержать любой распространяемый binding.

Шесть обязательных компонентов​

Любая реализация (plugin-system-python, plugin-system-typescript, планируемый plugin-system-go) состоит из следующих частей:

  1. Схема манифеста — JSON Schema 2020-12, источник правды хранится в plugin-system-spec/_meta/manifest.schema.json. Все реализации генерируют свои типы из этой схемы — поля одинаковы, имена одинаковы, правила валидации одинаковы.
  2. Диспетчер хуков — отвечает за регистрацию плагинов, маршрутизацию вызовов, обработку жизненного цикла. Нормативная семантика — в ADR-0002.
  3. Реестр плагинов (PluginRegistry) — оболочка над диспетчером, которая добавляет обнаружение, валидацию манифеста, настройку жизненного цикла.
  4. Контекст плагина (PluginContext) — контейнер сквозных сервисов, который передаётся плагину на setup: конфиг, логгер, метрики, трассировщик, шина событий, реестр, опциональный тенантный контекст.
  5. Адаптеры исполняющих сред — минимум три: in_process (нативный для языка), mcp_stdio (подпроцесс через stdio), mcp_http (удалённый HTTP-сервис). Последние два — одинаковые во всех языках, потому что MCP — кросс-языковой протокол.
  6. Рамка контрактных тестов — набор обязательных сценариев (валидность манифеста, чистое завершение жизненного цикла, отсутствие утечек ресурсов), которые каждый плагин обязан проходить.

Три способа распространения плагинов​

Архитектура поддерживает три сценария размещения плагинов одновременно, без выбора «или-или»:

  • A · In-tree — папка plugins/ в монорепо приложения-потребителя. Плагины живут рядом с бизнес-кодом, деплоятся с ним, версионируются в том же git-репо.
  • B · Приватные пакеты — отдельные модули в приватном реестре пакетов (Nexus PyPI, private npm-registry). Подходит, когда плагин переиспользуется несколькими приложениями внутри одной организации.
  • C · Публичные пакеты — публикация в PyPI / npmjs.org / Go module registry. Подходит для плагинов, рассчитанных на open-source сообщество.

Один и тот же плагин может быть опубликован во всех трёх каналах одновременно. Приложение-потребитель выбирает, откуда его загрузить, на этапе сборки окружения.

Обязательный минимум манифеста​

Манифест декларируется в одном из стандартных файлов:

  • dagstack.toml (соглашение для Python и Go);
  • dagstack.json (соглашение для TypeScript/Node);
  • секция [tool.dagstack.plugin] в pyproject.toml (для Python-плагинов, публикуемых как pip-пакет);
  • поле dagstack в package.json (для TypeScript-плагинов).

Минимальный набор обязательных полей:

ПолеТипНазначение
schema_versionstringВерсия JSON-схемы манифеста (на v1.0 = "1").
namestringУникальное имя плагина в пределах вида.
kindstringВид плагина — какой контракт он реализует.
runtimestring | arrayИсполняющая среда: in_process / mcp_stdio / mcp_http.
core_versionstringТребование к версии ядра (semver range).

Полная схема манифеста (нормативный JSON Schema 2020-12) — в spec-репозитории: _meta/manifest.schema.json.

Проверка совместимости версий​

Плагин декларирует поддерживаемые версии ядра в поле core_version как semver-диапазон (^0.2, >=0.3.0 <1.0.0). При регистрации реестр проверяет, что установленная версия dagstack-plugin-system (или эквивалента в других языках) удовлетворяет диапазону. Несовместимые плагины отклоняются с ошибкой VersionIncompatible — с явным указанием, какая версия требуется и какая установлена.

Это защищает от ситуации, когда плагин тихо перестаёт работать после обновления ядра: вместо странного поведения во время исполнения пользователь получает понятную ошибку на старте.

Опциональная изоляция процесса​

Адаптеры mcp_stdio и mcp_http позволяют запускать плагин как отдельный подпроцесс или удалённый сервис. Решение принимается per-plugin, не глобально:

  • in-tree плагин работает в основном процессе (in_process);
  • внешний плагин, которому нужна изоляция (например, «подозрительный» сторонний плагин, который нельзя пускать в основное адресное пространство), запускается как MCP-подпроцесс (mcp_stdio);
  • плагин на другом языке или развёрнутый отдельно — через mcp_http.

Для приложения-потребителя все три варианта выглядят одинаково — оно работает с объектом-прокси плагина, не зная, в каком процессе он исполняется.

Пример минимального плагина​

Ниже — тот же echo-плагин на трёх языках. Все три манифеста эквивалентны; различия в коде — идиоматические особенности языков, а не расхождения в контракте.

plugins/echo/dagstack.toml
[plugin]
schema_version = "1"
name = "echo"
kind = "tool"
runtime = "in_process"
core_version = ">=0.1.0,<1.0.0"
plugins/echo/plugin.py
from dagstack.plugin_system import PluginContext


class EchoPlugin:
async def setup(self, ctx: PluginContext) -> None:
self._ctx = ctx

def invoke(self, payload: str) -> str:
return payload

async def teardown(self) -> None:
pass

Последствия​

Положительные:

  • Один плагин может быть написан один раз (как спецификация) и реализован одинаково на любом языке — без расхождений в поведении.
  • Приложение-потребитель выбирает язык ядра независимо от языков плагинов — сторонние плагины подключаются через MCP, даже если написаны на другом языке.
  • Контракт совместимости версий зафиксирован нормативно; бинарные несовместимости превращаются в понятные ошибки на старте.

Компромиссы:

  • Любое изменение в схеме манифеста требует согласованного обновления всех реализаций — иначе расхождение в валидации ломает совместимость.
  • Добавление нового обязательного поля в манифест — breaking change для всех существующих плагинов, поэтому делается только через bump schema_version + migration path.

Что запрещено этим ADR:

  • Добавлять в binding поля манифеста, которых нет в нормативной схеме (расширения допустимы только через x-* namespaced-поля или через отдельный ADR).
  • Менять порядок фаз жизненного цикла плагина (регистрация → инициализация → завершение) — ADR-0002 фиксирует его нормативно.

Связанные ADR​

  • ADR-0002 — семантика вызова хуков, которая опирается на понятие реестра, введённое здесь.
  • ADR-0003 — восемь runtime-инвариантов, которые гарантируют orchestration-neutrality.
  • ADR-0004 — формализм, по которому из спецификации вида плагина эмитируются типы для каждого языка.
  • ADR-0006 — как именно реестр находит плагины в файловой системе.

Нормативный источник​

Полный текст ADR-0001 с формальными требованиями к каждой реализации: plugin-system-spec/adr/0001-plugin-architecture-core.md.