← Назад в блог

Почему Cradle больше не десктоп: цена Electron для одного разработчика

Open Cradle начинался как десктопное приложение на Electron. Весь движок сидел внутри: node-llama-cpp для инференса, better-sqlite3 и sqlite-vec для хранения и векторного поиска, операторский triage с двухслойной оценкой рисков. Логика была прямая: данные не покидают машину оператора, потому что приложение и есть эта машина.

Сейчас это не так. Ниже — почему я отказался от Electron, во что он обходился и что пришло на замену.

Что изменилось в постановке

Первая версия отвечала на вопрос «как дать оператору локального ассистента». Текущая отвечает на другой: как поставить control layer между агентами, данными, инструментами и людьми. Формула, вокруг которой строится продукт:

LLM proposes → Evidence proves → Ontology constrains → Policy permits → Cradle executes + records.

Свой multi-agent framework я не строю. Агентов делают внешние инструменты, Cradle задаёт границы: что агент может предложить, что из предложенного разрешено, что в итоге исполнено и записано.

Честная оговорка про онтологии. Это направление архитектуры, а не работающая функция. Движок верификации есть, реестр верификаторов пока пуст. Если где-то прочитаете, что Cradle «ограничивает действия онтологией», это описание цели, а не текущего состояния.

Из новой постановки десктоп перестал следовать. Control layer — это сервер, а не окно на ноутбуке.

Что стоил Electron

Пока продукт был десктопом, издержки казались платой за локальность. Для одного разработчика они складывались так:

  • Подпись и notarization под macOS. Каждый релиз — 3–10 минут ожидания ответа от notary service, и временами процесс просто зависает.
  • macOS-runner в GitHub Actions считается с множителем 10× по минутам. Поэтому mac-сборка делалась локально, а Windows NSIS — в CI.
  • Нативные модули пересобираются между ABI. Тесты бегут под Node, приложение — под Electron, у них разный ABI. better-sqlite3 и node-llama-cpp пересобирались туда и обратно; скрипт тестов до сих пор начинается с pnpm rebuild better-sqlite3.
  • sqlite-vec — платформенные бинарники, свои для каждой ОС и архитектуры.
  • main-процесс без hot reload. Любая правка в IPC или движке — перезапуск приложения.
  • Три платформы = три релизных конвейера, у каждого своя подпись, свой инсталлятор и свои способы упасть.

Ни один пункт сам по себе не фатален. Вместе они съедали время, которого у одного человека на ранней стадии продукта нет.

Что вместо

cradle-server — headless-процесс. Ставится локально или на GPU-сервер. UI — веб-консоль: тот же код интерфейса, который жил в рендерере Electron, но поверх HttpBackend вместо IPC-моста.

Ключевой ход — реестр RPC-хендлеров. Тело хендлера пишется один раз. В десктопе его привязывает к IPC функция bindRpcToIpc, на сервере — маршрут POST /api/v1/rpc/:channel. События, которые раньше летели через webContents.send, теперь идут по SSE.

// One handler body, two transports.
registerRpc('agents:list', {
  handler: (_args, ctx) => listAgents(ctx.projectId),
  scopes: ['agents:read']
})

// Desktop: ipcMain.handle('agents:list', ...) — via bindRpcToIpc('agents')
// Server:  POST /api/v1/rpc/agents:list      — via registerRpcRoutes(server)
// Events:  webContents.send(...)             — now SSE on GET /api/v1/events

Маршрут сначала проверяет scopes ключа и только потом говорит, существует ли канал: анонимный вызов всегда получает 401, а не подсказку о составе API.

API-ключ точки хранится только в браузере. Консоль кладёт его в localStorage, в учётной записи остаются имя и URL точки. Сервер, на котором крутится консоль, ключей к вашим cradle-server не видит.

Полностью локальный режим

pnpm console:local

Одна команда поднимает сайт с консолью на localhost:3000 и cradle-server на :31416. Без Firebase, без почты, без внешних сервисов: код входа печатается в терминале, админский API-ключ — в логе сервера при первом старте. Это и есть режим «всё на одной машине», с которого десктоп когда-то начинался, только без Electron.

Три вида точек

В настройках консоли переключаются точки, к которым она подключена:

  1. Локальная машина — cradle-server на ноутбуке, модели GGUF.
  2. Свой GPU-сервер — тот же cradle-server, вынесенный на железо в контуре.
  3. Тестовый EC2 worker с моделями Bedrock — только для экспериментов. GPU-инстанс в EC2 оказался слишком дорогим, а Bedrock позволяет гонять сценарии без своего железа. Для закрытого контура этот вариант не подходит по определению.

Ограничения

  • Electron из репозитория ещё не удалён. Forge, preload и IPC-привязки живут рядом с сервером, пока консоль не достигнет паритета по функциям.
  • Сайт на https не может ходить на http://<ip>: браузер режет mixed content. http://localhost браузеры пускают, а удалённой точке нужен TLS.
  • Онтологии — см. выше. Направление, не функция.

Источники

  • Electron, Code Signing — требования к подписи и notarization под macOS.
  • Electron, Native Node Modules — почему нативные модули пересобираются под ABI Electron.
  • GitHub Docs, About billing for GitHub Actions — множители минут для Windows и macOS runner'ов.

Эта статья создана в гибридном формате человек + ИИ. Я задаю направление и тезисы, ИИ помогает с текстом, я редактирую и проверяю. Ответственность за содержание — моя.

← Назад в блог