[AI] 에이전트 하네스란 무엇인가(단일 에이전트 대 멀티 에이전트 구조)
하네스란?
같은 프롬프트의 결과물이 요청마다 다르다면, 매번 다시 결정되고 있다는 뜻이다. 어떤 순서로 문서를 쓸지, 코드를 짜기 전에 테스트를 먼저 쓸지, 리뷰를 누가 어떤 등급으로 판정할지, 위험한 명령을 실행 전에 막을지, 이런 결정을 세션마다 모델이 즉흥적으로 내리게 두면 품질이 그 세션의 모델 컨디션에 종속된다.
모델은 추론을 수행하는 엔진이고, 런타임은 그 모델을 호출하고 도구를 실행하는 CLI(Command Line Interface, 명령줄 인터페이스)다. 하네스는 이 둘 사이에서 무엇을 어떤 순서로, 누가, 어떤 기준으로 할지를 정의한 규칙·페르소나·절차·그래프의 묶음이다. 하네스는 어느 때마다 요청을 해도 균일한 결과물이 나오도록 하게 한다.
| 용어 | 의미 |
|---|---|
| 모델(model) | 추론을 수행하는 엔진. opus, sonnet, gpt-5.6-sol 등 |
| 런타임(runtime) | 모델을 호출하고 도구를 실행하는 CLI. claude-code, codex 등 |
| 하네스(harness) | 무엇을 어떤 순서로 할지 정의한 규칙·페르소나·절차·그래프의 묶음 |
| 역할(role) | 하네스가 구체 에이전트·런타임·모델을 참조하지 않고 참조하는 추상 단위 |
| 등급(tier) | 역할 안에서의 성능 등급. T1 설계 판단, T2 규격 산출물, T3 기계적 변환 |
| SSOT | Single Source Of Truth, 단일 진실 원천. 정의가 원본으로 존재하는 한 곳 |
| 어댑터(adapter) | SSOT 정의를 특정 런타임의 설정 포맷으로 투영하는 변환기 |
| 게이트(gate) | 다음 단계로 넘어가기 전 막아 세우는 지점. 차단, 확인, 권고 세 등급 |
하네스의 구조
모델이 능력을 제공하고, 하네스가 그 능력을 실제 작업으로 바꿀 때의 신뢰성을 결정한다. 그리고 구조의 중심에는 언제나 에이전트 루프가 있다. 추론으로 다음 행동을 정하고, 도구를 호출하고, 결과를 관찰해 다시 추론으로 돌아가는 순환이다. 루프가 없으면 도구를 쥔 프롬프트일 뿐 에이전트가 아니다.
루프를 둘러싸는 층은 네 갈래로 수렴한다. 컨텍스트는 추론 단계에 무엇을 넣을지 정한다. 시스템 프롬프트, AGENTS.md, 스킬 메타데이터, 메모리와 세션 상태, 컨텍스트 압축이 여기 속한다. 능력은 행동 단계가 실제로 쓸 수 있는 것을 정한다. 도구 레지스트리, MCP 서버, 스킬 본문, 서브에이전트다. 거버넌스는 행동이 실행되기 전에 통과해야 하는 관문이다. 권한과 승인, 샌드박스, 훅이 여기 있다. 관측은 루프 밖에서 무엇이 실행됐는지 남긴다. 트레이스, 평가, 실행 이력이다. 모델과 환경은 하네스 밖에 있다. 추론 단계가 모델을 호출하고, 능력 단계가 파일시스템과 셸과 네트워크를 읽고 쓴다.
참조 구조를 이 하네스에 대응시키면 아홉 층이 된다
~/.harness의 디렉토리를 위 참조 구조에 대응시키면 아홉 개 층으로 갈라진다. 이 아홉 갈래는 표준 분류가 아니라 참조 구조를 이 구현에 맞춰 쪼갠 결과다.
| 참조 구조 층 | 이 하네스의 대응 |
|---|---|
| 컨텍스트 | 규칙(rules/ 20개), 페르소나(agents/ 18개) |
| 능력 | 절차(skills/ 13개), 도구 연결(mcp/) |
| 거버넌스 | 강제(hooks/ 14개), 계약(contracts/ 3개) |
| 관측 | 관측(runs/state.json) |
| 참조 구조가 얇게 다루는 층 | 그래프(orchestration/ 9노드), 라우팅(roles.json) |
단일 에이전트 LLM 하네스 (~/.claude)
단일 에이전트 하네스는 에이전트 하나가 전 단계를 처리하는 구조다. 정의의 소유자가 런타임 자신이다. Claude Code라면 ~/.claude 디렉토리 하나가 규칙·페르소나·절차·훅·설정을 전부 담고, 그 CLI가 실행하는 순간 그대로 해석한다.
| 표면 | 담는 것 |
|---|---|
CLAUDE.md | 항상 로드되는 지침 |
agents/ | 페르소나 |
skills/ | 절차 |
hooks/ + settings.json | 강제 게이트와 권한 |
모델 선택이 정의에 붙는다
페르소나 파일의 frontmatter에 model:을 적는 방식이 대표적이다. 그 순간 파일 하나가 무엇을 하는 역할인가와 어느 모델로 돌릴 것인가를 동시에 갖는다.
# 모델이 정의에 붙은 형태
---
name: code-reviewer
description: 코드 리뷰어
model: opus
---# 모델을 분리한 형태 (라우팅은 외부 파일이 결정한다)
---
name: code-reviewer
description: 코드 리뷰어
---라우팅을 바꾸려면 페르소나 파일을 전부 열어 고쳐야 한다. 이 필드는 그 CLI 밖에서 의미가 없다. 다른 런타임으로 같은 페르소나를 옮기면 그 런타임이 이해하지 못하게 된다.
작성과 검수가 같은 모델이다
에이전트가 하나면 문서를 쓴 주체와 그것을 검수하는 주체가 같다. 같은 모델은 같은 맹점을 공유한다. 작성이 놓친 것은 검수도 놓친다. 리뷰 단계를 절차에 넣어도 독립적인 관측이 되지 못하고 형식만 남는다.
단일 구조의 장점
빌드 단계가 없어 페르소나 파일 하나를 고치면 다음 호출에 바로 반영된다. 문제가 생겼을 때 원인을 추적하는 경로가 짧다. 설정 하나, CLI 하나, 소스 하나뿐이라 확인할 층이 적다. 그 CLI가 제공하는 플러그인·팀·태스크 같은 고유 기능을 별도 변환 없이 그대로 쓸 수 있다. 그리고 무엇보다 학습 곡선이 없다. role, tier, 계약 스키마 같은 추상 개념을 몰라도 파일 하나 고치는 것으로 끝난다.
멀티 에이전트 LLM 하네스 (~/.harness)
멀티 에이전트 하네스는 역할별로 에이전트를 나누고, 그 역할을 서로 다른 런타임과 성능 등급에 배정한다. 에이전트가 여러 개가 되는 순간 단일 구조에는 없던 세 가지가 필요해진다.
| 필요한 것 | 왜 필요한가 |
|---|---|
| 정의를 런타임 밖으로 | 여러 런타임이 같은 정의를 읽어야 한다 |
| 역할 라우팅 | 어느 역할을 어느 런타임과 등급으로 돌릴지를 정의에서 분리해야 한다 |
| 노드 간 계약 | 한 에이전트의 산출물이 다음 에이전트의 입력이라 형태를 강제해야 한다 |
아래는 이 세 가지를 갖춘 실제 구조 하나를 예시로 든 것이다. 이 글을 쓰는 환경의 ~/.harness이며, 배치가 정답이라는 뜻은 아니다.
~/.harness의 구조
~/.harness
├── HARNESS.md 진입점·우선순위
├── policy.json 권한·게이트 (런타임 중립)
├── capabilities.md 런타임 능력·강등 규칙
├── roles.json 역할 → 런타임·등급 배정
├── rules/ 규칙
├── agents/ 페르소나
├── skills/ 절차
├── commands/ 슬래시 커맨드
├── hooks/ 강제 스크립트
├── mcp/ 도구 연결 설정
├── contracts/ 노드 간 입출력 스키마
├── orchestration/ 절차를 엮은 그래프
├── adapters/ 런타임별 투영 빌더
├── bin/harness 빌드·검증·실행 CLI
├── evals/ 골든 태스크
└── runs/ 실행 상태·이력성격으로 묶으면 다섯 갈래다. 무엇을 어기면 반려인지를 정하는 규범(policy.json·capabilities.md·roles.json·rules/), 그 규범을 실행할 정의(agents/·skills/·commands/·hooks/·mcp/), 정의들 사이의 인터페이스인 계약과 그래프(contracts/·orchestration/), 그것을 투영하고 검증하는 실행 도구(adapters/·bin/harness), 그리고 실행 이력인 상태(runs/)다. 성격이 다른 것을 한 디렉토리에 섞으면 규칙을 고치려다 상태 파일을 건드리게 되므로 이렇게 갈라 둔다.
정의를 런타임 밖으로 옮긴다
정의(규칙·페르소나·절차·훅·도구 설정)는 SSOT 한 곳에만 두고, 런타임 홈 디렉토리에서는 심링크로 그것을 가리킨다. 반면 런타임마다 스키마가 다른 설정 파일(settings.json 같은 것)은 중립 표현에서 빌드해 생성한다.
| 정의 | 산출물 |
|---|---|
| 규칙·페르소나·절차·훅·도구 설정 | 심링크로 공유. 포맷이 호환되므로 변환이 필요 없다 |
| 런타임별 설정 파일 | 빌드로 생성. 런타임마다 스키마가 달라 변환이 필요하다 |
이 분리가 이식성을 만든다. 런타임을 하나 더 붙이려면 그 런타임용 어댑터 하나를 쓰면 되고, 규칙을 고치면 모든 런타임에 한 번에 전파된다.
역할을 런타임과 등급에 배정한다
역할은 무엇을 하는 단위인지만 정하고, 어느 런타임의 어느 등급으로 돌릴지는 별도 파일이 단독으로 결정한다. 절차와 그래프는 역할 이름만 참조하고 구체 모델을 하드코딩하지 않는다. 등급은 판단의 무게로 나눈다.
| 등급 | 기준 |
|---|---|
| T1 | 설계 판단이나 리뷰 판정. 틀리면 하위 노드 전부가 오염된다 |
| T2 | 규격이 정해진 산출물 생성. 틀려도 그 노드에서 잡힌다 |
| T3 | 기계적 변환. 컨벤션 문서가 답을 이미 갖고 있다 |
{
"runtimes": {
"codex": {
"tiers": {
"T1": { "model": "sol", "effort": "high" },
"T2": { "model": "terra", "effort": "medium" },
"T3": { "model": "luna", "effort": "low" }
}
}
}
}라우팅을 파일 하나로 외부화하면 프로파일을 갈아 끼우는 것으로 배정 전체를 바꿀 수 있다. 페르소나 파일에 모델을 다시 적으면 그 값이 라우팅을 이겨 무력화되므로, 정적 검사가 그 필드의 재등장을 오류로 잡는다.
작성과 검수를 다른 런타임에 배정한다
이것이 멀티 구조의 가장 실질적인 이득이다. 작성 주체와 검수 주체를 다른 런타임에 두면 리뷰가 독립적인 관측이 된다.
| 산출물 성격 | 작성 | 검수 |
|---|---|---|
| 문서(PRD·설계·티켓) | codex | claude |
| 코드 | claude | codex |
자기 산출물을 자기가 검수하는 경로가 없다는 것이 이 배정의 규칙이다. 어느 쪽이 작성을 맡는지는 중요하지 않다. 작성과 검수가 갈라져 있다는 사실이 중요하다.
그림의 파이프라인에는 사용자 승인 게이트가 두 곳 있고, 검수 노드가 반려하면 되돌아갈 지점과 재시도 횟수가 그래프에 선언돼 있다. 반려됐을 때 어디까지 되돌릴지를 사람이 기억하는 것이 아니라 그래프가 정한다는 점이 핵심이다.
한계도 있다
정적 검사가 잡는 것은 정의가 문법을 통과하는가이지 그 값이 사실인가가 아니다. 존재하지 않는 모델 이름을 적어 둬도 형식이 맞으면 통과한다. 실제로 호출해 보기 전까지는 모른다.
런타임의 능력을 확인하지 않고 전제하는 것도 위험하다. 어떤 런타임이 서브에이전트를 열 수 있는지, 훅으로 도구 호출을 실제로 차단할 수 있는지는 런타임마다 다르다. 확인되지 않은 능력을 전제한 그래프는 실행 시점에 조용히 실패한다. 그래서 능력을 원자 단위로 선언하고 부족하면 중단이 아니라 강등하는 경로를 미리 정해 둬야 한다.
단일 에이전트 하네스 대 멀티 에이전트 하네스
| 단일 에이전트 하네스 | 멀티 에이전트 하네스 | |
|---|---|---|
| 정의의 소유자 | 런타임 자신 | 런타임 밖의 SSOT |
| 모델 배정 | 페르소나 파일에 하드코딩 | 라우팅 파일로 외부화 |
| 교차 검증 | 자기 산출물을 자기가 검수 | 작성 런타임과 검수 런타임 교차 |
| 능력 전제 | 암묵적 전제 | 능력 단위로 선언, 부족하면 강등 |
| 검증 방식 | 사람이 읽고 판단 | 계약 스키마와 정적 검사 |
| 상태 관리 | 세션 컨텍스트 | 실행 상태 파일 |
| 비용 상한 | 무제한 가정 | 사용량 임계 도달 시 전환 질의 |
| 이식성 | 낮음, 런타임을 바꾸면 전면 재작성 | 어댑터 하나 작성으로 확장 |
| 구축·유지 비용 | 거의 0, 파일 편집만 | 빌드·검사·계약 검증기 유지 필요 |
단일 에이전트 하네스라고 해서 병렬로 작업을 수행 못하는 것도, 그래프 엔지니어링이 불가능한 것도 아니다. 클로드를 예시로 본다면, 에이전트 팀을 구성하고 각 에이전트 노드마다 페르소나를 부여하고 어떤 모델, effort를 줄지를 커스텀하게 부여할 수 있다. 또한 dag를 동적으로 구성하고 병렬로 작업을 하는 것 또한 가능하다. 그리고 특정 노드의 작업이 종료되면, 다른 에이전트 노드를 호출하여 작업에 대한 리뷰 또한 가능하다. 이런 면에서 단일 llm 이라고 안좋은 점만 있는 것이 아니다.
멀티 에이전트를 동작시키는 하네스는 llm 에이전트마다 장단점이 존재할 것인데, 그래프 노드에 장점들을 십분발휘하도록 배치하는 것 그리고 서로 다른 시선에서 교차검증하도록 하는 것이 장점이라고 생각한다.
멀티 에이전트 하네스를 위해 아키텍쳐를 추상화시키고, 검증하고, 신규 llm 에이전트를 붙일 수 있도록 adapter 계층을 만든다던지 등에 대한 운영적인 코스트가 꽤나 높다고 생각한다. (이들을 아우르는 프레임워크도 아직 없고, 어떤 llm을 오케스트레이터로 둘지도 애매하기 때문..) 이런 의미에서 가장 쉽게 접근할 수 있는 단일 에이전트 하네스도 좋은 선택지라고 생각한다.