Cordis: плагинная архитектура как новый слой композиции JS/TS-приложений
- понедельник, 7 сентября 2026 г. в 00:00:08
Что изменится, если плагин станет не дополнением, а основной единицей архитектуры приложения? На примере Cordis и DeepSeek Harness разберём, как среда композиции управляет зависимостями, эффектами и жизненным циклом компонентов.
Сравним три способа доставки клиентских плагинов: виртуальные модули в общем графе сборки, Module Federation и собственную модульную систему DSH. Сопоставим Cordis с микросервисами и Kubernetes, обсудим его ограничения и роль в разработке, которую ведут независимые команды и AI-агенты. Затронем также метатеорию, лежащую в основе Cordis, и спекулятивную архитектуру, в которой сама композиция приложения становится предметом эксперимента.
Статья будет полезна архитекторам, техлидам и JS/TS-разработчикам, которые проектируют модульные продукты, плагинные платформы или системы с динамически подключаемыми AI-компонентами.

От документированных предположений к проверяемым инвариантам
Научное основание: пространственно-временная композируемость
Клиент и сервер: единая модель композиции, разные механизмы загрузки
Module Federation: связывание независимых сборок во время выполнения
Клиентская система модулей DSH: специализированный механизм загрузки
Плагинные системы — один из самых успешных, но недооценённых архитектурных механизмов JavaScript-экосистемы.
Webpack называет плагины основой своей архитектуры и сам построен на той же системе плагинов, которая доступна пользователям. Vite предоставляет расширяемый API плагинов. Fastify строит серверное приложение как граф вложенных плагинов, а Avvio управляет порядком их загрузки и завершением работы. Похожий принцип используют линтеры, тестовые среды, редакторы и многие другие инструменты разработки.
Плагинная архитектура уже доказала, что способна поддерживать огромные экосистемы независимо развиваемых расширений. Однако обычно она находится вокруг приложения: расширяет bundler, dev server, редактор или framework. Но внутри самого приложения компоненты по-прежнему связываются привычными способами: через прямые импорты, общие реестры сервисов, dependency injection и вручную согласованные функции инициализации и очистки.
Cordis предлагает сделать следующий шаг:
Превратить плагинную систему из механизма расширения приложения в основной способ композиции самого приложения.
Именно так Cordis используется в DeepSeek Harness. Проект прямо заявляет принцип Everything is a Plugin: плагинами являются model adapters, инструменты, session log, agent loop и другие части системы. Более того, Cordis работает не только на стороне хоста: браузер запускает собственное Cordis-дерево, в котором UI-возможности также представлены динамически загружаемыми плагинами.
Поэтому Cordis точнее рассматривать не просто как backend plugin framework и не как ещё один DI container. Это среда композиции приложения, одинаково применимая к frontend- и backend-компонентам. Её задача заключается в том, чтобы сделать явными и управляемыми фундаментальные отношения внутри сложной системы:
какие компоненты существуют; что им необходимо; что они предоставляют; какими изменениями они владеют; когда они могут запуститься; что должно произойти при изменении композиции.
Научная работа A Programming Paradigm for Spatiotemporal Composability формализует свойства, необходимые такой архитектуре, а Cordis реализует их в TypeScript. Поэтому Cordis интересен не только как удобный API для плагинов. Он предлагает выделить самостоятельный архитектурный слой со своей моделью композиции, жизненного цикла и корректности.
Чтобы увидеть, чем этот слой отличается от обычной плагинной системы, сначала сравним две модели: приложение, расширяемое плагинами, и приложение, собранное из них.
В традиционной плагинной архитектуре существует привилегированное ядро:

Ядро определяет основное поведение приложения. Плагины подключаются к заранее выделенным точкам расширения и добавляют необязательные возможности.
Такая модель хорошо работает для редакторов, CMS, сборщиков и других расширяемых продуктов. Но она сохраняет жёсткое различие между «настоящим приложением» и «дополнениями».
Плагинно-ориентированная архитектура Cordis предлагает другую картину:

В такой системе значительная часть того, что прежде считалось ядром, становится обычными компонентами, подчинёнными общей модели.
DeepSeek Harness показывает радикальный вариант этого подхода: в его архитектурном описании нет привилегированного ядра, которое необходимо модифицировать ради расширения системы. Новая возможность добавляется рядом с остальными плагинами, а registries, services, events и agent loop также являются частями общей композиции.
Это не означает, что приложение вообще не имеет стабильного основания. Основание просто меняется. Вместо большого функционального ядра появляется сравнительно небольшой composition runtime, отвечающий за универсальные правила:
Компонент = предоставляемые возможности + требования к окружению + конфигурация + принадлежащие ему эффекты + жизненный цикл
Бизнес-функциональность остаётся внутри плагинов. Runtime не обязан знать, как работает поиск, как строится интерфейс или как вызывается модель. Он должен знать, как эти возможности входят в систему, как взаимодействуют и какие изменения в окружение вносит каждый компонент. Именно это позволяет называть Cordis дополнительным архитектурным слоем.
Однако само по себе выделение такого слоя ещё не объясняет, зачем он нужен. Его ценность проявляется, когда система развивается сразу в нескольких направлениях, а связи между её компонентами уже трудно согласовывать вручную.
Плагинные системы существовали задолго до Cordis. Новизна здесь не в самой идее плагинов, а в масштабе задач и скорости изменений: продукты обрастают возможностями и интеграциями, а AI удешевляет создание кода и позволяет небольшой группе авторов одновременно развивать несколько направлений.
Архитектурная сложность поэтому растёт даже без увеличения команды, и полную модель системы становится трудно удерживать в голове. Связи между компонентами нужно формализовать на уровне всей системы, чтобы каждый компонент можно было понимать и изменять локально.
Плагинная архитектура отвечает на эту проблему, задавая компоненту ограниченный контекст и явные правила подключения. В 2026 году команда Яндекс Трекера описала такую платформу для продукта с 600 000 пользователей. За первый месяц беты команды создали 25 плагинов, часть — с помощью LLM. Плагины подключаются только через заданные точки расширения и работают в рамках явных разрешений и изоляции.
В этом же году команда Cloudflare описала систему AI-ревью кода на плагинах: каждый плагин реализует ReviewPlugin, имеет явный жизненный цикл и получает только свою конфигурацию. Это не прямой аналог Cordis, но принцип тот же: стабильные интерфейсы и ограниченный контекст компонентов.
Подход, при котором компоненту не нужно знать устройство всей системы, восходит к работе Дэвида Парнаса об информационном сокрытии. Cordis переносит этот принцип в runtime, управляя зависимостями, эффектами и жизненным циклом компонентов.
Представим приложение, включающее поиск, аналитику, авторизацию, интерфейс, AI-платформу и инфраструктуру. Эти направления могут развивать как несколько автономных команд, так и небольшая группа авторов с помощью AI. Организация работы меняется, но необходимость понимать связи между компонентами остаётся:
кто какие события публикует; кто предоставляет сервисы; кто зависит от конкретной реализации; в каком порядке всё запускается; какие регистрации создаёт каждый компонент; что изменится при обновлении конфигурации.
По мере роста системы возникает разрыв:
сложность приложения растёт число компонентов и связей растёт скорость изменений растёт возможность понять систему целиком снижается
Без отдельного композиционного слоя архитектура оказывается распределена между прямыми импортами, bootstrap-файлами, framework hooks, React contexts, DI container, event bus и негласными договорённостями команд. Компонент может выглядеть локально простым, но для его корректного изменения разработчику приходится исследовать большую часть системы.
Плагинная архитектура предлагает заменить часть этого глобального знания локальным контрактом:
Плагин «Исследование» требует: поиск базу данных предоставляет: исследование конфигурация: стратегия ограничения эффекты: управляются средой выполнения
Сложность при этом не исчезает. Но она частично переносится:
из памяти разработчиков ↓ в явную модель композиции

Это и есть один из главных источников снижения когнитивной нагрузки. Разработчик декларативно задаёт зависимости, а Cordis определяет порядок запуска и управляет жизненным циклом компонентов.
В Cordis контракт плагина может стать границей не только между компонентами, но и между командами. Каждая команда может самостоятельно менять внутреннюю реализацию своего плагина, пока сохраняет его требования и предоставляемые возможности.
Процесс остаётся общим, но границы ответственности становятся явными:
Команда поиска: предоставляет «поиск» Команда исследований: требует «поиск» предоставляет «исследование» Команда интерфейса: требует «исследование» добавляет слоты интерфейса
В официальном руководстве Cordis сервис определяется как именованная возможность, которую один плагин предоставляет, а другие получают через ctx. Зависимый компонент обращается к сервису через свойство контекста — например, ctx.tools, ctx.llm или ctx.agents, — а не импортирует конкретную реализацию. Имя обязательного сервиса указывается в inject; среда выполнения удерживает компонент в состоянии PENDING, пока не появятся все необходимые зависимости.
Модуль помогает организовать исходный код, а контракт плагина делает явными условия, при которых компонент может быть активен в текущей композиции. Это не превращает большую систему в маленькую и не отменяет необходимость архитектурного проектирования.
Cordis снижает объём глобального знания, необходимого для локального изменения.
Автору компонента достаточно понимать:
доступный Context;
объявленные зависимости;
предоставляемые сервисы;
события и реестры, к которым он подключается;
собственную конфигурацию;
эффекты, которые не охватываются стандартными средствами Cordis.
Но такая локальность сохраняется лишь в том случае, если общие договорённости не остаются только в документации, а автоматически проверяются во время выполнения.
Значительная часть ошибок сложной архитектуры возникает из-за предположений, существующих только в документации или памяти команды:
«этот сервис всегда запускается первым»; «этот обработчик событий обязательно снимается»; «поставщик никогда не исчезает»; «другой плагин не зарегистрирует такое же имя»; «эта конфигурация всегда корректна».
Cordis не доказывает правильность бизнес-логики. Он не может установить, что поисковый алгоритм выдаёт хорошие результаты или что интерфейс, созданный AI, соответствует продуктовым требованиям. Но он позволяет сделать проверяемыми некоторые композиционные утверждения:
обязательные сервисы существуют; плагин не активируется, пока недоступны зависимости; потеря зависимости меняет состояние жизненного цикла; сервис имеет владельца; регистрация связана со временем жизни плагина; конфигурация проходит проверку по схеме; удаление компонента запускает его функции очистки.
Проверки композиции в DeepSeek Harness дополняет реестр инвариантов времени выполнения — условий, которые должны соблюдаться во время работы приложения. Каждый пакет может зарегистрировать собственную проверку в ctx.invariants под своим npm-именем. Если условие нарушено, реестр создаёт ошибку с указанием пакета, которому принадлежит этот контракт.
Такие проверки должны описывать содержательное поведение системы. Например, они могут проверять, что одно событие возникает только после другого или что связанные изменяемые данные не противоречат друг другу. Простая проверка наличия сервиса или метода инвариантом не считается.
Поэтому вместо утверждения:
Cordis доказывает корректность приложения,
лучше говорить:
Cordis превращает часть архитектурных предположений в явные контракты и инварианты, соблюдение которых можно автоматически проверять во время работы приложения.
Базовое устройство Cordis строится вокруг трёх сущностей. Context определяет, какие сервисы доступны компоненту, Service регистрирует сервис под постоянным именем, а Fiber представляет загруженный экземпляр плагина и его жизненный цикл. Вместе они позволяют отслеживать зависимости компонента и отменять внесённые им изменения при выгрузке.
В основе Context — контейнер именованных сервисов. Один компонент регистрирует сервис под постоянным именем, а другой объявляет, что этот сервис ему необходим.
Упрощённый компонент, предоставляющий сервис, выглядит так:
import { Service, type Context } from "cordis"; declare module "cordis" { interface Context { search: SearchService; } } class SearchService extends Service { constructor(ctx: Context) { super(ctx, "search"); } query(text: string) { // ... } } export function apply(ctx: Context) { ctx.plugin(SearchService); }
Зависимый компонент обращается не к классу SearchService, а к сервису search:
import type { Context } from "cordis"; export const inject = ["search"]; export function apply(ctx: Context) { ctx.search.query("spatiotemporal composability"); }
Блок declare module "cordis" сообщает TypeScript, что в Context есть сервис search типа SearchService. Благодаря этому TypeScript может проверять обращения к ctx.search. Сам сервис это объявление не создаёт.
Настоящий сервис регистрирует класс SearchService во время работы приложения. Когда предоставивший сервис компонент выгружается, Cordis удаляет его регистрацию.
Но Context — не только контейнер сервисов. Он связывает требования компонента с его эффектами: определяет доступные сервисы и отслеживает внесённые изменения, чтобы Cordis мог отменить их при выгрузке.
Вызов ctx.on() регистрирует обработчик событий, а ctx.plugin() — дочерний компонент. Сервисы предоставляют другим компонентам именованные возможности. Для внешних ресурсов используется ctx.effect(): переданная ему функция создаёт ресурс и возвращает функцию его очистки.
Например:
export function apply(ctx: Context) { ctx.on("request", handleRequest); ctx.effect(() => { const timer = setInterval(refreshCache, 10_000); return () => { clearInterval(timer); }; }); }
Cordis связывает все такие регистрации с текущим экземпляром плагина. Каждому загруженному экземпляру соответствует Fiber — служебный объект, который хранит состояние и управляет жизненным циклом плагина:

PENDING означает, что плагин объявлен, но обязательная зависимость пока отсутствует. Если зависимость исчезает, активный Fiber проходит через UNLOADING и возвращается в PENDING; когда зависимость возвращается, он может загрузиться заново. В UNLOADING среда выполнения вызывает функции очистки. DISPOSED означает окончательное удаление Fiber. Вызов fiber.dispose() завершается только после выполнения зарегистрированных в Cordis функций очистки и рекурсивной выгрузки дочерних плагинов.
Получается иерархия владения:
Корневой Context │ ├── Fiber плагина A │ ├── сервис │ ├── обработчики событий │ └── дочерний плагин │ └── Fiber плагина B ├── таймер └── промежуточный обработчик
Такая иерархия становится основой жизненного цикла приложения.
Устройство Cordis опирается на идею пространственно-временной композируемости, описанную в работе A Programming Paradigm for Spatiotemporal Composability. Так авторы называют способность системы безопасно добавлять и удалять компоненты во время работы. Для этого необходимо решать две независимые задачи:
отслеживать зависимости между компонентами;
полностью отменять изменения, внесённые удаляемым компонентом.
Первую задачу авторы называют пространственной композируемостью, вторую — временной.
Пространственная композируемость описывает зависимости между компонентами.
Например, плагин «Исследование» может объявить, что для работы ему нужны сервис поиска и база данных. Cordis активирует его только тогда, когда доступны оба сервиса. Если затем сервис поиска исчезает, плагин больше не может оставаться активным, даже если база данных по-прежнему доступна.

Cordis рассматривает граф зависимостей не как схему, однократно построенную при запуске, а как живую структуру.
Официальное руководство подчёркивает, что зависимости из inject продолжают отслеживаться после загрузки. Если обязательный сервис исчезает из-за выгрузки или горячей замены предоставляющего его компонента, зависимые плагины также выгружаются. Когда сервис возвращается, они загружаются заново уже с новой реализацией.
В научной работе это моделируется через реактивные коэффекты: компонент объявляет требования к контексту, а изменения контекста заставляют среду выполнения повторно проверять, может ли компонент оставаться активным.
Временная композируемость отвечает за изменения, которые компонент произвёл во времени.
Если плагин:
добавил обработчик событий; зарегистрировал сервис; создал таймер; подключил промежуточный обработчик; добавил дочерний плагин;
то при его удалении эти изменения должны быть сняты.

Регистрации, выполненные через API Cordis, считаются эффектами. Для ресурсов вне стандартных API предназначен ctx.effect(): переданная ему функция создаёт ресурс и возвращает функцию очистки. При выгрузке плагина-владельца среда выполнения вызывает такие функции.
В научной работе это формализовано через обратимые эффекты: каждое преобразование контекста сопровождается обратной операцией, которую отслеживает среда выполнения.
Пространственная композируемость определяет, при каком составе окружения компонент может быть активен. Временная композируемость определяет, как отменить внесённые им изменения при выгрузке.
обязательная зависимость исчезла ↓ требования компонента больше не выполнены ↓ компонент выгружается ↓ система снимает созданные им эффекты
Без реактивного отслеживания зависимостей среда выполнения не знает, когда их структура стала недопустимой. Без привязки эффектов к владельцам автоматическая реакция на изменение этой структуры оставляет остаточное состояние.
Главный теоретический шаг авторов — объединить учёт эффектов и требований к окружению в одной абстракции Context. Так отслеживание требований и откат эффектов становятся частями одной модели динамической композиции. На этой основе авторы формулируют метатеорию, которая переносит свойства отдельного компонента на систему компонентов с чередующимся выполнением. Также они связывают формальную модель с ядром Cordis, декларативным загрузчиком, согласованием конфигурации и HMR.
Для JS/TS-фреймворка такой теоретический фундамент необычен. Конечно, он не доказывает корректность любого приложения: формальные результаты действуют только в рамках заданной модели и её предпосылок. Важно другое: ключевые свойства Cordis описаны не только как удобные соглашения API, но и на уровне операционной семантики и метатеории.
Новизна Cordis не в отдельных механизмах. Плагины, внедрение зависимостей и динамические сервисы существовали в других экосистемах задолго до него.
Например, OSGi давно предоставляет Java-приложениям динамический реестр сервисов. Модули могут публиковать и находить сервисы, а также получать уведомления об изменениях в реестре. Механизм Declarative Services деактивирует компонент, если обязательный сервис исчезает, и активирует новый экземпляр, когда появляется подходящая замена.
Поэтому Cordis нельзя считать изобретателем динамического жизненного цикла компонентов. Его новизна — в сочетании нескольких механизмов в одной модели:
Cordis объединяет реактивное управление зависимостями, владение эффектами, иерархический
Context, согласование конфигурации и формальное описание этих свойств.
Особенно важно, что Cordis одинаково управляет разными видами эффектов. Обработчик событий, регистрация сервиса, инструмент, промежуточный обработчик, дочерний плагин и пользовательский ресурс рассматриваются как разные проявления одной общей идеи:
изменение принадлежит времени жизни определённого компонента.
Сила подхода — в единой дисциплине, которую можно применить не к одной подсистеме, а ко всему работающему приложению. Поэтому новизна Cordis заключается не в отдельном примитиве, а в сочетании этих механизмов на уровне всего приложения.
Одна из сильных сторон Cordis — одна и та же модель композиции для клиентской и серверной частей. DeepSeek Harness показывает, что принципы Cordis могут управлять компонентами по обе стороны границы.
В процессе Node.js работает одно дерево плагинов Cordis, в браузере — второе, где каждая функция интерфейса также представлена плагином. В официальном описании клиентской архитектуры это сказано прямо: обе стороны запускают Cordis, а React служит слоем проекции над клиентским контекстом.
Одинаковая модель композиции, однако, не требует одинаковой модульной системы. В процессе Node.js Cordis Loader может опираться на стандартные механизмы загрузки. В браузере код может попасть к приложению как часть общей сборки, как удалённый модуль или как артефакт специализированной модульной системы. Во всех случаях полезно разделять две ответственности:
Механизм загрузки: находит код; доставляет его в браузер; формирует экспорты модуля; Среда композиции Cordis: проверяет требования плагина к окружению; монтирует и выгружает плагин; связывает эффекты с его жизненным циклом; реагирует на изменение композиции.
Граница между этими слоями проходит в тот момент, когда загрузчик может вернуть экспорт модуля, пригодный для монтирования как плагин. Он может доставить тот же код разными способами, не меняя контрактов композиции.
Cordis не обязан владеть конкретным способом доставки кода. Он управляет жизнью компонента после того, как механизм загрузки сделал код доступным.
Три архитектуры ниже проводят эту границу по-разному. Виртуальный реестр, сформированный сборщиком, включает плагины в общий граф модулей приложения. Module Federation связывает приложение с независимо собранными и развёрнутыми удалёнными модулями. DeepSeek Harness строит собственную систему модулей под конкретные требования продукта.
Первый вариант — сформировать реестр плагинов на этапе сборки с помощью виртуального модуля. Так называют модуль, которого нет в файловой системе: его идентификатор и содержимое задаёт расширение сборщика. Приложение импортирует этот модуль как обычный, получая через него известный на момент сборки состав плагинов.
Этот приём не привязан к одному инструменту. Виртуальные модули поддерживают Vite, Rolldown, Rollup, esbuild и Rspack. Их API различаются, но принцип один: сгенерированный модуль становится частью общего графа и обрабатывается вместе с остальным кодом.
Например, плагин Vite может найти компоненты по конфигурации, манифестам или файловой структуре и создать реестр, который приложение импортирует как обычный ESM-модуль:
import { plugins } from "virtual:plugins";
Исходного файла virtual:plugins не существует. Когда приложение импортирует этот модуль, плагин Vite обрабатывает запрос: resolveId распознаёт виртуальный идентификатор, а load возвращает сгенерированный исходный код. Этот код может содержать статические или динамические импорты известных плагинов.
В такой архитектуре плагины связываются с основным приложением при формировании его модульного графа — во время разработки или итоговой сборки. Во время итоговой сборки динамический импорт плагина может быть преобразован в отдельный фрагмент, который браузер загрузит только при обращении к плагину. Этот фрагмент остаётся частью общей сборки: его содержимое и адрес определяются вместе с основным приложением. Поэтому плагин нельзя выпустить или обновить отдельно от основного приложения.

Эту схему сравнительно легко поддерживать: сборщик видит все плагины и формирует из них и основного приложения единый набор файлов. Плагины не нужно развёртывать отдельно и согласовывать их версии с приложением во время выполнения. Цена — общий цикл сборки и развёртывания: чтобы добавить неизвестный прежней сборке плагин, основное приложение нужно собрать заново либо заранее встроить в виртуальный модуль динамический загрузчик. Во втором случае доставку выполняет уже не сам виртуальный модуль, а сгенерированный им адаптер.
Второй вариант — собирать плагины как удалённые модули, совместимые с Module Federation, и связывать их с приложением во время работы. Эта архитектура не привязана к одному сборщику: официальные интеграции существуют для Webpack, Rspack, Vite, Rsbuild, Metro и других инструментов.
На стороне основного приложения можно использовать плагин сборщика или подключить Module Federation Runtime как самостоятельную библиотеку. Во втором случае Runtime регистрирует удалённые сборки, загружает их и возвращает экспорты через loadRemote. Проекту, публикующему удалённый модуль, всё равно нужна совместимая интеграция со сборщиком для создания артефакта Module Federation.
При регистрации удалённой сборки основное приложение может передать Module Federation Runtime URL remoteEntry.js или mf-manifest.json. remoteEntry.js не является самим MF Runtime. Это точка входа удалённого контейнера, предоставляющего операции get и init. Манифест описывает опубликованные модули, файлы и общие зависимости; Module Federation Runtime читает его и определяет итоговый URL точки входа.
Общая схема выглядит так:

Основное приложение и удалённую сборку можно собирать, обновлять и развёртывать независимо друг от друга. Module Federation Runtime связывает их во время работы и согласовывает общие зависимости. Цена — распределённый контракт версий, кэшей, сетевых отказов и порядка инициализации. Module Federation решает загрузку удалённой сборки и согласование общих модулей, но не отвечает на вопросы о том, какие сервисы среды выполнения нужны плагину, кто владеет его эффектами и как освободить эти эффекты при выгрузке. Эти задачи остаются за Cordis.
Третий вариант — не использовать готовый протокол удалённых модулей, а построить модульную систему под ограничения конкретного продукта. Именно так устроена клиентская система модулей DeepSeek Harness.
Каждый клиентский плагин DSH собирается в отдельный файл client.js и не включается в основную клиентскую сборку Vite. Пакет указывает этот файл в dsh.client. Сервер берёт только подключённые в Cordis плагины и составляет список модулей, которые понадобятся браузеру. Идентификатором каждого модуля служит имя npm-пакета.
Обычно client.js не прописан в index.html как готовый <script>. Сервер передаёт браузеру список модулей с их адресами и ревизиями, а сами файлы отдаёт по запросу. Когда модуль нужно предварительно загрузить или впервые использовать, ClientModuleSystem по умолчанию динамически создаёт асинхронный <script src="...">. Браузер получает файл с сервера DSH и исполняет его как классический скрипт, а не как ESM-модуль. Плагины с признаком immediately загружаются при запуске клиентской системы модулей, остальные — по мере необходимости.
Загрузка файла ещё не запускает плагин. Скрипт только регистрирует функцию, которая сможет создать модуль. ClientModuleSystem вызывает её при первом обращении к модулю, сохраняет полученные экспорты и затем использует их повторно. При горячей замене invalidate(id) удаляет старую фабрику и экспорты, после чего prefetch(id) загружает новый скрипт и регистрирует новую фабрику.

DSH не заменяет Cordis Loader своей таблицей модулей. Браузер повторяет то же разделение ответственности, что и сервер: модульная система отвечает за байты и идентификацию модуля, а одна и та же включённая в проект реализация Loader управляет ожиданием сервисов, активацией, обновлением и очисткой Fiber. Поэтому серверные и клиентские плагины подчиняются одинаковой модели жизненного цикла при разных механизмах получения модулей.
Собственная система даёт DSH контроль над порядком доставки, ленивым выполнением, версиями, картами исходного кода и горячей заменой модулей (HMR). Но эта гибкость превращает все перечисленные механизмы в ответственность самого проекта. Архитектурная записка прямо фиксирует, что Module Federation рассматривался, но был отклонён: проекту понадобился формат независимо собранных плагинных сборок, который рассматривавшаяся Vite-интеграция Federation не поддерживала.
На практике выбор начинается с одного вопроса: нужно ли обновлять плагины отдельно от основного приложения? Если нет, достаточно реестра, сгенерированного сборщиком. Если плагинные сборки должны обновляться и развёртываться независимо, Module Federation даёт готовый протокол. Собственный механизм, как в DSH, имеет смысл, когда готовый протокол не поддерживает нужный формат артефактов или модель загрузки и проект готов сам поддерживать эту систему.
Ни один из этих механизмов не отменяет задачи Cordis. Они определяют, как в браузере появляется экспорт модуля. Cordis определяет, как этот экспорт становится работающим компонентом и как его влияние на систему исчезает при выгрузке.
Способ доставки кода сам по себе не определяет силу границы между компонентами. Чтобы понять, что Cordis даёт и чего не может дать внутри одного процесса, полезно сравнение с микросервисами.
Микросервис получает сильную границу благодаря отдельному процессу. Если процесс остановлен, операционная система освобождает его память и локальные ресурсы. Не требуется отдельно выяснять, какие objects или timers ему принадлежали.
Граница процесса — дорогой, но надёжный механизм владения.
Микросервис идентичность = процесс взаимодействие = сеть жизненный цикл = развёртывание / остановка
Cordis пытается получить часть независимости жизненного цикла внутри одного процесса:
Плагин Cordis идентичность = Fiber взаимодействие = сервисы / события жизненный цикл = монтирование / выгрузка
Однако сходство заканчивается там, где начинается изоляция.
Плагин Cordis работает в том же процессе и в той же среде выполнения, что и остальные компоненты. Поэтому Cordis сам по себе не изолирует его сбои: бесконечный цикл может остановить обработку во всём процессе, а утечка памяти — исчерпать доступную процессу память. Такой плагин также нельзя масштабировать отдельно от приложения или реализовать на полностью независимом технологическом стеке, как микросервис.
Зато композиция внутри одного процесса:
значительно дешевле;
сохраняет локальные вызовы;
подходит для небольших функциональных возможностей;
подходит для множества относительно небольших компонентов;
не требует превращать каждую архитектурную границу в сетевую.
Поэтому Cordis занимает пространство между обычным модулем и микросервисом:
модуль ↓ плагин Cordis ↓ микросервис
У каждого уровня своя цена и сила границы.
Техническая история Kubernetes показывает, зачем Kubernetes понадобились явная модель состояния и независимые асинхронные контроллеры. Это помогает понять две ключевые идеи Cordis Loader: согласование желаемого и фактического состояния, а также явное владение ресурсами. Речь не о контейнерах или управлении кластером, а о модели управления композицией внутри работающего приложения.
Контроллер Kubernetes сравнивает фактическое состояние системы с желаемым. Пользователь описывает результат, а контроллер определяет, какие действия приблизят систему к нему, а затем вновь проверяет состояние.
Кроме того, Kubernetes явно описывает отношения владения и превращает удаление в управляемый процесс. Разбор полного жизненного цикла объекта показывает, как ownerReferences связывает зависимый ресурс с его владельцем, а финализаторы задерживают физическое удаление, пока контроллер не завершит необходимую очистку.
Cordis Loader применяет похожий принцип внутри приложения.
Вместо последовательности команд:
загрузить A загрузить B остановить C перезапустить D
задаётся желаемая композиция:
A B D-v2
Cordis Loader сравнивает заданную композицию с текущим деревом плагинов и определяет, какие плагины нужно подключить, обновить или выгрузить. Авторы научной работы описывают этот механизм как декларативный загрузчик компонентов: разработчик задаёт нужную конфигурацию, система приводит текущее состояние в соответствие с ней и может заменять модули без полной перезагрузки приложения.
Плагинный контракт особенно ценен не потому, что AI-код обязательно хуже человеческого. Coding agents делают новые реализации дешёвыми, но стоимость их интеграции, проверки и безопасного удаления остаётся высокой.
Если агент редактирует произвольные части приложения, изменение может затронуть код запуска, маршруты, реестры и общее состояние. Плагинный контракт локализует такое изменение: агент создаёт компонент с явными требованиями, возможностями и конфигурацией, а хост подключает его через заранее определённый протокол композиции.
Получается важный баланс:
Свобода генерации кода внутри компонента сочетается со строгостью правил его интеграции.
В этом смысле Cordis можно рассматривать как API между AI и архитектурой приложения. Подобно тому как API инструмента ограничивает способы, которыми AI-агент может обращаться к внешней системе, плагинный контракт определяет, как созданный агентом компонент встраивается в архитектуру приложения.

Недетерминированность AI не должна распространяться на архитектуру. Плагинный контракт не гарантирует, что созданный код будет правильным, но делает его встраивание предсказуемым: позволяет проверить зависимости, связывает побочные эффекты с компонентом и управляет освобождением ресурсов.
DeepSeek Harness предоставляет опциональный пакет @deepseek-ai/dsh-tool-cordis. При явном подключении он позволяет AI-агенту временно изменять состав плагинов прямо во время работы приложения.
Пакет регистрирует инструменты управления Cordis. С их помощью агент может просмотреть текущее дерево Cordis, создать временный плагин в памяти, подключить его, а затем остановить или удалить. При выгрузке Cordis освобождает ресурсы, чья очистка зарегистрирована через Context: удаляет зарегистрированные плагином инструменты, обработчики событий и сервисы, останавливает управляемые таймеры и выполняет другие disposer’ы. Выгрузка завершается только после окончания этой очистки. Плагин не создаёт файлы проекта и не меняет его конфигурацию; его runtime-определение исчезает после перезапуска DeepSeek Harness.
Иными словами, пользователь описывает нужную возможность в промпте, а LLM формирует JavaScript и при вызове cordis_define передаёт его строкой в поле code.host для серверной части плагина или в code.client — для части, выполняемой в браузере. Этот вызов проверяет синтаксис и регистрирует пакет в памяти серверной части приложения. Затем cordis_run запускает серверную и, при наличии, браузерную часть плагина. Если эксперимент оказался удачным, плагин можно оформить как постоянную часть проекта; если нет — удалить.
В таком режиме плагин можно воспринимать как архитектурную гипотезу:

Это уже не просто hot reload. Это спекулятивная архитектура, в которой сама композиция приложения становится предметом эксперимента. Фактически основное приложение задаёт пространство архитектурных степеней свободы — набор доступных сервисов, событий и точек расширения, внутри которого LLM может предлагать и проверять новые варианты композиции. Инициатором такого эксперимента может быть как разработчик, так и пользователь системы.
Управляемый жизненный цикл позволяет временно загружать плагины, наблюдать за их работой и затем выгружать. Однако это не означает, что Cordis видит все их эффекты, умеет обратить любое действие или обеспечивает безопасную изоляцию. Поэтому границы Cordis необходимо сформулировать явно.
Cordis управляет зависимостями, владением эффектами и жизненным циклом, но не изолирует недоверенный код. Плагин работает внутри процесса приложения и может обращаться к возможностям, доступным ему как обычному JavaScript-коду. Ограничение процессорного времени, памяти, импорта модулей, файловой системы и сети требует отдельного sandbox- или capability-слоя.
Cordis видит:
ctx.on(...) ctx.plugin(...) ctx.effect(...)
Но плагин способен обойти Context:
globalThis.value = 1 setInterval(...) externalLibrary.register(...) fetch(...) sendEmail(...)
Runtime не может автоматически узнать, кому принадлежит произвольное изменение и как его отменить.
Фундаментальное правило:
владение требует опосредования
Чтобы Cordis мог связать эффект с плагином и отменить его при выгрузке, эффект нужно создать через Context или явно зарегистрировать с помощью ctx.effect().
Локальные регистрации обычно можно удалить:
зарегистрировать обработчик ↔ удалить обработчик добавить маршрут ↔ удалить маршрут предоставить сервис ↔ отозвать сервис
Ресурсы можно завершить:
открыть соединение ↔ закрыть соединение запустить таймер ↔ остановить таймер
Но действия во внешнем мире часто необратимы:
отправлено письмо; совершён платёж; опубликовано сообщение; изменена удалённая база.
Для таких действий нужны транзакции, компенсирующие операции, идемпотентность, согласования и протоколы фиксации изменений. Временная композируемость не гарантирует, что внешнюю систему можно вернуть в прежнее состояние.
За управляемую композицию приходится платить новым уровнем сложности. Часть сложности уходит из кода компонентов, но переезжает в runtime.
Cordis добавляет:
динамический граф зависимостей;
пространство имён сервисов;
собственный жизненный цикл;
новые состояния вроде PENDING и UNLOADING;
необходимость диагностировать причины активации и выгрузки;
правила совместимости версий;
вопросы миграции состояния;
ошибки внутри disposers;
стоимость повторного запуска зависимых компонентов.
В простом приложении:
import { search } from "./search"; search();
почти наверняка лучше, чем полноценный plugin runtime.
Плагинная модель оправдана, когда системе действительно нужны:
расширения клиентской и серверной частей; вариативная композиция; заменяемые поставщики; конфигурация во время выполнения; горячая замена; длительно живущий процесс; возможности, создаваемые AI.
То есть принцип Everything is a Plugin не следует превращать в универсальную догму. Он полезен там, где глобальная связанность обходится дороже, чем механизм управления композицией компонентов.
Cordis предлагает смотреть на приложение не как на однажды собранный граф модулей, а как на живую композицию компонентов. Зависимости, эффекты и жизненный цикл становятся частью общей исполняемой модели для клиента и сервера.
В системах с AI это создаёт новый уровень взаимодействия с архитектурой: приложение задаёт допустимое пространство изменений, внутри которого разработчик или пользователь через AI-агента может экспериментировать с его композицией.
Поэтому главный вклад Cordis можно сформулировать так:
Cordis превращает систему плагинов из механизма расширения программы в протокол, управляющий появлением, заменой и исчезновением её компонентов во время работы.