A factory floor with human hands reaching toward quality checkpoints

인간의 판단은 소프트웨어 팩토리를 떠나지 않는다, 자리를 옮길 뿐이다 (번역)


원문: https://addyo.substack.com/p/human-judgment-doesnt-leave-the-software


들어가며

소프트웨어 팩토리란 소프트웨어 작업을 둘러싼 반복 가능한 루프다. 만약 당신이 소프트웨어 팩토리를 구축하고 있다면, 출시할 수 있을 만큼 좋은 코드에는 여전히 인간의 취향(human taste)과 오너십(ownership)이 필요하다. 본 글에서는 이에 대해, 그리고 당신이 정말 팩토리를 필요로 하는지까지 논의할 것이다.

만약 그렇다면:

팩토리를 설계할 때의 목표는: 인간 취향의 일부 측면이 환경에 인코딩되고, 에이전트가 자신의 작업이 올바르다는 증거(evidence)를 제공하며, 무엇이 프로덕션에 출시되는지에 대해 인간이 여전히 “오너십”을 가지는 것이다.


정말 소프트웨어 팩토리가 필요한가?

내 경험상, 기본 코딩 하네스(Claude Code나 Codex, 멀티 세션, 검증이 내장된 좋은 SPEC, 제약 조건)만으로도 놀랍게도 멀리 갈 수 있다. GitHub 이슈 배치를 구현 및 인간 개입 기준과 함께 던져줄 수도 있다. 하지만 이 시스템이 반복 가능하고(repeatable) 이벤트 구동형(event-driven) 이어야 할 때, 팩토리가 비로소 도움이 된다.

아주 작은 팩토리 루프를 보여주는 프롬프트 예시:

GitHub 이슈 #123과 저장소 지침을 읽고 코드를 변경하라.
명시된 수용 기준(acceptance criteria)만 구현하라.
인증, 결제, 마이그레이션, 기존 테스트 어서션은 수정하지 마라.
브랜치에서 작업하고 diff를 리뷰 가능하게 유지하라.
npm run lint, npm test, npm run build를 실행하라.
필요한 검사를 실행할 수 없으면 멈추고 이유를 설명하라.
실행한 검사, 남은 리스크, 인간이 결정해야 할 사항을 포함한 드래프트 PR을 열어라.
병합(merge)하지 마라.

목표(goal)는 검사가 통과할 때까지 이 루프를 계속 돌리는 것이다. 매일 아침 특정 레이블이 붙은 GitHub 이슈를 폴링하거나 오픈 PR을 리뷰할 수 있다. 브랜치 보호(branch protection)로 병합 경계를 강제할 수 있고, 인간은 무엇이 준비되었는지 선택하고, 리뷰하고, 최종 병합 호출을 하는 식으로 루프 안에 머무를 수 있다.

소프트웨어 팩토리는 이런 경우에 도입하라: 격리된 클라우드 환경에서 실행되는 이벤트 구동형 작업 큐(Slack 트리거, GitHub 이슈, Linear, 백로그)가 필요하고, 트리아지(triage), 구현, 테스팅을 명시적인 인간 베이비시팅과 함께 처리해야 하는 경우. 어떤 팀은 루프를 프로덕션을 모니터링하는 에이전트로 끝내고, 이슈를 생성하면 다시 트리아지로 돌아간다.

내 경험상 팩토리가 유용해지는 때는 다른 실행들(runs)이 일관되게 동작하도록 하고, 에이전트 간 작업을 핸드오프하며, 여러 세션이 동일한 이슈를 클레임하는 것을 방지하고, 증거를 보존하며, 인간 리뷰가 따라잡지 못할 때 프로덕션을 중단시키는 것이 어려운 지점이다.

이를 해결하는 방법은 다소 지루하게 들릴 수 있다. 예를 들어, Warp는 모든 들어오는 이슈를 네 가지 상태로 트리아징한다:

이 레이블은 한 번에 여러 역할을 한다: 큐(queue)이자 락(lock)이며, 세션이 “ready”로 표시된 것만 집어가기 때문에 인간이 영원히 ‘아니오’라고 말하지 않고도 작업을 보류할 수 있는 장소다.

워크플로우 측면에서 Claude/Codex만 사용하는 것과의 차이점:

좋은 팩토리에서 인간은 최종 diff를 맨 끝에서 리뷰하고 승인하는 것에 국한되지 않는다. 초기에 작업을 형성하고, 구현 중에 방향을 잡아주고, 핸드오프를 통해 진행하거나, 프로덕션 출시를 중단시킬 수 있다.

검증(Verification)은 책임 있는 팩토리가 많은 시간을 쏟는 부분이다. 이에 대해서는 뒤에서 더 다룬다.

만약 소프트웨어 팩토리가 정말 필요하다고 결정했다면, 직접 구축(build)하는 것이 유일한 선택지는 아니다. 팩토리를 확장할 인프라를 세우는 것은 많은 작업이며, 구매(buy) vs 구축(build)을 고려해야 한다. Factory, Warp, HumanLayer 등이 이 분야에서 작업 중이다.


지금 내 하루는 어떻게 생겼나

내 일상적인 소프트웨어 개발 경험은 지난 1년 동안 크게 변했다. 나는 점점 더 에이전트와의 병렬 작업을 하고, 불이 켜진(lights-on) 소프트웨어 팩토리로 나아가는 것에 대해 이야기해왔다. 많은 사람들이 이것이 실제로 무엇을 의미하는지 묻는다.

평범한 하루에, 나는 더 단순한 lights-on 소프트웨어 팩토리를 운영한다. 클라우드에서 실행 중인 태스크들이 있다. 이 중 절반은 내가 작업하는 소규모 회사들의 프로덕션 클라이언트 애플리케이션일 수 있다. 실제 사용자가 있고, 실제 인증, 결제, 구독이 있으며, 조심해야 할 진짜 리스크들이 있다. “에이전트야, 가서 이거 해와”라고 테스트와 제약 조건, 품질 검사 없이 말할 수는 없다.

오픈소스 프로젝트, 내 책의 컴패니언 사이트, 도구, 내 앱 등 매우 다양한 종류의 애플리케이션을 작업한다. 때로 이들의 공통점은 작업에 사용하는 도구뿐이다.

병렬 작업을 점점 더 많이 하고, 속도(velocity)와 생산성(productivity), 자율성(autonomy)을 개선하려면, 어떤 부분이 절대적으로 인간 코드 리뷰와 인간 입력을 필요로 하는지 생각해야 한다.

이 중 많은 부분은 초기에 필요하다. 스펙, 요구사항, 제품 디자인, 제품 의도를 정의할 때다. 그리고 에이전트가 작업을 올바르게 수행했는지 어떻게 검증할지, 기존 시스템을 망가뜨리지 않았는지, 품질 기준을 충족하는지 확인하는 방법도 마찬가지다.

코드 생성 자체는 가장 걱정할 부분이 아니다. 충분한 컨텍스트가 주어지면 에이전트는 구현을 작성하고, 테스트를 실행하고, 실패를 검사하고, 코드를 수정할 수 있다. 우리는 환경에 인간의 취향(human taste)이 충분히 인코딩되어 구축되는 것을 신뢰할 수 있는 상태에 도달해야 하며, 그래야 인간의 주의력을 가장 필요한 곳에 집중할 수 있다.

물론 이에 대한 반발도 있다. “이 모든 것을 그냥 자동화해버릴 수 있다고 믿지 않는다”는 의견이다. 하지만 우리가 모든 것을 자동화한다는 말이 아니다. 생성되는 코드의 양을 고려할 때, 인간이 모든 코드를 읽는 것은 현실적이지 않다. 특히 대부분의 경우 로켓을 만드는 것이 아니라 UI와 풀스택 애플리케이션을 만들 때 말이다.

우리의 판단과 취향은 가장 필요한 곳에 집중되어야 한다. 시스템의 가장 리스크가 큰 부분은 어디인가? 인간의 취향을 어디에 적용해야 하는가? 그것은 프론트엔드일 수도 있고, 시스템 작동 방식일 수도 있다. 모든 것이 100%일 필요는 없다.


내 인지 대역폭(cognitive bandwidth)은 에이전트와 함께 확장되지 않는다

현실은, 우리는 이제 수십, 수백, 수천 개의 에이전트를 병렬로 실행할 수 있지만, 우리 자신의 인지 대역폭은 같은 방식으로 확장되지 않는다. 이것은 내가 이전에 이야기했던 인지 부채(cognitive debt)나 이해 부채(comprehension debt)로 이어질 수 있다.

5년, 10년 전만 해도 엔지니어링 커뮤니티에서 컨텍스트 스위칭과 그 비용에 대한 논의가 많았다. 동료가 업무 중간에 당신의 책상으로 걸어오는 것을 사람들이 얼마나 싫어하는지 이야기했다. 플로우 상태로 돌아가는 데 오랜 시간이 걸렸기 때문이다.

우리는 이제 그때보다 훨씬 더 많이 컨텍스트 스위칭을 하고 있다. 어느 날이든, 소프트웨어 팩토리 없이 작업한다면, 한 번에 5~10개의 다른 프로젝트를 에이전트와 함께 작업하거나, 한 프로젝트의 5~10개의 다른 기능을 동시에 작업할 수 있다.

이는 내가 그 중 적어도 몇 개는 계속 추적할 수 있어야 함을 의미한다. 작업을 충분히 잘 정의하고, 결과물과 검증 방법을 충분히 잘 정의했다면 신뢰가 쌓여 일부 작업에 더 많은 자율성을 줄 수 있다. 하지만 리스크가 더 크거나 더 미묘한 작업이 있다면 주의를 기울여야 한다.

리뷰어를 위해 소프트웨어 팩토리를 최적화하라. 모든 접근 방식이 결국 한 사람의 주의력으로 출력을 라우팅한다면, 팩토리가 당신이 여전히 내려야 하는 결정들을 얼마나 더 저렴하게 만드는지 물어봐야 한다.


잘못된 프로젝트 실수

여러 병렬 프로젝트를 에이전트와 작업할 때, 실수로 잘못된 프로젝트 세션에 접근한 적이 있다. 웹 앱에 다크 모드를 추가하려고 했는데, 다른 프로젝트 세션에 가서 동일한 프롬프트를 입력하기 시작한 것이다. 그래서 전혀 필요하지 않은 것에 다크 모드를 구현하기 시작했다.

나는 그런 실수를 할 수 있다. 내 소프트웨어 팩토리는 그런 실수를 하면 안 된다.

이것을 시스템의 관점에서 생각해야 한다. 당신은 효과적으로 소프트웨어 엔지니어링 문화, 팀 문화를 시스템으로 인코딩하려는 것이다. 시스템이 동일한 종류의 행동을 하고, 오너십이 어딘가에 속해 있으며, 누군가가 여전히 결과에 대한 책임을 지도록 하는 것이다.


초록불(Green)이 오해를 불러일으킬 때

이런 시스템에서도 매우 조심해야 한다. AI에게 테스트를 통과하도록 도와달라고 요청하면, 조건을 만족시키기 위해 테스트 자체를 변경하거나 코드 로직을 변경할 수 있다. 그것이 실제로 당신의 의도(intent)를 따랐다는 의미는 아니다.

소프트웨어 팩토리가 모든 것이 초록색(green)이라고 보여준다고 해서 실제로 초록색이라는 의미는 아니다. 특히 초기 설정 단계에서는 더 그렇다. 검사와 검증이 올바르게 설정되었는지, 예상대로 작동하는지 많은 주의를 기울여야 한다. 오해를 불러일으키지 않도록 해야 한다.

A dashboard with green checkmarks while a magnifying glass finds a hidden crack

예를 들어, 테스트가 “인증 제공자를 변경했습니다”라고 말하는데, 실제로는 GitHub 인증을 추가하라고 했더니 UI에 공간이 3개만 있어서 기존 제공자 중 하나를 드롭해버린 상황을 상상해보라. 그게 고객이 원하던 제공자였다면? 이런 시스템이 어떻게 작동해야 하는지 매우 명시적으로 정의해야 한다.

보안도 매우 중요하다. 팩토리가 신뢰할 수 없는 입력(GitHub 이슈/Slack 메시지)을 읽는다면, 적대적(adversarial)일 수 있으며 공급망 공급(supply chain attack)과 같은 문제를 포함할 수 있다. Vercel과 같은 일부 소프트웨어 팩토리 탐구는 에이전트를 작업에 필요한 시크릿만 가진 격리된 샌드박스에서 실행한다. 이렇게 하면 손상된 실행이 작업에 필요하지 않은 것에 도달할 수 없다. 방어는 계층화되어야 한다.


어떤 오래된 프로젝트가 새 생명을 얻을 자격이 있는가?

AI 이전 시대에는 수많은 버려진 소프트웨어 엔지니어링 프로젝트, 주말 프로젝트, 개인 프로젝트가 있었다. 완료할 시간이 없어서, 우선순위를 정할 여유가 없어서 출시하지 못한 프로젝트들이다.

이제는 그런 프로젝트들을 완료하는 것이 상당히 쉬워졌다. 하지만 같은 인간의 판단 질문이 따라온다. 그 프로젝트들이 존재할 자격이 있는가? 출시되어야 하는가? 사용자가 5명뿐이라도 유지보수해야 할 수도 있고, 유지하려는 품질 기준이 있을 수 있다.

나는 수년간의 GitHub 프로젝트가 있는데, 에이전트가 생기면 가장 먼저 하는 일이 그것을 빌드되게 만드는 것이다. 클론하면 의존성이 모두 바뀌어서 빌드되지 않기 때문이다. 절반은 구식이거나 보안 취약점だらけ다. 업데이트해야 한다. 테스트가 없으면 테스트를 추가해야 한다.

그리고 Twitter Bootstrap에서 Tailwind와 shadcn으로 UI를 재구현해야 할 수도 있다. 그러면 갑자기 시간이 더 걸린다는 것을 알게 된다. 에이전트가 많은 것을 더 빠르게 처리할 수 있지만, 이제 제품 감각(product sense), 취향(taste) 등의 요소를 고려해야 하기 때문이다.

여전히 질문한다: 이것이 누구를 위한 것인가? 시장이 있는가? 나를 위한 것인가? 다른 사람들을 위한 것인가? 세상에 내놓았을 때, 이제 누구나 이것을 빠르게 만들 수 있는데 여전히 흥미로울까?

인간의 질문, 이 것들이 존재할 자격이 있는가? 우리의 취향과 판단을 어떻게 고려할까?, 이러한 것들이 계속해서 매우 중요하다고 생각한다. 그것이 인간 주의력이라는 희소 자원이 실제로 필요한 곳이다.


샘플 팩토리를 만들었을 때 일어난 일

82분 팩토리 실행에 대해 이야기하겠다. 사람들이 오랫동안 “소프트웨어 팩토리를 어떻게 구축하나요?” 또는 “Claude Code나 Codex를 사용하다가 소프트웨어 팩토리로 어떻게 진화시키나요?”라고 물어왔다.

내가 만든 것은 Factory라는 레포지토리와 데모 애플리케이션, 워크숍이다. 데모 앱은 영화 앱이다.

팩토리가 여러 기능을 구현하도록 했다: 즐겨찾기(favorites), 검색(search), 다크 테마(dark theme) 등.

이 구현의 이점 중 하나는 실제 문제를 잡아낸 것이었다. 한 번 실행으로 구현을 요청했다면 내가 발견하지 못했을 문제들이었다.

60분쯤 지났을 때, “이상하게 느리다”고 느껴서 팩토리에게 “왜 느린가?”라고 물었다. 팩토리는 “괜찮습니다. 모든 검증기(verifier)가 여전히 실행 중입니다”라고 답했다.

개별 태스크가 10분, 15분, 20분이 걸릴 것으로 예상했지만, 검증, 재시도, 브라우저 검사, 인간 리뷰 등이 포함되면 2~4배 더 오래 걸릴 수 있다.

이러한 추가 시간이 더 나은 품질과 시스템에 대한 더 큰 신뢰로 이어진다고 생각한다. 측정 관점에서, 병합된 PR당 비용(cost per merged PR)과 코드 유통기한(code shelf life)을 이해 부채(comprehension debt) 메트릭으로 볼 수 있다.

어떤 것이 유용한 지연(useful delay)이고 어떤 것이 팩토리 오버헤드(factory overhead)인지도 생각해야 한다. 검증기들은 실제 문제를 잡아냈다. 시간 중 일부는 내가 원하는 증거(evidence)를 생성하는 데 사용되었고, 일부는 팩토리 실행의 오버헤드였다.

가치 없는 검사를 많이 실행하는 팩토리가 고품질 팩토리는 아니다. 각 반복 검사가 무의미한지, 노이즈인지, 실제로 시스템을 더 안전하게 만드는지 연구해야 한다.


검증 예산(Verification Budget)

검증 예산에 대한 생각은 내가 역사적으로 성능 예산(performance budget)에 대해 생각해온 방식과 같다.

소프트웨어 개발 수명 주기 초기에 실행할 수 있는 특정 종류의 검사들이 있고, 매우 무겁지만 가치가 커서 나중에 실행하고 싶은 것들이 있다. 빠른 검사(린팅, 타입 체크 등)는 초기에 실행할 수 있다. 전체 테스트 스위트는 드래프트 PR 직전이나 이후에 실행할 수 있으며, 뮤테이션 테스팅, 브라우저 테스팅, 보안 검사 등이 포함된다.

이러한 검사들을 요약(summary)으로 대체하고 싶지는 않다. 실제 테스트가 필요하지만, 적절한 위치에 예산을 책정해야 한다. 개발 루프를 느리게 하고 싶지 않기 때문이다. 빠른 반복 루프는 중요하지만, 동시에 견제와 균형(checks and balances)도 필요하다.


출시되지 않은 실행(When a run doesn’t ship)

Vercel은 소프트웨어 팩토리에서 모든 에이전트 실행을 “success”, “flawed”, “blocked”, “manual” 로 표시하고, “success”만 프로덕션에 출시된다. 나머지는 시스템으로 재진입한다.

이 중 두 가지는 기계적 수정(mechanical fix)이 가능하고, 마지막 하나는 신뢰(trust)에 관한 문제다.

분류 자체는 비용(cost)을 보여주지 않는다. 내 TMDB 앱 팩토리 구현으로 돌아가면, 거부 없이 7분 걸린 Quick Finder 기능과 두 번의 거부와 중간의 인간 결정이 포함되어 56분 걸린 Favorites 기능이 같은 팩토리에서 실행되었다. 따라서 분류와 함께 단계별 타이밍(per-stage timing)을 짝지어야 한다. 그렇지 않으면 실행이 결함이 있었다는 것만 알 뿐, 그 결함을 찾는 데 얼마나 비용이 들었는지는 모른다.

또 다른 개선점은 경계에서의 핸드오프다. 내 샘플 팩토리는 첫 번째 이슈에서 멈추고 “factory:needs-info”로 이동시켰다. 이것은 올바른 선택이었지만, 내 답변을 어디에 넣어야 할지 몰랐다. 수동 실행(manual run)은 팩토리가 멈출 때 끝나는 것이 아니라, 인간이 다음에 무엇을 해야 할지 알 때 끝난다.


자율성(Autonomy)은 단일 설정값이 아니다

몇 주 전에 에이전트 자율성(agentic autonomy)과 그에 대한 사고 방식에 관한 글을 썼다. 자율성은 모든 프로젝트에 대해 단일 설정값이 될 수 없기 때문이다.

검증(Verification)은 신뢰(trust)를 사고, 에이전트에게 더 많은 자율성을 부여할 수 있는 능력을 산다. 예를 들어, 중요한 변경 작업을 할 때 여러 검사가 있고 모든 것이 올바르게 검증되었으며, 내가 직접 확인했다면, 다음에 같은 프로젝트에서 유사한 작업을 할 때 에이전트에게 조금 더 자율성을 주는 것이 편안해질 것이다.

소프트웨어 팩토리를 구축할 때 생각해야 할 점이다. 검증은 리스크에 따라 변한다. 목표는 최상의 신호 대 잡음비(signal-to-noise ratio)다. 거대한 체크리스트를 실행하는 것이 목표가 아니다.


내가 다시 배워야 했던 기능

여러 Claude 세션을 사용하던 날, 미루고 있던 기능이 있었다. Claude가 기능을 구현했고, 테스트가 통과하는 것처럼 보였다. 검증에 많은 생각을 하지 않았지만 테스트가 통과했으므로 작동한다고 생각했다. 병합(merge)했다.

즐겨찾기(favoriting) 기능이었다. 브라우저에서 확인했을 때 괜찮아 보였다. 하지만 며칠 후 코드로 돌아와서 약간의 조정을 하고 싶었다. 기능이 어떻게 작동하는지 이해할 수 없었다. 이 저장소는 내 것이었다. 내가 변경을 승인했다. 저장소의 많은 부분을 이해하고 있었지만, 내 이해가 쌓여온 모든 코드를 따라잡지 못한 것이다.

내가 흡수하지 못한 것은 이 기능이 실제로 어떻게 작동하는지, UI가 어떻게 작동하는지, 효과가 어떻게 적용되는지였다. 결국 이 기능을 다시 만들고 단계별로 “이게 어떻게 작동하지? 어떻게 이해할 수 있지?”라고 되짚어야 했다.


병렬 작업이 이해(understanding)에 미치는 영향

병렬 작업을 할 때 이 문제는 전체적으로 증폭되며, 소프트웨어 팩토리에서 할 때는 더욱 심해진다. 5~10개의 세션을 운영할 때, 단순한 리뷰 볼륨 문제 이상이 발생한다. 여러 개의 멘탈 모델이 생성되며, 다른 곳에서 작업하는 동안 꽤 차가워질(cold) 수 있다.

역사적으로 우리는 컨텍스트 스위칭의 어려움에 대해 이야기해왔다. 채팅이 압축(compact)되면, 일부 접근 방식을 거부하고, 다른 것들을 시도하고, 에이전트와 페어링하면서 세션에서 일어난 모든 것을 기억하기 어려워진다.

코드는 종종 내려진 결정(decision)은 보존하지만, 왜 그 결정이 내려졌는지(why)는 보존하지 않는다.

이것이 유용한 교훈이 될 수 있다고 생각하는 부분은, 에이전트에게 궤적(trajectory)이나 문제 접근 방식에 대한 정보를 저장하도록 요청하는 것이 중요하다는 점이다. 나중에 다시 참조할 수 있도록. 리포지토리에 커밋할 수도 있고, 로컬에 보관할 수도 있고, 팀과 공유할 수도 있다. 세션에 의존하거나 나중에 기억에 의존하는 것보다 낫다.


오너십(Ownership)은 사라지지 않는다

이 모든 것의 밑바탕에는 더 넓은 원칙이 있다.

인간이 물리적으로 입력하는 코드의 비율은 극적으로 감소할 수 있다. 하지만 인간의 오너십이 그와 함께 감소할 필요는 없다.

이것이 내가 소프트웨어 엔지니어링의 미래가 “인간이 루프를 떠나는 것”으로 가장 잘 설명되지 않는다고 생각하는 이유다. 대신, 인간의 판단(human judgment)이 재배치(relocated)되는 것이다.

기계가 더 강하고, 더 빠르고, 더 결정론적인 신호를 생성할 수 있는 루프의 부분에서는 인간을 제거해야 한다. 동시에, 컨텍스트, 취향, 리스크, 장기적 오너십이 가장 중요한 곳에 인간을 집중시켜야 한다.

최고의 소프트웨어 팩토리는 인간의 관여를 얼마나 완전히 제거했는지로 정의되지 않을 것이다.

그것은 인간을 얼마나 지능적으로 배치했는지로 정의될 것이다.

의도(intent), 시스템 형태(system shape), 품질 기준(quality bar)에 대한 인간의 판단을 상류(upstream)에 유지하라. 자동화된 백프레셔가 약해지거나 결과가 주관적이 되는 곳에서 코드를 리뷰하라. 모든 결정론적 신호를 가능한 한 빠르고 지속적으로 루프에 밀어 넣어라. 시스템이 신뢰를 얻거나 잃음에 따라 제약 조건을 의도적으로 강화하고 완화하라.

인간은 여전히 최종적으로 출시되는 코드에 대한 오너십을 가져야 한다. 출시할 가치가 있는 코드는 여전히 그곳에서 시작된다.


← 한 달에 2만 달러를 Devin에 써서 배운 것들 (번역) · TUI를 더 이상 만들지 말자 (번역) →