OpenAI Codex · Code Mode

호스트 분리: 프로그램은 누구 몸에 걸려 있나

지난 레슨은 exec와 wait의 의미였습니다. 이번엔 다른 질문입니다. 이 JavaScript는 누구 몸에 걸려 있고, 걸림점이 바뀌면 장애 영역과 상태의 주인은 어떻게 달라질까요.

강의 목표읽고 나면 세 가지를 말할 수 있어요. 첫째, 기본값은 이미 독립 호스트 프로세스이고 메인 프로세스는 세션 제공자만 쥐고 있습니다. 둘째, isolate는 이사할 수 있지만 중첩 도구의 승인은 이 기기에 남습니다. 셋째, 호스트가 죽거나 끊기면 돌고 있던 cell과 store가 같이 사라지고, 재연결은 다시 exec할 수만 있지 방금 그 스크립트를 이어 달릴 수는 없습니다.
먼저 해보기 · 같은 JS, 네 가지 호스트
같은 스크립트: store, 중첩 명령, 그리고 wait
호스트
왼쪽은 DSH의 프로세스 안 worker로 고정입니다. 오른쪽은 Codex 제공자를 바꿔 가며 격리 경계, 수명, 그리고 죽어도 누가 살아 있는지 보세요.
인터럽트가 cell을 죽임
기본은 끔입니다. 꺼져 있으면 Ctrl-C는 이번 턴만 취소하고, 호스트 위 스크립트는 계속 돌 수 있어요.
DSH · 프로세스 안 worker대조, 스위치에 안 움직임
메인 프로세스 건물
worker 작은 방이 비어 있음
격리같은 프로세스, 다른 스레드
수명run 한 번에 worker 하나
시작을 기다리는 중.
Codex · 로컬 자식 프로세스ProcessOwned
Codex 메인 프로세스
제공자를 쥐고 있음
stdio
호스트 프로세스
isolate가 아직 안 뜸
도구 콜백이 아직 안 나감
격리OS 프로세스
cell / store아직 없음
시작을 기다리는 중.
논리 궤적 · 애니메이션 각 단계가 소스의 어느 구간에 대응하는지
  1. thread manager가 기능으로 제공자를 고름thread_manager.rs L455
  2. 로컬 제공자가 호스트 파일이 있는지 확인remote_session.rs L70
  3. 호스트를 spawn, Unix에서는 별도 프로세스 그룹connection.rs L217
  4. 핸드셰이크 후 session/open, 호스트가 프로세스 안 세션을 newlib.rs L602
  5. 중첩 도구는 RemoteDelegate로 이 기기로 돌아옴delegate.rs L27
  6. app-server가 URL 스킴으로 WS 또는 gRPC로 바꿈code_mode_host.rs L32
  7. gRPC는 lease를 잃으면 세션을 닫음session.rs L105
  8. 재연결 후 cell ID에 세대 접두사generation.rs L49
  9. 인터럽트가 terminate할지는 기능 스위치tasks/mod.rs L888
  10. 호스트를 못 쓰면 도구 모드가 Direct로 후퇴tools/mod.rs L79
재생을 눌러, 같은 JS가 호스트를 바꾼 뒤 죽어도 누가 살아 있는지, 옛 cell이 아직 wait할 수 있는지 보세요.
격리 경계DSH의 프로그램과 메인 프로세스는 같은 건물에 삽니다. Codex는 기본으로 OS 프로세스를 한 겹 더 두고, 원격은 기계 한 대를 더 둘 수 있어요.
승인은 이 기기에평가는 이사했지만 중첩 exec_command는 여전히 session owner로 돌아옵니다. 호스트에는 승인 창도 execpolicy도 없어요.
실패한 뒤호스트가 죽거나 끊기면 cell과 store가 같이 사라집니다. 재연결은 다시 exec할 수 있지만 방금 그 스크립트를 이어 달릴 수는 없어요. gRPC 2세대는 cell 이름도 바꿉니다.
수업용 스케치: 네 호스트는 네 세션 제공자에 대응합니다. 프로세스 안 평가는 여전히 호스트 방 안에 그리고, 지금 프로덕션 배선은 그걸 메인 프로세스의 한 칸으로 두지 않습니다. 논리 궤적 오른쪽 행 번호는 openai/codex 저장소 commit 4f39251a01에 대응해요.
아이디어 1 · 평가를 메인 프로세스에서 떼어 내기
어떤 문제를 푸는가

모델이 while (true) {}를 씁니다. 지난 레슨에서 봤듯 isolate의 terminate로만 끊을 수 있어요. 이 프로그램이 Codex 이벤트 루프와 같은 프로세스를 다투면, 멈춤이 한 cell에서 세션 전체로 번집니다.

V8 힙, JIT, 항목 상한이 없는 store 표 — 셋 중 하나라도 메인 프로세스에서 터지면 TUI나 app-server를 같이 데려갑니다. 초기 자료는 기본 경로를 “메인 프로세스에서 isolate를 바로 돌린다”고 적곤 했어요. 지금 기능 주석은 그 문장을 이미 고쳤습니다.

출처:codex-rs/features/src/lib.rs 104–111행

아이디어는 무엇인가

Codex는 “모델이 쓴 코드를 돌린다”에 제공자 인터페이스를 남겨 둡니다. 비즈니스 층은 CodeModeSessionCodeModeSessionProvider만 의존하고, 런타임을 스스로 new하지 않아요. 프로토콜은 세션을 네 가지로 접습니다. execute, wait, terminate, shutdown. 구현은 프로세스 안일 수도, 원격일 수도 있어요. 같은 세션은 store를 공유하고, 다른 세션은 격리됩니다.

출처:codex-rs/code-mode-protocol/src/session.rs 146–167행

메인 프로세스가 제공자를 고를 때, InProcessCodeModeSession을 바로 new하는 프로덕션 경로는 이미 없습니다. CodeModeHost가 켜지거나 disable_in_process_fallback이 참이면 ProcessOwnedCodeModeSessionProvider로 갑니다. 둘 다 아니면 DisabledCodeModeSessionProvider로 가요.

출처:codex-rs/core/src/thread_manager.rs 455–462행

CodeModeHost는 이미 Stable이고 기본으로 켜져 있습니다. 사용자가 보는 기본값은 이미 로컬 자식 프로세스예요. 프로세스 안 평가는 그대로지만 호스트 프로세스 안으로 가라앉았습니다. 호스트가 세션을 열 때 new하는 것이 InProcessCodeModeSession입니다. isolate는 작은 방에 살고, 집주인은 독립 바이너리 codex-code-mode-host로 바뀌었어요.

출처:codex-rs/features/src/lib.rs 921–925행 · codex-rs/code-mode-host/src/lib.rs 599–608행

Codex thread 메인 프로세스 세션 제공자 인터페이스만 쥠 codex-code-mode-host 독립 OS 프로세스 InProcess 세션 V8 isolate가 이 방에 중첩 도구 콜백은 집으로, 승인은 메인 프로세스에 입력은 JS 한 줄. 일어나는 일은 프로세스 너머 평가. 출력은 cell 번호와 직접 내준 텍스트.
수업용 구조도: 기본 경로에서 V8은 호스트 프로세스에 떨어지고, 메인 프로세스는 제공자만 쥡니다.

프로세스를 띄울 때 stdin / stdout / stderr는 전부 파이프이고, Unix에서는 별도 프로세스 그룹이며, 환경 변수는 먼저 scrub합니다. 실행 파일을 못 찾으면 availability()가 바로 실패하고, 메인 프로세스에서 isolate를 new하도록 바꾸지 않아요. 진짜 후퇴는 도구 모드 층에서 일어납니다. 호스트를 못 쓰고, 요청이 보통 CodeMode이며, 후퇴를 끄지 않았을 때 유효 도구 모드가 Direct가 됩니다. 모델은 다시 보통 도구를 봐요.

출처:codex-rs/code-mode/src/remote_session.rs 69–83행 · codex-rs/core/src/tools/mod.rs 79–89행

disable_in_process_fallback이라는 이름은 “프로세스 안 V8로 후퇴”가 아직 있다고 착각하기 쉽습니다. 설정 주석은 다른 말을 해요. 호스트를 못 쓰면 Code Mode를 문을 닫고 실패시키라는 것입니다. 오늘은 “호스트가 사라진 뒤 Code Mode에서 보통 도구로 후퇴할지”를 제어합니다.

출처:codex-rs/core/src/config/mod.rs 1089–1096행

왜 오래가는가

신뢰할 수 없는 언어 런타임과 agent 메인 프로세스를 같은 운명에 두지 마세요. Python subprocess로 바꿔도, 다른 isolate로 바꿔도 물을 말은 같습니다. 이 코드가 죽으면, 누가 살아 있나.

아이디어 2 · 도구 콜백은 집으로 돌아와야 한다
어떤 문제를 푸는가

app-server를 원격 기계의 --code-mode-host로 가리키면, agent 전체가 이사했고 exec_command 승인 창도 원격에 떠야 한다고 생각하기 쉽습니다. 지금 소스는 맞지 않아요. 원격 호스트는 평가만 가져갔습니다. 중첩 도구의 승인, execpolicy, Guardian은 이 기기 세션에 남습니다.

아이디어는 무엇인가

호스트는 CodeModeSessionDelegateRemoteDelegate로 만들어 IPC로 Codex 메인 프로세스에 돌려보냅니다. 메인 프로세스의 dispatch broker가 중첩 도구를 걷습니다. isolate는 이사했지만 정책은 이사하지 않았어요. JS는 다른 곳에서 돌고, 부작용은 돌아와서 당신에게 물어야 합니다.

출처:codex-rs/code-mode-host/src/delegate.rs 26–50행

WebSocket과 gRPC는 같은 평가, 두 갈래 선입니다. WebSocket 리스너는 Origin 헤더가 있는 요청을 거절해, 브라우저 페이지가 이 기기 호스트에 교차 출처로 붙는 것을 막아요. gRPC는 도구 구독, 완료, 실행 스트림을 나누고, OpenSession 리스 스트림을 잃으면 세션이 닫히며 돌고 있던 cell도 같이 끝납니다. app-server의 --code-mode-hosthttp / https를 gRPC로, ws / wss를 WebSocket으로 봅니다. 여러 thread가 같은 원격 연결을 공유하고, store는 여전히 세션으로 자릅니다.

출처:codex-rs/code-mode-host/src/transport.rs 288–302행 · codex-rs/app-server/src/code_mode_host.rs 32–40행

이 기기 · session owner 승인 창 execpolicy Guardian 정책은 안 이사함 호스트 · 평가만 V8 isolate + store 승인 UI 없음 exec invoke_tool이 집으로
수업용 대조: 이사한 것은 신뢰할 수 없는 JS 세계이고, 남은 것은 UI가 있는 정책 세계입니다.
평가는 이사할 수 있고, 정책은 이 기기에 남습니다.
왜 오래가는가

신뢰할 수 없는 것은 JS 세계이고, 믿을 수 있는 것은 승인과 정책입니다. 전자를 이사시키고 후자는 UI가 있는 쪽에 두세요. 이 회로가 없으면 원격 호스트가 권한 체계 전체를 복제해야 합니다.

아이디어 3 · 끊김은 isolate와 store를 잃는 것과 같다
어떤 문제를 푸는가

흔한 기대는 이렇습니다. 인터럽트를 누르면 V8도 같이 꺼지고, 다음 wait가 바로 종료를 본다는 것. 지금 기본값은 맞지 않아요. 다른 기대는, 재연결 뒤에도 방금 그 스크립트가 같은 cell에서 이어 돈다는 것입니다. 둘 다 소스는 인정하지 않습니다.

아이디어는 무엇인가

turn이 Interrupted로 찍히면 작업 취소 토큰은 반드시 취소됩니다. 돌고 있는 cell을 terminate할지는 CodeModeInterrupt를 봐요. 이 스위치는 아직 개발 중이고 기본은 끔입니다. 꺼져 있으면 인터럽트는 이번 턴의 도구 호출과 승인만 취소해요. 호스트의 isolate는 스스로 끝나거나 wait(terminate: true)에 멈추거나 세션이 shutdown될 때까지 돌 수 있습니다. 사용자가 Ctrl-C를 누른다고 그 terminate가 되는 것은 아니에요.

출처:codex-rs/core/src/tasks/mod.rs 888–899행

끊김도 같습니다. gRPC는 lease를 잃으면 세션을 닫아요. 클라이언트는 리스를 하나 더 열 수 있고 generation은 1부터 올라갑니다. 1세대는 바깥에 원래 cell ID를 쓰고, 2세대는 g{generation}:{cell_id}가 됩니다. 모델이 1세대 번호로 wait하면 stale generation을 받아요. 로컬 자식 프로세스 경로에는 이 접두사가 없고, 상태 기계를 New로 되돌린 뒤 새 session-N을 줍니다. 바깥 cell ID는 여전히 1부터예요. 두 재연결 모두 돌고 있던 cell과 그 store를 잃습니다.

출처:codex-rs/code-mode/src/grpc_session/generation.rs 49–67행

로컬 자식 프로세스 / WebSocket Open 연결이 죽음 New로 돌아감 세션을 다시 열면 cell은 1부터 gRPC lease 1 스트림을 잃음 lease 2 바깥 ID는 g2:1, 옛 wait는 무효
수업용 상태도: 두 재연결 모두 옛 세계를 잃고, gRPC는 세대로 “이건 새 세계”를 호출자에게 보여 줍니다.

store 표는 호스트 쪽 SessionRuntime을 따릅니다. 디스크에 안 남고, 프로세스 너머 공유도 없고, TTL도 없어요. 원격 기계를 재시작하면 표가 사라집니다. 세션 ID 재사용은 거절되고, 옛 표를 몰래 다시 붙이지 않습니다. 재연결이 복구하는 것은 “다시 exec할 수 있다”이지 “방금 그 스크립트”가 아닙니다.

왜 오래가는가

cell의 수명은 세션 기준이지 turn 기준이 아닙니다. 턴을 끊는 것과 프로그램을 죽이는 것은 다른 일이에요. 재연결은 새 세계를 열고, 옛 신분증은 무효입니다. Agent를 직접 만들 때 사용자가 정지를 누르면 프로그램이 바로 죽기를 기대한다면 기본은 terminate여야 합니다. Codex가 기본으로 안 죽이는 이유는 cell을 턴을 넘어 이어 달릴 수 있는 대상으로 보기 때문이에요.

가로 비교 · 같은 문제에 대한 다른 답

토폴로지: DeepSeek Harness는 같은 건물을 고릅니다

DSH는 프로그램을 프로세스 안 worker_threads.Worker에 넣습니다. crate 머리 주석 첫 문장이 입장을 못 박아요. 이것은 containment이지 security boundary가 아닙니다. 모델 코드는 bash와 동등하게 다룹니다. run()마다 새 Worker를 띄우고, 환경은 비어 있으며, 힙에는 상한이 있어요. 프로그램 세계는 worker와 같이 죽고, run을 넘는 상태는 없습니다.

worker 하나가 죽어도 메인 프로세스 방은 남아 있지만, V8 버그나 네이티브 충돌은 여전히 Node 프로세스 전체를 데려갈 수 있어요. Codex의 isolate는 이미 Node worker보다 좁습니다. fs도, net도, import도 없어요. 그래도 “좁은 isolate와 메인 프로세스가 같은 운명”을 믿지 않습니다. 기본으로 OS 프로세스를 한 겹 더 둡니다. 대가는 패키지와 같이 배포해야 하는 codex-code-mode-host가 하나 더 생기고, 세션 ID, 세대, 끊김 의미가 더해지는 것입니다.

양쪽 모두 소스 대조 완료 · 2026-08-22 · 출처:packages/code-runtime/code-runtime-worker-thread/src/index.ts 1–6행 · README.md 23행 · DSH · Code Mode

대응물: Claude Code와 Grok는 이 일을 shell에 맡깁니다

양쪽 모두 “모델이 프로그램을 써서 독립 런타임에서 도구를 오케스트레이션한다”의 대응 구현이 없습니다. Claude Code의 isolation은 git worktree와 원격 CCR 세션에 나타나고, Grok의 isolation은 자식 agent의 worktree에 나타나요. 그건 작업 공간 격리이지 JS 호스트 분리가 아닙니다. 대응물이 없다는 것 자체가 결론입니다. 두 집은 모델이 쓴 코드를 돌리는 일을 보통 shell 도구에 맡깁니다.

저장소에는 exec-server도 있습니다. 가져간 것은 shell, PTY, 파일시스템 RPC이지 JavaScript가 아니에요. 중첩 tools.exec_command는 여전히 들어갈 수 있고, 그건 다음 층의 실행 분리입니다. 두 길을 같은 원격으로 접지 마세요.

대응 구현을 찾지 못함 · 2026-08-22 · 출처:codex-rs/exec-server/README.md 1–5행
수업 실습
01

옛 cell은 아직 wait할 수 있나

같은 스크립트가 gRPC 호스트에서 절반쯤 돌다 연결이 끊겼다 다시 붙습니다. 모델이 원래 cell_idwait하면 무엇을 볼까요. 로컬 자식 프로세스 경로는 이 번호를 바꿀까요. 두 경로의 store는 아직 있나요.

그다음 CodeModeInterrupt를 끄고, 인터럽트를 누른 뒤 다시 wait해 보세요. 답이 바뀔까요. 사용자가 정지를 누른다고 자동으로 terminate가 아닌 이유는 무엇일까요.

Takeaway:기본 토폴로지에서 V8은 이미 메인 프로세스에 없습니다. 원격은 평가만 가져가고, 승인은 이 기기로 돌아와요. 호스트가 죽거나 끊기면 cell과 store가 같이 사라집니다. Ctrl-C는 기본으로 돌고 있는 cell을 terminate하지 않습니다.