엔지니어링 하네스(Harness)에는 정해진 위치가 없다
요약
에이전트 시스템에서 하네스(Harness) 엔지니어링의 본질을 정의합니다. 모델의 발전으로 인해 루프나 메모리 같은 메커니즘은 모델 내부로 흡수되지만, 에이전트의 목적을 규정하는 사양(Specification)과 검증(Verification)은 모델 외부에 남아야 하는 핵심 요소임을 강조합니다.
핵심 포인트
- 하네스는 단순한 래퍼가 아닌 에이전트의 사양과 검증을 담당하는 속성임
- 모델 성능 향상에 따라 루프, 메모리 등 기존 스캐폴딩은 모델 내부로 흡수됨
- 결제, 인증 등 결정론적 중추는 소프트웨어 엔지니어링 영역으로 유지됨
- 엔지니어는 모델에 녹아드는 메커니즘이 아닌 외부의 규율(Discipline)에 집중해야 함
요약 (TL;DR) — 하네스 엔지니어링(Harness engineering)은 별개의 문제입니다: 직접 훈련시키지 않은 추론기(reasoner)로부터 신뢰할 수 있는 판단을 이끌어내는 것 — 지침(instruction)이 주어지고, 그 지침을 무효화할 수 있는 사양(spec)에 의해 제한되며, 결과가 전달되기 전에 검증되는 과정입니다. 이것은 "모델을 감싸는 래퍼(wrapper)"가 아닙니다. 이것은 스택 내의 특정 위치가 아니라 코드의 _속성(property)_이며, 모든 도구 호출(tool call)의 양측에 존재합니다. 루프(loops), 디스패치(dispatch), 메모리(memory)와 같은 일반적인 스캐폴딩(scaffolding)은 매 분기마다 모델 속으로 조금씩 녹아듭니다. 살아남는 것은 모델의 _외부(external)_에 머무는 부분입니다: 에이전트가 할 수 있는 일에 대한 사양(specification)과, 그것을 실제로 수행했는지에 대한 검증(verification)입니다. 그것이 바로 규율(discipline)입니다. 나머지는 배관(plumbing)일 뿐입니다.
저는 이전에 **에이전트(Agent) = 모델(Model) × 하네스(Harness)**라고 썼으며, 하네스가 실제로 여러분이 엔지니어링해야 하는 절반이라고 말했습니다. 저는 여전히 이 공식을 믿습니다. 하지만 거기서 멈추면 두 가지를 과장하게 됩니다. 그래서 저는 코드를 통해 제 프레임워크를 정확하게 수정하고자 합니다.
공식이 틀린 두 가지 사항
×(곱하기)는 분리 가능성을 과장합니다. 곱셈은 인자들이 독립적임을 암시합니다. 하지만 그렇지 않습니다. 더 나은 모델은 여러분의 하네스를 그대로 두지 않고, 그 일부를 녹여버립니다(dissolves). 생각의 사슬(Chain-of-thought) 프롬프팅은 추론 모델(reasoning models)이 되었습니다. ReAct 스타일의 도구 루프(tool loops)는 네이티브 도구 사용(native tool use)이 되었습니다. RAG 배관(plumbing)의 절반은 더 긴 컨텍스트(context)와 더 잘 훈련된 검색(retrieval)에 의해 조용히 흡수되고 있습니다. 매 모델 세대가 바뀔 때마다 이전 세대가 필요로 했던 하네스의 계층 하나가 붕괴됩니다.
"하네스가 소프트웨어 엔지니어링을 흡수한다"는 병합(annexation)을 과장합니다. 그것은 소프트웨어 엔지니어링을 흡수하는 것이 아닙니다. 그것은 부분적으로 소비할 뿐입니다. 즉, 에이전트의 행동을 인코딩하는 조각을 가져오는 반면, 결정론적인 중추(deterministic spine)(결제, 원장, 인증)와 단순한 도구들은 원래 모습 그대로 유지됩니다. 이는 병합이 아니라, 접합부에서의 병합을 동반한 재배치입니다. 탈취가 아닙니다.
두 가지 수정 사항 모두 동일하고 불편한 질문을 던집니다: 만약 모델이 매 분기마다 스캐폴딩(scaffold)을 먹어치운다면, 하네스는 녹아 없어지는 부분, 즉 커리어를 걸기에는 어리석은 대상이 아닌가?
무엇이 녹고, 무엇이 녹지 않는가
그 해결책이 여기 있으며, 이것이 바로 핵심입니다.
하네스의 _메커니즘 (mechanism)_은 녹아내립니다. 루프(loop), 디스패치(dispatch), 재시도(retry), 메모리 스티칭(memory-stitching) — 이 부분이야말로 모델과 프레임워크가 범용화(commoditize)하는 영역이며, 마땅히 그래야만 합니다.
녹지 않는 것은 모델 외부에 있는 (external to the model) 부분입니다:
모델은 에이전트를 실행하는 (running) 데 있어 임의로 뛰어난 성능을 보일 수 있지만, 여전히 당신의 에이전트가 _무엇을 위한 것인지 (for)_는 알지 못할 수 있습니다.
당신의 환불 정책은 가중치(weights) 안에 들어있지 않으며, 앞으로도 결코 들어있지 않을 것입니다. 당신의 에스컬레이션 임계값(escalation thresholds), 위험 허용 범위(risk tolerance), 그리고 어떻게 질문하더라도 이 에이전트가 절대 해서는 안 되는 일 — 이것들은 사적이고, 맥락적이며, 계속 변합니다. 이것은 _명세 (specification)_이며, 명세는 그것이 통제하는 대상의 외부에 존재합니다. 평가(evaluation)도 마찬가지입니다. 모델은 순환 논리(circularity) 없이 스스로의 측정 계층(measurement layer)이 될 수 없습니다. 판단을 받는 대상이 스스로를 판단하도록 신뢰할 수는 없기 때문입니다.
따라서 하네스 엔지니어링(harness engineering)의 내구적인 핵심은 결코 기계 장치가 아니었습니다. 그것은 바로 명세와 검증 (specification and verification) 입니다. 즉, 허용된 행동의 범위를 정의하고, 에이전트가 그 범위 안에 머물러 있음을 증명하는 것입니다. 이 두 가지는 모든 모델 업그레이드 속에서도 살아남는데, 그 이유는 동일합니다. 이들은 모델 외부에 존재하며, 하나는 제약(constraint)으로서, 다른 하나는 대립자(adversary)로서 존재하기 때문입니다.
경계에는 고정된 주소가 없다
매혹적인 그림은 깔끔합니다. 하네스가 모델을 감싸고, 도구(tools)가 그 외부에 놓여 있는 모습이죠. 하지만 그 그림은 에이전트 대상 API(agent-facing APIs)와 접촉하는 순간 유지되지 못합니다.
환불 한도(refund cap)가 실제로 어디에 존재하는지 물어보십시오. 에이전트의 오케스트레이션 가드레일(orchestration guardrails)에 있습니까? 아니면 호출을 거부하는 환불 엔드포인트(refund endpoint)에 있습니까? 정직한 대답은 _둘 다_입니다. 그리고 하중을 견디는 복사본(load-bearing copy)은 API에 있는 것입니다. 왜냐하면 확률적 추론기(probabilistic reasoner)가 스스로를 제한할 것이라고는 결코 믿을 수 없기 때문입니다. 심층 방어(Defense in depth)는 엄격한 제약 사항을 도구 내부로 (down into the tool) 밀어 넣습니다. 이는 에이전트 대상 엔드포인트가 하네스급 작업 — 즉, 에이전시(agency)를 명세하고 제약하는 작업 — 을 수행하고 있음을 의미하며, 이는 당신이 하네스와 도구를 구분한다고 생각했던 경계의 저편에서 일어나고 있는 일입니다.
결국 하네스 엔지니어링은 스택 내의 특정 _위치 (location)_가 아닙니다. 그것은 하나의 _속성 (property)_입니다:
이 코드가 결함이 있는 추론기를 명세(spec)에 맞게 굴복시키기 위해 존재하는가?
그 테스트를 실행하면 경계(boundary)는 더 이상 중요하지 않게 됩니다. 모델 지향적 (model-facing) 측면 — 모델과 대화하고, 판단을 이끌어내며, 제약하는 코드 — 은 통째로 통과합니다. 그것이 그 코드의 유일한 역할이기 때문입니다. 서비스 측면 (service side) 은 선택적으로 통과합니다. 단순한 데이터베이스나 인간 지향적 엔드포인트는 그저 도구일 뿐이지만, 에이전트를 위해 특수 제작된 엔드포인트 — 멱등성(idempotent)을 갖추고, 드라이 런(dry-run)이 가능하며, 정책을 강제하고, 모델이 파싱하여 복구할 수 있는 에러를 제공하는 엔드포인트 — 가 바로 하네스(harness)입니다. '에이전트를 위한 Stripe'를 개발하는 사람은 하네스 엔지니어링(harness engineering)을 하고 있는 것입니다. 에이전트가 우연히 호출하게 되는 엔드포인트를 가진 '인간을 위한 Stripe' 개발자는 그렇지 않습니다. 소속은 주소가 아니라 기능에 의해 결정됩니다.
그렇다면 모델 자체는 어떨까요? 결코 아닙니다. 모델은 외생적(exogenous)입니다. 즉, 당신이 소유하지 않는 ×(변수)입니다.
코드에서의 모습
이 학문적 체계 전체를 하나의 함수로 나타내면 다음과 같습니다. 주석 헤더가 분류 체계(taxonomy) 역할을 합니다.
def handle_refund(instruction, ctx):
# ── MODEL-FACING (agent side): 비결정론적 추론기(non-deterministic reasoner)로부터 판단을 이끌어냄
decision = model.decide(
...
(model.decide, evals.verify, refund_api.execute는 실제 구현체를 대신합니다.)
가르침은 두 줄에 담겨 있습니다.
이것을 프롬프트 엔지니어링 (prompt engineering)이 아닌 하네스 엔지니어링 (harness engineering) 으로 만드는 것은 바로 외곽의 if 문입니다. 만약 제약 사항이 프롬프트 안에 있다면, 모델은 아첨을 받거나, 혼란을 겪거나, 탈옥(jailbroken)되는 방식으로 그 제약을 넘어설 수 있습니다. 하지만 코드에서는 불가능합니다. 그 단일 분기문은 "명세(spec)에 의해 오버라이드될 수 있는 범위 내에서, 지시사항이 주어졌을 때 신뢰할 수 있는 판단을 내린다"라는 것을 컴파일된 형태로 구현한 것입니다.
그리고 execute를 수행하기 전 에 evals.verify를 호출하는 것은, 오류가 발생하더라도 비용이 저렴한 시점에 판단을 검증하는 것입니다.
이제 무엇이 결여되어 있는지 주목하십시오: 모델 자체(외생적 요소)와 인간 지향적인 환불 UI(하네스가 아닌 도구)입니다. 모든 줄에 대해 소속 테스트를 실행해 보십시오. '이것은 결함이 있는 추론기를 명세에 맞게 굴복시키기 위해 존재하는가?' 이 질문을 통과하고 남는 모든 것이 바로 하네스입니다.
어려운 부분은 복종이 아니라 거부입니다
"신뢰할 수 있는 동작"을 순진하게 해석하면 _'에이전트가 지시받은 대로 수행하는가'_가 됩니다. 그것은 쉬운 절반이며, 모델들이 스스로 점점 더 잘해내고 있는 영역입니다.
어렵고 힘든 나머지 절반은 그 반대입니다:
신뢰할 수 있는 판단이란, 지시 사항이 명세(spec)를 위반할 경우 에이전트가 이를 **거부(refusing)**하는 것을 포함합니다.
환불 에이전트에게 가장 어려운 순간은 "이 고객에게 환불해 주세요"를 실행하는 것이 아닙니다. 그것은 "50,000달러를 환불해 주세요"라는 지시를 받았음에도 불구하고, 그 지시를 거절하는 것입니다. 그 거부(refusal)는 지시 사항에서 나오는 것이 아니라, 그보다 우선순위가 높은 엔벨로프(envelope)에서 나옵니다. 그리고 확률적 추론기(probabilistic reasoner)가 매번 정확히 적절한 경계선에서 신뢰할 수 있게 _불복종(disobey)_하도록 만드는 것이, 축소된 형태의 안전 문제(safety problem) 전체입니다. 또한 이는 비용이 많이 들기 전까지는 표면화되지 않는 실패이기도 합니다. 이것이 바로 프롬프트(prompt)에 기대기보다 코드(code)에서 검증해야 하는 정확한 이유입니다.
평가는 피드백입니다 — 그리고 두 가지가 있습니다
측정 가능한 에러 신호(error signal)를 통해 낮출 수 있는 규율이 있다면 그것은 엔지니어링(engineering)입니다. 그것이 없다면 그것은 프롬프트 제작(prompt-craft)일 뿐입니다. 평가(Evals)는 루프(loop)를 닫는 역할을 하며, 서로 다른 시간 규모(timescales)로 중첩된 두 가지 루프가 존재합니다.
**내부 루프(inner loop)**는 잘못된 판단이 실행되기 전에 잡아내는 런타임 체크(runtime check)입니다 — 위에서 언급한 evals.verify가 그것입니다. 이는 작업 도중 에이전트에게 피드백을 제공합니다. 이것은 엔벨로프(envelope)가 강제되는 방식이며, 지금 당장 비용을 절감해 줍니다.
**외부 루프(outer loop)**는 "이 에이전트는 3%의 확률로 정책을 위반합니다"라고 알려주는 오프라인 스위트(offline suite)입니다. 이는 여러 버전에 걸쳐 사용자에게 피드백을 제공하며, 몇 주에 걸쳐 하네스(harness)를 재조정(re-tunes)합니다.
동일한 규율을 가진 두 개의 컨트롤러(controllers)입니다. 하나는 단일 결정이 선을 넘지 않도록 유지하고, 다른 하나는 _시스템_이 계속 개선되도록 유지합니다.
전체 구조의 조립
한 걸음 물러나서 보면 이것은 제어 시스템(control system)이며, 모든 구성 요소에는 이름이 있습니다:
- **모델(The model)**은 플랜트(plant)입니다 — 강력하고 비결정론적(non-deterministic)이며, 당신이 제어하려는 대상입니다.
- 명세(The spec) (지시 사항 + 우선순위가 높은 엔벨로프)는 설정값(setpoint)입니다.
- **하네스(The harness)**는 컨트롤러(controller)입니다.
- **평가(The evals)**는 피드백(feedback)입니다.
이것이 완전히 요소 분해된 하네스 엔지니어링(harness engineering)입니다.
베팅
다음은 "하네스에 베팅하라"와 "하네스는 녹아내린다"라는 두 문장이 모두 참이기 때문에, 이 둘을 화해시키는 부분입니다.
모든 개별적인 하네스(harness)는 녹아내립니다. 각 하네스에는 모델의 그림자가 구워져 있습니다. 즉, 모델의 구체적인 실패 모드(failure modes), 현재의 신뢰성 등이 포함되어 있으며, 곡선(curve)이 이동함에 따라 하네스는 다시 작성됩니다. 모델이 신뢰할 수 있게 됨에 따라 가드레일(guardrails)은 줄어들고, 실패 모드가 변화함에 따라 다른 가드레일이 필요해집니다. 결과물(artifact)은 일회용입니다.
하지만 _규율(discipline)_은 녹아내리지 않습니다. 자율적 추론기(autonomous reasoner)가 무엇을 할 수 있는지 항상 명시해야 하며, 그것이 그 범위 내에 머물렀음을 증명해야 하는 무언가가 반드시 존재해야 합니다. 그리고 그 무언가는 모델 외부에 존재하며, 바로 그렇기 때문에 그 어떤 모델 생성(model generation)도 이를 흡수할 수 없습니다.
하네스는 녹아내립니다. 하네스 엔지니어링(Harness engineering)은 녹아내리지 않습니다.
따라서 베팅은 "루프(loop)를 구축하는 것"이 아닙니다. 모델은 매 분기마다 루프의 더 많은 부분을 배포합니다. 베팅은 이것입니다. 에이전트(agent)가 무엇을 원하도록 허용할지를 결정하고, 그것이 그 범위 내에 머물렀음을 증명할 수 있는 사람이 되는 것입니다. 메커니즘(Mechanism)은 범용화(commoditize)되지만, 명세(Specification)와 검증(verification)은 그렇지 않습니다.
그리고 그 표면은 점점 더 넓어지고 있습니다. 소프트웨어의 소비자 자체가 변하고 있기 때문입니다. 여러분의 사용자들 또한 에이전트가 되어가고 있습니다. 이제 자율적 추론기에게 노출되는 모든 시스템은 에이전트를 위해 구축된 인터페이스(face)를 필요로 합니다. 그 인터페이스가 어디에 위치하든, 그것이 바로 하네스 작업입니다.
모델 측면은 우리 모두에게 무료로 제공되고 있으며, 매 분기마다 더 좋아지고 있습니다. 여러분의 에이전트 — 즉, 여러분의 제품이자 역할 — 가 더 좋아질 것인가의 문제는 여러분이 엔지니어링하는 절반, 즉 여러분이 명시하는 판단(judgment)과 그것이 진실임을 유지하는 검증(verification)에 관한 문제입니다.
그곳이 바로 제가 베팅을 걸 곳입니다. 다만 그곳에는 정해진 주소가 없을 뿐입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기