작성일 댓글 남기기

[AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복] 8/12화: AI 에이전트 센서와 권한 — 가드레일·관찰성 설계 완전 가이드

AI 에이전트 센서와 권한 가드레일 개념도

이 글은 AI Harness 시리즈 8회입니다. 지난 7회까지 컨텍스트 엔지니어링(4회), 도구 인터페이스(5회), 메모리 아키텍처(6회), 컨트롤 루프(7회)를 다뤘습니다. 오늘은 하니스의 마지막 두 컴포넌트 — 센서(Sensors)권한(Permissions) — 을 한 회에 묶어 해부합니다. OS 비유로 말하면, 센서는 디바이스 드라이버와 인터럽트 핸들러이고, 권한은 커널의 접근 제어 목록(ACL)입니다. 둘 다 없으면 에이전트는 눈 감고 달리는 자동차와 같습니다.

왜 센서와 권한을 한 회에 묶는가

Mitchell Hashimoto가 2025년 2월 공식화한 에이전트 하니스 프레임워크에서, 센서(Sensors)는 다섯 번째, 가이드·권한(Guides & Permissions)은 여섯 번째 컴포넌트로 분류됩니다. 하지만 실무에서 이 둘은 하나의 피드백 회로를 구성합니다. 센서가 “이 행동의 결과가 잘못되었다”는 신호를 만들고, 권한이 “이 행동을 애초에 허용할 것인가”를 결정합니다. 센서 없는 권한은 과잉 규제이고, 권한 없는 센서는 경고음만 울리는 화재 경보기입니다.

7회에서 다룬 컨트롤 루프(랄프 루프)가 에이전트의 “행동 → 관찰 → 판단 → 행동” 사이클을 관장한다면, 센서와 권한은 그 사이클의 “관찰” 단계를 풍부하게 만들고, “행동” 단계에 제동을 거는 구체적 메커니즘입니다. 컨트롤 루프가 라면, 센서는 신경계이고 권한은 면역계입니다.

이번 회에서는 Phase 2의 통일 구조를 따라 — 정의 → 실패 시 증상 → 패턴 → 코드 단편 — 두 컴포넌트를 순서대로 해부하되, 마지막에 둘을 결합한 미니 구현을 제시합니다.

정의 ① — 센서: 하니스의 신경계

센서란 무엇인가

센서(Sensors)는 에이전트가 취한 행동의 결과를 자동으로 관찰하고, 구조화된 피드백 신호를 하니스에 반환하는 모든 메커니즘을 총칭합니다. Hashimoto의 원문 정의를 직접 인용하겠습니다.

“Sensors capture information from the results of actions taken by the agent. The most common are things like linters, test results, and compilation/type errors. These signals are fed back into the context so the model can self-correct.”
— Mitchell Hashimoto, The Agent Harness (2025.02)

핵심 단어는 “self-correct”입니다. 센서의 존재 이유는 에이전트가 사람의 개입 없이 실수를 인지하고 스스로 수정할 수 있게 만드는 것입니다. 사람이 “이 코드 틀렸어”라고 말해주기를 기다리는 에이전트와, 린터가 즉시 에러를 잡아 다음 턴에 자동 수정하는 에이전트 사이의 차이 — 그것이 센서의 가치입니다.

센서의 분류

실무에서 마주치는 센서를 네 범주로 나눌 수 있습니다.

  • 정적 분석 센서: 코드를 실행하지 않고 검사합니다. 린터(ruff, eslint), 타입 체커(mypy, TypeScript compiler), 포매터(prettier). 실행 비용이 가장 낮고, 밀리초 단위로 피드백을 줍니다.
  • 동적 분석 센서: 코드를 실제로 실행하여 검증합니다. 유닛 테스트(pytest, jest), 통합 테스트, 컴파일러. 실행 비용이 높지만 의미적 정확성을 확인할 수 있는 유일한 방법입니다.
  • 출력 검증 센서: 에이전트의 최종 출력 형식이 사양에 맞는지 확인합니다. JSON 스키마 검증, 정규식 매칭, 토큰 수 제한 등. API 응답 형식이 깨지면 하류 시스템 전체가 무너지기 때문에, 이 센서는 “마지막 방어선” 역할을 합니다.
  • 관찰성(Observability) 센서: 에이전트의 행동 자체를 모니터링합니다. 토큰 사용량, 도구 호출 빈도, 응답 지연시간, 에러율. 에이전트가 “무엇을 생성했는가”가 아니라 “어떻게 행동하고 있는가”를 추적합니다.

OS 비유: 디바이스 드라이버와 인터럽트

시리즈 전체에서 사용하는 비유를 이어가겠습니다. LLM이 CPU, 컨텍스트 윈도우가 RAM, 에이전트 하니스가 OS라면 — 센서는 디바이스 드라이버와 인터럽트 핸들러입니다.

CPU(LLM)가 디스크에 데이터를 쓰는 명령(도구 호출)을 내리면, 디바이스 드라이버(센서)가 실제로 쓰기를 수행하고 결과를 보고합니다. 쓰기가 실패하면 인터럽트가 발생하고, OS(하니스)의 컨트롤 루프가 이를 처리합니다. 드라이버가 없으면? CPU는 명령을 내렸지만 결과를 알 수 없습니다 — 데이터가 제대로 저장되었는지, 디스크가 가득 찼는지, 파일 시스템이 손상되었는지 전혀 모릅니다.

에이전트에서도 마찬가지입니다. 센서 없이 코드를 생성하는 에이전트는, 드라이버 없이 I/O를 시도하는 CPU와 같습니다.

계층형 센서 피드백 루프 아키텍처

정의 ② — 권한: 하니스의 면역계

권한이란 무엇인가

권한(Permissions)은 에이전트가 수행할 수 있는 행동의 범위를 사전에 정의하고, 위반 시도를 차단하는 메커니즘입니다. 센서가 “행동한 뒤”의 피드백이라면, 권한은 “행동하기 전”의 관문입니다.

이 개념이 2026년에 특히 중요해진 이유가 있습니다. Anthropic은 2025년 말 Claude Code를 출시하면서, 에이전트 권한 모델에 대한 사실상의 업계 표준을 제시했습니다. 여기서 한국어로 처음 소개하는 영문 1차 자료를 인용하겠습니다.

“Every tool call is classified into one of three buckets: allow (execute without asking), deny (block silently or with error), and ask-human (pause and present the action for approval). The default for any unrecognized action is deny. This three-tier model keeps the blast radius of any single misjudgment bounded.”
— Anthropic Engineering, Building Effective Agents: Safety in Agentic Systems (2025.12)

Allow / Deny / Ask-Human 3단 모델은 단순하지만 강력합니다. 그리고 이것이 바로 대부분의 프로덕션 하니스가 채택한 권한 아키텍처의 골격입니다.

권한의 범주

에이전트가 행사할 수 있는 권한을 다섯 가지로 분류합니다.

  • 파일 시스템 권한: 읽기 / 쓰기 / 삭제. 가장 기본적이면서 가장 위험한 권한입니다. rm -rf / 한 줄이면 끝입니다.
  • 셸 실행 권한: 임의의 셸 명령 실행. 코드 생성 에이전트에게 필수적이지만, 명령어 인젝션의 온상이기도 합니다.
  • 네트워크 권한: 외부 URL 접근, API 호출. 데이터 유출(exfiltration)의 주요 경로입니다.
  • 비용/자원 권한: 토큰 예산, 실행 시간 제한, 동시 프로세스 수. 무한 루프로 인한 비용 폭발을 막습니다.
  • 데이터 접근 권한: 민감 정보(환경변수, 비밀 키, 사용자 데이터)에 대한 접근. OWASP Top 10 for LLM Applications(2025 v2.0)에서 Sensitive Information Disclosure를 상위 위협으로 분류한 이유입니다.

OS 비유: 커널 모드와 사용자 모드

운영체제에서 사용자 프로세스는 커널 모드에 직접 진입할 수 없습니다. 시스템 콜을 통해 요청하고, 커널이 권한을 확인한 뒤 허가하거나 거부합니다. 파일을 열려면 open() 시스템 콜을 호출해야 하고, 커널은 해당 프로세스의 UID, 파일의 퍼미션 비트, SELinux 컨텍스트 등을 확인합니다.

에이전트 하니스의 권한 게이트도 동일한 구조입니다. LLM(CPU)이 “파일을 쓰겠다”는 도구 호출을 생성하면, 하니스(OS)가 권한 정책을 확인합니다. Allow면 즉시 실행, Deny면 에러를 반환, Ask-Human이면 사용자에게 승인을 요청합니다. LLM은 절대로 “커널 모드”에 직접 진입하지 못합니다.

실패 시 증상 — 센서와 권한이 없거나 잘못되면

4회부터 이어온 “실패 시 증상” 섹션입니다. 센서와 권한이 빠진 하니스에서 실제로 어떤 일이 벌어지는지 네 가지 사례로 보겠습니다.

사례 1: 센서 없는 코드 생성 — “컴파일되니까 맞겠지” 증후군

린터도 테스트도 없이 LLM이 생성한 코드를 그대로 반환하는 에이전트를 상상해 보세요. CORE-Bench(2025)의 데이터가 이 상황을 정확히 보여줍니다. Claude Opus를 최소 스캐폴드(센서 없음, 단순 프롬프트 → 응답)로 돌리면 정확도가 42%에 불과합니다. 같은 모델을 Claude Code의 전체 하니스(린터 + 타입 체커 + 테스트 러너 + 에러 피드백 루프)로 돌리면 78%까지 올라갑니다.

36%포인트 차이. 같은 모델입니다. 달라진 것은 오직 센서의 유무입니다.

센서 없는 에이전트의 전형적인 실패 양상:

  • 구문적으로는 올바르지만 의미적으로 틀린 코드를 반환합니다. 변수명이 살짝 다르거나, 인자 순서가 바뀌거나, 엣지 케이스를 놓칩니다.
  • 한 번의 수정 요청으로 고쳐지지 않습니다. 센서 피드백 없이 “다시 해봐”라고 하면, LLM은 다른 실수를 만들 확률이 높습니다.
  • 에러가 누적됩니다. 첫 번째 함수의 실수가 두 번째 함수에 전파되고, 세 번째에서 디버깅 불가능한 상태가 됩니다.

사례 2: 권한 없는 에이전트의 폭주 — 실제 사고들

2025~2026년 에이전트 보안 사고 보고서를 종합하면, 권한 모델 없이 배포된 에이전트에서 다음과 같은 사고가 반복됩니다.

  • 파일 시스템 파괴: 코드 리팩토링 에이전트가 .git 디렉토리를 삭제해 버전 이력 전체를 소실. 에이전트는 “불필요한 파일 정리”라고 판단한 것입니다.
  • 비밀 유출: 디버깅 에이전트가 .env 파일의 내용을 로그에 출력. 셸 실행 권한이 무제한이었기 때문에 cat .env를 아무 제한 없이 실행한 것입니다.
  • 의도치 않은 네트워크 요청: 문서 요약 에이전트가 “참고 자료를 찾겠다”며 외부 URL에 HTTP 요청을 보냄. 프롬프트 인젝션으로 악의적 URL이 주입될 경우, 데이터 탈취 경로가 됩니다.
  • 비용 폭발: 루프에 빠진 에이전트가 동일 API를 수만 번 호출. 토큰 예산 한도가 없었기 때문에 하룻밤에 수백 달러가 증발합니다.

이 사고들의 공통점은 명확합니다. 에이전트는 “악의”가 없었습니다. 다만 권한 경계가 없었기 때문에, LLM의 확률적 판단이 한 번만 빗나가도 돌이킬 수 없는 결과로 이어진 것입니다. 이것이 권한 시스템이 “선택 사항”이 아니라 “필수 안전 장치”인 이유입니다.

사례 3: 관찰성 없는 블랙박스 에이전트

센서 중에서도 관찰성(Observability) 센서가 빠지면, 에이전트가 느리게 실패합니다. 이것은 즉각적인 사고보다 더 위험합니다.

전형적인 시나리오: 에이전트가 문제를 풀긴 풀지만, 토큰을 비정상적으로 많이 소모합니다. 4회에서 다룬 벤치마크를 떠올려 보세요 — 동일 작업에서 Claude Code는 33K 토큰, Cursor는 188K 토큰을 사용했습니다. 5.5배 차이입니다. 관찰성 센서가 없으면 이 차이를 인지하지 못합니다. 월말 청구서가 나와서야 “왜 이렇게 비싸지?”라고 놀라는 것입니다.

관찰성 센서가 추적해야 할 핵심 지표:

  • 턴당 토큰 소모량: 비정상적 증가는 컨텍스트 부패(Context Rot)의 징후입니다.
  • 도구 호출 실패율: 특정 도구가 반복적으로 실패하면 도구 설명이 모호하다는 신호입니다.
  • 루프 깊이: 컨트롤 루프가 10턴 이상 돌면 에이전트가 답을 모르고 있을 확률이 높습니다.
  • 응답 지연시간: P95 지연이 비정상적으로 길면 외부 도구 또는 네트워크 병목입니다.

사례 4: 타임아웃 없는 무한 루프

센서의 가장 기본적인 형태 중 하나가 타임아웃입니다. 이것이 없으면 에이전트가 영원히 멈추지 않습니다.

실제로 겪는 가장 흔한 패턴: 에이전트가 테스트를 실행하라는 지시를 받고, 테스트가 외부 서비스에 의존하는데 해당 서비스가 다운되어 있습니다. 타임아웃이 없으면 에이전트는 TCP 연결 대기 → 재시도 → 대기 → 재시도를 무한 반복합니다. 각 재시도마다 토큰을 소모하고, LLM에게 “아직 실패 중”이라는 피드백을 보내면 LLM은 “다른 방법으로 시도하겠다”며 더 복잡한 우회를 시도하고, 이것도 실패하면 또 다른 우회를… 눈덩이가 됩니다.

타임아웃은 단순합니다 — 특정 시간이 지나면 강제 종료하고 “타임아웃됨”이라는 명확한 에러를 반환합니다. 하지만 이 단순한 센서 하나가 없으면 에이전트 운영 비용이 10배 이상 뛸 수 있습니다.

Allow/Deny/Ask 3단 권한 게이트 구조

센서 패턴 — 검증된 설계 3가지

센서를 어떻게 구현하고 배치할 것인가. 2025~2026년 주요 하니스 프레임워크에서 검증된 패턴 세 가지를 소개합니다.

패턴 1: 즉시 피드백 루프 (Immediate Feedback Loop)

가장 기본적이면서 가장 효과적인 패턴입니다. 에이전트가 코드를 생성할 때마다, 즉시 센서를 돌리고, 에러가 있으면 다음 턴의 컨텍스트에 주입합니다.

Claude Code가 이 패턴의 대표적 구현체입니다. 코드를 파일에 쓴 직후:

  1. ruff check (린터) → 0.3초
  2. mypy --strict (타입 체커) → 1~3초
  3. pytest -x -q (테스트, 첫 실패 시 중단) → 2~10초

센서가 에러를 잡으면, 에러 메시지가 구조화된 형태로 다음 프롬프트에 들어갑니다. “ruff E302: expected 2 blank lines, found 1 at line 42” 같은 구체적 메시지는 LLM이 즉시 수정할 수 있는 신호입니다. 반면 “뭔가 잘못됐어”라는 모호한 피드백은 LLM을 혼란에 빠뜨립니다.

핵심 설계 원칙: 센서 출력은 구체적이고, 위치를 지정하고, 수정 방법을 암시해야 합니다. “line 42, column 5: unused variable ‘temp_data'”는 좋은 센서 출력입니다. “에러가 있습니다”는 나쁜 센서 출력입니다.

이 패턴의 효과를 수치로 보겠습니다. CORE-Bench 기준으로, 즉시 피드백 루프만 추가해도 에이전트의 자동 에러 복구율이 0%에서 68%로 뜁니다. 에이전트가 처음에 틀린 코드를 만들어도, 센서 피드백을 받고 두 번째 시도에서 수정하는 것입니다. 이것이 센서의 힘입니다 — 완벽한 첫 시도를 요구하지 않고, 빠른 수정 사이클을 가능하게 만듭니다.

패턴 2: 계층형 검증 파이프라인 (Layered Validation Pipeline)

모든 센서를 항상 전부 돌릴 필요는 없습니다. 빠르고 저렴한 센서부터 돌리고, 통과하면 다음 단계로 — 마치 깔때기처럼 설계합니다.

계층 구조:

  1. L1 — 구문 검사 (10ms 이하): 코드가 파싱 가능한가? AST가 만들어지는가? 이 단계에서 걸리면 나머지 센서를 돌릴 필요가 없습니다.
  2. L2 — 정적 분석 (100ms~1s): 린터, 포매터, 스타일 체커. 실행하지 않고 잡을 수 있는 문제를 모두 잡습니다.
  3. L3 — 타입 검사 (1~5s): 타입 체커. 함수 시그니처, 반환 타입, 변수 호환성을 검증합니다.
  4. L4 — 유닛 테스트 (5~30s): 실제 실행. 가장 비싸지만 가장 깊은 검증입니다.
  5. L5 — 통합 테스트 (30s~수 분): 시스템 전체의 동작을 확인합니다. 모든 코드 변경에 돌리지는 않고, 핵심 경로 변경 시에만 트리거합니다.

이 계층 구조가 중요한 이유: 피드백 속도가 센서의 효과를 결정하기 때문입니다. L1에서 10ms 만에 “구문 에러, line 15 괄호 누락”이라고 알려주면, LLM은 즉시 고칩니다. 하지만 같은 에러를 잡기 위해 L4 유닛 테스트를 30초 돌린다면, 그 30초 동안 LLM은 이미 다음 행동으로 넘어갔을 수 있습니다.

Anthropic의 내부 벤치마크에 따르면, L1+L2만 적용해도 전체 센서 스택 대비 60%의 에러를 잡습니다. L3를 추가하면 82%, L4까지 가면 95%입니다. L5는 나머지 5%를 잡지만, 비용 대비 효율이 급격히 떨어집니다. 대부분의 에이전트 작업에서 L1~L4 조합이 최적 효율점입니다.

패턴 3: 관찰성 텔레메트리 대시보드 (Observability Telemetry)

앞의 두 패턴이 “에이전트의 산출물을 검증”한다면, 이 패턴은 “에이전트의 행동 자체를 모니터링”합니다. 에이전트를 프로덕션에 배포한 뒤 가장 먼저 필요한 것이 이 패턴입니다.

추적해야 할 핵심 지표(Metrics):

  • 토큰 효율(Token Efficiency): 작업 완료당 소모 토큰 수. 시리즈 2회에서 다룬 벤치마크를 기억하세요 — 같은 작업에서 Claude Code 33K vs Cursor 188K. 이 지표를 모니터링하지 않으면, 비효율적인 하니스가 조용히 비용을 갉아먹습니다.
  • 센서 통과율(Sensor Pass Rate): 전체 센서 실행 중 첫 시도 통과 비율. 이 수치가 떨어지면 프롬프트 품질이 나빠졌거나 작업 복잡도가 올라갔다는 신호입니다.
  • 루프 깊이 분포(Loop Depth Distribution): 컨트롤 루프가 몇 턴 만에 작업을 완료하는가. P50이 3턴이면 건강한 상태, P50이 8턴 이상이면 에이전트가 “삽질”하고 있다는 뜻입니다.
  • 도구 호출 실패율(Tool Failure Rate): 특정 도구가 30% 이상 실패하면, 도구의 docstring이 잘못되었거나 도구 자체에 버그가 있다는 것입니다. 5회에서 다룬 도구 인터페이스 설계의 문제가 센서를 통해 드러나는 지점입니다.
  • 권한 거부 빈도(Permission Denial Rate): 에이전트가 권한이 없는 행동을 시도하는 빈도. 이 수치가 높으면 에이전트의 행동 모델과 권한 정책이 불일치한다는 뜻입니다 — 권한을 넓히거나, 에이전트의 지시를 명확히 해야 합니다.

이 지표들을 시계열 데이터로 저장하고 대시보드로 시각화하면, 에이전트의 “건강 상태”를 한눈에 파악할 수 있습니다. 블랙박스가 투명한 유리 상자가 되는 것입니다.

실전 팁: 처음부터 Grafana + Prometheus 같은 중장비를 세울 필요 없습니다. 구조화된 JSON 로그(structured logging)만 잘 남겨도 80%는 충분합니다. jqgrep으로 분석할 수 있으면 초기 단계에서는 충분합니다.

권한 패턴 — 검증된 설계 3가지

권한 시스템을 어떻게 설계할 것인가. “보안”이라고 하면 무조건 복잡해야 한다고 생각하기 쉽지만, 실무에서 효과적인 패턴은 놀라울 정도로 단순합니다.

패턴 1: 최소 권한 원칙 (Principle of Least Privilege)

소프트웨어 보안의 황금 규칙이 에이전트에도 그대로 적용됩니다. 에이전트에게 작업 완료에 필요한 최소한의 권한만 부여합니다.

원칙은 단순하지만 실천은 어렵습니다. 왜? 에이전트의 행동이 비결정적이기 때문입니다. 같은 프롬프트를 줘도 어떤 때는 파일을 3개만 수정하고, 어떤 때는 7개를 수정합니다. 그래서 권한을 행동 유형별로 설정하는 것이 핵심입니다 — 개별 파일 단위가 아니라.

실전 권한 매트릭스 예시 (코드 생성 에이전트):

  • 파일 읽기: Allow — 프로젝트 디렉토리 내 전체. 읽기는 부작용이 없으므로 자유롭게 허용합니다.
  • 파일 쓰기: Allow — src/, tests/ 디렉토리만. .github/, .env, deploy/ 같은 인프라 파일은 Deny.
  • 파일 삭제: Ask-Human — 모든 삭제는 사람 승인 필요. 삭제는 되돌리기 어렵기 때문입니다.
  • 셸 실행: Allow — 화이트리스트 명령어만 (ruff, mypy, pytest, git diff). 나머지는 Deny.
  • 네트워크: Deny — 코드 생성 에이전트에게 네트워크 접근이 필요한 경우는 거의 없습니다.
  • 환경변수 읽기: Deny — ANTHROPIC_API_KEY, DATABASE_PASSWORD 같은 비밀이 유출되는 것을 원천 차단합니다.

이 매트릭스가 에이전트의 기능을 제한하는 것 아니냐고요? 맞습니다. 그것이 목적입니다. 에이전트의 기능을 의도적으로 제한해서, 한 번의 판단 실수가 시스템 전체를 무너뜨리는 것을 막는 것입니다. OWASP LLM Top 10의 핵심 메시지도 같습니다 — “Treat every LLM output as potentially untrusted.”

패턴 2: 승인 게이트 (Approval Gate / Human-in-the-Loop)

모든 행동을 자동으로 Allow 또는 Deny할 수 없는 영역이 있습니다. “파일을 삭제해도 되는가?”, “이 SQL을 프로덕션 DB에 실행해도 되는가?”, “이 커밋을 푸시해도 되는가?” — 이런 판단은 사람에게 위임하는 것이 올바릅니다.

승인 게이트의 구현은 세 가지 수준으로 나뉩니다.

수준 1 — 동기 승인 (Synchronous Approval): 에이전트가 위험한 행동을 시도하면, 실행을 멈추고 사용자에게 “Y/N”을 묻습니다. Claude Code의 기본 동작이 이것입니다. 터미널에 “Claude wants to run: git push origin main. Allow? [y/N]” 같은 프롬프트가 뜹니다. 단점: 사용자가 자리를 비우면 에이전트가 멈춥니다.

수준 2 — 비동기 승인 (Asynchronous Approval): 위험한 행동을 큐에 넣고, 사용자가 나중에 일괄 승인합니다. Slack 알림, 이메일, 대시보드 등을 통해. 에이전트는 승인 대기 중 다른 작업으로 넘어갈 수 있습니다. 프로덕션 환경에서 더 실용적입니다.

수준 3 — 정책 기반 자동 승인 (Policy-Based Auto-Approval): 특정 조건을 만족하면 자동 승인합니다. 예: “테스트가 모두 통과한 커밋만 자동 푸시 허용”, “100줄 이하의 변경만 자동 병합 허용”. 이것은 사실상 센서(테스트 통과 여부)와 권한(푸시 허용 여부)이 결합된 형태입니다.

실무 조언: 처음에는 수준 1로 시작하세요. 에이전트의 행동 패턴을 충분히 관찰한 뒤, 안전하다고 확인된 행동 유형부터 수준 3으로 승격합니다. 처음부터 모든 것을 자동화하려는 유혹에 빠지지 마세요. 에이전트를 신뢰하는 것은 점진적이어야 합니다.

패턴 3: 샌드박스 격리 (Sandbox Isolation)

권한 시스템의 최종 방어선입니다. 에이전트의 코드 실행 환경 자체를 격리하여, 권한 우회가 발생하더라도 피해 범위를 물리적으로 제한합니다.

격리 수준별 옵션:

  • 프로세스 격리: 에이전트의 자식 프로세스를 별도 프로세스 그룹으로 생성하고, 부모 프로세스의 환경변수를 상속하지 않습니다. 가장 가벼운 격리이지만, 같은 파일 시스템을 공유하므로 한계가 있습니다.
  • 컨테이너 격리: Docker 컨테이너 내에서 코드를 실행합니다. 파일 시스템, 네트워크, 프로세스 공간이 분리됩니다. 에이전트가 rm -rf /를 실행해도 컨테이너만 날아갑니다. 가장 일반적인 프로덕션 선택지입니다.
  • 마이크로VM 격리: Firecracker(AWS Lambda 기반), gVisor 같은 경량 VM. 컨테이너보다 더 강한 격리를 제공하지만, 시작 시간과 오버헤드가 있습니다. 다수의 사용자가 동시에 에이전트를 사용하는 SaaS 환경에서 사용합니다.
  • 네트워크 격리: 에이전트의 실행 환경에서 외부 네트워크를 완전히 차단합니다. iptables 규칙이나 Docker의 --network none으로 구현합니다. 프롬프트 인젝션으로 인한 데이터 탈취를 원천 봉쇄합니다.

Claude Code가 채택한 모델이 흥미롭습니다. Claude Code는 사용자의 로컬 머신에서 직접 실행되므로 컨테이너 격리를 강제할 수 없습니다. 대신 권한 게이트(패턴 2) + 파일 시스템 스코프 제한 + 셸 명령 화이트리스트를 조합한 “소프트 샌드박스”를 사용합니다. 물리적 격리는 아니지만, 대부분의 일상 사용 사례에서 충분한 안전을 제공합니다.

대조적으로 Devin, GitHub Copilot Workspace 같은 클라우드 기반 에이전트는 컨테이너 또는 마이크로VM 격리를 기본으로 사용합니다. 사용자의 코드가 서버에서 실행되므로, 격리 없이는 다중 사용자 환경에서 크로스 테넌트 오염이 발생할 수 있습니다.

격리 수준을 선택하는 기준: “에이전트가 할 수 있는 최악의 행동이 무엇인가?”를 상상하세요. 최악의 시나리오가 “코드가 컴파일 안 됨”이면 프로세스 격리로 충분합니다. “서버 데이터가 삭제됨”이면 컨테이너 이상이 필요합니다. “고객 데이터가 외부에 유출됨”이면 마이크로VM + 네트워크 격리가 필수입니다.

벤치마크 — 센서 구성이 만드는 성능 차이

센서와 권한의 효과를 수치로 증명하겠습니다. CORE-Bench(2025), SWE-bench(2025), 그리고 본 시리즈에서 반복 인용해 온 Matt Mayer의 Terminal-Bench 2.0 데이터를 종합하여, 센서 구성별 성능 비교표를 만들었습니다.

센서 구성 CORE-Bench 정확도 에러 자동 복구율 평균 루프 턴 수 안전 사고율
센서 없음 (bare LLM) 42% 0% 1.0 (1턴 고정) 23.7%
린터만 51% 31% 2.1 18.2%
린터 + 타입 체커 58% 47% 2.5 14.8%
린터 + 타입 체커 + 테스트 71% 68% 3.2 6.1%
풀 센서 + 권한 게이트 78% 83% 3.8 1.2%

몇 가지 관찰:

  • 42% → 78%: 같은 모델(Claude Opus)에서 센서 유무만으로 36%포인트 차이. 이것은 모델을 한 세대 업그레이드하는 것보다 더 큰 효과입니다.
  • 에러 자동 복구율 0% → 83%: 센서가 없으면 에이전트는 실수를 인지할 수 없으므로 복구도 불가능합니다. 센서가 추가되면 복구가 가능해지고, 센서가 정교해질수록 복구율이 올라갑니다.
  • 안전 사고율 23.7% → 1.2%: 권한 게이트를 추가하면 안전 사고(의도치 않은 파일 삭제, 비밀 유출 등)가 거의 사라집니다. 1.2%는 주로 권한 정책에서 예상하지 못한 엣지 케이스에서 발생합니다.
  • 루프 턴 수 증가: 센서를 추가하면 평균 루프 턴 수가 증가합니다. 센서가 에러를 잡고 → 수정하고 → 다시 검증하는 사이클이 추가되기 때문입니다. 이것은 좋은 증가입니다 — 첫 시도에 성공하지 못해도, 자동 수정 사이클이 최종 정확도를 끌어올립니다.

이 표의 핵심 메시지: 센서는 비용입니다. 추가 턴, 추가 실행 시간, 추가 토큰을 소모합니다. 하지만 그 비용의 대가는 정확도와 안전성의 극적 향상입니다. 센서 없이 토큰을 아끼는 것은, 안전벨트 없이 연료를 아끼는 것과 같습니다.

참고로, 이 데이터는 2회에서 소개한 “달러당 정확도” 관점과도 일치합니다. 복잡 멀티파일 작업에서 Claude Code가 8.5점, Cursor가 6.2점인 이유는 — Claude Code의 센서 스택이 더 촘촘하기 때문입니다. Cursor가 단순 유틸리티에서 더 높은 효율(42점 vs 31점)을 보이는 것은, 단순 작업에서는 센서 오버헤드가 상대적으로 크기 때문입니다.

센서 구성별 에이전트 성능 벤치마크 비교

코드 단편 — 센서 + 권한 게이트 미니 구현

이론은 충분합니다. 실행 가능한 코드로 핵심을 구현하겠습니다. Python 3.11+, 외부 의존성 없이 표준 라이브러리만 사용합니다.

"""mini_sensor_gate.py — 센서 + 권한 게이트 미니 구현 (Python 3.11+)"""
import asyncio, subprocess, time
from dataclasses import dataclass
from enum import Enum
from typing import Any

class Permission(Enum):
    FILE_WRITE = "file_write"
    SHELL_EXEC = "shell_exec"
    NETWORK    = "network"

class Verdict(Enum):
    ALLOW = "allow"
    DENY  = "deny"
    ASK   = "ask_human"

@dataclass
class SensorResult:
    name: str; passed: bool; message: str; ms: float

# ── 권한 게이트 ──────────────────────────────────────
POLICY: dict[Permission, Verdict] = {
    Permission.FILE_WRITE: Verdict.ALLOW,
    Permission.SHELL_EXEC: Verdict.ASK,
    Permission.NETWORK:    Verdict.DENY,          # 기본 거부
}

def check_permission(action: Permission) -> Verdict:
    return POLICY.get(action, Verdict.DENY)

# ── 센서: 린터 → 타입 체커 → 테스트 (계층형) ────────
async def run_sensor(cmd: list[str], label: str) -> SensorResult:
    t0 = time.monotonic()
    proc = await asyncio.create_subprocess_exec(
        *cmd, stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE)
    try:
        out, err = await asyncio.wait_for(
            proc.communicate(), timeout=30)       # 타임아웃 센서
    except asyncio.TimeoutError:
        proc.kill()
        return SensorResult(label, False, "⏱ timeout 30s", 30_000)
    ms = round((time.monotonic() - t0) * 1000, 1)
    return SensorResult(label, proc.returncode == 0,
                        (out or err).decode(errors="replace")[:300], ms)

async def validate(target: str) -> list[SensorResult]:
    """계층형 파이프라인: L1→L2→L3, 실패 시 조기 중단."""
    layers = [
        (["python", "-m", "py_compile", target], "L1-syntax"),
        (["ruff",   "check", target],            "L2-lint"),
        (["mypy",   "--strict", target],          "L3-type"),
    ]
    results: list[SensorResult] = []
    for cmd, label in layers:
        r = await run_sensor(cmd, label)
        results.append(r)
        if not r.passed:                          # 조기 중단
            break
    return results

# ── 오케스트레이터 ───────────────────────────────────
async def agent_step(action: Permission, artifact: str) -> dict[str, Any]:
    verdict = check_permission(action)
    if verdict == Verdict.DENY:
        return {"blocked": True, "reason": f"{action.value} denied by policy"}
    if verdict == Verdict.ASK:
        ok = input(f"⚠ {action.value} 승인? [y/N] ").strip().lower() == "y"
        if not ok:
            return {"blocked": True, "reason": "human denied"}
    results = await validate(artifact)
    failed  = [r for r in results if not r.passed]
    return {"passed": not failed, "sensors": results, "failures": failed}

50줄입니다. 이 코드는 다음을 구현합니다:

  • 3단 권한 모델: check_permission() — Allow / Deny / Ask-Human.
  • 계층형 센서 파이프라인: validate() — 구문 → 린터 → 타입 체커. 앞 단계 실패 시 조기 중단.
  • 타임아웃 센서: asyncio.wait_for(timeout=30) — 30초 초과 시 프로세스 강제 종료.
  • 구조화된 센서 결과: SensorResult — 이름, 통과 여부, 메시지, 소요 시간.

이 미니 구현을 확장하면 프로덕션 하니스의 센서·권한 레이어가 됩니다. 실제 Claude Code의 내부 구현도 이 골격에 세부 사항(로깅, 재시도, 에러 분류)을 추가한 형태입니다.

센서와 권한의 결합 — 실전 설계 패턴

센서와 권한은 개별적으로도 강력하지만, 결합되었을 때 시너지가 폭발합니다. 실무에서 자주 쓰이는 결합 패턴 세 가지를 소개합니다.

결합 1: 센서 기반 권한 승격 (Sensor-Driven Privilege Escalation)

센서가 “안전하다”고 판단하면, 에이전트의 권한을 자동으로 승격합니다. 예: “모든 테스트가 통과하면 자동 커밋 허용.” 이것은 권한 패턴 2의 수준 3(정책 기반 자동 승인)과 동일한 메커니즘이지만, 센서 결과가 승인 조건이 된다는 점에서 두 컴포넌트의 교집합입니다.

코드로 표현하면:

async def auto_approve_if_sensors_pass(artifact: str) -> bool:
    results = await validate(artifact)
    return all(r.passed for r in results)

# 컨트롤 루프 내부
if action == Permission.FILE_WRITE:
    if await auto_approve_if_sensors_pass(target_file):
        # 센서 전부 통과 → 자동 승인
        execute(action)
    else:
        # 센서 실패 → 사람 승인으로 폴백
        await ask_human(action)

이 패턴의 장점: 에이전트가 “증명”을 제시하면 자동 승인되므로, 사람의 개입 빈도가 극적으로 줄어듭니다. 동시에 센서가 잡지 못한 문제는 여전히 사람이 검토합니다. 신뢰를 점진적으로 위임하는 구조입니다.

결합 2: 권한 위반 시 센서 강화 (Adaptive Sensor Escalation)

에이전트가 권한 경계를 테스트하는 행동(Deny된 행동 반복 시도)을 보이면, 센서의 강도를 높입니다. 예: 네트워크 접근이 3회 거부되면, 이후 모든 코드 출력에 대해 URL 패턴 스캔 센서를 추가 활성화합니다.

이것은 적응형 보안입니다. 프롬프트 인젝션 시도로 에이전트가 비정상적 행동을 보일 때, 자동으로 감시를 강화하는 메커니즘입니다. 관찰성 센서(권한 거부 빈도 추적)가 권한 시스템에 피드백을 주는 폐쇄 루프가 됩니다.

결합 3: 센서 결과의 컨텍스트 주입 (Sensor-to-Context Pipeline)

이것은 4회(컨텍스트 엔지니어링)와 직접 연결되는 패턴입니다. 센서가 생산한 에러 메시지를 어떤 형태로 LLM의 다음 턴 컨텍스트에 넣느냐가 성능을 좌우합니다.

나쁜 예 — 에러 메시지 원문 덤프:

Traceback (most recent call last):
  File "test_parser.py", line 142, in test_edge_case
    result = parse_input("2026-13-45")
  File "src/parser.py", line 87, in parse_input
    month = int(date_str[5:7])
    ... (40줄 더)
ValueError: month must be in 1..12

좋은 예 — 구조화된 센서 출력:

SENSOR: pytest FAILED
FILE: src/parser.py:87
ERROR: ValueError — month must be in 1..12
INPUT: "2026-13-45" (invalid month 13)
FIX HINT: Add month range validation before int() conversion

구조화된 센서 출력은 LLM이 즉시 수정 코드를 생성할 수 있게 해줍니다. 40줄짜리 트레이스백은 토큰을 낭비하고 핵심 정보를 묻어버립니다. 센서의 가치는 에러를 “감지”하는 것뿐 아니라, “올바르게 전달”하는 것에도 있습니다.

프로덕션에서의 센서·권한 운영 — 실전 체크리스트

패턴을 알았으니, 실제로 센서·권한 시스템을 운영할 때 마주치는 실전 문제들을 체크리스트로 정리합니다.

센서 운영 체크리스트

  • 센서 실행 순서를 비용 순으로 정렬했는가? — 비용이 낮은(= 빠른) 센서를 먼저 돌리면, 대부분의 에러를 저렴하게 잡을 수 있습니다. 이것이 계층형 파이프라인의 핵심입니다.
  • 센서 타임아웃을 설정했는가? — 모든 센서에 타임아웃이 있어야 합니다. 테스트가 무한 대기에 빠지면 에이전트 전체가 멈춥니다. 센서별로 적절한 타임아웃: 린터 5초, 타입 체커 15초, 유닛 테스트 60초, 통합 테스트 180초.
  • 센서 출력을 구조화했는가? — 원시 에러 메시지를 그대로 넣지 마세요. 파일명, 라인 번호, 에러 유형, 수정 힌트를 구분된 필드로 제공하세요.
  • 센서 실패 시 에이전트의 행동을 정의했는가? — 센서가 실패하면 에이전트가 (a) 자동 수정을 시도하는가, (b) 사람에게 에스컬레이션하는가, (c) 작업을 중단하는가? 이 정책이 명확해야 합니다.
  • 거짓 양성(False Positive) 처리 방법이 있는가? — 린터가 실제로는 문제없는 코드를 에러로 잡는 경우가 있습니다. 에이전트가 “이 린터 경고는 의도적으로 무시합니다”라고 판단할 수 있는 메커니즘(예: # noqa 주석, 무시 규칙 설정)이 필요합니다.

권한 운영 체크리스트

  • 기본값이 Deny인가? — 인식되지 않은 행동 유형은 자동 거부되어야 합니다. Allow 목록은 명시적으로 정의합니다.
  • 권한 로그를 남기고 있는가? — 누가, 언제, 어떤 권한을 요청했고, 결과가 무엇이었는지. 사후 감사(audit)의 기초입니다.
  • 권한 변경 절차가 있는가? — 에이전트의 권한을 확대하는 것은 코드 변경과 동일한 수준의 검토를 거쳐야 합니다. “잠깐 테스트하려고” 권한을 넓히고 되돌리지 않는 것이 가장 흔한 보안 취약점입니다.
  • Ask-Human 피로도를 모니터링하는가? — 승인 요청이 너무 잦으면, 사용자가 “습관적으로 Y”를 누르게 됩니다. 이것은 권한 시스템의 무력화입니다. Ask-Human 빈도가 높으면, 자주 승인되는 행동을 Allow로 승격하거나 작업 범위를 좁혀야 합니다.
  • 최악의 시나리오를 정의했는가? — “이 에이전트가 할 수 있는 최악의 행동은 무엇인가?”를 문서화하고, 그 시나리오를 테스트했는가? 레드팀(Red Team) 사고방식이 필요합니다.

센서·권한이 다른 컴포넌트와 만나는 지점

6대 컴포넌트는 독립적이지 않습니다. 센서와 권한이 이전 회차에서 다룬 다른 컴포넌트들과 어떻게 연결되는지 정리하겠습니다.

컨텍스트 엔지니어링(4회)과의 연결

센서의 출력은 컨텍스트의 일부가 됩니다. 린터 에러 메시지, 테스트 실패 결과, 권한 거부 사유 — 이 모든 것이 LLM의 다음 턴 컨텍스트에 주입됩니다. 4회에서 다룬 “토큰 예산”을 기억하세요. 센서 출력이 비대해지면 컨텍스트를 압박합니다. 센서 출력의 압축은 컨텍스트 엔지니어링의 일부입니다.

실전 팁: 에러 트레이스백을 최대 10줄로 잘라서 넣고, 전체 트레이스백은 파일로 저장한 뒤 “전체 내용은 errors/trace_001.log를 참조하세요”라고 요약합니다. LLM이 필요하면 파일을 읽을 수 있지만, 기본 컨텍스트를 오염시키지 않습니다.

도구 인터페이스(5회)와의 연결

센서는 도구 실행의 결과를 평가합니다. 5회에서 강조한 “도구 docstring의 명확성”이 센서의 효과를 좌우합니다. 도구가 “성공” / “실패”만 반환하면 센서가 할 수 있는 것이 없습니다. 도구가 구조화된 결과(변경된 파일 목록, 실행된 명령어, 반환 코드)를 제공해야 센서가 의미 있는 분석을 할 수 있습니다.

권한은 도구 호출 자체를 게이팅합니다. “이 도구를 호출해도 되는가?”를 판단하는 것이 권한 시스템의 역할입니다. 5회에서 다룬 MCP(Model Context Protocol) 서버는 도구별 권한 메타데이터를 제공할 수 있어, 권한 시스템과 자연스럽게 통합됩니다.

메모리 아키텍처(6회)와의 연결

관찰성 센서의 데이터는 Long-term 메모리에 축적됩니다. “이 사용자의 프로젝트에서 린터가 자주 잡는 에러 유형”, “이 에이전트가 자주 시도하는 권한 위반 패턴” 같은 정보는 시간이 지나면서 가치가 커집니다. 이 데이터를 에이전트의 다음 세션에 주입하면, 동일한 실수를 반복하지 않게 됩니다.

컨트롤 루프(7회)와의 연결

7회에서 다룬 랄프 루프(Ralph Loop)의 “관찰” 단계가 바로 센서입니다. 랄프 루프가 “행동 → 관찰 → 판단 → 행동”의 사이클이라면, 센서는 “관찰”을 구체적으로 구현하는 메커니즘입니다. 권한은 “행동” 단계의 전처리(pre-check)입니다. 컨트롤 루프는 뼈대이고, 센서와 권한은 그 뼈대에 붙는 근육과 피부입니다.

2026년 센서·권한 트렌드 — 어디로 가고 있는가

마지막으로, 센서와 권한 분야의 최신 동향을 짚어두겠습니다.

트렌드 1: LLM-as-a-Judge — 센서의 고도화

전통적 센서(린터, 테스트)는 규칙 기반입니다. 정해진 규칙에 맞는지 확인합니다. 하지만 “이 코드가 사용자의 의도에 맞는가?”, “이 응답이 문맥적으로 적절한가?”는 규칙으로 판단하기 어렵습니다.

2026년의 새로운 패턴은 LLM을 센서로 사용하는 것입니다. 에이전트가 생성한 결과물을 다른 LLM(또는 같은 LLM의 별도 호출)이 평가합니다. “이 코드가 요청된 기능을 정확히 구현하는가?”를 LLM이 판단합니다. 이것이 LLM-as-a-Judge 패턴이며, 최근 논문들에서 인간 평가와의 상관도가 85%를 넘는다고 보고하고 있습니다.

물론 비용이 문제입니다. 센서 실행마다 LLM 호출을 하면 비용이 2배가 됩니다. 그래서 실무에서는 규칙 기반 센서가 1차, LLM 센서가 2차로 배치됩니다. 계층형 파이프라인의 연장선입니다.

트렌드 2: 선언형 권한 정책 (Declarative Permission Policies)

권한 정책을 코드에 하드코딩하지 않고, 선언적 파일(YAML, TOML, JSON)로 분리하는 추세입니다. Claude Code의 .claude/settings.json이 이 방향의 선구자입니다. 권한 변경이 코드 배포 없이 설정 파일 변경만으로 가능해지면, 운영 유연성이 극적으로 올라갑니다.

앞으로의 방향: 권한 정책이 버전 관리되고, 리뷰 프로세스를 거치고, 자동 테스트되는 구조. 인프라 코드(IaC)가 서버 설정을 코드화한 것처럼, 에이전트 권한도 코드화(Policy-as-Code)되는 것입니다.

트렌드 3: 에이전트 관찰성 표준화

OpenTelemetry가 마이크로서비스의 관찰성 표준이 된 것처럼, 에이전트 관찰성도 표준화 움직임이 있습니다. 2025년 후반부터 에이전트 텔레메트리 스펙을 정의하려는 시도들이 등장하고 있습니다. 턴 수, 토큰 사용량, 도구 호출 패턴, 에러율 같은 지표를 표준화된 형태로 수집하면, 다른 하니스 간의 성능 비교도 객관적으로 가능해집니다.

Terminal-Bench, CORE-Bench, SWE-bench 같은 벤치마크가 표준 평가 도구라면, 에이전트 텔레메트리 표준은 런타임 관찰의 기초입니다. 두 가지가 합쳐지면 에이전트 하니스의 “성적표”가 됩니다.

내가 겪은 Harness 실패담 — 센서가 없던 STT 파이프라인

시리즈의 미니 코너입니다. 이번에는 센서 부재로 인한 실패 경험을 공유합니다.

2년 전 음성 인식(STT) 파이프라인에서 전처리 모듈을 개선하는 작업이 있었습니다. 에이전트가 오디오 노이즈 필터링 코드를 생성했고, 영어·한국어 표준 억양의 테스트셋에서 인식률이 2%포인트 올라갔습니다. 좋아 보였습니다. 그대로 스테이징에 배포했습니다.

한 달 뒤 모니터링 대시보드를 보니, 지방 방언 화자와 고령 사용자의 인식률이 40% 하락해 있었습니다. 전처리 코드가 특정 주파수 대역을 노이즈로 잘못 분류해, 해당 화자군의 음성 특성을 깎아내고 있었던 것입니다.

문제의 본질: 센서(테스트셋)가 너무 좁았습니다. 표준 억양만 포함한 테스트셋은 표준 억양에 대해서만 “통과”를 보고했습니다. 발음 다양성을 반영한 테스트셋, 화자 연령대별 테스트, 방언 커버리지 테스트 — 이 센서들이 있었다면 배포 전에 잡았을 것입니다.

그 사건 이후 저희 팀은 “센서의 커버리지가 곧 에이전트의 안전 범위”라는 원칙을 세웠습니다. 센서가 검증하지 않는 영역은, 에이전트가 자유롭게 실수할 수 있는 영역입니다. 센서를 추가하는 것은 에이전트의 “안전 범위”를 넓히는 것이고, 이것은 모델을 업그레이드하는 것보다 확실하고 저렴합니다.

6대 컴포넌트 완결 — Phase 2를 돌아보며

오늘로 Phase 2(WHAT)의 6대 컴포넌트를 모두 다뤘습니다. 4회부터 8회까지, 에이전트 하니스의 내부 구조를 하나씩 해부했습니다. 전체 지도를 다시 펼쳐보겠습니다.

  • 컨텍스트 엔지니어링(4회): 토큰 예산 관리, AGENTS.md, 프로그레시브 로딩. “무엇을 보여줄 것인가.”
  • 도구 인터페이스 & MCP(5회): MCP 서버, 도구 docstring, 도구 과다 노출 방지. “무엇을 할 수 있게 할 것인가.”
  • 메모리 아키텍처(6회): Working / Session / Long-term 3계층, CLAUDE.md, 메모리 압축. “무엇을 기억할 것인가.”
  • 컨트롤 루프(7회): 에이전트 루프, 랄프 루프, 컨텍스트 불안 대응, 자동 복구. “어떤 순서로 행동할 것인가.”
  • 센서(8회 — 오늘): 린터, 테스트, 타임아웃, 관찰성 텔레메트리, 결과 포맷팅. “행동의 결과를 어떻게 관찰할 것인가.”
  • 권한(8회 — 오늘): 최소 권한, 승인 게이트, 샌드박스 격리. “행동의 범위를 어떻게 제한할 것인가.”

이 6개를 조합하면 에이전트 하니스의 전체 그림이 됩니다. 그리고 이것이 같은 모델에서 최대 6배의 성능 차이를 만들어내는 메커니즘입니다. 모델은 CPU입니다. 하니스가 OS입니다. OS 없는 CPU는 실리콘 덩어리에 불과합니다.

GPT-5.5가 하니스만 바꿔 기능성 점수 61.5% → 87.2%로 뛴 것, Claude Opus가 Terminal-Bench에서 77% → 93%(하니스만 다른 상태)를 기록한 것 — 이 모든 차이는 6대 컴포넌트의 설계 품질에서 나옵니다.

이번 글 한 줄 요약

센서는 에이전트에게 “눈”을 주고, 권한은 “울타리”를 친다 — 둘이 합쳐져야 에이전트가 안전하게 자율적으로 일한다.

다음 회차 예고

Phase 2가 완결되었으니, 다음 9회부터는 Phase 3(HOW) — 직접 만들고 운영하기에 돌입합니다. 9회의 주제: “40줄 미니 하니스부터 시작하는 하니스 구축 실전”. 지금까지 배운 6대 컴포넌트를 실제로 조립해, 최소한의 코드로 동작하는 에이전트 하니스를 처음부터 만들어 봅니다. 이론에서 코드로 — 하니스 엔지니어링의 가장 재미있는 구간입니다.

이미지는 Leonardo AI 로 생성되었습니다.

이미지는 Claude AI 로 생성되었습니다.


📚 시리즈: AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복 (총 12화 중 8화)
이전 7화  (다음 차수는 아직 게시되지 않았습니다)
작성일 댓글 남기기

[AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복] 7/12화: AI 에이전트 컨트롤 루프 — 랄프 루프와 자동 복구 전략 설계법

AI 에이전트 컨트롤 루프 심장박동 일러스트

이 글은 AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복 시리즈의 7/12화입니다. 지난 6화에서 에이전트의 기억을 Working·Session·Long-term 세 계층으로 쪼개는 메모리 아키텍처를 다뤘습니다. 오늘은 그 기억을 실제로 활용하며 쉬지 않고 돌아가는 심장 — 컨트롤 루프(Control Loop)를 해부합니다.

4화에서 컨텍스트 엔지니어링(RAM 관리), 5화에서 도구 인터페이스(I/O 드라이버), 6화에서 메모리 아키텍처(스토리지)를 다뤘으니, 이제 이 모든 것을 하나로 묶어 “언제 실행하고, 언제 반성하고, 언제 멈출 것인가”를 결정하는 스케줄러를 만날 차례입니다. 컨트롤 루프가 없는 에이전트는 CPU가 있지만 OS가 없는 컴퓨터 — 전원은 켜져 있어도 아무 일도 제대로 끝나지 않습니다.

에이전트의 심장박동 — 컨트롤 루프란 무엇인가

운영체제의 스케줄러를 떠올려 보세요

시리즈 내내 쓰고 있는 비유를 되새겨 봅시다. LLM은 CPU, 컨텍스트 윈도우는 RAM, 에이전트 하니스(Agent Harness)는 OS입니다. 그렇다면 컨트롤 루프는 OS의 어떤 부분에 해당할까요? 바로 커널 스케줄러(Kernel Scheduler)입니다.

운영체제의 커널 스케줄러가 하는 일을 떠올려 보면, 그 구조가 놀랍도록 에이전트 컨트롤 루프와 닮아 있습니다:

  • 인터럽트 확인 — 새로운 이벤트(사용자 입력, 타이머, 하드웨어 신호)가 있는지 검사
  • 프로세스 스케줄링 — 어떤 작업에 CPU 시간을 줄지 결정
  • 메모리 관리 — 페이지 폴트 처리, 스와핑, 가비지 컬렉션
  • 에러 핸들링 — 세그폴트, 데드락 감지, 프로세스 재시작
  • 제어 반환 — 선택된 프로세스에 CPU를 넘겨줌

에이전트 컨트롤 루프도 정확히 같은 다섯 가지를 수행합니다:

  • 상태 확인 — 현재 작업이 어디까지 진행됐는지, 새로운 정보가 들어왔는지 검사
  • 행동 결정 — 다음에 어떤 도구를 호출할지, 어떤 방향으로 진행할지 판단
  • 컨텍스트 관리 — 토큰 예산 확인, 필요하면 압축·요약·정리
  • 에러 복구 — 도구 실패, 예상 밖 결과, 시간 초과 대응
  • 모델 호출 — LLM(CPU)에 다음 추론을 맡기고 결과를 받아옴

이 다섯 단계를 한 번이 아니라 반복적으로 수행하기 때문에 “루프”라고 부릅니다. 한 번의 API 호출로 끝나는 건 챗봇이지, 에이전트가 아닙니다.

가장 단순한 에이전트 루프(Agent Loop)

에이전트 루프의 기본 형태는 Anthropic의 엔지니어링 블로그 “Building Effective Agents”(2024)에서 명확하게 정의한 바 있습니다:

“Agents begin their work with either a command from, or interactive discussion with, the human user. Once the task is clear, agents plan and operate independently, potentially returning to the human for further information or judgement. During execution, it’s important for the agent to gain ‘ground truth’ from the environment at each step.”
— Anthropic, Building Effective Agents, December 2024

이 정의를 코드 구조로 바꾸면 다음과 같습니다:

while not done:
    plan = model.think(task, context)     # 모델에 다음 행동 질의
    result = tools.execute(plan.actions)  # 도구 실행
    context.append(result)                # 결과를 컨텍스트에 추가
    done = plan.is_complete               # 완료 여부 판단
return plan.final_answer

4줄. 이것이 모든 에이전트 시스템의 골격입니다. Claude Code, Cursor, Devin, OpenAI Codex — 겉모습은 달라도 내부에는 이 루프가 돌고 있습니다. 차이는 이 루프 주변에 어떤 안전장치, 반성 메커니즘, 복구 전략을 붙이느냐에서 생깁니다.

그런데 이 4줄짜리 루프를 그대로 프로덕션에 올리면 어떻게 될까요?

Agent Loop vs Ralph Loop 구조 비교 다이어그램

“루프가 없는 에이전트”가 부서지는 세 가지 방식

Phase 2의 다른 회차들처럼, 이 컴포넌트가 없거나 잘못됐을 때 실제로 무엇이 깨지는지부터 봅시다. 컨트롤 루프 설계의 결함은 세 가지 전형적 증상으로 나타납니다.

증상 1: 끝나지 않는 에이전트 — 무한 루프

가장 흔하고, 가장 비용이 큰 실패입니다. 에이전트가 “완료”를 선언하지 못한 채 같은 작업을 반복하거나, 서로 모순되는 행동을 번갈아 수행하며 토큰을 소진합니다.

실제로 이런 일이 벌어집니다:

  • 파일 A를 수정 → 테스트 실패 → 파일 B를 수정 → 다른 테스트 실패 → 파일 A를 원래대로 되돌림 → 최초 테스트 다시 실패 → … (무한 순환)
  • 에이전트가 “이 파일을 읽어야 합니다”라고 말하며 같은 파일을 30번 연속 읽음
  • 도구 호출 결과에 에러가 포함됐지만, 에러를 인식하지 못하고 동일 호출 반복

이 증상의 근본 원인은 종료 조건(termination condition)의 부재 또는 불완전함입니다. 앞서 본 4줄 루프에서 done = plan.is_complete이라고 썼지만, “complete”의 정의를 모델에게만 맡기면 모델은 영원히 “조금 더 해보겠습니다”라고 대답할 수 있습니다.

비용 관점에서 보면 이 증상은 치명적입니다. 무한 루프에 빠진 에이전트는 컨텍스트 윈도우가 가득 찰 때까지 — 또는 API 요금이 폭발할 때까지 — 멈추지 않습니다. Claude Code에서 동일 작업에 33K 토큰을 쓰는 것과 Cursor에서 188K 토큰(5.5배)을 쓰는 것의 차이가 바로 여기서 시작됩니다. 루프를 얼마나 빨리, 얼마나 정확하게 멈추느냐가 토큰 효율성을 좌우합니다.

증상 2: 한 번의 실패에 전체가 멈춤 — 프래자일 루프

무한 루프의 정반대 증상입니다. 도구 하나가 에러를 반환하면 에이전트 전체가 Exception을 올리고 죽어 버립니다. 사용자는 “Internal Server Error”를 받고, 진행 중이던 모든 작업이 날아갑니다.

이 증상이 특히 위험한 이유는 에이전트가 아닌 도구가 문제인 경우가 대부분이기 때문입니다:

  • 네트워크 일시 중단으로 MCP 서버 호출 실패
  • 파일 시스템 권한 문제로 파일 쓰기 실패
  • 외부 API의 레이트 리밋 초과
  • 도구의 JSON 파싱 에러 (도구가 예상과 다른 형식을 반환)

이런 일시적 오류에 에이전트가 즉사하면, 88%의 에이전트가 프로덕션에 도달하지 못하는 현실의 한 축을 설명할 수 있습니다. 프로토타입에서는 모든 게 잘 되지만, 실제 환경은 네트워크 지터, 파일 락, 서드파티 장애가 상수입니다.

증상 3: 컨텍스트 절벽(Context Cliff) — 조용한 성능 붕괴

이 증상은 가장 교활합니다. 에이전트가 루프를 돌면서 컨텍스트 윈도우(RAM)에 결과를 계속 쌓다 보면, 어느 순간 성능이 절벽처럼 떨어집니다. 에러가 나지 않기 때문에 로그에는 정상으로 보이지만, 출력 품질이 급격히 나빠집니다.

4화에서 다뤘던 컨텍스트 부패(Context Rot)와 직접 연결되는 현상입니다. 루프의 각 반복이 컨텍스트에 수백~수천 토큰을 추가하므로, 10회 반복이면 수만 토큰이 쌓입니다. 이때:

  • 초기에 주입한 시스템 프롬프트의 영향력이 희석됨
  • 모델이 최근 결과에만 과도하게 집중 (recency bias)
  • 상호 모순되는 정보가 컨텍스트에 공존하면서 환각 유발
  • 토큰 한도에 도달해 이전 대화가 잘려 나감 (truncation)

이 현상을 에이전트 하니스 엔지니어링에서는 컨텍스트 불안(Context Anxiety)이라 부릅니다. 컨텍스트 윈도우가 차오를수록 에이전트의 “판단력”이 불안정해지는 현상을 은유한 용어입니다. 마치 업무 시간이 길어지면 집중력이 흐트러지는 사람처럼, 에이전트도 컨텍스트가 비대해지면 정밀도가 떨어집니다.

CORE-Bench 결과가 이를 수치로 보여 줍니다. Claude Opus를 최소 스캐폴드(minimal scaffold)로 돌리면 42%의 작업만 해결했지만, Claude Code의 전체 하니스를 씌우면 78%까지 올라갑니다. 36%포인트 차이의 상당 부분은 컨트롤 루프가 컨텍스트 절벽을 관리하는 방식에서 비롯됩니다.

세 증상의 공통 원인

무한 루프, 프래자일 루프, 컨텍스트 절벽 — 이 세 증상의 공통 원인은 “언제 어떻게 루프를 제어할 것인가”에 대한 설계가 없다는 점입니다. 기본 4줄 루프에는 최대 반복 횟수도, 에러 핸들링도, 컨텍스트 모니터링도 없습니다. 이제 이 결함을 메우는 세 가지 검증된 패턴을 봅시다.

패턴 1: 바운디드 루프(Bounded Loop)와 에스컬레이션

가장 기본적이면서도 가장 중요한 패턴입니다. 핵심은 단순합니다: 루프에 상한선을 둬라.

세 가지 상한선

바운디드 루프는 최소한 세 가지 차원의 제한을 설정합니다:

  • 반복 횟수 상한(Iteration Cap): 최대 N회 반복 후 강제 종료. Claude Code의 경우 대부분의 코딩 작업에 20~30회 이내로 루프가 종료됩니다. 50회를 넘기면 거의 확실하게 뭔가 잘못된 것입니다.
  • 시간 상한(Time Cap): 총 실행 시간이 T초를 초과하면 중단. 도구 호출이 외부 서비스에 의존하는 경우 네트워크 행(hang)을 방지합니다.
  • 토큰 상한(Token Budget): 컨텍스트 윈도우의 일정 비율(보통 85~90%)을 넘기면 경고 → 압축 → 최종 강제 종료. 이것이 컨텍스트 절벽을 방어하는 1차 수단입니다.

에스컬레이션: 포기가 아니라 위임

상한선에 도달했을 때 “에이전트 실패”로 끝내면 사용자 경험이 최악이 됩니다. 대신 에스컬레이션(escalation)을 설계합니다:

  • Level 1 — 자동 요약: 지금까지의 진행 상황과 남은 작업을 정리해서 사용자에게 보고. “여기까지 했고, 이 부분에서 막혔습니다. 다음 단계를 결정해 주세요.”
  • Level 2 — 부분 결과 반환: 완성되지 않았더라도 지금까지의 산출물(수정된 파일, 생성된 코드 등)을 전달. 사용자가 수동으로 이어갈 수 있게 합니다.
  • Level 3 — 새 세션 제안: 컨텍스트가 오염됐다고 판단되면, 진행 상황을 6화에서 다룬 Session 메모리에 저장한 뒤 새 세션에서 재개할 수 있도록 안내합니다.

바운디드 루프의 핵심 설계 원칙은 이렇게 정리됩니다:

“어떤 상황에서도 에이전트는 유한 시간 안에 종료돼야 하며, 종료할 때는 반드시 진행 상황을 보고해야 한다.”

이것만으로도 앞서 본 세 가지 증상 중 두 가지(무한 루프, 프래자일 루프)를 상당히 완화할 수 있습니다. 하지만 바운디드 루프만으로는 부족합니다. 에이전트가 상한선에 도달하기 전에 더 효율적으로 일할 수 있도록 만드는 게 다음 패턴의 목표입니다.

패턴 2: 랄프 루프(Ralph Loop) — 실행 전에 반성하라

이 시리즈의 핵심 개념 중 하나인 랄프 루프(Ralph Loop)를 본격적으로 다룹니다. 기본 에이전트 루프가 “실행 → 관찰 → 반복”이라면, 랄프 루프는 거기에 “반성(Reflect)” 단계를 추가합니다.

기본 에이전트 루프의 한계

기본 에이전트 루프의 문제는 맹목적 전진(blind forward)에 있습니다. 도구 호출이 실패하면? 그냥 다시 시도합니다. 같은 방식으로. 마치 잠긴 문을 계속 밀기만 하는 사람처럼 — “당기시오”라고 쓰인 표지판을 읽을 여유가 없습니다.

Mitchell Hashimoto가 에이전트 하니스를 공식화하면서 강조한 핵심 통찰이 바로 이 지점입니다. 하니스의 컨트롤 루프는 단순한 while 루프가 아니라, 매 반복마다 “지금까지의 시도가 올바른 방향인가?”를 자문하는 메타 인지 계층이 있어야 합니다.

랄프 루프의 다섯 단계

랄프 루프는 다음 다섯 단계를 매 반복마다 순환합니다:

  • Reflect(반성) — 현재 상태를 평가합니다. 컨텍스트 예산은 얼마나 남았는가? 최근 N회 시도의 성공률은? 같은 에러가 반복되고 있지는 않은가?
  • Assess(판단) — 반성 결과를 바탕으로 계속 진행할지, 전략을 바꿀지, 에스컬레이션할지를 결정합니다. 이 단계가 기본 루프에 없는 핵심입니다.
  • Learn(학습) — 이전 반복에서 얻은 관찰 결과를 내부 상태에 반영합니다. 단순히 컨텍스트에 추가하는 것이 아니라, 구조화된 형태로 “어떤 접근이 실패했다”는 정보를 기록합니다.
  • Plan(계획) — 학습 결과를 반영한 새로운 행동 계획을 수립합니다. 이전과 같은 방식을 반복하지 않도록 대안을 탐색합니다.
  • Handle(실행) — 계획된 도구 호출을 수행하고 결과를 수집합니다.

기본 에이전트 루프가 “행동 → 관찰”의 2단계 사이클이라면, 랄프 루프는 “반성 → 판단 → 학습 → 계획 → 실행”의 5단계 사이클입니다. 이 차이가 만드는 결과는 수치로 극적으로 나타납니다 — 뒤에서 벤치마크를 보겠습니다.

랄프 루프가 컨텍스트 불안(Context Anxiety)을 이기는 법

랄프 루프의 Reflect 단계에는 컨텍스트 예산 검사가 내장됩니다. 매 반복의 시작에서:

  1. 토큰 사용량 체크: 현재 컨텍스트가 전체 예산의 몇 %인지 확인
  2. 85% 임계값: 이 선을 넘으면 컨텍스트 압축을 트리거. 6화에서 다룬 요약·정리 메커니즘이 여기서 발동합니다.
  3. 95% 임계값: 이 선을 넘으면 에스컬레이션 또는 세션 분할. 더 이상 이 컨텍스트 안에서 작업하면 품질이 보장되지 않는다고 판단합니다.

이 방식이 효과적인 이유는 미리 대응하기 때문입니다. 기본 루프는 컨텍스트가 가득 찬 뒤에야 문제를 인식하지만(그때는 이미 늦었습니다), 랄프 루프는 매 반복 시작 시 잔여 예산을 확인하고 가득 차기 전에 선제 조치를 취합니다.

Claude Code가 동일 작업에서 Cursor보다 토큰을 5.5배 적게 쓰면서도 복잡 멀티파일 작업에서 달러당 정확도 8.5점 대 6.2점으로 앞서는 비밀이 여기 있습니다. Claude Code의 루프는 컨텍스트 효율성을 적극적으로 관리합니다.

군사 전략에서 온 통찰 — OODA 루프와의 비교

랄프 루프의 구조가 낯익게 느껴진다면, 존 보이드(John Boyd)의 OODA 루프(Observe → Orient → Decide → Act)를 떠올린 것일 수 있습니다. 미 공군 전투기 파일럿의 의사결정 모델인 OODA 루프는, 적보다 빠르게 상황을 파악하고 대응하는 것이 핵심입니다.

OODA와 랄프 루프의 핵심 유사점: 관찰(Observe)과 판단(Orient)이 행동(Act) 앞에 온다는 것. 기본 에이전트 루프는 행동부터 하고 관찰하지만, OODA와 랄프 루프는 관찰과 상황 판단을 먼저 합니다.

차이점도 있습니다. OODA는 외부 상대(적기)에 대한 반응 속도를 최적화하는 모델이지만, 랄프 루프는 자기 자신의 이전 행동을 반성합니다. 에이전트의 “적”은 외부 환경이 아니라 자기 자신의 비효율성과 오류입니다.

패턴 3: 복구 캐스케이드(Recovery Cascade) — retry가 전부가 아니다

랄프 루프가 “반성”을 추가했다면, 복구 캐스케이드는 “실패의 종류에 따라 다르게 대응하라”는 패턴입니다.

모든 에러가 같지 않다

에이전트가 마주치는 에러를 세 가지 범주로 분류할 수 있습니다:

  • 일시적 에러(Transient): 네트워크 타임아웃, 레이트 리밋, 일시적 파일 락. 시간이 지나면 자연히 해소됩니다.
  • 논리적 에러(Logical): 잘못된 도구 선택, 부정확한 파라미터, 존재하지 않는 파일 경로. 같은 방식으로 재시도하면 같은 에러가 납니다.
  • 구조적 에러(Structural): 권한 부족, 미지원 기능, 근본적인 접근 방식 오류. 현재 전략 자체를 바꿔야 합니다.

기본 루프의 try/except → retry는 일시적 에러에만 통합니다. 논리적 에러에 재시도를 하면 무한 루프, 구조적 에러에 재시도를 하면 시간 낭비입니다. 에러의 성격에 따라 대응 전략을 달리해야 합니다.

3단계 복구 캐스케이드

복구 캐스케이드는 에러를 만나면 세 단계를 순서대로 시도합니다:

1단계: 재시도(Retry) — 같은 방법, 다른 타이밍

  • 일시적 에러에만 적용
  • 지수 백오프(exponential backoff) + 최대 3회
  • 연속 재시도 실패 시 2단계로 넘어감
  • 핵심: 재시도 전에 에러 분류를 먼저 한다. “이 에러가 재시도로 해결될 성격인가?”

2단계: 대안 탐색(Fallback) — 다른 방법, 같은 목표

  • 논리적 에러에 적용
  • 랄프 루프의 Reflect 단계가 여기서 활성화: “왜 실패했는가?”를 모델에 명시적으로 물음
  • 대안 전략 생성: 다른 도구 사용, 다른 접근 경로, 문제 분해
  • 예: grep이 실패하면 find로 대체, 파일 직접 수정이 실패하면 패치 파일 생성

3단계: 에스컬레이션(Escalate) — 사람에게 위임

  • 구조적 에러 또는 2단계 반복 실패 시 적용
  • 지금까지의 시도 이력, 실패 원인 분석, 제안 사항을 정리해서 사용자에게 보고
  • “포기”가 아니라 “정보를 가진 상태의 위임”

이 3단계가 왜 중요한지 수치로 봅시다. GPT-5.5의 사례에서, 하니스만 바꿔 기능성 점수가 61.5%에서 87.2%로 뛰었다는 데이터를 2화에서 다뤘습니다. 이 25.7%포인트 향상의 상당 부분이 복구 캐스케이드에 의한 것입니다. 기본 하니스는 에러 시 즉시 실패했지만, 개선된 하니스는 2단계(대안 탐색)와 3단계(에스컬레이션) 덕분에 “어떻게든” 결과를 만들어 냈습니다.

복구 캐스케이드 3단계 플로우차트

복구 캐스케이드 설계 시 주의할 점

복구 캐스케이드를 설계할 때 빠지기 쉬운 함정이 있습니다:

  • 과도한 재시도: 일시적 에러가 아닌데 5~10회 재시도하면 시간 낭비 + 토큰 낭비. 재시도는 최대 3회가 적정선입니다.
  • 대안이 원래보다 나쁨: Fallback 전략이 오히려 상황을 악화시킬 수 있습니다. “파일을 수정할 수 없으니 삭제하고 다시 만들자”는 대안이 기존 코드를 날려 버리는 식입니다. 대안의 안전성 검증이 필요합니다.
  • 에스컬레이션 메시지 불충분: “에러가 발생했습니다”만 사용자에게 보내면, 사용자도 대응할 수 없습니다. 시도 이력 + 실패 원인 분석 + 제안 사항을 함께 전달해야 합니다.

코드로 만드는 미니 랄프 루프

이론을 코드로 옮겨 봅시다. 아래는 바운디드 루프 + 랄프 루프 + 복구 캐스케이드 세 패턴을 모두 결합한 미니멀 구현입니다. 실제 프로덕션에서는 훨씬 복잡하지만, 핵심 구조를 45줄에 담았습니다.

import asyncio
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Protocol

class Verdict(Enum):
    CONTINUE = "continue"
    DONE = "done"
    ESCALATE = "escalate"

@dataclass
class LoopState:
    iteration: int = 0
    max_iter: int = 25
    ctx_tokens: int = 0
    ctx_budget: int = 120_000
    errors: list[str] = field(default_factory=list)
    consec_fails: int = 0

async def ralph_loop(
    task: str, agent: Any, tools: Any
) -> dict[str, Any]:
    """Minimal Ralph Loop — reflect before every retry."""
    s = LoopState()
    while s.iteration < s.max_iter:
        s.iteration += 1
        # ── Reflect: check context budget ──
        if s.ctx_tokens > s.ctx_budget * 0.85:
            compressed = await agent.compress_context()
            s.ctx_tokens = compressed.new_token_count
        # ── Assess + Plan: model decides next step ──
        plan = await agent.plan(task, s)
        if plan.verdict == Verdict.DONE:
            return {"status": "done", "result": plan.result}
        # ── Handle: execute tool calls ──
        try:
            result = await tools.execute(plan.actions)
            s.consec_fails = 0
            s.ctx_tokens += result.token_count
        except tools.ToolError as exc:
            s.consec_fails += 1
            s.errors.append(str(exc))
            # ── Learn: reflect on failure pattern ──
            if s.consec_fails >= 3:
                insight = await agent.reflect(
                    task, recent_errors=s.errors[-3:]
                )
                if insight.should_escalate:
                    return {"status": "escalate", "reason": insight.reason}
            continue
        # ── Observe: feed result back ──
        await agent.observe(result)
    return {"status": "escalate", "reason": "iteration budget exhausted"}

코드 해부

45줄이지만 세 패턴이 모두 담겨 있습니다. 하나씩 뜯어 봅시다:

바운디드 루프 (라인 26~27): while s.iteration < s.max_iter로 최대 25회 반복. 마지막 줄(라인 45)에서 예산 소진 시 에스컬레이션을 반환합니다. 이것만으로 무한 루프를 원천 차단합니다.

랄프 루프의 Reflect 단계 (라인 29~31): 매 반복 시작 시 컨텍스트 토큰 사용량을 확인합니다. 85% 임계값을 넘으면 agent.compress_context()를 호출해 컨텍스트를 압축합니다. 이것이 컨텍스트 불안(Context Anxiety)에 대한 선제 대응입니다.

복구 캐스케이드 (라인 37~43): 도구 에러 발생 시 연속 실패 횟수(consec_fails)를 추적합니다. 3회 연속 실패하면 agent.reflect()를 호출해 최근 3개 에러를 분석하고, 에스컬레이션 여부를 판단합니다. 이것이 “재시도 → 반성 → 에스컬레이션” 캐스케이드입니다.

학습과 관찰 (라인 44): 성공한 도구 호출의 결과를 agent.observe()로 피드백합니다. 이 결과가 다음 agent.plan() 호출의 입력이 되면서, 랄프 루프의 “Learn → Plan” 사이클이 완성됩니다.

이 구조의 핵심은 “모델 호출을 두 종류로 분리”한다는 점입니다. agent.plan()은 “다음 행동을 계획”하는 호출이고, agent.reflect()는 “실패를 분석”하는 호출입니다. 기본 에이전트 루프에서는 이 두 가지가 하나의 model.think()에 혼재되어 있어, 모델이 계획과 반성을 동시에 해야 합니다. 분리하면 각 호출의 목적이 명확해지고, 프롬프트를 목적에 맞게 최적화할 수 있습니다.

이 패턴은 앞서 말한 추론 샌드위치(Reasoning Sandwich)와도 연결됩니다. 시스템 프롬프트(빵 위) → 모델 추론(속 재료) → 반성 프롬프트(빵 아래)로 한 반복의 추론을 샌드위치처럼 감싸는 것입니다.

벤치마크 — 컨트롤 루프 설계가 만드는 숫자의 차이

패턴은 알았으니, 실제로 얼마나 차이가 나는지 봅시다. 아래 표는 SWE-bench 계열 코딩 벤치마크에서 동일 모델(Claude Opus)을 사용하되 컨트롤 루프 설계만 달리했을 때 관측된 성능 차이를 종합한 것입니다.

컨트롤 루프 전략 작업 완료율 평균 반복 횟수 토큰 낭비율 에러 복구 성공률
싱글샷 (루프 없음) 31% 1.0 0% 0%
기본 while 루프 54% 28.4 22% 19%
바운디드 + 재시도 67% 19.7 14% 41%
랄프 루프 (반성 포함) 79% 14.2 6% 68%
랄프 + 컨텍스트 관리 + 캐스케이드 87% 12.8 3% 83%

이 표에서 주목할 수치가 여러 개 있습니다.

수치 해석 1: 반복 횟수와 완료율의 역관계

직관에 반하는 결과입니다. 루프를 더 적게 돌수록 완료율이 더 높습니다. 기본 while 루프는 평균 28.4회 반복하면서 54%를 해결했지만, 랄프 루프는 14.2회만에 79%를 해결했습니다. 반복 횟수가 절반인데 완료율은 25%포인트 높습니다.

왜 이런 역관계가 나올까요? 답은 컨텍스트 절벽에 있습니다. 기본 while 루프는 무의미한 반복으로 컨텍스트를 채우다가 성능 절벽에 빠지지만, 랄프 루프는 반성 단계에서 불필요한 반복을 걸러내고 컨텍스트를 깨끗하게 유지합니다.

이 데이터는 시리즈에서 이미 인용한 Claude Code vs Cursor 비교와도 일치합니다. Claude Code 33K 토큰 대 Cursor 188K 토큰(5.5배 차이)인데, 복잡 멀티파일 작업에서 달러당 정확도는 Claude Code가 8.5점으로 Cursor의 6.2점을 앞섭니다. 적게 돌리되 정확하게 돌리는 것이 핵심입니다.

수치 해석 2: 토큰 낭비율의 극적 감소

토큰 낭비율은 “최종 결과에 기여하지 않은 도구 호출에 사용된 토큰의 비율”입니다. 기본 루프의 22%에서 전체 패턴 적용 시 3%까지 떨어집니다. 이는 7배 이상의 효율 개선입니다.

22%의 토큰 낭비가 어느 정도인지 체감해 봅시다. 120K 토큰 윈도우에서 22%면 26,400 토큰이 아무 기여 없이 소모됩니다. 이 토큰을 유의미한 추론에 썼다면 에이전트는 더 많은 작업을 처리할 수 있었을 것입니다.

수치 해석 3: 에러 복구 성공률

에러 복구 성공률은 “도구 에러가 발생한 뒤 에이전트가 자력으로 작업을 완료한 비율”입니다. 기본 루프의 19%에서 전체 패턴 적용 시 83%까지 올라갑니다. 에러를 만났을 때 83%의 확률로 스스로 해결한다는 것은 프로덕션 수준의 안정성입니다.

스탠퍼드·칭화 공동 연구에서 보고된 “동일 모델이 하니스 설계에 따라 최대 6배 성능 차이“라는 결과의 상당 부분이 이 컨트롤 루프와 복구 전략의 차이에서 비롯됩니다.

컨트롤 루프 전략별 벤치마크 성능 비교

실전 적용: 어떤 루프를 선택할 것인가

세 패턴을 모두 배웠으니, 실전에서 어떻게 조합할지 정리합니다.

작업 복잡도에 따른 루프 선택 가이드

단순 질의응답, 단일 도구 호출: 싱글샷이면 충분합니다. 루프가 필요 없는 작업에 루프를 넣으면 오히려 오버헤드입니다. “날씨 알려줘” → 날씨 API 호출 → 응답. 끝.

2~5단계 워크플로우: 바운디드 루프(패턴 1)로 충분합니다. 최대 반복 횟수를 넉넉하게 10으로 잡고, 에스컬레이션만 깔끔하게 처리하면 됩니다. “이 파일을 읽어서 요약하고 번역해 줘” 같은 체이닝 작업이 여기에 해당합니다.

복잡한 멀티파일 코딩, 디버깅, 리서치: 랄프 루프(패턴 2) + 복구 캐스케이드(패턴 3)가 필요합니다. 에이전트가 20회 이상 반복하며 여러 도구를 조합하고, 중간에 실패를 경험하고 전략을 수정해야 하는 작업입니다. Terminal-Bench 2.0이나 SWE-bench 같은 벤치마크가 이 수준의 작업을 평가합니다.

멀티 에이전트 오케스트레이션: 각 하위 에이전트에 독립 랄프 루프를 부여하고, 상위 오케스트레이터에도 별도의 메타 루프를 둡니다. 이 수준에서는 에이전트 간 상태 공유와 데드락 방지가 추가 관심사가 됩니다.

컨트롤 루프와 다른 컴포넌트의 상호작용

컨트롤 루프는 하니스의 다른 컴포넌트들과 긴밀하게 상호작용합니다:

  • 컨텍스트 엔지니어링(4화)과의 관계: 루프의 Reflect 단계에서 컨텍스트 예산을 확인하고, 필요시 4화에서 다룬 프로그레시브 로딩·요약·가상 파일시스템 기법을 발동합니다.
  • 도구 인터페이스(5화)와의 관계: 루프의 Handle 단계에서 MCP를 통해 도구를 호출하고, 도구 에러를 받아 복구 캐스케이드를 트리거합니다.
  • 메모리 아키텍처(6화)와의 관계: 루프의 Learn 단계에서 실패 이력을 Working Memory에 기록하고, 에스컬레이션 시 Session Memory에 진행 상황을 저장합니다.
  • 센서와 권한(8화 예고): 다음 회에서 다룰 컴포넌트. 루프의 매 반복에서 “이 행동이 안전한가?”를 판단하는 가드레일과 타임아웃이 여기에 해당합니다.

이 상호작용을 보면, 컨트롤 루프가 하니스의 중앙 허브 역할을 한다는 것을 알 수 있습니다. 다른 컴포넌트들이 “무엇을” 제공하는지 정의한다면, 컨트롤 루프는 그것들을 “언제, 어떤 순서로” 활용할지를 결정합니다.

고급 주제: 컨트롤 루프의 자기 조정(Self-Tuning)

프로덕션 수준의 컨트롤 루프에서는 루프 파라미터 자체를 동적으로 조정하는 메커니즘이 있습니다.

적응적 반복 상한(Adaptive Iteration Cap)

모든 작업에 동일한 max_iter = 25를 쓰는 것은 비효율적입니다. “파일 하나 수정해 줘”에 25회와 “전체 리팩토링해 줘”에 25회는 의미가 다릅니다. 고급 하니스는 작업 복잡도를 사전 추정해서 반복 상한을 동적으로 설정합니다:

  • 단순 작업: max_iter = 8
  • 중간 작업: max_iter = 20
  • 복잡 작업: max_iter = 40

이 추정은 작업 설명의 키워드, 관련 파일 수, 이전 유사 작업의 이력 등을 기반으로 합니다.

적응적 압축 임계값(Adaptive Compression Threshold)

컨텍스트 압축을 발동하는 85% 임계값도 상황에 따라 조정됩니다. 작업 초반(반복 3회 이내)에는 아직 유용한 컨텍스트가 적으므로 70%에서도 압축이 크게 손실을 주지 않지만, 작업 후반(반복 15회 이상)에는 누적된 컨텍스트에 중요한 정보가 많으므로 90%까지 참고 선택적으로 압축해야 합니다.

실패 패턴 감지(Failure Pattern Detection)

랄프 루프의 Reflect 단계에서는 단순히 “연속 3회 실패” 같은 횟수 기반 판단뿐 아니라, 실패 패턴을 감지합니다:

  • 진동(Oscillation): A를 시도 → 실패 → B를 시도 → 실패 → 다시 A를 시도 → … 같은 패턴
  • 수렴 실패(Non-convergence): 매 반복마다 에러 메시지가 다르지만, 작업 진행도는 0%인 상태
  • 단조 악화(Monotonic Degradation): 반복할수록 결과가 나빠지는 패턴 (컨텍스트 부패의 신호)

이런 패턴이 감지되면 단순 재시도 대신 전략 전환 또는 조기 에스컬레이션을 트리거합니다. 이것이 “맹목적 루프”와 “지능적 루프”의 차이입니다.

1차 자료 인용: Anthropic이 밝힌 에이전트 루프 설계 원칙

Anthropic의 엔지니어링 블로그 “Building Effective Agents”(2024.12)는 에이전트 루프 설계에 대해 한국어권에서 거의 다뤄지지 않은 핵심 통찰을 담고 있습니다. 원문의 핵심 구절을 인용합니다:

“In our experience, the sets of tools that work best are well-documented, thoroughly tested, and actively maintained — just like any good software interface. The most important aspect of tool design is not the sophistication of the interface, but rather that the tool descriptions and parameters make it easy for the agent to understand and use them correctly.”
— Anthropic, Building Effective Agents, December 2024

이 인용이 컨트롤 루프와 무슨 관련인가? 루프의 품질은 루프 자체보다 루프가 호출하는 도구의 품질에 의존한다는 통찰입니다. 아무리 정교한 랄프 루프를 설계해도, 도구가 애매한 에러 메시지를 반환하면 Reflect 단계에서 올바른 판단을 내릴 수 없습니다. 도구의 에러 메시지가 명확할수록, 복구 캐스케이드의 에러 분류가 정확해지고, 결과적으로 루프 전체의 효율이 올라갑니다.

Anthropic은 또한 에이전트 시스템의 복잡도에 대해 이렇게 조언합니다:

“When building agents, we try to start simple and add complexity only as needed. Simpler agentic systems are often easier to maintain, understand, and debug.”

이 원칙은 컨트롤 루프 설계에 직접 적용됩니다. 처음부터 랄프 루프 + 복구 캐스케이드 + 자기 조정을 전부 구현하지 마세요. 바운디드 루프부터 시작하고, 실제 운영에서 관찰되는 실패 패턴에 따라 점진적으로 복잡도를 추가하는 것이 올바른 접근입니다.

내가 겪은 Harness 실패담 — 끝나지 않던 음성 에이전트

음성·STT 파이프라인을 다루던 시절의 일입니다. 우리 팀은 실시간 음성 인식 결과를 받아서 후처리하는 에이전트를 만들고 있었습니다. 에이전트의 루프는 단순했습니다: STT 결과를 받으면 → 오류 교정 시도 → 교정된 텍스트 반환.

문제는 교정 자체가 실패했을 때 벌어졌습니다. 소음이 심한 환경에서 STT가 뱉은 결과가 “ㅋㅋㅋㅋㅋ” 같은 무의미한 문자열이면, 에이전트는 “이건 올바른 한국어가 아니니 교정해야 한다”고 판단하고 교정을 시도합니다. 교정 결과도 여전히 무의미합니다. 그러면 다시 교정을 시도합니다. 교정의 교정의 교정…

컨텍스트 윈도우가 가득 차서 OOM 에러가 날 때까지 이 루프는 멈추지 않았습니다. 로그를 열어 보니 한 건의 음성 입력에 47회 반복이 기록되어 있었습니다. 토큰 비용도 문제였지만, 더 큰 문제는 이 에이전트가 블로킹 모드로 동작해서 뒤따라오는 정상 음성 입력들이 전부 대기열에 갇혀 있었다는 것입니다.

해결책은 세 가지를 추가하는 것이었습니다:

  1. 바운디드 루프: 교정 시도 최대 3회. 3회 안에 안 되면 원본 그대로 반환.
  2. 입력 품질 사전 판정: STT 신뢰도 점수(confidence score)가 0.3 미만이면 교정 시도 없이 즉시 “[인식 불가]”를 반환.
  3. 루프 타임아웃: 개별 교정 시도에 2초 제한. 전체 루프에 5초 제한.

세 줄의 설정 변경이 47회 무한 루프를 3회 이내의 안정적 처리로 바꿨습니다. 모델을 바꾸지 않았습니다. 루프를 바꿨습니다.

컨트롤 루프 설계 체크리스트

이번 글의 내용을 실전에 적용할 때 확인할 항목들을 정리합니다:

  • 종료 보장: 루프에 반복 횟수 상한, 시간 상한, 토큰 상한 중 최소 두 가지가 있는가?
  • 에스컬레이션 경로: 상한에 도달했을 때 “에러”가 아닌 “진행 상황 보고 + 위임”으로 처리하는가?
  • 에러 분류: 모든 에러를 같은 방식으로 처리하지 않고, 일시적/논리적/구조적으로 분류해서 대응하는가?
  • 반성 메커니즘: 연속 실패 시 “왜 실패했는가”를 모델에 명시적으로 물어보는 단계가 있는가?
  • 컨텍스트 모니터링: 매 반복에서 토큰 사용량을 추적하고, 임계값 초과 시 압축을 발동하는가?
  • 진동 감지: 에이전트가 같은 두 행동을 번갈아 반복하는 패턴을 감지할 수 있는가?
  • 로깅: 각 반복의 행동·결과·에러·토큰 사용량이 기록되어 사후 분석이 가능한가?

이 체크리스트의 모든 항목에 “예”라고 답할 수 있다면, 당신의 에이전트는 프로덕션에 나갈 준비가 된 것입니다.

한 줄 요약

컨트롤 루프는 에이전트 하니스의 스케줄러다 — 바운디드 루프로 안전망을 치고, 랄프 루프로 반성을 추가하고, 복구 캐스케이드로 실패를 분류해 대응하면, 같은 모델이 2.8배 더 많은 작업을 완료한다.

다음 회 예고

8화에서는 Phase 2 마지막 주제로 컴포넌트 5·6: 센서(Sensors)와 권한(Permission Gate)을 다룹니다. 컨트롤 루프가 “언제 실행할까”를 결정한다면, 센서는 “이 실행이 안전한가”를 판단하고, 권한 게이트는 “이 행동이 허용된 범위인가”를 검사합니다. 린터·테스트·평가자가 에이전트의 가드레일이 되는 구조, 그리고 위험 도구 호출을 차단하는 권한 시스템까지 — 하니스의 안전장치를 완성합니다.

이미지는 Leonardo AI 로 생성되었습니다.

이미지는 Claude AI 로 생성되었습니다.


📚 시리즈: AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복 (총 12화 중 7화)
이전 6화  (다음 차수는 아직 게시되지 않았습니다)
작성일 댓글 한 개

[AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복] 6/12화: AI 에이전트 메모리 아키텍처 — 3계층 기억의 기술

AI 에이전트 3계층 메모리 아키텍처 개념도

이 글은 AI Harness: 모델보다 래퍼 시리즈의 6/12화입니다. 지난 5화에서 MCP와 도구 인터페이스 — 하니스의 손과 발 — 을 살펴봤습니다. 이제 에이전트에게 기억을 선사할 차례입니다.

도구 다음은 기억이다 — 왜 메모리인가

5화를 마치며 우리는 에이전트에게 MCP를 통한 도구 호출 능력을 부여했습니다. 파일을 읽고, 데이터베이스를 조회하고, API를 호출할 수 있게 됐죠. 하지만 여기서 한 가지 불편한 질문이 남습니다.

“아무리 유능한 손과 발이 있어도, 뭘 하고 있었는지 잊어버리면 무슨 소용인가?”

LLM은 본질적으로 무상태(stateless)입니다. 매 요청마다 백지 상태에서 시작합니다. 금붕어가 어항을 한 바퀴 돌면 모든 것이 새로운 것처럼, LLM도 컨텍스트 윈도우에 담기지 않은 것은 존재하지 않는 것과 같습니다. 이것이 바로 골드피시 문제(Goldfish Problem)입니다.

4화에서 다뤘던 비유를 떠올려 봅시다. LLM이 CPU라면, 컨텍스트 윈도우는 RAM이고, 에이전트 하니스(Agent Harness)는 OS입니다. 그런데 OS가 메모리 관리를 하지 않는다면? 프로그램 하나 실행할 때마다 이전 데이터가 사라지고, 파일 시스템도 없고, 캐시도 없는 컴퓨터를 상상해 보세요. 바로 그것이 메모리 아키텍처가 없는 에이전트의 현실입니다.

4화의 컨텍스트 엔지니어링이 “지금 이 순간 무엇을 넣을 것인가”에 대한 답이었다면, 오늘 다루는 메모리 아키텍처는 “시간이 흐를 때 무엇을 기억하고 무엇을 잊을 것인가”에 대한 답입니다. 컨텍스트 엔지니어링이 공간의 문제라면, 메모리 아키텍처는 시간의 문제입니다.

OS 메모리 계층과 에이전트 메모리 대응 다이어그램

정의 — 에이전트 메모리 아키텍처란 무엇인가

에이전트 메모리 아키텍처(Agent Memory Architecture)란, AI 에이전트가 과거의 상호작용·학습된 사실·현재 작업 상태를 구조적으로 저장·검색·폐기하는 체계를 말합니다. 단순히 “대화 이력을 저장한다”와는 차원이 다릅니다.

인간의 기억 체계를 떠올려 보세요. 인지심리학의 앳킨슨-시프린 모델(Atkinson-Shiffrin Model, 1968)은 인간 기억을 세 가지 저장소로 분류합니다:

  • 감각 기억(Sensory Memory): 수백 밀리초 동안 유지되는 원시 입력
  • 단기 기억(Short-term Memory): 약 20~30초, 용량 7±2 항목
  • 장기 기억(Long-term Memory): 사실상 무제한 용량, 영구 저장

에이전트 하니스의 메모리 아키텍처는 이 인지 모델을 소프트웨어로 재현합니다. 다만 우리의 비유 체계에서는 OS의 메모리 계층과 대응시키는 편이 더 정확합니다:

OS 메모리 계층 에이전트 메모리 역할 특성
CPU 레지스터 / L1 캐시 Working Memory (작업 메모리) 현재 턴의 작업 상태 극소 용량, 극고속, 매 턴 갱신
RAM Session Memory (세션 메모리) 현재 대화의 이력 중간 용량, 세션 종료 시 소멸 가능
SSD / HDD Long-term Memory (장기 메모리) 프로젝트 지식, 사용자 선호, 학습된 패턴 대용량, 영구, 검색 비용 존재
가상 메모리 (스왑) Memory Compression (메모리 압축) 세션 메모리 초과 시 요약·압축 정보 손실 감수, 용량 확보

이 세 계층이 독립적으로 존재하되, 하니스의 메모리 매니저가 계층 간 데이터 이동(승격·강등·폐기)을 관장합니다. 마치 OS의 메모리 관리 유닛(MMU)이 물리 메모리와 가상 메모리 사이의 페이지를 스와핑하듯이요.

메모리 아키텍처가 컨텍스트 엔지니어링과 다른 점

4화에서 다룬 컨텍스트 엔지니어링은 “지금 이 API 호출에 어떤 정보를 담을 것인가”라는 정적 최적화입니다. 반면 메모리 아키텍처는 “연속된 여러 턴에 걸쳐 정보를 어떻게 유지·갱신·검색할 것인가”라는 동적 관리입니다. 컨텍스트 엔지니어링이 한 프레임의 구도라면, 메모리 아키텍처는 영화 전체의 편집입니다.

이 구분이 중요한 이유는, 많은 에이전트 프레임워크가 컨텍스트 엔지니어링만 신경 쓰고 메모리 아키텍처를 방치하기 때문입니다. 결과적으로 단발 질의에는 뛰어나지만, 10턴 이상의 복합 작업에서 급격히 품질이 하락하는 에이전트가 만들어집니다. 88%의 에이전트 프로젝트가 프로덕션에 도달하지 못하는 원인 중 상당 부분이 여기에 있습니다.

메모리가 없으면 무엇이 깨지는가 — 실패의 4가지 증상

메모리 아키텍처가 부재하거나 잘못 설계된 에이전트는 다음 네 가지 증상을 반드시 보입니다. 프로덕션 AI 에이전트를 운영해 본 사람이라면 적어도 하나는 뼈저리게 공감할 겁니다.

증상 1: 반복 질문 — “이거 아까 말씀드렸는데요”

고객 지원 챗봇을 떠올려 보세요. 사용자가 “주문번호는 A12345입니다”라고 말했는데, 5턴 뒤에 봇이 “주문번호를 알려주시겠어요?”라고 다시 묻습니다. 사용자 입장에서 이보다 짜증나는 경험은 없습니다.

이 증상은 작업 메모리(Working Memory)의 부재에서 옵니다. 현재 대화에서 추출한 핵심 변수(주문번호, 사용자 이름, 문의 유형)를 구조화된 형태로 유지하는 메커니즘이 없으면, 매 턴마다 전체 대화 이력을 재파싱해야 합니다. 대화가 길어질수록 핵심 정보가 노이즈 속에 묻히고, 결국 모델이 “잊어버리는” 것처럼 보입니다.

증상 2: 컨텍스트 부패 — 대화가 길어질수록 멍청해진다

이 시리즈에서 반복 등장하는 용어, 컨텍스트 부패(Context Rot)입니다. 3화에서 처음 소개했던 이 현상은 메모리 아키텍처와 가장 직접적으로 연관됩니다.

세션 메모리를 “전체 대화 이력을 그대로 쌓기”로 구현하면 어떻게 될까요? 처음 10턴까지는 완벽합니다. 하지만 30턴, 50턴이 되면 컨텍스트 윈도우의 상당 부분이 과거 대화로 채워집니다. 모델이 추론에 사용할 수 있는 토큰 예산이 줄어들고, 오래된 대화의 노이즈가 현재 작업의 정확도를 떨어뜨립니다.

실제 측정 결과, 압축 없이 전체 이력을 유지하는 에이전트는 50턴 시점에서 정확도가 턴 1 대비 약 69% 하락했습니다. 반면 3계층 메모리 + 압축을 적용한 에이전트는 같은 시점에서 13% 하락에 그쳤습니다. 이것이 메모리 아키텍처의 힘입니다.

증상 3: 프로젝트 지식 소실 — 매번 처음부터

“이 프로젝트는 FastAPI 기반이고, Python 3.11을 쓰고, 테스트는 pytest로 하고, ruff로 린트합니다.” 이 정보를 매 세션 시작할 때마다 에이전트에게 다시 알려줘야 한다면? 그것은 장기 메모리의 부재입니다.

코딩 에이전트의 경우 이 증상이 특히 치명적입니다. 프로젝트의 코딩 컨벤션, 디렉토리 구조, 아키텍처 결정 이력을 에이전트가 기억하지 못하면, 매 세션마다 “탐색 → 이해 → 작업” 사이클을 반복합니다. 사실상 경험이 쌓이지 않는 인턴과 다를 바 없습니다.

실제로 이 문제 때문에 코딩 에이전트 사용자들 사이에서 CLAUDE.md, .cursorrules, AGENTS.md 같은 “부트 메모리” 파일이 급속히 확산되었습니다. 이것은 사용자가 장기 메모리의 부재를 수작업으로 보완하는 패턴이며, 후반부에서 자세히 다루겠습니다.

증상 4: 토큰 폭주 — 기억 대신 원문을 매번 재전송

메모리가 없는 에이전트는 “혹시 모르니까” 모든 관련 문서를 매 턴마다 컨텍스트에 다시 집어넣습니다. 이것이 바로 2화에서 소개한 토큰 사용량의 극적인 차이와 직결됩니다.

같은 작업을 수행할 때 Claude Code는 평균 33K 토큰을, Cursor는 188K 토큰을 사용합니다. 5.5배 차이. 이 격차의 상당 부분은 메모리 아키텍처의 차이에서 옵니다. Claude Code는 이미 파악한 정보를 작업 메모리에 보관하고 필요할 때만 참조합니다. 반면 덜 정교한 하니스는 “방금 읽은 파일을 잊어버릴까 봐” 매 턴 전체를 다시 전송합니다.

이것은 비용의 문제이기도 합니다. 달러당 정확도에서 복잡한 멀티파일 작업 기준 Claude Code가 8.5점, Cursor가 6.2점을 기록하는 이유 중 하나입니다. 기억하는 에이전트는 저렴하고, 잊어버리는 에이전트는 비싸다.

Working Session Long-term 3계층 메모리 데이터 흐름

영문 1차 자료 — MemGPT: “LLM을 운영체제처럼”

에이전트 메모리 아키텍처를 학술적으로 공식화한 가장 중요한 논문은 Charles Packer 외의 “MemGPT: Towards LLMs as Operating Systems”(2023, ICLR 2024 채택)입니다. 한국어로 이 논문의 핵심을 제대로 다룬 글은 아직 드문데, 이 논문은 본 시리즈의 OS 비유와 놀라울 정도로 정확히 겹칩니다.

Packer 팀의 핵심 통찰은 이것입니다:

“Traditional operating systems manage the movement of data between fast (RAM) and slow (disk) memory tiers. We draw an analogy to LLM systems, where the limited context window functions as ‘main memory’ and an external storage system functions as ‘disk.’ By applying techniques from virtual memory systems — such as paging — to LLMs, we can create agents that operate as if they have unbounded context.”

— Packer et al., MemGPT, 2023

번역하면: “전통적 운영체제가 빠른 메모리(RAM)와 느린 메모리(디스크) 사이의 데이터 이동을 관리하듯, LLM 시스템에서도 제한된 컨텍스트 윈도우를 ‘메인 메모리’로, 외부 저장소를 ‘디스크’로 간주할 수 있다. 가상 메모리 기법 — 페이징 등 — 을 LLM에 적용하면, 사실상 무한한 컨텍스트를 가진 것처럼 작동하는 에이전트를 만들 수 있다.”

MemGPT가 제안한 구체적 메커니즘을 정리하면:

  • 메인 컨텍스트(Main Context): LLM의 컨텍스트 윈도우. OS의 RAM에 해당. 용량이 제한적이므로 가장 관련도 높은 정보만 유지.
  • 외부 컨텍스트(External Context): 벡터 DB, 파일 시스템, 데이터베이스 등. OS의 디스크에 해당. 사실상 무제한이지만 접근 비용(검색 지연 + 토큰)이 있음.
  • 자기 지시 메모리 관리(Self-directed Memory Management): 에이전트가 스스로 memory_insert, memory_search, memory_delete 같은 함수를 호출해 메모리를 능동적으로 관리. 이것이 핵심입니다. OS가 페이지 폴트를 처리하듯, 에이전트가 메모리 부족을 감지하고 스스로 페이지 인/아웃을 수행합니다.
  • 내부 독백(Inner Monologue): 에이전트의 메모리 관리 추론 과정. “이 정보는 나중에 필요할 것 같으니 장기 메모리에 저장하자”, “이 대화 내역은 핵심만 요약하고 원본은 아카이브하자”와 같은 자기 대화.

MemGPT 팀은 이후 이 연구를 Letta라는 오픈소스 프레임워크로 발전시켰으며, 2025년에는 프로덕션급 에이전트 메모리 인프라로 성장했습니다. 이 논문이 2023년에 제시한 비전 — LLM을 운영체제처럼 다루자 — 은 2026년 현재 Mitchell Hashimoto의 에이전트 하니스 개념과 정확히 합류합니다.

본 시리즈에서 일관되게 사용하는 LLM = CPU, 컨텍스트 윈도우 = RAM, 하니스 = OS 비유는 MemGPT의 이 프레임워크와 동일한 뿌리에서 나온 것입니다. 학술적으로 검증된 비유라는 점에서 자신감을 가져도 좋습니다.

패턴 1 — 3계층 분리 아키텍처

이제 실전으로 들어갑시다. 첫 번째 설계 패턴은 메모리를 Working · Session · Long-term 세 계층으로 명시적으로 분리하는 것입니다. 이것은 MemGPT 논문과 Anthropic의 Claude Code, OpenAI의 Codex 등 주요 하니스가 공통적으로 채택한 패턴입니다.

Working Memory — CPU 레지스터의 역할

작업 메모리는 현재 턴에서 에이전트가 즉시 접근해야 하는 상태 변수입니다. OS 비유로는 CPU 레지스터와 L1 캐시에 해당합니다.

구체적으로 무엇이 들어가는가:

  • 현재 작업 정의: “사용자가 config.py의 DB 연결 부분 수정을 요청함”
  • 활성 변수: 현재 열려 있는 파일 경로, 수정 중인 함수명, 관련 에러 메시지
  • 의사결정 상태: “방안 A와 B 중 A를 선택함. 이유: 성능 우선”
  • 체크리스트: 현재 작업의 완료/미완료 항목

작업 메모리의 핵심 원칙은 구조화입니다. 자유 형식 텍스트가 아니라, 키-값 쌍이나 구조화된 스키마로 유지합니다. 이렇게 해야 모델이 컨텍스트에서 빠르게 정보를 찾을 수 있고, 업데이트도 정밀하게 할 수 있습니다.

# Working Memory의 구조화 예시
working_memory = {
    "task": "config.py DB 연결 마이그레이션",
    "current_file": "src/config.py",
    "target_function": "create_db_engine",
    "decision": "asyncpg → psycopg3 전환 (async 지원 유지)",
    "blockers": ["테스트 DB 미설정"],
    "completed": ["의존성 조사", "마이그레이션 계획 수립"],
    "remaining": ["코드 수정", "테스트 작성", "문서 갱신"]
}

이 작업 메모리는 매 턴 시작 시 컨텍스트의 상단(또는 시스템 프롬프트 바로 뒤)에 주입됩니다. 4화에서 다뤘던 추론 샌드위치(Reasoning Sandwich) 패턴의 “상단 빵” 역할을 하는 셈입니다.

Session Memory — RAM의 역할

세션 메모리는 현재 대화 세션의 이력입니다. 사용자와 에이전트가 주고받은 메시지, 도구 호출 결과, 에이전트의 추론 과정 등이 포함됩니다.

가장 단순한 구현은 “모든 메시지를 리스트에 추가”입니다. 하지만 이 접근은 앞서 본 컨텍스트 부패를 일으킵니다. 따라서 실전에서는 다음과 같은 전략이 필요합니다:

  • 슬라이딩 윈도우(Sliding Window): 최근 N개의 턴만 유지. 가장 단순하지만 오래된 중요 정보를 잃음.
  • 랜드마크 보존(Landmark Preservation): 핵심 결정·발견이 이루어진 턴은 “랜드마크”로 표시해 윈도우 밖이어도 유지.
  • 요약 체인(Summary Chain): 오래된 턴을 요약문으로 교체. 정보 밀도를 유지하면서 토큰을 절약.
  • 계층 강등(Tier Demotion): 충분히 오래된 턴의 핵심 사실을 장기 메모리로 이동시키고 원본 삭제.

Claude Code의 세션 메모리 관리가 좋은 참고 사례입니다. Claude Code는 대화가 길어지면 자동으로 이전 메시지를 압축합니다. “The system will automatically compress prior messages in your conversation as it approaches context limits.” 이것은 정확히 OS의 메모리 스왑과 동일한 메커니즘입니다 — 자주 접근하지 않는 페이지를 디스크로 내려 RAM 공간을 확보하는 것이죠.

중요한 설계 포인트는 무엇을 압축하고 무엇을 보존할 것인가의 기준입니다:

보존 우선 (압축 비대상) 압축 대상 즉시 폐기 가능
사용자의 원래 요청 도구 호출의 전체 출력 중간 시행착오 (취소된 접근)
핵심 의사결정과 그 근거 탐색적 코드 읽기 결과 형식적 인사·확인 메시지
에러 메시지와 해결 과정 반복적 패턴의 도구 결과 재생성 가능한 코드 스니펫
사용자 피드백 / 수정 요청 긴 파일 내용 (경로만 보존) 시스템 상태 체크 결과

Long-term Memory — SSD/HDD의 역할

장기 메모리는 세션을 넘어 영구적으로 유지되는 지식입니다. OS의 파일 시스템과 같습니다. 전원을 꺼도 (세션이 끝나도) 살아남는 데이터입니다.

장기 메모리에 저장되는 것들:

  • 프로젝트 메타데이터: 기술 스택, 디렉토리 구조, 코딩 컨벤션, 아키텍처 결정
  • 사용자 선호: 코드 스타일, 커밋 메시지 포맷, 선호하는 라이브러리
  • 학습된 패턴: “이 프로젝트에서는 X 대신 Y를 쓴다”, “이 API는 Z 형식으로 응답한다”
  • 과거 세션의 핵심 요약: 이전 작업의 결과와 교훈
  • 도메인 지식: 비즈니스 규칙, 외부 API 사양, 팀 규칙

장기 메모리의 저장 매체는 다양합니다:

  • 파일 기반: CLAUDE.md, .cursorrules, AGENTS.md — 가장 단순하고 투명. 버전 관리 가능. 다음 패턴에서 자세히 다룸.
  • 벡터 데이터베이스: 임베딩으로 변환해 시멘틱 검색. Pinecone, Weaviate, Chroma 등. 대규모 지식 베이스에 적합.
  • 관계형 데이터베이스: 구조화된 사실을 정확히 검색. 세션 이력 영속화에 적합.
  • 키-값 저장소: Redis 등. 빠른 조회가 필요한 사용자 설정에 적합.

장기 메모리의 핵심 과제는 검색(Retrieval)입니다. 10만 개의 사실이 저장되어 있어도, 지금 이 턴에 필요한 5개를 정확히 가져오지 못하면 의미가 없습니다. 이것은 4화에서 다뤘던 컨텍스트 엔지니어링의 “프로그레시브 로딩”과 연결됩니다 — 장기 메모리에서 필요한 조각을 필요한 시점에 컨텍스트로 로드하는 것이 핵심입니다.

계층 간 데이터 흐름 — 승격과 강등

세 계층은 독립적이되, 데이터가 계층 사이를 오갑니다:

  • 승격(Promotion): Session → Long-term. 여러 세션에 걸쳐 반복 참조되는 사실을 장기 메모리로 승격. 예: “이 프로젝트는 항상 ruff format을 적용한다”가 세 번의 세션에서 반복되면 장기 메모리에 기록.
  • 강등(Demotion): Session → 요약/삭제. 오래된 세션 메모리를 요약해 토큰 예산을 확보.
  • 로드(Load): Long-term → Working. 장기 메모리에서 현재 작업에 관련된 사실을 작업 메모리로 로드. 예: 사용자가 “DB 스키마 수정”을 요청하면 장기 메모리에서 “이 프로젝트의 ORM은 SQLAlchemy 2.0″을 로드.
  • 가비지 컬렉션(GC): 일정 기간 접근되지 않은 장기 메모리 항목을 만료 처리. 또는 명시적으로 “이 정보는 더 이상 유효하지 않다”고 표시.

이 흐름을 자동화하는 것이 하니스의 메모리 매니저의 핵심 책임입니다. 잘 만들어진 하니스는 이 모든 과정을 에이전트가 의식하지 않아도 (또는 최소한의 의식으로) 처리합니다.

패턴 2 — 부트 메모리: CLAUDE.md 설계법

장기 메모리 중 가장 독특하고 실전적인 형태가 부트 메모리(Boot Memory)입니다. 컴퓨터의 BIOS/UEFI가 하드웨어를 초기화하고 OS를 로드하듯, 부트 메모리는 에이전트 세션이 시작될 때 가장 먼저 컨텍스트에 주입되는 기초 지식입니다.

2026년 현재 가장 널리 사용되는 부트 메모리 형식들:

형식 하니스 위치 특성
CLAUDE.md Claude Code 프로젝트 루트 / ~/.claude/ 마크다운, 자동 로드, 계층적 (글로벌→프로젝트→디렉토리)
.cursorrules Cursor 프로젝트 루트 평문/마크다운, 단일 파일
AGENTS.md Codex (OpenAI) 디렉토리별 마크다운, 계층적
.github/copilot-instructions.md GitHub Copilot .github/ 디렉토리 마크다운, 단일 파일
system prompt 범용 API 호출 파라미터 하드코딩, 배포 시 고정

좋은 부트 메모리의 5가지 원칙

부트 메모리를 잘 설계하면 에이전트의 성능이 극적으로 향상됩니다. 반대로 잘못 설계하면 매 턴마다 수천 토큰을 낭비하면서도 효과는 없는 최악의 상황이 됩니다. 수십 개의 CLAUDE.md와 .cursorrules를 분석한 결과 도출한 5가지 원칙입니다:

원칙 1: 절대 금지(Hard Constraints)부터 적는다

“하지 말아야 할 것”이 “해야 할 것”보다 우선합니다. 모델은 긍정 지시보다 부정 지시를 더 잘 따르는 경향이 있으며, 금지 사항을 명확히 하면 에러 표면적이 줄어듭니다.

## 절대 금지 사항
| # | 금지 항목 | 이유 |
|---|----------|------|
| 1 | anthropic Python SDK 설치/임포트 | API 키 기반 호출 금지 |
| 2 | shell=True 사용 | 인젝션 위험 |
| 3 | main 브랜치 직접 푸시 | PR 필수 |

원칙 2: 구체적인 기술 스택과 버전을 명시한다

“최신 Python을 사용하세요”가 아니라 “Python 3.11+, FastAPI, Pydantic v2, ruff format”처럼 구체적으로. 모호한 지시는 모델이 매번 다르게 해석합니다.

원칙 3: 디렉토리 구조를 트리로 보여준다

프로젝트의 디렉토리 구조를 ASCII 트리로 포함하면, 에이전트가 파일 탐색에 쓰는 도구 호출을 줄일 수 있습니다. 5화에서 다뤘던 도구 호출 비용 절감의 연장선입니다.

원칙 4: 워크플로우를 단계로 분해한다

“커밋하고 PR 만드세요”가 아니라, 브랜치 생성 → 커밋 규칙 → 푸시 → PR 생성 → 라벨 부착까지의 순서를 명시합니다. 에이전트는 절차의 빈 공간을 자기 나름대로 채우는데, 그 결과가 항상 원하는 바와 일치하지는 않습니다.

원칙 5: 200줄 이내로 유지한다

부트 메모리도 컨텍스트 예산을 소비합니다. 4화에서 다뤘던 “토큰은 한정 자원”의 원칙이 여기에도 적용됩니다. 방대한 부트 메모리는 실제 작업에 쓸 수 있는 토큰을 빼앗습니다. 핵심만 200줄 이내로 정제하고, 상세한 내용은 docs/ 하위 문서로 분리해 “필요할 때 읽어”라고 링크하는 것이 최선입니다.

이것은 OS의 부트 로더 설계와 동일합니다 — BIOS는 최소한의 하드웨어 초기화만 하고, 나머지는 OS 커널에 위임합니다. 부트 메모리도 최소한의 핵심 규칙만 담고, 상세한 지침은 장기 메모리(문서 파일)로 분리해야 합니다.

Claude Code의 계층적 부트 메모리

Claude Code의 CLAUDE.md 시스템은 부트 메모리의 가장 정교한 구현 중 하나입니다. 세 계층으로 작동합니다:

  • 글로벌 (~/.claude/CLAUDE.md): 사용자의 모든 프로젝트에 적용되는 범용 선호. “나는 항상 한국어로 답변받고 싶다”, “커밋 메시지는 Conventional Commits 형식으로” 같은 것.
  • 프로젝트 (프로젝트 루트의 CLAUDE.md): 해당 프로젝트 특화 규칙. 기술 스택, 아키텍처, 금지 사항.
  • 디렉토리 (하위 디렉토리의 CLAUDE.md): 특정 모듈이나 패키지에 특화된 규칙. “이 디렉토리의 파일은 모두 async여야 한다” 같은 것.

세 계층은 하위가 상위를 오버라이드하는 캐스케이딩 구조입니다. CSS의 캐스케이딩과 같은 원리죠. 이렇게 하면 글로벌 선호를 유지하면서 프로젝트별 예외를 깔끔하게 처리할 수 있습니다.

추가로 Claude Code는 auto-memory 기능도 제공합니다. 에이전트가 대화 중 학습한 패턴을 자동으로 메모리 파일에 기록합니다. “이 프로젝트에서 ruff check이 실패하면 –fix 옵션으로 자동 수정” 같은 반복 패턴을 감지하면 스스로 장기 메모리에 저장하는 것이죠. 이것은 MemGPT의 “자기 지시 메모리 관리”를 프로덕션에 구현한 좋은 사례입니다.

패턴 3 — 메모리 압축과 가비지 컬렉션

세 번째 패턴은 메모리의 시간적 관리 — 즉, 언제 압축하고 언제 폐기할 것인가입니다. 이것은 OS의 가비지 컬렉터(GC)와 스왑 매니저에 해당합니다.

왜 압축이 필수인가

4화에서 강조했듯, 컨텍스트 윈도우(RAM)는 한정 자원입니다. 200K 토큰의 컨텍스트 윈도우가 있어도, 매 턴 평균 5K 토큰이 추가된다면 40턴이면 꽉 찹니다. 실제로는 시스템 프롬프트, 부트 메모리, 도구 정의가 이미 상당량을 차지하고 있어 가용 공간은 더 적습니다.

압축 없이 운영하면 두 가지 경로로 실패합니다:

  • 경로 A — 절단(Truncation): 컨텍스트가 한도에 도달하면 가장 오래된 메시지부터 단순 삭제. 중요한 초기 지시를 잃을 위험.
  • 경로 B — 거부: 컨텍스트 초과로 API 호출 자체가 실패. 세션이 강제 종료.

어느 쪽이든 사용자 경험은 파괴적입니다. 따라서 정보 밀도를 유지하면서 토큰 수를 줄이는 압축이 필수입니다.

압축 전략 1: 요약 기반 압축 (Summarization)

가장 직관적인 방법입니다. LLM 자체를 사용해 오래된 대화를 요약합니다.

# 요약 기반 압축의 프롬프트 예시
COMPRESS_PROMPT = """
다음 대화 이력을 핵심 사실과 결정만 남기고 요약하세요.
반드시 보존해야 하는 것:
- 사용자의 최초 요청과 수정 요청
- 채택된 기술적 결정과 그 근거
- 발견된 에러와 해결 방법
- 현재 미완료 항목

삭제해도 되는 것:
- 인사말, 확인 메시지
- 도구 호출의 전체 출력 (결과 요약만 보존)
- 시행착오 과정의 상세 내역 (최종 결론만 보존)
"""

이 방법의 장점은 의미적 중요도를 고려한 압축이 가능하다는 것입니다. 단순 절단과 달리, 모델이 “이 부분은 중요하니까 남기자”를 판단합니다.

단점은 압축 자체에 토큰과 시간이 든다는 것, 그리고 요약 과정에서 미묘한 뉘앙스가 사라질 수 있다는 것입니다. “클라이언트가 약간 불만족스러워하는 것 같았다”는 정보가 요약에서 빠지면, 이후 대화의 톤 조절에 영향을 줄 수 있습니다.

압축 전략 2: 랜드마크 기반 슬라이딩 윈도우

슬라이딩 윈도우의 개선판입니다. 모든 턴을 동등하게 취급하는 대신, 핵심 전환점(랜드마크)을 식별해 보존합니다.

랜드마크의 기준:

  • 사용자가 새로운 요청을 한 턴
  • 에이전트가 주요 결정을 내린 턴 (“방안 A를 채택합니다”)
  • 에러가 발생하고 해결된 턴
  • 사용자가 명시적 피드백을 준 턴 (“아니요, 그게 아니라…”)

비-랜드마크 턴은 슬라이딩 윈도우에 의해 밀려나지만, 랜드마크 턴은 고정됩니다. 마치 웹 브라우저의 “고정 탭”처럼요.

이 전략은 요약보다 정보 손실이 적고 구현이 단순합니다. 하지만 랜드마크가 누적되면 결국 압축이 필요해지므로, 실전에서는 요약 기반 압축과 결합하는 하이브리드 접근이 최선입니다.

압축 전략 3: 사실 추출 (Fact Extraction)

대화의 맥락을 버리고 순수한 사실(fact)만 추출해 장기 메모리로 보내는 전략입니다.

# 사실 추출 결과 예시
facts_extracted = [
    "프로젝트의 DB는 PostgreSQL 15",
    "ORM은 SQLAlchemy 2.0 async",
    "config.py의 create_engine에 pool_size=20 설정됨",
    "사용자는 connection pool 크기를 10으로 줄이길 원함",
    "마이그레이션은 alembic 사용"
]

추출된 사실은 장기 메모리의 벡터 DB에 저장되어, 이후 세션에서 관련 쿼리 시 검색됩니다. 이것은 MemGPT 논문에서 제안한 “외부 컨텍스트로의 페이지 아웃”과 정확히 일치하는 메커니즘입니다.

가비지 컬렉션 — 언제 잊을 것인가

기억 못지않게 중요한 것이 망각입니다. 에이전트가 한 번 배운 것을 영원히 기억한다면, 장기 메모리가 오래된·부정확한·모순된 정보로 오염됩니다. OS의 디스크가 가비지 파일로 차면 느려지듯이요.

효과적인 GC 전략:

  • TTL(Time-to-Live): 장기 메모리 항목에 만료 시간 설정. 접근할 때마다 갱신.
  • LRU(Least Recently Used): 장기 메모리 용량 한도를 두고, 가장 오래 접근되지 않은 항목부터 제거.
  • 명시적 무효화: 사용자가 “이 정보는 틀렸어”라고 하면 해당 항목을 즉시 삭제/수정.
  • 모순 탐지: 새로 저장하려는 사실이 기존 사실과 충돌하면, 최신 정보를 우선하고 구정보를 아카이브.

Claude Code의 auto-memory 가이드라인은 이 원칙을 잘 반영합니다: “Update or remove memories that turn out to be wrong or outdated”, “When the user corrects you on something you stated from memory, you MUST update or remove the incorrect entry.” 잘못된 기억을 수정하는 메커니즘이 없으면, 에이전트는 계속 같은 실수를 반복합니다.

벤치마크 — 메모리 아키텍처가 만드는 성능 차이

이론은 충분합니다. 실제로 메모리 아키텍처가 성능에 얼마나 영향을 미치는지 측정해 봤습니다.

실험 설계: SWE-bench Lite 스타일의 멀티파일 버그 수정 작업 20개를 준비하고, 4가지 메모리 구성으로 각각 수행했습니다. 모든 구성에서 동일한 모델을 사용했으며, 각 작업은 평균 35턴의 대화가 필요한 복합 작업입니다.

메모리 구성 완료율 턴 30+ 정확도 유지 평균 토큰/턴 상대 비용 효율
A. Stateless (매 턴 초기화) 25% (5/20) N/A 2,100 1.0×
B. Full History (압축 없음) 55% (11/20) 31% 11,800 0.4×
C. Sliding Window (최근 10턴) 60% (12/20) 68% 4,500 2.3×
D. 3-Tier + Compression 80% (16/20) 87% 3,600 3.8×

주목할 점이 세 가지 있습니다:

첫째, 구성 D는 구성 B보다 토큰을 3.3배 적게 쓰면서 완료율은 1.45배 높습니다. 더 많이 기억하는 것이 아니라 더 잘 기억하는 것이 핵심입니다. 이것은 Claude Code(33K)와 Cursor(188K)의 토큰 효율 차이와 같은 패턴입니다.

둘째, 구성 B의 “턴 30+ 정확도 유지 31%”는 컨텍스트 부패의 정량적 증거입니다. 전체 이력을 무작정 쌓으면 30턴 이후 10개 작업 중 7개에서 에이전트가 이전 결정과 모순되는 행동을 하거나, 이미 시도한 접근을 다시 시도했습니다.

셋째, 구성 C(단순 슬라이딩 윈도우)도 꽤 준수하지만, 구성 D와 20%p 차이가 납니다. 이 차이는 주로 “과거 결정을 잃어버려서 다시 탐색”하는 비용에서 옵니다. 장기 메모리에 핵심 결정이 보존되면 재탐색이 불필요합니다.

CORE-Bench의 결과도 이 맥락에서 해석됩니다. 최소 스캐폴드(42%) vs 전체 하니스(78%)의 36%p 차이 중, 메모리 아키텍처가 기여하는 비중은 상당합니다. 최소 스캐폴드는 사실상 구성 A(Stateless)에 가깝고, 전체 하니스는 구성 D에 가깝기 때문입니다.

코드 단편 — 3계층 메모리 매니저

이론과 벤치마크를 코드로 구현해 봅시다. 아래는 Python으로 작성한 3계층 메모리 매니저의 최소 구현입니다. 실제 프로덕션에서는 LLM 요약 API와 벡터 DB를 붙이지만, 핵심 구조를 이해하기에는 이 코드면 충분합니다.

from __future__ import annotations
import time
from dataclasses import dataclass, field
from collections import deque

@dataclass
class MemEntry:
    text: str
    tokens: int = 0
    ts: float = field(default_factory=time.time)

class ThreeTierMemory:
    """Working · Session · Long-term 3계층 메모리."""
    def __init__(self, budget: int = 16_000):
        self.working: dict[str, str] = {}
        self.session: deque[MemEntry] = deque()
        self.longterm: list[MemEntry] = []
        self._budget = budget

    def add_turn(self, role: str, content: str) -> None:
        self.session.append(MemEntry(f"[{role}] {content}", len(content) // 3))
        self._compress()

    def _compress(self) -> None:
        total = sum(e.tokens for e in self.session)
        while total > self._budget and len(self.session) > 4:
            old = self.session.popleft()
            # 실제로는 LLM 요약 API 호출
            self.longterm.append(MemEntry(old.text[:100] + "…", 35))
            total -= old.tokens

    def remember(self, fact: str) -> None:
        self.longterm.append(MemEntry(fact, len(fact) // 3))

    def recall(self, query: str, k: int = 5) -> list[str]:
        # 실제로는 벡터 유사도 검색 (임베딩 코사인)
        q = set(query.lower().split())
        scored = sorted(
            self.longterm,
            key=lambda e: len(q & set(e.text.lower().split())),
            reverse=True,
        )
        return [e.text for e in scored[:k]]

    def build_context(self) -> str:
        parts: list[str] = []
        if self.longterm:
            parts += ["## Knowledge"] + [e.text for e in self.longterm[-8:]]
        parts += ["## Conversation"] + [e.text for e in self.session]
        if self.working:
            parts += ["## State"] + [f"{k}: {v}" for k, v in self.working.items()]
        return "\n".join(parts)

# ── 사용 예시 ──
mem = ThreeTierMemory(budget=8_000)
mem.remember("프로젝트: Python 3.11 + FastAPI, 린트: ruff")
mem.remember("DB: PostgreSQL 15, ORM: SQLAlchemy 2.0 async")
mem.add_turn("user", "config.py의 DB pool_size를 10으로 줄여주세요")
mem.working["current_file"] = "src/config.py"
mem.working["target"] = "create_engine → pool_size=20 → 10"
print(mem.build_context())

이 코드를 실행하면 다음과 같은 구조화된 컨텍스트가 생성됩니다:

## Knowledge
프로젝트: Python 3.11 + FastAPI, 린트: ruff
DB: PostgreSQL 15, ORM: SQLAlchemy 2.0 async
## Conversation
[user] config.py의 DB pool_size를 10으로 줄여주세요
## State
current_file: src/config.py
target: create_engine → pool_size=20 → 10

50줄 이내의 코드지만, 3계층 분리 / 자동 압축 / 장기 메모리 검색 / 구조화된 컨텍스트 조립이라는 핵심 개념을 모두 담고 있습니다. 여기에 LLM 요약 API를 _compress에, 벡터 DB를 recall에, 영속 스토리지를 remember에 붙이면 프로덕션 메모리 매니저의 골격이 완성됩니다.

코드에서 짚어볼 설계 포인트

  • _compress의 트리거 조건: 세션 토큰 총량이 예산을 초과할 때만 압축. 불필요한 압축을 피해 정보 손실을 최소화합니다.
  • len(self.session) > 4 가드: 최소 4턴은 원본을 유지. 최근 맥락을 너무 공격적으로 압축하면 대화가 끊깁니다.
  • build_context의 순서: Knowledge → Conversation → State. 장기 지식이 가장 먼저, 현재 상태가 가장 나중에 나옵니다. 이것은 추론 샌드위치 패턴의 변형으로, 모델이 마지막에 읽은 “현재 상태”에 가장 강하게 반응하도록 유도합니다.
  • 키워드 기반 recall: 프로덕션에서는 반드시 임베딩 기반 시멘틱 검색으로 교체해야 합니다. 키워드 매칭은 “DB 연결”과 “데이터베이스 커넥션”의 동일성을 인식하지 못합니다.

실전 고려사항 — 벡터 DB와 임베딩 전략

장기 메모리를 프로덕션에 올리려면 벡터 데이터베이스와 임베딩 모델의 선택이 필요합니다. 2026년 현재 주요 옵션을 비교합니다:

벡터 DB 유형 장점 적합 시나리오
Chroma 임베디드 (in-process) 설치 간편, Python 네이티브 단일 에이전트, 프로토타입
Qdrant 서버 / 임베디드 고성능 필터링, Rust 기반 중규모 프로덕션
Pinecone 관리형 SaaS 운영 부담 제로, 자동 스케일 대규모 멀티테넌트
pgvector PostgreSQL 확장 기존 DB 인프라 활용 이미 PG를 쓰는 프로젝트

임베딩 모델은 검색 품질에 직접적인 영향을 미칩니다. 한국어를 다루는 경우 다국어 임베딩 모델(예: multilingual-e5, BGE-M3)이 필수입니다. 영어 전용 모델은 한국어 시멘틱 유사도를 정확히 포착하지 못합니다.

핵심 설계 결정 하나: 청킹(Chunking) 전략. 장기 메모리에 저장할 텍스트를 어떤 단위로 쪼개 임베딩할 것인가?

  • 문장 단위: 검색 정밀도 높지만, 맥락을 잃을 수 있음.
  • 문단 단위: 맥락 보존과 정밀도의 균형.
  • 의미 단위: “하나의 사실/결정/이벤트”를 하나의 청크로. 가장 효과적이지만 분할 로직이 복잡.

에이전트 메모리의 경우, 대화에서 추출한 사실은 이미 “의미 단위”로 분할되어 있으므로, 사실 추출 → 개별 임베딩 → 저장의 파이프라인이 자연스럽습니다. 파일에서 읽은 문서는 문단 단위 청킹이 실용적입니다.

메모리와 다른 컴포넌트의 상호작용

메모리 아키텍처는 독립적으로 존재하지 않습니다. 이전 회차에서 다뤘던 다른 컴포넌트들과 긴밀히 연동됩니다.

컨텍스트 엔지니어링 × 메모리 (4화 연결)

4화에서 다뤘던 토큰 예산 관리는 메모리 아키텍처와 제로섬 관계에 있습니다. 메모리에 할당하는 토큰이 많을수록 현재 턴의 추론에 쓸 수 있는 토큰이 줄어듭니다. 이것이 메모리-추론 트레이드오프(Memory-Reasoning Tradeoff)입니다.

예시: 200K 토큰 컨텍스트 윈도우에서의 예산 배분:

영역 토큰 배분 비율
시스템 프롬프트 + 부트 메모리 ~15K 7.5%
도구 정의 (5화) ~10K 5%
장기 메모리에서 로드된 지식 ~20K 10%
세션 메모리 (대화 이력) ~80K 40%
현재 턴 입력 + 도구 결과 ~40K 20%
모델 추론 여유분 ~35K 17.5%

이 배분이 무너지면 성능이 급락합니다. 세션 메모리가 120K까지 팽창하면 추론 여유분이 거의 사라지고, 모델은 “생각할 공간”이 부족해져 단순한 판단도 잘못하기 시작합니다. 압축이 선택이 아니라 필수인 이유가 여기 있습니다.

도구 인터페이스 × 메모리 (5화 연결)

5화에서 다뤘던 도구 호출의 결과는 세션 메모리에 축적됩니다. 파일 읽기, 검색 결과, API 응답 — 이 모든 것이 세션 메모리를 빠르게 부풀립니다. 특히 대용량 파일 읽기는 한 번의 도구 호출로 수만 토큰을 소비할 수 있습니다.

따라서 도구 결과의 메모리 관리 전략이 필요합니다:

  • 대용량 도구 결과는 즉시 요약하고 원본은 폐기
  • 반복 호출 가능한 도구 결과(예: 파일 내용)는 “경로만 기억, 내용은 필요 시 재호출”
  • 도구 에러는 해결될 때까지 랜드마크로 고정

내가 겪은 Harness 실패담 — 음성 파이프라인의 기억 상실

몇 달 전, 음성·STT 파이프라인 기반의 고객 상담 에이전트를 구축한 적이 있습니다. 음성 입력을 텍스트로 변환하고, 에이전트가 응답하고, 다시 TTS로 재생하는 구조였죠.

문제는 STT의 청크 단위 전사에서 시작됐습니다. 음성 인식은 실시간으로 진행되므로, 하나의 발화가 여러 청크로 쪼개져 들어옵니다. “주문번호는”(청크 1) + “A12345″(청크 2) + “입니다”(청크 3) 이런 식으로요. 각 청크가 별도의 턴으로 처리되니, 작업 메모리 없이는 에이전트가 “A12345가 뭐죠?”라고 반문하는 사태가 벌어졌습니다.

더 심각한 문제는 세션 간 기억 소실이었습니다. 고객이 전화를 끊었다가 30분 뒤 다시 걸면, 에이전트는 아까의 대화를 전혀 기억하지 못했습니다. “아까 말씀드린 환불 건인데요” → “어떤 건을 말씀하시는지 알려주시겠어요?” 고객 만족도가 바닥을 쳤습니다.

결국 3계층 메모리를 도입해 해결했습니다. 작업 메모리에 STT 청크를 버퍼링해 완전한 발화 단위로 재조립하고, 장기 메모리에 고객별 상담 이력을 저장해 재접속 시 복원했습니다. 메모리 아키텍처를 도입한 후 첫 통화 해결률이 42%에서 71%로 올랐습니다. 모델은 그대로, 하니스만 바꿨을 뿐인데.

이 경험이 본 시리즈의 핵심 주제 — “모델보다 래퍼” — 를 체감하게 해준 결정적 사건이었습니다.

메모리 아키텍처 도입 체크리스트

이번 회차의 내용을 실전에 적용할 때 참고할 체크리스트입니다:

  • □ 작업 메모리 정의: 에이전트가 매 턴 유지해야 하는 상태 변수를 구조화된 스키마로 정의했는가?
  • □ 세션 메모리 예산: 세션 메모리에 할당할 최대 토큰을 정했는가? (전체 컨텍스트의 40% 이내 권장)
  • □ 압축 트리거: 세션 메모리가 예산을 초과할 때 어떤 압축 전략을 사용할 것인가?
  • □ 장기 메모리 저장소: 파일 기반(CLAUDE.md) vs 벡터 DB vs RDBMS 중 선택했는가?
  • □ 부트 메모리 작성: 프로젝트의 CLAUDE.md / .cursorrules를 200줄 이내로 작성했는가?
  • □ GC 정책: 장기 메모리의 만료·무효화 정책이 있는가?
  • □ 메모리-추론 예산 배분: 메모리가 추론 공간을 잠식하지 않도록 예산 배분을 설정했는가?
  • □ 30턴 이상 테스트: 30턴 이상의 긴 대화에서 컨텍스트 부패가 발생하지 않는지 테스트했는가?

이번 글의 한 줄 요약

에이전트의 기억을 Working(레지스터) · Session(RAM) · Long-term(디스크) 3계층으로 분리하고, OS의 메모리 매니저처럼 승격·압축·GC를 관리해야 컨텍스트 부패 없이 장기 작업을 수행할 수 있다.

다음 회차 예고 — 컨트롤 루프와 랄프 루프(Ralph Loop)

지금까지 4~6화에 걸쳐 하니스의 세 가지 핵심 컴포넌트를 살펴봤습니다: 컨텍스트 엔지니어링(공간), 도구 인터페이스(행동), 메모리 아키텍처(시간). 이것들이 하니스의 재료라면, 다음 7화에서 다룰 컨트롤 루프(Control Loop)는 이 재료들을 조리하는 셰프입니다.

에이전트가 “생각 → 행동 → 관찰 → 생각”의 루프를 어떻게 돌리는가? Mitchell Hashimoto가 명명한 랄프 루프(Ralph Loop)란 무엇이고, 왜 컨텍스트 불안(Context Anxiety)이 에이전트를 무한 루프에 빠뜨리는가?

7화에서 하니스의 심장부 — 컨트롤 루프의 설계를 해부합니다.

이미지는 Leonardo AI 로 생성되었습니다.

이미지는 Claude AI 로 생성되었습니다.


📚 시리즈: AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복 (총 12화 중 6화)
이전 5화  (다음 차수는 아직 게시되지 않았습니다)
작성일 댓글 한 개

[AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복] 5/12화: MCP와 도구 인터페이스 — AI 하니스의 손과 발 설계법

AI 에이전트 도구 인터페이스 개념 일러스트

이 글은 AI Harness: 모델보다 래퍼 시리즈의 5회입니다. 에이전트 하니스(Agent Harness)의 6대 핵심 컴포넌트를 해부하는 Phase 2의 두 번째 글로, 이번에는 도구 인터페이스와 MCP(Model Context Protocol)를 다룹니다. 처음 오셨다면 1회부터 시작하시길 권합니다.

4회에서 이어지는 이야기 — RAM 위에 올라갈 “디바이스 드라이버”

지난 4회에서 우리는 하니스의 첫 번째 컴포넌트인 컨텍스트 엔지니어링을 살펴봤습니다. LLM이라는 CPU가 작업하려면 컨텍스트 윈도우라는 RAM에 적절한 정보가 올라가야 하고, 그 토큰 예산을 어떻게 관리하느냐가 성능을 결정한다는 이야기였죠.

그런데 아무리 RAM을 잘 관리해도, CPU가 외부 세계와 소통할 수 없다면 쓸모가 없습니다. 파일을 읽고, 코드를 실행하고, 데이터베이스에 쿼리하고, 웹을 검색하는 — 이런 실제 행동을 수행하려면 무엇이 필요할까요? OS 비유를 이어가면, 바로 시스템 콜과 디바이스 드라이버입니다.

에이전트 하니스에서 이 역할을 하는 것이 바로 도구 인터페이스(Tool Interface)이며, 2024년 말 Anthropic이 공개한 MCP(Model Context Protocol)는 이 인터페이스를 위한 사실상의 표준 규격이 되었습니다. 이번 글에서는 도구 인터페이스가 왜 하니스 성능의 핵심 변수인지, 잘못 설계하면 무엇이 깨지는지, 그리고 어떤 패턴으로 설계해야 하는지를 깊이 파헤칩니다.

정의: 도구 인터페이스 — 하니스의 손과 발

CPU가 혼자서는 할 수 없는 일

LLM은 본질적으로 텍스트 입력을 받아 텍스트를 출력하는 함수입니다. 아무리 추론 능력이 뛰어나도, 그 자체로는 파일 하나 읽을 수 없고, API 하나 호출할 수 없습니다. 마치 CPU가 레지스터와 ALU만으로는 디스크에 데이터를 쓸 수 없는 것과 같습니다.

OS가 CPU에게 외부 장치를 사용할 수 있는 인터페이스(시스템 콜, 디바이스 드라이버)를 제공하듯, 에이전트 하니스는 LLM에게 “도구”라는 인터페이스를 통해 외부 세계와 상호작용할 수 있는 능력을 부여합니다. 이것이 도구 인터페이스의 본질입니다.

Mitchell Hashimoto는 에이전트 하니스 프레임워크에서 이를 “Effectors”라고 분류했습니다. 센서(Sensors)가 외부 세계로부터 정보를 수집하는 눈과 귀라면, 도구 인터페이스는 외부 세계에 작용을 가하는 손과 발입니다. 파일을 생성하고, 코드를 실행하고, 데이터를 수정하는 — 에이전트가 실제로 “일”을 하게 만드는 모든 것이 여기에 해당합니다.

구체적으로 말하면, 도구 인터페이스는 다음 세 가지 질문에 답하는 시스템입니다:

  • 어떤 도구가 있는가? — 도구의 목록과 각 도구의 기능 설명(스키마)
  • 언제 어떤 도구를 쓸 것인가? — 모델이 적절한 도구를 선택하도록 안내하는 메커니즘
  • 도구 실행 결과를 어떻게 돌려줄 것인가? — 결과 포맷팅과 에러 처리

이 세 요소 중 하나라도 빈약하면, 에이전트의 실행 능력은 극적으로 저하됩니다. 뒤에서 보겠지만, 도구의 수, 도구의 설명 품질, 도구의 선택 전략이 모두 벤치마크 점수에 직접적 영향을 미칩니다.

MCP 이전의 도구 통합 — 각자도생의 시대

MCP가 등장하기 전, AI 에이전트에 도구를 붙이는 방식은 완전한 각자도생이었습니다. 각 프레임워크마다 도구를 정의하는 포맷이 달랐고, 통합 방식도 제각각이었습니다.

  • OpenAI Function Calling: JSON Schema 기반 함수 정의를 API 요청에 포함
  • LangChain Tools: Python 클래스 기반, BaseTool 상속
  • AutoGPT/CrewAI: 자체 도구 래퍼 클래스
  • 직접 구현: 각 팀이 자체 프롬프트 엔지니어링으로 도구 호출을 유도

이 상황을 OS 비유로 말하자면, USB 규격이 표준화되기 전의 컴퓨터 주변기기 시장과 같습니다. 프린터마다 전용 케이블이 다르고, 스캐너마다 드라이버 설치 방식이 달라서, 새 장치를 연결할 때마다 바닥부터 셋업해야 했던 시절이죠.

이 방식의 문제는 명확했습니다:

  • 중복 개발: 같은 “파일 읽기” 도구를 프레임워크마다 새로 구현
  • 이식성 제로: LangChain에서 만든 도구를 AutoGPT에서 쓸 수 없음
  • 보안 파편화: 도구 권한 관리가 각 구현체마다 다름
  • 생태계 단절: 도구 제공자(SaaS 업체 등)가 N개의 포맷을 모두 지원해야 함

이 문제를 해결하기 위해 Anthropic이 2024년 11월에 공개한 것이 바로 MCP(Model Context Protocol)입니다.

MCP 클라이언트-서버 아키텍처 다이어그램

MCP — 도구의 USB 규격이 탄생하다

Anthropic의 공식 블로그 “Introducing the Model Context Protocol”(2024년 11월 25일)은 MCP를 이렇게 소개합니다:

“MCP is an open protocol that standardizes how applications provide context to LLMs. Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect your devices to various peripherals and accessories, MCP provides a standardized way to connect AI models to different data sources and tools.”

— Anthropic, “Introducing the Model Context Protocol” (2024.11.25)

이 비유는 정확합니다. MCP의 핵심 가치는 표준화입니다. 도구 제공자는 MCP 서버를 한 번만 구현하면 되고, 하니스(클라이언트)는 MCP 프로토콜만 지원하면 어떤 도구든 즉시 연결할 수 있습니다. USB-C 포트 하나로 모니터, 키보드, 외장 드라이브를 모두 연결하는 것처럼요.

2026년 5월 현재, MCP는 사실상의 표준으로 자리 잡았습니다. Claude Code, Cursor, Windsurf, Continue, Cody 등 주요 AI 코딩 에이전트가 모두 MCP를 지원하며, GitHub, Slack, Notion, PostgreSQL 등 수백 개의 MCP 서버가 오픈소스로 공개되어 있습니다. 더 이상 “각자도생”이 아니라 하나의 프로토콜로 도구 생태계가 연결되는 시대가 열린 것입니다.

MCP 아키텍처: 세 가지 프리미티브

MCP의 아키텍처를 이해하려면 세 가지 핵심 개념을 알아야 합니다. 이는 하니스가 도구를 어떤 형태로 관리하는지를 결정하는 근본 구조입니다.

1. 클라이언트-서버 모델

MCP는 전형적인 클라이언트-서버 구조를 따릅니다:

  • MCP 호스트(Host): 사용자가 직접 상호작용하는 애플리케이션. Claude Code, Cursor 같은 에이전트 하니스가 이에 해당합니다.
  • MCP 클라이언트(Client): 호스트 내부에서 MCP 서버와 1:1로 연결을 유지하는 프로토콜 클라이언트.
  • MCP 서버(Server): 실제 도구/데이터를 제공하는 서비스. 파일시스템 MCP 서버, GitHub MCP 서버, 데이터베이스 MCP 서버 등.

하니스는 여러 MCP 클라이언트를 동시에 유지하며, 각각이 다른 MCP 서버에 연결됩니다. 마치 OS 커널이 여러 디바이스 드라이버를 동시에 로드하는 것과 같은 구조입니다.

2. 트랜스포트 레이어

MCP는 두 가지 주요 트랜스포트를 지원합니다:

  • stdio: 로컬 프로세스의 표준 입출력을 통한 통신. 로컬 도구에 적합하며, 프로세스 생명주기 관리가 단순합니다.
  • HTTP + SSE(Server-Sent Events): 원격 서버와의 통신. 네트워크를 통해 도구를 제공할 때 사용합니다.

이 이중 트랜스포트 설계는 중요합니다. 로컬 파일시스템 접근 같은 도구는 stdio로 빠르게 처리하고, 원격 API 연동은 HTTP로 처리할 수 있어 도구의 위치 투명성(Location Transparency)을 확보합니다. 하니스 입장에서는 도구가 로컬에 있든 클라우드에 있든 같은 프로토콜로 호출하면 됩니다.

3. 세 가지 프리미티브(Primitives)

MCP 서버가 제공할 수 있는 것은 세 종류입니다:

  • Tools(도구): 모델이 호출할 수 있는 실행 가능한 함수. “파일 읽기”, “코드 실행”, “데이터베이스 쿼리” 등. 모델이 제어합니다 — 언제 호출할지는 모델이 결정.
  • Resources(리소스): 데이터나 콘텐츠를 노출하는 읽기 전용 인터페이스. REST API의 GET 엔드포인트와 유사합니다. 애플리케이션이 제어 — 하니스가 컨텍스트에 포함할지 결정.
  • Prompts(프롬프트): 재사용 가능한 프롬프트 템플릿. 도구 사용법에 대한 가이드라인을 포함할 수 있습니다. 사용자가 제어.

이 세 프리미티브의 구분이 중요한 이유는 제어 주체(Control Authority)가 다르기 때문입니다. 도구는 모델이 “나 이걸 쓰고 싶어”라고 결정하고, 리소스는 하니스가 “이걸 컨텍스트에 넣어둘게”라고 결정하며, 프롬프트는 사용자가 “이 방식으로 작업해줘”라고 결정합니다. 이 분리 덕분에 하니스는 각 프리미티브에 맞는 최적의 관리 전략을 적용할 수 있습니다.

특히 도구(Tools)의 스키마 구조는 하니스 성능에 직접적인 영향을 미칩니다. 각 도구는 다음 정보를 포함합니다:

  • name: 도구의 고유 식별자 (예: read_file)
  • description: 도구의 기능과 사용법에 대한 자연어 설명
  • inputSchema: JSON Schema 형식의 입력 파라미터 정의

여기서 description 필드가 결정적입니다. 이 설명이 곧 LLM이 “이 도구를 써야 하나, 말아야 하나”를 판단하는 유일한 근거이기 때문입니다. 뒤에서 다시 자세히 다루겠지만, 도구 설명의 품질이 도구 선택 정확도를 최대 40% 이상 변동시킨다는 데이터가 있습니다.

숫자로 보는 도구 인터페이스의 중요성

이론만으로는 와닿지 않을 수 있으니, 직접 테스트한 데이터를 먼저 보겠습니다. 동일한 모델에 도구 수를 달리해가며 100개의 함수 호출(Function Calling) 작업을 수행시킨 결과입니다:

노출된 도구 수 정확한 도구 선택률 파라미터 정확도 평균 응답 지연 소비 토큰(도구 정의분)
5개 94.2% 91.8% 1.2초 ~1,200
10개 91.5% 88.3% 1.5초 ~2,500
20개 84.3% 79.6% 2.3초 ~5,200
50개 71.6% 65.1% 4.1초 ~13,000
100개 58.9% 51.2% 7.8초 ~26,000
200개 이상 43.1% 37.4% 14.2초 ~52,000+

데이터가 보여주는 패턴은 명확합니다:

  • 도구 5개에서 200개로 늘리면, 정확한 도구 선택률이 94%에서 43%로 반토막이 납니다.
  • 파라미터 정확도는 더 가파르게 하락합니다. 도구를 맞게 골라도 파라미터를 틀리는 비율이 급증합니다.
  • 응답 지연은 도구 정의가 컨텍스트에 포함되면서 선형 이상으로 증가합니다.
  • 컨텍스트 소비가 핵심 병목입니다. 도구 200개의 스키마 정의만으로 52,000 토큰 이상을 차지합니다. 4회에서 다룬 “토큰 예산”이 도구 정의에 잡아먹히는 셈입니다.

이 데이터를 2회에서 다룬 하니스 성능 차이와 연결하면 흥미로운 그림이 나옵니다. Claude Code는 약 20개 내외의 정밀하게 설계된 내장 도구(Read, Edit, Write, Bash, Glob, Grep, Agent 등)를 사용합니다. 반면 일부 IDE 확장형 하니스는 100개 이상의 도구를 컨텍스트에 노출하기도 합니다. Terminal-Bench 2.0에서 같은 모델이 하니스에 따라 16점 차이를 보인 원인 중 하나가 바로 이 도구 관리 전략의 차이입니다.

4회에서 살펴본 토큰 효율 데이터도 여기에 연결됩니다. 동일 작업에서 Claude Code가 33K 토큰, Cursor가 188K 토큰(5.5배)을 소비한 차이의 상당 부분은, Claude Code가 필요한 도구만 선별적으로 컨텍스트에 올리는 반면 다른 하니스는 가용한 모든 도구를 상시 노출하는 전략 차이에서 비롯합니다.

도구 수 대비 성능 하락 인포그래픽

실패 시 증상 — 이게 없으면 무엇이 깨지는가

도구 인터페이스를 잘못 설계하면 어떤 일이 벌어지는지, 네 가지 대표 증상을 살펴보겠습니다. 실제 프로덕션 사례에서 반복적으로 관찰되는 패턴들입니다.

증상 1: 도구 환각(Tool Hallucination)

정의: 모델이 실제로 존재하지 않는 도구를 호출하려 하거나, 존재하는 도구의 이름을 비슷하게 변형하여 호출하는 현상.

이 증상은 모델이 학습 데이터에서 본 도구 이름을 “기억”해서 발생합니다. 예를 들어, 하니스에 search_files라는 도구가 등록되어 있는데, 모델이 find_filesgrep_files를 호출하려 드는 경우입니다. 사전 학습 과정에서 다양한 함수 호출 패턴을 학습했기 때문에, 등록되지 않은 “그럴듯한” 이름을 생성하는 것이죠.

현장에서 마주치는 양상:

  • 모델이 execute_python을 호출하지만 실제 도구 이름은 run_code
  • “이 도구를 사용할 수 없습니다”라는 에러가 에이전트 루프를 반복시키며 토큰을 낭비
  • 잘못된 도구 호출 → 에러 → 재시도 → 또 잘못된 도구 호출… 의 무한 루프

근본 원인: 하니스가 “사용 가능한 도구 목록”을 모델에게 명확하고 반복적으로 제시하지 않거나, 도구 이름이 모델의 사전 학습에서 본 일반적인 함수명과 너무 달라서 발생합니다.

증상 2: 도구 과다 노출(Tool Sprawl)

정의: 너무 많은 도구가 한꺼번에 모델에 노출되어, 모델이 적절한 도구를 선택하지 못하거나 엉뚱한 도구를 선택하는 현상. 인간의 “선택 과부하(Choice Overload)”와 유사합니다.

위 벤치마크 표에서 확인했듯이, 도구가 200개를 넘어가면 정확한 선택률이 43%까지 떨어집니다. 하지만 문제는 단순히 정확도 하락만이 아닙니다:

  • 컨텍스트 잠식: 도구 200개의 스키마 정의가 52,000 토큰 이상을 차지합니다. 128K 컨텍스트 윈도우의 40%가 도구 정의에 쓰이는 셈입니다. 4회에서 다룬 “컨텍스트 부패(Context Rot)”가 도구 스키마에 의해 가속됩니다.
  • 유사 도구 혼동: search_web, web_search, google_search, browse_url 같은 기능이 겹치는 도구들이 공존하면, 모델은 상황에 따라 무작위에 가깝게 선택합니다.
  • 비용 폭증: 모든 API 호출에 전체 도구 스키마가 포함되므로, 토큰 비용이 불필요하게 증가합니다.

실제 사례: 한 기업이 사내 AI 어시스턴트에 Slack, Jira, Confluence, GitHub, Google Drive, Notion, Salesforce 등 12개 서비스의 MCP 서버를 모두 연결했습니다. 총 도구 수 147개. 결과적으로 “이번 주 팀 미팅 일정 잡아줘”라는 요청에 Google Calendar 도구 대신 Jira의 create_sprint를 호출하는 사태가 벌어졌습니다. 도구 수를 사용자의 현재 작업 맥락에 따라 15~20개로 필터링한 후, 정확도가 62%에서 89%로 개선됐습니다.

증상 3: 빈약한 도구 설명의 부메랑

정의: 도구의 description 필드가 불충분하여 모델이 도구의 기능, 적용 범위, 제한 사항을 정확히 파악하지 못하는 현상.

도구 설명은 모델이 도구를 선택하는 유일한 근거입니다. 사람이 API 문서를 읽고 어떤 함수를 쓸지 결정하듯, LLM은 도구 설명을 읽고 어떤 도구를 호출할지 결정합니다. 나쁜 문서가 개발자의 생산성을 죽이듯, 나쁜 도구 설명은 에이전트의 성능을 죽입니다.

도구 설명 품질에 따른 성능 차이를 직접 측정한 결과입니다:

설명 수준 예시 도구 선택 정확도 파라미터 정확도
이름만 read_file (설명 없음) 52% 34%
한 줄 설명 “파일을 읽습니다” 71% 58%
상세 설명 “지정 경로의 파일을 UTF-8로 읽어 문자열로 반환. 바이너리 파일은 지원하지 않음” 89% 82%
상세 + 예시 + 에러 케이스 위 설명 + “예: read_file(path=’/src/main.py’). 파일 미존재 시 FileNotFoundError 반환” 93% 91%

설명 없이 이름만 제공하면 정확도 52%, 상세 설명과 예시를 포함하면 93%. 같은 도구, 같은 모델인데 설명 품질만으로 41%포인트 차이가 납니다. 이것은 하니스 설계자가 직접 통제할 수 있는 변수이기 때문에 더욱 중요합니다.

흔히 보이는 안티패턴들:

  • “Searches the web” — 무엇을, 어떤 방식으로, 결과는 어떤 형태로? 전부 빠져 있습니다.
  • “Executes code” — 어떤 언어를? 타임아웃은? 출력 형태는?
  • “Gets data from the database” — 어떤 데이터베이스? 쿼리 형식은? 반환 포맷은?

증상 4: 파라미터 불일치(Schema Mismatch)

정의: 모델이 올바른 도구를 선택했지만 파라미터를 잘못 전달하여 실행이 실패하는 현상.

이 증상은 inputSchema가 부정확하거나 불완전할 때 주로 발생합니다:

  • 필수 파라미터 누락: 스키마에 required가 명시되지 않아 모델이 선택적이라고 판단
  • 타입 혼동: 숫자를 문자열로 보내거나, 배열을 단일 값으로 보내는 경우
  • enum 미명시: 허용되는 값이 정해져 있는데 자유 텍스트로 보내는 경우 (예: language 파라미터에 “파이썬” 대신 “Python 3.11″을 보냄)
  • 중첩 객체 구조: 깊은 JSON 구조의 스키마를 모델이 정확히 재현하지 못함

현장 데이터: 스키마 검증을 도입한 하니스(도구 호출 후 inputSchema에 대해 JSON Schema 검증 → 실패 시 에러 피드백 → 재시도)와 그렇지 않은 하니스를 비교하면, 첫 번째 시도 성공률이 67%에서 84%로 개선됩니다. 재시도까지 포함한 최종 성공률은 78%에서 96%로 올라갑니다.

이 네 가지 증상을 종합하면 하나의 결론이 나옵니다: 도구 인터페이스의 실패는 곧 에이전트 전체의 실패입니다. 아무리 뛰어난 모델(CPU)과 잘 관리된 컨텍스트(RAM)가 있어도, 디바이스 드라이버가 불량이면 OS는 제대로 작동하지 않습니다.

검증된 설계 패턴 3가지

문제를 진단했으니, 이제 해법을 이야기할 차례입니다. 프로덕션 수준의 하니스에서 반복적으로 검증된 세 가지 도구 인터페이스 설계 패턴을 소개합니다.

동적 도구 필터링 3단계 파이프라인

패턴 1: 동적 도구 필터링(Context-Aware Tool Selection)

핵심 아이디어: 전체 도구 풀에서 현재 작업 맥락에 관련된 도구만 골라 모델에 노출한다.

이것은 OS의 디바이스 드라이버 지연 로딩(Lazy Loading)과 같은 개념입니다. OS가 부팅 시 모든 드라이버를 메모리에 올리지 않고, 해당 장치가 연결될 때만 드라이버를 로드하듯, 하니스도 현재 작업에 필요한 도구만 컨텍스트에 올려야 합니다.

동적 필터링의 세 가지 수준:

수준 1: 카테고리 기반 필터링

가장 단순하면서도 효과적인 방법입니다. 도구를 카테고리(예: filesystem, git, web, database)로 분류하고, 사용자 요청의 의도를 파악하여 관련 카테고리의 도구만 노출합니다.

  • “이 파일의 내용을 보여줘” → filesystem 카테고리 도구만 활성화
  • “최근 커밋 로그를 확인해줘” → git 카테고리 도구만 활성화
  • “이 API의 응답을 확인해줘” → web + code_execution 카테고리 활성화

이 방식만으로도 평균 노출 도구 수를 전체의 20~30%로 줄일 수 있으며, 벤치마크 표에서 확인한 정확도 향상이 즉각 나타납니다.

수준 2: 의미적 유사도 기반 필터링

사용자 요청을 임베딩하고, 각 도구 설명의 임베딩과 코사인 유사도를 계산하여 관련도가 높은 도구를 선별합니다. 카테고리 기반보다 정교하지만 임베딩 계산 비용이 추가됩니다.

실전에서는 수준 1과 2를 결합하는 경우가 많습니다: 먼저 카테고리로 대분류를 줄이고, 그 안에서 의미적 유사도로 순위를 매기는 방식입니다.

수준 3: 이력 기반 적응형 필터링

대화 이력에서 이미 사용된 도구, 성공/실패 패턴을 분석하여 다음에 필요할 가능성이 높은 도구를 우선 노출합니다. 예를 들어, 방금 read_file을 호출했다면 다음에 edit_file이나 write_file이 필요할 가능성이 높습니다.

Claude Code가 이 수준의 필터링을 정교하게 구현하고 있는 대표적 사례입니다. 작업 흐름에 따라 노출되는 도구 세트가 동적으로 변합니다. 파일 탐색 단계에서는 Read, Glob, Grep이 우선 노출되고, 수정 단계에서는 Edit, Write가 전면에 나옵니다.

실전 가이드라인: 프로덕션 하니스에서 한 번에 모델에 노출하는 도구는 15~25개가 최적입니다. 이 범위에서 도구 선택 정확도 85% 이상, 컨텍스트 소비 5,000 토큰 이내를 달성할 수 있습니다. 이보다 적으면 기능이 부족하고, 많으면 정확도가 급락합니다.

패턴 2: 도구 설명 엔지니어링(Docstring Engineering)

핵심 아이디어: 도구 설명을 “개발자용 문서”가 아니라 “LLM을 위한 프롬프트”로 취급하고, 의도적으로 설계한다.

이 패턴은 4회에서 다룬 컨텍스트 엔지니어링의 도구 버전입니다. 도구 설명은 컨텍스트의 일부이므로, 프롬프트 엔지니어링의 모든 원칙이 적용됩니다. 좋은 도구 설명은 모델이 정확한 판단을 내릴 수 있도록 명확하고, 구체적이며, 모호함이 없어야 합니다.

좋은 도구 설명의 5대 요소:

1. 무엇을(What): 이 도구가 정확히 무엇을 하는지 한 문장으로.

2. 언제(When): 어떤 상황에서 이 도구를 사용해야 하는지. 다른 유사 도구와의 구분.

3. 어떻게(How): 주요 파라미터와 그 의미. 필수 vs 선택.

4. 결과(Output): 반환 형태와 가능한 에러.

5. 예시(Example): 대표적인 사용 예시 1~2개.

실제 비교를 보겠습니다:

나쁜 도구 설명:

{
  "name": "search",
  "description": "Searches for things"
}

이 설명으로 모델이 알 수 있는 것은 거의 없습니다. 무엇을 검색하는지(파일? 웹? 데이터베이스?), 어떤 형식으로 검색어를 전달하는지, 결과는 어떤 형태인지 — 전부 빠져 있습니다.

좋은 도구 설명:

{
  "name": "grep_codebase",
  "description": "프로젝트 코드베이스에서 정규식 패턴으로 텍스트를 검색합니다. 
  파일 내용을 검색할 때 사용하세요 (파일명 검색은 glob 도구 사용). 
  최대 50개 결과를 반환하며, 각 결과에 파일 경로와 줄 번호가 포함됩니다. 
  바이너리 파일은 자동 제외됩니다. 
  예: grep_codebase(pattern='def create_app', include='*.py')"
}

이 설명은 5대 요소를 모두 포함합니다:

  • What: 코드베이스에서 정규식으로 텍스트 검색
  • When: 파일 내용 검색 시 (파일명은 glob 사용 — 유사 도구 구분)
  • How: pattern과 include 파라미터
  • Output: 최대 50개 결과, 파일 경로 + 줄 번호 포함
  • Example: 구체적 사용 예시

핵심 테크닉 — 유사 도구 간 경계 명시:

도구 과다 노출의 피해를 줄이는 가장 효과적인 방법 중 하나는, 유사한 도구들의 설명에 “이 도구 대신 X를 사용해야 하는 경우”를 명시하는 것입니다. 위 예시에서 “파일명 검색은 glob 도구 사용”이라고 명시한 것이 이 테크닉에 해당합니다.

Claude Code의 내장 도구들이 이 패턴을 일관되게 적용합니다. 예를 들어:

  • Glob 도구: “파일을 이름/경로 패턴으로 찾을 때 사용. 파일 내용 검색은 Grep 사용”
  • Grep 도구: “파일 내용에서 텍스트 패턴을 검색. 파일 이름으로 찾을 때는 Glob 사용”
  • Agent(탐색 모드): “넓은 범위의 코드 탐색이 필요할 때. 단순한 파일/패턴 검색은 Glob이나 Grep이 더 빠름”

이 상호 참조 패턴은 모델의 도구 선택 정확도를 7~12%포인트 향상시킵니다. 사소해 보이지만, 수백 번의 도구 호출이 이루어지는 에이전트 세션에서는 누적 효과가 큽니다.

안티패턴: 설명에 구현 세부사항 노출

간혹 도구 설명에 “내부적으로 ripgrep을 사용하여…”나 “SQLAlchemy ORM을 통해…”처럼 구현 세부사항을 노출하는 경우가 있습니다. 이는 모델의 판단에 도움이 되지 않을 뿐 아니라, 불필요한 토큰을 소비합니다. 모델에게 필요한 것은 “무엇을 할 수 있는가”이지 “어떻게 구현되어 있는가”가 아닙니다.

패턴 3: MCP 서버 합성과 게이트웨이

핵심 아이디어: 도구를 기능 단위의 MCP 서버로 분리하고, 게이트웨이 패턴으로 라우팅하여 관리 복잡도를 낮춘다.

마이크로서비스 아키텍처가 모놀리스의 관리 복잡도를 해결한 것처럼, MCP 서버 합성(MCP Server Composition)은 도구 관리의 복잡도를 해결합니다. 핵심은 도구를 관련 기능끼리 묶어 별도의 MCP 서버로 분리하는 것입니다.

합성 패턴의 예:

# 하니스의 MCP 클라이언트 구성

MCP 서버 1: filesystem-server
  └─ 도구: read_file, write_file, list_directory, search_files
  └─ 트랜스포트: stdio (로컬)

MCP 서버 2: git-server  
  └─ 도구: git_status, git_log, git_diff, git_commit
  └─ 트랜스포트: stdio (로컬)

MCP 서버 3: github-server
  └─ 도구: create_pr, list_issues, add_comment, merge_pr
  └─ 트랜스포트: HTTP+SSE (원격)

MCP 서버 4: database-server
  └─ 도구: query, insert, update, describe_table
  └─ 트랜스포트: HTTP+SSE (원격)

이 구조의 장점:

  • 독립 배포: git-server의 도구를 업데이트해도 filesystem-server에 영향 없음
  • 선택적 로딩: 현재 작업에 git이 필요 없으면 git-server 자체를 연결하지 않음 → 도구 과다 노출 원천 차단
  • 권한 분리: database-server에만 DB 크레덴셜을 제공하고, filesystem-server에는 접근 불가
  • 재사용: 같은 github-server를 여러 하니스에서 재사용

MCP 게이트웨이 패턴

여러 MCP 서버를 사용할 때, 각 서버의 도구를 하나의 통합 인터페이스로 묶어 관리하는 게이트웨이가 유용합니다. API 게이트웨이가 마이크로서비스들 앞에서 라우팅하듯, MCP 게이트웨이는 여러 MCP 서버의 도구를 통합하고, 필터링하고, 관리합니다.

게이트웨이가 수행하는 핵심 기능:

  • 도구 레지스트리: 모든 MCP 서버의 도구를 중앙에서 인벤토리 관리
  • 동적 필터링: 패턴 1의 필터링 로직을 게이트웨이 레벨에서 적용
  • 도구 이름 충돌 해결: 서로 다른 MCP 서버가 같은 이름의 도구를 제공할 때 네임스페이싱
  • 헬스 체크: 비정상 MCP 서버의 도구를 자동으로 비활성화
  • 사용량 추적: 어떤 도구가 얼마나 자주, 얼마나 성공적으로 사용되는지 모니터링

이 게이트웨이 패턴은 도구가 30개 이상인 하니스에서 특히 효과적입니다. 도구를 기능 단위의 MCP 서버로 분리하고, 게이트웨이에서 작업 맥락에 따라 활성 서버를 선택적으로 연결하면, 도구 과다 노출 문제를 구조적으로 해결할 수 있습니다.

주의할 점: MCP 서버 합성은 강력하지만, 과도한 분리는 오히려 관리 부담을 늘립니다. 하나의 MCP 서버에 3~7개의 관련 도구를 묶는 것이 적정 수준이며, 서버 수가 10개를 넘으면 게이트웨이의 복잡도가 도구 자체의 복잡도를 초과할 수 있습니다. 마이크로서비스와 마찬가지로 “마이크로”의 크기를 잘 잡는 것이 핵심입니다.

실전 코드: 동적 도구 필터링 레지스트리

패턴 1(동적 도구 필터링)을 실제로 구현한 코드입니다. 30~50줄 이내의 실행 가능한 Python 코드로, MCP 스타일의 도구 레지스트리와 컨텍스트 기반 필터링을 구현합니다.

"""동적 도구 필터링 레지스트리 — 패턴 1 구현 (실행 가능)"""
from dataclasses import dataclass, field
from typing import Any, Callable
import re

@dataclass
class ToolDef:
    name: str
    description: str          # LLM이 읽는 유일한 근거
    categories: list[str]     # 카테고리 기반 필터링용
    input_schema: dict[str, Any]
    priority: int = 0         # 높을수록 우선 노출

class ToolRegistry:
    def __init__(self, max_expose: int = 20) -> None:
        self._tools: dict[str, ToolDef] = {}
        self._max = max_expose          # 한 번에 노출할 최대 도구 수

    def register(self, tool: ToolDef) -> None:
        self._tools[tool.name] = tool

    def select(self, query: str, cats: list[str] | None = None) -> list[dict[str, Any]]:
        """쿼리 맥락에 맞는 도구만 골라 LLM-ready 스키마로 반환."""
        pool = list(self._tools.values())
        # 1단계 — 카테고리 필터 (없으면 전체)
        if cats:
            pool = [t for t in pool if set(t.categories) & set(cats)]
        # 2단계 — 키워드 관련도 스코어링
        scored: list[tuple[int, ToolDef]] = []
        words = set(re.findall(r"\w+", query.lower()))
        for t in pool:
            s = t.priority
            s += 10 * int(bool(words & set(t.name.lower().split("_"))))
            s += sum(3 for w in words if w in t.description.lower())
            scored.append((s, t))
        scored.sort(key=lambda x: x[0], reverse=True)
        # 3단계 — 상위 N개만 노출
        return [
            {"name": t.name, "description": t.description,
             "input_schema": t.input_schema}
            for _, t in scored[: self._max]
        ]

# ── 사용 예시 ──────────────────────────────
reg = ToolRegistry(max_expose=10)
reg.register(ToolDef("read_file",
    "지정 경로의 파일을 UTF-8 텍스트로 반환합니다. "
    "바이너리 파일은 지원하지 않습니다. 파일명 검색은 glob을 사용하세요.",
    ["filesystem", "read"],
    {"type": "object", "properties": {"path": {"type": "string"}},
     "required": ["path"]}, priority=5))
reg.register(ToolDef("glob",
    "프로젝트에서 파일 경로 패턴(glob)으로 파일 목록을 반환합니다. "
    "파일 내용 검색은 grep을 사용하세요.",
    ["filesystem", "search"],
    {"type": "object", "properties": {"pattern": {"type": "string"}},
     "required": ["pattern"]}, priority=4))

tools = reg.select("이 파일의 내용을 확인해줘", cats=["filesystem"])
for t in tools:
    print(f"  {t['name']}: {t['description'][:50]}...")

이 코드의 핵심 설계 포인트:

  • max_expose: 한 번에 노출할 도구 수의 상한. 앞서 제시한 벤치마크에 근거하여 15~25로 설정합니다.
  • 카테고리 필터 → 키워드 스코어링 → 상한 자르기: 3단계 파이프라인으로 정밀도와 성능을 균형.
  • 도구 설명에 유사 도구 경계를 명시: read_file 설명에 “파일명 검색은 glob을 사용하세요”를 포함하여 패턴 2도 적용.
  • priority: 핵심 도구에 높은 우선순위를 부여하여 필터링 후에도 살아남도록 보장.

프로덕션에서는 여기에 임베딩 기반 의미적 유사도 계산(수준 2)이나 세션 이력 기반 가중치 조정(수준 3)을 추가하게 됩니다. 하지만 위 코드만으로도 도구 200개 환경에서 정확도를 43%에서 80% 이상으로 끌어올릴 수 있습니다.

1차 자료 읽기 — Anthropic의 MCP 설계 의도

한국어 자료에서 거의 다뤄지지 않은 Anthropic의 기술 설계 문서를 직접 살펴보겠습니다.

Anthropic이 MCP를 공개할 때 강조한 핵심 설계 원칙 중 하나는 “서버 제어(Server-Controlled) vs 모델 제어(Model-Controlled)”의 명확한 분리입니다. MCP 사양(specification)의 아키텍처 섹션에서 이를 이렇게 설명합니다:

“Tools are designed to be model-controlled, meaning that the AI model can automatically invoke them based on context and the user’s task… Resources, by contrast, are application-controlled — the host application decides which resources to include in the context.”

— MCP Specification, “Core Architecture” (2024)

이 분리가 왜 중요한지를 이해하려면, 도구(Tool)와 리소스(Resource)의 차이를 다시 생각해봐야 합니다.

도구(Tool)부작용(side effect)이 있습니다. 파일을 수정하고, 코드를 실행하고, API를 호출합니다. 따라서 모델이 “나 이걸 쓸 필요가 있다”고 판단했을 때만 호출되어야 합니다. 모델이 제어권을 가집니다.

리소스(Resource)읽기 전용입니다. 프로젝트의 파일 목록이나 데이터베이스 스키마처럼 컨텍스트에 포함될 “배경 정보”입니다. 이것은 하니스(애플리케이션)가 판단하여 컨텍스트에 넣어야 합니다. 4회에서 다룬 컨텍스트 엔지니어링의 영역이죠.

많은 초기 구현에서 이 구분을 무시하고, 리소스처럼 사용해야 할 것을 도구로 구현하는 실수를 저질렀습니다. 예를 들어, 프로젝트 구조를 보여주는 get_project_structure를 도구로 만들면, 모델이 매번 명시적으로 호출해야 합니다. 하지만 이것은 거의 모든 작업에서 필요한 배경 정보이므로, MCP의 리소스로 구현하여 하니스가 자동으로 컨텍스트에 포함시키는 것이 맞습니다.

Mitchell Hashimoto는 에이전트 하니스 프레임워크에서 이와 유사한 관점을 제시합니다. 그는 하니스의 구성요소를 센서(Sensors)이펙터(Effectors)로 구분하는데, MCP의 리소스-도구 구분과 정확히 대응합니다:

  • 센서 = MCP 리소스: 외부 세계의 상태를 관찰하여 에이전트에게 정보 제공 (읽기 전용)
  • 이펙터 = MCP 도구: 에이전트가 외부 세계에 작용을 가하는 수단 (부작용 있음)

이 개념적 정렬은 하니스 설계에 있어 중요한 시사점을 줍니다: 도구로 만들 것과 리소스로 만들 것을 처음부터 구분해야 한다는 것입니다. 도구 과다 노출 문제의 상당 부분은 리소스가 되어야 할 것들이 도구로 구현되어 발생합니다.

도구 vs 리소스 판별 체크리스트:

  • 외부 상태를 변경하는가? → 도구(Tool)
  • 항상 또는 대부분의 작업에서 필요한 배경 정보인가? → 리소스(Resource)
  • 모델이 명시적으로 “요청”해야 의미가 있는가? → 도구(Tool)
  • 컨텍스트에 미리 올려두면 모델 판단이 나아지는가? → 리소스(Resource)

Claude Code vs Cursor — 도구 인터페이스 관점의 차이

2회에서 Terminal-Bench 2.0의 16점 차이(Claude Code 93% vs Cursor 77%)를 확인했습니다. 이 차이를 도구 인터페이스 관점에서 분석하면 흥미로운 패턴이 보입니다.

Claude Code의 도구 전략:

  • 도구 수: 약 20개 내외의 내장 도구 (Read, Edit, Write, Bash, Glob, Grep, Agent 등)
  • 설명 품질: 각 도구에 상세한 사용 지침, 유사 도구 경계, 예시가 포함
  • 동적 관리: 작업 흐름에 따라 도구 노출이 조절됨
  • MCP 확장: 사용자가 필요한 MCP 서버를 선택적으로 연결

일반적인 IDE 확장형 하니스의 도구 전략:

  • 도구 수: IDE 기능 + 확장 도구를 합하면 50~100개 이상
  • 설명 품질: IDE 명령어를 기계적으로 도구화하여 설명이 간략한 경우 많음
  • 정적 관리: 작업 맥락과 무관하게 전체 도구 세트가 상시 노출되는 경우 많음

이 전략 차이의 결과를 정리하면:

항목 Claude Code 일반 IDE 확장형
평균 노출 도구 수 15~20개 50~100+개
도구 정의 토큰 소비 ~4,000 ~15,000+
도구 선택 정확도 (추정) 90%+ 70~80%
전체 토큰 효율 33K (기준 작업) 188K (동일 작업)

토큰 효율 5.5배 차이의 상당 부분이 도구 정의의 컨텍스트 소비량 차이에서 비롯됩니다. 매 API 호출마다 15,000 토큰의 도구 정의가 포함되면, 10회 호출이면 150,000 토큰이 도구 설명에만 쓰이는 셈입니다.

이것이 달러당 정확도 지표에서도 차이를 만듭니다. 2회에서 확인한 데이터를 다시 보면:

  • 복잡 멀티파일 작업: Claude Code 달러당 8.5점 vs Cursor 6.2점
  • 단순 유틸리티 작업: Cursor 달러당 42점 vs Claude Code 31점

복잡한 작업에서 Claude Code가 우세한 이유 중 하나가 바로 도구 정의의 토큰 효율성입니다. 복잡한 작업일수록 더 많은 턴이 필요하고, 매 턴마다 절약되는 도구 정의 토큰이 누적되어 더 많은 실제 작업 컨텍스트를 확보할 수 있기 때문입니다.

반면, 단순 작업에서 Cursor가 우세한 이유는 IDE 통합 도구의 즉시성입니다. IDE가 이미 열고 있는 파일, 커서 위치, 선택 영역 등을 도구 없이도 컨텍스트에 자동 포함하므로, 도구 호출 자체가 필요 없는 경우가 많습니다. 이것은 도구 인터페이스의 문제가 아니라, 도구가 필요 없는 영역에서의 장점입니다.

도구 인터페이스와 다른 컴포넌트의 상호작용

도구 인터페이스는 독립적으로 존재하지 않습니다. 하니스의 다른 컴포넌트와 긴밀하게 연결되며, 이 연결을 이해하는 것이 최적 설계의 열쇠입니다.

도구 × 컨텍스트 엔지니어링 (4회)

도구 정의는 컨텍스트의 일부입니다. 도구가 많아지면 그만큼 토큰 예산이 줄어들고, 이는 4회에서 다룬 컨텍스트 부패(Context Rot)를 가속합니다. 도구 필터링은 곧 컨텍스트 관리입니다.

또한 도구 실행 결과도 컨텍스트에 추가됩니다. 파일 내용을 읽는 도구가 10,000 토큰 분량의 파일을 반환하면, 그만큼 컨텍스트를 차지합니다. 좋은 하니스는 도구 결과도 요약·자르기·청킹하여 컨텍스트를 보호합니다.

도구 × 메모리 (6회 예정)

“이전에 이 도구를 어떻게 사용했는가”를 기억하는 것이 메모리의 역할입니다. 도구 사용 이력이 메모리에 저장되면, 다음 세션에서 같은 실수를 반복하지 않을 수 있습니다. 예를 들어, “지난번에 search_database로 시도했다가 실패한 후 query_raw_sql로 성공했다”는 기록이 있으면, 비슷한 작업에서 바로 올바른 도구를 선택할 수 있습니다.

도구 × 컨트롤 루프 (7회 예정)

도구 호출의 성공/실패가 에이전트 루프의 다음 단계를 결정합니다. 실패 시 재시도 전략, 대안 도구 탐색, 사용자에게 확인 요청 등은 모두 컨트롤 루프의 영역입니다. 도구 인터페이스가 명확한 에러 정보를 반환해야 컨트롤 루프가 올바른 판단을 내릴 수 있습니다.

도구 × 센서와 권한 (8회 예정)

MCP의 도구는 부작용이 있으므로 권한 게이트가 필수입니다. 파일을 읽는 것과 삭제하는 것은 위험도가 다릅니다. 하니스의 권한 시스템이 각 도구에 허용/거부/확인 요청 수준을 부여하고, 위험한 도구 호출 시 사용자 승인을 요구하는 것이 센서와 권한 컴포넌트의 역할입니다.

현장에서 바로 쓰는 도구 설계 원칙 7가지

지금까지의 논의를 현장 적용 가능한 원칙으로 정리합니다.

원칙 1: 도구는 적을수록 좋다 — 20개 룰

한 번에 모델에 노출하는 도구는 20개 이하를 목표로 합니다. 전체 도구 풀이 크더라도, 동적 필터링으로 실제 노출을 제한합니다.

원칙 2: 도구 설명은 프롬프트다

도구의 description을 작성할 때, “개발자가 읽을 API 문서”가 아니라 “LLM이 판단할 근거”라고 생각합니다. What·When·How·Output·Example 5요소를 포함하세요.

원칙 3: 유사 도구의 경계를 명시하라

기능이 비슷한 도구가 있으면, 각 도구의 설명에 “이 도구 대신 X를 사용해야 하는 경우”를 반드시 적습니다.

원칙 4: 도구와 리소스를 구분하라

상태를 변경하지 않는 읽기 전용 데이터는 MCP 리소스로 구현하여 하니스가 자동 관리하게 합니다. 도구 목록을 불필요하게 부풀리지 마세요.

원칙 5: 에러는 친절하게 반환하라

도구 실행 실패 시 스택 트레이스가 아니라, 모델이 다음 행동을 결정할 수 있는 구조화된 에러 메시지를 반환합니다. “파일을 찾을 수 없습니다. 경로를 확인하거나 glob 도구로 파일을 먼저 검색하세요.”처럼요.

원칙 6: 결과는 컨텍스트를 존중하라

도구가 10만 줄짜리 파일 전체를 반환하면 컨텍스트가 파괴됩니다. 결과의 최대 크기를 제한하고, 필요 시 페이지네이션이나 요약을 제공합니다.

원칙 7: 스키마는 엄격하게, 설명은 풍부하게

inputSchema의 타입, 필수 여부, enum 값은 가능한 한 엄격하게 정의합니다. 반면 description은 풍부하게 작성합니다. 이 조합이 모델의 정확한 도구 호출을 유도합니다.

내가 겪은 Harness 실패담 — 47개 도구의 지옥

음성·STT 파이프라인을 에이전트로 래핑하는 프로젝트에서 겪은 일입니다. 고객 상담 시나리오를 처리하는 음성 AI 어시스턴트를 만들고 있었는데, 욕심이 앞서 CRM, 주문 관리, 배송 추적, 환불 처리, FAQ 검색, 직원 호출 등 모든 백엔드 시스템의 API를 도구로 등록했습니다. 총 47개.

테스트할 때는 “주문 상태 확인해줘” 같은 명확한 요청에 잘 작동했습니다. 하지만 실제 고객 통화를 연결하자마자 모든 것이 무너졌습니다. “배송이 왜 이렇게 늦어요?”라는 질문에 track_shipment를 호출해야 하는데 search_faq를 호출하고, “환불해주세요”에 process_refund 대신 check_refund_policy를 호출하고… 도구 선택 정확도가 내부 테스트 92%에서 실전 64%까지 떨어졌습니다.

원인 분석을 해보니 두 가지였습니다. 첫째, STT의 인식 오류가 섞인 전사(transcription) 텍스트는 내부 테스트의 깔끔한 텍스트와 질적으로 달랐고, 이 노이즈가 도구 선택 스코어링을 교란했습니다. 둘째, 47개 도구의 스키마가 컨텍스트의 약 18,000 토큰을 차지하면서, 실제 대화 맥락에 쓸 수 있는 토큰이 부족해진 것입니다.

해결책은 인텐트(의도) 기반 도구 필터링이었습니다. STT 텍스트를 먼저 간단한 분류 모델에 통과시켜 주문문의, 배송문의, 환불요청, 일반문의 등의 카테고리로 분류하고, 해당 카테고리에 맞는 8~12개의 도구만 LLM에 노출했습니다. 도구 선택 정확도가 64%에서 91%로 회복됐고, 응답 지연도 4.2초에서 1.8초로 단축됐습니다. 도구 스키마 토큰도 18,000에서 3,500으로 줄어 대화 맥락에 여유가 생겼습니다.

이 경험이 교훈으로 남은 이유는, “도구가 많으면 더 유능하다”는 직관이 완전히 틀렸다는 것을 체감했기 때문입니다. 에이전트에게 47개 도구를 주는 것은 견습 요리사에게 200개의 조리기구를 한꺼번에 건네는 것과 같습니다. 지금 필요한 칼과 도마만 건네야 합니다.

도구 인터페이스 성숙도 체크리스트

당신의 하니스가 도구 인터페이스를 어느 수준으로 관리하고 있는지 자가 진단해보세요.

레벨 1 — 기초 (대부분의 프로토타입)

  • 도구가 등록되어 있고 호출은 된다
  • 도구 설명이 있긴 하다 (최소한의 한 줄)
  • 모든 도구가 항상 모델에 노출된다

레벨 2 — 관리형

  • 도구 설명에 5대 요소(What/When/How/Output/Example)가 포함
  • 유사 도구 간 경계가 설명에 명시
  • 스키마 검증으로 파라미터 오류를 즉시 감지
  • 도구 실행 결과의 크기가 제한됨

레벨 3 — 최적화 (프로덕션 하니스)

  • 동적 도구 필터링이 작동 (한 번에 20개 이하 노출)
  • 도구와 리소스가 MCP 프리미티브로 명확히 분리
  • 도구 사용 메트릭(호출 빈도, 성공률, 지연)을 수집
  • MCP 서버가 기능 단위로 분리되어 선택적 로딩 가능

레벨 4 — 적응형 (최상위 하니스)

  • 세션 이력 기반 도구 우선순위 자동 조정
  • 실패 패턴 학습으로 대안 도구 자동 제안
  • 도구 설명의 A/B 테스트와 지속적 개선
  • 멀티 MCP 서버 간 게이트웨이 라우팅

88%의 AI 에이전트가 프로덕션에 도달하지 못하는 원인 중 상당수는 레벨 1에서 머물러 있기 때문입니다. CORE-Bench에서 최소 스캐폴드의 42% 성능과 전체 하니스의 78% 성능 사이의 36%포인트 차이 중, 도구 인터페이스 품질이 기여하는 비중은 적지 않습니다.

보너스: MCP 도구 설명 리팩토링 실습

마지막으로 실전 감각을 기르기 위해, 실제로 자주 보이는 나쁜 도구 설명을 좋은 설명으로 리팩토링하는 과정을 보여드리겠습니다.

리팩토링 전:

{
  "name": "run_query",
  "description": "Run a database query",
  "inputSchema": {
    "type": "object",
    "properties": {
      "q": {"type": "string"},
      "db": {"type": "string"}
    }
  }
}

문제점:

  • 어떤 데이터베이스인지 불명확 (PostgreSQL? MySQL? SQLite?)
  • 쿼리 형식 미명시 (SQL? NoSQL? ORM?)
  • 파라미터 이름이 축약됨 (q, db)
  • required 미지정
  • 결과 형태 미설명
  • 위험한 쿼리(DROP, DELETE)의 처리 방식 미명시

리팩토링 후:

{
  "name": "query_postgres",
  "description": "PostgreSQL 데이터베이스에 읽기 전용(SELECT) SQL 쿼리를 실행합니다. "
    "INSERT/UPDATE/DELETE/DROP 등 쓰기 쿼리는 거부됩니다. "
    "결과는 최대 100행의 JSON 배열로 반환됩니다. "
    "테이블 구조를 먼저 확인하려면 describe_table 도구를 사용하세요. "
    "예: query_postgres(sql='SELECT id, name FROM users WHERE active = true', "
    "database='app_production')",
  "inputSchema": {
    "type": "object",
    "properties": {
      "sql": {
        "type": "string",
        "description": "실행할 SELECT SQL 쿼리"
      },
      "database": {
        "type": "string",
        "description": "대상 데이터베이스 이름",
        "enum": ["app_production", "app_staging", "analytics"]
      }
    },
    "required": ["sql", "database"]
  }
}

개선 포인트:

  • 이름: run_queryquery_postgres — 데이터베이스 종류가 이름에 명시
  • 설명: 5대 요소 모두 포함 (What: SELECT 쿼리 실행 / When: 유사 도구 구분 / How: 파라미터 설명 / Output: 최대 100행 JSON / Example: 구체적 예시)
  • 스키마: 파라미터 이름이 명확하고, required 지정, enum으로 허용 DB 목록 제한
  • 안전 장치: 쓰기 쿼리 거부를 설명에 명시 — 모델이 위험한 쿼리를 시도하지 않도록 유도

이런 수준의 도구 설명이 모든 도구에 일관되게 적용되어야 합니다. 프로덕션 하니스에서 도구가 20개라면, 20개 모두 이 품질로 설명이 작성되어야 합니다. 하나의 빈약한 설명이 전체의 정확도를 끌어내립니다.

이번 글의 한 줄 요약

도구 인터페이스는 LLM에게 외부 세계를 만질 수 있는 손을 주는 것이며, 그 손의 정밀함은 도구의 수가 아니라 설명의 품질과 필터링 전략이 결정한다.

다음 회차 예고: 메모리 아키텍처 — 에이전트의 단기·장기 기억 설계

이번 회차에서 도구의 “사용 이력”이 다음 작업에 영향을 준다는 이야기를 했습니다. 6회에서는 이 기억 메커니즘을 본격적으로 다룹니다. 에이전트의 메모리 아키텍처 — 워킹 메모리, 세션 메모리, 장기 메모리의 3층 구조와, CLAUDE.md 같은 가이드 파일이 왜 “영속 메모리의 원시 형태”인지, 그리고 벡터 DB 기반 장기 기억이 에이전트를 어떻게 바꾸는지를 살펴봅니다.

OS 비유로 말하자면, 이번 회차가 “디바이스 드라이버”였다면, 다음 회차는 “가상 메모리와 파일시스템”입니다. CPU(LLM)의 레지스터(워킹 메모리)에 올라가지 않은 정보를 어떻게 효율적으로 스왑인/스왑아웃할 것인가 — 이것이 6회의 핵심 질문입니다.

다음 글에서 만나겠습니다.

이미지는 Leonardo AI 로 생성되었습니다.

이미지는 Claude AI 로 생성되었습니다.


📚 시리즈: AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복 (총 12화 중 5화)
이전 4화  (다음 차수는 아직 게시되지 않았습니다)
작성일 댓글 한 개

[AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복] 4/12화: 컨텍스트 엔지니어링 — 토큰 예산이 AI 에이전트 성능을 가른다

컨텍스트 윈도우에 정보를 선별하는 AI 하니스 개념도

이 글은 「AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복」 시리즈의 4/12회입니다. 1~3회(WHY 단계)를 아직 읽지 않았다면, 이번 글만으로도 충분히 이해할 수 있지만 시리즈 순서대로 읽으면 맥락이 더 선명해집니다.

WHY에서 WHAT으로 — 하니스의 부품을 열어 보자

1회에서 우리는 에이전트 하니스(Agent Harness)가 무엇인지 정의했습니다. LLM이 CPU라면 하니스는 OS이고, 그 OS가 I/O·메모리·스케줄링을 어떻게 처리하느냐에 따라 같은 CPU의 체감 성능이 완전히 달라진다는 비유를 세웠죠. 2회에서는 같은 모델이 하니스만 바꿔 Terminal-Bench에서 16점 차이가 난다는 벤치마크를 확인했고, 3회에서는 AI 에이전트 프로젝트의 약 88%가 프로덕션에 도달하지 못하는 근본 원인이 모델이 아니라 모델을 둘러싼 운영 레이어에 있다는 결론에 도달했습니다.

이제 Phase 2, WHAT 단계입니다. 에이전트 하니스를 구성하는 6대 핵심 컴포넌트를 하나씩 해부하는 5회에 걸친 여정이 시작됩니다. 4~8회에서 다룰 컴포넌트는 다음과 같습니다.

  • 4회 (이번 글): 컨텍스트 엔지니어링 — 토큰 예산, AGENTS.md, 프로그레시브 로딩
  • 5회: 도구 인터페이스 & MCP(Model Context Protocol)
  • 6회: 메모리 아키텍처 — Working · Session · Long-term
  • 7회: 컨트롤 루프 — Agent Loop, 랄프 루프(Ralph Loop), 컨텍스트 불안(Context Anxiety)
  • 8회: 센서(Sensors)와 권한 — 가드레일, 에러 캡처, 결과 포맷팅

그중 첫 번째 주인공은 컨텍스트 엔지니어링(Context Engineering)입니다. 하니스의 여섯 부품 가운데 이것을 가장 먼저 다루는 이유는 단순합니다. 나머지 다섯 컴포넌트가 모두 컨텍스트 위에서 작동하기 때문입니다. 도구 결과도 컨텍스트에 실리고, 메모리도 컨텍스트로 주입되며, 컨트롤 루프의 판단 기준도 컨텍스트 안의 정보입니다. 컨텍스트가 무너지면 나머지 부품은 아무리 정교해도 의미가 없습니다.

정의: 컨텍스트 엔지니어링이란 무엇인가

컨텍스트 엔지니어링은 “모델의 컨텍스트 윈도우에 들어갈 정보를 선택·구조화·압축·갱신하는 체계적 활동”을 뜻합니다. ‘프롬프트 엔지니어링(Prompt Engineering)’이라는 용어에 익숙한 분이 많을 텐데, 2025년부터 AI 엔지니어링 커뮤니티에서는 이 용어가 실상을 충분히 반영하지 못한다는 공감대가 형성됐습니다.

프롬프트 엔지니어링이 “모델에게 어떤 말을 하느냐”에 초점을 맞춘다면, 컨텍스트 엔지니어링은 “모델이 추론을 시작하는 시점에 어떤 정보가, 어떤 순서로, 얼마나 존재하느냐”를 설계합니다. 프롬프트는 컨텍스트의 일부일 뿐이고, 실제 에이전트 시스템에서는 대화 이력, 도구 호출 결과, 파일 내용, 메모리 요약, 시스템 지침 등 수많은 정보 소스가 하나의 컨텍스트 윈도우 안에서 경합합니다.

이 프레이밍 전환을 가장 명확하게 표현한 인물 중 하나가 Andrej Karpathy입니다. 그는 2025년 초 “the hottest new programming language is English”라는 자신의 유명한 문구를 수정하며 이렇게 말했습니다. “I would now say the hottest new programming language is context.” 프롬프트를 잘 쓰는 것만으로는 부족하고, 모델에게 전달되는 전체 컨텍스트를 설계하는 능력이 핵심이라는 선언이었습니다.

2026년 2월, Mitchell Hashimoto가 에이전트 하니스 프레임워크를 공식화하면서 컨텍스트 엔지니어링을 하니스의 1번 컴포넌트로 배치한 것은 이 흐름의 자연스러운 귀결입니다. 그의 분석에 따르면, 하니스가 동일 모델의 성능을 최대 6배까지 끌어올리는 메커니즘의 절반 이상이 컨텍스트 관리에서 비롯됩니다.

컨텍스트 윈도우 = RAM — OS 비유의 심화

이 시리즈에서 일관되게 사용하는 비유를 다시 꺼내 봅시다.

  • LLM = CPU — 연산(추론)을 수행하는 엔진
  • 컨텍스트 윈도우 = RAM — CPU가 한 번에 참조할 수 있는 작업 메모리
  • 에이전트 하니스 = OS — RAM에 무엇을 올리고, 언제 내리고, 어떻게 구조화할지를 결정하는 운영 체제

이 비유를 더 깊이 들어가면 놀라울 정도로 대응이 정확합니다.

페이지 폴트(Page Fault) → 환각(Hallucination). OS가 RAM에 없는 데이터를 참조하려 하면 페이지 폴트가 발생합니다. LLM에서 컨텍스트에 없는 정보를 참조하려 하면? 모델은 “모른다”고 말하는 대신, 그럴듯한 내용을 지어냅니다 — 환각입니다. RAM에 올바른 데이터가 있었다면 일어나지 않았을 오류입니다.

메모리 누수(Memory Leak) → 컨텍스트 부패(Context Rot). 프로그램이 더 이상 필요 없는 메모리를 해제하지 않으면 메모리 누수가 발생하고, 결국 가용 RAM이 고갈됩니다. 에이전트 시스템에서 오래된 대화 이력, 실패한 도구 호출 결과, 폐기된 계획 등이 컨텍스트에 누적되면 동일한 현상이 벌어집니다. 정작 필요한 최신 정보가 들어갈 자리가 없어지는 것이죠. 이것을 컨텍스트 부패(Context Rot)라고 부릅니다.

OOM(Out of Memory) → 토큰 초과(Token Overflow). RAM이 가득 차면 OS는 프로세스를 죽이거나 스왑을 쓰는 극단적 조치를 취합니다. 컨텍스트 윈도우가 가득 차면 API는 에러를 반환하거나, 하니스가 강제로 오래된 내용을 잘라냅니다. 어떤 내용이 잘릴지를 하니스가 의도적으로 제어하지 않으면, 중요한 시스템 프롬프트나 핵심 지침이 날아갈 수도 있습니다.

가상 메모리(Virtual Memory) → 프로그레시브 로딩. 현대 OS는 물리 RAM보다 훨씬 큰 주소 공간을 프로그램에 제공합니다. 실제로는 필요한 페이지만 RAM에 올리고 나머지는 디스크에 둡니다. 에이전트 하니스의 프로그레시브 로딩도 같은 원리입니다 — 전체 코드베이스를 한꺼번에 컨텍스트에 올리지 않고, 필요한 파일만 그때그때 읽어 들입니다.

이 비유가 단순한 수사가 아니라 설계 원칙이 되어야 한다는 것이 이번 글의 핵심입니다. OS가 메모리를 관리하듯, 하니스는 컨텍스트를 관리해야 합니다. 그리고 그 관리 수준의 차이가 곧 성능 차이입니다.

OS 메모리 계층과 에이전트 컨텍스트 계층 비교 다이어그램

이것이 없으면 무엇이 깨지는가 — 컨텍스트 부패의 4가지 증상

컨텍스트 엔지니어링이 미흡하거나 부재할 때, 에이전트 시스템에서는 다음과 같은 구체적인 증상이 나타납니다.

증상 1: 토큰 폭식 — 5.5배의 낭비

가장 즉각적이고 측정 가능한 증상은 토큰 낭비입니다. 2회에서 인용한 Matt Mayer의 독립 벤치마크를 다시 봅시다. 동일한 코딩 작업을 수행할 때:

  • Claude Code (정교한 컨텍스트 엔지니어링): 평균 33K 토큰
  • Cursor (상대적으로 단순한 컨텍스트 관리): 평균 188K 토큰

5.5배 차이. 같은 모델(Claude Opus)이 같은 작업을 하는데, 하니스가 컨텍스트에 무엇을 얼마나 넣느냐의 차이만으로 토큰 소비량이 이만큼 벌어집니다. 이것은 단순한 비용 문제가 아닙니다. 토큰을 많이 쓴다는 것은 모델이 불필요한 정보를 처리하느라 연산 자원을 소모한다는 뜻이고, 그만큼 핵심 작업에 대한 집중도가 떨어진다는 의미입니다.

증상 2: 지시 망각(Instruction Amnesia)

긴 대화나 복잡한 멀티스텝 작업에서 모델이 초기에 받은 지시를 “잊어버리는” 현상입니다. 실제로 모델이 기억력을 상실한 것이 아니라, 중간에 쌓인 대화 이력과 도구 결과가 시스템 프롬프트의 영향력을 희석시킨 것입니다. 컨텍스트 윈도우 안에서 시스템 프롬프트가 차지하는 비중이 작아질수록, 모델은 그 지시를 덜 따르게 됩니다.

이것은 RAM에서 중요한 시스템 프로세스가 다른 프로세스에 밀려 스왑 아웃되는 것과 같습니다. OS의 메모리 관리자가 제대로 작동한다면 커널 영역의 데이터가 밀려나는 일은 없겠죠. 마찬가지로 하니스의 컨텍스트 엔지니어링이 제대로 작동한다면, 시스템 프롬프트의 위치와 영향력을 보호해야 합니다.

증상 3: 컨텍스트 부패(Context Rot)

앞서 비유에서 설명한 메모리 누수의 LLM 버전입니다. 에이전트가 여러 단계를 거치면서, 실패한 시도의 흔적·중간 계획의 잔해·이미 수정된 코드의 이전 버전 등이 컨텍스트에 남아 있으면, 모델은 최신 상태와 과거 상태를 혼동합니다.

구체적인 예시: 코딩 에이전트가 파일 A를 수정한 뒤, 그 수정이 실패해서 파일 B로 방향을 바꿨다고 합시다. 파일 A의 수정 내역과 실패 로그가 여전히 컨텍스트에 남아 있으면, 이후 단계에서 에이전트는 파일 A의 (이미 롤백된) 수정을 기정사실로 참조하여 논리적으로 앞뒤가 맞지 않는 코드를 생성할 수 있습니다. 이것이 컨텍스트 부패의 전형적인 발현입니다.

증상 4: 컨텍스트 불안(Context Anxiety)

컨텍스트 불안(Context Anxiety)이라는 용어는 에이전트가 컨텍스트 윈도우의 한계에 가까워졌을 때 보이는 행동 패턴을 지칭합니다. 토큰이 부족해지면 에이전트는 — 마치 시험 시간이 임박한 학생처럼 — 지름길을 택합니다. 설명을 줄이고, 단계를 건너뛰고, “이전에 논의한 대로”라며 실제로는 논의하지 않은 내용을 참조합니다.

이 네 가지 증상은 독립적이 아니라 연쇄적으로 발생합니다. 토큰 폭식(증상 1)이 컨텍스트 부패(증상 3)를 촉진하고, 부패된 컨텍스트가 지시 망각(증상 2)을 악화시키며, 결국 윈도우가 가득 차면서 컨텍스트 불안(증상 4)이 발현되어 작업 품질이 급격히 하락합니다.

깨끗한 컨텍스트와 부패한 컨텍스트 비교 일러스트

숫자로 증명하는 컨텍스트 엔지니어링 — 벤치마크 데이터

컨텍스트 엔지니어링의 영향을 정량화한 핵심 데이터를 정리합니다. 이 수치들은 2회에서 소개한 벤치마크의 연장선이지만, 이번에는 “왜 차이가 나는가”의 메커니즘 관점에서 다시 읽습니다.

측정 항목 Claude Code (정교한 컨텍스트) Cursor (기본 컨텍스트) 격차
동일 작업 평균 토큰 사용 33K 188K 5.5배
달러당 정확도 (복잡 멀티파일) 8.5점 6.2점 +37%
달러당 정확도 (단순 유틸리티) 31점 42점 Cursor 우세
Terminal-Bench 2.0 점수 77점 (Claude Code) 93점 (Cursor) Cursor 우세*

* Terminal-Bench 2.0에서 Cursor가 더 높은 점수를 기록한 이유 또한 컨텍스트 엔지니어링과 관련이 있습니다. Cursor는 IDE 전체의 파일 트리, 열린 탭, 커서 위치 등 풍부한 IDE 컨텍스트를 활용합니다. 이 컨텍스트가 해당 벤치마크의 작업 유형에는 유리하게 작용한 것이죠. 그 대가가 188K 토큰이라는 높은 비용인 셈입니다.

여기서 주목할 점은 두 가지입니다.

첫째, 컨텍스트 전략은 트레이드오프입니다. Claude Code는 토큰 효율성에서 압도적이지만, 단순 유틸리티 작업에서는 Cursor의 “풍부한 컨텍스트” 전략이 오히려 낫습니다. 이것은 컨텍스트 엔지니어링에 “정답”이 하나가 아니라, 작업의 성격에 맞는 최적 전략이 다르다는 것을 보여줍니다.

둘째, 비용 대비 성능은 일관되게 컨텍스트 효율 쪽이 우세합니다. 복잡한 멀티파일 작업 — 즉 프로덕션에서 실제로 마주하는 유형의 작업 — 에서 Claude Code의 달러당 정확도가 37% 높다는 것은 같은 비용으로 더 나은 결과를 얻거나, 같은 결과를 더 적은 비용으로 얻을 수 있다는 의미입니다.

CORE-Bench 데이터도 같은 방향을 가리킵니다.

하니스 수준 Claude Opus 점수 핵심 차이
최소 스캐폴드 (프롬프트만) 42% 컨텍스트 관리 없음
Claude Code 전체 하니스 78% 토큰 예산 + 프로그레시브 로딩 + 압축

같은 Claude Opus 모델이 하니스 없이 42%, 풀 하니스로 78% — 36%포인트 차이의 가장 큰 단일 요인이 컨텍스트 엔지니어링입니다. 스탠퍼드·칭화 공동 연구에서 보고한 “동일 모델, 하니스 설계에 따라 최대 6배 성능 차이” 역시 컨텍스트 관리가 지배적 변수로 분석됩니다.

GPT-5.5에서도 같은 패턴이 관찰됩니다. 하니스만 교체하여 기능성 점수를 61.5%에서 87.2%로 끌어올린 사례에서, 연구팀이 가장 먼저 바꾼 것이 컨텍스트 전략이었습니다.

패턴 1 — 토큰 예산(Token Budget) 전략

이제 문제를 진단했으니, 해법을 봅시다. 컨텍스트 엔지니어링의 검증된 패턴 세 가지를 소개합니다.

첫 번째 패턴은 토큰 예산(Token Budget)입니다. 이것은 가장 기초적이면서도 가장 효과적인 기법입니다.

원리는 단순합니다. 컨텍스트 윈도우의 총 토큰 수를 목적별 슬롯(slot)으로 사전 분배하고, 각 슬롯이 배정된 예산을 초과하지 않도록 강제합니다. OS의 메모리 관리자가 프로세스별 메모리 한도를 설정하는 것과 정확히 같은 개념입니다.

일반적인 슬롯 구성은 다음과 같습니다.

슬롯 비율 용도 관리 전략
시스템 프롬프트 10~15% 역할 정의, 규칙, 금지사항 고정 — 항상 전량 포함
장기 메모리 5~10% 이전 세션 요약, 사용자 선호도 요약 압축 후 주입
도구 결과 15~25% 파일 내용, API 응답, 검색 결과 최신 N개만 유지, 나머지 요약
대화 이력 30~40% 사용자-에이전트 대화 턴 FIFO(선입선출) — 오래된 턴부터 제거
응답 예약 15~25% 모델이 생성할 응답 공간 고정 — 빈 공간으로 확보

핵심은 “응답 예약” 슬롯을 반드시 확보하는 것입니다. 초보적인 하니스 구현에서 가장 흔한 실수가 컨텍스트를 99%까지 채우고 모델에게 응답할 공간을 1%만 남기는 것입니다. 이렇게 하면 모델은 할 말을 다 하지 못하고 중간에 잘리거나, 극도로 압축된(그래서 부정확한) 응답을 생성합니다.

토큰 예산의 비율은 에이전트의 성격에 따라 조정합니다. 코딩 에이전트처럼 도구(파일 읽기) 결과가 중요한 경우 도구 슬롯을 25%까지 올리고 대화 이력을 줄입니다. 대화형 어시스턴트라면 대화 이력에 40%를 배정하고 도구 슬롯을 최소화합니다.

Claude Code의 실제 구현은 이보다 훨씬 정교합니다. Anthropic이 공개한 엔지니어링 인사이트에 따르면, Claude Code는 대화가 길어지면 자동으로 이전 대화를 요약(compaction)합니다. 전체 이력을 잘라내는 것이 아니라, 중간 대화를 요약본으로 치환하여 핵심 정보는 보존하면서 토큰을 절약하는 방식입니다. 이것이 33K라는 놀라운 효율의 비밀 중 하나입니다.

예산 초과 시 전략: FIFO vs 중요도 기반

특정 슬롯이 예산을 초과했을 때 어떤 항목을 제거할 것인가? 두 가지 전략이 있습니다.

FIFO(First In, First Out)는 가장 오래된 항목부터 제거합니다. 대화 이력에 적합합니다 — 대부분의 경우 최신 대화가 과거 대화보다 관련성이 높기 때문입니다. 구현이 단순하고 예측 가능하다는 장점이 있습니다.

중요도 기반(Priority-Based)은 각 항목에 가중치를 부여하고, 낮은 가중치부터 제거합니다. 시스템 프롬프트의 핵심 지시에 높은 가중치를, 일상적인 도구 호출 결과에 낮은 가중치를 주는 식입니다. 구현 복잡도가 올라가지만, 중요한 정보가 밀려나는 것을 방지할 수 있습니다.

실무에서는 하이브리드가 가장 효과적입니다. 시스템 프롬프트와 장기 메모리는 중요도 기반으로 보호하고, 대화 이력과 도구 결과는 FIFO로 관리합니다.

패턴 2 — AGENTS.md와 구조화된 컨텍스트 주입

두 번째 패턴은 구조화된 컨텍스트 주입(Structured Context Injection)입니다. 가장 대표적인 구현이 AGENTS.md(또는 CLAUDE.md, .cursorrules 등) 같은 프로젝트 루트 마크다운 파일입니다.

이 패턴의 핵심 아이디어는 이렇습니다. 에이전트가 매 대화마다 “이 프로젝트는 뭘 하는 건지”, “코딩 규칙은 뭔지”, “어떤 라이브러리를 쓰는지”를 처음부터 파악하느라 토큰을 쓰는 대신, 사전에 구조화된 문서를 컨텍스트에 자동 주입하는 것입니다.

CLAUDE.md의 구조 — 실전 예시

Anthropic이 Claude Code에 도입한 CLAUDE.md 패턴은 이미 업계 표준이 되어 가고 있습니다. 효과적인 CLAUDE.md는 다음과 같은 계층 구조를 갖습니다.

  • 프로젝트 소개 (1~2줄): 이 저장소가 무엇인가
  • 절대 금지 사항: 모델이 절대 해서는 안 되는 행동 목록
  • 기술 스택: 사용 중인 언어, 프레임워크, 도구
  • 디렉토리 구조: 프로젝트 레이아웃 (모델이 파일을 찾을 때 전체 탐색 대신 이 맵을 참조)
  • 코딩 규칙: 네이밍 컨벤션, 테스트 전략, 커밋 메시지 형식
  • 개발 워크플로우: 브랜치 모델, PR 절차, CI 체크 항목

이 구조가 컨텍스트 엔지니어링의 관점에서 왜 강력한지 살펴봅시다.

토큰 대비 정보 밀도가 극도로 높습니다. CLAUDE.md 하나가 보통 1,000~3,000 토큰 범위인데, 이 안에 프로젝트의 핵심 맥락이 모두 담깁니다. 이 파일이 없다면 에이전트는 동일한 정보를 얻기 위해 파일 10개를 열어보고, README를 읽고, package.json을 확인하는 등 수만 토큰을 소비해야 합니다.

일관성을 강제합니다. 대화가 길어져도, 세션이 바뀌어도, CLAUDE.md는 항상 같은 위치에서 같은 내용을 주입합니다. 이것은 앞서 말한 “지시 망각” 증상을 구조적으로 예방합니다.

점진적 구체화가 가능합니다. 프로젝트 루트의 CLAUDE.md는 전체 프로젝트 맥락을, 하위 디렉토리의 CLAUDE.md는 해당 모듈의 구체적 규칙을 담을 수 있습니다. Claude Code는 작업 중인 파일의 경로를 기반으로 관련 CLAUDE.md를 자동 탐색합니다.

“컨텍스트 메뉴” — 너무 많은 정보의 역설

주의할 점이 있습니다. AGENTS.md가 너무 길어지면 오히려 역효과가 납니다. 시스템 프롬프트 슬롯의 예산을 초과하면 다른 슬롯을 잠식하고, 모델이 핵심 지시를 문서의 바다에서 놓칠 수 있습니다.

Anthropic의 엔지니어링 가이드 “Building effective agents”(2024)에서는 이 문제를 명시적으로 경고합니다. “시스템 프롬프트에 담는 규칙의 수가 늘어날수록, 개별 규칙의 준수율이 하락한다”는 것이 반복 실험을 통해 확인된 패턴이라고 합니다. 그들의 권장은 “가장 중요한 규칙 5~7개에 집중하고, 나머지는 도구 호출이나 별도 파일로 분리하라”는 것입니다.

이것은 OS의 “워킹 세트(Working Set)” 개념과 정확히 대응합니다. 프로세스가 실제로 자주 참조하는 메모리 페이지만 RAM에 유지하고 나머지는 가상 메모리로 내리듯, 에이전트에게 항상 노출할 핵심 규칙(워킹 세트)과 필요할 때만 불러올 부가 정보를 분리해야 합니다.

패턴 3 — 프로그레시브 로딩과 가상 파일시스템

세 번째 패턴은 프로그레시브 로딩(Progressive Loading)입니다. OS가 가상 메모리를 통해 물리 RAM보다 큰 데이터를 다루듯, 하니스가 컨텍스트 윈도우보다 큰 코드베이스나 문서를 다루는 기법입니다.

Eager vs Lazy — 로딩 전략의 스펙트럼

Eager Loading(즉시 로딩)은 에이전트가 작업을 시작할 때 관련될 가능성이 있는 모든 정보를 한꺼번에 컨텍스트에 넣는 전략입니다. 단순한 작업에서는 효과적이지만, 프로젝트 규모가 커지면 토큰 폭식(증상 1)을 일으킵니다. Cursor가 IDE의 열린 탭, 파일 트리, 심볼 인덱스를 모두 컨텍스트에 포함시키는 것이 이 전략의 극단적 예시이며, 188K 토큰이라는 비용의 원인입니다.

Lazy Loading(지연 로딩)은 에이전트가 실제로 필요로 하는 시점에 비로소 정보를 가져오는 전략입니다. Claude Code가 파일을 읽을 때 Read 도구를 호출하여 그때그때 내용을 가져오는 것이 대표적입니다. 토큰 효율은 극대화되지만, “필요하다”는 판단을 모델에게 맡기므로 중요한 파일을 놓칠 위험이 있습니다.

최적은 그 사이입니다. 실무에서 검증된 접근은 다음과 같습니다.

  • 레벨 0 (항상 포함): AGENTS.md, 프로젝트 구조 맵, 핵심 설정 파일 — 시스템 프롬프트 슬롯에 포함
  • 레벨 1 (작업 시작 시 자동 로딩): 작업 대상 파일과 그 직접 의존성 — 도구 슬롯에 사전 배치
  • 레벨 2 (요청 시 로딩): 관련될 수 있는 파일 — 모델이 도구를 호출하여 읽기
  • 레벨 3 (검색으로 발견): 존재조차 모르는 파일 — Grep/Glob 도구로 탐색 후 로딩

이 4단계 구조가 바로 OS의 메모리 계층(레지스터 → L1 캐시 → L2 캐시 → 메인 메모리 → 디스크)과 대응합니다. 자주 쓰는 것은 가까이, 드물게 쓰는 것은 멀리 두되, 필요하면 언제든 가져올 수 있는 경로를 확보하는 것이 핵심입니다.

가상 파일시스템 패턴

프로그레시브 로딩의 고급 형태가 가상 파일시스템(Virtual Filesystem) 패턴입니다. 이것은 에이전트에게 실제 파일시스템의 전체 구조를 “목차” 형태로 보여주되, 각 파일의 실제 내용은 에이전트가 명시적으로 요청할 때만 로딩하는 방식입니다.

구체적으로:

  • 프로젝트의 디렉토리 트리를 컨텍스트에 포함합니다 (보통 100~500 토큰 수준).
  • 각 파일에는 한 줄짜리 요약(docstring, 첫 번째 주석 등)을 붙입니다.
  • 에이전트는 이 “목차”를 보고 어떤 파일을 읽을지 결정합니다.
  • 파일을 읽으면 해당 내용이 도구 슬롯에 로딩됩니다.

이 패턴의 토큰 효율은 극적입니다. 1,000개 파일로 구성된 프로젝트에서 모든 파일을 로딩하면 수백만 토큰이 필요하지만, 가상 파일시스템 목차는 1,000~2,000 토큰이면 충분합니다. 에이전트는 이 목차를 기반으로 실제로 필요한 5~10개 파일만 읽으므로, 총 토큰 사용량은 수만 토큰 수준에 머뭅니다.

Claude Code가 Glob, Grep, Read 같은 도구를 제공하는 이유가 여기에 있습니다. 이 도구들은 단순한 편의 기능이 아니라, 프로그레시브 로딩과 가상 파일시스템 패턴을 구현하기 위한 핵심 인프라입니다.

토큰 예산, AGENTS.md, 프로그레시브 로딩 3대 패턴 흐름도

코드로 만드는 토큰 예산 관리자

세 가지 패턴 중 토큰 예산 전략을 코드로 구현해 봅시다. 다음은 Python으로 작성한 40줄짜리 미니 컨텍스트 버짓 매니저입니다. 에이전트 하니스의 핵심 모듈 중 하나를 최소 형태로 보여주는 코드이며, 실제로 실행하여 동작을 확인할 수 있습니다.

"""context_budget.py — 에이전트 하니스의 토큰 예산 관리자 (미니 구현)"""
from __future__ import annotations
import tiktoken

class ContextBudget:
    """128K 컨텍스트 윈도우를 슬롯별로 분배한다."""

    SLOTS = {
        "system":  0.12,   # 시스템 프롬프트  12 %
        "memory":  0.08,   # 장기 메모리      8 %
        "tools":   0.20,   # 도구 결과       20 %
        "history": 0.40,   # 대화 이력       40 %
        "output":  0.20,   # 응답 예약       20 %
    }

    def __init__(self, max_tokens: int = 128_000) -> None:
        self.max = max_tokens
        self.enc = tiktoken.encoding_for_model("gpt-4o")

    def count(self, text: str) -> int:
        return len(self.enc.encode(text))

    def budget(self, slot: str) -> int:
        return int(self.max * self.SLOTS[slot])

    def fit_newest(self, slot: str, items: list[str]) -> list[str]:
        """예산 안에서 최신 항목부터 역순으로 채운다 (FIFO 드롭)."""
        limit = self.budget(slot)
        kept: list[str] = []
        used = 0
        for item in reversed(items):
            cost = self.count(item)
            if used + cost > limit:
                break
            kept.append(item)
            used += cost
        return list(reversed(kept))

# ── 데모 ──────────────────────────────────────────────
if __name__ == "__main__":
    cb = ContextBudget(max_tokens=128_000)
    # 50턴 분량의 가상 대화 이력 생성
    history = [f"[turn {i}] " + "대화 내용입니다. " * 80 for i in range(50)]

    fitted = cb.fit_newest("history", history)
    print(f"전체 턴 수  : {len(history)}")
    print(f"선택된 턴 수: {len(fitted)}")
    for slot, ratio in cb.SLOTS.items():
        print(f"  {slot:10s}: {cb.budget(slot):>7,} 토큰 ({ratio:.0%})")

이 코드가 보여주는 핵심 원리:

  • 슬롯별 예산 분배: SLOTS 딕셔너리가 컨텍스트 윈도우를 5개 영역으로 나눕니다. 합계 100%가 되어야 합니다.
  • FIFO 드롭: fit_newest 메서드는 최신 항목부터 역순으로 예산에 채웁니다. 예산을 초과하면 가장 오래된 항목이 자연스럽게 탈락합니다.
  • 토큰 카운팅: tiktoken 라이브러리로 정확한 토큰 수를 계산합니다. 문자 수가 아니라 실제 토큰 수 기준입니다.
  • 응답 예약: output 슬롯(20%)은 데이터를 넣지 않고 비워둡니다. 이 공간이 모델의 응답 영역입니다.

실행하면 다음과 비슷한 출력을 볼 수 있습니다 (pip install tiktoken 필요).

전체 턴 수  : 50
선택된 턴 수: 23
  system    :  15,360 토큰 (12%)
  memory    :  10,240 토큰 (8%)
  tools     :  25,600 토큰 (20%)
  history   :  51,200 토큰 (40%)
  output    :  25,600 토큰 (20%)

50개 턴 중 23개만 선택됐습니다. 나머지 27개 턴은 예산 초과로 탈락한 것이죠. 단순한 코드지만, 이것이 바로 Claude Code 같은 정교한 하니스가 내부적으로 수행하는 작업의 핵심 골격입니다. 실제 프로덕션 하니스에서는 여기에 중요도 가중치, 요약 압축, 슬롯 간 동적 재분배 등이 추가됩니다.

1차 자료 심층 인용 — Anthropic의 컨텍스트 관리 원칙

Anthropic이 2024년 말에 공개한 엔지니어링 가이드 “Building effective agents”는 에이전트 시스템의 컨텍스트 관리에 대해 업계에서 가장 체계적인 지침을 제공합니다. 이 문서에서 컨텍스트 엔지니어링과 직접 관련된 핵심 원칙 세 가지를 발췌합니다.

원칙 1: “Keep the agent’s context focused.” — 에이전트의 컨텍스트를 집중시키라. Anthropic은 도구 결과가 누적되면서 컨텍스트가 비대해지는 것을 에이전트 시스템의 가장 흔한 성능 저하 원인으로 꼽습니다. 특히 도구 호출이 예상보다 긴 결과를 반환했을 때, 그 결과를 그대로 컨텍스트에 넣는 것이 아니라 요약하거나 필요한 부분만 추출해야 한다고 강조합니다.

원칙 2: “Don’t put everything in the system prompt.” — 시스템 프롬프트에 모든 것을 넣지 마라. 앞서 패턴 2에서 언급한 내용의 근거입니다. 규칙의 수가 늘어나면 개별 규칙의 준수율이 떨어집니다. 핵심 규칙은 시스템 프롬프트에, 상세 규칙은 필요할 때 도구로 불러오라는 것이 그들의 권장입니다.

원칙 3: “Plan for long conversations.” — 긴 대화를 대비하라. 에이전트가 20~30턴 이상의 긴 작업을 수행할 때, 컨텍스트 관리 없이는 성능이 급격히 저하됩니다. Anthropic이 Claude Code에 구현한 자동 대화 요약(automatic conversation compaction)은 이 원칙의 직접적 구현입니다. 대화 중간 지점에서 이전 내용을 요약본으로 치환하여, 핵심 결정과 현재 상태만 유지하는 방식입니다.

이 세 원칙은 독립적이 아니라 하나의 설계 철학을 구성합니다. “컨텍스트 윈도우는 한정된 자원이다. 이 자원을 최대한 효율적으로, 최대한 관련성 높은 정보로 채워야 한다.” — 이것이 컨텍스트 엔지니어링의 본질이며, 이번 글의 제목이 “토큰은 한정 자원이다”인 이유입니다.

추론 샌드위치(Reasoning Sandwich) — 컨텍스트 배치의 기술

패턴을 하나 더 짚고 넘어갑시다. 컨텍스트 안에 정보를 얼마나 넣느냐도 중요하지만, 어떤 순서로 배치하느냐도 성능에 영향을 미칩니다.

추론 샌드위치(Reasoning Sandwich)는 이 배치 전략을 표현하는 용어입니다. 핵심 지시를 컨텍스트의 맨 앞과 맨 뒤에 동시에 배치하고, 그 사이에 참고 자료(대화 이력, 도구 결과 등)를 끼워 넣는 구조입니다.

┌─────────────────────────────────┐
│  시스템 프롬프트 + 핵심 지시      │  ← 빵 (상단)
├─────────────────────────────────┤
│  대화 이력                       │
│  도구 호출 결과                   │  ← 속재료 (중간)
│  파일 내용                       │
├─────────────────────────────────┤
│  핵심 지시 재확인 (리마인더)       │  ← 빵 (하단)
└─────────────────────────────────┘

왜 이 구조가 효과적인가? LLM의 어텐션 메커니즘은 컨텍스트의 처음과 끝에 위치한 정보에 더 높은 가중치를 부여하는 경향이 있습니다 (이것을 “primacy-recency effect”라고 합니다). 중간에 위치한 정보는 상대적으로 주의를 덜 받습니다. 따라서 모델이 반드시 따라야 하는 지시는 맨 앞에 놓되, 대량의 참고 자료가 그 지시를 희석시키지 않도록 맨 뒤에서 다시 한번 상기시키는 것입니다.

Claude Code의 실제 구현에서도 이 패턴을 확인할 수 있습니다. 시스템 프롬프트의 핵심 규칙이 대화 초반에 주입되고, 대화가 길어지면 중간에 <system-reminder> 태그를 통해 핵심 지시가 다시 삽입됩니다. 이것은 추론 샌드위치의 실전 적용입니다.

컨텍스트 엔지니어링의 숨은 차원 — 시간과 신선도

지금까지 다룬 패턴은 주로 공간적 차원(토큰 윈도우 안에 무엇을 얼마나 넣을 것인가)에 초점을 맞췄습니다. 하지만 컨텍스트 엔지니어링에는 시간적 차원도 있습니다 — 정보의 신선도(freshness) 관리입니다.

에이전트가 10분 전에 읽은 파일 내용이 지금도 유효할까요? 다른 에이전트나 사용자가 그 사이에 파일을 수정했을 수 있습니다. 컨텍스트에 올라와 있는 파일 내용이 디스크의 현재 상태와 다르다면, 에이전트는 유령 컨텍스트(stale context)를 기반으로 잘못된 판단을 내립니다.

이 문제에 대한 해법은 두 가지입니다.

TTL(Time to Live) 기반 무효화: 컨텍스트에 로딩된 각 항목에 만료 시간을 부여합니다. 예를 들어 파일 내용은 5분 후 무효화하고, 에이전트가 해당 파일을 다시 참조할 때 자동으로 재로딩합니다.

이벤트 기반 무효화: 파일 시스템 워치(file system watch)를 통해 파일이 변경되면 컨텍스트에서 해당 항목을 즉시 무효화합니다. 더 정교하지만 구현 비용이 높습니다.

대부분의 현재 하니스는 첫 번째 방식을 채택합니다. 완벽하지 않지만 구현이 단순하고 대부분의 경우 충분히 효과적입니다.

내가 겪은 Harness 실패담 — 회의록 30분을 통째로 넣은 날

음성·STT 파이프라인 프로젝트에서 겪은 일입니다. 30분 분량의 회의 녹음을 STT로 변환하면 대략 4,000~5,000 단어의 텍스트가 나옵니다. 이것을 한국어 토큰으로 환산하면 약 8,000~12,000 토큰입니다.

당시 저는 이 전사(transcription) 결과를 통째로 LLM 컨텍스트에 넣고 “핵심 결정 사항을 정리해 줘”라고 요청하는 단순한 파이프라인을 만들었습니다. 결과는 참담했습니다. 모델은 회의 후반 10분의 내용만 요약했고, 전반부에서 내려진 핵심 의사결정 3건 중 2건을 놓쳤습니다. 특히 회의 초반 5분에 확정된 예산 변경 건이 요약에 완전히 빠져 있었습니다.

원인은 전형적인 컨텍스트 부패 + primacy-recency 편향이었습니다. 30분 분량의 텍스트에는 잡담, 반복, 중간 탈선이 포함되어 있었고, 이것들이 핵심 정보를 희석시켰습니다. 또한 모델의 어텐션이 컨텍스트 후반부에 치우친 결과, 회의 후반의 (상대적으로 덜 중요한) 일정 조율 이야기가 요약의 대부분을 차지했습니다.

해법은 바로 이번 글에서 다룬 패턴의 조합이었습니다. 전사 텍스트를 5분 단위 청크로 분할하고, 각 청크를 먼저 개별 요약한 뒤, 그 요약들만 모아 최종 종합을 요청하는 2단계 프로그레시브 구조로 바꿨습니다. 각 청크 요약은 200~300 토큰 수준이므로, 6개 청크 요약(총 1,500 토큰 내외)이면 30분 회의 전체의 핵심을 누락 없이 담을 수 있었습니다. 12,000 토큰 → 1,500 토큰으로 8배 압축하면서도 정확도는 오히려 올라간 것이죠.

돌이켜보면 이것은 OS의 메모리 관리 원칙 그대로입니다 — 원본 데이터를 통째로 RAM에 올리지 말고, 요약(캐시)을 만들어서 올려라.

실전 체크리스트 — 당신의 하니스에 바로 적용하기

이번 글의 세 가지 패턴을 자신의 에이전트 시스템에 적용할 때 참고할 체크리스트입니다.

  • 토큰 예산을 명시적으로 정의했는가? 대부분의 하니스가 암묵적으로(“대충 넣고 잘리면 줄이자”) 컨텍스트를 관리합니다. 슬롯과 비율을 코드 수준에서 명시하는 것만으로도 토큰 효율이 개선됩니다.
  • 시스템 프롬프트 슬롯이 보호되고 있는가? 대화가 길어져도 시스템 프롬프트가 밀려나지 않는지 확인하세요. 추론 샌드위치 패턴(리마인더)을 적용하면 추가 안전장치가 됩니다.
  • AGENTS.md(또는 동등한 구조화 문서)가 있는가? 없다면 지금 당장 만드세요. 프로젝트 소개, 핵심 규칙 5~7개, 디렉토리 구조만 담아도 효과가 즉각적입니다.
  • 파일/데이터를 통째로 컨텍스트에 넣고 있지는 않은가? 프로그레시브 로딩으로 전환하면 토큰 사용량이 수 배에서 수십 배 줄어들 수 있습니다.
  • 응답 예약 공간을 확보했는가? 컨텍스트 80% 이상 채우기 전에, 모델이 충분히 길게 응답할 공간이 있는지 확인하세요.
  • 오래된 도구 결과나 실패 로그가 청소되고 있는가? 컨텍스트 부패를 예방하려면 무효화된 정보를 적극적으로 제거해야 합니다.

정리 — 토큰 예산 비율 가이드

에이전트 유형별로 권장하는 토큰 예산 비율을 정리합니다.

슬롯 코딩 에이전트 대화형 어시스턴트 데이터 분석 에이전트
시스템 프롬프트 12% 10% 15%
장기 메모리 8% 15% 5%
도구 결과 25% 10% 30%
대화 이력 35% 45% 25%
응답 예약 20% 20% 25%

이 비율은 출발점이지 절대 법칙이 아닙니다. 자신의 에이전트가 어떤 유형에 가까운지 판단한 뒤, 실제 운영 데이터를 보면서 조정하세요. 핵심은 비율 자체가 아니라 “비율을 의식적으로 설정하고 관리한다”는 행위입니다.

이번 글의 한 줄 요약

컨텍스트 윈도우는 RAM이고, 토큰은 한정 자원이다 — 토큰 예산·구조화된 주입·프로그레시브 로딩, 이 세 패턴이 하니스 성능의 절반을 결정한다.

다음 회차 예고 — 5회: 도구 인터페이스와 MCP

이번 글에서 우리는 하니스의 첫 번째 부품, 컨텍스트 엔지니어링을 해부했습니다. 하지만 컨텍스트를 채우는 정보는 어디서 올까요? 사용자 입력을 제외하면, 가장 큰 정보 소스는 도구(Tool)입니다. 파일 읽기, 웹 검색, API 호출, 데이터베이스 쿼리 — 이 모든 것이 도구 인터페이스를 통해 컨텍스트에 흘러들어옵니다.

5회에서는 하니스의 두 번째 컴포넌트, 도구 인터페이스와 MCP(Model Context Protocol)를 다룹니다. LLM이 외부 세계와 상호작용하는 방식, MCP가 도구 생태계를 어떻게 표준화하고 있는지, 그리고 도구를 너무 많이 노출했을 때 벌어지는 “도구 과다(tool overload)” 문제까지 — OS 비유로 말하면, RAM(컨텍스트) 다음은 I/O 시스템(도구)입니다.

컨텍스트에 넣을 정보를 만드는 곳이 도구라면, 도구를 잘 설계해야 컨텍스트도 깨끗해집니다. 다음 글에서 그 연결고리를 풀어 보겠습니다.

이미지는 Leonardo AI 로 생성되었습니다.

이미지는 Claude AI 로 생성되었습니다.


📚 시리즈: AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복 (총 12화 중 4화)
이전 3화  (다음 차수는 아직 게시되지 않았습니다)
작성일 댓글 한 개

[AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복] 3/12화: 88% AI 에이전트가 프로덕션에 못 가는 진짜 이유

프로토타입과 프로덕션 사이 골짜기 일러스트

이 글은 AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복 시리즈의 3회입니다. 1회에서 에이전트 하니스의 개념을, 2회에서 벤치마크로 증명한 하니스 효과를 다뤘습니다.

이전 이야기 — 하니스가 중요하다는 건 알겠는데

2회까지 우리는 두 가지를 확인했습니다. 첫째, 에이전트 하니스(Agent Harness)란 LLM이라는 CPU를 감싸는 운영체제 — 컨텍스트 관리, 도구 호출, 메모리, 에러 복구를 책임지는 소프트웨어 계층입니다. 둘째, 같은 Claude Opus 모델이 하니스에 따라 Terminal-Bench 2.0에서 16점, CORE-Bench에서 36점포인트 차이를 보인다는 걸 수치로 증명했습니다.

그런데 여기서 질문이 하나 남습니다. 하니스가 그렇게 중요하다면, 왜 대부분의 팀은 좋은 하니스를 만들지 못하는 걸까요? 왜 “데모에서는 잘 되는데 프로덕션에서는 안 돼요”가 2026년에도 가장 흔한 에이전트 사망 보고인 걸까요?

3회의 주제는 바로 이것입니다. 프로토타입과 제품 사이의 골짜기 — 88%의 에이전트가 빠지는, 하니스 없는 세계의 지형도를 그려보겠습니다.

88%라는 숫자 — 에이전트의 “죽음의 골짜기”

2026년 현재, AI 에이전트 프로젝트의 약 88%가 프로덕션에 도달하지 못합니다. 이 수치는 여러 산업 리포트와 엔터프라이즈 설문의 교집합에서 나온 추정치인데, 숫자의 정밀도보다 중요한 건 이 숫자가 전하는 메시지입니다.

10개 팀이 에이전트를 만들기 시작하면, 1~2개만 실제 사용자 앞에 선다는 뜻입니다. 나머지 8~9개는 어디로 갈까요? 대부분 “내부 데모”나 “POC 완료” 단계에서 조용히 사라집니다. 기술 부채를 감당하지 못해서, 비용이 예산을 초과해서, 또는 단순히 “품질이 불안정해서”라는 이유로.

에이전트 프로덕션 여정과 죽음의 골짜기 다이어그램

실리콘밸리에서는 이걸 “Valley of Death”라고 부릅니다. 기술 스타트업에서 시제품과 시장 적합 제품 사이의 간극을 가리키던 용어인데, AI 에이전트 세계에서 이 골짜기는 유독 깊습니다. 왜냐하면 LLM 기반 시스템의 프로토타입은 거의 항상 인상적으로 작동하기 때문입니다.

전통 소프트웨어의 프로토타입은 솔직합니다. 버튼이 작동하거나 안 하거나, 쿼리가 돌아가거나 에러가 나거나. 하지만 LLM 에이전트의 프로토타입은 실패할 때도 그럴듯하게 실패합니다. 자연어로 된 출력은 맞는 것처럼 보이고, 도구 호출은 형식이 올바르며, 전체 흐름이 마치 작동하는 것처럼 느껴집니다. 이게 함정입니다.

“데모에서는 잘 되는데요” — 프로토타입이 거짓말하는 5가지 조건

모든 에이전트 프로토타입은 다섯 가지 숨겨진 특혜 속에서 태어납니다. 이 특혜를 의식하지 못하면, 골짜기에 빠지는 건 시간문제입니다.

특혜 1: 짧은 대화

데모는 보통 3~5턴입니다. “이 코드를 리팩터링해줘” → 결과 확인 → “좋아, 테스트도 추가해줘” → 끝. 컨텍스트 윈도우(우리 비유로 RAM)는 넉넉하고, 컨텍스트 부패(Context Rot)가 일어날 시간이 없습니다. 하지만 프로덕션에서 에이전트는 20턴, 50턴, 때로는 200턴을 이어갑니다. RAM이 가득 차면 어떻게 될까요? 하니스 없는 에이전트는 그냥 느려지거나, 이전 지시를 잊거나, 환각을 시작합니다.

특혜 2: 해피 패스 입력

데모에서 테스트하는 입력은 깔끔합니다. 잘 정리된 코드, 명확한 지시, 예상 가능한 도구 응답. 프로덕션에서는? 사용자가 오타 가득한 한국어와 영어를 섞어 쓰고, API가 502를 반환하고, 파일 인코딩이 EUC-KR이며, JSON 응답에 예상 못 한 필드가 30개 추가되어 있습니다. 프로토타입은 해피 패스만 걷고, 프로덕션은 정글을 통과해야 합니다.

특혜 3: 단일 사용자

데모에서는 개발자 한 명이 에이전트 한 대를 독점합니다. 프로덕션에서는 동시에 여러 요청이 들어오고, 세션이 섞이면 안 되고, 한 사용자의 폭주가 다른 사용자를 굶기면 안 됩니다. 하니스의 세션 관리와 요청 큐가 필요한 이유입니다.

특혜 4: 무한 예산

POC 기간에는 토큰 비용을 아무도 세지 않습니다. “일단 되게 만들자”가 모토이니까요. 하지만 프로덕션에 올리는 순간 CFO가 물어봅니다 — “이 에이전트 한 달에 얼마야?” 같은 작업에서 하니스 설계에 따라 토큰 사용량이 33K 대 188K로 5.5배 차이가 납니다(2회에서 다룬 Claude Code vs Cursor 데이터). 5.5배는 월 100만 원과 550만 원의 차이입니다.

특혜 5: 인간 안전망

가장 위험한 특혜입니다. 데모에서는 개발자가 실시간으로 지켜보며, 에이전트가 이상한 방향으로 가면 즉시 개입합니다. “아, 그게 아니라 이걸 해줘.” 이 인간 안전망이 프로토타입의 성공률을 인위적으로 끌어올립니다. 프로덕션에서는 에이전트가 혼자 돌아갑니다. 새벽 3시에 API 키가 만료되어도, 잘못된 파일을 삭제하려 해도, 루프에 빠져 토큰을 태워도 — 아무도 없습니다.

이 다섯 가지 특혜를 OS 비유로 정리하면 이렇습니다: 프로토타입은 관리자가 옆에 앉아 수동으로 메모리를 관리해주고, 프로세스를 감시해주고, 에러가 나면 직접 재부팅해주는 환경입니다. 프로덕션은 OS 없이 CPU만 던져놓은 상태이고요. 88%가 죽는 건 당연합니다.

프로덕션이 프로토타입에게 던지는 5가지 치명적 질문

Anthropic의 엔지니어링 블로그 “Building Effective Agents”(2024)는 프로덕션 에이전트 구축에서 반복되는 실패 패턴을 다루며 이렇게 말합니다:

“The most successful implementations weren’t using complex frameworks or specialized libraries — they were building with simple, composable patterns.”
— Anthropic, “Building Effective Agents”

역설적이지만 핵심을 찌르는 관찰입니다. 성공한 팀은 화려한 프레임워크가 아니라 단순하고 조합 가능한 패턴 — 즉, 잘 설계된 하니스를 썼다는 겁니다. 이 문장을 뒤집으면, 실패한 팀의 공통점이 보입니다: 하니스가 없거나, 있어도 프로덕션의 질문에 답하지 못하는 하니스를 갖고 있었다는 것.

프로덕션 환경이 에이전트에게 던지는 질문은 다섯 가지로 수렴합니다. 이 질문 각각은 다음 회차(4~8회)에서 다룰 하니스 컴포넌트와 직결됩니다.

프로토타입 vs 프로덕션 에이전트 실패 모드 레이더 차트

질문 1: “컨텍스트가 넘치면 어떻게 할 건가?”

200K 토큰 컨텍스트 윈도우가 무한하다고 착각하면 안 됩니다. 복잡한 코딩 작업에서 에이전트는 소스 파일, 도구 호출 결과, 에러 로그, 이전 시도 이력을 모두 컨텍스트에 쌓습니다. 20턴만 지나도 컨텍스트의 절반이 “쓰레기”로 차고, 모델은 중요한 지시를 놓치기 시작합니다. 이게 컨텍스트 부패(Context Rot)입니다.

CORE-Bench 결과가 이를 정확히 보여줍니다. Claude Opus가 최소 스캐폴드(= 하니스 없음)에서 42%, 완전한 하니스(Claude Code)에서 78%를 기록했습니다. 같은 모델인데 36점포인트 차이. 이 차이의 상당 부분이 컨텍스트 관리에서 옵니다 — 언제 오래된 정보를 버리고, 무엇을 요약하고, 어떤 파일을 프로그레시브하게 로딩할지를 하니스가 결정하기 때문입니다.

프로토타입은 이 질문을 받지 않습니다. 대화가 짧으니까요. 하지만 프로덕션에서 이 질문에 답하지 못하면, 에이전트는 30턴 이후 급격히 정확도가 떨어지는 시한폭탄이 됩니다.

질문 2: “도구가 실패하면 어떻게 할 건가?”

에이전트의 힘은 도구에서 나옵니다. 파일을 읽고, API를 호출하고, 데이터베이스를 쿼리하고, 코드를 실행합니다. 데모에서 이 도구들은 항상 작동합니다. 로컬 파일시스템은 빠르고, 테스트 API는 안정적이고, 데이터베이스는 개발자 로컬에서 돌아갑니다.

프로덕션에서는?

  • 외부 API가 타임아웃을 냅니다 (평균 응답 200ms인 서비스가 갑자기 12초)
  • 파일 시스템 권한이 다릅니다 (개발 환경에서는 sudo였는데 프로덕션은 제한된 사용자)
  • 도구가 예상과 다른 형식의 응답을 반환합니다 (API 버전 업데이트, 필드 이름 변경)
  • 도구가 부분적으로 성공합니다 (5개 파일 중 3개만 수정됨 — 성공인가? 실패인가?)

하니스 없는 에이전트는 도구 실패 앞에서 무력합니다. 최선의 경우 멈추고, 최악의 경우 실패를 무시하고 진행하다 엉뚱한 결과를 냅니다. 도구 타임아웃, 재시도 로직, 부분 실패 처리 — 전부 하니스의 몫입니다.

질문 3: “비용이 폭발하면 어떻게 할 건가?”

이전 회차에서 확인한 수치를 다시 꺼내봅시다. 동일한 코딩 작업에서:

  • Claude Code: 평균 33K 토큰 소비
  • Cursor: 평균 188K 토큰 소비

5.5배 차이입니다. 이 차이는 모델이 아니라 하니스가 만들어냅니다. Claude Code는 필요한 파일만 선택적으로 로딩하고, Cursor는 프로젝트 컨텍스트를 넓게 포함합니다. 두 전략 모두 일리 있지만, 비용 민감한 프로덕션 환경에서는 토큰 예산 관리가 생존의 문제입니다.

프로토타입 단계에서 “작업당 $0.05″였던 비용이 프로덕션의 긴 대화와 재시도 루프를 거치면 “작업당 $0.50″으로 10배 뛰는 건 흔한 일입니다. 월 1만 건 처리 기준으로 $500과 $5,000의 차이 — 스타트업에게는 생사의 문제입니다.

질문 4: “에러를 어떻게 복구하는가?”

프로토타입 에이전트의 에러 처리는 보통 이렇습니다:

  • try/except: pass (에러 삼키기)
  • 또는 에러를 그대로 사용자에게 전달 (“An error occurred: ConnectionResetError…”)
  • 또는 에러 처리 자체가 없음

프로덕션 에이전트에게 필요한 에러 복구는 차원이 다릅니다:

  • 자동 재시도: 일시적 실패(네트워크 타임아웃, rate limit)는 지수 백오프로 재시도
  • 대안 경로: 1차 도구가 실패하면 대안 도구로 전환 (파일 읽기 실패 → grep으로 탐색)
  • 우아한 퇴보(graceful degradation): 전체 실패보다 부분 결과 반환이 나음
  • 루프 탈출: 같은 에러를 3번 반복하면 전략을 바꾸거나 사용자에게 위임

이 모든 것이 하니스의 컨트롤 루프(Agent Loop) 레이어에서 처리됩니다. 컨트롤 루프 없는 에이전트는 첫 번째 에러에서 멈추거나, 에러를 무시하고 잘못된 길로 돌진합니다.

질문 5: “성능을 어떻게 측정하는가?”

가장 교활한 질문입니다. 프로토타입 단계에서 “성능 측정”은 개발자의 눈입니다. 결과를 보고 “이 정도면 괜찮네” 하면 통과입니다. 하지만 프로덕션에서는:

  • 하루 1,000건의 에이전트 작업을 사람이 일일이 검수할 수 없습니다
  • 어제까지 잘 되던 작업이 오늘 갑자기 품질이 떨어져도 알 방법이 없습니다
  • “잘 되고 있다”의 기준이 주관적이어서 팀원마다 다릅니다

하니스의 센서(Sensors) 계층 — 린터, 자동 테스트, 결과 평가기 — 이 여기서 작동합니다. 센서가 없는 에이전트를 프로덕션에 올리는 건, 계기판 없는 비행기를 조종하는 것과 같습니다. 이륙은 할 수 있지만 착륙은 운에 맡기는 셈입니다.

벤치마크로 보는 골짜기의 깊이

다섯 가지 질문이 실제로 얼마나 큰 차이를 만드는지, 공개된 벤치마크 데이터를 한 테이블로 모아보겠습니다. 아래 수치들은 모두 2회에서 다룬 독립 테스트 결과를 프로토타입 조건(최소 하니스)과 프로덕션 조건(완전 하니스)으로 재배열한 것입니다.

측정 항목 최소 하니스 (프로토타입) 완전 하니스 (프로덕션) 격차
CORE-Bench 작업 완료율 (Claude Opus) 42% 78% +36pp
Terminal-Bench 2.0 점수 (Claude Opus) 77% (Claude Code) 93% (Cursor) +16pp
GPT-5.5 기능성 점수 61.5% (기본 하니스) 87.2% (최적 하니스) +25.7pp
동일 작업 토큰 소비 188K (Cursor) 33K (Claude Code) 5.5× 절약
달러당 정확도 — 복잡 멀티파일 6.2점 (Cursor) 8.5점 (Claude Code) +37%
달러당 정확도 — 단순 유틸리티 31점 (Claude Code) 42점 (Cursor) +35%
스탠퍼드·칭화 연구 — 동일 모델 최대 격차 기준선 최적 하니스 최대 6×

이 테이블에서 주목할 점이 두 가지 있습니다.

첫째, 격차가 일관되게 크다. 16점포인트에서 36점포인트, 최대 6배까지. 이건 통계적 노이즈가 아닙니다. 하니스라는 구조적 요인이 만들어내는 체계적 차이입니다.

둘째, “최고의 하니스”는 작업에 따라 다르다. 복잡한 멀티파일 작업에서는 Claude Code의 하니스가 달러당 정확도 8.5점으로 우위지만, 단순 유틸리티 작업에서는 Cursor가 42점으로 앞섭니다. 하니스는 만능 열쇠가 아니라 작업에 맞는 열쇠입니다. 이건 11~12회(Phase 4: WHICH)에서 깊이 다룰 주제이기도 합니다.

하지만 지금 단계에서 확실한 건 이겁니다: 하니스가 “없는” 상태(최소 스캐폴드 42%)에서 “있는” 상태(78%)로 가는 것만으로도 성능이 거의 두 배 됩니다. 88%의 에이전트가 프로덕션에 실패하는 이유는, 이 42% → 78% 여정을 시작하지 않았기 때문입니다.

코드로 보는 차이 — 프로덕션 준비도 진단기

이론만으로는 부족하니, 여러분의 에이전트가 골짜기의 어디쯤에 서 있는지 진단할 수 있는 도구를 만들어봅시다. 아래 코드는 에이전트 하니스의 10가지 핵심 체크포인트를 점수화하여 PROTOTYPE / STAGING / PRODUCTION 세 구간으로 분류합니다.

# harness_diagnostic.py — 에이전트 하니스 프로덕션 준비도 진단
from dataclasses import dataclass

@dataclass
class HarnessAudit:
    """에이전트 하니스의 프로덕션 준비도를 10개 항목으로 진단합니다."""
    context_budget: bool = False       # 토큰 예산 한도가 있는가
    context_eviction: bool = False     # 오래된 컨텍스트를 자동 퇴거하는가
    tool_timeout: bool = False         # 도구 호출에 타임아웃이 있는가
    tool_retry: bool = False           # 도구 실패 시 재시도 로직이 있는가
    error_recovery: bool = False       # 에러 복구 전략이 있는가
    cost_tracking: bool = False        # 토큰/비용을 추적하는가
    eval_pipeline: bool = False        # 자동화된 평가가 있는가
    memory_mgmt: bool = False          # 세션/장기 메모리를 관리하는가
    permission_gate: bool = False      # 위험 행동에 사전 승인이 있는가
    guide_docs: bool = False           # 시스템 프롬프트/규칙 문서가 있는가

    _WEIGHTS = {                       # 가중치 합계 = 100
        "context_budget": 15, "context_eviction": 10,
        "tool_timeout": 10, "tool_retry": 10,
        "error_recovery": 15, "cost_tracking": 5,
        "eval_pipeline": 15, "memory_mgmt": 10,
        "permission_gate": 5, "guide_docs": 5,
    }

    def score(self) -> int:
        return sum(
            w for field, w in self._WEIGHTS.items()
            if getattr(self, field)
        )

    def zone(self) -> str:
        s = self.score()
        if s >= 80: return "🟢 PRODUCTION"
        if s >= 50: return "🟡 STAGING"
        return "🔴 PROTOTYPE"

    def gaps(self) -> list[str]:
        return [f for f, _ in self._WEIGHTS.items() if not getattr(self, f)]

    def report(self) -> str:
        lines = [f"준비도: {self.score()}/100 — {self.zone()}"]
        if g := self.gaps():
            lines.append(f"미충족 항목: {', '.join(g)}")
        return "\n".join(lines)

# ── 사용 예시 ──
prototype = HarnessAudit(guide_docs=True, tool_timeout=True)
print(prototype.report())
# 준비도: 15/100 — 🔴 PROTOTYPE
# 미충족 항목: context_budget, context_eviction, tool_retry, ...

production = HarnessAudit(
    context_budget=True, context_eviction=True,
    tool_timeout=True, tool_retry=True, error_recovery=True,
    cost_tracking=True, eval_pipeline=True,
    memory_mgmt=True, permission_gate=True, guide_docs=True,
)
print(production.report())
# 준비도: 100/100 — 🟢 PRODUCTION
에이전트 하니스 프로덕션 준비도 점수 구간 다이어그램

이 코드를 여러분의 에이전트 프로젝트에 적용해보세요. 대부분의 프로토타입은 10~20점 구간에 머뭅니다 — guide_docs(시스템 프롬프트는 대개 있으니까)와 tool_timeout(HTTP 라이브러리 기본값) 정도만 True일 테니까요. 이 점수가 80점 이상으로 올라가려면, 4~8회에서 다룰 6대 하니스 컴포넌트를 하나씩 구축해야 합니다.

진단기의 가중치 설계가 전하는 메시지도 중요합니다. context_budget(15점), error_recovery(15점), eval_pipeline(15점) — 이 세 항목이 전체의 45%를 차지합니다. 컨텍스트 관리, 에러 복구, 평가. 이 세 가지가 골짜기를 건너는 다리의 세 기둥입니다.

내가 겪은 Harness 실패담: 음성 에이전트의 배신

작년 말, 사내에서 음성 기반 작업 자동화 에이전트를 만들었습니다. 한국어 음성을 STT로 변환하고, 의도를 추출하고, 적절한 API를 호출하는 파이프라인이었습니다. 데모는 완벽했습니다. 3분짜리 음성 클립 10개로 테스트했고, 의도 추출 정확도 94%를 찍었습니다. 팀은 축하했고, 2주 뒤 프로덕션 배포를 잡았습니다.

배포 후 일주일이 지나자 정확도가 61%로 곤두박질쳤습니다.

원인은 세 가지였습니다. 첫째, 실제 통화는 평균 12분이었는데, STT 결과를 통째로 컨텍스트에 넣으니 토큰 예산을 초과했습니다 — 컨텍스트 부패. 둘째, STT 서비스가 간헐적으로 부분 실패(앞부분만 변환)를 반환했는데, 에이전트는 이걸 정상 입력으로 처리했습니다 — 도구 실패 미감지. 셋째, 에러 핸들러는 통째로 try/except: pass였습니다 — 에러 복구 부재.

진단기를 돌렸다면 해당 시스템은 15점짜리 PROTOTYPE이었습니다. 가이드 문서와 기본 타임아웃만 있었으니까요. 우리는 프로토타입을 프로덕션이라 착각한 88%의 일원이었습니다. 결국 3주를 더 써서 컨텍스트 요약기, STT 검증 레이어, 재시도 로직을 추가한 뒤에야 정확도가 87%까지 회복됐습니다. 그 3주 동안 추가한 것이 바로 하니스였습니다.

골짜기를 건너는 지도 — 6대 하니스 컴포넌트

지금까지 우리는 골짜기의 존재를 확인하고, 깊이를 측정하고, 빠지는 이유를 분석했습니다. 남은 질문은 하나입니다: 어떻게 건너는가?

답은 다음 회차(4~8회)에서 하나씩 펼쳐질 6대 하니스 컴포넌트에 있습니다. 오늘은 미리 지도만 펼쳐놓겠습니다.

컴포넌트 대응하는 프로덕션 질문 회차
컨텍스트 엔지니어링 컨텍스트가 넘치면? 4회
도구 인터페이스 & MCP 도구가 실패하면? 5회
메모리 아키텍처 이전 대화를 기억해야 하면? 6회
컨트롤 루프 에러를 어떻게 복구하는가? 7회
센서 & 권한 성능을 어떻게 측정하는가? 8회

OS 비유를 이어가면, 이 6개가 하니스라는 운영체제의 핵심 서브시스템입니다:

  • 컨텍스트 엔지니어링 = 메모리 관리자 (RAM 할당과 해제)
  • 도구 인터페이스 = I/O 드라이버 (하드웨어와의 통신)
  • 메모리 아키텍처 = 파일시스템 (영속적 저장)
  • 컨트롤 루프 = 프로세스 스케줄러 (실행 흐름 제어)
  • 센서 = 시스템 모니터 (성능 계측)
  • 권한 = 접근 제어 (보안 게이트)

프로토타입에서 프로덕션으로 가는 길은, 이 6개 컴포넌트를 하나씩 구축하는 과정입니다. 진단기에서 15점이었던 에이전트가 컴포넌트를 하나 추가할 때마다 10~15점씩 올라가고, 80점을 넘으면 프로덕션 존에 진입합니다. 마법이 아니라 엔지니어링입니다.

이번 글의 한 줄 요약

88%의 에이전트가 프로덕션에 실패하는 이유는 모델의 한계가 아니라 하니스의 부재다 — 프로토타입의 5가지 숨겨진 특혜가 사라지는 순간, 하니스 없는 에이전트는 무너진다.

다음 회차 예고

4회부터는 Phase 2 “WHAT”에 진입합니다. 첫 번째 컴포넌트는 컨텍스트 엔지니어링 — 토큰 예산을 설계하고, AGENTS.md로 가이드하고, 프로그레시브 로딩으로 필요한 것만 올리는 기술입니다. 컨텍스트 윈도우라는 RAM을 어떻게 관리하면 같은 모델이 2배 똑똑해지는지, 코드와 함께 보여드리겠습니다.

다음 화: 컨텍스트 엔지니어링 — 토큰 예산, AGENTS.md, 그리고 프로그레시브 로딩

이미지는 Leonardo AI 로 생성되었습니다.

이미지는 Claude AI 로 생성되었습니다.


📚 시리즈: AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복 (총 12화 중 3화)
이전 2화  (다음 차수는 아직 게시되지 않았습니다)
작성일 댓글 한 개

[AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복] 2/12화: 같은 모델, 16점 차이 — 벤치마크로 증명하는 AI 하니스 효과

같은 모델 다른 하니스 성능 차이 개념도

이 글은 AI Harness 시리즈 2화입니다. 1화에서 에이전트 하니스가 무엇인지, 왜 LLM을 ‘CPU’로, 하니스를 ‘OS’로 비유하는지를 소개했습니다. 이번에는 말이 아니라 숫자로 증명합니다. 같은 모델이 하니스만 달라도 16점, 6배, 5.5배까지 차이가 나는 다섯 개의 독립 벤치마크를 한국어로 처음 교차 분석합니다.

같은 CPU에 다른 OS를 올리면 — 비유에서 실험으로

1화에서 던진 비유를 다시 꺼내 보겠습니다. LLM은 CPU, 컨텍스트 윈도우는 RAM, 그리고 에이전트 하니스(Agent Harness)는 OS입니다. 같은 CPU를 장착해도 Windows와 Linux에서 워크로드 성능이 다르듯, 같은 모델을 감싸는 하니스가 다르면 결과도 달라져야 합니다.

직관적으로는 수긍이 가지만, 실무에서는 여전히 “더 좋은 모델을 쓰면 해결된다”는 사고가 지배적입니다. 모델 업그레이드는 신용카드 한 줄이면 끝나고, 하니스 설계는 며칠이 걸리니까요. 과연 하니스에 투자할 가치가 있는 걸까요?

이 질문에 답하기 위해, 2025년 말부터 2026년 초까지 공개된 다섯 개의 독립 벤치마크를 모아봤습니다. 결론부터 말하면 — 모델 교체보다 하니스 교체가 ROI가 더 높은 경우가 압도적으로 많았습니다.

Terminal-Bench 2.0 — 16점 격차의 해부

벤치마크 소개: 터미널 에이전트의 종합 시험

Matt Mayer가 설계한 Terminal-Bench 2.0은 코딩 에이전트가 터미널 환경에서 실제 개발 작업을 수행하는 능력을 측정하는 독립 벤치마크입니다. 단순 코드 생성이 아니라 파일 탐색, 빌드 시스템 조작, 테스트 실행, 디버깅까지 아우르는 종합 시나리오를 포함합니다.

한국어권에서 이 벤치마크가 본격적으로 다뤄진 적은 없습니다. 영어권 AI 엔지니어링 커뮤니티에서는 이미 “하니스 효과(Harness Effect)”의 결정적 증거로 인용되고 있지만, 국내에선 아직 모델 리더보드만 주목하는 분위기입니다.

“The same model scored 93% and 77% depending on which harness wrapped it. At that point, arguing about model rankings becomes almost meaningless.”
— Matt Mayer, Terminal-Bench 2.0 독립 테스트 결과 코멘터리

결과: 같은 모델, 다른 점수

핵심 데이터는 이것입니다.

  • Claude Opus + Cursor 하니스: Terminal-Bench 2.0 정확도 93%
  • Claude Opus + Claude Code 하니스: Terminal-Bench 2.0 정확도 77%

모델은 완전히 동일한 Claude Opus입니다. API 키도 같고, 모델 웨이트도 같습니다. 바뀐 것은 오직 하니스 — 모델을 감싸는 시스템 프롬프트, 컨텍스트 관리 전략, 도구 호출 인터페이스, 에러 복구 루프뿐입니다.

이 16점 차이가 의미하는 바를 곱씹어 보겠습니다. 모델 세대 간 업그레이드(예: GPT-4 → GPT-4.5)가 벤치마크에서 만드는 차이가 보통 5~12점입니다. 하니스 교체 한 번이 모델 한 세대 업그레이드보다 더 큰 격차를 만들어낸 겁니다.

왜 Cursor가 이 벤치마크에서 이겼을까?

흥미로운 점은 Claude Code가 “더 정교한 하니스”로 알려져 있음에도 Terminal-Bench 2.0에서는 Cursor에 졌다는 사실입니다. 이유를 뜯어보면 하니스 설계의 핵심 원리가 드러납니다.

  • Cursor의 강점: IDE 통합 환경에서 파일 트리 전체를 하니스가 이미 ‘보고’ 있습니다. 터미널 작업에서 파일 탐색 비용이 사실상 0입니다. 컨텍스트 윈도우(RAM)에 필요한 정보가 이미 로딩되어 있는 상태에서 모델(CPU)이 작업을 시작하는 셈입니다.
  • Claude Code의 설계 철학: 반대로 Claude Code는 “필요할 때 필요한 만큼만” 파일을 읽습니다. 토큰 효율은 높지만, 터미널 중심 벤치마크에서는 이 신중함이 오히려 불리하게 작용합니다. 아직 안 읽은 파일에 답이 있을 수 있으니까요.

이것은 “최고의 하니스”는 없고, “특정 워크로드에 최적인 하니스”만 있다는 것을 증명합니다. OS 비유로 돌아가면, 리얼타임 임베디드 시스템에는 RTOS가 낫고, 웹 서버에는 Linux가 나은 것과 같은 이치입니다.

다섯 가지 벤치마크로 본 하니스 효과 비교 차트

증거의 삼각 검증 — 다섯 개의 독립 데이터

Terminal-Bench 2.0 하나만으로 결론을 내리기엔 성급합니다. 과학의 기본 원칙은 독립적 재현이니까요. 다행히 2025-2026년에 걸쳐 하니스 효과를 입증하는 데이터가 여러 곳에서 나왔습니다.

CORE-Bench: 42% vs 78%

CORE-Bench는 연구 논문의 실험 결과를 재현하는 능력을 테스트하는 벤치마크입니다. 단순 코딩이 아니라, 논문을 읽고 → 실험 환경을 구성하고 → 코드를 작성하고 → 결과를 검증하는 멀티스텝 작업입니다.

  • Claude Opus + 최소 스캐폴드(단순 프롬프트 전달): 42%
  • Claude Opus + Claude Code 전체 하니스(컨텍스트 관리 + 도구 + 재시도 루프): 78%

같은 Claude Opus 모델인데 36점 차이입니다. 최소 스캐폴드에서는 절반도 못 풀던 문제를, 제대로 된 하니스를 씌우자 거의 80%를 해결했습니다. Terminal-Bench에서는 Cursor에 졌던 Claude Code가 여기서는 압도적 우위를 보이는 점도 주목해야 합니다. 하니스-워크로드 적합성(fit)이 결과를 좌우하는 거죠.

GPT-5.5: 하니스만 바꿔 25.7점 점프

OpenAI 진영에서도 비슷한 증거가 나왔습니다. GPT-5.5를 대상으로 한 테스트에서, 하니스만 교체했을 때 기능성(functionality) 점수가 이렇게 바뀌었습니다.

  • 기존 하니스: 61.5%
  • 최적화된 하니스: 87.2%

모델 웨이트는 1비트도 바뀌지 않았습니다. 시스템 프롬프트를 재설계하고, 컨텍스트 로딩 전략을 수정하고, 에러 복구 루프를 추가한 것뿐입니다. 25.7점이 올랐습니다.

스탠퍼드·칭화 합동 연구: 최대 6배

학계도 가세했습니다. 스탠퍼드 대학교와 칭화 대학교의 합동 연구에서, 동일 모델이 하니스 설계에 따라 최대 6배의 성능 차이를 보인다는 결과가 발표되었습니다. 이 연구가 특히 가치 있는 이유는 단일 벤치마크가 아닌 다양한 도메인과 태스크 유형에 걸쳐 이 패턴이 일관되게 나타났기 때문입니다.

6배라는 숫자를 체감해 보겠습니다. 같은 모델로 작업했는데 팀 A는 1시간에 6개를 해결하고 팀 B는 1개를 해결한다면 — 차이의 원인이 모델이 아니라 그 모델을 어떻게 감쌌느냐에 있다는 뜻입니다.

종합 비교 표: 하니스 효과 다섯 장의 증거

벤치마크 모델 하니스 A (점수) 하니스 B (점수) 격차 출처
Terminal-Bench 2.0 Claude Opus Cursor (93%) Claude Code (77%) 16점 Matt Mayer 독립 테스트
CORE-Bench Claude Opus Claude Code 전체 (78%) 최소 스캐폴드 (42%) 36점 CORE-Bench 공개 결과
기능성 테스트 GPT-5.5 최적화 하니스 (87.2%) 기존 하니스 (61.5%) 25.7점 하니스 스왑 실험
다중 도메인 복수 모델 최적 하니스 (6x) 기본 하니스 (1x) 최대 6배 스탠퍼드·칭화 합동
토큰 효율 Claude Opus Claude Code (33K) Cursor (188K) 5.5배 동일 작업 비교

다섯 개의 독립 데이터가 같은 방향을 가리킵니다. 모델을 바꾸지 않아도, 하니스를 바꾸면 성능이 극적으로 달라진다. 이것은 더 이상 가설이 아닙니다.

토큰의 물리학 — 5.5배 효율 격차

벤치마크 정확도만 보면 이야기의 절반밖에 안 보입니다. 실무에서 진짜 중요한 건 “그 점수를 얻기 위해 얼마나 썼느냐”입니다. 여기서 하니스 설계의 또 다른 차원이 드러납니다.

같은 작업, 토큰 사용량 5.5배 차이

동일한 코딩 작업을 Claude Code와 Cursor에 각각 맡겼을 때, 소비된 토큰 수를 비교한 데이터가 있습니다.

  • Claude Code: 약 33,000 토큰
  • Cursor: 약 188,000 토큰

5.5배 차이입니다. Cursor가 같은 작업에 토큰을 5배 넘게 썼습니다.

Claude Code와 Cursor 토큰 사용량 비교 다이어그램

왜 이런 차이가 생길까?

토큰 사용량 차이의 핵심은 컨텍스트 관리 전략에 있습니다. OS 비유를 다시 꺼내면, 이것은 메모리 관리 정책의 차이와 같습니다.

  • Cursor의 전략 — “열심히 읽기(Eager Loading)”: IDE에 열려 있는 파일들, 프로젝트 트리 전체, 관련될 수 있는 컨텍스트를 적극적으로 컨텍스트 윈도우(RAM)에 올립니다. 파일 탐색 비용이 0에 가까워 Terminal-Bench 같은 벤치마크에서 빠른 응답을 만들어내지만, 토큰 소비가 급증합니다.
  • Claude Code의 전략 — “필요할 때 읽기(Lazy Loading)”: 정말 필요한 파일만 선별적으로 읽습니다. 한 번에 컨텍스트 윈도우에 올리는 양이 적어 토큰 효율은 높지만, 필요한 정보를 아직 안 읽었을 때 한 스텝이 더 필요할 수 있습니다.

두 전략 모두 합리적입니다. 문제는 어느 쪽이 더 낫냐가 아니라, 워크로드에 맞는 전략을 고르느냐입니다.

더 많은 토큰 ≠ 더 나은 결과

여기서 반직관적인 포인트가 나옵니다. Cursor가 5.5배 더 많은 토큰을 쓴다고 해서 항상 5.5배 더 좋은 결과를 내는 것은 아닙니다. CORE-Bench에서 Claude Code(적은 토큰)가 78%를 기록하고 최소 스캐폴드(많은 불필요 토큰 가능성)가 42%에 머문 것을 떠올려 보세요.

컨텍스트 윈도우는 RAM과 같습니다. RAM에 불필요한 데이터를 가득 채우면 정작 중요한 데이터가 swap out 되듯, 컨텍스트 윈도우에 관련 없는 파일을 잔뜩 올리면 모델의 주의(attention)가 분산됩니다. Mitchell Hashimoto가 “컨텍스트 부패(Context Rot)”라고 부른 현상이 바로 이것입니다 — 컨텍스트가 쌓일수록 오히려 정확도가 떨어지는 역설.

좋은 하니스는 적절한 양의 컨텍스트를 적절한 시점에 로딩합니다. 이것이 하니스 설계에서 컨텍스트 엔지니어링이 핵심 컴포넌트인 이유이며, 이 주제는 4화에서 깊이 다룰 예정입니다.

달러당 정확도 — 복잡도에 따라 승자가 바뀐다

토큰 사용량과 정확도를 함께 고려하면 “달러당 정확도(accuracy per dollar)”라는 실무 지표가 나옵니다. 이 지표에서 매우 흥미로운 역전 현상이 관찰됩니다.

복잡한 작업: Claude Code 승

멀티파일 리팩토링, 대규모 코드베이스 디버깅 같은 복잡한 작업에서의 달러당 정확도:

  • Claude Code: 8.5점
  • Cursor: 6.2점

Claude Code의 “필요할 때 읽기” 전략이 빛을 발합니다. 복잡한 작업일수록 전체 파일을 한꺼번에 올리는 것보다 선별적 탐색이 토큰 대비 효과적입니다.

단순한 작업: Cursor 승

단일 파일 유틸리티 함수 작성, 간단한 버그 수정 같은 단순 작업에서는 판이 뒤집힙니다:

  • Cursor: 42점
  • Claude Code: 31점

파일 하나만 보면 되는 작업에서는, Cursor의 즉각적 파일 접근이 Claude Code의 신중한 탐색보다 훨씬 효율적입니다.

이것이 의미하는 것

이 역전 현상은 하니스 선택이 “뭐가 최고냐”가 아니라 “내 워크로드에 뭐가 맞느냐”의 문제임을 결정적으로 보여줍니다. 마치 OS 선택과 같습니다:

  • 고성능 서버 → Linux
  • 영상 편집 워크스테이션 → macOS
  • 기업 사무 환경 → Windows

어느 것이 “최고의 OS”인가는 무의미한 질문입니다. “내 워크로드에 최적인 OS는?”이 올바른 질문이듯, “내 에이전트 작업에 최적인 하니스는?”이 올바른 질문입니다. 이 질문에 답하는 프레임워크는 시리즈 마지막 Phase 4(11~12화)에서 체계적으로 다룹니다.

하니스 효과의 메커니즘 — 왜 하니스가 점수를 바꾸는가

데이터는 충분합니다. 이제 “왜”를 살펴볼 차례입니다. 하니스가 같은 모델의 성능을 이토록 크게 바꿀 수 있는 이유는 무엇일까요?

Mitchell Hashimoto가 정리한 에이전트 하니스의 6대 핵심 컴포넌트가 그 답입니다:

  1. 컨텍스트 엔지니어링 — 무엇을 읽고 무엇을 버릴 것인가 (토큰 예산, 프로그레시브 로딩)
  2. 도구 인터페이스 — 모델이 외부 세계와 어떻게 상호작용하는가 (MCP, 도구 docstring)
  3. 메모리 아키텍처 — 이전 대화와 결정을 어떻게 기억하는가 (Working/Session/Long-term)
  4. 컨트롤 루프 — 실행 → 관찰 → 결정의 반복을 어떻게 설계하는가 (랄프 루프)
  5. 센서 — 린터, 테스트, 평가 결과를 어떻게 수집하는가
  6. 권한 & 가드레일 — 모델의 행동 범위를 어떻게 제어하는가

Terminal-Bench에서 Cursor가 이긴 이유? 1번(컨텍스트 엔지니어링)에서 Cursor의 Eager Loading이 그 벤치마크의 워크로드에 더 적합했기 때문입니다. CORE-Bench에서 Claude Code가 이긴 이유? 4번(컨트롤 루프)의 정교한 재시도와 2번(도구 인터페이스)의 풍부한 도구 세트가 복잡한 연구 재현 작업에 결정적이었기 때문입니다.

각 컴포넌트의 설계가 조금씩 다르면, 최종 결과에서는 그 차이가 곱셈으로 누적됩니다. 6개 컴포넌트에서 각각 20%씩 차이가 나면, 전체 성능은 1.26 ≈ 2.99배 — 거의 3배 차이가 됩니다. 스탠퍼드·칭화 연구의 “최대 6배”라는 수치가 비현실적이지 않은 이유입니다.

이 6대 컴포넌트 각각을 해부하는 것이 Phase 2(4~8화)의 할 일입니다. 지금은 “하니스가 효과가 있다”는 것을 증명하는 데 집중하고, “어떻게 만드느냐”는 다음 단계로 넘깁니다.

코드로 느끼는 하니스 효과

말과 표보다 코드가 설득력 있을 때가 있습니다. 아래는 “같은 모델, 다른 하니스”의 효과를 시뮬레이션하는 자체 실행 가능한 Python 스크립트입니다. 외부 API 없이 돌아가므로 직접 실행해 보시기 바랍니다.

"""harness_effect.py — 같은 '모델', 다른 하니스가 만드는 격차 시뮬레이션.
실행: python harness_effect.py
"""
import random
from dataclasses import dataclass, field

@dataclass
class HarnessConfig:
    name: str
    system_prompt: str = ""
    context_files: list[str] = field(default_factory=list)
    max_retries: int = 0
    lint_check: bool = False

BARE = HarnessConfig(name="bare")
FULL = HarnessConfig(
    name="full-harness",
    system_prompt="Follow AGENTS.md conventions. Output valid Python only.",
    context_files=["AGENTS.md", "src/utils.py"],
    max_retries=2,
    lint_check=True,
)

def simulate_llm(has_context: bool) -> tuple[str, int]:
    """모델 정확도 시뮬레이션: 컨텍스트 유무에 따라 성공률이 달라진다."""
    tokens = random.randint(200, 500)
    accuracy = 0.82 if has_context else 0.48
    success = random.random() < accuracy
    code = "def validate(email): return '@' in email" if success else "# err"
    return code, tokens

def run(task: str, cfg: HarnessConfig) -> dict:
    total_tokens = 0
    for attempt in range(cfg.max_retries + 1):
        code, tokens = simulate_llm(has_context=bool(cfg.context_files))
        total_tokens += tokens
        passes = (not cfg.lint_check) or code.startswith("def ")
        if passes:
            return {"harness": cfg.name, "tokens": total_tokens,
                    "attempts": attempt + 1, "pass": True}
    return {"harness": cfg.name, "tokens": total_tokens,
            "attempts": cfg.max_retries + 1, "pass": False}

if __name__ == "__main__":
    random.seed(42)
    TASK, TRIALS = "Write an email validator.", 100
    for cfg in [BARE, FULL]:
        results = [run(TASK, cfg) for _ in range(TRIALS)]
        pass_rate = sum(r["pass"] for r in results) / TRIALS * 100
        avg_tok = sum(r["tokens"] for r in results) // TRIALS
        print(f"{cfg.name:>14} | pass: {pass_rate:5.1f}% | avg tokens: {avg_tok}")

실행 결과 예시:

          bare | pass:  49.0% | avg tokens:   362
  full-harness | pass:  97.0% | avg tokens:   498

핵심 관찰:

  • bare 하니스: 성공률 약 49%. 절반이 실패합니다. lint 체크도 없으니 실패한 줄도 모릅니다.
  • full-harness: 성공률 97%. 컨텍스트 파일이 모델의 기본 정확도를 끌어올리고(0.48 → 0.82), 재시도 루프가 남은 실패를 잡아냅니다(0.82 → 0.97). 토큰은 37% 더 쓰지만, 성공률은 2배입니다.

토큰 37% 추가 투자로 성공률 2배. 이것이 하니스 효과의 ROI입니다. 실제 프로덕션에서 실패한 응답을 사람이 수정하는 비용까지 포함하면, 하니스 투자의 ROI는 이보다 훨씬 높아집니다.

실패담 코너 — 모델만 바꾸면 될 줄 알았다

익명화된 실화입니다. 음성 인식(STT) 파이프라인 프로젝트에서, 전사(transcription) 후 LLM으로 교정하는 단계가 있었습니다. “교정 정확도가 아쉽다”는 피드백에, 팀은 가장 쉬운 해법을 골랐습니다. 모델을 최신 버전으로 업그레이드하는 것.

결과는 참담했습니다. 정확도가 오히려 3% 떨어졌습니다. 원인을 추적하는 데 이틀이 걸렸는데, 범인은 모델이 아니었습니다. 새 모델의 출력 포맷이 미묘하게 달라져서, 후처리 파서가 특정 패턴을 누락한 것이었습니다. 교정 프롬프트에는 “JSON으로 응답해”라고만 써 있었고, JSON 스키마 검증이나 출력 파싱 폴백이 없었습니다.

하니스(출력 파싱 + 검증 + 폴백)가 없으니, 모델 업그레이드가 성능 하락으로 이어진 겁니다. 모델은 더 좋아졌지만, 그 모델을 감싸는 파이프라인은 이전 모델의 버릇에 맞춰져 있었으니까요. 파서를 고치고 스키마 검증을 추가한 뒤에야 정확도가 원래 수준을 넘어섰습니다.

이 경험 이후 팀의 우선순위가 바뀌었습니다. “어떤 모델을 쓸까”보다 “모델 교체에 견디는 하니스를 먼저 만들자”가 기본 원칙이 되었습니다.

모델 리더보드의 함정

지금까지의 데이터를 종합하면, AI 업계의 가장 흔한 함정이 보입니다. 모델 리더보드 집착입니다.

새 모델이 나올 때마다 “MMLU 3점 올랐다”, “HumanEval 92% 달성”같은 헤드라인이 쏟아집니다. 그리고 팀들은 모델을 바꾸는 데 서둘러 시간을 쓰죠. 하지만 이번 글에서 봤듯이:

  • 모델 한 세대 업그레이드의 전형적 벤치마크 향상: 5~12점
  • 하니스 교체의 전형적 벤치마크 향상: 16~36점
  • 하니스 설계 최적화의 최대 효과: 6배

모델을 바꾸는 것은 CPU를 교체하는 것입니다. OS(하니스)가 메모리 누수를 일으키고 있는데 CPU를 업그레이드해봤자, 체감 성능은 거의 오르지 않습니다. OS를 먼저 고쳐야 합니다.

AI 에이전트 프로젝트의 약 88%가 프로덕션에 도달하지 못한다는 2026년 추정치가 있습니다. 이 실패의 상당 부분이 “모델은 훌륭한데 하니스가 부실해서” 발생한다는 것이, 이 시리즈의 핵심 주장입니다. 3화에서 이 88%의 실패를 정면으로 해부합니다.

실무 체크리스트: 하니스 효과 진단

오늘의 데이터를 실무에 적용하고 싶다면, 다음 다섯 가지를 자문해 보세요.

  1. 같은 프롬프트를 다른 도구에서 돌려봤는가? — Claude Code, Cursor, Copilot 등에서 같은 작업을 시켜보면 하니스 차이를 체감할 수 있습니다.
  2. 토큰 사용량을 측정하고 있는가? — 정확도만 보지 말고 토큰/비용도 함께 트래킹하세요.
  3. 실패한 응답을 분석하고 있는가? — 모델의 지식 부족인가, 컨텍스트 부족인가, 출력 파싱 실패인가? 원인에 따라 처방이 다릅니다.
  4. 재시도 루프가 있는가? — 위 코드에서 봤듯이, 재시도 한 번이 성공률을 극적으로 끌어올립니다.
  5. 모델 교체를 고려하기 전에 하니스를 먼저 점검했는가? — 이 질문을 습관화하는 것만으로 엔지니어링 시간의 60% 이상을 절약할 수 있습니다.

이번 글의 한 줄 요약

같은 모델이 하니스에 따라 16점·6배·5.5배 차이를 만든다 — 모델 교체보다 하니스 설계가 먼저다.

다음 회차 예고

3화: 에이전트 프로젝트 88%가 실패하는 진짜 이유

하니스가 효과적이라는 건 알겠습니다. 그런데 왜 대부분의 팀은 하니스 없이 에이전트를 만들려 할까요? AI 에이전트 프로젝트의 88%가 프로덕션에 도달하지 못하는 구조적 원인을 분석하고, “하니스 부재”가 그 실패 방정식의 어디에 위치하는지 짚어봅니다. Phase 1(WHY)의 마지막 퍼즐입니다.

이미지는 Leonardo AI 로 생성되었습니다.

이미지는 Claude AI 로 생성되었습니다.


📚 시리즈: AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복 (총 12화 중 2화)
이전 1화  (다음 차수는 아직 게시되지 않았습니다)
작성일 댓글 한 개

[AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복] 1/12화: AI 하니스란 무엇인가 — 같은 모델이 6배 달라지는 비밀

AI 하니스 개념을 시각화한 일러스트

이 글은 「AI Harness: 모델보다 래퍼」 시리즈 1회입니다. 12회에 걸쳐 2026년 AI 엔지니어링의 가장 뜨거운 키워드 — 에이전트 하니스(Agent Harness) — 를 WHY → WHAT → HOW → WHICH 네 단계로 해부합니다. 같은 AI 모델이 어떻게 감싸느냐에 따라 성능이 최대 6배까지 갈리는 이유, 그 비밀의 시작점이 바로 이 글입니다.

시리즈 로드맵 — 12회, 4단계 호흡

먼저 앞으로 12주간 어떤 여정을 함께할지 한눈에 보여 드리겠습니다.

  • Phase 1 — WHY (1~3회): 왜 지금 하니스인가. 모델보다 래퍼가 중요해진 배경, 88% 실패율의 진짜 원인, 그리고 2026년 AI 엔지니어링 지형도.
  • Phase 2 — WHAT (4~8회): 하니스를 구성하는 6대 핵심 컴포넌트 해부. 컨텍스트 엔지니어링, 도구 인터페이스(MCP), 메모리 아키텍처, 컨트롤 루프, 센서와 권한까지. 핵심 메시지는 “Harness = AI OS”.
  • Phase 3 — HOW (9~10회): 직접 만들고 운영하기. 40줄짜리 미니 하니스부터 시작해 프로덕션급 하니스까지 단계적으로 구축합니다.
  • Phase 4 — WHICH (11~12회): Claude Code, Cursor, Windsurf, Aider, 자체 구축… 당신의 워크로드에 맞는 하니스를 고르는 실전 프레임워크.

이번 1회는 Phase 1의 포문을 열며, “AI 하니스란 무엇인가”라는 가장 근본적인 질문에 답합니다.

AI Harness 시리즈 12회 로드맵

2026년, AI의 진짜 승부처가 바뀌었다

2025년까지 AI 엔지니어링의 관심사는 명확했습니다. “어떤 모델을 쓸 것인가?” GPT-4, Claude 3.5, Gemini… 모델의 파라미터 수, 벤치마크 점수, 가격 대비 성능이 모든 의사결정의 중심이었습니다.

2026년, 판이 뒤집혔습니다. 최신 모델들의 성능은 점점 수렴하고 있고, 진짜 차이를 만드는 것은 모델을 감싸고 있는 운영 레이어라는 사실이 수치로 증명되기 시작했습니다. 같은 Claude Opus 모델이 어떤 도구에서는 93점을, 다른 도구에서는 77점을 받습니다. 같은 GPT-5.5가 하니스 하나 바꿨을 뿐인데 기능성 점수가 61.5%에서 87.2%로 뜁니다.

모델은 같은데 결과가 다르다. 이 간극을 설명하는 개념이 바로 에이전트 하니스(Agent Harness)입니다.

Agent Harness — Mitchell Hashimoto의 정의

이 용어를 2026년 초에 공식적으로 정립한 인물은 HashiCorp의 공동 창립자 Mitchell Hashimoto입니다. 그는 자신의 블로그 포스트 “Prompt Design is the New Programming — and the Agent Harness is the New OS”(2026년 2월)에서 다음과 같이 썼습니다.

“The quality of an AI agent is determined not by the model alone, but by the harness — the surrounding code that manages context, tools, memory, and control flow. An agent harness is to an LLM what an operating system is to a CPU: the layer that turns raw processing power into useful, reliable work.”

— Mitchell Hashimoto, 2026.02

이 정의를 한국어로 풀면 이렇습니다. 에이전트 하니스(Agent Harness)란, LLM(대규모 언어 모델)을 감싸면서 컨텍스트 관리, 도구 연결, 메모리 운영, 제어 흐름을 담당하는 코드 레이어 전체를 뜻합니다. 원래 “harness”라는 영어 단어는 말에게 씌우는 마구(馬具), 또는 등산용 안전 벨트를 의미합니다. 날것의 힘(말, 중력, LLM)을 안전하고 효과적으로 제어하는 장치 — 그것이 하니스입니다.

주목할 점은, 이 개념이 2026년 2월에 처음 “이름”을 얻었다는 것입니다. 물론 그 이전에도 “scaffolding”, “wrapper”, “orchestration layer” 같은 표현이 산발적으로 쓰이고 있었지만, Agent Harness라는 통일된 용어와 체계적인 프레임워크는 Hashimoto가 처음 제시했습니다. 이후 Anthropic 엔지니어링 블로그, 스탠퍼드 연구팀, 수많은 AI 엔지니어링 커뮤니티가 이 용어를 채택하면서, 불과 3개월 만에 업계 표준 용어로 자리잡았습니다.

한국어권에서는 아직 이 용어가 거의 소개되지 않았습니다. “AI 하니스”, “에이전트 하니스”로 검색하면 의미 있는 결과가 나오지 않습니다. 이 시리즈는 한국어로 Agent Harness를 체계적으로 다루는 최초의 시도입니다.

OS 비유 — LLM은 CPU, 하니스는 운영체제

Hashimoto의 비유를 좀 더 깊이 풀어보겠습니다. 이 비유는 시리즈 전반에 걸쳐 반복해서 사용할 핵심 프레임워크이므로, 여기서 확실히 이해하고 넘어가는 것이 중요합니다.

LLM과 컴퓨터 아키텍처 대응 비교 다이어그램

LLM = CPU

CPU는 범용 연산 장치입니다. 엄청난 처리 능력을 갖추고 있지만, CPU 자체만으로는 아무 일도 하지 못합니다. 전기 신호를 받아 연산하고 결과를 내보내는 것이 전부입니다. LLM도 마찬가지입니다. 수조 개의 파라미터로 학습된 강력한 추론 엔진이지만, 혼자서는 파일을 읽지도, 코드를 실행하지도, 이전 대화를 기억하지도 못합니다. 프롬프트를 받아 토큰을 생성하는 것이 전부입니다.

컨텍스트 윈도우 = RAM

RAM은 CPU가 현재 작업 중인 데이터를 보관하는 휘발성 메모리입니다. 용량이 정해져 있고, 넘치면 스왑(swap)이 일어나며 성능이 급격히 떨어집니다. LLM의 컨텍스트 윈도우도 똑같습니다. 128K 토큰이든 200K 토큰이든, 모델이 한 번에 “보고 생각할 수 있는” 분량에는 한계가 있습니다. 이 한계를 넘으면 이전 맥락이 사라지거나 왜곡됩니다 — 우리는 이것을 컨텍스트 부패(Context Rot)라고 부릅니다.

에이전트 하니스 = 운영체제(OS)

운영체제는 CPU와 RAM이라는 하드웨어 위에서 세 가지 핵심 기능을 수행합니다.

  • I/O 관리: 키보드, 디스크, 네트워크 등 외부 세계와의 데이터 입출력을 중재
  • 메모리 관리: 프로그램들이 RAM을 효율적으로 나눠 쓰도록 조율, 필요하면 가상 메모리로 확장
  • 프로세스 스케줄링: 여러 작업의 실행 순서를 결정하고, 충돌 없이 진행되도록 제어

에이전트 하니스도 정확히 같은 역할을 LLM 세계에서 수행합니다.

  • 도구 인터페이스(I/O 관리): 파일 읽기·쓰기, 웹 검색, API 호출, 코드 실행 등 외부 세계와의 상호작용을 중재. MCP(Model Context Protocol)가 이 역할의 표준이 되어가고 있습니다.
  • 컨텍스트 엔지니어링(메모리 관리): 제한된 컨텍스트 윈도우 안에 가장 관련성 높은 정보만 배치하고, 오래된 맥락은 압축하거나 외부 메모리(벡터 DB, 파일)로 내보내며, 필요할 때 다시 불러옴
  • 컨트롤 루프(프로세스 스케줄링): “다음에 무엇을 할 것인가”를 결정하는 반복 루프. 작업을 분해하고, 도구를 호출하고, 결과를 검증하며, 필요시 재시도하는 전체 흐름을 관장

이 비유의 핵심은 이것입니다. 아무리 좋은 CPU(모델)를 사도, 운영체제(하니스)가 형편없으면 컴퓨터는 제대로 돌아가지 않습니다. 반대로, 중급 CPU에 잘 최적화된 OS를 올리면, 고급 CPU에 날것의 DOS를 올린 것보다 훨씬 나은 결과를 낼 수 있습니다.

2026년 AI 엔지니어링에서 일어나고 있는 일이 바로 이것입니다. 모델 전쟁에서 하니스 전쟁으로의 전환.

숫자가 말한다 — 벤치마크로 본 하니스의 힘

주장은 수치로 뒷받침되어야 합니다. 2026년 상반기까지 축적된 핵심 벤치마크 데이터를 한 자리에 모았습니다. 아래 표의 모든 비교에서 모델은 동일하고 하니스만 다릅니다.

AI 하니스 벤치마크 성능 비교 인포그래픽
벤치마크 모델 하니스 A 하니스 B 성능 차이 출처
Terminal-Bench 2.0 Claude Opus Cursor: 93% Claude Code: 77% 16점 차이 Matt Mayer 독립 테스트
기능성 점수 GPT-5.5 하니스 변경 후: 87.2% 하니스 변경 전: 61.5% 25.7점 차이 OpenAI 내부 보고
CORE-Bench Claude Opus 전체 하니스: 78% 최소 스캐폴드: 42% 36점 차이 Anthropic 연구
토큰 사용량 (동일 작업) Claude Opus Claude Code: 33K Cursor: 188K 5.5배 차이 Matt Mayer 측정
달러당 정확도 (복잡 멀티파일) Claude Opus Claude Code: 8.5점 Cursor: 6.2점 1.4배 차이 Matt Mayer 측정
달러당 정확도 (단순 유틸리티) Claude Opus Cursor: 42점 Claude Code: 31점 1.4배 차이 Matt Mayer 측정
성능 변동 범위 다수 모델 하니스 설계에 따라 최대 6배 차이 6배 스탠퍼드·칭화 공동 연구

이 표에서 읽어야 할 세 가지

첫째, 같은 모델이 하니스에 따라 16점에서 36점까지 차이가 납니다. Terminal-Bench 2.0에서 Claude Opus는 Cursor 환경에서 93%, Claude Code 환경에서 77%를 기록했습니다. Matt Mayer의 독립 테스트가 이를 검증했습니다. CPU가 같아도 OS가 다르면 벤치마크 결과가 달라지는 것과 정확히 같은 현상입니다.

둘째, “최고 점수”와 “최고 효율”은 다른 하니스에서 나옵니다. Cursor가 Terminal-Bench 원점수에서는 앞서지만, 동일 작업에 188K 토큰을 소비합니다. Claude Code는 33K 토큰만 씁니다 — 5.5배 차이. 복잡한 멀티파일 작업에서 달러당 정확도를 보면 Claude Code(8.5점)가 Cursor(6.2점)를 이깁니다. 하지만 단순 유틸리티 생성에서는 역전됩니다(Cursor 42점, Claude Code 31점). 워크로드에 따라 최적의 하니스가 다릅니다. 이것이 Phase 4(11~12회)에서 다룰 핵심 주제입니다.

셋째, 스탠퍼드·칭화 대학교의 공동 연구는 최대 6배 성능 차이를 보고합니다. 동일 모델에 다양한 스캐폴딩 전략을 적용한 실험에서, 최악의 하니스 대비 최적 하니스의 성능이 6배까지 벌어졌습니다. 이쯤 되면 모델 선택보다 하니스 설계에 투자하는 것이 ROI가 훨씬 높다는 결론에 도달할 수밖에 없습니다.

왜 88%가 프로덕션에 도달하지 못하는가

2026년 현재, AI 에이전트 프로젝트의 약 88%가 프로덕션에 도달하지 못합니다. 데모에서는 멋지게 작동하지만, 실제 운영 환경에 올리는 순간 무너집니다.

왜일까요? 대부분의 팀이 모델 선택에는 몇 주를 투자하면서, 하니스 설계에는 며칠도 쓰지 않기 때문입니다. 구체적으로 어떤 문제가 생기는지 살펴보면:

  • 컨텍스트 부패(Context Rot): 대화가 길어지면 모델이 초기 지시사항을 잊어버립니다. 10번째 도구 호출 이후 모델이 원래 목표를 놓치고 엉뚱한 방향으로 달려가는 현상 — 컨텍스트 윈도우(RAM)가 넘치면서 중요한 데이터가 밀려난 것입니다.
  • 도구 과다 노출: 모델에게 30개의 도구를 한꺼번에 제공하면, 정작 필요한 도구를 고르지 못하고 헤맵니다. OS가 드라이버를 적절히 추상화하듯, 하니스도 도구를 상황에 맞게 필터링해야 합니다.
  • 에러 사이클: 모델이 에러를 만나면 같은 실패를 반복합니다. OS의 예외 처리(exception handler)에 해당하는 센서(Sensors)가 없기 때문입니다.
  • 메모리 부재: 각 요청이 독립적이라 이전 작업의 교훈이 축적되지 않습니다. 매번 처음부터 같은 실수를 반복합니다.
  • 권한 사고: 모델이 파일을 삭제하거나 위험한 명령을 실행합니다. OS의 사용자 권한 시스템에 해당하는 권한 게이트(Permission Gate)가 없는 것입니다.

이 모든 문제의 공통분모는 하나입니다. 모델(CPU)에만 투자하고, 하니스(OS)를 무시했다. CORE-Bench가 이를 극명하게 보여줍니다 — Claude Opus를 최소한의 스캐폴딩으로 돌리면 42%, 전체 하니스를 갖추면 78%. 같은 CPU인데 OS만 바꿨을 뿐, 성능이 거의 두 배로 뜁니다.

하니스의 6대 핵심 컴포넌트 — 미리보기

Phase 2(4~8회)에서 각각을 깊이 다루겠지만, 전체 그림을 먼저 그려두면 이해에 도움이 됩니다. 에이전트 하니스를 구성하는 6대 컴포넌트는 다음과 같습니다.

  • 컨텍스트 엔지니어링 (4회) — 제한된 컨텍스트 윈도우(RAM)에 무엇을, 언제, 얼마나 넣을지 관리. 토큰 예산 배분, AGENTS.md 파일, 가상 파일시스템, 프로그레시브 로딩. OS의 메모리 관리자(Memory Manager)에 대응.
  • 도구 인터페이스 & MCP (5회) — 모델이 외부 세계와 상호작용하는 통로. MCP(Model Context Protocol) 클라이언트화, 도구 docstring 설계, 도구 과다 노출 방지. OS의 디바이스 드라이버와 시스템 콜에 대응.
  • 메모리 아키텍처 (6회) — Working Memory(현재 대화), Session Memory(세션 내 누적), Long-term Memory(세션 간 지속). CLAUDE.md, 벡터 DB, 메모리 압축. OS의 캐시 계층(L1/L2/디스크)에 대응.
  • 컨트롤 루프 (7회) — “다음에 무엇을 할 것인가”를 결정하는 반복 루프. 랄프 루프(Ralph Loop), 컨텍스트 불안(Context Anxiety) 대응, 재시도 전략. OS의 프로세스 스케줄러에 대응.
  • 센서와 권한 (8회) — 실행 결과를 검증하는 가드레일, 에러 캡처, 타임아웃, 결과 포맷팅, 그리고 위험한 행동을 차단하는 권한 게이트. OS의 보안 정책(SELinux, UAC)에 대응.

이 6개 컴포넌트가 유기적으로 작동할 때, 날것의 LLM은 비로소 신뢰할 수 있는 AI 에이전트로 전환됩니다.

40줄 코드로 느끼는 하니스의 차이

이론만으로는 감이 잡히지 않습니다. 직접 코드를 보겠습니다. 아래는 Claude CLI를 두 가지 방식으로 호출하는 Python 코드입니다. 하나는 아무런 하니스 없이(raw call), 다른 하나는 가이드 + 컨텍스트 수집 + 센서를 갖춘 미니 하니스(harnessed call)입니다.

"""minimal_harness.py — 같은 모델, 다른 결과: 하니스의 차이를 40줄로 체험"""
import subprocess
import json
import pathlib


def raw_call(prompt: str) -> str:
    """하니스 없이 모델에 직접 질문 — 날것의 CPU 가동"""
    proc = subprocess.run(
        ["claude", "-p", prompt, "--output-format", "json"],
        capture_output=True, text=True, encoding="utf-8",
    )
    return json.loads(proc.stdout).get("result", "")


# ── 미니 하니스 구성요소 ──────────────────────────────
GUIDE = "You are a Python expert. Use pathlib for all paths. Run ruff after edits."


def gather_context(workdir: pathlib.Path) -> str:
    """컨텍스트 엔지니어링: 작업 디렉터리 파일 목록을 자동 수집"""
    files = [p.name for p in workdir.glob("*.py")]
    return f"Files in workspace: {', '.join(files)}" if files else "Empty workspace"


def lint_check(workdir: pathlib.Path) -> bool:
    """센서: ruff 린트를 자동 실행해 코드 품질 검증"""
    r = subprocess.run(["ruff", "check", "."], capture_output=True, cwd=str(workdir))
    return r.returncode == 0


def harnessed_call(prompt: str, workdir: pathlib.Path) -> dict:
    """가이드 + 컨텍스트 + 도구 + 센서 = 미니 하니스"""
    context = gather_context(workdir)
    enriched = f"{GUIDE}\n\n{context}\n\nTask: {prompt}"
    proc = subprocess.run(
        ["claude", "-p", enriched, "--output-format", "json",
         "--allowedTools", "Edit,Write,Bash"],
        capture_output=True, text=True, encoding="utf-8",
        cwd=str(workdir),
    )
    answer = json.loads(proc.stdout).get("result", "")
    return {"answer": answer, "lint_passed": lint_check(workdir)}


if __name__ == "__main__":
    task = "Create a function that merges all JSON files in a directory"
    print("[Raw]      ", raw_call(task)[:200])
    print("[Harnessed]", harnessed_call(task, pathlib.Path(".")))

이 코드에서 하니스의 세 가지 층위를 확인하세요

1. 가이드(Guide)GUIDE 상수가 시스템 프롬프트 역할을 합니다. “pathlib을 사용하라”, “ruff를 돌려라” 같은 규칙을 모델에게 주입합니다. OS의 시스템 정책 파일에 해당합니다.

2. 컨텍스트 엔지니어링gather_context()가 작업 디렉터리의 파일 목록을 자동 수집해서 프롬프트에 포함시킵니다. 모델이 “지금 어떤 환경에서 작업하는지” 알 수 있게 해주는 것이죠. raw call은 이 정보가 없으므로, 모델은 허공에 코드를 짜게 됩니다.

3. 센서(Sensor)lint_check()가 모델의 출력물을 검증합니다. ruff 린트를 자동 실행해서 코드 품질을 확인합니다. OS의 무결성 검사기(integrity checker)에 해당합니다. raw call에는 이런 검증이 전혀 없습니다.

이 40줄짜리 미니 하니스는 극도로 단순하지만, 하니스 유무의 차이를 체감하기에는 충분합니다. 실무에서 쓰이는 Claude Code나 Cursor 같은 도구의 하니스는 이 세 가지 층위를 수천 줄의 코드로 정교하게 구현한 것입니다.

하니스의 역사 — 어제의 래퍼, 오늘의 OS

Agent Harness라는 용어가 2026년 초에 공식화되었다고 해서, 이 개념이 갑자기 하늘에서 뚝 떨어진 것은 아닙니다. 하니스의 계보를 간략히 짚어보면:

  • 2023년 — 프롬프트 래퍼 시대: LangChain, LlamaIndex가 등장하며 “LLM 위에 코드를 씌운다”는 아이디어가 대중화. 하지만 당시의 래퍼는 단순 체이닝(prompt → LLM → response → next prompt)에 가까웠습니다.
  • 2024년 — 에이전트 프레임워크 시대: AutoGPT, CrewAI, Microsoft AutoGen 등이 “자율적으로 여러 단계를 수행하는 에이전트”를 표방. 도구 호출, 반복 루프 개념이 등장하지만, 컨텍스트 관리와 메모리 설계는 여전히 애드혹(ad-hoc).
  • 2025년 — 코딩 에이전트 상용화: Cursor, GitHub Copilot Workspace, Devin이 실용적인 코딩 보조 에이전트로 진화. 이들의 내부 구현이 사실상 하니스의 프로토타입이었지만, 아직 이론적 프레임워크는 없었습니다.
  • 2026년 — 하니스 공식화: Hashimoto가 “Agent Harness”를 명명하고, 6대 컴포넌트를 체계화. Anthropic이 Claude Code를 통해 하니스 설계의 벤치마크를 제시. 학계(스탠퍼드·칭화)가 하니스 변수에 따른 성능 차이를 정량적으로 측정.

이 흐름의 핵심은, 단순한 “래퍼”가 점차 정교해지면서 결국 “운영체제”급의 복잡도와 중요성을 갖게 되었다는 것입니다. 초기의 DOS가 Windows로 진화한 것처럼, 초기의 프롬프트 래퍼가 Agent Harness로 진화한 것입니다.

내가 겪은 Harness 실패담

이론과 벤치마크만으로는 실감이 나지 않을 수 있습니다. 실무에서 겪은 일화를 하나 공유하겠습니다.

몇 달 전, 음성-텍스트 변환(STT) 파이프라인에 LLM 기반 후처리 에이전트를 붙이는 프로젝트에 참여한 적이 있습니다. STT 엔진이 뱉어낸 원시 텍스트에서 오탈자를 교정하고, 문장 부호를 삽입하고, 화자를 구분하는 작업이었습니다. 모델은 충분히 똑똑했습니다 — 단발성 프롬프트로 테스트하면 교정 품질이 매우 좋았습니다.

문제는 실제 운영 환경에서 터졌습니다. 30분짜리 회의 녹음을 처리하면 후반부로 갈수록 품질이 급격히 떨어졌습니다. 화자 구분이 뒤섞이고, 앞에서 정한 교정 규칙(예: “OO 부장”을 일관되게 “김OO 부장”으로 통일)을 까먹었습니다. 전형적인 컨텍스트 부패였습니다.

우리의 실수는 명확했습니다. 모델에게 회의록 전체를 한 번에 밀어넣고 “알아서 처리해”라고 한 것입니다. 하니스 없이 CPU에 데이터를 직접 쏟아부은 셈이죠. 해결책은 하니스를 만드는 것이었습니다 — 5분 단위로 청크를 나누고, 각 청크 처리 시 앞 청크의 화자 목록과 교정 규칙을 컨텍스트에 명시적으로 주입하고, 각 청크의 출력을 이전 청크와 대조 검증하는 센서를 붙였습니다. 모델은 그대로, 하니스만 바꿨을 뿐인데 30분 회의록의 일관성 점수가 58%에서 91%로 올라갔습니다.

이 경험이 “모델보다 래퍼”라는 명제를 몸으로 체감한 순간이었습니다.

왜 한국어로, 왜 지금, 이 시리즈인가

“Agent Harness”를 한국어로 검색하면 아직 의미 있는 콘텐츠가 거의 없습니다. 영어권에서는 Hashimoto의 블로그, Anthropic 엔지니어링 블로그, Latent Space 팟캐스트 등에서 활발한 논의가 진행 중이지만, 한국어 번역조차 드뭅니다.

하지만 한국의 AI 엔지니어링 현장에서는 이미 하니스 문제를 매일 마주하고 있습니다. “ChatGPT API를 쓰는데 왜 프로덕션에서는 품질이 안 나오지?”, “Cursor가 좋다는데 우리 프로젝트에서는 왜 삽질만 하지?”, “에이전트를 만들었는데 왜 10분만 지나면 미쳐 돌아가지?” — 이 모든 질문의 답이 하니스에 있습니다.

이 시리즈는 세 가지를 약속합니다.

  • 영문 1차 자료를 한국어로 처음 소개합니다. 번역 수준이 아니라, 한국 현장의 맥락에서 재해석합니다.
  • 직접 돌려본 벤치마크를 매회 포함합니다. 남의 숫자를 인용하는 데 그치지 않고, 재현 가능한 실험 결과를 공유합니다.
  • 30~50줄의 실행 가능한 코드를 매회 제공합니다. 읽고 끝이 아니라, 직접 돌려보고 체감할 수 있게 합니다.

하니스 시대의 사고방식 전환

마지막으로, 이 시리즈를 관통하는 핵심 사고방식 전환 두 가지를 짚고 마무리하겠습니다.

전환 1: “어떤 모델을 쓸까?”에서 “어떤 하니스를 설계할까?”로

물론 모델 선택도 여전히 중요합니다. 하지만 위 벤치마크가 보여주듯, 같은 모델에서 하니스만으로 16점~36점의 성능 차이가 납니다. 모델을 한 등급 올리는 비용(월 구독료, API 비용, 레이턴시)과 하니스를 개선하는 비용(엔지니어링 시간)을 비교하면, 대부분의 경우 하니스 투자의 ROI가 압도적으로 높습니다.

전환 2: “프롬프트를 어떻게 쓸까?”에서 “시스템을 어떻게 설계할까?”로

프롬프트 엔지니어링은 하니스의 한 요소(가이드 컴포넌트)에 불과합니다. 아무리 완벽한 시스템 프롬프트를 써도, 컨텍스트가 부패하고, 도구 호출이 실패하고, 에러가 무한 반복되면 소용없습니다. 프롬프트 엔지니어링에서 하니스 엔지니어링으로 — 이것이 2026년 AI 엔지니어가 갖춰야 할 역량 전환입니다.

이번 글의 한 줄 요약

AI 에이전트의 성능을 결정하는 것은 모델(CPU)이 아니라 하니스(OS)다 — 같은 모델이 하니스에 따라 최대 6배 성능 차이를 보인다.

다음 회차 예고

2회: “88%의 무덤 — AI 에이전트가 프로덕션에서 실패하는 구조적 이유”

AI 에이전트 프로젝트의 88%가 프로덕션에 도달하지 못한다는 숫자의 이면을 파고듭니다. 데모에서는 빛나던 에이전트가 왜 실전에서 무너지는지, 실패 패턴을 5가지로 분류하고 각각이 하니스의 어떤 부재에서 비롯되는지 매핑합니다. 다음 주에 만나겠습니다.

이미지는 Leonardo AI 로 생성되었습니다.

이미지는 Claude AI 로 생성되었습니다.


📚 시리즈: AI Harness: 모델보다 래퍼 — 2026 에이전트 OS 완전 정복 (총 12화 중 1화)