Действия и триггеры
Action — переиспользуемое типизированное бизнес-действие. Trigger — подписка на событие. Как объявить, чем действие отличается от инструмента и когда что выбирать.
Действие (Action) — именованная бизнес-операция с типизированным входом и выходом. Вы вызываете её из кода бота. Или отдаёте агенту как инструмент.
Триггер (Trigger) — подписка на событие. Срабатывает сама, когда происходит именованное событие: старт бота, новое сообщение, событие интеграции или ваш собственный сигнал.
Инструмент агент зовёт сам внутри цикла рассуждения. Действие и триггер — про детерминированную логику. Действие вы запускаете явно. Триггер платформа запускает за вас по событию. Все три живут в src/ рядом с обработчиком диалога и попадают в бандл при сборке.
Действие
Объявите действие через new Action({ name, input, output, handler }). Схемы input и output — zod. Платформа валидирует данные на входе и на выходе обработчика. Значит, действие нельзя вызвать с кривыми аргументами или молча вернуть не тот результат.
import { Action, z } from '@holocronlab/botruntime-runtime'
export default new Action({
name: 'listPrograms',
title: 'Список программ',
description: 'Возвращает активные программы, опционально по категории.',
input: z.object({
category: z.string().optional(),
}),
output: z.object({
programs: z.array(
z.object({ id: z.string(), title: z.string() }),
),
}),
handler: async ({ input }) => {
const rows = await ProgramsTable.findRows({
filter: input.category ? { category: input.category } : undefined,
orderBy: 'title',
})
return {
programs: rows.rows.map((r) => ({ id: r.id, title: r.title })),
}
},
})handler получает { input, client }. input — уже провалидированный объект. client — сырой клиент API-бэкенда: таблицы, файлы, сообщения. Возвращаемое значение обязано соответствовать схеме output.
Поля конструктора:
| Поле | Тип | Обяз. | Назначение |
|---|---|---|---|
name | string | да | Идентификатор действия; только буквы и цифры, начинается с буквы |
input | zod-схема | да | Валидация аргументов |
output | zod-схема | да | Валидация результата |
handler | async ({ input, client }) => output | да | Тело действия |
title | string | нет | Человекочитаемое имя |
description | string | нет | Описание; становится описанием инструмента при asTool() |
cached | boolean | нет | Кэшировать результат по входу (по умолчанию false) |
Имя действия обязано матчить /^[a-zA-Z][a-zA-Z0-9]*$/: только буквы и цифры, без подчёркиваний и дефисов, первый символ буква. На невалидном имени конструктор бросает ошибку. Поэтому операцию вроде prescreen_estimate действием не оформить — её делают инструментом (см. ниже).
Действие или инструмент
Действие и инструмент решают разные задачи. Хотя одно превращается в другое.
- Инструмент (
Autonomous.Tool) — то, что агент решает вызвать сам внутриexecute(...). Имя допускает snake_case. Инструмент обычно контекстно-зависим: нужныconversation,state. - Действие (
Action) — переиспользуемая операция, которую вы запускаете явно из кода. Имя строго alphanumeric. Логика самодостаточна и не зависит от текущего диалога.
Любое действие превращается в инструмент методом asTool(). Отдаёте его агенту тем же списком tools:
import { Conversation } from '@holocronlab/botruntime-runtime'
import listPrograms from '../actions'
export default new Conversation({
channel: '*',
handler: async ({ execute }) => {
await execute({
instructions: 'Помоги подобрать программу. Список бери инструментом.',
tools: [listPrograms.asTool()],
})
},
})asTool() берёт name, input, output действия и его description как подсказку модели. Описание можно переопределить: asTool({ description: '…' }). Обратно инструмент в действие не конвертируется — направление одно.
Правило имени решает и выбор примитива. Нужны подчёркивания (snake_case, как любят LLM-инструменты) — берите инструмент. Операция чисто серверная, переиспользуемая, без диалога — берите действие. Понадобилось агенту — оборачиваете в asTool().
Вызов действия из кода
Кроме asTool(), действие доступно по имени через рантайм-глобал actions. Из обработчика диалога, из воркфлоу, из другого действия:
import { actions } from '@holocronlab/botruntime-runtime'
const { programs } = await actions.listPrograms({ category: 'training' })actions.<name>(input) валидирует вход, выполняет handler, валидирует выход. У той же ссылки есть actions.<name>.asTool() — короткий путь получить инструмент без импорта модуля действия.
Цены, лимиты и сроки рассчитывает код действий и инструментов. Модель может объяснить результат, но не должна подменять расчёт.
Триггер
Объявите триггер через new Trigger({ name, events, handler }). Он подписывается на список именованных событий. Запускается автоматически, когда любое из них происходит — без агента и без вызова из вашего кода.
import { Trigger } from '@holocronlab/botruntime-runtime'
export default new Trigger({
name: 'onRegister',
description: 'Инициализирует состояние при старте бота.',
events: ['register'],
handler: async ({ event }) => {
console.log('Бот запущен, событие:', event.type)
},
})handler получает { event } — совпавшее событие с полями id, type, payload, status, createdAt. Для событий диалога добавляются conversationId и userId. Обработчик ничего не возвращает (Promise<void>).
Поля конструктора:
| Поле | Тип | Обяз. | Назначение |
|---|---|---|---|
name | string | да | Имя триггера; 3–255 символов, /^[A-Za-z0-9_]+$/ |
events | string[] | да | Имена событий-источников |
handler | async ({ event }) => void | да | Реакция на событие |
description | string | нет | Описание, до 1024 символов |
Имя триггера допускает подчёркивания (/^[A-Za-z0-9_]+$/, 3–255 символов) — в имени действия их нельзя. Событие register особое: оно срабатывает один раз при старте бота. Поэтому триггер на register — обычное место для инициализации состояния.
Источники событий
Триггер подписывается на события трёх происхождений. Имя события — просто строка в массиве events.
Жизненный цикл бота — генерирует платформа:
register— старт бота (однократно при инициализации).message.created— пришло новое сообщение.conversation.started/conversation.ended— диалог начался или завершился.user.created— появился новый пользователь.workflow.started/workflow.completed/workflow.failed— переходы состояния воркфлоу.
События интеграций — в формате интеграция:событие. Платформа резолвит имя интеграции по алиасу или каноническому имени и подписывает триггер на реальный канал:
import { Trigger } from '@holocronlab/botruntime-runtime'
export default new Trigger({
name: 'reactToWebchatStart',
events: ['webchat:conversationStarted', 'slack:reactionAdded'],
handler: async ({ event }) => {
console.log('Событие интеграции:', event.type, event.conversationId)
},
})Набор событий у каждой интеграции свой — он объявлен в её манифесте. Про модель интеграций и их установку — Модель интеграций.
Свои события — объявите их в agent.config.ts со схемой, затем подпишитесь по имени:
import { defineConfig, z } from '@holocronlab/botruntime-runtime'
export default defineConfig({
name: 'my-bot',
events: {
paymentConfirmed: {
description: 'Платёж подтверждён провайдером',
schema: z.object({ orderId: z.string(), amount: z.number() }),
},
},
})import { Trigger } from '@holocronlab/botruntime-runtime'
export default new Trigger({
name: 'onPayment',
events: ['paymentConfirmed'],
handler: async ({ event }) => {
// event.payload типизирован схемой из agent.config.ts
},
})Когда что выбирать
Три примитива закрывают три сценария. Держите их разделёнными.
| Нужно | Берите | Кто запускает |
|---|---|---|
| Логику вызовет агент по ходу диалога | Инструмент (Autonomous.Tool / action.asTool()) | Модель внутри execute(...) |
| Переиспользуемую операцию, вызываемую явно из кода | Действие (Action) | Ваш код (actions.<name>) или агент через asTool() |
| Реакцию на событие без участия агента | Триггер (Trigger) | Платформа по событию |
Маршрут простой. Чистая серверная операция — действие. Понадобилось звать её агенту — оборачиваете в asTool(). Нужна автоматическая реакция на факт (регистрация, сообщение, вебхук интеграции, свой сигнал) — триггер. Долгий многошаговый процесс с таймерами и ретраями — это уже воркфлоу, а не триггер.