디지스트 파이프라인¶
이 페이지는 CLAUDE.md의 구현 노트를 미러합니다. 서브시스템 변경 시
양쪽 다 업데이트하세요.
디지스트 파이프라인은 최근 화면 활동을 짧은 narrative 요약으로
바꿔 digest.md (그리고 SQL digests.summary)에 저장합니다.
두 백엔드, 하나의 fail-closed 정책 게이트, 하나의 PII 출력 scrub
레이어.
아키텍처¶
키프레임 + MCAP 이벤트
│
▼
cue.digest._build_digest()
│
▼
cue.frame_select.select_digest_frames(max=10) ← anchor + transition + dedupe
│
├── ≤ 10 스크린샷, 다운스케일 (896 px max), JPEG q75, EXIF strip
└── timeline: window/title/event narrative
▼
cue.llm.summarize_digest_with_policy() ← 정책 게이트
├── digest_backend=local → LocalVisionBackend
│ │
│ ├── llama-server 서브프로세스 (관리됨)
│ ├── model.gguf + mmproj.gguf
│ └── POST /v1/chat/completions (OpenAI 호환, image_url data URLs)
│ │
│ └── LocalUnavailable / LocalTimeout
│ ├── allow_cloud_fallback=False → SKIP (tombstone)
│ └── allow_cloud_fallback=True → CloudVisionBackend (opt-in)
└── digest_backend=cloud → CloudVisionBackend (Haiku w/ images)
▼
cue.pii.scrub() (Presidio + custom recognizer)
▼
digest.md + SQL digests.summary
▼
memory.py (Opus) / suggest.py (Opus) ← 항상 scrub된 텍스트만 봄
두 백엔드¶
| 백엔드 | 기본값 | 보내는 것 |
|---|---|---|
| Cloud (Anthropic Haiku) | yes | 선택된 스크린샷 + 이벤트 timeline → vision content block 포함 messages.create. |
Local (번들된 llama-server + Gemma 4 GGUF) |
opt-in | 같은 프롬프트 + 스크린샷 → POST localhost:<port>/v1/chat/completions, OpenAI 호환. allow_cloud_fallback이 명시적으로 켜지지 않는 한 이미지 바이트는 디바이스를 떠나지 않음. |
cue.llm.summarize_digest_with_policy(prompt, frames, timeline)이
단일 호출 사이트. digest_backend 설정 키에 따라 백엔드 선택,
LocalUnavailable / LocalTimeout 시 fail-closed 정책 적용,
모델 raw 출력 반환.
Fail-closed 정책¶
flowchart TD
A[digest_backend?] -->|cloud| B[CloudVisionBackend]
A -->|local| C[LocalVisionBackend]
C -->|성공| D[summary]
C -->|LocalUnavailable / LocalTimeout| E{allow_cloud_fallback?}
E -->|false| F[SKIP — tombstone row]
E -->|true| B
B --> D
D --> G[pii.scrub]
G --> H[digest.md + digests row 영속화]
로컬 백엔드 실패하고 allow_cloud_fallback=False면 사이클 skip —
cloud 호출 없음, 작성 없음. 고정 문자열 tombstone row가 DB에 들어감
(summary="[skipped: local vision model unavailable]")으로 파일
경로 / 포트 / stderr 누설 방지. 자세한 사유 (모델 경로, exception
타입, stderr tail)는 scrub 후 privacy.log에만 기록.
프레임 선택¶
cue.frame_select.select_digest_frames(keyframe_dir, window_secs,
max_frames=10)은 다음 기준으로 최대 10프레임 반환:
- 보호된 anchor — 윈도우의 first / middle / last는 절대 dedupe 되거나 drop되지 않음. narrative 연속성에 load-bearing.
- 나머지는 4개 scorer로 scoring:
window_app_title_change_score— foreground 앱 / 윈도우 타이틀 delta.user_event_spike_score— 프레임 근처 키스트로크 / 클릭 burst.visual_change_score— 이전 키프레임에 대한 dhash Hamming 거리 (윈도우 타이틀이 안 바뀌어도 장면 전환 — 같은 브라우저에서 탭 전환, 긴 문서 스크롤 — 잡음).text_density_score— OCR / dhash-text-region 휴리스틱.- non-anchor 끼리 dhash Hamming 거리 ≤ 4로 dedupe. anchor는 무조건 유지.
- dedupe 후 부족하면 top up.
- 반환 전 chronological 정렬.
일시정지 인터벌 안의 프레임은 (privacy.is_paused() 히스토리 읽고)
제외.
이미지 전처리¶
cue.image_preprocess.prepare_for_digest(path, max_dim=896,
quality=75) -> bytes:
- Pillow로 open.
- 이미지가 이미 작고 EXIF / ICC profile 없으면 원본 바이트 short-circuit return.
ImageOps.exif_transpose적용, 필요 시 RGB 변환.- width/height 재계산 (transpose가 swap 가능).
max_dim(기본 896 px = Gemma 4 비전 타워 native) 안에 들어가게 다운스케일.- quality 75에
optimize=True로 JPEG 재인코딩. EXIF + ICC는 생략으로 strip. - bytes 반환 — 디스크에 절대 안 씀.
픽셀은 data:image/jpeg;base64,... URL로 Cue → llama-server (또는
Anthropic API)로 직행. 원본 키프레임 경로는 프롬프트 텍스트나
어떤 로그 라인에도 절대 안 나타남. 프롬프트에 들어가는 프레임
메타데이터는 frame_index + chronological_offset_seconds 만.
PII 출력 scrub¶
cue.pii.scrub()이 Presidio 기본값 + Cue 전용 custom recognizer
(API 키 / DB URL / 파일 경로 / 미팅 URL / 한국 PII)를 디지스트
텍스트에 실행 — digest.md, SQL row, memory.md에 쓰기 전.
멱등성은 테스트된 contract.
다층 방어:
- LocalVisionBackend / CloudVisionBackend return에 출력 scrub.
memory._compute_memory()return에 출력 scrub._meta.scrub_versionbump 시 기존 row 일회성 backfill.- summary / prompt / payload를 다루는 모든 로그 사이트 scrub 또는 redact. 이미지 바이트는 SQL이나 로그 파일에 절대 안 씀.
cue.pii에서 recognizer 카탈로그 참고.
Skip 시 tombstone¶
정책 게이트가 None (skip) 반환 시 mark_digest_skipped(reason)이
고정 문자열 row 삽입 — digest.md가 이전 요약 대신 "[no recent
activity]"를 반영. 자세한 사유는 privacy.log에만.
라텐시 budget¶
| 백엔드 | 목표 p95 | 비고 |
|---|---|---|
| Cloud Haiku w/ ≤10 이미지 | ~2초 | 기본값. |
| Local Gemma 4 E2B Q8 (CPU) | 22-35초 | 스파이크에서 측정된 Apple Silicon M2 Pro. |
| Local Gemma 4 E2B Q8 (M5+ Metal) | ~6초 (계획) | 하드웨어 일반 출시 전. 기본값 flip은 이 위에 게이팅. 온디바이스 비전 참고. |
더 보기¶
cue.digest— 모듈 API.cue.llm— 백엔드 추상화.cue.frame_select— selector 내부.cue.image_preprocess— 사전 인코딩 파이프라인.cue.pii— PII scrub 레이어.- 프롬프트 —
DIGEST_HEADER템플릿 + 동반자. - 온디바이스 비전 — 로컬 백엔드 라이프사이클.