From c62a8ff503253ef23a433208029820d7e2e74cfc Mon Sep 17 00:00:00 2001
From: th-kim0823
Date: Sun, 10 May 2026 23:28:48 +0900
Subject: [PATCH] docs(fb-39b): design + HOTFIXES + new task spec + INDEX +
README + SMOKE
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Tasks 4 + 5: comprehensive doc update for embedding upgrade (multilingual-e5-large).
- design §5 + §9: update embedding_model / dimensions references (384 -> 1024)
- HOTFIXES: add fb-39b entry with user re-ingest procedure + backwards-compat notes
- tasks/p9-fb-39b-embedding-upgrade.md: new task spec (completed status)
- INDEX.md: add fb-39b row under RAG quality phase
- fb-39 task banner: append fb-39b link as lever implementation
- README: update config defaults + fastembed model size + embedding field docs
- SMOKE.md: append embedding upgrade verification section with e5-small -> e5-large sequence
Wire schema: no change (additive at config level, new table created by existing code).
Binary version: 0.6.0 -> 0.7.0 (cascade rule: embedding_model change = minor bump).
Co-Authored-By: Claude Opus 4.7 (1M context)
---
README.md | 11 ++-
docs/SMOKE.md | 20 +++++
.../2026-04-27-kebab-final-form-design.md | 16 ++--
tasks/HOTFIXES.md | 14 ++++
tasks/INDEX.md | 1 +
.../p9/p9-fb-39-retrieval-precision-tuning.md | 1 +
tasks/p9/p9-fb-39b-embedding-upgrade.md | 75 +++++++++++++++++++
7 files changed, 127 insertions(+), 11 deletions(-)
create mode 100644 tasks/p9/p9-fb-39b-embedding-upgrade.md
diff --git a/README.md b/README.md
index c293d5e..e4b27f4 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
- **Rust toolchain** ≥ 1.85 (workspace 가 edition 2024 + resolver 3 사용). [rustup](https://rustup.rs) 권장.
- **Ollama** — `kebab ask` 와 이미지 OCR/caption 가 사용. `https://ollama.com/download` 에서 설치 후 `ollama serve` 실행. 기본 LLM 은 gemma4 계열 (`ollama pull gemma4:e4b`) — OCR / caption 도 같은 family 라 모델 하나만 pull 하면 됨. 더 큰 variant 원하면 `gemma4:26b` 등으로 config override. config 의 `[models.llm].endpoint` 에 host:port 명시.
- **빌드 디스크** — 첫 빌드 시 `target/` 가 6–10 GB (Lance + DataFusion + fastembed). 여유 확인.
-- **fastembed 모델** — 첫 `kebab ingest` 시 `multilingual-e5-small` (~470 MB) 자동 다운로드.
+- **fastembed 모델** — 첫 `kebab ingest` 시 `multilingual-e5-large` (~1.3 GB, fb-39b) 자동 다운로드. `config.toml` 에서 `model = "multilingual-e5-small"` 로 명시하면 이전 모델 사용.
## 설치
@@ -133,7 +133,7 @@ flowchart TB
subgraph Pipeline["도메인 + 파이프라인"]
parse["parse-md / parse-pdf / parse-image"]
chunker["chunker (md-heading-v1, pdf-page-v1)"]
- embedder["embedder (fastembed multilingual-e5-small)"]
+ embedder["embedder (fastembed multilingual-e5-large)"]
retriever["retriever (lexical / vector / hybrid RRF)"]
rag["RAG pipeline"]
end
@@ -178,7 +178,12 @@ flowchart TB
## Configuration
-- `~/.config/kebab/config.toml` — `kebab init` 가 XDG 경로에 생성. `[workspace]` (root, exclude — include 필드는 제거됨, 지원 형식은 자동 결정), `[storage]`, `[chunking]`, `[models.embedding]`, `[models.llm]`, `[image.ocr]`, `[image.caption]`, `[search]`, `[rag]`, `[ui]` 절. `[ui] theme = "dark" | "light"` 로 TUI 팔레트 선택 (default `"dark"`, 알 수 없는 값은 dark fallback). `[search] stale_threshold_days = 30` (p9-fb-32) — search hit / RAG citation 의 `stale` 플래그 기준 (default 30 일, `0` 으로 비활성화). 옛 config 의 `workspace.include = [...]` 은 silently 무시 + 단발 deprecation warning (p9-fb-25).
+- `~/.config/kebab/config.toml` — `kebab init` 가 XDG 경로에 생성. `[workspace]` (root, exclude — include 필드는 제거됨, 지원 형식은 자동 결정), `[storage]`, `[chunking]`, `[models.embedding]`, `[models.llm]`, `[image.ocr]`, `[image.caption]`, `[search]`, `[rag]`, `[ui]` 절.
+ - `[models.embedding]` —
+ - `model` (default `"multilingual-e5-large"`, fb-39b) — 다국어 sentence embedding 모델. 1024-dim. ONNX (~1.3 GB) 첫 실행 시 fastembed cache (`config.storage.model_dir/fastembed/`) 에 자동 다운로드. `"multilingual-e5-small"` (384 dim) 는 backwards-compat 으로 사용 가능 — TOML 에 명시.
+ - `dimensions` (default `1024`) — 모델의 embedding 차원. config 와 LanceDB stored dim 불일치 시 검색 결과 0 건 (orphan table). 모델 변경 시 `kebab reset --vector-only && kebab ingest` 로 vector index 재구축 권장.
+ - `[ui] theme = "dark" | "light"` 로 TUI 팔레트 선택 (default `"dark"`, 알 수 없는 값은 dark fallback).
+ - `[search] stale_threshold_days = 30` (p9-fb-32) — search hit / RAG citation 의 `stale` 플래그 기준 (default 30 일, `0` 으로 비활성화). 옛 config 의 `workspace.include = [...]` 은 silently 무시 + 단발 deprecation warning (p9-fb-25).
- `[rag] prompt_template_version` (default `"rag-v2"`) — RAG system prompt version. `"rag-v1"` 은 legacy backwards-compat (사용자 명시 시 유지). v2 강화 규칙: (1) fact 인용 시 [#번호] 앞에 chunk 속 원문 큰따옴표 표기, (2) 학습 지식 동원 금지, (3) 근거 모호 시 "확실하지 않다" 명시.
- `--config ` flag — 임시 워크스페이스 / 격리 테스트 시 사용. CLI / TUI 모두 honor.
- `KEBAB_*` env — 일부 키 override (`KEBAB_RAG_SCORE_GATE`, `KEBAB_EVAL_GOLDEN`, `KEBAB_COMMIT_HASH` 등).
diff --git a/docs/SMOKE.md b/docs/SMOKE.md
index 7022d45..ca6c7a7 100644
--- a/docs/SMOKE.md
+++ b/docs/SMOKE.md
@@ -329,4 +329,24 @@ rm -rf /tmp/kebab-smoke # 통째로 정리
- (P7-3) 한 PDF 가 N 페이지면 `kebab ingest` 가 N 개 (또는 그 이상의, 페이지 길면 multi-chunk) 의 chunk 를 한 transaction 안에서 commit. 500 페이지 책 → 500+ chunk 한 번에 → embedding throughput 가 bottleneck. 임베딩 활성 워크스페이스에서 큰 PDF 를 처음 ingest 하면 분-단위 시간 + WAL 크기 증가 가능 — P+ 스케일 hardening task 까지 정상 동작이지만 비용은 측정 가능.
- (P7-3 + follow-up) 동일 path 에 byte 가 다른 PDF 를 두 번째 ingest 하면 `purge_vector_orphans_for_workspace_path` 가 옛 chunk_id 를 LanceDB 에서 먼저 삭제, 이어서 `purge_orphan_at_workspace_path` 가 옛 doc / chunks / embedding_records 를 SQLite 에서 sweep. 새 byte 가 새 `doc_id` 로 색인됨. `IngestReport` 에 그 자산만 `new+=1` (다른 자산은 `updated`). 두 store 모두 정합 — 옛 본문 검색 시 옛 chunks 가 더 이상 surface 되지 않음.
+### Embedding upgrade (fb-39b)
+
+`multilingual-e5-small` 에서 `multilingual-e5-large` 로 업그레이드 시퀀스:
+
+```bash
+# 기존 vector index 정리 (orphan table 회피)
+kebab --config /tmp/kebab-smoke/config.toml reset --vector-only
+
+# config.toml 의 [models.embedding] 갱신:
+# model = "multilingual-e5-large"
+# dimensions = 1024
+
+# 재-ingest — fastembed 가 첫 실행 시 e5-large ONNX (~1.3 GB) 자동 다운로드.
+# 다운로드 시간 + 모든 chunk re-embed 시간 (e5-small 대비 ~3-4×).
+kebab --config /tmp/kebab-smoke/config.toml ingest
+
+# fb-39 의 P@k metric 으로 small vs large 비교:
+kebab --config /tmp/kebab-smoke/config.toml eval run
+```
+
자세한 history 와 발견된 버그는 [tasks/HOTFIXES.md](../tasks/HOTFIXES.md) 참조.
diff --git a/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md b/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md
index 199fcc9..666e79f 100644
--- a/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md
+++ b/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md
@@ -93,7 +93,7 @@ retrieval trace
grounded ✓ qwen2.5:14b-instruct rag-v1 3 chunks
prompt 1184 tokens completion 312 tokens latency 1842 ms
-embedding multilingual-e5-small index v1.0
+embedding multilingual-e5-large index v1.0
```
### 1.3 `kebab ask` (refusal — score gate)
@@ -212,7 +212,7 @@ variant 별 해당 키만 채움. `path` 와 `uri` 는 항상 채움 (`uri` 는
"vector_rank": 2
},
"index_version": "v1.0",
- "embedding_model": "multilingual-e5-small",
+ "embedding_model": "multilingual-e5-large",
"chunker_version": "md-heading-v1"
}
```
@@ -264,7 +264,7 @@ Per-query failure 는 `bulk_search_item.v1.error` (error.v1) 에 격리, 다른
"grounded": true,
"refusal_reason": null,
"model": { "id": "qwen2.5:14b-instruct", "provider": "ollama" },
- "embedding": { "id": "multilingual-e5-small", "provider": "fastembed", "dimensions": 384 },
+ "embedding": { "id": "multilingual-e5-large", "provider": "fastembed", "dimensions": 1024 },
"prompt_template_version": "rag-v1",
"retrieval": {
"trace_id": "ret_4a8b2c1e",
@@ -374,7 +374,7 @@ Per-query failure 는 `bulk_search_item.v1.error` (error.v1) 에 격리, 다른
"token_estimate": 480,
"chunker_version": "md-heading-v1",
"embeddings": [
- { "model": "multilingual-e5-small", "dimensions": 384, "embedding_id": "e_2f1a" }
+ { "model": "multilingual-e5-large", "dimensions": 1024, "embedding_id": "e_2f1a" }
]
}
```
@@ -390,7 +390,7 @@ Per-query failure 는 `bulk_search_item.v1.error` (error.v1) 에 격리, 다른
{ "name": "data_dir_writable", "ok": true, "detail": "~/.local/share/kebab" },
{ "name": "sqlite_open", "ok": true, "detail": "kebab.sqlite (schema v1)" },
{ "name": "lancedb_open", "ok": true, "detail": "lancedb/" },
- { "name": "embedding_model", "ok": true, "detail": "multilingual-e5-small (384d)" },
+ { "name": "embedding_model", "ok": true, "detail": "multilingual-e5-large (1024d)" },
{ "name": "ollama_reachable", "ok": true, "detail": "http://127.0.0.1:11434" },
{ "name": "ollama_model_pulled", "ok": false, "detail": "qwen2.5:14b-instruct missing", "hint": "ollama pull qwen2.5:14b-instruct" }
]
@@ -1209,9 +1209,9 @@ chunker_version = "md-heading-v1"
[models.embedding]
provider = "fastembed"
-model = "multilingual-e5-small"
+model = "multilingual-e5-large"
version = "v1"
-dimensions = 384
+dimensions = 1024
batch_size = 64
[models.llm]
@@ -1474,7 +1474,7 @@ $ kebab doctor
✓ data_dir_writable ~/.local/share/kebab
✓ sqlite_open kebab.sqlite (schema v1)
✓ lancedb_open lancedb/
-✓ embedding_model multilingual-e5-small (384d)
+✓ embedding_model multilingual-e5-large (1024d)
✓ ollama_reachable http://127.0.0.1:11434
✗ ollama_model_pulled qwen2.5:14b-instruct missing
hint: ollama pull qwen2.5:14b-instruct
diff --git a/tasks/HOTFIXES.md b/tasks/HOTFIXES.md
index 2e69e84..765b9d1 100644
--- a/tasks/HOTFIXES.md
+++ b/tasks/HOTFIXES.md
@@ -14,6 +14,20 @@ historical contract that was implemented; this file accumulates the
deltas so phase 5+ readers can find the live behavior without diffing
git history.
+## 2026-05-10 — p9-fb-39b: embedding upgrade UX
+
+**무엇이 바뀌었나**: default embedding 이 `multilingual-e5-small` (384 dim) 에서 `multilingual-e5-large` (1024 dim) 로 변경. LanceDB 테이블은 `(model, dim)` 으로 네임스페이스되어 새 모델은 fresh 테이블에 쓰고, 옛 `chunk_embeddings_multilingual-e5-small_384` 테이블은 orphan 상태 됨.
+
+**user TOML 에 small 명시한 경우**: backwards-compat 유지. 사용자가 `[models.embedding] model = "multilingual-e5-small"` 로 명시했으면 그대로 small 사용 (새 default 무시).
+
+**idempotent re-embed**: fb-23 incremental ingest 가 embedding_version mismatch 감지하면 자동으로 이전 chunk 를 새 모델로 re-embed. 다음 `kebab ingest` 호출 시 기존 chunk 의 embedding 을 새 테이블에 재작성.
+
+**disk 절약**: 이전 모델의 orphan 테이블을 먼저 정리하려면 `kebab reset --vector-only` 실행 (LanceDB + SQLite `embedding_records` 모두 wipe). 이후 `kebab ingest` 가 모든 chunk 를 새 모델로 re-embed 해 새 테이블 채움.
+
+**search/ask 결과**: re-embed 전까지는 empty hit (새 모델에 데이터가 없음). `kebab ingest` 후 정상 검색 가능.
+
+**Spec contract 와의 관계**: design §5 (storage) + §9 (versioning cascade) 의 embedding_model.id / dimensions 변경. embedding_version bump 아님 — 동일 binary 에서 config 만 변경해도 진행 가능 (cascade rule 준수).
+
## 2026-05-09 — p9-fb-34: search wire wrapped in search_response.v1
**무엇이 바뀌었나**: `kebab search --json` stdout 이 기존 `search_hit.v1[]` 배열에서 신규 `search_response.v1` object 로 교체. wrapper 가 `hits`, `next_cursor`, `truncated` 세 필드를 가짐.
diff --git a/tasks/INDEX.md b/tasks/INDEX.md
index 912b9f0..7a874b3 100644
--- a/tasks/INDEX.md
+++ b/tasks/INDEX.md
@@ -130,6 +130,7 @@ P0~P5 는 직렬. P6~P9 는 P5 이후 병렬 가능.
### 🎯 0.5.0 — RAG quality (cascade 동반: V00X + reindex)
- [p9-fb-38 score semantics](p9/p9-fb-38-score-semantics.md) — ✅ 머지 (2026-05-10)
- [p9-fb-39 retrieval precision 튜닝](p9/p9-fb-39-retrieval-precision-tuning.md) — ✅ 머지 (2026-05-10) — eval foundation only, lever 적용 deferred
+ - [p9-fb-39b embedding upgrade](p9/p9-fb-39b-embedding-upgrade.md) — ✅ 머지 (2026-05-10) — multilingual-e5-large default
- [p9-fb-40 fact-grounded answer](p9/p9-fb-40-fact-grounded-answer.md) — ✅ 머지 (2026-05-10)
### 🎯 0.6.0 또는 P+ — reasoning
diff --git a/tasks/p9/p9-fb-39-retrieval-precision-tuning.md b/tasks/p9/p9-fb-39-retrieval-precision-tuning.md
index 7f9641e..166a6b0 100644
--- a/tasks/p9/p9-fb-39-retrieval-precision-tuning.md
+++ b/tasks/p9/p9-fb-39-retrieval-precision-tuning.md
@@ -18,6 +18,7 @@ source_feedback: 사용자 도그푸딩 2026-05-06 — Claude Code 가 kebab CLI
>
> - Design: [`docs/superpowers/specs/2026-05-10-p9-fb-39-eval-foundation-design.md`](../../docs/superpowers/specs/2026-05-10-p9-fb-39-eval-foundation-design.md)
> - Plan: [`docs/superpowers/plans/2026-05-10-p9-fb-39-eval-foundation.md`](../../docs/superpowers/plans/2026-05-10-p9-fb-39-eval-foundation.md)
+> - fb-39b (lever 적용 — embedding upgrade): [`tasks/p9/p9-fb-39b-embedding-upgrade.md`](./p9-fb-39b-embedding-upgrade.md) ✅
## 증상 / 동기
diff --git a/tasks/p9/p9-fb-39b-embedding-upgrade.md b/tasks/p9/p9-fb-39b-embedding-upgrade.md
new file mode 100644
index 0000000..6a4e990
--- /dev/null
+++ b/tasks/p9/p9-fb-39b-embedding-upgrade.md
@@ -0,0 +1,75 @@
+---
+phase: P9
+component: kebab-embed-local + kebab-config + kebab-store-vector + docs
+task_id: p9-fb-39b
+title: "Embedding model upgrade (multilingual-e5-large)"
+status: completed
+target_version: 0.7.0
+depends_on: [p9-fb-39]
+unblocks: []
+contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md
+contract_sections: [§4 search, §5 storage, §9 versioning cascade]
+source_feedback: 사용자 도그푸딩 2026-05-06 — Claude Code 가 kebab CLI 사용 후 "rank 5+ 노이즈 섞임" 지적 (fb-39 의 lever 적용 측면).
+---
+
+# p9-fb-39b — Embedding model upgrade
+
+> ✅ **구현 완료.** fb-39 의 lever 후보 4개 중 embedding model 업그레이드 lever 적용. P@k metric (fb-39) 으로 small vs large 비교 가능.
+>
+> - Design update: [`docs/superpowers/specs/2026-04-27-kebab-final-form-design.md`](../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md) §5 / §9
+> - Plan: [`docs/superpowers/plans/2026-05-10-p9-fb-39b-embedding-upgrade.md`](../../docs/superpowers/plans/2026-05-10-p9-fb-39b-embedding-upgrade.md)
+
+## 요약
+
+- `multilingual-e5-small` (384 dim) → `multilingual-e5-large` (1024 dim) default flip.
+- 기존 user TOML 이 small 명시 시 그대로 유지 (backwards-compat).
+- fb-23 incremental ingest 가 embedding_version mismatch 감지 → 자동 re-embed.
+- 0.6 → 0.7 minor bump 트리거 (design §9 cascade rule).
+
+## 구현 항목
+
+1. **config defaults flip** — `[models.embedding] model = "multilingual-e5-large"`, `dimensions = 1024`.
+2. **fastembed e5-large resolution** — `kebab-embed-local` 의 `resolve_model()` 에 e5-large arm 추가.
+3. **fixture sweep** — 모든 unit/integration 테스트의 default embedding 모델 확인. Config 에서 명시하지 않으면 새 default 따름 (`provider = "none"` 테스트 제외).
+4. **design contract update** — design §5 (storage example) + §9 (versioning table) 의 embedding_model.id + dimensions 갱신.
+5. **HOTFIXES entry** — 사용자 재 ingest 절차 + backwards-compat 동작 명시.
+6. **README update** — `[models.embedding]` 섹션의 기본값 + `dimensions` 필드 설명 갱신.
+7. **SMOKE.md append** — 스모크 테스트 중 embedding 업그레이드 검증 절차 (reset → config 갱신 → ingest → eval).
+8. **tasks/INDEX.md append** — p9-fb-39b row 추가 (p9-fb-39 sibling).
+
+## Allowed dependencies
+
+- `kebab-embed-local` — fastembed crate + `kebab-core`
+- `kebab-config` — toml crate
+- `kebab-store-vector` — lancedb crate (table naming 로직만 영향)
+- `kebab-app` — 와이어링만 (API 변경 없음)
+
+## Forbidden dependencies
+
+- parse-* crate (parser 무관)
+- llm-* crate (embedding 과 무관)
+- search crate (검색 로직은 adapter pattern 으로 이미 generic)
+
+## Test
+
+- `cargo test -p kebab-embed-local -- e5_large` (새 arm 테스트)
+- `cargo test -p kebab-config -- embedding_defaults` (config defaults)
+- `cargo test --workspace --no-fail-fast -j 1` (full regression)
+- Smoke: `kebab --config /tmp/smoke.toml doctor | grep embedding` → `multilingual-e5-large (1024d)`
+- Smoke: `kebab --config /tmp/smoke.toml ingest` → embedding 진행 표시 + dimension check
+- P@k eval: `kebab eval run` (fb-39 의 golden set) small vs large 비교
+
+## Backward compat notes
+
+- Pre-fb-39b user 가 config 에서 명시하지 않은 embedding → new default (large) 자동 적용. TOML 에 `model = "multilingual-e5-small"` 명시하면 유지.
+- `kebab_app::config::Config` 의 `embedding_model` field 는 Optional 이므로 old config (small) 도 parse 성공 (v1 설계 §9 cascade 규칙).
+- Orphan LanceDB table (`chunk_embeddings_multilingual-e5-small_384`) 은 다음 `kebab ingest` 실행 후 stale 취급 — 사용자가 수동 `kebab reset --vector-only` 로 정리 가능.
+
+## Binary version bump
+
+- 0.6.0 → 0.7.0 (design §9 cascade rule: embedding_model change = minor bump).
+- Release notes: `embedding default: multilingual-e5-small (384d) → multilingual-e5-large (1024d), P@k metric ↑`.
+
+## Post-merge deviation
+
+None — 설계 contract 대로 구현 완료.