Taste Log 야생#1: 스킬이 백 개여도 컨텍스트는 1%만 쓴다
스킬이 백 개여도 컨텍스트는 1%만 쓴다
다 보여주는 게 목표가 아니다. 다 닿게 하는 게 목표다.
⸻
야생편을 시작하며
추상편은 트레이드오프 앞에서 “나는 이쪽이다”를 선언하는 글이었다. 야생편은 반대다. 매일 쓰는 도구가 실제로 내놓은 판단을 뜯어보고 그 뒤에 깔린 taste를 거꾸로 캔다. 누가 선언한 게 아니라 코드와 설정값에서 읽어내는 거라 근거가 있다.
첫 표본은 지금 이 글을 쓰면서 켜둔 Claude Code다. 정확히는 2.1.179 바이너리. 질문은 하나다. 스킬이 늘어날 때 그걸 다 어떻게 알려주나.
⸻
내가 처음 떠올린 답
Claude Code에는 스킬이 있다. 특정 작업에 필요한 지식 묶음이고, 내 환경에만 수십 개가 깔려 있다. 에이전트는 매 턴 어떤 스킬이 있는지 알아야 한다. 모르면 못 쓰니까.
그럼 어떻게 알려줄까? 답은 바로 떠오른다. 이름이랑 설명을 다 나열하면 된다. 스킬마다 한 줄씩 시스템 프롬프트에 붙여서 매 턴 보낸다. 명료하고 구현도 금방이다. 나라면 이렇게 짰을 거고 아마 당신도 그럴 거다.
⸻
근데 문제가 그게 아니었다
다 나열하는 순간 비용이 붙는다. 그 목록은 매 턴 컨텍스트에 실린다. 스킬 50개에 설명이 두 줄씩이면 100줄, 100개면 200줄이 대화 내내 토큰마다 따라온다.
컨텍스트는 유한하다. 게다가 스킬 목록은 일을 하는 게 아니라 일을 소개하는 군더더기다. 소개가 본문을 잡아먹으면 배가 산으로 간다.
진짜 문제는 “어떻게 다 보여주나”가 아니었다. 최소한의 신호로 모든 스킬에 닿을 수 있게 유지하는 거다. 에이전트가 스킬을 쓰려면 그게 있다는 것만 알면 된다. 풀 설명을 매 턴 들고 다닐 필요가 없다. “다 보여주기”는 욕심이고 “다 닿게 하기”가 진짜 지켜야 할 선이다. 이 둘을 가른 게 첫 taste다.
⸻
바이너리가 흘린 한 줄
추측만으로는 해부가 안 된다. Claude Code는 컴파일된 바이너리(Mach-O)라 소스는 안 보이지만, strings로 긁으면 설정 설명이 평문으로 남는다. 2.1.179를 긁다 이 줄을 만났다(strings 캐시 약 116,598번째 줄).
“Fraction of the context window (in characters) reserved for the skill listing sent to Claude (default: 0.01 = 1%). When the listing exceeds this, descriptions are shortened to fit. Raise to opt in to higher per-turn context cost.”
한 문장에 결정 세 개가 들어 있다. 스킬 목록에 컨텍스트의 1%라는 하드 예산을 박았고(default 0.01, 추측이 아니라 숫자다), 예산을 넘으면 스킬을 빼는 게 아니라 설명을 짧게 깎아 욱여넣고, 예산은 올릴 수 있는 손잡이로 빼뒀다. 기본값은 인색하게 잡고 비싸게 쓰는 건 직접 켜라는 거다.
⸻
스킬이 아니라 설명을 깎는다
셋 중 taste가 제일 진한 건 두 번째다.
목록이 1%를 넘쳤다고 하자. 제일 쉬운 처리는 스킬을 떨구는 거다. 공간 없으니 뒤쪽 몇 개는 이번 턴에 안 보여준다. 자르면 끝이라 구현도 간단하다. 그런데 그 순간 도달가능성이 깨진다. 떨궈진 스킬은 에이전트 눈에 존재하지 않는다. 하필 그 작업에 필요한 스킬이 잘렸다면 에이전트는 있는 줄도 모르고 맨손으로 헤맨다.
Claude Code는 반대로 갔다. 스킬은 하나도 안 뺀다. 대신 설명을 깎는다. 50개가 100개가 되면 설명은 한 줄에서 반 줄로, 다시 이름과 토막 힌트로 쪼그라든다. 그래도 사라지는 건 없다.
희생할 축을 분명히 골랐다. 양보한 건 충실도다. 설명이 짧아지면 매칭이 둔해질 뿐 치명적이진 않다. 절대 안 건드린 건 도달가능성이다. 스킬은 언제나 다 목록에 있다.
여기서 Linus의 good taste가 겹쳐 보인다. 그가 TED에서 보여준 linked list 삭제 코드는 특수 케이스(head인지 분기하는 if)를 없애 모든 경우를 같은 길로 흐르게 만들었다. Claude Code도 같은 모양이다. “어떤 스킬은 이번 턴에 안 보임”이라는 특수 케이스를 아예 안 만든다. 모든 스킬은 늘 보이고 해상도만 다르다. 분기가 사라진다.
retrieval을 안 만든 것도 같은 결이다. 임베딩 깔고 관련 스킬만 top-k로 골라주는 RAG는 멋있고 정석처럼 보인다. 그런데 스킬이 수십 개일 때는 과설계다. 인덱스 관리, 임베딩 갱신, 검색 지연, 틀린 top-k가 진짜 스킬을 가리는 위험을 다 떠안고 얻는 게, “예산 넘으면 자르기” 30줄이 이미 주는 결과와 별 차이가 없다. 아직 필요 없는 걸 안 지었다.
⸻
이름 붙이기
이 표본에서 캔 taste에 이름을 붙이면 셋이다.
하나는 progressive disclosure. 존재는 이름으로 싸게 알리고 무게는 쓸 때 로드한다. 스킬 목록은 메뉴판이지 요리가 아니다. 주문이 들어오면 그때 주방을 돌린다.
다음은 graceful degradation. 예산을 넘으면 기능을 끄는 게 아니라 품질을 낮춘다. 100개를 넘겨도 시스템은 안 죽고 설명만 둔해진다. 무너질 때 절벽에서 떨어지는 대신 비탈을 미끄러진다.
마지막은 YAGNI. 지금 규모에 필요 없는 retrieval을 안 지었다. 천장은 알지만 거기 닿기 전까진 단순함을 산다.
재밌는 건 같은 취향이 한 겹 더 반복된다는 점이다. MCP 도구에서 Claude Code는 이름만 목록에 띄우고 실제 schema는 안 보낸다. 대신 ToolSearch라는 도구로 필요할 때 schema를 불러온다. 지금 내 세션 시스템 프롬프트에도 이 문장이 있다.
“The following deferred tools are now available via ToolSearch. Their schemas are NOT loaded — calling them directly will fail… Use ToolSearch … before calling them.”
이름은 알려주되 무게는 미룬다. 스킬에서 설명을 깎아 도달가능성을 지킨 판단과, 도구에서 schema를 미뤄 이름만 띄운 판단은 같은 손에서 나온 같은 취향이다. 한 군데서 보이면 우연일 수 있어도 두 군데서 같은 모양이면 원칙이다.
⸻
이 베팅은 어디서 터지나
여기서 멈추면 찬양글이다. 해부의 핵심은 이 판단이 언제 틀리는지 찾는 데 있다.
1% 베팅에는 천장이 있고, 그 천장은 설명을 깎는 방식 자체에 내장돼 있다. 스킬이 진짜 수백 개가 됐다고 하자. 도달가능성은 여전히 지켜진다. 다 목록에 있으니까. 그런데 1% 고정 예산을 수백 개가 나눠 가지면 설명이 무의미하게 다져진다. 이름과 단어 몇 개만 남는다. 이름이 비슷한 스킬이 여럿이면(review-pr, code-review, pr-test-analyzer) 에이전트는 토막 신호로 옳은 걸 못 고른다. 목록엔 있는데 매칭이 붕괴한다. 도달가능성이 명목상 살아도 실질적으론 죽는다.
바로 거기가 retrieval이 정당해지는 지점이다. 신호가 압축에 다 갈려 나가면 그제야 임베딩으로 “이 작업엔 이 다섯 개”를 골라줄 값이 비용을 넘는다. 아까 안 지은 게 여기선 안 지으면 안 되는 게 된다.
그러니까 “1% + 깎기”는 보편 정답이 아니다. 스킬 수가 적당할 때만 성립하는 베팅이다. Claude Code는 지금 그 구간에 있어서 단순한 게 이긴다. 영구 설계가 아니라 현재 규모에 건 도박이고 설계자도 안다. 그래서 예산을 손잡이로 빼놨다.
그런데 한 번 더 의심해야 한다. 그 손잡이는 진짜 선견지명인가, 아니면 어려운 결정을 사용자에게 떠넘긴 건가.
너그럽게 읽으면 단순함을 기본값으로 두되 탈출구를 연 설계다. 인색하게 읽으면 정반대다. 언제 retrieval로 갈아탈지라는 진짜 어려운 판단을 시스템이 안 내리고 슬라이더 하나로 포장해 사용자에게 넘긴 거다. 매칭이 붕괴하는 순간을 시스템은 감지하지 않는다. 설명이 토막 나도 경고가 없다. 사용자가 어느 날 “왜 스킬을 못 찾지” 하고 직접 알아채서 손잡이를 올리거나 retrieval을 붙여야 한다. graceful degradation은 조용해서 좋은 동시에 조용해서 위험하다. 품질이 비탈을 미끄러지는데 아무도 안 알려준다.
그럼 미룬 부채인가. 지금은 아니라고 본다. 천장이 충분히 멀고, 1%를 넘겨 설명이 의미를 잃는 임계가 현실 사용자에겐 아직 안 닥쳤다. YAGNI는 “절대 필요 없다”가 아니라 “아직 필요 없다”이고, 손잡이는 그 “아직”을 사는 값을 솔직하게 가격표로 붙여놨다(“Raise to opt in to higher per-turn context cost”). 다만 규모에 묶인, 유통기한 있는 taste다. 스킬 생태계가 커지면 조용한 열화는 자랑이 아니라 결함이 되고, 그때 손잡이는 선견지명에서 책임 회피로 의미가 뒤집힌다.
그래서 진짜 taste는 “1%로 간다”도 “손잡이를 빼놨다”도 아니다. 지금 규모에선 1%가 맞지만 어느 규모부턴 틀리고, 그 틀려지는 지점이 조용히 온다는 것까지 아는 거다. 정답이 바뀌는 지점은 설계에 새겼지만 그게 다가오는 걸 시스템이 스스로 감지하게는 안 만들었다. 거기까지가 이 표본의 taste이자 한계다.
⸻
제약이 설계를 만든다
이 표본은 추상편 #7(제약이 좋은 설계를 만든다)의 야생 증거다. “컨텍스트 1%“는 스스로 채운 족쇄다. 무한히 써도 되는데 0.01로 묶었다. 그 제약이 없었으면 “다 나열하면 되지”에 머물렀을 거고, 도달가능성과 충실도를 나눠 생각할 이유조차 없었을 거다. 예산이라는 가드레일이 “뭘 양보하고 뭘 지킬까”를 강제했고, 그 질문이 progressive disclosure를 끌어냈다. 자유도가 높았으면 안 나왔을 설계다.
⸻
참고한 것들
실물 표본
- Claude Code 2.1.179 바이너리(Mach-O) 설정 문자열, strings 캐시 약 116,598번째 줄: “Fraction of the context window (in characters) reserved for the skill listing… default: 0.01 = 1%… descriptions are shortened to fit. Raise to opt in to higher per-turn context cost.”
- 같은 세션 시스템 프롬프트의 MCP deferred-tool 안내(ToolSearch로 schema 지연 로드)
taste의 렌즈
- Linus Torvalds, “The mind behind Linux”, TED 2016. 특수 케이스를 없애 normal case로 만드는 눈.
- Paul Graham, “Taste for Makers”, 2002. 좋은 디자인은 단순하다.
교차링크
- 추상편 #7: 제약이 좋은 설계를 만든다
- 야생#2(예정): MCP 도구 deferred 로드