사람도 에이전트도, 덜 읽을수록 더 잘 고칩니다

기술 블로그

여섯 개의 파일

한 진단 작업의 기록을 펼쳐 봤습니다. 에이전트에게 정산 원장에 의심스러운 대목이 있으니 확인해 보라고 시킨 작업입니다. 재봉인을 하면 옛 원장 라인이 남아 새 값과 이중으로 잡히는 것 아니냐는 의혹이었죠.

에이전트가 그 작업에서 Read로 연 코틀린 파일은 봉인 로직 둘, 원장 모델 둘, 저장소 어댑터 둘, 모두 여섯 개였습니다. 파일 경로를 보니 인프라도 모델도 저장소도 계층은 달랐지만, 여섯 개 앞머리에 붙은 도메인 이름은 하나같이 정산이었습니다. 물리 모듈은 넷으로 나뉘어 있어도 도메인 이름은 하나였던 셈이죠.

Read로 직접 연 파일만 놓고 보면, 여섯 개가 전부 한 도메인의 모듈 안에 있었습니다. 원장이라는 한 가지를 좇는 동안 내내 그랬습니다. 모듈 경계는 오랫동안 컴파일러가 도메인 사이 참조를 함부로 얽히지 못하게 막으려고 그어 둔 선이었습니다. 그런데 에이전트가 코드를 읽기 시작하면서, 그 선이 뜻밖의 자리에서 한 번 더 눈에 띄었습니다. 도메인과 계층이 파일 경로에 그대로 적혀 있어, 에이전트가 연 파일이 지금 어느 도메인의 무엇인지를 경로만으로 읽을 수 있었죠. 이 글에서 좌표라 부르는 건 그 겹침입니다. 그리고 이 좌표는 읽을 범위를 좁히려는 설계와 맞닿아 있습니다. 덜 읽어도 되면 고칠 곳에 더 집중할 수 있다는 이야기로 이어지죠. 우선 여섯 파일이 한 도메인에 모인 게 이 작업만의 우연인지, 아니면 되풀이되는 모양인지부터 확인해 봤습니다.


로그 전체로 넓혀 보면

이 정산 저장소는 도메인별로 모델·서비스·저장소 어댑터 같은 역할을 물리 모듈로 나눕니다. 모듈 이름 끝의 접미사가 곧 계층이죠. 여기서 에이전트 작업 중에 남은 세션 로그를 모아, 파일을 연 기록만 추렸습니다.

한 가지 함정을 먼저 피해야 했습니다. 그냥 "가장 많이 읽은 도메인"을 세면 수치가 부풀 수 있습니다. 이 저장소는 정산 도메인 하나가 전체 코드의 1/4을 훌쩍 넘는, 유독 큰 도메인이거든요. 파일을 무작위로 골라도 정산 파일이 잡힐 확률이 높습니다. 그래서 기준을 뒤집었습니다. 작업이 시작될 때 요청에 적힌 목표 도메인을 먼저 정하고, 읽기가 그리로 모였는지를 봤습니다. 그러고는 실제 읽은 비율을, 목표 도메인이 저장소 전체에서 차지하는 파일 비중과 견줬습니다. 무작위로 골랐다면 그 비중만큼만 나올 테니까요.

읽는 범위를 셀 때는 조건을 좁혔습니다. 에이전트가 Read 도구로 직접 연 src/main의 코틀린 파일만, 그것도 다섯 개 이상 연 로그만 남겼습니다. Read로 연 파일의 내용이 컨텍스트로 들어오기 때문입니다. Grep이나 셸 검색으로 스친 것은 세지 않았습니다. 그 결과 목표 도메인을 하나로 특정할 수 있는 작업 로그가 백아흔세 건 남았습니다.

기준선은 목표 도메인이 저장소 전체 코틀린 파일에서 차지하는 비중으로 잡았습니다. 도메인마다 다르고, 무작위로 골랐다면 딱 그만큼만 나올 값이죠. 각 작업에서 실제 읽기가 기준선을 얼마나 넘었는지 쟀습니다.

지표
목표 도메인에 모인 읽기 (중앙값) 33%
작업별 (읽기 – 기준선)의 중앙값 +14%p
기준선보다 더 모인 로그 129 / 193

작업마다 실제 읽기에서 각자의 기준선을 뺀 값의 중앙값은 십사 퍼센트포인트였습니다. 백아흔세 건 중 셋에 둘꼴로, 무작위로 골랐을 때보다 목표 쪽에 더 모여 있었습니다.

백아흔세 건을 읽기 비율 구간별로 나누면 이렇습니다.

분포는 가운데가 봉긋한 종 모양이 아닙니다. 목표에 거의 안 모인 작업이 양 끝 구간에 몰려 있습니다. 작업 성격에 따라 갈리는 것이죠.

백아흔세 건 가운데 백스물아홉 건이, 도메인 크기만으로 예상되는 자리보다 목표 쪽으로 더 모였습니다. 기준선을 넘은 쪽이 못 미친 쪽의 두 배가 넘습니다. 에이전트는 목표 도메인 쪽으로, 그 도메인의 크기만으로 설명되는 것보다 더 치우쳐 읽습니다.

표본은 한 저장소에서 나왔고, 로그끼리 서로 독립도 아니며(한 작업에서 하위 에이전트 로그가 여러 개 생기기도 했습니다), 규칙으로 추출한 목표 도메인에는 오분류 가능성도 있습니다. 그런 조건에서도 읽기는 목표 도메인 쪽으로 더 자주 기울었습니다.


결합도가 높은 저장소에도 비슷한 조사를 시켜 봤습니다

한 저장소 안만 보면 알 수 없는 게 있습니다. 도메인이 한 덩어리로 뭉친 저장소였다면, 비슷한 조사를 시켰을 때 읽는 범위가 더 넓어질까요. 그래서 비교 사례를 하나 마련했습니다. 도메인 서른 개가 한 모듈에 오만 육천 줄로 뭉쳐 있는 다른 저장소에서, 성격이 비슷한 결함 조사를 에이전트에게 시켰습니다. 앞서 본 조사는 정산 원장의 일관성을, 새로 맡긴 조사는 사용자 도메인의 일관성을 진단했습니다. 둘 다 고치지 않고 읽기만 합니다.

두 저장소는 도메인 코드를 나누는 방식이 다릅니다.

각 조사에서 Read로 연 코틀린 파일의 전체 소스를 같은 cl100k_base 토크나이저로 계산해 비교했습니다. 에이전트가 파일 일부만 읽은 경우도 있으니, 이 값은 실제로 컨텍스트에 올라간 양이 아니라 연 파일들이 담고 있는 소스의 크기로 봐야 합니다.

저장소 연 파일 소스 토큰(원문 기준)
잘 갈린 쪽(정산 원장 조사) 6 980 13,891
뭉친 쪽(사용자 도메인 조사) 15 4,235 34,695

두 조사에서 뭉친 쪽은 연 파일 수도, 그 파일들의 소스 토큰도 두 배 반쯤 컸습니다. 읽은 파일을 시간순으로 놓아 보면 차이가 더 선명합니다.

잘 갈린 쪽은 정산 도메인 안에서 여섯 파일로 끝났습니다. 계층만 인프라에서 모델로, 저장소로 옮겨다녔을 뿐입니다. 뭉친 쪽은 사용자 도메인 파일 열 개를 읽은 뒤 직무 체계 패키지까지 살펴야 했습니다. 사용자 코드가 직무 체계를 끌어다 쓰고 있었거든요.

번진 이유가 중요합니다. 에이전트는 눈앞의 참조를 따라 읽습니다. 사용자 코드가 직무 체계를 부르니, 그 참조를 좇아 직무 체계 파일까지 열게 됩니다. 여기서 두 구조의 차이가 갈립니다. 도메인이 다른 모듈로 갈려 있으면, 사용자 모듈이 직무 체계를 부르려면 빌드 파일에 의존을 먼저 선언해야 합니다. 선언하지 않은 참조는 컴파일 단계에서 막히죠. 모듈이 도메인 사이 참조를 자동으로 줄여 주는 건 아닙니다. 선언만 해 두면 두 도메인은 얼마든 촘촘히 얽힐 수 있습니다. 다만 그렇게 얽으려면 빌드 파일에 한 줄을 적어 두는, 눈에 보이는 결정을 거쳐야 합니다. 한 모듈 안에서는 그 문턱이 없어 도메인 사이 참조가 제약 없이 이어졌고, 사용자 코드는 직무 체계 패키지까지 곧장 닿아 있었습니다.

두 조사에서 읽기 범위가 이만큼 벌어졌고, 결합도가 높은 쪽에서는 사용자 참조가 직무 체계까지 번져 목표 밖 도메인이 컨텍스트에 함께 올라와서 토큰 사용량이 높아졌습니다.

그렇다고 잘 갈린 저장소의 읽기가 늘 한 도메인 안에서 끝난다는 뜻은 아닙니다. 작업이 여러 도메인의 연계를 확인해야 하면, 읽는 범위도 그만큼 넓어집니다.


한 도메인을 벗어난 읽기도 있었습니다

실제로 잘 갈린 저장소에서도 모든 작업이 한 도메인에서 끝난 건 아닙니다. 같은 저장소의 코드 작업 로그를 다시 훑으니, 네 도메인 이상을 읽은 작업이 여럿 있었습니다. 상태 전이 규약이 여러 도메인에 어떻게 퍼져 있는지 정리한 작업은 일곱 도메인을 읽었죠. 애초에 여러 도메인의 연계를 확인해야 하는 작업들이었습니다. 여러 도메인을 읽는 것 자체는 문제가 아닙니다. 작업이 넓은 범위를 요구하면 읽기도 넓어지고, 그때도 모듈 이름은 지금 어느 도메인을 보고 있는지 알려 줍니다.

테스트를 함께 읽을 때도 마찬가지였습니다. 이 저장소에서는 테스트가 대개 자기 도메인 모듈 안에 함께 있어, 테스트를 연 작업 로그 가운데 열에 아홉은 테스트 때문에 읽는 도메인이 더 늘지 않았습니다. 다만 작업이 여러 도메인의 연계를 다룰 때는 테스트도 여러 도메인에 흩어졌습니다. 그때도 테스트 파일은 저마다 자기 도메인의 이름을 달고 있었죠.

여러 도메인에 걸친 한 작업에서, 파일을 읽은 순서를 보겠습니다. 근태 계산이 어디로 전파되는지 추적한 조사입니다.

파일 열 개를 네 도메인에 걸쳐 읽었습니다. 근태의 모델과 서비스에서 시작해, 입력 변환 어댑터로, 정산으로, 근무 가능일로 옮겨 갑니다. 그런데 파일을 열 때마다 지금 어느 도메인의 무슨 계층인지가 파일 경로에 그대로 드러납니다. 함께 읽은 테스트 세 건도 각자 자기 도메인의 이름을 달고 있었죠. 여러 도메인을 살핀 조사인데도, 어디를 읽는 중인지는 파일을 열 때마다 분명했습니다.

읽은 도메인의 범위와 작업이 확인하려는 연계의 범위가 나란히 움직인 셈입니다. 모듈로 나누는 건 모든 읽기를 한 도메인에 가두려는 게 아닙니다. 한 도메인짜리 작업은 이름 하나로 묶고, 여러 도메인이 얽힌 작업에서는 지금 어느 도메인을 읽고 있는지 짚어 주려는 것입니다.


이름만으로는 부족한 자리

파일 경로와 모듈 이름이 맞아떨어진 대목을 좀 더 살펴보겠습니다. 도입에서 본 원장 조사에서 에이전트가 연 여섯 파일은 서로 다른 네 모듈에 있었지만, 앞머리에 붙은 도메인 이름은 모두 정산이었습니다. 인프라도 모델도 저장소도 이름은 같았죠.

로그가 보여 주는 것은 저희가 붙여 둔 도메인별 모듈 이름과 에이전트가 실제로 연 파일 경로의 대응입니다. 그 대응이 다수의 로그에서 되풀이됐다는 것까지가 사실이고, 그 이름이 읽을 곳을 고르게 만들었다는 인과까지는 이 로그로 확인하지 못합니다. 정산 원장 코드가 원래 자기들끼리만 얽혀 있어, 모듈로 나누지 않았어도 읽기가 모였을 수 있으니까요.

대응의 단위도 짚어 둘 만합니다. 겹친 것은 낱낱의 빌드 모듈이 아니라, 같은 도메인 이름을 공유하는 모듈 묶음입니다. 여섯 파일은 물리 모듈 네 개에 흩어져 있었지만, 이름 앞머리는 하나로 모였습니다. 그렇다면 이름만 잘 붙이면 될 뿐, 굳이 물리 모듈까지 쪼갤 이유가 있느냐는 물음이 남습니다. 이름은 지금 어디를 읽는지 알려 주는 표지입니다. 디렉터리 규칙만으로도 그 표지는 붙일 수 있죠. 모듈을 갈라 두는 것이 표지에 더하는 것은 강제력입니다. 도메인이 다른 모듈로 나뉘면, 한 도메인이 다른 도메인을 참조하려면 빌드 파일에 의존을 선언해야 하고, 선언하지 않은 참조는 컴파일 단계에서 막힙니다. 얽힘을 못 하게 막는다는 뜻은 아닙니다. 선언해 두면 얼마든 얽을 수 있죠. 다만 무심코 얽는 대신, 빌드 파일에 한 줄로 남는 결정을 거치게 합니다. 한 모듈 안에서는 그 문턱이 없어 참조가 조용히 얽히고, 뭉친 쪽에서 사용자 코드가 직무 체계까지 번진 것이 그 예였습니다. 이름은 읽는 자리를 알려 주고, 모듈 경계는 도메인 사이 얽힘을 눈에 보이는 선택으로 바꿉니다.


코드를 덜 읽으면 컨텍스트에 여유가 생깁니다

덜 읽는 일은 토큰 절약에서 끝나지 않습니다. 컨텍스트에는 한도가 있고, 목표 밖 코드가 그 한도를 차지한 만큼 정작 봐야 할 구현과 테스트, 호출 관계에 쓸 자리가 밀려납니다. 반대로 읽을 범위가 목표 도메인으로 좁혀지면, 같은 예산 안에서 결함과 실제로 얽힌 코드를 더 많이, 더 오래 쥔 채로 고칠 수 있습니다. 덜 읽어도 되는 코드가 더 잘 고쳐지는 이유입니다.

모듈 경계는 목표 밖 참조를 빌드 파일에 남는 결정으로 바꿔, 이 자리를 관리할 여지를 줍니다. 선언하지 않은 도메인 사이 참조와 순환을 컴파일 단계에서 막아, 참조가 조용히 번지는 것을 눈에 보이는 선언으로 바꾸는 것이죠. 물론 그 울타리가 코드의 옳고 그름까지 봐 주지는 않습니다. 빌드 파일에 의존을 선언해 두면 그 안에서 계층을 거스르는 참조는 별도 검사 없이는 통과하니까요. 다만 그 울타리가 남기는 자취를 이제 사람만 읽는 게 아닙니다. 익숙한 코드베이스라면 사람은 이름 없이도 감으로 어디를 열지 알지만, 에이전트에게는 그런 배경지식이 없습니다. 대신 이번 로그에서는 에이전트가 연 파일 경로가 도메인 이름과 나란히 놓여 있었습니다.


이 관찰을 뒷받침하는 연구들

여기까지는 한 팀, 한 저장소의 로그입니다. 그런데 이 관찰을 이루는 고리들은 다른 분야의 연구가 이미 같은 방향으로 가리켜 온 것들입니다.

첫째, 컨텍스트가 길어지는 것 자체가 성능을 갉아먹는다는 것. Du 등은 관련 근거를 모델이 완벽히 찾아낸 상황에서도, 입력이 길어지면 추론 성능이 떨어지는 것을 보였습니다. 정보가 아니라 공백 이만 오천 개를 끼워 넣기만 해도 오답으로 기울었죠. 못 찾아서가 아니라 길이 자체가 부하라는 뜻입니다(EMNLP 2025 Findings). 실효 컨텍스트가 광고된 창보다 훨씬 짧다는 측정도 같은 방향입니다. 여러 모델이 삼만 이천 토큰 지점에서 단문 성능의 절반 아래로 떨어졌습니다(NoLiMa, ICML 2025).

둘째, 에이전트가 저장소를 탐색할 때 무엇을 컨텍스트에 담느냐가 결과와 강하게 연관된다는 것. SWE-Explore는 저장소 탐색 능력을 수리 성공과 분리해 재면서, 컨텍스트 효율이 수리 성공과 가장 강하게 붙어 있는 것을 확인했습니다(상관계수 0.95, 2026 프리프린트). 다만 결이 중요합니다. 이 연구의 지배적 실패는 "무관한 코드가 많아서"가 아니라 "핵심 근거를 못 담아서"였습니다. 핵심 근거만 확보되면 여분 코드에는 의외로 관대했고, 반대로 핵심이 빠지면 무관 코드가 해결률을 더 끌어내렸습니다. 그러니 요점은 "무관 코드를 0으로 만들라"가 아니라 "관련 근거가 들어갈 자리를 확보하라"입니다. 저희가 이 글에서 "유리한 조건"이라 부른 것과 맞닿는 이야기죠.

셋째, 구조가 겉으로 드러나면 그걸 다시 추론하는 비용이 준다는 것. 저장소를 그냥 텍스트로 평탄화하면 모델이 의존·호출 관계를 머릿속에서 되짚어야 하는데, 이 재추론이 제한된 컨텍스트에서 특히 비쌉니다. 코드의 그래프 구조를 명시해 주면 어디를 고쳐야 하는지 짚는 일이 쉬워진다는 결과가 있습니다(LocAgent, ACL 2025). 다만 이건 "구조를 표현해 주면"에 대한 것이지, "도메인 경계로 모듈을 나누면"을 직접 실험한 건 아닙니다.

세 갈래는 한 방향을 가리킵니다. 긴 컨텍스트와 무관한 내용은 부하가 되고, 관련 근거가 들어갈 자리를 확보하는 쪽이 이롭다는 것. 저희 로그에는 목표 도메인으로 모인 읽기와, 뭉친 쪽에서 목표 밖으로 번진 읽기가 남았습니다. 여기에 짧은 컨텍스트가 추론에 유리하다는 연구와 컨텍스트 효율이 수리 성공에 강하게 연관된다는 결과를 이으면, 덜 읽어도 되는 코드는 더 잘 고쳐지는 코드라는 방향에 닿습니다. 물론 "모듈화가 곧 수정 정확도를 높인다"를 한 저장소에서 통제해 실험한 연구는 아직 없습니다. 그 마지막 한 칸을 저희가 메운 것은 아닙니다. 다만 나머지 칸들은 이미 여러 자리에서 같은 방향으로 채워져 있습니다.


모듈 경계는 이제 컴파일러만의 것이 아닙니다

이번 측정으로 확인한 것부터 정리하겠습니다. 잘 갈린 저장소의 로그 백아흔세 건에서 읽기는 목표 도메인 쪽으로, 도메인 크기만으로 예상되는 자리보다 중앙값 십사 퍼센트포인트 더 모였습니다. 두 실제 진단에서 연 파일들이 담은 소스 토큰은 만 삼천팔백구십일 개와 삼만 사천육백구십오 개로 약 2.5배 벌어졌고, 뭉친 쪽에서는 사용자 참조가 직무 체계까지 번졌습니다. 방식이 다른 두 관찰에서 읽기가 같은 방향으로 움직인 것입니다.

그렇다면 이 결과는 누구에게 쓸모가 있을까요. 도메인을 물리 모듈로 가르는 건 아무 코드베이스에나 권할 선택이 아닙니다. 단일 모듈로도 충분한 규모라면 모듈을 쪼개는 순간 빌드 설정과 의존 관리가 늘고, 그 손이 편익을 넘어서기 쉽습니다. 그러니 이 글은 "그러니 나누라"는 이야기가 아닙니다. 이미 여러 모듈로 갈라 두었거나, 규모가 커져 어차피 가르는 값을 치르기로 한 팀에게, 그 값의 계산서에 항목이 하나 더 생겼다는 이야기입니다.

그 값으로 얻는 것은 두 갈래입니다. 하나는 표지입니다. 파일 경로만 보고도 어느 도메인의 무슨 계층인지, 테스트가 어느 도메인에 속하는지 드러나면, 에이전트가 읽는 파일마다 지금 어디를 보고 있는지가 경로에 함께 실립니다. 이 표지는 사실 물리 모듈까지 가지 않아도, 디렉터리와 이름 규칙만으로 붙일 수 있습니다. 물리 모듈이 표지에 더하는 것은 강제력입니다. 도메인을 다른 모듈로 갈라 두면, 도메인 사이 참조는 빌드 파일에 선언해야만 통과합니다. 선언하지 않은 참조는 컴파일이 막으니, 도메인을 가로지르는 결합은 무심코 쌓이는 대신 빌드 파일에 남는 결정을 거칩니다. 표지는 지금 읽는 자리를 알려 주고, 강제력은 에이전트가 따라갈 수 있는 도메인 사이 참조를 눈에 보이게 선언된 것으로 한정합니다. 표지는 이름 규칙만으로도 얻지만, 강제력은 모듈을 가르는 값을 치른 팀만 손에 쥡니다.

그 강제력으로 무심코 얽히는 참조가 줄면, 에이전트가 따라 들어가 열게 되는 목표 밖 파일도 줄어듭니다. 그만큼 한정된 컨텍스트를 관련 구현과 테스트에 더 남겨, 결함과 얽힌 코드를 쥔 채로 고칠 수 있습니다. 이번 두 진단에서도 경계가 물리 모듈로 드러난 쪽은 소스 범위가 더 작았고, 단일 모듈 쪽에서는 한 도메인의 참조를 따라 목표 밖 도메인까지 읽기가 번졌습니다. 큰 코드베이스를 에이전트에게 맡길수록, 모듈 경계는 에이전트가 덜 읽고 더 잘 고치게 하는 조건이 됩니다.

로그에 남은 읽기 범위의 차이는, 모듈 이름이 단순한 미관이 아니라 에이전트의 컨텍스트 배분과 이어진다는 것을 보여 줍니다. 이름을 예쁘게 정리하는 문제가 아니라, 에이전트가 탐색에 쓰는 컨텍스트를 어디에 둘지의 문제인 것이죠.

물리 모듈로 나누는 데는 비용이 듭니다. 디렉터리를 파고, 빌드 파일을 쓰고, 성격을 선언하고, 의존을 적어야 하죠. 그 손이 만만치 않아, 단일 모듈로 충분한 규모라면 대개 거기 머뭅니다. 저희에게 그 비용은 엔지니어링으로 감당하기로 한 종류의 것이었습니다. 이미 그 값을 치르기로 한 팀에게, 오래도록 그 대가는 도메인 사이 결합을 무심코 쌓지 못하게 막는 데 있었습니다. 그런데 그 경계는 조용히 한 가지를 더 해 왔습니다. 낯선 코드를 손볼 때, 이름만 보고도 어디부터 열면 되는지 짚어 준 것이죠. 저희가 이 저장소를 오래 다뤄 오며 몸으로 겪은 것이기도 합니다. 관련된 것끼리 한 이름 아래 모여 있으면, 사람도 덜 헤매고 고칠 곳에 곧장 닿습니다. 다만 사람에게는 그게 눈에 잘 안 띄는 덤이었습니다. 익숙해지면 이름 없이도 감으로 여니까요. 이제는 에이전트도 코드를 읽습니다. 그 배경지식이 없는 에이전트에게는, 사람에게 덤이던 그 길잡이가 훨씬 크게 작동합니다. 도입에서 본 여섯 파일이 한 이름 아래 모여 있던 장면은, 그 덤이 더는 덤이 아니게 된 순간이었습니다. 사람이든 에이전트든, 덜 읽어도 되는 코드는 더 잘 고쳐집니다. 이미 모듈을 가르는 값을 치르는 팀이라면, 그 계산서에 이 항목을 새로 적어 둘 만합니다.


🚀플렉스팀 채용페이지 바로가기

☕flex Private Talk 신청하기

글이 마음에 드셨나요?
공유하기