Skip to content
Apothem
Блог

Внутри устройства мультиокруженческого адаптера

Технический разбор протокола HarnessAdapter в Apothem и трёх классов адаптеров, которые материализуют один профиль в семнадцать окружений.

Apothem материализует один общий профиль в семнадцать нативных для окружения конфигураций. Семнадцать целей, семнадцать форматов поставщиков, семнадцать мест установки — и один однородный интерфейс, на который опираются и оператор, и CLI. Этот пост объясняет, как слой адаптеров удерживает этот контракт вместе.

Протокол HarnessAdapter

Каждый адаптер окружения реализует структурный протокол HarnessAdapter. Протокол — это шов между CLI и деталями, специфичными для поставщика. Он объявляет операции, которые вызывает CLI, и метаданные, которые CLI читает:

  • name — зарегистрированный идентификатор адаптера.
  • output_path — куда на диск попадает материализованная конфигурация.
  • install — отрендерить и записать нативную для окружения конфигурацию.
  • update — повторно отрендерить нативную для окружения конфигурацию после изменения профиля.
  • uninstall — удалить материализованную конфигурацию, оставив общий профиль нетронутым.
  • is_installed — сообщить, присутствует ли конфигурация окружения.
  • verify — обнаружить расхождение между профилем и конфигурацией на диске.

Поскольку контракт — это typing.Protocol, адаптер удовлетворяет ему по форме, а не по наследованию. Во время выполнения CLI разрешает каждый адаптер из статического реестра — таблицы HARNESS_REGISTRY в src/apothem/lib/harness_registry_data.py, которую harness_registry.py объявляет авторитетной во время выполнения, — поэтому чистый чекаут загружает каждый адаптер без каких-либо установленных метаданных точек входа в sys.path. Таблица [project.entry-points."apothem.harnesses"] в pyproject.toml и обход файловой системы discover_adapters() — это проверка соответствия на паритет, а не резолвер времени выполнения: тест утверждает, что обнаруженный набор адаптеров равен статическому реестру, так что конвенционально-корректный, но незарегистрированный адаптер проявляется как регрессия покрытия, а не разрешается молча. Таким образом, добавить окружение означает зарегистрировать его в данных реестра (и отразить в таблице точек входа ради паритета упаковки) — тест на паритет явно проваливается, если пропущено любое из двух.

Один подпакет на адаптер

Каждый адаптер живёт в собственном подпакете под src/apothem/harnesses/<name>/:

  • __init__.py — класс <Name>Adapter, который реализует протокол и делегирует соседним модулям действий.
  • install.py / uninstall.py / update.py / verify.py — реализации по каждому действию, которые класс адаптера импортирует и вызывает.
  • materializer.py — необязательный materialize_native_config(profile) -> str, который рендерит общий профиль в нативный для окружения текст конфигурации.

Разделение каждого действия на собственный модуль держит класс адаптера тонким: это диспетчер, который сводит методы протокола с функциями действий. Читатель, открывающий verify.py, видит ровно логику обнаружения расхождения для одного этого окружения, без шума установки или удаления, через который пришлось бы продираться.

Три класса адаптеров

Семнадцать окружений потребляют конфигурацию не все одинаково. Apothem сортирует их на три класса по тому, сколько преобразований нужно профилю, прежде чем он достигнет окружения.

Класс I — сырое распространение

Окружение потребляет те же артефакты, которые Apothem уже пишет — плоские правила .md, agents, хуки — без шага рендеринга. Адаптер копирует сырые шаблоны в дерево конфигурации окружения. Здесь нет materializer.py, потому что ничего не рендерится; артефакты профиля и есть вывод.

Класс II-A — сырое плюс шаблоны поставщика

Окружение потребляет сырые артефакты Apothem И набор специфичных для поставщика файлов-шаблонов (файлы настроек, манифеста или каркаса, которые требуются окружению). Адаптер копирует сырые артефакты и шаблоны поставщика вместе. Шаблоны поставщика живут рядом с адаптером в каталоге templates/.

Класс II-B — рендеринг из профиля

Окружение хочет один нативный файл конфигурации, отрендеренный из структурированных полей общего профиля. materializer.py адаптера читает профиль и выдаёт нативный для окружения текст — config.yaml для Hermes, openclaw.json для Open-Claw, opencode.json для OpenCode или settings.json для Qwen Code. Класс II-B — это место, где сосредоточена работа по согласованию форматов; разбор конвергенции конвенций рассматривает её подробно.

claude-code: адаптер полной поверхности

У адаптера claude-code самая широкая поверхность конфигурации — правила, agents, хуки, output-стили, настройки и каталоги конвенций. Он принадлежит к Классу II-A: он распространяет сырые шаблоны Apothem и уплощает каталоги конвенций, а также поставляет шаблоны настроек поставщика под src/apothem/harnesses/claude_code/templates/. Он не несёт materializer.py — он не рендерит нативный файл конфигурации из профиля; он распространяет артефакты и каталоги конвенций напрямую.

Одна намеренная граница: адаптер claude-code управляет только управляемым блоком, ограниченным служебными маркерами, внутри CLAUDE.md; текст вне этого блока сохраняется. Остальная часть этого файла — территория, принадлежащая оператору. Apothem материализует окружающую поверхность конфигурации — правила, хуки, настройки — и сохраняет прозу оператора вне этого блока дословно. Адаптер, который переписывал бы весь файл, стирал бы написанные вручную инструкции при каждом apothem install; этот адаптер переписывает только свой управляемый блок.

Почему однородность важна

Протокол даёт оператору одну ментальную модель через семнадцать инструментов. apothem install, apothem verify, apothem uninstall ведут себя одинаково, рендерит ли цель один файл или уплощает дерево каталогов. Различия поставщиков живут внутри подпакетов адаптеров, за протоколом, там, где им и место.

Прочтите ориентированный на оператора справочник в Окружениях и модель профиля в Концепциях.

On this page