본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 08. 22:07

600줄짜리 스크립트였던 AI 에이전트를 하네스(Harness)로 재구축하여 토큰 비용을 40% 절감한 경험

요약

600줄의 단일 스크립트로 구성된 AI 에이전트를 '하네스(Harness)' 개념을 도입하여 재설계함으로써 토큰 비용을 40% 절감한 사례를 다룹니다. 컨텍스트 관리를 위해 대화 전체를 유지하는 대신 상태 요약본을 디스크에 저장하고 플래너와 워커를 분리하는 아키텍처 개선 방법을 제시합니다.

핵심 포인트

  • 에이전트는 모델과 이를 둘러싼 하네스의 결합체임
  • 전체 대화 기록 대신 상태 요약본을 전달하여 컨텍스트 최적화
  • 플래너와 워커를 분리하여 컨텍스트 윈도우 낭비 방지
  • 상태(State)를 메모리가 아닌 디스크에 저장하여 비용 절감

내 API 예산을 잡아먹던 600줄짜리 스크립트

나의 첫 번째 실제 에이전트는 하나의 Python 파일이었습니다. 약 600줄 정도였죠. 이 스크립트는 작업을 읽고, 필요할 법한 모든 것들—전체 리포지토리 트리(repo tree), 모든 문서, 전체 대화 기록, 모든 도구 정의(tool definitions)—을 가져와서 하나의 거대한 프롬프트(prompt)에 쑤셔 넣은 다음, 모델이 승리를 선언할 때까지 루프(loop)를 돌게 했습니다.

작동은 했습니다. 그것이 함정이었습니다. 너무 잘 작동했기에 나는 두 달 동안 아키텍처(architecture)에 의문을 갖지 않았습니다.

그러다 청구서를 보게 되었습니다.

하네스(harness)란 모델을 제외하고 모델을 둘러싸고 있는 모든 것을 의미합니다. 즉, 컨텍스트 관리(context management), 도구 실행(tool execution), 중단 시점을 결정하는 루프(loop), 가드레일(guardrails), 그리고 관찰 가능성(observability) 등이 이에 해당합니다. 2026년 초 Mitchell Hashimoto가 제시한 프레임링(framing)이 제 머릿속에 깊이 남았습니다. 에이전트(agent)는 모델에 하네스를 더한 것이며, 당신은 그 둘 중 하나만을 제어할 수 있다는 것입니다. 화요일 오후에 갑자기 모델을 더 똑똑하게 만들 수는 없습니다. 하지만 모델 주변의 요소들이 주의력(attention)을 낭비하지 않도록 만들 수는 있습니다.

저의 600줄짜리 스크립트에도 하네스는 있었습니다. 다만 비즈니스 로직(business logic)과 동일한 파일에 뒤섞여 있고, 모든 결정이 암시적(implicitly)으로 이루어진 나쁜 하네스였을 뿐입니다. 이를 재구축한다는 것은 프레임워크(framework)를 추가한다는 뜻이 아니었습니다. 하네스를 별도의 독립된 요소로 분리해내고, 세 가지의 암시적인 결정 사항을 명시적(explicit)으로 만드는 것을 의미했습니다.

변경 사항 1: 대화 전체를 영원히 들고 다니지 않기

이것이 가장 큰 변화였습니다. 아마도 절감한 40포인트 중 25포인트 정도가 여기서 나왔을 것입니다.

기존 스크립트는 모든 도구 결과(tool result)를 실행 중인 메시지 목록(message list)에 추가하고, 호출할 때마다 전체 뭉치를 다시 보냈습니다. 12단계에서도 여전히 2단계의 가공되지 않은 출력값(raw output)을 계속 끌고 다니고 있었습니다. 모델에는 그것이 필요하지 않았습니다. 저는 어쨌든 호출당 요율(per-call rate)에 따라 비용을 지불하고 있었고, 이는 복리로 쌓여가고 있었습니다.

해결책은 Anthropic이 장기 실행 에이전트(long-running agents)를 위해 문서화한 패턴인데, 너무 단순하게 들릴 정도입니다. 하나의 컨텍스트 윈도우(context window)가 전체 작업을 수행하게 두지 마십시오. 초기화하십시오. 전체 대화 기록(transcript) 대신 상태(state)에 대한 짧은 서면 요약본을 전달하십시오.

구체적으로, 저는 루프를 플래너(planner)와 워커(worker)로 분리했습니다. 워커는 작업의 한 덩어리(chunk)를 수행하고, 파일에 짧은 진행 상황 노트를 작성한 뒤 종료합니다. 다음 워커는 깨끗한 컨텍스트(clean context)에서 시작하여, 그 노트를 읽고 작업을 계속합니다. 상태(state)는 프롬프트(prompt)가 아닌 디스크(disk)에 존재합니다.

변경 전 -- 모든 것이 메모리(memory)에 축적됨:

# 스크립트: 계속해서 커지는 하나의 메시지 목록
messages = [system_prompt]
while not done:
...

변경 후 -- 상태는 디스크에 저장되고, 컨텍스트는 작게 유지됨:

# 하네스: 각 워커는 새로 시작하며, 노트를 읽음
def worker(task):
    state = read_progress("progress.md")      # 80k가 아닌 ~400 토큰
...

Anthropic의 자체 기술 문서에서는 이 근본적인 문제를 "컨텍스트 불안 (context anxiety)"라고 부릅니다. 특정 채움 수준을 넘어서면, 윈도우 (window)가 혼잡해짐에 따라 모델의 출력 품질이 실제로 저하됩니다. 따라서 비대해진 프롬프트 (prompt)는 단순히 비용만 많이 드는 것이 아닙니다. 에이전트의 성능을 떨어뜨립니다. 저는 제 결과물의 품질을 떨어뜨리기 위해 추가 비용을 지불하고 있었던 셈입니다. 이는 매우 뼈아픈 경험이었습니다.

진행 파일 (progress-file) 트릭은 숙련된 엔지니어라면 누구나 무의식적으로 수행하는 방식에서 빌려온 것입니다. git 히스토리 (git history)는 무엇이 변했는지 알려주고, 메모 (scratch note)는 현재 어디에 있는지를 알려줍니다. 새로운 에이전트에게 이 두 가지를 모두 제공하면, 에이전트는 세상을 다시 유도 (re-deriving)하는 대신 단 한 번의 읽기로 맥락을 파악합니다.

변경 사항 2: 모든 도구를 보여주는 대신 도구에 게이트(gate)를 설정하기

기존 스크립트는 매 호출마다 모델에게 18개의 모든 도구 정의 (tool definitions)를 전달했습니다. 데이터베이스 도구, 파일 도구, HTTP 도구, 셸 (shell) 등 모든 것이 포함되었습니다. 도구 스키마 (tool schemas)는 공짜가 아닙니다. 모호한 설명은 오작동을 유발하기 때문에, 좋은 설명은 의도적으로 장황하게 작성됩니다. 18개의 도구는 호출당 수천 토큰의 오버헤드 (overhead)를 발생시켰으며, 그중 대부분은 우리가 진행 중인 단계와 무관한 것들이었습니다.

비용보다 더 심각한 문제는 이것입니다. 18개의 옵션을 바라보고 있는 모델은 잘못된 선택을 더 자주 합니다. 서랍에 18개의 도구가 들어있을 때 누군가에게 "서랍에서 도구를 꺼내줘"라고 요청하면, 가끔 잘못된 것을 가져올 것입니다. 하지만 작업에 적합한 3가지만 보여준다면 문제없이 수행할 것입니다.

그래서 저는 도구에 게이트를 설정했습니다. 하네스 (harness)는 현재 단계에 따라 어떤 도구가 가시적인지 결정합니다. 계획 (Planning) 단계에서는 읽기 전용 도구만 보입니다. 편집 (Editing) 단계에서는 파일 및 셸 도구가 보입니다. 작업이 실제로 데이터에 접근하지 않는 한, 데이터베이스 도구는 아무에게도 보이지 않습니다.

# 하네스는 전체 무기고가 아닌, 단계별 범위(phase-scoped)의 부분 집합을 노출합니다
TOOLSETS = {
    "plan":   ["read_file", "list_dir", "search"],
...

이것은 비용 절감 40포인트 중 약 8포인트 정도를 차지했습니다. 조용하지만 더 큰 승리는 정확도였습니다. 잘못된 도구 호출이 눈에 띄게 줄어들었고, 이는 낭비되는 재시도 루프 (retry loops)가 줄어듦을 의미하며, 이는 그 자체로 토큰 절약으로 이어졌습니다. 절감 효과는 예측하지 못한 방향으로 복리로 쌓입니다.

2026년 가이드라인이 이를 뒷받침합니다. 핵심은 에이전트에게 작고 관련성 있는 결과 세트만 제공하고, 충분한 정보가 모이면 멈추게 하는 것이지, 전체 도구 상자와 데이터베이스 전체를 컨텍스트 창(window)에 쏟아붓고 요행을 바라는 것이 아닙니다. 여기서는 '적은 것이 진정으로 더 많은 것(Less is genuinely more)'이며, 이번만큼은 그것이 단순한 슬로건이 아니라 실제 비용 항목(line item)으로 다가옵니다.

Three changes -- context reset, tool gating, staged loading -- mapped to their share of a 40 percent token reduction

변경 사항 3: 컨텍스트를 한꺼번에 로드하지 않고 단계별로 로드하기

기존 스크립트의 로딩 로직은 "모두 가져온 다음, 생각하라"였습니다. 모델이 아무것도 수행하기 전에 전체 리포지토리 트리(repo tree), 모든 문서 등 전부를 가져왔습니다.

그중 대부분은 불필요한 데이터(dead weight)였습니다. 파일 3개만 다루는 작업에 모델은 400개 파일 전체의 지도가 필요하지 않습니다.

하네스(harness)는 단계별로 로드합니다. 얇은 슬라이스(thin slice)로 시작합니다: 작업 내용, 디렉토리 개요, 진행 상황 메모 등입니다. 모델이 더 많은 정보를 요청하게 하세요. 모델이 파일을 요청하면 하네스가 이를 읽어옵니다. 문서가 필요하면 하네스가 해당 문서 하나만 가져옵니다. 컨텍스트는 상상 속의 최악의 상황을 대비해 미리 로드하는 대신, 실제 문제에 맞춰 성장합니다.

# 단계별 로드: 모델이 필요한 것을 가져오며, 하네스는 미리 채워 넣지 않음
def build_context(task, state):
    ctx = [task, state, dir_outline()]   # 시작은 가볍게
...

이것이 40% 절감 효과의 마지막 조각이었으며, 변경 사항 1과 겹치기 때문에 제가 가장 다시 해보고 싶은 부분이기도 합니다. 두 가지는 두 가지 관점에서의 동일한 직관입니다. 즉, 컨텍스트 창은 현재 단계에 필요한 것만 담고 있어야 하며, 단지 필요할지도 모르는 것은 담아서는 안 된다는 것입니다. 그 외의 모든 것은 모델 외부, 즉 디스크나 도구 호출(tool call) 뒤에 존재하며, 단 한 번의 호출(fetch)만으로 가져올 수 있습니다.

이 '모델 외부'라는 아이디어가 세 가지 변경 사항을 하나로 묶어주는 실타래입니다. 600줄짜리 스크립트는 컨텍스트 창을 모든 것이 한꺼번에 있어야 하는 장소로 취급했습니다. 반면 하네스는 컨텍스트 창을 작업대(workbench)로 취급합니다. 작업 중인 부품을 가져와서 작업을 마치고, 다시 제자리에 돌려놓은 뒤, 다음 부품을 가져오는 방식입니다.

솔직한 수치들

이에 대해 당당히 말씀드릴 수 있습니다. 이것은 제가 직접 만든 멀티 스텝 코딩 에이전트(multi-step coding agent)이며, 동일한 모델을 사용하여 동일한 작업 배치(batch)를 기준으로 실행 전후를 측정했습니다. 모든 실행 과정에서 발생한 모든 모델 호출의 총 토큰(total tokens) 합계를 기준으로 했습니다.

600줄짜리 스크립트하네스 (harness)변화량
작업당 평균 토큰~118k~70k-41%
...

40%라는 수치는 실제 결과이지만, 이는 벤치마크가 아닌 저의 개인적인 결과입니다. 여러분의 스크립트에서 발생하는 불필요한 데이터(sediment)는 저의 경우와 다른 곳에 있을 것입니다. 발표된 2026년 연구 결과도 비슷한 영역에 속합니다. 즉, 부피가 큰 도구 출력(tool output)을 컨텍스트 윈도우(context window) 보고서에 포함시키지 않는 컨텍스트 모드(context-mode) 패턴은 정확도 저하 없이 20% 이상의 비용 절감을 가져오며, 토큰 사용량이 모델 선택보다 멀티 에이전트(multi-agent) 설정에서의 성능 변동을 설명하는 가장 큰 요인이라는 광범위한 발견이 그 예입니다. 정확한 퍼센트가 중요한 것이 아닙니다. 방향성이 중요합니다. 비용의 원인은 모델이 아니었습니다. 제가 모델에 계속해서 무엇을 먹였느냐가 문제였습니다.

과거의 나에게 해주고 싶은 말

제가 두 달 동안 잘못 생각했던 점은 이것입니다. 저는 "에이전트를 더 좋게 만든다"는 것이 더 나은 프롬프트(prompt)나 더 나은 모델을 사용하는 것을 의미한다고 생각했습니다. 하지만 그것은 더 나은 하네스(harness)를 만드는 것을 의미했습니다. 모델은 전혀 바뀌지 않았습니다. 스크립트와 하네스는 동일한 작업에 대해 동일한 모델을 실행했으며, 한쪽이 40% 더 저렴했던 이유는 컨텍스트 윈도우(context window)를 잡동사니 서랍처럼 취급하는 것을 멈췄기 때문입니다.

만약 현재 실제로 작동하는 스크립트를 가지고 있다면, 그것을 프레임워크(framework)로 다시 작성할 필요는 없습니다. 대신 다음의 세 가지 작은 조치들이 필요합니다.

  • 컨텍스트 초기화 (Reset the context): 전체 대화 기록(transcript)을 계속 다시 보내는 것을 멈추세요. 상태(state)를 파일에 저장하고, 새로 시작한 뒤 그 파일을 읽으세요.
  • 도구 제한 (Gate the tools): 모델에게 18개의 도구를 전부 보여주지 말고, 현재 단계에 필요한 3개의 도구만 보여주세요.
  • 단계적 로딩 (Stage the loading): 가볍게 시작하세요. 좀처럼 발생하지 않는 최악의 상황을 대비해 미리 채워 넣는 대신, 모델이 필요할 때(on demand) 더 많은 정보를 가져오도록 하세요.

저는 이와 관련된 실패 사례인 "에이전트가 자신의 숙제를 스스로 채점하게 만든 것"에 대해서도 글을 쓴 적이 있습니다. 당시 저는 자동 승인(auto-approve)을 켰다가 30분 만에 CI를 망가뜨린 적이 있습니다. 다른 각도에서 본 동일한 교훈입니다. 승리는 모델이 아니라 하네스(harness)에서 나옵니다.

프롬프트의 크기가 가장 큰 호출(call)을 찾아보세요. 그 안에 무엇이 들어있는지 살펴보십시오. 저는 그 내용의 대부분이 모델이 세 단계 전부터 읽기를 중단했을 '침전물(sediment)'일 것이라고 확신합니다. 그 침전물이 바로 당신의 40%입니다.

전체 내용을 알고 싶으신가요? 컨텍스트 리셋(Context resets), 도구 게이팅(tool gating), 관측 가능성(observability), 그리고 하네스(harness) 해부학의 나머지 요소들은 Harness Engineering Guide의 중추를 이룹니다. 이 책은 단순히 프롬프트를 입력하는 대신 AI 에이전트를 제어하는 시스템을 구축하는 방법을 다루며, 본 기사는 이 책에서 발췌되었습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0