ouroboros · 오케스트레이터 실행 루프(Seed→프롬프트→에이전트 실행)

한 줄 요약

Seed(무엇을 만들지 적은 명세서) 안의 합격 기준들을 의존성에 따라 레벨로 나눠 AI 에이전트를 병렬로 돌리는 실행 엔진이다. 왜 배우나: “한 번의 명령으로 여러 작업을 동시에·안전하게 돌려 코드를 완성하는” ouroboros의 심장부이기 때문이다.

그림

flowchart TD
    A["ooo run → execute_seed 호출"] --> B["prepare_session: 세션·취소·하트비트 등록"]
    B --> C["프롬프트 만들기<br/>build_system_prompt / build_task_prompt"]
    C --> D{"합격기준 2개 이상<br/>or fat-harness 모드?"}
    D -- 예 --> E["DependencyAnalyzer.analyze<br/>LLM에게 의존성 질의 → 의존성 그래프"]
    E --> F["to_execution_plan<br/>레벨 그래프 만들기"]
    F --> G[레벨을 위에서 아래로 직렬 루프]
    G --> H["한 레벨 안의 합격기준들은 병렬 실행<br/>_execute_atomic_ac → adapter.execute_task"]
    H --> I["메시지마다 stall 타이머 리셋<br/>+ 하트비트 + 진행 이벤트"]
    I --> J["코디네이터: 파일충돌 감지·중재 리뷰 게이트"]
    J --> K{"다음 레벨 있나?"}
    K -- 예 --> G
    K -- 아니오 --> L[OrchestratorResult 반환]
    M["Watchdog 벽시계 / 취소 레지스트리"] -. 항상 감시 .- G

쉽게 풀기

흔한 오해부터 깨자. “AI에게 일 시키기 = 프롬프트 한 번 던지고 답 한 번 받기”가 아니다. 요리 주방에 비유하면 쉽다.

  1. 주문서(Seed)를 받는다. “무엇을 만들지(goal)“와 “완성 조건들(합격 기준, Acceptance Criteria=AC, 보통 여러 개)“이 들어 있다.
  2. 요리 순서를 파악한다(의존성 분석). “소스를 졸이려면 먼저 육수를 내야 한다”처럼 선후 관계를 LLM에게 직접 물어 의존성 그래프를 만든다.
  3. 동시에 할 것끼리 묶는다(레벨/스테이지). 서로 안 막는 작업은 한 레벨로. 야채 썰기와 물 끓이기는 같은 레벨이다. 이 레벨들이 위→아래로 줄선다.
  4. 레벨 하나를 통째로 병렬 조리한다. 한 레벨 안 AC들은 각자 별도 에이전트(요리사)가 동시에 작업하고, 각자는 주문서 전체가 아니라 자기 AC 한 개 + 옆 요리사 경계 + 직전 단계 정보만 받는다 → 충돌·혼선 감소.
  5. 레벨이 끝나면 심판(코디네이터)이 검사한다. “두 요리사가 같은 도마(파일)를 동시에 썼나?”를 보고, 충돌이 있으면 중재 AI 세션으로 정리·경고를 다음 레벨에 전달한다. 충돌 없으면 이 비싼 단계는 건너뛴다(비용 0).
  6. 성적표를 낸다. 전부 성공했는지 판정해 OrchestratorResult(성공·세션ID·요약·소요시간)로 반환한다.

전 과정을 OrchestratorRunner.execute_seed()가 지휘하고, 도는 내내 watchdog·하트비트·취소 레지스트리가 멈춤/타임아웃/취소를 따로 감시한다.

flowchart LR
    subgraph L0["레벨0 · 병렬"]
        A0[AC1]
        A1[AC2]
    end
    subgraph L1["레벨1 · 병렬"]
        A2[AC3]
    end
    L0 -->|코디네이터 게이트| L1
    A0 -. 자기 AC+경계+직전컨텍스트만 .-> A0

핵심 모양: "단일 호출"이 아니라 레벨 그래프를 위→아래 한 줄씩, 각 줄은 병렬로 도는 루프다. 세로는 직렬, 가로는 병렬.

핵심 정리

루프가 다루는 주요 데이터 구조 4종. 전체 스키마는 콜아웃으로 분산했다.

구조한 줄 역할근거
OrchestratorResult루프의 최종 성적표(반환값)runner.py:149
AgentMessage에이전트가 흘리는 메시지 한 건adapter.py:645
ExecutionStage / StagedExecutionPlan레벨 그래프(직렬 묶음)dependency_analyzer.py:118,137
FileConflict / CoordinatorReview레벨 사이 충돌 검사·중재coordinator.py:85,102

생명주기(트리거→반환)는 한눈에 보자.

sequenceDiagram
    participant U as 사용자(ooo run)
    participant R as Runner
    participant A as DependencyAnalyzer
    participant P as ParallelACExecutor
    participant C as Coordinator
    U->>R: execute_seed(seed, exec_id)
    R->>R: prepare_session(세션·취소·하트비트)
    R->>R: build_system/task_prompt
    R->>A: AC 2개↑ or fat-harness → analyze
    A-->>R: 레벨 그래프(StagedExecutionPlan)
    loop 스테이지 직렬
        R->>P: 레벨 AC 병렬 실행
        P->>C: 레벨 끝 → 충돌 감지·중재
    end
    R-->>U: OrchestratorResult

실제 예시

단일 AC를 실제 실행시키는 핵심 메시지 루프의 골자: async for로 스트림을 소비하며 메시지마다 stall 데드라인을 리셋(900s 침묵=stall)하고, resume_handle을 갱신하며, 30s마다 하트비트를 얹는다.

직접 만들 때 참고할 최소 골격(개념 재현용):

직접 구현 체크리스트:

  • Seed의 goal+acceptance_criteria를 번호 매긴 task 프롬프트로 변환했는가
  • system 프롬프트에 전략 fragment/계약/AC 추적/복구 프로토콜을 합쳤는가
  • AC 의존성을 그래프화·위상정렬(스테이지)했는가 (분석 실패 폴백=단일 병렬 레벨)
  • 스테이지는 직렬, 스테이지 내 AC는 병렬(task group)로 돌리는가
  • 선행 AC 실패/차단 시 의존 AC를 blocked로 건너뛰는가
  • execute_taskasync for로 소비하며 매 메시지 resume_handle을 갱신하는가
  • stall(침묵 900s) CancelScope를 메시지마다 리셋, 30s heartbeat를 방출하는가
  • 레벨 끝에 파일 충돌을 감지하고 있을 때만 코디네이터 리뷰를 띄우는가(없으면 비용 0)
  • 코디네이터 경고를 다음 레벨 프롬프트로 주입하는가
  • 취소 레지스트리 체크 + watchdog 벽시계(session_wall_clock_seconds)로 강제 종료하는가
  • 최종적으로 OrchestratorResult(success/session_id/summary/duration)로 반환하는가

요약 & 셀프체크

3줄 요약:

  • Seed의 합격 기준들을 의존성 레벨로 나눠 레벨은 직렬·레벨 안은 병렬로 AI 에이전트를 돌린다.
  • 각 에이전트는 자기 AC 한 개와 경계 안내만 받아 충돌을 줄이고, 레벨이 끝날 때마다 코디네이터가 파일 충돌을 검사·중재한다.
  • 도는 동안 stall 타이머·하트비트·watchdog·취소 레지스트리가 멈춤과 폭주를 막고, 끝나면 OrchestratorResult로 성적표를 낸다.

스스로 답해보기:

  1. “세로는 직렬, 가로는 병렬”은 레벨 그래프에서 정확히 무엇을 가리키는가?
  2. 코디네이터 리뷰 세션은 항상 도는가, 어떤 조건일 때만 도는가? 그 이유는?
  3. AI가 15분 동안 아무 메시지도 안 보내면 무슨 일이 일어나며, 메시지가 올 때마다 리셋되는 것은 무엇인가?

연결

OB_개요 · _분석축_루브릭 · OB_20_spec-engine-seed-and-double-diamond(Seed가 어떻게 만들어지나) · OB_30_event-sourcing-and-projection-readmodel(진행 이벤트가 어디 쌓이나) · OB_50_provider-adapters-and-backend-neutral-runtime(execute_task 어댑터) · OB_10_entrypoint-cli-and-ooo-command-routing(ooo run 진입)