함수 이름을 create에서 createStripeClient로 바꿨습니다. 그것뿐인데 에이전트가 뒤져야 할 파일이 459개에서 3개로 줄었죠.

Modem은 2025년 초부터 AI 코드 생성 도구만으로 제품을 만들어온 팀입니다. 지금 애플리케이션 코드 36만 줄에 테스트 코드 32만 줄을 굴리는데, 그중 99.9%를 LLM이 썼죠. 1년간의 시행착오를 시리즈로 풀기 시작했는데, 1편의 주제가 “에이전트는 실제로 코드를 어떻게 찾는가”입니다.
출처: How coding agents read your code (and how to write for them) – Modem Blog
임베딩도 벡터 DB도 아니고, 그냥 grep
에이전트에게 함수 이름을 바꿔달라고 하면 무슨 일이 벌어질까요. Claude Code, Codex, OpenCode 모두 레포를 훑어 그 이름이 등장하는 자리를 찾습니다. 훑는 도구는 grep, 정확히는 더 빠른 사촌인 ripgrep이죠.
컴파일러가 의존성 그래프를 넘겨주지도, 언어 서버가 심볼을 풀어주지도 않습니다. 전부 문자열이죠. Claude Code 팀은 초기에 임베딩과 벡터 DB를 붙였다가 걷어냈는데, 평범한 텍스트 검색이 더 나았거든요. 학계 쪽 에이전트인 SWE-agent도 같은 자리에 도착했습니다.
파일 경로도 검색어입니다. “세션 브로커가 어떻게 동작하죠?”라고 물으면 에이전트는 session-broker, sessionBroker 같은 변형으로 파일명부터 훑죠. session-broker/라는 디렉터리는 코드 한 줄 읽히기 전에 이미 명중인 셈입니다.
이름 하나가 컨텍스트 예산을 갉아먹는다
원문은 같은 일을 하는 함수를 이름만 셋으로 바꿔, 실제 모노레포(TypeScript 파일 약 2,900개)에서 검색해봤습니다.
| 검색어 | 일치한 줄 | 파일 | 소요 시간 |
|---|---|---|---|
create | 1,585 | 459 | 약 50ms |
createClient | 466 | 23 | 약 47ms |
createStripeClient | 43 | 19 | 약 47ms |
속도는 셋 다 비슷합니다. 문제는 다른 데 있죠. grep은 답이 아니라 일치한 줄을 돌려줍니다. const client = create(config)라는 한 줄만 봐서는 이게 찾던 그 클라이언트인지, create라는 이름을 쓰는 수백 개 중 하나인지 알 길이 없죠.
그래서 에이전트는 그 줄 주변을 읽습니다. 부족하면 파일을 통째로 열죠. 한 줄에 대략 10토큰이라 치면 틀린 후보 하나를 배제하는 데만 수백에서 수천 토큰이 나갑니다. 열두어 개를 걸러내면 정작 작업은 시작도 못 한 채 수만 토큰이 사라져 있는 겁니다.
손해는 두 번 납니다. 헛다리를 짚는 데 쓴 만큼 실제 작업에 쓸 몫이 줄고, 근접 오답으로 컨텍스트가 채워질수록 모델은 그 안에 든 정보마저 제대로 못 쓰게 되죠.
“import 문을 따라가면 되지 않나” 싶을 수 있죠. 그런데 import에는 구멍이 셋 있습니다.
- 역방향이 없다 — 호출 지점에서 정의로 가는 한 방향 포인터일 뿐, 정의에서 호출자를 되짚는 인덱스는 없죠.
- 배럴이 이름을 지운다 —
export * from './api'는 무슨 이름을 넘기는지 기록하지 않습니다. - 메서드는 아예 없다 —
client.getUsers()의getUsers는 어떤 import 문에도 등장하지 않죠. 파이썬, Go, 자바도 마찬가지입니다.
구별되는 이름 자체가 가장 이식성 높은 역방향 조회인 셈입니다.
타입은 에이전트가 건너뛸 수 없는 문서
주석과 README는 에이전트가 넘길 수 있습니다. 컴파일 에러는 못 넘기죠. 게다가 정확한 시그니처는 구현을 열지 않아도 답을 줍니다. function enrichUser(user: User): EnrichedUser를 grep 결과에서 만나면 그냥 지나가면 되지만, enrichUser(data: any)였다면 data의 정체를 알아내려고 구현과 호출자를 줄줄이 열게 되거든요.
타입 이름도 결국 검색어죠. 인자를 전부 string으로 받으면 순서를 뒤바꿔 넣어도 컴파일러가 잡지 못하지만, UserId와 OrgId로 나누면 빌드 에러가 납니다. 그 에러가 가리키는 OrgId는 다른 파일에 있고, 에이전트가 거기 도달하는 방법은 하나뿐이죠. 이름을 grep하는 것.
1,680번 돌려본 결과
Modem은 이 글의 내용을 write-discoverable-code라는 한 장짜리 스킬 파일로 압축했습니다. Haiku 4.5(Claude Code)와 GPT-5.6 Sol(Codex CLI)이 같은 알림 파이프라인 라이브러리를 스킬 없이 한 번, 스킬을 얹고 한 번 작성했죠. 이렇게 나온 네 버전을 실제 코드 500개 파일 사이에 묻어두고, 14가지 모델·하네스 조합이 질문 10개를 세 번씩 던졌습니다. 총 1,680회.
Haiku가 무보조로 쓴 코드에서는 토큰 소비가 중앙값 32% 줄었습니다. 이름부터 달랐거든요. signer.ts가 hmac-payload-signer.ts로, validateConfig()가 validateNotificationDeliveryConfig()로 바뀌었죠. 반면 Sol은 스킬 없이도 이미 구체적인 이름을 쓰고 있어서 절감폭이 12%에 그쳤습니다.
확신에 찬 오답도 Haiku의 제네릭 이름 버전에만 몰렸습니다. 배달 이력을 물으면 레포 다른 곳의 Slack 전송 원장을, HMAC 서명 위치를 물으면 미디어 프록시의 URL 서명기를 가리켰죠. 스킬 적용본에서는 그런 오답이 한 건도 없었는데, hmac-payload-signer.ts라는 이름을 근처의 다른 코드가 흉내낼 수는 없으니까요.
두 번째 실험 대상은 실제 오픈소스였죠. Odysseus라는 자체 호스팅 AI 워크스페이스는 이메일 라우트 파일 하나가 4,943줄인데, 그중 약 3,900줄이 함수 하나입니다. 81개 라우트 핸들러가 그 안에 중첩 클로저로 들어앉아 있죠. 이 파일만 개념별 모듈로 쪼개자 Haiku는 34.3%, Grok 4.5는 31.5% 저렴해졌습니다.
같은 코드에 동일한 버그 4개를 심고 8턴 예산 안에 찾게 한 시험이 더 선명하죠. 모노리스에서 Haiku는 8번 중 4번, Sonnet 5는 3번만 성공했습니다. 나머지는 틀린 답을 낸 게 아니라, 파일을 넘기다 예산을 다 쓰고 결론에 도달조차 못한 경우였죠. 재편성한 코드에서는 둘 다 8번을 모두 잡아냈습니다.
약한 모델일수록 이득이 크다는 뜻
표를 관통하는 규칙이 하나 있습니다. 무보조 상태에서 코드를 못 찾던 모델일수록 절감폭이 컸다는 것. 뒤집어 보면 코드의 읽기 쉬움이 “어떤 모델을 써야 하는가”의 비용을 정한다는 얘기가 됩니다. 같은 작업을 Haiku로 끝낼 수 있는 코드베이스와 Opus를 불러야 하는 코드베이스는 다르니까요.
Sol이 자기가 쓴 코드를 읽을 때조차 스킬 적용본이 13~38% 저렴했다는 점도 남습니다. 코드를 쓴 모델이라고 어디에 뭐가 있는지 기억하지는 않죠. 다른 에이전트와 똑같은 검색을 처음부터 돌릴 뿐입니다.
다만 원문 저자도 인정하듯 이 실험은 깨끗하지 않습니다. 스킬 파일이 이름과 파일 분할, 타입, 구조를 한꺼번에 바꾸는 탓에 이름만의 효과를 떼어낼 수는 없죠. 결정적인 벤치마크라기보다, 조건을 비슷하게 뒀을 때 방향이 어느 쪽인지를 본 실험으로 읽는 게 맞습니다.
해보려면
환경: 에이전트 CLI(Claude Code, Codex, OpenCode 등)와 스킬 파일을 컨텍스트에 로드할 수 있는 설정. 원문 실험은 TypeScript 모노레포와 파이썬 레포에서 각각 측정했습니다.
출발점: Modem이 공개한 write-discoverable-code 스킬을 에이전트 컨텍스트에 얹고 코드를 생성시킵니다.
확인: 생성된 파일명과 심볼명을 봅니다. signer.ts, validateConfig()처럼 나오면 적용이 안 된 것이고, hmac-payload-signer.ts, validateNotificationDeliveryConfig() 쪽이면 걸린 겁니다.
걸리는 지점: 이미 구체적인 이름을 쓰는 모델에는 효과가 작습니다. 원문 저자도 Sol의 스킬 적용본을 두고 과잉 명세 같다고 적었죠. 리팩터링도 마찬가지여서, 한 파일만 손보면 그 옆의 손대지 않은 파일이 새로운 바닥이 됩니다. 실제로 Odysseus 실험에서 Haiku는 손대지 않은 1,800줄짜리 헬퍼 파일에서 다시 길을 잃었습니다.
참고자료:

답글 남기기