반응형
프롬프트를 아무리 잘 써도 AI가 엉뚱한 파일 수정하고 없는 API 만들어냅니다. 문제는 프롬프트가 아닙니다. AI가 작업하는 환경 자체가 문제입니다. Harness Engineering은 그 환경을 설계하는 기법입니다.
[핵심 요약]
→ Harness: 모델을 제외한 에이전트의 모든 것 (Agent = Model + Harness)
→ 핵심 철학: "프롬프트를 잘 쓰는 것"이 아니라 "AI가 작업하는 환경을 설계"
→ 두 가지 제어 방식: Guides(사전 방지) + Sensors(사후 자동 교정)
→ 두 가지 실행 유형: Computational(결정론적) + Inferential(AI 기반)
→ 계층 구조: 모델 → 빌더 하네스 → 유저 하네스 (3겹)
→ 효과: LangChain, Terminal-Bench 2.0에서 하네스 변경만으로 52.8% → 66.5% 달성
→ 출처: Martin Fowler(4/2), Red Hat(4/7), Anthropic 내부 패턴, OpenAI 필드 리포트
왜 프롬프트만으로는 부족한가
프롬프트 엔지니어링 (2023~2024):
→ 단일 상호작용 최적화
→ 하나의 컨텍스트 윈도우 안에서 동작
→ "더 잘 물어보면" 해결됨
컨텍스트 엔지니어링 (2025):
→ 여러 턴에 걸친 토큰 세트 관리
→ 하나의 컨텍스트 윈도우 안에서 동작
→ "더 많은 컨텍스트를 주면" 해결됨
Harness Engineering (2026):
→ 컨텍스트 윈도우 경계를 넘어서 동작
→ 여러 세션, 여러 에이전트 조율
→ "환경 자체를 설계하면" 해결됨
Red Hat 엔지니어 Marco Rizzi의 실제 경험입니다.
실패 패턴 (하네스 없음):
→ Jira 티켓 "CSV export for SBOMs 추가해줘" → AI에 붙여넣기
→ AI가 잘못된 모듈 수정
→ 없는 API 만들어냄
→ 엉뚱한 파일 경로 환각
→ 매번 똑같은 실수 반복
성공 패턴 (하네스 적용):
→ AI가 실제 코드베이스를 먼저 분석
→ 변경 영향 범위 맵 생성
→ 사람이 검토 후 승인
→ 구조화된 태스크 템플릿으로 구현
→ 자동 피드백 루프로 자가 교정
실전 1 — 하네스의 3계층 구조
[하네스 3계층]
1. 모델 (Core):
→ LLM 자체 (Claude, GPT, Gemini 등)
→ 직접 수정 불가
2. 빌더 하네스 (Middle):
→ 에이전트 제공자가 구축
→ 시스템 프롬프트, 코드 검색 메커니즘, 오케스트레이션
→ Claude Code: 기본 내장 하네스
→ Cursor: Agent Window + 기본 권한 시스템
3. 유저 하네스 (Outer):
→ 개발자가 직접 구축하는 영역
→ AGENTS.md, CLAUDE.md, .cursorrules
→ 커스텀 린터, 구조적 테스트, 리뷰 에이전트
→ 이 글에서 다루는 핵심 영역
실전 2 — Guides(사전 방지)와 Sensors(사후 교정)
하네스는 두 종류의 제어로 구성됩니다.
# harness 구조 개념도
harness = {
"guides": { # 피드포워드 — 사전에 AI 행동 유도
"computational": [ # 결정론적, 빠름
"AGENTS.md",
"bootstrap 스크립트",
"코드모드 도구",
],
"inferential": [ # AI 기반, 풍부한 의미
"아키텍처 원칙 문서",
"코딩 컨벤션 가이드",
"How-to 가이드",
]
},
"sensors": { # 피드백 — 사후 자가 교정
"computational": [ # 결정론적, 모든 변경에 실행
"정적 분석 (린터)",
"모듈 경계 구조적 테스트",
"타입 체커",
"pre-commit 훅",
],
"inferential": [ # AI 기반, 의미적 판단
"AI 코드 리뷰 에이전트",
"LLM-as-Judge 품질 게이트",
]
}
}
Computational Guide — AGENTS.md 작성
# AGENTS.md — 유저 하네스의 핵심 파일
# Claude Code: CLAUDE.md / Cursor: .cursorrules / OpenAI Codex: AGENTS.md
## 프로젝트 구조
backend/ → Rust (Axum 프레임워크)
frontend/ → TypeScript (React 18)
infra/ → Helm charts (Kubernetes)
tests/ → 통합 테스트 (pytest)
## 코딩 규칙
- Rust: 에러는 Result<T, AppError> 반환 (unwrap/expect 금지)
- TypeScript: strict mode 필수, any 타입 금지
- API: RESTful, 응답은 {data, error, meta} 구조 통일
- 테스트: 새 기능은 반드시 단위 테스트 포함
## 절대 하지 말 것
- 환경변수 하드코딩
- 기존 API 시그니처 변경 (하위 호환성 파괴)
- backend/와 frontend/ 간 직접 파일 접근
## 변경 승인이 필요한 영역
- database/migrations/ — DB 스키마 변경
- infra/ — 인프라 변경
- 외부 API 통합 추가
Computational Sensor — 구조적 테스트
# structural_test.py — AI가 모듈 경계를 침범하면 자동 감지
import ast
import os
import pytest
def get_imports(filepath: str) -> list[str]:
"""파일에서 import 목록 추출"""
with open(filepath) as f:
tree = ast.parse(f.read())
return [
node.names[0].name
for node in ast.walk(tree)
if isinstance(node, ast.Import)
] + [
node.module
for node in ast.walk(tree)
if isinstance(node, ast.ImportFrom) and node.module
]
def test_frontend_cannot_import_backend():
"""frontend가 backend 모듈을 직접 임포트하면 실패"""
frontend_files = []
for root, _, files in os.walk("frontend/src"):
for f in files:
if f.endswith(".ts") or f.endswith(".tsx"):
frontend_files.append(os.path.join(root, f))
violations = []
for filepath in frontend_files:
imports = get_imports(filepath)
backend_imports = [i for i in imports if "backend" in i or "rust_" in i]
if backend_imports:
violations.append(f"{filepath}: {backend_imports}")
assert not violations, f"모듈 경계 침범 감지:\n" + "\n".join(violations)
def test_no_hardcoded_secrets():
"""하드코딩된 시크릿 패턴 감지"""
import re
secret_patterns = [
r'api_key\s*=\s*["\'][^"\']+["\']',
r'password\s*=\s*["\'][^"\']+["\']',
r'secret\s*=\s*["\'][^"\']+["\']',
]
violations = []
for root, _, files in os.walk("."):
if ".git" in root or "node_modules" in root:
continue
for filename in files:
if filename.endswith((".py", ".ts", ".rs")):
filepath = os.path.join(root, filename)
with open(filepath, encoding="utf-8", errors="ignore") as f:
content = f.read()
for pattern in secret_patterns:
if re.search(pattern, content, re.IGNORECASE):
violations.append(f"{filepath}: {pattern}")
assert not violations, f"하드코딩 시크릿 감지:\n" + "\n".join(violations)
실전 3 — Red Hat의 2단계 워크플로우
Red Hat 엔지니어가 실제 사용한 패턴입니다. Rust 백엔드 + TypeScript 프론트엔드 + Helm 차트 멀티레포 환경에서 적용했습니다.
Phase 1: 레포지토리 임팩트 맵
# impact_map_generator.py
# MCP + LSP로 실제 코드베이스 분석 후 임팩트 맵 생성
import subprocess
import json
from anthropic import Anthropic
client = Anthropic()
def analyze_repository(feature_description: str) -> str:
"""
AI가 실제 코드베이스를 분석해서 변경 영향 범위 맵 생성
자유형식 티켓 → 구체적 파일/심볼 수준 임팩트 맵
"""
# 1. 실제 코드베이스 구조 스캔
repo_structure = subprocess.run(
["find", ".", "-type", "f",
"-name", "*.py", "-o", "-name", "*.ts", "-o", "-name", "*.rs",
"-not", "-path", "*/node_modules/*",
"-not", "-path", "*/.git/*"],
capture_output=True, text=True
).stdout
# 2. AI에게 실제 파일 보고 분석 요청
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2000,
system="""당신은 코드베이스 분석 전문가입니다.
주어진 기능 요구사항과 실제 파일 구조를 보고,
어떤 파일을 수정해야 하는지 구체적으로 분석합니다.
출력 형식:
## 임팩트 맵
### [서비스명]
변경 파일:
- 경로/파일.py: [변경 이유]
새로 생성:
- 경로/새파일.py: [생성 이유]
영향받는 심볼:
- ClassName.method_name""",
messages=[{
"role": "user",
"content": f"""기능 요구사항: {feature_description}
실제 파일 구조:
{repo_structure}
위 실제 파일들을 기반으로 임팩트 맵을 작성하세요.
존재하지 않는 파일은 절대 언급하지 마세요."""
}]
)
return response.content[0].text
# 사용 예시
feature = "SBOM 목록에 CSV 내보내기 기능 추가"
impact_map = analyze_repository(feature)
print(impact_map)
# 출력 예시:
# ## 임팩트 맵
#
# ### trustify (backend)
# 변경 파일:
# - src/sbom/handler.rs: CSV 직렬화 엔드포인트 추가
# - src/sbom/model.rs: CSV 변환 트레잇 구현
# 새로 생성:
# - src/sbom/csv_export.rs: CSV 내보내기 로직
#
# ### trustify-ui (frontend)
# 변경 파일:
# - src/components/SbomList.tsx: "CSV 내보내기" 버튼 추가
Phase 2: 구조화된 태스크 템플릿
# AI에게 넘기는 구조화된 태스크 (임팩트 맵 사람이 검토 후 생성)
## 레포지토리
trustify
## 설명
SBOM 쿼리 결과에 CSV 직렬화 추가
## 영향 파일 (실제 존재 확인됨)
- src/sbom/handler.rs
- src/sbom/model.rs
- src/sbom/csv_export.rs (신규 생성)
## 따라야 할 기존 패턴
- JSON 내보내기 참고: src/sbom/json_export.rs
- 엔드포인트 패턴: GET /api/v2/{resource}/export
## 수락 기준
- [ ] GET /api/v2/sbom/export?format=csv 엔드포인트 동작
- [ ] 기존 JSON 내보내기 테스트 통과 유지
- [ ] 새 CSV 내보내기 단위 테스트 추가
- [ ] 빈 결과 처리 (헤더만 있는 CSV)
## 절대 수정 금지
- src/auth/ — 인증 관련 파일
- Cargo.toml — 의존성 (새 크레이트 필요 시 먼저 확인)
[2단계 워크플로우 핵심]
→ Phase 1: AI가 실제 코드베이스 스캔 → 임팩트 맵 생성
→ 사람 검토: 잘못된 모듈, 없는 파일, 엉뚱한 패턴 여기서 차단
→ Phase 2: 구체적 파일·패턴·수락 기준 포함한 구조화 태스크 생성
→ AI 구현: 자유형식 아닌 제약 내에서 코드 생성
→ Sensors 실행: 구조적 테스트, 린터 자동 검증
→ 핵심 통찰: "잘못된 계획을 임팩트 맵에서 발견하는 비용 << 잘못된 코드 수정 비용"
실전 4 — 스티어링 루프: 하네스를 계속 개선하기
하네스는 한 번 만들고 끝이 아닙니다. AI가 같은 실수를 반복할 때마다 하네스를 개선합니다.
# steering_loop.py — 반복 실수를 하네스 개선으로 전환
class HarnessSteeringLoop:
"""
AI의 반복 실수를 자동으로 감지하고
Guides/Sensors 개선안을 제안
"""
def __init__(self):
self.error_log = []
def log_error(self, error_type: str, description: str, fix: str):
self.error_log.append({
"type": error_type,
"description": description,
"fix": fix,
"count": 1
})
def get_harness_improvements(self) -> dict:
"""반복 에러 → 하네스 개선안 매핑"""
improvements = {
"guides": [], # AGENTS.md에 추가할 규칙
"sensors": [], # 추가할 자동 체크
}
# 같은 에러 3번 이상 발생 시 하네스 개선 필요
from collections import Counter
repeated = Counter(e["type"] for e in self.error_log)
for error_type, count in repeated.items():
if count >= 3:
if "wrong_module" in error_type:
improvements["guides"].append(
f"AGENTS.md에 모듈 경계 명시 필요: {error_type}"
)
improvements["sensors"].append(
f"모듈 경계 구조적 테스트 추가: {error_type}"
)
elif "hardcoded" in error_type:
improvements["sensors"].append(
"하드코딩 감지 린터 규칙 추가"
)
elif "api_pattern" in error_type:
improvements["guides"].append(
"API 패턴 How-to 가이드 추가 필요"
)
return improvements
# 사용 패턴
loop = HarnessSteeringLoop()
# AI가 같은 실수 반복하면 로깅
loop.log_error("wrong_module", "frontend에서 backend 직접 임포트", "모듈 경계 위반")
loop.log_error("wrong_module", "frontend에서 backend 직접 임포트", "모듈 경계 위반")
loop.log_error("wrong_module", "frontend에서 backend 직접 임포트", "모듈 경계 위반")
# 개선안 자동 생성
improvements = loop.get_harness_improvements()
print(improvements)
# → {"guides": ["AGENTS.md에 모듈 경계 명시 필요"],
# "sensors": ["모듈 경계 구조적 테스트 추가"]}
[스티어링 루프 원칙]
→ 같은 실수 반복 = 하네스 개선 신호
→ Guide 개선: AGENTS.md에 규칙 추가 (사전 방지)
→ Sensor 개선: 구조적 테스트·린터 추가 (자동 감지)
→ AI로 하네스 자체를 개선 가능 (메타 루프)
→ 목표: "토큰 낭비 없이, 리뷰 부담 없이, 품질 유지"
마무리
✅ Harness Engineering 도입해야 하는 경우
→ AI 코딩 툴 써도 매번 비슷한 실수가 반복될 때
→ 멀티레포, 멀티서비스 복잡한 코드베이스 환경
→ 여러 세션에 걸친 장시간 에이전트 작업
→ 팀 전체가 AI 코딩 툴을 쓰고 일관성이 필요할 때
→ CI/CD에 AI 에이전트를 무감독으로 실행하고 싶을 때
❌ 아직 필요 없는 경우
→ 단순 스크립트, 1회성 작업
→ 프로젝트 초기 탐색 단계 (구조가 아직 안 잡힘)
→ 혼자 쓰는 작은 프로젝트
→ 프롬프트 개선만으로 충분한 단순 태스크
관련 글
- Spec-Driven Development — Vibe Coding 다음 단계, AI 에이전트 개발 방법론
- CLAUDE.md 잘 쓰는 법 — 세션마다 시니어 개발자를 고용하는 효과
- Vibe Coding은 끝났다 — Karpathy가 선언한 Agentic Engineering 시대
- AI 에이전트 옵저버빌리티 완전 가이드 — 에이전트가 뭘 하는지 추적하는 법
반응형
'AI Agent' 카테고리의 다른 글
| LLMWiki 완전 가이드 — Karpathy가 제안한 AI가 스스로 관리하는 지식 베이스 (0) | 2026.05.19 |
|---|---|
| uv + Ruff 완전 가이드 — OpenAI가 인수한 Python 툴링의 새 표준 (0) | 2026.05.18 |
| Slopsquatting 완전 가이드 — AI가 추천한 패키지가 악성코드일 수 있다 (0) | 2026.05.18 |
| PydanticAI 완전 가이드 — LLM 출력을 문자열 말고 타입으로 받는 법 (0) | 2026.05.18 |
| Mastra AI 완전 가이드 — TypeScript로 AI 에이전트 만드는 LangChain 대안 (0) | 2026.05.18 |