AI 26.1100.0: срок жизни задачи очереди ИИ-запросов задаётся на запрос
В модуле ai коробочного Битрикс24 версии 26.1100.0 срок жизни задачи очереди ИИ-запросов больше не зашит в четыре часа. Его задают на запрос через Engine::setQueueJobTtl(), момент истечения пишется в новую колонку b_ai_queue.EXPIRE_DATE, а просроченную задачу QueueJob::createFromHash() теперь проваливает и удаляет сам. Ещё установщик переехал с SQL-скриптов на миграции ядра, и появилась заготовка триггера перезапуска ИИ-агентов.
В коробку версия вышла 14 сентября 2026 года, по данным официального канала «Битрикс24 changelog». Читать стоит тем, кто шлёт ИИ-запросы из своего кода через Engine или достаёт задачи очереди по хешу.
Что может сломаться
Удалённых и несовместимо изменённых публичных сигнатур нет, новых @deprecated тоже, в API добавились только новые методы и необязательные параметры. Сломать ваш код может изменившееся поведение.
Проверьте вызовы createFromHash()
Раньше QueueJob::createFromHash() (lib/QueueJob.php) отдавал задачу любого возраста, а старые задачи чистил только агент clearOldAgent(). Теперь у метода сигнатура createFromHash(string $hash, bool $allowExpired = false), и просроченную задачу он по умолчанию не отдаёт.
Просроченной считается задача, у которой EXPIRE_DATE в прошлом. Если EXPIRE_DATE равен NULL, срок считается от DATE_CREATE плюс DEFAULT_TTL, то есть 14400 секунд, четыре часа. Для такой задачи метод возвращает null и сам вызывает приватный expire(). Тот удаляет строку, шлёт backend- и frontend-события провала с ошибкой HASH_EXPIRED и откатывает списание лимита. Просроченную «битую» строку, у которой не распаковались движок или payload, метод удаляет сразу.
Колбэки в ядре ведут себя по-разному. Колбэки успеха передают allowExpired: true и принимают поздний результат, пока строка существует. Это callbackBodyAction в lib/controller/queue.php и callbackSuccessAction в lib/controller/integration/b24cloudai.php и thirdparty.php. Колбэки ошибок (callbackErrorAction в тех же файлах) зовут метод со значением по умолчанию, так что просроченную задачу провалит и удалит сам createFromHash().
Если вы вызываете createFromHash() в своём коде, проверяйте null и учитывайте, что сам вызов теперь может удалить задачу и разослать событие провала. Если нужен поздний результат, как в колбэках успеха, передавайте allowExpired: true.
getTTL() больше не константа
Приватную константу TTL_SECONDS = 14400 заменили две публичные, QueueJob::DEFAULT_TTL = 14400 и QueueJob::MAX_TTL = 86400. getTTL() теперь возвращает TTL конкретной задачи. У задачи, загруженной из БД, это разница EXPIRE_DATE − DATE_CREATE, а если EXPIRE_DATE пуст, DEFAULT_TTL. Если ваш код считал, что задача всегда живёт четыре часа, берите срок из getTTL().
QueueJob::createWithinFromEngine(IEngine $engine, ?int $ttl = null) получил необязательный TTL. Он и Engine::setQueueJobTtl() бросают \InvalidArgumentException, если значение меньше единицы или больше 86400.
could_not_lock стал ошибкой провайдера
Ответ с кодом could_not_lock теперь считается ошибкой провайдера (lib/Engine/Engine.php, QueueJob::fail()). Пользователь получит AI_ENGINE_ERROR_PROVIDER, как на коды 100 и от 500 и выше. Для кода завели константу Engine::ERROR_CODE_COULD_NOT_LOCK = 'could_not_lock'.
Задайте срок жизни своему запросу
Engine::setQueueJobTtl(int $ttl): static задаёт время жизни задачи очереди для конкретного запроса, getQueueJobTtl(): int его возвращает (по умолчанию QueueJob::DEFAULT_TTL). Engine и ThirdParty (lib/Engine/ThirdParty.php) передают это значение в QueueJob::createWithinFromEngine(), а QueueJob::register() записывает в EXPIRE_DATE момент «сейчас + TTL». Потолок — MAX_TTL, сутки. Срок выставляйте до отправки запроса, потому что createWithinFromEngine() получает его при постановке задачи в очередь.
Метод бросает исключение на недопустимом значении, так что в сервисе его удобно обернуть в Result:
<?php declare(strict_types=1);
namespace Vendor\Assistant\Application\Service;
use Bitrix\AI\Engine\Engine;
use Bitrix\AI\QueueJob;
use Bitrix\Main\Error;
use Bitrix\Main\Loader;
use Bitrix\Main\Result;
final class AiQueueTtlService
{
public function __construct()
{
Loader::requireModule('ai');
}
public function apply(Engine $engine, int $ttlSeconds): Result
{
$result = new Result();
try {
$engine->setQueueJobTtl($ttlSeconds);
} catch (\InvalidArgumentException) {
return $result->addError(new Error(
sprintf('TTL задачи должен быть от 1 до %d с, передано %d', QueueJob::MAX_TTL, $ttlSeconds),
'AI_QUEUE_TTL_OUT_OF_RANGE',
));
}
return $result->setData(['ttl' => $engine->getQueueJobTtl()]);
}
}
Не стройте процессы на триггере перезапуска ИИ-агентов
В модуле появилась активити бизнес-процессов aiagentrestarttrigger (install/activities/bitrix/aiagentrestarttrigger/). Это триггер CBPAiAgentRestartTrigger extends \Bitrix\Bizproc\Activity\BaseTrigger в группах STARTER и AI, секция AI_AGENT. Он возвращает restartRunId, previousRunId, initiatorId (в виде user_N), timestamp и runType (по умолчанию restart), а runType и restartRunId дублирует в переменные процесса. Триггер скрыт, если опция feature_ai_agents модуля bizproc не задана или равна N, а также если NodeAvailabilityServiceInterface не зарегистрирован в ServiceLocator или сообщает, что узел недоступен.
Параметры события тоже расширили. В enum ProcessedEvent появилось OnAiAgentRestart. Конструктор AiListenerParameters получил необязательные agentInstanceId, restartRunId, previousRunId, initiatorId, timestamp и runType = RUN_TYPE_INITIAL. В классе появились константы KEY_AGENT_TEMPLATE_ID, KEY_AGENT_INSTANCE_ID, KEY_RESTART_RUN_ID, KEY_PREVIOUS_RUN_ID, KEY_INITIATOR_ID, KEY_TIMESTAMP, KEY_RUN_TYPE, RUN_TYPE_INITIAL и RUN_TYPE_RESTART. toArray() добавляет новые ключи, только когда runType === 'restart'.
Отправлять событие пока некому. В коробочном Битрикс24 с этой версией OnAiAgentRestart в bitrix/modules встречается только в enum ProcessedEvent, а параметры перезапуска есть только в AiListenerParameters. Сервис SystemTemplateActivationService в bizproc (lib/Internal/Service/AiAgentGrid/) отправляет только OnAiAgentStart. Bizproc запускает триггеры по коду (Starter::addEvent(code: 'AiAgentStartTrigger', …) для старта), а код AiAgentRestartTrigger вне папки самой активити не встречается, так что процесс на новом триггере пока не запустится.
БД: проверьте колонку EXPIRE_DATE
Установщик переехал на миграции ядра. Файлы install/db/mysql/install.sql, uninstall.sql и их пары в install/db/pgsql/ удалены. Вместо них появились install/migrations/tables.php на построителе Bitrix\Main\UpdateSystem\Migration, events.php и agents.php. В install/index.php installDB() вызывает installMigrations(), а uninstallDB() вызывает uninstallMigrations($dropTables). Из 1349 удалённых строк релиза 908 приходятся на эти SQL-скрипты.
tables.php создаёт те же 31 таблицу, что и старый install.sql. По именам колонок и индексов новая схема отличается от MySQL-скрипта только в b_ai_queue, где добавились колонка EXPIRE_DATE datetime и индекс IX_B_EXPIRE_DATE.
Как колонка появится на уже установленных порталах, из кода модуля не видно, потому что скрипт обновления в папке модуля не остаётся. Комментарий в QueueJob::clearOldAgent() упоминает «interrupted updater backfill». Код везде готов к EXPIRE_DATE = NULL и тогда считает срок от DATE_CREATE + DEFAULT_TTL. На MySQL после обновления проверить колонку и индекс можно так:
SHOW COLUMNS FROM b_ai_queue LIKE 'EXPIRE_DATE';
SHOW INDEX FROM b_ai_queue WHERE Key_name = 'IX_B_EXPIRE_DATE';
Из install/index.php ушли ручные registerEventHandler, CAgent::AddAgent, CAgent::removeModuleAgents('ai') и приватные методы configureBoxBitrixGptFeatures() и getConnectionType(). Агентов при удалении модуля снимает ModuleManager::unRegisterModule() из main. Руками отписывается только старый обработчик PublicAgreement::onPageStart, рядом в коде комментарий «legacy cleanup».
agents.php ставит агентов по окружениям с теми же интервалами, что были в index.php:
- везде:
QueueJob::clearOldAgent(раз в 120 с),Updater::refreshDbAgent(раз в 3600 с),PropertiesSync::retrieveModelsAgent(раз в 86400 с); - в облаке (
BX24_HOST_NAME):EngineSettings::resetToBitrixAudioInCloudAgent,resetToBitrixGPTInCloudAgent,enforceEngineBaselineAgent, раз в час, первый запуск через 600, 600 и 3600 с, как раньше; - в коробке:
resetFollowUpTextStepsToBGPTAgentиresetFlowsToBGPTAgent, раз в час, первый запуск через 600 с.
Мелочи и находки
- Внутреннее JS-расширение
ai.roles-dialog(install/js/ai/roles-dialog/src/) переделали. Глобальные событияEventEmitterupdateиupdate-completeубраны вместе с перезагрузкой диалога после закрытия слайдера библиотеки ролей. Перед открытием библиотеки диалог теперь закрывается (this.hide()), аналитикуopen_listотправляет самRolesDialog.components/roles-dialog-roles-library.jsэкспортирует фабрикуgetRolesDialogRolesLibrary(onOpenRolesLibrary)вместо объектаRolesDialogRolesLibrary, аgetRolesDialogEmptyGroupStubWithStates()получил второй параметрonOpenRolesLibrary. Если вы подписывались наupdateилиupdate-completeснаружи, эти обработчики больше не вызовутся. - Приватный
QueueJob::tryDelete()удаляет строку сырымDELETE FROM ?# WHERE ID = ?iчерезSqlExpressionи проверяетgetAffectedRowsCount(). События провала и откат лимита отправляет только тот запрос, который действительно удалил строку. Почему не ORM, сказано в комментарии: «ORM delete() gives no affected-rows contract». Так закрыта гонка между поздним колбэком и тиком агента. - Комментарий у
Engine::ERROR_CODE_COULD_NOT_LOCKтребует, чтобы значение совпадало сBitrix\AiProxy\Controller\Query::ERROR_CODE_COULD_NOT_LOCK. Модуляaiproxyв коробочном Битрикс24 нет, это серверная сторона облачного прокси. - Агенты
EngineSettings::resetToBitrixGPTInCloudAgent()иresetToBitrixAudioInCloudAgent()теперь возвращают__METHOD__ . '();', то есть имя без ведущего обратного слэша. Раньше возвращалось'\Bitrix\AI\Agents\EngineSettings::…'. В коробке это не заметно,agents.phpставит их только в облаке. - В
install/migrations/agents.phpсреди английских комментариев затесался один русский. В нём сказано, что legacy-вызовPropertiesSyncбез интервала переведён в явные 86400 с, «поведение сохранено (подтверждено verify-харнессом)». - В
lib/Payload/Formatter/StatementTrait.phpиUserMarkers.phpпередmb_strtolower()иstr_replace()добавили приведение к(string).EngineSettingsServiceтеперь пропускает пустой или нестроковый код движка. VERSION_DATEвinstall/version.phpровно2026-07-21 12:00:00, а в коробку версия вышла 14 сентября.- 20 JS/CSS-файлов в
install/js/ai/copilot/значатся в статистике с нулём строк. У них сменились только права, 100755 → 100644.
Что делать
- Найдите в своём коде
createFromHash(. Проверяйтеnull, а если нужен поздний результат, передавайтеallowExpired: true. - Если вы рассчитывали на фиксированные четыре часа, берите срок задачи из
getTTL(), а свой задавайте черезEngine::setQueueJobTtl()в пределах 1–86400 секунд. - После обновления проверьте, что в
b_ai_queueесть колонкаEXPIRE_DATEи индексIX_B_EXPIRE_DATE. - Уберите подписки на события
updateиupdate-completeизai.roles-dialog, если они у вас были. - Триггер
aiagentrestarttriggerв шаблоны пока не ставьте, событиеOnAiAgentRestartникто не отправляет.