"똑같은 부분은 공유하고; 소유가 다른 딱 그 데서 갈라져. 한 줄 일찍도, 한 줄 늦게도 아니라."
공유되는 중간
Pippa Go 엔 이미지를 나르는 두 흐름이 있어: Prompt Macro(대화 턴 없이 composer 를 돌리는 유틸리티)랑 Ask(canonical 스트리밍 챗). 둘 다 정확히 같은 문제로 시작해 — 사용자가 이미지를 골랐고, 그게 이제 기기 로컬 staging 에 blob 이랑 메타데이터로 앉아 있어. 그 staged blob 을 진짜, 쓸 수 있는 바이트 + 올바른 media type 으로 바꾸는 건 어느 흐름이든 동일한 작업이야. 그래서 단일 공유 staged-media resolver 에서 한 번 해. 그 해소를 두 데서 복제하면 고전적 drift 버그야: 인코딩, 방향, media-type 감지에 대해 천천히 어긋나는 두 복사본.
의도된 분기
그다음, 바이트를 해소했으니, 두 흐름이 갈라져 — 그리고 그 분기는 우연이 아니라 원칙적이야. 소유 경계에서 갈라져. 이미지한테 뭐가 일어나는지가 결과를 누가 소유하냐에 달렸으니까:
- Prompt Macro → utility 봉투. Macro 는 대화가 아니라 apply-free 유틸리티야. 그 이미지는 canonical 상태를 안 만드는 one-shot 호출에 맞는 가벼운 utility 봉투(인라인 인코딩 바이트)로 다녀.
- Ask → canonical 봉투. Ask 는 진짜 대화 턴을 만드니까, 그 이미지는 canonical 첨부로 업로드되고 chat 메시지가 참조해 — canonical 턴의 다른 모든 것처럼 백엔드가 소유해.
같은 해소된 바이트, 두 봉투. Macro 의 결과는 일회용이고 Ask 의 결과는 canonical 이라서. 분기는 사마귀가 아냐; 코드를 통해 비치는 소유 모델이야.
규칙: 경계까지 공유, 경계에서 분기
이건 재사용 가능한 설계 본능이야. 두 흐름이 앞 작업을 공유하고 나중에 다르면, 공유 부분을 한 컴포넌트로 빼고 정확히 요구사항이 실제로 다른 지점 — 여기선 결과 이미지의 소유 — 에서 갈라지게 해. 너무 조금 공유하면 resolver 를 복제해(drift). 너무 많이 공유하면 Macro 랑 Ask 를 둘 다 안 맞는 하나의 봉투로 밀어넣어(leaky abstraction). staged-media resolver 는 선을 맞는 데 그어: '진짜 바이트 + media type 을 가졌다'까지는 공통; 그 바이트가 어디 사는지는 흐름별.