Агент-инженер по репозиторию · Модуль 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).

service.go: вебхук, пул воркеров, graceful shutdown (новый файл, фрагмент)
+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".

Проверка знаний

Почему вебхук обрабатывают через очередь + пул воркеров, а не запускают горутину на каждое событие?

  • A Горутины в Go дорогие
  • B Пул ограничивает конкурентность: защищает от исчерпания ресурсов и шторма запросов к API (rate-limit); очередь сглаживает всплески
  • C Так требует GitHub
  • D Очередь ускоряет модель

Что обязательно сделать с входящим вебхуком до постановки задачи в очередь?

  • A Сразу запустить агента для скорости
  • B Проверить подпись (HMAC) и отфильтровать по лейблу/репозиторию — это недоверенный внешний вход
  • C Залогировать тело целиком, включая секреты
  • D Ответить 200 и забыть