실제 업무에서 겪은 흐름을 바탕으로 썼다. 조직, 저장소, 티켓, 사람을 식별할 수 있는 정보는 걷어내고 구조와 판단만 남겼다.
AI 에이전트로 여러 개발 작업을 병렬로 진행하는 날이 늘었다. 그렇다고 내가 여러 문제를 동시에 생각할 수 있게 된 것은 아니다.
오늘 오후 git reflog를 열어 하루를 되짚어봤다. 화면에는 대략 이런 흐름이 남아 있었다.
09:13 [기능] 카드 할부 표기, 리뷰 반영과 캐시 회귀 수정
10:37 [기능] rebase 정리
11:03 전환 -> [성능] 화면 렌더 최적화 브랜치
11:09 [성능] 실험 커밋
11:58 전환 -> [성능] 데이터 프리페치 브랜치
12:37 [성능] prefetch 커밋
13:32 [성능] 계측 A/B 토글 커밋
13:42 복귀 -> [기능] 카드 할부 표기
14:17 [기능] 리뷰 반영 커밋 두 건
세 시간 남짓 동안 큰 작업 두 개와 파생 작업 하나를 오갔다. 그 사이에는 팀 채널 공유, 업무 기록 수정, 동료 코드 리뷰도 끼어 있었다.
한동안 나는 “멀티태스킹은 환상”이라는 말을 믿었다. 사람은 한 번에 하나밖에 못 한다는 뜻이다. 그 말은 여전히 맞다. 그런데 요즘 내 하루에는 여러 일이 분명히 동시에 흐른다.
모순처럼 보이지만, 병렬화한 대상이 달라졌다.
병렬인 것은 작업이고, 직렬인 것은 판단이다
일을 진행시키는 것은 병렬이 됐지만, 판단하고 승인하는 것은 여전히 직렬이다.
한 세션은 결제 도메인의 기능을 구현하고 있었다. 다른 세션은 성능 실험의 타입 체크와 테스트를 돌렸다. 또 다른 리뷰 세션은 방금 올라온 변경을 처음 보는 눈으로 검토했다. 각 작업은 서로 기다리지 않고 앞으로 나아갔다.
하지만 무엇을 고칠지, 어떤 주장을 믿을지, 무엇을 제품 코드로 옮길지, 언제 멈출지는 결국 내가 하나씩 정했다. 에이전트가 여러 갈래의 실행을 맡아도 내 주의는 여전히 단일 창구다.
이 구분이 없으면 에이전트를 더 띄우는 일은 생산성 향상이 아니라 진행 중인 작업만 늘리는 일이 된다. 반대로 판단 직전까지의 대기와 실행을 병렬화하면, 내가 한 작업을 검토하는 동안 다른 작업의 빌드와 계측이 끝난다.
핵심은 사람의 판단을 병렬화하는 것이 아니라, 판단에 필요한 증거가 준비되는 시간을 겹치는 것이다.
로컬과 원격은 같은 일을 두 번 하는 곳이 아니었다
그날 두 개의 큰 흐름은 작업 공간도 달랐다. 로컬에서는 며칠치 맥락이 쌓인 기능 작업을 이어갔다. 실행 중인 앱과 브라우저를 바로 확인하며 결제 흐름을 끝까지 따라가야 했다. 원격 세션에는 성능 가설의 구현과 변경 내용 검토, 오래 걸리는 검증을 맡겼다.
성능 수치의 최종 확인은 다시 동일한 로컬 프로덕션 빌드에서 했다. 원격 세션은 가설과 변경을 준비하고, 로컬은 실제 사용자 경로를 재현했다. 로컬과 원격을 나눈 기준은 어느 쪽이 더 똑똑하거나 빠른지가 아니었다. 한쪽의 상태를 깨지 않고 다른 일이 진행될 수 있는가였다.
로컬 기능 트랙: 긴 도메인 맥락 -> 구현 -> 실제 흐름 확인
원격 성능 트랙: 한 가지 가설 -> 코드·리뷰 -> 검증 결과 준비
별도 worktree: 검증된 변경만 -> 작은 제품용 diff
|
사람의 판단 게이트
기능 작업에서 응답을 기다리는 동안 성능 쪽 검증이 돌고, 성능 결과를 읽는 동안 기능 쪽 테스트가 끝났다. 여러 작업이 가능해진 이유는 내가 더 자주 전환했기 때문이 아니라, 한 작업의 대기 시간을 다른 작업의 실행 시간으로 바꿨기 때문이다.
다만 이 구조는 규칙이 없으면 금방 엇나간다. 원격 세션이 기능 브랜치까지 건드리거나, 로컬의 임시 측정 코드를 제품 변경에 섞거나, 두 세션이 서로 다른 완료 기준을 갖는 순간 병렬화로 아낀 시간이 통합 비용으로 되돌아온다.
실제로 동시에 흐른 세 갈래
그날의 작업은 성격이 서로 달랐다.
1. 오래 맥락을 유지해야 하는 결제 기능
첫 번째는 카드별 할부와 무이자 할부 정책을 바꾸는 기능이었다. 기능 플래그가 꺼져 있을 때는 기존 데이터, 켜져 있을 때는 새 데이터를 사용하되 동일한 결제 조건에서는 실제 결제 결과가 달라지지 않아야 했다.
이 작업은 며칠치 결정이 이어졌다. 데이터를 어떤 시점에 가져올지, 현재 결제 금액에 맞춰 언제 가공할지, 여러 결제 게이트웨이에 어떤 값을 전달할지 함께 봐야 했다. 이런 일은 대화 맥락을 오래 유지하는 구현 세션에 맡겼다.
2. 실패를 허용하는 성능 실험실
두 번째는 주문 화면의 성능 측정 브랜치였다. 먼저 로컬 프로덕션 빌드에서 첫 화면이 표시되기까지의 구간과 API 대기 시간을 나눠 볼 수 있는 계측 기반을 만들었다. 그 위에서 화면 밖 콘텐츠의 렌더링을 미루는 실험과, 앞 페이지에서 다음 페이지 데이터를 미리 불러오는 실험을 각각 진행했다.
첫 가설은 기대에 못 미쳤다. 고정된 결제 버튼과 렌더링 격리 경계가 충돌했고, 위치를 조정한 뒤에도 개선 폭은 0에서 수십 밀리초 정도로 측정 노이즈에 가까웠다. 가장 무거운 영역도 실제로는 충분히 건너뛰지 못했다. 이 실험은 제품 코드로 옮기지 않고 멈췄다.
두 번째 가설인 데이터 프리페치는 달랐다. 같은 경로와 조건에서 기능을 켜고 끈 소규모 A/B 측정에서 약 1.7초가 1.5초로 줄었다. 표본은 작았지만 관측값의 범위가 겹치지 않았고, 개선 원인도 설명할 수 있었다. 앞 화면의 서버 처리가 끝난 뒤 다음 화면에서 사용할 요청을 미리 시작해 네트워크 대기를 겹친 결과였다.
중요한 것은 두 실험 모두 커밋했다는 사실이 아니라, 하나는 버리고 하나만 다음 단계로 보냈다는 판단이었다.
3. 검증된 부분만 옮기는 배포용 작업
세 번째는 프리페치 실험에서 검증된 코드만 제품용 변경으로 추출하는 일이었다.
측정 브랜치에는 계측 코드, A/B 토글, 실험 중 남긴 로그가 함께 있었다. 그것을 그대로 정리해 배포용으로 만들면 무엇이 실제 변경이고 무엇이 실험 도구인지 리뷰하기 어려워진다. 그래서 최신 기본 브랜치에서 별도의 git worktree를 만들고, 검증된 프리페치와 기능 플래그, 실패 시 기존 요청으로 돌아가는 경로만 다시 옮겼다.
측정 브랜치
-> 가설 A: 효과 미미, 종료
-> 가설 B: 효과 확인
-> 최신 기본 브랜치의 별도 worktree
-> 제품 코드만 추출
-> 타입 체크와 테스트
-> 독립 리뷰
-> 사람의 승인
측정 환경은 실험실에 남고, 제품 변경은 작은 diff로 분리됐다. 기존 작업 폴더를 건드리지 않으니 결제 기능과 성능 작업도 서로의 브랜치 상태를 오염시키지 않았다.
병렬화를 가능하게 한 것은 에이전트 수가 아니라 규칙이었다
에이전트를 여러 개 띄우기만 해서는 이 흐름이 유지되지 않는다. 각 세션이 같은 파일과 브랜치를 만지고, 서로 다른 가정으로 코드를 고치고, 결과를 긴 대화 속에 묻어버리면 사람이 합치는 비용이 더 커진다.
그래서 작업을 시작하기 전에 짧은 운영 규칙을 정해 두었다.
규칙 1. 작업 하나에 상태 공간 하나
서로 독립적으로 진행할 작업은 브랜치와 작업 폴더도 분리한다. 특히 긴 기능 작업이 진행 중인 기본 작업 폴더에서 다른 에이전트가 브랜치를 바꾸지 못하게 한다.
- 깊은 기능 작업: 기존 작업 폴더와 대화 맥락 유지
- 성능 실험: 계측 전용 브랜치
- 제품용 추출: 최신 기본 브랜치에서 만든 별도
worktree - 리뷰: 코드를 수정하지 않는 읽기 전용 세션
격리 단위가 명확하면 “누가 내 브랜치를 바꿨지”를 추적하는 대신 각 결과의 품질을 볼 수 있다.
규칙 2. 세션마다 인지 역할을 하나만 준다
긴 맥락이 필요한 구현과, 처음 보는 코드에 대한 냉정한 리뷰는 서로 다른 인지 모드다.
| 역할 | 맡기는 일 | 종료 조건 |
|---|---|---|
| 구현 세션 | 도메인 추적, 설계, 코드 수정 | 테스트 결과와 인계 메모 |
| 검증 세션 | 타입 체크, 테스트, 성능 측정 | 재현 가능한 명령과 결과 |
| 리뷰 세션 | diff 기반 위험 검토 |
블로커, 근거, 남은 위험 |
| 사람 | 우선순위, 불변식, 승인 | 다음 한 가지 결정 |
한 세션이 구현하고 스스로 승인하게 두지 않는다. 작성자의 맥락을 모르는 리뷰 세션이 오히려 “왜 이렇게 했는가”를 다시 묻게 만든다.
규칙 3. 한 문장 목표와 작업 계약을 적는다
각 에이전트에는 긴 프롬프트보다 먼저 한 문장짜리 목표가 필요했다.
- 기능: 데이터 출처는 바꾸되 결제 게이트웨이에 전달되는 값은 바꾸지 않는다.
- 성능: 한 번에 가설 하나만 같은 조건의 A/B로 검증한다.
- 제품용 추출: 측정 코드 없이 효과가 확인된 변경만 옮긴다.
그 목표 아래에 짧은 작업 계약을 붙였다.
기준 브랜치: 어디에서 시작하는가
작업 범위: 어떤 파일과 동작만 바꾸는가
불변식: 무엇이 절대 달라지면 안 되는가
검증: 어떤 테스트와 측정으로 끝낼 것인가
중단 조건: 언제 더 진행하지 않을 것인가
인계 형식: 변경 내용, 증거, 위험, 다음 판단을 어떻게 남길 것인가
이 계약이 있어야 에이전트의 결과가 “많이 고친 코드”가 아니라 내가 승인하거나 거절할 수 있는 작업 단위로 돌아온다.
집중을 만든 것은 에이전트에게 더 많은 맥락을 주는 일이 아니었다. 지금 풀 문제를 하나로 줄이고, 바꾸면 안 되는 것과 멈춰야 할 지점을 먼저 고정하는 일이었다. 한 문장 목표, 불변식, 수정할 파일 범위, 중단 조건, 인계 형식이 각 세션의 주의를 좁혀줬다.
규칙 4. 성능 주장은 측정 문턱을 넘겨야 한다
성능 개선은 코드 모양이 아니라 같은 조건의 A/B로 판단한다.
- 같은 사용자 경로인가
- 캐시 상태와 빌드 조건이 같은가
- 개선 구간을 설명할 수 있는가
- 작은 표본이라면 분포가 얼마나 겹치는가
- 새로 생긴 레이아웃 보정이나 네트워크 중복은 없는가
효과가 수 밀리초에서 수십 밀리초이고 편차 안에 섞이면 멈춘다. 개선 폭이 반복해서 보이고 원인도 설명되면, 그때 제품용 작업으로 승격한다. 이 문턱 덕분에 “1초 넘게 빨라졌다”는 첫인상 대신 실제로 설명 가능한 약 100~200밀리초만 남겼다.
규칙 5. 사람의 판단 대기열을 제한한다
에이전트 세션은 여러 개 실행할 수 있지만, 사람의 판단을 기다리는 결과는 한두 개만 남겨 둔다. 검토할 결과가 다섯 개 한꺼번에 쌓이면 각각의 맥락을 다시 불러오는 비용이 커지고, 결국 가장 그럴듯한 설명부터 통과시키게 된다.
에이전트의 동시 실행 수보다 중요한 것은 사람의 판단 대기열 길이다.
언제 속도를 내도 안전한가
속도를 내도 되는지는 작업의 난이도보다 실패 비용이 제한돼 있는지로 판단한다. 잘못된 결과가 나와도 다른 작업을 오염시키지 않은 채 폐기하거나 원래 경로로 돌아갈 수 있어야 한다.
| 기준 | 속도를 내도 되는 상태 | 직렬화해야 하는 상태 |
|---|---|---|
| 상태 | 브랜치와 worktree가 분리돼 있다 |
같은 작업 폴더와 파일을 함께 수정한다 |
| 변경 | 기능 플래그와 대체·복구 경로가 있다 | 데이터 변경처럼 되돌리기 어렵다 |
| 계약 | 불변식과 완료 조건이 한 문장으로 고정돼 있다 | 요구사항과 도메인 판단이 계속 바뀐다 |
| 증거 | 테스트, 타입 체크, 재현 가능한 측정이 있다 | 결과 설명이 “될 것 같다”에 머문다 |
| 권한 | 원격 세션은 변경안과 검증 결과까지만 만든다 | 배포나 운영 데이터 변경까지 자동으로 이어진다 |
| 판단 | 사람이 검토할 결과가 한두 개다 | 확인하지 못한 결과가 계속 쌓인다 |
성능 실험은 이 조건을 만들기 쉬웠다. 전용 브랜치에서 실패한 가설을 통째로 버릴 수 있고, 같은 조건으로 다시 측정할 수 있었다. 제품용 프리페치도 기능 플래그와 기존 요청 경로가 있어 되돌아갈 자리가 있었다.
결제 기능은 위험도가 더 높았다. 그래도 구현, 테스트, 독립 리뷰는 병렬로 진행할 수 있었다. 대신 “결제 게이트웨이에 전달되는 값이 같은가” 같은 도메인 판단과 최종 승인은 직렬로 남겼다. 고위험 작업을 병렬화하지 않는 것이 아니라, 고위험 판단을 병렬화하지 않는 것이 기준이었다.
AI가 AI의 코드를 리뷰했을 때
이 규칙의 가치가 가장 잘 드러난 것은 결제 기능 리뷰였다.
구현 세션과 함께 만든 코드를 별도 리뷰 세션이 검토하면서 세 가지 위험을 제기했다.
- 결제 금액이 최소 기준 아래로 내려가도 사용자가 이전에 선택한 할부 값이 남을 수 있다.
- 기능 플래그가 백그라운드에서 갱신되면 미리 받은 데이터와 화면 정책이 섞일 수 있다.
- 빈 목록을 “데이터 없음”으로 오해하면 새 경로에서 다시 옛 경로로 돌아갈 수 있다.
나는 지적을 구현 세션에 전달했고, 수정 뒤 다시 리뷰 세션에 보냈다. AI가 만든 코드를 다른 AI가 리뷰하고, 사람이 둘 사이에서 맥락과 판단을 전달하는 형태였다.
그 과정에서 더 근본적인 문제가 드러났다. 소액 주문에서 할부가 과도하게 노출되는 것을 막기 위해 임의의 바닥값을 둔 방어 코드가 있었다. 문제는 이 값이 기능 플래그가 켜진 경로에만 적용됐다는 점이다. 화면에서는 할부를 숨기지만, 결제 게이트웨이로 보내는 경로는 이전 선택값을 그대로 읽을 수 있었다.
사용자가 선택한 개월 수와 실제로 전달되는 값이 달라질 수 있는 문제였다. 배포됐다면 결제 장애로 이어질 수 있었다.
해결은 방어 로직을 더 쌓는 것이 아니었다. 다음 한 줄의 도메인 불변식으로 돌아가는 일이었다.
기능 플래그는 데이터 출처를 바꿀 수 있지만, 같은 결제 조건에서 게이트웨이로 전달되는 값까지 바꾸면 안 된다.
이 원칙을 기준으로 임의의 바닥값을 걷어냈고, 화면 표시와 결제 값 계산이 같은 기존 기준을 사용하도록 맞췄다. 기능 플래그에 따라 전달 값이 달라지지 않는 회귀 테스트도 추가했다.
리뷰 에이전트도 처음부터 이 문제의 심각도를 완전히 알아본 것은 아니었다. 코드 지적을 도메인 원칙과 연결하고, 어느 문제가 실제 사고로 이어질 수 있는지 판단한 것은 사람이었다.
병렬화할수록 사람의 리뷰는 더 중요해진다
AI 에이전트는 대기 시간을 줄이고 여러 가설을 빨리 펼친다. 동시에 그럴듯한 과잉 설계와 잘못된 확신도 더 많이 만든다. 처리량이 커질수록 최종 판단의 정확도가 자동으로 높아지는 것은 아니다.
기능 플래그가 있다고 도메인 리뷰가 필요 없어지는 것도 아니고, 테스트가 통과했다고 성능 주장이 증명되는 것도 아니다. 안전 장치는 실패 반경을 줄여줄 뿐, 무엇이 맞는지 결정해주지는 않는다.
그래서 효과가 측정 노이즈 안이면 제품 코드로 옮기지 않고, 불변식을 한 문장으로 설명할 수 없으면 머지하지 않는다. 구현 세션이 자신의 결과를 최종 승인하게 두지 않고, 실험 코드와 배포 코드도 같은 PR에 섞지 않는다.
이 조건들은 에이전트를 많이 쓰기 위한 규칙이 아니다. 에이전트가 벌린 작업을 사람이 검토할 수 있는 크기와 형태로 되돌리는 규칙이다.
하루의 질감도 달라졌다
코드를 직접 치는 시간은 줄었지만 일이 가벼워진 것은 아니다.
성능 측정 결과에서 과장된 숫자를 걷어내고, 기술적인 설명을 비개발자도 이해할 문장으로 바꾼다. 어제 남긴 업무 기록이 오늘의 설계 변경과 모순되지 않도록 고친다. 구현 에이전트가 낸 결과를 검증 에이전트에 보내고, 리뷰 지적을 다시 구현 맥락으로 번역한다.
일의 병목은 “코드를 얼마나 빨리 치는가”에서 “얼마나 정확히 맥락을 복구하고, 증거를 비교하고, 무엇을 버릴지 결정하는가”로 옮겨갔다.
브랜치 전환마다 드는 맥락 복구 비용도 사라지지 않았다. 오히려 병렬 작업이 늘수록 상태를 복원하는 기록이 더 중요해졌다. 그래서 각 세션의 마지막에는 최소한 다음을 남긴다.
- 지금까지 확인한 사실
- 바꾸지 말아야 할 불변식
- 실행한 검증과 결과
- 아직 결정하지 않은 위험
- 사람이 다음에 판단할 한 가지
이 짧은 인계 메모가 없으면 병렬로 아낀 시간이 다음 전환에서 그대로 사라진다.
동시에 처리한다는 것
“동시에 처리하고 있다”는 것은 내가 여러 문제를 같은 순간에 생각한다는 뜻이 아니다. 여러 작업이 독립적으로 진행되도록 상태를 격리하고, 증거가 준비되는 시간을 겹쳐두고, 그 결과를 하나의 좁은 판단 창구로 차례로 통과시킨다는 뜻이다.
규칙 없는 병렬화는 진행 중인 작업을 늘린다. 규칙 있는 병렬화는 기다리는 시간을 줄인다.
내가 얻은 결론은 단순하다.
병렬화의 단위는 작업이고, 직렬화의 단위는 판단이다.
에이전트는 가능한 선택지를 늘린다. 사람은 증거와 도메인 원칙으로 그 선택지를 좁힌다. 늘리는 것은 AI가 하고, 좁히는 것은 내가 한다.