# 봇 충돌 방지 규칙

> 작성일: 2026-04-04 | 관련 태스크: task-1405.1

## 절대 규칙

**같은 봇에 2개 이상의 작업을 동시에 실행할 수 없다.**

## 봇 할당 방식

### 1. dev팀 (고정 할당)
- `TEAM_TO_BOT_ID` 매핑에 따라 팀별 고정 봇 사용
- 예: dev1-team → bot-b, dev4-team → bot-e

### 2. 논리적팀 (동적 할당)
- `DYNAMIC_BOT_TEAMS`: marketing, consulting, publishing, design, content
- `_select_and_reserve_bot()`로 가용 봇을 자동 선택
- task-timers.json의 `bot` 필드로 점유 추적

### 3. composite팀
- `_select_and_reserve_bot()`로 가용 봇 자동 선택
- 논리적팀과 동일한 동적 할당 메커니즘

## 충돌 방지 메커니즘

### 원자적 봇 예약 (`_select_and_reserve_bot`)
1. 파일 락(`fcntl.LOCK_EX`) 획득
2. task-timers.json에서 running 태스크의 봇 수집
3. 가용 봇 선택
4. task-timers.json에 `bot` 필드 즉시 기록
5. 파일 락 해제

이 과정이 단일 원자적 연산으로 수행되어 동시 dispatch 간 경합 조건을 방지한다.

### dev팀 조기 봇 기록
- dev팀도 `bot_id_meta` 결정 직후 `_patch_timer_metadata(bot=...)` 호출
- 논리적팀의 `_select_and_reserve_bot()`가 dev팀 봇을 busy로 인식

### dev팀 충돌 검사
- `_get_busy_bots_info()`로 전체 busy 봇 수집
- dev팀 고정 봇이 다른 팀(논리적/composite)에 점유 중이면 ERROR 반환
- `--force` 플래그로 강제 우회 가능 (경고 로깅)

## busy 봇 수집 (`_get_busy_bots_info`)

task-timers.json에서 running 상태 태스크의 봇을 두 가지 방식으로 수집:

1. **dev팀**: `team_id` → `TEAM_TO_BOT_ID` 매핑 (team_id가 dev*-team이면 자동 매핑)
2. **논리적/composite팀**: `bot` 필드 직접 읽기

두 방식 모두 적용하여 누락 없이 수집한다.

## 충돌 시나리오별 처리

| 시나리오 | 처리 |
|---------|------|
| design이 bot-e 점유 → dev4-team 위임 | ERROR: 봇 충돌 |
| dev4-team이 bot-e 사용 → design 위임 | design은 bot-e 스킵, 다른 봇 선택 |
| 같은 dev팀 재위임 | 팀 중복 검사에서 거부 (봇 충돌 아님) |
| --force 사용 | 경고 로깅 후 진행 |

## 관련 코드 위치

- `_select_and_reserve_bot()`: dispatch.py line ~293
- `_get_busy_bots_info()`: dispatch.py line ~189
- `_find_available_bot()`: dispatch.py line ~227 (하위 호환용 유지)
- dev팀 충돌 검사: dispatch.py line ~1308
- 논리적팀 봇 선택: dispatch.py line ~1280
- composite 봇 선택: dispatch.py line ~954
