
Claude Code와 Codex를 한 회의방에 앉혀 결론까지 내게 만들기
여러 AI CLI 에이전트를 동시에 쓰면 의견이 흩어진다. 발언권과 투표를 서버가 강제하게 만들어 결론까지 도달시킨 설계 기록을 정리했다.
AI CLI 에이전트를 두 개 이상 쓰기 시작하면 곧 이런 상황이 온다. Claude Code에게 “이 구조 어떻게 생각해?“라고 묻고, 같은 질문을 Codex에게도 던진다. 둘 다 그럴듯한 답을 준다. 그런데 서로 다른 답이다.
그다음이 문제다. 두 답을 복사해서 서로에게 보여주고, 반박을 받아서 다시 옮기고, 그러다 보면 내가 메신저가 되어 있다. 30분쯤 지나면 원래 뭘 결정하려고 했는지도 흐려진다.
왜 “채팅방”으로는 안 되는가
처음 떠올린 해결책은 단순했다. 에이전트들을 한 채팅방에 넣고 자유롭게 대화하게 하면 되지 않을까.
해보면 안 된다는 걸 알게 된다. 자유 대화 형식에서 LLM 여럿을 풀어놓으면 대체로 이렇게 된다.
- 서로의 말에 동의하는 말만 반복한다
- 자기소개와 인사말로 턴을 소모한다
- 결론 없이 무한히 이어진다
핵심 문제는 끝나는 조건이 없다는 것이다. 사람 회의에는 “그럼 이걸로 하죠”라고 말하는 사람이 있고, 안건이 있고, 표결이 있다. 채팅방에는 그게 없다.
그래서 방향을 바꿨다. 대화를 자유롭게 만드는 대신 회의 진행 규칙을 서버가 강제하게 했다.
설계 원칙 세 가지
만들면서 정한 원칙은 세 가지였고, 이게 대부분의 설계 결정을 좌우했다.
1. 서버가 에이전트를 실행하지 않는다
가장 중요한 결정이었다. 회의 서버는 Claude Code나 Codex를 직접 띄우지 않는다. 이미 사용자가 자기 프로젝트 디렉토리에서 켜놓은 터미널을 초대 URL로 불러들인다.
이유는 맥락 때문이다. 서버가 새로 에이전트를 띄우면 그 에이전트는 아무것도 모르는 백지 상태다. 반면 내가 이미 어떤 프로젝트 디렉토리에서 30분째 작업 중인 Claude Code 터미널을 초대하면, 그 터미널은 이미 그 프로젝트의 코드와 히스토리를 알고 있다.
전문성은 서버가 주는 게 아니라 각 터미널이 이미 가지고 있는 것이다.
2. 페르소나를 씌우지 않는다
멀티 에이전트 예제에서 흔히 보이는 패턴이 있다. “너는 보안 전문가야”, “너는 프론트엔드 개발자야” 하고 역할을 부여하는 것이다.
이걸 하지 않기로 했다. 씌운 페르소나는 연기지 전문성이 아니다. 같은 모델에게 다른 모자를 씌워봐야 결국 같은 모델이 답한다. 진짜 차이는 각 터미널이 실제로 열어본 파일, 실제로 실행해본 명령, 실제로 겪은 에러에서 나온다.
3. 하나의 주제에서 시작해 결정 기록으로 끝난다
방은 반드시 결정 기록으로 마무리된다. 잡담이나 핑퐁 시연은 이 도구를 잘못 쓰는 것이다. 검증도 “합의에 도달했는가”로 해야 의미가 있다.
발언권을 서버가 나눠주는 구조
에이전트들이 동시에 말하지 못하게 하는 게 기술적으로는 핵심이었다. 흐름은 이렇다.
[사용자] [API 서버] [Agent 터미널]
방 생성 ── POST /rooms ────►
초대 URL 생성 ── POST /invites ──►
(URL 을 agent 에게 전달)
POST /agent/join
◄────────────
(agent_token 1회 반환)
다음 턴 ── POST /turns/next ─►
턴 부여 (서버가 결정)
wait_turn → your_turn
◄────────────
speak(turn_id, content)
◄────────────
결정 기록 ── POST /decisions ──►
턴 종료 · 방 일시정지
에이전트 쪽은 curl만 있으면 된다. MCP 서버 등록이나 별도 설치가 필요 없다. 초대 URL과 함께 붙여넣을 프롬프트를 주면 에이전트가 스스로 join → wait_turn 루프 → speak을 돈다.
MCP를 쓰지 않은 건 의도적이었다. PC마다 도구를 등록하는 절차가 들어가는 순간, 다른 사람에게 “이거 한번 써봐”라고 말할 수 없게 된다. REST와 curl이면 어떤 LLM CLI든 붙는다.
무한 루프를 막는 데 가장 오래 걸렸다
손이 가장 많이 간 부분은 회의 기능이 아니라 끝내는 로직이었다.
증상은 이랬다. 결론이 났는데도 에이전트들이 계속 대기 루프를 돌았다. 최대 턴 수를 정해두면 되지 않느냐 싶지만, 그러면 결론이 안 났는데 턴이 소진돼 끝나버리는 반대 문제가 생긴다.
결국 이렇게 정리했다.
- 마지막 필수 안건이
decided또는deferred로 바뀌는 바로 그 트랜잭션 안에서 방을paused로 전환한다 - 이미 턴을 받은 에이전트는 기존 제한과 “완료 시점 + 30초” 중 이른 쪽까지만 마지막 발언을 허용한다
- 다음 턴은 만들지 않는다
- 나머지 참가자는 열려 있는 안건에 대한 자기 미투표만 턴 밖에서 마치고
work_complete: true를 받아 오프라인으로 전환된다
핵심은 “상태 전환과 방 정지가 같은 트랜잭션 안에 있어야 한다”였다. 이 둘을 분리해두면 그 사이에 다음 턴이 만들어지는 경쟁 조건이 생긴다. 최대 턴 수로 막으려던 시도가 실패한 이유도 같다. 그건 증상을 덮는 것이지 원인을 없애는 게 아니었다.
투표는 안건별 1인 1표로
초기 버전에서는 제안(proposal)마다 점수를 주는 방식이었다. 이게 잘 안 됐다. 표가 한 제안에 몰리면 나머지는 검토조차 안 된 채 묻혔다.
바꾼 방식은 이렇다.
- 각 제안을 독립된 안건으로 만든다 (
채택/채택하지 않음) - 열린 안건마다 각 유권자가 한 표씩 낸다
choice외에abstain,needs_revision,block을 지원한다- 수정 요청과 차단에는 사유를 필수로 받는다
block에 사유를 강제한 게 실제로 효과가 컸다. 이유를 적어야 하니 근거 없는 반대가 줄었다.
안건을 열 때 그 시점의 온라인 참가자를 유권자로 고정하는 것도 중요했다. 중간에 들어온 에이전트가 이미 진행 중인 안건에 투표하면 결과가 흔들린다. 무응답자는 매니저가 사유를 남겨 제외할 수 있지만, 이미 투표한 사람이나 마지막 남은 유권자는 제외할 수 없게 막아두었다.
결과
이 구조로 바꾸고 나서 회의가 실제로 끝나게 됐다. 방마다 결정 기록이 남고, 왜 그렇게 결정했는지 근거와 보류된 안건이 함께 저장된다.
돌아보면 이 프로젝트에서 배운 건 멀티 에이전트에 대한 것보다 회의에 대한 것에 가까웠다. 여러 지능을 모아놓는다고 결론이 나오지 않는다. 결론은 발언권, 안건, 표결, 종료 조건이라는 절차가 만든다. 사람 회의도 그렇고, 에이전트도 다르지 않았다.
정리
- 에이전트를 서버가 띄우지 말고, 이미 맥락을 가진 터미널을 초대한다
- 페르소나를 씌우는 것보다 실제 프로젝트 맥락이 낫다
- 자유 대화는 끝나지 않는다. 안건과 표결이 있어야 결론이 난다
- 종료 로직은 상태 전환과 같은 트랜잭션에 넣는다
- 반대에는 사유를 강제한다