하네스 엔지니어링: 바이브코딩을 프로덕션 엔지니어링으로 바꾸는 방법
1. 하네스 엔지니어링이란 무엇인가
하네스 엔지니어링(Harness Engineering)은 AI 에이전트(agent)가 실제 업무를 수행할 때, 모델 바깥의 실행 환경·도구·권한·컨텍스트·검증·관측성·승인 절차를 설계하는 공학이다.
간단히 말하면 이렇다.
모델이 “두뇌”라면, 하네스는 그 두뇌가 실제 개발 환경에서 안전하게 움직이도록 붙여주는 “작업 장비, 안전벨트, 계기판, 규칙, 검증 장치”다.Martin Fowler는 하네스를 “모델을 제외한 에이전트의 모든 것”에 가깝게 설명한다. 즉 에이전트는 단순히 LLM 하나가 아니라, Model + Harness로 구성된 시스템이다. 하네스에는 프롬프트, 컨텍스트, 도구, 메모리, 테스트, 피드백 루프, 권한 제어, 사람 개입 지점이 포함된다.
OpenAI도 Codex 기반 작업 경험을 설명하면서, 엔지니어의 역할이 단순히 코드를 직접 작성하는 것에서 에이전트가 일할 수 있는 환경과 제약을 설계하는 것으로 확장된다고 설명한다. 이 관점에서 하네스 엔지니어링은 AI 시대의 새로운 소프트웨어 공학 층이다.
2. 왜 하네스가 필요한가
바이브코딩(vibe coding)은 빠르다. 사용자가 “이런 기능 만들어줘”라고 말하면 AI가 파일을 만들고, 코드를 수정하고, 테스트까지 작성한다. 프로토타입에서는 매우 강력하다.
문제는 실제 서비스다.
AI는 보통 다음을 잘한다.
```
- 반복적인 코드 생성
- boilerplate 작성
- 익숙한 프레임워크 패턴 적용
- 테스트 초안 작성
- 작은 버그 수정
- 문서 기반 코드 수정
```
하지만 다음에는 취약하다.
```
- 인증/인가 보안 경계
- 결제 로직
- 개인정보 처리
- 운영 DB 접근
- 장기 유지보수성
- 사내 아키텍처 규칙
- 과거 장애 이력
- 도메인별 암묵적 예외
- 배포 후 실패 비용
```
즉 바이브코딩의 위험은 “AI가 코드를 못 짠다”가 아니다. 더 정확히는 AI가 너무 쉽게 그럴듯한 코드를 만들기 때문에, 사람이 검증하기 전에 위험한 변경이 들어갈 수 있다는 점이다.
하네스 엔지니어링은 이 문제를 이렇게 바꾼다.
```
나쁜 방식:
AI에게 “보안 신경 써서 만들어줘”라고 말한다.
좋은 방식:
AI가 보안 규칙을 위반하면 PR 자체가 생성되지 않게 만든다.
```
프롬프트는 조언이다.하네스는 제도와 인프라다.
3. 기존 에이전트 시스템과 무엇이 다른가
기존 에이전트 시스템은 보통 이런 구조다.
```
사용자 목표
↓
LLM이 계획
↓
도구 호출
↓
결과 관찰
↓
다시 계획
↓
완료
```
이건 agent loop, 즉 에이전트 실행 루프다.
하네스 엔지니어링은 그 바깥을 설계한다.
```
이 에이전트가 어떤 파일을 읽을 수 있는가?
어떤 파일을 쓸 수 있는가?
어떤 명령은 실행할 수 없는가?
운영 DB에는 접근할 수 없는가?
테스트 실패 시 어떻게 재시도하는가?
보안 스캔 실패 시 누가 승인하는가?
PR에는 어떤 증거를 남겨야 하는가?
나중에 실패 원인을 추적할 수 있는가?
```
즉 기존 agent framework가 “AI가 행동하는 루프”를 만드는 데 집중했다면, 하네스 엔지니어링은 그 행동 루프가 안전하고, 검증 가능하고, 감사 가능하고, 조직의 품질 기준을 따르도록 만드는 구조에 집중한다.
2026년 arXiv에 올라온 하네스 엔지니어링 관련 프리프린트도 소프트웨어 에이전트의 능력을 모델 단독이 아니라 model-harness-environment system, 즉 모델·하네스·환경의 결합으로 봐야 한다고 설명한다. 해당 논문은 task specification, context selection, tool access, memory, observability, verification, permissions 등을 하네스의 책임으로 정리한다. 다만 프리프린트이므로 동료심사를 거친 확정 이론으로 보기보다는 현재 연구 흐름으로 읽는 편이 안전하다.
4. 하네스 엔지니어링의 핵심 구성요소
실무에서 하네스는 보통 다음 층으로 나뉜다.
```
1. 작업 명세 계층
2. 위험도 분류 계층
3. 컨텍스트 수집 계층
4. 에이전트 런타임 계층
5. 도구 접근 계층
6. 샌드박스 실행 계층
7. 검증 게이트 계층
8. 사람 승인 계층
9. 관측성·로그 계층
10. CI/CD 운영 계층
```
전체 구조는 이렇게 볼 수 있다.
```
사용자 요청 또는 GitHub Issue
↓
작업 위험도 분류
↓
관련 코드·문서·이슈·정책 검색
↓
에이전트 실행
↓
허용된 도구만 호출
↓
샌드박스 안에서 코드 수정
↓
테스트·타입체크·린트·보안 스캔
↓
위험 작업이면 사람 승인
↓
PR 생성
↓
CI에서 다시 검증
↓
로그와 trace 저장
```
이 구조의 핵심은 모델이 자유롭게 움직이게 두는 것이 아니라, 좁고 명확한 통로를 통해 움직이게 만드는 것이다.
5. 실제 실무 스택 조합
하네스 엔지니어링은 특정 라이브러리 하나로 끝나지 않는다. 여러 도구를 목적별로 조합한다.
5.1 에이전트 런타임
대표 선택지는 다음이다.
```
- OpenAI Agents SDK
- LangGraph
- Pydantic AI
- LlamaIndex Workflows
- OpenHands SDK
```
OpenAI Agents SDK는 Agent와 Runner를 통해 turns, tool execution, guardrails, handoffs, sessions를 관리하는 구조다. 즉 단순 API 호출 래퍼가 아니라, 도구 호출형 에이전트 실행 루프를 관리하는 런타임이다.
LangGraph는 durable execution, streaming, human-in-the-loop 같은 장기 실행 에이전트 오케스트레이션 기능에 초점을 둔다. 중간 승인, 실패 후 재시작, 상태 기반 워크플로우가 중요한 경우 적합하다.
5.2 도구 연결 계층
최근에는 MCP(Model Context Protocol, 모델 컨텍스트 프로토콜)가 많이 언급된다. MCP는 AI 애플리케이션이 파일, DB, 검색, 계산기, 업무 시스템 같은 외부 시스템에 연결될 수 있게 하는 오픈 표준이다. 공식 문서는 MCP를 AI 애플리케이션을 외부 데이터·도구·워크플로우에 연결하는 표준으로 설명한다.
하지만 MCP를 붙였다고 자동으로 안전해지는 건 아니다. 하네스에서는 MCP tool마다 다음 제약을 붙여야 한다.
```
- 호출 가능한 tool allowlist
- 위험 tool denylist
- path validation
- argument schema validation
- rate limit
- approval policy
- audit log
```
즉 MCP는 연결 표준이고, 하네스는 그 위의 통제 체계다.
5.3 샌드박스 계층
AI가 만든 코드는 격리된 환경에서 실행해야 한다.
대표 도구는 다음이다.
```
- Docker
- devcontainer
- E2B
- Kubernetes isolated runner
- Firecracker 계열 microVM
- GitHub Actions ephemeral runner
```
E2B는 AI-generated code를 클라우드의 secure isolated sandbox에서 실행할 수 있는 오픈소스 인프라로 설명된다. Hugging Face의 secure code execution 문서도 LLM 생성 코드를 로컬 환경에서 실행하는 데는 본질적 위험이 있으며, 더 강한 격리를 위해 E2B나 Docker 같은 remote execution 또는 sandbox 접근이 필요하다고 설명한다.
5.4 검증 게이트
하네스에서 가장 중요한 원칙은 이것이다.
모델의 자기평가가 아니라, 외부 검증 시스템의 결과를 믿는다.보통 다음 도구를 사용한다.
```
테스트:
- pytest
- Jest
- Vitest
- Playwright
- Cypress
타입체크:
- mypy
- pyright
- TypeScript tsc
린트:
- ruff
- eslint
- biome
보안:
- Semgrep
- CodeQL
- Snyk
- secret scanning
- dependency scan
```
Semgrep은 CI와 PR 스캔에 통합했을 때 pull request에서 새로 도입된 이슈를 보고하도록 사용할 수 있다. GitHub CodeQL은 코드의 취약점과 오류를 찾아 GitHub code scanning alert로 표시하는 도구다.
5.5 관측성 계층
운영 환경에서는 “AI가 왜 그렇게 했는지”를 추적할 수 있어야 한다.
대표 도구는 다음이다.
```
- Langfuse
- LangSmith
- OpenTelemetry
- Arize Phoenix
- custom audit log
```
OpenAI Agents SDK는 LLM generation, tool call, handoff, guardrail, custom event를 포함한 tracing을 제공한다. Langfuse는 OpenAI Agents workflow를 monitor, debug, evaluate하기 위한 통합을 제공한다.
관측성 없이 에이전트를 운영하면 나중에 이런 질문에 답하기 어렵다.
```
- AI가 어떤 파일을 읽고 수정했는가?
- 어떤 테스트가 실패했는가?
- 실패 후 무엇을 바꿨는가?
- 어떤 tool call이 위험했는가?
- 같은 실수가 반복되고 있는가?
- 비용이 어디서 폭주했는가?
```
6. 실무 적용 패턴
6.1 개인 개발자용 최소 하네스
개인 개발자라면 복잡한 플랫폼을 만들 필요는 없다. 최소한 이 정도만 해도 안전성이 크게 올라간다.
```
- AI가 수정할 branch를 따로 만든다.
- repo 전체를 무제한으로 맡기지 않는다.
- 수정 가능한 파일 범위를 정한다.
- .env, secret, production DB 접근을 주지 않는다.
- 테스트가 없으면 먼저 테스트를 만들게 한다.
- pytest/npm test/tsc/eslint를 반드시 실행한다.
- git diff를 사람이 읽고 merge한다.
- GitHub Actions에서 다시 검증한다.
```
개인용 최소 스택:
```
AI coding tool:
- Cursor
- Claude Code
- Codex CLI
- Aider
- OpenHands
검증:
- pytest 또는 Jest
- mypy 또는 TypeScript
- ruff 또는 eslint
- Semgrep
운영:
- Git
- Docker
- GitHub Actions
```
6.2 스타트업 제품팀용 하네스
스타트업에서는 “AI가 issue를 보고 PR까지 생성”하는 구조가 적합하다.
```
입력:
- GitHub issue
- Linear ticket
- Jira ticket
에이전트:
- OpenAI Agents SDK
- Pydantic AI
- LangGraph
컨텍스트:
- 코드 검색
- 문서 검색
- 과거 PR
- 관련 이슈
실행:
- Docker
- E2B
- GitHub Actions runner
검증:
- unit test
- integration test
- type check
- lint
- Semgrep
출력:
- PR
- 변경 요약
- 검증 결과
- 위험도 보고
```
흐름은 다음과 같다.
```
1. issue에 ai-fix 라벨을 붙인다.
2. 하네스가 issue 내용을 읽는다.
3. 위험도 분류를 한다.
4. 관련 코드와 테스트를 검색한다.
5. 샌드박스에서 임시 브랜치를 만든다.
6. AI가 patch를 제안한다.
7. 테스트, 타입체크, 린트, 보안검사를 실행한다.
8. 통과하면 PR을 만든다.
9. 사람이 review하고 merge한다.
```
6.3 엔터프라이즈용 하네스
금융, 의료, 교육, B2B SaaS처럼 실패 비용이 큰 조직에서는 더 강한 구조가 필요하다.
```
Agent runtime:
- LangGraph
- OpenAI Agents SDK
- OpenHands SDK
Tool protocol:
- MCP
- internal tool gateway
Context:
- 사내 문서 RAG
- 코드 검색
- ADR
- 장애 이력
- 보안 정책
- CODEOWNERS
Sandbox:
- Kubernetes isolated runner
- E2B private deployment
- Firecracker 계열 microVM
- ephemeral CI runner
Validation:
- unit test
- integration test
- e2e test
- SAST
- SCA
- secret scanning
- policy-as-code
Approval:
- GitHub CODEOWNERS
- Slack approval
- Jira transition
- security review
Observability:
- OpenTelemetry
- Langfuse
- LangSmith
- audit database
```
엔터프라이즈에서는 AI에게 “더 많은 권한”을 주는 것이 아니라, 오히려 권한을 더 잘게 쪼개고, 승인 절차를 명확히 하는 것이 핵심이다.
7. 실제 구현 예시: 안전한 코딩 하네스
아래는 Python 기준의 최소 구현 예시다.목표는 “AI가 파일을 수정하되, 허용된 경로만 수정하고, 테스트와 린트를 실제로 실행하게 하는 구조”다.
7.1 프로젝트 구조
```
repo/
├─ app/
│ └─ calculator.py
├─ tests/
│ └─ test_calculator.py
├─ harness/
│ ├─ safe_paths.py
│ ├─ policy.py
│ ├─ tools.py
│ ├─ agent.py
│ └─ run_task.py
└─ requirements.txt
```
7.2 예제 대상 코드
```
# app/calculator.py
def divide(a: float, b: float) -> float:
return a / b
```
요구사항은 다음이다.
```
b가 0이면 Python 기본 ZeroDivisionError를 그대로 노출하지 말고,
ValueError를 발생시켜라.
```
테스트는 다음처럼 둔다.
```
# tests/test_calculator.py
import pytest
from app.calculator import divide
def test_divide_normal_case():
assert divide(10, 2) == 5
def test_divide_by_zero_raises_value_error():
with pytest.raises(ValueError):
divide(10, 0)
```
7.3 path 정책: repo 바깥 접근 차단
```
# harness/safe_paths.py
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
ALLOWED_WRITE_DIRS = [
REPO_ROOT / "app",
REPO_ROOT / "tests",
]
DENIED_PARTS = {
".env",
".git",
"secrets",
"private_key",
"id_rsa",
}
def resolve_repo_path(relative_path: str) -> Path:
"""
사용자가 넘긴 상대 경로를 repo 내부의 실제 경로로 바꾼다.
핵심:
- ../../ 같은 path traversal을 막는다.
- repo 바깥 파일 접근을 막는다.
- secret 계열 파일 접근을 막는다.
"""
target = (REPO_ROOT / relative_path).resolve()
if not str(target).startswith(str(REPO_ROOT)):
raise PermissionError(f"repo 바깥 경로 접근 차단: {relative_path}")
lowered = str(target).lower()
if any(part in lowered for part in DENIED_PARTS):
raise PermissionError(f"민감 파일 접근 차단: {relative_path}")
return target
def assert_can_write(relative_path: str) -> Path:
"""
AI가 파일을 수정하기 전에 반드시 통과해야 하는 정책 검사.
"""
target = resolve_repo_path(relative_path)
allowed = any(str(target).startswith(str(base)) for base in ALLOWED_WRITE_DIRS)
if not allowed:
raise PermissionError(f"쓰기 허용 범위 밖 파일: {relative_path}")
return target
```
이 코드가 중요한 이유는 단순하다.AI에게 “민감 파일 읽지 마”라고 말하는 것보다, 애초에 코드로 접근을 막는 것이 훨씬 강하다.
7.4 위험도 분류
```
# harness/policy.py
from dataclasses import dataclass
from typing import Literal
Risk = Literal["low", "medium", "high", "critical"]
@dataclass
class TaskPolicy:
risk: Risk
requires_human_approval: bool
reason: str
HIGH_RISK_KEYWORDS = [
"auth",
"authentication",
"authorization",
"login",
"password",
"payment",
"billing",
"invoice",
"personal data",
"pii",
"database migration",
"production",
]
def classify_task(description: str) -> TaskPolicy:
"""
단순한 룰 기반 위험도 분류기.
실무에서는 여기에 LLM 분류기, CODEOWNERS,
파일 소유권, 장애 이력, 보안 정책을 같이 넣는다.
"""
text = description.lower()
for keyword in HIGH_RISK_KEYWORDS:
if keyword in text:
return TaskPolicy(
risk="high",
requires_human_approval=True,
reason=f"고위험 키워드 감지: {keyword}",
)
return TaskPolicy(
risk="low",
requires_human_approval=False,
reason="일반 코드 수정 작업",
)
```
이 계층의 목적은 “AI가 결제·인증·개인정보 코드를 아무 승인 없이 수정하는 상황”을 막는 것이다.
7.5 안전 도구 만들기
OpenAI Agents SDK는 Python 함수를 function tool로 노출할 수 있다. 이때 중요한 점은 AI에게 범용 shell을 주지 않는 것이다. 대신 테스트 실행, 파일 읽기, 파일 쓰기, 검색 같은 좁은 도구만 제공한다. OpenAI Agents SDK는 Python 함수와 docstring을 기반으로 tool schema를 구성하는 function tool 방식을 제공한다.
```
# harness/tools.py
import subprocess
from agents import function_tool
from harness.safe_paths import REPO_ROOT, resolve_repo_path, assert_can_write
def _run_command(args: list[str]) -> str:
"""
shell 문자열을 받지 않고 list[str]만 받는다.
shell=True를 쓰지 않는 이유:
- 명령어 인젝션 위험을 줄인다.
- AI가 '; rm -rf' 같은 문자열을 섞어도 셸 해석이 일어나지 않는다.
"""
result = subprocess.run(
args,
cwd=REPO_ROOT,
text=True,
capture_output=True,
timeout=60,
shell=False,
)
return (
f"$ {' '.join(args)}\n\n"
f"[stdout]\n{result.stdout}\n\n"
f"[stderr]\n{result.stderr}\n\n"
f"[exit_code]\n{result.returncode}"
)
@function_tool
def read_file(path: str) -> str:
"""
repo 내부 파일을 읽는다.
Args:
path: repo root 기준 상대 경로. 예: app/calculator.py
"""
target = resolve_repo_path(path)
if not target.exists():
return f"파일이 존재하지 않습니다: {path}"
if target.is_dir():
return f"디렉터리는 읽을 수 없습니다: {path}"
return target.read_text(encoding="utf-8")
@function_tool
def write_file(path: str, content: str) -> str:
"""
허용된 경로의 파일만 전체 덮어쓴다.
Args:
path: repo root 기준 상대 경로. 예: app/calculator.py
content: 새 파일 내용
"""
target = assert_can_write(path)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content, encoding="utf-8")
return f"파일 저장 완료: {path}"
@function_tool
def search_code(query: str) -> str:
"""
app, tests 디렉터리에서 문자열을 검색한다.
Args:
query: 찾을 문자열. 예: divide
"""
return _run_command(["rg", "-n", query, "app", "tests"])
@function_tool
def run_pytest() -> str:
"""
pytest를 실행한다.
"""
return _run_command(["pytest", "-q"])
@function_tool
def run_mypy() -> str:
"""
mypy 타입 체크를 실행한다.
"""
return _run_command(["mypy", "app", "tests"])
@function_tool
def run_ruff() -> str:
"""
ruff lint를 실행한다.
"""
return _run_command(["ruff", "check", "app", "tests"])
@function_tool
def run_semgrep() -> str:
"""
Semgrep 보안 스캔을 실행한다.
"""
return _run_command(["semgrep", "scan", "--config", "auto", "app", "tests"])
```
이 코드의 본질은 다음이다.
```
AI에게 준 것:
- read_file
- write_file
- search_code
- run_pytest
- run_mypy
- run_ruff
- run_semgrep
AI에게 주지 않은 것:
- raw shell
- production DB 접근
- secret 접근
- 배포 권한
- repo 바깥 파일 접근
```
이게 하네스 엔지니어링의 핵심이다.AI가 할 수 있는 일을 늘리는 게 아니라, 할 수 있는 일을 안전한 형태로 재정의하는 것이다.
7.6 에이전트 정의
```
# harness/agent.py
from agents import Agent
from harness.tools import (
read_file,
write_file,
search_code,
run_pytest,
run_mypy,
run_ruff,
run_semgrep,
)
coding_agent = Agent(
name="SafeCodingAgent",
instructions="""
너는 안전한 코딩 에이전트다.
반드시 다음 규칙을 따른다.
1. 코드를 수정하기 전에 관련 파일을 먼저 읽어라.
2. 파일 구조를 모르면 search_code 도구로 확인해라.
3. 수정은 write_file 도구로만 수행해라.
4. 수정 후 반드시 run_pytest, run_mypy, run_ruff를 실행해라.
5. 보안 관련 코드가 포함되면 run_semgrep도 실행해라.
6. 테스트가 실패하면 실패 로그를 읽고 수정한 뒤 다시 검증해라.
7. 최종 답변에는 다음을 포함해라.
- 변경한 파일
- 변경 이유
- 실행한 검증 명령
- 통과/실패 결과
- 사람이 추가 검토해야 할 위험
""",
tools=[
read_file,
write_file,
search_code,
run_pytest,
run_mypy,
run_ruff,
run_semgrep,
],
)
```
여기서 프롬프트도 중요하지만, 더 중요한 것은 도구 설계다.프롬프트는 에이전트에게 방향을 준다.도구와 정책은 에이전트의 행동 가능 범위를 결정한다.
7.7 실행기
```
# harness/run_task.py
import sys
from dotenv import load_dotenv
from agents import Runner
from harness.agent import coding_agent
from harness.policy import classify_task
load_dotenv()
def main() -> None:
if len(sys.argv) < 2:
raise SystemExit(
"사용법: python -m harness.run_task '작업 설명을 여기에 입력'"
)
task_description = sys.argv[1]
policy = classify_task(task_description)
print("=== Task Policy ===")
print(f"risk: {policy.risk}")
print(f"requires_human_approval: {policy.requires_human_approval}")
print(f"reason: {policy.reason}")
print()
if policy.requires_human_approval:
print("사람 승인 필요. 에이전트 실행을 중단합니다.")
return
prompt = f"""
작업:
{task_description}
허용 범위:
- app/ 아래 파일
- tests/ 아래 파일
완료 조건:
- pytest 통과
- mypy 통과
- ruff 통과
- 변경 요약과 검증 결과 보고
주의:
- 허용 범위 밖 파일은 수정하지 마라.
- 테스트 결과를 실제 도구 실행 결과로 확인하라.
"""
result = Runner.run_sync(
coding_agent,
prompt,
max_turns=12,
)
print("=== Agent Final Output ===")
print(result.final_output)
if __name__ == "__main__":
main()
```
실행 예시는 다음이다.
```
python -m harness.run_task \
"app/calculator.py의 divide 함수가 0으로 나눌 때 ValueError를 발생시키도록 고쳐라"
```
이 작업에서 에이전트가 이상적으로 수행해야 하는 순서는 다음이다.
```
1. search_code("divide")
2. read_file("app/calculator.py")
3. read_file("tests/test_calculator.py")
4. write_file("app/calculator.py", 수정된 코드)
5. run_pytest()
6. run_mypy()
7. run_ruff()
8. 최종 보고
```
최종 코드 결과는 대략 이렇게 된다.
```
# app/calculator.py
def divide(a: float, b: float) -> float:
if b == 0:
raise ValueError("division by zero is not allowed")
return a / b
```
8. CI에서 최종 검증하기
에이전트가 로컬 또는 샌드박스에서 “통과했다”고 해도, 최종 신뢰는 CI가 담당해야 한다.
```
# .github/workflows/ai-harness-check.yml
name: AI Harness Check
on:
pull_request:
branches: [main]
permissions:
contents: read
security-events: write
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
run: pytest -q
- name: Run type check
run: mypy app tests
- name: Run lint
run: ruff check app tests
- name: Run Semgrep
run: semgrep scan --config auto app tests
```
보안 강도를 더 높이려면 CodeQL도 추가한다.
```
# .github/workflows/codeql.yml
name: CodeQL
on:
pull_request:
branches: [main]
push:
branches: [main]
permissions:
security-events: write
packages: read
actions: read
contents: read
jobs:
analyze:
name: CodeQL Analyze
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
```
CI의 의미는 단순 자동화가 아니다.하네스 관점에서 CI는 AI의 작업물을 조직의 품질 기준으로 다시 검증하는 최종 게이트다.
9. 샌드박스 적용
로컬에서 subprocess를 실행하는 구조는 간단하지만, 실제 서비스에서는 격리된 환경이 필요하다.
E2B를 쓰면 AI-generated code를 샌드박스에서 실행할 수 있다. E2B는 AI agent를 위한 secure computer와 sandbox runtime을 제공한다고 설명한다.
개념 코드는 다음과 같다.
```
# harness/e2b_sandbox_example.py
from dotenv import load_dotenv
from e2b_code_interpreter import Sandbox
load_dotenv()
def run_in_e2b(code: str) -> str:
"""
E2B 샌드박스에서 코드를 실행한다.
실무에서는 여기에 다음을 붙인다.
- repo clone
- dependency install
- patch apply
- pytest/mypy/ruff/semgrep 실행
"""
sbx = Sandbox.create()
execution = sbx.run_code(code)
return str(execution)
if __name__ == "__main__":
output = run_in_e2b(
"""
print("샌드박스 내부에서 실행 중")
print(1 + 2)
"""
)
print(output)
```
실제 운영 흐름은 다음에 가깝다.
```
1. GitHub issue 수신
2. 샌드박스 생성
3. repo clone
4. dependency install
5. AI가 patch 생성
6. patch 적용
7. 테스트·타입체크·린트·보안 스캔 실행
8. 통과하면 PR 생성
9. 실패하면 로그를 agent에게 다시 제공
10. 반복 횟수 초과 시 사람에게 넘김
```
샌드박스를 쓴다고 모든 문제가 해결되는 건 아니다. 반드시 다음을 지켜야 한다.
```
- production credential을 넣지 않는다.
- 실제 결제 API key를 넣지 않는다.
- 운영 DB에 연결하지 않는다.
- 외부 네트워크 접근을 제한한다.
- 실행 시간과 리소스를 제한한다.
- 모든 파일 변경과 tool call을 기록한다.
```
10. LangGraph로 확장하는 방식
OpenAI Agents SDK는 빠르게 도구 호출형 에이전트를 만들기 좋다.하지만 승인, 재시도, 장기 실행, 상태 저장이 많아지면 LangGraph 스타일이 더 적합하다.
LangGraph는 node와 edge로 workflow를 구성하는 방식이다. 각 node는 현재 state를 받아 처리하고, edge가 다음 단계를 결정한다.
하네스를 LangGraph로 설계하면 다음과 같다.
```
classify_task
↓
collect_context
↓
generate_patch
↓
apply_patch
↓
run_checks
↓
security_review
↓
human_approval?
↓
create_pr
```
간단한 골격은 다음과 같다.
```
from typing import TypedDict, Literal
from langgraph.graph import StateGraph, START, END
class HarnessState(TypedDict):
task: str
risk: Literal["low", "medium", "high", "critical"]
context: str
patch_summary: str
check_output: str
approved: bool
def classify_task_node(state: HarnessState) -> dict:
task = state["task"].lower()
if "payment" in task or "auth" in task or "database" in task:
return {"risk": "high"}
return {"risk": "low"}
def collect_context_node(state: HarnessState) -> dict:
return {
"context": "관련 파일과 테스트, 문서, 과거 PR을 수집했다는 가정"
}
def generate_patch_node(state: HarnessState) -> dict:
return {
"patch_summary": "LLM이 생성한 patch 요약"
}
def run_checks_node(state: HarnessState) -> dict:
return {
"check_output": "pytest passed\nmypy passed\nruff passed"
}
def route_after_checks(state: HarnessState) -> str:
if state["risk"] in {"high", "critical"}:
return "wait_for_approval"
return "create_pr"
def wait_for_approval_node(state: HarnessState) -> dict:
return {"approved": False}
def create_pr_node(state: HarnessState) -> dict:
print("PR 생성")
print(state["patch_summary"])
print(state["check_output"])
return {}
builder = StateGraph(HarnessState)
builder.add_node("classify_task", classify_task_node)
builder.add_node("collect_context", collect_context_node)
builder.add_node("generate_patch", generate_patch_node)
builder.add_node("run_checks", run_checks_node)
builder.add_node("wait_for_approval", wait_for_approval_node)
builder.add_node("create_pr", create_pr_node)
builder.add_edge(START, "classify_task")
builder.add_edge("classify_task", "collect_context")
builder.add_edge("collect_context", "generate_patch")
builder.add_edge("generate_patch", "run_checks")
builder.add_conditional_edges(
"run_checks",
route_after_checks,
{
"wait_for_approval": "wait_for_approval",
"create_pr": "create_pr",
},
)
builder.add_edge("wait_for_approval", END)
builder.add_edge("create_pr", END)
graph = builder.compile()
result = graph.invoke(
{
"task": "app/calculator.py divide 버그 수정",
"risk": "low",
"context": "",
"patch_summary": "",
"check_output": "",
"approved": False,
}
)
print(result)
```
이 방식은 특히 다음 상황에서 유리하다.
```
- 작업 시간이 길다.
- 중간에 사람 승인이 필요하다.
- 실패 지점부터 재시작해야 한다.
- 여러 에이전트가 역할을 나눠야 한다.
- audit trail이 중요하다.
```
11. 하네스 설계 원칙
11.1 raw shell을 주지 말 것
나쁜 설계:
```
run_shell(command: str)
```
좋은 설계:
```
run_unit_tests()
run_typecheck()
run_linter()
run_security_scan()
read_file(path)
write_file(path, content)
```
범용 shell은 너무 강하다.하네스에서는 범용 능력을 좁은 안전 도구로 쪼갠다.
11.2 프롬프트보다 코드로 강제할 것
약한 방식:
```
운영 DB는 절대 건드리지 마.
```
강한 방식:
```
if target_env == "production":
raise PermissionError("Agent cannot access production database")
```
AI에게 금지사항을 설명하는 것은 필요하지만 충분하지 않다.금지는 코드, 네트워크, 권한, CI policy로 강제해야 한다.
11.3 검증은 시스템이 수행할 것
나쁜 방식:
```
AI: 테스트는 통과할 것 같습니다.
```
좋은 방식:
```
CI:
- pytest passed
- mypy passed
- ruff passed
- semgrep passed
```
모델의 판단은 참고다.최종 근거는 검증 결과다.
11.4 위험도별 자동화 수준을 다르게 둘 것
```
문서 수정:
- 자동 PR 가능
UI 문구 수정:
- 자동 PR 가능
테스트 추가:
- 자동 PR 가능
일반 API 로직:
- PR + 사람 리뷰
인증/인가:
- 사람 승인 필수
결제:
- 사람 승인 필수
개인정보 처리:
- 사람 승인 필수
DB migration:
- 별도 승인 필수
운영 배포:
- 원칙적으로 사람 승인 필수
```
하네스 엔지니어링의 목표는 모든 것을 자동화하는 것이 아니다.자동화해도 되는 것과 사람이 반드시 봐야 하는 것을 나누는 것이 핵심이다.
12. 적용 순서
실제 팀에 도입한다면 다음 순서가 좋다.
1단계: 읽기 전용 에이전트
처음부터 코드 수정을 맡기지 않는다.
```
- 이슈 요약
- 관련 파일 찾기
- 영향 범위 분석
- 테스트 후보 제안
- 위험도 분류
```
이 단계에서는 에이전트가 코드를 쓰지 않는다.
2단계: 테스트 작성 에이전트
다음으로 테스트만 작성하게 한다.
```
- tests/ 아래만 수정 가능
- app/ 수정 금지
- 테스트 실패 로그 보고
```
테스트는 기능 요구사항을 고정하는 역할을 한다.AI가 구현 전에 테스트를 만들면, 이후 구현의 기준이 생긴다.
3단계: 저위험 코드 수정
다음은 낮은 위험도의 코드만 맡긴다.
```
- 문서
- UI copy
- 단순 유틸 함수
- 타입 오류 수정
- lint fix
- 테스트 보강
```
이 단계부터는 PR 생성까지 자동화할 수 있다.
4단계: 샌드박스와 CI 필수화
이 단계에서는 모든 AI 실행을 격리한다.
```
- Docker 또는 E2B
- production secret 미주입
- mock DB 사용
- timeout 설정
- resource limit 설정
- CI 재검증
```
5단계: 고위험 도메인 승인 체계
마지막으로 고위험 도메인을 명확히 분리한다.
```
- auth
- payment
- user data
- database migration
- deployment
- infrastructure
```
이 영역은 AI가 patch를 제안할 수는 있어도, 사람 승인 없이 반영하면 안 된다.
13. 하네스 엔지니어링의 본질
하네스 엔지니어링은 “프롬프트를 잘 쓰는 법”이 아니다.또 “AI 코딩 툴을 하나 도입하는 법”도 아니다.
더 본질적으로는 다음이다.
AI가 실수할 것을 전제로, 그 실수가 시스템으로 들어가기 전에 막고, 발견하고, 되돌리고, 기록하게 만드는 소프트웨어 공학이다.기존 바이브코딩은 빠르지만, 검증과 통제 장치가 약하면 위험하다.하네스 엔지니어링은 그 속도를 버리지 않으면서, 다음을 추가한다.
```
- 허용 범위
- 금지 범위
- 컨텍스트 공급
- 샌드박스 실행
- 테스트 검증
- 보안 스캔
- 사람 승인
- 감사 로그
- 실패 재현성
```
그래서 한 문장으로 정리하면 다음과 같다.
하네스 엔지니어링은 AI 에이전트를 “그럴듯한 코드를 만드는 도구”에서 “검증 가능한 소프트웨어 생산 과정에 참여하는 통제된 작업자”로 바꾸는 공학이다.앞으로의 개발자는 AI에게 단순히 “코드 짜줘”라고 말하는 사람이 아니라,AI가 안전하게 일할 수 있는 작업장, 도구, 규칙, 검증 체계를 설계하는 사람이 된다.
이 변화의 핵심은 모델이 아니다.모델은 계속 바뀐다. GPT, Claude, Gemini, Llama, Codex 계열은 계속 교체될 수 있다.
하지만 좋은 하네스는 남는다.
```
모델은 교체 가능해야 한다.
도구는 제한 가능해야 한다.
검증은 자동화되어야 한다.
권한은 최소화되어야 한다.
위험 작업은 승인되어야 한다.
모든 행동은 추적 가능해야 한다.
```
이 원칙이 지켜질 때, 바이브코딩은 단순한 감각적 자동완성이 아니라 실제 프로덕션 엔지니어링의 일부가 된다.
'개발' 카테고리의 다른 글
| Background Task의 강자: Celery를 대체할 강력한 선택지, RQ와 Dramatiq (0) | 2025.03.05 |
|---|---|
| Litellm: OpenAI API 비용 절감과 성능 최적화를 동시에 잡는 비밀 무기 (0) | 2025.03.05 |
| Apache Arrow: 대용량 데이터 처리의 게임 체인저 (0) | 2025.03.05 |
| Redis의 숨겨진 강자: RedisJSON과 RedisSearch로 NoSQL 초고속 검색 및 분석하기 (0) | 2025.03.05 |
| DuckDB: OLAP에 특화된 초고속 컬럼형 데이터베이스, SQLite를 대체할 차세대 솔루션 (0) | 2025.03.05 |