Как мы скрестили Go, Rust и MCP, чтобы построить масштабируемый оркестратор ИИ-агентов
- вторник, 1 сентября 2026 г. в 00:00:10
Все сейчас пилят ИИ-агентов, но когда их становится больше трех, начинается ад. Протокол MCP (Model Context Protocol) частично решает проблему, но готовых инструментов для глобальной оркестрации, настройки прав (RBAC) и визуального контроля логики на рынке просто нет. Мы задолбались собирать костыли и решили написать свой кастомный хаб
Когда мы говорим об оркестрации ИИ агентов важным становится конкурентность, предсказуемое потребление памяти и скорость сетевого I/O.
Легковесные горутины (Goroutines) позволяют нам держать тысячи параллельных соединений (сессий с агентами и клиентами) без оверхеда, который возник бы в Node. js или Python.
Строгая типизация спасает, когда нужно жестко контролировать структуры данных ролевой модели (RBAC) при передаче контекста.
Быстрый компилятор и простой деплой в один бинарник — идеальное решение для инди-разработки.
Для описания логики взаимодействия агентов нам нужен был декларированный подход. Поэтому мы разработали движок Flow_json где весь граф выполнения (DAG — Directed Acyclic Graph) описывается обычным JSON-файлом. Поэтому мы использовали Json, а не изобретали велосипед или тяжёлыми YAML конфигурациями:
У меня фронтед написан на Vue 3 и в нем Json работает «из коробки» визуальный графический редактор сценариев просто парсит это в json и при сохранении отдает валидный бекенду
Динамическая валидация: Go позволяет на лету валидировать входящие JSON-схемы через кастомные структуры, проверяя связи между нодами (агентами, триггерами и инструментами) до запуска пайплайна.
Гибкость LLM: ИИ-агенты по своей природе отлично генерируют и парсят JSON (через Structured Outputs). Движок flow_json позволяет агенту самому модифицировать или динамически собирать подзадачи для других агентов прямо в процессе выполнения потока.
Это самая сложная инженерная часть проекта. Спецификация Model Context Protocol (MCP) требует быстрой и строгой обработки протокола JSON-RPC 2.0. Писать парсинг и валидацию на Go — долго и чревато ошибками. Мы решили отдать эту низкоуровневую работу Rust, написав модуль agent-hub-mcp.
Как устроена связка через cdylib:
На стороне Rust мы используем мощную экосистему сериализации serde и библиотеки для работы с асинхронным JSON-RPC.
Мы компилируем проект на Rust в динамическую библиотеку (.so / .dylib), используя тип сборки cdylib в Cargo. toml.
Через FFI (Foreign Function Interface) мы экспортируем наружу чистые C-шные функции (с аннотацией #[no_mangle]), которые принимают указатели на строки.
В Go-репозитории через cgo мы подключаем скомпилированный Rust-бинарник и вызываем эти функции напрямую.
Если вы просто начнете передавать строки между Go и Rust через cgo, ваш сервис упадет от Out-of-Memory (OOM) через пять минут. У этих языков принципиально разные подходы: у Go — сборщик мусора (GC), у Rust — модель владения (Ownership). Грабли № 1: Кто освобождает память? Строка, созданная в Go (C. CString), аллоцируется в куче Си. Её обязательно нужно очищать вручную на стороне Go через defer C. free(unsafe. Pointer(ptr)).
Грабли № 2: Возврат данных из Rust. Когда Rust обрабатывает JSON-RPC и возвращает строку-ответ в Go, Go не может просто забрать этот указатель, так как аллокатор Rust (jemalloc или std) считает эту память своей. Если Rust очистит её раньше времени — Go поймает Segmentation Fault. Если не очистит — будет утечка.
Но я был бы не я, если не подумал бы как это исправить и вот как я решил эту проблему. Я выделил два правила:
Данные переданные из Go в Rust, копируются в память Rust после чего Go сразу же освобождает свой C-указатель.
Для возврата данных из Rust мы написали специальную функцию free_rust_string(ptr), которую Go вызывает принудительно после того, как скопировал результат в свои внутренние структуры. Под нагрузкой мы прогнали это через valgrind и pprof — утечек нет, профиль памяти стабилен.
Фронтенд agent-hub-ui написан на Vue 3 (Composition API). Наша главная задача здесь — дать разработчику максимальную прозрачность над тем, что «думает» и делает ИИ в данный момент. И тут как раз вступает в дело наш виртуальный редактор потоков (Flow Editor): Интерфейс представляет собой интерактивный холст. Каждая нода на холсте — это либо конкретный ИИ-агент, либо MCP-инструмент (например, доступ к базе данных, файловой системе или внешнему API), либо логический оператор.
Я связал ноды реактивными линиями чтобы пользователь вживую мог перетащить мышкой связь между выходом одного агент к выходу другого.
Под капотом Vue 3 реактивно отслеживает изменения координат и связей объектов и формирует тот самый древовидный граф, который при сохранении улетает на бэкенд в формате flow_json.(Тот самый о котором я говорил выше).
Живой чат для дебага в реальном времени: Обычные логи в консоли при отладке агентов читать невозможно — там терабайты JSON-RPC логов. Мы вывели это в UI в виде красивого мессенджера.
Бэкенд через WebSockets транслирует каждый шаг выполнения потока.
В живом чате видно не просто финальный ответ, а весь внутренний диалог системы: «Агент А запросил инструмент Б» -> «Инструмент Б вернул ошибку доступа» -> «Агент А перефразировал запрос».
Любой шаг можно поставить на паузу (breakpoint), переписать промпт агенту прямо на лету в чате и пустить выполнение дальше. Это экономит кучу денег на токенах API и часы времени на дебаг. Для тех кто заинтересовался вот ссылка на github проекта: https://github. com/orgs/Zettaverse
P.S. Буду благодарен каждой звезде