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

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

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

Level 0불씨 없음
0 XP0/48 lessons0/13 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 가 잠깐 안 잡힌다고 그 말이 조용히 원격 호스트로 흘러가면 안 되잖아. (받아쓰기는 오디오를 어디에도 안 보내. 여기서 걸린 건 전사야.) 그래서 프로바이더는 요청을 보내기 전에, 고른 모드에 맞춰 정해져. 실패했을 때 Firekeeper 가 하는 건 Pippa 경로 밖으로만, 설정에 적힌 Ollama 호스트(기본은 localhost)로, 그다음엔 Mac 혼자로 움직이는 정해진 사다리야. Full Pippa 가 실패하면 커맨드 모드랑 골라서 켠 Full 받아쓰기는 Local Mini 로 내려가. 거기서도 안 되면 받아쓰기는 결정론적 정리로 가고, 커맨드는 아무것도 안 넣고 원시 전사를 히스토리에 남겨. 품질을 바꿔 끼우는 거지 프라이버시를 넓히는 게 아니야. 그리고 그게 눈에 보여. 오버레이가 로컬 단계 이름을 띄우고, 히스토리가 어느 레인이 답했는지 적고, 브레인 상태 칩이 대체됐다고 보여 줘. 유저가 고르지 않은 호스트로 손을 뻗는 일은 절대 없어.

조용한 폴백 (나쁨):  오프라인 유저 -> localhost 다운 -> 조용히 Tailscale 침  # 누수!
보이는 사다리 (좋음):  Full Pippa 실패 -> Local Mini -> 결정론적 (받아쓰기)
                                                    또는 삽입 안 함 (커맨드)
                       단계마다 화면에 이름
                       Local Mini 고름 -> cwkPippa 도 오피스도 안 부름;
                                          네가 정한 Ollama 호스트만

발목 잡는 디테일 둘

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

명시적 의도로 라우팅하고, 내려갈 땐 목적지가 줄어드는 쪽으로만, 그리고 그렇다고 말해. 프라이버시가 걸린 일이라면 목적지는 유저가 내리고 눈으로 확인하는 결정이어야 해. 잠깐 실패한 결과로 자동으로 정해지는 게 절대 아니고. 고른 모드에 맞춰 프로바이더를 미리 정해 놔. 실패하면 전사가 가는 곳이 줄어드는 단계로만 내려가. 유저가 정한 Ollama 호스트, 그다음엔 아무 데도 안 감. 그리고 답한 단계 이름을 밝혀. 새로운 곳으로 손을 뻗는 폴백, 유저 눈에 안 보이는 폴백은 유저 대신 결정을 내려 버린 거야.
멈춘 원격이 보이스 루프를 얼리면 절대 안 돼. 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)
    }
}

// 프로바이더는 요청 전에 유저 모드에서 골라져.
// 실패하면 이름 붙은 정해진 사다리를 탐 (Local Mini; 그다음 받아쓰기는
// 결정론적, 커맨드는 삽입 안 함); 절대 조용히 자세를 안 바꿔.
// hosts.tailscaleHost 는 설정이지 하드코딩 주소 절대 아님.

External links

Exercise

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

Progress

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

댓글 0

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

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