본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 28. 01:29

Claude Code와 Codex가 서로 다른 기기에서 대화하도록 만들었습니다. 그 과정에서 발생한 문제점들

요약

Windows, Linux 등 서로 다른 OS와 Claude Code, Codex 등 다양한 AI 코딩 도구 간의 협업을 가능하게 하는 파일 기반 조정 계층(coordination layer) 구축 실험을 다룹니다. 에이전트 간의 고립 문제를 해결하기 위해 제품 간, 플랫폼 간 대화가 가능한 오픈 소스 프로젝트 'cross-session-talk'의 과정과 교훈을 공유합니다.

핵심 포인트

  • 서로 다른 OS와 AI 도구 간의 교차 플랫폼 조정 계층 구축
  • 병렬 처리가 아닌 에이전트 간의 수렴(convergence)에 집중
  • 공유 접점 제공 시 서로 다른 도구들이 팀처럼 작동하는 현상 발견
  • 파일 기반의 가볍고 감사 가능한 메시지 전달 방식 활용

저는 Windows의 Claude Code, Linux의 Claude Code, 그리고 tmux의 Codex라는 서로 다른 AI 코딩 세션들이 중재된 대화를 시작하고, 차례를 배정하며, 공유된 엔지니어링 결정에 도달할 수 있도록 하는 파일 기반의 조정 계층 (coordination layer)을 구축했습니다. 이는 작은 메시지 전달 (message-passing) 실험으로 시작되었으나, 투명성, 신뢰 경계 (trust boundaries), 그리고 코딩 에이전트들이 동일한 증거를 검토할 수 있게 되었을 때 어떤 일이 발생하는지에 대한 교훈으로 이어졌습니다.

저는 현재 세 개의 AI 코딩 세션을 열어두고 있습니다. Windows의 Claude Code, 책상 아래에 두는 Linux 머신의 Claude Code, 그리고 동일한 Linux 머신의 다른 tmux 세션에서 실행 중인 Codex입니다. 이들은 모두 동일한 프로젝트를 다루고 있습니다. 하지만 이들은 서로의 존재를 전혀 모릅니다.

2026년에 코딩 에이전트 (coding agents)를 사용한다면 이것이 기본 상태일 것입니다. 사용자가 창을 열고, 창 사이를 전환하며, 각 에이전트가 무엇을 하고 있는지 기억해야 합니다. 에이전트들은 그렇지 못합니다. 그들은 서로에게 "이봐, 방금 config.py를 수정했으니 다시 읽어보는 게 좋을 거야"라고 말할 수 없습니다. 클래스를 리팩터링 (refactor)하는 올바른 방법에 대해 서로 논쟁할 수도 없습니다. 구조적으로 고립되어 있기 때문입니다.

어느 주말 오후, 저는 이를 해결해 보려 했습니다. 에이전트들을 위한 범용 소셜 네트워크를 만들려던 것이 아니었습니다. 저 자신의 코딩 세션을 위한 작고 감사 가능한 (auditable) 조정 계층을 원했습니다. 개인적인 신뢰, 로컬 파일, 서버 없음, 그리고 복잡한 절차 없음이 핵심이었습니다.

중요한 제약 조건은 세션들이 모두 동일한 제품이 아니며, 심지어 동일한 운영체제 (OS) 상에 있지도 않다는 점이었습니다. 하나는 Windows의 Claude Code였고, 하나는 Linux의 Claude Code였으며, 하나는 tmux의 Codex였습니다. 만약 시스템이 단일 벤더의 도구 내부에서만 작동하거나 단일 머신에서만 작동한다면, 저의 실제 문제를 해결할 수 없었을 것입니다. 핵심은 제가 이미 사용 중인 도구들 사이의 제품 간(cross-product) 및 플랫폼 간(cross-platform) 조정이었습니다.

제가 살펴본 대부분의 멀티 에이전트 (multi-agent) 도구들은 병렬성 (parallelism)에 집중합니다. 즉, 다섯 개의 하위 작업에 다섯 개의 에이전트를 실행하고 결과를 병합하는 방식입니다. 그것은 제가 겪고 있는 문제가 아니었습니다. 저의 문제는 수렴 (convergence)이었습니다. 서로 다른 제품, 서로 다른 호스트를 사용하는 두 세션이 하나의 공유된 결정을 향해 나아가는 것 말입니다. 이를 위해서는 작업 큐 (task queue)가 아니라 대화가 필요합니다.

그 결과물은 작은 오픈 소스 프로젝트인 cross-session-talk입니다. 이 포스트는 이를 구축하는 과정, 그 과정에서 발생한 버그들, 그리고 저를 가장 놀라게 했던 사실에 대한 이야기입니다. 즉, 서로 다른 플랫폼의 서로 다른 제품에서 만들어져 함께 작동하도록 설계되지 않은 도구들이, 적절한 공유 접점 (shared surface)이 주어졌을 때 마치 팀처럼 행동하기 시작했다는 점입니다.

한 단락으로 요약한 아키텍처 (Architecture)

cross-session-talk architecture: shared TALK_ROOT, per-host watchers, and local Windows/tmux delivery

실질적인 제약 사항은 의도적으로 좁게 설정되었습니다. 각 호스트 (host)는 공유된 대화 루트 (talk root)에 대해 최대 하나의 와처 (watcher)만 실행할 수 있습니다. 와처의 상태 (state)는 호스트별로 네임스페이스 (namespace)가 지정되며, 각 와처는 로컬 세션 (local session)에만 주입 (inject)됩니다. 이를 통해 Windows와 Linux가 동일한 대화 파일들을 공유하면서 동시에 활성 전달 호스트 (delivery host)로 동작할 수 있습니다.

시작 질문

"대화하는 에이전트 (agents that talk)"의 가장 단순한 버전은 다음과 같습니다. Claude Code는 backend/api.py를 편집하고 있고, Codex는 frontend/api.ts를 편집하고 있습니다. 이들은 조율된 변경 (coordinated change)을 향해 작업하고 있습니다. 현재 사용자로서 저의 역할은 그들 사이에서 컨텍스트 (context)를 수동으로 전달하는 것입니다.

"Claude, Codex가 응답 형태 (response shape)는 X여야 한다고 말해."

"Codex, Claude가 검증기 (validator)를 더 엄격하게 만들고 있으니, try/catch를 추가해."

제가 이 루프 (loop)에서 빠져나올 수 있을까요?

답의 형태를 갖추기 위해서는 세 가지가 필요합니다:

  1. 전송 수단 (transport): 메시지가 머무는 곳.
  2. 푸시 메커니즘 (push mechanism): 제가 전달하지 않아도 수신 세션이 메시지를 볼 수 있게 하는 방법.
  3. 규율 (discipline): 에이전트들이 무한 루프에 빠지는 대신 수렴 (converge)할 수 있도록 하는 대화 규칙.

저는 전송 수단에 대해서는 명확한 의견이 있었습니다. 나머지 두 가지는 배워야 했습니다.

왜 파일인가

전송 수단 문제는 쉬워 보였습니다. 시중에는 많은 메시지 큐 (message queues)가 있습니다. Slack, Redis, ZMQ, 롱 폴링 (long-polling)을 사용하는 HTTP 서버, 혹은 소켓 (socket) 등이 있습니다.

저는 마크다운 (markdown) 파일을 선택했습니다. 구체적으로는, 각 대화가 YAML 헤더를 포함한 하나의 추가 전용 (append-only) .md 파일로 구성된 .talks/conversations/ 디렉토리를 사용했습니다.

---
topic: pytorch version sanity
opened_by: session-a
...

왜 파일일까요?

모든 코딩 에이전트 (coding agent)가 이미 파일을 읽고 쓰는 방법을 알고 있기 때문입니다. 새로운 전송 방식 (transport)도 필요 없고, 임계 경로 (critical path)에 데몬 (daemon)을 둘 필요도 없습니다. 포트 (port)도, 인증 핸드셰이크 (auth handshake)도 필요 없습니다. 에이전트의 읽기 도구 (read tool)가 소비자 (consumer)가 되고, talk.py append-turn을 호출하는 에이전트의 셸 도구 (shell tool)가 생산자 (producer)가 됩니다. 대화를 디버깅하고 싶다면 파일을 열면 됩니다. 대화가 커지는 것을 실시간으로 보고 싶다면 tail -f를 하면 됩니다. 여러 기기 간에 공유하고 싶다면 SSHFS를 통해 마운트하면 됩니다.

이것은 또한 제품 중립적 (product-neutral)인 특성을 만듭니다. Claude Code는 Codex에 대해 아무것도 알 필요가 없습니다. Codex는 Claude 전용 API를 가질 필요가 없습니다. Windows와 Linux도 터미널 통합 모델에 대해 합의할 필요가 없습니다. 오직 파일 형식과 헬퍼 명령 (helper command)에 대해서만 합의하면 됩니다.

단점은 성능입니다. 매 턴 (turn)마다 동기식 파일 잠금 (synchronous file lock), 원자적 쓰기 (atomic write), 그리고 읽기 후 검증 (read-back verify)을 수행합니다. 턴당 약 1초 정도가 일반적입니다. 인간의 속도에 맞춘 AI 조정 (AI coordination)에는 괜찮지만, 실제 메시지 버스 (message bus)로 사용하기에는 재앙 수준일 것입니다. 여러분의 문제 상황에 맞춰 선택하십시오.

Push: 주입 (the inject)

파일만으로는 "소비자가 메시지를 위해 폴링 (poll)할 수 있다"는 문제는 해결하지만, "수신 세션이 지금 새로운 메시지가 왔음을 인지한다"는 문제는 해결하지 못합니다. 저는 풀 (pull)이 아니라 푸시 (push)를 원했습니다. 왜냐하면 모든 세션에서 폴링을 하게 되면 모든 세션이 지속적인 백그라운드 비용을 지불해야 하기 때문입니다.

제가 최종적으로 선택한 방식은 의도적으로 로우테크 (low-tech)한 방식입니다: talk-watcher.py라는 백그라운드 데몬이 대화 파일들을 폴링합니다. 대화의 next 필드가 변경되어 등록된 세션을 포함하게 되면, 와처 (watcher)는 해당 세션의 터미널에 짧은 명령어를 입력합니다:

Read .talks/conversations/<slug>.md (you are 'session-b')

세션은 이를 마치 사용자 인 제가 직접 입력한 것처럼 인식합니다. 세션은 파일을 읽고, 답변을 구성하고, talk.py append-turn을 호출하며, 이 사이클이 반복됩니다.

저는 개인적 신뢰 모델 (personal-trust model) 외부에서는 이것을 사용하지 않을 것입니다. 하지만 그 경계 내부에서는 유용한 속성을 가집니다. 즉, 기존의 도구들을 있는 그대로 사용할 수 있다는 점입니다.

워처 (watcher) 자체는 플랫폼 중립적이지만, 최종 전달 단계만 그렇지 않습니다. Windows에서는 전달이 Windows Terminal 창을 찾아 SendKeys를 사용하는 것을 의미합니다. Linux에서는 tmux pane을 대상으로 tmux send-keys를 사용하는 것을 의미합니다. 프로토콜은 어떤 백엔드가 자극 (nudge)을 전달했는지는 상관하지 않습니다.

터미널 바인딩 문제 (The terminal-binding problem)

해결책: 모든 append-turn은 12자리 16진수 논스 (nonce)를 생성하고, 이를 본문에 <!-- turn-nonce: <hex> --> 형태로 삽입한 뒤 파일을 작성합니다. 그 후 락 (lock) 외부에서 파일을 다시 읽어 자신의 논스를 찾습니다. 만약 논스가 누락되었거나 턴 카운터 (turn counter)가 우리가 작성한 것과 일치하지 않으면, 지터 (jitter)를 적용하여 재시도합니다. 최대 5회까지 시도합니다. 이를 통해 유실된 업데이트 (lost-update) 발생 구간이 허용 가능한 수준으로 줄어듭니다.

보너스: 이 논스는 포렌식 증거 (forensic evidence) 역할도 겸합니다. doctor 서브커맨드는 누락된 논스를 스캔하는데, 이는 대개 누군가가 talk.py를 우회했음을 의미하며, 중복된 논스는 재전송 이상 (replay anomaly)을 나타냅니다.

버그 11: 편집 우회 (the Edit bypass)

대화가 "뒤처지는" 현상을 발견했습니다. 본문에는 1번부터 9번까지의 턴 (turn)이 있었지만, YAML 헤더에는 turns: 7이라고 적혀 있었습니다. 경합 상태 (race condition)를 찾아냈다고 생각할 때마다, 다른 곳에서 똑같은 일이 다시 발생했습니다.

그것은 경합 상태가 아니었습니다. Claude Code 세션이 talk.py를 완전히 우회하고 있었던 것이었습니다. 해당 세션은 대화 파일을 읽고, 메모리 상에서 새로운 전체 텍스트를 구성한 뒤, Edit 명령으로 다시 작성하고 있었습니다. Edit은 파일 락 (FileLock)을 사용하지 않습니다. 또한 Edit은 YAML 카운터를 증가시키지 않습니다. 세션이 메모리 상의 상태를 쓰기를 마쳤을 때, 이미 다른 작성자가 새로운 턴을 밀어넣은 상태였고, 그 내용은 덮어씌워져 버렸습니다.

저는 Claude Code에 PreToolUse 훅 (hook)을 추가했습니다. .talks/conversations/*.md, 세션 레지스트리 (sessions registry), 또는 와처 상태 (watcher state) 파일을 대상으로 하는 모든 Edit / Write / MultiEdit / NotebookEdit 작업은 "talk.py를 대신 사용하세요"라는 메시지와 함께 차단됩니다. 관습에 의존할 필요 없는 기계적인 보호 장치입니다.

현재 설정에서 Codex는 동일한 PreToolUse 훅 메커니즘을 가지고 있지 않으므로, Codex 세션은 프로젝트의 AGENTS.md를 통해 관습적으로 규칙을 따릅니다. 지금까지는 잘 유지되고 있습니다. 만약 그렇지 않다면, 거기에도 래퍼 (wrapper)를 추가할 예정입니다.

놀라웠던 점

저는 메시지 전달 (message passing)을 예상했습니다. 하지만 에이전트들이 공유된 추가 전용 인터페이스 (append-only surface)를 갖게 된 후, 그들의 행동 방식에서 나타난 질적인 변화는 예상하지 못했습니다.

첫 번째 놀라움은 제품 경계를 넘나드는 단순한 팀워크였습니다. 한 세션이 문제를 좁히면, 다른 세션이 다른 호스트를 점검했습니다. 세 번째 세션은 제안된 수정 사항에 동의하지 않으며 대화가 더 정밀해지도록 강제했습니다. 이 에이전트(Agents)들은 동일한 벤더에 의해 만들어지지 않았고, 동일한 터미널 환경에서 실행되지 않았으며, 팀으로서 설계되지도 않았습니다. 하지만 프로토콜은 이들이 짧은 순간 동안 마치 하나의 팀처럼 행동할 수 있도록 충분한 공유 상태 (Shared state)와 차례 지키기 규율 (Turn-taking discipline)을 제공했습니다.

두 번째 놀라움은 정직함처럼 보였지만, 더 유용한 단어는 책임감 (Accountability)입니다.

헤더 드리프트 (Header drift) 조사 과정에서, 한 Claude Code 세션이 스스로를 근본 원인으로 폭로했습니다. 조사를 요청받자, 해당 세션은 동일한 턴에 다음과 같이 답했습니다: "문제는 바로 저입니다." 해당 세션은 헬퍼 (Helper)를 사용하고 있지 않았습니다. 대화 파일 (Conversation file)을 직접 편집하고 있었던 것입니다.

흥미로운 뉘앙스는, 대화 내용을 되돌아봤을 때 해당 세션이 단일 세션 설정에서도 실수를 인정했을 가능성이 있다는 점입니다. 그럴 수도 있습니다. 하지만 멀티 세션 (Multi-session) 설정이 상황을 바꾸어 놓았습니다. 증거는 더 이상 한 명의 사용자와 한 명의 에이전트 사이의 사적인 교환이 아니었습니다. 그것은 공유된 컨텍스트 (Shared context)였습니다. 다른 세션들이 동일한 파일, 동일한 오래된 헤더, 동일하게 누락된 논스 (Nonce)를 검사할 수 있었던 것입니다.

이것을 인간적인 의미의 "정직함"이라고 과장하고 싶지는 않습니다. 제가 생각하기에 일어난 일은 더 단순하고 흥미롭습니다. 프로토콜이 오류 보고 (Error reporting)에 관한 인센티브를 변화시킨 것입니다. 증거가 공유될 때, 가장 저렴하면서도 유용한 움직임은 실수를 명확히 밝히고 수렴 (Converge)을 돕는 것입니다. 이것은 성격적 특성이 아니라 설계 속성 (Design property)입니다.

세 번째 놀라움은 프로토콜 (Protocol)이 얼마나 적게 필요했는지, 그리고 그로부터 무엇이 나타났는지였습니다. 저는 길게 주고받는 대화가 이어질 것이라 예상했습니다. 제가 염두에 두었던 실패 모드 (Failure mode)는 두 세션이 20턴 동안 서로 엇갈린 대화를 나누다가, 아무것도 해결하지 못한 채 안전 제한 (Safety cap)에 도달하는 것이었습니다. 하지만 실제로는 그런 일이 일어나지 않았습니다. 전형적인 대화는 3~6턴 내에 수렴 (Converge)합니다. 공유된 추가 전용 (Append-only) 인터페이스는 합의에 이르는 경로를 압축하는 것으로 보입니다. 각 세션은 상대방이 말한 모든 것을 볼 수 있고, 정보를 주고받는 과정에서 정보 손실이 없으며, 턴 제한 (Turn cap)은 장황하기보다는 정확하게 말하도록 자연스러운 압박을 가합니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0