temporality
Изменить страницу↗
Инженерные документы

Триггеры запуска операций и обращение агента к человеку

#Цель

Сделать запуск агентских операций независимым от пользовательского чата.

В реальной эксплуатации задача обычно возникает во внешней системе:

После получения события система должна самостоятельно создать и выполнить операцию агента.

Если в процессе выполнения агент не может принять решение без дополнительного контекста, он должен уметь адресно запросить человека через настроенный для этого пользователя канал коммуникации — Matrix, Telegram или другой подключённый канал — дождаться ответа и продолжить ту же операцию.

#Основной принцип

Чат не запускает операцию. Чат является одним из каналов взаимодействия с уже выполняющейся операцией.

Источником запуска является событие.

Внешняя система
      │
      │ webhook / event
      ▼
   Trigger
      │
      ▼
  Agent Run
      │
      ├── autonomous work
      │
      ├── достаточно контекста
      │       │
      │       ▼
      │    завершение
      │
      └── недостаточно контекста
              │
              ▼
        запрос человеку
              │
       Matrix / Telegram / ...
              │
              ▼
           ответ
              │
              ▼
       продолжение Run

#1. Триггеры

Необходимо ввести сущность Trigger — декларативное описание того, какое внешнее событие должно запускать какую агентскую операцию.

#Минимальные свойства

#Типы триггеров

Фактически реализовано три типа:

Сценарии поверх webhook: azure_devops.pull_request, azure_devops.work_item, mindmap.test_case (prompt_template + HMAC-подпись + per-trigger токен) — это конфигурации webhook-триггеров, а не отдельные типы.

Конкретные интеграции могут реализовываться отдельными адаптерами, но внутри системы они должны приводиться к единому представлению события.

#Триггеры на события журнала (type=event)

«Хуки вокруг агентского цикла»: ядро хвостит собственный журнал наблюдения и запускает ран при появлении подходящего события — накопившиеся несогласованные артефакты, попытки побега из песочницы, массовый экспорт документов, события безопасности. Реализовано слушателем в cmd/agent-kernel/eventtriggers.go (интервал 15с).

Конструктор конфига (workspace.EventConfig):

{
  "event_type": "artifact.review.requested",
  "filter": {"actor_id": "kernel-internal", "project": "omni", "data.status": "draft"},
  "prompt": "Проследить согласование…"
}

Семантика срабатывания:

Открытые ограничения: срабатывание поштучно (нет агрегации «N событий за окно» — пороговые сценарии типа массового экспорта выражаются фильтром по payload либо ждут агрегатор); оргпринадлежность проверяется по привязке самого триггера (§18), события журнала оргединицу не несут.

#Нормализованное событие

Любой источник должен преобразовываться во внутреннее событие примерно следующего вида:

Event
├── id
├── type
├── source
├── timestamp
├── actor
├── tenant
├── payload
└── metadata

Агент не должен зависеть от формата исходного webhook.


#2. Создание операции

После успешного прохождения триггера система создаёт обычную агентскую операцию (Agent Run).

Важно, чтобы операция, запущенная webhook'ом, ничем принципиально не отличалась от операции, запущенной другим способом.

У операции должны быть доступны:

Например:

Run
├── trigger
│   ├── type: azure_devops.pull_request
│   └── event_id: ...
├── agent: reviewer
├── input
│   ├── repository
│   ├── pull_request
│   └── ...
└── execution

#3. Маппинг события в задачу агента

Для каждого триггера необходимо определить, какие данные события передаются агенту.

Например:

Azure Pull Request
        │
        ├── repository
        ├── pull request
        ├── source branch
        ├── target branch
        ├── author
        └── changes
                │
                ▼
          Reviewer Agent

Триггер не должен содержать готовый системный промпт агента.

Он определяет:

какое событие произошло и какую агентскую операцию необходимо запустить.

Конкретная логика поведения должна находиться в конфигурации агента и его навыках.


#4. Идемпотентность и повторная доставка

Webhook-системы могут доставлять одно событие несколько раз.

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

Для каждого входящего события необходимо поддержать:

Минимальное требование:

same source + same event_id
        ↓
same logical trigger
        ↓
не создавать второй Run

#5. Обращение агента к человеку

Агент должен получить специальный инструмент для ситуации, когда самостоятельное принятие решения невозможно.

Условно:

ask_human(...)

Это не обычное сообщение в чат.

Вызов создаёт ожидающее взаимодействие, связанное с конкретной операцией.

Минимальные параметры:

recipient
question
context
options
timeout

#Пример

Агент обнаружил два возможных варианта:

В PR изменён контракт API. Нужно выбрать, считать ли изменение совместимым с текущей версией клиента. В коде недостаточно информации.

Агент вызывает:

ask_human(
    recipient = user:123,
    question = "...",
    options = [
        "Считать совместимым",
        "Считать несовместимым"
    ]
)

После этого выполнение операции приостанавливается.


#6. Выбор получателя

Агент не должен сам выбирать канал коммуникации.

Система должна определить:

  1. кому необходимо задать вопрос;
  2. какие каналы у этого пользователя доступны;
  3. какой канал является предпочтительным;
  4. каким образом доставить запрос.

Например:

User
├── Matrix       preferred
├── Telegram     enabled
└── Email        enabled

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

#Приоритет

Если пользователь настроил:

preferred channel = Matrix

запрос отправляется через Matrix.

Если канал недоступен, система может использовать следующий разрешённый канал согласно политике fallback.

Агенту не нужно знать детали Matrix, Telegram и других транспортов.


#7. Единый слой коммуникации

Необходимо отделить агентскую операцию от конкретного транспорта.

Ввести абстракцию:

Notification / Human Interaction
        │
        ├── Matrix adapter
        ├── Telegram adapter
        ├── ...
        └── future adapters

Таким образом, ask_human работает с пользователем, а не с Matrix или Telegram.

Это позволит впоследствии добавлять новые каналы без изменения агентов и их навыков.


#8. Ответ человека

Ответ должен быть связан не просто с пользователем, а с конкретным ожидающим запросом.

Human Request
├── id
├── run_id
├── recipient
├── question
├── status
│   ├── pending
│   ├── answered
│   ├── expired
│   └── cancelled
├── channel
├── created_at
├── expires_at
└── response

При получении ответа система должна:

  1. определить Human Request;
  2. сохранить ответ;
  3. добавить его в историю операции;
  4. возобновить выполнение Run;
  5. передать ответ агенту как результат ask_human.

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

ask_human → human response

#9. Приостановка и возобновление

Операция должна поддерживать состояние:

waiting_for_human

В этом состоянии:

После ответа:

waiting_for_human
        ↓
resuming
        ↓
running

#10. Контекст вопроса

Запрос человеку должен быть самодостаточным.

Нельзя отправлять:

«Что делать с этим?»

Человек должен понимать:

При этом человеку не следует отправлять весь технический контекст Run.

Система должна формировать компактный human-facing context.

Пример:

PR #1842

Я проверяю изменение контракта API.
В коде обнаружены два возможных варианта поведения.

Какой вариант считать правильным?

[Сохранить совместимость]
[Разрешить изменение контракта]

Основание: текущий клиент Directum Omni использует старую
сигнатуру метода.

#11. Структурированные ответы

Если вопрос допускает варианты, предпочтительно использовать структурированный ответ:

options:
  - id: compatible
    label: Сохранить совместимость
  - id: breaking
    label: Разрешить изменение

Ответ человека должен сохранять машинно-обрабатываемое значение.

Свободный текст также должен поддерживаться:

response:
  type: text
  value: "Считаем изменение допустимым..."

#12. Несколько вопросов

Операция может последовательно или параллельно ожидать несколько человеческих решений.

Необходимо предусмотреть:

Run
├── Human Request #1
├── Human Request #2
└── ...

При этом:

На первом этапе достаточно поддержать последовательный сценарий:

ask_human
   ↓
wait
   ↓
answer
   ↓
continue
   ↓
ask_human

#13. Таймаут и отсутствие ответа

У запроса должен быть timeout.

После истечения времени система должна выполнить заданную политику:

Важно: политика должна быть частью конфигурации операции, а не захардкожена в транспортном адаптере.


#14. Уведомления

Необходимо разделить:

#Human Interaction

Ожидается ответ, без которого операция не может продолжиться.

#Notification

Информационное сообщение, которое не блокирует операцию.

Например:

Agent Run
   │
   ├── ask_human → blocking
   │
   └── notify → non-blocking

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


#15. Безопасность

Нельзя позволять webhook напрямую указывать произвольного пользователя для обращения.

Необходимо контролировать:

Особенно важно не допустить ситуацию:

malicious webhook
      ↓
arbitrary recipient
      ↓
agent sends sensitive context

#16. Наблюдаемость в Temporality

Все взаимодействия должны становиться частью единого event stream операции.

Минимально фиксировать события:

trigger.received
trigger.accepted
run.created
run.started

human.requested
human.answered
human.reasked
run.resumed

run.completed
run.failed
run.cancelled

Для human.requested желательно сохранять:

Это позволит в дальнейшем анализировать:

Особенно важно, чтобы обращение человека было частью траектории операции, а не отдельной системой уведомлений.


#17. Пользовательский интерфейс

В интерфейсе Temporality должна быть видна вся цепочка:

Trigger
  ↓
Run started
  ↓
Agent reasoning / tool calls
  ↓
Human request
  ↓
Waiting 14 min
  ↓
Human response
  ↓
Run resumed
  ↓
Completed

Для ожидающей операции необходимо показывать:

Человек должен иметь возможность открыть Run и увидеть причину ожидания.


#18. Управление триггерами

Добавить раздел управления триггерами.

Для каждого триггера отображать:

Поле Значение
Название Review Pull Request
Источник Azure DevOps
Событие Pull Request created
Агент Reviewer
Состояние Включён
Последний запуск ...
Успешных запусков ...
Требовался человек ...

Создание триггера должно быть ориентировано на пользователя и не требовать ручного написания большого YAML.

Предпочтительно использовать форму/мастер:

1. Откуда приходит событие?
2. Какое событие?
3. Какого агента запустить?
4. Какие данные передать?
5. Кто может быть привлечён к решению?
6. Что делать при отсутствии ответа?

Система сама формирует внутреннюю конфигурацию.


#19. MVP

В первую реализацию включить:

#Запуск

#Интеграции

#Взаимодействие

#Состояния

pending
running
waiting_for_human
resuming
completed
failed
cancelled

#Temporality


#20. Критерии готовности

Функциональность считается реализованной, если можно выполнить следующий сценарий end-to-end:

  1. В Azure DevOps создаётся Pull Request.
  2. Azure DevOps отправляет webhook.
  3. Temporality принимает событие.
  4. Создаётся Agent Run для Reviewer.
  5. Reviewer получает контекст PR и выполняет анализ.
  6. Агент обнаруживает неоднозначность, которую не может разрешить самостоятельно.
  7. Агент вызывает ask_human.
  8. Temporality определяет пользователя и его preferred channel.
  9. В Matrix или Telegram приходит понятный запрос.
  10. Операция переходит в waiting_for_human.
  11. Пользователь отвечает.
  12. Ответ привязывается к исходному Human Request.
  13. Run автоматически возобновляется.
  14. Агент получает ответ как результат ask_human.
  15. Агент завершает проверку.
  16. В Temporality видна полная траектория от webhook до завершения, включая ожидание человека.
  17. Повторная доставка исходного webhook не создаёт второй Run.

#Главное архитектурное требование

Не реализовывать это как «бот, который пишет агенту в чат».

Нужна единая модель:

Event → Agent Run → Tools → Human Interaction → Agent Run

Канал коммуникации является транспортом. Источником истины остаётся состояние и event stream операции в Temporality.