Skip to main content
Summary

판단은 모델이 아니라 도구가 한다

AI 에이전트에게 "코딩 스타일을 지켜라", "타입 오류를 만들지 마라", "테스트를 통과시켜라"라고 말할 수는 있다. 하지만 이 지시는 확률적으로만 작동한다. 모델은 대부분의 경우 잘 따르지만, 긴 작업 중 컨텍스트가 흐려지거나 여러 파일을 오가다 보면 규칙을 놓친다.

하네스의 관점은 다르다. 포매터, 린터, 타입 체커, 테스트를 모델이 기억해야 할 지시가 아니라 모델 바깥의 검증 계층으로 둔다. Birgitta Böckeler는 코딩 에이전트 하네스를 사전 가이드와 사후 센서의 조합으로 설명하며, 린터·타입 체커·테스트 같은 계산형 센서는 빠르고 재현 가능한 신호를 제공한다고 정리한다.1

핵심 원칙은 단순하다.

LLM에게 린터 일을 시키지 말고, 린터 결과를 LLM이 고치게 하라.

LLM은 포맷 오류를 찾는 데 느리고 비싸다. 반면 포매터는 같은 입력에 항상 같은 출력을 만든다. 린터는 파일과 라인, 규칙 이름을 알려준다. 타입 체커는 기대한 타입과 실제 타입의 차이를 보여준다. 테스트는 기대 동작과 실제 동작의 차이를 드러낸다. 이런 신호는 프롬프트 지시보다 훨씬 일관적이다. HumanLayer의 CLAUDE.md 작성 가이드에서도 LLM을 비싼 린터처럼 쓰지 말고 가능한 곳에는 결정론적 도구를 우선하라고 강조한다.2

검증 계층은 보통 다음 순서로 쌓는다.

계층역할에이전트에게 주는 신호
포매터공백, 줄바꿈, import 정렬 등 형식 통일자동 수정된 파일 또는 남은 포맷 실패
린터금지 패턴, 안전 규칙, 팀 컨벤션 검사파일·라인·규칙·수정 힌트
타입 체커데이터 구조와 함수 계약 확인기대 타입과 실제 타입의 불일치
단위 테스트함수와 모듈의 동작 확인실패 케이스, 입력, 기대값, 실제값
구조 테스트레이어 의존성, 파일 위치, 아키텍처 규칙 확인어떤 경계를 위반했는지

OpenAI의 하네스 사례도 구조적 테스트와 커스텀 린터로 로깅 방식, 네이밍, 파일 크기, 플랫폼별 신뢰성 규칙을 강제한다고 설명한다.3 중요한 점은 이런 규칙이 '조언'이 아니라 '통과 조건'이라는 것이다.

결정론적 도구와 품질 피드백 루프

파일 수정 직후 도는 가장 짧은 루프

품질 피드백은 늦을수록 비싸다. PR을 올린 뒤 CI에서 깨지는 것보다, 파일을 수정한 직후 깨지는 편이 낫다. 에이전트가 방금 만든 코드의 맥락을 아직 기억하고 있기 때문이다.

실무에서는 파일 쓰기 직후 실행되는 훅을 둔다. Claude Code의 PostToolUse 같은 생명주기 훅이 대표적이다. 이름은 도구마다 달라도 구조는 같다.

파일 수정
  → 포매터 자동 실행
  → 린터 자동 수정 가능한 항목 수정
  → 타입 검사
  → 변경 파일과 관련된 테스트 실행
  → 실패 로그 압축
  → 다음 에이전트 입력에 주입
  → 에이전트가 다시 수정

이 루프의 목표는 실패를 사용자에게 넘기지 않는 것이다. 에이전트가 파일을 수정했고, 린터가 실패했고, 그 결과를 에이전트가 읽고 다시 고친다. 사용자는 중간 오류를 보는 사람이 아니라 최종 변경을 검토하는 사람이 된다.

다만 모든 검사를 매번 전체 실행할 필요는 없다. 빠른 검사는 자주, 느린 검사는 늦게 실행한다.

  • 파일 저장 직후: 포매터, 해당 파일 린트

  • 작은 변경 후: 타입 체크, 관련 단위 테스트

  • 완료 선언 전: 전체 린트, 전체 타입 체크, 핵심 테스트

  • PR 또는 병합 전: 전체 테스트, 빌드, 보안 검사, 구조 테스트

이렇게 하면 피드백은 왼쪽으로 당기되 비용은 통제할 수 있다. PostToolUse 훅을 사용할 때는 단순히 로그를 출력하는 것보다, 에이전트가 다음 턴에서 읽을 수 있는 추가 컨텍스트로 실패 요약을 주입하는 편이 효과적이다. 관련 실무 가이드에서는 자동 수정 가능한 항목을 먼저 처리하고, 남은 진단만 에이전트 컨텍스트에 넣는 패턴을 제안한다.4

실패는 네 갈래로 분기한다

나쁜 피드백 루프는 모든 실패를 같은 방식으로 재시도한다. 테스트가 실패하든, 네트워크가 끊기든, 타입이 틀리든 그냥 "다시 해"라고 한다. 이 방식은 비용만 늘린다.

실패는 최소한 네 갈래로 나눠야 한다.

실패 유형예처리
자동 수정 가능포맷, import 정렬, 일부 린트 fix도구가 먼저 수정
일시적 실패네트워크 타임아웃, rate limit짧은 대기 후 retry
추론 오류잘못된 인자, 잘못된 타입, 실패 테스트원인 요약 후 reflect
구조적 한계반복 실패, 모호한 요구사항, 권한 필요사람에게 escalate

retry는 같은 입력으로 다시 시도하는 것이다. 일시적 장애에는 유효하다. 그러나 잘못된 타입을 넣은 코드에 retry를 걸면 같은 오류가 반복될 가능성이 높다.

reflect는 실패 원인을 에이전트에게 알려주고 다음 시도를 바꾸게 하는 것이다. 이때 피드백은 길 필요가 없다. "무엇을 했는지", "왜 실패했는지", "다음에 무엇을 바꿔야 하는지"만 있으면 된다. 실패를 retry와 reflect로 구분하고, 반복 실패에는 사람 검토로 넘기는 구조가 필요하다는 점은 하네스 피드백 루프의 핵심 패턴으로 자주 언급된다.5

반드시 출구도 있어야 한다. 예를 들어 같은 테스트가 3번 연속 실패하거나, 누적 토큰 비용이 임계값을 넘거나, 에이전트가 서로 다른 수정으로 같은 오류를 되살리면 루프를 멈춘다. 좋은 루프는 영원히 고치는 루프가 아니라, 고칠 수 있는 실패와 사람이 봐야 하는 실패를 구분하는 루프다.

피드백은 줄일수록 강해진다

실패 로그를 통째로 붙여 넣는 것은 좋은 피드백이 아니다. 수백 줄의 빌드 로그, 중복된 stack trace, 이미 자동 수정된 포맷 메시지는 컨텍스트를 오염시킨다. 에이전트는 중요한 한 줄을 놓치고 엉뚱한 파일을 고칠 수 있다.

피드백 최소화의 원칙은 다음과 같다.

  1. 자동 수정 가능한 것은 먼저 도구가 고친다.

  2. 남은 오류만 전달한다.

  3. 파일, 라인, 규칙, 실패 테스트를 중심으로 요약한다.

  4. 같은 오류는 하나로 묶는다.

  5. 전체 로그가 필요하면 파일 경로만 알려주고 본문에는 넣지 않는다.

예를 들어 에이전트에게 줄 피드백은 다음 정도면 충분하다.

검증 실패: pnpm typecheck

1) src/user/create-user.ts:42
   TS2322: UserId | null 값을 UserId에 할당할 수 없습니다.
   다음 시도: null 분기를 먼저 처리하거나, 호출 전에 존재 여부를 검증하세요.

2) src/user/create-user.test.ts
   실패 테스트: createUser rejects duplicate email
   기대값: DuplicateEmailError
   실제값: Error
   다음 시도: 원시 Error 대신 도메인 에러를 사용하세요.

이 메시지는 짧지만 행동을 바꿀 정보가 있다. 어떤 명령이 실패했는지, 어느 파일을 봐야 하는지, 무엇이 기대와 달랐는지, 다음 수정 방향이 무엇인지가 들어 있다. 반대로 "테스트가 실패했습니다. 다시 고치세요"는 정보가 거의 없다.

린터 메시지 자체도 에이전트 친화적으로 설계할 수 있다. "규칙 위반"이라고만 하지 말고 "이 프로젝트에서는 console.log 대신 logger.info({ event, ...data })를 사용하세요"처럼 수정 방향을 담는다. 그러면 린터 결과가 곧 다음 프롬프트가 된다.

완료 게이트는 착각을 막는다

에이전트는 종종 스스로 완료했다고 착각한다. 파일 몇 개를 수정했고, 눈에 보이는 오류가 사라졌고, 그래서 "완료했습니다"라고 말한다. 하지만 타입 체크를 돌리지 않았거나, 관련 테스트를 빠뜨렸거나, 변경 범위가 요구사항보다 넓을 수 있다.

이를 막는 장치가 Completion Gate다. 에이전트가 완료를 선언하는 순간, 하네스가 다음을 확인한다.

  • 필수 검사가 실제로 실행되었는가?

  • 실행된 검사 결과가 모두 통과했는가?

  • 변경 파일 범위가 작업 계획과 일치하는가?

  • 테스트를 삭제하거나 약화하지 않았는가?

  • 금지된 파일이나 설정을 건드리지 않았는가?

  • 새로 생긴 실패를 "기존 실패"라고 넘기지 않았는가?

검사가 실패했거나 실행 기록이 없으면 종료를 막는다. 이때도 단순 차단이 아니라 다음 행동을 알려줘야 한다.

완료 게이트 실패
- pnpm test가 실행되지 않았습니다.
- 변경 파일: src/payment/refund.ts, src/payment/refund.test.ts
- 다음 행동: 관련 테스트를 실행하고 실패 시 수정하세요. 통과 로그가 있어야 완료할 수 있습니다.

완료 게이트는 에이전트를 불신하기 위한 장치가 아니다. 완료의 정의를 모델의 자기 보고에서 검증 가능한 상태로 옮기는 장치다. "했다고 말함"이 아니라 "검사가 통과함"이 완료 조건이 된다.

결함은 하네스에 누적되어야 한다

품질 피드백 루프의 마지막 단계는 누적이다. 에이전트가 만든 결함을 사람이 한 번 고치고 끝내면 다음 세션에서 같은 문제가 반복된다. 하네스 엔지니어링에서는 결함을 발견할 때마다 저장소 수준의 검증으로 바꾼다.

  • 같은 포맷 문제가 반복된다 → 포매터 설정을 추가한다.

  • 금지 API를 계속 쓴다 → 린터 규칙을 만든다.

  • 레이어 의존성을 어긴다 → 구조 테스트를 추가한다.

  • 버그가 재발했다 → 회귀 테스트를 추가한다.

  • 테스트를 지우고 통과시킨다 → 테스트 삭제 감지 게이트를 둔다.

이것이 품질의 래칫이다. 한 번 조인 규칙은 다음 세션에도 남는다. 개인의 프롬프트 노하우가 아니라 저장소의 자동 검증 자산으로 축적된다.

도입 순서는 거창할 필요가 없다. 먼저 format, lint, typecheck, test를 하나의 check 명령으로 묶는다. 그다음 파일 수정 직후 빠른 검사를 실행한다. 이후 완료 게이트를 붙이고, 반복 결함을 회귀 테스트로 바꾼다. 마지막으로 구조 테스트와 커스텀 린터를 추가한다.

좋은 하네스는 에이전트를 더 똑똑하게 만드는 것처럼 보이지만, 실제로는 에이전트가 틀렸을 때 바로 알 수 있는 환경을 만든다. 결정론적 도구가 판단하고, 에이전트가 수정하고, 게이트가 종료를 통제한다. 이 루프가 있어야 AI 코딩은 빠른 생성에서 신뢰 가능한 변경으로 넘어간다.

참고

1Harness engineering for coding agent users

2Writing a good CLAUDE.md

3Harness engineering: leveraging Codex in an agent-first world

4Harness Engineering Best Practices for Claude Code / Codex Users, Explained Plainly

5Harness Engineering 101 : Feedback Loop — 실패를 고치게 만드는 반복 구조

책 목록

AI 하네스 엔지니어링: 신뢰 가능한 에이전트를 만드는 실행 구조

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기