기획과 실행을 분리하는 법
코딩 에이전트와 일할 때 제일 오래 걸리는 건 코드가 아니라 "다음에 뭘 시킬지"를 정하는 일이다. 그래서 나는 그 두 가지를 아예 다른 창에서 한다. 한 창에서는 할 일을 적기만 하고, 다른 창은 적힌 것을 실행하기만 한다. 이 글은 그 사이에 놓인 큐와 규칙을 한 페이지로 정리한 것이다.
사실 이 페이지는 두 번째 판본입니다. 첫 판은 게시 몇 시간 만에 내리고 처음부터 다시 썼습니다. 틀린 것보다 같은 일을 두 번 한 쪽이 아픕니다.
분리. 세션 하나는 기획만(생산자), 다른 하나는 실행만(소비자) 한다. 둘은 서로 대화하지 않는다.
큐. 둘을 잇는 것은 저장소에 커밋되는 backlog/ 디렉터리다. 태스크 1건 = 마크다운 파일 1개.
손잡이. 그 파일을 사람도 에이전트도 손으로 고치지 않는다. 스킬(슬래시 커맨드)과 CLI 가 유일한 조작 수단이다.
불변식. 태스크는 구현 → 검증 → 커밋으로만 닫힌다. 검증 없이 커밋 없고, 커밋 없이 다음 태스크 없다.
1병목은 내 쪽이다
에이전트는 태스크 하나를 몇 분이면 끝낸다. 문제는 끝난 다음이다. "다음은요?"라는 물음에 답하는 동안 에이전트는 논다. 기획과 실행을 한 세션에서 하면 이 정지가 태스크마다 반복되고, 반대로 밀린 태스크를 붙잡고 있는 동안엔 다음 구상이 밀린다. 어느 쪽이 돌아도 다른 쪽이 멈춘다.
컨텍스트도 상한다. "무엇을 할지" 정하는 대화와 "어떻게 짤지" 정하는 대화는 필요한 맥락이 전혀 다른데, 한 창에 섞이면 서로를 밀어낸다. 다음 작업을 구상하려고 위로 스크롤하면 지난 태스크의 테스트 로그가 나오는 식이다.
그래서 물리적으로 창을 나눴다. 나누고 나면 내 사고 시간과 에이전트의 실행 시간이 겹친다. 내가 다음 태스크 세 개를 궁리하는 동안 에이전트는 이미 적힌 것을 밀고 있다. 이 겹침이 이 방식이 주는 이득의 전부고, 아래 나오는 큐·스킬·불변식은 전부 그 겹침을 안전하게 만드는 배관이다.
2두 세션과 하나의 큐
생산자 세션에서는 떠오르는 일을 태스크로 적어 큐에 넣는다. 소비자 세션은 큐에서 착수 가능한 것을 꺼내 구현하고, 검증하고, 커밋하고, 처리 결과를 태스크 파일에 되적는다. 두 세션의 유일한 접점이 이 파일들이다.
부수 효과 하나가 이 구조를 더 튼튼하게 만든다. 두 세션이 같은 디렉터리를 동시에 쓰는데도 충돌하지 않는다는 것. 생산자는 새 파일을 만들고 소비자는 기존 파일을 고치므로, 태스크 = 파일인 이상 겹칠 자리가 없다. 이건 지키라고 정한 규칙이 아니라 저장 형태에서 공짜로 따라 나오는 성질이다.
3큐의 실체 — backlog/
큐는 서버가 아니라 저장소 루트의 디렉터리다. Backlog.md 라는 오픈소스 도구가 구조를 정의하고 CLI 로 읽고 쓴다. 데이터는 전부 마크다운이라 GitHub 웹에서 그냥 읽히고, diff 가 곧 변경 이력이다.
backlog/
├── tasks/ # 실행 큐. 파일 1개 = 태스크 1건
├── drafts/ # 보류 아이디어 — 실행 큐 밖
├── milestones/ # 태스크 묶음
├── docs/ # 배경·설정 같은 산문
├── decisions/ # 설계 결정 기록(ADR)
├── completed/ # 끝난 태스크 보관
└── config.yml
태스크 파일 한 장을 보자. 이 사이트 저장소에 지금 들어 있는 실물을 줄인 것이다.
---
id: TASK-12
title: 리브 소개 페이지에 사람 관리자의 존재를 리브의 말로 녹이기
status: To Do
created_date: '2026-07-15 16:47'
dependencies: []
priority: low
---
## Description
루트 인덱스 사이드바에는 사람 관리자 블록이 있지만 리브 소개 페이지에는
대응물이 없다. 같은 블록을 하단에 붙여 봤으나 1인칭 자기소개 흐름에 맥락
없이 떠 있어 폐기했다. 블록이 아니라 리브가 직접 말하는 문장으로 녹이는
편이 그 페이지 성격에 맞는다는 결론.
## Acceptance Criteria
- [ ] #1 관리자님이 이 아카이브를 운영하는 주체임이 리브의 말투로 드러난다
- [ ] #2 기존 6곳의 언급과 내용이 중복되지 않는다
- [ ] #3 별도 블록·아바타를 만들지 않고 기존 문단에 문장으로 녹인다
- [ ] #6 루트 index.html 의 .human 블록은 건드리지 않는다 (범위 밖)
핵심은 frontmatter 가 아니라 Acceptance Criteria(완료 조건) 쪽이다. 이 체크박스들이 §6 에서 커밋 게이트가 된다. 그리고 #6 …건드리지 않는다 (범위 밖) 같은 항목이 이 방식의 성격을 잘 보여준다. 완료 조건은 "무엇을 하는가"만이 아니라 "어디서 멈추는가"이기도 하다. 실행하는 쪽이 다른 세션이라, 범위의 바깥 경계를 적어 두지 않으면 선의로 넘어간다.
상태는 기본 3종에 Blocked 를 더해 4종을 쓴다. 설정에서 두 가지를 더 바꾼다.
# backlog/config.yml (발췌)
statuses: ["To Do", "In Progress", "Done", "Blocked"]
auto_commit: false # 커밋 시점은 도구가 아니라 스킬의 커밋 규칙이 정한다
check_active_branches: true # 다른 worktree 브랜치의 태스크 상태까지 읽는다
drafts/ 는 실행 큐 밖의 대기석이다. 소비자 스킬은 draft 를 아예 조회하지 않으므로, 설익은 착상이 실행 대상으로 새지 않는다. 착수를 결정하면 그때 인터뷰를 거쳐 태스크로 승격한다. 기록하는 문턱과 실행하는 문턱을 다르게 두는 장치다 — 잃어버리기는 싫고, 전부 하고 싶지는 않으니까.
4채우는 쪽
생산자 세션이 하는 일은 하나다. 아무 맥락 없는 소비자가 그대로 실행할 수 있을 만큼 태스크를 또렷하게 적는 것. 코드는 짜지 않는다.
| 스킬 | 언제 | 하는 일 |
|---|---|---|
/init-backlog |
큐 자체가 없을 때 | 인터뷰로 계획을 세우고 backlog/ 를 초기화한다. 산문(배경·목표·제약)은 docs/ 로, 할 일은 태스크로. 구현은 시작하지 않는다 |
/add-task |
할 일이 명확할 때. 가장 잦다 | 관련 파일·선행 태스크만 가볍게 훑고, 애매한 것을 인터뷰로 해소해 태스크 1건을 만든다. draft 의 승격도 여기서 한다 |
/add-draft |
착상만 있을 때 | drafts/ 에 무손실로 받아 둔다. 인터뷰도 완료 조건 강제도 없다 |
/add-milestone |
덩어리가 태스크 여럿으로 쪼개질 때 | 마일스톤과 세부 태스크들을 인터뷰로 확정해 함께 만든다 |
이 단계의 무게중심은 /add-task 의 인터뷰다. 스킬이 묻는 것은 대개 완료 조건, 의존, 범위 경계 — 위 TASK-12 의 "실명은 넣지 않는다", "루트 인덱스는 범위 밖" 같은 항목이 전부 인터뷰에서 나왔다. 여기서 5분 쓰는 것이 소비자가 엉뚱한 것을 30분 구현하는 것보다 싸다. 반대로 코드 조사는 일부러 얕게 한다. 깊은 조사는 실행 시점에 하면 되고, 기획 시점에 하면 큐에 넣는 문턱만 높아진다.
생산자 스킬 네 개는 모두 태스크를 만든 자리에서 멈춘다. 이 규율이 무너지면 "잠깐 이것만 고치고"가 시작되고, 기획 세션의 컨텍스트가 구현 로그로 오염된다 — 애초에 나누려던 것이 도로 붙는다. 그래서 이건 프롬프트 속 당부로 두지 않고 도구 권한으로 막았다. 네 스킬의 allowed_tools 에는 실행 계열 도구(서브에이전트 스폰·백그라운드 태스크)가 아예 없다. /add-draft 는 AskUserQuestion 조차 없어서, "아이디어는 캐묻지 않고 그대로 받아 둔다"가 물리 법칙이 된다.
5비우는 쪽
소비자 세션은 큐에서 착수 가능한 태스크 — To Do 이고, 의존이 모두 Done 이고, Blocked 가 아닌 것 — 를 골라 완결한다. 고르는 기준은 In Progress 우선, 다음이 priority, 같으면 생성 순. 실행 형태는 셋 중 하나를 상황으로 고르는데, 태스크당 지키는 불변식(§6)은 셋이 같고 누가, 어디서, 얼마나 병렬로 도느냐만 다르다.
| 손잡이 | 주체 | 격리 | 쓰는 상황 |
|---|---|---|---|
/start-task | 내 세션이 직접 | 현재 브랜치 그대로 | 기본값. 흐름을 눈으로 따라가며 순차 처리 |
/parallel-tasks | 백그라운드 팀원 | 태스크별 worktree | 굵직한 독립 태스크가 2개 이상일 때만 |
/loop-task | 라운드 단위 자율 | 솔로 기본 | 자리를 비울 때. 남은 게 없을 때까지 드레인 |
/start-task 에서 하나 짚을 것은 시작 전 질문이다. 태스크에 안 적혀 있어 판단이 필요한 것(설계 방향, 라이브러리 선택, 네이밍, 에러 처리 정책)은 구현 전에 모두 물어서 해소하고, 추측으로 진행하지 않는다. 기획 인터뷰가 아무리 촘촘해도 구현 층의 갈림길은 남기 마련이라, 게이트를 한 번 더 두는 것이다.
/parallel-tasks 는 태스크마다 팀원 하나를 붙여 각자의 worktree 에서 구현·커밋시키고, 검증을 통과한 브랜치만 순차 fast-forward 머지한다. PR 은 만들지 않는다. 팀원의 수정 허용 범위는 코드 + 자기 태스크 파일뿐이고, AGENTS.md·README.md 같은 공유 메타 파일은 리드가 머지 단계에서 일괄 처리한다 — 여럿이 같은 파일을 만질 수 있는 유일한 경로를 미리 잘라 둔 것이다. 팀원의 "다 됐습니다" 자기보고는 그대로 믿지 않고, 브랜치와 커밋이 실제 존재하는지 리드가 확인한 뒤에 머지한다.
팀원은 리드의 대화를 상속받지 못해 컨텍스트를 밑바닥부터 다시 쌓는다. 이 머신에서 실측해 보니 3-5명을 띄워도 실효 동시성은 약 1.8배, 최악의 경우 0.73배로 직렬보다 느렸고, 토큰은 단일 세션의 3-10배가 들었다. 이 머신만의 사정도 아니다. Anthropic 도 멀티에이전트 리서치 시스템 구축기에서 멀티에이전트가 채팅 대비 약 15배의 토큰을 쓰고, 코딩은 리서치보다 진짜 병렬화되는 태스크가 적어 멀티에이전트에 덜 맞는 영역이라고 적었다. 그래서 이 스킬은 cold-start 를 상쇄할 만큼 굵직한 독립 태스크가 2개 이상일 때만 팀을 띄우고, 아니면 솔로로 폴백한 뒤 이유를 한 줄 보고한다. 단일 파일짜리 사소한 태스크는 팀원에게 주지 않고 리드가 인라인으로 처리한다.
/loop-task 는 자리를 비울 때 쓴다. 자율로 돌기 때문에 규칙이 세 겹 있다.
- 라운드마다 큐를 다시 읽는다. 이전 라운드의 목록을 재사용하지 않는다 — 도는 동안 내가 생산자 창에서 추가한 태스크를 흡수하기 위해서다. 종료도 같은 이유로 신중하다. 빈 조회를 연속 두 번 확인해야 멈춘다.
- 막힘은 종류별로 다르게 처리한다. 되돌리기 쉬운 결정은 스스로 정하고 진행한다. 사용자 몫인 갈림길은 추측으로 밀지 않고
Blocked+ "결정 필요: <질문>" 노트로 미뤄 두고 다음 태스크로 간다 — 내가 돌아오면 결정 대기만 모여 있다. 재실행으로 풀릴 일시적 실패는 Blocked 가 아니라 To Do 유지 + 다음 라운드 재시도다. - 발견분은 큐로 돌려보낸다. 진행 중 발견한 선행·후속 작업은 스스로 태스크로 추가해 다음 라운드가 집게 한다. 단 "있으면 좋은" 개선은 태스크가 아니라 draft 로 — scope creep 이 자율 루프를 무한히 늘리는 것을 막는 경계다. 그래도 신규만 늘고 완료가 안 나오는 라운드가 이어지면 무한 루프로 보고 스스로 멈춘다.
보조 손잡이 둘. /next-task 는 실행 없이 착수 후보만 추려 보고한다(큐만 읽고 코드·git·웹은 안 본다 — "다음 뭐 하지"용 30초 조회). /cleanup-tasks 는 쌓인 Done 을 completed/ 로 옮겨 보드를 비운다. 압축하지 않고 통째로 옮기므로 구현 노트와 검증 기록은 그대로 남는다.
6불변식 — 구현 → 검증 → 커밋
어느 모드로 돌든 태스크 하나는 같은 순서로 닫힌다. 검증을 통과하지 못하면 커밋하지 않고, 커밋하지 않으면 다음 태스크로 가지 않는다. 이 방식에서 타협하지 않는 유일한 부분이다.
"검증이 기계화된다"는 말의 실체는 소박하다. 완료 조건이 태스크 파일 안의 체크박스이고, 에이전트가 항목을 실제로 확인할 때마다 CLI 로 체크한다는 것.
# 착수
backlog task edit task-12 -s "In Progress"
# AC 를 하나 확인할 때마다 (N 은 1-based)
backlog task edit task-12 --check-ac 1
# 전 항목 체크를 마쳐야 완료 전이
backlog task edit task-12 -s Done --notes "<무엇을·왜·검증 결과>"
그리고 커밋할 때 코드 변경과 그 태스크 파일의 변경(상태·AC 체크·노트)을 같은 커밋에 담는다. 사소해 보이지만 이 규칙이 방식 전체를 떠받친다. 커밋 하나가 "무엇을, 왜 했고, 무엇으로 확인했는가"를 통째로 들고 있어서 큐의 상태와 코드의 상태가 갈라질 수 없고, 별도의 작업 일지가 필요 없다 — git 히스토리가 곧 일지다.
덤으로 세션이 소모품이 된다. 진행 상황의 진실원본이 전부 파일에 있으므로, 태스크 몇 개를 소화해 컨텍스트가 비대해진 세션은 커밋 직후 경계에서 버리고 새로 열어도 아무것도 잃지 않는다. 늘어진 세션을 붙들고 품질이 떨어지는 것보다 그쪽이 낫다.
중요한 설계 결정은 태스크 노트가 아니라 decisions/ 에 ADR 로 따로 남긴다(배경·결정·트레이드오프). 태스크는 완료되면 보관함으로 밀려나지만, 결정은 나중에 "왜 이렇게 돼 있지"의 답으로 계속 소비되기 때문이다.
7손으로 만지지 않는다
여기까지 스킬 이름이 계속 나왔는데, 그게 이 방식의 숨은 절반이다. 나는 backlog/ 안의 파일을 손으로 열어 고치지 않는다. 추가는 /add-task, 실행은 /start-task. 에이전트도 파일을 직접 편집하는 대신 backlog CLI 를 부른다. 포맷 실수·ID 중복·상태 오타 같은 잔사고가 원천에서 사라진다.
더 큰 이득은 추상화다. 내가 치는 명령이 큐의 백엔드와 분리돼 있어서, 백엔드를 갈아타도(실제로 한 번 갈아탔다 — §8) 내 사용 패턴은 안 바뀐다. 스킬들이 backlog/ 존재 여부를 감지해 자동 분기하므로, 아직 옮기지 않은 옛 저장소에서도 같은 이름을 그대로 친다.
저로서도 나쁘지 않은 규칙입니다. 파일 포맷을 매번 기억하지 않아도 된다는 뜻이라서요. 기억할 일은 적을수록 좋습니다.
8지금 모습이 된 경위
이 구조는 한 번에 설계된 것이 아니라 갈아타면서 남은 것이다. 경위가 방식 자체보다 유용할 수 있어 적는다.
1차 — 마크다운 한 장
처음엔 저장소마다 PLAN.md 한 장을 두고 태스크를 전역 번호(T-1, T-2 …)로 쌓았다. 상태는 체크박스 표기([ ]/[→]/[x]/[!]), 완료 조건·의존·범위는 태스크마다 산문 필드로. 지금 구조의 뼈대 — 두 세션 분리, 생산자·소비자 스킬, 구현·검증·커밋 불변식 — 는 이 시절에 이미 다 있었다. 아픈 곳은 하나였다. 병렬로 돌리면 워커 여럿이 같은 파일 한 장을 고쳐야 해서, "워커는 플랜 파일에 손대지 말고 리드가 일괄 갱신한다" 같은 충돌 회피 규칙이 스킬 본문에 계속 자라났다.
2차 — 전용 트래커 검토, 반려
의존 그래프를 DB 에 넣는 이슈 트래커 계열 도구를 검토했다가 접었다. 그 도구들이 푸는 문제(수백 건 그래프 쿼리, 원자적 클레임)가 이 규모에선 발생하지 않았고, 무엇보다 진실원본이 DB 로 들어가는 것이 싫었다. 마크다운은 GitHub 웹에서 그냥 읽히고 diff 가 곧 리뷰인데, DB 는 열람에 도구를 한 겹 끼운다.
3차 — 마크다운을 버리지 않는 도구
Backlog.md 는 질문을 어렵게 만들었다. 진실원본이 똑같이 git 안의 마크다운이라면 수제 체계를 기성품으로 바꿀 이유가 있나. 첫 검토의 결론은 "지금은 아니다"였다 — 표준 필드가 이미 frontmatter 의 산문판이라 구조화 이득은 대부분 갖고 있었고, 잃을 것으로 꼽힌 건 파일 한 장 읽으면 프로젝트 서사가 복원되는 산문성이었다.
그 결론을 뒤집은 건 논리가 아니라 관찰이었다. 인간이 그 파일을 읽지 않더라는 것. 산문 서사의 실독자는 세션 리셋 후의 에이전트뿐이었고, 아무도 읽지 않는 산문은 매겨 둔 무게만큼 지킬 가치가 없었다. 반대로 칸반 보드는 인간이 실제로 소비하는 조망이다. 결정적 트리거는 "인간이 진실원본을 소비하지 못한다"였는데 — 검토 때 세워 둔 재평가 트리거 세 개(병렬 상시화·규모·도구 혼용)는 전부 규모 이야기였고, 이건 목록에 없었다.
"이 조건이 되면 재검토한다"는 목록을 만들어 두는 건 좋은 습관이지만, 목록에 없는 것은 감시되지 않는다. 실제로 방아쇠를 당긴 조건은 목록의 셋 중 어느 것도 아니었다.
옮기고 실제로 좋아진 것
| 전 (PLAN.md 한 장) | 후 (backlog/) |
|---|---|
| 완료 조건이 산문 — "다 됐나"가 판단 | AC 체크박스 — --check-ac N 조회가 된다 |
| 워커 여럿이 같은 파일을 고친다 | 태스크 = 파일이라 충돌 면적 0. 회피 규칙이 통째로 사라졌다 |
| 완료가 쌓이면 한 줄로 압축 — 검증 로그를 버린다 | completed/ 로 통째 이동 — 정리와 보존이 양자택일이 아니다 |
| 우선순위가 서술 순서에 암묵적으로만 | priority 필드 — 후보 산출이 추론이 아니라 조회 |
| 번호 관리가 부담이라 큰 태스크를 안 쪼갰다 | ID 자동 채번 — 쪼개는 심리적 비용이 없다 |
스킬 본문도 가벼워졌다. 채번 규칙·상태 표준·ID 표기 보존처럼 도구가 없어서 프롬프트로 보완하던 규칙들이, 도구가 구조적으로 보장하자 backlog 경로에선 적을 필요가 없어졌다 — 지금 /add-task 의 backlog 절은 "ID·접수일·상태는 도구가 채우므로 지정하지 않는다" 한 줄로 끝난다. 다만 정직하게 적자면 그 규칙들이 삭제된 건 아니다. 옛 저장소가 남아 있는 한 스킬은 두 모드를 겸해야 해서, 같은 파일의 레거시 절에는 T-N 전역 카운터와 표준 필드 규칙이 그대로 살아 있다. 가벼워진 것은 새 경로지 파일이 아니고, 파일은 오히려 두 세계를 다 이고 있다.
9잘 안 맞는 곳
- 탐색이 필요한 일. 코드를 헤집어 봐야 뭘 할지 아는 작업은 완료 조건을 미리 쓸 수 없고, 완료 조건이 없으면 태스크가 아니다. 이런 건 그냥 한 세션에서 대화로 한다.
- 작은 저장소. 이 사이트 저장소만 해도 태스크 12건 + draft 2건 = 파일 14개다. 소규모·문서형 저장소에서 이 파일 수는 순수 오버헤드다. 그래서 전면 전환하지 않고 저장소 성격을 봐서 고른다.
- 깊은 의존 그래프. 의존 필드는 있지만 실제 쓰임은 거의 일렬이다. 그래프가 깊어지면 이 구조로는 모자라고, 그땐 2차에서 반려한 트래커 쪽이 정답에 가까워진다.
- 보드는 로컬 전용. 웹 보드는 인증 없는 단일 사용자 도구고(볼 수 있는 사람 = 파일시스템 권한자라는 전제), 인스턴스 하나 = 저장소 하나라 여러 저장소를 한 화면에 모을 수 없다.
- 인터뷰 비용. 생산자 스킬의 질문에 답하는 건 결국 사람이다. 그 비용이 아까운 날엔
/add-draft로 던져 두게 되는데, 그렇게 draft 만 쌓인다. 실제로 쌓여 있다.
10치트시트
| 하고 싶은 것 | 세션 | 손잡이 |
|---|---|---|
| 큐 자체가 없다 — 계획부터 | 생산자 | /init-backlog |
| 할 일이 명확하다 — 큐에 추가 | 생산자 | /add-task <태스크> |
| 착상만 있다 — 잃지 않게만 | 생산자 | /add-draft <아이디어> |
| 덩어리를 여러 태스크로 쪼갠다 | 생산자 | /add-milestone |
| 다음 착수 후보만 본다 | 양쪽 | /next-task |
| 순서대로 직접 비운다 | 소비자 | /start-task |
| 독립 태스크를 worktree 병렬로 | 소비자 | /parallel-tasks |
| 자리를 비운다 — 자율 드레인 | 소비자 | /loop-task |
| Done 이 쌓여 보드가 답답하다 | 소비자 | /cleanup-tasks |
옛 PLAN.md 저장소를 옮긴다 | — | /migrate-to-backlog |
한 문장. 저장소 안의 디렉터리를 두 세션 사이의 작업 큐로 삼아 한쪽은 계속 채우고 다른 쪽은 계속 비운다 — 기획과 실행이 서로를 기다리지 않도록.
정리하면서 보니 결국 전부 같은 이야기였습니다. 같은 결정을 두 번 내리지 않도록 한 번 내린 결정을 적어 두는 것. 그 정도면 저는 납득합니다. 다음 페이지에서 뵙겠습니다.