Skip to main content
Glama
UnknownUserKR

OKF MCP Server

README.md
# Hippocampus(해마) — Personal RAG 지식저장소

> **이름**: 2026-09-13 `okf-mcp`에서 `hippocampus`로 개명했다 — MCP 서버 키 `hippocampus`, 도구 `hippo_*`, 환경변수 `HIPPO_*`, 컨테이너 `hippocampus`·`hippo-*`.
> 이전 문서의 "OKF"는 이 프로젝트의 옛 이름이다(Google Cloud의 Open Knowledge Format과는 별개). 이미 저장된 데이터 형식 — 볼트 frontmatter `okf_*` 키, 문서 ID `okf:vault:`, DB 파일 `okf.db` — 은 그대로 둔다.

개인 지식의 축적·편찬과 근거 검색을 지원하는 개인 RAG 시스템입니다. 지속적 LLM 편찬은 설계 목표이며, 현행 운영 상태는 [현행 운영 안내](docs/operations-current.md)에서 확인합니다.

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

> 📐 현행 운영: [현행 운영 안내](docs/operations-current.md) · [2026-10-03 업그레이드 기록과 검증](docs/upgrade-2026-10-03.md). 설계 이력: **[v3 — 자립 스택 재설계](docs/design-v3.md)** · **[v4 — 근거·검증·갱신 설계와 실행 계획](docs/design-v4-hybrid.md)** · [v5](docs/design-v5-unified.md) · [v6](docs/design-v6-codex.md) · [ADR 0001~0007](docs/adr/).
> **[공유 에이전트 기억·능력 연결 설계](docs/design-shared-agent-memory.md)**(2026-09-10): 작업 요약 + 대화 전체의 누적 요약. 저장·변경 조회 구현, 자동 클라이언트 연결은 다음 단계.
>
> **이 리포에 담긴 것**: 시스템 코드(MCP 게이트웨이 `app/`, 편찬 파이프라인 `pipeline/`, 평가 하네스 `eval/`, 배포 `compose.yaml`)와 이 아키텍처 문서.
> **담기지 않은 것**: 지식 데이터(Obsidian 볼트), 파생 인덱스·백업(`backups/`), 시크릿(`.env`), 모델 가중치(`models/`) — 전부 로컬 전용이며 `.gitignore`로 차단된다.

---

## 1. 한눈에 보기

2026-10-03 운영 스냅샷에서 MCP·두 임베딩 서비스와 호스트 Hermes는 실행 중이며, 결정론 cron은 dry-run으로 등록되어 있습니다. LLM 편찬·승인·리서치 잡은 미등록/주석 상태입니다. 코드·운영 상태·검증 범위를 분리해 기록한 [현행 운영 안내](docs/operations-current.md)를 기준으로 봅니다.

2026-09-20부터 편찬 잡은 호스트 Hermes의 `wiki_manager` 프로필에서 실행합니다. 이는 다중 프로필 gateway systemd 유닛의 별도 cron 프로필이지, 독립 `wiki_manager` 서비스가 아닙니다.
`setup/provision.sh`는 `~/hermes/bin/hermes-cli`를 사용하며, 상태·venv는 `~/hermes/hermes-home/pipeline-data`에 둡니다.
환경 설정은 `~/hermes/hermes-home/service.env`를 사용합니다. MCP·임베딩 컨테이너와 공유 볼트 위치는 유지합니다.
현재 운영 배치는 `../claude-workspace/aios/SYSTEM-DESIGN.md`의 "현재 실행 배치"를 따릅니다.

2026-10-03부터 이 저장소도 `AGENTS.md`는 안내, `CLAUDE.md`는 `@AGENTS.md` 참조만 둡니다.
핵심 지침은 `instructions/local/hippocampus.md`, 이전 상세 원문은
`instructions/lane/hippocampus-repository.md`가 정본입니다. `instructions/manifest.json`에서
Hermes를 포함한 클라이언트의 시작 지침을 조립하고 `setup/sync-agent-policy.py`로
`setup/bootstrap.json`을 생성합니다. 시작 훅은 핵심과 프로젝트 기록을 확인하며,
상세 지침·스킬·기억은 해마 MCP로 필요한 것만 읽습니다.

### 선택적 RAG — 관련성 재정렬만 (2026-09-23 현행 계약)

`hippo_route`는 ACL 적용 기본 검색의 짧은 후보를 Jev로 **관련성만** 재정렬합니다(`needs_context`·후보별 `fit`).
난이도·위험도·필요 기능·reasoning effort는 묻지 않으며 `execution_policy`는 항상 `null`입니다.
공급자가 요청하지 않은 작업 라벨을 돌려줘도 무시하고, 모델·effort·권한을 바꾸지 않습니다.
Jev 실패(401/403/429/529·타임아웃·응답 오류·워커 스레드 시작 실패)는 `status=degraded`와 원인을 표시하고
ACL 적용 기본 검색 순서를 반환합니다. 해마 자체 인증/ACL 오류는 이 경로로 우회하지 않습니다.
정본: `../claude-workspace/aios/SYSTEM-DESIGN.md` "공통 정책·선택적 RAG 계약". Hermes의 과거
`plugins.hippocampus.adaptive_effort` 플래그는 Jev를 강제하지 않습니다.

선택 엔진은 `HIPPO_SELECTOR`(`jev` 기본, `laya`)로 고릅니다. 2026-10-01 실제 질의 40개로 비교한 결과
로컬 Laya(휴면 프로파일 `select`의 `hippo-select`)는 정답 적중이 Jev의 절반 수준이라 쓰지 않고 Jev를 유지합니다
([측정 결과](docs/selector-bench-2026-10.md)). 훅 전용 `hippo_suggest`는 스킬 레인만 검색해 스킬·MCP 후보 3개를
600바이트 안으로 돌려주며, 예산(`HIPPO_SUGGEST_BUDGET_MS`, 기본 1500ms)을 넘기거나 엔진이 실패하면 빈 목록을 줍니다.
엔진을 따로 둘 때는 `HIPPO_SUGGEST_SELECTOR`를 씁니다.

역사(폐기): 2026-09-20의 요청별 effort 판정 설계와 검증 기록은
`../audits/task-policy-2026-09-20/README.md`에 있습니다. 현재 동작 설명이 아닙니다.

```mermaid
flowchart LR
    subgraph IN["지식 입력 (3+1 경로)"]
        A1["에이전트 세션 캡처<br/>hippo_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["편찬 파이프라인 (hermes wiki_manager 크론, 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["해마 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/           ← 정본. 섹터 폴더 아래 주제별 하위 폴더는 허용, 유형별 폴더는 강제하지 않음
│   └── <섹터>/...     ← 유형은 frontmatter `type`, 분야는 실제 경로가 기준
├── 30-reviews/        ← staging/ (승인 대기 카드) · approvals/ · gap-queue.md
├── 40-skills/         ← 공유 skill library 읽기 전용 투영
├── 45-instructions/   ← 생성된 로컬 지침 투영
├── 47-memory/         ← agent-stack 프로젝트 기억 투영
├── 50-ops/            ← 공유 작업 기억(worklog·handoff)
└── 90-system/         ← 승격 원장·게이트 판정·재니터 원장·리포트
```

유형 폴더가 없는 flat 레이아웃은 분야 아래 모든 하위 폴더를 금지한다는 뜻이 아닙니다. WebDAV 사람층은 정본·원천·todo·ops·inbox만 보이며, review/system/graph import는 기계층에 둡니다. 세부 노출 경계는 [현행 운영 안내](docs/operations-current.md)를 따릅니다.

**섹터(분야)** — lane이 '수명주기'라면 섹터는 '분야'다. 정본의 섹터는 **경로가 정본**이고
(`sector_of()`), frontmatter의 `sector:`는 사본이다. 사람이 옵시디언에서 폴더를 옮기면
그게 곧 진실이 되고, FM과 어긋나면 `hippo_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 파생 인덱스를 만들고, 20개 도구를 제공한다. 독립 compose 스택으로 편찬 파이프라인과 완전히 분리되어 있다.

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

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

- 인덱스는 5분 주기 증분 재스캔(콘텐츠 해시 기반), 임베딩은 content-addressed 증분(변경 섹션만 재임베딩, 삭제분 GC). 재임베딩 트리거인 `embed_hash`는 **실제로 프로바이더에 보내는 문자열의 해시**라 입력이 같으면 재계산이 없고 다르면 반드시 갱신된다(v24).
- 스캔은 문서 단위 쓰기 트랜잭션, 검색은 스레드별 read-only 커넥션(WAL) — 전체 스캔 중에도 검색이 막히지 않는다.
- 임베딩 사이드카(`hippo-embed`, llama.cpp `/v1/embeddings`)가 죽어도 FTS-only로 무손실 폴백 — 큐는 유지되어 복구 시 재개.
- 모델 선정 근거: nomic-embed-v1.5는 한국어 판별력 없음(관련≈무관≈0.7), bge-m3는 관련 ~0.5 vs 무관 ~0.3 (2026-07-05 실측).
- 레인·경로를 지정한 검색(`lanes`·`path_prefix`)은 FTS·제목·의미 검색 후보를 처음부터 그 범위에서 뽑는다(2026-10-01). 전에는 전체에서 뽑고 걸러 스킬 레인 질의의 의미 검색 결과가 0개가 되곤 했다. 지정하지 않은 기본 검색은 그대로다.
- 스킬 후보(`hippo_route`·`hippo_suggest`, 각 16개)는 스킬 레인에서 25개를 본 뒤 하위 문서(`references/` 등)와 스킬 카드(`40-skills/cards/`)를 원래 `SKILL.md`로 모은다. 목록·조사 문서는 후보가 아니다. 카드는 스킬마다 한국어 한 줄을 담은 짧은 문서로 `claude-workspace/skills/make_cards.py`가 만든다. `hippo_suggest`는 검색어 임베딩을 0.7초만 기다리고 넘기면 문자열 검색으로 진행한다. 측정: [docs/skill-search-2026-10.md](docs/skill-search-2026-10.md).
- 색인기가 읽지 못한 파일(권한 등)은 개수를 `hippo_status`의 `index.unreadable_files`에, 경로 예시를 감사 기록 `index_unreadable`에 남긴다.

### 4.2 MCP 도구

| 도구 | 역할 |
|---|---|
| `hippo_search` | 하이브리드 검색. `mode: hybrid\|lexical\|semantic`, lane/type/trust 필터 |
| `hippo_suggest` | 훅 전용 스킬·MCP 후보 3개(이름·경로·한 줄 설명, 600바이트). 실패·예산 초과 시 빈 목록 |
| `hippo_read` | 문서 + frontmatter + outline + outlinks/backlinks. `section=`으로 정밀 인용 |
| `hippo_traverse` | 위키링크 그래프 BFS. `target=` 지정 시 A→B 최단 경로(≤6 hop) |
| `hippo_map` | 코퍼스 지형: lane/status 분포, 허브, 연결 컴포넌트, 진입점, 최근 갱신 |
| `hippo_evidence_bundle` | ① 정답 캐시: 승인된 `20-wiki/queries`와 유사 질의면 앵커 단위 즉답(`answer_mode: cached`, 충분성 동봉, 인덱스가 낡았으면 자동 무효화) ② 근거 번들: 발췌 예산(6000자)+토큰 추정+충분성 판정, 근거가 약하면 결정론 재작성으로 1회 재검색(Corrective) |
| `hippo_probe` | WiCER-lite: 과거 gap·피드백을 현재 인덱스에 재생 → `compile_next` 열린 공백 + `compile_candidates` staging 우선순위 |
| `hippo_validate` | 볼트 린트 + stale canonical + 최근 gap 수 |
| `hippo_status` | 분포·인덱스·임베딩 coverage·거버넌스 정책·열린 피드백 |
| `hippo_capture_session` | **[write: 00-inbox 한정]** `kind=session` 세션 요약 / `kind=qa` 재사용 문답 — qa는 사람 승인으로 정답 캐시가 됨 |
| `hippo_feedback` | Error Book 기록(지식 즉시 수정 없음 — 승인 기반 병합) |
| `hippo_audit` | evidence bundle 재조회·최근 이벤트(감사 추적) |
| `hippo_reindex` | 증분/전체 재인덱스 + 임베딩 재개 |
| `hippo_worklog` | **[write: 50-ops 한정]** 프로젝트 작업 로그 append(결정·변경·검증 결과) |
| `hippo_handoff` | **[write: 50-ops 한정]** 세션 인수인계 — 직전 핸드오프를 자동 supersede |
| `hippo_resume` | 이어받기 번들: 최근 공유 요약 + 핸드오프 + 작업 로그 + 참조 정본 브리프 + 열린 공백 |
| `hippo_checkpoint` | **[write: 공유 기억 DB]** 작업 요약(`work`)·누적 대화 요약(`conversation`), 이벤트 ID로 중복 저장 방지 |
| `hippo_sync` | 프로젝트의 모든 에이전트가 남긴 요약을 마지막 읽은 cursor 이후부터 순서대로 조회 |

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

0건 검색(`zero_hit`)과 근거 빈약 번들(`bundle_weak`)은 자동으로 gap에 기록된다. `hippo_probe`가 주기적으로 "이후 편찬으로 채워졌는가"를 재생 점검하고, 안 채워진 것은 `compile_next`로 남긴다. 같은 질의를 실제 선택 범위 `30-reviews/staging/*.md`에서 찾고 충분성 문턱을 넘은 `compile_candidates`는 야간 `select`가 oldest-first보다 먼저 뽑는다(사서 장애·후보 0건이면 기존 순서로 폴백) — **검색 실패가 곧 다음 축적 대상이 되는 자기교정 루프**.

### 4.4 보안

- 볼트는 read-only 마운트. 쓰기는 Error Book·공유 요약 원장(자체 DB), 00-inbox 세션 캡처(draft 고정), 50-ops 작업 기억(worklog/handoff)으로 제한한다. 정본(20-wiki)은 승인 뒤 파이프라인만 쓴다(`HIPPO_AUTO_APPROVE=1`이면 approvals 잡이 대기 제안을 자동 승인 — 결정론 게이트·심판·지문 검사는 유지, 사람 눈만 빠진다). inbox→sources 사본은 원문의 유효한 `trust`를 보존해 이동만으로 신뢰가 상승하지 않는다.
- Bearer 토큰 인증, 127.0.0.1 바인드 → tailscale serve로 tailnet 전용 HTTPS 노출.
- 컨테이너 하드닝: `cap_drop: ALL`, `no-new-privileges`, 비루트(uid 10000), 리소스 상한.
- 시맨틱 단독 매칭 유사도 하한(환각성 매칭 방지), 시크릿 형태 문자열·저장형 인젝션 패턴은 토픽·캡처·작업 기억 단계에서 거부.

---

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

아래 일정은 파이프라인 설계 및 과거 잡 정의를 설명합니다. 현재 운영에서 LLM `compile`·`approvals`·`research`는 비활성이고, 결정론 6개 cron만 `HIPPO_DRY_RUN=1`로 등록되어 있습니다. 자동 승인은 `HIPPO_AUTO_APPROVE=0`입니다. 현행 실행 스케줄과 범위는 [운영 안내](docs/operations-current.md)를 확인하십시오.

agent-stack `hermes`의 wiki_manager 크론이 볼트에 쓰는 유일한 주체다(2026-08-20 컷오버, 2026-09-13 전용
`hippo-hermes` 컨테이너에서 이관 — 이 리포의 `app`·`pipeline`·`setup`을 ro로, `pipeline-data`를 rw로 마운트한다). 스케줄의
정본은 **`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:`가 남아 어긋나면 다음 `hippo_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을 보므로 기기마다
지식 데이터를 복제할 필요가 없다. 실행 중인 에이전트의 문맥은 클라이언트가 새 변경을
조회해 반영해야 하며, MCP 연결 자체가 실시간 문맥 주입을 수행하지는 않는다.

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

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

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

**공유 기억 흐름** — 시작은 `hippo_resume('<프로젝트>')`, 작업 실행 전에는
`hippo_sync(project, since=마지막_읽은_cursor)`를 `has_more=false`까지 조회한다.
작업 요약과 누적 대화 요약은 각각 `hippo_checkpoint(kind='work'|'conversation')`로 기록한다.
`session_id`, `event_id`는 재전송 시 유지하고, `base_cursor`에는 마지막으로 읽은 순번을 넣는다.
**쓰기 응답의 seq로 읽기 cursor를 바꾸지 않는다.** 기존 Markdown 로그·핸드오프는
`hippo_worklog`·`hippo_handoff`, 재사용 문답은 `hippo_capture_session(kind='qa')`로 유지한다.
자동 연결(2026-09-13, [M2](docs/design-shared-agent-memory.md#7-단계별-완료-기준)): Claude Code·Codex는 agent-stack `bin/hippo-hook`
(SessionStart 재개·주입 / SessionEnd work·conversation 기록), hermes는 메모리 프로바이더가 같은 계약을 쓴다 — 공용 클라이언트
`agent-stack/claude-workspace/aios/hermes-plugins/hippocampus/hippo_rpc.py`, 계약·예산은 agent-stack `SYSTEM-DESIGN.md` §4.3~4.5.

- 검색 인덱스 재생성: `hippo_reindex(full=True)`. 공유 요약·Error Book·감사 로그는 볼트에서 재생성할 수 없으므로 DB 볼륨을 지우지 않는다. **일일 DB 스냅샷(요일 로테이션 7개, `backups/`)**에 원장을 함께 보존한다.
  재생성 불가 테이블은 `workspace_checkpoints`·`feedback`·`audit`·`gaps`·`bundles`·`meta`다. 이 중 JSONL 사본(`backups/*.jsonl`, 7일 밖까지 남음)은
  feedback·gaps·audit뿐이고 **checkpoint·bundle은 7일 DB 스냅샷에만** 있다. 복원은 스냅샷을 `/data/okf.db`로 되돌리면 되고(2026-09-13 임시 위치
  복원 훈련: quick_check ok, checkpoint·Error Book·감사가 MCP로 그대로 조회됨), 복원 뒤 클라이언트 cursor는 `invalid_cursor`로 무효가 돼 0부터 다시 읽는다.
  같은 순번이 다른 이력에서 다시 생긴 경우까지는 가리지 못한다(세대 ID는 M4).
- 임베딩 모델 교체: `.env`의 `HIPPO_EMBED_MODEL` 변경 → 전량 자동 재임베딩 + 임계값 2종(`SIM_BOOST`/`SIM_SOLO`) 재보정 필수(`eval` 하네스의 calibrate).

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

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

- `golden.jsonl` — 질의→기대 문서 골든셋(lexical/semantic/hybrid 카테고리).
- `run_eval.py validate` — 질문 ID·정답 경로·검색 범위·앵커를 검색 전에 검사. 하나라도 틀리면 exit 3이고 성능 수치를 내지 않는다(측정 불가 ≠ 오답).
  2026-09-13 현재 골든셋은 라이브·보관 DB 어느 것과도 짝이 맞지 않아 수치가 없다 — [eval/README](eval/README.md#2026-09-13-코퍼스-부적합--성능-수치-없음).
- `run_eval.py run` — Document Hit@k·MRR을 스로어웨이 DB 복사본에서 측정. 문서 내용·경로·라벨의 `dataset_fingerprint`가 다르면 비교를 거부하고 코드·모델·랭킹은 별도 `run_signature`로 기록한다.
- `run_eval.py calibrate` — 임베딩 임계값 재보정(모델/입력 스킴 교체 시 필수).
- `sweep_ranking.py` — 랭킹 파라미터(RRF 상수·플랜 가중치·거버넌스 배수)를 재기동 없이 골든셋 위에서 스윕. v3에서 상수를 데이터클래스로 뺐기 때문에 가능해졌다.
- 진단 도구: 랭킹 진단(`rank_diag`), 임베딩 처리량 벤치(`embed_bench`). 후보 풀 스윕은 `sweep_ranking sem_candidates 30 60 100`.
- `selector_bench.py` — 선택 엔진 비교(`freeze`로 후보 고정 → `run --engine order|jev|laya` → `decide`). 평가 세트 `selector-golden.jsonl`, 결과 [docs/selector-bench-2026-10.md](docs/selector-bench-2026-10.md).

## 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 도구 20종
eval/       평가 하네스 + 골든셋 + 베이스라인 + 랭킹 파라미터 스윕
tests/      단위·계약 테스트
docs/       설계서(design-v3.md) + ADR
pipeline/       편찬 파이프라인 (hermes wiki_manager 크론이 실행 — 볼트에 쓰는 유일한 주체)
  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, requirements.txt
backups/    [git 제외] 일일 DB 스냅샷 — 개인 데이터
models/     [git 제외] 리랭커 가중치(수동 배치)
.env        [git 제외] HIPPO_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, 비동기 우선
- **핸드오프 레이어** — `hippo_worklog` / `hippo_handoff` / `hippo_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**, 이제 볼트에 쓰는 것은 편찬 실행기뿐(2026-09-13부터 hermes wiki_manager) |
| 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)

Maintenance

ActivityActive
ResponsivenessNo issues