← 모든 글

Agent Harness라는 개념을 처음 접한 날

단순히 LLM을 호출하던 코드가 왜 어느 순간부터 복잡해지는지, 그 이유를 harness라는 틀로 이해하게 된 과정을 기록한다.

작년 말쯤이었다. 팀에서 LLM을 활용한 내부 도구를 몇 가지 만들다 보니, 코드가 묘하게 비슷한 패턴으로 불어나고 있다는 걸 눈치챘다. 모델을 부르고, 응답을 파싱하고, 필요하면 도구를 실행하고, 다시 모델에 돌려보내는 반복. 처음에는 그냥 “루프를 잘 짜면 되지”라고 생각했다. 그러다 어딘가에서 agent harness라는 표현을 처음 접했다.

Harness가 무엇인지 이해하는 데 걸린 시간

처음에는 그냥 래퍼 클래스 이야기인 줄 알았다. 그런데 읽을수록 뭔가 달랐다. 단순한 추상화가 아니라, 에이전트가 실행되는 환경 자체를 규정하는 구조였다. 어떤 도구를 쓸 수 있는지, 오류가 나면 어떻게 처리할지, 몇 번까지 재시도할지, 루프를 끊는 조건은 무엇인지. 이 모든 것을 하나의 레이어에서 다루는 것이 harness였다.

비유하자면 테스트 harness와 비슷하다. 테스트를 실행할 때 우리는 개별 테스트 케이스만 작성하지, 테스트 러너가 어떻게 실행 환경을 세팅하고 결과를 수집하는지는 직접 관여하지 않는다. Agent harness도 마찬가지다. 에이전트 로직과 실행 인프라를 분리하는 것이 핵심이었다.

직접 만들어보면서 달라진 시각

이해는 됐는데 와닿지 않아서, 간단한 harness를 직접 구현해봤다. 입력을 받아서 모델에 넘기고, 모델이 도구 호출을 요청하면 실제로 실행하고, 결과를 다시 컨텍스트에 붙여서 돌려보내는 루프였다. 핵심 로직은 몇십 줄 안에 들어왔다.

while not done:
    response = model.call(context)
    if response.is_tool_call:
        result = execute_tool(response.tool, response.args)
        context.append(result)
    else:
        done = True
        return response.text

단순해 보이지만, 실제로는 여기에 타임아웃, 최대 반복 횟수, 도구 실행 실패 처리, 컨텍스트 크기 관리가 모두 붙어야 했다. 그 “붙어야 하는 것들”의 집합이 곧 harness였다.

결국 남은 질문

하나 만들고 나니 새로운 질문이 생겼다. 에이전트를 여러 개 엮으면 어떻게 될까? 각 에이전트가 서로를 도구처럼 호출하는 구조에서는 harness가 어디서 어디까지를 담당해야 하나. 단일 에이전트에서는 명확했던 경계가 다중 에이전트 구조에서는 흐릿해진다.

아직 완전히 정리된 답은 없다. 다만 2025년 마지막 날을 앞두고, 이 개념을 이름 붙여서 부를 수 있게 된 것만으로도 한 해가 의미 있었다는 생각이 든다.

— by slecs