에이전틱 엔지니어링(Agentic Engineering)은 바이브 코딩(Vibe Coding)이 아니다: 분산 시스템을 구축하기 위해 내가
요약
바이브 코딩과 에이전틱 엔지니어링의 차이를 정의하며, 분산 시스템 구축을 위한 명세 기반 개발과 루프 엔지니어링 워크플로우를 제안합니다. Claude Code를 활용해 모호한 의도를 검증 가능한 명세로 전환하고 피드백 루프를 설계하는 시스템적 접근법을 다룹니다.
핵심 포인트
- 바이브 코딩은 속도 중심이나, 에이전틱 엔지니어링은 검증된 결과물 중심임
- 명세 기반 개발은 의도를 에이전트가 읽을 수 있는 문서화된 산출물로 관리함
- 루프 엔지니어링은 에이전트의 오류를 빠르게 식별하는 피드백 루프 설계임
- 에이전트 활용은 단순 프롬프팅이 아닌 상위 수준의 시스템 설계 영역임
현재 우리 업계에는 범주 오류(category error)가 퍼져 있으며, 이는 팀들에게 실제 비용 손실을 초래하고 있습니다.
그 오류란 '바이브 코딩 (vibe coding)'과 '에이전틱 엔지니어링 (agentic engineering)'을 단순히 열정의 정도만 다른 동일한 활동으로 취급하는 것입니다. 이 둘은 동일한 활동이 아닙니다. 이들은 서로 다른 작업 단위, 서로 다른 실패 모드(failure modes), 서로 다른 산출물(artifacts)을 가지며, 무엇보다 중요한 점은 타인의 자금을 이동시키는 프로덕션 결제 시스템에 적용했을 때 경제적 측면이 완전히 다르다는 것입니다.
저는 생업으로 분산 시스템을 구축합니다. 최근에는 대부분 결제 인프라를 다루어 왔는데, 이는 제가 아는 곳 중 오류가 발생했을 때 가장 용납되지 않는 분야입니다. 지난 시간 동안 저는 대략 3분의 2는 명세 기반 개발 (spec-driven development)이고, 나머지 3분의 1은 루프 엔지니어링 (loop engineering)으로 구성된 워크플로우로 수렴해 왔으며, 이를 서로 인계하는 세 가지 Claude Code 기술로 구현했습니다.
이 포스트는 해당 워크플로우와, 아무도 경고해주지 않았지만 이 과정에서 망가졌던 것들에 대해 다룹니다.
명확하게 정의하는 차이점
| 바이브 코딩 (Vibe coding) | 에이전틱 엔지니어링 (Agentic engineering) | |
|---|---|---|
| 최적화 대상 | 첫 출력까지의 시간 (Time-to-first-output) | 검증된 증분까지의 시간 (Time-to-verified-increment) |
| ... |
바이브 코딩은 정당한 방식입니다. 저는 스파이크(spikes), 일회성 스크립트, 익숙하지 않은 API를 탐색할 때 끊임없이 사용합니다. 하지만 멱등성(idempotency) 요구 사항이 있는 모든 작업에는 최악의 방식입니다.
이 포스트의 나머지 내용은 두 번째 열에 관한 것입니다.
명세 기반 개발과 루프 엔지니어링의 결합
여기에는 두 가지 핵심 아이디어가 작용하고 있으므로, 제가 실제로 사용하는 방식대로 정의해 보겠습니다.
**명세 기반 개발 (Spec-driven development)**이란 의도(intent)가 단일 에이전트 세션보다 오래 지속되는 산출물(artifact)에 존재함을 의미합니다. 프롬프트(prompt)에 있는 것이 아닙니다. 당신의 머릿속에 있는 것도 아닙니다. Slack 스레드에 있는 것도 아닙니다. 에이전트가 매 실행 시작 시점에 다시 읽을 수 있고, 사람이 검토하고 승인하며, '바이브(vibe)'의 변화가 아닌 가시적인 편집을 통해 변경되는 문서 안에 존재해야 합니다.
**루프 엔지니어링 (Loop engineering)**은 에이전트가 작동하는 피드백 루프 (feedback loop)를 의도적으로 설계하는 것을 의미합니다. 에이전트가 무엇을 실행할 수 있는가? 무엇이 에이전트에게 자신이 틀렸음을 빠르고 명확하게 알려줄 수 있는가? 어디에서 멈추고 사람에게 에스컬레이션 (escalate) 해야 하는가?
타이트한 루프 (tight loop)가 없는 모델은 매우 비싼 자동 완성 (autocomplete) 기능에 불과합니다. 타이트한 루프를 가진 모델은 테스트 스위트 (test suite)를 실행하는 것을 전혀 지루해하지 않는 동료와 같습니다.
둘 중 어느 것도 프롬프팅 트릭 (prompting trick)이 아닙니다. 둘 다 코드보다 한 단계 높은 수준에서 적용되는 시스템 설계 (systems design)입니다.
워크플로우: 세 가지 기술, 하나의 핸드오프 체인 (handoff chain)
rough prompt
│
▼
...
1. /to-prd: 모호한 의도를 명세서 (specification)로 전환하기
모든 기능과 모든 사소하지 않은 버그는 여기서 시작됩니다. 저는 대략적인 프롬프트 (rough prompt)를 제공합니다. 때로는 당혹스러울 정도로 대략적일 때도 있는데, 문장 하나와 Sentry 이슈 링크 하나가 전부인 경우도 있습니다.
이 기술은 다음 네 가지를 수행합니다:
- 프롬프트를 후보 요구사항 (candidate requirements)으로 추론합니다.
- 실제 코드베이스 (codebase)를 읽어 해당 요구사항을 존재하는 것에 기반하도록 구체화합니다.
- 저에게 명확히 하기 위한 질문 (clarifying questions)을 던집니다.
- 완전한 PRD (제품 요구사항 문서), 사용자 스토리 (user stories), 결과물 (outcomes), 명시적인 범위 외 사항 (explicit out-of-scope)을 생성하여 GitHub Issues에 게시합니다.
세 번째 단계가 전체 가치 제안 (value proposition)의 핵심이며, 제가 이를 깨닫기까지는 시간이 좀 걸렸습니다.
명확히 하기 위한 질문은 모호함을 6주 차의 결함 (defect)이 아닌, 3분 차의 결정 (decision)으로 전환합니다. 제가 _"결제 웹훅 (settlement webhook)에 재시도 로직 (retry logic)을 추가해줘"_라고 작성할 때, 바이브 코딩 (vibe-coded) 세션은 저에게 재시도 로직을 제공합니다. 하지만 /to-prd는 다음과 같이 되묻습니다:
- 제공업체의 키 (provider's key) 기준인가요, 아니면 우리의 키 기준인가요? (Idempotent)
- 두 번째 시도가 첫 번째 시도보다 먼저 도착하면 순서 (ordering)는 어떻게 되나요?
- 오염된 메시지 (poisoned message)는 DLQ (Dead Letter Queue)로 가나요, 아니면 파티션 (partition)을 차단하나요?
- 부분 결제 (partial settlement)가 유효한 최종 상태 (terminal state)인가요?
저는 이 질문들 중 두 개에 대한 답을 몰랐습니다. 이것이 핵심입니다. "내가 요청한 것"과 "내가 의도한 것" 사이의 간극이 바로 운영 환경의 장애 (production incidents)가 발생하는 지점이며, 명세서 검토 (specification pass)는 그 간극을 조기에 찾아내는 기계입니다.
범위 외 사항 (out-of-scope) 섹션은 하중을 견디는 핵심 요소입니다.
이 부분에 대해서는 매우 강조하고 싶습니다. 에이전트(Agents)는 의욕이 넘칩니다. 해결해야 할 정산 웹훅(settlement webhook)이 주어지면, 제약이 없는 에이전트는 재시도 유틸리티(retry utility)를 리팩터링하고, 서킷 브레이커(circuit breaker)를 도입하며, 그 과정에서 세 가지 정도의 이름을 바꿔버릴 것입니다.
명시적인 범위 외 사항(out-of-scope) 목록은 전체 워크플로우에서 가장 비용이 적게 드는 가드레일(guardrail)입니다. 이는 200줄짜리 디프(diff)와 아무도 리뷰할 수 없는 2,000줄짜리 디프 사이의 차이를 만듭니다.
로컬 파일 대신 GitHub Issues에 게시하는 것은 의도적인 것입니다. 이는 스펙(spec)을 팀이 이미 활동하고 있는 곳에 배치하고, URL을 부여하며, 댓글을 달 수 있게 만듭니다. 또한 단 한 줄의 구현(implementation)이 존재하기 전에, **스펙(specification)**에 대한 리뷰를 일급 시민(first-class) 이벤트로 만듭니다.
2. /to-issues, 수직적 슬라이스(vertical slices)로 분해하기
이 기술은 승인된 PRD(제품 요구 사항 문서)를 가져와 구현 이슈(implementation issues)로 분해합니다. 분해 규칙에서 중요한 부분은 다음과 같습니다: 수직적이어야 하며, 절대 수평적(horizontal)이어서는 안 됩니다.
수평적 분해 (조직도 기준 슬라이싱) 수직적 분해 (내가 실제로 사용하는 방식)
┌───────────────────────────┐ ┌────┐┌────┐┌────┐┌────┐
...
수평적 슬라이싱(Horizontal slicing)은 엔지니어링의 결과물이 아니라 조직도의 산물입니다. 이것의 결정적인 특징은 _모든 것이 완료될 때까지 아무것도 테스트할 수 없다_는 점입니다. 2주 동안 진행 중인 작업(work in progress)을 쌓아두었다가, 통합(integration) 날이 되어서야 양측이 합의한 API 계약(API contract)이 서로 다른 의미였다는 사실을 깨닫게 됩니다.
수직적 슬라이싱(Vertical slicing)은 모든 슬라이스가 전체 스택(stack)을 관통함을 의미합니다: 얇지만 완전합니다. 각 푸시(push)는 그 자체로 검증될 수 있습니다. 다음 푸시는 그 위에 반복(iterate)됩니다.
이것은 인간과 다를 바 없이 에이전트의 경우에 더욱 중요하며, 그 이유는 다음과 같습니다:
검증 신호(validation signal)는 당신이 가진 가장 저렴한 자원이며, 슬라이싱은 그 신호를 얼마나 자주 얻을 수 있는지를 결정합니다. 만약 슬라이스가 독립적으로 테스트 가능하다면, 에이전트는 스스로를 수정(self-corrects)하고 당신 없이도 루프(loop)를 닫습니다. 만약 그렇지 않다면, 당신이 테스트 스위트(test suite)가 되어버리며, 당신이 제거하려고 노력했던 바로 그 지점에 병목 현상(bottleneck)으로서 스스로를 다시 도입하게 되는 것입니다.
두 번째 이점은 제가 처음에 과소평가했던 것입니다. 슬라이스 2(slice two)를 검증할 때 PRD(제품 요구사항 문서)의 가정이 틀렸음이 드러나면, 에이전트가 해당 슬라이스에 손을 대기도 전에 수정된 컨텍스트가 슬라이스 3부터 7까지 하류(downstream)로 폭포수처럼 전달됩니다. 당신은 일곱 개의 구현체를 수정하는 것이 아닙니다. 단 하나의 문서를 수정하고, 그로부터 나머지 작업들을 다시 도출(re-derive)하는 것입니다.
슬라이스 2에서 발견된 잘못된 가정은 수정(edit) 한 번의 비용이 들지만, 통합(integration) 단계에서 발견된 동일한 가정은 재작성(rewrite)의 비용을 초래합니다.
그 폭포수 효과(cascade)가 바로 루프 엔지니어링(loop engineering)의 핵심입니다. 내부 루프(inner loop)는 _에이전트가 코드를 작성하고, 테스트가 실패하면, 에이전트가 코드를 수정하는 것_입니다. 외부 루프(outer loop)는 _검증을 통해 명세(spec)의 가정이 무효화되면, 명세를 업데이트하고, 취소된 작업들을 다시 도출하는 것_입니다.
3. 게이트(gates)와 병렬로 수행하는 엔지니어링 기술, 실행(execute)
세 번째 기술은 부모 PRD를 참조하는 생성된 이슈(issues)들을 가져와 이를 실행하는 것입니다.
의존성 그래프(dependency graph)가 허용하는 범위 내에서의 병렬성(Parallelism). 독립적인 슬라이스들은 동시에 실행됩니다. 실제 시간(wall-clock)상의 이득은 모델이 당신보다 더 빨리 타이핑하기 때문이 아니라, 당신이 회의에 참석하는 동안 네 개의 슬라이스가 동시에 진행되기 때문에 발생합니다.
인간 참여(Human-in-the-loop) 마커. 일부 이슈는 인간의 개입이 필요한 것으로 표시됩니다. 이 기술은 준비하고, 멈추고, 제시하고, 기다립니다. 그 외의 모든 것은 자리를 비운 상태(AFK)에서도 실행됩니다.
저의 게이트 규칙(gating rule)은 난이도가 아니라 폭발 반경(blast radius)입니다:
| 감독 없이 실행 | 내가 직접 참여해야 함 |
|---|---|
| 까다로운 조정(reconciliation) 알고리즘 | 스키마 마이그레이션(Schema migration) |
| ... |
에이전트는 까다로운 조정 알고리즘을 감독 없이도 작성할 수 있습니다. 왜냐하면 잘못된 알고리즘은 속성 테스트(property test)에서 실패하기 때문입니다. 난이도는 잘못된 지표입니다. '틀렸을 때의 비용(Cost-of-being-wrong)'이 올바른 지표입니다.
결제 시스템에서 이 모든 것이 선택이 아닌 필수인 이유
CRUD 앱에서는 이 방식을 대충 실행해도 대부분 큰 문제 없이 넘어갈 수 있습니다. 하지만 돈을 움직이는 분산 시스템(Distributed systems)은 당신에게 그런 관용을 베풀지 않을 것입니다.
제가 명세(specification)를 진지하게 받아들이게 만든 실패 사례는 다음과 같습니다:
// 괜찮아 보입니다. 리뷰를 통과했습니다. 테스트도 통과했습니다.
async function settle(payment: Payment) {
return retry(3, () =>
...
해당 코드는 네트워크 파티션(network partition) 상황에서 고객에게 이중 결제를 발생시킵니다. 재시도(retry) 로직에 멱등성 키(idempotency key)가 없으며, 근본적인 전달 보장(delivery guarantee) 방식이 처음부터 최소 한 번(at-least-once)이었기 때문입니다. 타임아웃(timeout)은 모호하며(ambiguous), 결제가 이미 완료되었을 가능성이 충분히 있습니다.
// 명세(spec)에 명시되어 있었기에 코드상으로도 명시적으로 표현된 불변량(invariant).
async function settle(payment: Payment) {
const key = idempotencyKey(payment.id, payment.attemptEpoch);
...
첫 번째 버전은 리뷰 단계에서 전혀 잘못된 점이 없어 보입니다. 하지만 이 코드는 불변량(invariants) 수준에서 틀렸으며, 불변량은 디프(diff) 상에서는 보이지 않습니다.
이는 명세(specification)가 동작(behavior) 이상의 것을 담아야 함을 의미합니다. 결제 경로(payment path)에 있는 모든 것에 대한 저의 PRD(제품 요구 사항 문서)는 비기능적 요구 사항(non-functional requirements)을 명시적으로 인코딩합니다:
invariants:
idempotency:
key: provider_ref + attempt_epoch
...
또한 검증(verification)은 **확인적(confirmatory)이기보다는 적대적(adversarial)**이어야 합니다. 코드를 작성할 때와 동일한 이해를 바탕으로 작성된 예시 기반 테스트(example-based tests)는 잘못된 부분까지 포함하여 그 이해를 충실히 확인해 줄 뿐입니다. 속성 기반 테스트(property-based tests), 결함 주입(fault injection), 그리고 계약 테스트(contract tests)는 에이전트가 말로 때울 수 없는 진실(ground truth)입니다. 이것들이 바로 자기 수정(self-correction)을 자기 만족(self-congratulatory)이 아닌 의미 있는 과정으로 만드는 요소입니다.
실제로 무엇이 개선되었는가
구체적이어야 합니다. 왜냐하면 "10배의 생산성"이라는 말은 제가 그 누구에게서도 믿지 않는 주장이기 때문입니다:
- 모호함은 구현 후에가 아니라 구현 전에 드러납니다. 명확한 질문을 던지는 단계(clarifying-questions pass)는 워크플로우에서 가장 레버리지가 높은 부분이며, 비용은 약 4분 정도 소요됩니다.
- 멀티 슬라이스 기능(multi-slice features)에 소요되는 실제 시간(Wall-clock time)이 대폭 감소했습니다. 주로 병렬성(parallelism) 덕분이며, 제가 직접 컨텍스트 스위칭(context-switching)을 하지 않아도 되기 때문입니다.
- 스펙(spec)이 존재합니다. 제 커리어에서 처음으로, 설계 문서(design doc)가 감사(auditor)를 만족시키기 위해 배포 후에 작성하는 것이 아니라, 시스템을 지탱하는 핵심 요소(load-bearing)로서 유지 관리됩니다.
- 온보딩(Onboarding) 방식이 달라졌습니다. 새로운 엔지니어가 PRD(제품 요구 사항 문서) 체인을 읽고 '무엇(what)'뿐만 아니라 '왜(why)'를 이해합니다.
- 저의 최악의 날들이 나아졌습니다. 지루하고, 이미 잘 알고 있으며, 기계적으로 피로한 작업들이 이제는 제 주의를 기울이지 않아도 수행됩니다.
이제 솔직한 이야기를 해보겠습니다: 병목 현상을 제거한 것이 아니라, 이동시켰을 뿐입니다
이 섹션은 사람들이 가장 깊이 고민해 보길 바라는 부분입니다. 왜냐하면 업계의 대화가 생성 속도(generation speed)에만 머물러 있기 때문인데, 생성 속도는 이미 오래전에 더 이상 흥미로운 변수가 아니게 되었기 때문입니다.
코드 리뷰는 코드 생성 속도와 함께 확장되지 않습니다
이제 생성(generation)은 거의 비용이 들지 않습니다. 하지만 이해(comprehension)의 비용은 2019년과 정확히 동일합니다.
시니어 엔지니어가 하루에 의미 있게 리뷰할 수 있는 코드의 양은 정해져 있습니다. 그 수치는 우리 중 누구도 인정하고 싶지 않을 만큼 적으며, 리뷰어가 주변 컨텍스트(context)를 직접 작성하지 않았을 때는 더욱 급격히 줄어듭니다. 해당 리뷰어에게 에이전틱 워크플로우(agentic workflow)를 적용하면 선형적인 속도 저하가 발생하는 것이 아니라, 단순 승인(rubber-stamping) 현상이 발생합니다. 이는 리뷰를 아예 하지 않는 것보다 훨씬 더 나쁜데, 왜냐하면 가짜 확신(false confidence)을 만들어내고, 누군가 확인했을 것이라고 집단적으로 믿게 됨으로써 팀 전체에 책임(accountability)을 분산시키기 때문입니다.
저에게 효과적이었던 순서대로 도움이 되는 것들은 다음과 같습니다:
- 수직적 슬라이스(Vertical slices)는 리뷰 범위를 인간이 감당할 수 있는 크기로 유지합니다. 명확한 사양(spec)이 포함된 200줄짜리 슬라이스는 리뷰가 가능합니다. 하지만 2,000줄짜리 기능 드롭(feature drop)은 보여주기식 행위(theater)에 불과합니다.
- 차이점(diff)이 아니라 사양(spec)을 기준으로 리뷰하세요. 질문은 "이것이 명시된 불변량(invariants)을 충족하는가"여야 하며, "내가 이 코드를 직접 쳤을 것인가"가 되어서는 안 됩니다. 후자의 질문은 대량의 코드 앞에서는 답할 수 없을 뿐더러, 대부분 자존심(ego)의 문제일 뿐입니다.
- 구현 에이전트(implementing agent)와 다른 컨텍스트를 가진 리뷰 에이전트(reviewing agent)를 활용하세요. 동일한 컨텍스트는 동일한 사각지대를 재현합니다. 구현(implementation)이 아닌 제품 요구 사항 문서(PRD)로부터 도출된 서로 다른 컨텍스트가 오류를 잡아냅니다.
- 틀렸을 때 비용이 많이 발생하는 지점에는 엄격한 인간 게이트(human gates)를 두세요. 속도(velocity)를 위해 타협할 수 없는, 예외 없는 원칙이어야 합니다.
정답은 더 많은 인간의 눈이 아니라, 에이전틱 검증(agentic validation)입니다
생성(generation)은 에이전틱한데 검증(verification)이 수동이라면, 당신은 결국 한 사람에게 수렴하는 깔때기를 만든 것입니다.
구조적으로 건전한 유일한 대응책은 검증 또한 에이전틱하게 만드는 것입니다. 즉, 사양(spec)으로부터 독립적으로 도출된 테스트 생성, 결함 주입(fault injection), 계약 준수(contract conformance), 불변량 확인(invariant checking), 그리고 별도의 자동화된 단계로서의 사양 준수 리뷰(spec-conformance review)를 도입하는 것입니다.
인간은 스택의 상위 단계로 이동합니다. 당신은 사양(specification)과 불변량(invariants)을 리뷰합니다. 에이전트는 그것들에 대한 준수 여부를 리뷰합니다. 이것은 업무의 본질이 바뀌는 진정한 변화이며, 저는 이것이 격하(demotion)라고 생각하지 않습니다.
백로그(backlog)가 하류(downstream)로 이동했습니다. 이것이 실제 이야기입니다
출시 데모에서 아무도 언급하지 않은 사실이 있습니다.
제약 이론(Theory of constraints)은 지난 40년 동안 우리에게 이렇게 말해왔습니다: 병목 현상(bottleneck)을 제거하는 것이 아니라, 위치를 옮기는 것입니다. 과거에는 코드가 제약 사항이었습니다. 이제는 그렇지 않습니다. 그렇다면 병목이 어디로 이동했는지 살펴보십시오.
이전(BEFORE) 이후(AFTER)
제품(product) ▓▓ 제품(product) ▓▓
...
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기