# 자동화 오케스트레이터 통합 구현 — 맥락노트
> 버전: 1.0 | 작성일: 2026-03-24 | 작성자: 오딘 (2팀장) | 에이전트 미팅 만장일치 합의

---

## 1. 왜 이 설계를 선택했는가

### 1.1 레이어 분리 원칙 (Layer 2 ↔ Layer 3)

**결정**: .done 이벤트를 Layer 2(알림)와 Layer 3(오케스트레이션)가 **독립적으로** 처리하도록 설계.

**근거**:
- Layer 2(notify-completion + done-watcher)는 **알림 전용** — .done 파일을 소비(삭제)하지 않고 .done.notified 마커만 생성
- Layer 3(event_bus.py)는 **오케스트레이션 전용** — .done 파일을 orchestrator/incoming/으로 **복사** 후 원자적 소비
- 두 레이어가 .done 원본을 두고 경쟁하지 않으므로 TOCTOU 리스크 제거

**대안 방안과 기각 사유**:
| 대안 | 기각 사유 |
|------|-----------|
| Layer 3가 .done을 직접 소비(mv) | Layer 2 알림 경로가 .done 부재로 실패 가능 |
| .done 생성 시 Layer 3에 직접 알림 | finish-task.sh 수정 필요 = 기존 호환성 위반 |
| Layer 2와 Layer 3를 단일 스크립트로 통합 | 관심사 분리 위반, 장애 시 알림+오케스트레이션 동시 실패 |

### 1.2 아누와 auto_orch 공존 모델

**결정**: auto_orch는 아누의 **독립 피어** (하위도 대체도 아님)

**근거** (task-899.1 미팅 합의):
- 아누: 대화형, 즉흥 판단, 제이회장님과 1:1 대화
- auto_orch: 자동화, 사전 정의된 YAML 파이프라인 반복 실행
- 동일 팀에 대한 동시 접근은 TeamLock으로 후발 거부 (아누 우선 원칙은 없음 — 먼저 락 획득한 쪽이 우선)

**대안 방안과 기각 사유**:
| 대안 | 기각 사유 |
|------|-----------|
| auto_orch를 아누 하위에 배치 | 아누 다운 시 자동화도 전부 중단 |
| 아누를 auto_orch로 대체 | 즉흥 판단 능력 상실, 대화형 인터페이스 불가 |
| 우선순위 기반 (아누 > auto_orch) | 구현 복잡도 대비 이득 미미, TeamLock으로 충분 |

### 1.3 30초 polled tick vs 이벤트 기반

**결정**: 30초 systemd timer + stateless tick

**근거** (task-899.1 미팅, 오딘-야누스 합의):
- 작업 최소 단위가 수 분 ~ 수 시간 → 30초 폴링 오버헤드 무시할 수 있는 수준
- Stateless = crash recovery 자동 (상태가 파일에 있으므로 재시작만 하면 복구)
- inotifywait 미설치 + 신규 데몬 금지 원칙 (2026-03-10 결정)

**야누스 원안(5초 tick) 기각 사유**: CPU 낭비 대비 이득 미미 (30초 → 5초로 줄여도 사용자 체감 차이 없음)

### 1.4 chain.py + dispatch.py 무수정 래퍼

**결정**: auto_orch.py가 chain.py, dispatch.py를 subprocess로 호출 (코드 수정 없음)

**근거**:
- 기존 코드의 안정성 보존 (dispatch.py: 800+ LOC, chain.py: 550+ LOC — 수정 시 회귀 위험 높음)
- subprocess 격리로 에러 전파 차단 (auto_orch crash ≠ dispatch crash)
- shell=False 강제로 인젝션 방어 (펜리르 시나리오 1-B 대응)

### 1.5 파일 기반 상태 관리

**결정**: JSON 파일 + flock 기반 상태 관리 (SQLite 미사용)

**근거** (task-899.1 미팅):
- 현재 8팀 규모에서 파일 기반 충분 (2026-03-14 기각 결정 존중)
- 디버깅 용이성: cat/jq로 즉시 상태 확인 가능
- SQLite는 Phase 3 확장 계획으로 유보

---

## 2. task-897.1 미팅 핵심 결정과 배경

### 2.1 2-Layer 하이브리드 알림 (만장일치 합의)

| 결정 | 배경 |
|------|------|
| Layer 1(Primary): requests.post() 직접 Telegram API | cokacdir --cron은 새 Claude 세션 생성 = 토큰 소모. requests.post()는 토큰 0 |
| Layer 2(Fallback): done-watcher.sh 강화 | notify-completion.py 실패 시 30초 이내 fallback 보장 |
| inotifywait 기각 | 미설치 + 신규 데몬 금지 원칙 |
| Systemd Path Unit 기각 | events 디렉토리에 bot-activity.json 등 노이즈 과다 → 불필요 트리거 폭주 |

### 2.2 mv 기반 원자적 소유권 (펜리르 원칙)

- `.done` → `.done.processing` mv로 소유권 획득 (POSIX rename = atomic)
- 성공: `.done.processing` → `.done.notified`
- 실패: `.done.processing` → `.done` (원상 복구, 재시도 허용)
- 이 패턴을 event_bus.py에도 동일 적용

### 2.3 activity-watcher.py 핵심 버그 2건

| 버그 | 원인 | 영향 |
|------|------|------|
| `find_done_file` 팀 무관 반환 | 첫 번째 .done 파일을 무조건 반환 | 잘못된 작업 ID로 알림 → dev8 11시간 유휴 원인 추정 |
| `BOT_TEAM_MAP` 불완전 | dev1-3만 등록, dev4-8 누락 | 5팀의 idle 전환 미감지 |

---

## 3. task-899.1 미팅 핵심 결정과 배경

### 3.1 로키 6대 최소 보안 기준

| # | 기준 | 구현 컴포넌트 | 구현 Phase |
|---|------|--------------|-----------|
| 1 | 팀별 뮤텍스 | TeamLock (fcntl.flock) | Phase 3 |
| 2 | YAML gate 필수 | pipeline_validator | Phase 2 |
| 3 | DAG 검증 | validate_dag() (Kahn's algorithm) | Phase 2 |
| 4 | 일일 토큰 한도 | token_ledger.py | Phase 2 |
| 5 | 시크릿 분리 | scan_secrets() + env:// | Phase 2 |
| 6 | 단일 .done 이벤트 버스 | event_bus.py | Phase 2 |

### 3.2 펜리르 P0 대응

| 위협 | 대응 | 구현 Phase |
|------|------|-----------|
| 프롬프트 인젝션 (1-A) | injection_guard 하드블록 | Phase 2 |
| 셸 인젝션 RCE (1-B) | shell=False + shlex.quote | Phase 3 |
| 토큰 소진 공격 (9-A) | DAILY_HARD_LIMIT + MAX_CONCURRENT | Phase 2 |
| 무한 세션 루프 (2-A) | MAX_RETRIES=2 + 지수 백오프 | Phase 3 |

### 3.3 10대 위험 → 구현 Phase 매핑

| RISK | 위험 | 완화 메커니즘 | Phase |
|------|------|--------------|-------|
| RISK-01 | Anu-auto_orch 동시 dispatch | TeamLock | Phase 3 |
| RISK-02 | CEO 승인 게이트 우회 | gates/ .approved 파일 | Phase 3 |
| RISK-03 | 파이프라인 무한루프 | validate_dag() | Phase 2 |
| RISK-04 | 토큰 예산 소진 | token_ledger.py | Phase 2 |
| RISK-05 | 스텝 간 데이터 검증 부재 | outputs 파일 존재 확인 | Phase 3 |
| RISK-06 | QC/보안 게이트 우회 | gate_before 필수 검증 | Phase 3 |
| RISK-07 | 블래스트 반경 | allowed_teams + blast_radius | Phase 3 |
| RISK-08 | .done TOCTOU | event_bus.py 원자적 소비 | Phase 2 |
| RISK-09 | 파이프라인 시크릿 노출 | scan_secrets() | Phase 2 |
| RISK-10 | 롤백 불가 | rollback_cmd 역순 실행 | Phase 5+ |

---

## 4. 기존 시스템 코드 위치/구조 정리

### 4.1 Layer 0 (통신 인프라)
- `cokacdir` CLI — `/usr/local/bin/cokacdir` (외부 바이너리)
- `.env.keys` — `/home/jay/workspace/.env.keys` (환경변수: BOT_KEYS, ANU_BOT_TOKEN 등)

### 4.2 Layer 1 (작업 위임/체이닝)
- `dispatch.py` — `/home/jay/workspace/dispatch.py` (~839 LOC)
  - 팀장에게 작업 위임, cokacdir --cron 독립 세션 생성
  - task-timer 자동 시작, chain_manager.py 연동
- `chain.py` — `/home/jay/workspace/chain.py` (~554 LOC)
  - 멀티팀 Phase 오케스트레이션, Phase별 dispatch 후 전팀 완료 대기
- `chain_manager.py` — `/home/jay/workspace/chain_manager.py` (~753 LOC)
  - 단일팀 순차 체이닝, 한정승인(scoped delegation) Phase 관리

### 4.3 Layer 2 (.done 감지/알림)
- `finish-task.sh` — `/home/jay/workspace/scripts/finish-task.sh` (~37 LOC)
  - .done 원자적 생성 → task-timer end → notify-completion.py 호출
- `notify-completion.py` — `/home/jay/workspace/scripts/notify-completion.py` (~313 LOC)
  - 체인 인식 완료 통보: 체인 중간 → chain_manager.py next, 그 외 → Telegram 알림
  - **현재 문제**: cokacdir --cron → 새 Claude 세션 = 토큰 소모
- `done-watcher.sh` — `/home/jay/workspace/scripts/done-watcher.sh`
  - systemd 30초 타이머, stale .done 30분 후 에스컬레이션
  - **개선 예정**: 미알림 .done 직접 Telegram 알림 추가
- `activity-watcher.py` — `/home/jay/workspace/scripts/activity-watcher.py`
  - bot-activity.json 3초 폴링, processing→idle 감지
  - **현재 상태**: 미실행 (systemd 미등록)
  - **핵심 버그**: find_done_file 팀 무관 반환, BOT_TEAM_MAP 불완전

### 4.4 Layer 3 (자동화 오케스트레이션) — 신규
- `orchestrator/` — `/home/jay/workspace/orchestrator/` (신규 디렉토리)
  - `auto_orch.py` — 메인 오케스트레이터
  - `pipeline_validator.py` — YAML 검증
  - `event_bus.py` — .done 이벤트 소비
  - `token_ledger.py` — 토큰 예산 관리
  - `state/` — 파이프라인 실행 상태
  - `locks/` — 팀 뮤텍스 파일
  - `incoming/` — .done 이벤트 수신
  - `processed/` — 처리 완료 이벤트
  - `gates/` — 게이트 승인 파일

### 4.5 공유 인프라
- `task-timer.py` — `/home/jay/workspace/memory/task-timer.py` (작업 시간 추적)
- `task-timers.json` — `/home/jay/workspace/memory/task-timers.json` (작업 상태 DB)
- `bot-activity.json` — `/home/jay/workspace/memory/events/bot-activity.json` (봇 상태)
- `events/` — `/home/jay/workspace/memory/events/` (.done, .done.acked 등)
- `chains/` — `/home/jay/workspace/memory/chains/` (체인 데이터)

---

## 5. 통합 시 핵심 충돌 지점과 해소 방안

### 5.1 .done 소비자 경합 (Layer 2 vs Layer 3)

**문제**: notify-completion.py, done-watcher.sh, activity-watcher.py, event_bus.py — 4개 소비자가 동일 .done 파일에 접근

**해소 방안**:
- Layer 2 소비자 (notify-completion, done-watcher, activity-watcher): .done 파일을 **읽기만** 하고 마커 파일(.done.notified)로 상태 추적
- Layer 3 소비자 (event_bus): .done 파일을 **복사** 후 incoming/ 디렉토리에서 원자적 소비
- 원본 .done 파일은 **아누가 .done.acked 처리할 때까지 보존**

### 5.1b 에스컬레이션 알림 cokacdir 허용 근거

done-watcher.sh의 stale .done 에스컬레이션(30분+ 미처리) 경로에서 cokacdir를 명시적으로 유지한다.

**허용 사유**:
- 에스컬레이션은 비정상 상황 알림으로 빈도 매우 낮음 (정상 운영 시 0건)
- 새 Claude 세션 생성이 이 경우에는 **의도적** (아누 주의 환기 목적)
- requests.post() Telegram 텍스트 알림만으로는 아누 세션이 기동하지 않아 에스컬레이션 인지 가능성 저하
- 2026-03-16 "Push 폐기" 결정의 예외: 에스컬레이션은 Push(아누 세션 생성)가 적절한 유일한 경우

### 5.2 아누 체이닝 vs auto_orch 체이닝

**문제**: chain_manager.py next (아누 경로)와 auto_orch.py 파이프라인 다음 스텝이 동일 팀에 dispatch하면 충돌

**해소 방안**:
- TeamLock: 동일 팀에 대해 한 번에 하나의 dispatch만 허용
- 체인 식별: chain_manager.py 체인과 auto_orch 파이프라인은 서로 다른 네임스페이스 사용
  - chain_manager.py: `memory/chains/chain-{id}.json`
  - auto_orch: `orchestrator/state/{pipeline_id}.json`
- auto_orch 파이프라인에서 chain.py를 사용할 때, chain_id를 `orch-{pipeline_id}`로 네이밍하여 충돌 방지

### 5.3 토큰 회계 이중 관리

**문제**: task-timer.py가 작업 시간을 추적하고, token_ledger.py가 토큰 예산을 관리 → 중복?

**해소 방안**:
- task-timer.py: **실제 소요 시간/비용** 추적 (사후 기록)
- token_ledger.py: **예산 한도 강제** (사전 제어)
- 역할이 다르므로 중복 아님. 연동 포인트: token_ledger가 task-timers.json의 세션 수를 참조하여 일일 실제 사용량 추정

---

*맥락노트 초안 종료 — 에이전트 미팅 검토 대기*
