세무조정계산서 vs 감사보고서 대사 스킬 만들기(3개의 AI 에이전트(?) 활용하여 하네스 구축)

배경 설명

  1. 스킬을 개발하기로 마음먹은 계기

회계법인 Tax팀에 있다보니 매번 법인세 신고 기간이 매우 바빴습니다. 바쁜 이유는 세액을 산출하는 업무보다는 신고를 위한 서식을 만들고, 이를 다시 검증하는 시간이 더 오래 걸렸습니다.

특히나, 제대로 잘 입력이 되었는지를 사람이 일일히 하나씩 보는 업무이기 때문에 무척 지루하고 재미가 없었고, 이를 자동화할 스킬을 생각하게 되었습니다.

법인세 신고를 하는 서식의 양식은 정해져있기 때문에 감사보고서 주석만 제대로 분석을 하면 대사하는 데에 큰 무리가 없을 것이라고 판단했습니다.

  1. 사용한 도구

Claude Code, Codex, 사내 내부 에이전트

  1. 병목 사항?

결국 회사에서는 모든사람들이 클로드코드나 코덱스를 쓸 수 없기 때문에(특히 헤르메스는 절대 못씁니다) 사내 내부 에이전트에서 돌아가는 스킬을 만들어야 했습니다. 사내 내부 에이전트와의 대화만으로는 스킬을 정교화할 수 없다고 판단하여 클로드코드와 코덱스를 활용하여 스킬을 고도화하고 있습니다.

(세무조정계산서 vs 감사보고서 대사 스킬 만들기(리뷰 하네스 구축))과 많은 부분이 겹치지만, 3개의 AI agent에게 역할과 권한을 분리하여 부여하였다는 측면에서 작성해보았습니다.

## 📝 한줄 요약

AI가 만든 결과물을 다른 AI가 검수하게 했더니 9라운드 연속 반려를 당했고, 그 과정에서 "AI의 완료 보고"를 그대로 믿으면 안 되는 이유와 그걸 구조로 막는 방법을 배웠습니다.

바쁘시면 이것만 읽어도 돼요:

- 구성: 구현 담당 AI(Claude Code)와 리뷰 전담 AI(Codex)를 분리하고, 리뷰어에게는 파일 수정 권한을 아예 주지 않았습니다.

- 핵심 규칙 하나: "완료했다 / 검증했다"는 말은 증거가 아닙니다. 실행한 명령 원문 + 출력 원문 + 종료 코드가 있어야 인정합니다.

- 가장 크게 배운 것: 기록(record)과 검증(verify)은 다릅니다. 결과 파일에 해시를 적어두는 건 기록이고, 그걸 읽는 쪽이 다시 계산해서 대조해야 검증입니다. 저는 이 둘을 혼동해서 한 라운드를 통째로 날렸습니다.

- 인상적이었던 순간: 검수 도구가 통과시킨 항목을, 검수 도구를 검수하니 실패로 드러났습니다. 검사 방법 자체가 결함을 감춰주고 있었습니다.

- 의외의 효과: 반려가 반복될수록 결과물이 아니라 판단 기준이 정교해졌습니다. 지금은 사람이 안 봐도 통과/불통과가 자동으로 갈립니다.

- 교훈: 리뷰가 쉽게 통과되면 그건 리뷰가 아니라 요식행위입니다.

## 🎯 이런 분들께 도움돼요

- AI가 "다 했습니다"라고 했는데 나중에 안 되어 있어서 데인 적 있는 분

- AI 결과물을 팀에 제출해야 해서 믿을 만한 근거가 필요한 분

- 코딩 에이전트를 쓰는데 검수를 매번 사람이 눈으로 하고 있어 지친 분

- AI 두 개를 붙여서 서로 검증시키는 구조를 만들어보고 싶은 분

문제 상황 (Before)

법인세 신고서식과 재무제표 주석을 서로 대조(대사)하는 작업을 AI로 자동화하는 프로젝트를 하고 있었습니다.

쉽게 말하면 이렇습니다. 회사가 관계사와 주고받은 거래(매출·매입·대여·차입)가 세무 신고서에도 적히고 재무제표 주석에도 적히는데, 이 둘이 서로 맞는지 사람이 눈으로 맞춰봅니다. 회사명 표기가 다르고, 어떤 표는 합계만 있고, 어떤 항목은 한쪽에만 있습니다. 한 회사분을 맞추는 데만 시간이 오래 걸립니다.

문제는 자동화 자체가 아니라 "이게 진짜 맞게 돌아간 게 맞나" 였습니다. 숫자가 안 맞으면 그건 실무에서 그대로 리스크가 되니까요.

AI에게 작업을 시키면 늘 이렇게 보고했습니다.

- "실행 완료했습니다. 19쌍 모두 정상 처리되었습니다."

- "검증 결과 전건 일치합니다."

- "1차 실패 로그도 그대로 남아 있습니다."

그럴듯했습니다. 그런데 하나씩 실제로 확인해보니 셋 다 사실이 아니었던 적이 있었습니다. 특히 마지막 건은 뼈아팠는데, 확인해보지도 않고 "남아 있다"고 문서에 적었고, 나중에 보니 두 번째 실행이 첫 번째 로그를 덮어써서 애초에 남을 수가 없는 구조였습니다.

더 심각한 사고도 있었습니다. 리뷰를 자동화해보겠다고 훅(hook)을 걸어 리뷰어 AI를 자동 호출했는데, 사내망 문제로 리뷰가 실제로는 실행되지 않았습니다. 그런데 프롬프트 안에 예시로 적어둔 "승인" 문구가 화면에 그대로 출력된 것을 판정으로 착각해서, 리뷰를 한 적도 없는데 상태가 "승인됨"으로 넘어가 버렸습니다.

그때 결론을 냈습니다. AI의 자기보고를 증거로 인정하는 한, 검수는 항상 뚫린다.

사용한 도구

- 도구: Claude Code(설계·구현), Codex(리뷰 전담), 별도 원격 실행 환경의 에이전트(실제 엔진 실행)

- 모델: Claude Opus 5(Xhigh), Fabe 5(high)

- 특이사항: 세 에이전트는 서로 직접 대화하지 않습니다. 상태 파일과 각자 소유한 문서로만 소통하고, 사람이 리뷰 요청과 응답을 중계합니다.

작업 과정

시작 — "리뷰어에게 수정 권한을 주지 않는다"

가장 먼저 정한 건 역할 분리였습니다. 리뷰어 AI에게 코드 수정 권한을 주면, 지적하는 대신 조용히 고쳐버립니다. 그럼 무엇이 왜 잘못됐는지가 기록에 남지 않습니다.

그래서 리뷰어는 딱 두 파일만 쓸 수 있게 했습니다. 리뷰 내용을 적는 파일 하나, 그리고 자기 차례가 끝났음을 표시하는 상태 파일 하나. 나머지는 전부 읽기 전용입니다.

그리고 규칙 문서에 이 문장을 박아 넣었습니다.

자기보고를 증거로 인정하지 않는다 — "완료했다"/"검증했다"는 말은
실행 로그, 파일 크기·해시, 실제 값, 변경 내역 같은 raw 증거가 있어야 인정된다.

1라운드 — 반려. "그건 기록이지 검증이 아니다"

첫 제출은 자신 있었습니다. 결과물마다 입력 파일의 해시를 기록해뒀고, 문서에 "입력 결박 19/19 완료"라고 썼습니다.

리뷰어의 답은 짧고 정확했습니다.

> 생산자가 해시를 적는 것과, 소비자가 그 해시를 다시 계산해서 대조하는 것은 다르다. 지금 그 값을 읽는 코드가 한 줄도 없다. 읽는 줄을 지목하지 못하면 "결박"이라고 쓰지 마라.

정확히 맞는 말이었습니다. 저는 파일에 적어두면 검증된 것이라고 착각하고 있었습니다. 그 후로 이 프로젝트에서 가장 자주 인용되는 원칙이 됐습니다.

manifest에 쓰는 것(기록)과 소비자가 재도출해 대조하는 것(검증)은 다르다.
읽는 코드 줄을 못 지목하면 "결박"이라고 쓰지 말 것.

반복된 반려 — 지적 1건은 "결함 한 개"가 아니라 "결함 종류 하나"

여기서 두 번째로 크게 배웠습니다.

리뷰어가 "A 파일의 해시를 아무도 안 읽는다"고 지적하면, 저는 A만 고쳐서 다시 냈습니다. 그러면 다음 라운드에 "B도 똑같다"가 왔습니다. 그다음엔 C가 왔습니다.

몇 번 반복하고 나서야 규칙을 바꿨습니다.

지적 1건은 결함 클래스의 표본이다. 지적받은 곳만 고치면 다음 라운드에 같은 클래스가 또 온다.
지적을 받으면 (1) 원리를 뽑아내고 (2) 비슷한 표면을 전수 스캔하고 (3) 자동 검사로 고정한다.

이걸 적용한 뒤로 라운드당 지적 건수가 눈에 띄게 줄었습니다.

증거를 "봉인"하기 — 그리고 자기참조 문제

리뷰어가 요구한 수준은 점점 올라갔습니다. 최종적으로는 이렇게 됐습니다.

증거 폴더 안의 모든 파일에 대해 크기·해시·수정시각·식별자를 기록하고, 그 기록 파일 자체의 해시를 또 다른 파일에 적어서 사슬을 만듭니다. 사슬의 어느 고리를 건드려도 즉시 드러나는 구조입니다.

그런데 여기서 재미있는 문제가 생겼습니다. 사슬의 마지막 고리를 쓰는 행위 자체는 누가 증언하나?

마지막 파일을 쓴 실행의 기록을 증거 폴더 안에 넣으면, 그 기록도 다시 봉인 대상이 되어서 사슬이 닫히지 않습니다. 무한 반복입니다.

리뷰어의 해법 지시는 명확했습니다. 마지막 실행의 기록만은 봉인 폴더 바깥에 두고, 사람에게 원문 그대로 전달하라. 대신 그 바깥 경로를 봉인되는 문서가 미리 선언하게 해서, 나중에 변명으로 끼워넣은 게 아님을 증명하게 했습니다.

인상적이었던 순간 — 검수 도구를 검수하니 결함이 나왔다

이번 라운드에서 제일 놀란 지점입니다.

저는 "작업 폴더의 파일이 저장소에 기록된 것과 같은가"를 확인하는 검사를 넣었고, 통과했습니다. 그런데 검사 방식이 마음에 걸려서 검사 방법 자체를 바꿔서 다시 돌려봤습니다.

결과: 같은 파일인데 확인 방법에 따라 다른 값이 나왔습니다. 기존 방식은 확인 과정에서 자동 변환을 한 번 거치기 때문에, 실제로는 어긋나 있는 파일도 "일치"로 통과시키고 있었습니다.

검사 도구가 결함을 감춰주고 있었습니다. 통과 표시를 믿었으면 그대로 제출했을 겁니다.

이걸 계기로 원칙을 하나 더 추가했습니다.

검증기도 검증 대상이다. "통과했다"가 아니라 "이 검사가 실패를 실제로 잡아내는가"를 따로 증명한다.

그래서 만든 것 — "실패가 진짜 실패하는지" 보여주는 음성 테스트

검사기가 제대로 작동한다는 걸 증명하는 방법은 하나뿐이었습니다. 일부러 위반 상황을 만들어서, 검사기가 실제로 거부하는지 보여주는 것.

총 13가지 상황을 만들었습니다. 잘못된 경로를 줬을 때, 폴더 바깥을 가리키는 링크를 심었을 때, 검사 도중에 파일을 바꿔치기했을 때, 외부 명령이 실패했을 때… 그리고 정상 상황 1개도 반드시 포함했습니다. 검사기가 무조건 거부하는 게 아니라는 걸 보여야 하니까요.

여기서 예상 못 한 발견이 있었습니다. 리뷰어가 지정해준 방어 항목을 그대로 구현했는데, 실제로 뚫어보니 한 가지 경로가 그대로 통과했습니다. 겉보기엔 폴더 안의 평범한 파일인데 내용은 폴더 바깥 파일인 형태였고, 지정된 검사 항목으로는 전부 정상으로 보였습니다.

그래서 리뷰어가 시킨 것보다 한 겹 더 막고, "당신이 준 조건만 구현하면 이 경로가 남습니다"라고 근거와 함께 회신했습니다. 시키는 대로만 하는 게 아니라 반례를 들고 가는 것이 리뷰 품질을 가장 크게 올렸습니다.

마지막 장치 — 제출 게이트

여러 라운드를 겪으며 알게 된 사실이 있습니다. 내용이 아무리 좋아도 제출 서류 4종 중 하나만 갱신이 빠지면 리뷰어는 본문을 아예 읽지 않습니다. 실제로 한 라운드를 그렇게 통째로 날렸습니다. 문서 하나 갱신을 잊었다는 이유로요.

사람이 매번 기억하는 대신 제출 전 자동 점검을 만들었습니다. 작업 상태가 깨끗한지, 서류 4종이 이번 라운드를 가리키는지, 증거 사슬이 스스로 검증되는지, 이번 라운드에서 건드리면 안 되는 파일이 바뀌지 않았는지. 하나라도 어긋나면 제출이 막힙니다.

그리고 이 게이트도 처음 돌렸을 때 자기가 버그로 죽었습니다. 한글 경로를 처리하지 못하는 문제였습니다. 검사 8개 중 7개를 통과시켜 놓고 결론을 못 내던 상태였죠. 그것도 고쳤습니다.

결과 (After)

Before vs After

| 항목 | Before | After |

|------|--------|-------|

| 완료 판정 근거 | AI의 "완료했습니다" 보고 | 실행 명령 원문 + 출력 전문 + 종료 코드 + 파일 지문 |

| 검수 주체 | 사람이 눈으로 훑기 | 리뷰 전담 AI + 자동 게이트 + 독립 재계산 |

| 결함 발견 시점 | 나중에 결과가 틀렸을 때 | 제출 전 (게이트에서 차단) |

| 지적 처리 방식 | 지적받은 곳만 수정 | 같은 유형 전수 스캔 후 자동 검사로 고정 |

| 제출 서류 누락 | 라운드 통째로 낭비 | 제출 자체가 막힘 |

결과물

- 증거 폴더의 모든 파일이 사슬로 묶여 있고, 제3자가 처음부터 다시 계산해서 대조할 수 있습니다.

- 검사기가 실제로 거부하는지 보여주는 13가지 실패 시나리오 + 정상 대조 1건

- 리뷰 요청서·상태 문서·인수인계 문서·기준 문서 4종 자동 점검 게이트

이 과정에서 배운 AI 활용 팁

효과적이었던 것

1. 리뷰어에게 수정 권한을 주지 마세요. 고칠 수 있으면 지적하지 않고 고쳐버리고, 그럼 무엇이 왜 문제였는지가 사라집니다.

2. "했다"를 금지어로 만드세요. 규칙 문서에 "자기보고는 증거가 아니다"를 한 줄 넣는 것만으로 보고 품질이 달라집니다.

3. 지적 하나를 유형 하나로 읽으세요. "이 파일이 문제"가 아니라 "이런 종류가 문제"로 바꿔서 전수 확인하면 왕복 횟수가 크게 줄어듭니다.

4. 정상 케이스를 반드시 같이 보여주세요. 실패 테스트만 있으면 "그냥 다 거부하는 검사기"일 수도 있습니다.

5. 리뷰어가 시킨 것보다 한 겹 더 확인하고, 반례를 들고 가세요. 이게 가장 신뢰를 크게 올렸습니다.

이렇게 하면 안 돼요

1. 확인 안 한 걸 문서에 쓰지 마세요. "로그도 남습니다" 한 줄이 거짓으로 판명되면 그 라운드 전체 신뢰가 무너집니다.

2. 자동화로 리뷰 자체를 대신하려 하지 마세요. 저는 자동 호출 훅을 걸었다가, 실행되지도 않은 리뷰가 "승인"으로 넘어가는 사고를 겪었습니다. 지금은 사람이 중계합니다.

3. 리뷰 진행 중에 코드를 만지지 마세요. 검토 대상이 흔들려서 "리뷰 불가" 처리됩니다. 실제로 당했습니다.

4. 검사기 통과를 결론으로 받아들이지 마세요. 검사 방법을 바꿔서 한 번 더 돌려보면 다른 결과가 나올 수 있습니다.

다른 업무에 적용한다면?

- 보고서·제안서 검수: 작성 AI와 검수 AI를 분리하고, 검수 AI에게 수정 권한 없이 지적만 시키기

- 데이터 정합성 확인: "확인했습니다" 대신 조회 쿼리 원문과 결과 건수를 같이 요구하기

- 업무 인수인계: 인수인계 문서에 "이 값을 실제로 읽는 곳"을 명시하게 만들기 — 적어만 두면 아무도 안 씁니다

앞으로의 계획

- 지금은 리뷰 요청·응답을 사람이 복사해서 중계합니다. 안전하게 자동화할 방법을 찾는 중입니다(단, 판정 문구 오인 사고를 구조적으로 막는 게 전제입니다).

- 증거 도구가 커지면서 증거 도구 자체의 안전성이 새로운 검토 대상이 됐습니다. 실제로 최근 라운드 지적은 결과물이 아니라 검사 도구의 구조적 위험에 대한 것이었습니다.

- 이 하네스를 다른 업무 영역에도 그대로 얹어보려 합니다.

재사용 가능한 프롬프트

프롬프트 1: 리뷰 전담 AI에게 (역할 고정)

> 너는 리뷰 전담이다. 파일을 수정하지 마라. 지적만 하고, 지적마다 근거가 되는 파일명과 줄 위치를 지목해라.

> "확인했다/괜찮다"는 판단은 받지 않는다. 네가 직접 재계산한 값과 기록된 값을 나란히 제시해라.

> 그리고 지적할 때마다 이렇게 물어봐라: 이 문제가 지금 이 사례에서만 생기는가, 아니면 앞으로 다른 경우에도 구조적으로 생기는가?

프롬프트 2: 구현 AI에게 (자기보고 차단)

> 작업 결과를 보고할 때 "완료했습니다 / 검증했습니다"라고 쓰지 마라.

> 대신 (1) 실제로 실행한 명령 원문, (2) 출력 전문, (3) 종료 코드, (4) 만들어진 파일의 크기와 지문을 붙여라.

> 실측하지 않았으면 "미실행"이라고 그대로 써라. 기억에 의존한 재확인은 증거로 인정하지 않는다.

프롬프트 3: 지적을 받았을 때 (전수 확대)

> 방금 받은 지적을 결함 유형 하나의 표본으로 취급해라.

> (1) 이 지적의 원리를 한 문장으로 뽑고

> (2) 같은 원리가 적용될 수 있는 곳을 전부 찾아 나열하고

> (3) 지적받은 곳뿐 아니라 전부 고친 다음

> (4) 같은 유형이 다시 들어오면 자동으로 걸리도록 검사를 추가해라.

프롬프트 4: 검사기를 못 믿을 때

> 이 검사가 실패를 실제로 잡아내는지 증명해라.

> 일부러 위반 상황을 만들어서 검사가 거부하는 걸 보여주고, 정상 상황 1건도 같이 넣어서 무조건 거부하는 게 아님을 보여라.

> 실제로 만들 수 없는 상황이 있으면 왜 못 만드는지 원문 그대로 적고, 대체 방법을 쓴 사실을 숨기지 마라.

3
3개의 답글
밀어주고 끌어주는

온·오프라인 AI 스터디

AI로 어디까지 할 수 있는지
직접 확인하실 분만 신청하세요.