Thesis 에이전트에게 "계속 확인해"를 시킬수록 운영은 나빠진다. 정상은 조용해야 하고, 깨우는 일은 사건의 몫이어야 한다.
1. 계속 확인하는 에이전트는 금방 무너진다
AI 에이전트를 처음 운영하면 누구나 같은 함정에 빠진다. 불안하니까 계속 보게 만든다. 5분마다 상태를 확인시키고, 작업이 끝날 때까지 옆에서 기다리게 하고, "정상인지 봐줘"를 cron에 걸어 둔다. 성실해 보인다. 그런데 며칠만 굴려 보면 이 성실함이 곧 소음이라는 걸 알게 된다.
반복 확인은 세 가지 비용을 만든다. 첫째, 토큰과 실행 시간을 쓴다. 둘째, "정상입니다" 보고가 Slack과 로그를 채운다. 셋째, 정작 중요한 실패가 그 소음 속에 묻힌다.
Hermes를 운영하면서 나도 이 길을 거꾸로 걸었다. 기다림 루프와 반복 확인을 하나씩 걷어냈다. Happy lane은 완료 webhook 중심으로 옮겼고, Sentry나 Grafana 신호는 정말 중요한 사건일 때만 작업과 스레드로 올라오게 했다. 그쪽이 훨씬 안정적이었다.
에이전트가 계속 들여다보고 있다는 사실은 안전을 보장하지 않는다. 오히려 무엇이 중요한지 흐려 놓는다. 정상은 조용해야 한다. 실패와 위험과 결정만 수면 위로 올라와야 한다.
2. Webhook lifecycle: 사건은 작게 들어오고 근거는 크게 남는다
사건 기반 운영은 webhook URL 하나 뚫는 일이 아니다. lifecycle 전체가 맞물려야 한다. 외부 시스템이 사건을 보내고, 이벤트 허브가 서명과 중복을 확인하고, 사건 종류를 분류하고, 필요한 경우에만 Hermes를 깨우고, 최종 결과는 Slack·Kanban·Git·로그 같은 운영 표면에 남아야 한다.
sequenceDiagram
participant External as 외부 시스템 (CI/Sentry/Gmail/Grafana/Voice)
participant Hub as Webhook/Event Hub
participant Joe as Hermes Joe
participant Surface as Slack/Kanban/Git/Logs
participant Human as Joon
External->>Hub: event payload
Hub->>Hub: signature verification
Hub->>Hub: deduplication
Hub->>Hub: route by event type
alt normal or informational
Hub->>Surface: quiet state update or no-op record
else failure/risk/decision
Hub->>Joe: wake with minimal payload
Joe->>Surface: inspect primary evidence
Joe->>Surface: act or create task with evidence
Joe->>Human: ask only if decision boundary is reached
end
payload는 작아야 한다. webhook이 원본 데이터를 통째로 실어 나를 이유는 없다. 사건 id, source, type, timestamp, correlation key, 안전한 요약, 그리고 원천을 다시 조회할 포인터. 이 정도면 충분한 경우가 대부분이다. 핵심은 이거다. webhook payload는 판단의 전부가 아니라 판단의 시작점이다.
3. Signature, dedup, routing이 운영 품질을 결정한다
웹훅은 "200을 받았다"로 끝나지 않는다. Hermes를 붙이면서도 route test가 200 ignored를 돌려주는데 정작 에이전트는 깨어나지 않는 경우를 여러 번 겪었다. 어떤 통합은 서명을 보내고, 어떤 plain webhook은 서명 구조가 아예 달랐다. event type이 예상과 어긋나 route가 무시되기도 하고, tunnel 경로가 바뀌어 외부 수신이 통째로 죽기도 했다.
그래서 웹훅의 성공 기준은 더 좁고 더 엄격해야 한다.
| 단계 | 질문 | 실패하면 생기는 일 |
|---|---|---|
| 수신 | 외부 시스템이 올바른 URL로 도달했는가 | 운영자는 연결됐다고 믿지만 사건이 오지 않는다 |
| 인증 | 서명 방식과 secret 범위가 맞는가 | 진짜 사건을 거부하거나 가짜 사건을 받아들인다 |
| 중복 제거 | 같은 사건을 여러 번 처리하지 않는가 | Slack·Kanban 작업이 중복으로 생성된다 |
| 라우팅 | event type과 source가 의도한 handler로 가는가 | 200 ignored 같은 조용한 실패가 된다 |
| 깨우기 | LLM 판단이 필요한 사건만 Joe를 깨우는가 | 정상 소음이 에이전트를 태운다 |
| 증거화 | 결과가 스레드·작업·로그에 남는가 | 나중에 무엇을 했는지 복기할 수 없다 |
표가 말하는 건 하나다. 웹훅은 기술 연결이 아니라 운영 계약이다. 연결에 성공한 것과 운영에 성공한 것은 다른 일이다.
4. OK-only watchdog은 받지 않는다
많은 시스템이 "정상입니다"를 자주 외치고 싶어 한다. 하지만 OK-only watchdog은 대체로 나쁜 기본값이다. 정상 메시지가 매번 올라오면 사람은 그 채널을 무시하기 시작하고, 에이전트는 가치 없는 상태 확인에 시간을 쓴다. 더 고약한 건, 정상 보고가 한 번 빠졌을 때 그게 장애인지 통신 문제인지를 또 해석해야 한다는 점이다.
물론 heartbeat 자체를 버리자는 말은 아니다. 차이는 위치에 있다. heartbeat를 사람과 LLM에게 계속 들이미느냐, 아니면 더 낮은 계층에서 조용히 기록하고 실패 조건만 위로 올리느냐. 정상 상태는 metrics, logs, health table, dashboard에 남으면 된다. Slack과 Joe를 깨우는 건 실패, 장기 침묵, 임계치 초과, 승인 필요, 사용자 영향 가능성 같은 사건이어야 한다.
에이전트 운영에서 던져야 할 질문은 "정상도 알려줘"가 아니다. "정상이 아닐 때 무엇이 증거인가"다.
5. Cron과 no-agent path를 구분한다
사건 기반으로 간다고 cron이 사라지는 건 아니다. cron은 여전히 쓸모가 많다. 다만 모든 cron이 에이전트를 깨울 이유는 없다. 정기 데이터 수집, 리포트 스냅샷 생성, 로그 정리, 만료 검사처럼 판단이 끼어들 여지가 없는 일은 no-agent path로 처리하면 된다.
구분 기준은 단순하다.
| 작업 유형 | 처리 방식 | Joe를 깨우는 조건 |
|---|---|---|
| 정기 수집 | no-agent cron | 수집 실패, schema mismatch, 인증 실패 |
| 단순 전달 | no-agent webhook | payload가 위험·결정 조건에 해당할 때 |
| 오류 사건 | event hub → Joe | 재발, 사용자 영향, 새 release 관련성 |
| 작업 완료 | lane webhook → Joe | 검증해야 할 artifact가 생겼을 때 |
| 광고·예산 변경 | Joe 분석 후 사람 승인 | 실제 변경 전 항상 사람이 결정 |
이 구분이 없으면 에이전트가 모든 작업의 병목이 된다. Hermes Joe는 판단 계층이지 모든 cron job의 runtime이 아니다. 판단이 필요 없는 일은 조용히 흘려보내고, 판단이 필요한 순간에만 깨운다.
6. Slack thread follow-up rule
사건 기반 운영에서는 후속 보고를 어디에 남기느냐가 의외로 중요하다. CI 실패 알림이 한 스레드에 떴는데 Joe의 최종 보고는 다른 채널의 새 메시지로 올라오면, 감사 흐름이 그 순간 끊긴다. Sentry 사건도, Voice Memory 코멘트도, Happy lane 완료도 같은 원칙을 따른다. 최초 사건이 스레드를 만들었다면, 후속 판단과 결과는 그 스레드 아래에 남긴다.
이건 정리 습관이 아니라 trace id에 가깝다. 사람이 나중에 같은 사건을 다시 펼쳐 볼 때, 원인부터 실행, 증거, 결론까지를 한 스레드에서 읽을 수 있어야 한다. 같은 사건을 여러 채널에 cross-post하면 알림은 늘어나지만 운영 지식은 흩어진다.
규칙으로 요약하면 이렇다.
slack_follow_up_rule:
if_event_started_in_thread: reply_in_same_thread
if_event_has_correlation_key: reuse_existing_thread_when_safe
if_new_human_decision_needed: mention_decision_in_thread
if_final_result_exists: attach_evidence_summary_in_thread
avoid:
- duplicate_channel_broadcasts
- ok_only_updates
- result_without_source_event
7. Pitfall: 200 OK를 성공으로 착각할 때
웹훅 운영에서 가장 흔한 함정은 HTTP 200을 성공으로 읽는 것이다. 200은 서버가 요청을 받았다는 뜻일 뿐이다. 그 뒤에서 route는 ignored였을 수 있고, signature가 우회됐을 수 있고, 중복 제거가 잘못 걸려 사건이 버려졌을 수 있고, Joe를 깨우는 조건에 끝내 도달하지 않았을 수 있다. 더 심하면 작업은 생겼는데 최종 결과가 Slack 스레드에는 남지 않았을 수도 있다.
그래서 webhook 검증은 end-to-end로 해야 한다. 외부 시스템에서 test event를 쏘고, event hub log를 확인하고, route handler가 제대로 선택됐는지 보고, 필요하면 Kanban 작업이나 Slack 스레드가 생겼는지 보고, Joe가 근거를 실제로 조회했는지 보고, 최종 결과가 같은 감사 표면에 남았는지까지 본다. 여기까지 와야 "연결됐다"고 말할 수 있다.
번거로워 보이지만 반복 polling보다 싸다. 사건 경로를 한 번 제대로 깔아 두면, 그다음부터 정상 상태는 조용해지고 에이전트는 필요한 순간에만 깨어난다.
8. 운영 규칙
- 반복 확인을 기본값으로 두지 않는다.
- webhook은 수신·인증·중복 제거·라우팅·깨우기·증거화까지 검증한다.
- payload는 작게 보내고, 원천 근거를 다시 찾을 포인터를 남긴다.
- OK-only watchdog으로 사람과 LLM을 깨우지 않는다.
- cron은 유지하되 no-agent path와 Joe wake path를 구분한다.
- 최초 사건이 만든 Slack 스레드가 있으면, 후속 판단과 최종 결과를 같은 스레드에 남긴다.
- 작업 완료 webhook은 merge 권한이 아니라 검증 시작 신호로 취급한다.
- 사건 기반 구조에서도 파괴적 변경, 운영 배포, secret 출력, 광고 예산 변경은 승인 경계를 넘지 않는다.
다음 글
다음 글은 /writing/observable-surface/execution-lanes-kanban-codex-happy/로 이어진다. 사건이 에이전트를 깨운 다음에는, 실행과 검증을 어떻게 나눌 것인가가 문제가 된다. Codex, Happy, Kanban lane에서 "완료했습니다" 보고가 아니라 원천 증거를 남기는 구조를 다룬다.