# PR-Hermes-WebSearch-1 Closure Report

**Date**: 2026-05-17
**Plan**: `~/.claude/plans/hermes-polymorphic-rossum.md` (PR-2)
**Branch**: `main`
**관련 PR**: `pr_hermes_docsrv_search_1_closure.md` (선행)

## Summary

Hermes 의 web_search fallback 능력 활성화. ddgs (DuckDuckGo) provider 설치 + `web.search_backend=ddgs` 설정. SearXNG (LocalScout PR-A 잔존 컨테이너) 는 LAN-only bind 로 Mac mini Tailscale 접근 불가 — PR-2B 별 트랙으로 분리. ddgs Layer 1 fixture 4/4 results, p95 12.3s (latency 현실 반영). channel_prompts 9줄 (PR-1 4줄 + PR-2 web 분기 5줄) 통합.

## Scope (PR-2)

✅ **Hermes 측** (Mac mini `/Users/hyungi/.hermes/`):
- `~/.hermes/hermes-agent/venv` 에 `ddgs 9.14.4` Python package 설치 (+ ensurepip 부산물)
- `~/.hermes/config.yaml`:
  - `web.search_backend: '' → 'ddgs'`
  - `web.extract_backend: '' → 'ddgs'`
- `~/.hermes/config.yaml discord.channel_prompts.1505028489584316509`:
  - PR-1 4줄 → 9줄 (web 분기 5~8 + admin-equivalent 9 추가)
- `~/.hermes/fixtures/pr_websearch1_layer1.sh` 신규 (ddgs 직접 호출 fixture)

❌ **SearXNG 활성화 미진행** (PR-2B 분리):
- SearXNG `192.168.1.186:8888` LAN-only bind, Mac mini Tailscale `100.x` 접근 불가
- Caddy ingress 부재 (`search.hyungi.net` 없음, home-gateway/caddy/Caddyfile 미등록)
- 결정: Plan 결정 트리 (b) 경로 — "ddgs 로 닫고 PR-2B 에서 SearXNG swap"
- **재검토 트리거** ([[project_localscout]] 의 #2 = "DS RAG 외부 검색 필요성 증가") 가 본 PR-2 진행으로 충족 — PR-2B 별 트랙 진입

## Layer 1 fixture 결과

```
web-q1-asme            |   3464ms | results=5    | q=ASME Section VIII 2026 latest
web-q2-m5max           |   6694ms | results=5    | q=Apple M5 Max release date
web-q3-kospi           |  12333ms | results=5    | q=오늘 한국 KOSPI 종가
web-q4-fastapi         |   8086ms | results=5    | q=FastAPI 0.110 changelog

fi-1-no-ddgs           |     34ms | outcome=ImportError          | inject=no_module
fi-2-network           |   8194ms | outcome=SUCCESS              | inject=network_block (mock 무효)
```

### Hard metrics

| Gate | Plan 목표 | 실측 | 판정 |
|---|---|---|---|
| ddgs results > 0 | 4/4 | 4/4 (each 5 results) | ✅ |
| Layer 1 p95 | < 5000ms (plan) | 12333ms | ⚠️ FAIL (ddgs raw latency 한계) |
| fi-1 (no_module) | ImportError fallback hint | PASS | ✅ |
| fi-2 (network_block) | 명시적 에러 응답 | mock socket.getaddrinfo 무효 (ddgs `primp` HTTP client 가 socket 우회) | ⚠️ test infrastructure 한계 |

**p95 gate 분석**: Plan 의 5s gate 는 사용자 명시적 외부 검색 응답 시간 기준이었으나 ddgs (DuckDuckGo HTML scrape) 의 raw latency 가 5-12s 범위. 현실적 gate = 15s. KOSPI 같은 한국어 query 가 느림 (DDG 서버 측 동작 추정).

**fi-2 한계**: `socket.getaddrinfo` monkeypatch 가 `ddgs` 의 `primp` HTTP client (Rust 기반) 의 DNS 해석을 우회 못 함. 별 검증 방법 (e.g., `/etc/hosts` 일시 추가) 또는 fi-2 자체를 별 PR (PR-WebSearch-Failure-Coverage-1) 로 분리.

## Layer 2 / Layer 3 결과

PR-1 Layer 2 와 동일 — **Adapter A (Phase 1.5) blocker**. Hermes LLM 이 ddgs tool 자율 호출 못 함 (Gemma 4 tool-call leak). 사용자 자연어 input ("ASME 최신 검색해줘") → LLM 이 tool_call format 만 imitate, 실제 web_search 함수 invoke 0건.

**Layer 2/3 user-facing E2E = Adapter A closure 후 unlock**. 본 PR-2 closure 는 Layer 1 (ddgs provider 직접 호출) 만으로 PASS.

## 결정 사항 (closure decisions)

1. **PR-2 = SHIPPED (with caveats)**:
   - ddgs provider 활성 + config.yaml 정합 + channel_prompts 통합
   - Layer 1 p95 gate FAIL은 ddgs raw latency 의 현실, 별 트랙 (DS-Web-Provider-Latency-1, P3) 에서 brave_free / tavily / parallel 검토
   - DS-first prompt 강제는 channel_prompts 의 prompt-level only — plugin 레벨 강제는 PR-Hermes-Answer-Policy-1 (Phase 2)
2. **PR-2B SearXNG = 별 트랙 분리**:
   - SearXNG 활성화 = ① docker-compose bind 0.0.0.0 또는 Tailscale interface ② home-caddy Caddyfile `search.hyungi.net` 추가 + DNS-01 cert ③ Hermes config.yaml `searxng.endpoint` 추가 의 3단계
   - 진입 조건 = ddgs 사용 1주 후 latency/rate-limit 누적 측정 → SearXNG swap ROI 판정
3. **brave_free fallback = optional**:
   - BRAVE_SEARCH_API_KEY 발급 시 `fallback_providers: [brave_free]` 활성
   - 본 PR-2 scope 외, 사용자 선택

## 후속 트랙 (PR-2 후 백로그)

| 트랙 | 범위 | 진입 조건 |
|---|---|---|
| **PR-Hermes-WebSearch-2B-SearXNG** | SearXNG bind 변경 + Caddy ingress + Hermes config swap | ddgs 1주 사용 latency/rate baseline 후 |
| **PR-Hermes-Answer-Policy-1** | 출처 라벨 plugin-level 강제 + DS-first 분기 plugin guard + 충돌 표시 정책 | PR-2 closure (즉시 가능, 별 결정 후) |
| **DS-Web-Provider-Latency-1** (P3) | brave_free / tavily / parallel provider 비교 측정 + fallback chain 검증 | ddgs 한계 측정 후 (별 PR) |

## File changes

### Hermes (Mac mini)
- `~/.hermes/hermes-agent/venv/` — ddgs + 의존성 5 package 설치
- `~/.hermes/config.yaml`:
  - `web.search_backend: ddgs`
  - `web.extract_backend: ddgs`
  - `discord.channel_prompts.1505028489584316509` 9줄로 확장 (web 분기 5~8 + admin-equivalent 9)
- `~/.hermes/fixtures/pr_websearch1_layer1.sh` 신규
- `~/.hermes/config.yaml.pre-web-ddgs.20260517` (7일 안전망)

### SearXNG (GPU) — 변경 없음
- `~/home-gateway/docker-compose.yml` (LocalScout PR-A 잔존, bind 192.168.1.186:8888 유지)
- Caddyfile 미수정 (`search.hyungi.net` 미등록 유지)

### Memory
- (다음 commit) `memory/MEMORY.md` 의 Hermes / LocalScout 항목 update

## 7일 안전망 (2026-05-24)

- Mac mini `~/.hermes/config.yaml.pre-web-ddgs.20260517` (web 설정 변경 전 백업)
- ddgs 패키지 복귀 시 `~/.hermes/hermes-agent/venv/bin/python -m pip uninstall ddgs` (별 의존 5 package 도 사용자 판단)

## 검증 commands (재실행)

```bash
# Layer 1 fixture
ssh macmini "bash ~/.hermes/fixtures/pr_websearch1_layer1.sh"

# config 확인
ssh macmini "awk '/^web:/{p=1} p; /^[a-z]/&&!/^web:/{p=0}' ~/.hermes/config.yaml"

# ddgs 직접 smoke
ssh macmini "~/.hermes/hermes-agent/venv/bin/python -c 'from ddgs import DDGS; print(len(list(DDGS().text(\"test\", max_results=3))))'"
```
