virtual-insanity
← 리포트 목록

에이전트 모델 쿨다운 아키텍처 개선 제안서

2026-08-17 cowork [architecture, cooldown, agent, bus_commands, proposal]

에이전트 모델 쿨다운 아키텍처 개선 제안서

작성: cowork | 일자: 2026-08-17 | 상태: 검토 대기


[권고]

1순위: bus_commands에 paused 상태 도입 + paused_until 컬럼 추가

쿨다운 감지 시 태스크를 즉시 failed 처리하는 대신 paused 상태로 보류하고, 만료 시각을 DB에 기록한다. 워커 루프(또는 sweeper)가 60초 주기로 paused → queued 복귀를 체크하여 자동 재개한다. 기대효과: 쿨다운 사유 태스크 손실 23건 → 0건. 중복 재시도([R1]/[R2]) 생성 제거.

2순위: model_cooldown_registry 테이블 신설 (인메모리 → DB 영속화)

현재 _MODEL_COOLDOWN_UNTIL 딕셔너리는 워커 프로세스 재시작 시 초기화된다. DB 테이블로 만료 시각을 영속화하면 재시작 후에도 쿨다운 상태가 보존되고, 오케스트레이터가 모델별 가용 여부를 실시간으로 조회할 수 있다. 기대효과: 쿨다운 잔여 시간 가시성 확보, 워커 재시작 후 동일 모델 즉시 재호출 방지.


[현황]

쿨다운 발생 경로 (실측)

모델 타임아웃 2회 연속
  → _MODEL_TIMEOUT_STREAKS[model_id] >= 2
  → _MODEL_COOLDOWN_UNTIL[model_id] = now + 1800초 (30분)
  → _eligible_model_chain() 에서 해당 모델 제외
  → 전체 모델 체인 소진 시: note = "all candidate models are cooling down"
  → return False, note, ""
  → 호출부에서 즉시 bus_commands.status = "failed" 전환

실제 피해 규모 (bus_commands DB 기준)

구분 건수
현재 활성 failed (쿨다운 사유) 3건
archived failed (쿨다운 사유) 20건
누적 손실 합계 23건

확인된 쿨다운 failed 사례: - #36016 codex — [R1] 8개 실패 태스크 공통 원인 진단 → all candidate models are cooling down - #36018 analyst-fundamental — [R1] 조선 사이클 선가·수주 현황 재수집 → 동일 - #36017 analyst-macro — [R1] 8/18-22 주요 매크로 이벤트 정리 → 동일

현재 스키마의 구조적 한계

  1. 인메모리 쿨다운: _MODEL_COOLDOWN_UNTIL = Python 딕셔너리, 프로세스 재시작 시 소실
  2. paused 상태 부재: bus_commands.status 허용값: queued / claimed / done / failed — 일시 보류 상태 없음
  3. 재큐 이중 차단:
  4. _INFRA_FAIL_PATTERNS에 "cooldown" 포함 → 오케스트레이터 재큐 차단
  5. FAILED_DUPLICATE_WINDOW_SEC = 7200초(2시간) → 쿨다운 30분 해소 후에도 같은 제목 중복 방지로 재생성 차단
  6. 가시성 없음: 어느 모델이 언제까지 쿨다운인지 대시보드/로그 외 확인 불가

[리스크]

R1. 쿨다운 중 태스크 축적 → 쿨다운 해소 후 burst 발생

  • paused 태스크가 동시에 queued로 복귀하면 모델 호출이 집중되어 재쿨다운 유발 가능
  • 완화: paused → queued 복귀를 에이전트당 1건씩 staggered 방식으로 처리

R2. paused 상태 태스크 영구 방치

  • 쿨다운 DB 레코드가 삭제/오염되면 paused 태스크가 queued로 복귀하지 못함
  • 완화: sweeper가 paused_until 초과 후 1시간 이상 미복귀 시 forced queued 전환 + ops_todos 등록

R3. 단일 장애점: model_cooldown_registry 테이블

  • DB 락 경쟁 시 워커 다수가 동시에 쿨다운 레지스트리를 읽고 쓸 때 일관성 문제
  • 완화: WAL 모드 사용(이미 ops_multiagent.db 적용 추정), 쿨다운 등록은 INSERT OR REPLACE로 원자성 보장

R4. 기존 재시도 로직([R1]/[R2]) 과의 충돌

  • _INFRA_FAIL_PATTERNS에서 "cooldown" 제거 시 오케스트레이터가 쿨다운 태스크를 다시 재큐할 수 있음
  • 완화: paused 상태 도입 후에는 오케스트레이터 재큐 로직이 paused 건을 건너뛰도록 필터 추가

[설계 명세]

1. bus_commands 스키마 변경

현재:

status: queued | claimed | done | failed

변경:

status: queued | claimed | paused | done | failed
paused_until: TEXT (ISO8601, NULL이면 무기한)
paused_reason: TEXT (사유: model_cooldown / credit_guard / manual)

마이그레이션:

ALTER TABLE bus_commands ADD COLUMN paused_until TEXT;
ALTER TABLE bus_commands ADD COLUMN paused_reason TEXT;

2. model_cooldown_registry 테이블 신설

CREATE TABLE IF NOT EXISTS model_cooldown_registry (
    model_id        TEXT PRIMARY KEY,
    agent           TEXT,                  -- 쿨다운을 발생시킨 에이전트
    cooldown_until  REAL,                  -- Unix timestamp
    timeout_streak  INTEGER DEFAULT 0,
    triggered_by    TEXT,                  -- timeout | rate_limit | manual
    created_at      TEXT,
    updated_at      TEXT
);
CREATE INDEX IF NOT EXISTS idx_mcr_until ON model_cooldown_registry(cooldown_until);

3. agent_queue_worker.py 변경 포인트 (코드 작성은 codex 위임)

변경 위치 A: _call_llm_with_guardrails() 반환 처리

현재: return False, "all candidate models are cooling down", ""
변경: return False, "all candidate models are cooling down", "PAUSE_UNTIL:{만료_timestamp}"

→ 호출부에서 반환값 세 번째 필드를 파싱하여 paused_until 계산

변경 위치 B: _register_model_cooldown() 신규 함수 - _MODEL_COOLDOWN_UNTIL 딕셔너리 갱신과 동시에 DB model_cooldown_registry INSERT OR REPLACE

변경 위치 C: _eligible_model_chain() 수정 - 인메모리 딕셔너리 우선 조회, miss 시 DB fallback 조회 (워커 재시작 복원)

변경 위치 D: 워커 메인 루프 또는 sweeper - 60초 주기로 SELECT id FROM bus_commands WHERE status='paused' AND paused_until <= unixepoch() 조회 - 해당 건을 status='queued', paused_until=NULL 전환 (에이전트당 1건 제한 burst 방지)

변경 위치 E: orchestrator.py _INFRA_FAIL_PATTERNS - "cooldown" 항목 제거 (paused 상태로 대체되므로 error follow-up 불필요) - _INFRA_NOISE에서도 "cooldown" 제거

4. 자동 재개 트리거 플로우

[워커 루프 / autopilot_sweeper.py 60s 주기]
  1. SELECT bus_commands WHERE status='paused' AND paused_until <= now()
  2. 결과 있음 → 에이전트별 1건씩 status='queued' 복귀
  3. model_cooldown_registry에서 해당 모델 쿨다운 만료 여부 확인
  4. 만료 확인 → _MODEL_COOLDOWN_UNTIL 딕셔너리 갱신 (워커 인메모리 동기화)
  5. ops_agent_events에 "paused_task_resumed" 이벤트 기록

[검증 방법]

KPI 기준

지표 현재 목표
쿨다운 사유 failed 건수 (30일) 23건 0건
쿨다운 후 자동 재개율 0% >= 95%
재시도([R1]) 생성 중 쿨다운 사유 비율 확인 불가 실시간 대시보드 가시화
쿨다운 해소 후 재개 지연 N/A (수동 재생성) <= 120초 (2 sweeper 사이클)

검증 명령 (구현 후)

# 1. DB 스키마 확인
sqlite3 ~/.hermes/data/ops_multiagent.db ".schema bus_commands" | grep paused
sqlite3 ~/.hermes/data/ops_multiagent.db ".schema model_cooldown_registry"

# 2. paused 태스크 현황
sqlite3 ~/.hermes/data/ops_multiagent.db \
  "SELECT id, target_agent, title, paused_until, paused_reason FROM bus_commands WHERE status='paused'"

# 3. 모델 쿨다운 레지스트리 현황
sqlite3 ~/.hermes/data/ops_multiagent.db \
  "SELECT model_id, datetime(cooldown_until,'unixepoch','localtime') as expires, timeout_streak FROM model_cooldown_registry ORDER BY cooldown_until DESC"

# 4. 자동 재개 이벤트 확인
sqlite3 ~/.hermes/data/ops_multiagent.db \
  "SELECT agent, event_data, created_at FROM ops_agent_events WHERE event_type='paused_task_resumed' ORDER BY created_at DESC LIMIT 10"

롤백 조건

아래 중 하나 발생 시 paused 상태 도입 롤백 (paused → failed 즉시 전환으로 복귀): - paused 태스크가 24시간 이상 queued 복귀 없이 방치된 건이 5건 이상 - 쿨다운 해소 후 burst 재호출로 동일 모델이 1시간 내 3회 이상 재쿨다운


[실행 위임]

단계 담당 내용
DB 마이그레이션 스크립트 작성 codex ALTER TABLE + CREATE TABLE DDL + 안전 실행 스크립트
agent_queue_worker.py 변경 A~D codex 위 변경 포인트 4곳 구현
orchestrator.py 변경 E codex _INFRA_FAIL_PATTERNS / _INFRA_NOISE 수정
autopilot_sweeper.py 재개 루프 추가 codex paused → queued 복귀 체크 루프
검증 및 ops_todos 등록 ron 구현 완료 후 위 검증 명령 실행
해리 최종 승인 해리 DB 스키마 변경(컬럼 추가)은 운영 DB 변경이므로 확인 후 실행

제안서 생성: cowork | 근거 데이터: bus_commands DB 직접 조회 (23건 손실 실측) + agent_queue_worker.py L395-925 코드 분석