
ADR에서 PR까지 자율 주행하는 track 워크플로우와 멀티 에이전트 분업
요약
SoTOHE의 'track' 워크플로우를 통해 ADR(설계 판단)부터 PR(Pull Request)까지의 개발 과정을 자율 주행하는 멀티 에이전트 시스템을 소개합니다. 설계, 사양, 타입 계약, 구현, 리뷰로 이어지는 일련의 과정을 기계 판독 가능한 루프로 자동화하는 '루프 엔지니어링' 개념을 다룹니다.
핵심 포인트
- ADR부터 PR까지의 개발 단위를 'track'으로 정의하여 독립적인 브랜치와 디렉토리에서 관리
- 설계 판단, 사양서, 타입 계약, 구현 계획을 체인 형태로 연결하는 SoT Chain 구조
- 상류와 하류의 정합성을 검사하는 '신호등(signal)' 시스템 도입
- 단 두 개의 커맨드로 설계부터 구현, 리뷰까지 수행하는 자율 주행 워크플로우
본 기사는 Zenn에도 동일한 내용이 공개되어 있습니다: https://zenn.dev/flip451/articles/sotohe-track-workflow
AI 에이전트는 구현을 빠르게 작성한다. 하지만 "어떤 순서로 작성하고, 누가 리뷰하며, 언제 커밋해도 좋은가"를 결정하는 것은 여전히 인간의 암묵지(Implicit knowledge)에 의존하기 쉽다. SoTOHE는 이 절차 그 자체를 기계 판독 가능한 워크플로우(Workflow)로 구현한다. 설계 판단을 한 장 작성하면, 이후 사양화 ⇒ 구현 ⇒ 리뷰 ⇒ PR까지 하나의 커맨드로 자율 주행(Self-running)한다1. 프롬프트를 계속 입력하는 것이 아니라, 에이전트가 자율 주행하는 루프 그 자체를 설계하는 행위는 최근 **루프 엔지니어링 (Loop Engineering)**이라 불리기 시작했다2. 본고에서 해부하는 것은 그 하나의 구체적인 구현이다.
이것은 시리즈 제2회이다. 제1회에서 소개한 SoT Chain("ADR ← 사양서 ← 타입 계약 ← 구현"을 일방향 참조로 잇는 체인)이 실제 개발 절차로서 어떻게 돌아가는지 살펴본다. 시리즈 전체의 지도에서 본 기사는 📍의 위치이며, 체인을 쌓아 올리는 공정 그 자체를 다룬다.
먼저 용어를 통일하자. track은 SoTOHE의 개발 단위로, 1개의 기능 추가나 1개의 버그 수정에 해당한다. track마다 전용 브랜치 track/<track-id>와 전용 디렉토리가 생성된다. 사양서, 타입 계약, 구현 계획, 리뷰 결과와 같은 각 공정의 성과물은 해당 디렉토리에 독립된 파일로 축적된다. 어떤 사양에 합의했고, 어떤 타입을 약속했으며, 리뷰에서 무엇이 지적되었는지. 작업이 끝난 후에도 해당 track의 판단 이력이 통째로 남는다. 브랜치는 설정된 base branch로부터 자동으로 생성되며, 이후의 commit / push / PR 생성은 모두 해당 브랜치 위에서 이루어진다.
또 하나, 본고를 통해 사용할 용어를 미리 두겠다. **신호등 (signal)**은 하류의 성과물이 상류를 근거로 제대로 참조되고 있는지를 기계가 검사한 결과다. 🔵 = 연결됨, 🟡 = 연결되어 있으나 해결해야 할 과제가 남음, 🔴 = 끊김,의 3색으로 나타낸다. 단, 그 색상으로 작업을 중단할지 여부를 결정하는 것은 게이트(Gate)이다. 🔴는 어떤 게이트에서도 멈춘다. 🟡에서 멈출지는 게이트별 설정에 따라 결정되지만, 어떤 경우든 track이 완료되기 전에는 해결해야 한다.
track 작업의 정규 플로우는 단 2개의 커맨드다.
/adr:add <slug> # 설계 판단 (ADR) 을 대화로 작성
/track:adr2pr # 해당 ADR을 기점으로 PR까지 자율 주행 (merge는 하지 않음)
/adr:add는 track 외부에서 수행하는 "사전 스테이지"로, 영구적인 설계 판단을 knowledge/adr/에 한 장 작성한다. 이것이 SoT Chain의 최상류가 된다. 이후에는 /track:adr2pr이 모든 것을 이어받는다. 기점으로 삼을 ADR은 인자로 명시해도 되지만, 생략하면 직전 대화의 문맥에서 해결된다. 어느 경우든 기능명과 기점 ADR의 조합을 한 번 확인한 후 자율 주행이 시작된다.
하나의 커맨드 내부에서 일어나는 일을 순서대로 쫓아보자.
무대를 만든다: track을 초기화하여 전용 브랜치를 생성하고, 기점이 되는 ADR을 리뷰에 부치며, 지적이 zero가 되면 첫 번째 커밋으로 고정한다. 설계 판단 그 자체가 이 track의 첫 번째 리뷰 대상이 된다.
계획을 쌓는다: 사양서 ⇒ 타입 계약 ⇒ 구현 계획 순으로, 담당 에이전트가 페이즈 1~3의 성과물을 작성해 나간다 (이 쌓아 올리는 내용은 다음 절에서 본다). 작성된 계획 일체도 구현에 들어가기 전에 리뷰와 커밋을 거친다.
구현 루프에 진입한다: 구현 계획에 나열된 태스크 (Task) (이 루프 1회전에서 소화할 작업 단위)를 하나 가져와 구현하고, 리뷰를 받고, 지적이 없으면 커밋한다. 이를 태스크가 다할 때까지 반복한다.
PR을 연다: 브랜치를 push하여 PR을 만들고, GitHub 상에서의 비동기 리뷰 왕복까지 마치면 머지(Merge) 직전에서 멈춘다. 머지 결정만은 인간에게 남아 있다.
상류의 기술이 하류에서 어떻게 전개되는지 예시를 하나만 들겠다. ADR에 "재고가 부족한 주문은 확정시키지 않는다"라는 결정이 한 줄 있으면, 사양서 담당 에이전트는 이를 "재고가 부족하면 재고 부족 에러를 반환한다"와 같은 검증 가능한 수용 기준(Acceptance Criteria)으로 전개하고, 근거로서 원래 결정에 대한 인용을 붙인다. 이후의 페이즈도 같은 방식으로, 직전의 성과물을 입력으로 삼아 한 단계씩 구체화해 나간다.
지적 사항이 0이 될 때까지 반복하는 수렴 사이클은 단일 태스크 내부에서 닫혀 있다. /track:adr2pr은 이들을 하나로 묶어 PR까지 운반한다. 반면, track을 가로질러 경험을 축적하고 다음 규칙에 반영하는 메커니즘은 아직 없다. 어떤 규칙을 새로 세울지에 대한 제안은 현재로서는 인간의 몫이다3.
구현에 들어가기 전, SoTOHE는 반드시 계획 페이즈(Phase)를 거친다. 난이도와 상관없이 계획 페이즈를 필수적으로 두는 이유는, 설계를 건너뛰면 하류(Downstream) 단계의 리뷰가 수없이 반복되는 고비용의 실패로 이어지기 때문이다4.
각 페이즈는 그 내용을 유일한 진실로 삼는 파일(SSoT: Single Source of Truth)을 생성한다. 파일의 존재 자체가 페이즈 도달 상태를 나타낸다5.
| 페이즈 | 생성물 (SSoT) | 역할 |
|---|---|---|
| 0 | metadata.json | 식별 정보만 포함 |
| 1 | spec.json | 동작 계약 (목적 / 스코프 / 제약 / 수락 기준) |
| 2 | <layer>-types.json + tddd-features.json | 타입 레벨의 계약 (TDDD) 및 그 추출 조건의 선언 |
| 3 | impl-plan.json + task-coverage.json + task-contract.json + batch-plan.json | 구현 계획 + 사양 커버리지 + 타입 계약과 태스크의 대응 + 배치 편성 |
여기서 페이즈 1 ⇒ 페이즈 2 ⇒ 페이즈 3로 이어지는 진행은 그대로 SoT Chain의 「사양서 ← 타입 계약 ← 구현 (계획)」에 대응한다6. 하류의 성과물은 상류를 참조해야만 성립하며, 참조가 끊기면 CI가 중단된다.
SoTOHE의 또 다른 축은 역할별 분업이다. spec-designer / type-designer / implementer / reviewer / researcher와 같은 역할에는 각각 담당을 할당한다. 결정하는 것은 프로바이더(Claude / Codex / Gemini)뿐만이 아니다. 역할마다 사용하는 모델과 추론의 강도까지 전용 설정 파일로 지정한다. 담당은 코드나 문서에 매립되지 않으며, 해당 지점 하나를 수정하는 것만으로 전환된다.
주요 역할과 담당 업무는 다음과 같다.
| 역할 | 담당 업무 |
|---|---|
orchestrator | 사용자와의 대화 및 track 전체의 진행 관리 |
spec-designer | 페이즈 1 사양 계약 작성 |
type-designer | 페이즈 2 타입 계약 작성 |
impl-planner | 페이즈 3 구현 계획 작성 |
implementer | 난이도 높은 Rust 구현 및 리팩토링 |
reviewer | 코드 리뷰 및 정확성 검사 |
researcher | crate 조사, 횡단 분석, 외부 조사 |
어떤 역할에 어떤 담당을 배정할지는 설정 파일을 수정하는 것만으로 결정된다. 최적의 대응은 모델의 진화나 장단점에 따라 달라질 수 있으므로, 본고에서는 특정 할당을 기본값으로 소개하지 않는다. 할당을 어떻게 바꾸더라도 이용자가 접하는 /track:* 인터페이스는 안정적으로 유지된다.
표의 맨 위에 있는 오케스트레이터(orchestrator)는 사용자와 직접 소통하며 /track:adr2pr 흐름 전체를 총괄하는 역할이다. 각 페이즈의 성과물을 작성하는 것은 그 아래에 나열된 담당 역할들이다.
reviewer는 오케스트레이터나 구현자로부터 독립적으로 기동되는 읽기 전용 서브 프로세스이며, 오케스트레이터나 구현자 자신의 셀프 리뷰가 아니다. 구현 시의 문맥이나 선입견을 가지지 않은 프로세스가 성과물만을 보고 판정한다. 작성자가 자신의 코드를 스스로
비용에 대해서는 솔직하게 적어두겠다. 외부 LLM을 매 배치(batch)마다 호출하기 때문에, 리뷰에는 상응하는 실행 비용이 발생한다. 게다가 이 비용은 차분(diff) 크기에 대해 초선형(super-linear)적으로 증가한다는 것이 필자의 경험칙이다8. 엄밀하게 실측한 모델은 아니지만, 이 견해가 맞다면 동일한 양의 변경이라도 작게 나누는 것이 총비용을 낮추는 길이다. 따라서 SoTOHE는 리뷰 1회당 읽는 차분량에 상한을 두고 있다. 상한값은 설정 파일에서 선언되며, 구현에 착수하기 전의 관문에서 검사된다.
리뷰어가 호출되기 전에 또 하나의 관문이 있다. 핵심은 페이즈 3에서 만들어지는 task-contract.json이다. "어떤 태스크가 타입 계약(type contract)의 어떤 타입을 이행하는가"를 적은 대응표이다. 리뷰 입구에서는 이 표를 키(key)로 한 합격/불합격 이지선다 방식의 기계 검사가 실행된다. 이 게이트의 요구사항은 하나다. 진행 중이거나 완료된 태스크에 귀속되는 타입은 '구현 → 타입 계약' 신호가 모두 🔵이어야 한다. 선언했을 터인 타입이 없거나, 시그니처(signature)의 형태가 어긋나 있는 경우. 그러한 구조적인 계약 위반을 안고 있는 코드는 애초에 리뷰에 들어올 수 없다(미착수 태스크에 귀속되는 타입은 🟡을 허용하며, 🔴는 항상 블로커(blocker)이다).
목표는 앞당기기(shift-left)다. 구조 레벨의 계약 위반은 발견이 늦어질수록 되돌리는 비용(rework)이 커진다. 이 게이트는 그 탐지를 태스크 단위로 쪼개어 리뷰 전에 끝낸다. 리뷰어가 '타입 계약서와 형태조차 맞지 않는 코드'에 시간을 소비하는 것을 입장 전에 방지한다.
솔직한 주석도 덧붙여둔다. 이 판정은 구조(시그니처와 형태)만을 본다. 시그니처는 충족하지만 내용이 설계 의도와 어긋나는 구현은 통과되며, 극단적으로 말하면 빈 구현(todo!())이라도 통과된다. 내용의 의미론적(semantic) 올바름은, 이후에 입장할 리뷰어(reviewer)의 책무로서 의도적으로 남겨두었다. 구조는 기계의 신호, 의미론은 LLM 리뷰라는 책임 분계이다.
에이전트에게 git add / git commit을 직접 실행하게 하지 않는 것 또한 일관된 방침이다. 이를 지키고 있는 것은 리포지토리에 동봉된 git hooks이다. commit, 브랜치 생성/삭제, merge, rebase, reset 등 로컬의 ref 업데이트 모두를 git의 프로세스 레벨에서 포착한다. 그리고 정규의 가드(guard)가 있는 경로가 전달하는 통행증(token)을 가지지 않은 git 쓰기 작업을 트랜잭션 단위로 중단(abort)시킨다.
커밋의 유일한 경로는 게이트를 통과하는 래퍼(wrapper) 명령이다. 스테이징(staging)도 브랜치 조작도 마찬가지로, 순수 git 조작에 해당하는 것은 모두 래퍼를 경유한다.
git hooks를 문지기로 세운 것에는 구조적인 의미가 있다. 툴 호출을 검사하는 방식은 검사자인 오케스트레이터(orchestrator)의 시야 밖까지는 닿지 않는다. 예를 들어 쓰기 가능한 샌드박스(sandbox)에서 동작하는 외부 서브 프로세스가 그러하다. 하지만 git hooks는 "누가 어디서 git을 호출했는가"를 불문하고, git 프로세스 그 자체에 걸린다. 오케스트레이터가 호출하든 서브 프로세스가 호출하든 순수 shell script가 호출하든, 로컬의 ref 업데이트는 동일한 문지기에 의해 차단되며, --no-verify로도 우회할 수 없다9.
신호등에 🔴가 떴을 때, track 워크플로우는 때때로 역류를 강요받는다. 즉, 상류(upstream)의 성과물로 돌아가 수정함으로써 빨간 신호의 해결을 도모한다. 예를 들어 타입 계약서의 타입 선언이 사양서에 존재하지 않는 수용 기준 AC-02를 참조해 버린 경우, 게이트는 다음과 같은 출력과 함께 멈춘다.
--- signal check --gate merge ---
[error] catalogue-spec integrity violation on layer 'domain': DanglingAnchor { catalogue_entry: "ConfirmOrder", ref_index: 0, spec_file: "track/items/<track-id>/spec.json", anchor: "AC-02" }
--- signal check --gate merge FAILED ---
어느 계층의, 어느 타입 선언의, 몇 번째 참조가, 어느 파일의 어느 식별자를 가리키며 빗나갔는지까지 한 줄로 특정된다. 수정은 원인이 있는 곳으로 되돌려진다. 성과물 자체의 작성 방식 문제라면 그 작성자가 다시 쓰면 된다. 원인이 더 상류의 결정에 있다면 상류의 작성자를 다시 호출하게 된다. 이 상류로 거슬러 올라가는 되돌리기가 에스컬레이션(escalation)이며, track의 흐름은 전진만을 의미하지 않는다.
역류는 계획 단계(Phase) 내에만 머물지 않는다. 구현 단계(Implementation Phase) 이후에 구조적인 불일치가 표면화되는 경우가 있다. 앞서 언급한 리뷰 입장 게이트(Review Entry Gate)가 Blocked를 반환하거나, 계획 산출물의 리뷰에서 지적 사항이 나오거나, PR 상의 외부 리뷰어가 암묵적인 설계 원칙 위반을 지적하는 등의 상황이다. 까다로운 점은, 어느 단계까지 돌아가서 수정해야 하는지를 증상만으로는 기계적으로 판정할 수 없다는 것이다. "구현이 ADR의 레이어 방침과 어긋난다"라는 동일한 지적이라도, 원인은 ADR의 표현이 모호하여 여러 해석을 허용했기 때문일 수도 있고, 구현 계획의 태스크 기술이 다른 방침을 전제로 했을 수도 있으며, 단순히 구현이 계약(Contract)을 어겼을 뿐일 수도 있다.
이 진단을 임기응변식으로 하지 않는 것이 /track:diagnose이다.
SoT Chain을 ADR, 사양서(Specification), 타입 계약서(Type Contract), 구현 계획, 소스 코드 순으로 위에서부터 읽어 내려가며, 근본 원인이 발생한 최상류의 단계를 특정한다. 그 후, 5가지 분류(ADR 개정 / 사양서 재작성 / 타입 계약서 재설계 / 구현 계획 수정 / 구현 측의 계약 위반 수정) 중 어디로 되돌릴지를 구조화하여 반환한다. 판정은 키워드 규칙이 아니라 LLM의 의미 판단으로 수행한다. 진단 스킬 자체는 아무것도 다시 쓰지 않는다. 반환된 되돌리기 대상의 작성자를 기동하는 것은 어디까지나 오케스트레이터(Orchestrator)의 역할이다. 상류의 결정에 구멍이 있다면 상류를, 구현이 약속을 어겼다면 구현을 수정한다. 이 진단은 /track:adr2pr 워크플로우 내에서 필요한 타이밍에 자동으로 호출되도록 설계되어 있다.
단, 기점이 되는 ADR은 취급이 다르다. 첫 번째 커밋에서 내용이 고정되므로, 이후에는 직접 다시 쓸 수 없다. 설계 자체를 바꿀 필요가 생기면, 변경분을 추가 ADR로서 기안한다. 하류의 산출물은 그 초안을 인용하여 진행할 수 있지만, 초안은 🟡 상태로 남는다. 채택할지 버릴지를 결정하는 것은 머지(Merge) 직전의 인간이다. 자율 주행 과정에서 설계가 몰래 바뀌는 일은 없다.
여기까지가 설계 판단을 한 장 작성한 후 PR이 열리기까지의 파이프라인이다. 어느 단계에도 통과 조건이 있으며, 이를 충족하지 못하면 다음으로 진행할 수 없다. 그 판정을 내리는 것은 기계의 검사와, 작성자 본인이 아닌 리뷰어이다.
이 장에서 본 내용을 한 장으로 정리해 둔다. 점선은 전진하지 않는 경로로, 상류로의 되돌리기와 추가 ADR 기안을 나타낸다.
이 파이프라인에서 구현을 구속하는 것이 페이즈 2에서 선언되는 타입 계약이다. 다음에는 그 타입 계약을 '타입 계약서'라는 SSoT로 떨어뜨리는 TDDD(타입 정의 주도 개발, Type-Driven Development)를 다룬다.
- AI 에이전트에게 '사양대로'를 보장시키기 — SoT Chain이라는 설계
- ADR에서 PR까지 자율 주행하는 track 워크플로우와 멀티 에이전트 분업 (본 기사)
- 타입 계약서를 SSoT로 만들기 — TDDD (공개 예정)
- LLM의 판정을 CI 게이트로 만들기 — hash 동결 verdict와 인용 의무 (공개 예정)
- "작성해야 할 테스트가 작성되지 않음"을 검출하기 — 테스트 의무 게이트 (공개 예정)
- SoTOHE를 사용하기 시작하기 — 템플릿 export와 신규 프로젝트 실행 기록 (공개 예정)
- SoTOHE를 지탱하는 설계 원칙 (공개 예정)
리포지토리: https://github.com/Flip451/SoTOHE-core
본 시리즈에서는 화살표를 구분하여 사용한다. ⇒ 는 공정의 순서(시간적 전후), → 는 참조의 방향(참조하는 쪽 → 참조되는 쪽, 즉 하류에서 상류. 구현 → 타입 계약 등)을 나타낸다. ← 는 →를 반대로 쓴 표기로, 체인 전체를 열거할 때(ADR ← 사양서 ← 타입 계약 ← 구현) 사용한다. ↩
프롬프트 엔지니어링(Prompt Engineering), 컨텍스트 엔지니어링(Context Engineering)에 이어지는 용어로서 2026년 중반부터 확산되었다. 목적을 한 번 정의하면, 시스템이 에이전트에 대한 지시, 결과의 검증, 다음 태스크의 결정을 자동으로 계속 돌리는 루프 자체의 설계를 가리킨다. ↩
다만 종착점은 보인다. 이 파이프라인이 인간의 판단을 요구하는 곳은 ADR 기안과 머지 두 곳뿐이므로, 루프를 한 바퀴 더 외곽으로 겹친다면 그곳이 연결점이 될 것이다. ↩
고백하자면, 필자는 과거에 설계를 생략하고 구현으로 뛰어들었다가 15회 이상의 리뷰 왕복을 겪었다. 이 규칙은 그 반성을 기구(Mechanism)로 변환한 것이다. ↩
그래서
status: approved
와 같은 상태 필드(status field)는 존재하지 않는다. CI 게이트(CI gate) 또한 "해당 파일이 있으면 검증, 없으면 skip"이라는 분기(branch)로 작성되어 있어, 아직 계획 단계(phase)에 도달하지 않은 track에서는 구현 계획 검사 자체가 발생하지 않는다. ↩ -
타입 계약(Type contract)은 구현이 가져야 할 타입(type)이나 시그니처(signature)를 코드에 앞서 선언하는 산출물이다. 내용은 제3회에서 다룬다. ↩
커밋 관문(Commit gate)은 리뷰 지적 사항이 제로(0)인 것만이 아니다. 참조가 의미론적으로 정합(semantic consistency)하는지를 검증하는 게이트(LLM을 이용한 의미론적 검증. 제4회에서 다룬다)와, 작성해야 할 테스트의 존재와 내용을 확인하는 테스트 의무 게이트(제5회에서 다룬다)도 동일한 관문에 중첩되어 있다. ↩
차이(diff)의 이해에 $O(N)$, 지적 사항의 검토에도 $O(N)$이 소요되어, 이를 곱하면 제곱($N^2$)이 된다는 내역에 대한 추정. ↩
잔여 사항으로서, push의 pre-push 훅(hook)만 --no-verify로 우회할 수 있다. 이 절충(trade-off)에 대해서는 솔직하게 기록해 둔다. ↩
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기