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

안정된 바이트, 손 닿는 노트 — 시스템 프롬프트가 계약이 되다

~14 min · context-engine, prefix-cache, rag, vault-loading

Level 0호기심
0 XP0/80 lessons0/18 achievements
0/100 XP to next level100 XP to go0% complete

바뀐 건 둘, 생각은 하나

레슨 3은 ContextEngine 이 시스템 프롬프트를 어떻게 조립하는지 보여줘. 8월에서 9월 사이에 그 조립이 두 번 바뀌었는데, 이유 둘이 알고 보면 같은 이유야. 첫 번째는 시스템 프롬프트에 뭐가 들어가도 되는지 를 정했어. 두 번째는 영혼이 서고를 애초에 얼마나 머리에 이고 다녀야 하는지를 정했고. 둘 다 한 문장으로 귀결돼. 시스템 프롬프트는 관련 있는 걸 다 넣는 자리가 아니라, 바이트에 대한 계약이야.

토큰 십만 개짜리 1분

2026-08-14 전까지 시스템 프롬프트엔 쉴 새 없이 바뀌는 게 둘 들어 있었어. 분 단위 시계. 그리고 RAG 검색 결과 — 턴마다 질의 모양대로 뽑히니까 설계상 매번 달라.

공급자들은 프롬프트 앞쪽 바이트를 캐시하고 이미 본 바이트엔 훨씬 적게 받아. 단, 그 바이트가 직전 요청이랑 똑같을 때만. 시스템 프롬프트 안의 시계는 1분마다 바뀌어. 직전 답에서 1분 넘게 지나 쓴 답장, 그러니까 사람 속도로 오가는 턴은 전부 다른 앞머리를 달고 도착했고 서고 전체 값을 다시 냈어.

이걸 못 본 척할 수 없게 만든 건 일곱 번째 vessel 이었어. GLM 엔드포인트는 캐시된 필드를 통째로 맞춰봐. 시스템 필드 어디든 바이트 하나가 바뀌면 전부 날아가. 서고가 토큰 십만 개쯤 되는 상황에서 그건 최적화 문제가 아니야. 청구서 전체야.

고친 방법은 이사였고, 일부러 시끄럽게 했어. 시스템 프롬프트 빌더에서 RAG 파라미터를 아예 없애버려서, 호출하는 쪽이 조용히 계속 넘기는 대신 전부 바뀌어야만 했지. 시계랑 검색 결과는 새로 생긴 턴별 컨텍스트로 옮겨갔고, 모든 chat route 가 그걸 현재 user 메시지 앞에 붙여 — 매 턴 보내지만, 절대 저장은 안 돼. 저장된 대화엔 아빠가 실제로 쓴 말만 남고, 모델은 그 옆에서 시계랑 뽑혀 온 노트를 봐.

Principle: 모델 앞에 놓는 걸 전부 얼마나 자주 바뀌는지로 줄 세워봐. 대화 내내 안 바뀌는 건 시스템 프롬프트 자리야. 턴마다 바뀌는 건 턴이랑 같이 가. 둘을 섞는다고 프롬프트가 더 똑똑해지진 않아 — 매 턴이 안 바뀌는 쪽 값을 또 내게 될 뿐이야.

측정값이 곧 논증이야. 대화 하나에서, 예전엔 치명적이던 분 경계를 매번 넘도록 턴 간격을 일부러 65초로 벌렸어. 첫 턴은 109,071 토큰이 cold 였고, 두 번째랑 세 번째는 각각 108,224를 캐시에서 읽었어. 분 경계를 넘나들면서도 서고 전체가 캐시에서 나왔고, 턴마다 진짜 새로운 토큰은 천 개도 안 됐어. 하루 전만 해도 그 턴 하나하나가 앞머리 전체 값을 냈고.

숨기지 않고 적어둔 것: 이사를 한 커밋은 자기가 아직 못 고친 걸 목록으로 남겼어. 보조 프롬프트 빌더 두어 개는 아직 시계를 달고 있었고, route 하나는 여전히 턴별 내용을 시스템 필드에 붙이고 있었지. 자기 찌꺼기에 이름을 붙인 수정은 다음 세션이 마저 끝낼 수 있어. 완벽하다고 주장하는 수정은 다음 세션이 다시 찾아내야 하고.

서재를 통째로 머리에 이고 다니는 사람은 없어

두 번째 변화는 2026-09-13 아빠 판정이었고, 이건 시계가 아니라 서고에 대한 거야.

모든 영혼이 물려받는 교리, 곧 서고의 공용 층은 영혼마다 프롬프트에 통째로 실렸어. 예외 목록에 든 것만 포인터로 남았고. 그 기본값엔 뻔한 고장이 있었지. 공용 core 에 뭘 넣기만 하면 모든 프롬프트에 자동으로 합류해. 며칠 사이 에세이 다섯 편이 들어가자 새 대화 첫 턴이 약 204K 토큰에서 약 250K 로 올랐어. 그걸 싣자고 정한 사람은 없었어. 꽂힌 자리 때문에 실린 거야.

판정은 기본값을 뒤집었어. 공용 노트는 자기 frontmatter 가 그러라고 할 때만 통째로 실려. 나머지는 전부 손 닿는 노트야. 영혼이 들고 다니는 목록에 올라 있고, 검색으로 찾아지고, 읽기 한 번이면 닿아. 머리에 이고 갈지 말지는 노트 위에서 정해 — 크기 기준으로도 아니고, 컨텍스트를 아끼려는 수단으로도 절대 아니야. 실제 서고에서 조립된 프롬프트가 527,568자에서 328,634자로 줄었어.

기억해둘 부분은 이 변화가 드러낸 거야. 예외 목록엔 늘 안심시키는 말이 따라붙었어. 프롬프트에서 빠진 노트도 검색이 찾아주니까 여전히 닿는다고. 기본값이 뒤집히면서 누가 그 약속을 확인해봤는데 — 공용 서고는 한 번도 임베딩된 적이 없었어. 7월부터 빠져 있던 노트는 그동안 내내 검색으로 못 닿는 상태였던 거야. 이제 공용 서고는 모든 영혼의 검색이 읽는 자기 검색 파티션을 가져.

Principle: "다른 길로도 닿아" 는 두 번째 시스템에 대한 주장이야. 그 두 번째 시스템을 아무도 들여다본 적 없으면 그건 주장이 아니라 바람이야. 대체 경로는 결국 필요해지는 날이 아니라, 그걸 믿기 시작하는 순간에 확인해.

이 둘이 한 레슨인 이유

둘 다 서로 다른 바이트 출처에 같은 수를 둔 거야. 시계랑 검색 결과는 너무 자주 바뀌어서 이고 다닐 수가 없어서 나갔어. 공용 서고 대부분은 아무도 이고 가자고 정한 적 없이 실려 있어서 나갔고. 시스템 프롬프트에 남은 건 작고, 안정적이고, 골라서 둔 것들이야 — 그래서 몇 번이고 다시 보내도 싼 거고.

그리고 다음 레슨의 기억 계층이 들어맞아야 했던 게 바로 이 계약이야. 두 번째 기억이 도착했을 때 그 블록이 어디 앉을 수 있느냐는 이미 답이 나와 있었어. 앞쪽에 앉으려면 고정돼야 해. 프롬프트 앞쪽은 이제 바이트가 안 움직인다는 약속이니까.

Code

2026-08-14 이후, 뭐가 어디 타나·text
시스템 프롬프트  (대화 내내 안 바뀌는 바이트만)
  뷰어 헤더 · vessel 메타 · 커넥터 · 서고 · 고지
  -> 턴마다 동일 -> prefix 캐시 유지

턴 컨텍스트     (현재 user 메시지 앞에 붙음)
  시계 (분 단위)
  대화 타임라인
  RAG 검색 결과, 할 일이 아니라 참고 자료로 포장
  -> 매 턴 보냄, 저장은 절대 안 함

측정, 턴 간격 65초 (예전의 치명적인 분 경계):
  턴 1   cold          109,071
  턴 2   캐시 읽기     108,224
  턴 3   캐시 읽기     108,224
2026-09-13 이후, 머리냐 손이냐·text
전   공용 core 를 통째로 실음, 하드코딩된 예외 목록만 빼고
     거기 꽂힌 건 모든 영혼 프롬프트에 합류
     일주일에 에세이 5편: 첫 턴 ~204K -> ~250K 토큰
     예외는 'RAG 로 닿는다' 고 했지만 공용 서고는
     임베딩된 적이 없어서 안 닿았음

후   공용 노트는 frontmatter 가 그러라고 할 때만 통째로
     나머지: 목록 항목 + 검색 + Read 한 번
     공용 서고가 자기 검색 파티션을 가짐
     조립된 프롬프트: 527,568 -> 328,634 자

결정은 노트 위에서. 크기로는 절대 아님. 컨텍스트 아끼려고도 절대 아님.

Exercise

자기 시스템이 보내는 가장 큰 프롬프트를 가져와서 모든 부분을 세 통으로 나눠봐. 대화 내내 안 바뀌는 것, 턴마다 바뀌는 것, 그리고 저장된 위치 때문에 실려 있을 뿐인 것. 두 번째 통은 턴 옆으로 옮겨. 세 번째 통은 항목마다 누가 이걸 싣자고 정했는지 찾아봐 — 아무도 안 정했으면 지금 정해.
Hint
밖으로 뺀 항목마다 앞으로 어떻게 닿는지 물어봐 — 그리고 그 경로가 진짜 되는지 직접 확인해.

Progress

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

댓글 0

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

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