Агент-инженер по репозиторию · Модуль 6 · Урок 6.1
Агент как сервис: вебхук, пул воркеров, graceful shutdown
Зачем (какую проблему чиним)
До сих пор агент — это CLI: запустил, дождался, вышел. В проде задачи приходят сами — по issue с определённым лейблом. Нужен сервис: принять вебхук, поставить задачу в очередь, обработать пулом воркеров, корректно завершиться по сигналу, не бросив задачу на полпути.
Решение и альтернативы
Решение: HTTP-сервер принимает вебхук (issue labeled → Task), кладёт в буферизованный канал; пул воркеров-горутин разбирает задачи; context.Context несёт отмену для graceful shutdown — по SIGTERM закрываем приём, ждём wg.Wait(), незавершённое (durable) переподнимется при следующем старте. Каждая задача по-прежнему идёт через worktree-изоляцию и весь конвейер версии v4.
Альтернативы: горутина на каждый вебхук без ограничения — неконтролируемая конкурентность, исчерпание ресурсов и rate-limit API; синхронная обработка в обработчике вебхука — таймауты, потеря задач при рестарте. Пул воркеров + очередь + graceful shutdown — идиоматичный Go для сервиса-агента.
DIFF
Добавляем HTTP-вебхук, очередь задач, пул воркеров и graceful shutdown по сигналу.
⚠ Безопасность
Вебхук — внешняя точка входа: проверяем подпись вебхука (HMAC от GitHub/GitLab) и фильтруем по лейблу/репозиторию, иначе кто угодно запустит агента на произвольной задаче. Каждая задача — в своём изолированном worktree (версия v3); инварианты «не main» и «нет auto-merge» действуют и в сервисном режиме. Graceful shutdown не бросает durable-задачу на полуслове.
Проверка
POST вебхука с валидной подписью и нужным лейблом ставит задачу в очередь; воркер обрабатывает её до MR. SIGTERM закрывает приём и ждёт завершения текущих задач. Вебхук с неверной подписью — отклонён.
Глубже
Агент как сервис, конкурентность на Go, durable workflows, graceful shutdown — курс «Продакшн-разработка», Модуль 6 (деплой, гл. 13) и Модуль 1 (durable execution, гл. 1).
+package main
+
+import (
+ "context"
+ "net/http"
+ "sync"
+)
+
+type Service struct {
+ tasks chan Task
+ wg sync.WaitGroup
+}
+
+// handleWebhook принимает issue-событие. Проверка подписи и фильтр лейбла —
+// ДО постановки в очередь (внешний недоверенный вход).
+func (s *Service) handleWebhook(w http.ResponseWriter, r *http.Request) {
+ if !validSignature(r) { // HMAC секрета вебхука
+ http.Error(w, "bad signature", http.StatusUnauthorized)
+ return
+ }
+ ev, ok := parseLabeledIssue(r)
+ if !ok || ev.Label != "agent" {
+ w.WriteHeader(http.StatusNoContent) // не наш триггер — игнор
+ return
+ }
+ s.tasks <- Task{ID: ev.IssueID, Issue: ev.Body}
+ w.WriteHeader(http.StatusAccepted)
+}
+
+func (s *Service) Start(ctx context.Context, n int) {
+ for i := 0; i < n; i++ {
+ s.wg.Add(1)
+ go s.worker(ctx, i)
+ }
+}
+
+// Shutdown: закрыть приём и дождаться текущих задач; durable-незавершённое
+// переподнимется при следующем старте.
+func (s *Service) Shutdown() { close(s.tasks); s.wg.Wait() }Anti-patterns
| Грабля | Почему плохо | Как правильно |
|---|---|---|
| Горутина на каждый вебхук без ограничения | Неконтролируемая конкурентность: исчерпание ресурсов, шторм запросов к API и rate-limit | Буферизованная очередь + пул воркеров фиксированного размера |
| Не проверять подпись вебхука | Любой может запустить агента на произвольной задаче (внешний недоверенный вход) | Проверка HMAC-подписи + фильтр лейбла/репозитория до постановки в очередь |
Останов через os.Exit без ожидания воркеров | Durable-задачи бросаются на полуслове; трейсы не закрываются | Graceful shutdown: отмена контекста → закрытие очереди → wg.Wait() |
Практическое задание (RA-v5)
- Поднять HTTP-вебхук (issue labeled), очередь задач и пул воркеров; задача → весь конвейер версии v4 в изоляции.
- Проверять подпись вебхука и фильтровать по лейблу; реализовать graceful shutdown по сигналу.
- Закоммитить:
git commit -m "v5: agent-as-service with worker pool".
Проверка знаний
Почему вебхук обрабатывают через очередь + пул воркеров, а не запускают горутину на каждое событие?
Верный ответ: B
B. Неограниченное число горутин при всплеске событий исчерпает ресурсы и завалит API запросами (429). Пул фиксированного размера + буферизованная очередь дают предсказуемую конкурентность и сглаживают пики. Горутины дёшевы (A), но их количество всё равно надо ограничивать.
Что обязательно сделать с входящим вебхуком до постановки задачи в очередь?
Верный ответ: B
B. Вебхук — внешний недоверенный вход; без проверки подписи кто угодно запустит агента на произвольной задаче. Подпись + фильтр лейбла/репозитория — обязательная валидация до любой работы.