본문 바로가기
C.W.K.
Stream
Lesson 04 of 04 · published

모드는 고르는 거지 추측하는 게 아냐

~11 min · connection-modes, explicit, privacy, tailscale

Level 0불씨 없음
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"네 목소리가 어디로 가는지는 네가 내린 결정이어야지, 네트워크가 대신 내린 결정이면 안 돼."

세 모드, 하나의 선택

Firekeeper 의 정리 브레인은 세 곳에 살 수 있어. 그리고 어디에 둘지는 유저가 골라. 숨은 폴백 체인이 아니라 설정이야.

  • Pippa Full, This Mac. localhost 의 cwkPippa. 풀 볼트, 소울, 브레인 라우팅 전부.
  • Pippa Full, Tailscale. 같은 풀 Pippa 인데 설정해 둔 호스트로 사설 Tailscale 링크를 타고 가. 능력은 같고, 전송만 원격이고, 인증을 거쳐.
  • Local Mini, Ollama. 컴팩트한 Pippa 맛 정리 프로토콜을 돌리는 작은 로컬 모델. cwkPippa 는 아예 안 써.

"This Mac" 이랑 "Tailscale" 은 같은 능력을 서로 다른 전송에 실어 나르는 거야. "Local Mini" 는 아예 다른, 가벼운 능력이고. 어느 쪽을 감수할지는 유저가 직접 골라.

왜 자동 폴백이 아냐?

이걸 조용한 체인으로 만들고 싶어지기 쉬워. This Mac 을 시도하고, 안 되면 Tailscale, 그다음 Local Mini, 그다음 결정론적. 바로 그 조용한 체인이 프라이버시 버그야. 오프라인으로만 쓰겠다고 일부러 고른 유저인데 localhost 가 잠깐 안 잡힌다고 오디오가 조용히 원격 호스트로 흘러가면 안 되잖아. 풀 Pippa 품질을 기대한 유저가 아무 말 없이 약한 로컬 결과를 받아도 안 되고. 그래서 프로바이더는 요청을 보내기 전에, 고른 모드에 맞춰 정해져. 그리고 고른 대상이 실패하면 앱이 유저 등 뒤에서 하나 골라 버리는 대신 대안을 내밀어. 다시 해볼래, 다른 Full 대상으로 갈래, Local Mini 로 내려갈래, 아니면 원시 그대로 넣을래.

조용한 폴백 (나쁨):  오프라인 유저 -> localhost 다운 -> 조용히 Tailscale 침  # 누수!
명시적 선택 (좋음):  오프라인 유저 -> localhost 다운 -> "재시도? / Local Mini? / 원시?"

발목 잡는 디테일 둘

원격 모드는 조심할 게 있어. 연결 모드를 바꾸면 원격 인증이랑 세션 상태가 무효가 돼. Tailscale PIN 세션을 This Mac 요청에 끌고 갈 수 없거든. 그리고 원격 호출은 전부 유저가 취소할 수 있는 제한 시간을 달아야 해. 음성 캡처가 멈춰 버린 원격 경로를 기다리며 매달려 있으면 안 되니까. 쓴 경험에서 나온 규칙 하나 더. 원격 호스트를 절대 하드코딩하지 마. Firekeeper 는 호스트를 설정에 둬. 기계 하나의 주소를 코드에 박으면 어디서나 도는 앱이 그 Mac 전용 앱으로 쪼그라들거든.

명시적 의도로 라우팅하고, 실패하면 대안을 내밀어. 프라이버시가 걸린 일이라면 목적지는 유저가 내리고 눈으로 확인하는 결정이어야 해. 잠깐 실패한 결과로 자동으로 정해지는 게 절대 아니고. 고른 모드에 맞춰 프로바이더를 미리 정해 놔. 실패하면 프라이버시나 품질 자세를 조용히 바꿔치기하지 말고 선택지를 꺼내 보여줘.
멈춘 원격이 보이스 루프를 얼리면 절대 안 돼. Tailscale 정리 호출이 매달리면 단축키부터 삽입까지 루프 전체가 그걸 기다리게 돼. 그러면 안 되지. 원격 요청은 전부 제한 시간으로 묶고 진행 중인 작업은 취소할 수 있게 만들어. 그래야 네트워크가 나빠도 앱이 얼어붙어서 유저 단어를 인질로 잡는 대신 '원시로 넣을까?' 로 내려가.

Code

전송 전에, 고른 모드에서 프로바이더 결정·swift
enum ConnectionMode { case pippaFullThisMac, pippaFullTailscale, localMiniOllama }

func provider(for mode: ConnectionMode, hosts: HostSettings) -> CleanupProvider {
    switch mode {
    case .pippaFullThisMac:   return CwkPippaProvider(baseURL: hosts.localhost)
    case .pippaFullTailscale: return CwkPippaProvider(baseURL: hosts.tailscaleHost)  // 설정 가능!
    case .localMiniOllama:    return OllamaMiniPippaProvider(baseURL: hosts.ollama)
    }
}

// 프로바이더는 요청 전에 유저 모드에서 골라져.
// 실패 시 앱이 선택지를 제안; 절대 조용히 자세를 안 바꿔.
// hosts.tailscaleHost 는 설정이지 하드코딩 주소 절대 아님.

External links

Exercise

서비스를 로컬로도 원격으로도 부를 수 있는 앱을 두고, 조용한 폴백 체인과 대안을 내미는 명시적 선택이 어떻게 다른지 써봐. 그리고 조용한 체인이 물어보지도 않고 돌았다는 걸 알면 유저가 화날 만한 구체적인 경우를 하나 그려봐.
Hint
화나는 경우는 거의 항상 프라이버시야. 오프라인이라고 믿고 있었는데, 로컬 대상이 잠깐 깜빡였다고 자기 데이터가 원격 호스트로 갔다는 걸 나중에 알게 되는 것. 조용한 폴백은 '어떻게든 계속 돌아가게' 를 보고 만든 거고, 명시적 선택은 '내가 허락 안 한 일은 내 데이터에 안 일어나게' 를 보고 만든 거야. 목소리라면 두 번째만 받아들일 수 있는 기본값이고.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.