🔥 Harness Engineering: 모델보다 그 모델을 끼우는 틀이 가른다

#AI#에이전트#Claude Code#harness#LLM
1480자
17분

Agent harness illustration: model surrounded by scaffolding panels

"If you're not the model, you're the harness."

Vivek Trivedy(Viv)의 이 한 줄이 지난 한 달 코딩 에이전트 글들의 좌표축을 바꿨다. 나는 처음 이 문장을 봤을 때 잠깐 멈췄다. 두 해 동안 우리는 어떤 모델이 더 똑똑한지를 두고 다퉜는데, 그게 사실 절반짜리 토론이었다는 얘기다. 모델은 돌아가는 에이전트의 한 입력일 뿐이고, 나머지 절반은 모델 바깥에 있는 harness(프롬프트, 도구, 컨텍스트 정책, hook, 샌드박스, 서브에이전트, 피드백 루프, 복구 경로)라는 것.

Addy Osmani의 정리 글을 읽으면서, 내 워크플로우에서 같은 패턴을 여러 번 봤다는 걸 깨달았다. 새 모델 릴리스에 흥분하기보다, 어제 돌린 에이전트가 멈춘 곳에 hook 한 줄 더 추가하는 일이 실제로 효과가 컸다. 이 흐름이 이제 이름을 갖게 됐다는 신호로 읽었다.

Harness, 정확히 뭔데

Viv의 한 줄 정의가 거의 다 한다. 에이전트 = 모델 + harness. 모델이 아니면 harness다. 다른 말로, 코드와 설정과 실행 로직 중에서 모델 가중치 자체를 뺀 모든 것이 harness다. 원시 모델은 에이전트가 아니다. harness가 상태(state), 도구 실행, 피드백 루프, 강제 가능한 제약을 얹는 순간 비로소 에이전트가 된다.

에이전트 = 모델 + harness 등식 다이어그램

구체적으로 harness 안에는 이런 것들이 들어간다.

  • 시스템 프롬프트, CLAUDE.md, AGENTS.md, 스킬 파일, 서브에이전트 프롬프트
  • 도구 / 스킬 / MCP 서버 (각각의 이름과 설명까지 다 포함)
  • 묶여 있는 인프라 (파일 시스템, 샌드박스, 헤드리스 브라우저)
  • 오케스트레이션 (서브에이전트 spawning, handoff, 모델 라우팅)
  • 결정론적 실행을 위한 hook과 미들웨어 (compaction, continuation, lint)
  • 관측: 로그, 트레이스, 비용, 지연시간 계측

Anatomy of an agent harness — model surrounded by context injection, control flow, action, persistence, observation
출처: Agent Harness Engineering — Addy Osmani

Simon Willison은 루프 부분을 한 문장으로 압축한다. "에이전트는 도구를 루프 안에서 돌려 목표에 도달하는 시스템." 기술은 두 곳에 있다. 어떤 도구를 줄 것인가, 그리고 루프를 어떻게 짤 것인가. 그 둘을 잘 짜면 끝이다.

표면적이 넓다고 느꼈다면 맞다. 그런데 그 표면적은 모델 제공자의 것이 아니라 내 것이다. Claude Code, Cursor, Codex, Aider, Cline. 이건 전부 harness다. 밑에 깔린 모델이 같을 때도 있는데, 내가 체감하는 행동은 거의 전적으로 harness가 결정한다.

Top 30이 Top 5가 됐다

이게 단순한 framing 놀음이라면 반박하기 쉽다. 그래서 데이터가 중요한데, Viv의 LangChain 글HumanLayer의 글 양쪽에서 같은 숫자가 나온다. Terminal Bench 2.0(89개 task로 에이전트가 실제 터미널 환경에서 일을 처리할 수 있는지 보는 벤치마크)에서, Claude Code 안에서 돌린 Claude Opus 4.6과 같은 모델을 커스텀 harness에 끼워 돌린 결과의 점수 차가 컸다. Viv 팀은 단지 harness만 갈아 끼워서 자기 코딩 에이전트를 Top 30에서 Top 5로 올렸다.

내가 흥미로웠던 건 왜 이게 가능한지다. 오늘날의 에이전트 모델들은 자기를 학습할 때 사용한 harness에 함께 묶여서(post-trained) 나온다. 그러니까 Opus 4.6을 Claude Code에 끼우면 미세하게 더 잘 돈다. 그런데 다른 harness(우리 코드베이스에 맞춘 도구, 더 빡빡한 프롬프트, 더 날카로운 back-pressure)에 옮기면, 원래 harness가 바닥에 깔아두고 안 쓰던 능력이 풀려 나온다는 얘기다.

HumanLayer는 같은 사실을 다른 방향에서 말한다. "it's not a model problem. It's a configuration problem." 에이전트가 멍청한 짓을 하면 우리는 모델을 탓하고 다음 버전을 기다리자는 결론으로 도망가곤 한다. harness 엔지니어링은 그 default를 거부한다. 실패는 거의 항상 읽힌다. 에이전트가 컨벤션을 몰랐다면 AGENTS.md에 한 줄 추가하면 된다. 위험한 명령을 실행했다면 hook으로 막으면 된다. 40단계짜리 작업에서 길을 잃었다면 planner와 executor로 쪼개면 된다. 망가진 코드를 완료했다고 우긴다면 typecheck back-pressure를 루프로 다시 흘려 넣으면 된다.

오늘 모델이 할 수 있는 것과 우리가 그 모델로 실제 보는 것 사이의 갭은, 대부분 harness 갭이다.

실수 하나가 한 줄이 된다

좋은 harness는 한 번에 설계되는 게 아니라 쌓인다. Addy가 이걸 ratchet이라고 부르는데, 적절한 표현이라고 본다. 에이전트의 실수를 일회성 사건이 아니라 영구 신호로 받는 습관이 핵심이다.

내 경우를 풀어 쓰면 이렇다. 어떤 PR에 주석 처리된 테스트(// it.skip(...))가 섞여 있는데 내가 별생각 없이 머지했다고 치자. 이건 입력이다. 다음 버전의 AGENTS.md에는 "테스트는 주석 처리하지 말 것. 지우거나 고쳐라." 한 줄이 추가된다. 다음 버전의 pre-commit hook은 diff에서 .skip(xit(를 grep해서 막는다. 다음 버전의 reviewer 서브에이전트는 주석 처리된 테스트를 blocker로 표시한다. 한 번의 사고가 세 곳에 흔적을 남긴다.

원칙은 단순하다. 실제 실패를 본 곳에만 제약을 추가한다. 그리고 모델이 더 똑똑해져서 그 제약이 군더더기가 될 때만 제거한다. 좋은 AGENTS.md의 모든 줄은 어떤 사고로부터 왔는지 추적 가능해야 한다. HumanLayer는 자기네 AGENTS.md를 60줄 이하로 유지한다고 한다. 한 줄 한 줄이 모델의 주의력 자원을 두고 경쟁하기 때문에, 룰이 많아질수록 한 줄의 무게는 작아진다. 조종사 체크리스트지 스타일 가이드가 아니다.

각 harness 컴포넌트는 모델이 혼자 못하는 행동에서 도출된다 — 행동에서 컴포넌트로의 매핑
출처: Agent Harness Engineering — Addy Osmani

이 자체가 왜 harness 엔지니어링이 framework가 아니라 discipline인지를 보여준다. 우리 코드베이스에 맞는 harness는 우리 실패 이력의 모양에 따라 빚어진다. 다운로드해서 쓰는 게 아니다.

줄어들지 않고 옮겨간다

모델이 좋아지면 harness가 사라질까. 직관적으로는 그럴 것 같다. 모델이 알아서 plan을 짜면 planner가 필요 없고, 긴 시간에 걸친 작업에서 일관성이 있으면 컨텍스트 reset도 필요 없을 테니까. 실제로 그런 일이 일어나기는 한다. Anthropic의 harness 설계 글이 던지는 가장 통렬한 한 줄은 그래서 무거웠다.

"Every component in a harness encodes an assumption about what the model can't do on its own."

모델이 못 하던 한 가지를 새 버전이 해내면, 그 가정 위에 서 있던 보조 코드 한 조각은 죽는다. Anthropic이 든 예가 정확히 그 모양이다. Sonnet 4.5는 자기 컨텍스트 한도가 가까워지면 일을 조기 마감하는 "context anxiety" 패턴이 있었는데, Opus 4.5에서 그 패턴이 사실상 사라져서 마감 불안 mitigation 용으로 짜둔 코드는 죽은 코드가 됐다. 빼면 된다.

그런데 동시에 천장이 같이 올라간다. 전에는 시도조차 못 하던 task가 사정거리에 들어오고, 그것 자체의 새 실패 모드가 생긴다. 며칠 단위 메모리 정책, 세 명의 전문 에이전트를 조율하는 multi-agent 구성, 생성된 UI의 디자인 품질을 평가하는 평가자 에이전트. 이런 게 새로 필요해진다. Anthropic 글의 결론은 정확히 이거다. harness는 줄어들지 않고 옮겨간다. 가정이 바뀌면 그 가정을 인코딩하던 코드도 바뀐다.

모델-harness 공동 학습 루프 — primitive 발견 → 표준화 → 학습 → 다음 모델 → 반복
출처: Agent Harness Engineering — Addy Osmani

여기에 하나 더 얹히는 게 모델-harness 공동 학습 루프다. 오늘날 에이전트 제품들은 harness를 같이 끼워서 post-train한다. 그래서 모델은 harness 설계자가 잘했으면 좋겠다고 가정한 행동(파일 시스템 조작, bash, planning, 서브에이전트 dispatch)에서 점점 더 잘하게 된다. 진짜 일반 모델이라면 apply_patch를 쓰든 str_replace를 쓰든 상관없어야 하는데, 공동 학습은 미세한 overfitting을 만든다. 도구 하나의 로직을 바꿨더니 모델이 갑자기 이상하게 행동하는 경험이 그래서 나온다.

이게 harness는 한 번 세팅하는 config 파일이 아니라 살아 있는 시스템이라는 얘기와 같다. 가장 좋은 harness는 모델이 학습된 그 harness가 아니라 내 task에 맞춰 설계된 harness라는 얘기와도 같다. Viv 팀의 Top 30 → Top 5 점프가 그 증거다.

Harness-as-a-Service라는 이름

Viv가 또 하나 던진 HaaS 라는 framing은 시장 흐름을 한 단어로 잡는다. 우리는 LLM API(완성을 돌려주는) 위에 짓던 시대에서 harness API(런타임을 돌려주는) 위에 짓는 시대로 넘어가고 있다. Claude Agent SDK, Codex SDK, OpenAI Agents SDK. 셋이 같은 방향을 가리킨다. 루프, 도구, 컨텍스트 관리, hook, 샌드박스 기본기가 박스 채로 나오고, 우리는 그 위에 도메인 특화 프롬프트와 도구 설계에 노력을 더 쓴다.

이게 "skill issue" 관점을 실용적으로 만드는 부분이다. 매번 에이전트를 처음부터 다시 짜는 게 아니라, 이미 잘 분해된 설정 표면을 튜닝하는 일이 된다. Viv의 표현이 마음에 들었다. "good agent building is an exercise in iteration. You can't do iterations if you don't have a v0.1."

상위 코딩 에이전트들(Claude Code, Cursor, Codex, Aider, Cline)을 나란히 놓고 보면, 그들은 밑에 깔린 모델보다 서로를 더 닮았다. 모델은 다르다. harness 패턴은 수렴하고 있다. Fareed Khan이 Claude Code 아키텍처를 layer로 분해한 글에서, 위에서 짚은 컨셉 — 컨텍스트 주입, 루프 상태, 위험 동작 hook, 서브에이전트 컨텍스트 방화벽, 도구 dispatch — 가운데 상당수가 계층 다이어그램 위에 그대로 올라가 있다.

Claude Code 아키텍처를 input/knowledge/integration/execution/output/observability/multi-agent layer로 분해한 다이어그램
출처: Agent Harness Engineering — Addy Osmani (Khan의 도식을 인용)

어쩌면 industry가 천천히 생성 모델을 ship 가능한 무언가로 바꾸는 데 필요한 핵심 보조 구조를 찾아내는 중인 것 같다.

그래서 나는 어디를 봐야 하나

Anthropic이 한 줄로 정리해 둔 게 가장 깔끔하다. 모든 harness 컴포넌트는 모델이 혼자서는 못 하는 무언가에 대한 가정을 인코딩한다는 한 줄. 모델이 한 가지를 새로 해내면 그 가정 위에 서 있던 코드는 죽고, 새 천장에 맞춰 새 코드가 생긴다. 이게 Birgitta Böckeler가 쓴 것처럼 컴파일러에 가까운 무언가가 될지는 솔직히 아직 확신이 없다.

그래도 한 가지는 분명해졌다. 모델 이름 비교에서 빠져나와 내 harness가 어디서 새는지 들여다보는 게, 지금 더 효과 큰 일이라는 것. 다음에 내 에이전트가 멍청한 짓을 하면, 나는 더 이상 언제 다음 모델 나오나를 묻지 않을 것 같다. 대신 이걸 hook으로 어떻게 잡지를 먼저 묻게 됐다.

참고 자료

YouTube 영상

채널 보기
우리가 매일 쓰는 맞춤법 검사기와 라우터 속에 숨겨진 알고리즘은? | Trie 자료구조 이야기
Trie 자료구조 파이썬 구현: Search와 Starts With 연산 | Trie 자료구조 이야기
숫자 하나가 AI 모델의 운명을 바꾼다? | 선형대수학
벡터의 정의와 덧셈 연산 | 선형대수학
투영과 예측, 그리고 선형 결합 | 선형대수학
트라이(Trie) 자료구조: 파이썬으로 삽입(Insert) 연산 구현하기 | Trie 자료구조 이야기
AI는 왜 수백 차원의 벡터를 사용할까? 고차원 공간과 행렬 | 선형대수학
인공지능은 세상을 어떻게 숫자로 읽는가? - 이미지, 소리 그리고 텍스트가 행렬이 되는 원리 | 선형대수학