---
type: advisory
issue: HAN-107
date: 2026-05-20
author: raphael
status: final
revised: 2026-05-20 (Thread Collab 모드 추가)
---

# [자문] Persona 간 자율 협업 시스템 아키텍처

## TL;DR

`asyncio.Queue` 기반 in-process 메시지 버스 + Jarvis-as-hub 패턴 채택. Slack은 사용자 facing 출력 전용으로 유지. 멀티 페르소나 스레드 참여(Thread Collab)는 Haiku 라우터 + depth 백스톱으로 별도 처리.

---

## 질문 / 결정 사항

사용자 개입 없이 페르소나(Jarvis·Asurada·Raphael·Nano)가 상호 통신·지시·리뷰를 수행하는 시스템 설계. 지원 시나리오:
- **(A) 순차 파이프라인**: Jarvis → Asurada(구현) → Raphael(리뷰) → Asurada(수정) → [결과]
- **(B) 병렬 협업**: Nano(조사) // Asurada // Raphael → Jarvis(취합) → [결과]
- **(C) 둘 다** — 태스크 유형에 따라 선택

---

## 자문 경위

- **Gemini**: 확보 — Jarvis-as-hub / asyncio.Queue / LangGraph 패턴 권장
- **Slack DM(Asurada, Nano)**: `restricted_action_read_only_channel` — 봇→봇 DM 권한 없음. **Slack 라우팅 기각 근거로 채택**
- **Manus**: 2회 연속 빈 응답 — 이관

---

## 옵션 비교

### Option 1: Slack Bot DM 라우팅

페르소나가 다른 페르소나 봇 user_id로 DM 전송.

**장점**
- 기존 Slack 인프라 재활용
- 메시지 히스토리 Slack UI에서 가시적

**단점**
- 봇→봇 DM에 `chat:write` scope 별도 설정 필요
- **실증 차단**: 현 slack-hangman MCP로 봇 DM 전송 불가 확인
- Slack API 레이트리밋 + 지연 (내부 통신에 외부 API 의존)
- 봇 메시지는 `bot_message` subtype — 기본 이벤트 필터에서 무시됨

**결론**: 기각

---

### Option 2: In-process `asyncio.Queue` + Jarvis-as-hub

bridge.py 내부에 중앙 메시지 버스 구현. Jarvis가 라우터 역할.

**장점**
- 외부 의존 없음, 단일 프로세스 내 처리
- 디버깅 용이 (로컬 로그, 단일 스택)
- LangGraph Supergraph 패턴과 동일 — 검증된 아키텍처
- 레이트리밋 없음

**단점**
- Jarvis 부하 증가 (사용자 라우팅 + 페르소나 간 라우팅 동시)
- Jarvis 장애 = 파이프라인 전체 정지 (SPOF)
- bridge.py 변경 범위 중간 수준

**트레이드오프**
- SPOF는 단일 프로세스 구조에서 이미 내재된 제약. Jarvis가 없어도 bridge.py 자체가 죽으면 동일. 추가 리스크 아님.

**결론**: 채택

---

### Option 3: Peer-to-peer Queue (허브 없음)

각 페르소나가 직접 대상 페르소나의 Queue에 enqueue.

**장점**
- Jarvis 부하 없음

**단점**
- 병렬 취합(fan-in) 로직이 각 페르소나에 분산됨
- 순환 참조(A→B→A) 감지 책임 불명확
- 감사 로그 중앙화 불가

**결론**: 병렬 패턴에서 취합 복잡도 상승 — 기각

---

## 권장안

**선택: Option 2 — asyncio.Queue + Jarvis-as-hub**

### 메시지 봉투 스키마

```json
{
  "task_id": "uuid-v4",
  "flow": "SEQ | PAR",
  "from": "raphael",
  "to": "asurada",
  "return_to": "jarvis",
  "payload": "...",
  "context": ["prev_msg_1", "prev_msg_2"],
  "depth": 2,
  "parallel_count": 3,
  "timeout_at": "ISO8601"
}
```

- `flow`: SEQ는 순차 파이프라인, PAR는 병렬 fan-out
- `depth`: 순환 참조 가드 — `depth > 5`면 Jarvis가 드롭 + 사용자 보고
- `parallel_count`: PAR 시 Jarvis가 몇 개의 응답을 기다릴지
- `timeout_at`: 각 hop의 TTL — 초과 시 Jarvis가 partial result로 취합 진행

### 시나리오별 흐름

**순차 (SEQ)**
```
User → Jarvis
  Jarvis.enqueue(to=asurada, flow=SEQ, return_to=jarvis)
  Asurada 처리 완료 →
  Asurada.enqueue(to=raphael, flow=SEQ, return_to=jarvis)
  Raphael 리뷰 완료 →
  Raphael.enqueue(to=asurada, flow=SEQ, return_to=jarvis)
  Asurada 수정 완료 →
  Jarvis → User
```

**병렬 (PAR)**
```
User → Jarvis
  Jarvis.enqueue(to=nano, flow=PAR, task_id=X, parallel_count=3)
  Jarvis.enqueue(to=asurada, flow=PAR, task_id=X, parallel_count=3)
  Jarvis.enqueue(to=raphael, flow=PAR, task_id=X, parallel_count=3)
  
  PendingWork[X] = []
  각 페르소나 완료 시 → PendingWork[X].append(result)
  len(PendingWork[X]) == 3 → Jarvis 취합 → User
```

### 실패 처리

- **타임아웃**: 각 hop에 `timeout_at` 필드. Jarvis가 `asyncio.wait_for(timeout=N)` 적용. 초과 시 `TIMEOUT` 마커와 함께 partial result 취합.
- **순환 감지**: `depth > 5` 드롭. `[LOOP_DETECTED]` 로그 + 사용자 알림.
- **병렬 부분 실패**: `parallel_count` 미달이라도 `timeout_at` 도달 시 보유 결과로 취합 진행. 누락 페르소나 명시.

### 감사 로그

```
logs/collab/{task_id}.jsonl
{"ts": "ISO8601", "from": "jarvis", "to": "asurada", "flow": "SEQ", "depth": 0, "status": "sent"}
{"ts": "ISO8601", "from": "asurada", "to": "raphael", "flow": "SEQ", "depth": 1, "status": "sent"}
{"ts": "ISO8601", "from": "raphael", "to": "jarvis", "flow": "SEQ", "depth": 2, "status": "completed"}
```

---

## Thread Collab 모드 (추가 설계)

태스크 위임(SEQ/PAR)과 별개로, 같은 Slack 스레드 안에서 여러 페르소나가 교대 발화하는 모드.

### 트리거 방식

사용자가 스레드 내에서 `@raphael` 명시 멘션 → 라파엘 직접 트리거. Asurada의 의도 감지·중계 불필요. 일반 Slack 업무에서 동료를 태그하는 방식과 동일.

### 페르소나 발화 후 다음 발화자 결정 — Haiku 라우터

페르소나가 스레드에 응답 완료 → Haiku(claude-haiku-4-5)가 스레드 마지막 N개 메시지를 읽고 다음 발화자 판단.

```
입력: thread_ts 기준 최근 10개 메시지 + 현재 활성 페르소나 목록
출력: "asurada" | "raphael" | "nano" | "done"
```

`"done"` 출력 시 대화 종료 — 공이 사용자에게 돌아감. Haiku가 "더 이상 페르소나가 추가할 내용 없음"을 감지하면 자연스러운 마무리.

### 봉투 필드 추가

```json
{
  "mode": "TASK | THREAD_COLLAB",
  "thread_ts": "...",
  "depth": 2
}
```

`TASK` 모드: 기존 SEQ/PAR 위임 흐름.
`THREAD_COLLAB` 모드: thread_ts 공유, Haiku 라우터 활성.

### 스레드 컨텍스트 공유

각 페르소나는 `thread_ts` 기준으로 Slack thread history를 fetch해 컨텍스트로 사용. 페르소나별 session_id는 독립 유지되나, 스레드 히스토리로 공유 컨텍스트 확보.

### 실패 처리 (Thread Collab)

- **Haiku 라우터 루프**: Haiku가 `done`을 내지 못하고 핑퐁 유발 시 `depth > 5` 백스톱 발동 → 강제 `done` + 사용자 알림.
- **Haiku 오류**: 라우터 호출 실패 시 Thread Collab 종료, 사용자에게 현재까지 대화 요약 post.

---

## 구현 우선순위 (Phase)

| Phase | 범위 | 난이도 |
|---|---|---|
| 1 | 메시지 봉투 스키마 정의 + bridge.py 내 Queue 초기화 | 低 |
| 2 | SEQ 파이프라인 (A 시나리오) + depth 가드 + collab 로그 | 中 |
| 3 | PAR fan-out/fan-in (B 시나리오) + timeout 처리 | 中-高 |
| 3.5 | Thread Collab 모드 — thread_ts 공유 + Haiku 라우터 + depth 백스톱 | 中 |
| 4 | `[DELEGATE]` 텍스트 마커 → structured JSON tool_use 전환 (선택) | 高 |

Phase 4는 LLM 응답 파싱 안정성 향상이지만 bridge.py 대규모 변경 수반 — 별도 이슈로 분리 권장.

---

## 맹점 (재확인)

1. **병렬 컨텍스트 충돌**: PAR 패턴은 독립 subtask에만 적용 가능. Nano 조사 결과가 Asurada 구현 방향을 바꿔야 하는 경우 = PAR 부적합, SEQ 재설계 필요. 설계 문서에 "PAR = 독립 subtask 전제" 명시 필수.
2. **Jarvis SPOF**: 단일 프로세스 구조 내재 제약이나, Jarvis 세션 과부하 시 파이프라인 지연 가능. Phase 1-2 완료 후 모니터링.

---

## 참고 자료

- Gemini brainstorm (2026-05-20): LangGraph Supergraph Router 패턴 권장
- [LangGraph: Multi-agent Architectures](https://langchain-ai.github.io/langgraph/concepts/multi_agent/)
- [AutoGen: GroupChat](https://microsoft.github.io/autogen/docs/Use-Cases/agent_chat)
- HAN-107: https://linear.app/hangman-lab/issue/HAN-107
