
AI가 코드를 고치기 전에 계획서를 내게 만들기 — 실행 관문 다섯 개
AI 코딩 에이전트가 엉뚱한 파일을 고치는 걸 막으려면 규칙보다 관문이 낫다. 계획 제출과 변경 지문 검증을 강제한 CLI 설계를 정리했다.
AI 코딩 에이전트에게 작은 수정을 맡길 때 자주 겪는 일이 있다.
“이 함수의 버그를 고쳐줘”라고 했는데, 돌아와 보면 다섯 개 파일이 바뀌어 있다. 그중 셋은 요청과 상관없는 리팩터링이다. 테스트가 깨지고, 뭘 되돌려야 할지 알 수 없다.
문제는 에이전트가 나쁜 일을 해서가 아니다. 어디까지가 요청 범위인지 아무도 정하지 않았기 때문이다.
규칙으로 막으려던 시도
앞서 다른 글에서 쓴 적이 있는데, 규칙 문서로 이걸 막으려 하면 잘 안 된다. “요청 범위 밖의 파일은 수정하지 마라”고 적어도 가끔 어긴다. 그리고 그 “가끔”에 사고가 난다.
그래서 접근을 바꿨다. 부탁하는 대신 통과해야 하는 관문을 만든다. 그렇게 만든 게 실행 관문이 있는 코드 에이전트 CLI다.
관문 다섯 개
핵심은 다섯 가지 강제 규칙이다.
1. 계획 제출 없이는 아무것도 못 한다
에이전트는 먼저 계획을 제출해야 한다. 계획이 없으면 파일 변경도, 테스트 실행도 허용되지 않는다.
이게 첫 번째 관문인 이유는, 계획을 쓰게 하면 범위가 드러나기 때문이다. “이 파일의 이 함수를 이렇게 고치겠다”고 적으면 그게 곧 범위 선언이 된다. 나중에 다른 파일을 건드리면 계획과 어긋나는 게 보인다.
2. 읽지 않은 파일은 수정할 수 없다
이게 실질적으로 가장 효과가 컸다.
LLM은 파일 내용을 모르는 상태에서도 그럴듯한 수정안을 만들 수 있다. 함수 이름과 맥락만 보고 “아마 이렇게 생겼겠지” 하고 패치를 생성한다. 이게 맞을 때도 있지만 틀리면 조용히 망가진다.
읽기를 선행 조건으로 걸면 이 경로가 막힌다. 추측으로 고칠 수 없다.
3. propose → apply 순서를 강제한다
변경을 제안(propose)하는 단계와 적용(apply)하는 단계가 분리돼 있다. 제안만 하면 파일은 그대로다.
./run.sh add --dry-run "README 요약을 업데이트" # 변경안만 생성, 파일 안 건드림
./run.sh add --apply "테스트 실패 원인 수정" # 적용 + 테스트 실행
이 분리가 있으면 “일단 보고 결정”이 가능해진다. 한 번에 적용되는 구조에서는 결과를 본 뒤에야 되돌릴 수 있다.
4. 변경 지문이 일치해야 적용된다
제안 시점의 파일 상태와 적용 시점의 파일 상태가 같아야 한다. 그 사이에 파일이 바뀌었으면 적용이 거부된다.
이건 낙관적 잠금(optimistic locking)과 같은 발상이다. 에이전트가 제안을 만드는 동안 내가 그 파일을 편집했다면, 제안은 이미 낡은 전제 위에 서 있다. 그대로 적용하면 내 수정이 조용히 덮인다.
5. 테스트가 실패하면 범위를 좁혀 재계획한다
테스트가 깨지면 그냥 “고쳐볼게요” 하고 다시 시도하지 않는다. 초점 범위를 좁히고 재계획을 강제한다.
이유는 실패한 상태에서 넓은 범위로 다시 시도하면 변경이 눈덩이처럼 불어나기 때문이다. 원래 한 줄 고치려던 게 열 파일이 되는 경로가 대체로 이거다.
모든 실행을 기록한다
관문과 별개로, 모든 실행이 실행 기록 디렉토리에 남는다. 계획, 제안, 적용, 테스트 결과가 순서대로 쌓인다.
이게 필요한 이유는 뭐가 왜 바뀌었는지 나중에 물어볼 수 있어야 하기 때문이다. git 히스토리에는 결과만 남는다. “왜 이 파일을 건드렸나”는 계획서에 있다.
이 구조의 비용
솔직히 적으면, 느리다.
계획을 쓰고, 파일을 읽고, 제안하고, 확인하고, 적용한다. 한 줄짜리 오타 수정에도 이 절차를 거친다. 그냥 고치면 3초인 걸 30초 쓴다.
그래서 이건 모든 작업에 쓰는 도구가 아니다. 잘못되면 곤란한 변경, 되돌리기 어려운 수정, 남이 쓰는 코드에 손댈 때 쓰는 도구다. 실험적인 코드를 빠르게 굴릴 때는 오히려 방해가 된다.
도구를 만들 때 “언제 쓰지 않을지”를 같이 정해두는 편이 낫다. 만능이라고 주장하는 순간 아무도 안 쓴다.
왜 Rust 로 만들었나
이 CLI는 Rust로 짰다. 이유는 두 가지다.
첫째, 단일 바이너리로 배포된다. 파이썬으로 만들면 가상환경과 의존성이 따라붙는다. 여러 프로젝트에 가져다 쓸 도구인데 프로젝트마다 환경을 만들고 싶지 않았다.
둘째, 파일을 읽고 지문을 계산하고 패치를 적용하는 게 이 도구의 대부분인데, 여기서 실수하면 남의 코드가 깨진다. 컴파일 단계에서 잡아주는 게 많을수록 좋다.
앞서 토큰 사용량 도구는 파이썬 표준 라이브러리로 만들었다. 기준은 같다. 틀렸을 때의 손해가 얼마나 큰가. 사용량을 잘못 세면 숫자가 틀리고, 패치를 잘못 적용하면 코드가 사라진다.
정리
- 규칙 문서로 범위를 지키게 하는 건 확률적이다. 관문은 결정적이다
- 계획 제출을 선행 조건으로 걸면 범위가 문서로 남는다
- 읽지 않은 파일은 수정 못 하게 하는 것만으로 추측 패치가 사라진다
- propose 와 apply 를 분리하면 “보고 결정”이 가능해진다
- 변경 지문을 검증해 그 사이의 편집이 덮이지 않게 한다
- 테스트 실패 시 범위를 넓히지 말고 좁힌다
- 이런 도구는 느리다. 언제 쓰지 않을지도 함께 정해둔다