콘텐츠로 이동

디지스트 파이프라인

이 페이지는 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_version bump 시 기존 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은 이 위에 게이팅. 온디바이스 비전 참고.

더 보기