요즘 출근해서 가장 먼저 켜는 건 코드 에디터가 아니라 봇이다. 사내 Slack에서 쓰는 봇의 이름은 Anvil이다. 미리 말하면 이 글은 봇 자랑이 아니다. 봇 하나를 붙였다고 하루가 달라지지는 않았다. 실제로 달라진 건 그 뒤에 누가 일을 시작해도 같은 품질에 도달하도록 만든 하네스였다.

예전에는 QA가 “이 문구를 바꾸면 어디까지 영향이 가나요?”라고 물으면 하던 일을 멈췄다. 머릿속 맥락을 지우고 저장소를 열어 관련 코드를 찾고, 답을 보낸 다음 다시 원래 작업으로 돌아왔다. 질문 하나가 반나절을 먹는 날도 있었다. 내 일이 아닌 막힌 곳을 뚫어 주는 일이었고, 기다리는 사람과 답해야 하는 사람 모두 멈췄다.

지금 QA는 Anvil에 티켓 번호와 “이거 고쳐줘”를 보낸다. 봇은 티켓과 코드를 읽고 영향 범위를 나눈다. 단순 변경이면 코드를 고치고 품질 검사를 거쳐 MR까지 올린다. 범위가 크면 먼저 담당자에게 승인 요청을 보낸다. 나는 점심 무렵 쌓인 MR을 한꺼번에 본다. 중요한 차이는 속도가 아니다. QA가 시작한 수정도 내가 터미널에서 시작한 수정과 같은 규칙을 통과한다는 점이다.

이 글의 숫자는 한 내부 작업을 되짚은 개인 기록이다. 제품 성능을 비교한 벤치마크가 아니며, 같은 결과가 다른 조직에서 재현된다는 뜻도 아니다. 공개적으로 확인 가능한 부분은 Claude Code, ESLint, TypeScript 공식 문서에 연결했다. Anvil의 이름과 사례, 수치는 내가 제공한 작업 기록에서 왔다.

이 글을 쓰게 된 배경

“맡긴다”는 말은 봇에 메시지를 보내는 순간 완성되지 않는다. 내가 다시 코드를 전부 읽어야 한다면 입력 창만 바뀐 것이다. 위임은 맡긴 뒤에도 같은 결과가 나온다는 보장이 있을 때 성립한다. 그 보장은 모델의 영리함보다 프로젝트의 규칙에서 나왔다.

처음에는 나도 프롬프트를 길게 쓰면 된다고 생각했다. 그런데 “한 달은 28일로 계산한다”, “최소 주문 금액은 5만 원이다”, “통화 단위가 맞지 않으면 저장하지 않는다” 같은 기준은 대화창에 매번 붙일 내용이 아니었다. 이미 코드베이스 어딘가에는 구현돼 있었지만 새 작업을 시작할 때 누구나 같은 순서로 확인할 수 있는 형태가 아니었다.

그래서 정책 파일, 위험 등급, 검사 명령, 사람에게 넘기는 조건을 한곳에 모았다. Slack이든 터미널이든 시작점만 다를 뿐 안쪽에서 쓰는 판단 기준은 같게 했다. 봇은 별도 지능이 아니라 평소 쓰던 코딩 에이전트를 헤드리스 모드로 부르는 얇은 입구에 가깝다. Claude Code 공식 문서claude -p도 한 번의 질의를 실행하고 종료하는 비대화형 사용법을 제공한다.

기준으로 삼은 업무

기준 업무는 QA 티켓 하나였다. 티켓을 받으면 먼저 관련 저장소와 파일을 찾고, 변경이 정책과 핵심 모듈에 닿는지 확인한다. 영향이 작으면 수정과 테스트를 진행하고 MR을 만든다. 크면 구현 전에 담당자의 승인을 기다린다. 어느 경로든 마지막에는 사람이 기능 의도와 배포 여부를 판단한다.

Slack 요청이 영향 분석, 코드 수정, 품질 게이트, MR 생성, 사람의 최종 판단으로 이어지는 동일한 작업 흐름

Slack에서 시작한 작업은 다음 흐름을 탄다.

[QA의 Slack 요청]
→ Anvil
→ 코딩 에이전트의 비대화형 실행
→ 프로젝트 규칙과 위험 등급 확인
→ 코드 변경
→ lint · typecheck · test
→ MR 생성 또는 담당자 승인 요청

여기서 Slack은 인터페이스일 뿐이다. 프로젝트 정의 파일, 검사 명령, 차단 규칙이 실제 하네스다. Claude Code 설정 디버깅 문서도 프로젝트 지침은 맥락을 주고, 권한과 훅은 실행 경계를 강제하는 서로 다른 장치라고 설명한다. 문서에 “위험한 명령을 쓰지 마라”고 적는 것과 실제로 실행을 막는 것은 같은 일이 아니다.

실제로 비교한 기준

품질 게이트는 응답 직전에 자동으로 돈다. 고칠 수 있는 lint 문제는 ESLint --fix로 먼저 정리하고, 남은 오류와 tsc --noEmit, 테스트가 모두 통과해야 결과를 내보낸다. Claude Code의 Stop 훅처럼 에이전트가 멈추려는 시점에 명령을 실행하면 “꼭 검사해”라는 부탁보다 더 일관된 경계를 만들 수 있다.

위험한 행동은 두 층으로 나눴다. rm -rf, DROP TABLE, .env 생성처럼 실행 자체가 위험한 것은 막는다. git push --force, --no-verify처럼 예외적으로 필요할 수 있지만 기록과 판단이 필요한 행동은 사람 확인 뒤에만 통과시킨다. 대장간으로 치면 하나는 용광로 둘레의 울타리고, 다른 하나는 “여기 뜨거움”이라는 표지판이다.

문구와 단순 매핑은 자동 진행하고, 컴포넌트 변경은 사후 검토하며, 정책과 코어 변경은 사전 승인하는 위험 등급

영향 등급 예시 흐름
MINOR 문구 수정, 단순 매핑 추가 바로 구현 → 품질 게이트 → MR
MODERATE 컴포넌트 구조, 스타일 리팩터링 구현 → 품질 게이트 → 담당자 검토
MAJOR 정책 상수, 검증 규칙, 코어 모듈 담당자 사전 승인 → 구현 → MR 검토

MAJOR에 병목을 둔 덕분에 MINOR와 MODERATE는 더 넓게 열 수 있었다. 모든 변경을 같은 속도로 통과시키는 것은 자동화가 아니라 검증을 생략하는 일이다. 반대로 모든 변경에 사전 승인을 붙이면 봇은 기다림을 자동화할 뿐이다.

운영에서 걸리는 부분

코드를 직접 치는 시간은 줄었지만 일이 사라진 것은 아니다. 내 체감으로 구현은 약 60%에서 거의 0%로, 테스트 작성은 약 20%에서 거의 0%로 내려갔다. 대신 정책과 요구사항을 명시하는 일은 약 60%, 리뷰는 약 30%, 머지와 배포는 약 10%까지 올라갔다. 합계가 정확히 100이 되지 않는 이유는 시간 측정표가 아니라 업무 비중을 되짚은 개인 추정치이기 때문이다.

구현과 테스트 작성에 쓰던 시간이 정책과 요구사항 명시, 리뷰, 머지와 배포로 이동한 개인 추정치

키보드는 여전히 친다. 다만 코드를 적는 대신 사용자 흐름, 입력 검증, 오류 메시지, 실패 케이스를 적는다. “로그인 개선”이라고 한 줄 남기던 요청은 이제 어느 화면에서 시작해 어떤 값이 들어오며 실패하면 무엇을 보여 줄지 적은 작업 문서가 된다. 그 문서가 에이전트에 주는 사양이자 사람이 결과를 검토하는 기준이 된다.

이 변화가 편하기만 했던 것은 아니다. 에러 모달이 여러 개인 화면에서 봇이 어느 레포를 고쳐야 하는지 자동으로 못 정해 처음 입력부터 막힌 적이 있다. 실패했을 때 사용자가 후보 레포를 고르게 바꾸고 나서야 흐름이 이어졌다. 사람의 실패 경험까지 설계 범위라는 걸 그때 배웠다.

더 자주 막힌 이유는 도구가 멍청해서가 아니라 정책이 모호해서였다. 도메인 규칙을 “당연히 이렇게 해야지”라고 머릿속에만 두면 에이전트는 읽을 수 없다. 실패가 반복될수록 코드를 고치는 대신 정책 문서와 하네스를 손봤다. 모델이 내 머릿속을 읽지 못한다는 사실은 결함이 아니라 경계다.

테스트가 통과해도 기능은 빠질 수 있었다

한 작업 결과를 코드만 놓고 다시 평가했다. 기술 완성도는 85점, 명세 반영률은 62.5%였다. Jest 테스트 151개는 모두 통과했고 모달 여섯 상태도 빠진 곳이 없었다. 하지만 명세 40개를 대조하니 실제로 반영된 것은 25개였다. 빠진 15개는 카테고리 정책, 담당자 범위, 검색 필터처럼 기술 게이트가 알아서 추론할 수 없는 업무 규칙이었다.

기술 완성도 85%와 명세 반영률 62.5%, Jest 151개 통과, 모달 6개 확인, 명세 40개 중 25개 반영을 비교한 내부 회고

더 이상했던 건 누락을 몰랐던 것이 아니라는 점이다. 보고서에는 “카테고리, 담당자 표시, 저장 확인은 미완”이라고 적혀 있었다. 빈칸을 찾고 기록하는 데서 멈췄고, 그 빈칸을 완료 조건과 연결하지 못했다. lint, typecheck, test만으로는 잡히지 않는 종류의 실패였다.

그래서 기능 검증용 층을 하나 더 만들기 시작했다. 변경된 코드가 어떤 정책에 닿는지 찾고, 관련 수용 기준을 실행하며, 문서와 결과가 맞지 않으면 경고한다. 아직 모든 프로젝트에서 자동으로 돌아가는 단계는 아니다. 문서에 적히지 않은 암묵적 규칙까지 자동으로 찾는다고 말할 수도 없다. 지금은 최소한 “무엇을 만족해야 끝인가”를 테스트와 연결해, 미완 항목을 보고서에만 남기고 지나가지 않게 하는 정도다.

다시 선택한다면

봇을 먼저 만들지 않겠다. 같은 일을 두 사람이 시작해도 지켜야 할 규칙과 검사가 같아지는지부터 보겠다. 그 상태가 아니면 Slack 봇은 혼란을 더 빨리 전달하는 입구가 된다.

남길 것은 세 가지다. 첫째, 위험한 명령은 프롬프트가 아니라 실행 경계에서 막는다. 둘째, 영향 등급이 커질수록 사람의 승인을 앞당긴다. 셋째, 테스트 통과와 요구사항 충족을 서로 다른 점수로 본다. 기술 게이트는 코드가 망가지지 않았는지 확인하고, 수용 기준은 만들려던 것이 맞는지 확인한다.

사람도 사라지지 않는다. 돈과 고객 데이터, 핵심 정책이 걸린 변경에서는 경험이 있는 사람이 승인한다. 실패한 자동화의 복구 경로와 최종 배포 책임도 사람이 가진다. 목표는 사람을 빼는 것이 아니라, 같은 확인을 반복하는 곳에서는 기계가 일하고 판단이 필요한 곳에서는 사람이 보게 만드는 것이다.

적용 전 체크리스트

  • 팀이 반복해서 설명하는 정책 세 가지를 먼저 문서로 적습니다.
  • 자동 수정, 사람 검토, 실행 차단의 경계를 명령 단위로 나눕니다.
  • lint, typecheck, test를 응답 직전의 강제 게이트로 연결합니다.
  • 문구·매핑, 컴포넌트, 정책·코어 변경을 서로 다른 승인 흐름에 둡니다.
  • 테스트 개수와 별도로 명세 항목의 반영률을 셉니다.
  • 실패하면 사용자가 레포와 다음 행동을 고를 수 있는 복구 화면을 둡니다.
  • 누가 최종 merge와 외부 배포를 승인하는지 한 명으로 명확히 합니다.

처음부터 사내 봇을 만들 필요는 없다. 터미널에서 같은 정의 파일과 훅을 적용하는 것만으로도 절반은 얻을 수 있다. 하루에 몇 번씩 반복되는 질문 하나를 골라 위험이 낮은 변경만 자동으로 MR까지 보내 보고, 사라진 시간이 아니라 다시 열린 MR의 비율과 빠진 요구사항 수를 먼저 보는 편이 낫다.

운영 경계를 문서로 남기기

이 글의 내부 회고는 하나의 운영 사례일 뿐, 같은 통제가 다른 저장소에서도 검증됐거나 그대로 효과가 난다는 증거는 아닙니다. 에이전트가 변경을 시작하기 전에 영향 경로, 위험 등급, 승인 시점, 필요한 검사를 적는 용도로 워크시트를 활용하세요. AI 에이전트 변경 위험 워크시트 다운로드.

확인한 자료

내 하루가 바뀐 이유는 코드를 안 쳐서가 아니었다. 무엇을 만들어야 하는지 더 정확히 적고, 경계 안에서는 모델을 자유롭게 두고, 경계 밖에서는 사람이 판단하게 만든 덕분이었다. 하네스 엔지니어링은 모델을 더 강하게 만드는 일이 아니라 어디까지 맡길 수 있는지를 분명하게 만드는 일에 가까웠다.

참고한 자료

보도 내용, 공식 문서, 정책 배경, 제품 정보처럼 시간이 지나면 바뀔 수 있는 내용을 확인할 때 본 자료입니다.