토론 시스템 MVVM 아키텍처 — 기능명세서 v1.0
설계자: 회장 영감: Android MVVM + RecyclerView DiffUtil + OOM 방지 패턴 검증: 5인 전문가 패널 전원찬성 (프로젝트 최초, 2026-04-05) 상태: 설계 완료, 구현 미착수
1. 왜 필요한가 — 현재 시스템의 근본 문제
1.1 회장 원문
"진짜 문제는 토론을 하냐 안하냐가 아니야. 하네스 구조에서는 이게 필수야. 진짜 문제는 토론을 하되, 내부 토론이 어떻게 이루어져지고 있는지 등등 최적화가 안된거야."
1.2 Android OOM과의 유사성
현재 토론 시스템은 Android ListView의 OOM 문제와 구조적으로 동일하다.
ListView (OOM 패턴):
매번 convertView를 새로 inflate → 메모리 누적 → OOM 크래시
현재 토론 (-c 패턴):
매 라운드 이전 대화 전체를 재로딩 → 토큰 누적 → 비용 폭발
| Android 문제 | 토론 시스템 문제 | 공통 근본 원인 |
|---|---|---|
| ListView: 매번 전체 inflate → OOM | -c: 매번 전체 대화 로드 → 토큰 폭발 | 재사용 없이 매번 전체를 새로 로드 |
| convertView 재활용 없음 | 이전 라운드 요약 재사용 없음 | 이전 결과를 구조화하지 않음 |
| Adapter가 View+Data+Logic 전부 담당 | Agent가 컨텍스트+판단+출력 전부 담당 | God Object (책임 미분리) |
1.3 비용 실측 — 낭비의 정체
실측 043 기준 (토론 ON, 5,097원):
| 호출 | 누적 입력 | 신규 정보 | 낭비율 |
|---|---|---|---|
| Call 1 (Director 초기) | 8K | 8K | 0% |
| Call 2 (합의, -c) | 41K | 6K (압축 의견) | 85% |
| Call 3 (최종, -c) | 84K | 5K (Evaluator 결과) | 94% |
3가지 근본 원인:
-c컨텍스트 누적: 합의 Director 호출 시 이전 대화 전체 재로딩 (85-94% 토큰 낭비)effort: high고정: Director 호출당 6.6분 (회장 CLI 직접 사용 시 <2분) — ⚠️ 토론 품질상 high 유지 확정. 해결은 1·3번으로.- 시스템 프롬프트 반복: DIRECTOR_SYSTEM_PROMPT + HARD_RULES + HIRED_AGENTS = 8.1K chars, 매 호출
1.4 구조별 비용 비중
| 구분 | 시간 | 비용 | 비중 |
|---|---|---|---|
| 토론 과정 (Director+Clone+합의) | 22.2분 | 2,683원 | 52.6% |
| 실제 코딩 (Worker 6명) | 8.0분 | 800원 | 15.7% |
| 빌드 검증 | 5.0분 | 200원 | 3.9% |
| 평가 (Evaluator) | 1.7분 | 292원 | 5.7% |
| 후처리 | 3.6분 | 1,000원 | 19.6% |
비용의 52.6%가 토론 과정이고, 그 안에서 -c 누적이 85~94% 낭비.
2. 해결 — Filter + Consigliere MVVM 아키텍처
2.1 회장 설계 원문
"MVVM패턴에서 VM을 Consigliere가 역할을 하고, 생각이나 토론이나 의견 등을 '데이터'로 해서 클론과 원본 디렉터가 Consigliere를 실시간 옵저빙해서 데이터를 교류하는 방식. 추가적으로 '필터' 레이어를 추가. 에이전트는 어차피 sonnet, opus, haiku 등 오리지날이야. 템플릿에 의해 에이전트화되는거지. 이걸 역이용하자."
2.2 Android MVVM 대응표
| Android MVVM | 토론 MVVM | 역할 |
|---|---|---|
| View (Activity/Fragment) | Agent (Claude/Pizza/Codex) | 순수 렌더러. 데이터를 받아서 출력만 |
| ViewModel (LiveData/StateFlow) | Consigliere (append-only log) | 구조화 데이터 저장소. 옵저버에게 변경 알림 |
| Interface (Repository/UseCase) | Filter (Input+Output) | 데이터 변환. Raw → 구조화 |
| DiffUtil | Input Filter | 변경분만 전달 (이전 vs 현재 델타) |
| Converter (Gson/Moshi) | Output Filter | 비구조화 출력 → 구조화 데이터 파싱 |
| RecyclerView | fresh -p 호출 | ViewHolder 재활용 = 매번 깨끗한 상태에서 시작 |
2.3 RecyclerView 비유 (전문가 B)
ListView (현재 -c 방식):
매번 convertView를 새로 inflate
→ 스크롤할수록 메모리 누적
→ OOM 크래시
RecyclerView (MVVM 방식):
ViewHolder 재활용 + DiffUtil로 변경분만 업데이트
→ 스크롤해도 메모리 일정
→ OOM 없음
ListView= 현재-c전체 로드RecyclerView= Consigliere 델타DiffUtil= Input Filter ("이전 Consigliere 상태 vs 현재"를 비교해서 델타만 Agent에게 줌)ViewHolder 재활용= fresh-p호출 (매번 깨끗한 상태)
3. 전체 아키텍처 다이어그램
3.0 레이어 구조
┌─────────────────────────────────────────────────────────────────────┐
│ 서버 (Node.js 오케스트레이터) │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ debate.ts — 토론 라이프사이클 관리 │ │
│ │ (debateLoop → collectOpinions → buildConsensus → test) │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Layer 2: Filter (Interface) │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌──────────────────────┐ │ │
│ │ │ Input Filter │ │ Output Filter │ │ │
│ │ │ (순수 코드, 0원) │ │ (코드 80%+gemma 20%) │ │ │
│ │ │ │ │ │ │ │
│ │ │ ∙ 이전 라운드 │ │ ∙ DELEGATE 파싱 │ │ │
│ │ │ 요약 압축 │ │ ∙ reasoning 추출 │ │ │
│ │ │ ∙ 의견 구조화 │ │ ∙ 분기 감지 (게이트) │ │ │
│ │ │ ∙ 규칙 선택 │ │ ∙ 구조화 변환 │ │ │
│ │ │ ∙ 델타 계산 │ │ │ │ │
│ │ └────────┬────────┘ └───────────┬──────────┘ │ │
│ │ │ │ │ │
│ └───────────┼────────────────────────────────┼──────────────────┘ │
│ │ │ │
│ ▼ ▲ │
│ ┌───────────────────────┐ ┌────────────────────────────┐ │
│ │ Layer 1: Agent (View) │ │ Layer 3: Consigliere (ViewModel)│ │
│ │ │ │ │ │
│ │ Claude/Pizza/Codex │ │ append-only log │ │
│ │ 항상 fresh -p │──────│ sequence_number │ │
│ │ 상태 없음 (stateless)│ │ parent_sequence │ │
│ │ 최소 프롬프트만 │ │ debate_id (트레이스) │ │
│ │ │ │ │ │
│ │ sonnet/haiku/gemma │ │ ∙ opinions │ │
│ │ (모델 무관) │ │ ∙ consensus │ │
│ │ │ │ ∙ test_results │ │
│ │ │ │ ∙ delegates │ │
│ │ │ │ ∙ filter_logs │ │
│ └───────────────────────┘ └────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 기존 유지: NodeTree (문제 추적) + DebateQueue (큐 관리) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
3.0.1 데이터 흐름 순서 (1 라운드)
① debate.ts: 라운드 시작
│
▼
② Input Filter: Consigliere에서 이전 라운드 읽기 → 델타 계산 → 최소 프롬프트 구성
│
▼
③ Agent(View): fresh -p 호출 → raw 출력 생성
│
▼
④ Output Filter: raw 출력 파싱 → 구조화 (정규식 80% → gemma3:4b 20%)
│
▼
⑤ Consigliere(ViewModel): append(sequence, type, content, reasoning)
│
▼
⑥ debate.ts: 테스트 → 통과? → 다음 라운드 or 해결
3.0.2 호출 스택 상세
debateLoop()
│
├── collectOpinions()
│ ├── InputFilter.buildOpinionPrompt(consigliere, nodeId, round) ← 0원
│ ├── callAgentsParallel(filteredTasks) ← Agent(View)
│ ├── OutputFilter.parseOpinions(rawResults) ← 0원 or gemma
│ └── consigliere.append(type:'opinion', ...) ← Consigliere
│
├── buildConsensus()
│ ├── InputFilter.buildConsensusPrompt(consigliere, nodeId, opinions) ← 0원
│ ├── callDirectorStateless(filteredPrompt) ← Agent(View), fresh -p
│ ├── OutputFilter.parseConsensus(rawConsensus) ← 0원 or gemma
│ └── consigliere.append(type:'consensus', ...) ← Consigliere
│
├── executeProposal()
│ ├── parseDelegates(consensus) ← 기존 코드
│ └── callAgentsParallel(delegates) ← Worker 에이전트
│
└── runTests()
├── (빌드/기술충돌 체크)
└── consigliere.append(type:'test_result', ...) ← Consigliere
3.0.3 Ollama gemma3:4b 호출 타이밍 (지연 없음)
시간 →
회장 msg ─┬─ Router(gemma3:4b) ──────────────────────────────────┐
│ ~1-2초, 이후 gemma 유휴 │
│ │
├─ Director(sonnet) ─────────── 6분 ──────────────────┐ │
│ │ │
├─ Clone(sonnet) ────────────── 6분 ──────────────┐ │ │
│ │ │ │
│ ← gemma3:4b 유휴 상태 (6분간 안 쓰임) → │ │ │
│ │ │ │
├─ Output Filter(gemma3:4b) ─ <1초 ─┐ │ │ │
│ (opinions 파싱) │ │ │ │
│ │ │ │ │
├─ Output Filter(gemma3:4b) ─ <1초 ─┤ │ │ │
│ (consensus 파싱) │ │ │ │
│ │ │ │ │
└───────────────────────────────────┘ │ │ │
↑ Router와 Output Filter는 절대 동시 호출 안 됨 │ │ │
Router(토론 시작 전) → Director/Clone(6분간 gemma 유휴) → Output Filter(라운드 후) → 같은 gemma3:4b를 시간차로 재사용. 경합 없음.
3. 3개 레이어 상세 설계
3.1 Agent (View) — 순수 베이스 모델
역할: 입력을 받아서 출력만 생성. 상태를 보관하지 않음.
핵심 원칙:
- 항상 fresh
-p호출.-c완전 제거. - 시스템 프롬프트는 Filter가 주입한 최소한만.
- Agent는 자기가 "토론 중"인지조차 모름. 그냥 프롬프트에 응답할 뿐.
- "에이전트는 어차피 sonnet, opus 등 오리지날이야. 템플릿에 의해 에이전트화되는거지. 이걸 역이용하자." — 회장
이전 (God Object Agent):
Agent = 컨텍스트 관리 + 판단 + 출력 생성 + 상태 보관
(시스템 프롬프트 8.1K + -c 누적 84K + effort:high)
이후 (View Agent):
Agent = 프롬프트 입력 → 출력 생성
(Filter가 구성한 최소 프롬프트 ~6K + fresh -p + effort:high — 토론은 항상 high)
3.2 Filter (Interface) — Input Filter + Output Filter
역할: Agent와 Consigliere 사이의 데이터 변환 레이어.
Agent에게 들어가는 것(Input)과 나오는 것(Output)을 가공.
Input Filter (비용: 0원 — 순수 코드)
Consigliere 데이터 → Input Filter → Agent 프롬프트
하는 일:
- 이전 라운드 압축: Consigliere에서 이전 라운드 요약만 추출 (라운드번호 + 결정 + 테스트결과)
- 의견 구조화: raw opinion text에서 핵심만 추출 (입장, 근거, 위험요소)
- 관련 규칙 선택: HARD_RULES 전체가 아니라 이 토론에 관련된 규칙만 주입
- 델타 계산: DiffUtil처럼 "이전 상태 vs 현재" 비교 → 변경분만 전달
안 하는 일:
- 판단, 요약, 해석 (이건 Agent의 역할)
- AI 모델 호출 (순수 코드만)
Output Filter (비용: 0원 — 코드 파서 80% + Ollama gemma3:4b 20%)
Agent 출력 → Output Filter → 구조화 데이터 → Consigliere
하는 일:
- DELEGATE 파싱: 기존
parseDelegates()확장 — 정규식 (0원) - 결정 추출:
[CONSENSUS],[EVAL]등 구조화 태그 파싱 — 정규식 (0원) - reasoning 보존: 핵심 근거 1-2문장 추출 — 정규식 우선, 실패 시 gemma3:4b (0원, 로컬)
- 분기 감지: "새 주제 파생" vs "기존 주제 반박" 구분 → Consigliere에 올바른 parent_id
- 게이트 역할: "이 분기는 합의 가능" vs "새 라운드 필요" 판단
Output Filter 모델 결정 (회장 승인, 2026-04-07):
- ~~haiku API~~ → Ollama gemma3:4b (이미 Router에서 사용 중, 비용 0원, 레이턴시 <2초)
- 정규식으로 80% 처리, 나머지 20%만 gemma3:4b 폴백
전문가 D: "Director 출력은 이미 [DELEGATE] 태그로 구조화되어 있으니 정규식 파싱만으로 80%는 추출 가능하다." 전문가 B: "이건 안드로이드의 Converter 패턴과 동일. Retrofit에서 JSON 파싱을 Gson/Moshi가 하지, 매번 서버에 보내지 않는다."
Filter 안티패턴 경고 (전문가 B)
"안드로이드에서 Repository 패턴을 과도하게 추상화하면 보일러플레이트 지옥이 된다. Filter 레이어가 '무엇을 필터링할지'의 규칙이 복잡해지면 Filter 자체가 새로운 God Object가 된다. Filter의 규칙은 하드코딩이 아니라 데이터 기반이어야 한다."
3.3 Consigliere (ViewModel) — append-only 구조화 데이터 저장소
역할: 토론의 모든 상태를 구조화 데이터로 저장.
Agent(View)는 Consigliere(ViewModel)를 옵저빙하여 필요한 데이터만 읽음.
핵심 원칙: 이벤트 소싱 (Event Sourcing)
전문가 C: "MVVM + 옵저빙 구조는 본질적으로 이벤트 소싱 패턴이다. Consigliere 쓰기를 append-only log 방식으로 하고, 각 엔트리에 sequence_number를 붙여서 순서를 보장하라. 덮어쓰기 없이 추가만 — 이벤트 소싱의 기본 원칙이다."
엔트리 구조
interface DebateConsigliereEntry {
sequence: number; // 단조 증가, 순서 보장
parent_sequence: number | null; // 분기 지원 (어떤 의견에 대한 반박인지)
debate_id: string; // 트레이스 ID (디버깅)
node_id: string; // NodeTree 노드 (기존 호환)
round: number;
type: 'opinion' | 'consensus' | 'test_result' | 'delegate' | 'ceo_input' | 'filter_log';
agent_id: string;
content: string; // 구조화된 내용 (raw 아님)
reasoning?: string; // 핵심 근거 1-2문장 (정보 손실 방지 — 전문가 A 제안)
metadata?: Record<string, unknown>; // 확장용
timestamp: string;
}
쓰기 규칙
| 규칙 | 설명 |
|---|---|
| append-only | 덮어쓰기 금지. 추가만. |
| sequence 보장 | 서버(단일 Node.js 프로세스)가 순서를 관리 |
| parent_sequence | null = 독립 의견, 숫자 = 해당 엔트리에 대한 반박/확장 |
| 반쪽 데이터 방지 | 쓰기 성공 후에만 sequence 발급 |
Consigliere ↔ NodeTree 관계
기존 NodeTree(node-tree.ts)는 그대로 유지한다. Consigliere는 NodeTree를 대체하는 게 아니라 보완한다.
| NodeTree | Consigliere |
|---|---|
| 문제 추적 트리 (Open/Blocked/Resolved) | 데이터 교환 저장소 |
| 토론의 "구조" | 토론의 "내용" |
node_id로 문제 위치 | sequence로 시간 순서 |
Consigliere 엔트리의 node_id 필드가 NodeTree와 연결된다.
4. 데이터 흐름 — 현재 vs MVVM
4.1 현재 (ListView 패턴)
Round 1:
Director(-c) ← 회장 msg + agent list + system prompt 8K
Director → 출력 25K (raw text, 비구조화)
Round 2 (합의):
Director(-c) ← [8K + 25K + Clone 의견 8K] = 41K ← 신규 정보 6K뿐
Director → 출력 38K (raw text, 비구조화)
Round 3 (최종):
Director(-c) ← [41K + 38K + Evaluator 5K] = 84K ← 신규 정보 5K뿐
Director → 출력 15K
4.2 MVVM (RecyclerView 패턴)
Round 1:
Input Filter: 회장 msg → 구조화 프롬프트 ~3K (관련 규칙만 선택)
Agent(-p) ← 3K (fresh, 깨끗한 상태)
Agent → 출력 25K (raw text)
Output Filter: 25K raw → { decision, reasoning, delegates } 1K (구조화)
Consigliere ← append(seq:1, type:opinion, content: 구조화 1K)
Round 2 (합의):
Input Filter: Consigliere.summarize() → 이전 라운드 요약 1K + Clone 의견 요약 2K = 3K
Agent(-p) ← 3K (fresh, 이전 대화 없음)
Agent → 출력 20K
Output Filter: 20K → { consensus, delegates } 1K
Consigliere ← append(seq:4, type:consensus, content: 1K)
Round 3 (최종):
Input Filter: Consigliere.summarize() → 합의 요약 1K + Evaluator 결과 1K = 2K
Agent(-p) ← 2K (fresh)
Agent → 출력 10K
Output Filter: 10K → { final_decision, delegates } 0.5K
Consigliere ← append(seq:7, type:consensus, content: 0.5K)
4.3 비교
| 항목 | 현재 | MVVM |
|---|---|---|
| Round 2 입력 토큰 | 41K | 3K |
| Round 3 입력 토큰 | 84K | 2K |
| 총 입력 토큰 (3라운드) | ~133K | ~8K |
| 입력 비용 | ~$0.40 | ~$0.024 |
| 입력 비용 절감 | — | 94% |
| 총 비용 (토론 과정) | ~2,683원 | ~400원 |
| 총 시간 (토론 과정) | ~22분 | ~6분 |
5. Node Tree 분기와 Consigliere의 관계
토론 진행 시 의견이 여러 갈래로 나뉘는 분기 현상:
회장 지시: "넷플릭스 클론"
├─ Director [seq:1]: "SwiftUI + Room + MVVM"
│ ├─ Clone 1 [seq:2, parent:1]: "동의, 단 Room 대신 CoreData" ← 기술 분기
│ └─ Clone 2 [seq:3, parent:1]: "동의, 단 MVVM 대신 Clean Arch" ← 아키텍처 분기
│ ├─ 합의 [seq:4, parent:2,3]: "CoreData + MVVM" (Clone 2 미반영)
│ └─ Clone 2 재반박 [seq:5, parent:4]: "Clean Arch 근거 재제시"
└─ 별도 분기 [seq:6, parent:null]: "멀티모듈 구조 필요"
Consigliere의 sequence + parent_sequence로 트리 구조를 자연스럽게 표현:
parent_sequence: null= 독립 의견 (새 주제)parent_sequence: N= N번 엔트리에 대한 반박/확장
Filter가 분기 감지 + 합의 가능 여부 판단을 하는 게이트 역할:
- "이 분기는 합의 가능" → 다음 라운드로
- "새 주제 파생" → NodeTree에 하위 노드 추가
- "불필요한 분기" → 조기 종료
무한 분기 방지: 기존 MAX_DEBATE_ROUNDS = 20 + 트리 깊이 상한.
6. 디버깅 — 3곳 분산 문제 해결
6.1 문제 (전문가 E)
"현재 -c 방식의 유일한 장점은 디버깅이 쉽다는 것이다. Filter + Consigliere 방식은 데이터가 3곳에 분산된다: Agent stdout, Filter 변환 로그, Consigliere 상태."
6.2 해결: 트레이스 시스템
| 도구 | 설명 |
|---|---|
| debate_id | 모든 로그에 토론 ID 포함. Agent/Filter/Consigliere 크로스 참조 가능 |
| Filter 변환 로그 | Input/Output Filter가 "무엇을 걸렀는지" 기록 (dropped 필드 목록) |
| Consigliere 타임라인 | debate_id로 시간순 조회. 구조화 데이터라 grep이 훨씬 쉬움 |
Consigliere 엔트리 중 type: 'filter_log'로 Filter 변환 내역도 Consigliere에 함께 저장.
전문가 E: "이 3가지가 있으면 디버깅은 오히려 현재보다 나아진다 — 구조화된 데이터라 grep이 훨씬 쉬움."
7. 동시성 제어 (전문가 C)
7.1 현재 환경: 단일 Node.js 프로세스
전문가 A → C 반론: "Clone 1, 2가 '동시에' 쓰는 게 아니라 서버가 Promise.allSettled로 병렬 실행 후 모든 결과를 수집한 다음 한꺼번에 Consigliere에 쓴다. 이건 진짜 분산 시스템이 아니다 — 단일 Node.js 프로세스에서 돌아가는 오케스트레이터이다."
→ 현재 단계에서 Write Conflict, Read-after-Write 문제 없음.
7.2 SaaS 단계 대비 (전문가 C 재반론)
"맞다, 현재는 단일 프로세스이다. 하지만 이 프로젝트의 로드맵에 'BigBoss OS Cloud (SaaS)'가 있다. 멀티테넌트 환경에서는 동시성 문제가 실제로 발생한다. 지금 append-only log 구조로 설계해두면 SaaS 전환 시 비용이 0에 가깝다."
→ append-only + sequence_number 구조가 SaaS 확장까지 커버.
8. 멀티 CLI 간 토론 — OS 단계 확장 비전
8.1 회장 발견
"OS 단계가 되면 자체 모델들과 다른 AI 모델들끼리 서로 CLI를 켜서 어떻게 토론이나 작업상황들을 교류해야 하나 고민했는데, MVVM과 Consigliere가 이걸 풀어버린다."
8.2 핵심: Consigliere 스키마 = 통신 프로토콜
현재 -c 방식은 대화 컨텍스트가 프로세스 내부에 갇혀있어 외부 CLI가 접근 불가능하다. Consigliere가 ViewModel(공유 데이터 저장소)이 되면 누가 쓰든 상관없이 observe하는 쪽은 데이터만 받으면 된다.
| 시나리오 | 구조 |
|---|---|
| 같은 머신, 다른 CLI 2개 | 로컬 Consigliere 공유 (파일 기반) |
| 다른 머신 2대 | Supabase를 Consigliere 백엔드로 (이미 Realtime 인프라 있음) |
| Claude vs Gemma vs Pizza (다른 모델) | Filter가 각 모델의 출력 포맷을 구조화 → 같은 Consigliere 스키마로 |
| 팀 단톡방에서 토론 | I19~I24 인프라 그대로 활용 |
"CLI 간 통신 프로토콜"이 필요 없다. Consigliere 스키마가 곧 프로토콜이다. append-only log + sequence_number + parent_sequence 구조가 멀티 프로세스 / 멀티 머신 / 멀티 모델을 이미 지원한다.
9. R4 메모리 제약 해법 — Pizza 모델 16GB 실행
9.1 회장 원문
"어떻게 보면 OS단계에 문제가 되었던 'Pizza 모델로 하네스 구현시 64GB 메모리 구입이라는 장벽을 소프트웨어로 풀어야한다'의 실마리가 될 수 있겠다."
9.2 현재 문제 (병렬 로딩)
Director(5GB) + Clone1(5GB) + Clone2(5GB) + Worker×6(5GB×6) = 최소 45GB
→ 64GB 필요 → 타겟 유저 24GB(75%)에서 실행 불가
9.3 Consigliere MVVM 해법 (순차 로딩)
모델 1개만 메모리에 로드 (5GB)
→ Director 실행 → 의견을 Consigliere에 구조화 기록 → 모델 언로드
→ Clone 실행 → Consigliere에서 원본 의견 읽기 → 반론 기록 → 모델 언로드
→ Worker 순차 실행 → Consigliere에서 합의 결과 읽기 → 코드 생성 → 모델 언로드
= 베이스 모델 5GB + Consigliere(수MB) + OS 8GB = ~13GB → 24GB 충분!
Consigliere가 ViewModel 역할 → 모델 간 직접 통신 불필요 → 동시 로드 불필요.
9.4 LoRA + Consigliere MVVM 결합
Gemma 4 베이스 5GB (상주)
+ LoRA 어댑터 300MB (순차 스왑, ~1-2초)
+ Consigliere (수MB, 상주)
+ OS 8GB
= ~13.3GB → 16GB 가능!
→ 기존 "16GB 불가" 판정을 뒤집을 수 있는 실마리
| 기존 접근 | Consigliere MVVM |
|---|---|
| 모델 5개 동시 로드 → 64GB | 모델 1개 순차 로드 → 16GB |
| 모델 간 직접 통신 프로토콜 필요 | Consigliere가 통신 매개 (프로토콜 불필요) |
| 스왑 대기 시간이 블로킹 | Consigliere 기록이 비동기 → 스왑 숨김 |
| Pizza 전용 설계 | Claude + Pizza + Codex 멀티 CLI 호환 |
10. 전문가 패널 합의 사항
5인 전문가 패널 (2026-04-05, 전원찬성):
| # | 전문가 | 도메인 |
|---|---|---|
| A | LLM 오케스트레이션 아키텍트 | 멀티에이전트, 컨텍스트 윈도우 최적화 |
| B | 안드로이드 시니어 아키텍트 | MVVM, RecyclerView, 리액티브 패턴 |
| C | 분산 시스템 엔지니어 | pub/sub, 이벤트 소싱, 데이터 일관성 |
| D | AI 비용 최적화 전문가 | 토큰 경제학, 모델 선택, ROI |
| E | 프로덕션 인프라 엔지니어 | 레이턴시, 안정성, 디버깅 |
합의 7개항
| 항목 | 합의 | 제안자 |
|---|---|---|
| 도입 여부 | 전원 찬성 | 전원 |
| Output Filter 구현 | 코드 파서(parseDelegates 확장) 80% + 로컬 모델(gemma3:4b) 20% | D+B |
| Input Filter 구현 | 순수 코드 (0원). Consigliere 델타 조회 + 관련 규칙 선택 | 전원 |
| Consigliere 쓰기 방식 | append-only log + sequence_number | C+A |
| 디버깅 | debate_id 트레이스 + Filter 변환 로그 | E |
| 마이그레이션 | 3 Phase 점진적 | E |
| 최대 리스크 | Output Filter 정보 손실 → reasoning 필드 보존으로 완화 | A |
회장 최종 확정 (2026-04-07)
회장: "이거 핵심합의사항이니까 앵간하면 이대로 가자. haiku 대신 gemma 쓰는 거 빼고."
위 합의 7개항은 회장 확정이다. haiku→gemma3:4b 변경 외 수정 없이 그대로 구현한다.
| # | 확정 사항 | 비용 |
|---|---|---|
| 1 | Output Filter = parseDelegates 확장(정규식) 80% + gemma3:4b(Ollama 로컬) 20% | 0원 |
| 2 | Input Filter = 순수 코드. Consigliere 델타 조회 + 관련 규칙 선택 | 0원 |
| 3 | Consigliere = append-only log + sequence_number + parent_sequence | 0원 |
| 4 | 마이그레이션 = 3 Phase 점진적 (Phase 1→2→3, 각 Phase 실측 검증) | — |
| 5 | 디버깅 = debate_id 트레이스 + Filter 변환 로그 | — |
| 6 | 리스크 완화 = Output Filter에 reasoning 필드 보존 (정보 손실 방지) | — |
| 7 | 유일한 변경 = ~~haiku API~~ → gemma3:4b (이미 Router에서 사용 중) | — |
11. 구현 계획 — 3 Phase 점진적 마이그레이션
전문가 E: "현재 -c 방식을 하루아침에 걷어내면 위험하다. 각 Phase마다 실측해서 비교."
Phase 1: Output Filter + Consigliere 도입 ✅ (실측044 통과)
목표: Agent 출력을 구조화 → Consigliere에 저장. -c는 아직 유지.
변경: debate-consigliere.ts 신규, debate-filter.ts 신규 (Output만)
효과: 디버깅 개선 + Consigliere 데이터 축적 시작
검증: 실측044 — 11/11 통과, ₩0
Phase 2: Input Filter + -c 제거 ✅ (실측045 통과)
목표: Consigliere에서 읽기. -c 제거, fresh -p 전환.
변경: debate-filter.ts (Input 추가), llm.ts (callDirectorStateless), debate.ts 수정
효과: 토큰 85-94% 절감, 비용 2,683원 → ~400원
검증: 실측045 — 16/16 통과, ₩0. Input Filter ~50-144tok (기존 41-84K 대비 99% 감소)
다음: 풀 파이프라인 실측으로 실제 비용 절감 확인
Phase 3: 시스템 프롬프트 동적 선택 ✅ (실측045 통과)
목표: Filter가 토론 주제에 관련된 규칙만 Agent에게 주입.
변경: buildDebateSystemRules() — RULE_BLOCKS 태그 기반 선택
효과: getHardRules ~1.5K → 동적 248-906자 (40-83% 축소)
검증: 실측045 — 21/21 통과, ₩0. 설계전용 71tok, iOS전용 132tok, 전체 259tok.
12. 기존 시스템과의 관계
| 기존 모듈 | MVVM 후 | 변경 여부 |
|---|---|---|
debate.ts (토론 오케스트레이터) | buildConsensus/collectOpinions MVVM 전환 | 수정 |
node-tree.ts (NodeTree) | 그대로 유지 (Consigliere와 병행) | 유지 |
debate-queue.ts (DebateQueue) | 그대로 유지 | 유지 |
sections.ts (섹션 매핑) | 그대로 유지 | 유지 |
llm.ts (callClaude) | callDirectorStateless() 추가 | 추가 |
consigliere.ts (기존 Consigliere 도구) | 별개. debate-consigliere는 토론 전용 인메모리 | 유지 |
신규 파일
| 파일 | 역할 |
|---|---|
debate-consigliere.ts | DebateConsigliere 클래스 (ViewModel) |
debate-filter.ts | Input Filter + Output Filter (Interface) |
13. 관련 문서
| 문서 | 내용 |
|---|---|
| debate.md | 기존 토론 시스템 명세 (v1.0) |
| todolist.md I-2-1 | 파이프라인 비용 분석 원본 |
| todolist.md I-2-2 | 5인 전문가 패널 토론 전문 |
| todolist.md R4 | 메모리 제약 해법 원본 |
| strategy/saas-incubator.md | SaaS 부화장 모델 (Consigliere = SaaS 인프라 토대) |
| spec/soldato.md | Pizza 모델 (LoRA + Consigliere 결합 비전) |