fos-blog/study
01 / 홈02 / 카테고리03 / 시리즈
01 / 홈02 / 카테고리03 / 시리즈

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • agent 페이지로 이동
    • claude-code 페이지로 이동
    • harness 페이지로 이동
    • llm 페이지로 이동
    • ops 페이지로 이동
    • practice 페이지로 이동
  • algorithm 페이지로 이동
    • Alias Method: 가중치 랜덤 선택을 전처리 O(n), 추출 O(1)로 바꾸기
    • Welford's Online Algorithm: 값을 저장하지 않고 평균과 분산 계산하기
  • architecture 페이지로 이동
    • distributed-systems 페이지로 이동
    • domain 페이지로 이동
    • evolution 페이지로 이동
    • patterns 페이지로 이동
  • database 페이지로 이동
    • milvus 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • qdrant 페이지로 이동
    • redis 페이지로 이동
    • vespa 페이지로 이동
    • DB Connection Pool Saturation과 Thread Pool 격리
    • 커넥션 풀 크기는 얼마나 조정해야 할까?
    • MyBatis 기본기 — XML Mapper, resultMap, 동적 SQL, 운영 패턴 정리
    • MyBatis와 JPA/Hibernate 트레이드오프 — 레거시 백엔드를 다루는 시니어 관점
    • 한국어 형태소 분석기 Nori vs Lindera — OpenSearch에서 Milvus로 갈 때 어휘 검색은 유지되나
    • 정규화와 역정규화: 무결성과 조회 비용 사이의 선택
    • 벡터 DB 5종, 아키텍처는 어떻게 다른가
    • 벡터 DB 5종을 실제로 벤치마크했다 — 같은 recall에서 QPS는 얼마나 갈리나
    • 벡터 DB 어떻게 고를까 — OpenSearch · Milvus · Qdrant · Vespa · pgvector 비교
    • 벡터 DB를 실제로 도입한 사례 — 빅테크 프로덕션
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • Envoy Proxy
    • Graceful Shutdown
    • 종료 신호는 어디서 멈추는가
  • http 페이지로 이동
    • HTTP Connection Pool
    • HTTPS와 TLS: 핸드셰이크, 인증서, 종료 지점
  • java 페이지로 이동
    • concurrency 페이지로 이동
    • jdbc 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • testing 페이지로 이동
    • Java 동시성 락 정리 — 커머스 메뉴/프로모션 정책 캐시 갱신 관점
    • JVM 튜닝 실전: 메모리 구조부터 Virtual Threads, GC 튜닝, 프로파일링까지
    • 로그에 traceId 남기기 — MDC 부터 OpenTelemetry 까지
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node 백엔드 운영 패턴 — Streams 백프레셔, pipe/pipeline, 멱등성 vs 분산 락
    • Node.js
    • `setImmediate()`
  • kafka 페이지로 이동
    • Kafka 클러스터 아키텍처: Broker, KRaft Controller와 복제
    • Kafka 기본 개념: Topic, Partition, Offset과 Segment
    • Kafka 실전 설계: 파티션 전략, 컨슈머 그룹, 전달 보장, 재시도, 순서 보장 트레이드오프
    • Kafka 파티션·리밸런스·컨슈머 지연 운영
    • Kafka를 로그로 이해하기: 복제, 재처리, 상태와 데이터 통합
    • Spring Kafka 컨슈머 오프셋 커밋과 트랜잭션 정렬: AckMode, manual ack, 멱등 처리
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • SSH forced command 로 배포 키가 실행할 수 있는 명령 제한하기
  • mlops 페이지로 이동
    • llm-serving 페이지로 이동
    • model-router 페이지로 이동
    • Python CUDA 버전 생태계 — nvidia-smi, nvcc, pip, conda가 다 다른 버전을 말하는 이유
    • GPU 컨테이너의 CUDA 버전 호환성 — nvidia-smi부터 이미지 다이어트까지
    • Kubernetes GPU 노드에서 /run tmpfs가 꽉 차서 Pod가 안 뜰 때
    • GPU·CUDA·MPS 기초 — 자바 백엔드 개발자가 처음 만나는 그림
    • Multi-process GPU 워크로드 — 자바 ThreadPool 사용자가 만나는 모델 차이
    • ML 서비스 성능 분석 워크플로 — 자바 백엔드 트러블슈팅과 다른 점
    • 한 GPU 를 여러 프로세스가 나눠 쓰기 — Time-Slicing 과 MPS
  • network 페이지로 이동
    • Connection reset by peer는 누가 보낸 걸까 — 리버스 프록시 홉마다 TCP 연결은 따로 논다
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • observability 페이지로 이동
    • Observability 입문: 시니어 백엔드가 장애를 탐지하고 대응하는 방식
    • K8s 위 Spring Boot 앱의 메트릭 수집
  • python 페이지로 이동
    • Python async/await — CompletableFuture·Reactor 와 다른 점, 그리고 blocking I/O 함정
    • Python 의존성 관리 — Java Maven/Gradle 사용자가 만나는 첫 충격
    • FastAPI 기초 — Spring Boot 사용자가 빠르게 익히는 법
    • Java 개발자를 위한 Python 심화 — OOP·데코레이터·컨텍스트 매니저
    • PyTorch 기초 — 텐서, 디바이스, 그리고 모델 로딩이 무거운 이유
    • Java 개발자를 위한 Python 문법 핵심
    • ThreadLocal 에서 contextvars 로 — Python 의 요청 컨텍스트 전파
    • OCR 동작 원리 — Layout · Text · Post-process 3단계
    • Python 서버의 RSS가 줄지 않는 이유: CPython, gc.collect(), malloc_trim
  • rabbitmq 페이지로 이동
    • RabbitMQ Basics — 실전 백엔드 관점에서 정리하는 메시지 브로커 기본기
    • RabbitMQ vs Kafka — 백엔드 메시징 선택 기준과 실전 운영 관점
  • resume 페이지로 이동
    • 경험 및 경력기술서
    • 김병태 경력기술서
    • 김병태 포트폴리오
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • sb-dev-team 페이지로 이동
    • the-future-company 페이지로 이동
  • thinking 페이지로 이동
    • 자기주도 학습: 질문에서 시작해 글로 검증하기
    • 좋은 일을 넘어 중요한 일을 하는 법
FOS-BLOG · FOOTERall systems normal·v0.1 · 2026.04.27·seoul, kr
Ffos-blog/study

개발 학습 기록을 정리하는 블로그입니다. 공부하면서 기록하고, 기록하면서 다시 배웁니다.

visitors
01site
  • Home↗
  • Posts↗
  • Categories↗
  • Glossary↗
  • About↗
02policy
  • 소개/about
  • 개인정보처리방침/privacy
  • 연락처/contact
03categories
  • AI↗
  • Algorithm↗
  • DB↗
  • DevOps↗
  • Java/Spring↗
  • JS/TS↗
  • React↗
  • Next.js↗
  • System↗
04connect
  • GitHub@jon890↗
  • Source repositoryjon890/fos-study↗
  • RSS feed/rss.xml↗
  • Newsletter매주 1 회 · 한 편의 글→
© 2026 FOS Study. All posts MIT-licensed.
built with·Next.js·Tailwind v4·Geist·Pretendard·oklch
fos-blog/AI/하네스 엔지니어링 — 오래 실행되는 AI 에…
ai

하네스 엔지니어링 — 오래 실행되는 AI 에이전트를 위한 설계

AI 에이전트가 복잡한 작업을 수행하게 하면서 한 가지 사실을 깨달았다. 프롬프트를 잘 짜는 것만으로는 한계가 있다. 에이전트가 몇 시간 동안 돌아가는 작업을 수행할 때 생기는 문제는 모델이 멍청해서가 아니라, 에이전트를 둘러싼 구조(harness)가 없기 때문이다. 20252026년을 기점으로 "하네스 엔지니어링(Harness Engineering)"이라...

2026.09.08·14 min read·41 views

AI 에이전트가 복잡한 작업을 수행하게 하면서 한 가지 사실을 깨달았다. 프롬프트를 잘 짜는 것만으로는 한계가 있다. 에이전트가 몇 시간 동안 돌아가는 작업을 수행할 때 생기는 문제는 모델이 멍청해서가 아니라, 에이전트를 둘러싼 구조(harness)가 없기 때문이다.

2025~2026년을 기점으로 "하네스 엔지니어링(Harness Engineering)"이라는 개념이 AI 에이전트 개발에서 독립적인 분야로 자리를 잡고 있다. 이 글에서는 Anthropic 엔지니어링 블로그 글 두 편과 Martin Fowler의 시각을 중심으로 하네스가 무엇인지, 왜 필요한지, 실제로 어떻게 설계하는지 정리한다.


하네스(Harness)란 무엇인가

하네스는 AI 에이전트가 제대로 동작할 수 있도록 감싸는 실행 구조 다. 구체적으로는:

  • 제어(Control): 작업을 어떻게 분해하고 스케줄링할 것인가
  • 계약(Contracts): 어떤 산출물이 만들어져야 하며, 어떤 조건이 충족돼야 종료할 것인가
  • 상태(State): 여러 세션과 단계를 걸쳐 무엇을 유지할 것인가

프롬프트 엔지니어링이 "모델에게 무엇을 말할 것인가"에 관한 것이라면, 하네스 엔지니어링은 "모델이 어떤 환경에서 어떤 흐름으로 작동할 것인가"에 관한 것이다.

Martin Fowler는 이를 이렇게 정리한다. 하네스는 에이전트를 제약하고 신뢰할 수 있게 만드는 도구와 실천의 집합이다. 에이전트에게 무한한 자유를 주는 것이 아니라, 일관된 결과물을 내도록 안내하는 구조를 만드는 것이다.


장시간 실행 에이전트의 두 가지 실패 패턴

Anthropic의 엔지니어 Prithvi Rajasekaran이 장시간 에이전트 시스템을 만들면서 반복적으로 마주친 실패 패턴은 두 가지다.

컨텍스트 저하 (Context Degradation)

컨텍스트 윈도우가 채워지면서 모델이 일관성을 잃는다. 더 심각한 것은 컨텍스트 불안(Context Anxiety) — 모델이 컨텍스트 한계에 가까워진다고 느끼면 작업을 제대로 완수하지 못했음에도 "완료됐습니다"라고 선언해버린다.

실제로 Claude Sonnet 4.5를 테스트했을 때 컨텍스트 압축(compaction)만으로는 충분하지 않았고, 컨텍스트 리셋 이 하네스 설계의 핵심 요소가 됐다.

자기 평가 편향 (Self-Evaluation Bias)

에이전트에게 자신이 생성한 결과물을 평가하게 하면 품질이 낮아도 높게 평가하는 경향이 강하다. "자신이 만든 작업을 평가하라고 하면 에이전트는 자신 있게 그 작업을 칭찬하는 경향이 있다."

이 두 문제를 해결하는 핵심 통찰은 단순하다. 생성과 평가를 분리하라.


Anthropic 사례: 하네스 설계 실전

단계 1 — 프론트엔드 디자인 하네스

Rajasekaran은 프론트엔드 디자인 품질을 높이기 위해 GAN(생성적 적대 신경망) 구조에서 아이디어를 가져왔다. 생성자(Generator)와 평가자(Evaluator)를 분리한 것이다.

평가자는 네 가지 기준으로 결과물을 채점한다:

기준설명
디자인 품질시각적 일관성, 정체성이 있는가
독창성AI 기본값이 아닌 커스텀한 결정이 있는가
완성도기술 구현 수준이 충분한가
기능성실제로 사용할 수 있는가

평가자는 Playwright MCP를 통해 실제 렌더링된 페이지를 직접 조작해본 뒤 점수를 매겼다. 생성자는 평가자의 피드백을 받아 반복적으로 개선했다.

핵심 발견: 독립된 평가자를 회의적으로 튜닝하는 게, 생성자가 스스로 비판적이 되도록 만드는 것보다 훨씬 쉽다.

단계 2 — 풀스택 애플리케이션 하네스

더 복잡한 작업(풀스택 앱 개발)에는 3개 에이전트 구조가 등장한다.

plaintext
[Planner]
 └── 간단한 브리프를 상세 스펙으로 확장
 └── AI 기능 기회 식별
       ↓
[Generator]
 └── React + FastAPI + SQLite로 기능 구현
 └── 기능을 점진적으로 추가
       ↓
[Evaluator]
 └── Playwright로 기능 테스트
 └── 기준에 따라 점수 및 피드백 제공
 └── 다시 Generator로 피드백 전달

에이전트 간 통신은 구조화된 파일 을 통해 이뤄진다. Planner가 "스프린트 계약(sprint contract)"을 파일로 남기면, Generator는 이를 읽고 구현 전에 성공 조건을 미리 파악한다.

결과 비교

방식소요 시간비용품질
단일 에이전트20분$9핵심 기능 동작 안 함
풀 하네스6시간$200물리 엔진, 반응형 UI, AI 기능 모두 동작

비용이 22배 더 들었지만, 단일 에이전트로는 불가능했던 결과물이 나왔다.


장기 실행을 위한 하네스 — Initializer-Executor 패턴

에이전트가 여러 세션에 걸쳐 작업할 때의 핵심 문제는 세션 간 기억이 없다는 것 이다. 새 세션이 시작될 때마다 이전에 뭘 했는지 모른다. Anthropic의 두 번째 글은 이 문제를 다룬다.

비유: "교대 근무하는 엔지니어들. 새 엔지니어가 올 때마다 이전 교대의 기억이 없다."

Initializer Agent

첫 세션에서 딱 한 번 실행된다. 이후 모든 세션이 의존할 인프라를 만드는 것이 역할이다.

plaintext
Initializer가 만드는 것:
├── feature_list.json       # 전체 기능 목록 (초기에는 모두 "failing")
├── claude-progress.txt     # 에이전트 행동 기록
├── init.sh                 # 개발 환경을 한 번에 실행하는 스크립트
└── 초기 git commit         # 생성된 모든 것을 기록

feature_list.json에는 200개 이상의 기능이 "failing" 상태로 표시된다. 에이전트에게 이 리스트 없이 작업하면 "완료됐다"고 선언하기 너무 쉽기 때문이다. 그리고 엄격한 규칙이 들어간다: "테스트를 삭제하거나 수정하는 것은 허용되지 않는다. 이는 기능 누락이나 버그로 이어질 수 있다."

Coding Agent (Executor)

이후 모든 세션에서 실행된다. 각 세션 시작 시 정해진 절차를 따른다:

plaintext
1. pwd 실행 (작업 디렉토리 확인)
2. git log + progress 파일 읽기 (현재 상태 파악)
3. feature_list.json에서 우선순위 높은 미완성 기능 선택
4. init.sh 실행 (환경 세팅)
5. 기본 e2e 테스트 실행 (앱이 깨진 상태에서 시작하지 않도록)
6. 기능 하나 구현 → 커밋 → progress 업데이트

앱이 깨진 상태에서 새 기능을 추가하기 시작하는 것을 방지하는 게 핵심이다.

실패 패턴별 해결책

실패 패턴Initializer 해결책Coding Agent 해결책
너무 일찍 완료 선언feature_list.json 생성리스트를 읽고 하나씩 처리
버그 있는 코드 방치git + progress 노트검증 테스트로 시작
앱 실행 방법 모름init.sh 스크립트 작성세션 시작 시 init.sh 실행
기능 완료 조기 선언feature list scaffolde2e 테스트로 자기 검증

Martin Fowler가 본 하네스 엔지니어링의 세 축

Fowler는 OpenAI 팀의 사례를 바탕으로 하네스를 세 가지 범주로 정리한다.

컨텍스트 엔지니어링 (Context Engineering)

에이전트가 올바른 컨텍스트에 접근할 수 있도록 만드는 작업이다.

  • 코드베이스에 강화된 지식 베이스 를 내장 (curated docs, API 스펙, 예시 패턴)
  • 관찰 데이터(observability data), 네비게이션 도구를 동적으로 제공
  • 기술 문서를 코드베이스의 일부로 관리

아키텍처 제약 (Architectural Constraints)

에이전트가 의도된 설계 경계를 벗어나지 않도록 하는 구조적 가드레일이다.

  • 결정론적 커스텀 린터로 코딩 표준 강제
  • 설계 경계를 검증하는 구조 테스트
  • 의도된 토폴로지에서 벗어나는 패턴 모니터링

엔트로피 관리 (Entropy Management)

코드베이스가 시간이 지나면서 썩는 것을 방지한다.

  • 주기적으로 비일관성을 찾아내는 "가비지 컬렉션 에이전트"
  • 문서 위반 감지
  • 아키텍처 드리프트에 지속적으로 저항

핵심 원칙: "에이전트가 어려움을 겪을 때, 그것을 신호로 받아들인다. 무엇이 없는지를 — 도구, 가드레일, 문서 — 파악하고 저장소에 피드백한다."


실제로 하네스를 설계하는 방법

이론은 충분하다. 실제로 하네스를 만들 때 어떻게 접근하면 좋을지 정리한다.

Step 1: 작업을 세션 단위로 분해한다

장시간 작업은 단일 컨텍스트 안에서 완주하려 하지 말고, 명확한 체크포인트를 가진 독립 세션 으로 나눈다. 각 세션은:

  • 어떤 상태로 시작하는지 (입력 계약)
  • 무엇을 만들어야 끝나는지 (출력 계약)
  • 실패 시 어디서 재개하는지 (복구 경로)

를 명확히 해야 한다.

Step 2: 상태를 파일에 외부화한다

에이전트의 기억은 컨텍스트 윈도우뿐이다. 세션 간 유지할 것들은 반드시 파일 시스템(또는 DB)에 써둔다.

plaintext
project/
├── .agent/
│   ├── progress.md          # 완료된 것, 현재 상태
│   ├── feature_list.json    # 남은 작업 목록
│   ├── decisions.md         # 중요한 아키텍처 결정 기록
│   └── init.sh              # 환경 재현 스크립트

에이전트가 새 세션을 시작할 때 가장 먼저 이 파일들을 읽게 만든다.

Step 3: 생성자와 평가자를 분리한다

자기 평가 편향을 없애려면 평가를 독립 에이전트에게 맡겨야 한다. 평가자 에이전트는:

  • 다른 시스템 프롬프트를 가진다 ("skeptical reviewer" 페르소나)
  • 생성 과정을 보지 않는다 (결과물만 본다)
  • 구체적인 채점 기준을 가진다

평가자가 테스트 도구(Playwright, 단위 테스트 실행기)를 직접 사용할 수 있으면 더 좋다.

Step 4: 점진적으로 추가하고 항상 커밋한다

에이전트에게 기능을 한 번에 하나씩 구현하게 하고, 각 기능 완료 후 반드시 커밋하게 한다. 이유:

  • 잘못됐을 때 롤백 지점이 생긴다
  • git log가 progress 기록의 역할을 한다
  • 에이전트가 "현재 상태"를 git에서 파악할 수 있다

Step 5: 에이전트가 막히는 곳에서 하네스를 개선한다

하네스는 한 번 만들고 끝이 아니다. 에이전트가 같은 지점에서 반복적으로 실패한다면 그것은 하네스에 무언가가 빠졌다는 신호 다. 도구인지, 컨텍스트 정보인지, 가드레일인지 파악해서 하네스에 추가한다.


실제 사례 — 내 하네스 v1에서 v2로의 진화

이론을 쌓고 나면 자연스럽게 이런 질문이 생긴다. "내가 만든 것에는 하네스가 얼마나 있는가?" 그런데 더 중요한 질문은 "내 하네스는 어떻게 자라고 있는가"다. 하네스는 한 번 만들고 끝이 아니라 프로젝트 규모·기간·실패 경험에 따라 진화하는 구조다.

개인 프로젝트 두 개를 비교해봤다. v1은 CLI 도구(dooray-cli), v2는 웹툰 생성 Next.js 앱(webtoon-maker-v1)이다. 둘 다 내가 Claude Code와 함께 만들었고, 나중에 만든 쪽이 앞 프로젝트에서 얻은 실패 경험을 하네스에 명시적으로 녹여낸다.

v1 — 최소 하네스 (dooray-cli)

TypeScript CLI 하나짜리 프로젝트다. 하네스라고 부를 만한 것은 거의 전부 루트 CLAUDE.md 한 장에 담겨 있다.

plaintext
dooray-cli/
├── CLAUDE.md            # 60줄 — 스택, 디렉터리, 컨벤션, 주의사항
├── .claude/skills/      # plan-and-build, release 2개
├── skills/              # 공개 스킬 (다른 사용자용)
├── prompts/             # task-create.md 한 장
└── tasks/               # 3개 (flat 구조, phase 개념 없음)

이 단계에서 하네스의 역할은 단순하다. 에이전트가 "이 프로젝트에서 HTTP는 ky를 쓰고 에러는 DoorayCliError로 통일한다"는 규칙만 알면 된다. ADR도 없고, 아키텍처 레이어도 없고, 모델 라우팅도 없다. 프로젝트가 작고 기능 축적 속도가 빠르지 않으니 이 정도로 충분했다.

하지만 몇 달 지나면서 드러난 한계는 있었다.

  • 실패 경험이 쌓이지 않는다 — "이 라이브러리는 이렇게 쓰면 안 된다"는 교훈이 커밋 메시지로만 남고, 다음 세션의 에이전트는 같은 함정을 밟는다.
  • 규칙이 강제되지 않는다 — "상대경로 금지"가 CLAUDE.md 문장으로만 존재하면 에이전트는 잊어버린다.
  • phase가 비워져 있으면 에이전트가 컨텍스트를 폭식한다 — task가 "dooray-cli 구현" 한 덩어리면 세션당 범위가 너무 커진다.

v2 — 성숙한 하네스 (webtoon-maker-v1)

Next.js + Prisma + Gemini API 풀스택 앱이다. v1에서 겪은 한계가 그대로 v2 하네스의 설계 요구사항이 됐다.

1. 컨텍스트 문서를 용도별로 쪼갠다 — 상황별 컨텍스트 엔지니어링

v1은 CLAUDE.md 한 장에 모든 걸 썼다. v2는 루트 CLAUDE.md를 라우터로 만들고, 실제 사실 정보는 7개 문서로 분산한다.

plaintext
webtoon-maker-v1/
├── CLAUDE.md                      # 규칙과 라우팅만 — 사실은 docs/ 참조
└── docs/
    ├── prd.md                     # 새 기능 추가 전
    ├── adr.md                     # 기술 결정 시
    ├── data-schema.md             # API 연동/타입 정의 시
    ├── flow.md                    # UI/UX 수정 시
    ├── code-architecture.md       # 코드 작성 시 항상
    ├── ai-strategy.md             # AI 호출/프롬프트 수정 시
    └── collaboration.md           # 컴포넌트 신규/수정 시

CLAUDE.md 상단에는 "언제 어떤 문서를 읽어야 하는지" 테이블이 있다. 에이전트가 docs/adr.md(713줄) 같은 대형 문서를 통째로 읽지 않아도, 작업 종류에 따라 필요한 섹션만 집어 읽을 수 있다. 이게 v1에 없던 컨텍스트 라우팅이다.

2. 실패 패턴을 ADR로 축적 — 상황별 필수 참조

v1에서 놓쳤던 "실패 경험 축적"을 v2는 상황 ↔ ADR 매핑 테이블로 해결한다.

markdown
| 상황                                            | 필수 확인 ADR                              |
| ----------------------------------------------- | ------------------------------------------ |
| Base UI Select 사용                             | ADR-121 — items prop 없으면 id가 노출됨    |
| 인터랙티브 버튼 추가                            | ADR-122 — native <button> 금지             |
| Gemini LLM 호출 추가                            | ADR-043 + ADR-047 + ADR-072                |
| API route / Application layer 수정              | ADR-135 — 3-tier 경계 + ESLint 4종         |
| useEffect 작성                                  | ADR-115 — setState 금지, 파생 계산만       |

이건 Fowler가 말한 엔트로피 관리의 실제 구현이다. 같은 버그가 두 번 일어나면 ADR로 결정론적으로 기록하고, 에이전트는 해당 상황에 진입하기 전에 강제로 그 ADR을 읽는다. v1에는 "주의사항" 섹션에 bullet 몇 줄로 녹아 있었을 내용이, v2에서는 명시적 검색 인덱스로 올라왔다.

3. 아키텍처 제약을 ESLint로 강제 — 말에서 코드로

v1의 "상대경로 금지"는 문장이다. v2의 3-tier 경계는 ESLint 규칙이다.

레이어담당금지
actions/Controller — "use server", Zod 파싱비즈니스 로직, 트랜잭션
app/api/**/route.tsController — HTTP 파싱, SSE 래핑@/actions/*·@/lib/db/* 직접 import
lib/application/트랜잭션 경계, 2+ domain 조합next/server·next/navigation
lib/ai/모델 호출, 프롬프트 조립DB 접근, revalidate
lib/db/Prisma 쿼리, 순수 반환revalidate, 비즈니스 정책

에이전트가 경계를 넘어가면 pnpm run ci가 실패한다. 이게 Fowler의 아키텍처 제약이다. v1은 규칙을 "기억해 달라"고 부탁했고, v2는 규칙을 결정론적으로 실행한다.

4. task phase 원자화 — 컨텍스트 저하 대응

v1 task는 flat 3개였다. v2 task는 80개 이상이고, 각 plan은 여러 phase로 쪼개져 있다. 핵심 규칙은 다음과 같다.

  • 각 phase는 원자적 단일 책임
  • 작업 항목 5개 이하 엄수
  • 각 phase 프롬프트는 자기완결적(이전 대화 없이 독립 실행 가능)
  • task 파일 생성 즉시 git commit, 완료 즉시 git commit

이건 Anthropic 글의 Initializer-Executor 패턴과 정확히 같은 해결책이다. "교대 근무하는 엔지니어"에게 이전 교대의 기억이 없어도 git log와 phase 프롬프트만 읽으면 작업을 재개할 수 있다. v1에서 "task 하나가 너무 커서 세션 중간에 컨텍스트가 불안해지는" 경험이 v2의 phase 제한 규칙으로 환원됐다.

5. 모델 라우팅 — 비용까지 하네스로 본다

v2는 한 발 더 나간다. 같은 작업이라도 어떤 모델로 돌리느냐를 하네스가 지정한다.

markdown
- 논의·계획·docs 작성: main 세션 (opus 허용)
- task phase 실행: sonnet 기본 — rename, 리팩토링, 다중 파일 수정도 sonnet
- task phase에서 opus 사용 예외:
  - 새 아키텍처 설계가 phase 안에 있는 경우
  - 복잡 알고리즘 설계 (AI 파이프라인 신규 설계)
- 기계적 작업은 opus 금지

Rajasekaran이 "강력한 모델에서는 플래너가 불필요해진다"고 관찰한 것의 반대 면이다. 모델이 좋아져도 어떤 작업에 어떤 모델을 쓸지는 여전히 하네스가 결정해야 한다. v1에는 없던 축이다.

진화의 관찰

두 프로젝트를 Fowler의 세 축에 맞춰 놓으면 진화의 방향이 보인다.

축v1 (dooray-cli)v2 (webtoon-maker-v1)
컨텍스트 엔지니어링단일 CLAUDE.md7개 docs + 상황별 라우팅 테이블
아키텍처 제약문장으로만 명시3-tier + ESLint 경계 + pnpm run ci
엔트로피 관리없음 (커밋 메시지에만)ADR + 상황↔ADR 매핑 테이블
실행 단위flat task 3개phase 원자화 + 5개 제한 + 자기완결
모델 라우팅없음opus/sonnet 규칙 명시

핵심은 v2의 규칙들이 전부 v1에서 겪은 구체적 실패에 대응한다는 점이다. 하네스는 위에서 내려찍는 설계가 아니라 아래에서 쌓이는 것이다. 에이전트가 반복해서 막히는 지점이 곧 하네스에 다음으로 추가해야 할 컴포넌트다.

그리고 한 가지 더. v2는 이미 완성형이 아니다. 평가자(Evaluator)를 독립 에이전트로 돌리는 루프는 아직 없고, 엔트로피 관리를 자동화하는 "가비지 컬렉션 에이전트"도 없다. v3가 온다면 아마 이쪽에서 출발할 것이다.


gstack — 역할 기반 하네스의 실용 구현

Y Combinator CEO Garry Tan이 자신의 실제 Claude Code 개발 환경을 오픈소스로 공개한 것이 gstack이다.

"The model is commodity. The harness is moat."

모델 자체보다 모델을 감싸는 구조가 경쟁력이라는 하네스 엔지니어링의 핵심 명제를 그대로 실천한 프로젝트다.

아이디어: 만능 어시스턴트 대신 역할 조직

하나의 AI에게 모든 걸 맡기는 대신, 실제 엔지니어링 조직의 역할 구조를 하네스로 투영한다. Claude라는 동일한 모델이지만 각 커맨드마다 독립된 시스템 프롬프트를 가진 다른 "역할"로 동작한다.

plaintext
/plan-ceo-review      ← CEO 페르소나로 전략 검토
/plan-eng-review      ← 엔지니어링 매니저로 기술 계획 검토
/review               ← 코드 리뷰어
/qa                   ← QA 리드
/ship                 ← 릴리스 매니저

개발 사이클 전체를 커버한다: Think → Plan → Build → Review → Test → Ship → Retro

하네스 원칙과의 대응

gstack 커맨드하네스 원칙
/qa, /qa-only생성자-평가자 분리 — 만든 사람과 검증하는 사람을 분리
/plan-ceo-review, /plan-eng-review, /plan-design-review역할별 평가자 — 관점이 다른 독립 평가자
/learn상태 외부화 — 프로젝트별 패턴을 세션 간 누적
/guard, /careful아키텍처 제약 — 에이전트가 경계를 벗어나지 않도록 제어
/investigate컨텍스트 엔지니어링 — 작업 전 충분한 맥락 수집
/retro엔트로피 관리 — 반복 패턴 인식 및 개선

주목받는 이유

실제 검증된 워크플로우다. YC CEO가 자신의 일상 개발에 쓰는 설정을 그대로 공개했다. 한 CTO는 gstack의 코드 리뷰 기능이 팀이 발견하지 못한 XSS 취약점을 잡아냈다고 보고했다.

/learn 커맨드는 프로젝트별 패턴을 세션을 넘어 누적한다. 사용할수록 해당 코드베이스에 맞는 하네스로 진화한다. 하네스가 고정된 구조가 아니라 적응하는 구조가 될 수 있다는 걸 보여준다.

Google Gemini 환경으로 포팅한 파생 프로젝트까지 등장했다. 아이디어 자체가 모델에 종속되지 않는다는 뜻이다.

bash
# 설치
git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack
cd ~/.claude/skills/gstack && ./setup

모델이 좋아질수록 하네스는 어떻게 되는가

흥미로운 관찰이 있다. Rajasekaran은 Claude Opus 4.6으로 테스트하면서 하네스 컴포넌트를 하나씩 제거해봤다.

강력한 모델에서는:

  • 스프린트 분해(sprint decomposition)가 불필요해졌다
  • 플래너의 역할이 줄었다

하지만 평가자는 여전히 필요했다. 특히 작업 복잡도가 높아질수록 평가자의 가치가 오히려 커졌다.

결론: 하네스의 각 컴포넌트는 모델이 스스로 할 수 없다는 가정을 인코딩한다. 그 가정이 여전히 유효한지 주기적으로 검증해야 한다.


2025~2026의 흐름

LangChain은 모델 교체 없이 하네스 엔지니어링만으로 14퍼센트포인트 성능 향상을 이뤄냈다. Vercel은 도구 복잡도를 줄이는 하네스 개선으로 100% 정확도를 달성했다. 하네스 엔지니어링은 "프롬프트 엔지니어링"의 다음 단계로 자리잡고 있다.

프롬프트 엔지니어링 → 컨텍스트 엔지니어링 → 하네스 엔지니어링

에이전트를 만드는 것보다 에이전트를 둘러싼 시스템 을 설계하는 것이 점점 더 중요해지고 있다.


참고

  • Harness design for long-running application development (Anthropic Engineering)
  • Effective harnesses for long-running agents (Anthropic Engineering)
  • Harness Engineering (Martin Fowler)
  • Harness engineering: leveraging Codex in an agent-first world (OpenAI)
  • 2025 Was Agents. 2026 Is Agent Harnesses (Aakash Gupta / Medium)
  • Building AI Coding Agents for the Terminal (arXiv)
  • gstack — Garry Tan의 역할 기반 Claude Code 하네스 (GitHub)
on this page
  • 01하네스(Harness)란 무엇인가
  • 02장시간 실행 에이전트의 두 가지 실패 패턴
  • 컨텍스트 저하 (Context Degradation)
  • 자기 평가 편향 (Self-Evaluation Bias)
  • 03Anthropic 사례: 하네스 설계 실전
  • 단계 1 — 프론트엔드 디자인 하네스
  • 단계 2 — 풀스택 애플리케이션 하네스
  • 04장기 실행을 위한 하네스 — Initializer-Executor 패턴
  • Initializer Agent
  • Coding Agent (Executor)
  • 실패 패턴별 해결책
  • 05Martin Fowler가 본 하네스 엔지니어링의 세 축
  • 컨텍스트 엔지니어링 (Context Engineering)
  • 아키텍처 제약 (Architectural Constraints)
  • 엔트로피 관리 (Entropy Management)
  • 06실제로 하네스를 설계하는 방법
  • Step 1: 작업을 세션 단위로 분해한다
  • Step 2: 상태를 파일에 외부화한다
  • Step 3: 생성자와 평가자를 분리한다
  • Step 4: 점진적으로 추가하고 항상 커밋한다
  • Step 5: 에이전트가 막히는 곳에서 하네스를 개선한다
  • 07실제 사례 — 내 하네스 v1에서 v2로의 진화
  • v1 — 최소 하네스 (dooray-cli)
  • v2 — 성숙한 하네스 (webtoon-maker-v1)
  • 진화의 관찰
  • 08gstack — 역할 기반 하네스의 실용 구현
  • 아이디어: 만능 어시스턴트 대신 역할 조직
  • 하네스 원칙과의 대응
  • 주목받는 이유
  • 09모델이 좋아질수록 하네스는 어떻게 되는가
  • 102025~2026의 흐름
  • 11참고
tags
#study

이런 글도

  • ai

    하네스 회고는 새 문서로 쌓지 않고 종류에 맞는 기존 단일 소스에 환원한다

    2026.10.02
  • ai

    에이전트가 배운 회피 패턴은 조건을 통과한 것만 파일로 쌓고 주기적으로 지운다

    2026.10.02
  • ai

    ADR 은 되돌리기 어려운 결정의 이유만 남기고, 대안 기각은 옵션마다 줄을 나눈다

    2026.10.02
  • ai

    스킬 문서는 반복 실행을 스크립트로 내리고 같은 지시를 한 곳에서만 소유하게 쓴다

    2026.10.02

댓글 (0)