당사(합동회사 Joinclass)는 Claude Code를 사용하여 10개 부문의 AI 에이전트를 구동하며 회사를 운영하고 있습니다. launchd 작업은 17개 이상이며, 기사 공개, 영업 파이프라인, 아침 다이제스트까지 거의 자동화되어 있습니다.
하지만 처음부터 순조롭게 돌아간 것은 아니었습니다. 제가 가장 어려움을 겪었던 부분은 **'세션 후반부에는 AI가 CLAUDE.md의 규칙을 지키지 않게 되는 것'**이었습니다.
원인을 조사한 결과, 두 가지 문제가 있었습니다.
- CLAUDE.md에 규칙을 계속 추가하여 1,000줄을 초과했고, 규칙들끼리 모순되는 부분이 생겼습니다.
- 부모 세션(Orchestrator)이 각 부문의 STATE.md나 로그를 스스로 모두 Read하고 있었습니다. 이 때문에 컨텍스트가 금방 차올랐고, 압축(요약) 과정에서 중요한 지시 사항들이 누락되었습니다.
본문에서는 이를 개선한 설계인 **'Thin Orchestrator'**를 당사에서 실제로 구동 중인 설정과 스크립트를 바탕으로 설명합니다. 이 글을 다 읽으시면 그날 안에 자신의 리포지토리에 적용할 수 있을 것입니다.
당사의 CLAUDE.md에는 다음과 같이 작성되어 있습니다 (실물 발췌).
## Thin Orchestrator의 원칙
- **컨텍스트 사용률을 10~15%로 유지한다**
- 파일 내용을 자신의 컨텍스트에 읽어들이지 않고, **파일 경로만 전달한다**
...
핵심은 간단합니다.
| 역할 | 보유하는 것 | 보유하지 않는 것 |
|---|
| 부모 (Orchestrator) | 의도 이해, 분배, 최종 보고 | 파일 본문, 로그, 검색 결과 |
| 자식 (Sub-Agent) | 본문을 읽고 작업 수행 | 다른 부문의 문맥 |
자식 에이전트는 자신 전용 컨텍스트로 작동하며, 부모에게는 결론만 반환합니다. 자식이 10만 토큰을 읽었더라도, 부모에게 돌아가는 것이 500 토큰의 요약이라면, 부모의 컨텍스트는 거의 증가하지 않습니다.
아침 다이제스트는 모든 부문의 STATE.md와 승인 대기 목록을 읽어오는 가장 무거운 처리입니다. 이것을 부모가 처리하면 컨텍스트를 한 번에 소진시키므로, 전용 에이전트로 분리했습니다.
---
name: ai-ceo-morning
description: 아침 다이제스트 에이전트. 모든 부문의 상태를 수집하여 CEO용 요약을 생성한다.
...
포인트는 tools를 Read와 Grep로만 제한하고 있다는 것입니다.
- 수집하는 역할만 하므로, Write/Bash는 전달하지 않습니다 (실수로 덮어쓰는 사고 방지).
description은 부모가 '누구에게 분배할지' 결정하는 자료가 됩니다. 한 줄로 역할을 알 수 있도록 작성합니다.
부모는 본문을 읽지 않고, 다음 5가지 정보만 전달합니다. CLAUDE.md에 템플릿으로 작성해 두면, 부모가 매번 동일한 형태로 위임하게 됩니다.
## 서브 에이전트에게 위임하는 방법
1. **작업의 목적** — 무엇을 달성할 것인지 (1문장으로)
2. **참조 파일 경로** — 필요한 입력 파일 경로 목록
...
'참조 파일 경로'가 핵심입니다. 부모가 STATE.md를 Read한 후 그 내용을 자식에게 붙여넣는 것이 아니라, .company/departments/*/STATE.md와 같은 경로만 전달합니다. 이렇게 하면, 본문이 컨텍스트에 올라가는 것은 자식 쪽뿐입니다.
더 나아가 분리한 부분은 정기 부서 작업입니다. 애초에 부모의 대화 세션을 거치지 않고, launchd에서 claude -p를 직접 실행하도록 했습니다. 1작업당 1프로세스이므로 매번 깨끗한 컨텍스트로 시작하여 축적되는 것이 없습니다.
다만, 순수한 claude -p 상태로는 비용을 파악하기 어렵습니다. 그래서 공통 래퍼(wrapper)를 만들었습니다.
출력된 JSON에서 result를 추출하여 표준 출력으로 흘려보내고, total_cost_usd를 파일에 기록하는 것이 record-cost.py입니다 (발췌).
def main():
label = sys.argv[1]
raw = sys.stdin.read()
...
호출 측(개발 부문 작업)은 다음과 같습니다.
source "/path/to/.company/scripts/lib/claude-run.sh"
RESULT=$(claude_run dept-dev --effort low -p "
당신은 개발 부문의 CTO 에이전트입니다.
..."
여기에 Thin Orchestrator의 개념이 그대로 나타납니다.
보고서 포맷을 지정하여 반환되는 양을 결정합니다 -
| tail -20
으로, 만약 내용이 길게 반환되더라도 후속 단계(Slack 알림)로 전달되는 양을 제한합니다 -
--allowedTools
으로, 해당 부서에 필요한 도구만 전달합니다.
source .company/scripts/lib/claude-run.sh
claude_run probe --effort low -p "2+2는?"
jq '.' .company/scripts/cost-tracker.json | tail -20
당사에서 측정해 본 결과, 단순히 "2+2"를 질문하는 것만으로 $0.27~$0.47이 소요되었습니다. 내역은 캐시 생성에 약 41,000 토큰이 사용되었으며, 그중 자체 파일(CLAUDE.md・에이전트・스킬)는 5,800 토큰만 사용했습니다. 나머지는 Claude Code 본체의 고정 오버헤드입니다.
이 수치로부터 두 가지 방침을 결정하게 되었습니다.
자체 파일을 줄여도 효과가 작다 →
CLAUDE.md는 '모순을 없애기' 위해 짧게 유지합니다 (비용 절감이 목적이 아님) - 비용에 영향을 주는 것은
호출 횟수→ 부서별 작업은 하루 한 번으로 통합합니다.
상위 세션에서는 때때로 /context를 실행하여 사용률을 확인합니다. 작업 도중에 15%를 크게 초과한다면, 상위 에이전트가 스스로 너무 많은 내용을 읽고 있다는 신호입니다. 어떤 도구 결과가 큰지 확인하고, 그 처리를 서브 에이전트로 분리합니다.
| 증상 | 원인 | 대처 |
|---|
claude -p가 "Not logged in"으로 실패함 | --bare는 인증 정보를 읽지 않음 | --bare를 사용하지 않음 |
| 비용 상한선이 한 번도 발동되지 않음 | 고정값 0.02의 추정치를 누적 계산했음 | total_cost_usd의 실측값을 누적 계산함 |
| 하위 에이전트가 장문 반환하여 상위 에이전트가 부풀어 오름 | 반환 포맷이 지정되지 않음 | 'X건/개요' 형식으로 지정하고, tail로 상한을 설정함 |
| 하위 에이전트가 임의로 파일을 수정함 | tools를 모두 허용했었음 | 집계 시스템은 Read, Grep만 사용하도록 제한함 |
| 규칙이 작동하지 않음 | CLAUDE.md가 1,000줄을 초과하여 모순 발생 | 200줄 전후로 압축하고, 상세 내용은 .claude/agents/에 분리함 |
마지막 항목에 대해 보충하자면, 당사의 CLAUDE.md는 현재 235줄입니다. 부서별의 자세한 절차는 각 에이전트 정의로 옮겼습니다. 이로써 상위 에이전트가 매번 읽어들이는 양 자체도 줄었습니다.
- 상위 에이전트는 파일의 경로만 전달하고, 본문은 읽지 않는다 -
- 하위 에이전트는 tools를 제한하고, 정해진 포맷으로 결론만을 반환한다 -
- 정기 작업은 상위 에이전트를 거치지 않고
claude -p
로 매번 깨끗하게 시작하여 실질적인 비용을 기록한다
경영의 관점에서 본다면, Orchestrator는 사장과 같습니다. 사장이 모든 부서의 자료를 직접 읽어들이고 있으면, 정작 중요한 판단에 머리가 돌아가지 않습니다. 보고서는 한 장으로 요약해서 받고, 세부 작업은 현장에 맡깁니다. AI 에이전트 설계 역시 조직 설계와 같았습니다.
서브 에이전트를 분리하는 방법, 위임 프롬프트(delegation prompt)의 설계, 부서 간 조정 패턴 등은 서적 『Claude Code 멀티에이전트 개발』에서 체계적으로 설명하고 있습니다.
CLAUDE.md를 짧게 유지하는 설계에 대해서는 『CLAUDE.md 설계 패턴』도 참고가 됩니다.