Codex CLI의 동기 설계 — app server를 경유하여 「열려 있는 Codex에, 다른 Codex로부터 프롬프트를 흘려보내기」를
요약
본 기사는 개인 게임 개발 프로젝트 Anemora에서 AI 세션 운영의 효율성을 높이기 위해 Codex CLI의 동기화 설계와 운용 방식을 다룹니다. 기존에는 중앙 총괄 역할의 AI가 프롬프트를 생성하면, 이를 수동으로 다른 워커(Worker) 세션에 붙여넣는 방식이었으나, 작업량이 증가하며 이 수동 개입이 한계에 도달했습니다. 이에 필자는 Codex CLI의 `app-server` 기능을 활용하여, 외부 스크립트에서 열려 있는 AI 세션(`thread`)으로 직접 프롬프트를 전송하는 PowerShell 도구를 개발했습니다. 이 도구는 '대기(wait)' 모드와 '연속 전송' 모드를 하나의 인터페이스로 통합하여, 수동 개입과 자율 주행 사이의 전환을 원활하게 만듭니다.
핵심 포인트
- Codex CLI의 `app-server` 기능을 활용하여 외부에서 AI 세션으로 직접 프롬프트를 전송하는 PowerShell 도구를 개발했다.
- 기존 수동 개입 방식(중앙 총괄 → 수동 복사/붙여넣기)은 작업량 증가에 따라 병목 현상(bottleneck)이 발생하여 한계에 도달했다.
- `send <thread> "..." --wait <sec>` 명령을 통해, AI 세션의 '턴 완료 대기'와 '연속 전송(자율 주행)' 모드를 하나의 송신 측면에서 전환할 수 있게 되었다.
- AI 세션 운영의 핵심은 단순히 자동화가 아니라, 중앙 총괄과 개별 워커 간에 '동기점(synchronization point)'을 효과적으로 만드는 데 있다.
본 기사는 개인 개발 중인 HD-2D 탐색 액션 어드벤처 Anemora의 제작 중에 구축한, Codex CLI의 세션 간 동기 설계와 운용을 다룹니다. 프로젝트 소개는 선행 기사 (Anemora 소개 기사), 인간 측의 역할에 대한 회고는 (AI가 손을 움직이는 개인 게임 개발에서, 인간이 놓지 않았던 3가지 업무)를 참조해 주세요.
TL;DR
열려 있는 Codex 세션에, 다른 Codex로부터 직접 프롬프트를 보낼 수 있는 PowerShell 도구 (약 470행)를, Codex CLI의 app-server 모드 위에 만들었습니다 - 그전까지는 「중앙 총괄 역할을 하는 Codex가 prompt를 내뱉음 → 내용을 확인하면서 다른 머신의 Codex CLI에 수동으로 붙여넣기」 방식으로 돌리고 있었으나, 인수인계서 (~/notes/_handover/ 하위의 상태 기록 markdown)가 하루 70건 이상에 달하기 시작하면서 수동으로 전부 개입하기 어려워졌습니다 -
send <thread> "..." --wait <sec>를 통해 **「turn 완료까지 대기 (= 인간 리뷰 시점)」**와 --wait 없이 연타하기 (= 손을 떼고 자율 주행)를 동일한 송신 측면에서 전환할 수 있습니다. 9일간의 인수인계서 추이는 5 → 89 → 12 → 7 → 21 → 76 (수동 한계) → 40 (도구 도입) → 86 (안정 운용) → 16건입니다. 6~7일째에 수동 운용이 한계에 도달했고, 8일째에 도구를 경유하여 동등하거나 그 이상의 스루풋 (throughput)을 확보했습니다. graphics 영역만은 총괄 역할의 배하에 두면 보고도 지시도 양방향으로 전달되지 않는 것처럼 보이는 상태가 빈번하게 발생하여, 세션 재구축이나 task 분할 대책으로도 해결되지 않았기에, 독립적인 autonomous 상태로 실행시키고 「보고와 확인 질문」만을 총괄 역할과 연결하는 구조로 만들었습니다. 본 기사는 app-server API의 망라적 해설이 아니라, AI 세션 운용에서 「동기점 (synchronization point)」을 어떻게 만드는가에 대한 이야기입니다.
0. 동기 — 왜 만들었는가
Anemora 제작을 시작한 지 약 1주일 만에, commit이 150건 이상, 인수인계서 (~/notes/_handover/ 하위에 두는 AI 세션 간의 상태 기록)가 300건 이상, worker로서의 AI 세션은 날마다 3~5개 병렬로 늘어나 있었습니다. 당초에는 중앙 총괄 역할을 하는 AI 세션이 prompt를 내뱉으면 → 제가 화면에서 내용을 확인 → 다른 머신 (Windows)의 Codex CLI에 수동으로 전달하는 방식으로 운용하고 있었습니다. 수동으로 개입하기 때문에 내용을 매번 확인할 수 있다는 장점이 있지만, 규모가 커지면 다음과 같은 불편함이 쌓여갑니다.
- 인수인계서의 수가 하루 70건을 넘어가면, 전부 수동으로 개입하는 것은 시간적으로 어렵다.
- 한편으로, 백그라운드에서 자율 주행 중인 worker의 상황을 이쪽에서 살피러 가고 싶거나, 직접 세부적인 지시를 넣고 싶은 상황이 상당한 빈도로 발생한다.
「전부 자동」과 「전부 수동」 어느 한쪽으로 치우쳐도 제대로 돌아가지 않기에, 자동 ↔ 수동을 가볍게 오가고 싶다는 요구가 있었습니다. 두 가지를 모두 그대로 하나의 도구로 전환할 수 있다면 현재의 운용을 그대로 확장할 수 있을 것이라는 점이 본 도구를 만든 동기입니다. 이 다음부터는 그것을 어떻게 만들었는지, 무엇이 가능하고 무엇이 불가능한지, 현재의 운용 구조는 어떻게 되어 있는지에 대한 이야기입니다.
1. Codex CLI의 app-server로 「열려 있는 Codex에 외부에서 프롬프트를 흘려보낼 수 있음」
Codex CLI에는 app-server라는 모드가 있습니다. 이것은 OpenAI 공식 문서에서는 VS Code 확장과 같은 외부 클라이언트로부터 Codex를 구동하기 위한 WebSocket + JSON-RPC 엔드포인트로 소개되고 있으며, 일반적인 TUI 조작과는 별개의 루트로 Codex를 제어할 수 있습니다.
용어 미니 정의
| 용어 | 의미 |
|---|---|
| app-server | Codex CLI가 구축하는 WebSocket 서버. codex app-server --listen ws://127.0.0.1:<port>로 기동 |
| thread | 하나의 대화 세션을 가리키는 ID (UUID 형태), 019e0ed6-5ca3-...와 같은 문자열 |
| turn | 1 턴 (= 사용자 발화 1회 + 어시스턴트 응답 1회) 단위 |
| JSON-RPC | 요청(Request) / 응답(Response)을 JSON으로 주고받는 경량 프로토콜 |
| 인수인계서 / handover | Anemora 프로젝트 고유의 운용 방식으로, AI 세션의 한 단락이 끝날 때마다 기록해 두는 상태 Markdown 파일 |
| orchestration / worker | 중앙의 총괄 역할과 그 휘하에서 개별 태스크(Task)를 수행하는 역할 (후술) |
通常 (통상) / resume / app-server send 의 3가지 패턴
A. 通常 (통상) codex
B. codex resume
C. app-server + 외부 send
포인트는 app-server를 경유하는 패턴으로, **「app-server를 경유하면, 사용자가 TUI로 대화 중인 현재 세션에 대해 외부에서 JSON-RPC로 turn을 발생시킬 수 있다」**는 점입니다. 이는 다른 두 패턴에는 없는 성질입니다. 대략적으로 말하자면, 화면을 열어둔 채로 별도의 프로세스에서 「Enter를 누른 것과 유사한 경험」을 외부에서 만들어낼 수 있는 형태에 가깝습니다.
2. 자체 도구의 설계
구현한 PowerShell 스크립트는 파일 1개 / 약 470행으로 구성되어 있으며, 다음과 같은 7개의 서브커맨드(Subcommand)를 가집니다.
| 서브커맨드 | 용도 |
|---|---|
new | 한 번에 app-server 하위에 신규 세션을 생성 |
start <thread-id> | 기존 thread에 대해 app-server를 구축하고, Windows Terminal의 새 탭에서 TUI에 접속 |
send <thread-id> <message> [--wait <sec>] | 기존 세션에 JSON-RPC로 turn을 전송. --wait 옵션 사용 시 turn 완료까지 대기 |
list | 관리 중인 세션 목록 |
status <thread-id> | 1개 세션의 상태 (프로세스 생존 여부 / 포트 리스닝(listening) 상태)를 확인 |
stop <thread-id> | app-server 프로세스를 정지하고, token / launcher를 정리 |
init-check <thread-id> | app-server에 WebSocket으로 접속하여 thread의 로드 상태를 반환 |
내부 설계를 대략적으로 나열하면 (운용상의 중심은 start / send / list / status):
- 상태의 영속화 (Persistence):
~\.local\state\<tool>\sessions.json에thread_id ↔ port ↔ pid ↔ launcher 경로 ↔ resume 인자를 저장. (이유: 프로세스를 넘나들며list/status/send를 성립시키기 위함) - 인증 (Authentication): capability-token 방식으로 32 byte CSRNG 기반의 토큰을
--ws-token-file을 통해 Codex CLI에 전달하고, WebSocket 접속 시Authorization: Bearer <token>헤더로 전송. (이유: 루프백(Loopback) 접속 한정 + 세션별로 별도 토큰을 사용하여 분리) - 빈 포트 (Free Port):
[System.Net.Sockets.TcpListener]::new([IPAddress]::Loopback, 0).LocalEndpoint.Port를 통해 동적 획득. (이유: 여러 세션이 고정 포트 충돌 없이 병렬로 구동될 수 있도록 함) - launcher 자동 생성: 각 세션 전용의
launch-<thread>.ps1을 생성하여, TUI 측에서codex resume --remote ws://... --remote-auth-token-env CODEX_REMOTE_TOKEN <thread>를 실행하도록 함
실행하도록 하고 있다. (이유: 임의의 셸에서 동일한 연결을 재현 가능하게 하기 위해) -
TUI 탭 자동 오픈: wt.exe -w 0 new-tab --title "codex:<thread_id_앞8자리>" powershell.exe ... -File <launcher>
를 통해 Windows Terminal에 직접 탭을 연다. (이유: start <thread-id> 한 번으로 「app-server + 연결 + 조작 화면」이 모두 갖춰지도록 하기 위해)
이 중 눈에 띄는 점은 launcher의 자동 생성과 Windows Terminal 탭 실행이며, 이를 통해 start <thread-id>를 입력하는 것만으로 app-server가 백그라운드에서 실행되고, 새로운 Terminal 탭에 TUI가 열리며, 외부에서 send를 던질 수 있는 상태가 한 번에 준비되는 경험을 제공한다.
3. 세 가지 이점 — 「동기 설계 (Synchronous Design)」란 무엇인가
제목에 「동기 설계」라고 적었지만, 이는 기술 용어인 동기 (Sync I/O 등)가 아니라, **「AI와의 상호작용에서 '어디서 잠시 멈춰서 확인할 것인가'를 도구 측에서 선택할 수 있도록 하는 것」**을 의미한다. send <thread> "..." --wait 300이라면 「보내고, 완료될 때까지 기다린 후, 내용을 확인하고 다음으로 진행」, --wait가 없다면 「보내고 즉시 다음으로 진행」한다. 동일한 하나의 명령어로 두 가지 흐름을 모두 구성할 수 있다는 점을 본 기사에서는 「동기 설계」라고 부르고 있다.
세 가지 이점을 차례대로 설명한다.
① 라우팅 (Routing)의 자유도
중앙 관리 역할을 하는 세션에서 그 하위의 worker 세션으로 보내는 방식으로 구성할 수도 있고, 사용자가 직접 특정 worker 세션으로 보내는 방식으로 구성할 수도 있다. 동일한 도구가 두 가지를 모두 커버하므로, 용도에 따라 경로를 바꾸는 것만으로 운용을 그대로 이어갈 수 있다.
② 대화 상태를 유지한 채 연결 가능
이것이 「단순히 프롬프트를 다른 세션에 전달하는 것」뿐인 도구와의 가장 큰 차이점이다. app-server에 연결되어 있는 한, TUI에서 보고 있는 것과 동일한 thread에 외부에서 turn을 흘려보내기 때문에, 과거의 상호작용도, 현재의 대화 상태도 TUI를 열면 그대로 확인할 수 있다. Codex 측에서 보면, 사용자가 직접 입력한 turn과 외부에서 send로 보내진 turn은 동일한 thread의 연속된 turn으로 취급된다.
③ 「리뷰 겸용」과 「방치형 연타」를 전환 가능
이것이 일상적인 운용에서 가장 효과적이다.
# 리뷰를 겸하고 싶을 때 (사람이 동기 지점을 삽입)
send <thread> "다음 task ..." --wait 300
# 방치하여 자율 주행시키고 싶을 때 (보내고 즉시 다음으로 진행)
...
전자의 --wait 300은, 보낸 turn이 turn/completed 이벤트를 받을 때까지 CLI 측이 블록 (Block)한다 (최대 300초). 기다리는 동안 대응하는 TUI 탭을 열면 실행 중인 흐름도 볼 수 있으므로, 완료된 타이밍에 결과물을 확인한 뒤 다음 지시를 구성하는 운용이 가능하다. 후자는 보내면 즉시 return하므로, 여러 worker에 백로그 (Backlog)를 밀어 넣고 사용자는 다른 판단에 시간을 쓰는 운용이 가능하다.
지금까지 수동으로 개입하던 장면에서 얻을 수 있었던 「내용을 읽으면서 확인하는」 경험은 --wait가 포함된 send로 동일하게 구현할 수 있다. 복사/붙여넣기로 담보했던 「사람이 중간에 개입하는 지점」을 명시적인 플래그로 재현할 수 있는 형태로 만들었다.
실제 운용 사례
실제로 동작하고 있는 사례 두 가지.
예시 1: 중앙 관리자 → worker로의 task 할당
중앙 관리자 세션에서 각 worker에게 codexctl send <thread> "..."로 task를 할당한다. worker는 받은 turn을 처리하고, 완료 후에 결과물 경로와 보고용 markdown을 중앙 관리자에게 보낸다. 이것이 일상 운용의 주류이다 (상세한 구조는 §4에서 다룬다).
예시 2: 독립 목적의 세션 ↔ 중앙 관리자의 대화
주된 task 루프에서 벗어난 독립 목적의 세션을 세우는 케이스가 있다. 예를 들어 **AI 관련 도구의 채택 검토 세션 (3D 모델 생성 도구 비교, 메카 구현 방식 선정 등)**이나, 개발 지원용 도구를 만드는 세션 등이 있다.
이러한 세션에 "구현 중, 사양으로 고민된다면 그때마다 orchestration에 확인해 주세요"라고 전달해 두면, 세션 측에서 codexctl send <orchestration> "..." --wait 300을 던지고, orchestration이 답변 turn을 반환하며, 이를 받아들여 작업을 계속하는 대화 진행이 성립됩니다. codexctl의 송신 루트를 사용하면, 누구로부터든, 누구에게든 동일한 방법으로 통할 수 있습니다.
4. 현행 운용 구조
현재의 운용 구조는 대략 다음과 같은 형태입니다.
역할의 내용:
- orchestration: 전체를 총괄하는 AI 세션. 각 worker에게 지시 분배, 보고 수신, 정리 수행
- impl: Unity의 C# 구현 및 Scene 배선
- runtime: 실행 시의 동작 (Editor Play / Standalone 실행) 검증
- character: 도트 그래픽 캐릭터 생성 파이프라인 (밑그림 생성 → 마무리 → import)
- search: 새로운 AI 도구 및 구현 기법의 사전 조사 (예: 3D 모델 생성 도구, 게임 메카닉 구현 패턴 검토)
- graphics: 그래픽 개선을 위한 자율 세션. 왜 독립적으로 두었는지는 §5에서 설명
나 (사용자 측)는 orchestration에는 일상적인 task 지시를 내리고, graphics에 대해서는 나만이 명령을 내리는 구조입니다.
5. graphics만 독립시킨 이유
graphics도 처음에는 orchestration (총괄 역할)의 배하(配下)에 두는 형태로 운용했습니다. 그런데, 배하에 두면 보고나 지시가 양방향으로 전달되지 않는 것처럼 보이는 상태가 빈번하게 발생했고, 보고 타이밍에 작업이 멈추는 현상이 다발했습니다.
관측된 증상을 나열하면 다음과 같습니다:
- worker (graphics) → orchestration으로의 보고가 orchestration 측에 도달하지 않는 것처럼 보임
- orchestration → worker (graphics)로의 지시도 마찬가지로 도달하지 않는 것처럼 보임
- 보고 타이밍에 worker가 대기 상태로 들어가, 그대로 작업이 멈춤
원인은 현재까지 밝혀지지 않았습니다. 동일한 메커니즘을 사용하는 다른 worker (impl / runtime / character / search)에서는 유사한 정지가 일어나지 않으므로, graphics 특유의 무언가 (= 출력 사이즈? turn의 길이? 기타?)에 걸려 있는 듯하지만 특정하지는 못했습니다. 어디까지나 현상으로서 그렇게 보이는 단계이며, 원인을 단정 짓지는 않았습니다.
배하에 둔 채로 어떻게든 돌릴 수 없을까 고민하며, 세션을 재구축하거나, task 사이즈를 작게 쪼개거나, 보고 빈도를 낮추는 등의 시도도 했습니다. 하지만 어떤 대책도 근본적으로는 해결되지 않았고, 재구축 직후에는 잠시 작동하다가 곧바로 같은 증상이 재발하는 패턴이 많았습니다.
최종적으로 graphics는 orchestration의 배하에서 제외하여 독립적인 autonomous(자율) 구조로 실행하도록 전환했습니다. 구체적으로는:
- 명령은 나 (사용자)로부터만 내린다. 그래픽 개선 테마나 방침은 내가 직접 graphics 세션에 전달한다.
- orchestration → graphics로의 지시는 내보내지 않는다. 다른 worker로부터의 간섭도 들어오지 않는다.
- graphics → orchestration으로의 보고는 일방적으로 보낸다. 결과물의 위치나 진척도를 orchestration에 전달한다.
- 확인 사항이 있다면, 양방향으로 질문만은 한다. 단, '지시'가 아니라 '문의'로서 취급한다.
이렇게 하니 멈추지 않게 되었습니다.
부차적인 관찰로서, 배하에 포함시키지 않는 편이 자율 주행이 멈추기 어렵다는 운용 법칙 같은 것을 느끼고 있습니다. 다만 이는 graphics 단독의 사례이며, 아직 충분히 검증되지는 않았습니다. 향후 search나 character에서도 같은 경향이 나타날지는 계속해서 관찰 대상입니다.
6. 결과 수치 — 수동 개입 운용이 한계에 달한 날과, 동기 설계가 효과를 발휘한 날
일별 인수인계(handover) 건수의 추이는 다음과 같습니다 (프로젝트 시작일을 1일째로 간주). 또한, 여기서 「handover 1건」은 task 완료 / 보고 / 판단 대기 등을 포함하므로, 엄밀한 성과량이 아니라 **「인간이 개입하는 기회의 근사치」**로 읽어주시기 바랍니다.
| 일 | handover (건) | 발생한 상황 |
|---|---|---|
| 1일째 | 5 | 컨셉 논의 (취합 역할과 사용자의 대화 중심) |
| ... | 6일째 | 76 |
| 7일째 | 40 | 도구 도입. 중간까지는 수동으로 개입, 중간부터 send를 경유하는 방식으로 전환 |
| 8일째 | 86 | 안정 운용. 2일째 피크에 필적하는 양을, 방치(hands-off) + 확인을 병용하여 처리 |
| 9일째 | 16 | (본 기사 집필 시점에서 진행 중) |
2일째(89건)와 8일째(86건)는 모두 80건을 초과하지만, 2일째는 「5 병렬 + 사용자가 화면에 붙어 있음」으로 달성한 양이고, 8일째는 「send로 배분하고, --wait로 확인을 끼워 넣으며」 달성한 양입니다. 6일째의 76건에서 수동 개입 운용의 한계가 보였고, 7일째에 도구를 도입하여 8일째에 동등하거나 그 이상의 양을 처리하게 된 흐름이 되었습니다.
2일째의 89건은 「사용자가 화면에 붙어 있었던 날」이기도 하여 재현 가능성이 낮습니다. 반면 8일째의 86건은 「사용자가 다른 판단에 시간을 쓰면서도 달성한 양」이라는 차이가 있습니다.
7. 남은 과제
현 시점에서 남아 있는 과제:
- graphics 하위 정지 원인 불명: §5에서 언급한 바와 같이, 왜 graphics 영역만 하위에서 멈추는지 알 수 없다. orchestration ⇔ worker 간의 특정 패턴 (= 지시의 길이? 보고 빈도? 병렬 수?)에서 발생하는 것으로 보이지만, 특정하지 못했다.
- Codex CLI의 버전 의존성: app-server의 JSON-RPC 사양은 공식 docs와 GitHub repo에 기재되어 있으나, Method 명이나 이벤트 명은 내부 API에 가까워 CLI 버전 업데이트에 따라 변경될 가능성이 있다 (집필 시점에는 Codex CLI
0.130.0에서 동작 확인). - 에러 리커버리 (Error Recovery): 현재의
send실패 시 동작은 다소 거칠며, token 만료나 app-server 크래시(crash)로부터의 자동 복구는 구현하지 않았다 (= 수동으로stop→start수행). - 자주(自走) 중의 품질 게이트 (Quality Gate):
--wait없이 연타하는 자주 운용에서는 AI가 내놓는 결과물의 품질 보증이 orchestration 측에 집중된다. orchestration 세션 측의 판정 능력이 사실상의 병목(bottleneck)이 되고 있으며, 이 부분은 별개의 논점으로 남아 있다.
마치며
여기까지가 개인 게임 개발에서 AI agent CLI를 여러 개 병렬로 구동하는 형태를 약 1주일간 지속하며 보인 기록입니다. 전반부는 수동으로 개입하는 경로로, 후반부는 app-server를 경유하는 send로 전환한 형태로, 운용 측면에서는 하나의 연속된 1주일이었습니다. 도구 본체 자체는 몇 시간 만에 완성되었지만, 하위 구조를 정비하거나 graphics 영역만 독립적으로 분리하는 등의 운용 측 조정은 며칠에 걸쳐 조금씩 다듬어 나갔습니다. 비슷한 규모로 AI agent를 병렬 운용하고 계신 분들이나, 이제 시작하려는 분들께 참고가 되기를 바랍니다.
참고
- Anemora 소개 기사 (선행 기사): Anemora — HD-2D 탐색 ADV를 AI 협업으로 개인 개발하기
- Anemora 제작자 관점의 회고: AI가 손을 움직이는 개인 게임 개발에서, 인간이 놓지 않았던 3가지 업무
- OpenAI Codex 공식: App Server / Remote connections / codex-rs/app-server (GitHub)
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기