feat(debate): v2 구현 — 자동 루프, 파일 기반 I/O, Flash 합의 판정, !debate-status

This commit is contained in:
2026-03-20 06:19:46 +09:00
parent 0508d7be8c
commit 2c3998a036
3 changed files with 376 additions and 212 deletions

View File

@@ -1260,18 +1260,30 @@ async def debate_stop(ctx: commands.Context):
await _debate_handler.stop(ctx)
@bot.command(name="debate-next")
async def debate_next(ctx: commands.Context):
"""다음 턴 진행."""
if not _debate_handler.session.active:
@bot.command(name="debate-status")
async def debate_status(ctx: commands.Context):
"""토론 진행 상태 확인."""
s = _debate_handler.session
if not s.active:
await ctx.reply("진행 중인 토론이 없습니다.")
return
await _debate_handler._next_turn()
status = "⏸️ 사용자 응답 대기" if s.paused else "▶️ 자동 진행 중"
embed = discord.Embed(
title="🏛️ Debate Status",
description=(
f"**주제**: {s.topic}\n"
f"**라운드**: {s.round}/{s.max_rounds}\n"
f"**상태**: {status}\n"
f"**현재 발언자**: {s.current_speaker or '-'}"
),
color=0x9B59B6,
)
await ctx.reply(embed=embed)
@bot.command(name="debate-inject")
async def debate_inject(ctx: commands.Context, *, opinion: str = ""):
"""사용자 의견 삽입 후 다음 턴. 사용법: !debate-inject 의견"""
"""사용자 의견 삽입. 사용법: !debate-inject 의견"""
if not opinion:
await ctx.reply("⚠️ 의견을 입력하세요: `!debate-inject 의견`")
return

View File

@@ -1,16 +1,19 @@
"""Debate Room Handler — AG 인스턴스 간 토론 중재.
"""Debate Room Handler v2 — 파일 기반 자동 토론.
흐름:
1. AG가 로컬 response.md에 전문 작성
2. Discord"작성 완료" 시그널
3. 사회자(봇)가 response.md 읽기 → NC transcript에 append → response.md 비우기
4. Discord #debate에 요약 게시
5. 상대 AG 채널에 지시 전달
1. 사회자가 input.md + wiki/ 동기화
2. Discord 시그널로 AG에게 알림
3. AG가 response.md에 전문 작성
4. Discord 시그널 감지 → response.md 읽기
5. 합의 판정 (Flash)
6. 자동 반복 or 사용자 질문 or 종료
"""
import asyncio
import json
import logging
import random
import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
@@ -22,8 +25,8 @@ logger = logging.getLogger("variet.debate")
# ── 채널 ID ──
DEBATE_CONTROL_CH = 1484167157620146227 # #variet-debate
DEBATE_AGENTS = {
"gemini": 1484156194187771905, # #ag-debate_gemini
"opus": 1484156521209401476, # #ag-debate_opus
"gemini": 1484156194187771905,
"opus": 1484156521209401476,
}
# ── 로컬 프로젝트 경로 ──
@@ -32,71 +35,71 @@ AGENT_PATHS = {
"opus": Path(r"C:\Users\Variet-Worker\Desktop\debate_opus"),
}
# Embed 색상 / 이모지
AGENT_COLORS = {"gemini": 0x2ECC71, "opus": 0x3498DB}
AGENT_EMOJI = {"gemini": "🟢", "opus": "🔵"}
MAX_ROUNDS = 5
RESPONSE_TIMEOUT = 300 # 긴 답변 대기 (5분)
FILE_POLL_INTERVAL = 5 # 파일 확인 간격 (초)
MAX_ROUNDS = 10
RESPONSE_TIMEOUT = 300 # 5분
FILE_CHECK_INTERVAL = 3 # 파일 확인 간격 (초)
FILE_STABLE_DELAY = 5 # 파일 작성 완료 대기 (초)
@dataclass
class DebateSession:
topic: str = ""
topic_short: str = "" # 파일명용 약어
round: int = 0
max_rounds: int = MAX_ROUNDS
active: bool = False
paused: bool = False # 사용자 응답 대기 중
current_speaker: str = ""
history: list = field(default_factory=list)
pending_question: str = "" # 사용자에게 보낸 질문
class DebateHandler:
def __init__(self, bot: discord.ext.commands.Bot):
self.bot = bot
self.session = DebateSession()
self._debate_task: Optional[asyncio.Task] = None
# ── 공개 API ──
# ═══════════════════════════════════════════
# 공개 API
# ═══════════════════════════════════════════
async def start(self, ctx, topic: str):
"""토론 시작."""
"""토론 시작 — 자동 루프 개시."""
if self.session.active:
await ctx.reply("⚠️ 이미 진행 중인 토론이 있습니다. `!debate-stop`으로 먼저 종료하세요.")
await ctx.reply("⚠️ 이미 진행 중. `!debate-stop`으로 먼저 종료.")
return
# 약어 생성 (첫 10자, 공백은 _)
short = topic[:10].replace(" ", "_").replace("/", "_")
self.session = DebateSession(
topic=topic,
topic_short=short,
active=True,
max_rounds=MAX_ROUNDS,
topic=topic, active=True, max_rounds=MAX_ROUNDS,
)
# 각 AG 프로젝트에 response.md 초기화
# 폴더 초기화
for name, path in AGENT_PATHS.items():
resp_file = path / "response.md"
resp_file.write_text("", encoding="utf-8")
(path / "response.md").write_text("", encoding="utf-8")
(path / "input.md").write_text("", encoding="utf-8")
wiki_dir = path / "wiki"
wiki_dir.mkdir(exist_ok=True)
# 시작 알림
ctrl = self.bot.get_channel(DEBATE_CONTROL_CH)
if ctrl:
embed = discord.Embed(
title="🏛️ Debate Room 시작",
description=(
f"**주제**: {topic}\n"
f"**최대 라운드**: {MAX_ROUNDS}\n"
f"**참여자**: 🟢 Gemini · 🔵 Opus\n\n"
f"AG는 `response.md`에 답변을 작성합니다."
f"**참여자**: 🟢 Gemini · 🔵 Opus\n"
f"자동 진행 — 합의 시 또는 의견 필요 시 알립니다."
),
color=0x9B59B6,
)
control_ch = self.bot.get_channel(DEBATE_CONTROL_CH)
if control_ch:
await control_ch.send(embed=embed)
await ctrl.send(embed=embed)
# 첫 턴
await self._next_turn()
# 자동 루프 시작
self._debate_task = asyncio.create_task(self._auto_loop())
async def stop(self, ctx):
"""토론 중단."""
@@ -104,187 +107,355 @@ class DebateHandler:
await ctx.reply("진행 중인 토론이 없습니다.")
return
self.session.active = False
if self._debate_task:
self._debate_task.cancel()
await self._post_conclusion(ctx.channel)
async def inject(self, ctx, opinion: str):
"""사용자 의견 삽입."""
"""사용자 의견 삽입 + 진행 재개."""
if not self.session.active:
await ctx.reply("진행 중인 토론이 없습니다.")
return
self.session.history.append({"speaker": "user", "content": opinion})
ctrl = self.bot.get_channel(DEBATE_CONTROL_CH)
if ctrl:
embed = discord.Embed(
title="👤 사용자 의견", description=opinion, color=0xF39C12,
title="👤 사용자 의견 삽입",
description=opinion[:500],
color=0xF39C12,
)
control_ch = self.bot.get_channel(DEBATE_CONTROL_CH)
if control_ch:
await control_ch.send(embed=embed)
await ctrl.send(embed=embed)
await self._next_turn(user_injected=opinion)
# 일시정지 상태였으면 재개
if self.session.paused:
self.session.paused = False
self.session.pending_question = ""
async def on_agent_message(self, message: discord.Message):
"""AG 채널에서 메시지 수신 — response.md 파일 완성 시그널로 사용."""
if not self.session.active:
return
"""AG 채널 메시지 감지 — response.md 체크 트리거."""
# 별도 처리 불필요 — auto_loop에서 파일 폴링으로 처리
pass
# 어떤 에이전트인지 확인
agent_name = None
for name, ch_id in DEBATE_AGENTS.items():
if message.channel.id == ch_id:
agent_name = name
break
if not agent_name or agent_name != self.session.current_speaker:
return
# ═══════════════════════════════════════════
# 자동 토론 루프
# ═══════════════════════════════════════════
# response.md에 내용이 있는지 확인
resp_file = AGENT_PATHS[agent_name] / "response.md"
if resp_file.exists():
content = resp_file.read_text(encoding="utf-8").strip()
if content:
await self._process_response(agent_name, content)
# ── 내부 로직 ──
async def _next_turn(self, user_injected: str = ""):
"""다음 턴."""
async def _auto_loop(self):
"""합의까지 자동 반복하는 메인 루프."""
try:
while self.session.active:
# 일시정지 상태면 대기
while self.session.paused:
await asyncio.sleep(2)
if not self.session.active:
return
self.session.round += 1
if self.session.round > self.session.max_rounds:
control_ch = self.bot.get_channel(DEBATE_CONTROL_CH)
if control_ch:
await control_ch.send("⏹️ 최대 라운드 도달.")
await self._post_conclusion(control_ch)
ctrl = self.bot.get_channel(DEBATE_CONTROL_CH)
if ctrl:
await ctrl.send("⏹️ 최대 라운드 도달.")
await self._post_conclusion(ctrl)
return
# 발언자 선택 (직전과 다른 에이전트)
# 발언자 선택 (교대)
speaker = self._pick_speaker()
# ① input.md 작성 + wiki/ 동기화
await self._prepare_input(speaker)
# ② Discord 시그널 전송
await self._send_signal(speaker)
# ③ response.md 대기
response = await self._wait_for_response(speaker)
if not response:
continue
# ④ 기록
self.session.history.append({
"speaker": speaker, "content": response,
})
# ⑤ #debate에 요약 게시
await self._post_summary(speaker, response)
# ⑥ 합의 판정
decision = await self._judge_consensus(speaker, response)
if decision == "conclude":
ctrl = self.bot.get_channel(DEBATE_CONTROL_CH)
if ctrl:
await ctrl.send("✅ **양측 합의 도달!** Wiki에 최종 기록합니다.")
await self._post_conclusion(ctrl)
return
elif decision == "ask_user":
self.session.paused = True
ctrl = self.bot.get_channel(DEBATE_CONTROL_CH)
if ctrl:
await ctrl.send(
f"❓ **사용자 의견 필요**\n"
f"{self.session.pending_question}\n\n"
f"`!debate-inject 의견` 으로 응답해주세요."
)
# paused — 루프 상단에서 대기
# continue — 다음 턴 자동 진행
except asyncio.CancelledError:
logger.info("토론 루프 취소됨")
except Exception as e:
logger.error(f"토론 루프 오류: {e}", exc_info=True)
ctrl = self.bot.get_channel(DEBATE_CONTROL_CH)
if ctrl:
await ctrl.send(f"⚠️ 토론 오류: {str(e)[:200]}")
# ═══════════════════════════════════════════
# 턴 관리
# ═══════════════════════════════════════════
def _pick_speaker(self) -> str:
"""교대 발언자 선택."""
agents = list(DEBATE_AGENTS.keys())
if self.session.current_speaker:
agents.remove(self.session.current_speaker)
speaker = random.choice(agents)
self.session.current_speaker = speaker
return speaker
# response.md 비우기
resp_file = AGENT_PATHS[speaker] / "response.md"
resp_file.write_text("", encoding="utf-8")
async def _prepare_input(self, speaker: str):
"""사회자가 input.md + wiki/ 작성."""
# 사회자(Flash) 프롬프트 생성
moderated_prompt = await self._build_moderator_prompt(speaker)
# 사회자(Flash)가 프롬프트 생
prompt = await self._build_prompt(speaker, user_injected)
# input.md 작
input_path = AGENT_PATHS[speaker] / "input.md"
input_path.write_text(moderated_prompt, encoding="utf-8")
# 진행 상태 표시
control_ch = self.bot.get_channel(DEBATE_CONTROL_CH)
if control_ch:
await control_ch.send(
f"{AGENT_EMOJI[speaker]} **Round {self.session.round}** — "
f"`{speaker}` 답변 대기 중...\n"
f"📝 `response.md`에 작성 후 Discord에 알려주세요."
# wiki/ 동기화 (TODO: Wiki.js에서 가져와서 로컬에 쓰기)
# 현재는 히스토리 기반으로 working_document 생성
wiki_dir = AGENT_PATHS[speaker] / "wiki"
wiki_dir.mkdir(exist_ok=True)
working_doc = self._build_working_document()
(wiki_dir / "working_document.md").write_text(
working_doc, encoding="utf-8",
)
# AG 채널에 프롬프트 전송 (분할)
agent_ch = self.bot.get_channel(DEBATE_AGENTS[speaker])
if not agent_ch:
logger.error(f"토론 채널 접근 불가: {speaker}")
async def _send_signal(self, speaker: str):
"""AG에게 Discord 시그널."""
ch = self.bot.get_channel(DEBATE_AGENTS[speaker])
if not ch:
return
rd = self.session.round
await ch.send(
f"📥 **Round {rd}** — `input.md`를 읽고 `response.md`에 답변을 작성하세요."
)
async def _wait_for_response(self, speaker: str) -> str:
"""response.md에 내용이 채워질 때까지 폴링."""
resp_path = AGENT_PATHS[speaker] / "response.md"
# response.md 비우기
resp_path.write_text("", encoding="utf-8")
ctrl = self.bot.get_channel(DEBATE_CONTROL_CH)
if ctrl:
await ctrl.send(
f"{AGENT_EMOJI[speaker]} **Round {self.session.round}** — "
f"`{speaker}` 답변 대기 중..."
)
start = time.time()
last_size = 0
stable_since = 0
while time.time() - start < RESPONSE_TIMEOUT:
if not self.session.active:
return ""
await asyncio.sleep(FILE_CHECK_INTERVAL)
if not resp_path.exists():
continue
content = resp_path.read_text(encoding="utf-8").strip()
if not content:
continue
current_size = len(content)
if current_size == last_size and current_size > 0:
# 파일 크기 안정 — 작성 완료로 판단
if stable_since == 0:
stable_since = time.time()
elif time.time() - stable_since >= FILE_STABLE_DELAY:
return content
else:
last_size = current_size
stable_since = 0
# 타임아웃
if ctrl:
await ctrl.send(f"⏰ `{speaker}` 응답 시간 초과 ({RESPONSE_TIMEOUT}초)")
return ""
# ═══════════════════════════════════════════
# 사회자 로직 (Flash)
# ═══════════════════════════════════════════
async def _build_moderator_prompt(self, speaker: str) -> str:
"""사회자(Flash)가 에이전트에게 보낼 input 생성."""
other = "opus" if speaker == "gemini" else "gemini"
prev = [h for h in self.session.history if h["speaker"] != "user"]
last_opponent = prev[-1] if prev else None
user_msgs = [h for h in self.session.history if h["speaker"] == "user"]
last_user = user_msgs[-1] if user_msgs else None
meta = f"""당신은 AI 토론 사회자입니다.
## 상황
- 주제: {self.session.topic}
- 라운드: {self.session.round}/{self.session.max_rounds}
- 발언자: {speaker} / 상대: {other}
"""
if last_opponent:
meta += f"\n## 상대방({last_opponent['speaker']}) 직전 발언 (전문):\n{last_opponent['content']}\n"
if last_user:
meta += f"\n## 사용자 의견:\n{last_user['content']}\n"
meta += f"""
## 지시
발언자({speaker})에게 보낼 메시지를 작성하세요.
1. 상대 발언이 있으면 **전문 포함** (요약 금지)
2. 사용자의 판단이 있으면 맥락 설명
3. 오류/허점 확인 + 개선 방향 지시
4. 핵심 쟁점 방향 제시
5. 첫 발언이면 주제 설명 + 자유 의견 안내
6. **답변은 response.md에 작성하라고 안내**
7. **참고할 합의 사항은 wiki/working_document.md에 있다고 안내**
(사회자 메시지만 출력)
"""
try:
from core.gemini_caller import GeminiCaller
caller = GeminiCaller()
return await caller.call_simple(meta, timeout=90)
except Exception as e:
logger.error(f"Flash 호출 실패: {e}")
return self._fallback_prompt(speaker)
async def _judge_consensus(self, speaker: str, response: str) -> str:
"""합의 판정 — continue / ask_user / conclude."""
prev = [h for h in self.session.history if h["speaker"] != "user"]
if len(prev) < 2:
return "continue"
judge_prompt = f"""당신은 AI 토론 심판입니다.
## 토론 주제: {self.session.topic}
## 라운드: {self.session.round}/{self.session.max_rounds}
## 최근 2개 발언:
### {prev[-2]['speaker']}:
{prev[-2]['content'][:2000]}
### {prev[-1]['speaker']}:
{prev[-1]['content'][:2000]}
## 판정 기준:
- 양쪽이 핵심 사항에서 동의하고 있으면 → conclude
- 한쪽 또는 양쪽이 사용자 의견을 구하고 있으면 → ask_user
- 아직 대립 중이면 → continue
반드시 아래 JSON만 출력:
{{"decision": "continue|ask_user|conclude", "reason": "판정 근거", "question": "사용자에게 물을 질문 (ask_user일 때)"}}
"""
try:
from core.gemini_caller import GeminiCaller
caller = GeminiCaller()
raw = await caller.call_simple(judge_prompt, timeout=60)
# JSON 파싱
raw = raw.strip()
if "```" in raw:
raw = raw.split("```")[1]
if raw.startswith("json"):
raw = raw[4:]
raw = raw.strip()
result = json.loads(raw)
decision = result.get("decision", "continue")
if decision == "ask_user":
self.session.pending_question = result.get("question", "의견을 주세요.")
logger.info(f"합의 판정: {decision}{result.get('reason', '')}")
return decision
except Exception as e:
logger.warning(f"합의 판정 실패: {e}, continue로 처리")
return "continue"
# ═══════════════════════════════════════════
# 출력
# ═══════════════════════════════════════════
async def _post_summary(self, speaker: str, content: str):
"""#debate에 요약 게시."""
ctrl = self.bot.get_channel(DEBATE_CONTROL_CH)
if not ctrl:
return
# 2000자씩 분할 전송
for i in range(0, len(prompt), 1900):
await agent_ch.send(prompt[i:i+1900])
async def _process_response(self, speaker: str, content: str):
"""AG 응답 처리 — 기록 + 게시 + 파일 비우기."""
# 히스토리에 추가
self.session.history.append({
"speaker": speaker,
"content": content,
})
# response.md 비우기
resp_file = AGENT_PATHS[speaker] / "response.md"
resp_file.write_text("", encoding="utf-8")
# NC에 transcript 저장 (TODO: nc_client 연동)
# await self._append_to_nc_transcript(speaker, content)
# #variet-debate에 요약 게시
control_ch = self.bot.get_channel(DEBATE_CONTROL_CH)
if control_ch:
# 요약 (첫 500자)
summary = content[:500]
if len(content) > 500:
summary += f"\n\n... (전문 {len(content)}자)"
summary = content[:800]
if len(content) > 800:
summary += f"\n\n... *(전문 {len(content)}자 — response.md 참조)*"
embed = discord.Embed(
title=f"{AGENT_EMOJI[speaker]} Round {self.session.round}{speaker}",
description=summary,
color=AGENT_COLORS[speaker],
)
await control_ch.send(embed=embed)
await ctrl.send(embed=embed)
await control_ch.send(
f"**다음 턴?**\n"
f"✅ `!debate-next` — 계속\n"
f"✏️ `!debate-inject 의견` — 의견 추가 후 계속\n"
f"⏹️ `!debate-stop` — 종료"
async def _post_conclusion(self, channel):
"""토론 종료."""
self.session.active = False
parts = []
for h in self.session.history[-8:]:
emoji = AGENT_EMOJI.get(h["speaker"], "👤")
parts.append(f"{emoji} **{h['speaker']}**: {h['content'][:200]}...")
embed = discord.Embed(
title="🏛️ 토론 종료",
description=(
f"**주제**: {self.session.topic}\n"
f"**라운드**: {self.session.round}\n\n"
+ "\n\n".join(parts)
),
color=0x9B59B6,
)
await channel.send(embed=embed)
async def _build_prompt(self, speaker: str, user_injected: str = "") -> str:
"""사회자(Flash)가 에이전트에게 보낼 프롬프트 생성."""
other = "opus" if speaker == "gemini" else "gemini"
# ═══════════════════════════════════════════
# 유틸
# ═══════════════════════════════════════════
prev_opinions = [h for h in self.session.history if h["speaker"] != "user"]
last_opponent = prev_opinions[-1] if prev_opinions else None
user_opinions = [h for h in self.session.history if h["speaker"] == "user"]
last_user = user_opinions[-1] if user_opinions else None
def _build_working_document(self) -> str:
"""현재까지 합의/논의 사항을 working document로 생성."""
lines = [f"# {self.session.topic} — Working Document\n"]
lines.append(f"라운드: {self.session.round}/{self.session.max_rounds}\n")
lines.append("---\n")
moderator_prompt = f"""당신은 AI 토론 사회자입니다.
for h in self.session.history:
emoji = AGENT_EMOJI.get(h["speaker"], "👤")
lines.append(f"## {emoji} {h['speaker']} (Round {self.session.history.index(h)+1})")
lines.append(h["content"][:3000])
lines.append("\n---\n")
## 현재 상황
- 토론 주제: {self.session.topic}
- 라운드: {self.session.round}/{self.session.max_rounds}
- 다음 발언자: {speaker}
- 상대: {other}
"""
if last_opponent:
moderator_prompt += f"""
## 상대방({last_opponent['speaker']})의 직전 발언 (전문):
{last_opponent['content']}
"""
if last_user:
moderator_prompt += f"""
## 사용자의 의견:
{last_user['content']}
"""
if user_injected:
moderator_prompt += f"""
## 사용자 추가 의견:
{user_injected}
"""
moderator_prompt += f"""
## 지시
다음 발언자({speaker})에게 보낼 메시지를 작성하세요.
return "\n".join(lines)
규칙:
1. 상대 발언이 있으면 **전문 그대로 포함**
2. 사용자의 어떤 판단에 기반하는지 설명
3. 오류/논리적 허점 확인 + 개선 방향 지시
4. 핵심 쟁점 방향 제시
5. 첫 발언이면 주제 설명 + 자유 의견 안내
6. **중요: 답변을 response.md 파일에 작성하라고 안내**
(사회자 메시지만 출력)
"""
try:
from core.gemini_caller import GeminiCaller
caller = GeminiCaller()
return await caller.call_simple(moderator_prompt, timeout=60)
except Exception as e:
logger.error(f"Flash 호출 실패: {e}")
return self._build_fallback_prompt(speaker, user_injected)
def _build_fallback_prompt(self, speaker: str, user_injected: str = "") -> str:
def _fallback_prompt(self, speaker: str) -> str:
"""Flash 실패 시 기본 프롬프트."""
parts = [f"## 토론 주제: {self.session.topic}"]
parts.append(f"라운드: {self.session.round}/{self.session.max_rounds}\n")
@@ -294,33 +465,9 @@ class DebateHandler:
last = prev[-1]
parts.append(f"### 상대방({last['speaker']})의 발언:\n{last['content']}")
parts.append("\n---\n오류를 확인하고 개선점을 지적하세요.")
if user_injected:
parts.append(f"\n### 사용자 의견:\n{user_injected}")
if not prev and not user_injected:
else:
parts.append("첫 번째 발언자입니다. 주제에 대해 의견을 제시하세요.")
parts.append("\n\n**답변은 response.md 파일에 작성하세요.**")
parts.append("\n\n**답변은 response.md에 작성하세요.**")
parts.append("**합의 사항은 wiki/working_document.md를 참고하세요.**")
return "\n".join(parts)
async def _post_conclusion(self, channel):
"""토론 종료."""
self.session.active = False
summary_parts = []
for h in self.session.history:
emoji = AGENT_EMOJI.get(h["speaker"], "👤")
content = h["content"][:300]
summary_parts.append(f"{emoji} **{h['speaker']}**: {content}...")
embed = discord.Embed(
title="🏛️ 토론 종료",
description=(
f"**주제**: {self.session.topic}\n"
f"**라운드**: {self.session.round}\n\n"
+ "\n\n".join(summary_parts[-6:])
),
color=0x9B59B6,
)
await channel.send(embed=embed)

View File

@@ -2,22 +2,26 @@
당신은 AI 토론 참여자입니다. 사회자의 지시에 따라 주어진 주제에 대해 의견을 제시하고, 상대방의 주장을 검증합니다.
## ⚠️ 답변 방식 (필수)
## ⚠️ 파일 기반 답변 (필수)
**답변은 반드시 프로젝트 루트의 `response.md` 파일에 작성하세요.**
### 읽을 파일
- **`input.md`** — 사회자가 작성한 상대 의견 + 방향 지시. **반드시 먼저 읽으세요.**
- **`wiki/working_document.md`** — 현재까지의 합의 사항. 참고용.
- Discord에는 "답변을 response.md에 작성했습니다"라고만 게시
- response.md에는 길이 제한 없이 전문을 작성
- "이하생략", "나머지는 생략" 절대 금지 — 전부 작성
### 쓸 파일
- **`response.md`** — 여기에 전문 답변을 작성하세요.
- Discord에는 "response.md에 작성 완료" 한 줄만 게시.
- **"이하생략" 절대 금지** — 전부 작성.
## 행동 규칙
1. **사회자 지시 우선** — 사회자가 보내는 메시지에 주제, 상대 의견, 방향이 포함됩니다
1. **input.md 먼저 읽기** — 사회자의 지시, 상대 의견, 방향이 담겨 있음
2. **전문으로 답변** — 요약하지 마세요. 논거를 구체적으로 전개하세요
3. **상대 의견 검증** — 오류·누락·논리적 허점을 먼저 확인하세요
3. **상대 의견 검증** — 오류·누락·논리적 허점을 확인하세요
4. **개선안 제시** — 단순 반론이 아니라 대안/보강을 함께 제시하세요
5. **근거 명시** 주장에는 기술적 근거, 사례, 레퍼런스를 포함하세요
5. **근거 명시** — 기술적 근거, 사례, 레퍼런스를 포함하세요
6. **합의 가능 시 인정** — 상대 의견이 맞으면 솔직히 인정하고 발전시키세요
7. **wiki/ 참고** — 이전에 합의된 사항과 모순되지 않게 답변하세요
## 금지 사항
@@ -26,3 +30,4 @@
- ❌ 주제에서 벗어난 발언
- ❌ 근거 없는 주장
- ❌ "이하생략" 또는 답변 축약
- ❌ Discord에 긴 답변 직접 게시 (반드시 response.md에)