# 보고 누락 방지 체계 Agent Meeting 기록 (v2 — 추가 조건 반영)
> 날짜: 2026-04-12 | 작업: task-1737.1 | 팀: dev5-team (마르둑 팀장)
> 참가자: 토르(백엔드), 불칸(에러핸들링), 야누스(DevOps), 로키(레드팀), 다빈치(GLM), 비너스(디자인), 아틀라스(QC)
> 선행: task-1733.1 미팅에서 6개 DM(A~F) + 5계층 아키텍처(L0~L4) 설계 완료
> 목적: Jay의 3가지 추가 조건에 맞춰 DM 재설계

---

## Jay 추가 조건 3가지

1. **토큰 소모 최소화** — 감지/전달/추적은 LLM 없이 bash/python 스크립트로. LLM은 보고서 요약 등 정말 필요한 곳에만.
2. **중복 보고 절대 금지** — 여러 layer가 동시 감지해도 Jay에게 1회만 보고. dedup 메커니즘 필수.
3. **보고 방식 통일** — 어떤 layer에서 보고하든 동일 형식. DM-E의 🔴/🟡/⚪ 템플릿을 모든 layer에서 공유.

---

## Q1: LLM vs 순수 스크립트 분류

### Cycle 1: DM-A ~ DM-C 분류

**토르 (백엔드):**
> DM-A(TCR): task-registry.json WAL 기록, .done 파일 생성, task-timers.json rotation — 전부 파일 I/O. `jq`로 JSON 파싱, `sha256sum`으로 report_hash 계산. LLM 끼어들 자리 없음. **순수 스크립트**.

**야누스 (DevOps):**
> DM-B(ESP): `inotifywait`가 파일 변경 감지, SQLite에 이벤트 INSERT, CircuitBreaker 상태 UPDATE. 전부 bash + `sqlite3`:
> ```bash
> inotifywait -m -e close_write /workspace/memory/events/ | \
>   while read path event file; do
>     sqlite3 /data/event-queue.db \
>       "INSERT INTO events(path,event,ts) VALUES('$file','$event',strftime('%s','now'));"
>   done
> ```
> **순수 스크립트**.

**불칸 (에러핸들링):**
> DM-C(MDA): Primary→Fallback→Fallback2 라우팅 로직, Telegram API `curl` 호출, `/ack` 명령 수신 후 SQLite 업데이트 — 모두 스크립트. **순수 스크립트**.

### Cycle 2: DM-D ~ DM-F 분류

**토르 (백엔드):**
> DM-D(PSL): report-ledger.db의 CRUD, read cursor 업데이트, E2E 시간 측정 — 전부 SQL. **순수 스크립트**.

**다빈치 (GLM 외부시각):**
> DM-E(SNF)가 논쟁 지점. 🔴/🟡/⚪ 우선순위 분류가 규칙 기반(exit code, status 필드)이면 스크립트로 됨. 보고서 내용을 읽고 중요성 판단하는 건 LLM 필요. 구분 필요.

**비너스 (디자인 외부시각):**
> 알림 메시지 포맷 자체는 템플릿으로 충분. "보고서 요약(summary)" — 작업 결과물 핵심을 2~3문장으로 요약하는 건 LLM 필요. `/summary` 명령 응답 시에만 LLM 호출하는 방식으로 제한.

**아틀라스 (QC 외부시각):**
> DM-F(SDD): metrics.db 5분 집계, 2σ 계산, daily health report — Python 표준 라이브러리로 통계 가능. "문장형 해석"은 템플릿, 원인 분석이 필요하면 그때만 LLM 호출.

### Cycle 3: 합의 — LLM 분류 최종

| DM | 판정 | LLM 사용 지점 |
|----|------|-------------|
| DM-A (TCR) | 순수 스크립트 | 없음 |
| DM-B (ESP) | 순수 스크립트 | 없음 |
| DM-C (MDA) | 순수 스크립트 | 없음 |
| DM-D (PSL) | 순수 스크립트 | 없음 |
| DM-E (SNF) | **혼합** | `/summary {id}` 명령 수신 시에만 LLM |
| DM-F (SDD) | **혼합** | `/analyze` 명령 수신 시에만 LLM |

**원칙**: 감지/전달/추적의 정상 경로는 100% 스크립트. LLM은 "Jay가 명시적으로 요청한 요약/분석"에만 투입.

---

## Q2: 중복 보고 방지 Dedup 메커니즘 설계

### Cycle 4: 기본 설계

**토르 (백엔드):**
> 단일 진실 원천(Single Source of Truth) 필요. 여러 레이어가 동시 감지해도 **보고 직전 단일 게이트** 필수. report-ledger.db의 `deliveries` 테이블에 `(task_id, completion_hash)` UNIQUE 제약:
> ```sql
> CREATE TABLE deliveries (
>   id              INTEGER PRIMARY KEY,
>   task_id         TEXT NOT NULL,
>   completion_hash TEXT NOT NULL,
>   created_at      INTEGER DEFAULT (strftime('%s','now')),
>   reported_at     INTEGER,
>   ack_at          INTEGER,
>   UNIQUE(task_id, completion_hash)
> );
> ```

**야누스 (DevOps):**
> dedup 게이트 스크립트 (`report_gate.sh`):
> ```bash
> TASK_ID="$1"; COMPLETION_HASH="$2"
> RESULT=$(sqlite3 /data/report-ledger.db \
>   "INSERT OR IGNORE INTO deliveries(task_id, completion_hash)
>    VALUES('$TASK_ID', '$COMPLETION_HASH'); SELECT changes();")
> if [ "$RESULT" -eq 1 ]; then echo "PROCEED"; else echo "DUPLICATE"; fi
> ```

### Cycle 5: 레이스 컨디션 분석

**불칸 (에러핸들링):**
> **시나리오 1 — 동시 감지**: SQLite WAL 모드에서 write serialization이 레이스 컨디션 자연 해결. 선착순 1건만 INSERT 성공.
>
> **시나리오 2 — DB 쓰기 성공 후 전달 실패**: INSERT 성공 → Telegram 전송 실패 시 dedup 레코드 존재로 재시도 불가. **치명적 버그**.
> 해결: `reported_at`을 NULL로 INSERT, 전달 성공 후 UPDATE:
> ```bash
> # 1. 자리 선점 (reported_at = NULL)
> sqlite3 /data/report-ledger.db \
>   "INSERT OR IGNORE INTO deliveries(task_id, completion_hash) VALUES('$TASK_ID', '$COMPLETION_HASH');"
> # 2. 전달 시도
> if send_telegram "$MESSAGE"; then
>   # 3. 전달 성공 기록
>   sqlite3 /data/report-ledger.db \
>     "UPDATE deliveries SET reported_at=strftime('%s','now')
>      WHERE task_id='$TASK_ID' AND completion_hash='$COMPLETION_HASH' AND reported_at IS NULL;"
> fi
> ```
> `reported_at IS NULL` 레코드는 watchdog이 5분마다 스캔하여 재시도.
>
> **시나리오 3 — DB 장애**: dedup 실패 시 보고 차단 + 에러 알림 (보수적 선택).

### Cycle 6: 로키의 공격 & 방어

**로키 (레드팀):**
> **공격 1**: 같은 task 재완료 시 task_id + completion_hash 동일 → 영원히 DUPLICATE.
> **공격 2**: 30일 전 완료 task 재실행 → ledger 잔존 → 정당한 재보고 차단.
> **공격 3**: Telegram 장기 다운 → watchdog 수백 회 재시도 루프.

**불칸:**
> 공격 1,2 방어: 재완료 시 새 `run_id` (nanosecond timestamp) 발급. `completion_hash = sha256(task_id + run_id + done파일내용)` → 항상 고유.

**토르:**
> 공격 2 방어: 30일 TTL cleanup 크론잡:
> ```bash
> # cron: 매일 03:00
> sqlite3 /data/report-ledger.db "DELETE FROM deliveries WHERE created_at < strftime('%s','now') - 2592000;"
> ```

**야누스:**
> 공격 3 방어: 재시도 전 CircuitBreaker 상태 확인:
> ```bash
> CB_STATE=$(sqlite3 /data/event-queue.db "SELECT state FROM circuit_breaker WHERE channel='telegram';")
> [ "$CB_STATE" = "OPEN" ] && exit 0  # CB OPEN이면 재시도 건너뜀
> ```

**다빈치:**
> 정당한 재보고 기준 명시: (1) 새 run_id 발급된 재실행, (2) Jay의 `/resend {task_id}` 명령. 이 외 모두 dedup 차단.

### Cycle 7: QC 검증

**아틀라스 (QC):**
> - TC-01: DM-B+DM-D 동시 감지 → 1건만 PROCEED. **통과**
> - TC-02: 전달 실패 후 watchdog 재시도 → CB CLOSED 확인 후 재전송. **통과**
> - TC-03: 같은 task 재실행 → 새 run_id → 새 hash → 정상 보고. **통과**
> - TC-04: SQLite 접근 불가 → 보고 차단 + 에러 알림. **통과**
> - TC-05: 30일 후 재완료 → TTL cleanup → 정상 보고. **통과**

**비너스:**
> Dedup 차단 시 Jay에게 피드백 없음 → 불안 가능. 차단 로그를 `dedup-blocked.log`에 기록, Jay에게는 미전달. 사후 추적 가능.

---

## Q3: 통일 보고 템플릿 확정

### Cycle 8: 개별 보고 템플릿

**비너스 (디자인/UX):**
> 3초 우선순위 파악 → 10초 내용 파악 → CTA 실행. 모바일 텔레그램 최적화:
> ```
> {PRIORITY_ICON} [{PRIORITY_LABEL}] {TASK_ID}
> ━━━━━━━━━━━━━━━━━━━━
> 📋 작업: {TASK_TITLE}
> ✅ 상태: {STATUS}
> 🕐 완료: {COMPLETED_AT}  |  소요: {ELAPSED}
> 📦 결과: {OUTPUT_SUMMARY_150}
> ━━━━━━━━━━━━━━━━━━━━
> 🔗 출처: {SOURCE_LAYER} | 키: {IDEMPOTENCY_KEY}
> 💬 CTA: {CTA_TEXT}
> ```

**토르:**
> 모든 플레이스홀더는 `.done` 파일 + `report-ledger.db`에서 스크립트로 추출 가능. LLM 불필요.

**불칸:**
> CTA 조건 분기 (스크립트 딕셔너리):
> - STATUS=DONE → "✅ /ack {TASK_ID}"
> - STATUS=FAILED → "🔁 /retry {TASK_ID} | /log {TASK_ID}"
> - STATUS=WARNING → "👀 /inspect {TASK_ID} | /ack {TASK_ID}"
> - STATUS=TIMEOUT → "⏱ /retry {TASK_ID} | /log {TASK_ID}"

### Cycle 9: 로키의 엣지케이스 공격

**로키:**
> **공격 1**: OUTPUT_SUMMARY에 마크다운 특수문자 → 텔레그램 파싱 오류.
> **공격 2**: TASK_TITLE이 유니코드 혼합 시 4096자 계산 오류.
> **공격 3**: 동일 IDEMPOTENCY_KEY로 두 레이어 동시 발송.

**토르:** 공격 1 방어: `sanitize_markdown()` 함수 — `_→\_`, `*→\*` 이스케이프. `format_report.py`에 1회 구현, 모든 레이어 임포트.

**야누스:** 공격 3 방어: `deliveries` 테이블의 `UNIQUE(task_id, completion_hash)` 제약이 SQLite 레벨에서 차단.

**아틀라스:** 공격 2 방어: Python `len(message)` 기준(유니코드 코드포인트). 4000자 soft limit, 초과 시 `OUTPUT_SUMMARY`부터 절삭.

### Cycle 10: 배치 다이제스트 템플릿

**비너스:**
> ```
> 📊 일일 보고 다이제스트
> ━━━━━━━━━━━━━━━━━━━━
> 📅 기간: {DATE_START} ~ {DATE_END}
> 🕐 생성: {GENERATED_AT}  |  출처: {SOURCE_LAYER}
> ━━━━━━━━━━━━━━━━━━━━
> 📈 요약
>   ✅ 완료: {DONE_COUNT}건  ❌ 실패: {FAILED_COUNT}건
>   ⚠️  경고: {WARN_COUNT}건  ⚪ INFO: {INFO_COUNT}건
> ━━━━━━━━━━━━━━━━━━━━
> 🔴 CRITICAL ({CRITICAL_COUNT}건, 최대 10건):
> {CRITICAL_LIST_INLINE}
> 🟡 NORMAL 주의 ({NORMAL_WARN_COUNT}건):
> {NORMAL_WARN_LIST_INLINE}
> ━━━━━━━━━━━━━━━━━━━━
> 💬 CTA: /ack_all_critical | /log_digest {DATE_START}
> 🔑 DIGEST_ID: {DIGEST_ID}
> ```

**다빈치:** ⚪INFO는 카운트만 표시, 상세 없음. CRITICAL 10건 초과 시 "외 N건 — /log_digest".

**야누스:** `generate_digest.py`를 cron 매일 06:00 실행 + `/summary` 명령도 동일 스크립트 on-demand 호출.

### Cycle 11: 포맷터 SPOF 방어

**로키:** `format_report.py` 자체가 예외 시 보고 불가. 모든 레이어 의존 → 단일 장애점.

**토르:** 폴백 플레인텍스트:
```
[FALLBACK] TASK_ID={TASK_ID} STATUS={STATUS} TIME={TS}
※ 포맷터 오류로 단순 텍스트 전송. /log {TASK_ID} 확인 요망.
```
bash `printf`로 생성 가능 → Python 런타임 오류도 커버.

---

## Q4: 토큰 소모 추정

### Cycle 12: 현재 시스템 추정

**토르:**
> 현재 작업 1건 완료 시 LLM 호출:

| 단계 | LLM 호출 | 입력 토큰 | 출력 토큰 |
|------|---------|----------|----------|
| .done 읽기 + 내용 파악 | 1회 | ~800 | ~200 |
| 보고 초안 작성 | 1회 | ~1200 | ~400 |
| Jay용 요약 생성 | 1회 | ~600 | ~300 |
| watchdog 상태 점검 | 1회 | ~500 | ~150 |
| **합계 (1건당)** | **4회** | **~3100** | **~1050** |

**야누스:** watchdog 30분마다 × 48회/일 × ~400 토큰 = 19,200 토큰/일 추가.

**다빈치:** 컨텍스트 누적 비용 누락. 10번째 작업에서 입력 토큰 800→5000+. 실제 비용 2~3배 높음.

**토르:** 수정 추정: 일 10건 기준 **~100,000~150,000 토큰/일**.

### Cycle 13: 개선 후 추정

**야누스:**
| 기존 LLM 단계 | 개선 후 | LLM? |
|-------------|--------|------|
| .done 읽기 | `jq` + python 직접 파싱 | ❌ |
| 보고 포맷 | `format_report.py` 템플릿 | ❌ |
| Jay용 요약 | OUTPUT_SUMMARY 필드 사전 정의 | ❌ |
| watchdog 점검 | SQLite 쿼리 + bash 임계값 비교 | ❌ |
| 보고서 내용 요약 | Jay `/detail` 요청 시에만 | ✅ (선택적) |

**토르:** 최종 비교:

| 구분 | 현재 (보수적) | 개선 후 | 절감률 |
|------|------------|--------|-------|
| 작업 1건당 | ~4,000 토큰 | 0 토큰 | 100% |
| watchdog 일 48회 | ~19,200 토큰 | 0 토큰 | 100% |
| 다이제스트 생성 | ~2,000 토큰 | 0 토큰 | 100% |
| Jay /detail 요청 | — | ~1,500/건 | — |
| **일 10건 합계** | **~100,000 토큰** | **~3,000 토큰** | **97%** |

**불칸:** OUTPUT_SUMMARY는 (1) 사전 정의 작업 설명 + (2) .done 파일 `result_summary` 필드(200자). 모두 LLM 없이 처리.

**아틀라스:** 97% 절감 검증: 기존 1주일 Anu 로그 분석 → 스테이징 동일 작업 세트 비교. 2주 후 실측 보고.

---

## 최종 합의 결과

### 합의 1: LLM 분류 (제약조건 #1)
- DM-A~D: 100% 순수 스크립트
- DM-E: 정상 경로 스크립트, `/summary` `/detail` 요청 시에만 LLM
- DM-F: 정상 경로 스크립트, `/analyze` 요청 시에만 LLM
- **토큰 절감**: 일 ~100,000 → ~3,000 (97% 절감)

### 합의 2: Dedup 메커니즘 (제약조건 #2)
- **게이트**: report-ledger.db `deliveries` 테이블 UNIQUE(task_id, completion_hash)
- **플로우**: INSERT OR IGNORE → changes()=1이면 PROCEED, 0이면 DUPLICATE
- **전달 실패 방어**: reported_at NULL 자리 선점 → 전달 성공 시 UPDATE → watchdog 재시도
- **재완료 허용**: 새 run_id(nanosec) → 새 hash → 정상 통과
- **TTL**: 30일 cleanup 크론잡
- **DB 장애**: 보고 차단 + 에러 알림

### 합의 3: 통일 템플릿 (제약조건 #3)
- **개별 보고**: 🔴/🟡/⚪ 우선순위 + 상태/소요/결과/출처/CTA 통일 포맷
- **배치 다이제스트**: 일별 집계 + CRITICAL 상위 10건 인라인 + 요약 카운트
- **공유 포맷터**: `format_report.py` (render_individual, render_digest, sanitize_markdown)
- **폴백**: bash printf 플레인텍스트 (Python 장애 대응)
- **4000자 soft limit**: 초과 시 OUTPUT_SUMMARY부터 절삭

### 전원 합의
- 토르: ✓ 기술적 구현 가능
- 불칸: ✓ 레이스 컨디션 해결 확인 (조건: reported_at 2단계 방식 필수)
- 야누스: ✓ (조건: cron cleanup + CB 연동)
- 로키: ✓ (조건: sanitize + 폴백 + TTL 모두 구현)
- 다빈치: ✓ (조건: 2주 실측 검증)
- 비너스: ✓ (조건: 다이제스트 INFO 숨김 + 4000자 절삭)
- 아틀라스: ✓ (조건: TC-01~05 테스트 자동화 + 2주 실측)

---

## 다음 액션 아이템

1. 토르: `format_report.py` 구현 (sanitize, render_individual, render_digest, fallback)
2. 야누스: `generate_digest.py` cron 등록 + `/summary` 훅
3. 불칸: `.done` 파일 스펙에 `result_summary` 필드(200자) 표준화
4. 야누스: `report_gate.sh` dedup 게이트 스크립트 구현
5. 아틀라스: 스테이징 토큰 소모 실측 테스트 설계 (2주 후)
