Layer-A Diagnose only. classify_worker.py:691 직전에 event_kind_hint 의
raw/normalized/in_valid/confidence 값 capture (logger.info 5줄 insert,
lazy formatting + %r repr). guard 통과 X 의 specific root cause (A1 field
부재 / A2 빈 string / A3 invalid enum) 확정용.
specific fix (default note / enum mapping / prompt 강화) 는 별 PR-4B-Fix-EventKindHint-Apply.
Apply PR closure gate 에 logging cleanup (info → DEBUG 또는 제거) 흡수.
plan: ~/.claude/plans/c-1-pr-infra-drift-1-phase-1b-linear-frost.md
backup: app/workers/classify_worker.py.pre-4b-eventkind-logging.20260517
Layer 1 root cause 진단을 위해 classify_worker.py:595 의 exception logging
을 lazy formatting + exc_info=True 로 강화. f-string 1줄 → 5줄 block.
- type=%s: exception class name (TimeoutError/JSONDecodeError/ValueError/etc.)
- repr=%r: full exception state
- exc_info=True: traceback 까지 capture (wrapper 정확 지점 추적)
본 PR scope = Diagnose only. Layer 1 specific fix (H1/H2/H3/H4) + Layer 2
escalate path ai_event_kind fallback set 은 별 PR queue.
plan: ~/.claude/plans/c-1-pr-infra-drift-1-phase-1b-linear-frost.md
backup: app/workers/classify_worker.py.pre-4b-diagnose.20260517
5곳 LLM_TIMEOUT_MS + 2곳 outer wait_for align (classifier 30s 와 동일 정책).
synthesis/evidence/verifier/query_analyzer 모두 동시 부하 시 30s 까지 필요.
Regression fixture 결과: 10/10 HTTP 200 + 5/5 search + 3/3 failure injection
모두 PASS (회귀 0). 응답 시간 +4~20s 증가 (안정성 ↑ 의도된 trade-off).
p95 12s gate 는 여전히 FAIL — B-1 Throughput-1 (priority queue / 모델 분리)
별 plan 으로 latency 단축 방향 진입.
Curl-Refine-2 (SHIPPED): 3 SKILL.md 본문 "Tool 선택 (필독)" 단락 추가 — terminal
direct curl 강조 + execute_code Python wrap 금지. E2E: Gemma 1st turn
execute_code → terminal 전환 + DS API 도달 0→1 + real corpus citations
("test-voice-memo", "The Good List") 첫 성공. Hard-Enforcement-1 의 hook 와
시너지 (1 call cap + 1st 정상 path).
MaxTokens-Followup 1차 (PARTIAL+REVERTED): agent.disabled_toolsets 15 toolsets
비활성 → stream 102KB→80KB 22% 감소. BUT Gemma terminal tool_call 시
"invalid tool call" 회귀 발생 → revert. toolset dependency graph 조사 후
minimal safe disabled list 결정 = 별 트랙 PR-Hermes-MaxTokens-Investigation-1.
A 카테고리 6 PR + 부산 Curl-Refine-2 모두 SHIPPED. PR-1/2 user-facing E2E 완성.
PR-Hermes-Docsrv-Search-1 / PR-Hermes-WebSearch-1 의 user-facing E2E 마지막 조각.
Adapter A 후 잔존한 401: execute_code/terminal 샌드박스가 HERMES_DOCSRV_TOKEN
strip. 해결 = ~/.hermes/config.yaml terminal.env_passthrough 1줄 추가.
검증:
- Direct: is_env_passthrough("HERMES_DOCSRV_TOKEN")=True, CLAUDE_API_KEY=False
(GHSA-rhgp-j443-p4rf provider blocklist 유지)
- E2E: Hermes chat → DS API 200 → conf=medium completeness=full + real corpus
citations ("test-voice-memo", "The Good List: 6 Things to Add Joy to Your Day")
PR-1/2 user-facing E2E unlock 완료 — Discord smoke 검증 진입 가능
(가족 onboarding 전 hyungi 채널 한정).
ddgs (DuckDuckGo) provider 활성. Layer 1 fixture 4/4 results (p95 12.3s, ddgs raw
latency 한계).
SearXNG (LocalScout PR-A 잔존) 활성화는 PR-2B 로 분리 — LAN-only bind 로 Mac mini
Tailscale 접근 불가. ddgs 1주 사용 후 SearXNG swap ROI 판정 예정.
channel_prompts 9줄 통합 (PR-1 4줄 + PR-2 web 분기 5줄). LLM tool-call 실제
실행은 Adapter A blocker — Layer 2/3 user-facing E2E 는 Adapter A closure 후.
llm_gate.py docstring 영구 룰: "MLX primary 호출 경로는 예외 없이 gate 획득 필수".
PR #20 이후 classifier (Mac mini 26B 신규) + evidence (triage→Mac mini 26B 통합)
모두 gate 외부 실행 — concurrent 안전성 별 검토 명시. 1주 관찰 결과: race 빈번.
본 PR-Hermes-Docsrv-Search-1 Layer 1 fixture 측정:
- 8/10 query "conservative_refuse(no_classifier)" — classifier 가 동시 부하 시
거의 모두 ReadTimeout 또는 wait_for(6s) timeout
- evidence ev_ms=15005 — synthesis 와 race 로 15s 누적
영향:
- ask total 시간 증가 (parallel race → serialized): query_analyzer 5s +
classifier 3-5s + evidence 5s + synthesis 30s ≈ 40-45s 상한 (현실 평균)
- 응답률 ↑: race timeout 으로 인한 conservative_refuse 해소
- 사용자 체감: 빠른 거절 → 의미있는 답변. 단 대기 시간 ↑
후속:
- skill `docsrv_ask` curl `--max-time 20` → 60s 상향 필요 (별 PR 또는 본 PR
안의 follow-up)
- 본 메모리 `2026-05-21 Mac mini 26B 1주 부하 측정` observation 의 결정
outcome: gate 복귀 (triage 별 작은 모델 재도입 옵션은 보류)
A1 (LLM_TIMEOUT_MS 5→15→30) + config(10→15→30) 후속 진단: 8/10 fixture query 가
"classifier ok" 또는 "classifier error" 로그 없이 conservative_refuse(no_classifier)
경로. search.py:518 의 outer wrapper `asyncio.wait_for(classifier_task, timeout=6.0)`
가 classifier_service.LLM_TIMEOUT_MS 와 httpx timeout 모두 override.
6s 한계 → 동시 부하 시 거의 모든 classifier 호출 6s 안에 못 끝남 → AsyncIO TimeoutError
→ ClassifierResult("timeout") → refusal_gate 가 verdict=None 받아 conservative_refuse.
15s 로 상향 — classifier_service 내부 30s 와 align 하지 않은 이유 = ask 응답 시간 상한
유지 (evidence parallel 종료 후 추가 9s 대기 cap). Mac mini 26B 동시 부하 시 실측
elapsed 11-14s 까지 자주 발생 → 15s 가 합리 균형.
본 fix 가 진짜 closure 효과. PR-Hermes-Docsrv-Search-1 Layer 1 fixture 의 8/10
no_classifier 경로 해소 예상.
A1+config(15s) 후속 진단: voice memo PoC plan 호출 elapsed_ms=14432 — 15s 한계 거의
밀착. Mac mini 26B 동시 부하 (classifier + evidence + synthesis 3-way) 시 빈번
ReadTimeout 잔존.
30s 로 2x 마진 확보 — config.yaml + classifier_service.py 양쪽 align. Phase 3.5
guardrail 동작 자체에는 영향 없음 (timeout 시 fallback 경로 동일).
향후 별 트랙 (DS-Mac-mini-26B-Concurrent-Load-1): asyncio.Semaphore 도입으로
Mac mini 26B 동시 호출 제한 vs triage 만 작은 모델 재도입. 본 PR 은 timeout
완화만.
A1 timeout 5s → 15s 후 진단 로그가 httpx.ReadTimeout('') 확정. classifier_service
의 asyncio.timeout 외부 wrap (15s) 보다 AIClient._request 내부 httpx timeout
(10s, config.yaml classifier.timeout) 가 먼저 fire → ReadTimeout 빈 메시지 raise.
두 timeout 을 15s 로 align — Mac mini 26B 동시 부하 (PR #20 후속) 시 classifier
지연 ≤15s 까지 허용.
후속: evidence_service.py / synthesis_service.py 의 timeout 도 동일 패턴 검토
필요 (별 PR, DS-Mac-mini-26B-Concurrent-Load-1 트랙).
PR-Hermes-Docsrv-Search-1 Layer 1 fixture 가 classifier error: <빈 메시지> 빈번 발생
보고. isolation 직접 호출은 3/3 성공, 동시 부하 (ask endpoint 의 classifier + evidence
parallel) 시에만 발생.
Exception type + repr 캡처해서 root cause 식별 (httpx.ReadTimeout / TimeoutError /
ConnectionError / 기타 무엇인지). 식별 후 후속 PR (DS-Classifier-Concurrent-Load-1)
에서 본격 mitigation.
PR #20 (f139945) GPU LLM 제거 후 Mac mini 26B 가 triage + classifier + chat + STT
동시 흡수. classifier_service hardcoded 5s timeout (config.yaml `timeout: 10` 무시)
이 동시 부하 시 빈번 초과 → CIRCUIT_THRESHOLD(5) 누적 → circuit 60s open →
verdict=None → refusal_gate conservative_refuse(no_classifier) 경로.
실측: 정상 부하 단독 호출 = 2.3s (500 prompt + 49 completion tokens), 동시 호출 시
ev_ms/synth_ms 가 15s 까지 누적 — 5s 한계가 architectural mismatch.
15s 로 상향 → classifier 정상 verdict 반환 → refusal_gate 가 classifier 의
sufficient/insufficient 사용 (conservative fallback 회피).
본 fix 는 [[2026-05-21 Mac mini 26B 1주 부하 측정]] observation 의 회귀 결과로
자연 정리. config.yaml `classifier.timeout: 10` 와는 별 변수 — 본 1줄은 코드 내
한계, config 항목은 별 PR (Config-Driven-Timeout-1) 에서 통합 검토.
발견 경로: PR-Hermes-Docsrv-Search-1 Layer 1 fixture (curl direct, 10/10 ask)
가 conservative_refuse(no_classifier) 8건 + timeout 2건 보고. fastapi log
"classifier circuit OPEN for 60s" + "classifier timeout" 페어 발견.
documents_md_status_check 제약은 {pending/processing/success/partial/failed/skipped}
만 허용. extract_worker 의 web HTML 분기가 'ready' 박아서 CheckViolationError
로 3회 실패. plan/docs/메모리에 'ready' 로 잘못 표기됐던 것 수정.
19668 (첫 sample doc) 검증 중 발견. fix 후 queue 'failed' 행 reset 으로 재실행.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
약한 국가 (TW/HK/IN/CN 활성 2) 보강 후보 8건. 자동 HEAD 검증 4/8 ✅:
- HKFP / The Hindu / TOI World / Caixin English
URL 갱신 필요 4건 — Focus Taiwan / 自由時報 / Scroll.in / RTHK
사용자가 직접 RSS index 확인 후 갱신 + enable 결정. 본 PR INSERT 안 함.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
asyncpg 가 :days || ' days' 의 int → text 암묵 변환을 거부함.
make_interval 사용으로 int 그대로 바인딩 가능.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
document_chunks.country 가 7일 분포 기준 99.9% NULL 이었던 root cause = news_collector 가
summarize + embed 만 enqueue 하고 chunk 를 enqueue 하지 않아 chunk_worker 가 news 문서에 한 번도 안 돌고 있었음.
queue_consumer.next_stages 의 summarize 키 부재가 follow-up 미연결 원인.
news 외 summarize 흐름 부수영향 회피를 위해 next_stages 가 아니라 news_collector RSS/API 양쪽에 chunk
enqueue 1줄씩 명시 추가. days_old <= 30 가드 안에서 embed 와 동일 정책.
scripts/news_chunk_country_backfill.py — doc 단위 small batch, 실패 doc skip,
50건마다 progress. queue 우회 직접 chunk_worker.process 호출로 timing 통제.
Gate (PR closure):
A) chunked_doc_pct > 95% 최근 7일 news doc 중 chunk 보유 비율
B) country null_pct < 5% 최근 7일 news chunk country NULL 비율
plan: ~/.claude/plans/7-whimsical-crab.md (PR-News-Prep-Layer-1)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
/digest 라우트 신규 — Phase 4 (7일 rolling country×topic batch digest) backend
운영 데이터 사용자 진입점. 최신 1건 (GET /api/digest/latest) 표시 + country
pill 탭 + topic 카드 (rank/label/summary/article_count/importance, fallback
Badge 조건부).
- frontend/src/routes/digest/+page.svelte 신규 (123 LOC) — Svelte 5 runes,
Tabs snippet 패턴, 404 EmptyState 흡수, country reload 보호.
- frontend/src/routes/+layout.svelte nav 1줄 추가 (아침 브리핑 뒤).
후속 별 PR: date picker, article click 라우팅, 국기+한국어 dictionary,
Phase 4.6 feedback loop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
watch_inbox() 가 new_count/changed_count 둘 다 0 일 때 silent — PR-NAS-Watch-Folder 검증 시 fire 추적 부재 확인 후 보완. else 분기 추가해 매 5min fire 마다 "변경 없음 (idle)" info 로그 한 줄.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ollama 는 home-gateway-network / document_server / ollama_default 3개 network 에 속해
range loop 가 모든 IP concat. (index .NetworkSettings.Networks "hyungi_document_server_default").IPAddress
로 명시. 다른 GPU 서비스 4개도 동일 single-network 이라 호환.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OCR/STT 컨테이너 안에 curl 미설치 (slim python image). docker exec curl 표준은
실측 OCI exec 실패. host curl + docker bridge IP (172.20.0.x) 로 변경 — host
publish 추가 아니라 docker network 내부 검증이라 보안 표면 동일.
reranker 만 curl 있고 OCR/marker/STT 는 python 만 있어 분기 발생을 회피.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GPU 서버 정체성 = embedding/rerank/STT/OCR/marker 특화 백엔드.
Generative LLM 0. Mac mini gemma-4-26B-A4B 가 triage + primary +
classifier 모두 흡수. fallback 은 Claude Sonnet 4 API (자동 trigger,
premium 과 budget 공유).
- triage: GPU Ollama gemma4:e4b → Mac mini :8801 26B (primary 동일 endpoint)
- fallback: GPU Ollama gemma4:e4b → Claude Sonnet 4 API (require_explicit_trigger=false)
- classifier: GPU Ollama gemma4:e4b → Mac mini :8801 26B (max_tokens 512)
- primary / premium / embedding / rerank: 변경 0
후속 (별 커밋): `ssh gpu "ollama rm gemma4:e4b-it-q8_0"` — VRAM ~11GB 회수.
Mac mini 단일화 위험 mitigation = (1) Mac mini uptime 31d 무중단 검증,
(2) Claude Sonnet 4 API daily_budget $5 안 (Mac mini up 가정 호출 빈도 낮음),
(3) Beszel siteMonitor :8801 health check + Synology Chat alert.
plan: ~/.claude/plans/rosy-launching-otter.md §C/§D/§E (7-device LLM 배치 + 운영 전략)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
사용자 요청 (2026-05-13):
- 오늘 briefing 만 보여주고 과거 못 보는 게 아쉬움 → 날짜 선택 UI
- 시간대 별 나열은 오히려 불편 → date dropdown 1단계 선택
- 각 카드에 읽음/하이라이트 토글
Schema (migrations 263~266, 단일 statement):
- briefing_topics.is_read BOOL NOT NULL DEFAULT false
- briefing_topics.read_at TIMESTAMPTZ
- briefing_topics.highlighted BOOL NOT NULL DEFAULT false
- briefing_topics.highlighted_at TIMESTAMPTZ
API (app/api/briefing.py):
- TopicResponse 에 id / is_read / read_at / highlighted / highlighted_at 추가
- GET /api/briefing/dates → 사용 가능 날짜 목록 (60일 cap)
· briefing_date / total_topics / total_articles / status / read_count / highlighted_count
- PATCH /api/briefing/topics/{id}/read body {value: bool} → 읽음 토글
- PATCH /api/briefing/topics/{id}/highlight body {value: bool} → 하이라이트 토글
- 토글 시 *_at 컬럼 자동 설정/NULL
UI (frontend/src/routes/news/+page.svelte):
- 헤더 우측 <select> date dropdown — 최신 + N일치 (highlighted_count 별 표시)
- 선택 시 /api/briefing?date=… 로 해당 날짜 briefing 로드
- 카드 우측 상단 ★ (하이라이트) + 읽음 버튼
- 하이라이트 = Card class ring-2 ring-yellow-400
- 읽음 = 외부 div class opacity-60 (시각 차분화, 펴기 가능)
- 토글 즉시 PATCH 호출 + 로컬 state 갱신
each key topic.topic_rank → topic.id 변경 (이미 unique).
briefing/digest 의 cross-country tech 토픽 다양성 확보용 source seed.
- KR ×2: GeekNews (Hada), AI Times
- US ×4: Hacker News, ArsTechnica AI, The Verge Tech, TechCrunch
- GB ×2: The Register, BBC Technology
- DE ×1: Heise Online
- JP ×2: ITmedia News, Gigazine
- CN ×1: 36Kr
- FR ×1: ZDNet France
- IN ×1: Analytics India Magazine
idempotent: WHERE NOT EXISTS (name). 운영 DB 에는 이미 적용됨,
백업 복원/신규 deploy 환경에서 자동 시드.
수집 검증 (2026-05-13 1차 fire, 8 source):
- 성공: Hacker News 30 / ArsTechnica AI 20 / Verge 10 / TC 20 / Register 50 / Heise 153 (총 283건 신규)
- 후속 fix: GeekNews 의 http redirect → feedburner 직접 URL, AI Times URL 오타 → S1N1.xml.
content category 는 news_sources.category (Tech / AI) 로 보존, briefing 의 country
필터 (MIN_COUNTRIES_PER_TOPIC ≥ 2) 와 호환.