AI 코딩을 쓰다 보면 처음에는 속도에 놀라게 된다.
기능 설명을 던지면 서비스 코드가 나오고, 테스트도 생기고, 리팩터링까지 제안한다.
예전 같으면 반나절은 걸렸을 작업이 몇 분 안에 초안으로 나온다.
그래서 자연스럽게 이런 생각이 든다.
이제 구현은 AI에게 맡기고 사람은 방향만 보면 되는 것 아닐까?
하지만 실제 프로젝트에 붙여보면 문제는 다르게 나타났다.
AI가 잘못된 방향으로 코드를 짜서 문제가 생기는 게 아니었다.
오히려 꽤 그럴듯하게 잘 짰다.
문제는 그 코드가 정말 우리가 합의한 계약과 규칙을 지키는지
그리고 AI가 스스로 만든 테스트로 자기 구현을 통과시키고 있는 건 아닌지 확인하기 어렵다는 점이었다.
AI에게 구현과 테스트를 한 번에 맡기면 AI는 자기 구현이 통과하기 쉬운 기준을 만들 수 있다.
모델은 요구사항의 빈틈을 메우려 하고 그 과정에서 자신이 만든 해석을 정답처럼 고정한다.
그러면 완성된 장난감이지만 조금이라도 툭 건드리면 무너지는 레고와 같다.
그래서 프로젝트에서 AI가 넘지 못할 구조를 먼저 만들기로 했다.
좋은 모델을 고르는 것도 중요하다.
하지만 좋은 모델도 감독 없이 두면 계약을 왜곡할 수 있고 설계가 부족한 하네스는 좋은 모델도 무력화한다.
그래서 AI를 어떤 구조 안에 가둘까를 먼저 설계했다.
이번 글에는 이런 내용을 담았다
1. 왜 AI 코딩에 하네스가 필요한지
2. LinkPocket에서 하네스를 어떤 계층으로 적용하려는지
3. 사람, Claude, Codex의 역할을 왜 나눴는지
4. 왜 훅, pre-push, CI, GitHub Ruleset을 겹겹이 두는지
5. 반복된 실수를 어떻게 skill/hook으로 승격할지
6. 지금은 일부러 하지 않는 경계가 무엇인지
AI가 시험 문제와 답안을 동시에 만들지 못하게 하고
계약을 통과한 변경만 독립된 게이트를 거쳐 main에 합치는 구조를 만들려고 했다.
문제는 AI가 틀린다는 데 있지 않다
AI 코딩의 가장 큰 위험은 "가끔 틀린 코드를 생성한다"가 아니다.
사람도 틀린 코드를 만든다.
테스트도 빠뜨린다.
리뷰에서도 놓치곤 한다.
이건 비단 AI만의 문제가 아니다.
문제는 AI가 구현과 검증 기준을 함께 만들 때 생긴다.
예를 들어 어떤 기능을 이렇게 요청했다고 하자.
링크를 저장하고 태그별로 조회할 수 있게 해줘.
중복 링크는 막아야 하고 공개 범위도 지켜야 해.
AI는 빠르게 구현한다.
링크 저장 API를 만들고, 태그 조회 쿼리를 만들고, 테스트도 작성한다.
겉으로 보기에는 잘 동작하는 코드 같아 보인다.
하지만 빠질 수 있는 조건은 많다.
같은 사용자의 중복 링크만 막아야 하는가?
다른 사용자가 같은 URL을 저장하는 것은 허용되는가?
비공개 링크가 태그 검색 결과에 섞이면 안 되는가?
태그 삭제 후 검색 인덱스는 어떻게 수렴해야 하는가?
권한이 없는 사용자가 공유 링크를 추측해서 접근하면 안 되는가?
이 조건들이 구현 전에 고정되어 있지 않으면 AI는 주변 코드와 프롬프트의 분위기를 보고 적당한 해석을 생성한다.
문제는 그 다음이다.
AI가 자신이 선택한 해석에 맞는 테스트까지 작성하면 테스트는 통과한다.
하지만 그 테스트가 제품의 진짜 계약을 검증하는지는 알 수 없다.
그래서 하네스의 출발점은 단순했다.
계약을 쓰는 자와 계약을 만족시키는 자를 분리한다.
프로젝트에는 이렇게 적용했다
하네스는 Top Down 구조로 잡았다.
Top에는 제품이 절대 깨면 안 되는 불변조건이 있고 그 아래에 기능별 계약이 있다.
그 계약을 기준으로 역할을 나누고 역할이 자기 선을 넘지 못하게 게이트를 둔다.
마지막으로 반복된 실수는 원장에 남겨 하네스 자체를 개선한다.
전체 구조는 이렇게 볼 수 있다.
제품 불변조건·문서 우선순위
↓
기능 계약(plan) + 위험 로직 ADR
↓
사람 / Claude / Codex 역할 분리
↓
Codex 훅 → git pre-push → CI → GitHub Ruleset·CODEOWNERS
↓
리뷰·실수 원장 → skill/hook 승격
구현 도구부터 정하면 AI는 빠르게 움직이지만 어디로 수렴해야 하는지 모호해진다.
반대로 제품 불변조건과 기능 계약을 먼저 고정하면 AI는 자유롭게 구현하더라도 결국 정해진 계약으로 돌아와야 한다.
4층: 컨텍스트를 먼저 고정했다
AI 에이전트는 세션마다 기억이 다르고, 읽는 문서도 다르고, 직전 판단을 놓칠 수 있다.
사람 개발자도 잠시 쉬었다 돌아오면 맥락을 다시 기억에서 끄집어내야 한다.
AI는 그 기억력이 더 짧다.
같은 작업 안에서도 어떤 파일을 먼저 읽었는지에 따라 판단이 달라질 수 있다.
그래서 가장 위에는 컨텍스트 계층을 두었다.
| 문서 | 역할 |
| invariants.md | 전역 불변조건과 문서 우선순위를 선언한다 |
| session-state.md | 직전 세션의 완료, 결정, 미완료, 다음 시작점을 넘긴다 |
| AGENTS.md | 작업 종류에 따라 읽어야 할 문서로 라우팅한다 |
| development-loop.md | 실행 루프, 정지 조건, 소유권 규칙을 설명한다 |
이 계층이 막으려는 것은 컨텍스트 드리프트이다.
AI가 오래된 문서를 참고하거나, 서로 충돌하는 규칙을 자기 마음대로 해석하거나, 직전 세션의 미완료 상태를 잊는 일을 줄이려 했다.
중요한 원칙은 이것이다.
문서가 충돌하면 추측하지 않고 멈춘다.
AI에게 "알아서 판단해줘"라고 넘기면 편하다.
하지만 제품 불변조건이나 권한, 데이터 정합성처럼 비용이 큰 판단은 자동 추론에 맡기지 않는 편이 낫다.
3층: 기능을 계약으로 만들었다
기능 개발은 구현으로 바로 시작하지 않는다.
먼저 plan/NN-*.md에 기능 계약을 쓴다.
여기에는 목적, 포함 범위, 제외 범위, 허용 쓰기 경로, Acceptance Criteria, 불변식, 실패 조건, 위험 ADR을 기록한다.
계약 문서가 필요한 이유는 단순하다.
구현 전에 성공과 실패의 기준을 고정하기 위해서다.
예를 들어 링크 저장 기능이라면 다음 질문을 먼저 답해야 한다.
이 기능의 목적은 무엇인가?
이번 PR에 포함되는 것은 어디까지인가?
어떤 파일과 경로를 수정할 수 있는가?
성공으로 판단할 Acceptance Criteria는 무엇인가?
절대 깨면 안 되는 불변식은 무엇인가?
어떤 상황에서는 구현을 멈추고 사람에게 올려야 하는가?
이렇게 하면 AI에게 주는 일이 자연스럽게 API 계약처럼 바뀐다.
입력, 출력, 허용 권한, 실패 조건이 있는 작업이 된다.
이 상태에서 Codex는 자유롭게 구현할 수 있지만 계약 밖으로 나가면 안 된다.
2층: 사람, Claude, Codex의 역할을 나눴다
하네스의 가장 중요한 설계는 역할 분리이다.
사람은 위험 결정을 승인한다.
Claude는 계약 테스트와 리뷰를 맡는다.
Codex는 보호된 계약을 수정하지 않고 구현과 내부 테스트를 반복한다.
역할을 이렇게 나눈 이유는 하나였다.
한 에이전트가 시험 문제와 답안을 함께 만들지 못하게 하기 위해서다.
구조는 다음과 같다.
| 역할 | 맡은 일 | 넘지 말아야 할 선 |
| 사람 | 위험 로직 합의, ADR 승인, plan 승인, PR merge 승인 | 판단이 필요한 결정을 AI에게 완전히 넘기지 않는다 |
| Claude | 계약 테스트 작성, 정확성·보안·동시성·호환성·범위 리뷰 | 구현 편의를 위해 계약을 느슨하게 만들지 않는다 |
| Codex | 구현, 내부 테스트, verify 통과까지 반복 | 계약 테스트, 게이트, CI 설정 같은 보호 경로를 수정하지 않는다 |
이 구조에서 중요한 건 blind spot을 분리하는 것이다.
같은 모델이 테스트와 구현을 같이 만들면 같은 착각을 공유할 수 있다.
서로 다른 역할이 서로 다른 산출물을 담당하면 적어도 구현자의 해석이 검증 기준까지 장악하는 상황은 줄일 수 있다.
물론 이 구조가 완벽하다는 뜻은 아니다.
Claude와 Codex의 blind spot이 실제로 상관되어 같은 유형의 놓침을 반복한다면 2-에이전트 분리의 전제 자체를 다시 열어야 한다. ADR에도 이 재검토 조건을 남겼다.
기능 하나는 이렇게 흐르게 했다
하네스 안에서 기능 하나는 다음 순서로 움직인다.
사람: 위험 로직 합의 → ADR → plan 승인
Claude: 계약 테스트(red) 작성
Codex: 계약 테스트는 건드리지 않고 구현·내부 테스트 반복
자동화: verify.sh green 확인
Claude: 정확성·보안·동시성·호환성·범위 등 7축 리뷰
사람: PR·merge 승인
이 흐름에서 중요한 지점은 Red 테스트 코드이다.
AI는 Green 코드를 잘 만든다.
통과하는 코드를 만드는 능력은 이미 충분히 좋다.
오히려 어려운 것은 실패하는 테스트를 먼저 만들고 그 테스트가 제품 계약을 정확히 대표하게 하는 일이다.
그래서 Codex는 계약 테스트를 고치지 않아야 한다.
테스트가 이상해 보이면 고치는 것이 아니라 멈춘다.
계약 테스트가 틀렸다면 계약 소유자가 고쳐야 한다.
구현자가 테스트를 고치는 순간 하네스의 핵심 전제가 깨진다.
실제로 프로젝트에서도 이 원칙이 작동했다.
Javadoc 주석의 **/가 */로 파싱돼 계약 테스트가 컴파일 실패한 적이 있었다.
또 WireMock 정적 stubFor가 기본 포트로 등록돼 동적 포트 서버와 어긋난 적도 있었다.
둘 다 Codex가 테스트를 몰래 고쳐 초록불을 만든 게 아니라 보호 경로라서 멈추고 에스컬레이션했다.
작은 사례이지만 내가 설계한 하네스 입장에서는 중요한 신호였다.
AI가 막혔을 때 우회하지 않고 멈췄기 때문이다.
1층: 규칙을 문서가 아니라 게이트로 만들었다
문서에 "하지 말라"고 쓰는 것만으로는 부족하다.
사람도 바쁘면 놓치고, AI는 더 쉽게 놓친다.
그래서 결정 가능한 규칙은 코드로 강제하기로 했다.
프로젝트에 네 겹의 강제 장치를 아래와 같이 둔다.
| 순서 | 장치 | 역할 |
| 1 | .codex/hooks/ | Codex가 계약 테스트·게이트·CI 설정을 편집하려는 시도를 즉시 차단한다. verify.sh가 실패하면 종료를 막는다 |
| 2 | .githooks/pre-push | push 전에 계약 테스트 삭제와 비밀값 유입을 검사한다 |
| 3 | .github/workflows/verify.yml | 깨끗한 CI 환경에서 보호 경로, 비밀값, 전체 테스트를 다시 검사한다 |
| 4 | GitHub Ruleset + CODEOWNERS | main에 PR과 verify 통과를 요구하고, 보호 인프라 변경에는 코드 오너 검토를 요구한다 |
로컬 훅은 우회될 수 있다.
그래서 최종 권위는 CI와 GitHub Ruleset에 둔다.
반대로 CI만으로는 피드백이 늦어질 수 있다.
구현자가 한참 작업한 뒤에야 CI에서 막히면 비용이 커진다.
그래서 로컬 훅과 pre-push는 빠른 피드백을 맡고 CI와 GitHub Ruleset은 최종 검증을 맡는 구조이다.
멈춰야 하는 순간도 미리 정했다
AI에게 가장 위험한 지시는 "계속해"일 수 있다.
테스트가 실패했는데도 계속 고치고,
정책이 충돌했는데도 추측하고,
범위 밖 파일을 만지고,
권한이나 스키마 변경까지 자연스럽게 밀고 가면 하네스는 의미가 없어진다.
그래서 정지 조건을 명확히 두었다.
- 정책, ADR, plan이 충돌하는 경우
- 범위 밖 파일을 수정해야 하는 경우
- 계획에 없던 스키마·권한·외부 전송이 필요한 경우
- 계약 테스트 자체의 버그처럼 보이는 실패가 발생한 경우
- 반복 실패가 일정 횟수를 넘은 경우
- 비밀값이나 개인정보 노출이 의심되는 경우
- 프롬프트 인젝션이 의심되는 경우
이 조건에서는 계속 구현하지 않는다.
조건에 걸리면 멈추고 사람에게 올린다.
이 원칙은 개발 속도를 조금 늦출 수 있지만 잘못된 방향으로 가는 것보다 낫다고 생각한다.
마지막은 자기개선이다
하네스는 처음부터 완벽하게 만들 수 없다.
오히려 처음부터 모든 규칙을 만들려고 하면 과설계로, 토큰이 무지막지하게 들 것이다.
실제로 반복되는 실수가 무엇인지 보기 전까지는 어떤 훅이 필요한지 어떤 skill이 필요한지 판단하기 어렵다.
그래서 실패를 바로 자동화하지 않고 먼저 원장에 남긴다.
mistake-ledger.md에는 리뷰, 게이트, postmortem에서 발견된 실패를 기록한다.
같은 유형이 2회 나오면 승격 후보가 되고 3회 나오면 사람 승인 아래 skill 또는 hook으로 승격한다.
흐름은 이렇게 잡았다.
실패 발견
↓
mistake-ledger 기록
↓
같은 유형 2회 = 승격 후보
↓
같은 유형 3회 = 사람 승인 아래 skill/hook 승격
여기서 중요한 것은 자동 승격하지 않는다는 것이다.
실패가 한 번 나왔다고 바로 훅을 만들면 하네스가 금방 비대해진다.
반대로 반복된 실패를 사람이 매번 리뷰로 잡게 두면 개발 속도가 늦어지고 피로가 쌓일 것이다.
그래서 이러한 기준을 둔 것이다.
두 번이면 패턴으로 의심하고 세 번이면 구조로 흡수한다.
왜 이렇게까지 해야 했을까
겉으로 보면 번거로워 보일 수 있다.
작은 프로젝트에 ADR, plan, 역할 분리, 훅, CI, CODEOWNERS, 실수 원장까지 두는 것은 과해 보인다.
하지만 개인 프로젝트일수록 팀원에게 코드리뷰를 받지 못하는 환경이기에 AI 하네스는 더욱 필요하다고 생각한다.
AI는 조용히 실패한다.
컴파일도 되고, 테스트도 통과하고, PR도 그럴듯한데 서비스 환경에선 에러가 줄줄이다.
특히 권한, 데이터 정합성, 멱등성, 외부 전송, 보안 같은 영역에서는 조용한 실패의 비용은 꽤나 크다.
그래서 네 가지 원칙을 잡았다.
1. 계약이 코드보다 먼저다.
2. 결정 가능한 규칙은 코드로 강제한다.
3. 판단이 필요한 일은 사람에게 남긴다.
4. 반복된 실패는 하네스에 흡수한다.
이 네 가지는 서로 연결되어 있다.
계약이 먼저 있어야 구현이 수렴할 수 있다.
규칙이 코드로 강제되어야 AI가 우회하지 못한다.
판단이 필요한 일은 사람이 승인해야 위험한 결정을 자동화하지 않는다.
반복된 실패를 하네스에 흡수해야 같은 실수를 계속 리뷰하지 않아도 된다.
일부러 아직 하지 않는 것도 있다
하네스라고 해서 구현에 좋은 것들을 모두 다 넣지는 않았다.
현재 경계는 로컬 구현과 PR 검증이다.
staging, production, canary, 배포 승인, 에이전트 전용 최소 권한 토큰은 실제 운영환경이 되었을 때 확장하기로 했다.
운영 배포 대상이 없는데 canary와 롤백 런북부터 만들면 하네스는 보호 장치가 아니라 관리 비용이 된다고 생각한다.
반대로 운영 대상이 생기면 지금의 하네스만으로는 부족하다.
그때는 배포 승인, 런북, 관측, 에이전트 권한 분리까지 확장하려 한다.
이 말인 즉슨 배포할 시점에 새로운 글로 다시 찾아오겠다는 것이다.
정리
이전 팀프로젝트에 도입한 하네스를 여러 글과 방법론을 더 찾아 보강해 보았다.
하네스의 필요성은 확실히 이해가 되었다.
구현 하면서 또 글로 적으면서 머릿속에 뚜렷하게 구조가 그려진다.
지금 개인 프로젝트에 적용해서 개발중인데 꽤나 만족스럽다.
난 설계와 기술 선택, 문제 정의에 더 집중을 할 수 있고
부족한 지식들을 꾸준히 학습하는데 시간을 더 쓸 수 있다.
다음은 운영 환경에 맞는 하네스 구조 주제로 찾아오겠다.