본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 30. 16:30

하네스 엔지니어링: 모델 주변의 코드가 가장 어려운 부분이다

요약

에이전트 시스템 구축 시 모델 자체보다 모델을 감싸는 '하네스(Harness)' 엔지니어링이 신뢰성을 결정하는 핵심임을 강조합니다. 프로덕션 환경의 예외 처리, 제어 루프, 컨텍스트 관리가 제품의 완성도를 좌우합니다.

핵심 포인트

  • 모델 교체보다 하네스 구축에 더 많은 시간이 소요됨
  • 프로덕션 에이전트는 타임아웃, 속도 제한 등 예외 처리가 필수적임
  • 하네스는 모델을 제어 가능한 시스템으로 만드는 스캐폴딩임
  • 제어 루프, 컨텍스트 관리, 도구 디스패치가 하네스의 핵심 구성 요소임

모두가 모델을 벤치마킹합니다. 하지만 하네스(Harness) — 즉, 원시 추론(raw inference) 호출을 감싸서 운영 환경에서 무인으로 실행될 수 있는 무언가로 변환해 주는 루프(loop), 도구 디스패치(tool dispatch), 컨텍스트 매니저(context manager), 재시도 로직(retry logic) — 를 벤치마킹하는 사람은 거의 없습니다. 에이전트 플랫폼(agentic platforms)을 구축해 온 제 경험에 따르면, 모델을 교체하는 것은 오후 한나절이면 배포할 수 있는 설정 변경 사항일 뿐입니다. 하네스에 수개월의 시간이 소요되며, 신뢰성(reliability)이 실제로 결정되는 곳도 바로 이곳입니다.

이 부분은 데모(demo)에서는 나타나지 않습니다. 데모용 에이전트는 도구를 호출하고, 깔끔한 결과를 얻어, 정돈된 답변을 출력합니다. 하지만 프로덕션(production) 에이전트는 타임아웃(timeout)이 발생하는 도구를 호출하고, 형식이 잘못된 본문(malformed body)과 함께 200 응답을 받으며, 재시도 시 속도 제한(rate limit)에 걸리기도 합니다. 그리고 이제 토큰 예산(token budget)을 준수하면서 다운스트림(downstream)의 무엇도 손상시키지 않은 채, 계속 진행할지 아니면 포기할지를 결정해야 합니다. 모델은 이를 해결하지 못합니다. 하네스가 해결합니다.

Harness Engineering: The Code Around the Model Is the Hard Part

하네스가 곧 제품이다

사람들이 "우리는 에이전트를 만들었다"라고 말할 때, 대개는 프롬프트(prompt)와 도구 스키마(tool schema)를 작성했다는 의미입니다. 그것은 쉬운 20%에 불과합니다. 나머지 80%는 모델을 언제 호출할지, 모델 앞에 무엇을 둘지, 돌아온 결과값을 신뢰할지 여부, 그리고 무언가 실패했을 때 _무엇을 할지_를 결정하는 스캐폴딩(scaffolding)입니다. 그 스캐폴딩이 바로 하네스이며, 여러분의 엔지니어링적 판단력이 발휘되는 곳입니다.

유용한 멘탈 모델(mental model): LLM은 단일하고, 비용이 많이 들며, 비결정론적인(non-deterministic) 함수 호출입니다. 그 호출을 안전하고, 제한적이며, 관찰 가능하고(observable), 반복 가능하게(repeatable) 만드는 모든 것이 여러분의 코드입니다. 모델은 여러분이 제어할 수 없는 컴포넌트로 취급하고, 하네스는 여러분이 제어하는 시스템으로 취급하면 대부분의 아키텍처(architecture) 결정이 더 명확해질 것입니다.

하네스의 해부학

프레임워크 브랜딩을 걷어내면, 모든 에이전트 하네스는 동일한 구성 요소들을 가지고 있습니다:

  • 작업이 완료되거나, 중단 조건(stop condition)이 발생하거나, 예산(budget)이 소진될 때까지 단계를 실행하는 제어 루프 (control loop).
  • 매 단계마다 시스템 지침(system instructions), 관련 이력(relevant history), 도구 사양(tool specs) 등을 모아 프롬프트를 구성하고, 용량이 초과될 경우 무엇을 삭제할지 결정하는 컨텍스트 관리자 (context manager).
  • 자체적인 타임아웃(timeout) 및 재시도 정책(retry policy)으로 감싸진 모델 호출 (model call).
  • 모델의 출력을 어떤 작업이 실행되기 전에 타입이 지정되고 검증된 액션(action)으로 변환하는 파싱 및 검증 (parse-and-validate) 단계.
  • 선택된 액션을 자체적인 타임아웃, 재시도 및 멱등성(idempotency) 처리를 통해 실행하는 도구 디스패처 (tool dispatcher).
  • 부수 효과(side effects)를 제어하는 가드레일 (guardrails) — 허용 목록(allow-lists), 인자 검증(argument validation), 속도 제한(rate limits).
  • 모든 단계를 구조화된 데이터로 기록하는 관측 가능성 (observability).

프레임워크들은 이러한 요소들에 대한 기본값(defaults)을 제공합니다. 이 기본값들은 프로토타입에는 괜찮지만, 프로덕션(production) 환경에서는 조용히 잘못된 결과를 초래할 수 있습니다. 왜냐하면 올바른 정책은 도메인 특화적(domain-specific)이기 때문입니다. 언제 중단할 것인가? 몇 단계까지 진행할 것인가? 재시도 가능한 도구 오류와 치명적인 오류의 차이는 무엇인가? 컨텍스트에서 무엇을 가장 먼저 삭제할 것인가? 아무도 당신을 대신해 이 질문들에 답해줄 수 없습니다.

도구 호출은 신뢰할 수 없는 경계이다

제가 목격한 가장 흔한 프로덕션 실패 사례는 모델의 출력을 이미 유효한 것처럼 취급하는 것입니다. 모델이 도구 호출을 제안하면, 하네스는 이를 문자 그대로 실행합니다. 그러다 어느 날 모델이 미묘하게 범위를 벗어난 인자를 내뱉거나, 존재하지 않는 도구 이름을 지어내거나, 끝에 주석이 달린 JSON을 반환하면, 디스패처는 실제 작업을 수행하는 시스템에 아무런 문제 없이 쓰레기 데이터를 전달하게 됩니다.

모델로부터 오는 도구 호출은 명령(instruction)이 아니라 _제안 (proposal)_입니다. 이를 신뢰할 수 없는 클라이언트로부터 들어온 입력값처럼 검증하십시오. 왜냐하면 그것이 바로 도구 호출의 본질이기 때문입니다.

def step(state: AgentState, tools: dict[str, Tool]) -> StepResult:
    # 1. 예산 내에서 컨텍스트 구성 — 가장 오래된 관찰(observations)부터 삭제
    prompt = state.context.render(token_budget=state.remaining_tokens())
...

실패 경로(failure paths)가 어떻게 작동하는지 주목하십시오. 이들은 예외를 발생(raise)시키지 않습니다. 잘못된 도구 이름, 유효하지 않은 인자(arguments), 또는 차단된 액션은 모두 _컨텍스트(context)로 다시 피드백되는 관찰값(observations)_이 됩니다. 모델은 자신의 실수를 확인하고 다시 시도할 수 있습니다. 이 단 하나의 패턴 — 하네스(harness) 수준의 오류를 모델이 볼 수 있는 피드백으로 전환하는 것 — 이 바로 스스로 회복하는 에이전트와 첫 번째 불완전한 출력에서 멈춰버리는 에이전트를 가르는 차이점입니다.

컨텍스트는 버퍼가 아니라 예산이다

단순한(naive) 하네스는 모든 것을 계속 늘어나는 트랜스크립트(transcript)에 추가하고 매 단계마다 이를 다시 전달합니다. 이는 작동하는 듯 보이지만 결국 한계에 부딪힙니다. 컨텍스트 윈도우(context window)를 초과하게 되고, 단계가 진행될수록 지연 시간(latency)이 상승하며, 긴 작업에 대해 비용은 이차 함수적으로(quadratically) 증가합니다. 또한 관련 신호가 오래된 도구 덤프(tool dumps)에 파묻히면서 모델의 주의력(attention)이 저하됩니다.

컨텍스트는 매 단계마다 의도적으로 소비해야 하는 예산입니다. 이는 능동적인 결정을 내려야 함을 의미합니다. 즉, 이전의 관찰값 중 어떤 것이 여전히 중요한지, 어떤 것을 요약할 수 있는지, 어떤 것을 완전히 버릴 수 있는지를 결정해야 합니다. 3단계 전에는 중요했던 40KB 크기의 API 응답은 이제 불필요한 짐일 뿐입니다. 그 응답이 알려준 내용에 대한 한 줄 요약만 남기고 본문은 폐기하십시오. 제어 루프(control loop)의 역할은 모든 것을 기억하는 것이 아니라, 유용한 상태를 모델 앞에 유지하고 나머지는 축출(evict)하는 것입니다. 이를 잘못 처리하면 8단계 만에 끝나야 할 작업이 12단계째에 윈도우를 초과하거나, 원래 비용보다 5배 더 많은 비용을 소모하게 됩니다.

실패는 기본값이므로, 실패를 계획하라

한 단계는 확률적 모델(probabilistic model)에 대한 네트워크 호출이고, 다음 단계는 불안정한(flaky) 제3자 API에 대한 네트워크 호출인 시스템에서, 실패는 예외가 아니라 상시 상태(steady state)입니다. 하네스는 모든 외부 호출이 타임아웃(time out)되거나, 잘못된 형식의 데이터(malformed data)를 반환하거나, 부분적으로만 성공할 수 있다고 가정해야 합니다.

여기서 제 역할을 다하는 부분들은 화려하지 않습니다. 모든 외부 호출(모델 포함)에 대한 타임아웃 (timeouts), 백오프 (backoff)를 포함한 제한된 재시도 (bounded retries), 상태를 변경하는 모든 도구(tool)에 대한 멱등성 키 (idempotency keys)를 적용하여 재시도가 중복 결제나 중복 전송을 일으키지 않도록 하는 것, 그리고 혼란에 빠진 에이전트가 토큰을 낭비하며 무한 루프를 돌지 않도록 하는 엄격한 단계 상한선 (hard step ceiling) 등이 그것입니다. 이 중 새로운 것은 없습니다. 우리가 지난 20년 동안 적용해 온 분산 시스템 (distributed-systems)의 규율과 동일합니다. 새로운 점은 신뢰할 수 없는 구성 요소 중 하나가 이제 의사 결정자 그 자체가 되었다는 것이며, 이는 재시도가 다른 결정을 내릴 수 있음을 의미합니다. 여러분의 하네스 (harness)는 단순히 일시적인 오류 (transient errors) 상황뿐만 아니라, 이러한 상황에서도 올바르게 작동해야 합니다.

볼 수 없는 것은 고칠 수 없다

재현 (replay)할 수 없는 비결정론적 시스템 (non-deterministic system)은 디버깅할 수 없는 시스템입니다. 에이전트가 프로덕션 환경에서 잘못된 행동을 할 때 — 잘못된 도구를 선택하거나, 루프를 돌거나, 너무 일찍 포기하는 경우 — "내 컴퓨터에서는 잘 됐는데"라는 말은 의미가 없습니다. 왜냐하면 여러분의 컴퓨터는 다른 샘플을 받았기 때문입니다.

따라서 모든 단계는 구조화된 데이터 (structured data)를 방출해야 합니다: 조립된 컨텍스트 (context), 모델의 결정, 호출된 도구, 인자 (arguments), 지연 시간 (latency), 토큰 사용량, 그리고 결과값입니다. 단순히 grep으로 검색하는 로그 라인이 아니라, 쿼리하고 집계하며 재현할 수 있는 구조화된 스팬 (structured spans)이어야 합니다. 이를 통해 "에이전트가 실패했다"는 말은 "7단계에서 이전 관찰값 (observation)이 컨텍스트에서 축출되었기 때문에 빈 쿼리로 검색 도구를 호출했다"라는 구체적인 문장이 되며, 이는 실제 수정 가능한 실제 버그가 됩니다. 이것이 없다면, 여러분은 미신에 의존하여 프롬프트를 튜닝하게 될 것입니다. 토큰 및 비용 회계 (token and cost accounting)는 동일한 트레이스 (trace)에 포함되어야 합니다. 장기 실행되는 에이전트에게 있어 이들은 단순한 청구서의 각주가 아니라 프로덕션 환경의 핵심 고려 사항이기 때문입니다.

핵심 요약

모델은 헤드라인을 장식하지만, 하네스는 호출기 (pager)를 담당합니다. 베이스 모델 (base models)이 계속 개선됨에 따라, 데모에서 잘 작동하는 에이전트와 프로덕션 환경의 실전에서 살아남는 에이전트를 가르는 차이점은 어떤 모델을 선택했느냐가 아니라, 모델을 감싸고 있는 코드의 엔지니어링 품질이 될 것입니다. 즉, 어떻게 검증하는지, 어떻게 컨텍스트 예산을 관리하는지, 어떻게 실패하는지, 그리고 얼마나 관찰 가능한지 (observable)가 될 것입니다.

그래서 제가 계속해서 되묻게 되는 질문은 이것입니다. 만약 내일 당신의 에이전트(agent)가 사용하는 기반 모델(underlying model)을 교체한다면, 당신이 확보한 신뢰성(reliability) 중 얼마나 많은 부분이 그 변화 속에서도 살아남을 수 있을까요? 그리고 그동안의 신뢰성 중 얼마만큼이 사실은 하네스(harness)가 떠받치고 있었던 것일까요?

실행 및 테스트 가능한 예제: https://github.com/mturac/harness-demo

AI 자동 생성 콘텐츠

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

원문 바로가기
1

댓글

0