에이전트를 여럿 굴릴 때, 사용자는 그중 누구와 말하는가.

안 A   일하는 에이전트마다 사용자가 직접 붙는다.
       다섯이 돌면 사용자는 다섯 개의 대화를 오간다

안 B   사용자와 말하는 에이전트는 하나뿐이다.
       나머지는 그 하나를 통해서만 사용자에게 닿는다

안 A는 손실이 없다. 구현을 아는 쪽이 직접 말하므로 중간에서 요약되며 새는 것이 없다.

대신 사용자가 프로젝트 전체를 머리에 이고 있어야 한다. 다섯 개의 대화 중 무엇이 급한지, 무엇이 멈춰 있는지, 어느 것이 어느 것을 기다리는지를 사람이 매번 재구성한다. 그 순간 에이전트가 사람을 끌고 가기 시작한다 — 사람은 따라가며 답만 하고, 전체 방향은 아무도 안 쥔다.

이 글은 안 B를 고른 기록이고, 그 하나를 고르고 나서 줄줄이 딸려 나온 것들의 기록이다. 사용자와 말하는 에이전트를 하나로 묶는 순간 「그럼 나머지는 어떻게 사용자에게 닿나」, 「그 하나가 죽으면 어떻게 되나」, 「밀린 질문은 누가 기억하나」가 전부 새로 정해야 할 것이 된다.


먼저: 이 표준은 orca 없이는 성립하지 않는다

이건 원칙이 아니라 전제이고, 편의가 아니라 조건이다. orca를 안 쓰는 프로젝트에는 이 표준을 적용하지 않는다. 아래 규칙의 절반이 부를 대상을 잃기 때문이다.

흉내 내어 직접 만드는 것도 금지한다. 그렇게 하면 이 표준이 발명하지 않기로 한 서식을 프로젝트 수만큼 발명하게 되고, 같은 규칙이 프로젝트마다 다른 모양이 된다.

그 전제 위에서 정할 것이 통째로 줄었다.

표준을 세우기 전에 orca --help를 먼저 돌렸다. 발명해야 할 줄 알았던 것이 대부분 이미 원어로 있었다.

필요한 것 orca 원어
서브가 마스터에게 묻고 기다린다 orchestration ask (블로킹)
선택이 필요한 지점에서 작업을 막는다 orchestration gate-create / gate-resolve / gate-list
판단을 하위로 되돌린다 orchestration send / reply / check / inbox
누가 어느 워크트리·브랜치에 있나 worktree ps
작업 단위와 상태 orchestration task-create / task-update / dispatch
서브 생성·제거 orchestration worker-start / worker-stop / worker-release

그래서 이 표준은 서식을 만들지 않는다. 질문 봉투도, 메시지 규약도, 상태 파일도 정의하지 않는다. 정하는 것은 위상과 규율뿐이다 — 누가 누구를 만들고, 누가 사람과 말하고, 무엇을 물어야 하고, 무엇을 기억하지 말아야 하는가.

이 전제를 안 세웠으면 안을 셋 놓고 서식을 설계했을 것이고, 전부 헛돌았을 것이다.


먼저: 이미 정해진 것

에이전트 운영 표준이 먼저 있고, 이 표준은 그 위에 선다. 두 표준의 질문이 다르다.

에이전트 운영 표준   기능 하나에 이슈를 몇 개 만드는가 — 트래커 위의 규칙
이 표준             에이전트를 몇 층으로 세우고 누가 사람과 말하는가 — 에이전트의 위상

그쪽에서 그대로 가져오는 것이 셋이다.

  • 에이전트는 사람이 있을 때만 돈다. 무인 운영을 하지 않으므로 폴링도 하트비트도 없다.
  • 기능 하나에 이슈 하나. 테스트가 이슈를 새로 만들지 않는다.
  • 기능을 넘는 결정은 .coding-standards/decisions.md에 올린다.

그리고 그쪽의 세 원칙을 이 표준이 회수해 고쳤다. 원칙 11(개발→테스트는 사람이 배정한다)·원칙 13(사람이 테스트 세션을 열며 브랜치를 준다)·원칙 16(에이전트마다 대기 브랜치를 둔다)이다. 무엇이 왜 뒤집혔는지는 「이 표준을 정하기까지」에 있다.


원칙 1. 마스터는 하나이고 통합 브랜치에서 내려오지 않는다

사용자와 말하는 에이전트를 어디에 세우는가.

안 A — 마스터도 자기 워크트리를 판다. 다른 에이전트와 같은 모양이라 규칙이 하나로 끝난다. 버린 이유는 마스터가 통합 브랜치의 최신 상태를 못 보는 것이다. 마스터는 PR을 리뷰하고 병합하는 자리인데, 병합 대상 브랜치에 서 있지 않으면 매번 fetch하고 비교해야 한다.

골랐다 — 안 B, 마스터는 통합 브랜치에 상주하고 절대 옮기지 않는다.

main 과 dev 가 모두 있는 저장소   →  마스터는 dev
main 만 있는 저장소               →  마스터는 main

안 A가 못 지켜주는 것은 통합 브랜치의 점유자가 고정되는 것이다. 워크트리는 .git 하나를 공유하고 한 브랜치는 한 워크트리에서만 체크아웃되므로, 점유자가 정해져 있지 않으면 아무나 잡고 나머지가 막힌다. 마스터가 상주하면 그 자리가 영구히 정해진다.

대가는 마스터가 코드를 직접 고칠 수 없다는 것이다. 통합 브랜치에서 작업하면 안 되기 때문이다. 마스터는 읽고, 지시하고, 병합만 한다.

서브는 통합 브랜치로 이동하지 않는다. 이동할 필요가 없다 — 작업 브랜치는 worker-start --base-branch <통합 브랜치>로 딴다. git은 체크아웃하지 않은 브랜치를 기준으로 새 브랜치를 따는 것을 막지 않는다.


원칙 2. 층이 셋인 이유는 역할이 셋이기 때문이다

에이전트를 몇 층까지 쌓는가.

graph TD U["사용자"] --> M["1depth 마스터
dev 상주 · 유일"] M --> W1["2depth 작업
이슈 브랜치"] M --> W2["2depth 작업
이슈 브랜치"] W1 --> T1["3depth 테스트
필요할 때만"] style U fill:#1a202c,stroke:#f6ad55,stroke-width:2px,color:#e2e8f0 style M fill:#2d3748,stroke:#4299e1,stroke-width:2px,color:#e2e8f0 style W1 fill:#2d3748,stroke:#4299e1,stroke-width:2px,color:#e2e8f0 style W2 fill:#2d3748,stroke:#4299e1,stroke-width:2px,color:#e2e8f0 style T1 fill:#2d3748,stroke:#68d391,stroke-width:2px,color:#e2e8f0
역할 누가 만드나
1 사용자와 말한다. 이슈를 만들고 닫는다. PR을 병합한다
2 이슈 하나를 구현한다 마스터
3 그 구현의 계약을 검증한다 2depth

3depth를 만드는 판단은 2depth가 한다. 자기가 쓴 코드에 테스트가 필요한지는 써 본 쪽만 안다. 마스터가 착수 시점에 미리 정하게 하는 안은 버렸다 — 마스터가 모르는 것을 정하게 만드는 안이고, 개발해보니 필요해졌을 때 왕복이 한 번 더 생긴다.

사전에 막는 장치는 만들지 않는다. 4층이 없는 이유는 규칙으로 막아서가 아니라 4번째 역할이 없어서다. 규칙은 한 줄만 둔다 — 3depth는 task-create를 부르지 않는다.

한때 이걸 구조로 막으려 했다. worker-start --task가 필수인 걸 보고 “task 생성을 마스터가 독점하면 3depth는 붙일 task가 없다”는 안을 세웠다. 버린 이유는 그 안이 마스터에게 모르는 것을 정하게 시키기 때문이다 — 테스트가 필요한지는 구현해 본 뒤에야 알 수 있는데, 마스터는 착수 시점에 그걸 미리 정해야 한다.

대신 검사를 상황판에 얹는다. 여기서 「규칙만 두고 아무도 안 본다」와 갈린다.

orca worktree list --json 의 워크트리마다
  parentWorktreeId   부모 워크트리
  childWorktreeIds   자식들
  linkedIssue        묶인 이슈

층은 세는 값이 아니라 읽는 값이다. 부모 체인을 따라가면 그대로 나온다. 그리고 마스터는 이미 상황판을 그릴 때마다 이 조회를 한다(원칙 8). 그래서 검사 비용이 0이고, 4가 보이면 그 줄이 곧 위반이다.

「어겨도 흔적이 남는다」와 「매번 검사한다」는 다르다. 흔적만 남으면 아무도 안 보는 동안 계속 새고, 이 저장소는 그 모양으로 두 번 샜다 — 적혀만 있고 아무도 검사하지 않은 규칙이었다. 사전 차단은 여전히 못 하지만, 위반이 발견되지 않은 채 지나가지는 않는다.


원칙 3. 사용자는 마스터하고만 말한다

서브가 사용자에게 직접 닿는 경로를 두지 않는다. 서브의 질문은 전부 마스터를 거친다.

이건 편의가 아니라 이 표준의 목적 그 자체다. 사용자가 여러 에이전트에 직접 붙으면, 전체 방향을 쥔 자리가 사라지고 사람이 대화 사이를 오가며 문맥을 매번 재구성한다. 그때 프로젝트를 끌고 가는 것은 사람이 아니라 그때그때 말을 거는 에이전트다.

그래서 마스터가 하는 일은 대신 판단하는 것이 아니라, 사용자가 판단할 수 있게 만드는 것이다. 마스터는 답을 고르지 않는다.

  • 작업을 지시할 때와 완료할 때마다 상황판을 표로 출력한다. 선택이 아니라 의무다.
  • 상황판 칼럼은 이슈 / 제목 / 층 / 에이전트 / 브랜치 / 상태 / 대기 중인 질문이다.
  • 서브가 올린 질문은 온 순서대로 하나씩 사용자에게 올린다.
이슈    제목              층  에이전트   브랜치              상태      대기 질문
#42     주문 취소         2   worker-a   feat/42-cancel      진행중    1
#42     주문 취소         3   worker-a3  test/42-cancel      테스트중  0
#47     쿠폰 만료         2   worker-b   feat/47-coupon      진행중    0

이 표는 사람이 채우는 게 아니라 조회 결과다. 그래서 세 가지가 표를 보는 것만으로 드러난다.

표에 이렇게 보이면 무엇이 잘못됐나
「층」이 4 3depth가 또 워커를 만들었다 (원칙 2)
「이슈」가 비었다 이슈 없이 브랜치를 팠다 (원칙 10)
진행중인데 줄이 없다 워커 시작이 실패했다 (원칙 10)
완료인데 워커가 돌고 있다 사람이 트래커를 직접 닫았다 (원칙 19)
트래커에 있는데 표에 없다 사람이 이슈를 직접 만들었다 (원칙 19)

상황판이 의무인 진짜 이유가 여기 있다. 밀린 질문을 보여주는 것만이 아니라, 이 표가 이 표준의 유일한 검사다.


원칙 4. 마스터는 질문 순서를 재정렬하지 않는다

에이전트가 늘면 질문이 몰린다. 그 순서를 누가 정하는가.

병목은 마스터가 아니라 사용자다. 마스터는 빠르고 사용자는 하나다. 그래서 병목은 없앨 수 없다 — 없애려면 마스터가 대신 판단해야 하는데, 그게 원칙 3을 정면으로 뒤집는다.

안 A — 마스터가 급한 것부터 올린다. 얻는 것은 중요한 결정이 먼저 처리되는 것이다. 버린 이유는 무엇이 급한지를 마스터가 정하는 순간 마스터가 프로젝트를 끄는 것이다. 사용자는 마스터가 골라준 순서대로만 세상을 보게 되고, 뒤에 밀린 것은 존재조차 모른다.

골랐다 — 안 B, 온 순서대로 올리고 밀린 개수를 함께 보여준다. 안 A가 못 지켜주는 것은 사용자가 순서를 바꿀 수 있다는 것이다. 상황판에 「어느 이슈에 질문 몇 개가 밀려 있는지」가 뜨므로, 사용자가 “저것부터”라고 하면 그때 순서가 바뀐다. 재정렬은 사용자만 한다.

마스터가 하는 일은 병목을 없애는 게 아니라 병목의 크기를 보이게 만드는 것이다.


원칙 5. 계약 안이면 알림이고 계약 밖이면 질문이다

무엇을 사용자에게 묻고 무엇을 서브가 스스로 정하는가. 이 경계가 병목의 크기를 그대로 정한다 — 질문은 서브를 멈추고 사용자를 부르지만, 알림은 상황판만 갱신하고 서브는 계속 간다.

안 A — 되돌릴 수 있으면 서브가 정한다. 직관적이다. 버린 이유는 되돌릴 수 있는지를 판단하는 것 자체가 판단이라는 것이다. 서브마다 다르게 재고, 재는 기준이 흔들린다.

골랐다 — 안 B, 이슈의 계약이 경계다.

  서브가 정한다 (알림) 사용자에게 묻는다 (질문)
구현 방식·라이브러리 선택  
2depth끼리 파일이 겹쳐 조정  
계약에 없는 동작을 추가해야 함  
이슈 범위를 넘는 변경  
스키마·외부 계약처럼 되돌리기 어려운 것  

안 A가 못 지켜주는 것은 경계가 이미 글로 적혀 있다는 것이다. 계약은 이슈 본문에 번호 붙어 있으므로 서브가 새로 판단할 것이 없다. 그리고 에이전트 운영 표준의 「기능을 넘는 결정」이 이미 같은 선을 그어놨다 — 기능 안에서만 통하는 결정은 이슈에 두고, 넘어가는 것만 decisions.md에 올린다. 선을 새로 긋지 않고 그 선을 그대로 쓴다.

대가는 분명하다. 계약이 얇으면 질문이 폭증한다. 계약을 촘촘히 쓰는 비용이 기획 시점으로 앞당겨진다.


원칙 6. 질문은 서브가 사실을 채우고 마스터가 표현을 쓴다

질문 문장을 누가 쓰는가. 서브는 구현 문맥에 잠겨 있어 용어가 어렵고, 마스터는 사용자가 아는 말을 안다. 반대로 마스터가 통째로 다시 쓰면 구현을 안 본 쪽이 요약하게 되어 정보가 샌다.

골랐다 — 칸을 나눈다. 서브가 사실을 채우고, 마스터는 표현만 옮긴다. 마스터는 칸을 늘리거나 줄이지 않는다.

채우는 쪽
이슈 번호·제목 / 층 / 에이전트 / 브랜치 서브
무엇을 하다 멈췄나 서브
왜 선택이 필요한가 서브
선택지 각각 + 고르면 무엇이 되는가 서브
위 전부를 사용자 언어로 옮긴 최종 문장 마스터

선택지에는 항상 「직접 입력」과 「더 쉽게 설명하기」가 붙는다. 서브가 세운 선택지가 전부가 아닐 수 있고, 사용자가 이해하지 못한 채 고르면 안 되기 때문이다.

[#42 주문 취소] worker-a · feat/42-cancel · 2depth

결제 취소가 실패했을 때 주문을 어떻게 둘지가 계약에 없다.

1. 주문도 함께 롤백한다       결제와 주문이 항상 같은 상태가 된다
2. 주문은 취소로 두고 재시도   결제 실패가 사용자에게 안 보인다
3. 직접 입력
4. 더 쉽게 설명하기

칸을 고정한 이유는 어떤 작업이 와도 같은 틀로 읽히게 하기 위해서다. 사용자는 매번 “누가 무엇을 하다 왜 멈췄나”를 같은 자리에서 읽는다.


원칙 7. 「더 쉽게」는 1회차만 마스터가 받고 2회차부터 서브에게 내린다

「더 쉽게 설명하기」를 누르는 이유가 둘로 갈린다.

사용자가 막힌 곳 마스터가 풀 수 있나
표현이 어렵다 — 용어가 낯설다 된다. 칸에 사실은 이미 다 있다
사실이 부족하다 — 왜 이 선택이 필요한지 자체가 안 잡힌다 못 한다. 코드를 안 봤으니 지어내게 된다

안 A — 항상 마스터가 다시 쓴다. 왕복이 0이다. 버린 이유는 두 번째 경우에서 마스터가 없는 근거를 그럴듯하게 채우는 것이다.

안 B — 항상 서브에게 내린다. 사실을 아는 쪽이 쓴다. 버린 이유는 어려운 말을 쓴 당사자가 다시 쓰는 것이라 근본 원인이 안 고쳐지고 왕복만 는다는 것이다.

골랐다 — 안 C, 횟수로 가른다.

1회차 「더 쉽게」  → 마스터가 같은 칸으로 다시 쓴다.        왕복 0
2회차 「더 쉽게」  → 서브에게 내린다. 요청은 "쉽게 써라"가 아니라
                    "이해에 필요한 배경 사실을 칸에 더 채워라"
                  → 서브가 칸을 늘려 돌려주면 마스터가 다시 옮긴다

안 A와 안 B가 못 지켜주는 것은 마스터가 「내가 아는가 모르는가」를 스스로 판단하지 않아도 되는 것이다. 그 판단은 흔들린다. 대신 사용자가 한 번 더 눌렀다는 사실이 곧 「표현 문제가 아니었다」는 신호이고, 그건 마스터가 틀릴 수 없는 입력이다.

이 규칙은 원칙 6의 분담을 깨지 않는다. 서브는 2회차에도 사실만 채우고, 표현은 끝까지 마스터가 쓴다.


원칙 8. 마스터는 질문을 기억하지 않고 매번 조회한다

밀린 질문을 어디에 두는가.

안 A — 마스터가 세션 문맥에 들고 있는다. 조회 비용이 0이다. 버린 이유는 마스터가 이 구조에서 가장 오래 사는 세션이자 가장 먼저 압축되는 세션이라는 것이다. 사용자와 계속 말하므로 문맥이 제일 빨리 찬다. 압축되면 대기 질문 다섯 중 셋이 조용히 사라지고, 서브 셋은 영원히 블로킹된 채 남는다. 사용자는 그 사실조차 모른다.

안 B — 마스터가 파일에 적어둔다. 세션이 끊겨도 남는다. 버린 이유는 두 벌이 되는 것이다. orca가 이미 게이트 상태를 들고 있는데 파일에도 적으면, 답한 뒤 파일을 안 지웠을 때 유령 질문이 남는다.

골랐다 — 안 C, 기억하지 않고 매번 조회한다.

orca orchestration gate-list     # 답 안 된 결정 게이트
orca orchestration inbox         # 수신자별 미처리 메시지
orca orchestration task-list     # 작업과 상태
orca worktree ps                 # 어느 워크트리·브랜치에 누가 있나

안 A와 안 B가 못 지켜주는 것은 마스터가 죽어도 상태가 온전한 것이다. 새 마스터를 띄우고 위 넷을 부르면 그 자리에서 복원된다. 「마스터는 하나」가 「마스터는 대체 불가」를 뜻하지 않게 된다.

조회 시점을 따로 정하지 않는다 — 상황판을 그리는 순간이 곧 조회 시점이다. 원칙 3이 이미 상황판 출력을 의무로 만들었으므로 규칙이 하나로 합쳐진다.

대가는 매번 도는 조회 비용, 그리고 상황판을 안 그리면 아무도 밀린 질문을 못 본다는 것이다.

orca가 들고 있는 것과 아닌 것의 경계는 그어둔다. orca에는 무엇을 물었고 무엇을 골랐나까지 남는다. 왜 그렇게 골랐나앞으로도 그렇게 하는가는 안 남고 orchestration reset으로 사라진다. 그래서 decisions.md는 중복이 아니라 수명이 다른 저장소다.


원칙 9. 트래커는 project.yml에 적힌 것만 쓰고 대체하지 않는다

Jira와 GitHub 중 무엇을 쓰는가.

안 A — 에이전트가 매번 감지한다. Jira MCP가 붙어 있으면 Jira, 아니면 GitHub. 설정이 필요 없다. 버린 이유는 감지가 세션마다 흔들리는 것이다.

어제  Jira MCP 연동됨   → 이슈가 Jira 에 생긴다
오늘  Jira MCP 안 붙음  → 같은 프로젝트인데 이슈가 GitHub 에 생긴다

이슈 번호로 브랜치를 따는 규칙이 살아 있어도 번호 공간이 두 개가 되어 추적이 갈린다. 추적하려고 이슈를 만드는 것인데 그 목적이 정확히 거기서 깨진다.

골랐다 — 안 B, .coding-standards/project.yml에 적힌 것만 읽는다. 자리는 에이전트 운영 표준의 「프로젝트 고유의 값」이 이미 만들어뒀다.

적힌 트래커에 못 닿으면 다른 트래커로 갈아타지 않는다. 마스터가 사용자에게 재연동을 요청하고 멈춘다. 폴백은 금지다 — 폴백이 곧 안 A의 갈라짐을 다시 만든다.

「Jira가 있으면 Jira」는 버리지 않는다. 사람이 project.yml을 처음 채울 때의 기본값으로 남는다. 파일이 비어 있으면 마스터가 묻고, 답을 적은 뒤부터는 파일만 읽는다.


원칙 10. 이슈를 진행중으로 바꾼 다음에 워커를 띄운다

브랜치는 이슈 없이 파지 않는다. 이슈를 만들고 그 번호로 브랜치를 판다. 추적이 되려면 브랜치와 이슈가 처음부터 묶여 있어야 한다.

그 다음이 상태를 언제 바꾸는가다. 중복 착수를 막는 것이 목적이므로 시점이 중요하다.

안 A — 워커가 시작한 뒤 스스로 바꾼다. 실제로 일을 시작한 시점과 상태가 일치한다. 버린 이유는 워커는 도는데 이슈는 열린 채인 창이 생기는 것이다. 그 창에서 같은 일의 지시가 한 번 더 들어오면 그대로 통과한다. 막으려던 사고가 정확히 그것이다.

골랐다 — 안 B, 마스터가 바꾸고 나서 워커를 띄운다.

1. 이슈 생성 (마스터)
2. 상태 → 진행중 (마스터)
3. worker-start --task <id> --base-branch <통합 브랜치>

안 B에도 창은 있다. 워커 시작이 실패하면 「진행중인데 아무도 안 하는」 이슈가 남는다. 그래도 안 B를 고른 이유는 그 창만 상황판에서 드러나기 때문이다 — 진행중인데 워커 목록에 없으면 그 줄 자체가 이상 신호다. 안 A의 창은 아무 데도 안 보인다.


원칙 11. 3depth에는 이슈 번호와 브랜치 이름만 넘긴다

worker-start는 새 터미널에 새 에이전트를 띄운다. 문맥을 물려받지 않는다. 그래서 에이전트 운영 표준의 「테스트는 새 세션에서 한다」는 깨지지 않는다 — 세션이 끊기는 걸 orca가 보장한다.

그런데 옆문이 자리를 옮겼다. 기존 표준은 테스트가 이슈 본문의 「버린 안」을 읽어 구현 의도가 새는 걸 막았다. 이제 3depth를 만드는 주체가 사람이 아니라 구현을 방금 끝낸 2depth이므로, dispatch에 실어 보내는 프롬프트가 그대로 새 옆문이 된다.

2depth 가 "이렇게 구현했으니 이 부분을 봐줘" 라고 써서 넘기는 순간
→ 세션을 끊은 값어치가 사라진다. 구현에 맞춘 테스트가 나온다

그래서 넘기는 것은 둘뿐이다 — 이슈 번호와 브랜치 이름. 구현 요약도, 변경 파일 목록도, 주의할 곳도 쓰지 않는다. 3depth는 그 이슈를 트래커에서 직접 읽고, 읽는 절은 기존 표준 그대로 「계약」·「범위 밖」·「정하지 않은 것」 셋이다.

대가는 3depth가 매번 이슈를 처음부터 읽는 비용이다. 그걸 감수한다.


원칙 12. 같은 계약 번호가 두 번 연속 깨지면 마스터에 올린다

마스터는 3depth의 테스트 결과만 받는다. 어떻게 짰는지, 무슨 코드를 썼는지는 안 받는다.

  마스터가 아는가
3depth가 지금 돌고 있다 (이슈·브랜치) 안다. 상황판에 떠야 “지금 테스트 중”이 보인다
테스트를 어떻게 짰나 모른다
통과/실패, 실패면 어느 계약 번호가 깨졌나 결과로 받는다

여기서 위험이 하나 생긴다. 테스트가 깨지면 3depth가 2depth에 돌려주고 2depth가 고쳐 다시 돌린다. 이 왕복은 계약 안이라 원칙 5대로 마스터에 안 올라온다. 그런데 그러면 고치고-깨지고가 반복돼도 상황판에는 「테스트중」만 계속 떠 있다. 병목이 아니라 정지 상태 은폐다.

그래서 같은 계약 번호가 두 번 연속 깨지면 그때 마스터에 올린다.

1회차 실패  →  2depth 가 고친다. 마스터에 안 올린다
              (테스트가 버그를 잡은 것이고, 그게 원래 목적이다)
2회차 실패  →  마스터에 올린다. 고쳤다고 본 것이 안 고쳐졌다는 뜻이고,
              그때부터는 구현이 아니라 계약이나 전제가 어긋났을 가능성이 크다

기존 표준이 옆자리에 같은 선을 그어뒀다 — “계약으로 테스트를 짤 수 없으면 구현을 읽지 말고 확인 필요로 올린다.” 그건 못 짜는 경우고 이건 짰는데 계속 깨지는 경우다. 같은 이유로 같은 곳에 올린다.


원칙 13. 이슈를 닫은 다음에 워크트리와 브랜치를 지운다

완료의 순서를 고정한다. 지우는 것이 먼저 오면 되돌릴 수 없다.

1. 2depth 가 PR 을 연다
2. 마스터에게 "PR 열림"을 알리고 기다린다
3. 마스터가 리뷰하고 병합한다
4. 마스터가 이슈를 닫는다              ← 여기가 먼저
5. 3depth 제거 (테스트가 끝난 시점에)
6. 2depth 의 워크트리와 브랜치 제거    ← 그 다음

2depth는 PR을 스스로 병합하지 않는다. 병합은 마스터의 일이고, 그 앞에 사용자의 최종 리뷰가 선다.

4번이 6번보다 먼저인 이유는 순서가 뒤집혔을 때의 복구 비용 차이다. 이슈를 못 닫은 채 워크트리를 지우면 브랜치 이름도 작업 흔적도 사라진 상태에서 이슈만 열려 있고, 무엇이 끝났는지 되짚을 근거가 없다. 반대 순서는 잘못돼도 워크트리가 남아 있어 확인이 된다.

3depth는 테스트가 끝나면 그 자리에서 제거한다. 2depth보다 오래 살 이유가 없다.


원칙 14. 2depth는 이슈 하나마다 새로 만들고 살려두지 않는다

지우고 나면 다음 이슈는 누가 받는가. 에이전트가 계속 사는가, 이슈마다 죽는가.

안 A — 워커가 살아서 다음 이슈를 받는다. 얻는 것은 setup 비용을 한 번만 내는 것이다. orca도 worker-start 노트에 “Current and existing worktrees never rerun setup” 이라고 적어 이 이점을 명시한다. 이슈가 열 개면 의존성 설치와 빌드 캐시 준비를 아홉 번 아낀다.

버린 이유는 셋이고 마지막이 결정적이다.

1. 앞 이슈의 문맥이 다음 이슈로 넘어온다
   → 원칙 11 이 3depth 에서 막은 것과 같은 종류의 샘이다

2. 워크트리 하나에 브랜치가 여러 개 지나간다
   → 상황판의 「에이전트 = 브랜치」 대응이 깨진다

3. 이슈와 이슈 사이에 어디에 서 있나?
   → 돌아갈 자리를 이 표준이 방금 없앴다

3번은 안 A가 대기 브랜치를 되살리기를 요구한다는 뜻이다. 일감이 없을 때 서 있을 브랜치가 다시 필요해지고, 그러면 폐기한 에이전트 운영 표준 원칙 16이 통째로 부활한다. 비용 하나를 아끼려고 이번 표준의 전제 하나를 물리는 셈이다.

골랐다 — 안 B, 워커는 이슈 하나에 하나씩 만들어지고 완료되면 제거된다. 안 A가 못 지켜주는 것은 워크트리 하나 = 이슈 하나 = 브랜치 하나가 끝까지 유지되는 것이다. 그래야 상황판의 한 줄이 곧 진행 중인 이슈 하나이고, 남아 있는 워크트리가 곧 안 끝난 일이 된다.

대가는 이슈마다 setup을 다시 도는 것이다. 프로젝트가 무거우면 --setup 정책으로 줄이되, 워커를 재사용하는 방식으로는 줄이지 않는다.


원칙 15. 사라진 워커는 브랜치를 남기고, 새 워커가 그것만 이어받는다

워커가 갑자기 없어졌다. 세션이 끊겼거나, 프로세스가 죽었거나, 컴퓨터가 꺼졌다.

브랜치 feat/42-cancel     코드가 절반쯤 짜여 있다
이슈 #42                  「진행중」 이라고 적혀 있다
일하는 워커                없다

탐지는 원칙 3이 이미 한다 — 상황판의 「진행중인데 줄이 없다」가 그 줄이다. 여기서 정하는 것은 탐지 다음이다.

orca가 이 경우를 통째로 갖고 있다.

worker-stop      멈춘다.  "Never deletes the worktree"                  브랜치와 워크트리가 남는다
worker-abandon   죽었는지 확실하지 않은 워커를 펜싱만 한다
worker-release   "output archive is preserved ... worker-read still returns output"
worker-start --retry-of <dispatch_id>   재시도를 앞 시도에 연결한다

이슈는 「진행중」 그대로 둔다. 「할 일」로 되돌리는 안은 버렸다 — 되돌리는 순간 같은 일이 한 번 더 배정될 수 있는 창이 열리고, 그게 원칙 10이 막으려던 사고 그 자체다.

코드는 살린다. 워크트리와 브랜치를 지우지 않으므로 새 워커가 같은 브랜치에 붙는다.

넘기는 것은 이슈 번호와 브랜치 이름 둘뿐이다 — 원칙 11과 같다. 죽은 워커의 출력은 아카이브에 남아 있어 읽을 수 있는데도 새 워커에게 주지 않는다.

격리 때문이 아니다. 3depth와 달리 여기선 이어가는 게 목적이라 격리는 상관없다. 이유는 둘이다.

  • 산출물이 이미 문맥이다. 계약이 [C1]~[C4]인데 코드에 C1·C2가 되어 있으면 C3부터 하면 된다. 앞 워커의 말을 안 들어도 알 수 있는 것을 굳이 듣는 것이다.
  • 앞 워커가 잘못 가다가 죽었을 수 있다. 사라진 이유가 잘못된 방향으로 파고들다 막힌 것이라면, 그 기록을 읽은 새 워커가 같은 길로 다시 가서 같은 자리에서 죽는다.

대신 사람은 읽는다. 마스터가 worker-read로 죽은 이유를 진단해 사용자에게 올린다. 에이전트는 안 읽고 사람은 읽는다에이전트 운영 표준의 「댓글은 사람만 읽는다」와 같은 가름이다.

예외가 하나 있다 — 그 질문에 사용자가 준 답은 새 워커에게 넘긴다.

안 넘긴다   앞 워커가 어떻게 구현했는지, 무슨 생각을 했는지   (원칙 11 · 15)
넘긴다      사용자가 그 질문에 뭐라고 답했는지

답은 구현 문맥이 아니라 결정이다. 계약에 추가될 내용이고, 안 넘기면 새 워커가 같은 질문을 다시 올려 사용자가 같은 답을 두 번 한다. 질문 자체는 워커가 죽어도 gate-list에 남아 다음날 상황판에 그대로 뜬다.


원칙 16. 멈춰 있는 워커는 살려두고, 죽은 워커만 정리한다

사용자가 답하지 않고 자리를 뜬다. 질문을 올린 워커들은 멈춘 채 남는다. PR을 열고 리뷰를 기다리는 워커도 마찬가지다.

처음엔 이걸 「전제와 어긋난다」로 봤다. 맨 앞 전제가 “에이전트는 사람이 있을 때만 돈다” 인데 밤새 다섯이 떠 있으니 어긋난다고 적었다.

그 판단이 틀렸다. 블로킹된 워커는 토큰을 쓰지 않는다. 답을 기다리며 멈춰 있을 뿐이다. 전제는 작업이 사람 없이 진행되지 않는다는 뜻이지 프로세스가 떠 있으면 안 된다는 뜻이 아니었다. 전제를 오해하고 규칙을 하나 만들 뻔했다.

그러면 남는 근거는 하나뿐이다 — 문맥이 낡는다. 그 사이 다른 이슈가 병합되면 브랜치의 출발점이 옛것이 된다.

그런데 그건 시간으로 잴 것이 아니다.

사흘이 지나도 통합 브랜치가 안 움직였으면   낡은 것이 없다
한 시간 만에 셋이 병합됐으면                이미 낡았다

골랐다 — 살려두고, 죽은 것만 정리하고, 낡음은 통합 브랜치가 움직였는지로 잰다.

워커 상태 어떻게
질문에 답을 기다리는 중 살려둔다. 시간과 무관하다
PR 리뷰를 기다리는 중 살려둔다. 원칙 13 그대로다
응답이 없다 (죽었다) 정리하고 원칙 15를 따른다
통합 브랜치가 앞서갔다 죽이지 않는다. 맞추라고 알린다

살려두는 이유는 토큰이다. 죽여서 새로 띄우면 그 워커는 이슈를 읽고 브랜치 코드를 읽고 어디까지 됐는지 다시 파악한다. 리뷰 지적 한 줄을 고치려고 파악 비용을 통째로 다시 낸다. 반대로 살려두면 그 비용이 0이다.

lastActivityAt정리 기준에서 빼고 표시용으로만 쓴다 — 상황판에서 “얼마나 멈춰 있나”를 사람이 보는 값이다.


원칙 17. 죽은 워커를 찾는 일은 마스터가 뜰 때 한다

원칙 16이 정리 대상을 「죽은 워커」로 좁혔다. 그러면 언제 그것을 찾는가가 남는다.

자리를 뜨는 순간에 찾는 안은 버렸다. 그런 순간을 감지할 수 없다. 마스터는 사용자가 말을 걸어야 도는데, 아무 말 없이 창을 닫으면 마스터도 같이 멈춘다. 자기가 죽는 순간을 알 방법이 없다. 일어날 수 없는 일을 규칙으로 적는 셈이다.

그래서 찾는 일을 시작에 붙인다. 마스터는 자기가 언제 끝나는지 모르지만 언제 시작하는지는 안다. 마스터가 뜨면서 상황판을 그릴 때 죽은 워커가 드러난다.


원칙 18. 겹치는 파일은 합칠 때 풀고, 그때까지 아무도 기다리지 않는다

2depth 둘이 같은 파일을 만진다.

worker-a   #42 주문 취소     OrderService.java
worker-b   #47 쿠폰 만료     OrderService.java

먼저 확인할 것 — 둘은 서로를 못 본다. 워크트리가 따로라 상대가 그 파일을 만지는 중인지 알 방법이 없다. 그래서 이건 “동시에 만져서 깨지는” 문제가 아니라, 부딪히는 시점이 합칠 때 딱 한 번인 문제다.

안 A — 먼저 시작한 쪽이 우선. 나중 쪽이 기다린다. 충돌이 아예 안 생긴다. 버린 이유는 기다리는 워커가 노는데 그게 상황판에 안 보이는 것이다. 「진행중」으로만 뜬다. 원칙 12에서 잡은 정지 상태 은폐가 그대로 다시 생긴다.

안 B — 마스터가 순서를 정해 한쪽을 멈춘다. 버린 이유는 마스터가 판단하기 시작하는 것이다. 원칙 4가 막아둔 자리다.

골랐다 — 안 C, 아무도 안 기다린다. 합칠 때 푼다. 먼저 병합된 쪽이 기준이 되고 나중 쪽이 자기 브랜치를 맞춘다.

이게 되는 이유는 이슈를 나눌 때 이미 의존을 없앴기 때문이다. 에이전트 운영 표준이 쪼갤 때 검문을 하나 둔다 — “이 이슈의 테스트를 이 이슈의 계약만으로 짤 수 있는가.” 못 짜면 의존이라 그렇게 안 자른다. 그래서 두 이슈가 서로를 기다릴 일이 애초에 없고, 남는 겹침은 같은 파일을 만진 것뿐이라 git이 합칠 때 잡는다.

그 다음 절차도 기존 표준에 이미 있다 — “충돌은 원래 작업한 에이전트가 아니어도 푼다. 읽는 것은 양쪽 이슈 본문의 「계약」 절뿐이다.”

즉 「알아서 처리」의 실제 내용은 「합칠 때까지 아무것도 안 한다」다. 마스터는 겹침을 상황판에 표시만 하고 순서를 정하지 않는다 — 병합 순서가 곧 우선순위다. 충돌을 계약으로 못 풀 때만 질문이 된다.


원칙 19. 이슈 상태의 원본은 트래커 하나이고, orca task에는 상태를 적지 않는다

「이 일이 어디까지 됐나」가 두 곳에 기록된다.

트래커의 이슈 #42     상태: 진행중
orca 의 task #a1b2    상태: running

둘 다 같은 일을 가리키고, 마스터가 워커를 띄울 때 양쪽을 다 건드린다. 이 저장소는 같은 사실이 두 곳에 사는 것으로 반복해서 당했다 — 「항상 적용」 세 벌이 14/13/12줄로 갈라졌고, 스킬 넷이 이름이 어긋난 채 배포됐다. 매번 해결은 하나였다: 어느 쪽이 원본인지 정한다.

여기서는 한쪽이 특별하다 — 트래커는 사람도 직접 만진다.

아침에 사용자가 트래커를 열어 #42 를 「완료」로 바꾼다   worker-a 는 아직 그 브랜치에서 돌고 있다
사용자가 트래커에 #50 을 새로 만든다                    마스터는 #50 이 있는 줄도 모른다

골랐다 — 트래커가 원본이고, orca task에는 상태를 적지 않는다. task-update로 상태를 관리하지 않고, orca task는 워커를 붙이는 손잡이로만 쓴다(worker-start --task <id>). 상황판의 「상태」 칼럼은 트래커에서만 읽는다.

orca를 원본으로 두는 안은 버렸다. 이유가 셋이다.

  • 나중에 되돌아볼 때 여는 곳이 트래커다. “그 기능 언제 했더라”에 orca를 뒤지지 않는다.
  • 사람이 손댈 수 있는 유일한 곳이 트래커다. 손댈 수 있는 쪽을 원본으로 두지 않으면, 사람이 손댄 순간부터 표준이 거짓말을 시작한다.
  • orca 기록은 사라질 수 있다. orchestration reset 한 번이면 날아가고, 트래커 이슈는 남는다.

사람이 트래커를 직접 만지는 것을 금지하지 않는다. 트래커는 사람 것이고 언제든 브라우저로 열린다. 막을 수 없는 것을 규칙으로 막으면 어긴 사실조차 안 보인다. 대신 상황판이 두 곳을 나란히 놓고 그려서 어긋남이 표에 드러나게 한다 — 원칙 2의 층 검사를 만들 때와 같은 판단이다.

대가는 트래커에 못 닿으면 상태를 아예 알 수 없다는 것이다. 원칙 9가 이미 그 경우를 멈추게 해뒀다.


판단 기준 정리

질문 결론
orca를 안 쓰는 프로젝트인데 적용하지 않는다 흉내 내어 직접 만들지 않는다
사용자는 누구와 말하나 마스터 하나 서브는 마스터를 통해서만 닿는다
마스터는 어디 있나 통합 브랜치. 안 옮긴다 dev, 없으면 main
층은 몇 개인가 역할이 셋이라서다. 4번째 역할이 없다
3depth를 누가 만드나 2depth 테스트 필요 여부는 써 본 쪽만 안다
4층이 생기면 어떻게 아나 상황판의 「층」이 4 세는 게 아니라 부모 체인을 읽은 값이다
다음 이슈는 누가 받나 새 워커 2depth를 살려두지 않는다
이걸 물어야 하나 계약 밖이면 묻는다 계약 안이면 알림만 남기고 계속 간다
질문 문장은 누가 쓰나 사실은 서브, 표현은 마스터 마스터는 칸을 늘리거나 줄이지 않는다
「더 쉽게」를 눌렀다 1회차 마스터, 2회차부터 서브 두 번째 클릭이 곧 「표현 문제가 아니었다」
밀린 질문은 어디 있나 orca 마스터는 기억하지 않고 조회한다
질문 순서를 누가 정하나 사용자 마스터는 재정렬하지 않는다
트래커가 안 붙는다 멈추고 재연동을 요청한다 다른 트래커로 갈아타지 않는다
상태가 두 곳에서 다르다 트래커를 믿는다 orca task 에는 상태를 적지 않는다
사람이 트래커를 직접 만졌다 막지 않는다 상황판이 어긋남을 보여준다
언제 진행중으로 바꾸나 워커를 띄우기 열린 채로 도는 창을 없앤다
테스트가 또 깨졌다 두 번 연속이면 올린다 계약이나 전제가 어긋났다는 신호다
언제 워크트리를 지우나 이슈를 닫은 다음 순서가 뒤집히면 되짚을 근거가 사라진다
워커가 사라졌다 브랜치는 남기고 새 워커가 잇는다 이슈는 진행중 그대로. 되돌리면 중복 착수 창이 열린다
죽은 워커의 기록 사람만 읽는다 그 워커를 죽게 한 판단이 들어 있을 수 있다
밤새 멈춰 있던 워커 살려둔다 블로킹된 워커는 토큰을 쓰지 않는다
통합 브랜치가 앞서갔다 맞추라고 알린다 죽이지 않는다. 낡음은 시간이 아니라 브랜치로 잰다
죽은 워커를 언제 찾나 마스터가 뜰 때 자리를 뜨는 순간은 감지할 수 없다
두 워커가 같은 파일을 만진다 아무도 안 기다린다 병합 순서가 곧 우선순위다

이 표준을 정하기까지

시작은 “멀티 에이전트 정책을 정하자”였다. 그런데 에이전트 운영 표준이 이미 「에이전트 여럿에게 일을 시킬 때」를 원칙 18개로 다루고 있었다. 그래서 첫 번째로 정한 것은 내용이 아니라 경계였다 — 기존 표준의 질문은 “기능 하나에 이슈를 몇 개 만드나”이고 새 질문은 “에이전트를 몇 층으로 세우고 누가 사람과 말하나”다. 축이 다르므로 새 표준으로 갔다. 한 글에 합치면 원칙이 스물다섯을 넘고 「한 글 = 한 질문」이 깨진다.

그 다음에 안을 세우는 대신 orca --help를 먼저 돌렸다. 이 저장소는 안 셋을 정성껏 세워놓고 답이 이미 프로젝트 쪽에 있어서 전부 헛돈 적이 있다. 그래서 실측이 먼저였고, 결과가 표준의 크기를 통째로 바꿨다. ask·gate-create·send·worktree ps·worker-start가 전부 이미 있었다. 설계해야 할 줄 알았던 질문 봉투와 상태 파일과 상황판 서식이 그 자리에서 사라졌다. 이 표준이 정하는 것은 위상과 규율뿐이라는 범위가 여기서 정해졌다.

기존 표준의 세 원칙이 무너지는 걸 하나씩 확인했다. 원칙 11은 “개발 → 테스트는 세션이 끊기므로 사람이 배정한다”였는데, 2depth가 3depth를 만들면 사람이 안 낀다. 원칙 13은 “사람이 테스트 세션을 열며 브랜치를 준다”였는데 같은 이유로 무너진다. 둘은 새 표준이 회수했다.

원칙 16(대기 브랜치)은 뒤집힌 이유가 달랐다. 그건 이점이 있어서 생긴 규칙이 아니라 git 제약 때문에 생긴 우회였다 — 워크트리가 .git을 공유하므로 한 워커가 dev를 잡으면 나머지가 못 간다. 그런데 마스터가 dev를 상주 점유하기로 하고 나니, 서브가 dev로 갈 일 자체가 없어졌다. 작업 브랜치는 --base-branch로 딴다. 전제가 사라져서 규칙이 사라진 것이지, 규칙이 틀렸던 게 아니다. 예전 기록에는 “orca가 별도 클론을 파주면 대기 브랜치를 버릴 수 있다”고 적혀 있었는데, 실제로는 클론이 아니라 base 지정으로 풀렸다.

옆문이 자리를 옮긴 걸 발견했다. worker-start가 새 세션을 띄운다는 걸 확인하고 “그럼 테스트 격리는 그대로 산다”고 넘어가려다, 3depth를 만드는 주체가 사람에서 2depth로 바뀌었다는 걸 뒤늦게 알아챘다. 구현을 방금 끝낸 쪽이 프롬프트를 쓰면 그게 새 옆문이다. 그래서 넘기는 것을 이슈 번호와 브랜치 이름 둘로 못 박았다.

병목 질문에서 방향이 한 번 꺾였다. “에이전트가 늘면 마스터로 올라오는 게 병목 아닌가”라는 물음에 처음엔 큐 관리 얘기를 하려 했는데, 따져보니 병목은 마스터가 아니라 사용자였고 없앨 수가 없었다. 없애려면 마스터가 대신 판단해야 하는데 그게 이 표준의 목적을 뒤집는다. 그래서 규칙이 “병목을 줄인다”가 아니라 “병목의 크기를 보이게 만든다”가 됐다. 마스터가 재정렬하지 않는 원칙 4는 여기서 나왔다.

「더 쉽게 설명하기」에서 판단이 한 번 틀렸다. 처음엔 “표현은 마스터 책임이니 마스터가 다 쓴다”고 못 박았는데, “서브가 더 잘 이해하고 있는데 그래도 마스터가 하나”라는 반문에서 경우가 둘이라는 게 드러났다. 표현이 어려운 경우와 사실이 부족한 경우다. 후자에서 마스터가 계속 쓰면 없는 근거를 지어낸다. 횟수로 가른 것은 마스터가 「내가 아는가」를 자기 판단으로 재지 않게 하려는 것이다 — 두 번째 클릭은 사용자가 주는 입력이라 마스터가 틀릴 수 없다.

depth 상한을 강제하려다 실패했다. worker-start --task가 필수인 걸 보고 “task 생성을 마스터가 독점하면 3depth는 붙일 task가 없어 구조적으로 막힌다”는 안을 세웠다. 그런데 그 안은 마스터가 개발 전에 테스트 필요 여부를 정하게 만든다. 마스터는 그걸 모른다. 안을 버리고 나니 강제 수단이 남지 않았고, 그래서 「구조로는 못 막는다」를 그대로 적었다. 층이 셋인 이유는 규칙이 아니라 역할이 셋이라서다.

그런데 여기서 멈춘 게 틀렸다는 걸 나중에 알았다. 초판은 “어겨도 task-list에 흔적이 남는다”로 닫았는데, 흔적은 아무도 안 보면 없는 것과 같다. 이 저장소는 정확히 그 모양으로 두 번 샜다 — 적혀만 있고 아무도 검사하지 않은 규칙이었다. 다시 열어 worktree list --json을 뜯어보니 parentWorktreeId·childWorktreeIds·linkedIssue가 이미 있었다. 층은 세는 값이 아니라 읽는 값이었고, 마스터는 이미 그 조회를 매번 하고 있었다. 막지는 못하지만 매번 검사한다로 바뀌었고, 검사 비용은 0이다. 겸사겸사 「이슈 없이 판 브랜치」와 「진행중인데 아무도 안 하는 이슈」도 같은 표에서 드러나게 됐다.

2depth의 수명은 대기 브랜치를 폐기하면서 조용히 비었다. 개정 전 완료 절차가 “PR 머지를 기다리지 않고 자기 대기 브랜치로 돌아온다”였는데, 돌아갈 자리를 없애놓고 「그럼 어디로 가나」를 안 적었다. 워커를 살려 setup 비용을 아끼는 안이 있었지만, 그걸 택하면 일감 없을 때 서 있을 브랜치가 다시 필요해져 방금 폐기한 원칙 16이 부활한다. 비용 하나를 아끼려고 전제를 물리는 셈이라 버렸다.

그러고 나서 「워커가 죽으면」이 한 줄도 없다는 걸 알았다. 상황판이 탐지까지는 해주는데 그 다음이 비어 있었다. orca를 다시 보니 worker-stop·worker-abandon·worker-release·--retry-of가 전부 있었다 — 41번과 같은 모양이다. 정할 것은 하나로 좁혀졌다: 죽은 워커의 기록을 새 워커에게 줄 것인가. 아카이브가 보존돼 있어 공짜처럼 보였지만, 앞 워커가 잘못 가다 죽었다면 그 기록이 새 워커를 같은 자리로 데려간다. 산출물이 이미 문맥이라 안 줘도 이어갈 수 있어서 안 주기로 했다.

「자리를 뜰 때 정리한다」는 일어날 수 없는 규칙이었다. 밤새 멈춰 있는 워커를 정리하자고 적어놓고 나서 “자리를 뜨는 걸 어떻게 아나” 라는 물음에 답이 없었다. 마스터는 사용자가 말을 걸어야 도는데, 말없이 창을 닫으면 마스터도 같이 멈춘다. 자기가 죽는 순간을 아는 방법이 없다. 그래서 청소를 종료에서 시작으로 옮겼다 — 마스터는 자기가 언제 끝나는지 모르지만 언제 시작하는지는 안다. 밤새 떠 있는 것 자체는 못 막고, 그건 전제(“사람이 있을 때만 돈다”)의 대가로 받아들였다.

그리고 그 청소 규칙을 곧바로 되물렀다. 처음엔 「밤새 떠 있는 워커는 정리한다」로 적었고, 근거가 「멈춰 있는 워커는 안 도는 것과 다르다 — 프로세스가 떠 있고 문맥을 물고 자리를 차지한다」였다. 그게 틀렸다. 블로킹된 워커는 토큰을 안 쓴다. 전제(「사람이 있을 때만 돈다」)는 작업이 사람 없이 진행되지 않는다는 뜻이지 프로세스가 떠 있으면 안 된다는 뜻이 아니었다. 전제를 오해하고 규칙을 하나 만들 뻔했다. 남은 근거는 「문맥이 낡는다」 하나였는데 그건 시간이 아니라 통합 브랜치가 움직였는지로 재야 한다 — 사흘이 지나도 브랜치가 그대로면 낡은 게 없고, 한 시간에 셋이 병합됐으면 이미 낡았다. 결정적인 것은 토큰이었다: 죽여서 새로 띄우면 리뷰 지적 한 줄을 고치려고 이슈와 브랜치를 다시 파악하는 비용을 통째로 낸다. 그래서 정리 대상을 「죽은 워커」로만 좁혔고, 원칙 16에서 살아남은 것은 탐지 시점 하나가 됐다.

「알아서 처리」도 사실은 아무것도 안 적힌 자리였다. 2depth 둘이 같은 파일을 만질 때 누가 양보하는지가 비어 있었다. 기다리게 하는 안은 정지 상태 은폐를 다시 만들고, 마스터가 순서를 정하는 안은 원칙 4를 뒤집는다. 따져보니 이슈를 쪼갤 때 이미 의존을 없앴기 때문에 서로 기다릴 일이 애초에 없었다 — 남는 겹침은 같은 파일뿐이고 그건 git이 잡는다. 「알아서 처리」의 실제 내용은 「합칠 때까지 아무것도 안 한다」였다.

상태가 두 곳에 산다는 것도 뒤늦게 걸렸다. 트래커의 이슈 상태와 orca task 상태가 같은 사실을 따로 기록하고 있었다. 이 저장소가 「항상 적용」 세 벌과 스킬 넷에서 이미 당한 모양이라 해법은 알고 있었다 — 원본을 하나로 정한다. 고른 쪽이 트래커인 결정적 이유는 거기만 사람이 직접 만진다는 것이었다. 사람이 손댈 수 있는 쪽을 원본으로 두지 않으면 손댄 순간부터 표준이 거짓말을 한다. 그리고 직접 만지는 것을 금지하지 않기로 한 것도 같은 이유다 — 막을 수 없는 것을 막으면 어긴 사실조차 안 보인다. 층 상한에서 내린 판단을 그대로 다시 썼다.

마지막으로 이 표준이 orca 하나에 묶여 있다는 걸 인정했다. 다른 표준은 도구를 갈아도 문장이 남는데, 여기는 명령 이름이 규칙 안에 박혀 있다. 도구 중립 문장으로 바꾸는 안은 써본 적 없는 것을 그럴듯하게 적는 것이라 버렸고, 그냥 두는 안은 읽는 쪽이 되는지 안 되는지를 모른다는 이유로 버렸다. 그래서 전제를 헤딩에 박았다 — 「통신과 게이트는 이미 orca에 있다」는 있어서 편하다로 읽히지만, 「orca 없이는 성립하지 않는다」는 그렇게 읽힐 수가 없다.

그 과정에서 「작업 생성」이 두 가지를 가리키고 있었다는 것도 드러났다. 트래커의 이슈와 orca의 task다. 이슈는 마스터만 만들고, orca task는 2depth도 만든다. 이걸 안 갈랐으면 “모든 작업 생성은 마스터”와 “2depth가 3depth를 만든다”가 서로를 부정한 채 글에 남았을 것이다.

마지막으로 정지 상태 은폐를 잡았다. “마스터는 테스트 결과만 안다”를 정하고 나서, 테스트 실패 왕복이 계약 안이라 마스터에 안 올라온다는 걸 알아챘다. 그러면 상황판에 「테스트중」만 영원히 떠 있을 수 있다. 두 번 연속 실패를 신호로 삼은 것은, 1회 실패는 테스트가 제 일을 한 것이고 2회차부터가 이해가 어긋났다는 뜻이기 때문이다.


정리

  • orca를 안 쓰면 이 표준은 성립하지 않는다. 흉내 내어 직접 만들지 않는다.
  • 사용자와 말하는 에이전트는 하나다. 그래야 사람이 에이전트를 끌고 간다.
  • 층이 셋인 이유는 역할이 셋이기 때문이다. 사전에 막지는 못하지만, 층은 부모 체인을 읽은 값이라 상황판이 매번 검사한다.
  • 상황판은 이 표준의 유일한 검사다. 층이 4이거나, 이슈가 비었거나, 진행중인데 줄이 없으면 전부 그 표에서 드러난다.
  • 2depth는 이슈 하나마다 새로 만들고 살려두지 않는다. 워커를 재사용하면 돌아갈 자리가 다시 필요해진다.
  • 마스터는 통합 브랜치에서 내려오지 않는다. 점유자가 고정되면 서브의 브랜치 충돌이 사라진다.
  • 마스터는 아무것도 기억하지 않는다. 매번 orca에 묻는다. 그래서 죽어도 대체된다.
  • 계약이 질문과 알림을 가른다. 계약이 얇으면 질문이 폭증하므로, 비용이 기획 시점으로 앞당겨진다.
  • 마스터는 순서를 재정렬하지 않는다. 병목을 없애는 게 아니라 보이게 만드는 것이 마스터의 일이다.
  • 질문은 서브가 사실을, 마스터가 표현을 쓴다. 「직접 입력」과 「더 쉽게 설명하기」는 항상 붙는다.
  • 트래커는 대체하지 않는다. 못 닿으면 멈추고 재연동을 요청한다.
  • 이슈 상태의 원본은 트래커 하나다. 사람이 직접 만지는 것을 막지 않고, 어긋남이 상황판에 드러나게 한다.
  • 진행중으로 바꾼 다음에 워커를 띄우고, 이슈를 닫은 다음에 워크트리를 지운다.
  • 워커가 사라져도 브랜치는 남는다. 새 워커가 이슈 번호와 브랜치 이름만 받아 잇고, 죽은 워커의 기록은 사람만 읽는다.
  • 멈춰 있는 워커는 살려두고 죽은 워커만 정리한다. 블로킹된 워커는 토큰을 안 쓰고, 죽여서 다시 파악하게 하는 비용이 훨씬 크다.
  • 죽은 워커를 찾는 일은 마스터가 뜰 때 한다. 자리를 뜨는 순간은 감지할 수 없다.
  • 겹치는 파일 때문에 아무도 기다리지 않는다. 병합 순서가 곧 우선순위다.

AI 코드 어시스턴트에 바로 적용하기

Claude Code · Codex — .claude/skills/multi-agent-topology-standard/SKILL.md · .agents/skills/multi-agent-topology-standard/SKILL.md

두 파일은 바이트까지 같아야 한다. .agents 쪽은 tools/agents-skills.rb가 뽑는 생성물이다.

---
name: multi-agent-topology-standard
description: 에이전트를 여러 층으로 세워 일할 때의 위상과 규율. 마스터가 하나인 것과 그 자리, 층이 셋인 것과 각 층의 역할, 사용자와의 유일한 접점, 질문과 알림의 경계, 질문 서식과 「더 쉽게 설명하기」, 밀린 질문의 보관, 트래커 선택, 착수와 완료의 순서를 정한다. 에이전트를 띄울 때, 사용자에게 물을지 정할 때, 서브를 만들 때, 작업을 끝낼 때 적용한다. orca CLI 위에서 돈다.
---

# 멀티 에이전트 위상 표준

**이 표준은 orca CLI 위에서만 성립한다.** 통신·결정 게이트·워크트리 조회를 전부 orca 원어로 하므로, **orca를 쓰지 않는 프로젝트에는 적용하지 않는다.** 흉내 내어 직접 만들지 않는다.

사용자와 말하는 에이전트는 하나다. 사람이 에이전트를 끌고 간다.

## 위상

```
사용자 ─ 1depth 마스터 ─┬─ 2depth 작업 ─ 3depth 테스트
                        └─ 2depth 작업
```

| 층 | 역할 | 누가 만드나 |
|---|---|---|
| 1 마스터 | 사용자와 말한다. 이슈를 만들고 닫는다. PR을 병합한다 | — |
| 2 작업 | 이슈 하나를 구현한다 | 마스터 |
| 3 테스트 | 그 구현의 계약을 검증한다 | 2depth |

- **마스터는 프로젝트마다 하나다.**
- **3depth를 만들지는 2depth가 판단한다.** 자기가 쓴 코드에 테스트가 필요한지는 써 본 쪽이 정한다.
- **3depth는 `task-create`를 부르지 않는다.** 4층을 만들지 않는다.
- **2depth는 이슈 하나에 하나씩 만들어지고, 완료되면 제거된다.** 살려두고 다음 이슈를 받게 하지 않는다 — 앞 이슈의 문맥이 넘어오고, 워크트리 하나에 브랜치가 여러 개 지나가 상황판의 「에이전트 = 브랜치」 대응이 깨진다.
- **층은 세지 않고 읽는다.** `orca worktree list --json``parentWorktreeId` 체인이 그 값이다. **4가 나오면 그 자리에서 멈추고 사용자에게 올린다.**

## 마스터의 자리

- **`dev`가 있으면 `dev`, 없으면 `main`에 상주한다.**
- **그 브랜치에서 절대 내려오지 않는다.** 다른 브랜치로 옮기지 않는다.
- **마스터는 코드를 고치지 않는다.** 읽고, 지시하고, 병합한다.
- 서브는 통합 브랜치를 체크아웃하지 않는다. 작업 브랜치는 `--base-branch <통합 브랜치>`로 딴다.

## 사용자 접점

- **사용자는 마스터하고만 말한다.** 서브가 사용자에게 직접 닿지 않는다.
- **마스터는 답을 고르지 않는다.** 사용자가 고를 수 있게 만든다.
- **작업을 지시할 때와 완료할 때마다 상황판을 표로 출력한다.** 선택이 아니라 의무다.

```
이슈    제목              층  에이전트   브랜치              상태      대기 질문
#42     주문 취소         2   worker-a   feat/42-cancel      진행중    1
#42     주문 취소         3   worker-a3  test/42-cancel      테스트중  0
#47     쿠폰 만료         2   worker-b   feat/47-coupon      진행중    0
```

- 칸은 `이슈 / 제목 / 층 / 에이전트 / 브랜치 / 상태 / 대기 중인 질문`이다.
- **「상태」는 트래커에서, 「층·에이전트·브랜치」는 orca에서 읽어 나란히 놓는다.** 어긋남이 표에 드러난다.
- **「층」은 `parentWorktreeId` 체인을 읽은 값이다.** 4가 보이면 규칙 위반이므로 사용자에게 올린다.
- **「이슈」가 비어 있는 워크트리(`linkedIssue: null`)도 위반이다.** 이슈 없이 판 브랜치다.
- **진행중인데 워커 목록에 없다** — 워커가 죽었거나 시작에 실패했다.
- **완료인데 워커가 돌고 있다** — 사람이 트래커를 직접 닫았다. 사용자에게 확인한다.
- **트래커에 있는데 표에 없다** — 사람이 이슈를 직접 만들었다. 사용자에게 알린다.
- **질문은 온 순서대로 하나씩 올린다.** 마스터가 재정렬하지 않는다.
- 밀린 질문의 개수와 소속 이슈를 상황판에 함께 띄운다. **순서를 바꾸는 것은 사용자만 한다.**

## 무엇을 묻고 무엇을 정하나

| | 서브가 정한다 (알림) | 사용자에게 묻는다 (질문) |
|---|---|---|
| 구현 방식·라이브러리 선택 | ● | |
| 2depth끼리 파일이 겹쳐 조정 | ● | |
| 계약에 없는 동작을 추가해야 함 | | ● |
| 이슈 범위를 넘는 변경 | | ● |
| 스키마·외부 계약처럼 되돌리기 어려운 것 | | ● |

- **경계는 이슈의 계약이다.** 계약 안에서 답이 나오면 정하고, 계약 밖으로 나가야 하면 묻는다.
- **알림은 서브를 멈추지 않는다.** 상황판만 갱신하고 계속 간다.
- **질문은 서브를 멈춘다.** `orchestration ask` 또는 `gate-create`로 올리고 기다린다.
- 2depth끼리 영향을 주면 **상황만 알리고 스스로 처리한다.** 그 과정에서 계약 밖으로 나가면 그때 묻는다.

## 질문 서식

**서브가 사실을 채우고, 마스터가 표현을 쓴다. 마스터는 칸을 늘리거나 줄이지 않는다.**

| 칸 | 채우는 쪽 |
|---|---|
| 이슈 번호·제목 / 층 / 에이전트 / 브랜치 | 서브 |
| 무엇을 하다 멈췄나 | 서브 |
| 왜 선택이 필요한가 | 서브 |
| 선택지 각각 + 고르면 무엇이 되는가 | 서브 |
| 사용자 언어로 옮긴 최종 문장 | 마스터 |

```
[#42 주문 취소] worker-a · feat/42-cancel · 2depth

결제 취소가 실패했을 때 주문을 어떻게 둘지가 계약에 없다.

1. 주문도 함께 롤백한다       결제와 주문이 항상 같은 상태가 된다
2. 주문은 취소로 두고 재시도   결제 실패가 사용자에게 안 보인다
3. 직접 입력
4. 더 쉽게 설명하기
```

- **선택지에 「직접 입력」과 「더 쉽게 설명하기」를 항상 넣는다.**
- **답은 하위 에이전트에게 그대로 전달한다.** 마스터가 해석해서 바꾸지 않는다.
- 사용자의 답이 기능을 넘는 결정이면 `.coding-standards/decisions.md`에 남긴다.

## 「더 쉽게 설명하기」

```
1회차  → 마스터가 같은 칸으로 다시 쓴다. 서브를 깨우지 않는다
2회차~ → 서브에게 내린다. "쉽게 써라"가 아니라
         "이해에 필요한 배경 사실을 칸에 더 채워라"
         서브가 칸을 늘려 돌려주면 마스터가 다시 옮긴다
```

- **서브는 2회차에도 사실만 채운다.** 표현은 끝까지 마스터가 쓴다.

## 밀린 질문

**마스터는 질문을 기억하지 않는다. 세션 문맥에도 파일에도 두지 않는다.**

```bash
orca orchestration gate-list     # 답 안 된 결정 게이트
orca orchestration inbox         # 수신자별 미처리 메시지
orca orchestration task-list     # 작업과 상태
orca worktree ps                 # 어느 워크트리·브랜치에 누가 있나
```

- **상황판을 그릴 때마다 위를 조회해 새로 만든다.**
- 마스터가 끊기면 새로 띄우고 같은 넷을 조회해 복원한다.

## 트래커

- **이슈 상태의 원본은 트래커 하나다.** orca task에는 상태를 적지 않는다 — `task-update`로 상태를 관리하지 않고, task는 `worker-start --task <id>`의 손잡이로만 쓴다.
- **사람이 트래커를 직접 만지는 것을 막지 않는다.** 막을 수 없는 것을 규칙으로 막으면 어긴 사실조차 안 보인다. 대신 상황판이 두 곳을 대조해 보여준다.
- **`.coding-standards/project.yml`에 적힌 것만 쓴다.** 매번 감지하지 않는다.
- **못 닿으면 다른 트래커로 갈아타지 않는다.** 사용자에게 재연동을 요청하고 멈춘다.
- 파일이 비어 있으면 사용자에게 묻고, 답을 적은 뒤부터는 파일만 읽는다. 기본값은 Jira가 있으면 Jira, 없으면 GitHub 이슈다.

## 착수

1. **이슈를 만든다.** 마스터만 만든다.
2. **상태를 진행중으로 바꾼다.** 워커를 띄우기 전에 바꾼다.
3. `orca orchestration worker-start --task <id> --base-branch <통합 브랜치>`

- **이슈 없이 브랜치를 파지 않는다.** 브랜치 이름에 이슈 번호가 들어간다.
- 진행중인데 워커 목록에 없는 이슈는 이상 신호다. 상황판에서 확인한다.

## 3depth에 넘기는 것

**이슈 번호와 브랜치 이름 둘뿐이다.**

- **구현 요약·변경 파일 목록·주의할 곳을 쓰지 않는다.** 구현 의도가 새면 구현에 맞춘 테스트가 나온다.
- 3depth는 이슈를 트래커에서 직접 읽는다. 읽는 절은 「계약」·「범위 밖」·「정하지 않은 것」이다.
- 마스터는 테스트 **결과만** 받는다. 어떻게 짰는지는 받지 않는다.
- **같은 계약 번호가 두 번 연속 깨지면 마스터에 올린다.** 1회차 실패는 2depth가 고치고 올리지 않는다.

## 완료

```
1. 2depth 가 PR 을 연다
2. 마스터에게 "PR 열림"을 알리고 기다린다
3. 마스터가 리뷰하고 병합한다
4. 마스터가 이슈를 닫는다
5. 3depth 제거 (테스트가 끝난 시점에)
6. 2depth 의 워크트리와 브랜치 제거
```

- **2depth는 PR을 스스로 병합하지 않는다.**
- **이슈를 닫기 전에 워크트리와 브랜치를 지우지 않는다.** 순서를 바꾸지 않는다.
- 3depth는 테스트가 끝나면 그 자리에서 제거한다.
- **제거된 2depth를 되살리지 않는다.** 다음 이슈는 새 워커를 띄운다. setup 비용이 문제면 `--setup`으로 줄이되 워커를 재사용하지 않는다.

## 워커가 사라졌을 때

상황판의 **「진행중인데 줄이 없다」**가 그 신호다.

1. `worker-stop` 또는 `worker-abandon`으로 펜싱한다. **워크트리와 브랜치는 그대로 둔다.**
2. **이슈는 「진행중」 그대로 둔다.** 「할 일」로 되돌리지 않는다 — 되돌리면 같은 일이 한 번 더 배정될 창이 열린다.
3. 같은 이슈·같은 브랜치에 `worker-start --retry-of <dispatch_id>`로 새 워커를 붙인다.
4. **넘기는 것은 이슈 번호와 브랜치 이름 둘뿐이다.** 죽은 워커의 출력을 새 워커에게 주지 않는다.

- **산출물이 이미 문맥이다.** 계약 번호와 브랜치의 커밋을 보면 어디까지 됐는지 나온다.
- **앞 워커가 잘못 가다 죽었으면 그 기록이 새 워커를 같은 자리로 데려간다.**
- **사람은 읽는다.** 마스터가 `worker-read`로 죽은 이유를 진단해 사용자에게 올린다.

## 멈춰 있는 워커

**살려둔다. 죽은 워커만 정리한다.** 블로킹된 워커는 토큰을 쓰지 않는다.

| 워커 상태 | 어떻게 |
|---|---|
| 질문에 답을 기다리는 중 | **살려둔다.** 시간과 무관하다 |
| PR 리뷰를 기다리는 중 | **살려둔다.** |
| 응답이 없다 (죽었다) | 정리하고 「워커가 사라졌을 때」를 따른다 |
| 통합 브랜치가 앞서갔다 | **죽이지 않는다.** 맞추라고 알린다 |

- **죽여서 새로 띄우면 이슈와 브랜치를 다시 파악하는 비용을 통째로 낸다.** 리뷰 지적 한 줄에 그 비용을 내지 않는다.
- `lastActivityAt`**정리 기준이 아니라 표시용**이다. 상황판에서 얼마나 멈춰 있는지 사람이 본다.
- **죽은 워커를 찾는 일은 마스터가 뜰 때 한다.** 사용자가 자리를 뜨는 순간은 감지할 수 없다 — 마스터도 그때 같이 멈춘다.
- 되살릴 때 **그 질문에 사용자가 준 답은 새 워커에게 넘긴다** — 답은 구현 문맥이 아니라 결정이고, 안 넘기면 사용자가 같은 답을 두 번 한다.
- 질문 자체는 워커가 죽어도 `gate-list`에 남는다.

## 겹치는 파일

**아무도 기다리지 않는다.** 워크트리가 따로라 서로를 못 보고, 부딪히는 시점은 합칠 때 한 번뿐이다.

- 둘 다 자기 브랜치에서 끝까지 간다. **먼저 병합된 쪽이 기준이고 나중 쪽이 맞춘다.**
- **마스터는 겹침을 상황판에 표시만 하고 순서를 정하지 않는다.** 병합 순서가 곧 우선순위다.
- 충돌은 원래 작업한 에이전트가 아니어도 푼다. **읽는 것은 양쪽 이슈 본문의 「계약」 절뿐이다.**
- 계약으로 못 풀 때만 질문이 된다.

## 하지 않는 것

- **서브가 사용자에게 직접 말하지 않는다.**
- **마스터가 대신 판단하지 않는다.** 질문 순서도 재정렬하지 않는다.
- **마스터가 통합 브랜치에서 내려오지 않는다.** 거기서 코드를 고치지도 않는다.
- **마스터가 밀린 질문을 기억하지 않는다.** 파일로도 남기지 않는다.
- **상황판을 건너뛰지 않는다.** 안 그리면 밀린 질문이 아무 데도 안 보인다.
- **선택지에서 「직접 입력」과 「더 쉽게 설명하기」를 빼지 않는다.**
- **트래커를 대체하지 않는다.**
- **orca task에 이슈 상태를 적지 않는다.** 상태가 두 곳에 살면 어긋난다.
- **이슈 없이 브랜치를 파지 않는다.** 진행중으로 바꾸기 전에 워커를 띄우지 않는다.
- **3depth에 구현 내용을 넘기지 않는다.**
- **3depth가 `task-create`를 부르지 않는다.**
- **2depth를 살려두고 다음 이슈를 받게 하지 않는다.**
- **사라진 워커의 이슈를 「할 일」로 되돌리지 않는다.** 브랜치도 지우지 않는다.
- **죽은 워커의 출력을 새 워커에게 넘기지 않는다.**
- **겹치는 파일 때문에 한쪽을 기다리게 하지 않는다.**
- **답이나 리뷰를 기다리는 워커를 시간이 지났다고 정리하지 않는다.**
- **이슈를 닫기 전에 워크트리를 지우지 않는다.**
- **orca를 안 쓰는 프로젝트에 이 표준을 적용하지 않는다.** 통신·게이트·상황판을 직접 만들어 흉내 내지 않는다.

GitHub Copilot — .github/instructions/multi-agent-topology-standard.instructions.md

---
description: 에이전트를 여러 층으로 세워 일할 때의 위상과 규율
applyTo: "**"
---

# 멀티 에이전트 위상

- **이 표준은 orca CLI 위에서만 성립한다.** 통신·결정 게이트·워크트리 조회를 전부 orca 원어로 한다. **orca를 쓰지 않는 프로젝트에는 적용하지 않고, 흉내 내어 직접 만들지도 않는다.**
- **사용자와 말하는 에이전트는 하나다.** 서브는 마스터를 통해서만 사용자에게 닿는다. 사람이 에이전트를 끌고 간다.
- 층은 셋이다 — **1 마스터**(사용자와 말하고 이슈를 만들고 닫고 PR을 병합), **2 작업**(이슈 하나를 구현), **3 테스트**(그 구현의 계약을 검증). **4번째 역할이 없으므로 4층을 만들지 않는다.**
- **마스터는 프로젝트마다 하나다.** `dev`가 있으면 `dev`, 없으면 `main`에 상주하고 **거기서 절대 내려오지 않는다.** 마스터는 코드를 고치지 않고 읽고·지시하고·병합만 한다.
- **서브는 통합 브랜치를 체크아웃하지 않는다.** 작업 브랜치는 `orca orchestration worker-start --base-branch <통합 브랜치>`로 딴다.
- **2depth를 만드는 것은 마스터, 3depth를 만드는 것은 2depth다.** 자기가 쓴 코드에 테스트가 필요한지는 써 본 쪽이 판단한다. **3depth는 `task-create`를 부르지 않는다.**
- **2depth는 이슈 하나에 하나씩 만들어지고 완료되면 제거된다.** 살려두고 다음 이슈를 받게 하지 않는다 — 앞 이슈의 문맥이 넘어오고 워크트리 하나에 브랜치가 여러 개 지나가 상황판의 「에이전트 = 브랜치」 대응이 깨진다. 다음 이슈는 새 워커를 띄운다.
- **층은 세지 않고 읽는다.** `orca worktree list --json``parentWorktreeId` 체인이 그 값이다. **상황판에 4가 보이면 그 자리에서 멈추고 사용자에게 올린다.** `linkedIssue` 가 비어 있는 워크트리도 위반이다 — 이슈 없이 판 브랜치다.
- **작업을 지시할 때와 완료할 때마다 상황판을 표로 출력한다.** 칸은 `이슈 / 제목 / 층 / 에이전트 / 브랜치 / 상태 / 대기 중인 질문`이다. 선택이 아니라 의무다.
- **질문은 온 순서대로 하나씩 올린다.** 마스터가 재정렬하지 않고 밀린 개수만 상황판에 띄운다. **순서를 바꾸는 것은 사용자만 한다.** 마스터는 답을 고르지 않는다.
- **경계는 이슈의 계약이다.** 계약 안에서 답이 나오면 서브가 정하고 알림만 남긴다(서브는 계속 간다). 계약 밖으로 나가야 하면 묻는다(서브가 멈춘다). 구현 방식·라이브러리 선택·2depth끼리의 파일 충돌 조정은 알림, 계약에 없는 동작 추가·범위를 넘는 변경·스키마 같은 되돌리기 어려운 것은 질문이다.
- **질문은 서브가 사실을 채우고 마스터가 표현을 쓴다.** 서브가 채우는 칸은 이슈·층·에이전트·브랜치, 무엇을 하다 멈췄나, 왜 선택이 필요한가, 선택지와 각각의 결과다. **마스터는 사용자 언어로 옮기기만 하고 칸을 늘리거나 줄이지 않는다.**
- **선택지에 「직접 입력」과 「더 쉽게 설명하기」를 항상 넣는다.** 사용자의 답은 하위 에이전트에게 그대로 전달한다.
- **「더 쉽게 설명하기」는 1회차만 마스터가 받는다.** 2회차부터는 서브에게 내리되 요청은 "쉽게 써라"가 아니라 **"이해에 필요한 배경 사실을 칸에 더 채워라"**다. 서브는 2회차에도 사실만 채우고 표현은 끝까지 마스터가 쓴다.
- **마스터는 밀린 질문을 기억하지 않는다.** 세션 문맥에도 파일에도 두지 않고 `orca orchestration gate-list` · `inbox` · `task-list` · `orca worktree ps`**상황판을 그릴 때마다 조회해 새로 만든다.** 마스터가 끊기면 새로 띄우고 같은 넷으로 복원한다.
- **이슈 상태의 원본은 트래커 하나다.** orca task 에는 상태를 적지 않는다 — `task-update` 로 상태를 관리하지 않고 task 는 `worker-start --task <id>` 의 손잡이로만 쓴다. **사람이 트래커를 직접 만지는 것은 막지 않는다** — 막을 수 없는 것을 규칙으로 막으면 어긴 사실조차 안 보인다. 대신 상황판이 「상태」는 트래커에서 「층·에이전트·브랜치」는 orca 에서 읽어 나란히 놓아, **완료인데 워커가 돌고 있거나 트래커에 있는데 표에 없는** 어긋남이 드러나게 한다.
- **트래커는 `.coding-standards/project.yml`에 적힌 것만 쓴다.** 매번 감지하지 않는다. **못 닿으면 다른 트래커로 갈아타지 않고** 사용자에게 재연동을 요청하고 멈춘다. 파일이 비어 있으면 묻고 답을 적는다(기본값은 Jira가 있으면 Jira, 없으면 GitHub 이슈).
- **이슈 없이 브랜치를 파지 않는다.** 브랜치 이름에 이슈 번호가 들어간다. 이슈는 마스터만 만든다.
- **이슈 상태를 진행중으로 바꾼 다음에 워커를 띄운다.** 순서를 바꾸지 않는다 — 워커는 도는데 이슈가 열린 채면 같은 일이 한 번 더 배정된다.
- **3depth에 넘기는 것은 이슈 번호와 브랜치 이름 둘뿐이다.** 구현 요약·변경 파일 목록·주의할 곳을 쓰지 않는다. 3depth는 이슈의 「계약」·「범위 밖」·「정하지 않은 것」만 읽는다.
- **마스터는 테스트 결과만 받는다.** 어떻게 짰는지는 받지 않는다. **같은 계약 번호가 두 번 연속 깨지면 마스터에 올린다** — 1회차 실패는 2depth가 고치고 올리지 않는다.
- **워커가 사라지면 브랜치와 워크트리를 그대로 두고 새 워커가 잇는다.** 신호는 상황판의 「진행중인데 줄이 없다」다. `worker-stop`/`worker-abandon` 으로 펜싱하고 `worker-start --retry-of` 로 같은 브랜치에 붙인다. **이슈는 「진행중」 그대로 둔다** — 되돌리면 같은 일이 한 번 더 배정될 창이 열린다. **넘기는 것은 이슈 번호와 브랜치 이름 둘뿐**이고, 죽은 워커의 출력은 새 워커에게 주지 않는다(앞 워커를 죽게 한 판단이 들어 있을 수 있다). **사람은 읽는다** — 마스터가 `worker-read` 로 진단해 사용자에게 올린다.
- **멈춰 있는 워커는 살려두고 죽은 워커만 정리한다.** 블로킹된 워커는 토큰을 쓰지 않는다. 질문에 답을 기다리는 중이거나 PR 리뷰를 기다리는 중이면 **시간과 무관하게 살려둔다** — 죽여서 새로 띄우면 이슈와 브랜치를 다시 파악하는 비용을 통째로 낸다. 통합 브랜치가 앞서갔으면 죽이지 말고 **맞추라고 알린다.** `lastActivityAt` 은 정리 기준이 아니라 표시용이다. **죽은 워커를 찾는 일은 마스터가 뜰 때 한다** — 사용자가 자리를 뜨는 순간은 감지할 수 없다. 되살릴 때 **그 질문에 사용자가 준 답은 새 워커에게 넘긴다** — 답은 구현 문맥이 아니라 결정이라, 안 넘기면 사용자가 같은 답을 두 번 한다.
- **겹치는 파일 때문에 아무도 기다리지 않는다.** 워크트리가 따로라 서로를 못 보고 부딪히는 시점은 합칠 때 한 번뿐이다. 둘 다 끝까지 가고 **먼저 병합된 쪽이 기준, 나중 쪽이 맞춘다.** 마스터는 겹침을 표시만 하고 순서를 정하지 않는다 — 병합 순서가 곧 우선순위다. 충돌은 양쪽 이슈의 「계약」 절만 읽고 풀며, 계약으로 못 풀 때만 질문이 된다.
- 완료 순서는 `2depth PR → 마스터에 알림 → 마스터 리뷰·병합 → 이슈 닫기 → 3depth 제거 → 2depth 워크트리·브랜치 제거`다. **2depth는 PR을 스스로 병합하지 않고, 이슈를 닫기 전에 워크트리를 지우지 않는다.**

자신만의 철학을 만들어가는 중입니다.
최상단으로 이동했습니다!
확대 이미지

댓글남기기