본문 바로가기
Engineering

멀티에이전트 세팅의 실전 비용 모델 — 정의 vs 호출

커스텀 서브에이전트를 19개까지 늘려놓고 불안해져 실제 비용을 뜯어봤다. description은 세션 내내 상주하지만 body는 호출될 때만 로딩된다는 구조, 그리고 진짜 낭비는 개수가 아니라 순차 체이닝이라는 결론과 호출 규율 네 가지.

에이전트를 19개나 만들어놓고 문득 불안해졌다. .claude/agents/ 안에 planner, architect, builder, frontend, backend, tutor, reporter… 필요할 때마다 하나씩 추가하다 보니 어느새 19개가 쌓여 있었다. 이 정도면 매 세션마다 이 목록 전체가 컨텍스트에 실려서 토큰을 갉아먹고 있는 거 아닐까? "에이전트는 꼭 필요한 것만 최소한으로 유지해야 한다"는 말을 어디선가 들은 것 같기도 하고.

그래서 실제로 뜯어봤다. 결론부터 말하면 걱정의 방향이 틀렸다. 개수는 거의 문제가 아니었고, 진짜 비용은 다른 곳에서 새고 있었다.

"많이 만들면 낭비"라는 통념

Claude Code 커스텀 서브에이전트는 .claude/agents/*.md 파일로 정의한다. 파일 구조는 두 부분으로 나뉜다.

  • frontmatter의 description — 이 에이전트가 뭘 하는지, 언제 써야 하는지 요약
  • 그 아래 본문(body) — 실제로 그 에이전트가 호출됐을 때 받게 되는 시스템 프롬프트

여기서 흔히 하는 오해가 "에이전트 파일을 많이 만들면 그만큼 컨텍스트가 무거워진다"는 것이다. 나도 그렇게 생각했다. 그런데 이 둘이 로딩되는 시점 자체가 다르다.

정의 비용 vs 호출 비용

description은 메인 대화가 시작되는 순간부터 항상 컨텍스트에 붙어 있다. 모델이 "지금 이 작업을 어떤 에이전트에게 맡길지"를 판단하려면 전체 목록을 알아야 하니까, description은 세션 내내 상주한다.

반면 body(시스템 프롬프트)는 그 에이전트가 실제로 호출될 때만 로딩된다. 평소에는 존재하지 않는 셈이다. 호출되지 않은 18개 에이전트의 body는 그 세션 동안 단 한 번도 컨텍스트에 올라오지 않는다.

내 환경에서 실측해보니 대략 이렇다 (에이전트 19개 기준, 수치는 대략적인 추정치로 봐주면 좋겠다):

구분로딩 시점크기(체감)비용 특성
description (19개 합산)세션 시작부터 항상 상주약 3,657자 → 대략 1.5~1.8k 토큰매 요청마다 붙는 고정비, 작다
body (에이전트 1개당)그 에이전트를 실제로 호출할 때만에이전트당 약 3천~1만 자호출 시에만 발생하는 변동비, 크다

💡 19개를 정의해둬도 평소 상주 비용은 description 몫인 약 1.5k 토큰 안팎이다. 실제 세션 컨텍스트 크기(수만~수십만 토큰)에 비하면 오차 범위 수준이다. "많이 쪼개면 낭비"라는 통념은 이 구조에서는 거의 성립하지 않는다.

진짜 함정은 개수가 아니라 순차 체이닝이다

그럼 어디서 비용이 새는가. 개수가 아니라 하나의 작업을 여러 에이전트로 순차적으로 체이닝할 때다.

에이전트 A가 끝나면 B를 부르고, B가 끝나면 C를 부르는 식으로 파이프라인을 짜면, 매 단계마다:

  • 새로운 서브에이전트가 새 컨텍스트로 뜬다 (이전 단계의 대화 맥락을 모른다)
  • 그래서 내가 맥락을 다시 요약해서 넘겨야 한다
  • 왕복마다 지연이 쌓인다

병렬로 돌려도 되는 작업을 이런 식으로 억지로 순차 체이닝하면, 개별 body 호출 비용(변동비, 크다)이 단계 수만큼 반복해서 쌓이고 거기에 맥락 재전달 오버헤드까지 더해진다. 개수를 19개에서 5개로 줄여도 이 패턴을 계속 쓰면 낭비는 그대로다.

flowchart LR
  subgraph seq["순차 체이닝 — 지양"]
    direction LR
    S1["에이전트 A"] -->|"맥락 재요약"| S2["에이전트 B"] -->|"맥락 재요약"| S3["에이전트 C"]
  end
flowchart TB
  M["메인 대화"] --> T1["tutor: 주제 1"]
  M --> T2["tutor: 주제 2"]
  M --> T3["tutor: 주제 3"]
  M --> T4["tutor: 주제 4"]
  M --> T5["tutor: 주제 5"]
  M --> T6["tutor: 주제 6"]

병렬 팬아웃은 각 서브에이전트가 독립적으로 자기 파일을 쓰고 끝나니 서로의 맥락을 몰라도 된다. 순차 체이닝은 그 반대다 — 이전 단계 결과를 다음 단계가 반드시 알아야 하는데, 서브에이전트는 메인 대화를 못 보니 내가 매번 손으로 이어줘야 한다.

그래서 세운 호출 규율 네 가지

개수를 줄이는 대신, 호출 방식에 규율을 두기로 했다.

  1. 단순 작업은 에이전트를 안 부른다. 부르더라도 하나만 쓴다. "혹시 몰라서" 여러 개를 릴레이로 태우지 않는다.
  2. 병렬 이득이 없는 순차 의존 작업은 여러 서브에이전트로 쪼개지 않는다. DB 스키마를 먼저 만들고 그 위에 행을 추가하는 것처럼 단계 간 의존이 강한 작업은 억지로 나누지 않고 한 흐름으로 처리한다.
  3. 서브에이전트는 메인 대화 이력을 모른다. 그러니 호출할 때 필요한 맥락(대상 목록, 배경, 제약조건)을 프롬프트에 명시적으로 적어 넣는다. "알아서 파악하겠지"는 통하지 않는다.
  4. 신뢰성이 필요한 규칙은 프롬프트가 아니라 Hook으로 강제한다. "항상 이렇게 해줘"를 시스템 프롬프트 문장에만 의존하면 언젠가 빠뜨린다. 강제해야 하는 동작은 hook 설정으로 옮긴다.

실전에서 확인한 것 — AI에게 대량 작업을 시켜보며

최근에 공부 노트를 28개 만들어야 하는 일이 있었다. 처음엔 순서대로 하나씩 시키려다가, 주제가 서로 독립적이라는 걸 깨닫고 방식을 바꿨다.

  • 주제별로 tutor 에이전트 6개를 병렬로 띄워서 각자 맡은 주제의 노트를 쓰게 했다. 서로 참조할 필요가 없는 작업이니 쪼개서 이득을 봤다.
  • 그 결과물을 옮겨 넣는 기계적·독립적 반복 작업은 general-purpose 에이전트에 위임하는 게 맞았다. 순차 의존이 없으니 이것도 병렬.
  • 반대로 순차 의존이 있는 단계는 그대로 뒀다. 예를 들어 구조를 먼저 잡고 그 위에 세부 항목을 채우는 작업은 억지로 쪼개지 않았다.

반례도 관찰했다. 서브에이전트에 넘기는 입력(JSON)에 한글을 수동 이스케이프하려다 반복해서 깨진 적이 있는데, 결국 그런 단순 조작은 우회 없이 바로 처리하는 게 빨랐다. "단순 작업은 굳이 에이전트/우회를 부르지 말라"는 규율 1번의 확인이었다.

부가: 역할이 겹치는 에이전트는 description에 경계를 박아둔다

계획 담당(strategist, architect, planner)과 구현 담당(builder, frontend, backend)처럼 역할이 미묘하게 겹쳐 보이는 에이전트군이 있다. 이런 그룹은 description에 서로에게 위임하는 경계를 명시해두면 모델이 어떤 걸 골라야 할지 헷갈리는 일이 줄어든다.

실제로 내 세팅에서 "계획 3형제"의 description 끝에는 서로를 가리키는 경계를 한 줄씩 박아뒀다.

  • architect → "코드는 작성하지 않는다(그건 builder)."
  • planner → "설계 자체를 만드는 건 architect, 모호하고 큰 일의 오케스트레이션은 strategist."
  • strategist → "코드·설계 산출물은 만들지 않는다(그건 architect/builder)."

셋이 서로 "그건 네 담당"이라고 되넘기게 해두니, 비슷해 보이는 셋 중 무엇을 부를지 모델이 훨씬 덜 헷갈렸다. 구현 쪽도 마찬가지로, frontend엔 "큰 아키텍처 설계는 architect, 디버깅은 debugger"처럼 자기가 안 맡는 영역을 명시해두는 식이다.

마무리

에이전트를 몇 개 만들었는지는 크게 중요하지 않았다. description은 상주 비용이 작고, body는 호출할 때만 켜진다. 진짜 아껴야 할 건 파일 개수가 아니라 — 필요할 때만, 맞는 에이전트 하나를, 순차로 억지로 엮지 않고 부르는 호출 규율이었다.