Большая часть веб-софта, с которым мы имеем дело сегодня, статична. У разработчиков ограничено время и внимание, поэтому усилия концентрируются на функциях, которые нужны наибольшей группе пользователей. Верхушка кривой спроса неплохо закрыта существующим ПО, но остаётся длинный хвост неудовлетворённых потребностей — разный для каждого пользователя.

Даже если бы разработчики были невероятно мотивированы впихнуть каждую возможную фичу, интерфейс не может усложняться бесконечно, не теряя юзабильности. Каждая дополнительная функция усложняет продукт для всех остальных пользователей. Если рынок для конкретной фичи мал, она может активно ухудшать продукт для всех, кому она не нужна.
На этом фоне рост LLM-ассистированного программирования стал реальным подспорьем для всех, чьи запросы попадали в этот длинный хвост.
Софт стал каким-то… размазанным
Стало очевидно, что LLM отлично справляются со сборкой Software for One — персональных приложений, обходящих стороной всю сложность и подотчётность корпоративного ПО и заточенных под рабочий процесс одного конкретного человека.
Пит Кумен из Y Combinator видит возможность для того, что там называют Small Software. Похоже, в этой идее что-то есть.
Агенты сильно упрощают создание персональных инструментов для себя или своей команды. Но развернуть, защитить и поделиться таким софтом всё ещё гораздо сложнее, чем его создать. Облако, спроектированное под small software, могло бы убрать эту сложность и сделать кастомные инструменты такими же простыми в передаче коллеге, как Google Doc.
Pi — хороший пример того, что можно назвать LLM-native софтом: проверенное временем ядро, но почти бесконечно расширяемое просто по запросу, причём пользователи могут делиться своими доработками друг с другом. За последний год пользователи внезапно обрели способность "проговаривать" код в существование. Большинство существующего софта не умеет этим пользоваться. Pi строится именно вокруг этой идеи.

Похоже, мы начнём чаще видеть софт, следующий этому паттерну самостоятельного расширения. Однако большинство существующих примеров расширяемого ПО — локальные: AI-агенты, IDE для разработчиков, моды для видеоигр, аддоны для Blender, расширения для CAD. Это, как правило, профессиональные инструменты с высоким порогом входа.
Веб — самая успешная система дистрибуции ПО в мире. Не стоит оставлять его за бортом.
Гипотеза в том, что для расширяемого ПО в вебе появилась новая возможность. LLM радикально снижают стоимость написания расширений, а современные примитивы песочниц снижают стоимость развёртывания и обеспечивают надёжные границы безопасности. Приложение можно построить как прочное, подотчётное ядро и позволить пользователям безопасно расширять его во многих направлениях, поручая LLM дописывать недостающие части. Пользователям можно дать суперспособности.
Дисклеймер: автор работает в Cloudflare, и плотное знакомство с текстами Кентона Варды во многом сформировало изложенные здесь взгляды. Ближе к концу будет аргументироваться, что Dynamic Workers особенно хорошо подходят под эту модель, но сперва разбор пройдёт по нескольким альтернативам.
Как это могло бы выглядеть?
Многие веб-системы сегодня полагаются на вебхуки, чтобы позволить пользователю реагировать на изменения в приложении. Это ~как-то работает, но задаёт очень высокую планку для расширения: нужно построить и обслуживать полностью отдельный сервис плюс разбираться с любыми проблемами доставки.
Хочется иметь возможность подключиться к обновлениям записи и вставить свою логику. "Когда я прикрепляю этот тег к записи, запусти мою функцию". "Делай это действие за меня по ежедневному крону".
На самом деле, хочется вообще не думать об этом. Хочется просто сказать своему приложению для отложенного чтения:
- Пожалуйста, отправляй каждую статью, которую я добавляю в избранное объёмом более 4000 слов, на мою
<читалку по выбору> - Ищи новые статьи, публикуемые на arxiv в области
<моя специализация>, каждую неделю, добавляй наверху своё резюме о том, как это связано с моей работой, и помечай тегом<тег> - Стандартный алгоритм полностью коверкает
<сайт, который я часто читаю>. Возьми несколько примеров и сделай для него кастомный парсер.
А дальше робот выдавит нужные крупицы кода, подключит их к нужным точкам расширения и заставит это работать. Заодно должна быть возможность поделиться созданным с кем-то ещё, кому нужна та же фича.1
Вот ещё несколько областей, где хотелось бы видеть LLM-native подход к расширениям.
AI-агенты
Это самый очевидный пример. pi, deepseek и opencode экспериментируют в этом пространстве.
Вместо того чтобы добавлять каждую новую идею в ядро, Pi предоставляет стабильные точки подключения для инструментов, команд, событий и UI, так что запрос превращается в маленькое TypeScript-расширение, которое перезагружается на месте. Такие расширения затем можно упаковать в пакеты, которыми делятся, позволяя экосистеме впитывать длинный хвост идей без раздувания самого харнесса.

Однако аудитория таких инструментов, по крайней мере сейчас, довольно узкая. Нужно быть готовым запускать кастомный софт на своей локальной машине. В корпоративной среде организация должна быть готова к тому, что сотрудник запускает код, который никто никогда не проверял и не проверит. Если Pi не запущен в отдельной песочнице, расширения Pi работают с теми же правами, что и сам Pi.
Инженеры найдут способ, но бухгалтеры, врачи, юристы и тысячи других профессий тоже заслуживают хороших инструментов. Им нужны агенты, которые можно безопасно и легко подстроить под их предметную область и рабочие процессы.
Если стоит задача привлечь к агентам больше людей, это не значит превратить их всех в разработчиков. Это значит подстроить софт под их нужды.
Внутренняя корпоративная платформа
У всех компаний в итоге скапливаются горы данных. Сотрудникам нужно их просматривать, запрашивать, исследовать, сопоставлять с другими данными из другой системы, находить клиентов, столкнувшихся с <проблемой X>, находить клиентов на грани оттока, и ещё миллион вещей.
Многие компании экспериментируют с тем, чтобы позволить увлечённым AI сотрудникам вайб-кодить собственный тулинг и, возможно, деплоить его на PaaS. Направление верное, но это порождает кучу последующих проблем. Когда таких приложений набирается сотни или тысячи, как их поддерживать? Как они получают доступ к нужным данным? Как они получают доступ только к нужным данным? Как проверить, что делает этот софт? Если используются токены доступа, каковы их скоупы? Кто их ротирует? Как убедиться, что клиентские данные не утекают третьей стороне? Как убедиться, что не нарушается GDPR?
Или ещё миллион других вопросов комплаенса и безопасности, о которых приходится беспокоиться реальному бизнесу.
Что если дать сотрудникам место для деплоя кода, где нет токенов авторизации, которые могли бы утечь? Где доступ к данным обрабатывается внутренней командой платформы, которая может гарантировать соблюдение всех требований комплаенса? Дать им пространство для создания собственных автоматизаций или кастомных представлений, но безопасно.2
Спойлер: по сути, это Cloudflare OS.
Платформа поддержки

Значительную часть карьеры пришлось потратить на разбор сложных тикетов поддержки. Неизбежно приходится копаться в дашбордах, искать по логам, вытягивать данные из миллиона разных мест. Хотелось бы создавать расширения, которые выводят в интерфейс поддержки данные о пользователе, открывшем тикет, из конкретной системы. Дать точки подключения, чтобы запускать агентов для первого раунда расследования, ещё до того, как я сам взгляну на тикет. Если есть частые задачи вроде "сбросить конкретную квоту X" — дать возможность добавить кнопку в свой интерфейс, которая это делает.
А ещё дать возможность делиться этим с командой, чтобы все могли помогать друг другу.
Платформа observability

Многие инструменты observability сошлись к одному и тому же набору функций: поиск по логам со столбчатым графиком сверху, водопадное представление для отдельных трейсов, настраиваемые дашборды метрик, возможно, карта сервисов. Кое-кто экспериментирует с новыми визуализациями, особенно на фоне роста популярности агентов.
Заслуженная диаграмма-водопад очень полезна для систем формата запрос/ответ, где важны в основном задержка и успешность выполнения. Многие сейчас имеют дело с системами, которые скорее... с состоянием... или динамические. Современные приложения запускают недетерминированных агентов или durable workflow движки, где одно действие может занимать часы или дни. Спаны трейсов — отличный источник истины, на котором можно строить, но хотелось бы иметь возможность экспериментировать со своими визуализациями (или устанавливать чужие).3
Помимо красивых картинок, хочется встраивать и свою логику:
- произвольные трансформации данных при приёме
- запуск собственных скриптов по алармам: детерминированного кода или собственного агента
- возможность запускать свой код в моменты наибольшего риска: деплои или раскатка feature flag
- если в логах встречается специальный
MyResourceID, превратить его в ссылку, которая ведёт прямо к этому ресурсу на другой платформе
Весь софт, вероятно, должен выглядеть так
Одно из архитектурных изменений в opencode2 — почти всё стало внутренним плагином Их 68 штук, они покрывают встроенных агентов, интеграции, загрузку конфигурации и т.д. Это значит, что можно отключить любое поведение, и заодно мы честно едим свой собственный дог-фуд плагиновых API

Расширяемое ПО в вебе — задача посложнее
Всё вышеописанное прозвучало слишком просто. На деле это далеко не так.
Obsidian — большой фаворит, и как инструмент для ежедневного использования, и как образец софта.
Выглядит как простой markdown-редактор, но парой кликов его можно расширить почти до чего угодно: отслеживать задачи на канбан-доске или превратить заметки в базу данных. Хотите закинуть все заметки в векторную базу для семантического поиска? Пожалуйста! А если хочется пойти дальше, лежащие в основе примитивы веб-интерфейса легко хачить.
Однако за эту мощь приходится платить: модель расширений Obsidian требует доверять каждому устанавливаемому плагину. Плагин в принципе может сделать что угодно. Obsidian борется с этим вызовом безопасности с помощью автоматической и ручной проверки и верификации авторов плагинов.
Для заметочного приложения это, вероятно, правильный компромисс. Он работает, потому что ставки низки, а сообщество относительно небольшое. Но эта модель разваливается, как только требуется такой же уровень расширяемости в софте, хранящем чужие данные: клиентские записи, финансовые транзакции, приватные сообщения. Расширяемость и веб-сервисы всегда были непростым сочетанием.
Исполнение произвольного кода изобилует вызовами безопасности и злоупотреблений. Неполный список:
- Ошибки или бесконечные циклы в коде пользователя не должны никогда обрушивать сервис
- Получив доступ к ключам, кастомные расширения пользователя могут переслать их третьей стороне
- Аналогично, если раскрываются чувствительные данные, нужно убедиться, что их нельзя вывести наружу
- Нужно исключить возможность использовать систему для DoS-атаки
- Нужно исключить, чтобы пользователь случайно не устроил DoS вам самим
- Защита от атак вроде Spectre
- Если люди смогут майнить крипту на бесплатных вычислениях за ваш счёт — они это сделают
- и многое другое…
Но ведь кто-то уже это делал?
Прежде чем списывать эту идею со счетов как невыполнимую, стоит вспомнить чёткий пример, где подобная расширяемость в вебе сработала в огромном масштабе: Salesforce.

Да, та самая Salesforce. И занимается этим с 2007 года. (Для сравнения: AWS S3 и EC2 запустились в 2006-м.)

Спросите большинство технарей, что такое Salesforce, и получите либо пустой взгляд, либо что-то вроде "разве это не CRM?". На деле точнее описать Salesforce как огромную мультитенантную программируемую платформу. В зарождающуюся эпоху облаков это шло вразрез с трендом: без контейнеров, заставляя людей писать на странном, кастомном Java-подобном языке Apex.
Однако с ростом serverless эта платформа начинает выглядеть куда более знакомо. Вот несколько примеров:
Если нужно выставить кастомный эндпоинт, это делается парой строк кода. Платформа сама берёт на себя роутинг, аутентификацию, изоляцию тенантов, исполнение. Веб-сервер деплоить не нужно. Если прищуриться, в этом можно увидеть предшественника современного serverless.
@RestResource(urlMapping='/customer-health')
global with sharing class CustomerHealthApi {
@HttpGet
global static Account getCustomer() {
String accountId =
RestContext.request.params.get('accountId');
return [
SELECT Id, Name, Health_Score__c, Renewal_Date__c
FROM Account
WHERE Id = :accountId
WITH USER_MODE
LIMIT 1
];
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const accountId = new URL(request.url).searchParams.get("accountId");
const account = await env.ACCOUNTS.prepare(`
SELECT id, name, health_score, renewal_date
FROM accounts
WHERE id = ?
LIMIT 1
`)
.bind(accountId)
.first();
return Response.json(account);
},
} satisfies ExportedHandler<Env>;
Или как насчёт запуска кастомной логики по расписанию?:
public class RenewalScanner implements Schedulable {
public void execute(SchedulableContext context) {
List<Account> accounts = [
SELECT Id, Needs_Attention__c
FROM Account
WHERE Renewal_Date__c = NEXT_N_DAYS:30
WITH USER_MODE
];
for (Account account : accounts) {
account.Needs_Attention__c = true;
}
update as user accounts;
}
}
// Schedule it to run daily at 2 a.m.:
System.schedule(
'Check upcoming renewals',
'0 0 2 * * ?',
new RenewalScanner()
);
export default {
async scheduled(controller: ScheduledController, env: Env) {
await env.ACCOUNTS.prepare(`
UPDATE accounts
SET needs_attention = TRUE
WHERE renewal_date BETWEEN date('now')
AND date('now', '+30 days')
`).run();
},
} satisfies ExportedHandler<Env>;
{
"name": "renewal-scanner",
/* ... */
"triggers": {
"crons": ["0 2 * * *"]
},
}
Есть и более высокоуровневые примитивы, позволяющие собрать кастомное приложение мышью, но в основе своей Salesforce безопасно исполняет пользовательскую логику прямо в ответ на события приложения, в рамках транзакций, позволяя закодировать особенности вашего бизнеса в их приложении.
Два десятилетия назад у Salesforce не было толком вариантов, как дёшево запускать код в песочнице от имени пользователей, поэтому они построили компилятор, систему типов, рантайм, стандартную библиотеку, отладчик, встроили SQL прямо в язык, добавили кучу хитрых трюков с базой данных и многое другое, а затем выстроили целую образовательную экосистему. Проблемы, которые это решало для бизнеса, оказались достаточно ценными, чтобы оправдать найм людей, специализирующихся именно на их платформе разработки.
Их опыт может вдохновлять, не требуя точного копирования. Сейчас, в 2026 году, вариантов гораздо больше, так что стоит разобраться, какие технические требования нужны для построения чего-то подобного, а затем — какие технологии сюда подходят.
Новый примитив
Нужен примитив, вокруг которого строить эту расширяемость. Чтобы это заработало, он должен обладать несколькими свойствами.
Экономичный в исполнении
Если планируется, что тысячи или миллионы пользователей запускают фрагменты кастомного кода, идея поднимать отдельный контейнер на каждого пользователя не годится. Стоимость должна быть ~$0, когда код не исполняется, а каждое исполнение в идеале должно стоить доли копейки.
К этому добавляется стоимость сборки или компиляции, хранения собранных артефактов, сбора логов и прочего. Особенно на фоне цен на RAM в 2026-м, объём затрат памяти на обработку запроса во многом определит, сколько пользователей можно уместить на одной машине.
Быстрый холодный старт
Все хотят быстрые веб-сервисы, так что если пользовательский код исполняется на критическом пути обработки запроса, ждать больше минуты, пока поднимется контейнер, недопустимо. В идеале холодный старт измеряется единицами миллисекунд.
Если предлагаются только расширения, реагирующие на события или работающие по расписанию, время старта может быть чуть выше без проблем.
Контроль над лимитами
Пользователи платформ делают всевозможные странные, пограничные вещи. Одна из любимых историй от инженера Heroku: кто-то опубликовал очень популярный гайд для новичков, который предлагал задеплоить такое Python-приложение:
while True:
print("hello world!");
С точки зрения системы внезапно появляется совершенно новое приложение, которое немедленно начинает извергать миллионы строк логов в секунду без остановки, а пользователь ожидает, что команда tail покажет что-то разумное.
Чтобы защитить систему, нужно уметь навязывать лимиты практически на всё: CPU, память, количество и размер сетевых запросов, размер ответа, объём и частоту логов, и многое другое.
Надёжная граница изоляции
Речь одновременно об изоляции сбоев и изоляции безопасности. Что бы ни делал пользователь: падение, бесконечный цикл, максимально быстрое выделение памяти — это не должно влиять ни на одного другого пользователя.
А активно вредоносный код не должен иметь возможности вырваться наружу или заглянуть к другим тенантам. Сюда же относятся атаки спекулятивного исполнения вроде Spectre.
Позволить коду совершать действия (безопасно)
Кастомный код, который ни на что не может повлиять, бесполезен, так что нужен какой-то контролируемый способ взаимодействия пользовательского кода с внешним миром. В простейшем случае можно смоделировать это как чистую функцию: код пользователя получает данные на входе и отвечает результатом. Без разрешённого I/O и с ограниченным выводом это довольно безопасно, хотя и ограниченно.
export default function shouldWeOrderPizzaTonight(data: Input): boolean {
// consider the options very carefully
const haveFoodAtHome = data.fridge.hasIngredients;
const haveEnergy = data.body.checkCapacity;
const haveTime = !data.schedule.isTight;
// return haveFoodAtHome && haveEnergy && haveTime;
// we don't believe in data-driven decision making in this household
return true;
}
Если нужно раскрыть пользователю больше возможностей, всё усложняется. Когда собственный код обращается к API, обычно к запросу добавляется какой-нибудь API-ключ:
const response = await fetch(api, {
headers: {
Authorization: `Bearer ${env.API_KEY}`,
},
});
Но такая гибкость опасна! Вредоносный код может немедленно слить эти данные, отправив их POST-запросом третьей стороне. Даже просто предоставление сырого fetch означает, что пользователь может использовать вашу инфраструктуру для DoS-атаки, если захочет.
Самое распространённое сегодня решение — прокси. Пользователю выдаётся непрозрачный токен, значимый только для прокси. Прокси проверяет запрос, а затем заменяет непрозрачный токен реальным ключом перед пересылкой запроса к месту назначения. Прокси также может навязывать allowlist разрешённых адресов назначения и лимиты частоты запросов. Это строго лучше сырого fetch, но всё ещё имеет проблемы.
const response = await fetch(apiViaProxy, {
headers: {
Authorization: `Bearer REPLACE_THIS_WITH_MY_API_KEY_IN_PROXY`,
},
});
Может понадобиться ограничить код лишь подмножеством возможностей API, что требует очень тонкой авторизации, которую большинство API не предлагают. Последствия могут быть весьма плачевными, если API даёт слишком много прав или содержит непредвиденные уязвимости.
Даже если сервис предлагает тонкую детализацию прав, например возможность читать почту, это всё равно может быть намного больше доступа, чем хотелось бы дать коду. Если нужно дать доступ только к одному конкретному письму, обычно не существует токена, который позволял бы только это.
Можно попытаться навязать это в прокси, но тогда придётся фильтровать все запросы, не соответствующие узкому набору критериев, и поддерживать это в актуальном состоянии по мере эволюции API. Код прокси быстро становится очень сложным. Трудно предугадать всё, что пользователь может сделать. Протестировать эту логику и убедиться в её надёжности — непростая задача.
async function proxyFetch(url: URL, headers: Headers) {
const opaqueToken = headers
.get("Authorization")
?.replace(/^Bearer\s+/i, "");
const grant = await parseToken(opaqueToken);
if (!grant || grant.action !== "read-email") {
throw new Error("Forbidden");
}
const allowedPath =
`/email/v1/users/messages/${encodeURIComponent(grant.messageId)}`;
if (
url.origin !== "https://email.service.com" ||
url.pathname !== allowedPath
) {
throw new Error("Forbidden");
}
const newHeaders = new Headers();
// Forward only explicitly permitted headers.
for (const name of ["accept", "if-none-match"]) {
const value = headers.get(name);
if (value !== null) {
newHeaders.set(name, value);
}
}
// Replace the opaque token with the real credential.
newHeaders.set("Authorization", `Bearer ${EMAIL_API_KEY}`);
return fetch(url, { headers: newHeaders });
}
И это фильтрующая логика лишь для одной операции на одном эндпоинте. В общем случае начинать с большого объёма прав, а затем пытаться точно их урезать — сложная задача.
Лучший способ — выдать недоверенному коду узкую capability (право доступа). На высоком уровне capability можно представить как ссылку на конкретную функцию — например, для получения одного заранее одобренного письма:
// Trusted host code
const getApprovedEmail = () => fetchEmailById(123, auth);
// Untrusted extension code
export default async function doSomethingWithAnEmail(
{ getApprovedEmail }: Capabilities,
) {
const email = await getApprovedEmail();
// do something with the email
}
Убрав окружающий (ambient) I/O, получаем, что код может совершать действия только через переданные ему ссылки. Такой паттерн намного проще анализировать. Не нужно возиться со сложной логикой прокси. Ключ API вообще никогда не попадает в недоверенный код. А без какой-либо другой исходящей capability утечь данным просто некуда.4
Как бонус, генерировать логику из TypeScript-описания capabilities для LLM гораздо проще и экономнее по токенам, чем скармливать ей груду JSON-определений OpenAPI.
Кто знаком с IFTTT, знает: сервис не выдаёт ключ Twitter API, он даёт twitter.post_new_tweet(). Вы не получаете полноценный email-клиент, вы получаете email.send_me_email.
Это и есть тот формат, к которому в целом стоит стремиться для безопасного расширяемого ПО.
Какая технология подходит?
Наиболее "прошаренные" в агентах уже заметили: это те же самые свойства, которые нужны от платформы исполнения агентов. И это не совпадение! По сути, это одна и та же задача: как исполнять логику от имени пользователя, которому нельзя доверять.
Пространство решений включает несколько вариантов:
Интерпретатор
Создание собственного языка сработало для Salesforce двадцать лет назад, и этот паттерн работает и сегодня.
Можно использовать готовый встраиваемый интерпретатор вроде Lua или QuickJS, либо написать свой.
V8 Isolates
Доведя подход с интерпретатором до логического конца, рано или поздно захочется перейти на байт-код, добавить JIT, и...
Переход сразу к V8 экономит время. Google вложил колоссальные средства и усилия разработчиков в укрепление движка V8 JavaScript. Cloudflare использует V8 isolates как границу изоляции для Workers, но это не единственный вариант в этой области.
- У Cloudflare есть Dynamic Workers
- У celld реализованы (частично) Dynamic Workers
- У Node есть
isolated-vm - У Rivet есть
secure-exec
MicroVM
Полноценные виртуальные машины эмулируют массу виртуального железа: USB, графику, диски и т.д., что позволяет запускать в них полноценные десктопные окружения, но за это приходится платить. Миллионы строк кода и сложности, которые нужно загрузить и которые занимают ресурсы.
MicroVM урезают это до минимума, запуская сильно ограниченные операционные системы, но выигрыш в том, что они стартуют менее чем за секунду, имеют очень небольшие затраты памяти и надёжную границу изоляции.
У MicroVM больше накладных расходов, чем у других вариантов, но есть свои преимущества:
- POSIX
- потенциал использовать много CPU и RAM
- полноценная ОС, способная запускать бинарники
Если задача в основном сводится к запуску логики, вызову эндпоинтов API, выполнению workflow, накладные расходы этого подхода могут оказаться избыточными. Однако даже если в качестве примитива изоляции выбраны V8 isolates или WASM, MicroVM всё равно могут быть очень полезны для написания, компиляции/сборки и тестирования пользовательских расширений.
Это очень горячая область с массой вариантов:
- Firecracker
libkrun- AWS Lambda MicroVMs
@deno/sandbox- smolvm
- Tensorlake
- Daytona
- Вероятно, ещё миллион вариантов…
WASM + WASI
WebAssembly начинается с чистого листа. Код может исполняться, выделять память, но встроенных модулей для HTTP-запросов или чтения переменных окружения нет. Это делает его привлекательным кандидатом с точки зрения безопасности!
WASI определяет стандартный интерфейс, через который хост задаёт capabilities, передаваемые недоверенному WASM-коду.
Интеграция на этом более низком уровне даёт потенциально большую производительность и позволяет пользователям писать на любом языке, компилируемом в WebAssembly, но при этом значительно усложняется тулчейн.
WebAssembly также можно запускать внутри V8 isolate или microVM. Ни один из этих вариантов не исключает остальные.
Если примитив изоляции не предоставляет собственную модель capabilities, всё равно полезно продумывать раскрытие функциональности через этот подход. В некоторых случаях подойдёт прокси, но стоит также рассмотреть протокол Object Capability вроде Cap'n Web поверх любого из этих примитивов.
Однако есть одно решение, которое хочется выделить особо…
Cloudflare Workers — это платформа для построения платформ От этой мысли немного плавится голова, но, кажется, это хороший способ думать о наших примитивах
Dynamic Workers от Cloudflare
Dynamic Workers от Cloudflare были построены именно под такое применение. Маркетинг вокруг них (что вполне объяснимо) фокусировался на code mode и сценариях с агентами, но, по мнению автора, область применения куда шире.
Помимо соответствия всем перечисленным выше критериям, это ближайшее к готовому "из коробки" фреймворку решение для построения расширяемых веб-приложений, которое удалось найти в 2026 году. (Хотя наверняка скоро появится больше вариантов.)
Есть несколько вещей, которые они предоставляют "из коробки", а с другими решениями пришлось бы строить самостоятельно:
Observability
(Основная работа автора и его личный конёк)
И владельцу платформы, и её пользователям нужна видимость того, что делает их код. Cloudflare Workers имеют встроенный в сам рантайм трейсинг OpenTelemetry и первоклассные примитивы, дающие большой контроль над формируемой телеметрией.
Мультитенантное хранение данных
Не каждой системе расширений нужно, чтобы пользователи могли хранить собственные данные, но это даёт им гораздо больше гибкости.
Можно выдать каждому собственную SQLite-базу через Durable Object facets. Или дать собственный бакет R2.
Durable Execution
Рост популярности Temporal и подобных систем показал, что многим задачам полезно durable execution. Dynamic Workflows позволяет пользователям выполнять действия на протяжении минут или дней, с корректными повторами и backoff.
Контроль версий
Пользователям, вероятно, нужно версионировать и итерировать свои расширения, а рассчитывать, что все пользуются GitHub, не приходится. Контроль версий встраивается прямо в продукт.
Размещённые LLM
Пользователи могут использовать LLM, чтобы черновиком набросать свои расширения, но LLM можно также выставить через Workers AI, чтобы пользователи применяли их внутри собственных расширений (с подходящими бюджетами токенов и лимитами частоты).
export async function analyzeArticle(env: Env, article: Article) {
return result = await env.AI.run(
messages: [
{
role: "system",
content: "Decide whether the supplied article talks about cute kittens.",
},
{
role: "user",
content: article.text,
},
],
)
}
Самостоятельный хостинг JavaScript-тулинга
Большая часть JavaScript-тулинга сама написана на JavaScript, а значит для сборки и тестирования кода расширений, возможно, не нужен отдельный контейнер или VM.
import { transform } from 'sucrase';
export function transpileUserCode(source: string): TranspileResult {
try {
const result = transform(source, {
transforms: ['typescript'],
disableESTransforms: true
});
return { type: 'success', code: result.code };
} catch (err) {
return { type: 'failure', error: String(err) } };
}
}
Время демо
При написании этого материала возникла мысль: "А что если превратить статичный блог в самую маленькую в мире платформу для вайб-кодинга?"5
Изначально планировалось включить сюда же руководство по работе с Dynamic Workers и несколько крутых демо, но этот текст и так вышел слишком длинным. Гайд по работе с Dynamic Workers вынесен отдельно, но финальные демо всё же добавлены и сюда.
Харнесс для демо построен вокруг идеи настраиваемого скрапера. По заданному URL он подтягивает содержимое (если только сайт не блокирует Cloudflare) и передаёт это содержимое вместе с несколькими утилитами в пользовательский код. Полное объяснение — в гайде.
Все примеры работают на Cloudflare Workers, исходники редактируемы. Можно изменить любой из них, чтобы запустить свой скрипт, либо выбрать "Write your own" — там есть промпт для LLM, чтобы начать с нуля.
Каждый пример проходит через один и тот же харнесс, но задействует свою комбинацию библиотек и capabilities. Выберите один, укажите предложенный URL или свой — и нажмите Run.
Здесь могут быть драконы
Последняя мысль.
Почти десять лет работы с платформами. "Превратить своё приложение в платформу" — это точно не про "легко". Платформы — это сложно: сложно проектировать, сложно эксплуатировать, сложно отлаживать.
Раскрытие API для клиентов требует немало предварительного продумывания и долгосрочной поддержки (хотя, возможно, LLM сильно это упростят?).
Но это ещё и по-настоящему увлекательно, и с точки зрения пользователя, и с точки зрения создателя. Креативность пользователей, делающих то, что никогда не приходило в голову или казалось невозможным, способна по-настоящему удивить.
Платформы — это сложно, но оно того стоит.
Приложение
Кое-что из того, что повлияло на этот материал:
Реально надоело это чувство "чёрт, @KentonVarda снова оказался прав ещё полгода назад"
- Многочисленные тексты и выступления Кентона Варды
- Cloudflare OS
- Ink and Switch и их Malleable Software
- Andy Matuschak и его Apps and programming: two accidental tyrannies
Примечания
Есть подозрение, что даже в полностью LLM-ускоренном мире неравенство участия никуда не денется. Небольшой процент людей будет создавать большинство расширений в любой экосистеме, как бы просто мы это ни делали.
Если приглядеться, платформы вайб-кодинга — это своего рода обобщённая версия того же самого, только вместо кастомной функциональности для организации они предоставляют универсальное хранение данных и хостинг. Ожидаемо, что они начнут добавлять такой же кастомизированный хостинг доступа, когда начнут продавать себя в Enterprise-сегмент.
Это полностью обходит стороной необходимость изолировать UI на клиентской стороне, где кастомный код может получить доступ к потенциально чувствительным данным. Эта тема заслуживает отдельного материала. Либо можно направить своего робота на
cloudflare-osи спросить, как это реализовано там.Тем, кто знаком с Workers, это может напомнить bindings... Да! Bindings и Service Workers работают на основе системы Object Capability RPC. Раскрытие capabilities пользователям можно рассматривать как генерацию bindings для конкретного сервиса.
Собственные "вайбы" придётся принести самостоятельно. Решение "раздать бесплатный доступ к LLM всему интернету" показалось не лучшей идеей с финансовой точки зрения.