나는 AI 에이전트를 사용하여 코딩한다: 신뢰를 구축하기 위해 만든 4가지 점검 사항
요약
1인 창업자가 AI 에이전트를 활용해 코딩할 때 발생할 수 있는 결함을 방지하기 위한 4가지 점검 규칙을 소개합니다. 에이전트의 결과물을 신뢰하기 위해 자동화된 테스트와 명확한 예외 처리의 중요성을 강조합니다.
핵심 포인트
- 규칙은 반드시 자동화된 테스트로 검증 가능해야 함
- 에이전트가 불확실한 상황에서 임의의 기본값(Fallback)을 사용하지 않도록 방지
- 결과물이 리뷰 가능한 범위를 초과할 때 발생하는 결함 패턴 분석
- 실행되지 않는 아티팩트나 중복 코드 생성 문제 해결
나는 1인 스타트업 창업자입니다. TribeROI를 합리적인 시간 내에 출시하기 위해, 나는 코드베이스를 읽고, 코드를 작성하며, 테스트를 실행하고, 풀 리퀘스트 (Pull Request)를 생성하는 AI 에이전트 (AI agents)를 사용합니다. 나의 역할은 코드를 리뷰하고 머지 (Merge)하는 것입니다.
이 프로세스가 작동하게 만드는 것은 에이전트가 아니라, 그들이 만들어낸 결과물을 신뢰할 수 있도록 내가 구축한 일련의 점검 사항들입니다.
모두가 AI가 나쁜 코드를 작성할까 봐 걱정합니다. 나쁜 코드는 한 번도 나의 문제가 아니었습니다. 문제는 에이전트가 한 사람이 제대로 리뷰할 수 있는 양보다 더 많은 결과물을 만들어낸다는 것이며, 바로 이때 결함이 드러납니다. 몇 주 전, 나는 단 하루 만에 12개의 큰 결함을 발견했는데, 이 결함들은 모두 나의 테스트를 통과한 상태였습니다. 나는 라이브 시스템을 읽으면서 겨우 그것들을 찾아냈습니다.
실패는 세 가지 패턴으로 나타났습니다. 첫째, 존재는 하지만 아무것도 실행하지 않는 아티팩트 (Artifacts). 둘째, 답을 찾지 못해 그럴듯한 기본값 (Default)으로 대체해 버리는 시스템. 셋째, 동일한 문제를 해결하는 기존 코드가 있는지 확인하지 않고 작업을 다시 구축하는 경우입니다.
두 번째 패턴은 나를 두 번이나 곤경에 빠뜨렸습니다. 에이전트가 무언가를 결정할 수 없을 때, 폴백 (Fallback)을 작성하곤 합니다. 이것이 에이전트에게는 합리적으로 보일지 모르지만, 결과적으로 프로덕션 (Production) 환경에 중복된 리소스를 생성하게 됩니다.
다음은 에이전트의 코드에 대해 더 높은 확신을 갖기 위해 내가 구축한 네 가지 규칙입니다.
1. 규칙은 테스트여야 하며, 그렇지 않다면 존재하지 않는 것이다
내가 유지하는 모든 안전 규칙은 누군가 규칙을 어겼을 때 실패하는 구체적인 자동화된 점검 (Automated check) 항목을 명시해야 합니다. 만약 명시할 수 없다면, 그것은 보장이 아니며 "체크해야 할 사항들"이라는 목록으로 분류됩니다.
내 규칙들을 그 기준에 맞춰 검토했을 때, 코드 리뷰 체크리스트에 있던 11개 항목 중 8개가 아무것도 강제하지 못하고 있었습니다. 그것들은 결과에 아무런 변화를 주지 않는, 단순히 체크만 하는 칸들이었습니다. 내가 "필수 요구 사항"이라고 표시했던 8개 규칙 중 3개도 같은 형태였으며, 한 무더기의 알림들은 내가 작성한 날 이후로 아무런 역할도 하지 못했습니다. 나는 그 체크리스트를 읽으며 모든 것이 괜찮다고 생각하고 있었습니다.
강제되지 않는 규칙은 규칙이 아예 없는 것보다 더 나쁩니다. 왜냐하면 그것에 의존하게 되기 때문입니다.
2. "알 수 없음"이 "괜찮음"처럼 보이게 두지 마세요
이것은 네 가지 규칙 중 가장 가치가 높으면서도 실행 비용이 가장 적게 드는 규칙입니다. 코드가 "이것을 결정할 수 없습니다"를 특정 값으로 변환하는 모든 곳을 찾아내어, 그렇게 하지 못하도록 만드세요.
이 문제는 애플리케이션 코드에서 치명적입니다. 제가 작성한 코드 중 하나는 인식하지 못하는 권한 역할(permission role)을 만났을 때, 안전한 선택이라고 느껴져 최소 권한(least privilege)으로 폴백(fallback)했습니다. 그 결과, 유료 고객 8명이 자신의 계정 내 관리자 화면에 접속하지 못하게 되었습니다. 아무런 오류도 발생하지 않았습니다. 폴백은 제가 지시한 대로 정확히 작동하고 있었기 때문입니다.
더 큰 비용이 발생했던 사례는 클라우드 리소스를 생성하거나 업데이트하는 운영(ops) 스크립트에서였습니다:
CHANNEL=$(gcloud beta monitoring channels list --format="value(name)" | head -n1)
파이프(|)는 종료 상태(exit status)를 head로 전달하며, head는 성공적으로 실행되므로 set -e가 작동하지 않습니다. 일시적인 인증 오류(auth blip)가 빈 문자열이 되고, 빈 문자열은 "존재하지 않음"으로 읽히며, 스크립트는 이미 존재하는 리소스의 두 번째 복사본을 즐겁게 생성합니다. 그 일이 7월 7일에 일어났습니다. 저는 그 스크립트 하나를 패치하고 상황이 종료되었다고 생각했지만, 7월 21일에 동일한 형태의 문제가 다른 곳에서 발생하여 11개의 알림 정책(alert policies)이 가리키고 있던 알림 채널을 고아(orphan) 상태로 만들었습니다.
두 번째 경험은 첫 번째보다 더 많은 것을 가르쳐 주었습니다. 모든 코드 경로가 의존하는 사실은 모든 경로가 호출하는 단 한 곳에 있어야 합니다:
must_read() {
if ! MR_OUT=$("$@" 2>"$MR_ERR"); then
echo "!! READ FAILED, refusing to continue: $*" >&2
...
VAR=$(must_read ...)와 같이 작성하면, 실패가 전파되고 할당이 실패하며, set -e가 실행을 종료합니다. 결과는 두 가지 중 하나로 남습니다: 실제 값이거나, 혹은 중단입니다.
3. 결과물뿐만 아니라 연결 구조(wiring)를 테스트하세요
파일이 존재한다는 사실은 그것이 작동한다는 증거처럼 보이지만, 정작 무엇인가가 그 파일을 실행하는지는 거의 아무도 확인하지 않습니다. 저는 경고 정책(alert policy)을 JSON으로 배포하고 매니페스트(manifest)에 선언했지만, 적용(apply) 스크립트는 해당 정책에 대해 apply_policy를 호출하지 않았습니다. 드라이 런(dry-run)은 이 모든 상황을 숨겼는데, 검증기(validator)는 디렉토리 전체를 스캔하는 반면, 적용기(applier)는 각 정책을 수동으로, 정책당 한 줄씩 이름을 지정하기 때문입니다. 하나는 파일을 확인했지만 다른 하나는 확인하지 못했습니다.
그래서 이제는 적용 스크립트에서 자체 호출을 grep으로 찾아 양방향을 모두 확인하는 테스트가 있습니다. 모든 선언된 정책이 적용되어야 하며, 모든 호출은 선언된 무언가를 가리켜야 합니다. 투박하고 자랑스럽지는 않지만, 파일에 접근한다는 것을 증명하는 유일한 방법입니다.
다른 단언(assertion) 중 복사할 가치가 있는 것은 다음과 같습니다:
def test_the_policy_pair_is_not_vacuous() -> None:
assert ALERT_POLICIES, "선언된 정책이 없음 — 가드(guard)가 무의미해짐"
assert _applied_policy_patterns(), "apply_policy 호출이 없음 — 동일함"
빈 컬렉션을 반복하는 가드는 아무것도 보지 않음으로써 통과해 버립니다. 가드가 읽는 상수의 이름을 바꾸면 양쪽 모두 빈 값이 되어, 아무것도 증명하지 못하면서도 체크는 계속 통과(green) 상태로 남게 됩니다. 이는 한 단계 위에서 발생하는 것과 동일한 실패입니다. 만약 가드를 작성한다면, 이미 이러한 상황 중 하나가 발생하고 있다고 가정하십시오.
4. 에이전트의 권한은 명령이 아니라 메커니즘입니다
저는 에이전트가 몇 시간 동안 무인 상태로 작동하는 모드를 운영하며, 에이전트에게 Bash(gcloud:*) 권한을 즉시 부여합니다. 왜냐하면 아무도 대답할 수 없는 권한 승인 프롬프트는 단순히 단계만 더 복잡한 대기 상태(hang)를 만들 뿐이기 때문입니다. 이것이 안전한 이유는 실제 경계(boundary)가 다른 곳에 있기 때문입니다. 즉, gcloud가 기본적으로 거부(default-deny) 상태이며, 읽기 작업과 지정된 추가 명령 세트만 허용하고 나머지는 모두 거부하는 훅(hook)이 존재하기 때문입니다.
설정 파일(config file)만으로는 이 작업을 수행할 수 없습니다. Claude Code에서는 허용(allow) 규칙이 아무리 구체적이라 하더라도 ask가 allow보다 우선하기 때문에, 좁은 범위의 allow를 광범위한 ask(gcloud *) 옆에 두는 것은 죽은 코드(dead code)나 다름없습니다. 접두사 매칭(Prefix matching) 또한 취약하여, 공백이 두 개인 gcloud run은 Bash(gcloud run *)와 일치하지 않습니다. 무언가를 허용하는 방식으로서 이는 허점(hole)이 됩니다. 다만 (default-deny) 훅(hook) 내부에서는 인식되지 않는 모든 것이 어차피 거부되므로 무해합니다.
훅의 catch 블록 또한 거부(deny)를 수행합니다. 제가 작성한 다른 모든 가드(guard)들은 실패 시 허용(fail open)되도록 설계되었으며, 이는 거의 모든 곳에서 올바른 결정입니다. 하지만 이 규칙은 광범위한 권한 부여와 프로덕션(production) 사이를 막아주는 유일한 장치이므로, 충돌(crash)이 발생했을 때는 추측하기보다 거부해야 합니다.
내가 가장 먼저 구축할 것
두 번째 규칙을 예로 들어보겠습니다. 코드베이스를 검색하여 무언가를 결정할 수 없을 때 실행되는 폴백(fallback)들을 찾아내고, 각각의 폴백이 대신 중단(stop)되도록 만드세요. 이는 빠르고 새로운 도구가 필요하지 않으며, 놀라울 정도로 자주 발생하는 실패 유형입니다.
이 모든 규칙을 관통하는 통찰은, 시스템이 명백히 그렇지 않은 상황에서도 자신 있게 성공을 보고한다는 점입니다. AI 개발에서 에이전트(agent)는 이 문제를 더욱 심각하게 만듭니다. 에이전트는 사용자가 읽는 속도보다 더 빠르게 코드를 생성하기 때문에, 이러한 점검 사항들은 반드시 확장성(scale)을 갖추어야 합니다.
다른 분들은 에이전트 기반 개발 워크플로우(agentic development workflows)를 강화하기 위해 무엇을 구축하고 있는지 듣고 싶습니다. 특히 제가 언급하지 않은 유형의 실패를 경험하셨다면 더욱 좋습니다. 댓글로 알려주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기