PR-DocSrv-Hier-PassageRAG-Diagnose-1 c1. /ask evidence retrieval 의 chunk leg 를
측정 뷰(prehier/hier_sim_*)로 교체 + exact_knn — passage evidence 단위(hier 절 vs
legacy 윈도우) 비교용. /search 와 동일 패턴, run_search 전달. EVAL-ONLY 박제,
default(미지정) 시 기존 /ask byte/behavior 동일(회귀 0). pattern 검증 → 잘못된 값 422.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
marker_worker 는 변환 시작 시 doc.md_status=processing 으로 표시하는데, 변환이
_fail()/_set_skipped() 를 거치지 않고 예외(예: 대형 batch ReadTimeout)로 죽으면
queue_consumer 가 큐 행만 failed 처리하고 doc.md_status 는 processing 에 영구 고착
= orphan (큐 failed, 문서 processing). markdown consumer 분리 후 이 orphan 이
tail 재처리에서 재발(5149/5201)하여 근본 원인 차단.
_process_stage except 블록에서 큐 항목이 영구 실패(attempts>=max)할 때 stage가
markdown 이고 doc.md_status=processing 이면 failed 로 동기화. 재시도 중
(attempts<max)엔 pending 큐 행이 남아 orphan 아니므로 미터치.
검증: synthetic 영구 실패 경로 → md_status processing→failed 동기화 PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
대형 PDF split 변환(5210 ≈ 40분 실측)이 단일 consume_queue 코루틴을 점유해
extract/classify/embed/chunk 등 전 파이프라인을 stall 시키던 문제 제거.
- consume_markdown_queue 신규 — markdown 전용 scheduler job (id=markdown_consumer)
- consume_queue 는 MAIN_QUEUE_STAGES (markdown 제외) 만 처리
- _process_stage / _load_workers 헬퍼로 per-stage 로직 공유
- reset_stale_items(stages, threshold_minutes) 파라미터화: main=10min(markdown 제외),
markdown=MARKDOWN_STALE_MINUTES(기본 120). marker_worker 는 heartbeat 미기록이라
40분 변환을 10분 stale 로 오인하던 함정 차단
- enqueue flow (classify -> embed,chunk,markdown) 불변
STT/deep_summary 분리 + GPU 동시성 튜닝은 out of scope (follow-up).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR-DocSrv-LargeDoc-Split-Markdown-1 follow-up (plan brisk-paging-quokka.md).
commit 4(marker_section→document_chunks) 드롭으로, split md_content/manifest 의
「권위 검색본 = document_chunks (source_type=marker_section)」 문구가 실제와 불일치.
실제 = 검색 인덱스는 기존 document_chunks(extracted_text long_pdf window chunks),
marker_section chunk 부재, md_content 는 Markdown 렌더링 preview.
- _build_large_md_content 헤더: 「검색 인덱스 = 기존 document_chunks long_pdf/
extracted_text window chunks. 아래는 Markdown 렌더링 preview.」
- _split_manifest: canonical_storage(marker_section) → search_index(legacy/extracted_text)
- 상수 주석 + _process_split docstring: commit 4 드롭/이중적재 회피 반영
뷰어에 없는 source_type 으로 디버깅 오도 방지. 이미 처리된 5 docs 의 md_content 는
즉시 재처리 X — 자연 reprocess 시 갱신(사용자 결정).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR-DocSrv-LargeDoc-Split-Markdown-1 commit 5 (plan brisk-paging-quokka.md).
이미 마크다운인 문서는 marker 변환 불필요 → _process_markdown_passthrough 로
파일 내용(없으면 extracted_text)을 md_content 에 직접 적재(success), 비면 skipped.
- _is_markdown_doc: file_format=md/markdown 또는 .md/.markdown 확장자
- 분기 위치 = file_path validation 이전 (fileless md = file_path NULL 처리 위함)
- engine=passthrough 로 marker 변환본과 구분
기존 버그 해소: fileless md 43건=「no file_path」 fail / .md 파일=unsupported extension
skip → 둘 다 md_content 미생성이었음.
검증(docker cp 격리): 13948(.md+file_path)→success md_len=1805(파일) /
23409(fileless 931자)→success(extracted_text) / 20237(fileless 6자)→success.
PDF 경로 무영향(_is_markdown_doc=False).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
사용자 결정 (2026-05-24, measurement chain 4-layer 정정 완료 후):
> Phase 2Q Query Rewrite is closed as an evaluated experiment.
> After result-level dedup correction, true net gain was marginal
> (NDCG +0.019, Recall t≥2 +0.030) while latency cost was high
> (cold +876%, warm +320%). Therefore, multi-query rewrite is not
> recommended for default production rollout. Keep opt-in path as
> experimental/deprecated reference only; do not proceed to
> Cache-Prewarm unless future real-query evidence shows a stronger gain.
변경:
- docs/phase_2q_apply_opt_in.md: 🛑 DEPRECATED / EXPERIMENTAL status 박제. measurement chain
정정 history (4-layer) + 진짜 효과 + Phase 2Q 성과 보존.
- app/api/search.py: rewrite_backend query param description 갱신 (⚠️ EXPERIMENTAL/DEPRECATED,
production 추천 문구 제거, opt-in 실험 reference 만 유지 명시).
5 액션 박제 (사용자 결정):
1. opt-in 코드 유지 (recommended=false / experimental)
2. docs/ deprecated 박제
3. search.py description production 추천 제거
4. PR-2Q-Cache-Prewarm + PR-2Q-Apply-Default-ON-1 폐기
5. Extended 4건 중 SynonymDict (deterministic, LLM 우회) 만 별도 후보 보존
신규 feedback memory: [[feedback_measurement_chain_audit]] — Diagnose 측정이 Apply/rollout
결정 기준일 때 retrieval/fusion/rerank/eval 모든 layer audit 필수. Phase 2Q 4-iteration
정정 chain (0.927→0.876→0.641→0.663) origin.
Phase 2Q 성과 (실패가 아닌 좋은 실험):
- chunk_id/doc_id 중복 inflation 발견 + measurement chain audit pattern 확립
- LLM rewrite 는 현재 DS 검색 기본값으로는 ROI 낮음 결론 확보
- search_pipeline 의 multi-query 합성 + 3-layer dedup 인프라 보존 (Extended SynonymDict
또는 미래 cloud LLM scaffold 재사용 가능)
- 신규 feedback memory 4건: fixture-first-call-shape / apply-prereq-structural-fix /
graded-ndcg-dedup-invariant / measurement-chain-audit
main 위 직접 commit (read-only docs / API description, retrieval path 영향 0).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
C5 of family-adaptive-bengio. summarize_worker.py 의 doc.ai_model_version 이 실제 모델 (Gemma) 과 무관한 \"qwen3.5-35b-a3b\" hardcode 였음 — 추적/분석/로그 신뢰도 영향. client.ai.primary.model (config.yaml ai.models.primary.model = \"mlx-community/gemma-4-26b-a4b-it-8bit\") 으로 동적 swap — 향후 config model 변경 시 자동 정합.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P3 of family-adaptive-bengio (Mac mini 4-lever bundle).
50k 초과 input 은 CHUNK_SIZE=50000 단위로 N 분할 + cumulative carry-over (prev chunk summary 를 다음 chunk prompt 에 prefix). 50k 이하 input = 기존 동작 (변동 0). 첫 chunk = client.summarize() legacy / 후속 chunk = call_primary + SUMMARY_PROMPT_CONTINUATION. log trace: single vs sliding chunk N/M done.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR-DocSrv-Ask-ToolCalling-ReAct-1 — Qwen3.6-27B-8bit 의 native tool calling
으로 ReAct loop 도입. 기존 /api/search/ask 무수정. 트랙 B (frontend /ask SSE)
와 파일 단위 충돌 0 (search.py 의 ask() 함수 line diff = 0, 순수 추가).
핵심 invariant:
- 별 endpoint /api/search/ask/react (qwen-macbook only, implicit opt-in)
- MacBook unavailable 시 HTTP 503 + error_reason=macbook_unavailable.
Gemma 자동 fallback X (정정 4 의 연장)
G0 (구현 전 hard gate, plan b-velvety-hare.md):
- G0-1 fixture (tests/fixtures/qwen_tool_call_response.json): 실제 mlx-vlm
응답 박제. shape = OpenAI 표준 호환 (choices[0].message.tool_calls +
function.arguments JSON string). generate_with_tools() 가 본 shape 기준 구현.
- G0-2 counter semantics: max_tool_rounds=2 + max_llm_calls=3 + search_exec_max=2.
마지막 LLM 호출은 tool_choice="none" + system instruction 으로 final 강제.
- G0-3 trace exposure: default response 의 debug_trace=null. debug=true 시만
채움. server log 에는 항상 round 기록.
backends.py (193 → 261줄):
- QwenMacBookBackend.generate_with_tools(messages, tools, tool_choice)
신규 method. 기존 generate() 무수정. BackendUnavailable 처리 동일.
react_loop.py 신규 (275줄):
- agentic_ask_loop(session, query, *, backend, max_tool_rounds, debug)
- tool round 안에서 run_search 호출, results dedup by id, final round 강제,
partial=True 조건 (final content 빈 경우)
search.py (+82줄):
- POST /api/search/ask/react + AskReactRequest/Response schema
- BackendUnavailable → JSONResponse(503, error_reason=macbook_unavailable)
config.yaml + config.py:
- search.ask.react: { enabled, max_tool_rounds=2, search_tool_limit=5,
search_tool_mode=hybrid }
tests (566줄, 18 신규 + 23 회귀 모두 PASS):
- test_react_loop.py 13건: G0-1 fixture shape / G0-2 counter cap / G0-3 trace
exposure / BackendUnavailable propagation / sources dedup
- test_search_ask_react_endpoint.py 5건: 503 + run_search 호출 0 / 정상 200 /
debug=true trace 노출 / max rounds partial
- 회귀 (test_ask_eval_auth 9 + test_search_ask_macbook_503 5 +
test_backend_dispatcher 9) 모두 PASS
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
사용자 결정 2026-05-19: 100KB cap 이 운영 7d 데이터 1.36MB 대비 부족 →
cap 상향만으로 raw 비대화 위험. cap 1MB + payload compaction 병행.
fetch_recap_context() 변경:
- memo payload item field 축소 = id/title/ai_tldr/ai_event_kind/created_at (5 필드)
(ai_bullets/file_type/source_channel/category/extracted_text 등 제외)
- memo top-N = RECAP_MEMO_TOP_N env (default 200) — 초과분은 aggregate 로
- aggregate = memos_by_day + memos_by_kind + omitted_memos
- payload_compacted flag = aggregate fallback 발현 여부
- events 는 raw (운영 7d 데이터에서 통상 0~소량)
internal_worker.py:
- PAYLOAD_MAX_BYTES → _payload_max_bytes() env override
(WORKER_RECAP_PAYLOAD_MAX_BYTES default 1_000_000)
- JobsRecapResponse 에 payload_compacted / omitted_memos 노출
- 413 detail 에 "after compaction" 명시 + RECAP_MEMO_TOP_N 조정 안내
테스트 3 항목 신규 + 기존 endpoint 413 test 업데이트:
- 700 memo → 200 kept + 500 omitted + compacted=true + < 1MB
- 10 memo → compacted=false + omitted=0
- 비정상 큰 title (compaction 후에도 cap 초과) → 413 유지
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chore-memo-NULL-backfill 6/6 H1 (historical artifact) 확정 후 Apply PR 영구 보류.
406b810 의 8-line logger.info 블록 제거 (behavior 변경 0, 진단 데이터 더 이상 불필요).
backup: app/workers/classify_worker.py.pre-eventkind-cleanup (7일 안전망 ~2026-05-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
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
완화만.
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>