Skip to main content
Glama
UnknownUserKR

OKF MCP Server

README.md
# OKF — Personal RAG 지식저장소

개인 지식이 **자동으로 축적·정제·정본화**되고, 어떤 LLM 에이전트에서든 **근거 인용과 함께 검색**되는 개인 RAG 시스템.

핵심 아이디어는 "질문할 때마다 원본 조각을 다시 찾는 단순 RAG"가 아니라, **LLM이 지속적으로 편찬하는 persistent Markdown wiki**(Karpathy LLM Wiki 패턴)를 중심에 두는 것이다. 원본은 불변의 사실 근거로 보존되고, 위키는 요약·개념·문답이 누적되는 복리(compounding) 산출물이며, Obsidian은 사람이 탐색하는 IDE, LLM은 편찬자다.

> 📐 설계 문서: **[v3 — 자립 스택 재설계](docs/design-v3.md)**(구현 완료, 2026-08-20 컷오버) · **[v4 — RAG × LLM 위키 하이브리드](docs/design-v4-hybrid.md)**(진행 중) · [ADR 0001~0005](docs/adr/).
>
> **이 리포에 담긴 것**: 시스템 코드(MCP 게이트웨이 `app/`, 편찬 파이프라인 `pipeline/`, 평가 하네스 `eval/`, 배포 `compose.yaml`)와 이 아키텍처 문서.
> **담기지 않은 것**: 지식 데이터(Obsidian 볼트), 파생 인덱스·백업(`backups/`), 시크릿(`.env`), 모델 가중치(`models/`) — 전부 로컬 전용이며 `.gitignore`로 차단된다.

---

## 1. 한눈에 보기

```mermaid
flowchart LR
    subgraph IN["지식 입력 (3+1 경로)"]
        A1["에이전트 세션 캡처<br/>okf_capture_session"]
        A2["사용자 투입<br/>00-inbox / 00.init"]
        A3["리서치 토픽 신청<br/>05-todo 체크박스"]
        A4["웹 보강<br/>딥리서치 (무료/로컬)"]
    end

    subgraph VAULT["Obsidian 볼트 (정본, git 저장소, 로컬 전용)"]
        V1["10-sources<br/>원본 사본 (불변)"]
        V2["30-reviews/staging<br/>승인 대기 카드"]
        V3["20-wiki/&lt;섹터&gt;/&lt;유형&gt;<br/>정본 위키 (분야별)"]
        V4["HOME.md · index.md · 섹터 허브<br/>대시보드·카탈로그·목차"]
    end

    subgraph PIPE["편찬 파이프라인 (okf-hermes 크론, setup/jobs.yaml)"]
        P1["인박스 스캔 (10분)"]
        P2["재니터 01:30"]
        P3["편찬 그래프 01:45<br/>정제→심판→인용검증 (LLM)"]
        P4["사람 승인 → 승격<br/>(결정론 게이트 5종) 09/21시"]
        P5["index/HOME/허브 재생성 03:25"]
        P6["큐레이터 (주기 점검)<br/>섹터·중복·노후 제안"]
    end

    subgraph MCP["OKF MCP 게이트웨이 (이 리포, read-only 사서)"]
        M1["SQLite 파생 인덱스<br/>FTS5 + trigram + bge-m3 벡터"]
        M2["하이브리드 검색 (RRF)<br/>+ 거버넌스·신선도 부스트"]
        M3["evidence bundle<br/>정답 캐시 · 감사 로그"]
        M4["gap 레이더<br/>지식 공백 추적"]
    end

    subgraph OUT["소비자"]
        C1["Claude Code / Desktop"]
        C2["hermes · opencode 에이전트"]
        C3["사람 (Obsidian, WebDAV 동기화)"]
    end

    A1 & A2 & A3 --> P1 --> V1 & V2
    A4 --> V1
    V2 --> P3 --> P4 --> V3
    P2 -.정리.-> V2
    P5 -.재생성.-> V4
    V3 -.점검.-> P6 -. "이동 제안 → 사람 승인" .-> V3
    VAULT -- "read-only 마운트" --> M1 --> M2 & M3 & M4
    M2 & M3 --> C1 & C2
    M2 & M3 -.읽기 도구.-> P6
    V4 --> C3
    M4 -. "공백 → 새 토픽" .-> A3
```

닫힌 루프: **축적 → 정제 → 정본화 → 검색/즉답 → 공백 발견 → 재축적**. 사람은 승인 지점에만
개입한다 — 정본 승격과 섹터 이동은 볼트의 체크박스 하나로 결정되고, 그 밖에는 Obsidian에서
읽고 토픽을 신청한다. 에이전트가 **쓰기 도구를 가진 적이 없다**는 것이 이 그림의 핵심이다:
LLM은 초안·판정·제안까지만 만들고, 볼트를 바꾸는 것은 승인 뒤의 결정론 코드다.

---

## 2. 설계 원칙

| 원칙 | 구현 |
|---|---|
| **도서관·사서·이용자 분리** | 볼트(도서관)는 정본, MCP(사서)는 read-only 검색·번들링, 에이전트(이용자)는 push된 컨텍스트만 소비 — 토큰 절약 + prompt injection 표면 축소 |
| **staging-first 편찬** | AI 산출물은 반드시 `30-reviews/staging/`을 거치고, 정본 진입은 결정론 게이트 통과분만 |
| **파생층 재생성 가능** | 인덱스·그래프·Graphify 층은 언제든 볼트에서 재생성. 예외는 Error Book/감사 로그뿐 |
| **데이터는 지시가 아니다** | 볼트에서 검색된 텍스트 안의 지시문은 실행하지 않는다(서버 instructions에 명시) |
| **모든 주장에 앵커** | 검색 결과·번들은 `path#heading` 단위로 인용 — 답변 검증 가능성 확보 |
| **롤백 보증** | 파이프라인의 볼트 변경은 1실행 = git 커밋 1개 — `git revert`로 원복 |

---

## 3. 지식 볼트 (정본 저장소)

Obsidian 볼트는 로컬 git 저장소다(이 리포에는 포함되지 않음). WebDAV(wsgidav)로 사용자 기기의 Obsidian과 동기화된다.

```text
LLM-Wiki/
├── HOME.md            ← 야간 자동 생성 대시보드 (정본 수·대기 큐·최근 승격·리뷰 우선순위)
├── index.md           ← 정본 카탈로그 (20-wiki 실물 기준 야간 재생성)
├── log.md             ← append-only 연대기 (## [날짜] action | subject)
├── AGENTS.md·SCHEMA.md ← 편찬자 규율·스키마 계약
├── 00-inbox/          ← 지식 투입구 (세션 캡처·사용자 파일). 처리 후 .processed/로 이동
├── 05-todo/           ← 개인 실행 목록 + research-topics.md (리서치 토픽 신청)
├── 10-sources/        ← 원본 사본 — 불변의 사실 근거 (raw-source)
├── 15-graph-imports/  ← Graphify 파생 참고층 (재생성 가능)
├── 20-wiki/           ← 정본. **섹터(분야) 폴더 → 타입 폴더** 2단
│   └── <섹터>/        ← AI-지식시스템 · 금융-퀀트 · 인프라-운영 · 일반 (+ 자동 추가)
│       ├── <섹터>.md  ← 섹터 허브(야간 자동 생성 목차. description만 사람이 고침)
│       └── concepts/ entities/ comparisons/ queries/ maps/
├── 30-reviews/        ← staging/ (승인 대기 카드) · approvals/ (승인 제안서) · gap-queue.md
├── 40-assets/         ← 첨부
└── 90-system/         ← 승격 원장·게이트 판정·재니터 원장·리포트
```

**섹터(분야)** — lane이 '수명주기'라면 섹터는 '분야'다. 정본의 섹터는 **경로가 정본**이고
(`sector_of()`), frontmatter의 `sector:`는 사본이다. 사람이 옵시디언에서 폴더를 옮기면
그게 곧 진실이 되고, FM과 어긋나면 `okf_validate`가 알린다. 등록부는 폴더 자체이며 별도
목록 파일은 없다. 새 섹터는 정제기가 제안하고 **승격 승인과 함께** 폴더가 생긴다
([ADR 0005](docs/adr/0005-sector-dimension-and-curator.md)).

**Frontmatter 계약** (모든 문서 공통):

```yaml
type:   concept | entity | comparison | query | map | source-summary | graph-import
status: draft | canonical | needs-review | archived | reference | raw-source
trust:  source | human_edit | llm_summary | llm_context | web_staging
# + title/created/updated/tags/sources/confidence/contested
```

`status`와 `trust`는 검색 랭킹·검색 범위·편찬 규칙이 모두 참조하는 거버넌스 축이다. `human_edit` 페이지는 AI가 직접 고치지 않고 staging 제안만 만든다. alpha/트레이딩 시그널은 저장 금지.

---

## 4. MCP 게이트웨이 (사서) — 이 리포의 코드

FastMCP 기반 Streamable HTTP 서버. 볼트를 **read-only로 마운트**해 SQLite 파생 인덱스를 만들고, 15개 도구를 제공한다. 독립 compose 스택으로 편찬 파이프라인과 완전히 분리되어 있다.

### 4.1 하이브리드 검색 파이프라인

```text
질의
 ├─ lexical: FTS5 unicode61 BM25 + trigram(오타·부분일치) + 구문/AND 부스트 + 2자 한국어 폴백
 ├─ semantic: bge-m3 1024d 코사인 (후보 풀 OKF_SEM_CANDIDATES=30, 단독 매칭 하한 SIM_SOLO=0.55, v24 재보정)
 ▼
정규 RRF 병합(w/(k+rank), 가중치는 RankingParams로 외부화) → 거버넌스 부스트(canonical·human_edit·source ↑, archived 제외)
        → 신선도 보정(updated 완만 감쇠)
        → [옵션·기본 off] cross-encoder 리랭크 (사이드카, 실패 시 무손실 폴백)
 ▼
섹션(heading) 청크 단위 결과 + anchor(path#heading) + match 근거(fts/semantic/title)
```

- 인덱스는 5분 주기 증분 재스캔(콘텐츠 해시 기반), 임베딩은 content-addressed 증분(변경 섹션만 재임베딩, 삭제분 GC). 재임베딩 트리거인 `embed_hash`는 **실제로 프로바이더에 보내는 문자열의 해시**라 입력이 같으면 재계산이 없고 다르면 반드시 갱신된다(v24).
- 스캔은 문서 단위 쓰기 트랜잭션, 검색은 스레드별 read-only 커넥션(WAL) — 전체 스캔 중에도 검색이 막히지 않는다.
- 임베딩 사이드카(`okf-embed`, llama.cpp `/v1/embeddings`)가 죽어도 FTS-only로 무손실 폴백 — 큐는 유지되어 복구 시 재개.
- 모델 선정 근거: nomic-embed-v1.5는 한국어 판별력 없음(관련≈무관≈0.7), bge-m3는 관련 ~0.5 vs 무관 ~0.3 (2026-07-05 실측).

### 4.2 도구 15종

| 도구 | 역할 |
|---|---|
| `okf_search` | 하이브리드 검색. `mode: hybrid\|lexical\|semantic`, lane/type/trust 필터 |
| `okf_read` | 문서 + frontmatter + outline + outlinks/backlinks. `section=`으로 정밀 인용 |
| `okf_traverse` | 위키링크 그래프 BFS. `target=` 지정 시 A→B 최단 경로(≤6 hop) |
| `okf_map` | 코퍼스 지형: lane/status 분포, 허브, 연결 컴포넌트, 진입점, 최근 갱신 |
| `okf_evidence_bundle` | ① 정답 캐시: 승인된 `20-wiki/queries`와 유사 질의면 앵커 단위 즉답(`answer_mode: cached`, 충분성 동봉, 인덱스가 낡았으면 자동 무효화) ② 근거 번들: 발췌 예산(6000자)+토큰 추정+충분성 판정, 근거가 약하면 결정론 재작성으로 1회 재검색(Corrective) |
| `okf_probe` | WiCER-lite: 과거 gap·피드백을 현재 인덱스에 재생 → 채워짐/공백 헬스, `compile_next` 편찬 우선순위 |
| `okf_validate` | 볼트 린트 + stale canonical + 최근 gap 수 |
| `okf_status` | 분포·인덱스·임베딩 coverage·거버넌스 정책·열린 피드백 |
| `okf_capture_session` | **[write: 00-inbox 한정]** `kind=session` 세션 요약 / `kind=qa` 재사용 문답 — qa는 사람 승인으로 정답 캐시가 됨 |
| `okf_feedback` | Error Book 기록(지식 즉시 수정 없음 — 승인 기반 병합) |
| `okf_audit` | evidence bundle 재조회·최근 이벤트(감사 추적) |
| `okf_reindex` | 증분/전체 재인덱스 + 임베딩 재개 |
| `okf_worklog` | **[write: 50-ops 한정]** 프로젝트 작업 로그 append(결정·변경·검증 결과) |
| `okf_handoff` | **[write: 50-ops 한정]** 세션 인수인계 — 직전 핸드오프를 자동 supersede |
| `okf_resume` | 이어받기 번들: 최신 핸드오프 + 최근 작업 로그 + 참조 정본 브리프 + 열린 공백 |

### 4.3 지식 공백(gap) 레이더

0건 검색(`zero_hit`)과 근거 빈약 번들(`bundle_weak`)은 자동으로 gap에 기록된다. `okf_probe`가 주기적으로 "이후 편찬으로 채워졌는가"를 재생 점검하고, 안 채워진 것은 `compile_next`로 편찬 파이프라인에 우선순위를 준다 — **검색 실패가 곧 다음 축적 대상이 되는 자기교정 루프**.

### 4.4 보안

- 볼트는 read-only 마운트. 쓰기는 Error Book(자체 DB)·00-inbox 세션 캡처(draft 고정)·50-ops 작업 기억(worklog/handoff)뿐 — 정본(20-wiki)은 승인 뒤 파이프라인만 쓴다.
- Bearer 토큰 인증, 127.0.0.1 바인드 → tailscale serve로 tailnet 전용 HTTPS 노출.
- 컨테이너 하드닝: `cap_drop: ALL`, `no-new-privileges`, 비루트(uid 10000), 리소스 상한.
- 시맨틱 단독 매칭 유사도 하한(환각성 매칭 방지), 시크릿 형태 문자열·저장형 인젝션 패턴은 토픽·캡처·작업 기억 단계에서 거부.

---

## 5. 편찬 파이프라인 (야간 자동화)

`okf-hermes` 컨테이너의 크론이 볼트에 쓰는 유일한 주체다(2026-08-20 컷오버). 스케줄의
정본은 **`setup/jobs.yaml`**이고 `setup/provision.sh`가 크론에 등록한다 — 잡은 전부
`--no-agent` 스크립트이고, LLM이 필요한 잡도 프롬프트 주입이 아니라 잡 코드가 직접 부른다
(모델 핀은 코드·설정에, 기동 시 preflight로 검증).

### 5.1 야간 타임라인

| 시각 | 잡 | 역할 |
|---|---|---|
| 10분 주기 (0-5·20-23시) | `inbox_scan` | 00-inbox → 10-sources 사본 + staging 카드 생성, 처리분 `.processed/` 이동 |
| 매시 :20 | `topic_bridge` | `05-todo/research-topics.md` 체크박스 → 리서치 큐 투입 |
| 01:30 | `janitor` | 만기·중복 카드 결정론 archived (§5.3) + 인박스 고착 화해 |
| 01:45 | **`compile`** | LangGraph 편찬: select → refine(LLM) → gate(LLM 심판) → merge → web_verify → 게이트 5종 → **사람 승인 대기** |
| 02:00 | `preflight` | 볼트 쓰기·git·게이트웨이·LLM 핀 전제 확인(fail-closed) |
| 03:25 | `index_home` | `index.md` 섹터별 카탈로그 + 섹터 허브 + `HOME.md` 대시보드 |
| 03:40 | `research` | 토픽 큐 1건 딥리서치(DeepAgents, 빈 큐면 LLM 미기동) → `10-sources` + staging 패킷 |
| 08:45 | `inbox_scan` | 새벽 정밀화 산출물 당일 재수록 |
| 09:00 / 21:00 | `approvals` | 볼트 체크박스를 읽어 멈춘 런을 이어가거나 섹터 이동을 적용(제안서 `kind`로 분기) |

수동 실행 전용(스케줄 없음): `sector_migrate`(레거시 정본 → 섹터 분류 제안, 일회성),
`curate`(큐레이터 점검), `agent_smoke`(에이전트 경로 점검).
`curate`는 섹터 이전 승인 뒤 시각을 정해 `setup/jobs.yaml`의 주석을 풀어 등록한다.

### 5.2 정본 승격 게이트 (결정론 5종)

LLM 판정(gatekeeper)을 그대로 믿지 않고, 승격 시점에 기계로 재검증한다:

1. `## 승격 메타` 존재 (정밀화를 거쳤는가)
2. 동일 제목 정본 부재 (중복 방지)
3. `10-sources/` 원본 사본 존재 (근거 실물)
4. 본문에 살아있는 `[[wikilink]]` (그래프 연결 — 고아 정본 방지)
5. 인용 앵커 존재 (`path#heading` 또는 URL — 출처 없는 정본 방지)

전부 통과해야 정본이 되고, 같은 커밋에서 index/log가 갱신되며, 승격 원장(`90-system/promotions/*.jsonl`)에 기록된다.

### 5.3 staging 재니터 (큐 수렴 장치)

유입(~7건/일)이 승격(~0.4건/일)보다 많아 대기 큐는 방치하면 발산한다. 재니터가 결정론 규칙으로 수렴시킨다 — 파일 삭제가 아니라 `status: archived` 전환이라 링크·검색(옵션) 모두 보존된다:

| 규칙 | 대상 | 기준 |
|---|---|---|
| R1 | agent-os 일일 제안서 | 최신 3개만 유지, 로테이션 |
| R2 | 일일 회고 프롬프트 | 7일 경과 |
| R3 | 근거 소실 카드 | source_refs 전멸 + 14일 경과 |
| R4 | 일반 카드 | 30일 경과 + 정밀화 미선정 (원본 사본은 10-sources에 보존) |
| R5 | deep-research 일일 재실행 | 슬러그별 최신 1개만 유지 |

부가로 스캐너가 해시 불일치로 영구 스킵하는 00-inbox 고착 파일을 화해한다(소스 사본 보장 후 `.processed/` 이동 — 내용 소실 0 보장).

---

## 6. 사람의 자리 — Obsidian 연동

- **보기**: 볼트가 WebDAV(:8090)로 기기의 Obsidian과 동기화된다. 진입점은 `HOME.md`(대시보드) → `index.md`(카탈로그) → 분야별 `20-wiki/<섹터>/<섹터>.md`(섹터 허브) → `log.md`(연대기).
- **넣기**: 아무 마크다운이나 `00-inbox/`에 떨어뜨리면 10분 스캔이 수록한다.
- **시키기**: `05-todo/research-topics.md`에 `- [ ] 궁금한 주제`를 적으면 매시 브리지가 큐에 넣고, 딥리서치가 조사해 축적한다. 처리되면 체크박스가 `[x] … ← queued`로 바뀐다.
- **승인하기**: 사람이 결정하는 모든 것이 `30-reviews/approvals/`의 체크박스 하나다 — 정본 승격도, 섹터 이동도. 켜 두면 다음 틱(09/21시)이 반영하고, 텔레그램은 "지금 볼 차례"일 때만 알린다. 제안 내용을 고치면 지문이 어긋나 자동 거부되므로, 고치고 싶으면 승인하지 말고 원본을 편집한다(단 섹터 이동 제안의 `to`는 예외 — 고쳐도 된다).
- **분류 고치기**: 옵시디언에서 정본을 다른 섹터 폴더로 직접 옮겨도 된다. 경로가 정본이라 그게 곧 반영이고, frontmatter의 `sector:`가 남아 어긋나면 다음 `okf_validate`가 알려 준다.

---

## 7. 배포·운영

```bash
docker compose up -d --build          # 기동 (볼트 경로는 compose.yaml의 바인드 마운트 수정)
curl -s http://127.0.0.1:8787/health  # 헬스체크 (인증 불필요)
docker compose --profile rerank up -d # [옵션] 리랭커 사이드카
```

### 7.1 클라이언트 연결 키트 (어느 기기·어느 에이전트든 같은 지식)

서버 하나 · 인덱스 하나 · 볼트 하나다. 모든 클라이언트가 같은 URL을 보므로 기기마다
동기화할 것이 없다.

```bash
# Claude Code
claude mcp add --transport http okf https://<tailnet-호스트>:8443/mcp \
  --header "Authorization: Bearer $OKF_MCP_TOKEN"
```

```toml
# Codex CLI — ~/.codex/config.toml
[mcp_servers.okf]
url = "https://<tailnet-호스트>:8443/mcp"   # 같은 호스트면 http://127.0.0.1:8787/mcp
bearer_token_env_var = "OKF_MCP_TOKEN"      # 토큰 값은 셸 env에 두고 파일에 쓰지 않는다
```

컨테이너 안의 에이전트는 tailscale을 거치지 않고 `http://okf-mcp:8787/mcp`(`agentnet`).

**작업 이어받기 의식** — 어느 에이전트든 같다: 시작할 때 `okf_resume('<프로젝트>')`
한 번(이전 핸드오프 + 최근 작업 로그 + 참조 정본 + 열린 공백), 일한 뒤 `okf_worklog`,
넘길 때 `okf_handoff`. 재사용 가치가 있는 답은 `okf_capture_session(kind='qa')`.
- 인덱스 초기화: `docker compose down -v` 후 재기동(볼트에서 전량 재생성). 단 Error Book·감사 로그는 파생층이 아니므로 **일일 DB 스냅샷(요일 로테이션 7개, `backups/`)**이 보호한다.
- 임베딩 모델 교체: `.env`의 `OKF_EMBED_MODEL` 변경 → 전량 자동 재임베딩 + 임계값 2종(`SIM_BOOST`/`SIM_SOLO`) 재보정 필수(`eval` 하네스의 calibrate).

## 8. 평가 하네스 (`eval/`)

검색 품질을 감으로 조정하지 않기 위한 골든셋 회귀 체계:

- `golden.jsonl` — 질의→기대 문서 골든셋(lexical/semantic/hybrid 카테고리).
- `run_eval.py run` — hit@k/recall 지표를 스로어웨이 DB 복사본에서 측정, `baselines/`와 비교. **코퍼스 지문 게이트**: 볼트 상태가 다른 베이스라인과의 비교를 거부(허위 회귀/개선 차단).
- `run_eval.py calibrate` — 임베딩 임계값 재보정(모델/입력 스킴 교체 시 필수).
- `sweep_ranking.py` — 랭킹 파라미터(RRF 상수·플랜 가중치·거버넌스 배수)를 재기동 없이 골든셋 위에서 스윕. v3에서 상수를 데이터클래스로 뺐기 때문에 가능해졌다.
- 진단 도구: 후보 풀 스윕(`sweep_candidates`), 랭킹 진단(`rank_diag`), 폴백 프로브, 임베딩 처리량 벤치.

## 9. 리포 구성

```text
app/            MCP 게이트웨이 (읽기 전용 사서)
  config.py   설정 단일 정의처 (Config.from_env — 주입 가능)
  core/       I/O 없는 순수 모듈: chunking(청킹·임베딩 입력 해시) / ranking(RankingParams,
              정규 RRF) / governance(lane·status·trust 어휘) / frontmatter / safety(시크릿·인젝션)
  infra/      db(읽기/쓰기 커넥션 분리) / embedder / reranker
  services/   answer_cache(정답 캐시 v2) / workspace(worklog·handoff·resume)
  store.py    파사드 — 조립·트랜잭션·검색 조율
  server.py   MCP 도구 15종
eval/       평가 하네스 + 골든셋 + 베이스라인 + 랭킹 파라미터 스윕
tests/      단위·계약 테스트
docs/       설계서(design-v3.md) + ADR
pipeline/       편찬 파이프라인 (okf-hermes 크론이 실행 — 볼트에 쓰는 유일한 주체)
  run.py        잡 엔트리포인트 · settings.py · budget.py · llm.py · notify.py
  jobs/         preflight / janitor / inbox_scan / index_home / topic_bridge /
                compile(그래프) / approvals / research
  nodes/        그래프 노드: select · refine · gate · web_verify · promote · approval
  graphs/       LangGraph 편찬 그래프(SqliteSaver 체크포인트)
  research/     DeepAgents 러너 + 게이트웨이 MCP 클라이언트
  vaultio/      볼트 파일 I/O · git(1실행 = 커밋 1개)
setup/          jobs.yaml(스케줄 정본) · provision.sh(멱등 등록) · env.example
compose.yaml, Dockerfile, Dockerfile.hermes, requirements.txt
backups/    [git 제외] 일일 DB 스냅샷 — 개인 데이터
models/     [git 제외] 리랭커 가중치(수동 배치)
.env        [git 제외] OKF_MCP_TOKEN 등 시크릿
```

## 10. v3 재설계

**[docs/design-v3.md](docs/design-v3.md) — 자립형 스택으로의 재설계 확정본** (2026-08-14)

v2 전수 감사에서 드러난 것(Graphify 47회 전부 no-op, LLM 편찬 체인 라이브락으로 월 정본 3편, 임베딩 해시 정의 불일치로 제목 변경 시 벡터 영구 stale)과 2026년 RAG 최신 조사를 근거로, 다음을 설계했다:

- **한 지붕 스택** — 게이트웨이 + 전용 hermes(크론·텔레그램·프로바이더) + WebDAV + 리랭커를 이 저장소의 compose 하나로. 남의 스택 의존 해소
- **편찬 재작성** — LangGraph 그래프(빈 큐면 LLM 미기동, 라이브락 자동 archive, 승인은 interrupt→텔레그램→resume) + 리서치만 DeepAgents
- **RAG 코어 정비** — 해시 정의 통일, 정규 RRF + 파라미터 외부화(eval 스윕), 리랭커 사이드카(옵션 — 실측 후 기본 off), 정답 캐시 v2, 비동기 우선
- **핸드오프 레이어** — `okf_worklog` / `okf_handoff` / `okf_resume`: 어떤 에이전트든 새 세션에서 한 번의 호출로 이전 작업 맥락을 이어받는다
- **볼트 이전** — 호스트 계정 소유의 전용 위치로 (Obsidian 주소는 불변)

### 진행 상태 (2026-08-23)

| Phase | 상태 |
|---|---|
| 0 비용 차단 | 완료 — LLM 편찬 크론 11개 pause |
| 1 RAG 코어 | 완료 — 설정 단일화, core/infra/services 분리, 정규 RRF+파라미터 외부화, ctx1 제거, embed_hash 통일(v24), 읽기/쓰기 커넥션 분리 |
| 1b 캐시·리랭커 | 정답 캐시 v2·Corrective 루프 완료. 리랭커는 실측 결과 **기본 비활성 유지**(이 CPU에서 문서당 3.7초, 풀 5에서 MRR 0.7765→0.7467) |
| 2 자립 스택 | 완료 — **컷오버 실행 2026-08-20**, 이제 볼트에 쓰는 것은 okf-hermes뿐 |
| 3 편찬 그래프 | 완료 — LLM 경로 개통(ADR 0003), 섀도가 드러낸 결함 수정, 승인 왕복 동작 |
| 4 리서치·에이전트 | **개통 2026-08-23** — langchain-openai + ChatOpenAI(Responses) + system→instructions 변환(ADR 0005 §3). `agent_smoke`로 도구 왕복·핀 확인 |
| **섹터 차원** | 완료 — 정본이 `20-wiki/<섹터>/<유형>/`. 이전 제안 생성됨(사람 승인 대기) |
| **큐레이터** | 완료 — 읽기 전용 DeepAgents 점검 → 승인 제안. 스케줄은 시각 확정 후 등록 |
| 5 볼트 이전 / 6 정리 | 런북 준비 완료([runbook §3·§4](docs/runbook-cutover.md)), 사람이 실행 |

결정 근거: [ADR 0001](docs/adr/0001-phase1-rag-core.md) · [ADR 0002](docs/adr/0002-phase2-4-pipeline.md) ·
실행 절차: [런북](docs/runbook-cutover.md)