AI 페어 프로그래밍을 위한 작동하는 결정적 파이프라인
AI 코더는 강력하지만 게이트가 없으면 표류합니다. 여기에 상태 파일과 계획 고정(plan freeze) 기능을 갖추고 AI 코더를 감싸는 계획, 빌드, 검증, 배포 파이프라인이 있습니다.
AI 어시스턴트에게 '그냥 모든 것을 다 해달라'는 지시와 함께 코딩 작업을 맡기면 예측 가능한 방식으로 실패합니다. 모델은 유능하며, 종종 눈앞의 좁은 영역에서는 예상보다 더 유능하지만, 작업이 어디서 끝나는지에 대한 감각이 없습니다. 단계 사이에서 맥락을 잃습니다. 자신이 발견한 무언가를 수정하기 위해 원래 목표에서 벗어납니다. 보기에는 올바른 코드를 작성하고 작성과 배포 사이에 테스트 없이 배포합니다. 무언가 고장 나면, 지난 40개의 수정 중 어느 것이 원인이었는지 알 수 없고, 깔끔하게 되돌릴 수도 없습니다.
해결책은 더 나은 프롬프트가 아닙니다. 그것은 하네스입니다. 인간이 설계하고 제어하는 결정론적 파이프라인으로 AI의 작업을 감쌉니다. 즉, 검토, 구현, 검증, 문서화, 배포 각 단계에 실패 시 실행을 중지하는 명시적인 게이트를 두는 것입니다. 모델은 각 단계 내부에서 창의성을 제공합니다. 파이프라인은 그 주변에서 규율을 제공합니다. 이 게시물은 그 하네스가 어떻게 구축되는지, 왜 모든 단계에 게이트가 필요한지, 그리고 전체 과정을 건너뛰는 것이 올바른 선택인 특정 경우에 대해 설명합니다.
유도되지 않는 AI가 표류하는 이유
실패 모드는 이름을 붙일 수 있을 만큼 일관적입니다. 이를 이해하면 어떤 게이트를 만들어야 할지 알 수 있습니다.
첫 번째는 컨텍스트 손실입니다. AI 어시스턴트는 최근 대화의 유한한 창(window) 내에서 작동합니다. 긴 작업에서는 그 창이 가득 차고, 가장 초기의 결정 사항들이 그 창에서 벗어나게 됩니다. 좁은 범위에 동의하며 시작했던 모델은 코드 깊숙이 들어갈 때쯤에는 더 이상 그 범위를 기억하지 못합니다. 이전 결정과 모순되는 말을 할 때, 그것은 거짓말을 하는 것이 아닙니다. 정말로 더 이상 그것을 볼 수 없기 때문입니다.
두 번째는 범위 확장(scope creep)입니다. 버그 수정을 요청하면 모델은 종종 개선할 수 있는 인접한 세 가지 사항을 발견하고 이를 개선하는데, 각각이 국소적으로는 합리적으로 보이기 때문입니다. 모든 개별 변경 사항은 정당화될 수 있습니다. 그 결과는 요청한 것보다 네 배나 큰 diff가 되고, 열려고 의도하지 않았던 파일을 건드리며, 실제 수정 사항은 그 안에 묻혀 있습니다.
세 번째는 검증 누락이며, 가장 큰 피해를 줍니다. 내버려 두면 AI는 코드가 올바르게 실행되는지가 아니라, 코드가 올바르게 보이는지에 근거하여 코드를 작성하고 성공을 보고합니다. 올바르게 보이는 것과 실제로 올바른 것은 흥미로운 버그가 존재하는 바로 그 지점에서 갈라집니다. 예를 들어 off-by-one 오류, 잘못된 필터, 로컬에서는 유지되었지만 배포된 환경에서는 실패한 가정 등이 있습니다. 코드를 실제로 실행하는 게이트가 없으면 모델의 자신감은 현실과 동떨어지게 됩니다.
낮은 가역성은 다른 문제들을 하나로 묶습니다. 작업이 무엇을 왜 결정했는지에 대한 기록 없이 하나의 크고 미분화된 변경 사항으로 도착하면, 롤백하는 것은 엉킨 매듭을 푸는 것과 같습니다. 기록하지 않은 결정은 되돌릴 수 없습니다.
이 중 어느 것도 모델의 순수한 능력을 불신할 이유는 되지 않습니다. 이것들은 구조화되지 않은 위임을 불신해야 할 이유입니다. 그 힘은 진짜입니다. 방향성과 게이트는 그 힘을 여러분이 지지할 수 있는 출시된 작업물로 바꾸는 것입니다.
5단계 파이프라인
하네스는 고정된 단계의 시퀀스입니다. 순서는 장식이 아닙니다. 각 단계는 다음 단계에 필요한 입력을 생성하며, 각 단계는 실행이 계속되기 전에 통과해야 하는 게이트로 끝납니다.
| 단계 | AI가 하는 일 | 게이트가 확인하는 것 | 실패 시 |
|---|---|---|---|
| 검토 | 컨텍스트를 읽고, 계획을 작성하고, 위험을 명시합니다 | 계획이 구체적이고, 범위가 한정되어 있으며, 사람이 승인합니다 | 중지, 계획 수정 |
| 구현 | 고정된 계획에 따라서만 코드를 작성합니다 | 코드가 컴파일되고, 타입이 확인됩니다 | 중지, 수정 또는 롤백 |
| 검증 | 테스트를 실행하고, 불변성을 확인합니다 | 테스트가 통과하고, 불변성이 유지됩니다 | 중지, 배포로 진행하지 않음 |
| 문서화 | 무엇이 왜 변경되었는지 기록합니다 | 변경 로그가 실제 diff와 일치합니다 | 중지, 조정 |
| 배포 | 일반적인 릴리스 경로를 통해 배포합니다 | 배포 후 상태 확인이 통과됩니다 | 롤백 |
검토는 사고가 이루어지는 단계이며 실수를 가장 저렴하게 잡아낼 수 있는 곳입니다. 모델은 관련 코드를 읽고, 무엇을 할 것인지 작성하며, 무엇이 잘못될 수 있는지 나열합니다. 사람이 그 계획을 읽고 승인하거나 반려합니다. 계획은 짧고 diff는 길기 때문에, 계획을 승인하는 것이 완료된 diff를 검토하는 것보다 훨씬 저렴하며, 여기서 잘못된 계획을 발견하면 롤백 대신 몇 분의 비용만 듭니다.
구현은 승인된 계획을 코드로 변환하며, 오직 승인된 계획만을 따릅니다. 검증은 이 파이프라인을 안내 없는 위임과 분리하는 게이트입니다. 코드를 실행하고 테스트가 실패하면 진행을 거부합니다. 문서화는 다음 사람이나 다음 AI 세션이 추론 과정을 재구성할 수 있도록 영구적인 기록을 남깁니다. 배포는 사람이 사용하는 것과 동일한 릴리스 경로를 통해 동일한 배포 후 확인을 거쳐 배포되며, 실패 시 절반만 배포된 상태로 두지 않고 롤백합니다.
상태는 모델의 메모리가 아닌 파일에 저장됩니다
AI 어시스턴트의 작업 메모리는 휘발성입니다. 세션을 닫거나, 컨텍스트 창을 초과하거나, 재시작을 유발하는 오류가 발생하면 모델이 가지고 있던 모든 것이 사라집니다. 파이프라인의 상태가 해당 메모리에만 저장되어 있다면, 리셋은 처음부터 다시 시작하는 것을 의미하며, 절반쯤 배포된 작업을 다시 시작하는 것은 위험합니다.
그래서 파이프라인은 상태를 파일에 씁니다. 현재 단계, 내려진 결정, 완료된 항목, 남은 항목 등 모든 것이 디스크에 저장되며, 작업이 진행됨에 따라 업데이트됩니다. 모델은 모든 단계의 시작에서 이 상태를 읽고 단계가 끝날 때 상태를 씁니다. 휘발성 컨텍스트는 신뢰할 수 있는 단일 소스(source of truth)가 아니라 영구적인 상태의 캐시가 됩니다.
pipeline-state:
task: "add rate limiting to the public API"
stage: verify # review -> implement -> verify -> document -> deploy
plan_frozen: true
decisions:
- "token bucket, 100 req/min per key, chosen over sliding window for simplicity"
- "limit stored in existing cache layer, no new dependency"
progress:
- [x] middleware written
- [x] unit tests for bucket refill
- [ ] integration test under concurrent load
failures:
- none
그 효과는 최악의 순간에 나타납니다. 작업 중간에 세션이 종료됩니다. 새 세션이 시작되지만 이전 컨텍스트는 전혀 없습니다. 상태 파일을 읽고 작업이 어디까지 진행되었는지 정확히 파악합니다. 즉, 계획은 확정되었고, 구현은 완료되었으며, 하나의 검증 단계가 남아 있습니다. 추측하거나, 더 나쁘게는 완료된 작업을 다시 실행하여 올바른 것을 되돌리는 대신, 중단된 지점부터 작업을 재개합니다. 파일에 상태를 저장하는 것은 AI 기반 작업에 특히 발생하기 쉬운 중단을 극복하게 만드는 핵심입니다.
계획 동결로 범위 확장 방지
검토 단계에서 승인된 계획이 생성되면 파이프라인은 이를 동결합니다. 동결은 범위가 잠겼음을 의미합니다. 구현하는 동안 AI는 계획에 항목을 추가하거나 제외할 수 없습니다. 합의된 내용만 정확히 빌드합니다.
이는 범위 확장(scope creep) 실패를 직접적으로 겨냥합니다. 동결이 없으면 인접한 문제를 발견하고 수정하는 모델의 습관이 제어되지 않아 diff가 비대해집니다. 동결이 있으면 그 본능은 벽에 부딪히게 됩니다. 구현 도중에 모델이 다른 변경이 필요하다고 판단하더라도 단순히 변경을 수행하지 않습니다. 해당 변경은 사람이 보고 승인하거나 거부할 수 있도록 계획에 대한 명시적인 수정안으로 기록되어야 합니다.
규칙은 범위가 절대 변경될 수 없다는 것이 아닙니다. 실제 작업에서는 예상치 못한 일이 발생하며, 이에 적응하기를 거부하는 것 자체가 또 다른 실패입니다. 규칙은 범위 변경이 조용히 누적되는 것이 아니라, 가시적이고 의도적으로 이루어져야 한다는 것입니다. 기록된 수정안의 짧은 목록이 있는 동결된 계획은 감사가 가능합니다. 우연히 커진 동결되지 않은 계획은 아무도 선택하지 않은 큰 diff일 뿐입니다.
plan (frozen at review):
1. add token-bucket middleware
2. wire it into the public router
3. unit + integration tests
amendment (recorded during implement, approved):
4. add a config flag to disable the limit per environment
reason: staging load tests need it off
파이프라인이 수정한 모든 것은 동결된 계획이나 기록된 수정안으로 추적됩니다. 아무도 동의하지 않은 내용은 diff에 나타나지 않습니다.
중지 규칙은 실패가 누적되는 것을 방지합니다
오류가 발생한 AI 어시스턴트는 다시 시도합니다. 이는 보통 좋은 현상입니다. 하지만 동일한 오류가 발생하여 동일한 해결 방법을 시도하는 어시스턴트는 루프에 빠져 때로는 오랜 시간 동안 노력을 낭비하고, 매번 시도할 때마다 상황을 악화시키기도 합니다. 파이프라인에는 언제 중지해야 하는지에 대한 엄격한 규칙이 필요합니다.
첫 번째 규칙은 반복 실패 제한입니다. 동일한 단계가 연속으로 두 번 실패하면, 파이프라인은 세 번째 시도를 하는 대신 중단하고 사람에게 문제를 알립니다. 두 번의 동일한 실패는 모델이 문제를 이해하지 못했다는 것을 의미하며, 더 많은 자율적인 시도로는 그 이해를 만들어낼 수 없습니다. 그저 헛돌기만 할 것입니다. 두 번에서 멈추는 것은 가능한 무한 루프를 명확한 인계 지점이 있는 유한 루프로 바꿉니다.
두 번째 규칙은 파괴적인 작업에 대한 별도의 관문입니다. 데이터베이스 삭제, 테이블 삭제, 히스토리 재작성, 강제 푸시(force-pushing) 등 되돌리기 어렵거나 불가능한 모든 작업은 일반적인 단계와 동일한 자동 승인을 통과하지 않습니다. 해당 특정 작업에 대해서는 명시적이고 구체적인 사람의 확인이 필요합니다. 그 이유는 비대칭성에 있습니다. 되돌릴 수 있는 실수는 롤백 비용이 들지만, 되돌릴 수 없는 실수는 다시는 되찾을 수 없는 데이터 손실을 초래할 수 있으므로, 그 앞의 관문은 더 무거워야 합니다.
on step failure:
record failure in state
if failures_of_this_step >= 2:
HALT and surface to human # no third attempt
else:
retry with adjusted approach
before any destructive action:
require explicit human approval for THIS specific action
# never covered by a blanket "yes, proceed"
이 규칙들은 함께 폭발 반경(blast radius)을 제한합니다. AI는 안전하고 되돌릴 수 있는 작업의 중간 과정에서는 자율적으로 행동할 수 있으며, 파이프라인은 자율성이 가장 위험한 두 지점, 즉 막혔을 때와 되돌릴 수 없는 작업을 하려고 할 때 정확히 사람이 개입하도록 합니다.
래퍼가 결정적이어야 하는 이유
이 파이프라인의 중심에 있는 AI는 본질적으로 비결정적입니다. 동일한 요청을 두 번 주면 두 개의 다른 답변, 두 개의 다른 구현, 동일한 단계의 두 개의 다른 순서를 얻을 수 있습니다. 그 가변성은 결함이 아닙니다. 그것은 애초에 모델을 유용하게 만드는 창의성의 이면입니다. 당신은 그것이 솔루션 공간을 탐색하기를 원합니다.
그것을 둘러싼 파이프라인은 그 반대여야 합니다. 단계들은 고정된 순서로 실행됩니다. 게이트는 매번 동일한 검사를 적용합니다. 상태는 동일한 장소에 동일한 방식으로 기록됩니다. 동일한 상태 파일이 주어지면, 파이프라인은 동일한 지점에서 재개되고 동일한 작업을 수행합니다. 이것이 핵심에 있는 무작위성에도 불구하고 전체 구성을 신뢰할 수 있게 만드는 것입니다.
래퍼의 결정성은 세 가지 구체적인 것을 보장합니다. 재현성: 한 단계 내에서 모델의 출력이 다르더라도 프로세스를 다시 실행하여 프로세스가 동일하게 동작하도록 할 수 있습니다. 감사 가능성: 모든 결정과 모든 단계 전환이 기록되기 때문에, 정확히 무슨 일이 왜 일어났는지 사후에 재구성할 수 있습니다. 신뢰: 예측 가능한 게이트가 출구에 서서 작업 결과가 유효해지기 전에 확인하고 있기 때문에, 단계 내에서 모델이 진정한 자율성을 가지고 행동하도록 할 수 있습니다.
역할 분담이 바로 이 모든 것의 핵심 아이디어입니다. AI는 변형과 탐색으로부터 이점을 얻는 부분인 창의성을 담당합니다. 파이프라인은 절대 변해서는 안 되는 부분인 규율을 담당합니다. 어느 쪽도 다른 쪽의 일을 하지 않습니다. 창의적인 파이프라인은 혼돈일 것입니다. 규율 잡힌 AI는 사용할 가치가 없을 것입니다.
파이프라인을 건너뛰고 그냥 요청해야 할 때
하네스는 공짜가 아닙니다. 계획 작성, 상태 파일 유지보수, 각 게이트 통과, 문서 기록과 같은 실질적인 오버헤드가 있습니다. 올바른 작업에 대해서는 그 오버헤드가 오히려 이득입니다. 잘못된 작업에 대해서는 2분짜리 일을 질식시키는 관료주의일 뿐입니다.
작업이 작고, 일회성이며, 쉽게 되돌릴 수 있을 때는 파이프라인을 건너뛰세요. 파일 전체에서 변수 이름 바꾸기. 읽고 버릴 일회용 스크립트 초안 작성하기. 일부 코드가 어떻게 작동하는지에 대한 질문에 답하기. 블록 서식 다시 지정하기. 이 중 어떤 경우든, 고정된 계획과 상태 파일이 있는 5단계 파이프라인을 가동하는 것은 순전한 낭비입니다. 그냥 모델에게 요청하고 결과를 읽으세요. 실수의 대가는 한 번 훑어보고 실행 취소하는 것뿐입니다.
작업이 반복적이고, 중요하며, 되돌리기 어려울 때, 특히 배포로 끝나는 경우에는 전체 파이프라인을 사용하세요. 프로덕션으로 출시되는 모든 것. 데이터베이스 스키마를 건드리거나 데이터를 마이그레이션하는 모든 것. 팀 동료가 그 위에 구축할 모든 것. 나중에 설명하거나 감사해야 할 모든 것. 여기서 오버헤드는 전혀 오버헤드가 아닙니다. 이는 안내 없는 위임이 초래하는 바로 그 실패, 컨텍스트 손실, 범위 확장, 검증되지 않은 배포, 되돌릴 수 없는 변경에 대비할 수 있는 가장 저렴한 보험입니다.
결정적인 질문은 간단합니다. 실수의 대가는 무엇인가? 대답이 한 번 훑어보고 실행 취소하는 것이라면, 하네스를 건너뛰고 모델이 자유롭게 실행되도록 하세요. 대답이 프로덕션 사고, 데이터 손실, 또는 추적 불가능한 diff를 통한 한나절의 포렌식 고고학이라면, 그것을 감싸세요. 프로세스의 무게를 결과의 무게에 맞추고, 리스크가 허용하는 한 AI가 빠르고 자유롭게 작동하도록 하세요.
관련 글
별칭을 더 만드는 대신, AI 에이전트를 셸 프런트엔드로 사용하세요
잊혀진 셸 스크립트의 무덤에는 대안이 있습니다. 작업을 평이한 말로 설명하면 AI가 여러분을 위해 grep, awk, jq를 조합해 줍니다.
Rust에서 Go로 백엔드 이전, 그리고 빌드 시간이 결정적이었던 이유
Rust는 우리에게 거의 필요하지 않은 안전성을 주었고, 매일 싸워야 하는 컴파일 루프를 안겨주었습니다. 한 서비스를 Go로 전환하게 만든, 수치를 포함한 솔직한 트레이드오프가 여기에 있습니다.
영어로 한 번 작성하고, 자동 번역하여 더 많은 독자에게 다가가세요
공개적인 글쓰기는 당신을 성장시키고 다른 사람들을 돕지만, 하나의 언어는 도달 범위를 제한합니다. 영어로 원본을 하나 작성하면, 파이프라인이 나머지를 번역하고 게시하도록 하세요.