--- name: loop-setup description: plan_setup/(PLAN.md·phase·task·STATE.md·LOG.md) 과 harness_setup/(HARNESS.md·quality_gates/) 을 읽고 이 프로젝트의 개발 루프를 loop_setup/LOOP.md 한 문서로 고정한다. 다음 태스크를 어떻게 고르고, 태스크의 verification 값으로 어느 게이트를 어떤 순서로 돌리며, 실패를 진짜 결함·낡은 스펙·환경 문제로 어떻게 분류하고, 재수정 사이클을 최대 3회로 세다가 언제 멈춰 사용자에게 보고하며, task_validation 을 구현 세션과 어떻게 분리하고, STATE.md·LOG.md 를 언제 갱신하고 언제 커밋하며, 차단이 풀린 태스크를 어떻게 여는지, 태스크마다 새 세션을 누가 어떻게 여는지(스크립트 구동·오르카·서브에이전트), 병렬 운용(기본)을 위해 태스크 단위 DAG 를 머메이드로 그리고 자원 충돌을 사전 검사하며 코디네이터·슬롯 워크트리를 어떻게 운용하는지, 오르카 구동이 진행할 수 있는데 멈춘 상태(잠든 워커·죽은 스크립트·사람 프롬프트)를 어떻게 감시하는지를 정한다. 사용자가 "개발 루프 세팅해줘", "이제 구현 시작하자", "루프 만들어줘", "LOOP.md 만들어줘", "태스크 어떤 순서로 진행할지 정해줘", "실패하면 몇 번까지 재시도할지 정해줘"라고 하거나, 하네스 검토를 마치고 다음 단계로 넘어갈 때 반드시 이 스킬을 사용한다. 멀티 에이전트·병렬 운용을 실제로 켜는 요청은 LOOP.md 가 이미 있으면 loop-update 의 몫이다. 플랜과 하네스는 있는데 "그래서 지금 무엇부터 하면 되는가"가 문서로 없는 상태에서 첫 태스크 구현을 시작하려 할 때도 이 스킬로 먼저 루프를 고정한다. loop_setup/LOOP.md 가 이미 있으면 이 스킬이 아니라 loop-update 를 쓴다. --- # 개발 루프 세팅 (Layer 5) ## 이 스킬의 목적 하네스가 **"태스크 하나가 끝났다고 말해도 되는 조건"**을 정했다면, 루프는 **누가 언제 그것을 확인하고 무엇을 기록하는가**를 정한다. 원본 사양은 `LOOP.md` 를 **개발 프로세스의 헌법**이라고 불렀다. 그 표현이 정확하다. 이 문서의 독자는 사람이 아니라 **다음 세션의 에이전트**이고, 세션은 계속 끊긴다. 끊긴 자리에서 다시 읽히는 것이 `STATE.md` 와 이 문서 둘뿐이다. ``` 구현 세션이 열린다 → LOOP.md 를 읽는다 (무엇을 어떤 순서로 하는가) → STATE.md 를 읽는다 (지금 어디인가) → 태스크 하나를 구현한다 → lint·unit·e2e 를 순서대로 돌린다 (하네스가 정의한 명령) → "task_validation 대기" 를 적고 닫는다 검증 세션이 열린다 → task_validation 만 돌려 판정 블록을 내고 닫는다 ← 루프가 실제로 만들어 내는 분리 반영 세션이 열린다 → 판정을 STATE.md·LOG.md 에 옮기고, 완료·커밋 또는 수정·재실행을 하고 닫는다 ``` 루프의 품질은 "순서가 촘촘한가"가 아니라 셋으로 판정된다. 1. **세션이 임의의 지점에서 죽어도 다음 세션이 같은 자리에서 이어지는가** — 원본이 STATE.md·LOG.md 를 둔 이유가 이것이다 2. **통과하지 않은 것이 통과로 기록되지 않는가** — 미활성 게이트, 분리되지 않은 검증, 소진된 재시도 3. **막혔을 때 사람에게 오는가** — 스스로 기준을 낮추는 쪽으로 풀지 않는다 ### 여기서 하는 일과 하지 않는 일 | 하지 않는 것 | 어디서 하는가 | |---|---| | 요구사항·기술 결정·설계를 읽고 해석하는 것 | 이미 태스크 문서로 내려왔다. 루프는 PRD·TRD·아키텍처를 읽지 않는다 | | 태스크·phase 를 나누거나 수용 기준·`verification` 을 고치는 것 | Layer 3 (`plan-generator` 10단계 부분 갱신) — 루프는 `Next Action` 에 무엇을 고쳐야 하는지 적고 멈춘다 | | 게이트가 무엇을 검사하고 어떤 명령으로 도는지 **정하는 것** | Layer 4 (`harness-setup`). 루프는 그 명령을 **꺼내 돌릴 뿐**이다 | | 게이트를 고치는 것 (흐름 정의, 활성 시점, `local-dev`) | `harness-update` — 루프는 **부르는 조건**만 안다 | | 태스크 코드를 실제로 구현하는 것 | 루프를 **도는** 세션. 이 스킬은 루프를 **세운다** | | 루프를 고쳐 나가는 것 | `loop-update` — 규칙은 phase 가 진행되며 바뀐다 | **루프가 게이트 내용을 다시 정하지 않는 이유가 있다.** 게이트를 돌리는 쪽과 게이트를 고치는 쪽이 같으면, 실패를 없애는 가장 빠른 길이 언제나 기준을 낮추는 것이 된다. 루프는 **돌리고 분류하고 보고하는 쪽**이고, 고치는 것은 `harness-update` 로 넘긴다. 이 분업이 무너지면 하네스는 조용히 죽는다. ### 이 스킬은 루프를 **세우지** 돌리지 않는다 가장 흔한 오해가 이것이다. 이 스킬을 실행한 결과물은 **`loop_setup/LOOP.md` 문서 하나**이고, 태스크 코드는 한 줄도 만들어지지 않는다. 사용자가 "이제 구현 시작하자"고 말해 이 스킬이 트리거됐더라도, 여기서 하는 일은 **첫 태스크를 구현하는 것이 아니라 앞으로 모든 태스크가 따를 순서를 고정하는 것**이다. 문서를 다 쓴 뒤에 "이 순서로 phase-01-task-01 부터 시작할까요?"라고 묻는다. 그 대답을 받고 시작하는 것이 첫 사이클이고, 그때는 이미 이 스킬이 아니라 `LOOP.md` 가 안내한다. --- ## 1단계. 입력 확보와 사전 점검 **두 곳만 읽는다.** 루프는 요구사항을 해석하지 않는다 — 그건 이미 태스크 문서의 수용 기준으로 내려왔고, 루프가 PRD를 다시 읽으면 태스크에 적히지 않은 요구를 구현하러 가게 된다. | 입력 | 여기서 무엇을 꺼내는가 | |---|---| | `plan_setup/PLAN.md` | phase 구성과 순서, 차단 목록 | | `plan_setup/phase-*/phase-*.md` | phase 수용 기준 → **phase 완료 판정의 근거** | | `plan_setup/phase-*/phase-*-tasks/*.md` | frontmatter 전부 — `id`·`depends_on`·`files`·`status`·`verification`·`blocked_by`. **DAG 의 재료다** | | `plan_setup/STATE.md` | 지금 어느 phase·태스크인가. **어떤 절이 이미 있는가** (절을 더할 자리를 정하려면 필요하다) | | `plan_setup/LOG.md` | 기록 형식. 이미 진행된 이력이 있으면 그것 | | `harness_setup/HARNESS.md` | 게이트별 **명령·활성 시점·현재 상태**, `local-dev` 명령과 옵션, '하네스 밖' | | `harness_setup/quality_gates/*.md` | 게이트 정의서의 **파일 이름**과 `## 명령` 절의 위치. 실패 분류 기준 | | `harness_setup/scripts/` | `local-dev` 의 실제 옵션 이름 (`-Status`·`-Reset`·`-Logs`·`-Down`) | ### 하네스가 없으면 루프를 세우지 않는다 `harness_setup/quality_gates/` 가 없으면 **멈추고 `harness-setup` 을 먼저 하자고 말한다.** 루프의 절반은 "게이트를 순서대로 돌린다"인데, 돌릴 명령이 없으면 그 절반이 빈 문장이 된다. 빈 문장으로 세운 루프는 첫 태스크에서 각 세션이 자기 나름의 검증을 만들어 내고, 그러면 무엇이 통과한 것인지 알 수 없다. `plan_setup/` 이 없으면 마찬가지로 `plan-generator` 가 먼저다. ### 실제 값을 꺼내 넣는다 `LOOP.md` 는 일반론이 아니라 **이 프로젝트의 문서**다. 아래는 하네스와 플랜에서 실제 값을 꺼내 채운다. - `local-dev` 의 **실제 경로와 옵션 이름** — `.ps1` 인지 `.sh` 인지, `-Status` 인지 `--status` 인지 - **첫 태스크 ID**와 phase 목록 - **차단 태스크의 개수와 ID 범위** — 루프가 매번 걸러야 할 대상이다 - 게이트별 **활성 시점 태스크 ID** - **phase 완료 묶음 명령 절**(`e2e.md` 의 `## phase 완료 묶음 명령`)이 있는지 — 구동 스크립트가 돌 때마다 그 절을 읽는다(9단계. 명령은 베끼지 않는다 — 4단계). 없거나 흐름 ID 만 있으면 `harness-update` 로 먼저 만든다 경로와 옵션을 일반형으로 남겨 두면 첫 세션이 그것을 추측하고, 추측이 틀리면 환경 준비가 실패한 채로 게이트가 돈다. --- ## 2단계. 한 사이클의 뼈대 원본 사양은 17단계 목록을 예시로 보여 준다. **그 목록을 그대로 옮기는 것이 목적이 아니다** — 앞 레이어가 확정한 것과 맞추면 순서가 달라지고, 억지로 예시를 지키면 아무도 못 지키는 항목이 남는다. 아래 넷을 반영해 다시 쓴다. **(1) 게이트 이름을 사이클에 하드코딩하지 않는다.** 원본 6~9단계는 `lint`·`unit`·`E2E`·`task validation` 을 네 줄로 적었지만, Layer 4가 **게이트 문서 이름 = 태스크의 `verification` 값**으로 확정했다. 사이클은 "`verification` 을 읽어 그 게이트들을 정해진 순서로 돈다" 한 줄이고, 네 이름은 순서 규칙에만 남는다. 게이트가 줄어든 태스크가 있어도 사이클을 고칠 필요가 없어진다. **(2) `task_validation` 앞에 세션 경계를 그린다.** 원본 목록에서 9단계는 앞의 셋과 같은 줄에 있지만, 실제로는 **세션이 바뀌는 지점**이다 — 그 앞에서 한 번, 그 뒤에서 한 번(7단계의 세 토막). 줄 하나로 두면 같은 세션이 이어서 판정하게 되고, 그 순간 이 게이트는 이름만 남는다. **여기가 Layer 5의 핵심 구현 지점**이므로 사이클에서 눈에 보이게 끊는다. **(3) 실패와 완료 앞에 판정을 하나씩 끼운다.** - 원본 10단계 "실패하면 원인 분석 및 수정" **앞에 분류**를 넣는다 — 분류 없이 고치면 거의 항상 기준을 낮추는 쪽으로 끝난다 - 원본 12단계 "성공하면 Task completed" **앞에 미활성 확인**을 넣는다 — 미활성은 통과가 아니다 **(4) 기록을 완료 시점에만 하지 않는다.** 원본은 13·14단계(STATE 갱신·LOG 기록)를 완료 뒤에 두었다. 그런데 세션은 **구현 중에 가장 많이 끊긴다.** 시작할 때 `in_progress` 를 남기지 않으면 다음 세션이 그 태스크를 `pending` 으로 보고 처음부터 다시 한다. 원본이 STATE.md·LOG.md 를 둔 목적("세션이 갑작스럽게 종료되더라도 이어서")에 맞추면 기록은 **시작·전이·완료 세 번**이다. 여기에 원본 본문이 목록 밖에서 말한 것 하나를 되살린다 — **"성공하면 커밋 → task done"**. 17단계 목록에는 커밋이 없지만 본문과 Agent/Worktree 절에는 있다. **본문 쪽이 맞다.** --- ## 3단계. 태스크 선택 규칙 원본의 다섯 규칙을 그대로 살리되, 판정 근거를 **frontmatter 필드**로 못 박는다. "blocked 를 실행하지 않는다"가 규칙으로만 있으면 매 세션이 blocked 를 다르게 판정한다. | 규칙 | 무엇을 읽어 판정하는가 | |---|---| | `blocked` 는 실행하지 않는다 | `status: blocked` 와 `blocked_by` 의 OQ·CF ID. **차단은 사용자가 미결을 풀어야 열린다** | | dependency 가 완료되지 않은 태스크는 실행하지 않는다 | `depends_on` 의 모든 ID가 `completed` 인가 | | 한 번에 하나만 완료 상태로 전환한다 | 진행 중인 태스크가 이미 있으면 그것을 끝낸다. `failed` 인 태스크는 `Next Action` 에 사람의 `답:` 이 있을 때만 이어받는다 | | 태스크 범위를 임의로 확장하지 않는다 | `files` 와 `architecture` 밖을 건드리지 않는다 | | 요구사항이 모호하면 추측하지 않고 멈춘다 | 수용 기준이 관찰 가능하지 않으면 Layer 3으로 | **범위 확장 금지가 실제로 판정되게 하는 것이 `files` 와 `architecture` 다.** Layer 3이 이 두 필드를 기계로 읽으라고 만들어 두었다. "관련된 김에 여기도 고쳤다"는 그 자체로 나쁘지 않지만, 그 파일은 다른 태스크의 `files` 이고 그 태스크의 검증을 받지 않은 채 커밋에 섞여 들어간다. 경계 밖을 고쳐야 한다고 판단되면 **고치지 말고 보고한다** — 태스크를 나누는 것은 Layer 3의 일이다. **막다른 곳도 규칙으로 다룬다.** 실행 가능한 태스크가 0개인데 미완료 태스크가 남아 있으면 그건 차단이 풀리기를 기다리는 상태이거나 `depends_on` 에 순환이 있는 것이다. 전자는 사용자에게 **어느 OQ·CF 를 풀면 무엇이 열리는지**를 보고하고, 후자는 Layer 3으로 돌려보낸다. `blocked_by: [폐기]` 인 태스크는 차단이 아니라 Layer 3이 치운 것이므로 이 보고에 넣지 않는다. 이걸 안 적어 두면 세션이 "할 게 없다"에서 멈춰 아무 말 없이 끝난다. ### 차단 해소 절차 — `blocked` 를 `pending` 으로 바꾸는 주인은 루프다 `blocked` 는 사용자가 미결(OQ)이나 충돌(CF)을 풀어야 열리는데, **푼 뒤에 태스크 문서를 누가 고치는가**가 정해져 있지 않으면 `status: blocked` 와 `blocked_by` 가 그대로 남아 루프가 그 태스크를 영원히 건너뛴다. Layer 3은 초기값만 만들고, `harness-update` 는 태스크 문서를 고치지 않는다. 그래서 **사이클을 도는 세션이 한다.** 사용자가 "OQ-007 풀렸다"고 알리면 그 세션은 다음 순서로 처리한다. 1. **앞 문서에 해소가 반영됐는지 확인한다** — PRD 8절·TRD 12절의 미결 표에서 그 OQ가 사라졌거나 결정으로 옮겨졌는지, CF면 overview.md 7절에서 `지금 취한 조치` 가 해소로 바뀌었는지. **반영되지 않았으면 태스크를 열지 않는다** — 대화에서만 풀린 미결 위에 코드를 얹으면 다음 세션은 여전히 막힌 문서를 읽는다. 그때는 `Next Action` 에 `질문: OQ-007 해소가 PRD 8절에 반영되지 않았다. prd-generator 로 반영한 뒤 "답: 해소 OQ-007" 로 다시 알려 달라` 를 적고 종료한다 — 어느 스킬로 고치면 되는지 적어야 사람이 한 번에 처리한다. 받은 `답:` 을 지우고 아무 말 없이 끝나면 사람은 왜 안 먹혔는지 모른다. (루프가 PRD·TRD·아키텍처를 읽지 않는다는 1단계 원칙의 **유일한 예외**다. 그 표 한 줄을 확인하는 것이지 요구사항을 해석하는 것이 아니다) 2. **그 ID를 `blocked_by` 에 가진 태스크 문서를 전부 찾는다** — `PLAN.md` 5절 차단 표가 색인이다 3. 각 태스크에서 `blocked_by` 의 그 ID를 지우고, **비어 있게 되면** `status` 를 `pending` 으로 바꾼다. 다른 ID가 남아 있으면 `blocked` 그대로다 4. **미결의 답이 수용 기준을 바꾸는 경우는 여기서 고치지 않는다** — 태스크 문서의 `acceptance_criteria` 를 다시 쓰는 것은 Layer 3의 일이라, 그 태스크만 `blocked` 로 두고 "수용 기준 재작성 필요"를 `Next Action` 에 적어 보고한다 5. `STATE.md` 의 `Task Status`·`Next Action` 을 고치고 `LOG.md` 에 한 줄 남긴다 — `### <태스크 ID> Unblocked (OQ-007 해소).` 6. **`harness-update` 를 부른다** — 그 태스크에 걸려 `대기` 로 빼 두었던 흐름·커버 영역이 하네스 문서에 있다(10단계 "시점 무관"). 무인 자식이면 ①(대기 → 활성 후보)만 하고, ②③이 필요하면 `질문:` 으로 남긴다(7단계의 규칙) 이 절차를 `LOOP.md` 에 적는다. 미결이 풀리는 것은 태스크 완료보다 드물어서, 적어 두지 않으면 매번 다르게 처리되거나 잊힌다. **어느 토막의 일인가.** 구동기 ①에서는 사람이 `STATE.md` 의 `Next Action` 에 **`답: 해소 OQ-007`** 로 적어 두고(대화형 세션이면 말로), 다음 **구현 토막**이 0절 5번에서 그것을 읽어 이 절차를 먼저 끝낸 뒤 태스크 선택으로 간다. 병렬(①-병렬·②)에서는 코디네이터 자식이 한다. 구현 지시문에 "0절대로 시작하라"가 들어 있는 이유가 이것이다 — 안 그러면 자식은 `blocked` 를 풀지 않고 다른 태스크를 집는다. --- ## 4단계. 게이트 실행 ### 이름으로 열고 명령을 꺼낸다 태스크의 `verification` 값 하나가 `quality_gates/` 아래 **같은 이름의 문서 하나**다. `verification: [lint, unit, e2e, task_validation]` → `quality_gates/lint.md`·`unit.md`·`e2e.md`·`task_validation.md`. **넷 중 셋만 명령으로 돈다.** 게이트 정의서를 열어 보면 형식이 두 가지다. | 게이트 | 어디를 읽는가 | 어떻게 판정하는가 | |---|---|---| | `lint`·`unit`·`e2e` | `## 명령` 절의 명령 한 줄 | 돌려서 **종료 코드**로 | | `task_validation` | `## 절차` 와 `## 판정 형식` | **세션을 열어** 판정 블록(`PASS`/`FAIL`/`판정 불가`)을 받아서 | **`task_validation.md` 에는 `## 명령` 절이 없다.** 넷을 같은 모양으로 다루려다 없는 절을 찾게 되면 거기서 멈추거나, 더 나쁘게는 앞 셋의 통과를 넷째의 통과로 대신 읽는다. 넷째는 명령이 아니라 **세션을 여는 것**이고, 그것이 이 게이트의 전부다 (6단계). **활성 시점도 마찬가지다.** 앞 셋은 정의서에 `## 활성 시점` 절이 있지만 `task_validation.md` 에는 없다. **정의서에 없으면 `HARNESS.md` 의 게이트 개요 표에서 읽는다.** 두 곳이 다르면 정의서가 이긴다 — 표는 요약이다. 명령을 `LOOP.md` 에 베껴 적지 않는다 — 하네스가 갱신되면 베낀 쪽이 낡고, 낡은 명령은 "돌긴 도는데 다른 것을 검사하는" 상태를 만든다. **LOOP.md 는 명령이 아니라 명령을 찾는 방법을 적는다.** 예외는 `local-dev` 하나다. 그건 게이트가 아니라 환경 준비 수단이고, 경로와 옵션이 일반형이면 첫 세션이 추측한다. ### 순서는 고정이고 앞이 실패하면 뒤를 안 돌린다 `lint → unit → e2e → task_validation`. `verification` 배열의 순서가 아니라 이 순서다 (배열에 없는 게이트는 건너뛴다). 비용이 싼 순서이기도 하지만 더 중요한 이유가 있다 — **타입이 안 맞는 코드로 돌린 e2e 실패는 그 흐름이 깨졌다는 뜻이 아니다.** 원인을 찾는 데 시간만 쓴다. ### 게이트를 돌리기 전에 환경을 준비한다 `e2e` 와 `task_validation` 은 실행 중인 애플리케이션을 전제한다. `local-dev` 를 먼저 돌리고, 그게 실패하면 그것은 **게이트 실패가 아니라 환경 문제**다 (5단계의 분류로 간다). ### 활성 시점 전에는 결과가 무엇이든 `미활성` 이다 게이트 정의서(없으면 `HARNESS.md` 표)의 활성 시점이 가리키는 태스크가 아직 `completed` 가 아니면, **그 게이트의 결과는 돌리든 안 돌리든 `미활성` 이다.** 명령을 돌려도 되고 비용이 아까우면 건너뛰어도 되지만, **나온 값을 통과로도 실패로도 읽지 않는다.** 이 규칙이 필요한 이유는 두 방향이다. 하네스의 게이트 정의서들이 각각 적어 둔 것을 보면 — "`package.json` 이 없어 명령이 실패하는 것", "테스트 0건으로 명령이 실패하는 것", "활성 흐름이 0개여서 `no tests found` 로 끝난 것" — **실패도 통과도 아닌 것이 양쪽으로 다 생긴다.** 0건 실행은 대부분의 러너에서 초록불이라 통과로 새고, 도구가 아직 없어서 나는 오류는 실패로 새어 멀쩡한 태스크를 재시도 한도까지 몰고 간다. 둘 다 막는 값이 `미활성` 이다. **활성 시점에 도달한 뒤에는 반드시 돌린다.** 그때 "검사 대상 없음"으로 끝나면 그건 미활성이 아니라 활성 시점이 틀렸거나 게이트 명령이 실제 파일 배치와 어긋난 것이고, `harness-update` 로 간다. ### `부분 활성` 이 실제로 쓰이는 자리 상태 어휘는 하네스가 정한 넷을 그대로 쓴다 — `활성` / `부분 활성` / `미활성` / `대기`. **루프가 새 이름을 만들지 않는다.** 만들면 하네스 문서와 STATE.md 가 다른 말을 하게 된다. 넷 중 `부분 활성` 은 **활성 시점이 하나가 아닐 때** 쓴다. 하네스는 게이트 안을 더 쪼개어 "전체는 이 태스크부터, 그중 어떤 부분은 저 태스크부터"로 적어 두는 일이 있다 (예: unit 게이트가 통과하기 시작하는 시점과 그 안의 통합 테스트가 도는 시점이 다르다). 그 사이 구간이 `부분 활성` 이고, **활성으로 올리면 아직 못 도는 부분이 통과로 새고 미활성으로 두면 이미 도는 부분이 판정에서 빠진다.** 흐름 목록에도 같은 표기가 붙는다 — 붙이는 것은 `harness-update` 이고 루프는 읽기만 한다. `대기` 는 `blocked` 태스크에 걸린 것이고, 사람이 미결을 풀어야 풀린다. 루프가 올리지 않는다. --- ## 5단계. 실패 처리 — 분류가 먼저, 재시도는 3회 ### 고치기 전에 무엇이 잘못됐는지부터 정한다 Layer 4가 셋을 확정했고, 게이트를 실제로 돌려 보면 **코드가 잘못된 것이 아닌 갈래가 둘 더 나온다.** **분류를 건너뛰고 바로 코드를 고치는 것이 이 루프에서 가장 비싼 실수**다 — 원인이 코드가 아닌데 코드를 고치면 고칠수록 멀쩡한 코드가 망가지고, 재시도 예산은 그대로 소모된다. | 분류 | 알아보는 법 | 무엇을 고치는가 | 게이트 재실행 | |---|---|---|---| | **진짜 결함** | 같은 순서를 수동으로 밟아도 같은 결과 | **코드** (루프) | **최대 3회, 처음부터 전부** | | **낡은 스펙** | 화면·규약이 의도적으로 바뀌었다. 게이트가 옛 정의를 본다 | 흐름 정의 (`harness-update`) | 1회, 실패한 게이트만 | | **환경 문제** | `local-dev` 가 0이 아닌 코드로 끝났다. 안 뜬 구성 요소, 남은 데이터, 네트워크 | 환경 (재준비 → `harness-update`) | 1회, 실패한 게이트만 | | **흔들림** | 같은 코드로 돌릴 때마다 결과가 다르다. 재시도로 살아난 통과(`flaky`) | **그 테스트** (`harness-update` — 안정화는 ①이라 무인 자식도 한다. 단언을 빼는 것은 ③) | 1회, 실패한 게이트만 | | **판정 불가** | 수용 기준이 관찰 가능하게 쓰이지 않았다 | 태스크 문서 (Layer 3) | 0회 — 즉시 중단 | **"재시도를 세지 않는다"가 "봐주고 넘어간다"는 뜻이 아니다.** 다섯 갈래 전부 그 태스크는 통과가 아니다. 세지 않는 이유는 하나뿐이다 — **3회는 코드를 고치는 예산인데 뒤 넷은 고칠 곳이 코드가 아니고**, 루프가 고칠 것이 없으니 다시 돌려도 같은 결과다. 그래서 **같은 분류가 그 태스크에서 또 나오면 즉시 멈춘다.** 남은 1회는 예산이 아니라 **고친 것이 고쳐졌는지 보는 확인 실행**이고, 0으로 만들면 그것을 모른다. **재실행 범위도 갈린다.** 흔들림·환경 문제·낡은 스펙을 고칠 때는 **제품 코드가 한 줄도 안 바뀐다** — 선택자를 고치거나 컨테이너를 다시 띄운 것뿐이라 lint·unit 결과가 바뀔 수 없다. 그래서 **실패한 게이트부터** 돈다. `진짜 결함` 만 처음부터 전부 다시 돈다 — 코드를 고치면 다른 파일이 깨질 수 있고, 하네스의 lint 게이트도 수정한 파일이 아니라 프로젝트 전체를 도는 것으로 정의돼 있다. ### 코드가 아닌 갈래를 알아보는 법 **환경 문제는 `local-dev` 의 종료 코드로 확정된다.** 하네스가 실패한 단계를 숫자로 구별해 두었으므로 **0이 아니면 그 자체가 환경 문제**이고 추측할 것이 없다. 다만 **누가 고치는지가 갈린다** — 런타임·엔진이 없거나 환경변수가 비어 있는 것은 사람이 설치·설정할 일이라 사용자에게 가고, 의존 서비스·스키마·앱이 안 뜨는 것은 재준비 후 안 되면 `harness-update` 다. `-Status` 와 `-Logs` 가 이 판정의 도구이고, 하네스가 그것을 둔 이유가 이것이다. **흔들림은 하네스가 통과 조건에서 이미 걸러 준다.** 그런데 이건 앞 셋 어디에도 안 맞는다 — 코드는 멀쩡하고(실제로 통과했다), 화면이 바뀐 것도 아니고, 서비스도 잘 떠 있다. **갈래를 안 만들면 거의 항상 `진짜 결함` 으로 찍히고, 코드를 고쳐도 흔들림은 안 사라져 3회가 통째로 날아간다.** 고칠 곳은 테스트 설계이고 그건 `harness-update` 의 일이라 루프의 예산이 나올 자리가 없다. 넘길 곳은 하네스 e2e 게이트의 흔들림 규칙(역할 기반 선택자, 실행마다 고유한 데이터, 인증 상태 재사용, 외부 값은 실제 경로에서 읽기)이다. **흔들림을 남겨 둔 채 진행하는 선택지는 없다** — 흔들리는 게이트는 꺼진 게이트다. **검증자가 "환경이 이상해서 판정을 멈췄다"고 하면 그건 `FAIL` 이 아니다.** 그 출력을 실패로 받으면 **안 뜬 화면을 보고 "FAIL" 을 적은 것과 같아져** 멀쩡한 코드를 세 번 고치게 된다. `환경 문제` 로 받는다. 하네스의 판정 형식이 그것을 위해 `RESULT: 환경 문제` 를 갖고 있다(`local-dev` 상태 확인이 비0이거나 앱이 응답하지 않을 때). 반영 토막은 그 값을 보면 코드가 아니라 환경을 재준비하고 `대기` 로 되돌린다. `판정 불가` 는 구현이 아니라 **태스크 문서의 문제**다. 고치지 말고 즉시 멈춰 Layer 3으로 보고한다. ### 3회는 루프가 정한 값이 아니다 **원본 사양이 문장으로 못 박은 규칙이고, 루프가 조정할 수 있는 값이 아니다.** `loop-setup` 이 하는 일은 그것을 **어떻게 셀지**를 이 프로젝트에 맞게 적는 것이지 몇 회로 할지 고르는 것이 아니다. 사용자가 "3번으로 자르지 말고 통과할 때까지 해 달라"고 요청하는 일이 실제로 있다. **그때 상한을 지우지 않는다.** - **한도가 사라지면 남는 선택지가 하나뿐이 된다.** 네 번, 다섯 번 고쳐도 안 통과할 때 다음 수는 수용 기준을 자기 구현에 맞게 읽는 것이고, 사람은 그것을 눈치채지 못한다 — 기록에는 통과만 남는다. **3회 상한은 그 지점에 사람을 부르려고 있는 것**이지 세 번이 충분해서가 아니다 - **불편의 진짜 원인은 대개 상한이 아니다.** 세 번을 다 쓰고 멈추는 일이 반복됐다면 그중 몇 번이 환경 문제나 흔들림에 소모된 것이다. 답은 상한 제거가 아니라 **분류를 먼저 하는 것**이고, 분류가 붙으면 코드가 아닌 실패는 예산을 쓰지 않으므로 세 번은 실제로 세 번이 된다 - **정말 늘려야 한다면 그건 원본 사양을 고치는 결정**이다. 루프 문서에서 조용히 바꿀 일이 아니다 같은 이유로 **게이트를 건너뛰게 하는 것, 분리 세션을 포기하는 것**도 요청받았다고 반영할 값이 아니다. 셋 다 "사용자가 요청한 검증을 사용자 모르게 줄이는" 쪽으로만 움직인다. **요청을 무시하라는 뜻이 아니다** — 왜 그 값인지, 원하는 결과를 다른 방법으로 얻을 수 있는지를 답하고, 그래도 바꾸겠다면 무엇이 사라지는지를 밝힌다. ### 어떻게 세고, 언제 멈추는가 ``` 게이트 실행 1회차 → 실패 (진짜 결함) 수정 1 → 재실행 2회차 → 실패 ← 사이클 1 수정 2 → 재실행 3회차 → 실패 ← 사이클 2 수정 3 → 재실행 4회차 → 실패 ← 사이클 3 → 여기서 멈춘다 ``` **세는 것은 '수정한 횟수'이지 게이트를 돌린 횟수가 아니고, `진짜 결함` 만 센다.** 카운터는 태스크마다이고 태스크가 완료되거나 바뀌면 0으로 돌아간다. **태스크를 넘어 반복되는 실패는 따로 센다.** 카운터가 태스크마다 리셋되므로, 같은 원인이 여러 태스크에 걸쳐 반복되면 영원히 안 잡힌다 — 흐름 하나가 계속 흔들리는데 태스크마다 "이번이 처음"이 되어 매번 낭비하고 넘어간다. 그래서 **실패한 흐름·검사 항목의 ID를 분류와 함께 `LOG.md` 에 남기고**, 같은 것이 **두 태스크에 걸쳐** 나오면 태스크 카운터와 무관하게 `harness-update` 로 올린다. 여기가 `harness-update` 0단계의 **"두 태스크에 걸친 같은 실패"** 트리거가 실제로 걸리는 자리이고, `LOG.md` 를 append-only 로 둔 이유이기도 하다 — 지금 상태만 보는 `STATE.md` 로는 이 패턴이 보이지 않는다. **규칙만 적어 두면 아무도 세지 않는다.** 실전(ERP, 2026-09)에서 이 규칙은 `LOOP.md` 에 있었는데, 같은 unit 테스트가 phase-06 부터 phase-11 까지 여러 태스크에 걸쳐 흔들리는 동안 한 번도 실행되지 않았다 — 코디네이터는 그것을 `알림:` 한 줄로 쌓고 다음 배정으로 넘어갔다. 원인은 셋이었고, 셋 다 막는다. 1. **세는 단계가 지시문에 없었다.** 규칙은 설명 절에 있었고 코디네이터가 실제로 따르는 지시문에는 없었다. 그래서 **세는 일을 지시문의 한 단계로 넣는다** — 병렬은 코디네이터 지시문(반영 뒤, 배정 전), ①-단일은 반영 지시문(완료 후 점검). 판단 없이 셀 수 있도록 **실패 줄의 형식을 고정한다**: `LOG.md` 에 `- 실패: <태스크 ID> / <게이트> / <검사 ID> / <분류>` (검사 ID = `FLOW-xx`·테스트 파일 경로·lint 규칙 이름). 같은 검사 ID 가 **서로 다른 태스크 ID 둘 이상**에서 나오면 걸린 것이다. 처리한 뒤에는 `- 반복 처리: <검사 ID> → <무엇을 했나>` 를 남기고, 다음 판정은 그 줄 **뒤의** 실패만 센다 — 안 그러면 이미 고친 것을 매 라운드 다시 올린다 2. **걸려도 할 수 있는 일이 없었다.** 흔들림의 원인이 공유 DB 처럼 격리 부족이면 고칠 것은 `loop-update`(격리 상향)와 `harness-update`(`local-dev` 에 슬롯 변수)이고, 둘 다 ②(범위 확대)라 무인 코디네이터가 할 수 없다. 할 수 없으면 **`질문:` 으로 올리고 새 배정을 멈춘다**(돌던 행은 끝까지 돈다). `알림:` 으로 남기는 선택지는 없다 — `알림:` 은 구동기를 멈추지 않으므로 사람이 볼 때까지 같은 낭비가 태스크마다 반복된다. 고칠 방법이 ①(테스트 안정화·흐름 정의 보정)이면 그 자리에서 `harness-update` 를 부른다 3. **흔들림이 격리 부족에서 오는지 가르는 기준이 없었다.** 격리 3에서 흔들리는 검사가 **DB·앱을 쓰는 테스트**(통합 unit·e2e)이면 격리 부족으로 본다 — 테스트를 아무리 안정화해도 다른 슬롯이 같은 DB 를 동시에 고친다. 그때 가는 곳은 `harness-update` 가 아니라 `loop-update`(격리 1·2)다 **한 태스크 안에서는 분류를 합쳐 센다.** 코드가 아닌 갈래는 각각 1회이지만, 그것들이 같은 태스크에서 **합쳐 3회** 쌓이면 `진짜 결함` 이 아니어도 멈춘다. 분류만 바꿔 가며 재실행을 이어 가면 예산이 사실상 없어지기 때문이다. ### 소진하면 무엇을 하는가 **작업을 일시 중단하고 사용자에게 보고한다. 스스로 풀지 않는다.** 원본이 정한 것이고, 계속 진행하게 두면 다음 선택지가 하나뿐이다 — 수용 기준을 자기 구현에 맞게 읽는 것. 멈출 때 **다음 세션이 이어받을 수 있게 남긴다.** 사용자의 답이 며칠 뒤에 올 수도 있다. - 태스크 `status: failed`, `STATE.md` 의 `Task Status` 도 같은 값 - `Last Verification` — 어느 게이트에서 무엇이 실패했는가 한 줄 - 재시도 이력 — 각 시도가 무엇으로 분류됐고 무엇을 고쳤는가 (**6단계에서 정한 자리에**) - `Next Action` — **사용자에게 무엇을 묻고 있는가**. 선택지가 있으면 선택지 - `LOG.md` — 관찰한 것. 원인 추정이 아니라 "무엇을 했더니 무엇이 보였는가" - **작업 트리의 미커밋 변경은 그대로 둔다.** 커밋하지 않고(검증받지 않은 상태를 기본선으로 만들지 않는다), 되돌리지도 않는다. 그 태스크는 `Current Task` 로 남아 있으므로 다음 세션은 다른 태스크를 집지 않는다. **병렬에서는 슬롯을 놓으며 그 변경을 `park/<태스크 ID>` 브랜치로 옮긴다**(11단계) — 어느 브랜치에도 병합하지 않는 보관함이라 기본선이 되지 않고, 답이 "이어서" 면 빈 슬롯에서 미커밋 상태로 되살린다 **사람의 답은 둘 중 하나이고, 각각 종착 상태가 정해져 있다.** 정해 두지 않으면 `failed` 가 영원한 상태가 되어 후행 태스크가 전부 막힌다. | 답 | 루프가 하는 것 | |---|---| | **"이어서"** (필요하면 힌트와 함께) | `status` 를 `in_progress` 로 되돌리고, `Retry` 의 `Cycle` 을 0으로 — **`History` 는 지우지 않는다**(같은 시도를 반복하지 않으려면 남아야 한다). `LOG.md` 에 "사용자 승인으로 재시도 예산 갱신" 한 줄. 원본이 "사용자의 응답에 따라 후속 작업을 진행"이라고 정한 자리라 3회 규칙의 예외가 아니다 | | **"건너뛰라"** | `files` 에 적힌 파일을 되돌리고(`files` 밖의 변경은 범위 확장이었으므로 보고), `status` 는 `failed` 그대로, `Current Task` 를 비우고 **`Retry` 를 초기화**하고 `LOG.md` 에 남긴다. 다음 세션은 이 태스크에 의존하지 않는 다른 실행 가능 태스크를 집는다. **후행 태스크는 `depends_on` 규칙으로 자동으로 열리지 않는다** — 그래서 건너뛴 태스크는 폐기하거나 다시 자를 때까지 후행이 서 있고, 그 결정은 `plan-generator` 10단계의 일이다. 무인 자식은 보고할 통로가 `STATE.md` 뿐이므로 `Next Action` 에 **`알림(plan-generator, ~다음 phase 전): 건너뜀 — 폐기 또는 재작성 필요, 막힌 후행 n개`** 를 남긴다. `알림:` 은 `질문:` 과 달리 구동기를 멈추지 않고, 처리 주체가 처리한 뒤 지운다(아래 "`알림:` 에는 주인과 만료가 있다") | 보고에는 **어느 분류였는지와 그 근거**를 반드시 넣는다. 사용자가 판단할 재료가 그것이다. ### `알림:` 에는 주인과 만료가 있다 `질문:` 은 구동기를 멈추고 `알림:` 은 멈추지 않는다. 멈추지 않는 것은 장점이지만, **주인도 기한도 없는 `알림:` 은 아무도 읽지 않는 목록이 된다** — 실전에서 phase-03 부터 60줄 넘게 쌓였고 그중 여럿이 이미 조건이 충족된 트리거(격리 재검토, `harness-update` 후보)였다. 그래서 형식을 고정한다: **`알림(<처리 주체>, ~<만료>): <내용>`**. 처리 주체는 `사람`·`plan-generator`·`harness-update`·`loop-update` 중 하나, 만료는 기본이 **다음 phase 를 열기 전**이다. - **루프가 스스로 할 수 있는 것은 `알림:` 으로 남기지 않는다** — `harness-update` ①로 되는 것이면 지금 한다. 남기는 것은 ②③이거나 다른 레이어의 일뿐이다 - **phase 를 열기 전에 만료된 `알림:` 을 확인한다** — 남아 있으면 한 번에 모아 `질문: 처리되지 않은 알림 n개 — 처리 / 다음 phase 까지 연장 / 폐기` 로 올리고 다음 phase 를 열지 않는다. phase 안에서는 묶어 두고 경계에서 한 번 묻는 것이라, 사람을 태스크마다 부르지 않으면서 쌓이지도 않는다 - **처리한 쪽이 지운다.** 연장은 만료만 바꾸고 `LOG.md` 에 한 줄 남긴다 --- ## 6단계. 세션이 끊겨도 남아야 하는 값 — STATE.md 에 절을 더한다 재시도 횟수는 **세션이 죽으면 사라지는 값인데 사라지면 안 되는 값**이다. 지금 STATE.md 에는 둘 자리가 없다. 원본의 다섯 절은 예시이지 상한이 아니므로 **절을 더한다.** `Last Verification` 에 끼워 넣지 않는다 — 그 절은 Layer 4가 "지금 상태 한 줄"로 정했고 게이트를 돌릴 때마다 덮어써진다. 덮어써지는 자리에 누적값을 두면 첫 재실행에서 카운터가 사라진다. ### 더하는 절 둘 **`# Retry`** — 현재 태스크의 재시도 예산. 태스크가 완료되거나 바뀌면 초기화한다. ```markdown # Retry * **Task:** phase-02-task-03 * **Cycle:** 2 / 3 (진짜 결함만 센다) * **Gate:** e2e * **Classification:** 진짜 결함 * **History:** * 1) e2e FLOW-03 / 흔들림 / harness-update 로 선택자 수정 (Cycle 미증가) * 2) e2e FLOW-03 / 진짜 결함 / 세션 만료 처리 수정 * 3) e2e FLOW-03 / 진짜 결함 / 쿠키 SameSite 수정 ``` `History` 가 있어야 다음 세션이 **같은 것을 또 고치지 않는다.** 숫자만 남기면 "1회 남았다"는 알지만 앞의 시도들이 무엇을 했는지 몰라 첫 시도를 반복한다. **코드가 아닌 갈래도 여기 쌓되 `Cycle` 은 올리지 않는다** — 세지 않기로 한 것이 이력에서는 보여야 "같은 분류가 두 번째"와 "같은 흐름이 두 태스크에 걸쳐"를 판정할 수 있다. 그래서 각 줄에 **게이트·흐름 ID·분류·무엇을 했는지**를 함께 적는다. 셋 중 하나라도 빠지면 판정이 안 된다. **`# Standing Decisions`** — 사람이 내렸고 **한 번 이상 유효한** 판단. ```markdown # Standing Decisions * **미활성 e2e 로 완료 허용:** phase-02-task-07 활성 시점까지 (2026-09-09 승인) ``` 이 절이 필요한 이유가 있다. **미활성 게이트를 달고 태스크를 완료시킬지는 루프가 아니라 사람이 정한다**고 Layer 4가 확정했는데, 초기에는 거의 모든 게이트가 미활성이라 그대로 두면 **모든 태스크마다 멈춰서 묻게 된다.** 한 번 물어 받은 답을 여기 적어 두면 그 게이트가 활성될 때까지 다시 묻지 않는다. 대화가 아니라 문서에 남기는 이유는, 그 승인이 다음 세션에도 유효해야 하기 때문이다. **둘 다 STATE.md 안이고 새 파일을 만들지 않는다.** Layer 4가 "검증 결과는 STATE.md 와 LOG.md 로만 간다"고 정한 것과 같은 이유다 — 기록처가 셋이 되면 셋이 서로 어긋난다. ### 승인은 만료된다 — 루프가 매 사이클 확인해 지운다 승인은 **"그 게이트가 활성될 때까지"** 로 발급된다. 그러니 **게이트가 활성되는 순간 죽은 종이가 된다.** 남겨 두면 **루프가 실제로 도는 게이트를 "승인받았으니 건너뛴다"로 읽는다** — 통과율은 오히려 좋아 보이는, 가장 조용한 종류의 구멍이다. 그리고 이것은 하네스가 고장 나서가 아니라 **잘 굴러가서** 생긴다. 게이트 활성은 정상 진행이고, 하네스가 좋을수록 더 일찍 온다. **이 확인은 루프가 매 사이클 스스로 한다.** 별도 갱신을 부를 일이 아니다 — 사이클을 열 때 `Standing Decisions` 를 이미 읽고(다시 묻지 않기 위해) 게이트 활성 여부도 이미 판정하므로 (3단계·4단계), **대조할 재료가 이미 손에 있다.** 만료된 항목은 그 자리에서 지우고 `LOG.md` 에 한 줄 남긴다. **루프가 자기 승인을 건드려도 안전한 이유는 방향 때문이다.** | 승인에 대해 | 누가 | 왜 | |---|---|---| | 사람에게 묻고 기록한다 | **루프** | 사람이 답한 것이다 | | **만료된 것을 지운다** | **루프** | 봐주는 것을 **없애는** 방향이라 자기를 봐줄 수 없다 | | 범위를 넓힌다 (게이트 단위로 등) | `loop-update` | 느슨해지는 방향이라 등급 판정과 보고가 필요하다 | | 절의 형식을 바꾼다 (병렬 등) | `loop-update` | Layer 3이 만든 절까지 건드리게 된다 | **무기한 승인을 만들지 않는다.** 범위나 기한이 없는 승인은 만료 확인이 불가능해지고, 그러면 이 절이 "한 번 봐주면 영원히 봐주는 자리"가 된다. --- ## 7단계. 세션 경계 — 한 태스크에 한 세션, 검증은 다른 세션 원본이 못 박았다 — **"한 task에 한 세션(클로드·코덱스 동일)을 전제로 한다."** 그 "한 세션"은 **구현하는 세션**을 말한다. 검증은 거기서 떼어 내야 하므로 한 태스크에 세션이 하나 더 붙고, 판정을 받아 마무리하는 자리까지 세면 **셋**이다(아래 "구동기"의 세 토막). ``` 구현 세션 : 태스크 하나를 구현하고 lint·unit·e2e 를 돌린다. 검증 대기를 적고 닫는다 검증 세션 : task_validation 만 돈다. 구현 세션이 무엇을 했는지 모른다 반영 세션 : 판정을 기록하고 완료·커밋 또는 수정·재실행을 한다 (병렬에서는 코디네이터 자식이, ③에서는 상위 세션이 겸한다) ``` ### 검증 세션에 무엇을 주고 무엇을 안 주는가 하네스의 `task_validation.md` 가 목록을 갖고 있다. **루프는 그 목록을 실제로 지키는 쪽**이다. | 준다 | 주지 않는다 | |---|---| | 태스크 문서 경로 | 구현 세션의 대화·요약·"무엇을 했는지" 설명 | | 실행 중인 앱의 접속 주소 | 구현자의 통과 보고나 자체 점검 결과 | | `quality_gates/task_validation.md` 경로 | 앞 게이트가 통과했다는 사실 이상의 실행 로그 | | 판정 블록을 그대로 출력하라는 지시 | 원인 추정, 확인 요청, "이 부분은 이렇게 만들었다" | | (필요 시) 태스크의 `architecture` 가 가리키는 설계 절 — 하네스가 허용한 유일한 추가 입력 | 그 밖의 문서, 커밋 메시지, 최근 변경 파일 | **주지 않는 목록이 이 게이트의 실질이다.** 설계 절을 여는 것을 허용하는 이유는 수용 기준이 `database.md §3.2` 처럼 설계를 인용하는 일이 흔하고, 그 절은 구현 세션이 아니라 Layer 2가 쓴 것이라 구현 판단이 새지 않기 때문이다. 가장 흔한 누수는 "참고하라고 붙이는 한 줄 요약"인데, 그 한 줄이 정확히 구현 중에 형성된 판단이다. ### 분리가 안 되는 환경이면 통과로 기록하지 않는다 환경마다 분리 수단이 다르다(서브에이전트, 새 세션, 다른 워크트리의 에이전트). `LOOP.md` 에는 **이 프로젝트에서 실제로 쓸 수단**을 적는다. 수단이 없으면 **`task_validation` 을 `미활성` 로 두고 사용자에게 알린다.** 같은 세션이 구현하고 판정한 것을 통과로 적으면, 통과 기록만 남고 실제로는 구현자가 자기 코드를 승인한 것이다. 그건 안 하느니만 못하다 — 아무도 그 태스크를 다시 보지 않게 되기 때문이다. ### 태스크가 끝나면 세션을 닫는다 이어서 다음 태스크를 하지 않는다. 컨텍스트가 넘어가면 다음 태스크가 앞 태스크의 판단 위에서 구현되고, 무엇보다 **다음 태스크의 검증 분리가 이미 오염된 상태로 시작한다.** 세션을 닫아도 잃는 것이 없도록 만든 것이 STATE.md·LOG.md 이므로, 여기서 그것을 실제로 쓴다. ### 세션을 누가 여는가 — 구동기 "한 태스크에 한 세션"과 "검증은 다른 세션"은 **누군가 세션을 계속 열어 줘야** 성립한다. 에이전트는 자기 세션을 닫고 새 세션으로 바뀔 수 없지만, **새 세션을 자식 프로세스로 띄우는 것은 된다.** 그래서 `LOOP.md` 에 구동기를 적는다 — 적지 않으면 태스크마다 사람이 세션을 열게 되고, 그건 자동화가 아니다. 구동기는 셋 중 하나다. **오르카가 있으면 ②, 없으면 ①이 기본**이고, 둘 다 병렬(11단계)을 운용한다. 사용자가 다른 방식을 지정하면 그것을 그대로 따른다. | 모드 | 무엇으로 세션을 여는가 | 언제 | |---|---|---| | **① 스크립트 구동** | `claude -p "<지시문>"` / `codex exec "<지시문>"` 을 반복하는 스크립트. **단일**(세 토막을 차례로)과 **병렬**(코디네이터 자식 하나 + 슬롯마다 구현·검증 자식) 두 형태가 있다 | **오르카가 없을 때의 기본.** 두 환경에서 같다 | | **② 오르카 스크립트 구동** | 오르카 터미널 하나에서 도는 **구동 스크립트**가 워커를 `orca orchestration task-create --spec "<지시문>" --display-name "worker_<태스크 ID>"` → `worker-start --task <그 ID> --worktree <슬롯> --agent claude\|codex` 로 띄우고 `check --wait` 로 받는다. 판단은 ①-병렬과 같은 **코디네이터 자식**(`claude -p`)이 라운드마다 새로 한다 | **오르카가 있을 때의 기본.** 워커 카드·질문 응답·생존 확인을 오르카가 맡는다. 명령 표면은 `orca skills get orchestration` 으로 그 버전 것을 읽는다 | | **③ 서브에이전트** | 상위 세션이 Agent 도구로 구현·검증 서브에이전트를 띄운다. 검증 서브에이전트의 출력을 상위 세션이 **판정 파일로 저장한 뒤** 같은 반영 절차를 밟는다 | 클로드 코드 전용 대안. 사용자가 지정할 때만 | 오르카 유무는 세팅 때 확인한다(`orca --version` 같은 CLI 응답). 있으면 ②, 없으면 ①-병렬로 적고, 사용자가 병렬을 끄면 ①-단일이다. **①-병렬의 모양.** 셸 스크립트는 판단하지 않는다는 원칙은 그대로다 — 판단은 **코디네이터 자식**이 하고, 스크립트는 **띄우고 기다리기**만 한다. ``` 반복 { 질문: 이 있고 답: 이 없으면 → 멈추고 사람에게 / Phase Overview 전부 completed 면 → 끝 묶음 대기: phase-NN 이 있고 결과 파일이 없으면 → phase 완료 묶음을 창 없이 돌려 결과를 (임시)/bundle-phase-NN.txt 에 (판단 없음 — 9단계) claude -p "<코디네이터 지시문>" ← 결과 파일 반영 → 병합 → 기록 → 완료·커밋 → 다음 배정 (STATE.md 의 배정표를 갱신) 배정표의 행마다 워커 트리에서 병렬로 띄우고 전부 끝날 때까지 기다린다: 토막 = 구현·수정 → claude -p "<구현 지시문> 태스크: " > <임시>/done-.txt 토막 = 검증 → claude -p "<검증 지시문> 태스크: " > <임시>/verdict-.txt 배정표가 비어 있고 코디네이터가 Next Action 에 "배정 없음" 을 남겼으면 → 멈춤 (차단 대기 또는 막다른 곳) } ``` 코디네이터는 **구현하지 않는다.** FAIL 로 돌아온 태스크의 수정도 코디네이터가 하지 않고 배정표의 그 행을 `수정` 으로 되돌려 같은 슬롯의 새 구현 자식이 고친다 (`Retry` 가 1 오른다). ②의 코디네이터도 이 자식과 같은 것이고, 매 라운드 새 세션이라 컨텍스트가 쌓이지 않는다. 구현 자식의 마지막 출력(`task_validation 대기` / `task_validation 미활성` / `failed: <사유>`)은 `done-.txt` 로 받고, 검증 자식의 판정 블록은 `verdict-.txt` 로 받는다. **병렬 워커는 `STATE.md`·`LOG.md`·태스크 문서·하네스 문서를 쓰지 않는다** — 워커 브랜치는 코드만 바꾸고, 기록할 것은 출력 블록으로 넘긴다(11단계 필자 규칙). 문서는 코디네이터 트리 경로에서 읽는다 — 워커 트리의 문서는 낡았을 수 있다. **②의 모양 — 코디네이터를 상주시키지 않는다.** ②는 ①-병렬과 **판단하는 쪽이 같고 워커를 띄우는 수단만 다르다** — 오르카 터미널에서 띄운 구동 스크립트가 워커를 `task-create`·`worker-start` 로 띄우고 `check --wait` 로 기다리며, 판단(반영·분류·배정)은 라운드마다 새로 여는 코디네이터 자식이 한다. 곁에 밖의 감시(멈춘 것을 알림)·상태판(도는 것을 보여 줌)·답하기(질문을 오르카 탭에 띄우고 답을 받음)가 있다. **①-병렬·② 공통 — 구동 스크립트도 `STATE.md` 를 한 칸 쓴다.** **스크립트가 `STATE.md` 를 건드리는 예외는 하나뿐이다** — 자식과 자식 사이에, 결과에 따라 `Assignments` 행의 **토막 값만** 기계적으로 바꾼다(격리 3 의 `e2e 대기` → e2e 종료 코드가 0 이면 `검증`, 아니면 `수정`). 종료 코드만 보는 일이라 판단이 없고, 자식과 필자가 겹치지 않는다 — 스크립트는 차례로 돌아 코디네이터 자식·질문 답변 자식이 도는 동안에는 쓰지 않고, 그때 도는 워커는 `STATE.md` 를 쓰지 않는다. 그사이 사람이(답하기 탭으로) `답:` 을 적을 수는 있으므로 그 칸을 바꿀 때는 파일을 새로 읽어 그 칸만 바꾸고, 바꿔 달기 직전에 읽은 뒤 파일이 바뀌었으면 다시 한다(답하기와 같은 방식 — 사람의 `답:` 을 지우지 않게). 그 밖의 칸(`Retry`·`Last Verification`·`Next Action`)은 쓰지 않는다 — e2e 출력은 `(임시)/e2e-.txt` 로 넘긴다. **②를 고르면 `references/orca.md` 를 읽는다** — 원리와 실측 근거(1절), `LOOP.md` 4절에 넣을 ② 블록(2절), 8절 ② 값(3절), 스모크(4절), 점검 행(5절)이 거기 있다. **한 태스크의 사이클은 세 토막이고, 토막마다 세션이 다르다.** 검증이 별도 세션이라는 것은 곧 **검증 앞과 뒤도 같은 세션일 수 없다**는 뜻이다 — 구현 세션은 검증 결과를 기다릴 수 없고(끝나야 검증 세션이 열린다), 검증 세션은 `STATE.md` 를 쓰지 않는다. 그래서 판정을 받아 완료·커밋·실패 처리를 하는 **반영** 토막이 따로 있다. | 토막 | 사이클의 단계 | 누가 하나 | |---|---|---| | **구현** | 태스크 선택 → 시작 전 점검 → `in_progress` 기록 → 구현 → `local-dev` → `lint`·`unit`·`e2e` → `Last Verification` 에 **`task_validation 대기`** 를 적고 종료. **`task_validation` 이 아직 미활성**(활성 시점 태스크 미완료)이면 대기 대신 **`task_validation 미활성`** 을 적는다 — 검증 세션을 열 것이 없다 | ① 구현 자식 / ② 워커 / ③ 서브에이전트 | | **검증** | `task_validation` 만. 판정 블록을 출력하고 종료 | ① 검증 자식 / ② 검증 워커 / ③ 검증 서브에이전트 | | **반영** | 판정 블록을 `STATE.md`·`LOG.md` 로 옮김 → PASS 면 완료 판정·커밋·완료 후 점검·phase 판정 / FAIL 이면 분류 → 수정 → 앞 게이트 재실행 → 다시 `task_validation 대기` / 판정 불가·환경 문제·3회 소진이면 `Next Action` | ①-단일 **반영 자식** / ①-병렬·② **코디네이터 자식**(수정은 슬롯의 구현 워커에게 되돌린다) / ③ 상위 세션 | ①에서 반영이 세 번째 자식인 이유는 셸 스크립트가 판정을 해석하지 못하기 때문이다. **구동기는 판단하지 않고 `STATE.md` 만 읽어 어느 토막을 열지 정한다** — 판정 파일이 있으면 반영, `Last Verification` 이 `task_validation 대기` 면 검증, `task_validation 미활성` 이면 **판정 파일 없이 바로 반영** (반영 토막은 `Standing Decisions` 의 승인이 있으면 완료로, 없으면 `질문:` 으로 처리한다), `Next Action` 에 `묶음 대기:` 가 있으면 phase 완료 묶음(결과 파일이 이미 있으면 반영 — 9단계), 그 외는 구현. 진짜 결함으로 수정한 뒤에는 앞 게이트를 다시 돌리고 다시 `대기` 를 적으므로 검증 토막이 다시 열린다. 재시도 카운터는 `Retry` 절에 있어 토막을 넘어 살아남는다. 초기 phase 에서는 거의 모든 태스크가 `미활성` 경로로 가고, 그것이 정상이다 — 세팅 때 받아 둔 승인이 `Standing Decisions` 에 있으면 사람이 붙지 않아도 돈다. **사람의 답이 돌아오는 길도 정한다.** 자식이 `Next Action` 에 질문을 적고 종료하면 구동기는 멈춘다. 사람은 둘 중 하나로 답한다 — `STATE.md` 의 `Next Action` 아래에 `답:` 줄을 직접 적거나, 대화형 세션을 열어 루프에게 말한다(그 세션이 `답:` 을 적는다). ②에서는 셋째로 오르카에 뜬 답하기 탭(`references/orca.md` 1절 "②의 답하기")에 적는다. 병렬이면 그 `STATE.md` 는 코디네이터 트리의 것이다. 다음 자식은 0절에서 `답:` 을 읽어 반영하고 지운다. 이 길이 없으면 자식이 던진 질문을 받을 곳이 없어 구동기가 영원히 멈춘다. 세 모드가 공통으로 지켜야 할 것 넷. - **지시문은 `LOOP.md` 에 고정된 것을 그대로 쓴다.** 구현·검증·반영 셋. 매번 손으로 쓰면 매번 다른 것이 새어 나간다. 검증 지시문은 4·7단계의 목록 그대로다 - **자식 세션은 사람에게 물을 수 없다.** 미활성 승인·모호한 기준·3회 소진처럼 물어야 할 일이 생기면 `Next Action` 에 적고 **종료**한다. 구동기는 `STATE.md` 를 읽어 `failed`·`Next Action` 에 질문이 있으면 멈추고 사람에게 넘긴다. ②는 워커의 `ask` 를 구동 스크립트가 받아 **질문 답변 자식**에게 넘기므로, 문서로 답할 수 있는 것은 사람 없이 답하고 나머지만 `질문:` 으로 올린다 - **권한은 허용 목록으로 주고, 토막마다 다르다.** 자식은 승인을 물을 사람이 없다(클로드 코드 `--allowedTools`, 코덱스 승인 모드). **구현·반영**: 파일 읽기·쓰기, 하네스 명령(`local-dev`, 게이트 명령), `git add`·`git commit`·`git restore`(건너뛰기의 되돌리기), 판정 파일 삭제. **검증**: 파일 읽기, `local-dev`, `HARNESS.md` 의 관찰 수단 도구 — **쓰기와 git 은 주지 않는다**(검증 세션은 `STATE.md` 를 쓰지 않고 코드를 고치지 않는다). 되돌리기와 삭제가 빠지면 건너뛰기와 판정 파일 정리가 거부로 끝나고, 검증에 관찰 수단이 빠지면 검증자가 코드를 읽는다. **판정 파일·결과 파일은 작업 폴더 밖(OS 임시 폴더)이라 허용 목록만으로는 지울 수 없다** — 그것을 다루는 반영·코디네이터·질문 답변 자식에는 `--add-dir <임시>` 를 함께 준다 (2026-10-01 실험: `Bash(rm:*)` 가 있어도 코디네이터 자식이 결과 파일을 지우지 못해 루프가 진전 없음으로 섰다). - **자식이 `harness-update` 를 부를 때는 ①(상태 반영)만 무인으로 한다.** ②(범위 확대)·③(약화)이 필요하다고 판단되면 고치지 않고 `Next Action` 에 `질문:` 으로 적고 종료한다. `harness-update` 가 ③에 승인을 요구하는데 자식은 승인을 받을 수 없기 때문이고, 이것이 "루프는 하네스를 고치지 않는다"와 "필요하면 적극 갱신한다"가 무인 실행에서 함께 성립하는 방법이다 목록 밖의 쓰기는 거부로 끝나고 멈추지 않는다는 것을 확인했고, 읽기 전용 셸 명령은 목록에 없어도 통과한다. **권한 전체 건너뛰기는 사용자가 명시적으로 켠 경우에만** 쓴다 - **판정 블록은 파일로 받는다.** 검증 세션의 표준 출력을 파일로 받아 반영 토막이 `STATE.md`·`LOG.md` 로 옮긴다. 검증 세션 자신이 `STATE.md` 를 쓰게 하지 않는다 — 병렬에서 필자를 하나로 두는 규칙과 같다. **판정 파일은 기록이 아니라 전달 수단이다.** `plan_setup/`·`loop_setup/` 밖에 두고 옮긴 뒤 지운다. "기록처는 STATE.md·LOG.md 둘뿐"이라는 규칙의 예외가 아니다 — 옮기고 나면 사라지기 때문이다. 경로는 **태스크 ID 로 파생**한다(`<임시 폴더>/verdict-<태스크 ID>.txt`) — 병렬에서 워커 셋이 한 경로에 덮어쓰지 않게. **잘린 파일은 판정이 아니다.** 하네스의 판정 형식은 마지막 줄이 `END` 다. `END` 가 없으면 검증 세션이 도중에 죽은 것이므로 파일을 지우고 `대기` 를 유지한다(검증이 다시 열린다). 같은 태스크에서 두 번 연속 잘리면 `질문:` — 검증 세션 자체가 죽는 원인은 환경이다 ①-병렬과 ② 모두 워크트리는 **태스크 수만큼이 아니라 슬롯만큼**이다(11단계의 슬롯 규칙). **`main` 에서는 아무도 작업하지 않는다** — 코디네이터와 **첫 워커(`worker-1`)는 코디네이터 트리(`coordinator`)에서**, 동시에 코드를 쓰는 둘째부터는 `worker-N` 워크트리에서 돈다. 검증 자식은 구현이 돈 자리에서 이어 돈다. 완료된 태스크는 코디네이터 트리에 병합된 뒤 **`main` 에 fast-forward** 된다 — `main` 은 언제나 "검증된 최신"이고 사용자의 자리다. 폭이 1인 구간은 코디네이터 트리의 `worker-1` 하나로 워크트리 0개, 단일과 같다. 폭이 2 이상인 구간의 DB·포트 격리는 11단계에서 세팅 때 정해 둔 것을 따른다. --- ## 8단계. 완료 판정과 기록 ### 언제 완료인가 원본의 문장이 그대로 답이다 — **"Task는 코드 작성이 끝났을 때 완료가 아니다."** 완료 조건은 하나다 — `verification` 의 게이트마다 **활성 상태에서 통과했거나, 미활성이면 `Standing Decisions` 에 유효한 승인이 있다.** 둘 중 어느 쪽도 아닌 게이트가 하나라도 있으면 완료가 아니다. 미활성 게이트에 승인이 없으면 완료가 아니라 **판단이 필요한 상태**다. 그때 **묻고**, 받은 답을 범위·기한과 함께 `Standing Decisions` 에 적는다. 커밋 조건도 같다 — 아래 "커밋"의 "네 게이트 통과"는 이 완료 조건을 뜻한다. ### 기록 순서가 중요하다 세션은 이 세 번의 쓰기 사이에서도 죽는다. 그래서 **순서를 정해 두면 중간에 죽어도 진실이 남는다.** ``` 1) 태스크 문서의 status ← 원본. depends_on 판정이 이것을 읽는다 2) STATE.md ← 현재 위치 요약 3) LOG.md ← append-only 이력 4) 커밋 ← 네 게이트 통과 후에만 ``` **어긋나면 태스크 문서가 이긴다.** 그래서 세션을 열 때 한 가지를 더 한다 — `STATE.md` 의 `Task Status` 와 태스크 frontmatter 의 `status` 가 다르면 **태스크 문서 기준으로 STATE.md 를 맞추고 그 사실을 LOG.md 에 남긴다.** 앞 세션이 1)과 2) 사이에서 죽은 흔적이고, 이 한 줄이 없으면 다음 세션이 이미 끝난 태스크를 다시 구현하거나 끝나지 않은 태스크를 완료로 읽는다. ### 무엇을 기록하는가 | 시점 | STATE.md | LOG.md | |---|---|---| | 태스크 시작 | `Current Task`, `status: in_progress`, `Retry` 초기화 | `### <태스크 ID> Started.` | | 게이트 결과 | `Last Verification` (태스크/결과/사유) | 통과는 한 줄. **실패는 관찰과 분류를 함께**, 그리고 `- 실패: / <게이트> / <검사 ID> / <분류>` 한 줄(반복 판정의 재료) | | 검증 대기 | `Last Verification` = ` / task_validation 대기 / —`. 구현 토막의 마지막 쓰기이고 구동기가 검증 토막을 여는 신호다 | `### lint·unit·e2e PASS. task_validation 대기.` | | 재시도 | `Retry` 의 `Cycle`·`History` | 무엇을 고쳤는가 | | 완료 | `status: completed`, 다음 태스크, `Retry` 초기화, `Last Verification` 을 ` / PASS / —` 로 덮어쓴다(미활성 승인 완료면 `PASS (미활성 승인)`). **`대기`·`미활성` 이 남아 있으면 구동기가 반영 토막을 다시 열어 구현하지 않은 다음 태스크를 완료시킨다** | `### <태스크 ID> Verification PASS.` | | 잘린 판정 | `Retry` 의 `History` 에 `검증 중단` 한 줄(`Cycle` 은 안 올린다). 같은 태스크에서 두 줄째면 `Next Action` 에 `질문:` — 반영 세션은 매번 새로 열리므로 여기 안 남기면 두 번째를 알 수 없다 | `### task_validation 판정 파일 잘림.` | | 중단 | `status: failed`, `Next Action` 에 물을 것 | 세 번의 시도와 관찰 | | phase 완료 | `Phase Overview`(그 phase `completed`, 다음 `in_progress`), `Current Phase`, phase 문서 `Status`, `Task Status` 교체 | `### phase-NN Complete.` | | phase 완료 묶음 대기 | `Phase Overview` 는 `in_progress` 그대로, `Next Action` 에 `묶음 대기: phase-NN`(구동기가 묶음을 돌린다 — 9단계) | `### phase-NN 태스크 전부 completed — 완료 묶음 대기.` | | phase 완료 묶음 실패 | `Phase Overview` 는 `in_progress` 그대로, `Next Action` 에 `질문:` | `### phase-NN 완료 묶음 FAIL — FLOW-xx (분류).` | **실패한 관찰을 LOG.md 에 남기는 것이 특히 중요하다.** 그게 없으면 다음 세션이 같은 것을 처음부터 다시 확인하고, 재시도 예산은 이미 줄어 있다. 관찰은 재현 가능한 문장으로 쓴다 — "정상 동작 안 함"이 아니라 "계정 B로 A의 인보이스 URL을 열었더니 200과 본문이 보였다". ### 커밋 **네 게이트를 통과한 뒤에만 커밋한다.** 커밋 메시지에 태스크 ID를 넣어 `LOG.md` 와 이력이 이어지게 한다. 통과 전에 커밋하면 검증받지 않은 상태가 기본선이 되고, 다음 태스크가 그 위에서 시작한다. **merge·rebase 는 루프가 하지 않는다** — 원본이 orchestration layer 의 책임으로 정했다. --- ## 9단계. phase 완료 판정 원본이 "phase 완료 여부는 자동 판정되어야 한다"고 적었다. 판정 근거는 셋이다. 1. 그 phase 의 모든 태스크가 `completed` 인가 — `blocked` 가 남아 있으면 **완료가 아니다.** 사용자에게 보고한다. 예외는 하나, `blocked_by` 가 `[폐기]` 뿐인 태스크(Layer 3이 폐기한 것)는 없는 것으로 친다. 건너뛰어 `failed` 로 남은 태스크는 예외가 아니다 — 그건 폐기하거나 다시 자를 때까지 phase 를 막는 것이 맞고, 그 결정은 `plan-generator` 10단계다 2. `phase-NN-<이름>.md` 의 **Acceptance Criteria** 가 충족되는가 — 태스크 하나로는 안 보이는 것이 여기 있다 3. 하네스의 **phase 완료 묶음**이 통과하는가 — 아래 ### phase 완료 묶음 하네스는 e2e 흐름 중 **시간이 오래 걸리는 몇 개를 매 태스크 묶음에서 빼서 'phase 완료 묶음'으로 둔다** (브라우저 여러 종, 전 구간 시간 측정 등). 매번 돌리면 느려지고, 느려지면 사람이 게이트를 끄기 때문이다. 그래서 **phase 완료 시점이 그것을 돌리는 유일한 자리**이고, 여기서 안 돌리면 그 흐름들은 영원히 안 돈다. **이 묶음이 실패하면 phase 를 완료로 찍지 않는다.** 하네스가 이것을 "게이트 명령이 아니다"라고 적어 둔 것은 **태스크마다 돌지 않는다는 뜻**이지 통과하지 않아도 된다는 뜻이 아니다. 여기서 넘어가면 그 흐름은 다음 phase 완료까지 아무도 다시 보지 않고, 그사이 태스크가 그 위에 쌓인다. **다만 재시도 3회를 적용하지 않는다.** 그 예산은 태스크 하나를 대상으로 하는데 이 실패는 phase 전체가 대상이라 "무엇을 세 번 고칠지"가 정해지지 않는다. 대신 **깨진 흐름과 그것을 만든 태스크를 찾아 `Next Action` 에 `질문:` 으로 적고 사용자에게 보고한다** — 그 태스크를 다시 열지, 흐름 정의를 고칠지는 사람이 정한다. (실패가 `흔들림`·`환경 문제` 이면 5단계의 분류가 그대로 적용된다. 여기서도 분류가 먼저다.) **실패한 phase 위에 다음 phase 를 쌓지 못하게 막는 것은 태스크 선택 규칙이다.** `Phase Overview` 에서 현재 phase 가 `completed` 가 아니면 다음 phase 의 태스크는 `depends_on` 이 다 차 있어도 열지 않는다. 이 한 줄이 없으면 phase-02 의 태스크가 전부 `completed` 라는 이유로 다음 구현 세션이 phase-03 을 집고, 완료 묶음 실패는 `Next Action` 한 줄로만 남는다. 기록은 `Phase Overview` 를 `in_progress` 그대로 두고 `LOG.md` 에 `### phase-NN 완료 묶음 FAIL — FLOW-xx (분류).` 를 남기는 것이다. `Last Verification` 은 태스크 단위라 여기 쓰지 않는다. **사람의 답이 온 뒤의 진입점도 정해 둔다.** 이 상태는 "실행 가능 태스크 0개 + 미완료 태스크 0개"라 막다른 곳 규칙에도 안 걸린다. 답은 둘 중 하나다 — **`답: 재판정`**(흐름 정의를 `harness-update` 로 고쳤거나 환경을 손봤을 때)이면 다음 세션이 0절에서 그것을 읽어 **반영 토막으로 phase 판정을 다시 돈다**(태스크 완료 없이 phase 판정만 하는 유일한 경우). **`답: <태스크 ID> 다시`** 면 그 태스크를 `in_progress`·`Cycle 0` 으로 되돌려 구현 토막이 집는다. 둘 다 아니면 `질문:` 을 지우지 않고 종료한다 — 답이 안 맞으면 구동기가 다시 멈추는 편이 낫다. **차단 때문에 `대기` 인 흐름은 실행 대상이 아니다.** 끝까지 측정할 수 없는 흐름의 부분 구간 값을 판정으로 쓰지 않는다 — 하네스가 그렇게 표시해 둔 흐름이 있으면 그 표시를 따른다. phase 완료를 STATE.md 와 LOG.md 에 남기고, `Task Status` 를 **다음 phase 의 태스크로 교체한다** (Layer 3이 "현재 phase 의 태스크만 나열한다"로 정했다). 이때 고치는 값이 셋이다 — 빠뜨리면 다음 세션이 끝난 phase 를 다시 돈다. - `Phase Overview` 의 그 phase 를 `completed` 로, 다음 phase 를 `in_progress` 로. **이 절의 값은 태스크 status 와 같은 어휘 중 `pending`·`in_progress`·`completed` 셋만 쓴다** (게이트 상태 어휘 `활성`·`미활성`… 과는 다른 공간이다). 첫 태스크를 시작할 때 그 phase 를 `in_progress` 로 올리는 것도 루프다 - `Current Phase` 를 다음 번호로 - 그 phase 문서(`phase-NN-<이름>.md`)의 `Status` 를 `completed` 로. Layer 3이 `pending` 으로 초기화만 해 둔 값이고 **여기 말고는 아무도 안 고친다** ### 완료 묶음을 돌리는 것은 구동기다 — 판단 자식은 결과만 읽는다 ①·②에서 phase 판정을 하는 것은 판단 자식(①-단일의 반영 자식, ①-병렬·②의 코디네이터 자식)이지만 **완료 묶음을 실행하는 것은 구동 스크립트다.** 이유 셋. - **판단이 없는 실행이다** — 결과는 종료 코드와 출력이 전부다. 격리 3 의 e2e 를 스크립트가 직접 돌리는 것과 같은 이유다 - **판단 자식은 돌릴 수 없다** — 허용 목록은 정해진 명령 모양만 받으므로 슬롯 환경변수를 먼저 정하는 명령·셸 안에서 셸을 또 띄우는 명령이 거부되고, 도구 호출 하나는 10분까지만 기다린다(Claude Code Bash 도구의 상한). 완료 묶음은 매 태스크에서 뺄 만큼 느린 것들이라 대개 그보다 길다 - 실전(두 번째 실전 프로젝트, 1.15.1, 2026-10-14): 이 일을 코디네이터 자식에게 맡겼더니 라운드마다 "완료 묶음을 이번에도 돌리지 못했다"만 남기고 phase-01·02 가 완료로 찍히지 않았다. 질문은 결국 사람에게 "직접 돌리고 결과를 알려 달라"로 넘어갔다. 스킬 안의 두 규칙(묶음은 코디네이터 몫 / 자식 권한은 정해진 명령 모양뿐)이 부딪친 것이다 주고받는 순서는 이렇다. 1. 판단 자식이 근거 1·2(태스크 전부 `completed`, phase 수용 기준)를 확인하면 **묶음을 돌리지 않고** `Next Action` 에 `묶음 대기: phase-NN` 한 줄을 적는다. `LOG.md` 에는 `### phase-NN 태스크 전부 completed — 완료 묶음 대기.` `Phase Overview` 는 `in_progress` 그대로다. **`배정 없음:` 은 적지 않고, 남아 있으면 지운다** — 막다른 곳이 아니라 구동기 차례이고, `배정 없음:` 은 구동기를 세운다(마지막 태스크가 돌던 라운드에 적힌 것이 남을 수 있다). 하네스의 `## phase 완료 묶음 명령` 절(`e2e.md`)이 비어 있으면(활성 시점 전) 신호 없이 4번으로 바로 간다 2. 구동기가 `묶음 대기:` 를 보고, 도는 워커가 없고 결과 파일 `(임시)/bundle-phase-NN.txt` 가 없으면 묶음을 돌린다 — 코디네이터 트리(①-단일이면 작업 트리)에서 슬롯 1 의 환경(8절 슬롯별 환경 표의 값. ①-단일이면 슬롯 변수 없이 기본 환경. 격리 3 이면 앱을 그 트리로 전환)으로 `local-dev` 를 한 뒤 **`e2e.md` 의 `## phase 완료 묶음 명령` 절을 그때 읽어** 코드 블록의 줄을 하나씩 차례로 돌린다(명령을 `LOOP.md` 나 스크립트에 베끼지 않는다 — 4단계. 하네스가 흐름을 활성하거나 뺄 때마다 그 절이 바뀐다). 하나가 실패해도 나머지를 돌린다(어느 검사가 깨졌는지가 판정 재료다). 다만 `local-dev` 가 실패하거나 단계 상한(8절 — ①에도 같은 값)을 넘기면 거기서 멈춘다. 출력은 `(임시)/bundle-phase-NN.log` 에, 결과 파일에는 `COMMIT: <코디네이터 트리 HEAD>` · 명령마다 `EXIT <코드> <명령>` · `LOG: <로그 경로>` · `END` 를 적는다. 멈췄으면 `LOCALDEV <코드>`, 절이 없거나 비었으면 `EMPTY`, 상한을 넘겼거나 결과 없이 죽었으면 구동 스크립트가 `EXIT 시간초과`·`EXIT 중단`(COMMIT 은 띄우기 전에 읽어 둔 값). 돌리는 동안 새 워커도 판단 자식도 띄우지 않는다(띄울 것도 없다 — 다음 phase 는 아직 닫혀 있다) 3. 다음 판단 자식이 결과 파일로 판정한다. `END` 가 없거나, `COMMIT` 뒤에 코드가 병합됐으면(`git diff --name-only HEAD` 에 `plan_setup/`·`loop_setup/` 밖의 파일) 낡은 결과다 — 파일만 지우고 `묶음 대기:` 를 두면 구동기가 다시 돌린다. **`EXIT` 줄이 하나 이상이고 전부 0 이면** phase 완료(위 세 값과 기록)에 `LOG.md` 한 줄 `- 완료 묶음 PASS: phase-NN / COMMIT / 명령 개` (결과 파일과 로그는 사라지므로 "실제로 돌았다"의 증거는 이 줄뿐이다). `EXIT` 줄이 0개(`EMPTY`)면 통과가 아니다 — 4번. 그 밖이면 로그를 읽어 5단계대로 분류하고 `- 실패: phase-NN 완료 묶음 / <게이트> / <검사 ID> / <분류>` 를 남긴다: 진짜 결함·낡은 스펙이면 위의 완료 묶음 실패(`질문:`), 환경 문제·흔들림·시간초과·중단이면 다시 돌릴 것이니 파일만 지우고 신호를 둔다 — **두 번째면 `질문:`** (그 phase 의 마지막 `### phase-NN 태스크 전부 completed — 완료 묶음 대기.` 뒤의 실패 줄만 센다 — `답: 재판정` 이 1번부터 다시 하면 새로 센다) 4. 미활성(묶음 명령 절이 비었다 — 신호 전이든 `EMPTY` 든)이면 4단계의 미활성 규칙 그대로 — `Standing Decisions` 에 그 범위의 승인이 있으면 근거 3을 승인으로 채우고, 없으면 미활성 승인 `질문:` 5. 판정을 마치면 — 완료든 `질문:` 이든 — `묶음 대기:` 줄과 결과 파일을 지운다(신호를 남긴 채 `질문:` 을 적으면 답 뒤에 구동기가 낡은 신호로 묶음을 또 돌린다). **`답: 재판정`** 은 1번부터 다시 — 신호를 다시 적으면 구동기가 묶음을 다시 돌린다 **이 다섯 단계는 `LOOP.md` 5절에 그대로 적는다** — 판단 자식이 읽는 것은 `LOOP.md` 이고 이 스킬이 아니다. 지시문은 "5절" 을 가리킨다. ③(서브에이전트)이면 상위 세션이 직접 돌린다 — 도구 한 번의 상한(10분)을 넘을 수 있으니 백그라운드로 띄우고 끝을 기다린다. --- ## 10단계. `harness-update` 를 부르는 자리 원본도 "필요한 경우 적극적으로 업데이트되어야 한다"고 적었고, `harness-update` 0단계에 조건이 정리돼 있다. **루프는 그 조건을 확인하는 자리를 갖는다.** 확인 자체는 비용이 거의 없고, 안 하면 비싼 쪽으로 걸린다. | 언제 | 무엇을 확인하는가 | |---|---| | **태스크 시작 전** | 이 태스크의 수용 기준을 판정할 수단이 있는가 / 덮는 흐름·커버 영역이 목록에 있는가 / 쓰는 의존 서비스가 `local-dev` 에 있는가 / 게이트 명령이 이 태스크 유형에 실제로 걸리는가 | | **태스크 완료 후** | 이 태스크가 어느 게이트의 활성 시점인가 / 어떤 흐름의 마지막 조각을 채웠나 / 필수 커버 영역을 덮었나 / 새 의존 서비스·환경변수가 생겼나 | | **시점 무관** | 차단(OQ·CF)이 해소됐을 때(3단계의 차단 해소 절차 마지막 단계) / 같은 흐름·검사 ID의 실패가 두 태스크에 걸쳐 나왔을 때 | **시작 전 점검을 빼먹으면 가장 비싸다.** 구현이 끝난 뒤에 "검증할 수단이 없다"를 알면 그 세션을 버리게 되고, 구현 토막이 세션 하나인 구조에서 그건 세션 하나를 통째로 잃는 것이다. **무인 자식이 `harness-update` 를 부를 때는 ①(상태 반영)만 한다.** ②③은 `질문:` 으로 남긴다(7단계). **병렬에서는 코디네이터만 자기 트리에서 부른다.** 워커는 시작 전 점검에서 부족을 보면 `STATUS: 하네스 부족` 으로 종료하고(11단계), 완료 후 점검은 코디네이터의 반영 토막이다. **"게이트가 실패했다"는 그 자체로 트리거가 아니다.** 5단계에서 `낡은 스펙`·`환경 문제`로 분류됐을 때만이다. 분류 없이 실패마다 하네스를 부르면, 실패를 없애는 가장 빠른 길이 기준을 낮추는 것이 된다. --- ## 11단계. 병렬 운용 — DAG·자원 충돌·슬롯·코디네이터 기본은 **병렬**이다(사용자 결정, 2026-09-13). 태스크가 이미 파일 단위로 잘려 있고 `depends_on`·`files` 가 다 적혀 있으면 동시에 열 수 있는 태스크를 하나씩 도는 것이 낭비이고, 병렬의 위험(파일 충돌·공유 런타임·기록 충돌)은 이 절이 전부 규칙으로 막는다. "한 task에 한 세션"은 그대로다 — 병렬은 그 세션이 **동시에 여럿** 열리는 것이지 한 세션이 여러 태스크를 하는 것이 아니다. **세팅 때 네 가지를 묻는다**(한 번씩). ①병렬로 갈 것인가 — 기본은 예. 아니라고 하면 ①-단일 구동기로 적고 아래 격리는 미결로 남긴다. ②동시 에이전트 수 상한 — 구간별 폭(아래)을 보여 주고 고르게 한다. 폭보다 큰 수는 논다. ③워크트리 간 DB·포트 격리 방식 — 아래 "환경 격리" 절의 셋 중 하나. 병렬이면 지금 정한다. **테스트가 DB 를 쓰면 1·2를 추천으로 제시한다**(아래). ④역할별 모델 — 아래 "역할별 모델" 절. 동시 수와 같은 자리에서 묻는다 — **폭 × 모델 = 사용량**이라 둘을 따로 정하면 한도를 넘긴 뒤에 안다. 조건부로 둘 더 — 저장소가 동기화 폴더 아래면 **슬롯 경로**("슬롯과 워크트리" 절), 오르카가 있으면 **에이전트 배치**(구현·검증·코디네이터 각각 claude/codex). 그리고 **오르카 유무를 확인해** 구동기를 ②(있음) 또는 ①-병렬(없음)로 적는다. 묻지 않고 단일로 적으면 이 절 전체가 "알아서 하라"가 된다. ### 역할별 모델 — 묻고, 모르면 추천한다 자식 세션은 플래그로 모델과 추론 강도를 받는다(`claude -p --model --effort`, `codex exec -m -c model_reasoning_effort=`, Agent 도구 `model`). 적지 않으면 세 역할이 **전부 사용자의 기본 모델**로 돌고 그게 무엇인지 어디에도 안 남는다. 코디네이터가 가벼운 모델이면 배정·병합 판단이 흔들려 슬롯 전부가 헛돌고, 구현 워커가 최상위 모델이면 호출 총량이 가장 큰 자리라 한도를 먼저 친다. 역할마다 다르게 두는 이유가 그것이다. **정하지 않고 묻는다.** 모델 이름은 몇 달 단위로 바뀌고 사용자의 요금제·한도는 스킬이 모른다. 세 역할을 한 번에 보여 주고 넷 중 하나를 고르게 한다. ``` 역할별 모델을 정합니다. 모르시면 "추천대로"를 고르세요. 코디네이터 — 배정·실패 분류·병합·질문 판단만. 라운드당 1회, 코드는 안 씀 구현 워커 — 가장 많이·길게 도는 세션 검증 워커 — 관찰·판정만. 구현과 다른 모델(또는 다른 CLI)이면 분리가 한 겹 더 강해집니다 A) 추천대로 (코디네이터 최상위 / 구현 보통 / 검증 보통) B) 전부 최상위 — 품질 우선. 사용량이 폭 × 최상위 단가 C) 전부 보통 — 비용 우선. 코디네이터의 병합·분류 판단이 흔들릴 수 있음 D) 역할마다 직접 지정 ``` **"추천대로"의 내용과 이유** — 사용자가 판단할 수 있게 이유를 같이 보여 준다. | 역할 | 급 | 추론 강도 | 왜 | |---|---|---|---| | 코디네이터 | **최상위** | 높음 | 판단만 하고 호출이 적다. 여기서 틀리면 슬롯 전부가 헛돈다 | | 구현 워커 | **보통** | 보통 | 호출 총량이 가장 크다. 태스크가 `priority: P0` 이고 `files` 3개 이상이거나, 3회 소진 뒤 사용자가 "이어서"로 답한 재배정이면 **한 급 올린다** | | 검증 워커 | **보통** | 보통 | 관찰·판정. 가벼운 급은 브라우저 관찰 단계가 많아 `판정 불가` 가 잦을 것으로 본다 (가정) | | 반영 토막(①-단일) | 보통 | 보통 | STATE.md·LOG.md 기록과 규칙 기반 분류 | **급을 실제 이름으로 바꾸는 것은 세팅 시점에 한다.** 클로드 코드는 `claude --help` 의 `--model` 별칭(`opus`·`sonnet`·`haiku` 등), 코덱스는 설치 버전의 모델 문서를 보고 `최상위`·`보통`·`가벼움` 에 해당하는 이름을 8절 표에 적는다. **스킬 본문과 지시문에는 이름을 박지 않는다** — 은퇴한 모델 이름이 스크립트에 남으면 자식이 뜨지 않는다. 모델은 **8절 표 한 곳**에 있고 구동기 스크립트가 거기서 읽는다. 바꿀 때 한 곳만 고친다(`loop-update`). `--fallback-model` 같은 자동 대체는 쓰지 않는다 — 급이 소리 없이 바뀌면 판정 기록이 어느 모델의 것인지 알 수 없다. ### DAG 는 반드시 만들고, `loop_setup/DAG.md` 에 스크립트로 그린다 원본이 못 박았다 — **"전체 phase(s)/task(s) 구조에서 DAG를 구현하여 모든 dependency를 확인"**. 재료는 이미 있다. 노드는 태스크, 간선은 `depends_on`. **손으로 그리지 않고 스크립트로 만든다.** **그림은 `LOOP.md` 가 아니라 `loop_setup/DAG.md` 에 들어간다**(사용자 결정 2026-09-14 — 원본 구조에 생성물 하나를 더한다). `LOOP.md` 는 모든 세션이 시작할 때 읽는 문서인데 태스크 50개짜리 머메이드는 10KB 를 넘어 그 문서에서 가장 큰 덩어리가 되고, 태스크가 바뀔 때마다 LLM 이 50개 노드를 손으로 다시 그리게 된다. 그래서 둘로 나눈다. | 파일 | 무엇 | 누가 만드나 | |---|---|---| | `harness_setup/scripts/dag-render.*` | 태스크 frontmatter 만 읽어 아래 넷을 뽑는 **생성 스크립트**. 언어·확장자는 하네스 스크립트와 같은 기준(사용자 OS·프로젝트 런타임). **`--ready` 옵션**은 `DAG.md` 를 쓰지 않고, 지금의 태스크 `status` 로 진입 차수를 다시 세어 **지금 열 수 있는 후보**(`status: pending`, `depends_on` 이 전부 `completed`, `blocked` 와 그 후행 아님)를 phase·`files` 와 함께 출력한다 — 코디네이터가 배정마다 부른다. **모르는 옵션이면 아무것도 쓰지 않고 0 이 아닌 코드로 끝난다**(코디네이터가 `--help` 를 시험하다 `DAG.md` 를 다시 쓰지 않게) | 이 스킬이 세팅 때 쓰고 **실제로 한 번 돌려 본다**(`--ready` 도) | | `loop_setup/DAG.md` | 스크립트의 출력. ①`mermaid` 그래프(phase 는 `subgraph`, 노드는 태스크 ID, 실선 `depends_on`, **점선 = `files` 겹침**, `blocked` 는 회색 점선 테두리) ②구간별 폭 표 ③순환·미존재 참조·`blocked` 후행 확인 결과 ④**의존 경로 없는 파일 겹침 쌍 목록** | 스크립트. **손으로 고치지 않는다** — 고쳐도 다음 실행에서 사라진다 | | `LOOP.md` 8절 | 규칙, 자원 충돌 **처리** 표(스크립트가 뽑은 쌍에 LLM 이 처리를 붙인 것), 슬롯·격리 표, 그리고 "그림은 `DAG.md`" 한 줄 | 이 스킬 | 사용자가 `DAG.md` 로 실행 순서를 검토한다. **코디네이터는 `DAG.md` 의 폭 표로 배정하지 않는다** — 폭 표는 세팅 때의 추정(같은 phase 안의 깊이로 묶은 단계)이고, 실제로는 선행이 끝나는 대로 태스크가 열린다. 배정은 **지금의 `completed` 집합으로 진입 차수를 다시 세서** 한다 — `dag-render --ready` 의 후보에 `LOOP.md` 8절 처리 표와 `DAG.md` 의 점선을 대어 고른다. 실전(ERP, 2026-09)에서 배정 지시문이 폭 표를 판단 입력으로 적어 두었더니, 선행이 다 끝난 태스크가 슬롯이 빈 채 3시간 가까이 배정되지 않았다(그중 절반 남짓은 1.10.0 이전의 상주 코디네이터가 잠든 시간이고, 나머지는 깨어 있는 코디네이터가 그 태스크를 열지 않은 시간이다). `PLAN.md` 3절의 phase 단위 그래프와 겹치지 않는다 — 그쪽은 phase 사이, 이쪽은 태스크 사이다. 태스크가 바뀌면 `loop-update` 가 **스크립트를 다시 돌린다.** 스크립트가 `harness_setup/scripts/` 에 있는 이유는 OS·런타임 기준이 거기 있고 `local-dev` 와 나란히 관리되기 때문이며, `harness-update` 는 이 파일을 구성 요소 표와 무관한 것으로 알고 지우지 않는다. 그리고 **DAG 를 만들 때 확인할 것**을 적는다. 스크립트가 확인 결과를 `DAG.md` 에 내고, 걸린 것이 있으면 세팅 때 사람이 본다. - **순환이 없는가** — 있으면 아무것도 실행 가능하지 않다. Layer 3으로 돌려보낸다(세팅 중이면 13단계 뒤 "앞 문서 반영"대로 `plan-generator` 10단계를 불러 고친다) - **없는 태스크 ID를 가리키는 `depends_on` 이 있는가** — 영원히 열리지 않는 노드가 된다 - **`blocked` 노드와 그 후행은 제외한다** — 후행까지 빼지 않으면 열리지 않을 태스크를 실행 가능으로 센다 - **동시 실행 후보 = 진입 차수 0 이고 `blocked` 가 아닌 노드** — 진입 차수는 `completed` 인 선행을 뺀 **지금의** 그래프로 센다(`--ready`) - **`files` 가 겹치는 두 태스크는 간선이 없어도 동시에 스케줄하지 않는다** — 의존 관계는 없지만 같은 파일을 고친다. Layer 3이 `files` 를 태스크마다 적어 둔 덕에 이건 기계로 판정된다 **마지막 규칙이 DAG 를 충돌 예방 수단으로 만드는 부분이다.** 순수한 의존 간선만으로는 "동시에 실행해도 된다"까지 말할 수 없다 — 의존이 없다는 것은 순서가 자유롭다는 뜻이지 자원이 안 겹친다는 뜻이 아니다. ### 간선을 세웠으면 폭도 잰다 DAG 를 만들었으면 **동시에 열리는 노드가 최대 몇 개인지, 구간마다 몇 개인지**를 함께 낸다. 간선만 확인하고 넘어가면 "병렬로 돌리자"가 근거 없는 결정이 된다. **폭이 실제 병렬도의 상한이기 때문이다.** 에이전트를 넷 띄워도 그 구간의 폭이 2면 둘은 논다. 그리고 폭은 대개 고르지 않다 — 앞쪽 phase 에서 넓어졌다가 뒤로 갈수록 좁아지고, 마지막 구간은 사실상 직렬인 경우가 흔하다. 첫 태스크는 거의 언제나 혼자 도는 병목이다. 그래서 `LOOP.md` 8절에 **구간별 폭과 권장 에이전트 수**를 표로 적는다(폭은 `dag-render` 가 `DAG.md` 에 낸 것을 옮겨 적고, 권장 수는 사용자 상한과 폭의 작은 쪽). 이 숫자가 없으면 "넷으로 돌리자"는 결정이 실제로 무엇을 얻는지 아무도 모르는 채로 내려지고, 그 결정에 딸려 오는 비용(환경 격리, 워크트리 관리, `STATE.md` 갱신 충돌)만 확실하게 발생한다. 세팅 때 묻는 "동시 에이전트 수 상한"은 이 표를 보여 주고 받는다. **이 표는 세팅 때의 추정이라 상한을 고르는 근거이지 배정의 근거가 아니다** — 배정은 `--ready` 다. ### 자원 충돌은 스케줄 전에 표로 확인한다 DAG 의 간선은 순서만 말한다. **간선이 없는 두 태스크가 동시에 돌아도 되는가**는 별도 검사이고, 그 결과가 없으면 코디네이터가 매번 다르게 판단한다. 그래서 배정 규칙과 별개로 **세팅 때 한 번 검사해 결과를 `LOOP.md` 8절에 표로 남긴다.** 검사는 셋이고 전부 frontmatter 로 기계 판정된다 (파일 겹침 쌍은 `dag-render` 가 뽑고, 나머지 둘은 같은 frontmatter 를 패턴으로 훑는다). | 검사 | 무엇을 보나 | 걸리면 | |---|---|---| | **파일 겹침** | 의존 경로가 없는 두 태스크의 `files` 에 같은 경로가 있는가 | DAG 에 점선을 긋고 **동시 금지**. 겹침이 우연이 아니라 같은 모듈을 나눠 만든 것이면 `알림(plan-generator, …):` 으로 의존 추가를 요청한다 | | **공유 런타임 자원** | `files` 에 마이그레이션·시드·스키마·환경 설정·포트 설정·CI 설정처럼 **여러 트리가 한 인스턴스를 나눠 쓰는 자원**이 있는가. 패턴은 `HARNESS.md` 2절 구성 요소 표에서 뽑는다(DB → 마이그레이션 폴더, 앱 → 포트·환경 파일) | 그 자원 이름으로 **자원 그룹**을 만들고 기본은 **그룹 직렬** — 같은 그룹끼리만 동시 금지, 다른 태스크와는 동시 가능. 격리 방식이 그 자원을 덮으면(슬롯별 DB 등) **격리가 덮음**으로 낮춘다 | | **`files` 가 비었는데 코드를 쓰는 태스크** | `files: []` 인데 수용 기준이 코드·화면을 말한다 | 무엇을 건드릴지 모르므로 **단독**(그 라운드에 혼자). `알림(plan-generator, …):` 으로 `files` 보완을 요청한다 | **처리 값은 셋이다** — `단독` / `그룹 직렬: <자원 그룹>` / `격리가 덮음`. 공유 자원을 전부 `단독` 으로 두면 마이그레이션 파일이 있는 태스크가 열 개인 phase 에서 폭이 1로 떨어진다. 마이그레이션 저널처럼 **파일 자체가 겹치는 것**은 그 그룹 안에서만 직렬이면 되고, 슬롯별 DB 가 있으면 런타임 충돌은 격리가 이미 덮는다. `단독` 은 무엇을 건드릴지 모르는 태스크에만 남긴다. 표의 열은 `태스크(쌍) / 자원 / 종류 / 자원 그룹 / 처리` 다섯이다. 결과가 0건이면 "0건"이라고 적는다 — 비어 있는 것과 안 한 것은 다르다. 코디네이터는 배정할 때 `--ready` 후보에 이 표와 `DAG.md` 의 점선만 대어 본다 — "돌고 있는 행과 같은 그룹이면 대기". **검사를 배정 때마다 다시 하지 않는다** — 태스크가 바뀌면 `loop-update` 가 다시 검사한다. ### 병렬에서 태스크는 코디네이터가 나눠 준다 단일 에이전트의 구현 지시문은 "실행 가능한 태스크 하나"를 세션이 스스로 고르게 한다. **병렬에서는 그러면 안 된다** — 워커 셋이 같은 DAG 를 읽으면 같은 진입 차수 0 노드를 집고, 워크트리가 갈린 순간 A가 쓴 `status: in_progress` 는 B의 트리에서 보이지 않아 선점 정보로 쓸 수 없다. 그래서 **스케줄러는 코디네이터**이고, 구현 지시문에 `태스크: ` 한 줄을 넣어 워커는 2절의 선택 규칙을 건너뛴다. 코디네이터가 `dag-render --ready` 의 후보(지금의 `completed` 집합으로 다시 센 진입 차수 0·`blocked` 아님) 중 현재 phase·자원 충돌 표에 안 걸림을 판정해 배정하고, 그 배정표(`STATE.md` 의 `Assignments` — ①-병렬과 ② 같다)가 병합 전까지 "무엇이 돌고 있는가"의 원본이다. **태스크 문서 `status` 도 병렬에서는 코디네이터가 자기 트리에서만 쓴다** — 배정할 때 `in_progress`, 병합 뒤 `completed`, 정지 때 `failed`. 워커가 자기 트리의 태스크 문서를 고치면 `failed` 뒤 "이어서"나 건너뛰기 경로에서 코디네이터 트리의 값과 워커 브랜치의 값이 병합 충돌을 낸다. 워커 브랜치는 **코드만** 바꾼다. "어긋나면 태스크 문서가 이긴다"는 코디네이터 트리에서 그대로 성립한다. 병렬에서 달라지는 것을 적는다. - **워커는 문서를 코디네이터 트리에서 읽는다** — `plan_setup/`(`STATE.md` 의 `Retry`·`Standing Decisions`·`Next Action`, 태스크 문서)과 `harness_setup/` 의 문서는 워커 트리의 것이 낡았을 수 있으므로 **코디네이터 트리 경로**(8절에 적는다)에서 읽는다. 코드·게이트 명령·`local-dev` 는 자기 트리에서 돌린다 - **워커는 0절의 쓰기 항목을 건너뛴다** — 불일치 맞추기(2번), 승인 만료 지우기(4번), `답:` 반영(5번), phase 를 열 때의 확인(7번)은 코디네이터의 일이다. 워커는 읽기만 한다. 차단 해소 절차도 코디네이터가 한다(3단계 "병렬에서는 코디네이터 자식이"와 같다) - **구현 워커의 출력은 블록이다** — 판정 블록처럼 형식을 고정하고 마지막 줄이 `END` 다. ``` TASK: STATUS: task_validation 대기 | task_validation 미활성 | e2e 대기 (격리 3) | failed: <사유> | 하네스 부족: <무엇이 없나> RETRY: <게이트 / 흐름·검사 ID / 분류 / 무엇을 했나> (구현 세션 안의 사이클마다 한 줄. 없으면 "없음") LOG: (여러 줄 가능) END ``` 코디네이터가 `RETRY` 줄을 그 태스크의 `Retry History` 로, `LOG` 줄을 `LOG.md` 로 옮긴다. 이것이 없으면 구현 세션 안에서 쓴 재시도가 기록에 남지 않아 "두 태스크에 걸친 같은 실패"를 잡을 수 없다. **`END` 가 없으면 워커가 도중에 죽은 것** — 판정 파일과 같이 다룬다(`History` 에 `구현 중단`, 행 유지, 두 번째면 `질문:`) - **`harness-update` 는 코디네이터만 자기 트리에서 부른다** — 두 슬롯이 각자 `HARNESS.md` 를 고치면 병합에서 충돌한다. 워커는 시작 전 점검(10단계)에서 부족을 발견하면 구현하지 않고 `STATUS: 하네스 부족` 으로 종료하고, 코디네이터가 ①이면 갱신 뒤 행을 `구현` 으로 되돌리고 ②③이면 `질문:` 이다. 완료 후 점검은 원래 반영 토막의 일이라 코디네이터가 한다 - **정지 행은 슬롯을 놓는다 — 미커밋 변경은 park 브랜치로**(1.15, 실전 2026-10-13). 3회 소진·판정 불가·중단 두 번째·워커 질문으로 `정지` 된 태스크의 미커밋 변경은 그 슬롯 트리에 있다. 1.14 까지는 그 행이 슬롯을 쥔 채 사람을 기다렸고, 답이 1시간 반 늦자 동시 3 중 하나가 그동안 놀았다. 그래서 코디네이터가 행을 `정지` 로 만드는 라운드에 **자기 문서를 커밋한 뒤** 그 슬롯 트리에서 `git add -A` → `git branch -f park/ $(git commit-tree $(git write-tree) -p HEAD -m "park — 미검증. 병합하지 않는다")` → 워커 트리면 `git reset --hard coordinator && git clean -fd`, 슬롯 1 이면 `git reset --hard HEAD && git clean -fd`. `commit-tree` 는 어느 브랜치도 움직이지 않고 커밋 훅도 돌지 않는다 — park 커밋은 기본선이 아니라 보관함이다(어느 브랜치에도 병합하지 않는다). 슬롯 1 에서 문서를 먼저 커밋하는 이유는 `git add -A` 가 코디네이터의 미커밋 문서까지 담기 때문이다. 행은 `Assignments` 에 `정지` 로 남고 워커 칸이 `park/` 가 된다 — **빈 슬롯으로 세어 다른 태스크를 배정하되, 충돌 검사에서는 돌고 있는 행으로 친다**(파일 겹침·자원 그룹·`단독`). 되살릴 변경이 그 파일 위에 있기 때문이다. 사람의 답이 "이어서" 면 빈 슬롯이 생기는 대로 **새 배정보다 먼저** 되살린다 — 재배정 청소 뒤 그 슬롯에서 `git cherry-pick --no-commit park/ && git reset -q`(변경이 다시 미커밋으로 놓인다), 행을 그 슬롯의 `수정` 으로(`Cycle 0`, 슬롯 1 이면 문서를 먼저 커밋한다). 빈 슬롯이 없으면 `답:` 을 지우지 않고 다음 라운드에 다시 본다. 되살리기가 충돌하면(그사이 병합된 태스크가 같은 줄을 고쳤다) 그 슬롯을 같은 청소 명령으로 비우고 전역 질문 `질문: 병합 충돌 park/ — …` 이다 (`--no-commit` 이라 `cherry-pick --abort` 는 없다). "건너뛰라" 면 슬롯은 이미 비어 있으므로 `git branch -D park/` 와 행 삭제만이다(`files` 되돌리기의 병렬판). park 브랜치는 그 태스크가 완료되거나 건너뛰어질 때 지운다. (2026-10-13 실제 저장소로 확인 — 수정·새 파일·삭제가 앞으로 나아간 코디네이터 위의 다른 슬롯에서 그대로 되살아나고, 무시 파일(`node_modules`)은 슬롯에 남아 재사용된다.) 단일(①-단일)에는 놓을 슬롯이 없으므로 이 절이 없다 - **완료는 한 번에 하나씩** — 원본의 "한 번에 하나의 Task만 완료 상태로 전환"은 병렬에서도 지킨다. 코디네이터는 도착한 판정을 순서대로 하나씩 병합·완료한다 - **그림에 없는 태스크는 배정하지 않는다** — `DAG.md` 에 없는 태스크 문서가 있으면(`plan-generator` 가 더한 뒤 `dag-render` 를 아직 안 돌린 것) `알림(loop-update, ~다음 phase 전): DAG 에 없는 태스크 ` 를 남기고 그 태스크는 건너뛴다. 충돌 검사를 안 거친 태스크를 동시에 돌리지 않기 위해서다 - **②의 코디네이터는 상주하지 않는다** — 판단은 라운드마다 새로 여는 코디네이터 자식(①-병렬과 같은 지시문)이 하고, 기다리기는 구동 스크립트가 한다(7단계 "②의 모양"·`references/orca.md` 1절). 그래서 phase 경계에서 코디네이터를 교체할 일이 없다 — 스크립트는 `Phase Overview` 전부 `completed` 까지 반복하며 phase 경계를 그냥 지나고, 판단 자식은 매 라운드 빈 컨텍스트로 시작하므로 앞 phase 의 대화가 새지 않는다(2026-09-23 변경. 그전에는 대화형 코디네이터가 phase 내내 살아 있다가 종료 직전에 다음 phase 코디네이터를 새 터미널로 띄웠는데, 상주 세션이 전체 토큰의 30%를 쓰고 터미널 생성이 반복해 시간 초과로 실패했다). 다음 라운드에 남길 말은 `STATE.md` `Next Action` 의 `알림:` 과 `LOG.md` 뿐이다. 루프가 멈추는 조건은 둘 — 답 없는 `질문:` 때문에 더 할 일이 없을 때(전역 질문이면 돌던 워커가 끝난 뒤, 태스크 질문뿐이면 그 밖의 행이 다 끝난 뒤), `Phase Overview` 가 전부 `completed` 일 때 - **질문의 범위 — 태스크 질문은 그 행만 세운다**(1.14, 실전 2026-10-12). 병렬에서 질문 하나가 루프 전체를 세우면, 그 질문과 무관한 슬롯이 성공해 남긴 결과까지 반영·검증되지 않은 채 사람을 기다린다 (단일 실행 시절의 "질문이 있으면 멈춘다" 를 병렬로 그대로 옮긴 것이었다). 그래서 질문을 둘로 가른다 — 머리로 가른다, 스크립트는 판단하지 않는다. **태스크 질문** `질문: <태스크 ID> — …` — 그 태스크 하나에 걸린 것: 3회 소진·판정 불가·중단/잘림 두 번째·워커가 물었는데 문서로 답할 수 없는 것. 그 행은 `정지` 로 서고 슬롯은 놓으며(미커밋 변경은 park 브랜치로 — 위), 그 태스크에 기대는 태스크는 `--ready` 에 안 나오며, phase 는 그 태스크가 끝나기 전에 완료되지 않는다 — 나머지는 평소대로 반영·검증·배정된다. **전역 질문** — 태스크 ID 로 시작하지 않는 머리: 병합 충돌·반복 실패(같은 검사 ID)·phase 완료 묶음·미활성 승인·하네스 ②③·환경 문제 두 번째·만료된 알림·판단 시점이 온 미결. 여러 태스크나 공유 상태에 걸리므로 지금처럼 새로 띄우기를 멈춘다. 머리를 잘못 쓰면(전역 문제를 태스크 질문으로) 루프가 그 문제 위에서 계속 돈다 — 애매하면 전역으로 쓴다. 사람이 답하면 다음 코디네이터 자식이 0절 5번대로 반영한다 — 스크립트가 도는 중이면 `답:` 만 적으면 되고, 멈췄으면 다시 띄운다(②면 오르카에 뜬 답하기 탭이 둘 다 해 준다 — `references/orca.md` 1절 "②의 답하기") - **②에서 달라지는 것** — 구동 터미널과 분리 실행, 워커별 에이전트, 워커 `ask` 의 포그라운드, Run 단위 대기·생존 확인·결과 파일 반영 순서는 `references/orca.md` 1절 "병렬 운용에서 ② 가 다른 것" - **넓힌 `Last Verification`·`Retry` 에서 어느 블록을 읽는가** — 지시문의 `태스크: ` 가 그 답이다. 코디네이터가 그 ID의 블록만 쓰고, 워커는 읽기만 한다. 워커가 단일 모드 지시문대로 `STATE.md`·`LOG.md` 를 쓰면 필자 규칙이 깨지므로 병렬 지시문에 "쓰지 말라"를 덧붙인다. ②의 워커는 표준 출력을 넘길 수 없으므로(대화형 에이전트다) **출력 블록·판정 블록을 `(임시)/done-.txt`·`verdict-.txt` 에 직접 쓰고** `worker_done --report-path <그 파일>` 로 끝낸다. `worker_done` 본문은 세 문장 요약이라 블록을 담을 자리가 아니다. 그다음은 ①-병렬과 똑같은 반영 절차다 ### 슬롯과 워크트리 — `main` 은 비워 두고, 워크트리는 필요한 자리에만 원본의 다섯 규칙을 그대로 살린다. 동시에 일하는 에이전트는 독립 워크트리에서 일하고, 남의 워크트리를 고치지 않고, 공통 파일을 건드릴 때는 충돌 가능성을 확인하고, 태스크 완료 후 커밋하고, merge·rebase 는 orchestration 의 책임이다. **이 문서에서 `main` 은 저장소의 기본 브랜치를 뜻한다.** 이름이 `master`·`develop`·`trunk` 면 그 이름이다. 세팅 때 한 번 알아내 8절에 실제 이름으로 적고, **명령은 전부 그 값을 쓴다** — `git symbolic-ref --short refs/remotes/origin/HEAD`(원격 기본), 없으면 `git branch --show-current`, 둘이 다르거나 애매하면 사용자에게 묻는다. 이름을 박아 두면 `master` 저장소에서 워크트리 생성·병합·fast-forward 가 전부 "브랜치 없음"으로 실패한다. **`main` 에서는 아무도 작업하지 않는다.** `main` 은 사용자의 자리다 — 완료·검증된 상태만 있고, 사용자가 진행 중에 따로 살펴보거나 손대는 곳이다. 루프의 자리는 코디네이터 트리 하나이고, 워크트리는 **동시에 코드를 쓰는 워커가 둘 이상일 때 둘째부터**만 더 만든다. 처음 설계는 첫 워커를 `main` 에 두었는데, 그 대가로 `main` 이 작업 중 파일로 더러워지고 사용자가 중간에 쓸 자리가 없었다(사용자 결정 2026-09-14). 오르카 화면에서 카드는 워크트리 단위라 코디네이터와 `worker-1` 은 한 카드에 보인다 — 그래도 된다. **슬롯은 워커의 자리이고, 워커 이름은 워크트리 유무와 무관하게 `worker-N` 이다.** 태스크마다 워크트리를 만들지 않는다 — 슬롯 2 이상만 워크트리이고, 슬롯 수(= 동시 에이전트 수 상한)만큼 만들어 재사용한다. | 트리 | 브랜치 | 누가 쓰나 | |---|---|---| | `main` | `main` | **사용자.** 루프는 완료된 태스크를 fast-forward 로 넣기만 한다 | | **코디네이터 트리** | `coordinator` | 코디네이터(문서의 유일한 필자 — 7단계·8절 표의 예외 밖, 배정, 병합, `harness-update`, phase 판정. 완료 묶음은 구동 스크립트가 이 트리에서 돌린다 — 9단계) **그리고 슬롯 1 의 워커 `worker-1`.** 폭이 1인 구간은 이것뿐이라 워크트리 0개. `files` 가 빈 비코드 태스크도 여기 | | **`worker-N`** (N = 2 … 상한) | `worker-N` | 같은 라운드에 **동시에 코드를 쓰는** 둘째 이후 워커. 코디네이터 트리에서 분기 | | (새로 없음) | 구현이 돈 자리 | **검증 워커** — 구현이 돈 트리의 앱을 봐야 하므로 같은 슬롯에서 **이어서**(동시가 아니라 순서대로) 돈다 | | (트리 없음) | `park/<태스크 ID>` | **정지 행이 놓은 슬롯의 미커밋 변경 보관함**(11단계 "정지 행은 슬롯을 놓는다"). 아무 트리에도 체크아웃하지 않고 병합하지 않는다. "이어서" 면 빈 슬롯에 되살리고, 그 태스크가 완료되거나 건너뛰어지면 지운다 | 코디네이터 트리는 세팅 때 `main` 에서 만든다. 워커 트리는 **폭이 2 이상인 구간에 들어갈 때** 코디네이터 트리에서 분기해 만들고, 태스크가 끝나면 지우지 않고 다음 배정에 재사용한다. **재배정 직전에 코디네이터가 청소한다** — 워커 트리는 `git reset --hard coordinator && git clean -fd`(자기 `worker-N` 브랜치를 코디네이터 브랜치 끝으로 맞추고 untracked 제거. `git checkout coordinator` 는 쓰지 않는다 — `coordinator` 브랜치는 코디네이터 트리에 체크아웃돼 있어 다른 워크트리에서는 git 이 거부한다. 병합이 끝난 뒤라 버려지는 커밋은 없다. `-x` 는 쓰지 않는다 — `node_modules`·빌드 캐시가 재사용의 이유다), 슬롯 1(코디네이터 트리)은 코디네이터가 자기 문서를 커밋한 뒤 `git clean -fd`. 코디네이터와 `worker-1` 이 같은 트리를 쓰므로 **코디네이터는 `git add -A` 를 쓰지 않고 문서 경로만 add 한다** — 워커의 작업 중 파일이 문서 커밋에 딸려 들어가면 안 된다. 브랜치만 되돌리면 앞 태스크가 만들다 만 파일이 남아 다음 워커의 lint 가 그것을 잡거나 자기 것으로 오인한다. 폭이 줄어 안 쓰는 워커 트리는 지워도 되고 두어도 된다 — 두면 환경 한 벌이 남아 있으므로 `LOOP.md` 에 어느 쪽인지 적는다. ②의 워커가 어느 트리에 뜨는지(`--worktree path:`)와 사이드바에 보이는 이름은 `references/orca.md` 1절 "병렬 운용에서 ② 가 다른 것". **`main` 반영은 태스크 완료마다다.** 워커 트리에서 커밋 → 코디네이터 트리에 병합 → `main` 을 코디네이터 브랜치로 fast-forward. `main` 은 항상 "검증된 최신"이라 사용자가 중간에 봐도 반쯤 된 것이 없다. **사용자가 `main` 에 직접 커밋했으면** 코디네이터가 라운드 시작 때 `git merge <기본 브랜치>` 을 자기 트리에 먼저 한다 — 충돌이면 `질문:`. fast-forward 가 안 되는 상태(사용자 커밋을 아직 안 받았다)면 병합 뒤 다시 시도하고, 그래도 안 되면 `질문:`. **단일 모드(병렬 끔, ①-단일)도 `main` 에서 돌지 않는다** — 코디네이터 트리(`coordinator`) 하나에서 세 토막이 돌고, 반영 토막이 완료마다 `main` 을 fast-forward 한다. **슬롯 경로는 세팅 때 정하고 8절 슬롯 표에 적는다.** 기본은 형제 폴더 `../<저장소>-coordinator`·`../<저장소>-worker-N` 이지만, **저장소가 동기화 폴더 아래이면 그 기본은 틀린다** — OneDrive·Dropbox·iCloud·Google Drive 같은 경로에서는 형제 폴더도 동기화 대상이라 슬롯마다 `node_modules` 한 벌이 올라가고 파일 잠금이 난다. 저장소 경로에 그런 흔적이 있으면 **저장소 안 `.wt/coordinator`·`.wt/worker-N`**(`.gitignore` 에 `.wt/` 를 더한다. 저장소 안의 워크트리는 git 이 허용한다) 또는 **동기화 밖 경로**를 제안하고, 어느 쪽이든 사용자가 고른 값을 적는다. 이건 플러그인이 아니라 환경의 문제라 세팅 때 한 번 묻고 끝낸다. 슬롯마다 의존성이 한 벌씩 생기는 것은 감수한다 — 패키지 저장소 공유(pnpm store 등)는 하네스 쪽 결정이라 보고에만 올린다. **병합은 코디네이터(orchestration)의 일이다.** PASS 뒤 워커 트리에서 커밋하고 코디네이터 트리에 병합한 뒤 `main` 을 fast-forward 한다. 충돌이 나면 코디네이터가 풀지 않고 `질문:` 이다 — 충돌은 자원 충돌 검사가 놓친 겹침이므로 사람이 보고 표를 고쳐야 한다. 여기에 앞에서 정한 것을 잇는다 — **각 슬롯은 자기 세션이므로 `task_validation` 분리는 여전히 필요하다.** 같은 슬롯의 같은 에이전트가 구현하고 판정하면 병렬로 돌든 아니든 이 게이트는 이름만 남는다. ### 병렬에서 `STATE.md` 가 꼬이는 두 가지와 그 해법 **`STATE.md` 는 하나인데 에이전트는 넷이다.** 여기서 서로 다른 문제가 둘 나오고, 해법도 다르다. **(가) 칸이 모자란다.** `Current Task` 는 값 하나, `Last Verification` 은 "지금 상태 한 줄", `Retry` 는 태스크 하나를 담게 되어 있다. 넷이 동시에 돌면 **넣을 자리가 없다.** → 그 절들을 **태스크별 블록이나 목록으로 넓힌다.** 넓힐 때 **기존 값을 새 형식으로 옮긴다** — 지우면 진행 중인 재시도 이력이 사라지고 그 태스크는 앞의 시도를 처음부터 반복한다. **(나) 동시에 쓰다가 서로 덮어쓴다.** 칸을 나눠 줘도 **파일 쓰기는 통째로** 일어나므로, "각자 자기 줄만 고친다"로는 여전히 잃는다. **잠금으로 풀지 않는다** — 잠근 채로 죽은 에이전트가 전체를 세운다. → **필자를 하나로 줄인다.** | | 단일 에이전트 | 병렬 | |---|---|---| | 태스크 문서의 `status` | 에이전트가 쓴다 | **코디네이터만 자기 트리에서** — 배정 시 `in_progress`, 병합 뒤 `completed`, 정지 시 `failed`. 워커 브랜치는 코드만 바꾼다 | | `STATE.md` | 에이전트가 쓴다 | **orchestration(코디네이터)만 쓴다** — 예외: 질문 답변 자식(②)은 `질문:` 한 줄, 답하기 탭(②)은 사람의 `답:` 한 줄, 구동 스크립트는 자식 사이에 `Assignments` 토막 값만(격리 3 의 e2e 결과, 7단계) | | `LOG.md` | 에이전트가 쓴다 | **orchestration 만 쓴다** — 워커는 출력 블록(`LOG:` 줄·판정 블록)을 넘기고, 병합 순서대로 append 된다 | | 하네스 문서 | 에이전트가 `harness-update` 로 | **코디네이터만 자기 트리에서** — 워커는 `하네스 부족` 으로 돌려보낸다 | **이 분업이 맞는 이유가 있다.** 워크트리가 갈리면 같은 파일을 두 트리가 고치는 순간 병합 충돌이 생기고, 문서 파일은 코드와 달리 "어느 쪽이 맞는가"를 병합이 판단할 수 없다. 그래서 **문서는 전부 코디네이터 트리의 한 필자**, 슬롯은 코드만이다. 워커가 중간에 죽어도 배정표와 워커 트리의 미커밋 변경이 남아 있어 코디네이터가 같은 슬롯에서 이어 가게 할 수 있다 — **세션이 끊겨도 이어지게 만든 장치가 여기서 그대로 쓰인다.** 루프의 "어긋나면 태스크 문서가 이긴다"(8단계)는 코디네이터 트리 안에서 그대로 성립한다. `STATE.md` 가 실시간이 아니게 되는 것은 감수한다. 실시간인데 틀린 요약보다 조금 늦고 맞는 요약이 낫고, 지금 무엇이 도는지가 급하면 **태스크 문서를 보면 된다** — 거기가 원본이다. **`Current Task`·`Last Verification` 은 Layer 3이 만든 절이다.** 넓히는 것은 정당하지만 **Layer 3이 초기화한 형식을 바꾸는 일이므로 보고에 올린다** — 조용히 바꾸면 `plan-generator` 의 형식 규칙과 실제 파일이 갈라지고, 다음에 누가 맞는지 아무도 모른다. ### 환경 격리는 병렬이면 세팅 때 정한다 **DAG 와 자원 충돌 표가 덮는 것과 덮지 못하는 것이 다르다.** - **덮는다** — 실행 순서(`depends_on`), 파일 충돌(`files` 겹침), 공유 자원 파일을 가진 태스크의 그룹 직렬 - **덮지 못한다** — 실행 중인 앱과 DB 같은 **공유 런타임 인스턴스.** 서로 간선도 파일 겹침도 없는 두 태스크가 각자의 e2e 로 같은 DB 에 데이터를 만들고 지운다. `local-dev` 의 초기화는 전역이다 포트는 겹치면 뜨지 않아 즉시 드러나지만 **DB는 조용히 오염된다.** 그래서 폭이 2 이상인 구간에 들어가기 전에 반드시 닫혀 있어야 한다. Layer 4가 이 문제를 여기로 넘겼고, 하네스는 **포트·DB 이름을 환경변수에서 읽게 해 두는 것**까지 했다. 병렬이 기본이므로 **세팅 때 묻고 정한다.** 선택지 셋과 되돌리기 비용은 이렇다. | 선택지 | 무엇 | 되돌리기 비용 | |---|---|---| | **1. 슬롯별 환경 통째 분리** | 슬롯 번호로 DB·포트·컨테이너 이름을 파생(`SLOT=2` → `PORT=3002`, `DB=app_slot2`). 완전 격리, 자원 N벌 | `local-dev` 가 슬롯 환경변수를 받아야 한다 — 안 받으면 `harness-update` | | **2. DB 서버 하나 + 슬롯별 데이터베이스·전용 계정** | 가볍다. `local-dev` 에 권한 발급 단계가 붙는다 | 같은 `harness-update`. 앱 포트는 여전히 슬롯별 | | **3. 격리 없이 앱을 쓰는 게이트만 직렬화** | 새로 만들 것이 거의 없다. 병렬 이득이 **구현·lint·unit 까지**이고, e2e 와 검증은 앱이 하나라 **슬롯 순서대로** 돈다(아래 "격리 3의 앱 전환") | 없음. 나중에 1·2로 올릴 수 있다 | **추천을 붙여 묻는다 — 테스트가 DB 를 쓰면 1·2, 안 쓰면 3.** `HARNESS.md` 2절의 "공유 DB 를 쓰는 게이트" 줄을 본다(없으면 unit 정의서에 DB 에 붙는 통합 테스트가 있는지, e2e 가 DB 를 쓰는지 직접 확인하고 그 줄이 없다는 것을 보고한다). 그렇다면 격리 3의 "되돌리기 비용 없음"은 착시다 — 격리 3에서도 **lint·unit 은 병렬로 돈다**(앱 전환은 e2e 부터다). 그래서 두 슬롯의 통합 unit 이 한 DB 에 동시에 데이터를 만들고 지우고, 제약을 떨어뜨렸다 붙이는 테스트가 있으면 서로의 스키마까지 바꾼다. 실전(ERP, 2026-09)에서 격리 3을 "나중에 재검토"로 시작했다가 phase-11 까지 같은 흔들림이 반복됐고, 그 비용은 `local-dev` 에 슬롯 번호로 DB 이름을 파생하는 분기 하나보다 훨씬 컸다. 3을 고르는 것이 맞는 경우는 테스트가 공유 DB 를 쓰지 않을 때(순수 unit, e2e 만 DB)이고, 그때도 `option.description` 에 "통합 테스트가 생기면 1·2로 올린다"를 적는다. 사용자가 DB 를 쓰는데도 3을 고르면 받아들이되, **9절에 판단 시점을 확인 가능한 형태로 적는다**(아래 "미결의 `언제`"). 정한 것을 `LOOP.md` 8절에 **슬롯별 환경변수 값 표**로 적는다(슬롯 / 포트 / DB 이름 / 환경 파일). 워커는 그 표의 값을 `local-dev` 에 넘긴다. **`harness_setup/scripts/` 가 그 변수를 읽는지 확인한다** — 읽지 않으면 격리는 정했지만 적용되지 않은 상태이므로, 9절 미결에 **`격리 적용 대기 — harness-update 필요`** 로 적고 보고에 올린다. 첫 태스크는 거의 언제나 폭 1이라 그때까지는 문제가 없지만, 폭 2 구간에 들어가기 전에 닫혀 있지 않으면 코디네이터는 그 구간을 폭 1로 낮춰 돌고 `알림(harness-update, ~다음 phase 전): 격리 적용 대기` 를 남긴다. **격리 3의 앱 전환과 e2e 토막.** 격리 3은 포트가 하나라 실행 중인 앱이 **어느 워커 트리의 코드인지**가 문제가 된다. 두 군데서 부딪힌다 — 검증 워커는 자기 슬롯의 앱을 봐야 하는데 다른 슬롯의 앱이 떠 있고, **구현 워커 안의 e2e 게이트**도 앱을 쓰므로 구현 워커 둘이 동시에 e2e 를 돌리면 같은 포트에서 충돌한다. "e2e·검증은 차례로"라고만 적으면 e2e 가 구현 토막 안에 있다는 사실과 어긋난다. 그래서 격리 3에서는 구현 토막을 e2e 앞에서 자른다. - **구현 워커는 `lint`·`unit` 까지만 돌리고 `STATUS: e2e 대기` 로 끝낸다.** e2e 를 돌리지 않는다 - 구동 스크립트(①·② 같다)가 `e2e 대기` 행을 **슬롯 순서대로 하나씩** 처리한다 — ①실행 중인 앱을 `local-dev -Down` ②그 워커 트리에서 `local-dev` ③**e2e 게이트 명령을 직접 돌린다** (종료 코드라 판단이 없다 — 스크립트가 해도 원칙에 맞는다) ④통과면 행을 `검증` 으로 바꾸고 바로 검증 자식을 띄운다, 실패면 행을 `수정` 으로 되돌리고 e2e 출력을 `(임시)/e2e-.txt` 에 남긴다(분류는 수정 자식이 그 파일을 읽고 하고, `Retry History` 에는 수정 자식의 `RETRY:` 줄을 코디네이터가 옮긴다. 스크립트는 `Retry` 를 쓰지 않는다) - 검증 행도 같은 전환을 거친다 — 앱이 그 슬롯의 것이 아니면 `-Down` 후 다시 띄운다. 전환은 라운드 안에서 슬롯 순서를 지키므로 e2e 뒤에 바로 검증이 오면 전환이 없다 - **②에서는 검증 워커가 비동기로 돈다** — ①-병렬처럼 자식이 끝나기를 기다리며 차례로 도는 것이 아니라 띄우고 나면 스크립트가 다음 줄로 넘어가므로, 같은 규칙을 명시해야 한다. 검증 워커는 **띄우기 직전에 그 슬롯으로 앱을 전환하고, 한 번에 하나만** 띄운다. 그 워커가 도는 동안 스크립트는 e2e·앱 전환·phase 완료 묶음을 하지 않고 **코디네이터 자식도 미룬다**(코디네이터의 `local-dev` 가 앱을 바꾼다). **`worker_done` 처리(결과 파일 확인·release·dispatch 파일 삭제)와 ack 는 미루지 않는다** — 오르카는 ack 안 된 배달 뒤의 메시지를 주지 않으므로, ack 를 미루면 그 검증 워커의 질문까지 막혀 교착한다. 자식을 미루는 동안 끝난 구현 워커의 결과 파일은 남아 있고, 행 띄우기는 결과 파일이 남은 행을 건너뛰므로 다시 뜨지 않는다 - 격리 1·2에서는 이 절이 없다. 슬롯마다 앱이 따로 떠 있어 구현 토막이 e2e 까지 한 번에 돈다 즉 격리 3의 병렬은 **정직하게 구현·lint·unit 까지**다. e2e 가 긴 프로젝트면 이득이 작으므로 세팅 때 그 사실을 `option.description` 에 적는다. **사용자가 병렬을 끄면** 이 절은 미결로 남긴다. 종류는 `유예`, 기본값은 단일, 판단 시점은 병렬을 켜기 직전(`loop-update`). 선택지는 위 셋 그대로다. **미결 번호는 앞 문서에서 이어진다.** PRD 8절·TRD 12절·아키텍처 overview.md 9절 중 마지막 OQ 다음 번호를 쓴다 (플랜과 하네스에는 미결 절이 없다). 이것이 이 스킬이 앞 문서를 **읽는** 유일한 순간이고(13단계 뒤 "앞 문서 반영"에서 사용자가 정한 변경을 그 문서의 스킬로 반영하는 것은 별개다), 보는 것은 **그 세 표의 마지막 행 번호**뿐이다 — 1단계의 "두 곳만 읽는다"는 요구사항을 해석하지 않는다는 뜻이지 번호 하나를 확인하는 것까지 막지 않는다. 레이어마다 `OQ-001` 로 다시 시작하면 어느 문서의 미결인지 구별되지 않는다. **미결의 `언제` 는 루프가 확인할 수 있는 시점으로 적는다** — `phase-03 진입 전`·`phase-02-task-05 완료 후`·`폭 2 구간 전` 처럼. 루프는 phase 를 열 때 이 열을 보고(0절 7번), 시점이 왔으면 `질문:` 으로 올린다. "e2e 가 길어지면" 같은 조건은 그 조건을 볼 시점을 함께 적는다. 실전에서 "phase-03 전에 재검토"로 적힌 격리 미결이 아무도 확인하지 않아 기본값으로 굳었다 — 적어 두기만 한 판단 시점은 없는 것과 같다. --- ## 12단계. 파일 쓰기 ``` project_name/loop_setup/ ├── LOOP.md └── DAG.md ← dag-render 의 출력. 손으로 고치지 않는다 project_name/harness_setup/scripts/ ├── dag-render.* ← 이 스킬이 만드는 생성 스크립트 (OS·런타임 기준은 local-dev 와 같다) ├── loop-drive.* ← 구동 스크립트. 4절의 ①/② 알고리즘을 옮긴 것. 판단하지 않는다 ├── loop-watch.* ← ②의 밖의 감시. 4절 "밖의 감시"(references/orca.md 2절)를 옮긴 것. 읽기만 하고 알림만 한다(질문이 생기면 답하기 탭을 띄운다) ├── loop-status.* ← ②의 상태판. 4절 "상태판"(references/orca.md 2절)을 옮긴 것. 읽기만 하고 사람에게 보여 주기만 한다 └── loop-answer.* ← ②의 답하기. 4절 "답하기"(references/orca.md 2절)를 옮긴 것. 사람이 입력한 답: 한 줄만 쓴다 ``` **원본이 그린 구조는 `LOOP.md` 하나이고, 규칙은 여전히 그 한 파일에만 있다.** `DAG.md` 는 규칙이 아니라 **생성물**이라 예외다(11단계, 사용자 결정 2026-09-14) — 매 세션이 읽는 문서에 10KB 그림을 넣지 않기 위해서다. 루프 규칙을 다른 파일로 쪼개지 않는다 — 흩어져 있으면 일부만 읽고 시작하게 된다. `dag-render` 는 쓰고 나서 **실제로 돌려 `DAG.md` 를 만들어 본다.** 돌지 않는 스크립트는 그림이 없는 것과 같다. `--ready` 도 한 번 돌려 첫 태스크가 후보로 나오는지 본다. **`loop-drive` 도 이 스킬이 쓰고 돌려 본다**(2026-09-23 추가). 구동기를 `LOOP.md` 의 의사 코드로만 두면 첫 실행 때 누군가 그 자리에서 스크립트를 지어내고, ②는 오르카 CLI 의 JSON(배달 ID·디스패치 ID·`--ack`)을 다뤄야 해서 지어낸 스크립트가 가장 잘 틀리는 곳이다. `dag-render` 와 같은 자리·같은 런타임 기준으로 둔다. 오르카 응답의 모양(JSON 봉투·결과 필드)은 지어내지 않는다 — `references/orca.md` 4절. Node 로 쓰면 **파일 지우기는 `fs.unlinkSync`, 폴더 복사·삭제는 직접 돈다** — Node 24·Windows 에서 경로에 한글 같은 비ASCII 가 있으면 `fs.rmSync` 는 오류 없이 아무것도 지우지 않고(단일 파일도), 재귀 `fs.cpSync` 는 프로세스를 메시지 없이 죽인다(2026-10-01 확인). dispatch·결과 파일이 지워지지 않으면 끝난 워커를 도는 것으로 보고 루프가 영원히 기다린다. - **①은 드라이런** — `STATE.md` 를 읽어 이번 라운드에 띄울 명령을 출력만 하고 끝나는 옵션을 두고 한 번 돌린다 - **묶음 모드도 한 번 돌려 본다**(①·② 모두) — 임시 폴더를 시험용으로 바꾸고 `--bundle phase-00 --commit ` 를 실제처럼 창 없이 띄운다. 묶음 명령 절이 비어 있으면 그대로(`EMPTY` 가 나와야 한다), 아니면 무해한 명령 한 줄짜리 시험 절을 읽게 해서. 창이 뜨지 않는지, 자식 환경에 `ORCA_*` 가 없는지, `bundle-phase-00.pid` 를 스스로 만들고 지우는지, 결과 파일이 임시 이름에서 바꿔 달리고 줄 모양(`COMMIT`·`EXIT`/`EMPTY`·`LOG`·`END`)이 4절과 같은지, `loop-exit.txt`·단계·생존 파일을 건드리지 않는지 본다. 이것을 안 보면 첫 phase 끝에서 묶음이 거부되거나 밖의 감시가 루프를 멈춘 것으로 읽는다 - **②는 스모크 테스트** — `references/orca.md` 4절대로 시험 워커 하나를 끝까지 돌린다(분리·터미널 닫기·상태판·답하기 탭·시작 화면·[워커 정리] 포함). 결과를 8절 "스모크 테스트"에 적는다 그리고 `plan_setup/STATE.md` 에 **6단계에서 정한 두 절(`# Retry`, `# Standing Decisions`)을 더한다.** 병렬(①-병렬)이면 **`# Assignments`** 도 더하고 `Current Task`·`Last Verification`·`Retry` 를 태스크별 블록으로 넓힌다(11단계 (가) — Layer 3 절을 바꾸므로 보고에 올린다). 기존 다섯 절은 건드리지 않고 `Last Verification` 뒤에 끼운다. `Retry` 는 비운 초기 상태로 두고, **`Standing Decisions` 에는 이 스킬이 8단계에서 사용자에게 물어 받은 미활성 승인을 게이트별로 범위·기한과 함께 적어 넣는다.** 비워 두면 첫 태스크에서 모든 게이트가 미활성이라 구동기가 곧바로 멈춘다 — 세팅 때 한 번 묻는 이유가 그것이다. **파일을 쓰고 `main` 에 커밋한 뒤 코디네이터 트리를 만든다** — `git worktree add <8절 경로> -B coordinator <8절 기본 브랜치>`. 워커 트리(둘째부터)는 코디네이터가 폭 2 구간에 들어갈 때 만든다. 커밋 전에 만들면 코디네이터 브랜치에 `LOOP.md` 가 없다 — 워크트리는 커밋된 것만 본다. 오르카가 있으면 카드 이름을 `coordinator` 로 둔다. **`loop_setup/LOOP.md` 가 이미 있으면 덮어쓰기 전에 읽는다.** phase 가 진행되며 `loop-update` 가 규칙을 조정해 두었다면 그게 사라진다. 갱신이 목적이면 이 스킬이 아니라 `loop-update` 다. ### `CLAUDE.md` · `AGENTS.md` 갱신 `loop_setup/LOOP.md` 를 쓴 뒤 프로젝트 루트의 `CLAUDE.md` 와 `AGENTS.md` 를 **같은 내용으로** 갱신한다. 사용자가 직접 연 세션과 루프의 자식 세션은 이 스킬을 읽지 않고 시작하는데, 그때 자동으로 읽히는 파일은 Claude Code 는 `CLAUDE.md`, Codex 는 `AGENTS.md` 뿐이다. 그래서 **지금 어느 호스트에서 돌든 둘 다 쓴다** — 한쪽만 쓰면 다른 호스트의 세션이 규칙을 모른 채 시작한다. 내용은 **어느 세션에서든 반드시 지킬 것**만 두고 세부 규칙은 문서를 가리킨다 — 규칙을 옮겨 적으면 두 곳이 곧 어긋나고, 어긋나면 세션은 짧은 쪽을 믿는다. - 둘 다 없으면 아래 뼈대로 만든다. 있으면 **`## 루프 (loop_setup/LOOP.md)` 절과 `현재 단계` 줄만** 고친다. 다른 절과 사용자가 손으로 적은 내용은 건드리지 않는다 - 두 파일이 서로 다르면 지금 호스트의 파일을 기준으로 다른 쪽을 맞춘다. 손으로 적은 줄은 양쪽에 남긴다 - 파일은 있는데 `## 문서 지도`·`## 공통 규칙` 이 없으면(예: `/init` 이 만든 파일) 기존 내용 **아래에** 뼈대 두 절을 더한다. 기존 내용은 손대지 않는다 - 절이 없으면 `공통 규칙` 뒤에 제품·기술·구조·플랜·하네스·루프 순서의 자리에 더한다 - 전체 **100줄 안팎, 200줄을 넘기지 않는다.** 넘치면 규칙을 문서 쪽으로 돌려보내고 가리키는 줄만 남긴다 ```markdown # <프로젝트 이름> > harness-loop-fullstack 스킬이 만들고 갱신하는 파일이다. 각 절은 그 레이어의 스킬만 고친다. > `CLAUDE.md` 와 `AGENTS.md` 는 같은 내용이다. 한쪽을 손으로 고치면 다른 쪽도 같이 고친다. > 세부 규칙은 각 문서에 있다 — 여기에는 어느 세션에서든 반드시 지킬 것만 둔다. 200줄을 넘기지 않는다. ## 문서 지도 - `docs/IDEA.md` → `docs/PRD.md` → `docs/TRD.md` → `docs/ARCHITECTURE/` → `plan_setup/` → `harness_setup/` → `loop_setup/LOOP.md` - 뒤 문서는 앞 문서를 근거로 한다. 어긋나면 앞 문서가 이긴다 — 바꿔야 하면 사용자 확인 뒤 **가장 앞 문서부터** 고치고(버전·`변경 이력` 한 줄. 프로젝트 완료 뒤에는 `feature-add`·`refactor-plan` 의 후미 절로) 사이 문서를 맞춘 다음 이어 간다(변경 전파). 손으로 고칠 때도 같다. 루프 세션은 앞 문서를 고치지 않고 LOOP.md 대로 멈춘다 - 현재 단계: <마지막으로 끝낸 스킬> → 다음: <다음 스킬> ## 공통 규칙 - 앞 레이어의 **파일**만 입력이다. 대화 기억이나 추측으로 요구사항·기술 결정을 만들지 않는다 - ID(`FR-`·`NFR-`·`US-`·`OQ-`·`TD-`·`phase-NN-task-MM`)는 참조만 한다. 새 ID 는 마지막 번호 다음으로만 만든다 — OQ 는 프로젝트 전체(`HARNESS.md`·`LOOP.md` 의 미결 표까지)에서, 나머지는 그 문서에서 센다(변경 전파로 앞 문서에 더할 때도) - 사용자가 말하지 않은 값은 `(가정)` 으로 표시한다. 모호하면 추측하지 말고 묻는다 - 요청된 범위를 조용히 넓히거나 줄이지 않는다. 범위 판단은 보고한다 ``` `## 루프 (loop_setup/LOOP.md)` 절에는 이것만 적는다. 자식 세션(`claude -p`·`codex exec`)은 스킬 없이 이 파일과 지시문만 들고 시작하므로, "LOOP.md 0절부터"가 여기 없으면 자식은 규칙을 모른 채 태스크를 고른다. 규칙 본문은 옮기지 않는다 — LOOP.md 가 규칙이고 이 절은 문지기다. ```markdown ## 루프 (loop_setup/LOOP.md) - 세션을 열면 `loop_setup/LOOP.md` 0절부터 따른다. 이 절은 요약이 아니다 — LOOP.md 가 규칙이다 - 완료는 네 게이트를 통과한 뒤다. 코드가 끝난 것은 완료가 아니다 - 실패→수정→재검증은 3회까지. 소진하면 STATE.md `Next Action` 에 `질문:` 을 적고 멈춘다 - 커밋은 네 게이트 통과 뒤. 시점과 횟수는 LOOP.md 5절. 검증 세션은 STATE.md·LOG.md 를 쓰지 않는다 - 병렬이면 자기 슬롯 워크트리 밖을 고치지 않고, 워커는 STATE.md·LOG.md·태스크 문서·하네스 문서를 쓰지 않는다(출력 블록으로 넘긴다). merge 는 코디네이터 몫 - 구동: <스크립트 / 오르카 / 서브에이전트> · 동시 · 격리 <방식> · 모델 코디 <급> / 구현 <급> / 검증 <급> ← loop-update 가 갱신 - (② 일 때만) 루프가 도는지·누가 무엇을 하는지는 `<상태판 명령> --once` 로 본다(읽기만). 오르카 사이드바에는 구동 스크립트와 판단 자식이 나오지 않는다 - (병렬일 때만) `질문:` 의 답은 코디네이터 트리의 `STATE.md` 에 적는다 — ②면 오르카에 뜬 답하기 탭이나 `<답하기 명령>` 이 대신 적는다. `main` 의 STATE.md 는 덮인다 ``` --- ## 13단계. 자체 점검 | 확인할 것 | 통과 조건 | 걸렸을 때 | |---|---|---| | 구조 | `loop_setup/LOOP.md` 가 원본 이름 그대로이고 규칙은 그 한 파일에만. `loop_setup/DAG.md` 는 생성물, `harness_setup/scripts/dag-render.*` 가 있고 **실제로 돌려 봤음**(`--ready` 포함) | 맞춘다. 규칙이 다른 파일에 있으면 옮긴다 | | 게이트 참조 | 게이트를 **`verification` 값으로 여는 방법**이 적혀 있고, 명령을 베껴 적은 곳이 0건 | 고친다. 베낀 명령은 반드시 낡는다 | | 게이트 형식 | 앞 셋은 `## 명령`+종료 코드, `task_validation` 은 **명령이 아니라 세션**이라고 구별됨 | 고친다. 넷째에 `## 명령` 절은 없다 | | 활성 시점 출처 | 정의서에 없으면 `HARNESS.md` 표에서 읽는다고 적혀 있음 | 채운다. 넷째 게이트가 여기 걸린다 | | 게이트 순서 | `lint → unit → e2e → task_validation` 고정이고 앞 실패 시 중단이 명시됨 | 채운다 | | 미활성 | 활성 시점 전에는 **결과가 무엇이든 미활성**이고, 그 상태로 완료시킬지는 사람이 정한다 | 채운다. 0건 실행은 초록불, 도구 부재는 빨간불로 샌다 | | 부분 활성 | **언제 그 값이 되는지**(활성 시점이 하나가 아닐 때)가 적혀 있음 | 채운다. 어휘만 나열하면 아무도 안 쓴다 | | 상태 어휘 | 게이트·흐름 상태에 `활성`·`부분 활성`·`미활성`·`대기` 넷 밖의 이름이 0건. (태스크·phase 상태의 `pending`…`completed` 와 `Last Verification` 의 `task_validation 대기` 는 다른 어휘 공간이다) | 맞춘다 | | 분류 우선 | 실패 시 **고치기 전에** 분류하는 단계가 수정 앞에 있음 | 순서를 바꾼다 | | 분류 개수 | `흔들림` 과 `판정 불가` 가 갈래로 있음 | 채운다. 없으면 둘 다 `진짜 결함` 으로 찍혀 코드를 고치게 된다 | | 환경 판정 | `local-dev` **종료 코드**로 환경 문제를 확정하고, 사람이 고칠 것과 재준비할 것이 갈림 | 채운다. 추측할 필요가 없는 자리다 | | 판정 중단 | 검증자가 "환경이 이상해 판정을 멈췄다"를 `FAIL` 이 아니라 환경 문제로 받음 | 고친다 | | 재시도 정의 | 3회가 **무엇을 세는지** 예시로 명확하고, `진짜 결함` 만 센다고 적혀 있음 | 채운다. 애매하면 안 지켜진다 | | 재시도 불변 | 3회가 **원본 사양이 못 박은 값**이고 루프가 조정하지 않는다고 적혀 있음 | 고친다. 요청받으면 분류를 먼저 하라고 답한다 | | 헛도는 재실행 | 코드가 아닌 갈래의 재실행이 태스크당 **분류마다 1회**로 묶여 있음 | 줄인다. 3회를 주면 아무것도 검증 않는 실행이 다섯 번 는다 | | 재실행 범위 | **실패한 게이트부터** 돌고, 앞 게이트는 제품 코드가 바뀌었을 때만 다시 돈다 | 채운다. 흔들림 하나가 lint·unit 까지 끌고 가면 1회로 묶은 의미가 없다 | | 태스크 간 반복 | 실패한 흐름·검사 ID가 `LOG.md` 에 남고, 두 태스크에 걸치면 `harness-update` 로 감 | 채운다. 없으면 같은 낭비가 태스크마다 반복된다 | | 중단 시 인계 | 3회 소진 시 남길 것(상태·이력·물을 것)이 항목으로 있음 | 채운다 | | 재시도 보존 | 재시도 횟수와 이력이 **세션을 넘겨 남는 자리**에 있음 | 정한다. 대화에만 있으면 세션이 죽을 때 사라진다 | | 승인 만료 | **사이클 0단계에 만료 확인이 있고**, 승인마다 범위·기한이 있음(무기한 0건) | 채운다. 죽은 승인은 도는 게이트를 건너뛰게 한다 | | 새 파일 | STATE.md·LOG.md 외에 새 상태 파일이 0건 (판정 파일·병렬의 `done-.txt`·격리 3의 `e2e-.txt` 는 `plan_setup/`·`loop_setup/` 밖의 일시 파일이고 반영 뒤 지운다. `loop_setup/DAG.md` 는 상태가 아니라 생성물이라 예외) | 지운다. 기록처가 셋이면 셋이 어긋난다 | | 병렬 필자 | 병렬이면 태스크 문서·`STATE.md`·`LOG.md`·하네스 문서의 필자가 **코디네이터(자기 트리) 하나**이고(예외: ②의 질문 답변 자식은 `Next Action` 의 `질문:` 한 줄, 답하기 탭은 사람의 `답:` 한 줄, 구동 스크립트는 자식 사이의 `Assignments` 토막 값 — 7단계), 워커는 문서를 코디네이터 트리에서 읽고 출력 블록(`END` 종결)으로 넘긴다고 적혀 있음 | 채운다. 슬롯이 문서를 고치면 병합 충돌이 되고, 출력 형식이 없으면 구현 세션 안의 재시도가 기록에서 사라진다 | | 모델 배치 | 8절 표에 역할마다 실제 모델 이름·추론 강도가 있고 사용자가 고른 선택지가 적혀 있음. 지시문·스크립트 본문에 모델 이름이 0건 | 묻지 않았으면 지금 묻는다. 이름이 지시문에 박혀 있으면 표 참조로 바꾼다 | | 분리 세션 | `task_validation` 앞에 세션 경계가 있고, **주지 않는 것** 목록이 있음 | 채운다. 없으면 분리가 말뿐이다 | | 분리 불가 | 분리 수단이 없을 때 **통과로 기록하지 않는다**가 적혀 있음 | 채운다 | | 구동기 | 세션을 여는 모드(①스크립트 단일·병렬 / ②오르카 스크립트 구동 / ③서브에이전트)가 **오르카 유무를 확인해** 정해져 있고, **구현·검증·반영 세 토막**의 고정 지시문·허용 목록·판정 파일 경로·구동기가 `STATE.md` 로 토막을 고르는 규칙이 있음. 병렬이면 **코디네이터 지시문**과 `done-.txt` 경로도, ②면 **질문 답변 지시문**과 워커가 결과 파일을 직접 쓰는 덧붙임도 | 채운다. 없으면 태스크마다 사람이 세션을 열거나 판정을 아무도 반영하지 않는다 | | 응답 경로 | 사람의 답이 `Next Action` 의 `답:` 줄로 돌아오고 0절이 그것을 읽는다고 적혀 있음 | 채운다. 없으면 자식이 던진 질문을 받을 곳이 없어 구동기가 영원히 멈춘다 | | phase 순서 | 현재 phase 가 `completed` 가 아니면 다음 phase 태스크를 열지 않는다는 선택 규칙과, 완료 묶음 실패 뒤 `답: 재판정`/`답: 다시` 진입점이 있고 **구동기가 `답: 재판정` 을 반영 토막으로 보낸다** | 채운다. 없으면 실패한 phase 위에 다음 phase 가 쌓이거나 정지도 진행도 아닌 루프가 된다 | | 완료 후 소거 | 완료 시 `Last Verification` 을 `PASS` 로 덮어써 `대기`·`미활성` 이 남지 않는다고 적혀 있음 | 채운다. 남으면 구동기가 반영 토막을 다시 열어 구현하지 않은 태스크를 완료시킨다 | | 잘린 판정 | 잘린 판정 파일이 `Retry History` 에 `검증 중단` 으로 남고 두 번째에 `질문:` 이 된다고 적혀 있음 | 채운다. 기록처가 없으면 "두 번 연속"을 셀 수 없어 검증↔반영이 무한 반복된다 | | phase 갱신 값 | phase 완료 시 `Phase Overview`(값 셋)·`Current Phase`·phase 문서 `Status` 를 루프가 고친다고 적혀 있음 | 채운다. 초기화만 있고 갱신 주인이 없는 값이 된다 | | 병렬 배정 | 병렬이면 코디네이터가 `태스크: ` 로 배정하고 워커는 선택 규칙을 건너뛰며, 배정표가 `STATE.md` 의 `Assignments` 절에 있음. 코디네이터 지시문의 배정 단계가 `dag-render --ready` 후보에서 고르고 **폭 표를 배정 근거로 쓰지 않음** | 채운다. 워커 셋이 같은 태스크를 집는다. 폭 표로 배정하면 선행이 끝난 태스크가 "다음 단계"로 읽혀 슬롯이 논다 | | 병렬 여부 | 세팅 때 병렬·에이전트 수 상한·격리 방식을 **물어 받은 답**이 문서에 있음(기본 병렬) | 묻는다. 묻지 않고 단일로 적은 것은 기본값 위반이다 | | 실패 작업 트리 | 3회 소진 시 미커밋 변경을 그대로 두고(병렬이면 `park/` 로 옮기고 슬롯을 놓는다), 사용자가 건너뛰라고 할 때만 `files` 를 되돌린다(병렬이면 park 브랜치를 지운다)고 적혀 있음 | 채운다. 없으면 다음 세션이 더러운 트리 위에서 다른 태스크를 시작한다 | | 기록 시점 | 시작·전이·완료 세 시점이 있고 쓰기 순서가 정해져 있음 | 채운다. 완료 시점만 있으면 중간에 죽은 세션이 복구되지 않는다 | | 불일치 복구 | STATE.md 와 태스크 `status` 가 어긋났을 때의 처리가 있음 | 채운다 | | 커밋 시점 | 네 게이트 통과 후이고, merge·rebase 는 루프 밖이라고 적혀 있음 | 맞춘다 | | 선택 규칙 | `blocked`·`depends_on`·범위 확장의 판정 근거가 **frontmatter 필드**로 적혀 있음 | 채운다. 규칙만 있으면 매번 다르게 판정한다 | | 막다른 곳 | 실행 가능한 태스크가 0개일 때의 처리가 있음 | 채운다 | | 차단 해소 | `blocked` → `pending` 을 **루프가** 어떤 순서로 바꾸는지(앞 문서 확인 → `blocked_by` → `status` → 기록 → `harness-update`)가 있음 | 채운다. 주인이 없으면 풀린 미결 위의 태스크가 영원히 건너뛰어진다 | | phase 판정 | 태스크 전부 완료 **와** phase 수용 기준 **와** phase 완료 묶음 통과 셋 다 | 채운다 | | phase 완료 묶음 | 실패 시 phase 를 완료로 찍지 않되 재시도 3회는 쓰지 않는다는 것이 적혀 있음. ①·②면 **구동 스크립트가 돌리고**(판단 자식의 `묶음 대기: phase-NN` → 구동 스크립트의 묶음 모드가 `e2e.md` 의 `## phase 완료 묶음 명령` 절을 그때 읽어 실행 → `bundle-phase-NN.txt`) 판단 자식은 결과 파일만 읽으며(`EMPTY` 는 통과가 아님), 5절에 1~5번이 있고, 코디네이터 허용 목록·자식 상한에 묶음이 없고 신호가 있는 동안 `배정 없음:` 이 남지 않으며, 묶음 모드를 한 번 돌려 봤음 | 채운다. "게이트 명령이 아니다"는 통과 불필요가 아니다. 판단 자식에게 돌리게 하면 슬롯 환경변수·10분 넘는 명령이 거부돼 phase 마다 사람에게 넘어간다(실전 2026-10-14) | | 병렬 폭 | DAG 를 세웠으면 **구간별 폭과 권장 에이전트 수**가 함께 나옴 | 잰다. 폭이 실제 병렬도의 상한이다 | | 하네스 갱신 | `harness-update` 를 부르는 조건이 시점별로 있음 | 채운다 | | DAG | 순환·미존재 참조·`blocked` 후행·`files` 겹침 확인이 있음 | 채운다 | | DAG 그림 | `loop_setup/DAG.md` 에 태스크 단위 `mermaid` 그래프·폭 표·확인 결과·겹침 쌍이 있고, **노드 수 = 태스크 문서 수**, `LOOP.md` 8절이 그것을 가리킴. 8절 안에 머메이드가 없음 | 스크립트를 돌린다. 8절에 그림이 있으면 뺀다 | | 자원 충돌 표 | 파일 겹침·공유 런타임 자원·빈 `files` 세 검사의 결과 표가 있고(0건이면 "0건"), 처리 값이 `단독`/`그룹 직렬`/`격리가 덮음` 셋 중 하나이며 공유 자원의 기본이 `그룹 직렬` | 검사한다. 전부 `단독` 이면 폭이 1로 떨어진다 | | 슬롯 경로 | 슬롯 워크트리 경로가 8절에 실제 값으로 있고, 저장소가 동기화 폴더 아래면 형제 폴더가 아님 | 정한다. 동기화 폴더의 형제 워크트리는 `node_modules` 를 동기화한다 | | 격리 3 | 격리 3이면 구현 토막이 `unit` 에서 끊기고(`STATUS: e2e 대기`) 앱 전환·e2e 직접 실행·검증이 슬롯 순서로 있음. ②면 검증 워커가 **띄우기 직전 앱 전환·한 번에 하나**이고, 도는 동안 스크립트가 코디네이터 자식·e2e·앱 전환을 미루되 `worker_done` 처리·ack 는 미루지 않으며, 코디네이터 지시문에도 같은 금지가 있음 | 채운다. 없으면 구현 워커 둘의 e2e 가 한 포트에서 충돌하고, ②의 검증 워커가 다른 슬롯의 앱을 보고 판정한다 | | ② 전용 | ②면 `references/orca.md` 5절의 행(대기·답 읽기·오르카 응답·워커의 ask·슬롯·임시 폴더·정지 감시·상태판·답하기)을 모두 통과 | 그 행대로 | | 질문의 범위 | ①-병렬·② 반복의 첫 줄이 **전역 질문만** 새로 띄우기를 멈추고, 태스크 질문(`질문: <태스크 ID> — …`)은 그 행만 정지로 세우며, "태스크 질문만 남고 행이 전부 정지면 질문 대기" 멈춤이 있음. 코디네이터 지시문이 질문의 머리를 범위대로 쓰고(애매하면 전역) 태스크 질문이 있어도 반영·배정을 계속함 | 채운다. 없으면 질문 하나에 무관한 슬롯의 성공한 결과까지 반영·검증이 멈춰 사람을 기다린다(실전 2026-10-12) | | 끝난 행 재기동 | 행 띄우기(①-병렬·② 모두)가 dispatch 파일뿐 아니라 **결과 파일이 남은 행도 건너뜀** | 채운다. ②는 `worker_done` 때 dispatch 파일을 지우므로, 자식이 결과를 반영하기 전(미룸·반영 실패)에 끝난 워커가 다시 뜬다 | | 워커를 죽이는 하네스 | 병렬이면 `harness_setup/scripts/` 의 프로세스 정리(`local-dev -Down`·게이트 실행기)가 이름이 아니라 자기가 띄운 PID 를 시작 시각과 대조해 끄고, 자손은 부모보다 늦게 시작한 것만 모음(또는 잡 오브젝트)을 읽어서 확인했음 | 어긋나면 `harness-update` 로 고친다(앞 문서 반영). 그대로 두면 다른 슬롯의 게이트가 끝날 때 PID 를 다시 받은 워커가 꺼진다(실전 2026-10-13) | | 늦은 worker_done | ②의 코디네이터 지시문이 dispatch 파일이 남은 행의 결과 파일을 반영하지 않고, 구동 스크립트가 dispatch 파일이 없는 `worker_done`(재생)으로 결과 파일을 만들지 않음 | 채운다. 없으면 먼저 반영된 결과 뒤에 늦게 온 `worker_done` 이 END 없는 사본을 다시 만들어 끝난 행이 중단으로 세어진다(실전 2026-10-13) | | 슬롯 반납 | 병렬이면 정지 행이 문서 커밋 뒤 `git add -A` → `commit-tree` → `git branch -f park/` → 슬롯 청소로 슬롯을 놓고, 워커 칸이 `park/` 이며, 배정은 그 행을 빈 슬롯으로 세되 충돌 검사에서는 도는 행으로 치고, "이어서" 는 새 배정보다 먼저 `cherry-pick --no-commit` 으로 되살리며(충돌이면 청소 뒤 전역 질문), "건너뛰라" 는 park 브랜치 삭제임 | 채운다. 없으면 질문 하나가 답을 받을 때까지 슬롯 하나를 놀린다(실전 2026-10-13, 1시간 반) | | 구동 스크립트 | `harness_setup/scripts/loop-drive.*`(②면 `loop-watch.*` 도)가 있고 4절 알고리즘과 같으며, ①은 드라이런·②는 스모크 테스트(분리해서 띄운 스크립트로 시험 워커의 ask/reply·결과 파일·worker_done·release, 터미널을 닫아도 계속 돎, 시험 알림)를 **실제로 돌렸음**. ②에 코디네이터가 상주하는 자리·phase 마다 코디네이터를 교체하는 명령이 0건이고, ② 워커를 `task-create --display-name "worker_<태스크 ID>"` → `worker-start --task` 로 띄우며, 스크립트를 오르카 터미널에서 띄워 터미널 프로세스 밖으로 분리해 돌리며(4절 ② 띄우기) 재시작 시 `run-use` 로 Run 을 이어받는다고 적혀 있음 | 쓰고 돌린다. 상주 코디네이터는 폴링마다 전체 컨텍스트를 다시 읽는다 | | 반복 판정 | 실패 줄 형식(`- 실패: / <게이트> / <검사 ID> / <분류>`)이 고정돼 있고, 반복 판정이 **코디네이터 지시문(병렬)·반영 지시문(단일)의 한 단계**로 있으며, ②③·격리 부족이면 `질문:` 과 배정 중지, 처리 뒤 `- 반복 처리:` 줄이 있음 | 채운다. 설명 절에만 있는 트리거는 아무도 실행하지 않는다 | | 알림 | `알림(<주체>, ~<만료>):` 형식이고, 루프가 스스로 할 수 있는 것은 알림으로 남기지 않으며, phase 를 열 때 만료된 알림을 `질문:` 으로 모은다(0절 7번) | 채운다. 주인·기한 없는 알림은 쌓이기만 한다 | | 미결 시점 | 9절 미결의 `언제` 가 루프가 확인할 수 있는 시점이고, phase 를 열 때 확인한다(0절 7번) | 고친다. 확인하지 않는 판단 시점은 기본값이 굳는 것과 같다 | | 격리 추천 | 테스트가 DB 를 쓰는지 확인했고, 쓰면 1·2를 추천으로 제시했음. 3을 골랐으면 그 판단 시점이 9절에 확인 가능한 형태로 있음 | 확인한다. 격리 3에서도 unit 은 병렬로 돈다 | | 슬롯 규칙 | `main` 에서 작업하는 자리가 0건이고, 코디네이터 트리(`coordinator`)에 코디네이터와 `worker-1` 이 있고 둘째부터 `worker-N` 트리이며, 검증은 구현이 돈 자리에서 이어 돌고, 재배정 청소(`git clean -fd`)·기본 브랜치 fast-forward·재사용·정리 규칙이 있으며, **기본 브랜치 이름이 8절에 실제 값으로** 적혀 있음 | 채운다. 태스크마다 만들면 환경 N벌이 낭비되고, `main` 에서 돌리면 사용자 자리가 더러워진다 | | 격리 | 병렬이면 격리 방식이 **확정**되고 슬롯별 환경변수 표가 있으며 `local-dev` 가 그 변수를 읽는지 확인했음. 단일이면 **기본값·선택지·판단 시점**과 함께 미결 | 정한다. 미결인 채로 폭 2에 들어가면 DB가 조용히 오염된다 | | 병렬 기록 | 병렬에서 `STATE.md` 가 꼬이는 둘(**칸 부족 / 동시 쓰기**)이 구별되고 각각의 해법이 있음 | 채운다. 칸만 나누면 통째 쓰기에서 서로 덮어쓴다 | | 필자 규칙 | 병렬의 `STATE.md`·`LOG.md`·태스크 문서·하네스 문서 필자가 **코디네이터(자기 트리) 하나**이고(예외: ②의 질문 답변 자식은 `Next Action` 의 `질문:` 한 줄, 답하기 탭은 사람의 `답:` 한 줄, 구동 스크립트는 자식 사이의 `Assignments` 토막 값 — 7단계) 워커 브랜치는 코드만 바꾼다 | 채운다. 잠금으로 풀면 잠근 채 죽은 에이전트가 전체를 세우고, 슬롯이 문서를 고치면 병합 충돌이 된다 | | 실제 값 | `local-dev` 경로·옵션, 첫 태스크 ID, 활성 시점이 **이 프로젝트의 실제 값** | 채운다. 일반형은 첫 세션이 추측하게 만든다 | | 하네스 침범 | 게이트 내용·활성 시점·흐름 목록을 이 스킬이 직접 **고친** 곳이 0건 ("앞 문서 반영"으로 `harness-update` 를 불러 — 부를 수 없으면 그 규칙대로 — 고친 것은 침범이 아니다) | 되돌린다. `harness-update` 의 몫이다 | | 플랜 침범 | 태스크·수용 기준·`verification` 을 이 스킬이 직접 고친 곳이 0건 ("앞 문서 반영"으로 `plan-generator` 10단계를 불러 — 부를 수 없으면 그 규칙대로 — 고친 것은 침범이 아니다) | 되돌리고 Layer 3으로 보고한다 | | `CLAUDE.md` · `AGENTS.md` | 두 파일이 같고 `## 루프` 절이 갱신됐고 각 200줄 이하 | 자기 절만 고친다. 넘치면 규칙을 문서로 돌려보내고 가리키는 줄만 남긴다 | **자체 점검 결과를 `LOOP.md` 안에 남기지 않는다.** 점검은 작업 기록이고 문서는 산출물이다. 문서에 남기면 규칙이 바뀌어도 점검표가 남아, 지킨 적 없는 것을 지켰다고 말하게 된다. **결과는 14단계 보고로 간다.** **고칠 것과 보고할 것이 다르다.** 형식 문제(빠진 단계, 잘못된 경로, 일반형으로 남은 옵션)는 고친다. **범위 판단은 보고한다** — 재시도 횟수를 바꾸는 것, 게이트를 건너뛰게 하는 것, 분리 세션을 포기하는 것은 사용자가 요청한 검증을 사용자 모르게 줄이는 일이다. --- ### 앞 문서 반영 — 세팅 중에 찾은 어긋남은 이 세션이 고친다 세팅은 루프가 돌기 전이라, 플랜·하네스와 어긋난 것을 여기서 찾으면 **보고하고 멈추지 않고 그 자리에서 고친 뒤 이어 간다** — 루프가 첫 사이클에서 같은 것에 3회 실패한 뒤 멈추는 것보다 싸다. 순환 `depends_on`·없는 태스크 참조·관찰할 수 없는 수용 기준은 `plan-generator` 10단계, 실행 불가능한 게이트 명령·없는 태스크를 가리키는 활성 시점은 `harness-update` 로 고친다(부를 수 없으면 그 스킬의 규칙대로 직접). 그 수정이 요구사항·기술·구조를 바꿔야 하면 사용자에게 묻고(답을 받기 전에는 고치지 않는다) **가장 앞 문서부터** 고친다 — PRD(`prd-generator` 8단계)·TRD(`trd-generator` 9단계)·아키텍처(`architecture-generator` 9단계), 버전과 `변경 이력` 한 줄, 사이 문서의 바뀐 ID. 다 고친 뒤 DAG 를 다시 그린다. **루프가 돌기 시작한 뒤에는 이 절이 적용되지 않는다** — 그때는 LOOP.md 의 `질문:` 규칙이다. 대화할 수 없는 세션(평가 등)이면 플랜·하네스·앞 문서를 고치지 않고 어느 스킬로 무엇을 고칠지 14단계 보고 8번에 적는다. ## 14단계. 작성 후 보고 이 순서로 보고한다. 1. **한 사이클이 어떻게 도는가** — 단계 목록을 짧게. **원본 17단계에서 바꾼 것과 그 이유**를 함께 2. **게이트를 어떻게 여는가** — `verification` → 문서 이름 → `## 명령`. 명령을 베껴 적지 않았다는 것 3. **실패 처리** — 다섯 분류와 각각의 재실행 횟수, 3회가 무엇을 세는지, 소진 시 무엇을 남기고 무엇을 묻는지. **사용자가 3회를 늘려 달라고 했다면 유지한 사실과 그 이유, 그리고 대신 제시한 답(분류가 먼저)을 여기 적는다** 4. **재시도 값을 어디에 두었는가** — STATE.md 에 더한 절과 그 이유. **STATE.md 를 고쳤다는 사실은 반드시 알린다** 5. **`task_validation` 분리를 이 환경에서 어떻게 만드는가** — 분리가 보장되지 않으면 **그 사실을 명시한다** 그리고 **구동기 모드** — 어느 모드를 기본으로 잡았고, 허용 목록에 무엇을 넣었으며, 사람에게 넘어오는 지점이 어디인지 6. **`harness-update` 를 부르는 자리** — 시점별로 몇 개인지 7. **병렬 운용** — 병렬 여부·에이전트 수 상한·격리 방식·슬롯 경로·**역할별 모델**(고른 선택지와 실제 이름, 사용량 = 폭 × 모델이라는 것)로 받은 답, **`loop_setup/DAG.md` 를 검토해 달라는 요청**(실행 순서가 의도와 맞는지)과 `dag-render` 를 실제로 돌려 봤다는 사실, 자원 충돌 표(몇 건이고 무엇이 `단독`·`그룹 직렬` 인지), **구간별 폭과 권장 에이전트 수**, 코디네이터 트리·워커 트리가 몇 개 어디에 생기고 `main` 이 언제 반영되는지, `local-dev` 가 슬롯 변수를 읽는지(안 읽으면 `harness-update` 필요), 격리 3이면 병렬 이득이 unit 까지라는 것과 테스트가 DB 를 쓰는지 확인한 결과. 단일로 갔다면 격리를 미결로 남긴 이유와 선택지. **구동 스크립트** — `loop-drive` 를 어디에 썼고 드라이런·스모크 테스트가 어떻게 돌았는지, 사람이 여는 명령이 무엇인지(②는 오르카 터미널 안에서). ②면 **알림이 언제 오는지**(문제일 때 + 태스크·phase 완료 때 — 완료 소식이 오래 없으면 상태판을 본다), **상태판 명령**과 "오르카 사이드바에는 구동 스크립트·판단 자식이 나오지 않으니 살아 있는지는 상태판에서 본다 — 아무 터미널에서나 띄우고 닫아도 된다" 를 함께. **질문이 생기면 오르카에 답하기 탭이 뜨고**(답 없는 채로는 30분·2시간·8시간에 다시 알린다) 거기 답하면 코디네이터 트리의 `STATE.md` 에 적히며, 루프가 멈춰 있었으면 그 탭에서 다시 띄운다는 것. 병렬이면 태스크 질문으로 선 행이 슬롯을 `park/` 로 놓는다는 것 8. **앞 문서 반영** — 세팅 중 찾은 어긋남(없는 태스크를 가리키는 활성 시점, 순환 `depends_on`, 실행 불가능한 게이트 명령)을 어느 스킬로 고쳤고 각 문서가 어느 버전이 됐는지. 사용자가 보류해 남은 것 9. 다음 단계 안내: "검토하시고, 괜찮으면 `phase-01-task-01` 부터 첫 사이클을 시작할까요?" **원본 예시에서 바꾼 것을 반드시 보고한다.** 원본의 17단계·Task Selection·Completion·Agent/Worktree 절은 형식을 보여주는 예시이고 조정해도 되지만, **조정 자체가 문제가 아니라 조정을 알리지 않는 것이 문제다.** 사용자는 자기가 설계한 것이 어디서 어떻게 달라졌는지 알아야 다음 판단을 한다. --- ## 산출물 템플릿 ### `loop_setup/LOOP.md` ````markdown # 개발 루프: (제품 이름) - **입력 문서**: plan_setup/(PLAN.md·phase·task·STATE.md·LOG.md), harness_setup/(HARNESS.md·quality_gates/) - **전제**: 한 태스크에 구현 세션 하나. 검증과 반영은 별도 세션(4절의 세 토막). **기본은 병렬**(8절) — 폭이 1인 구간은 단일과 같다 - **읽는 순서**: 세션을 열면 이 문서 → `plan_setup/STATE.md`. **구동기가 준 지시문이 토막을 지정하면 그 지시문이 0절 6번보다 앞선다** ## 0. 세션을 열 때 1. 이 문서와 `STATE.md` 를 읽는다 2. `STATE.md` 의 `Task Status` 와 태스크 문서의 `status` 가 어긋나면 **태스크 문서 기준으로 맞추고** LOG.md 에 남긴다 (앞 세션이 기록 도중 끊긴 흔적이다) 3. `Retry` 절에 진행 중인 이력이 있으면 **거기서 이어받는다.** 앞의 시도를 반복하지 않는다 4. **`Standing Decisions` 의 만료를 확인한다** — 각 항목이 걸린 게이트의 **활성 시점 태스크**(정의서, 없으면 `HARNESS.md` 표)가 이미 `completed` 면 그 승인은 죽은 것이다. `HARNESS.md` 표의 `활성` 표시를 기다리지 않는다 — 표는 `harness-update` 가 올리는 요약이라 한 박자 늦을 수 있고, 활성 시점 태스크의 완료는 루프 자신이 판정한 사실이다. **지우고 `LOG.md` 에 한 줄 남긴다.** 남겨 두면 실제로 도는 게이트를 "승인받았으니 건너뛴다"로 읽게 된다 — 통과율은 오히려 좋아 보인다. 지우는 것은 봐주는 것을 **없애는** 방향이라 루프가 스스로 해도 된다. **범위를 넓히는 것은 `loop-update` 다** 5. `Next Action` 에 **`답:`** 줄이 있으면 그것이 사람의 답이다. 반영하고 `질문:`·`답:` 두 줄을 지운 뒤 `LOG.md` 에 남긴다. 질문이 둘 이상이면 답에 그 질문의 머리를 붙인다(`답: phase-01-task-07 — 넣는다`). 머리가 없으면 바로 위 질문의 답으로 읽는다. "반영"은 답의 종류대로다 — 미활성 승인이면 `Standing Decisions` 에 **범위·기한과 함께** 적고, 3회 소진의 답이면 3절의 "이어서/건너뛰라" 처리, `답: 해소 OQ-0xx` 면 2절의 차단 해소 절차, phase 완료 묶음 실패의 답이면 `답: 재판정`(반영 토막으로 phase 판정 재실행 — `묶음 대기:` 를 다시 적으면 구동기가 묶음을 다시 돌린다, 9단계) 또는 `답: 다시`(그 태스크 `in_progress`·`Cycle 0`), 수용 기준 재작성이면 `plan-generator` 10단계로 넘긴 뒤 종료, `답: 반영함`(질문이 "<스킬> 로 반영한 뒤 "답: 반영함" 으로 알려 달라"였던 것)이면 앞 문서를 직접 읽지 않고 `plan-generator` 10단계로 넘긴다 — 플랜이 입력 버전을 대조해 바뀐 앞 문서에 걸린 태스크를 맞추거나 새 설계 단위에서 태스크를 자르고(루프가 플랜의 필자다, 수용 기준 재작성과 같은 방식), 질문을 남긴 쪽이 `harness-update`·`loop-update` 면 그 스킬도 시점 규칙대로 부른다. 넘긴 뒤 종료 — 플랜·하네스에 바뀔 것이 없으면(사람이 그쪽을 직접 고쳤거나 앞 문서 변경이 태스크에 닿지 않으면) 그대로 두 줄을 지우고 이어 간다. 답이 어느 종류에도 안 맞거나 **처리 도중 막히면**(예: 해소가 앞 문서에 없다) `답:` 을 지우지 않고 `질문:` 을 새로 적어 종료한다 — 흔적 없이 끝나면 구동기가 다음 태스크로 넘어가 버린다. 예외: 병렬에서 `park/` 행에 걸린 답은 되살릴 빈 슬롯이 생길 때까지 두 줄을 그대로 둔다(코디네이터 지시문 2.7) — 막힌 것이 아니라 미룬 것이라 새 `질문:` 을 적지 않는다 `알림(<주체>, ~<만료>):` 줄은 답이 아니다 — 그 주체가 처리한 뒤 지운다(만료 확인은 7번) 6. 어느 토막인지 정한다(지시문이 지정했으면 그것이 우선) — **판정 파일(4절)이 있으면 반영 토막**. 판정 파일이 없고 `Last Verification` 이 `task_validation 미활성` 이면 **판정 없이 반영 토막**(승인 있으면 완료, 없으면 `질문:`). `task_validation 대기` 면 이 세션의 일은 없다(검증 세션이 열려야 한다). 셋 다 아니면 구현 토막이다. 반영 토막은 판정 파일을 열어 **마지막 줄이 `END` 인지** 먼저 본다 — 없으면 검증 세션이 도중에 죽은 것이니 파일을 지우고 `대기` 를 유지한 채 종료한다 (같은 태스크에서 두 번 연속이면 `질문:`). 그다음 `LOG.md` 의 마지막 줄이 **이미 그 판정을 담고 있는지** 본다 — 있으면 앞 반영 세션이 파일을 지우기 전에 죽은 것이므로 파일만 지우고 그다음 단계(완료·커밋 또는 수정)를 이어 간다. 판정을 두 번 기록하지 않는다 7. **phase 를 열 때**(앞 phase 가 `completed` 가 되고 다음 phase 의 첫 태스크를 집기 직전) 두 가지를 확인한다. 걸리면 그 phase 를 열지 않고 `질문:` 을 남긴다 - **만료된 `알림(…, ~<만료>):`** — 남아 있으면 모아서 `질문: 처리되지 않은 알림 n개 — 처리 / 다음 phase 까지 연장 / 폐기` - **9절 미결 중 `언제` 가 지금인 것**(`phase-NN 진입 전`·`<태스크 ID> 완료 후` 처럼 적혀 있다) — `질문: 판단 시점 도달 — <선택지>`. 기본값을 그대로 이어 쓰려면 사람이 그렇게 답하면 된다. 아무도 확인하지 않는 판단 시점은 기본값이 영원히 굳는 것과 같다 (병렬이면 코디네이터 자식의 일이고 워커는 건너뛴다) ## 1. 한 사이클 한 태스크는 **구현 → 검증 → 반영** 세 토막이고 토막마다 세션이 다르다(4절). 표의 `토막` 열이 어느 세션의 일인지다. | # | 토막 | 단계 | 근거·기록 | |---|---|---|---| | 1 | 구현 | `STATE.md` 로 현재 phase·태스크 확인 | | | 2 | 구현 | 다음 실행 가능한 태스크 선택 | 2절 | | 3 | 구현 | **시작 전 하네스 점검** | 7절 | | 4 | 구현 | `status: in_progress`, `STATE.md`(그 phase 의 첫 태스크면 `Phase Overview` 도 `in_progress`), `LOG.md` 기록 | 5절 | | 5 | 구현 | 태스크 구현 — `files` 와 `architecture` 안에서만 | 2절 | | 6 | 구현 | `local-dev` 로 환경 준비 | 6절 | | 7 | 구현 | `verification` 의 `lint`·`unit`·`e2e` 를 **순서대로** 실행. 실패하면 3절대로 분류 → 수정 → 재실행을 **이 세션 안에서** 한다(최대 3회). **격리 3의 병렬 워커는 `unit` 까지만** 돌리고 `e2e 대기` 로 끝낸다(8절) | 3절 | | 8 | 구현 | `Last Verification` 에 `task_validation 대기` 를 적고 **세션을 닫는다**. `task_validation` 이 미활성이면 `task_validation 미활성` 을 적는다(검증 세션 없이 반영 토막이 12번부터). 3회 소진·환경 문제·판정 불가는 여기서도 3절대로 `failed`/`Retry`/`질문:` 을 남기고 닫는다 | 4·5절 | | 9 | 검증 | **새 세션**이 `task_validation` 만 돌려 판정 블록을 출력하고 닫는다 | 4절 | | 10 | 반영 | 판정 블록을 `STATE.md`·`LOG.md` 로 옮긴다 | 5절 | | 11 | 반영 | 실패하면 **분류 → 수정 → 앞 게이트 재실행 (최대 3회)** → 다시 8번 | 3절 | | 12 | 반영 | 전부 통과하면 완료 판정 (미활성 게이트 확인) | 5절 | | 13 | 반영 | `status: completed` → `STATE.md` → `LOG.md` → 커밋 | 5절 | | 14 | 반영 | **완료 후 하네스 점검** | 7절 | | 15 | 반영 | phase 의 모든 태스크가 끝났으면 phase 판정 | 5절 | | 16 | 반영 | 세션을 닫는다. 다음 태스크는 새 구현 세션 | 4절 | ## 2. 태스크 선택 | 규칙 | 판정 근거 | |---|---| | `blocked` 는 실행하지 않는다 | `status`·`blocked_by`. 사용자가 미결을 풀어야 열린다 | | dependency 미완료는 실행하지 않는다 | `depends_on` 이 전부 `completed` | | 한 번에 하나만 완료로 전환한다 | 진행 중인 것이 있으면 그것부터. `failed` 인 `Current Task` 는 `Next Action` 에 `답:` 이 있을 때만 이어받고, 없으면 새 태스크를 집지 않고 종료한다 | | 범위를 확장하지 않는다 | `files`·`architecture` 밖은 건드리지 않는다 | | 모호하면 추측하지 않고 멈춘다 | 관찰 불가능한 수용 기준은 Layer 3으로 | | 현재 phase 가 끝나기 전에는 다음 phase 를 열지 않는다 | `Phase Overview` 에서 현재 phase 가 `completed` 가 아니면 다음 phase 태스크는 `depends_on` 이 차 있어도 집지 않는다 (완료 묶음 실패가 여기서 막힌다) | **실행 가능한 태스크가 0개인데 미완료가 남았으면** 차단 대기이거나 `depends_on` 순환이다. 전자는 어느 미결을 풀면 무엇이 열리는지 보고하고, 후자는 Layer 3으로 돌려보낸다. `blocked_by: [폐기]` 는 차단이 아니다 — 보고에 넣지 않는다. ### 차단 해소 사용자는 `Next Action` 에 **`답: 해소 OQ-0xx`** 로 알린다(대화형이면 말로). 다음 **구현 토막**이 0절 5번에서 이것을 읽어 태스크 선택보다 먼저 아래를 한다(병렬이면 코디네이터 자식). 순서: ①앞 문서(PRD 8절·TRD 12절·overview 7절)에 해소가 반영됐는지 확인 — 안 됐으면 열지 않고, `답:` 을 지우지 말고 `질문: OQ-0xx 해소가 <문서 절>에 반영되지 않았다. <그 문서의 스킬 — PRD 는 prd-generator, TRD 는 trd-generator, overview 는 architecture-generator> 로 반영한 뒤 `답: 해소 OQ-0xx` 로 다시 알려 달라` 를 적고 종료 → ②`PLAN.md` 5절에서 그 ID를 `blocked_by` 에 가진 태스크를 찾는다 → ③`blocked_by` 에서 그 ID를 지우고, 비면 `status: pending` → ④답이 수용 기준을 바꾸면 그 태스크는 `blocked` 그대로 두고 "수용 기준 재작성 필요(Layer 3)"를 `Next Action` 에 → ⑤`STATE.md`·`LOG.md`(`### Unblocked (OQ-0xx 해소).`) → ⑥`harness-update` (대기 해제, 7절). ## 3. 게이트 **태스크의 `verification` 값 = `quality_gates/` 아래 문서 이름.** 형식이 둘이다. | 게이트 | 어디를 읽나 | 판정 | |---|---|---| | `lint`·`unit`·`e2e` | `## 명령` 의 명령 한 줄 | 돌려서 종료 코드로 | | `task_validation` | `## 절차`·`## 판정 형식` | **세션을 열어** 판정 블록을 받아서 (4절) | **`task_validation.md` 에는 `## 명령` 절이 없다.** 넷째는 명령이 아니라 세션이다. **명령을 이 문서에 베껴 적지 않는다** — 하네스가 갱신되면 베낀 쪽이 낡는다. 순서는 `lint → unit → e2e → task_validation` 고정. **앞이 실패하면 뒤를 돌리지 않는다.** ### 활성 시점과 미활성 활성 시점은 게이트 정의서에서 읽고, **없으면 `HARNESS.md` 표에서 읽는다**(`task_validation` 이 그렇다). 그 태스크가 아직 `completed` 가 아니면 **결과가 무엇이든 `미활성` 이다** — 돌려도 되고 건너뛰어도 되지만 나온 값을 통과로도 실패로도 읽지 않는다. 0건 실행은 초록불이 되고, 도구가 없어서 나는 오류는 빨간불이 된다. **활성 시점 이후에는 반드시 돌린다.** 그때 "검사 대상 없음"이면 미활성이 아니라 `harness-update` 다. `부분 활성` 은 **활성 시점이 하나가 아닐 때**다(게이트 안의 일부가 더 늦게 열리는 경우). `대기` 는 `blocked` 에 걸린 것이고 사람이 미결을 풀어야 풀린다. 어휘 넷 밖의 이름을 만들지 않는다. 미활성 게이트를 달고 완료시킬지는 **사람이 정한다** (`STATE.md` 의 `Standing Decisions` 에 승인이 있으면 그것을 따른다). **승인은 그 게이트가 활성되면 만료된다** — 0절에서 지우고 넘어온 상태여야 한다. 승인이 없으면 묻고, 받은 답을 범위·기한과 함께 기록한다. **무기한 승인을 만들지 않는다.** ### 실패 **고치기 전에 분류한다. "재시도를 안 센다"가 "봐준다"는 뜻이 아니다** — 전부 통과가 아니고, 세지 않는 이유는 **고칠 곳이 코드가 아니기 때문**이다(재시도 3회는 코드 수정 예산이다). | 분류 | 알아보는 법 | 무엇을 고치나 | 게이트 재실행 | |---|---|---|---| | 진짜 결함 | 수동으로 같은 순서를 밟아도 같은 결과 | **코드** | **최대 3회** | | 낡은 스펙 | 화면·규약이 의도적으로 바뀌었다 | 흐름 정의 (`harness-update`) | 고쳐진 뒤 1회 | | 환경 문제 | `local-dev` 가 **0이 아닌 코드로 끝났다** | 환경 (재준비 → `harness-update`) | 재준비 뒤 1회 | | 흔들림 | 재시도로 살아난 통과(`flaky`)가 1건이라도 있다 | **그 테스트** (`harness-update`) | 고쳐진 뒤 1회 | | 판정 불가 | 수용 기준이 관찰 가능하게 쓰이지 않았다 | 태스크 문서 (Layer 3) | 0회 — 즉시 중단 | **뒤 넷에 3회를 주지 않는 이유**: 루프가 고칠 것이 없어서 다시 돌려도 같은 결과다. 흔들림은 도구가 이미 재시도해서 낸 판정이라 더욱 그렇다. **같은 분류가 그 태스크에서 두 번째로 나오면 멈춘다.** 그 1회는 예산이 아니라 **고친 것이 고쳐졌는지 보는 확인 실행**이다. **재실행은 실패한 게이트부터 한다.** 앞 게이트는 **제품 코드가 바뀌었을 때만** 다시 돈다 — 흔들림·환경 문제·낡은 스펙을 고칠 때는 제품 코드가 안 바뀌므로 lint·unit 결과가 바뀔 수 없다. `진짜 결함` 만 처음부터 전부 다시 돈다(코드를 고치면 다른 파일이 깨질 수 있다). 그리고 **실패마다 `LOG.md` 에 이 형식의 줄을 남긴다** — `- 실패: <태스크 ID> / <게이트> / <검사 ID> / <분류>` (검사 ID = `FLOW-xx`·테스트 파일 경로·lint 규칙 이름). 형식이 고정돼야 판단 없이 셀 수 있다. **반복 판정**(병렬은 코디네이터 지시문 2.5), ①-단일은 반영 지시문의 완료 후 점검): 같은 검사 ID 가 **서로 다른 태스크 ID 둘 이상**에서 나왔으면 (그 ID 의 마지막 `- 반복 처리:` 줄 뒤만 센다) 태스크 카운터와 무관하게 처리한다 — 그러지 않으면 태스크 61개에서 61번 낭비한다. - 고칠 것이 **테스트 안정화·흐름 정의 보정(`harness-update` ①)** 이면 지금 한다 - **격리 3에서 DB·앱을 쓰는 테스트가 흔들리면** 격리 부족이다 — `loop-update`(격리 1·2)와 `harness-update`(슬롯 변수)가 필요하고 둘 다 ②라 무인으로 못 한다 - ②③이 필요하면 **`질문:` 을 남기고 새 배정을 멈춘다**(돌던 행은 끝까지). `알림:` 으로 미루지 않는다 - 처리했으면 `- 반복 처리: <검사 ID> → <무엇을 했나>` 를 남긴다 - **환경 문제는 `local-dev` 종료 코드로 확정된다** — 추측하지 않는다. (실제 코드 표: 아래 6절) 전제·환경변수 계열은 사람이 설치·설정할 일이라 **사용자에게**, 의존 서비스·스키마·앱 계열은 재준비 후 `harness-update`. - **검증자가 "환경이 이상해 판정을 멈췄다"고 하면 `FAIL` 이 아니라 환경 문제다.** 안 뜬 화면을 보고 FAIL 을 적으면 멀쩡한 코드를 세 번 고치게 된다. - **흔들림을 남겨 둔 채 진행하지 않는다.** 흔들리는 게이트는 꺼진 게이트다. ### 재시도 3회 ``` 실행 1회차 실패 → 수정1 → 재실행 → 실패 (사이클 1) → 수정2 → 재실행 → 실패 (사이클 2) → 수정3 → 재실행 → 실패 (사이클 3) → 중단·보고 ``` **세는 것은 수정 횟수이고 `진짜 결함` 만 센다.** 태스크가 바뀌면 0으로 돌아간다. 코드가 아닌 갈래도 같은 태스크에서 합쳐 3회 쌓이면 멈춘다(분류만 바꿔 예산을 우회하는 길을 막는다). 태스크를 넘어 같은 흐름·검사 ID가 두 태스크에 걸쳐 실패하면 `harness-update` 다(7절). **3회는 원본 사양이 못 박은 값이고 루프가 조정하지 않는다.** 늘려 달라는 요청을 받으면 상한을 지우는 대신 **분류가 먼저라는 점**을 답한다 — 세 번이 부족했던 이유는 대개 그중 몇 번이 환경 문제나 흔들림에 소모됐기 때문이고, 분류가 붙으면 세 번은 실제로 세 번이 된다. **소진하면 일시 중단하고 사용자에게 보고한다.** 스스로 기준을 낮춰 풀지 않는다. 남길 것: `status: failed` / `Last Verification` / `Retry` 이력 / `Next Action` 의 물을 것 / `LOG.md` 의 관찰. **미커밋 변경은 그대로 둔다** — 커밋도 되돌리기도 하지 않는다. 병렬이면 코디네이터가 그 변경을 `park/` 로 옮기고 슬롯을 놓는다(8절 "정지 행과 park"). 사람의 답이 **"이어서"** 면 `status: in_progress`, `Cycle` 0, `History` 유지, LOG 한 줄. **"건너뛰라"** 면 `files` 의 파일을 되돌리고 `failed` 그대로 둔 채 `Current Task` 를 비우고 `Retry` 를 초기화한다. 후행 태스크는 `depends_on` 규칙 때문에 자동으로 열리지 않는다 — 폐기·재작성은 `plan-generator` 10단계의 일이므로 `Next Action` 에 `알림(plan-generator, ~다음 phase 전): 건너뜀 — 폐기 또는 재작성 필요, 막힌 후행 n개` 를 남긴다(`알림:` 은 구동기를 멈추지 않는다. 만료는 0절 7번). ## 4. 세션 경계 한 태스크에 **구현 세션 + 검증 세션 + 반영 세션**(구동기 ①-단일). ①-병렬·②에서는 코디네이터 자식이, ③에서는 상위 세션이 반영을 겸한다. 병렬이면 **지시문의 `태스크: ` 와 `Assignments` 의 그 행이 토막을 정한다** — 0절 6번의 `Last Verification` 판독은 단일에서만 쓴다. **검증 세션에 주는 것**: 태스크 문서 경로 / 실행 중인 앱 주소 / `quality_gates/task_validation.md` / 판정 블록 출력 지시 / (필요 시) 태스크 `architecture` 가 가리키는 설계 절 **주지 않는 것**: 구현 세션의 대화·요약·"무엇을 했는지" / 통과 보고 / 원인 추정 / 커밋 메시지·최근 변경 파일 — **주지 않는 목록이 이 게이트의 실질이다.** 이 프로젝트의 분리 수단: (실제 수단). **수단이 없으면 `미활성` 로 두고 사용자에게 알린다.** 같은 세션이 구현하고 판정한 것을 통과로 적으면 통과 기록만 남고 아무도 그 태스크를 다시 보지 않는다. 태스크가 끝나면 **세션을 닫는다.** 이어서 다음 태스크를 하지 않는다. ### 구동기 — 세션을 여는 방법 **모드**: (① 스크립트 구동 단일·병렬 / ② 오르카 스크립트 구동 / ③ 서브에이전트 중 이 프로젝트가 쓰는 것. 오르카 있음 → ②, 없음 → ①-병렬. 병렬을 껐으면 ①-단일) **오르카 확인 결과**: (있음/없음 — 확인한 명령과 날짜) 한 태스크는 **구현 → 검증 → 반영** 세 토막이고 토막마다 세션이 다르다(1절 표). 지시문 셋은 고정이다. ``` 구현 지시문 (토막: 구현): loop_setup/LOOP.md 와 plan_setup/STATE.md 를 읽고 0절대로 시작하라. 실행 가능한 태스크 하나를 구현하고 lint·unit·e2e 까지 돌린 뒤, Last Verification 에 "task_validation 대기" 를 적고 종료하라. task_validation 의 활성 시점 태스크가 아직 completed 가 아니면 "task_validation 미활성" 을 적어라. task_validation 은 돌리지 마라. 앞 게이트가 실패하면 3절대로 분류하고 진짜 결함만 고쳐 재실행하라(최대 3회). 3회 소진·환경 문제·판정 불가면 3절대로 status·Retry·LOG 를 남기고 Next Action 에 "질문:" 으로 적고 종료하라. 미커밋 변경은 그대로 두라. harness-update 는 ①(상태 반영)만 하고 ②③이 필요하면 "질문:" 으로 남겨라. 검증 지시문 (토막: 검증): 다음 태스크의 수용 기준을 실행 중인 애플리케이션에서 관찰해 판정하라. - 태스크 문서: (경로) - 절차와 판정 형식: harness_setup/quality_gates/task_validation.md - 애플리케이션: (주소). 안 떠 있으면 (local-dev 명령) 으로 준비하고, 그래도 안 뜨면 RESULT 를 "환경 문제" 로 적어라 - 이 두 문서, 태스크의 architecture 가 가리키는 설계 절, 실행 중인 앱 외의 정보는 찾지 마라. STATE.md·LOG.md 를 쓰지 말고 코드를 고치지 말고, 판정 블록만 마지막 END 줄까지 출력하라. 반영 지시문 (토막: 반영): loop_setup/LOOP.md 와 plan_setup/STATE.md 를 읽고 0절대로 시작하라. (판정 파일 경로) 가 있으면 마지막 줄이 END 인지 보고, 아니면 잘린 파일이니 지우고 "task_validation 대기" 를 유지한 채 종료하라(두 번 연속이면 "질문:"). 잘린 파일이면 Retry History 에 "검증 중단" 한 줄을 남겨라(두 줄째면 "질문:"). END 가 있으면 판정 블록을 STATE.md·LOG.md 로 옮기고 판정 파일을 지워라. Last Verification 이 "task_validation 미활성" 이면 Standing Decisions 의 승인을 따라 완료 판정으로 가라. PASS(또는 승인된 미활성)면 완료 판정·커밋·완료 후 점검·phase 판정을 하고, Last Verification 을 " / PASS / —" 로 덮어쓴 뒤 종료하라. phase 판정에서 완료 묶음은 돌리지 마라 — 태스크 전부 completed 와 수용 기준이 차면 5절 "완료 묶음" 1번대로 Next Action 에 "묶음 대기: phase-NN" 을 적어라 (구동 스크립트가 돌린다. e2e.md 의 묶음 명령 절이 비었으면 신호 없이 5절 4번). Next Action 에 "묶음 대기:" 가 있고 (임시)/bundle-phase-NN.txt 가 있으면 태스크 없이 그 결과로 5절 3~5번대로 phase 판정을 마저 하라. Next Action 에 "답: 재판정" 이 있으면 태스크 완료 없이 phase 판정만 다시 하고 두 줄을 지워라. FAIL 이면 분류하고, 진짜 결함이면 고친 뒤 lint·unit·e2e 를 다시 돌리고 Last Verification 에 "task_validation 대기" 를 적고 종료하라. RESULT 가 "환경 문제" 면 코드를 건드리지 말고 local-dev 로 재준비한 뒤 "task_validation 대기" 로 되돌려 종료하라(두 번째면 "질문:"). 판정 불가·3회 소진·승인 없는 미활성·phase 완료 묶음 실패면 Next Action 에 "질문:" 으로 적고 종료하라. 실패는 LOG.md 에 "- 실패: / <게이트> / <검사 ID> / <분류>" 줄로 남기고, 완료 후 점검에서 3절의 반복 판정을 하라. harness-update 는 ①(상태 반영)만 하고 ②③이 필요하면 "질문:" 으로 남겨라. ① 스크립트 구동 — 구동기는 판단하지 않는다. STATE.md 만 읽어 어느 토막을 열지 정한다. 아래 모든 `claude -p` 에 8절 "에이전트·모델 배치" 표의 값을 `--model <모델> --effort <강도>` 로 붙인다(코덱스는 `-m <모델> -c model_reasoning_effort="<강도>"`). 토막마다 다르다 — 코디네이터·반영은 그 행, 구현·수정은 구현 행, 검증은 검증 행. 이름은 여기 박지 않고 표에서 읽는다. 판정 파일·결과 파일을 지우는 반영·코디네이터 자식에는 --add-dir <임시> 도 붙인다(자식은 작업 폴더 밖을 쓰지 못한다 — 허용 목록에 삭제가 있어도. 코덱스는 그 버전의 같은 옵션): 반복 { Next Action 에 "질문:" 이 있고 "답:" 이 없으면 → 멈추고 사람에게 Phase Overview 가 전부 completed 면 → 끝 Next Action 에 "묶음 대기: phase-NN" 이 있으면 → (임시)/bundle-phase-NN.txt 가 없으면 아래 "phase 완료 묶음" 을 돌리고, 있으면 claude -p "<반영 지시문>" --allowedTools "<허용 목록>" Next Action 에 "답: 재판정" 이 있으면 → claude -p "<반영 지시문>" --allowedTools "<허용 목록>" 판정 파일이 있으면 → claude -p "<반영 지시문>" --allowedTools "<허용 목록>" Last Verification 이 "task_validation 대기" 면 → claude -p "<검증 지시문>" --allowedTools "<검증 허용 목록>" > (판정 파일 경로) Last Verification 이 "task_validation 미활성" 이면 → claude -p "<반영 지시문>" --allowedTools "<허용 목록>" 그 외 → claude -p "<구현 지시문>" --allowedTools "<허용 목록>" } phase 완료 묶음 (①-단일·①-병렬·② 공통 — 판단하지 않는다. 9단계 "완료 묶음을 돌리는 것은 구동기다"): Next Action 에 "묶음 대기: phase-NN" 이 있고 (임시)/bundle-phase-NN.txt 가 없고 도는 워커(자식·dispatch)가 없을 때만 돈다 (임시)/bundle-phase-NN.pid 의 PID 가 살아 있고 시작 시각이 같으면 새로 띄우지 않고 그것을 기다린다 — 구동 스크립트가 재시작돼도 묶음이 둘이 되지 않는다(②는 기다리는 동안 15초마다 [정지 요청] — references/orca.md 2절. 멈춰도 묶음 프로세스는 끝까지 돈다) 아니면 코디네이터 트리의 COMMIT(git rev-parse HEAD)을 읽어 두고 자신을 묶음 모드(--bundle phase-NN --commit )로 창 없이 띄운다(②면 분리 실행과 같은 방식 — ORCA_* 없이) 묶음 모드 프로세스 — 같은 스크립트지만 묶음만 한다. 띄우기·ORCA_TERMINAL_HANDLE 확인·시작 확인·loop-drive.pid·밖의 감시·run-use·단계·생존 파일·loop-exit.txt 를 전부 건너뛰고 (임시)/bundle-phase-NN.* 셋만 쓴다(loop-exit.txt 를 쓰면 밖의 감시가 구동 스크립트가 멈춘 것으로 읽고 끝난다): 먼저 자기 PID·시작 시각을 bundle-phase-NN.pid 에 배타적으로 만든다(이미 있고 그 PID 가 살아 있으면 아무것도 하지 않고 끝 — 둘이 돌지 않는다. 죽은 PID 의 파일이면 지우고 다시) 코디네이터 트리(①-단일이면 작업 트리)에서, 8절 슬롯별 환경 표의 슬롯 1 값으로 환경변수를 정하고(그 밖의 슬롯 변수·ORCA_* 는 지운다. ①-단일이면 슬롯 변수 없이) → (임시)/bundle-phase-NN.log 를 새로 쓴다 → local-dev(격리 3 이면 local-dev -Down 뒤 이 트리에서 local-dev). 0 이 아니면 "LOCALDEV <코드>" 로 끝 → harness_setup/quality_gates/e2e.md 의 "## phase 완료 묶음 명령" 절 첫 코드 블록을 지금 읽어 빈 줄이 아닌 줄을 하나씩 차례로 셸로(Windows: powershell -NoProfile -Command "<줄>" / macOS·Linux: sh -c "<줄>") — 하나가 실패해도 다음을 돌린다. 명령마다 "EXIT <코드> <명령>", 출력은 로그에. 절이 없거나 블록이 비었으면 "EMPTY" 한 줄(판단 자식이 미활성으로 읽는다 — 통과가 아니다) → (임시)/bundle-phase-NN.txt 를 임시 이름에 쓰고 바꿔 단다: "COMMIT: <받은 sha>" / EXIT·LOCALDEV·EMPTY 줄들 / "LOG: <로그 경로>" / "END" → bundle-phase-NN.pid 를 지운다 구동 스크립트는 결과 파일이 생기거나 그 PID 가 죽을 때까지 기다린다(②: 단계 "phase 묶음", 기다리는 동안에도 생존 파일은 1분마다·check 는 부르지 않는다 — 도는 워커가 없다). 8절 단계 상한(phase 묶음 — ①에도 같은 값)을 넘기면 묶음 프로세스와 그 자손을 끈다 — 자손은 부모 PID 를 따라 모으고 시작 시각이 묶음 시작 뒤인 것만(PID·이름만으로 끄지 않는다 — 다시 받은 PID 가 남의 프로세스일 수 있다), local-dev 가 분리해 띄운 앱·의존 서비스는 끄지 않는다(하네스가 살려 두는 것이다). 상한을 넘겼거나 결과 없이 죽었으면 구동 스크립트가 "COMMIT: <띄우기 전에 읽은 sha>" / "EXIT 시간초과 <묶음>" 또는 "EXIT 중단 <묶음>" / "LOG: …" / "END" 로 결과 파일을 쓰고 pid 파일을 지운다 (판단 자식이 환경 문제로 센다 — 두 번째면 질문:) 격리 3 이면 묶음 뒤 "지금 떠 있는 앱" 기억을 슬롯 1(코디네이터 트리)로 바꾼다 — 묶음은 다른 프로세스라 그대로 두면 다음 e2e·검증이 앱 전환을 건너뛰고 다른 트리의 앱을 본다 끝나면 반복의 첫 줄로 — 다음 판단 자식이 결과 파일을 읽는다. 묶음을 돌린 바퀴는 진전이다(아래 "진전 없음" 에 세지 않는다). 결과 파일은 구동 스크립트가 지우지 않는다 — 판정한 판단 자식이 지운다(로그는 남겨 두고 다음 묶음이 덮어쓴다). STATE.md 도 쓰지 않는다. ①은 판단 자식(반영·코디네이터)이 끝난 뒤에도 그 결과 파일이 그대로면 멈춘다 "진전 없음: 묶음 결과 반영 안 됨"(--add-dir 누락 등 — 끝없이 다시 부르지 않는다) ② 오르카 스크립트 구동 (오르카 있음, 기본) — (스킬의 references/orca.md 2절 블록을 이 자리에 그대로 옮긴다: 구동 스크립트·밖의 감시·상태판·답하기. ②가 아니면 이 줄을 지운다) ③ 서브에이전트 — 상위 세션이 구현·검증 서브에이전트를 차례로 띄우고 반영 토막을 직접 한다 ①-병렬 (오르카 없음, 기본) — 스크립트는 띄우고 기다리기만, 판단은 코디네이터 자식이: 반복 { Next Action 에 "질문:" 이 있고 "답:" 이 없으면 → 전역 질문이 하나라도 있으면 멈추고 사람에게. 전부 태스크 질문이면 그 행만 정지로 두고 아래를 돈다 Phase Overview 가 전부 completed 면 → 끝 Next Action 에 "묶음 대기: phase-NN" 이 있고 (임시)/bundle-phase-NN.txt 가 없으면 → 위 "phase 완료 묶음" 을 돌리고 반복의 첫 줄로(결과는 다음 코디네이터 자식이 읽는다) claude -p "<코디네이터 지시문>" --allowedTools "<코디네이터 허용 목록>" --add-dir <임시> STATE.md 의 Assignments 행마다 — 결과 파일(done-·verdict-.txt)이 남은 행은 건너뛴다(끝난 자식이다. 다시 띄우면 결과를 덮어쓴다) (워커 트리에서, 병렬로 띄우고 전부 끝날 때까지 기다린다): 토막 = 구현 | 수정 → claude -p "<구현 지시문> 태스크: " --allowedTools "<허용 목록>" > (임시)/done-.txt 토막 = 검증 → claude -p "<검증 지시문> 태스크: " --allowedTools "<검증 허용 목록>" > (임시)/verdict-.txt 격리 3이면 위 구현·수정 행이 끝난 뒤, e2e 대기 행과 검증 행을 슬롯 순서대로 하나씩: 앱 전환 (local-dev -Down → 그 워커 트리에서 local-dev) → e2e 대기 행이면 e2e 게이트 명령을 직접 실행 → 종료 코드 0 이면 행을 검증으로 바꾸고 이어서 검증 자식, 아니면 행을 수정으로 되돌리고 출력을 (임시)/e2e-.txt 에 남긴다 (판단이 없는 전환·실행이라 스크립트가 한다. 분류는 다음 라운드의 수정 자식이 e2e-.txt 를 읽고 한다) Assignments 가 비어 있고 Next Action 에 "배정 없음:" 이 있고 "묶음 대기:" 는 없으면 → 멈춤 (차단 대기 또는 막다른 곳) 답 없는 태스크 질문이 있고 Assignments 행이 전부 정지면 → 멈춤 "질문 대기" } 코디네이터 지시문 (토막: 반영+배정. ①-병렬과 ② 공통): loop_setup/LOOP.md 와 plan_setup/STATE.md 를 읽고 0절대로 시작하라(0절의 쓰기 항목은 네 일이다). 너는 구현하지 않는다. 질문: 은 머리로 범위를 가른다(11단계 "질문의 범위") — 그 태스크 하나에 걸린 것(3회 소진·판정 불가·중단 두 번째·워커 질문)은 "질문: — …", 그 밖(병합 충돌·반복·phase 완료 묶음·미활성 승인·하네스 ②③·환경 문제 두 번째·만료된 알림·미결)은 "질문: 병합 충돌 — …" 처럼 태스크 ID 로 시작하지 않는 머리로 적어라(애매하면 이쪽). 답 없는 태스크 질문이 있어도 아래를 평소대로 하라 — 그 행은 정지로 두고 슬롯은 2.6) 대로 park 로 놓는다. 0) 사용자가 기본 브랜치(8절)에 커밋한 것이 있으면 git merge <기본 브랜치> 를 먼저 하라(충돌이면 질문: 과 종료). 그다음 Next Action 의 답: 을 0절 5번대로 반영하라 — 차단 해소, 재판정, 건너뛰라(git branch -D park/ 하고 행을 지운다 — park 가 없는 옛 정지 행이면 그 워커 트리에서 git reset --hard coordinator && git clean -fd. failed 유지·알림:). 정지 행을 다시 움직이는 답(이어서·워커 질문의 답· 다시·하네스 보강·수용 기준 재작성 뒤 등)은 워커 칸이 park/ 면 여기서 반영하지 말고 2.7) 에서 되살리며 반영한다(두 줄도 거기서 지운다). 워커 칸이 worker-N 이면(park 가 없는 옛 정지 행) 여기서 그 슬롯 행을 수정(이어서·답)·구현(다시·보강)으로. 1) (임시)/done-.txt 가 있는 행마다(②: dispatch-.txt 가 남은 행은 건너뛰어라 — 그 워커의 worker_done 을 구동 스크립트가 아직 처리하지 않았다. 다음 라운드에 반영한다): END 가 없으면 워커 중단 — Retry History 에 "구현 중단", 파일을 지우고 행을 유지하라(두 번째면 질문: — … 과 행 정지 — 2.6). STATUS 가 "task_validation 대기" 면 행을 검증으로, "e2e 대기"(격리 3) 면 행을 e2e 대기로, "미활성" 이면 승인을 따라 완료 판정으로, "failed:" 면 3절대로 Retry·질문: 을 남기고 행을 정지로(슬롯은 park 로 놓는다 — 아래 2.6), "하네스 부족" 이면 harness-update 를 ①만 하고 행을 구현으로 되돌려라(②③이면 질문: 과 정지). RETRY 줄은 그 태스크의 Retry History 로(분류가 진짜 결함인 줄마다 Cycle +1 — 격리 3 의 e2e 실패로 시작한 수정도 여기서 센다), LOG 줄은 LOG.md 로 옮기고 파일을 지워라. 2) (임시)/verdict-.txt 가 있는 행마다(②: dispatch-.txt 가 남은 행은 1)과 같이 건너뛴다): END 를 확인하고(없으면 잘린 것 — 지우고 행을 검증으로 유지, 두 번째면 질문: — … 과 행 정지 — 2.6) 판정을 STATE·LOG 로 옮겨라. PASS 면 한 번에 하나씩: 그 워커 트리에서 커밋하고 코디네이터 트리에 병합한 뒤 태스크 문서를 completed 로, 코디네이터 트리에서 커밋, 기본 브랜치(8절)를 fast-forward, 완료 후 점검(harness-update ①)·phase 판정(완료 묶음은 돌리지 마라 — 태스크 전부 completed·수용 기준이 차면 5절 "완료 묶음" 1번대로 Next Action 에 "묶음 대기: phase-NN" 을 적고 남은 배정 없음: 은 지운다. 구동 스크립트가 돌린다. e2e.md 의 묶음 명령 절이 비었으면 신호 없이 5절 4번), 행을 지우고 슬롯을 비워라(워커 트리는 git reset --hard coordinator && git clean -fd, 슬롯 1 은 문서 커밋 뒤 git clean -fd). 병합 충돌은 풀지 말고 질문: 이다. FAIL 진짜 결함이면 Retry 를 올리고 관찰을 Retry History 에 적은 뒤 행을 수정으로 되돌려라(같은 슬롯). 환경 문제면 local-dev 재준비 후 행을 검증으로. 판정 불가·3회 소진이면 질문: 과 행 정지 — 슬롯은 park 로 놓는다(아래 2.6). 실패는 LOG.md 에 "- 실패: / <게이트> / <검사 ID> / <분류>" 줄로 남겨라. 격리 3 이면 검증 행에 dispatch 파일이 있는 동안(검증 워커가 도는 중) local-dev·e2e 처럼 앱을 바꾸는 일을 하지 말고 다음 라운드로 미뤄라 — 앱 전환은 구동 스크립트가 슬롯 순서로 한다. 2.4) 완료 묶음 결과: Next Action 에 "묶음 대기: phase-NN" 이 있고 (임시)/bundle-phase-NN.txt 가 있으면 5절 "완료 묶음" 3~5번대로 — END 가 없거나 COMMIT 뒤에 plan_setup/·loop_setup/ 밖의 변경이 병합됐으면 파일만 지운다(구동 스크립트가 다시 돌린다). EXIT 줄이 하나 이상이고 전부 0 이면 phase 완료(5절 기록)와 "- 완료 묶음 PASS: phase-NN / COMMIT / 명령 개" 한 줄. EMPTY(EXIT 0개)는 통과가 아니다 — 미활성이다. 그 밖이면 LOG: 의 로그를 읽어 3절대로 분류해 "- 실패: phase-NN 완료 묶음 / <게이트> / <검사 ID> / <분류>" 를 남기고 — 진짜 결함·낡은 스펙이면 질문: phase-NN 완료 묶음 — …(전역), 환경 문제·흔들림·시간초과·중단이면 파일만 지운다(그 phase 의 마지막 "완료 묶음 대기." 뒤 두 번째면 질문:), 미활성이면 Standing Decisions 의 승인으로 완료, 없으면 미활성 승인 질문:. 완료든 질문: 이든 "묶음 대기:" 줄과 결과 파일을 지워라(다시 돌릴 때만 신호를 둔다). 묶음 명령을 직접 돌리지 마라 — 네 허용 목록에 없다. 2.5) 반복 판정(3절): LOG.md 에서 같은 검사 ID 가 서로 다른 태스크 둘 이상에 나왔는지 세라(그 ID 의 마지막 "- 반복 처리:" 뒤만). 걸렸고 harness-update ①로 고칠 수 있으면 지금 하고, ②③이거나 격리 부족(격리 3에서 DB·앱을 쓰는 테스트의 흔들림)이면 질문: 을 남기고 3) 배정을 건너뛰어라. 처리했으면 "- 반복 처리: <검사 ID> → <무엇을 했나>" 를 남겨라. 네가 할 수 있는 것을 알림: 으로 미루지 마라. phase 를 새로 열 차례면 0절 7번(만료된 알림·판단 시점이 온 미결)을 먼저 하라 — 걸리면 질문: 과 함께 다음 phase 를 열지 마라. 2.6) 슬롯 반납(8절 "정지 행과 park"): 워커 칸이 아직 worker-N 인 정지 행 중 태스크 질문(질문: <그 ID> — …)으로 선 것이고 그 슬롯에 코디네이터보다 앞선 커밋이 없는 것마다 (워커 트리면 git rev-list --count coordinator..HEAD 가 0. 슬롯 1 은 늘 그렇다. 커밋이 있으면 — PASS 뒤 병합 충돌처럼 — 되살릴 때 그 커밋이 빠지므로 슬롯을 쥔 채 둔다. 전역 질문으로 선 행도 둔다), 이번 라운드에 정지가 됐든 앞 라운드에서 못 옮겼든, 네 문서를 먼저 커밋한 뒤 그 슬롯 트리에서 git add -A → git branch -f park/ $(git commit-tree $(git write-tree) -p HEAD -m "park — 미검증. 병합하지 않는다") → 워커 트리면 git reset --hard coordinator && git clean -fd, 슬롯 1 이면 git reset --hard HEAD && git clean -fd. 행의 워커 칸을 park/ 로 바꿔라(토막은 정지 그대로). 그 슬롯은 이제 비어 있다. 2.7) 되살리기: 정지 행을 다시 움직이는 답이 달린 park 행마다(0) 에서 미룬 것) 빈 슬롯이 있으면 새 배정보다 먼저 — 그 슬롯을 재배정처럼 청소하고(슬롯 1 은 문서 커밋 뒤) git cherry-pick --no-commit park/ && git reset -q, 워커 칸을 그 슬롯으로, 답을 0절 5번대로 반영해 토막을 정하고(이어서·워커 질문의 답 → 수정 Cycle 0, 다시·보강·재작성 뒤 → 구현) 그 질문:·답: 두 줄을 지워라. 충돌하면 그 슬롯을 같은 청소로 비우고 park 행은 그대로, "질문: 병합 충돌 park/ — …"(전역). 빈 슬롯이 없으면 두 줄을 남겨 두고 다음 라운드로 — 막힌 것이 아니라 미룬 것이니 새 질문: 을 적지 마라(답: 이 남아 있어 구동기는 서지 않는다). 3) 배정: (dag-render 실행 명령) --ready 를 돌려 지금 열 수 있는 후보(지금의 completed 집합으로 다시 센 진입 차수 0, blocked 아님)를 받고, 그중 현재 phase·DAG.md 에 있음·돌고 있는 행과 파일 겹침(DAG.md 점선)·같은 자원 그룹·단독 규칙(8절 자원 충돌 표)에 안 걸리는 태스크를 빈 슬롯 수만큼 골라 행을 추가하고 태스크 문서를 in_progress 로 써라. 워커 칸이 park/ 인 정지 행은 슬롯을 쥐지 않지만 충돌 검사에서는 돌고 있는 행으로 쳐라 (되살릴 변경이 그 파일 위에 있다). 2.7) 의 되살리기가 새 배정보다 먼저다 — 빈 슬롯은 그것에 먼저 쓴다. DAG.md 의 폭 표는 세팅 때의 추정이다 — 배정 근거로 쓰지 마라(선행이 끝났으면 "다음 단계" 여도 지금 연다). 슬롯 1 은 이 트리(coordinator), 슬롯 2 이상은 worker-N 워크트리(없으면 여기서 git worktree add 로 만든다). Assignments 머리줄(동시 n / 상한 N · 워크트리 목록)을 갱신하라. DAG 에 없는 태스크 문서가 있으면 "알림(loop-update, ~다음 phase 전): DAG 에 없는 태스크 ". 배정할 것이 없고 미완료가 남았으면 Next Action 에 "배정 없음: <이유>" 를 남겨라(구동 스크립트의 정지 신호다. 다음 라운드에 배정하면 지운다). 묶음 대기: 가 있으면 적지 말고 남아 있는 배정 없음: 줄은 지워라 — 구동 스크립트 차례다(남기면 구동 스크립트가 선다). 4) STATE.md 의 Task Status·Current Phase 를 태스크 문서 기준으로 맞추고 종료하라. 병렬에서 문서를 쓰는 것은 너뿐이다(구동 스크립트가 자식 사이에 바꾸는 Assignments 토막 값, 질문 답변 자식의 질문: 한 줄, 답하기 탭이 적는 사람의 답: 한 줄만 예외). 질문 답변 지시문 (②, 워커의 ask·escalation 을 받을 때): loop_setup/LOOP.md 와 plan_setup/STATE.md, 그리고 태스크 의 문서를 읽어라. worker-N 이 이렇게 물었다: "<질문>" (선택지: <있으면>). 이 문서들과 harness_setup/ 문서로 답할 수 있으면 첫 줄에 "답: <답>" 만 출력하라. 요구사항 해석·수용 기준 변경·게이트 약화·범위 확장·미결 판단이 필요한 질문이면 답하지 말고, STATE.md Next Action 에 "질문: — <질문>" 을 적고 첫 줄에 "질문:" 만 출력하라. 그 밖의 파일은 쓰지 마라. 병렬 구현·검증 지시문에 덧붙이는 줄: "너는 worker-N 이다. plan_setup/·harness_setup/ 문서는 코디네이터 트리 (경로) 에서 읽고, 0절의 쓰기 항목은 건너뛰어라. STATE.md·LOG.md·태스크 문서·하네스 문서를 쓰지 마라. local-dev 는 8절 슬롯 환경변수 표의 값으로 자기 트리에서 돌려라. (구현) 시작 전 하네스 점검에서 부족하면 구현하지 말고 STATUS: 하네스 부족 으로 끝내라. 격리 3이면 e2e 를 돌리지 말고 unit 까지 통과한 뒤 STATUS: e2e 대기 로 끝내라. (수정) (임시)/e2e-.txt 가 있으면 그것이 실패한 e2e 출력이다 — 분류하고 진짜 결함만 고친 뒤 지워라. 마지막에 8절 워커 규칙의 출력 블록을 END 까지 출력하라." ``` **코디네이터 허용 목록**: 파일 읽기·쓰기(`STATE.md`·`LOG.md`·태스크 문서·`harness_setup/` 문서), `local-dev`, `dag-render --ready`(배정 단계 — 옵션 없는 실행은 `DAG.md` 를 다시 쓰므로 `--ready` 한 가지만), `git add`·`git commit`·`git merge`·`git worktree`·`git checkout`·`git branch`, 결과 파일 삭제. 제품 코드 수정은 주지 않는다. **phase 완료 묶음 명령은 주지 않는다** — 구동 스크립트가 돌린다(9단계). 주어도 슬롯 환경변수를 정하는 명령과 10분 넘는 실행이 막혀 돌지 못한다. **질문 답변 허용 목록**(②): 파일 읽기, `STATE.md` 쓰기. 답을 만드는 자리라 코드·git·하네스 명령은 주지 않는다. **②의 워커에는 허용 목록을 줄 수 없다** — `worker-start` 에 그 플래그가 없고, 워커는 사용자의 에이전트 권한 설정으로 돈다. 그래서 검증 워커의 "쓰기·git 금지"는 지시문으로만 지켜진다. 이 사실을 보고에 적고, 사용자의 권한 설정에 무엇을 허용해야 하는지(게이트 명령·`local-dev`·파일 쓰기)를 함께 안내한다. **격리 3의 e2e 게이트 명령은 구동 스크립트가 직접 돌린다**(①-병렬·② 같다) — 종료 코드만 보는 일이라 판단이 없다. **허용 목록**: 구현·반영은 파일 읽기·쓰기, `local-dev`, 게이트 명령, `git add`·`git commit`·`git restore`, 판정 파일 삭제. 검증은 파일 읽기, `local-dev`, 그리고 `HARNESS.md` 에 적힌 **관찰 수단**(브라우저 조작 도구 또는 e2e 도구 실행)만. 권한 전체 건너뛰기는 사용자가 명시적으로 켠 경우에만. **판정 파일**: `/verdict-<태스크 ID>.txt` (저장소 밖. 태스크 ID 로 파생해 병렬에서 겹치지 않는다). 검증 세션의 출력을 받는 **일시 파일**이고 반영 토막이 옮긴 뒤 지운다. 기록이 아니라 전달 수단이라 커밋하지 않고, 남아 있으면 반영이 안 된 것이다. 마지막 줄 `END` 가 없으면 잘린 것이다. **사람에게 넘어오는 지점**: `Next Action` 의 `질문:` (3회 소진·판정 불가·미활성 승인·차단 대기·phase 완료 묶음 실패·반복 판정의 ②③·만료된 알림·판단 시점이 온 미결·②에서 문서로 답할 수 없는 워커 질문). **사람의 답이 돌아오는 길**: `Next Action` 아래 `답:` 줄을 직접 적거나 대화형 세션에서 루프에게 말한다. ②면 질문이 생길 때 오르카에 뜨는 답하기 탭(또는 8절 답하기 명령)이 대신 적는다. 병렬이면 **코디네이터 트리의 `STATE.md`** 다(`main` 의 것은 fast-forward 로 덮인다). 다음 자식이 0절에서 읽어 반영하고 지운다. **판정 블록**은 검증 세션이 출력하고 반영 토막이 `STATE.md`·`LOG.md` 로 옮긴다 — 검증 세션은 그 두 파일을 쓰지 않는다. **앱 서버는 세션이 끝나도 살아 있어야 한다** — `local-dev` 가 분리된 프로세스로 띄운다(하네스의 규칙). 검증 세션은 `-Status` 로 확인하고 없으면 다시 띄운다. ## 5. 완료와 기록 **코드 작성이 끝난 것은 완료가 아니다.** `verification` 의 게이트마다 **활성 상태에서 통과했거나, 미활성이면 `Standing Decisions` 에 유효한 승인이 있어야** 완료다. 커밋 조건도 이것과 같다. 기록 순서: **태스크 `status` → `STATE.md` → `LOG.md` → 커밋.** 어긋나면 태스크 문서가 이긴다. | 시점 | STATE.md | LOG.md | |---|---|---| | 시작 | `Current Task`, `in_progress`, `Retry` 초기화. phase 의 첫 태스크면 `Phase Overview` 를 `in_progress` 로 | `### Started.` | | 게이트 | `Last Verification` | 통과는 한 줄, **실패는 관찰과 분류** + `- 실패: / <게이트> / <검사 ID> / <분류>` | | 재시도 | `Retry` 의 `Cycle`(진짜 결함만 +1)·`History`(모든 갈래) | 무엇을 고쳤는가 | | 검증 대기 | `Last Verification` = ` / task_validation 대기 / —` (구현 토막의 마지막 쓰기). 미활성이면 `task_validation 미활성` | `### lint·unit·e2e PASS. task_validation 대기.` | | 완료 | `completed`, 다음 태스크, `Retry` 초기화, **`Last Verification` 을 ` / PASS / —`(미활성 승인이면 `PASS (미활성 승인)`)로 덮어쓴다** — `대기`·`미활성` 을 남기면 구동기가 반영 토막을 또 연다 | `### Verification PASS.` | | 잘린 판정 | `Retry` 의 `History` 에 `검증 중단` 한 줄(`Cycle` 미증가). 두 줄째면 `Next Action` 에 `질문:` | `### task_validation 판정 파일 잘림.` | | 중단 | `failed`, `Next Action` | 세 번의 시도와 관찰 | | phase 완료 | `Phase Overview`·`Current Phase`·phase 문서 `Status`·`Task Status` 교체 | `### phase-NN Complete.` | | phase 완료 묶음 대기 | `Phase Overview` 그대로, `Next Action` 에 `묶음 대기: phase-NN`(`배정 없음:` 은 적지 않는다) | `### phase-NN 태스크 전부 completed — 완료 묶음 대기.` | | phase 완료 묶음 실패 | `Phase Overview` 그대로, `Next Action` 에 `질문:` | `### phase-NN 완료 묶음 FAIL — FLOW-xx (분류).` | 관찰은 재현 가능한 문장으로 — "정상 동작 안 함"이 아니라 "무엇을 했더니 무엇이 보였다". **커밋은 위 완료 조건을 만족한 뒤에만.** 메시지에 태스크 ID. **merge·rebase 는 루프 밖(orchestration)이다.** **phase 완료**: 모든 태스크가 `completed`(`blocked` 나 `failed` 가 남았으면 완료가 아니다. `blocked_by: [폐기]` 만 예외) **그리고** phase 문서의 Acceptance Criteria 충족 **그리고** 'phase 완료 묶음'이 통과. `Phase Overview`(그 phase `completed`, 다음 phase `in_progress`)·`Current Phase`·phase 문서의 `Status` 를 갱신하고 `Task Status` 를 다음 phase 의 태스크로 교체한다. `Phase Overview` 의 값은 `pending`·`in_progress`·`completed` 셋뿐이다. **phase 완료 묶음**(느려서 매 태스크에서 뺀 흐름들)은 **여기가 유일하게 도는 자리**다. 안 돌리면 영원히 안 돈다. **돌리는 것은 구동 스크립트다**(①·② — 4절 "phase 완료 묶음"). 판단 자식은 묶음을 직접 돌리지 않는다 — 허용 목록에 없고, 슬롯 환경변수와 10분 넘는 실행이 막힌다. 명령은 `harness_setup/quality_gates/e2e.md` 의 `## phase 완료 묶음 명령` 절에 있고 구동 스크립트가 돌 때마다 그 절을 읽는다(여기·스크립트에 베끼지 않는다). **완료 묶음** — 판단 자식이 하는 일: 1. 태스크 전부 `completed`·수용 기준이 차면 `Next Action` 에 `묶음 대기: phase-NN`, `LOG.md` 에 `### phase-NN 태스크 전부 completed — 완료 묶음 대기.` `배정 없음:` 은 적지 않고 남아 있으면 지운다(구동 스크립트가 선다). 묶음 명령 절이 비었으면 신호 없이 4번 2. (구동 스크립트가 돌린다 — 결과 `(임시)/bundle-phase-NN.txt`: `COMMIT: ` · 명령마다 `EXIT <코드> <명령>`(또는 `LOCALDEV <코드>`·`EMPTY`·`EXIT 시간초과`·`EXIT 중단`) · `LOG: <로그>` · `END`) 3. 결과 파일이 있으면 판정한다. `END` 가 없거나 `git diff --name-only HEAD` 에 `plan_setup/`·`loop_setup/` 밖의 파일이 있으면 낡은 결과 — 파일만 지운다(신호를 두면 다시 돈다). `EXIT` 줄이 하나 이상이고 전부 0 이면 phase 완료와 `- 완료 묶음 PASS: phase-NN / COMMIT / 명령 개`. 그 밖이면 로그를 읽어 3절대로 분류하고 `- 실패: phase-NN 완료 묶음 / <게이트> / <검사 ID> / <분류>` — 진짜 결함·낡은 스펙이면 아래 실패 처리(`질문:`), 환경 문제·흔들림·시간초과·중단이면 파일만 지운다. 두 번째면(그 phase 의 마지막 `완료 묶음 대기.` 뒤의 실패 줄로 센다) `질문:` 4. 미활성(절이 비었다 — 신호 전이든 `EMPTY` 든)이면 3절 미활성 규칙 — `Standing Decisions` 에 그 범위의 승인이 있으면 이 근거를 승인으로 채우고, 없으면 미활성 승인 `질문:` 5. 완료든 `질문:` 이든 `묶음 대기:` 줄과 결과 파일을 지운다. `답: 재판정` 은 1번부터 다시 **실패하면 phase 를 완료로 찍지 않는다** — "게이트 명령이 아니다"는 태스크마다 안 돈다는 뜻이지 통과하지 않아도 된다는 뜻이 아니다. 다만 **재시도 3회는 적용하지 않는다**(예산은 태스크 단위다). 깨진 흐름과 그것을 만든 태스크를 `Next Action` 에 적고 사용자에게 보고한다. 차단으로 `대기` 인 흐름은 실행 대상이 아니고, 부분 구간 값을 판정으로 쓰지 않는다. ## 6. 환경 준비와 실패 분류의 도구 `local-dev` 명령: (실제 경로와 옵션). **종료 코드가 실패한 단계를 구별한다.** | 코드 | 무엇이 실패했나 | 누가 고치나 | |---|---|---| | 0 | 성공 | — | | (전제) | 런타임·컨테이너 엔진이 없다 | **사용자** — 설치가 필요하다 | | (환경변수) | 필수 키가 비어 있다 | **사용자** — 값을 채워야 한다 | | (의존 서비스 / 스키마·시드 / 앱) | 컨테이너·마이그레이션·앱이 안 떴다 | 재준비 → 안 되면 `harness-update` | > 실제 코드 번호는 `HARNESS.md` 의 로컬 개발 환경 절에 있다. **0이 아니면 그 자체가 환경 문제 확정**이고 > 여기서 추측할 것이 없다. `-Status` 는 아무것도 띄우지 않고 판정만 출력하므로 분류에 먼저 쓴다. ## 7. 하네스 갱신을 부르는 자리 | 언제 | 확인 | |---|---| | 태스크 시작 전 | 수용 기준 판정 수단 / 덮는 흐름·커버 영역 / 의존 서비스 / 게이트 명령이 이 유형에 걸리는가 | | 태스크 완료 후 | 활성 시점인가 / 흐름의 마지막 조각인가 / 커버 영역을 덮었나 / 새 의존 서비스·환경변수 | | 시점 무관 | 차단(OQ·CF) 해소(2절 절차의 마지막 단계) / 같은 흐름·검사 ID의 실패가 두 태스크에 걸쳐 나옴 | **전부 예이면 부르지 않는다.** "게이트 실패"는 그 자체로 트리거가 아니다 — 분류가 먼저다. **병렬에서는 코디네이터만 자기 트리에서 부른다.** 워커는 시작 전 점검에서 부족을 보면 `STATUS: 하네스 부족` 으로 끝낸다(8절 워커 규칙). ## 8. 병렬 운용 **기본은 병렬.** 병렬 여부: (예/아니요 — 세팅 때 받은 답). 동시 에이전트 수 상한: (N). 구동기: (②/①-병렬/①-단일). 폭이 1인 구간은 단일과 같다. 아니요면 이 절의 DAG·충돌 표만 유효하고 슬롯·격리는 9절 미결이다. ### DAG (태스크 단위) **그림은 `loop_setup/DAG.md`** — `harness_setup/scripts/dag-render.*` 의 출력이고 손으로 고치지 않는다. 태스크가 바뀌면 `loop-update` 가 스크립트를 다시 돌린다. `DAG.md` 에 있는 것: `mermaid` 그래프(실선 = `depends_on`, **점선 = `files` 겹침(동시 금지)**, 회색 점선 테두리 = `blocked`) / 구간별 폭 표 / 확인 결과(순환·없는 ID·`blocked` 후행) / 파일 겹침 쌍 목록. 규칙: 순환 없음 / 없는 ID 참조 없음 / `blocked` 와 그 후행 제외 / **동시 실행 후보 = 진입 차수 0** / **`files` 가 겹치면 간선이 없어도 동시에 스케줄하지 않는다.** 재생성: `(dag-render 실행 명령)` → `loop_setup/DAG.md`. 마지막 생성: (날짜, 태스크 n개). **배정 후보**: `(dag-render 실행 명령) --ready` — 지금의 태스크 `status` 로 다시 센 진입 차수 0(`pending`·선행 전부 `completed`·`blocked` 아님). 코디네이터가 배정마다 부른다. ### 구간별 폭 (DAG.md 의 표를 세팅 때 옮겨 적은 요약. 권장 에이전트 수는 사용자가 정한 상한과 폭의 작은 쪽. **세팅 때의 추정이다 — 상한을 고른 근거이지 배정의 근거가 아니다. 배정은 `--ready`**) | 구간 | 폭 | 권장 에이전트 | |---|---|---| | phase-01 시작 | 1 | 1 | | phase-01-task-01 완료 후 | 1 (01-02·01-03 겹침이라 직렬) | 1 | ### 자원 충돌 검사 결과 (파일 겹침 / 공유 런타임 자원 / 빈 `files`. 0건이면 "0건". 처리 값은 `단독` / `그룹 직렬: <그룹>` / `격리가 덮음`. 코디네이터는 이 표와 `DAG.md` 점선만 본다) | 태스크(쌍) | 자원 | 종류 | 자원 그룹 | 처리 | |---|---|---|---|---| | 01-02 ↔ 01-03 | `src/app/layout.tsx` | 파일 겹침 | — | 동시 금지(점선). 같은 모듈이면 `알림(plan-generator, …):` 으로 의존 추가 요청 | | 01-01, 02-03, 03-01 | `db/migrations/*`, `drizzle/meta/_journal.json` | 공유 런타임(DB 스키마·저널) | `migration` | 그룹 직렬 — 같은 그룹끼리만 동시 금지, 다른 태스크와는 동시 가능 | | 04-02 | `files: []` | 빈 `files` | — | 단독. `알림(plan-generator, …):` 으로 `files` 보완 요청 | ### 배정 **코디네이터가 태스크를 나눠 준다.** 구현 지시문에 `태스크: ` 를 넣고 워커는 2절 선택을 건너뛴다. 코디네이터가 `--ready` 후보(지금의 `completed` 집합으로 다시 센 진입 차수 0·`blocked` 아님) 중 현재 phase·`DAG.md` 에 있음·돌고 있는 행과 충돌 없음(파일 겹침 점선, 같은 자원 그룹, `단독`)을 판정한다. 폭 표로 배정하지 않는다 — 선행이 끝난 태스크는 표의 "다음 단계"여도 지금 열린다. 배정표는 `STATE.md` 의 `Assignments`(①-병렬·② 같다)이고 병합 전까지 "무엇이 도는가"의 원본이다. ### 슬롯과 워크트리 **기본 브랜치**: `(실제 이름 — main / master / …)`. 아래의 `main` 은 이것을 뜻한다. **여기서는 아무도 작업하지 않는다** — 사용자의 자리. 완료된 태스크만 fast-forward 로 들어온다. **코디네이터 트리**: `(절대 경로)`, 브랜치 `coordinator`. 코디네이터(문서 필자 — 8절 표의 예외 밖·배정·병합) + 슬롯 1 의 `worker-1`. 코디네이터는 문서 경로만 add 한다(`git add -A` 금지). **워커 트리** `worker-N`(N = 2 … 상한): `(경로 규칙 — 기본 ../<저장소>-worker-N. 저장소가 OneDrive 같은 동기화 폴더 아래면 저장소 안 .wt/worker-N 또는 동기화 밖 경로)`. `.wt/` 를 쓰면 `.gitignore` 에 있어야 한다. 워커 이름은 워크트리 유무와 무관하게 `worker-N`. 검증 워커는 구현이 돈 자리에서 이어서 돈다. 폭 1 이면 코디네이터 트리의 `worker-1` 하나, 워크트리 0개. 워커 트리는 폭 2 구간에 들어갈 때 만들고 재사용한다 — 재배정 직전 `git reset --hard coordinator && git clean -fd`(`git checkout coordinator` 는 다른 워크트리에 체크아웃된 브랜치라 거부된다). 폭이 줄어 안 쓰면: (지운다 / 둔다). 태스크 완료 = 워커 트리 커밋 → 코디네이터 트리 병합 → 기본 브랜치 fast-forward. 사용자가 기본 브랜치에 커밋했으면 라운드 시작 때 `git merge <기본 브랜치>` 를 코디네이터 트리에 먼저. **정지 행과 park** — 태스크 질문으로 `정지` 된 행은 슬롯을 놓는다. 코디네이터가 그 라운드에 자기 문서를 커밋한 뒤 그 슬롯 트리에서 `git add -A` → `git branch -f park/ $(git commit-tree $(git write-tree) -p HEAD -m "park — 미검증. 병합하지 않는다")` → 워커 트리 `git reset --hard coordinator && git clean -fd` / 슬롯 1 `git reset --hard HEAD && git clean -fd`. 행은 `정지` 로 남고 워커 칸이 `park/` 다 — 슬롯 수에서는 빼고, 충돌 검사(파일 겹침·자원 그룹·`단독`)에서는 도는 행으로 친다. "이어서" 면 빈 슬롯이 생기는 대로 새 배정보다 먼저 — 재배정 청소 뒤 그 슬롯에서 `git cherry-pick --no-commit park/ && git reset -q`, 행을 그 슬롯의 `수정` 으로(`Cycle 0`). 충돌하면 같은 청소로 비우고 `질문: 병합 충돌 park/ — …`(전역). "건너뛰라" 면 `git branch -D park/` 와 행 삭제. park 브랜치는 태스크가 완료되거나 건너뛰어질 때 지운다. park 커밋은 어느 브랜치에도 병합하지 않는다 — 검증받지 않은 보관함이다. **에이전트·모델 배치** — 구동기가 자식을 띄울 때 이 표에서 읽는다. 이름은 여기에만 있다(바꾸면 `loop-update`). | 역할 | CLI | 모델 | 추론 강도 | 한 급 올리는 조건 | |---|---|---|---|---| | 코디네이터 | (claude / codex) | (최상위 급의 실제 이름) | high | — | | 구현 워커 | (claude / codex) | (보통 급의 실제 이름) | medium | `priority: P0` 이고 `files` 3개 이상 / 3회 소진 뒤 "이어서" 재배정 | | 검증 워커 | (claude / codex — 구현과 다르게 두면 분리가 한 겹 더 강해진다) | (보통 급의 실제 이름) | medium | — | | 반영 토막(①-단일) | (claude / codex) | (보통 급의 실제 이름) | medium | — | 사용자 선택: (추천대로 / 전부 최상위 / 전부 보통 / 직접 지정 — 날짜). 자동 대체(`--fallback-model`)는 쓰지 않는다. ②의 질문 답변 자식도 코디네이터 행으로 띄운다. **구동 스크립트**: `harness_setup/scripts/loop-drive.(실제 확장자)` — 4절의 ①/② 알고리즘을 그대로 옮긴 것. 모드: (①-단일 / ①-병렬 / ②). 모델은 이 표에서 읽는다. ②면 **밖의 감시** `harness_setup/scripts/loop-watch.(같은 확장자)` 와 **상태판** `harness_setup/scripts/loop-status.(같은 확장자)` 도. **구동 시작 명령**: (실제 명령. ②면 오르카 터미널 안에서 — 예: 코디네이터 트리의 오르카 터미널에서 `node harness_setup/scripts/loop-drive.mjs`. 스크립트가 터미널 프로세스 밖으로 자신을 분리해 띄우고 그 터미널에 상태판을 그린다 — 그 뒤 터미널을 닫아도 루프는 돈다. 이미 돌고 있으면 띄우지 않고 알린다. 멈추려면 ②는 `<구동 시작 명령> --stop`(정지 요청 — `references/orca.md` 3절. 구동기가 단계 사이에서 스스로 멈추고, 판단 자식이 도는 중이면 그것이 끝난 뒤다). ①이나 손으로 끌 때는 도는 `claude -p` 자식이 없을 때 PID 를 끝낸다 — 자식을 끊으면 STATE.md·커밋 도중에 끊긴다(①-단일은 구현 자식도 커밋한다). phase 완료 묶음은 따로 돌아 두면 끝까지 돌고 결과 파일을 남긴다(다음 시작이 그것을 기다리거나 읽는다). 손으로 끌 때는 먼저 `(임시)/loop-exit.txt` 에 "정지 — 사람 요청" 한 줄을 쓴다 — 안 쓰면 밖의 감시가 죽음으로 보고 30분·2시간·8시간에 다시 알린다. 다음 시작은 PID 가 없으니 그대로 된다. PID 파일 없이 흔적만 남았거나 그 PID 의 시작 시각을 읽지 못해 "비정상 종료" 로 거부되면 확인하고 `--force` 를 붙인다) 사람이 여는 것은 처음과 `질문:` 에 답한 뒤 재개할 때뿐이다. 스크립트가 죽었다 다시 뜨면 ②는 `(임시)/loop-run.txt` 의 Run 을 `run-use` 로 이어받는다. **phase 묶음 상한**(①·②): 120분(잠정 — 실측으로 조정). 넘기면 9단계대로 자손만 끈다. **창 없이 띄우는 방식**(①·② — 묶음 모드·분리 실행): (실제 값 — Windows: WMI `Win32_Process.Create`, 창 숨김 / macOS·Linux: `setsid nohup`) (②면 `references/orca.md` 3절의 값을 여기에 넣는다 — 상태판·답하기 명령, 사이드바 한계, 분리 실행 방식, 알림 수단, 감시 기준값, 모델 오류 문구, 임시 폴더의 구동 파일, 구동 터미널, 대기 길이) **프로세스 시작 시각 읽기**: (실제 명령 — Windows: `powershell.exe -NoProfile -Command "(Get-Process -Id ).StartTime.ToUniversalTime().ToString('o')"` / macOS·Linux: `ps -o lstart= -p `). 구동 스크립트의 종료 코드는 "전체 완료"·"질문 대기"·"배정 없음" 이면 0, 그 밖의 정지는 1. **스모크 테스트**: (날짜 — ②면 가벼운 모델의 시험 워커 하나로 run-create → worker-start → ask/reply → 결과 파일 + worker_done → release 까지 돈 결과. ①이면 드라이런 결과) 독립 워크트리에서 작업 / 남의 워크트리를 고치지 않는다 / 공통 파일은 충돌 가능성을 확인한다 / 태스크 완료 후 커밋 / **merge·rebase 는 코디네이터(orchestration)의 책임.** 병합 충돌은 풀지 않고 `질문:`. ### 슬롯별 환경 (격리 방식: 1 / 2 / 3) | 슬롯 | 포트 | DB 이름 | 환경 파일 | |---|---|---|---| | 1 | 3001 | app_slot1 | `.env.slot1` | | 2 | 3002 | app_slot2 | `.env.slot2` | `local-dev` 가 이 변수를 읽는지: (확인함 / **안 읽음 — harness-update 필요, 그때까지 폭 1**). **격리 3이면**: 구현 워커는 `unit` 까지만 돌리고 `STATUS: e2e 대기` 로 끝낸다. 구동기가 `e2e 대기`·`검증` 행을 슬롯 순서대로 하나씩 — 앱 전환(`local-dev -Down` → 그 워커 트리에서 `local-dev`) → e2e 명령 직접 실행(0이면 검증 자식, 아니면 `수정` 행 + `e2e-.txt`) → 검증. 병렬 이득은 구현·lint·unit 까지다. 슬롯이 여럿이어도 **`task_validation` 분리는 여전히 필요하다.** ### 병렬에서 기록이 꼬이지 않게 하는 두 규칙 **(가) 칸을 넓힌다** — `Current Task`·`Last Verification`·`Retry` 를 태스크별 블록이나 목록으로. 넓힐 때 **기존 값을 새 형식으로 옮긴다.** 지우면 진행 중인 재시도 이력이 사라진다. `Current Task`·`Last Verification` 은 Layer 3이 만든 절이므로 **바꾼 사실을 보고에 올린다.** **(나) 필자를 하나로 줄인다** — 칸을 나눠도 파일 쓰기는 통째로 일어나 서로 덮어쓴다. 잠금으로 풀지 않는다(잠근 채 죽은 에이전트가 전체를 세운다). | 무엇 | 병렬에서 누가 쓰나 | |---|---| | 태스크 문서의 `status` | **코디네이터만 자기 트리에서** — 배정 시 `in_progress`, 병합 뒤 `completed`, 정지 시 `failed`. 워커 브랜치는 코드만 바꾼다 | | `STATE.md` | **orchestration 만.** 넓힌 `Last Verification`·`Retry` 는 지시문의 `태스크: ` 블록만 읽고 쓴다. 예외: 질문 답변 자식(②)은 `Next Action` 에 `질문:` 한 줄, 답하기 탭(②)은 사람의 `답:` 한 줄, 구동 스크립트는 자식 사이에 `Assignments` 토막 값만(격리 3 의 `e2e 대기` → `검증`/`수정`) | | `LOG.md` | **orchestration 만.** 워커는 출력 블록(`LOG:` 줄·판정 블록)을 넘기고 병합 순서대로 append 된다 | | 하네스 문서 (`harness-update`) | **코디네이터만 자기 트리에서.** 워커는 `STATUS: 하네스 부족` 으로 돌려보낸다 | **문서는 전부 코디네이터 트리의 한 필자, 슬롯은 코드만.** 같은 문서를 두 트리가 고치면 병합이 어느 쪽이 맞는지 판단할 수 없다. 워커가 죽어도 배정표와 워커 트리의 미커밋 변경이 남아 같은 슬롯에서 이어 간다. `STATE.md` 가 실시간이 아니게 되는 것은 감수한다 — 지금 무엇이 도는지가 급하면 태스크 문서를 본다. ### 워커 규칙 (병렬) - 코디네이터 트리 경로: `(절대 경로)`. `plan_setup/`·`harness_setup/` 문서는 **여기서 읽는다**. 코드·게이트 명령·`local-dev` 는 자기 트리(슬롯 1 은 코디네이터 트리, 2 이상은 `worker-N`)에서 - 0절의 쓰기 항목(2·4·5·7)을 건너뛴다. 차단 해소·승인 만료·`답:` 반영·phase 열 때의 확인은 코디네이터 - 구현 워커의 출력 블록: `TASK:` / `STATUS:`(`task_validation 대기` | `task_validation 미활성` | `e2e 대기`(격리 3) | `failed: <사유>` | `하네스 부족: <무엇>`) / `RETRY:`(사이클마다 한 줄) / `LOG:`(남길 문장) / `END` - 수정 워커는 `(임시)/e2e-.txt` 가 있으면 그것이 실패한 e2e 출력이다 — 3절대로 분류하고 진짜 결함만 고친 뒤 파일을 지운다 - 시작 전 하네스 점검에서 부족을 보면 구현하지 않고 `STATUS: 하네스 부족` 으로 종료. `harness-update` 는 부르지 않는다 - 정지 행은 슬롯을 놓는다 — 미커밋 변경은 코디네이터가 `park/` 로 옮긴다(위 "정지 행과 park"). "이어서" 면 빈 슬롯에 되살리고, "건너뛰라" 면 park 브랜치를 지운다 - `DAG.md` 에 없는 태스크는 배정 대상이 아니다(`알림(loop-update, …)`). 완료는 한 번에 하나씩 병합·완료한다 ## 9. 미결 (병렬이면 격리는 8절에서 정했으므로 여기 없다. `local-dev` 가 슬롯 변수를 안 읽으면 아래 첫 행. 병렬을 껐으면 둘째 행. **`언제` 는 루프가 확인할 수 있는 시점으로** — `phase-NN 진입 전`·`<태스크 ID> 완료 후`·`폭 2 구간 전`. 루프가 phase 를 열 때 이 열을 보고 시점이 왔으면 `질문:` 으로 올린다(0절 7번).) | ID | 미결 | 종류 | 기본값 | 무엇을 보면 정하나 | 언제 | |---|---|---|---|---|---| | — | 격리 적용 대기 — `local-dev` 가 슬롯 환경변수를 읽지 않음 | 차단 | 폭 1로 운용 | `harness-update` 가 스크립트를 고쳤는가 | 폭 2 구간에 들어가기 전 | | OQ-0xx | (병렬을 껐을 때만) 슬롯 간 DB·포트 격리 방식 | 유예 | 단일 | 실제 동시 에이전트 수, DB를 공유해야 하는 태스크의 실재 여부 | 병렬을 켜기 직전 (`loop-update`) | 선택지와 되돌리기 비용(8절 격리 표와 같다): 1. **슬롯별 환경 통째 분리** — 슬롯 번호로 DB·포트·컨테이너 이름을 파생. 완전 격리, 자원 N벌 2. **DB 서버 하나 + 슬롯별 데이터베이스와 전용 계정** — 가볍다. `local-dev` 에 권한 발급 단계가 추가된다 3. **격리 없이 공유 자원을 쓰는 게이트만 직렬화** — 새로 만들 것이 거의 없다. 병렬 이득이 구현·lint·unit 까지 **DAG 와 충돌 표는 순서와 파일 충돌을 덮지만 공유 런타임 인스턴스(DB·앱)는 덮지 못한다.** 포트는 겹치면 안 떠서 즉시 드러나고, **DB는 조용히 오염된다.** 그래서 폭 2 구간에 들어가기 전에 반드시 닫혀 있어야 한다. ```` ### `plan_setup/STATE.md` 에 더하는 절 ````markdown # Retry * **Task:** — * **Cycle:** 0 / 3 * **Gate:** — * **Classification:** — * **History:** — --- # Standing Decisions * (없음) --- # Assignments 동시: 0 / 상한 N · 워크트리: coordinator | 태스크 | 워커 | 토막 | 시작 | |---|---|---|---| | (없음) | | | | ```` > `Last Verification` 뒤, `Next Action` 앞에 끼운다. 기존 다섯 절은 건드리지 않는다. > `Retry` 는 태스크가 바뀌면 초기화하고, `Standing Decisions` 는 사람이 승인한 것만 쌓인다. > `Assignments` 는 **병렬일 때만** 넣는다(①-병렬·②의 배정표. 코디네이터 자식이 쓰고 구동 스크립트가 읽는다 — 스크립트는 격리 3 의 e2e 결과로 토막 값만 바꾼다). 토막 값은 `구현`·`수정`·`e2e 대기`(격리 3만)·`검증`·`정지` 다섯. 워커 칸은 `worker-N`, 정지 행이 슬롯을 놓았으면 `park/`(8절 "정지 행과 park"). > 병렬이면 `Current Task`·`Last Verification`·`Retry` 도 **태스크별 블록**으로 넓힌다(11단계 (가)) — Layer 3 절을 바꾸는 것이므로 보고에 올린다. --- ## 에이전트별 참고 - Claude Code에서 실행 중이면: `references/claude-code.md` - Codex에서 실행 중이면: `references/codex.md` - 구동기가 ②(오르카)면 함께: `references/orca.md` — ② 원리·`LOOP.md` 4절 ② 블록·8절 ② 값·스모크·점검 행 **둘 다 반드시 확인할 것은 하나다** — 이 환경에서 `task_validation` 을 **어떻게 분리된 세션으로 돌리는가**. 루프가 그 분리를 실제로 만들어 내는 쪽이라, 여기가 Layer 5의 핵심 구현 지점이다. 분리 수단이 없는 환경이면 `LOOP.md` 에 그 사실을 적고 사용자에게 알린다. 분리된 척하는 검증은 통과 기록만 남기고 아무것도 막지 않는다.