본문 바로가기

AI Agent

Harness Engineering 완전 가이드 — AI가 더 잘 짜도록 환경 자체를 설계하는 법

반응형

프롬프트를 아무리 잘 써도 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회성 작업
→ 프로젝트 초기 탐색 단계 (구조가 아직 안 잡힘)
→ 혼자 쓰는 작은 프로젝트
→ 프롬프트 개선만으로 충분한 단순 태스크

관련 글

 

반응형