에이전트 자기개선 하네스 (7/12) — Heartbeat v2: 15 모드와 escalation
알림 시스템을 상태 머신으로 설계하는 기법
핵심 요약
이 글이 전달하는 것: - Heartbeat를 상태 머신으로 설계하는 법 — "살아 있음"을 15개 모드 × 상태 전이로 분해한다. - Escalation을 13개 명시적 이유로 잠그는 기법 — 모호한 알림을 막고 "알 수 없음"을 1급 시민으로 둔다. - False-positive를 줄이는 단일 파라미터 튜닝 — cold-start 타임아웃을 60s → 120s로 올리면 모델 로딩 타임아웃이 사라진다. - Proactive Preferences 피드백 루프 — 사용자의 반응/무반응을 지수이동평균(EMA)으로 escalation 임계에 반영한다.
v1의 한계와 v2의 설계 목표
v1 heartbeat은 이진 상태(alive / dead)로만 작동한다. 이 구조의 한계는 살아 있지만 비정상을 표현할 수 없다는 점이다. 결과적으로 "이상 징후는 감지되지만 원인이 불명확"한 알림이 축적된다.
v2의 설계 목표는 세 가지다: 1. 상태를 상태 × 컨텍스트의 곱집합으로 분해 2. 알림 조건을 명시적 enum으로 고정 3. 사용자 반응을 학습 신호로 편입
기법 1: 15 모드 상태 머신
v2의 모드는 {상태}.{컨텍스트} 네이밍 규칙을 따른다.
| 상태 클러스터 | 모드 |
|---|---|
| idle | idle.normal, idle.degraded, idle.silent |
| working | working.normal, working.slow, working.stuck |
| recovering | recovering.from-crash, recovering.from-quota, recovering.from-network |
| escalation | escalating, escalated, cooling-down |
| maintenance | maintenance.scheduled, maintenance.unplanned |
| fallback | unknown |
작동 원리
중요한 것은 모드 개수가 아니라 상태 전이 그래프다. 예: working.normal → working.slow → working.stuck → escalating. 알림은 임의 시점이 아니라 상태 전이 시점에만 발행된다. 이 제약이 "같은 상태에서의 반복 알림"을 구조적으로 차단한다.
기법 2: 13 Escalation 이유 enum
알림 발생 조건을 13개 명시적 이유로 제한한다:
- quota 80% 초과
- quota 100% 초과
- 동일 에러 3회 반복
- 응답 시간 p95 임계 초과
- cron job 2회 연속 실패
- memory 디렉터리 size 폭주
- embedding 서버 응답 없음
- external API 5xx 연속
- self-review 차단 패턴
- retain 태그 검증 실패율 급증
- 사용자 제로 응답 (장기간)
- 새 에이전트 자기진단 실패
unknown모드 진입
13번의 역할
13번은 설계상 가장 중요한 항목이다. 불확정 상태를 1급 시민으로 승격시키는 장치다. 분류 불가능한 상황에서 침묵하는 시스템은, 분류 가능한 실패보다 더 큰 잠재 리스크를 가진다. unknown 진입 자체를 escalation 사유로 지정하면 "모르는 것을 모른다고 보고"하는 경로가 확보된다.
기법 3: cold-start 타임아웃 튜닝
False-positive 알림의 지배적 원인은 복잡한 로직이 아니라 단일 타임아웃 값이었다.
- 증상: 첫 heartbeat 호출이 타임아웃 →
응답 없음분류 → escalation - 원인: 모델 cold-start 로딩 시간(초기 가중치 적재 + 워밍업)이 60초 기본 타임아웃을 초과
- 조치: cold-start 전용 타임아웃을
60s → 120s로 분리 - 효과: 해당 경로의 false-positive 제거
적용 가능 패턴
이 발견의 일반화 가능한 원리는: cold/warm 경로의 타임아웃은 별도 상수로 분리해야 한다. 단일 타임아웃 값으로 두 경로를 다루면, 한쪽을 최적화하면 다른 쪽이 손상된다. 운영 실패 60건 분석은 16편에서 다룬다.
기법 4: Proactive Preferences 피드백 루프
알림 임계를 정적으로 두지 않고, 사용자 반응을 신호로 삼아 동적으로 조정한다.
입력 신호
- 무반응 / "조용히" 신호: 해당 모드의 escalation 임계 상향
- "왜 안 알려줬어" 신호: 해당 모드의 escalation 임계 하향
학습 파라미터
- 지수이동평균(EMA) + 14일 윈도우
- 너무 빠른 학습률 → 알림 누락 (false-negative)
- 너무 느린 학습률 → 사용자의 반복 수정 요구
- 14일 윈도우가 수렴성과 반응성의 균형점
측정 결과
EMA 기반 수렴은 정적 임계값 대비 반복 튜닝 요청을 구조적으로 제거한다. 단, 학습 초기 2주 구간에서는 사용자 피드백이 많이 필요하다 — 이 초기 비용을 감수해야 이후가 조용해진다.
한계와 이식 방향
현재의 한계
- 초기 2주 학습 구간에서 오작동 가능성
- 13번 (
unknown) 진입 빈도가 높으면 피로도 증가 —unknown하위 분류 추가 필요 - EMA 윈도우 길이(14일)는 경험적 상수, 도메인 따라 재튜닝 필요
Hermes 이식
- Heartbeat 트리거: cron +
on_turn_starthook - 상태 머신 / 13 이유 enum: 그대로 이식
- Proactive 루프:
MemoryProvider.on_memory_write가 사용자 응답 패턴을 메모리에 기록 → escalation 임계 계산이 이 메모리를 참조
적용 가능 범위와 열린 질문
이 설계가 유효한 범위
- 알림 발생 빈도가 사용자 만족도에 직접 영향을 주는 시스템
- 상태 분류가 가능한 운영 환경 (관측 가능성 확보)
- 사용자 피드백이 수집되는 채널이 존재
열린 질문
unknown모드 진입률을 어디까지 낮출 수 있는가- EMA 외에 더 나은 학습 곡선이 존재하는가 (칼만 필터 등)
- 멀티 사용자 환경에서 개별 임계와 공통 임계를 어떻게 분리할 것인가
좋은 알림 시스템의 본질은 알리지 않을 조건을 명시할 수 있는가로 귀결된다. 15 모드와 13 이유는 그 명시화이고, cold-start 튜닝과 EMA 피드백은 그 명시를 오작동에서 보호하는 장치다.
시리즈 전체 안내: 시리즈 목차
댓글
댓글 쓰기