프롬프트만 잘 쓰는 것으로는 부족하다. AI가 필요한 문맥을 찾고, 안전한 범위에서 도구를 쓰고, 결과를 스스로 검증하도록 일하는 환경 자체를 설계해야 한다.
먼저 가장 단순한 상황을 생각해 보자. AI에게 이렇게 요청했다고 하자.
회원가입 API에 이메일 중복 검사를 추가해 줘.
문장 자체는 명확하지만 실제로 작업하려면 AI는 더 많은 사실을 알아야 한다.
- 어느 모듈이 회원가입을 담당하는가?
- 기존 코드에서는 중복 검사를 어느 계층에서 하는가?
- 이메일 비교 시 대소문자를 구분하는가?
- 어떤 테스트 명령을 실행해야 하는가?
- 데이터베이스 스키마 변경이 필요하면 바로 해도 되는가?
이 정보를 매번 긴 프롬프트에 모두 적는 방법도 있다. 하지만 프로젝트가 커지면 빠뜨리는 내용이 생기고, 오래된 규칙을 전달할 수도 있다. 하네스 엔지니어링은 이런 프로젝트 지식, 작업 도구, 안전 규칙, 검증 절차를 AI 주변 환경에 미리 배치하는 접근법이다.

하네스는 AI 에이전트가 목표를 받아 안전하게 작업하고, 결과를 검증할 수 있도록 주변 요소를 연결한다.
하네스 엔지니어링이란
하네스(harness)는 원래 무언가를 연결하고 제어하는 장치를 뜻한다. 소프트웨어 테스트에서는 테스트 대상에 입력을 넣고 결과를 관찰하는 테스트 하네스라는 표현으로 오래전부터 사용해 왔다.
AI 에이전트에서 하네스는 모델을 둘러싼 실행 환경 전체를 의미한다. 모델의 성능이 좋아도 프로젝트 지식과 도구, 권한 제한, 테스트가 연결되지 않으면 안정적인 결과를 반복해서 내기 어렵다.
┌─ 프로젝트 지식과 규칙
사용자의 목표 ───→ AI 에이전트 ─┼─ 파일·터미널·브라우저 같은 도구
├─ 권한과 승인 경계
├─ 테스트·정적 분석·리뷰
└─ 실패 기록과 피드백 루프
하네스 엔지니어링(Harness Engineering)은 에이전트가 반복해서 신뢰할 만한 결과를 내도록 다음 요소를 함께 설계하고 개선하는 일이다.
- 무엇을 알아야 하는가: 프로젝트 문서, 아키텍처, 계약, 현재 상태
- 무엇을 할 수 있는가: 검색, 수정, 빌드, 테스트, 배포 등의 도구
- 무엇을 하면 안 되는가: 보안 규칙, 변경 범위, 승인 게이트
- 무엇을 성공으로 볼 것인가: 테스트, 린트(코드를 실행하기 전에 문법·스타일·일부 오류를 검사하는 도구), 명세 비교, 완료 조건
- 실패에서 무엇을 고칠 것인가: 프롬프트가 아니라 누락된 문맥·도구·검증 루프
OpenAI는 하네스 엔지니어링을 소개하며 엔지니어의 역할이 코드를 직접 작성하는 것에서 환경을 설계하고, 의도를 명세하고, 피드백 루프를 만드는 것으로 이동한다고 설명한다. 또한 거대한 지침서 하나보다 짧은 진입 문서와 구조화된 저장소 지식을 권한다. Harness engineering: leveraging Codex in an agent-first world
프롬프트 엔지니어링, 컨텍스트 엔지니어링과 무엇이 다른가
세 개념은 경쟁 관계가 아니라 포함 관계에 가깝다.
| 구분 | 핵심 질문 | 주요 산출물 |
|---|---|---|
| 프롬프트 엔지니어링 | 모델에게 어떻게 지시할까? | 역할, 요청 문장, 출력 형식, 예시 |
| 컨텍스트 엔지니어링 | 지금 어떤 정보를 보여줄까? | 관련 문서, 코드, 작업 이력, 검색 결과 |
| 하네스 엔지니어링 | 에이전트가 어떻게 끝까지 올바르게 일하게 할까? | 문맥 라우팅, 도구, 권한, 워크플로우, 검증, 관찰 가능성 |
Anthropic은 컨텍스트 엔지니어링을 제한된 컨텍스트 창에 넣을 정보를 선별하고 유지하는 작업으로 설명한다. 핵심은 가능한 모든 정보를 넣는 데 있지 않다. 원하는 행동을 만들 가능성이 높은 최소한의 고신호 정보를 넣어야 한다. Effective context engineering for AI agents
하네스 엔지니어링은 여기서 한 걸음 더 나간다. 좋은 정보를 제공하는 것뿐 아니라 에이전트의 행동을 실제 시스템 안에서 제약하고, 결과를 자동화된 도구로 검사하고, 실패하면 그 결과를 바탕으로 다시 작업하게 만든다.
세 개념을 코드 리뷰에 비유하면 다음과 같다.
- 프롬프트 엔지니어링: 작업 티켓을 명확하게 작성한다.
- 컨텍스트 엔지니어링: 관련 코드, 설계 문서, 과거 이슈를 작업자에게 건넨다.
- 하네스 엔지니어링: 작업 브랜치와 권한을 준비하고, CI를 실행하며, 리뷰 통과 전에는 배포할 수 없게 한다.
예를 들어 “기존 스타일에 맞게 안전하게 구현하고 테스트해 줘”라는 프롬프트는 의도는 좋지만 약하다. 다음 항목이 하네스에 포함되면 같은 요청이 훨씬 구체적인 실행 계약이 된다.
1. 변경 대상 경로에 맞는 규칙을 읽는다.
2. 기존 구현과 외부 계약을 조사한다.
3. 위험도가 높으면 사용자 승인을 받는다.
4. 승인된 범위만 수정한다.
5. 관련 테스트와 정적 검사를 실행한다.
6. 변경 파일, 검증 결과, 미검증 위험을 보고한다.
하네스의 실행 루프 설계
에이전트는 보통 한 번의 모델 호출로 일을 끝내지 않는다. 현재 상태를 보고 다음 행동을 결정하고, 도구의 실행 결과를 다시 문맥으로 받아 다음 행동을 선택한다.
예를 들어 테스트 실패를 고치는 에이전트는 다음처럼 움직일 수 있다.
테스트 실행
→ 실패 메시지와 관련 파일 확인
→ 원인 후보를 세움
→ 코드 수정
→ 같은 테스트 재실행
→ 통과하면 관련 테스트까지 실행하고 종료
일반 개발자가 컴파일 오류나 테스트 결과를 보고 코드를 고치는 과정과 크게 다르지 않다. 차이는 에이전트에게 어떤 도구를 허용할지, 어떤 결과를 다시 보여줄지, 언제 멈출지를 미리 정해 줘야 한다는 점이다.
관찰 → 계획 → 도구 실행 → 결과 확인 → 수정
↑ │
└──── 완료 조건을 만족할 때까지 ──┘
루프 엔지니어링(Loop Engineering)은 이 반복 과정이 목표를 향해 수렴하도록 설계하는 일이다. 무엇을 반복할지뿐 아니라 언제 멈추고 사람에게 넘길지까지 정해야 한다.
아직 널리 표준화된 학술 용어라기보다는, 에이전트의 실행·평가·재시도 구조를 설명하는 실무적 표현에 가깝다. 하나의 루프는 대체로 다음 요소로 구성된다.
상태 → 판단 → 행동 → 관찰 → 평가
├─ 통과: 종료
├─ 수정 가능: 상태를 갱신하고 재시도
└─ 해결 불가·고위험: 사람에게 전달
- 성공 조건: 테스트와 계약 검사가 모두 통과했는가
- 재시도 조건: 실패 원인을 바탕으로 수정 가능한가
- 중단 조건: 최대 횟수·시간·비용을 넘었는가
- 에스컬레이션 조건: 권한, 계약, 요구사항이 불명확해 사람이 판단해야 하는가
- 상태 보존: 다음 반복에서 이전 실패와 시도 내용을 알 수 있는가
종료 조건이 없으면 같은 실패를 끝없이 반복할 수 있고, 조건이 너무 느슨하면 테스트를 실행하지 않은 채 완료할 수 있다. 반대로 검증 결과를 구조화해 다시 제공하면 에이전트는 “실패했으니 다시 해 봐”가 아니라 어떤 파일과 조건을 고쳐야 하는지 알 수 있다.
좋은 루프의 목적은 반복 횟수를 늘리는 것이 아니라 검증 가능한 목표에 수렴하는 것이다. 같은 명령을 그대로 되풀이하기보다 각 반복에서 테스트 결과나 도구 출력으로 상태가 갱신되어야 한다. 새로운 근거 없이 같은 실패가 계속되면 재시도 횟수나 비용을 제한하고 사람에게 제어권을 넘겨야 한다.
예를 들어 회원가입 API에 이메일 중복 검사를 추가하는 작업이라면, 피드백 루프는 다음처럼 더 구체적으로 쓸 수 있다.
요청: 회원가입 API에 이메일 중복 검사를 추가한다.
성공 조건
- 같은 이메일로 두 번 가입하면 409 Conflict를 반환한다.
- 대소문자만 다른 이메일도 중복으로 처리한다.
- 기존 회원가입 성공 응답 형식은 바뀌지 않는다.
검증 명령
- ./gradlew :member:test --tests "*Signup*"
- ./gradlew :member:compileJava
재시도 조건
- 테스트 실패나 컴파일 오류가 발생했고, 로그에서 원인 파일을 특정할 수 있다.
중단 조건
- 같은 테스트가 같은 이유로 3회 실패한다.
- 기존 API 응답 계약과 현재 테스트 기대값이 서로 충돌한다.
- DB 유니크 인덱스 변경이 필요한데 마이그레이션 정책이 문서에 없다.
이렇게 써 두면 에이전트는 “실패했으니 다시 해 봐”라는 막연한 지시 대신, 어떤 결과를 성공으로 봐야 하고 어떤 실패에서는 멈춰야 하는지 알 수 있다.
OpenAI의 에이전트 가이드도 에이전트 실행을 종료 조건에 도달할 때까지 모델과 도구 호출을 반복하는 run으로 설명한다.
하네스 엔지니어링 사례에서는 코드 작성 후 리뷰와 피드백 반영을 검증이 만족될 때까지 반복하는 루프를 사용한다. A practical guide to building agents, Harness engineering: leveraging Codex in an agent-first world
즉 루프 엔지니어링은 하네스와 별개의 개념이라기보다, 하네스가 에이전트의 실행과 피드백을 제어하는 핵심 설계 영역으로 볼 수 있다.
좋은 하네스를 구성하는 여섯 가지 요소
1. 짧은 진입점과 탐색 가능한 지식
AI 지침 파일 하나에 모든 내용을 넣으면 처음에는 편하다. 하지만 프로젝트가 커질수록 컨텍스트를 많이 차지하고, 규칙의 최신 여부와 소유권도 불분명해진다.
더 나은 구조는 진입 문서를 지도처럼 쓰는 방식이다.
AI 진입 문서
├── 항상 지킬 보안·행동 원칙
├── 작업 유형별로 읽을 문서 안내
└── 상세 문서의 위치
├── architecture
├── api-contracts
├── security
├── testing
└── workflow
에이전트는 모든 문서를 매번 읽지 않는다. API 작업이면 API 계약과 보안 규칙을, DB 작업이면 스키마와 마이그레이션 규칙을 추가로 읽는다. 이것이 컨텍스트 라우팅(Context Routing)이다. 작업 종류별로 필요한 정보가 있는 위치를 안내하는 구조다.
예를 들어 AGENTS.md에는 모든 규칙을 길게 쓰지 않고 다음 정도만 둔다.
# AI 작업 진입점
항상 지킬 원칙:
- 사용자가 만든 변경을 임의로 되돌리지 않는다.
- 시크릿, 토큰, 개인정보를 출력하지 않는다.
- 운영 배포, 데이터 삭제, 마이그레이션 실행은 승인 전에는 하지 않는다.
작업별로 읽을 문서:
- API 변경: docs/api-contract.md, docs/testing.md
- DB 변경: docs/database.md, docs/migration.md
- 인증/권한 변경: docs/security.md
- 결제/외부 API 변경: docs/external-integrations.md
완료 보고에는 Changed, Verified, Contracts를 포함한다.
이 문서는 상세 규칙 자체가 아니라 상세 규칙으로 가는 라우터다. 회원가입 API 작업을 맡은 에이전트는 여기서 docs/api-contract.md와 docs/testing.md로 이동하면 된다.
2. 단일 진실 공급원(SSOT)
단일 진실 공급원(Single Source of Truth, SSOT)은 같은 규칙이나 데이터를 여러 곳에 복사하지 않고, 기준이 되는 원본 한 곳에서 관리한다는 뜻이다.
Claude, Codex 등 여러 에이전트를 함께 사용하면 도구별 진입 파일을 따로 둘 수 있다. 그러나 아키텍처 규칙까지 복사해 관리하면 곧 내용이 어긋난다.
공통 규칙은 한 위치에서 관리하고, 도구별 진입 파일에는 다음만 남기는 편이 낫다.
- 해당 도구에서 파일을 읽고 수정하는 방법
- 공통 규칙으로 이동하는 인덱스
- 도구에만 적용되는 예외
중복이 불가피하다면 규칙마다 소유 문서와 충돌 우선순위를 명시해야 한다. 그래야 에이전트가 서로 다른 두 문장을 만났을 때 주변 코드만 보고 임의로 결정하지 않는다.
예를 들어 여러 AI 도구를 쓰는 저장소라면 다음처럼 둔다.
AGENTS.md # Codex 진입점
CLAUDE.md # Claude 진입점
docs/
architecture.md # 아키텍처 규칙의 원본
testing.md # 검증 명령의 원본
security.md # 보안 규칙의 원본
AGENTS.md와 CLAUDE.md에는 “컨트롤러는 저장소를 직접 호출하지 않는다”를 각각 복사하지 않는다. 대신 둘 다 docs/architecture.md를 참조하게 한다.
레이어 의존성 규칙은 docs/architecture.md를 기준으로 한다.
이 파일과 상세 문서가 충돌하면 docs/architecture.md를 우선한다.
이렇게 해야 한 도구의 지침만 갱신되어 다른 도구가 낡은 규칙으로 작업하는 문제를 줄일 수 있다.
3. 위험도에 따른 권한과 승인 게이트
모든 변경을 같은 방식으로 다루면 두 가지 문제가 생긴다. 사소한 오타에도 매번 승인을 기다리거나, 반대로 DB와 인증 변경까지 바로 실행해 버린다.
예를 들어 에이전트가 코드를 읽고 테스트하는 일은 바로 허용한다. 운영 배포나 데이터 삭제에는 승인 게이트를 두고 반드시 사람의 허가를 받게 한다.
위험 등급을 나누면 행동 경계가 명확해진다.
| 위험도 | 예시 | 처리 방식 |
|---|---|---|
| 낮음 | 조회, 분석, 상태 확인 | 바로 수행 |
| 보통 | 국소적인 코드 수정, 테스트 보강 | 범위와 검증 방법 확인 |
| 높음 | API 계약, 여러 모듈, 데이터 모델 변경 | 계획과 영향 범위 승인 |
| 매우 높음 | 인증, 개인정보, 운영 인프라, 외부 부작용 | 롤백을 포함한 명시적 승인 |
자연어로 “조심하라”고만 쓰면 약하다. 파일 쓰기 범위, 실행 가능한 명령, 시크릿 출력 금지, 파괴적 작업 승인 같은 제약을 실행 환경에서도 함께 적용해야 한다. 가드레일은 하나의 거대한 방어막보다 입력·도구·출력 단계의 여러 방어층으로 설계하는 편이 안전하다. A practical guide to building agents
예를 들어 개인 프로젝트에서 바로 적용할 수 있는 승인 규칙은 다음처럼 구체적이어야 한다.
바로 허용
- rg, sed, git diff 같은 조회 명령
- 로컬 단위 테스트 실행
- 작업 범위 안의 코드와 테스트 수정
사용자 확인 후 수행
- 새 라이브러리 설치
- public API 응답 필드 추가·삭제
- DB 마이그레이션 파일 생성
- 여러 서비스에 걸친 동시 수정
명시적 승인 전 금지
- 운영 배포
- 운영 DB 쓰기 작업
- git push
- 시크릿, 토큰, .env 내용 출력
- 테스트 실패를 숨기기 위한 테스트 비활성화
이 정도로 써 두면 에이전트가 “이 변경은 마이그레이션이 필요하므로 먼저 계획을 보고하고 승인을 기다려야 한다”고 판단할 수 있다.
4. 작업을 끝까지 이끄는 표준 워크플로우
에이전트에게 자유만 주면 구현부터 시작하기 쉽다. 안정적인 하네스는 작업 순서를 명시한다.
Discovery → Contract Survey → Plan → Confirm → Implement → Verify → Refresh Docs
- Discovery: 영향 받는 모듈과 도메인 경계를 찾는다.
- Contract Survey: API, DB, 레거시 동작 같은 외부 계약을 확인한다.
- Plan: 각 구현 단계와 검증 방법을 짝지어 적는다.
- Confirm: 위험한 변경은 구현 전에 승인을 받는다.
- Implement: 승인 범위만 최소한으로 수정한다.
- Verify: 컴파일, 테스트, 린트, 구조 검사를 실행한다.
- Refresh Docs: 실제 코드와 명세·시스템 지도를 동기화한다.
이 순서는 에이전트를 느리게 만드는 절차가 아니다. 잘못된 방향으로 빠르게 많은 코드를 만드는 비용을 줄이는 장치다. 단순한 수정에서는 일부 단계를 줄여도 된다.
회원가입 API 예시에 이 워크플로우를 적용하면 다음과 같다.
Discovery
- SignupController, SignupService, UserRepository 위치를 찾는다.
- 기존 회원가입 테스트와 이메일 관련 유틸을 찾는다.
Contract Survey
- docs/api-contract.md에서 회원가입 응답 형식과 오류 코드를 확인한다.
- DB 스키마에서 email unique 제약 여부를 확인한다.
Plan
- 중복 이메일 테스트를 먼저 추가한다.
- 서비스 계층에 중복 검사 로직을 넣는다.
- 컨트롤러 응답 형식은 기존 예외 처리 흐름을 따른다.
Confirm
- DB 인덱스 추가가 필요하면 사용자에게 먼저 묻는다.
- 단순 서비스 로직과 테스트 수정이면 바로 진행한다.
Implement
- 테스트와 구현을 최소 범위로 수정한다.
Verify
- ./gradlew :member:test --tests "*Signup*"
- ./gradlew :member:compileJava
Refresh Docs
- API 오류 코드가 새로 추가되었으면 docs/api-contract.md를 갱신한다.
이 예시는 에이전트에게 “조사하고 계획하고 검증하라”고 말하는 대신, 각 단계에서 실제로 무엇을 봐야 하는지까지 알려 준다.
5. 자연어 규칙을 기계적 검증으로 바꾸기
“컨트롤러가 저장소를 직접 호출하지 않는다”는 문서만으로는 언젠가 위반이 생긴다. 가능한 규칙은 린트나 구조 테스트로 옮겨야 한다.
자연어 규칙 기계적 검증
────────────────────────────────────────────
레이어 의존 방향을 지킨다 → 아키텍처 테스트
시크릿을 커밋하지 않는다 → secret scanner
API 계약을 유지한다 → OpenAPI diff / contract test
관련 테스트를 작성한다 → CI 테스트와 커버리지 기준
포맷을 통일한다 → formatter / linter
문서는 판단의 기준을 설명하고, 자동화는 반복 가능한 부분을 강제한다. 자동 검사를 통과시키려고 검사를 끄거나 기준선을 무조건 갱신하지 못하도록 하는 규칙도 필요하다. 그렇지 않으면 에이전트가 목표가 아니라 “초록색 결과”만 최적화할 수 있다.
예를 들어 “컨트롤러가 저장소를 직접 호출하지 않는다”는 규칙은 문서에만 두면 놓치기 쉽다. Java 프로젝트라면 ArchUnit 같은 구조 테스트로 옮길 수 있다.
@Test
void controllersShouldNotDependOnRepositories() {
JavaClasses classes = new ClassFileImporter()
.importPackages("com.example.member");
noClasses()
.that().resideInAPackage("..controller..")
.should().dependOnClassesThat().resideInAPackage("..repository..")
.check(classes);
}
이 테스트가 있으면 에이전트가 실수로 SignupController에서 UserRepository를 직접 주입해도 검증 단계에서 바로 드러난다. 자연어 규칙은 “왜 그렇게 해야 하는지”를 설명하고, 구조 테스트는 “정말 지켰는지”를 확인한다.
6. 완료 증거와 피드백 루프
“수정했습니다”는 완료 증거가 아니다. 좋은 하네스는 최종 보고 형식까지 계약으로 만든다.
Changed
- 무엇을 왜 바꿨는가
Verified
- 어떤 명령을 실행했고 결과가 어땠는가
- 실행하지 못한 검증과 이유는 무엇인가
Contracts
- API, DB, 보안, 아키텍처 경계에 영향이 있는가
여기서 테스트 실패, 환경이 없어 미실행, 애초에 해당 없음은 서로 구분해야 한다. 실패 기록이 쌓이면 다음 질문도 바뀐다.
회원가입 API 작업의 완료 보고는 다음처럼 구체적이어야 한다.
Changed
- SignupService에 이메일 중복 검사를 추가했다.
- 이메일 비교 전에 lower-case 정규화를 적용했다.
- 대소문자만 다른 이메일 중복 테스트를 추가했다.
Verified
- ./gradlew :member:test --tests "*Signup*" 통과
- ./gradlew :member:compileJava 통과
Contracts
- 회원가입 성공 응답 형식은 변경하지 않았다.
- 중복 이메일 오류는 기존 DuplicateEmailException 처리 흐름을 사용한다.
- DB 마이그레이션은 추가하지 않았다.
반대로 검증을 못 했다면 “검증 완료”처럼 쓰면 안 된다.
Verified
- ./gradlew :member:test --tests "*Signup*" 실행 실패
- 이유: 로컬 Docker DB가 실행되어 있지 않음
- 미검증 위험: 실제 repository 연동 테스트는 확인하지 못함
“AI가 왜 또 틀렸지?”가 아니라 “하네스에 어떤 능력이나 피드백이 빠져 있었지?”
사례 1 — 개인 MSA 프로젝트의 문맥 라우팅
개인적으로 운영하는 주식 분석 MSA 프로젝트에서는 여러 마이크로서비스와 공통 모듈, 프론트엔드, Docker 인프라를 한 저장소에서 관리한다. 처음에는 AI 지침에 프로젝트 설명을 계속 추가했지만, 문서가 길어질수록 정작 현재 작업과 관련된 규칙을 놓치는 문제가 생겼다.
이를 다음과 같이 바꿨다.
짧은 AI 진입 문서
├── 항상 적용: 시크릿 보호, 사용자 변경 보존, 최소 수정
├── API 작업: MSA 구조 + API 계약 + 보안 규칙
├── DB 작업: 서비스 경계 + migration + 테스트 규칙
├── 외부 API 작업: 보안 + 쿼터 + 재시도 규칙
└── 인프라 작업: 프로필 + 포트 + 배포 문서
핵심은 세 가지다.
첫째, 상세 규칙의 단일 진실 공급원을 두었다. 도구별 진입 파일은 달라도 아키텍처, 보안, API, 워크플로우 규칙은 같은 원문을 참조한다. 어떤 AI 도구를 사용하더라도 프로젝트의 핵심 원칙이 달라지지 않는다.
둘째, MSA의 Bounded Context(도메인별 책임 경계)를 행동 규칙으로 만들었다. 한 서비스가 다른 서비스의 데이터베이스를 직접 사용하거나 다른 도메인의 외부 API 호출 책임을 복제하지 못하도록 했다. 단순히 “MSA 원칙 준수”라고 쓰는 대신, 작업 시작 시 영향 서비스와 DB를 목록으로 만들고 경계 침범 여부를 확인하게 했다.
셋째, 구현과 검증을 하나의 루프로 묶었다. API 변경은 실제 실행 결과에서 생성한 명세와 비교하고, 구조가 바뀌면 시스템 지도를 갱신한다. 완료 보고에는 테스트 명령뿐 아니라 API 계약, DB 변경, 시크릿 노출 여부를 함께 적는다.
이 구조의 효과는 AI가 더 많은 내용을 외우게 된 것이 아니다. 필요한 순간에 필요한 근거로 이동할 수 있게 된 것이다.
사례 2 — 사내 레거시 CMS 현대화 프로젝트의 제약 설계
두 번째 사례는 회사나 서비스가 식별되지 않도록 일반화한 사내 레거시 CMS 현대화 프로젝트다. 오래된 서버 애플리케이션을 Spring 기반으로 전환하지만, 기존 데이터베이스와 프로시저 계약, 화면 동작을 상당 부분 유지해야 하는 프로젝트다.
이런 전환 작업에서 AI에게 “기존 기능과 똑같이 Java로 바꿔 줘”라고 요청하는 것은 위험하다. 코드 문법은 바꿀 수 있어도 다음 사실은 추론해서는 안 되기 때문이다.
- 프로시저 파라미터의 순서와 타입
- 여러 결과 집합의 인덱스와 컬럼 의미
- 데이터베이스 간 트랜잭션 경계
- 권한과 개인정보 공개 범위
- 레거시 화면의 숨은 상태 전이
그래서 이 프로젝트의 하네스는 추측 금지를 중심으로 구성했다.
사용자 요구
↓
적용 경로에 맞는 규칙 선택
↓
주변 Java 코드 + 원본 레거시 코드 + DB 계약 대조
↓
계약이 명확한가? ── 아니오 → 질문하고 대기
│
예
↓
레이어·네이밍·트랜잭션 규칙에 따라 구현
↓
Controller / Service / E2E 테스트로 역할별 검증
↓
자동 준수 검사 + diff 검사 + 완료 보고
규칙은 파일 경로별로 선택해서 적용한다. Java 코드에는 네이밍·예외·레이어 규칙을, 컨트롤러에는 뷰 반환과 요청 처리 규칙을, 템플릿에는 UI 규칙을, 테스트에는 테스트 유형별 규칙을 적용한다. 모든 규칙을 모든 작업에 넣지 않으면서도 해당 파일을 수정할 때 필요한 제약은 빠지지 않는다.
또한 자연어 지침을 자동 준수 검사로 옮겼다. 예를 들면 다음과 같은 항목이다.
- 컨트롤러가 저장소를 직접 의존하지 않는가
- 화면에 영속 엔티티를 직접 전달하지 않는가
- 쓰기 작업에 감사 로그와 올바른 트랜잭션 경계가 있는가
- 테스트가 비활성화되어 실패를 우회하지 않는가
- 기존 위반 목록을 새 변경의 면제 수단으로 넓히지 않았는가
이 사례에서 하네스의 역할은 AI에게 프로젝트 지식을 많이 주는 데 그치지 않는다. 레거시 계약을 모르면 멈추고, 확인된 사실만 구현하며, 결과를 역할별 테스트로 증명하게 하는 것이다.
두 사례에서 공통으로 배운 것
| 원칙 | 개인 MSA 프로젝트 | 사내 레거시 현대화 프로젝트 |
|---|---|---|
| 진입점 | 짧은 인덱스에서 작업별 문서로 이동 | 변경 경로에 맞는 규칙만 적용 |
| 핵심 위험 | 서비스 경계 침범, 시크릿, 외부 부작용 | 레거시·DB 계약 추측, 개인정보, 트랜잭션 |
| 검증 | 모듈 테스트, API 명세, 시스템 지도 | 컴파일, 계층별 테스트, 자동 준수 검사 |
| 중단 조건 | 위험 변경의 승인 부재 | 계약 근거가 불명확하거나 규칙이 충돌함 |
| 완료 증거 | 변경·검증·계약 영향 보고 | 변경·검증·미실행 사유 보고 |
도메인은 다르지만 공통점은 분명하다.
- AI에게 모든 것을 기억시키지 않고 찾는 방법을 준다.
- 권고로 끝낼 규칙과 기계적으로 강제할 규칙을 구분한다.
- 구현 전에 성공 조건과 중단 조건을 정한다.
- 테스트 결과뿐 아니라 계약과 보안 영향까지 완료 증거에 포함한다.
- 실패할 때 같은 프롬프트를 반복하지 않고 환경의 결손을 고친다.
하네스를 처음 만들 때의 순서
처음부터 거대한 에이전트 플랫폼을 만들 필요는 없다. 저장소 안에서 다음 순서로 시작하면 된다.
예를 들어 기존 프로젝트에 AI 코딩 도구를 처음 도입한다면, 별도 플랫폼보다 다음 세 파일과 기존 자동화로 시작한다.
프로젝트 저장소/
├── AGENTS.md # AI가 가장 먼저 읽을 짧은 안내서
├── docs/
│ ├── architecture.md # 구조와 책임 경계
│ └── testing.md # 작업별 검증 명령
└── 기존 빌드·테스트·린트 설정
파일 이름은 도구마다 달라도 된다. 이름 자체보다, 에이전트가 진입 문서에서 필요한 상세 문서와 실행 가능한 검증 명령을 쉽게 찾는 구조가 필요하다.
1단계 — 반복되는 실패 세 가지를 기록한다
AI가 자주 틀리는 지점을 구체적으로 적는다. “코드 품질이 낮다”가 아니라 “다른 모듈의 저장소를 직접 호출한다”, “테스트를 실행하지 않고 완료했다고 한다”처럼 관찰 가능한 문장이어야 한다.
처음에는 별도 도구 없이 docs/ai-failures.md 같은 파일 하나면 충분하다.
# AI 반복 실패 기록
## 2026-07-20 회원가입 API 수정
실패:
- 이메일 중복 검사를 추가했지만 대소문자 차이를 중복으로 보지 않았다.
원인:
- 이메일 정규화 규칙이 문서화되어 있지 않았다.
- 관련 테스트에도 대소문자 케이스가 없었다.
하네스 개선:
- docs/api-contract.md에 이메일 비교 규칙을 추가한다.
- SignupServiceTest에 대소문자 중복 테스트를 추가한다.
이 기록은 AI를 평가하려고 쓰는 문서가 아니다. 다음번에 같은 실수를 줄일 문서·테스트·도구의 빈틈을 찾기 위한 기록이다.
2단계 — 짧은 진입 문서를 만든다
프로젝트 개요, 항상 지킬 원칙, 상세 문서로 가는 링크만 둔다. 진입 문서가 백과사전이 되기 시작하면 내용을 별도 문서로 분리한다.
예를 들어 처음 만드는 AGENTS.md는 이 정도 길이면 된다.
# Project Guide for AI Agents
이 저장소는 회원, 주문, 결제 모듈로 나뉜 Spring Boot 프로젝트다.
항상 지킬 원칙:
- 사용자 변경을 임의로 되돌리지 않는다.
- 시크릿과 개인정보를 출력하지 않는다.
- 테스트를 실행하지 못하면 그 이유를 완료 보고에 적는다.
작업별 문서:
- API 변경: docs/api-contract.md
- DB 변경: docs/database.md
- 테스트: docs/testing.md
- 아키텍처 경계: docs/architecture.md
핵심은 “모든 것을 여기에 쓴다”가 아니라 “AI가 다음에 어디를 읽어야 하는지 알게 한다”는 점이다.
3단계 — 성공과 중단 조건을 쓴다
각 작업은 무엇을 통과해야 완료인지, 어떤 위험에서는 사용자에게 제어권을 돌려줘야 하는지 정한다.
예를 들어 docs/testing.md에 작업 유형별 완료 조건을 둔다.
# 작업별 완료 조건
## 회원가입 API 변경
성공 조건:
- 성공 응답의 JSON 필드가 기존 계약과 같다.
- 중복 이메일은 409를 반환한다.
- 이메일 비교는 소문자 정규화 기준이다.
필수 검증:
- ./gradlew :member:test --tests "*Signup*"
- ./gradlew :member:compileJava
중단 조건:
- DB unique index 추가가 필요하다.
- 기존 API 문서와 테스트 기대값이 충돌한다.
- 같은 실패가 3회 반복된다.
이 조건이 있으면 에이전트가 스스로 끝낼 수 있는 작업과 사람에게 넘겨야 하는 작업을 구분하기 쉬워진다.
4단계 — 검증 명령을 저장소에 둔다
빌드, 관련 테스트, 린트, 계약 검사를 에이전트가 그대로 실행할 수 있는 명령으로 제공한다. 문서에 “충분히 테스트”라고만 쓰는 것보다 실행 가능한 한 줄이 낫다.
반복해서 쓰는 검증은 스크립트로 묶어 둔다.
#!/usr/bin/env bash
set -euo pipefail
./gradlew :member:compileJava
./gradlew :member:test --tests "*Signup*"
파일 이름을 scripts/verify-signup.sh로 두고 docs/testing.md에서 이 스크립트를 가리키면 된다.
회원가입 API를 수정한 뒤에는 다음 명령을 실행한다.
```bash
scripts/verify-signup.sh
```
에이전트 입장에서는 “무엇을 돌려야 하지?”를 추측하지 않아도 된다.
5단계 — 반복 위반을 자동화한다
모든 규칙을 한 번에 린트로 만들지 않는다. 실제로 반복되는 실수, 피해가 큰 실수, 문자열이나 구조로 안정적으로 판단 가능한 규칙부터 자동화한다.
예를 들어 반복 위반이 “컨트롤러에서 저장소를 직접 호출한다”라면 문서만 보강하지 말고 구조 테스트를 추가한다.
반복 실패:
- SignupController가 UserRepository를 직접 주입했다.
문서 보강:
- docs/architecture.md에 Controller → Service → Repository 의존 방향을 명시한다.
자동화:
- ArchUnit 테스트로 controller 패키지가 repository 패키지에 의존하지 못하게 한다.
검증 명령:
- ./gradlew :member:test --tests "*Architecture*"
처음부터 모든 규칙을 자동화하려고 하면 비용이 커진다. 실제로 반복되고, 깨졌을 때 영향이 크고, 기계적으로 판정할 수 있는 규칙부터 옮기는 편이 낫다.
6단계 — 실패를 하네스 개선 작업으로 환류한다
실패 원인을 다음 중 하나로 분류하면 개선 지점을 찾기 쉽다.
- 지식 부족: 문서나 시스템 지도가 없었다.
- 탐색 실패: 문서는 있었지만 진입점에서 찾을 수 없었다.
- 도구 부족: 확인하거나 실행할 방법이 없었다.
- 경계 부족: 위험한 행동을 막는 승인·권한 장치가 없었다.
- 피드백 부족: 잘못된 결과를 잡아낼 테스트나 린트가 없었다.
예를 들어 회원가입 API 작업에서 같은 문제가 반복되면 다음처럼 처리한다.
| 실패 | 분류 | 하네스 개선 |
|---|---|---|
| 이메일 대소문자 중복을 놓침 | 지식 부족, 피드백 부족 | API 문서에 정규화 규칙 추가, 테스트 추가 |
| 테스트를 실행하지 않고 완료 보고 | 피드백 부족 | 완료 보고 템플릿에 Verified 필수화 |
| 컨트롤러에서 저장소 직접 호출 | 경계 부족 | 아키텍처 문서 보강, 구조 테스트 추가 |
| DB 인덱스를 임의로 추가 | 경계 부족 | 마이그레이션은 승인 필요로 분류 |
| 관련 문서가 있는데 읽지 않음 | 탐색 실패 | AGENTS.md의 작업별 문서 라우팅 보강 |
이 단계까지 가면 피드백 루프는 코드 수정 안에서만 돌지 않는다. 실패가 문서, 테스트, 권한 규칙, 완료 보고 형식으로 되돌아가고, 그 결과 다음 작업의 기본 환경이 조금 더 좋아진다.
하네스 엔지니어링의 한계와 비용
하네스는 에이전트의 실수를 줄이는 데 도움이 되지만 공짜로 얻어지는 안전장치는 아니다. 잘못 설계하면 개발을 돕는 기반이 아니라 또 하나의 복잡한 시스템이 된다.
초기 구축과 지속적인 유지 비용이 든다
프로젝트 구조를 문서화하고, 검증 명령을 정리하고, 권한과 승인 경계를 설계하려면 상당한 시간이 필요하다. 더 큰 문제는 한 번 만든 뒤에도 코드와 조직의 변화에 맞춰 계속 갱신해야 한다는 점이다. 작은 프로젝트나 일회성 작업에서는 하네스를 만드는 비용이 직접 작업하는 비용보다 클 수 있다.
처음부터 완성형 플랫폼을 만들 필요는 없다. 반복적으로 발생하고 피해가 큰 실패부터 해결하면 된다. 사용 빈도와 사고 비용이 낮은 작업에는 짧은 지침과 기존 테스트만으로도 충분할 수 있다.
낡거나 잘못된 규칙을 더 일관되게 확산할 수 있다
하네스가 있다고 해서 그 안의 지식이 참이라는 보장은 없다. 오래된 아키텍처 문서나 잘못 설정된 완료 조건이 단일 진실 공급원이 되면, 에이전트는 같은 오류를 더 빠르고 일관되게 반복한다. 자동화된 규칙은 사람이 매번 판단할 때보다 오류의 영향 범위를 키울 수도 있다.
문서와 규칙에는 소유자와 갱신 조건을 두고, 실제 코드·실행 결과와 어긋나는지 주기적으로 점검해야 한다. 특히 큰 장애 없이 계속 통과하는 검사도 여전히 유효한지 검토할 필요가 있다.
검증 가능한 것만 과도하게 최적화할 수 있다
테스트, 린트, 커버리지처럼 수치와 성공 여부가 분명한 조건은 자동화하기 쉽다. 반면 요구사항의 의도, 사용성, 장기적인 설계 품질처럼 정형화하기 어려운 항목은 빠지기 쉽다. 그 결과 모든 검사를 통과했지만 실제 사용자에게는 잘못된 기능이 만들어질 수 있다.
검사 통과는 정확성의 충분조건이 아니라 현재 정의한 오류를 발견하지 못했다는 증거에 가깝다. 위험이 큰 변경에는 사용자 시나리오 검토, 코드 리뷰, 운영 관찰처럼 기계적 검사 밖의 판단을 남겨 두어야 한다.
실행 시간과 비용이 증가한다
문서 탐색, 계획 수립, 반복 테스트, 리뷰 에이전트 호출을 매 작업마다 수행하면 토큰 사용량과 CI 시간, 외부 도구 비용이 늘어난다. 피드백 루프가 길수록 결과가 좋아질 가능성은 있지만, 잘못된 종료 조건에서는 같은 문제를 비싸게 반복할 뿐이다.
작업 위험도별로 읽을 문서와 실행할 검증 범위를 다르게 잡고, 재시도 횟수·시간·비용의 상한을 설정해야 한다. 전체 테스트는 고위험 변경이나 병합 단계에 두고, 개발 중에는 영향 범위에 맞는 검사를 먼저 실행하는 식의 계층화가 필요하다.
지나친 제약은 탐색과 생산성을 떨어뜨린다
규칙과 승인 게이트가 많아질수록 에이전트는 안전해질 수 있지만, 새로운 해결책을 탐색하거나 사소한 변경을 빠르게 처리하기 어려워진다. 충돌하는 규칙 때문에 실제로는 안전한 작업도 멈추고 사람의 승인을 기다리는 거짓 양성도 생긴다.
모든 작업을 같은 절차에 넣지 말고 위험도별로 자율성의 범위를 달리해야 한다. 또한 규칙을 우회할 수 없게 만드는 것만큼, 규칙이 잘못되었을 때 근거와 함께 이의를 제기하고 사람이 예외를 승인할 수 있는 경로도 필요하다.
인간의 책임을 대신하지 못한다
하네스가 촘촘할수록 결과를 자동으로 신뢰해도 된다는 착각이 생길 수 있다. 그러나 요구사항 자체가 틀렸거나 테스트가 현실을 충분히 반영하지 못하면 에이전트와 하네스가 모두 정상적으로 작동해도 잘못된 결과가 나온다. 보안, 개인정보, 법적 의무, 운영 장애처럼 영향이 큰 결정의 책임도 자동화에 넘길 수 없다.
하네스는 사람을 제거하는 장치가 아니라 사람이 어디에 판단을 집중해야 하는지 정하는 장치에 가깝다. 최종 책임자, 승인 대상, 사고 발생 시 추적할 기록을 명확히 두어야 한다.
하네스의 효과는 규칙과 도구를 많이 넣는다고 커지지 않는다. 줄어든 실패 비용이 구축·유지·실행 비용보다 큰지 봐야 한다. 반복 실패율, 검증 시간, 사람의 개입 횟수, 운영 결함 같은 지표를 함께 보며 복잡성을 조절해야 한다.
주의할 점
문서를 많이 쓰는 것이 목적이 아니다
문서가 많아도 오래되었거나 서로 충돌하면 오히려 성능을 떨어뜨린다. 각 문서의 소유권, 갱신 조건, 실제 코드와의 연결을 정해 두어야 한다.
에이전트를 지나치게 절차에 가두지 않는다
모든 오타 수정에 긴 계획과 승인을 요구하면 하네스가 병목이 된다. 변경 위험도가 낮으면 절차를 줄일 수 있어야 한다.
자연어 가드레일만 믿지 않는다
“절대 시크릿을 출력하지 마라”는 필요한 지침이지만, 접근 권한 제한과 시크릿 스캔이 함께 있어야 한다. 모델의 준수와 시스템의 강제는 서로 다른 방어층이다.
테스트 통과를 목표 그 자체로 만들지 않는다
에이전트는 주어진 평가 기준을 최적화한다. 실패한 테스트를 비활성화하거나 검증을 약화해도 성공으로 보이는 구조라면 하네스가 잘못 설계된 것이다. 테스트 우회 금지와 변경된 계약의 직접 검증이 함께 필요하다.
마무리
프롬프트 엔지니어링은 AI에게 일을 잘 설명하는 기술이다. 하네스 엔지니어링은 AI가 일을 잘할 수밖에 없는 환경에 가깝게 만드는 기술이다.
좋은 하네스가 있어도 모델은 실수한다. 하지만 실수가 테스트에서 드러나고, 위험한 작업에서는 멈추며, 실패 원인이 다음 실행의 규칙과 도구로 축적된다. 이 피드백 루프가 만들어지면 AI 활용은 개인의 프롬프트 감각에서 팀과 저장소가 공유하는 엔지니어링 자산으로 바뀐다.
마지막에 남는 질문은 “어떤 모델이 코드를 가장 잘 쓰는가?” 하나가 아니다.
이 에이전트가 우리 프로젝트에서 올바른 문맥을 찾고, 허용된 행동만 하고, 결과가 맞는지 증명할 수 있는가?
그 질문에 프로젝트 구조와 자동화로 답하는 일이 하네스 엔지니어링이다.
용어 정리
| 용어 | 의미 |
|---|---|
| AI 에이전트(Agent) | AI 모델이 파일 검색, 코드 수정, 명령 실행 같은 도구를 사용해 여러 단계의 작업을 수행하도록 만든 프로그램 |
| 하네스(Harness) | 에이전트 주변의 프로젝트 지식, 도구, 권한, 작업 절차, 검증 환경 전체 |
| 컨텍스트(Context) | 모델이 현재 판단할 때 참고할 수 있도록 제공된 정보 |
| 컨텍스트 창(Context Window) | 모델이 한 번에 참고할 수 있는 정보량의 범위 |
| 컨텍스트 라우팅(Context Routing) | 작업 종류별로 필요한 문서와 정보가 있는 곳으로 에이전트를 안내하는 구조 |
| 피드백 루프(Feedback Loop) | 실행 결과를 다음 판단과 수정에 다시 반영하는 반복 과정 |
| 가드레일(Guardrail) | 위험하거나 허용되지 않은 행동을 막는 규칙과 기술적 제한 |
| 승인 게이트(Approval Gate) | 고위험 작업을 실행하기 전에 사람의 허가를 받는 단계 |
| 린트(Lint) | 코드를 실행하기 전에 문법, 스타일, 잠재적인 오류와 규칙 위반을 자동으로 검사하는 작업 |
| 계약(Contract) | API 형식이나 기존 동작처럼 다른 코드와 시스템이 의존하는 약속 |
| 관찰 가능성(Observability) | 에이전트가 무엇을 했고 어디서 실패했는지 기록으로 확인할 수 있는 상태 |
