CLAUDE.md가 11줄에서 1줄로 줄어들 때까지 — 8개 문제풀이로 정제한 에이전트 운영 매뉴얼
AI Agent를 어떻게 사용하는게 “잘 쓰는” 걸까? 내가 최근에 갖는 고민이다. 그러던 중 AI TOP 100 대회를 알게되었다. 이 대회는 누구나 Agent를 활용해 푸는 문제를 제공하고 있다. 답안을 제출하고 이번 풀이에서 뭐가 아쉬웠는지 리뷰하면서 앞서 꺼냈던 “Agent를 잘 사용하는 방법” 을 훈련할 수 있었다.
너는 아틀란티스 입국 심사 에이전트다. (…) JSON 형식을 준수한다.
— 첫 프로젝트 CLAUDE.md (총 11줄, 역할·판정 원칙·JSON 형식까지 구체 지시)
에이전트가 컨텍스트 윈도우를 최대한 활용할 수 있는 단위로 작업을 쪼개서 여러 세션에 거쳐서 작업을 진행한다.
— 마지막 프로젝트 CLAUDE.md (총 1줄, 컨텍스트 윈도우 운영 원칙만)
8개 AI 문제풀이를 시간순으로 풀면서, CLAUDE.md는 11줄에서 1줄로 줄었다. 그 사이의 채점 결과는 우여곡절을 거치며 변화했다.
60점 → 80점 → 40점 → 50점 → 81점 → 56점 → 79점 → 92점
각 점수를 100점 만점 비율로 계산한 결과
CLAUDE.md를 더 잘 쓸수록 점수가 오를 거라 생각했다. 그렇게 “방법”을 발전시켜 갔지만 3번째에서 40점로 떨어지는 쓴맛을 보기도 했다.
한 번 에이전트 활용법의 변천사를 살펴보자. 첫 번째 문제부터 시작한다.
1. 시작점: 분리와 파이프라인의 발견 (60점 → 80점)
첫 문제(AI 입국 심사관)에서 나는 210장의 서류 이미지와 25개 입국 규정 텍스트를 받았다. CLAUDE.md 에는 에이전트의 정체성, strategy.md에는 처리 파이프라인을 담았다. CLAUDE.md에 모든 정보를 담는 것을 피한 것이다.
1
2
3
<!-- CLAUDE.md 첫 내용 -->
너는 아틀란티스 입국 심사 에이전트다.
입국 신청자 각각에 대해 제출된 서류를 검토하고 입국 허가 여부를 판정한다.
수량·서류·심사일은 strategy.md로 옮기고, 파이프라인은 3단계로 못박았다.
1
2
<!-- strategy.md 부분 내용 -->
Stage 1: PDF → 구조화 JSON / Stage 2: JSON → 규칙 검증 / Stage 3: 심사 결과 제출
위 마크다운에서 보여지는 Stage 2(검증)는 LLM이 아니라 순수 Python으로 처리하도록 했다. LLM 호출 한 번에 인식·검증·출력을 다 태우면 재현도 검증도 어렵기 때문이다.
이 문제에서 처음으로 호출당 비용을 의식했다. 같은 결과를 더 싸게 얻을 수 있는 두 가지 포인트를 발견했기 때문이다. 여권 이미지 한 장이 1654×2339픽셀인데, 이걸 긴 변 1024픽셀로 줄여 보내면(다운스케일) Claude가 인식해야 할 토큰이 약 60% 줄어든다. 글자와 사진은 그 해상도에서도 충분히 읽힌다. 30명 분량을 지금 당장 받아볼 필요가 없으면 Batch API로 한 번에 묶어 보낼 수 있고, 가격이 절반(50%)으로 떨어지는 대신 결과는 최대 24시간 안에 받는다. 두 포인트 모두 모델이나 프롬프트 내용은 그대로 두고, 호출 방식만 바꿔서 비용을 깎는 결정이다.
두 번째 문제(춘식도락메뉴 분석 챌린지)에서는 추가로 단계적 정밀화가 추가됐다. 메뉴판 528개 셀에서 OCR이 959kcal을 9591<31로 읽는 것을 확인했기 때문이다. kcal가 숫자로 오인식된 것이다. “마지막 줄 끝 4글자는 잘라낸다”는 정책을 MEMORY/feedback_calorie_kcal_tail.md에 영구 기록했다.
kcal 꼬리 정책만 적용한 1차 결과는 528칸 중 192칸(약 36%)이 맞았다. 그 후에 도메인 팩트 한 줄 “TAKE OUT 셀은 칼로리가 좌측 56% crop 바깥, 오른쪽에 있다.” 을 추가했다.
메뉴판은 일반 셀과 TAKE OUT(포장 전용) 셀이 섞여 있었는데, 일반 셀은 칼로리가 왼쪽에 있어 좌측 56%만 잘라(crop) OCR에 보내면 가격·메뉴명 같은 노이즈가 사라져 인식률이 올라갔다. 문제는 TAKE OUT 셀이었다. 칼로리가 오른쪽에 있어 같은 좌측 crop을 적용하면 칼로리 영역 자체가 잘려나가버렸다.
그래서 이미지 자르기 처리를 나눴다. TAKE OUT 셀은 자르지 말고 셀 전체를 그대로 보내되 해상도만 올려(업스케일) 인식 정밀도를 보전했고, 나머지 일반 셀은 한 줄(line) 단위로 더 잘게 쪼개 OCR에 보내 노이즈를 더 줄였다. 결과는 528칸 중 378칸(약 72%). 거의 두 배. 모델도 프롬프트도 그대로였다. 바뀐 건 입력을 자르는 단위와 거기 붙은 도메인 지식 한 줄뿐이었다. 단계적 정밀화는 알고리즘 정교화가 아니라 입력 형태의 정교화였다.
두 문제에서 얻은 가설은 단순하고 직선적이었다. 맥락을 분리하고, 단계를 쪼개고, 입력 단위에 도메인 지식을 붙이면 점수가 오른다. 1번에서 분리를, 2번에서 정밀화를 얻은 셈이다. 이제 더 좋은 점수를 받을 수 있을 줄 알았다.
그런데 3번째 프로젝트에서 점수는 절반으로 떨어졌다.
2. 첫 추락: 인계 규칙의 탄생 (40점 → 50점)
세 번째 문제(수능 경향 분석)는 수능 통계 PDF로 5개 선지를 OX 판정하는 문제였다. 자신감을 안고 들어갔지만 40점으로 떨어졌다. 8회차 중 단일 최저점. 단계적 정밀화도, 문서 분리도 그대로 들고 갔다. 원인은 입력 형식에 있었다.
문항 1의 통계 PDF를 pdfplumber로 열었더니 헤더만 나오고 숫자가 빠져있었다. extract_tables도, pypdf도 동일. page.chars로 unique chars를 찍어보고서야 알았다.
1
2
page.chars로 unique chars 출력 → 한글/특수문자만 있고 숫자 0~9가 아예 없음.
page.images 460개 — 숫자가 모두 Image XObject로 박혀 있음.
전략을 page.to_image(resolution=200)로 렌더링하고 시각 인식하는 방식으로 갈아치웠다. 왜 이 방식이 통했는지 이해하려면 PDF가 한 페이지 안에 서로 다른 표현 계층을 동시에 담을 수 있다는 점부터 짚어야 한다.
일반적인 PDF에서 글자는 텍스트 레이어에 “유니코드 코드 + 위치” 형태로 기록되고, pdfplumber나 pypdf 같은 도구는 이 레이어만 읽어 텍스트를 뽑아낸다. 그런데 이 PDF에는 별개의 계층인 Image XObject가 있었다. 이 수능 통계 PDF의 함정은 헤더의 한글 라벨은 텍스트 레이어에 있었지만, 본문의 숫자는 모두 Image XObject(그림 460개)로 박혀 있었다는 점이었다. 텍스트 추출 도구 입장에서 숫자는 글자가 아니라 그림이라 처음부터 추출 대상이 아니었던 것이다.
page.to_image()는 PDF 페이지 전체를 래스터화한다. 텍스트 레이어든 Image XObject든 벡터 도형이든, 페이지 위의 모든 요소를 한 장의 픽셀 이미지로 합성하는 작업이다. 합성된 PNG 안에서는 원래 텍스트였던 글자와 원래 그림에 박혀 있던 숫자가 더 이상 구분되지 않는다. 둘 다 그냥 같은 픽셀일 뿐이다. 이 PNG를 Claude의 Read로 넘기면 시각 인식은 데이터의 출신 계층이 아니라 눈에 보이는 픽셀 패턴에만 의존하므로, 텍스트 출신 한글이든 이미지 출신 숫자든 픽셀로 또렷이 보이기만 하면 동일하게 읽혀나온다. resolution=200은 작은 글자까지 또렷하게 보일 만큼만 해상도를 올린 값으로, 더 낮으면 픽셀이 뭉개져 인식이 깨지고 더 높이면 토큰 비용만 늘어난다.
정리하면 문제는 텍스트 추출 도구가 PDF 명세 수준의 추상(텍스트 객체 vs 이미지 객체)에서만 작동했기 때문이었다. 앞서 막연했던 직관이 여기서 입력을 의심하라는 패턴으로 정리됐다. 첫 5분에 형식부터 진단한다. extract_text 결과가 의심되면 chars/images로 한 단 더 내려가 데이터가 어느 계층에 어떻게 박혀 있는지부터 본다.
이 세션에서 처음으로 save-state.md와 next-task.md를 만들었다. 1세션 = 1문항 규약. 페이지 매핑(2022 p39~p41, 2026 p47~p49)과 1등급 인원/비율 역산 트릭을 주의사항에 남겼다. 다음 문항이 같은 PDF의 다른 표를 봐야 하니까.
(네 번째 문제는 큰 인사이트가 없어 넘어간다.)
그리고 5번째 문제에서, 점수는 81점으로 급상승했다.
3. 도약: 위임과 검증 (50점 → 81점)
다섯 번째 문제(스타트업 창업 여정: 투자의 조건)는 8회차 통틀어 점수가 가장 크게 오른 회차였다. 50점에서 81점으로. 새로 도입한 도구는 없었다. 달라진 건 서브에이전트에 일을 맡기고, 그 결과를 어떻게 신뢰할지뿐이었다.
관건은 심사관 3인이 작성한 19건의 스타트업 평가 보고서를 정독해 가점·감점·레드플래그 패턴을 뽑아내는 일이었다. 19건을 다 직접 읽으면 내 컨텍스트가 평가 텍스트로 가득 차서 정작 패턴을 뽑을 여유가 사라진다. 그래서 사례 1건만 직접 읽고 나머지는 Explore 서브에이전트에 위임했다. 위임 프롬프트엔 모든 패턴 주장 옆에 case ID를 2~3건씩 인용해라는 조건을 못박았다. 나중에 그 case ID로 사실관계를 되짚어 검증할 수 있어야 했다.
서브에이전트가 돌려준 결과는 489줄짜리 judge-profiles.md 보고서였다. 그대로 받아 쓰지 않고 7건만 골라 표본 점검(spot-check)을 돌렸다. 그 자리에서 정량 오류가 줄줄이 나왔다.
1
2
3
4
김투자 분포: 주장 D10/F4 → 실제 D9/F5
박기술 분포: 주장 C3/D5 → 실제 C2/D6
만장일치 F: 주장 6건 → 실제 3건
평균 등급: A/B/C/D/F 인데 +/− 변형으로 표기
발견된 오류는 모두 등급 분포·카운트·평균 표기 같은 수치 영역에 몰려 있었다. 반면 인용된 case ID들이 실제 등급과 일치한다는 사실은 7건 모두에서 확인됐다. 보고서가 어떤 사례를 근거로 어떤 패턴을 도출했는지에 대한 서술 부분은 출처에 충실했다는 뜻이다.
그래서 489줄짜리 보고서를 통째로 다시 쓰지 않았다. 서술은 그대로 신뢰하고, 정량 오류 네 곳만 Edit으로 골라 교체했다. 위임과 검증 — Trust-but-verify 방식이 이때 자리잡았다.
이 문제에서 얻은 교훈은 “에이전트의 출력은 여러 단계로 분해될 때만 검증할 수 있고, 검증할 수 있을 때만 신뢰할 수 있다” 이다. 에이전트가 한 번에 결론값을 출력하지 않고 분석 → 검증 단계로 여러 차례의 출력 과정을 거쳤기 때문에 결과가 좋아졌다. 분해되지 않은 출력은 통째로 믿거나 통째로 버려야 하는데, 어느 쪽도 위임의 효율을 살리지 못한다.
그런데 6번째 문제에서 점수는 다시 떨어졌다.
4. 두 번째 추락과 회복: 계획과 실행의 분리 (81점 → 56점 → 79점)
여섯 번째 문제(웹소설 플랫폼 복구하기)에서 점수는 56점으로 다시 떨어졌다. 이번엔 새 도메인이 등장한 게 아니었다. 18화짜리 웹소설 사이트의 HTML 구조 분석, 댓글 블러 메커니즘 파악, base64 디코드, 회차 ↔ BGM 매핑, 액자 사진 비교 — 6개 문항이 다 익숙한 작업이었다.
문제는 익숙한 작업이 6개 동시에 떨어진 것이었다. 한 문제에 깊이 들어갈 즈음 다른 문제 자료가 컨텍스트를 잠식한다. 익숙한 일이 익숙하게 풀리지 않는 이유다.
계획과 실행을 다른 세션에 두기
이 회차의 핵심 변화는 세션 자체를 어떻게 쓸지였다. 첫 번째 세션에서는 /plan 모드로 들어가 자료를 살펴보고 계획을 세웠다. 그리고 ExitPlanMode를 다음의 프롬프트로 막았다.
이 세션에서는 계획만 세우고 끝낼거야. 여러 세션에 거쳐 에이전트 성능을 유지할 수 있게 save-state.md와 next-task.md를 만들자.
계획은 이 세션에서, 실행은 다음 세션에서. 둘이 같은 컨텍스트 윈도우를 함께 쓰지 않게 한다는 게 핵심이었다. 계획을 세우면서 채워진 컨텍스트(자료 인벤토리, 가설, 의사결정 옵션)는 실행 단계에서 다시 사고를 위한 여유 공간으로 비워져야 한다.
하지만 한 가지 실수를 했다. 1세션 = 1문항 규칙을 깨트린 것이다.
Plan과 Execution은 분리했지만, 실행 세션 안에서 다시 6개 문항을 한꺼번에 몰아넣었다. Q1 모순 검출, Q2 위키 표제어, Q3 BGM 청취, Q4 빈 댓글, Q5 스포일러, Q6 액자 비교 — 진행 시점도, 사용자 협업 필요 여부도 다른 6개가 한 컨텍스트에서 서로의 자리를 잠식했다. 어느 하나에 깊이 들어갈 때마다 다른 5개 자료가 컨텍스트 가장자리를 차지했다.
그 실수는 에이전트 성능에 확실히 영향을 주었다. 점수가 56점까지 떨어졌기 때문이다.
“익숙한 작업이니까 컨텍스트는 신경 쓰지 말고 한 번에 풀어볼까” 라고 방심했던 게 낳은 결과였다.
(일곱 번째 문제는 큰 인사이트가 없어 넘어간다.)
5. 정점: 쌓인 전략들의 콜라보레이션 (79점 → 92점)
여덟 번째 문제(Art Detective)는 100장의 그림을 위작/진품으로 분류하는 일이었다. 거기에 동물이 등장한 작품 카운트, 3개 스토리 단서와의 매칭, 작가·제목·소장처 식별이 따라붙었다. 8문제 중 가장 높은 92점을 받았다.
흥미로운 건 이 회차에서 새로 만들어낸 기법이 거의 없다는 점이다. 1~7번에서 쌓인 전략들이 처음으로 시너지를 냈을 뿐이다.
7세션 하네스
Opus 4.7 + Vision로 100장을 판독하는 작업을 세션 단위로 쪼갰다. 그래서 5세션(20장 × 5)으로 1차 분류를 끝내고, S6에서 uncertain 후보들을 재검증하고, S7에서 답안을 조립하는 7세션 하네스가 만들어졌다.
| 세션 | 작업 | 입력·출력 |
|---|---|---|
| S0 | 계획 수립 + 하네스 셋업 | save-state.md, next-task.md, 빈 predictions.jsonl |
| S1~S5 | 1차 분류 (각 20장) | predictions.jsonl에 append-only |
| S6 | uncertain 11건 재검증 | predictions.v1.jsonl 백업 후 라인 단위 갱신 |
| S7 | 답안 조립 (Q1~Q4) | assemble_answers.py 한 번 실행 |
1세션 = 1문항 규약이 여기서 대칭 구조로 진가를 발휘한 것이다. 5세션은 부팅 절차도 동일하다.
next-task.mdReadsave-state.mdReadpredictions.jsonlRead- 작업
- 마무리에
next-task.md를 다음 세션용으로 갱신
한 세션이 끝나면 다음 세션의 시작이 자동으로 셋업된다. 부팅에 들이는 컨텍스트가 매번 같은 양으로 일정해진다.
Confidence 수치화와 자동 격리
이전 문제 풀이와 다르게 새로 추가된 것은 가시화였다. 각 판독 결과에 0~1 신뢰도를 함께 기록한다.
이 신뢰도가 임계치 미만인 것은 output/uncertain.txt로 자동 격리되어 S6 재검증의 입력이 된다.
JSON 스키마로 판독 결과 자료구조 고정
판독 결과는 11개 필드로 고정시킨 JSON 스키마로 저장했다.
1
2
id, verdict, confidence, artist, title, museum,
has_animal, animal_notes, story_match, ai_tells, rationale
매 세션 마무리에 uv run python으로 스키마 검증을 돌렸다. 3장에서 자리잡은 Trust-but-verify가 한 단계 더 내려간 것이다. 3장에선 위임 프롬프트에 “case ID를 2~3건 인용해라”를 박아 검증 가능한 출력을 만들도록 시켰다면, 8장에서 산출 파일 자체가 11개 필드를 모두 채워야 통과하도록 강제했다. 검증 시점이 “답을 어떻게 만들지 지시할 때”에서 “답이 어떤 모양이어야 하는지 결정할 때”로 옮겨간 것이다. 실제로 한 라인에 스키마에 없는 필드가 추가되었을 때, 검증 단계에서 즉시 잡혀 같은 세션 안에서 보정됐다.
1줄로 줄어든 CLAUDE.md
이 운영 모델 위에서 CLAUDE.md는 단 1줄로 줄었다.
에이전트가 컨텍스트 윈도우를 최대한 활용할 수 있는 단위로 작업을 쪼개서 여러 세션에 거쳐서 작업을 진행한다.
1장에서 본 CLAUDE.md 11줄(역할·판정 원칙·JSON 형식) 내용은 어디로 갔을까? 모두 인계 파일로 옮겨졌다.
- 역할은
next-task.md의 명령서로 — “S5: 81~100.jpg 분류, append-only, 마무리에 next-task를 S6으로 갱신”. - JSON 스키마는 분석 기준 섹션으로 — 11개 필드 정의와 confidence 임계값.
- 누적 상태는
save-state.md로 — Progress 표·Findings·Uncertain 리스트·Session log.
CLAUDE.md엔 어떻게 일할지에 대한 원칙만 남았다.
결론 — 아프니까 청춘이다.
이 경험을 통해 가장 많이 배운 건 점수가 급락했을 때 였다.
2번째 문제와 6번째 문제의 점수 급락은 컨텍스트 엔지니어링의 중요성을 알려줬다. save-state.md/next-task.md 두 파일. 하나의 세션 안에서 모든 걸 끝내지 않고 컨텍스트 윈도우 단위로 작업을 쪼개는 걸 하지 않으면 성능은 급감한다.
그리고 한 가지가 더 추가되었다. — 구체 지시는 인계 파일로 빠지고, CLAUDE.md엔 메타 운영 원칙만 남는다.
이외의 배운 점을 총정리하면
- 에이전트가 작업동안 기억을 유지해야 하는 건
MEMORY/*.md에 남겨라. - 필요할 때마다 회상할 필요가 있는 건 어울리는 이름을 붙여
*.md로 기록하라. - 계획과 실행의 프롬프트는 별도의 컨텍스트에 쌓아라. 한 가지 관심사로 집중된 컨텍스트의 가치가 더 높다.
- 1번, 2번, 3번은 결국 컨텍스트를 잘 활용하기 위한 것. 컨텍스트를 비울 수 있게 세션 간 인계파일을 유지하라.
- 규칙을 프롬프트가 아닌 스키마로 대체할 수 있으면 그렇게 하라. 스키마 검증은 코드로 돌릴 수 있어 컨텍스트를 절약한다.
- 에이전트에게 작업을 위임할 땐 출력이 분해될 수 있게 설계하라. 분해되지 않은 출력은 통째로 믿거나 통째로 버려야 하는데, 어느 쪽도 위임의 효율을 살리지 못한다.
- 출력을 분해할 땐 앞의 출력이 뒤의 검증에 이용될 수 있게 설계하라.
- CLAUDE.md에는 에이전트가 항상 기억해야할 것만 남기자.
정리하면서 느끼건데, 정말 많이 배웠다. 정리하는 과정도 괴롭고 아팠지만 아프니까 청춘이고 성장한다. 이래서 아픈 걸 즐기게 되는건가(??) 하는 어이없는 생각을 하며 웃어본다.