Cursor나 Claude Code를 쓰다 보면 어느 순간 에이전트가 느려지거나, 엉뚱한 파일을 잔뜩 읽고, 같은 작업을 반복하는 경험을 하게 된다. 이건 모델 성능의 문제가 아니다. 컨텍스트 윈도우를 어떻게 채우느냐의 설계 문제다. 최근 쏟아진 실측 데이터들을 엮어보면, AI 코딩 에이전트의 토큰 낭비는 대부분 세 가지 구조적 원인에서 비롯된다는 사실이 선명하게 드러난다.
Grep은 에이전트에게 너무 비싸다
dev.to에 공개된 실측 실험(agent-lsp.com)은 꽤 충격적인 숫자를 제시한다. TypeScript 코드베이스(24K 줄)에서 함수명을 24개 파일에 걸쳐 일괄 변경하는 작업을 두 가지 방식으로 비교했다. Grep 기반 접근은 492,954바이트를 컨텍스트에 밀어 넣었고, LSP(Language Server Protocol) 기반은 단 342바이트로 동일한 작업을 완료했다. 1,441배 차이다.
왜 이런 격차가 생기는가. Grep은 코드베이스 전체를 텍스트로 훑는다. 파일을 읽고, 매칭된 줄을 가져오고, 맥락을 파악하기 위해 또 읽는다. 319K 줄짜리 HashiCorp Consul 프로젝트에서 Close를 Grep으로 검색하면 1,156개의 결과가 나온다. 그런데 LSP로 실제 해당 심볼의 참조만 조회하면 12개다. 나머지 1,144개는 전부 노이즈—주석, 문자열 리터럴, 무관한 패키지에서 우연히 일치한 텍스트다. 에이전트는 이 노이즈를 전부 읽고, 판단하고, 무시하는 과정에서 토큰을 소모한다. 입력 토큰뿐 아니라 출력 토큰까지.
LSP는 구조화된 쿼리로 정확한 결과만 반환한다. rename_symbol 한 번 호출로 워크스페이스 전체에 걸친 원자적 변경 목록을 JSON으로 받는다. 코드베이스가 커질수록 이 차이는 더 벌어진다—15K 줄에서 5배였던 절감이 319K 줄에서는 34배가 된다. Grep 비용은 O(코드베이스 크기)이고, LSP 비용은 O(실제 참조 수)이기 때문이다. agent-lsp는 이 원리를 MCP 서버로 구현한 오픈소스 도구로, Claude Code·Cursor·GitHub Copilot 등 MCP 클라이언트와 바로 연결된다.
컨텍스트 오버플로우: 에이전트가 조용히 망가지는 방식
LSP 문제가 '넣지 말아야 할 것을 넣는' 비효율이라면, 컨텍스트 오버플로우는 '너무 많은 것을 한꺼번에 넣으려다' 실패하는 문제다. AWS 스페인 팀이 공개한 에이전트 실패 모드 분석에 따르면, 도구가 200KB짜리 로그 파일이나 대형 DB 쿼리 결과를 통째로 컨텍스트에 반환할 때 에이전트는 에러를 던지지 않는다. 조용히 데이터를 잘라내거나, 앞부분을 잊거나, 불완전한 답변을 완성된 것처럼 돌려보낸다.
IBM의 실험이 이를 수치로 보여준다. 동일한 소재과학 워크플로우가 컨텍스트 오버플로우 상태에서는 2,000만 토큰을 소모하고 실패했고, 메모리 포인터 패턴을 적용하자 1,234토큰으로 성공했다. 원리는 단순하다. 도구가 대용량 데이터를 에이전트 상태(agent.state)에 저장하고, 컨텍스트에는 포인터 문자열만 반환한다. 다음 도구는 그 포인터로 필요한 데이터만 꺼내 처리한다. LLM은 "logs-payment-service"라는 52바이트짜리 문자열만 보고도 흐름을 이어간다. 프론트엔드 개발자에게 익숙한 가상 스크롤이나 페이지네이션과 같은 맥락의 설계 원칙이다—렌더링할 수 있는 양만 넘긴다.
세 번째 실패 모드는 추론 루프다. 에이전트가 동일한 도구를 동일한 인자로 반복 호출하며 진전 없이 토큰을 태운다. 원인은 대개 도구의 피드백이 모호해서다. "더 많은 결과가 있을 수 있습니다"라는 응답은 에이전트에게 '다시 시도하면 더 나은 결과가 나올 것'이라는 신호로 읽힌다. 해법은 두 가지: 도구 응답에 SUCCESS/FAILED를 명시적으로 표기하거나, 프레임워크 수준에서 중복 호출을 차단하는 DebounceHook을 삽입하는 것이다. 명확한 상태 표기만으로 14번의 도구 호출이 2번으로 줄었다는 실측치는 시사하는 바가 크다.
LLM에게 시키지 말아야 할 일이 있다
토큰 낭비의 근본에는 더 단순한 안티패턴이 있다. LLM이 if 문으로 해결할 수 있는 문제를 풀고 있을 때다. 멀티 에이전트 시스템을 설계하는 개발자들이 반복적으로 저지르는 실수—권한 체크, 메시지 라우팅, 스키마 검증을 전부 LLM에게 묻는 것—가 여기 해당한다. "이 사용자가 파일을 삭제할 수 있는가?" 같은 질문은 LLM이 '추론'해야 할 문제가 아니다. 정책 코드가 결정론적으로 판단해야 한다.
이 원칙은 당연해 보이지만, AI 도구가 편리해질수록 경계가 흐려진다. Cursor나 Claude Code로 빠르게 에이전트를 붙이다 보면 어느새 비즈니스 로직의 경계 조건까지 프롬프트로 처리하게 된다. AI는 무엇을 할지 결정하고, 코드는 그것을 실행하며 경계를 강제한다—이 분리가 무너지면 토큰 비용과 비결정성이 동시에 올라간다.
설계 원칙으로 정리하면
세 소스를 관통하는 메시지는 하나다. 에이전트에게 넘기는 정보의 정밀도와 양이 곧 성능이자 비용이다. 구체적으로:
- 탐색은 LSP로: Grep 대신 구조화된 심볼 쿼리. 코드베이스가 클수록 효과는 기하급수적으로 커진다.
- 대용량 데이터는 포인터로: 도구가 반환하는 모든 것이 컨텍스트를 차지한다. 에이전트가 '볼 필요 없는' 데이터는 상태 저장소에 두고 참조만 넘긴다.
- 결정론적 문제는 코드로: 권한, 라우팅, 검증은 LLM의 일이 아니다. 인지 작업에만 모델을 쓴다.
- 도구 응답은 명시적으로: 모호한 피드백은 루프를 부른다. SUCCESS/FAILED를 명시하고, 중복 호출 방어를 프레임워크 수준에서 설계한다.
지금 팀에서 Cursor나 Claude Code를 쓰고 있다면, 에이전트가 어떤 경로로 컨텍스트를 채우고 있는지 한 번쯤 들여다볼 필요가 있다. 모델 업그레이드보다 훨씬 저렴하게, 에이전트의 실질적인 실효성을 끌어올릴 수 있는 지점이 거기 있다.