OpenAI Codex · 통합 실행

통합 입구: 명령 하나가 특징에 따라 갈라져요

모델은 exec_commandwrite_stdin만 봐요. 문을 지나면 tty, 원격 환경, 150밀리초 창이 같은 명령을 PTY, pipe, exec-server, 또는 재시도 문으로 보냅니다.

강의 목표읽고 나면 두 가지를 말할 수 있어요. 명령 하나가 통합 입구에 들어가면 tty, 원격 환경, 아직 살아 있는지로 PTY, pipe, exec-server로 갑니다. 샌드박스 거절은 프로세스가 빨리 끝날 때만 정책대로 한 번 더 돌릴 수 있어요.
먼저 해보기 · 문을 지난 뒤 어느 길로 가나
같은 명령: 먼저 띄우는 길을 가르고, 150밀리초 창을 봐요
명령
일찍 죽어 거절, PTY 세션, 늦게 죽어 거절, 원격 백엔드 — 네 길의 갈림이 달라요.
정책
이 둘은 일찍 죽은 뒤의 재시도 문에만 닿아요. 창을 넘겨 산 명령은 여기까지 못 옵니다.
통합 입구 · exec_command명령을 기다려요
PTYtty가 참
pipe기본 로컬 경로
exec-server원격 또는 snapshot
150밀리초 창램프가 아직 안 켜짐
업그레이드escalate
정책맨몸으로 돌릴 수 있나
deny-read샌드박스를 벗길 수 있나
다시 묻기승인이 아직 유효한가
두 번째샌드박스 종류
시작을 기다려요.
논리 궤적 · 애니메이션 한 걸음이 소스 어디에 대응하는지
  1. command에 cwd를 더한 요청을 만듦mod.rs L12
  2. 오케스트레이터 승인: bypass, 캐시, 또는 팝업mod.rs L14
  3. 프로필로 샌드박스를 고르고 transformmod.rs L15
  4. tty와 원격 환경으로 PTY, pipe, exec-server로 가름spawn.rs L97
  5. 150밀리초 안에 끝나면 샌드박스 거절을 검사process.rs L349
  6. 휴리스틱 또는 실행기 깃발이 서면 Denied로 표시process.rs L307
  7. 오케스트레이터가 escalate와 정책 문을 지남orchestrator.rs L356
  8. UnlessTrusted이고 이미 승인됐으면 다시 묻지 않음orchestrator.rs L411
  9. unsandboxed가 허용되면 두 번째는 Noneorchestrator.rs L459
  10. 창을 넘겨 살면 입고 후 yieldprocess_manager.rs L535
  11. 늦은 거절은 Ok 회신으로 거둠exec_command.rs L384
  12. 사용자 Esc는 turn만 취소protocol.rs L546
재생을 눌러, 같은 명령이 문을 지난 뒤 어떻게 갈라지는지 보세요.
첫 갈림로컬은 tty로 PTY 또는 pipe를 타고, 원격은 exec-server로 가요. 모델이 보는 도구는 그대로예요.
둘째 갈림150밀리초 안에 끝나야 오케스트레이터가 샌드박스 거절을 볼 수 있어요. 창을 넘겨 살면 입고하고, 그다음에 망가져도 두 번째 spawn은 없습니다.
당신이 바꿀 수 있는 경계정책을 Never로 돌리거나 deny-read를 켜면, 본길에 있는 샌드박스 없는 재시도가 꺼져요.
수업용 시연: 종료 시간과 거절 문장은 수업용 설정이에요. 갈림 조건을 보여 주려고요. 논리 궤적 오른쪽 행 번호는 openai/codex 저장소 commit 4f39251a01에 맞춰 있어요.
아이디어 1 · 도구 둘, 띄우는 방법 셋
어떤 문제를 푸는가

모델이 의존성을 깔려고 npm install을 보내요. 같은 턴에 vim으로 README도 고칩니다. 실행 종류마다 승인과 샌드박스를 따로 쓰면 Guardian, 네트워크 프록시, 승인 캐시가 세 벌이 되어, 한 곳을 고치면 두 곳을 빠뜨려요.

아이디어는 무엇인가

밖에는 exec_commandwrite_stdin만 있어요. 앞은 프로세스를 열고, 뒤는 이미 있는 프로세스에 쓰며, 빈 쓰기는 poll 한 번입니다. 안에서는 process_id를 쫓고, 모델 쪽 인자 이름은 session_id예요. 매니저는 요청만 준비하고, 승인, 샌드박스 고르기, 재시도는 오케스트레이터에 맡깁니다.

출처:codex-rs/core/src/unified_exec/mod.rs 12–17행

로컬 띄우기는 스위치 둘로 셋 중 하나를 고릅니다. tty가 참이면 PTY. tty가 거짓이고 stdin이 열려 있으면 stdin을 살린 pipe. 아니면 stdin 없는 pipe예요. 원격 환경이나 shell snapshot이 있는 요청은 로컬 spawn을 건너뛰고 exec-server로 갑니다. Windows 제한 토큰은 백엔드가 따로예요.

출처:codex-rs/sandboxing/src/spawn.rs 97–127행

기본 도구 호출은 종종 pipe예요. 모듈 주석의 PTY 띄우기는 tty=true 갈래만 덮습니다. 터미널을 온전히 쓰려면 모델이 tty를 직접 켜야 해요. 환경 변수도 TERM=dumb, PAGER=cat 같은 값을 박아, 대화형 프로그램이 먼저 한 겹 깎입니다.

exec_command 통합 입구 tty가 참 PTY 로컬 기본 pipe 원격 또는 snapshot 쓸 수 있는 stdin, 모델이 키를 보낼 수 있음 보통 문자는 이미 닫힌 stdin에 부딪힘 exec-server 백엔드
수업용 구조도: 같은 입구, 특징으로 띄우는 방법 셋에 착지해요.
왜 오래가는가

정책 논리는 한곳에 모이고, 프로세스 모양은 바꿀 수 있어요. 다른 언어로 다시 써도 입구는 하나, 띄우는 방법은 특징으로 가릅니다. PTY를 어떻게 구현할지는 자기 저장소에 남겨도 돼요.

아이디어 2 · 스탠드는 150밀리초만 켜져요
어떤 문제를 푸는가

샌드박스가 /etc/hosts 쓰기를 거절하고, stderr는 Operation not permitted예요. 런타임이 이걸 명령을 잘못 쓴 것으로 보면 모델은 소스를 고치고, 경로를 바꾸고, sudo를 붙입니다. 알아봐야 정책대로 한 번 더 돌리거나, 거절 본문을 모델에 먹여 권한을 신청할 기회가 생겨요.

아이디어는 무엇인가

로컬 프로세스를 받은 뒤, 종료 채널에 이미 코드가 있거나, 채널이 닫혔거나, 150밀리초 안에 끝났을 때만 거절 검사를 해요. 이 창을 넘기면 백그라운드 작업 하나만 걸어 종료를 기다리고, 아직 살아있는 프로세스를 돌려줍니다. 오케스트레이터 재시도는 여기서 돌아오는 SandboxDenied에 기대요. 프로세스가 150밀리초를 넘기면 오케스트레이터는 이미 Ok를 받았고, 그다음 죽어도 두 번째 spawn에 못 들어갑니다.

출처:codex-rs/core/src/unified_exec/process.rs 38행 · 349–367행

exec-server 경로에도 같은 타임아웃이 있어요. 검사 함수 스스로 먼저 20밀리초를 기다려, 출력 알림이 올 틈을 줍니다. 판정에는 숏컷 셋이 더 있어요. 프로세스가 아직 안 끝났으면 그냥 통과. 이미 SandboxType::None이고 실행기가 거절을 안 알렸어도 통과. 나머지만 공유 휴리스틱을 돌려요.

출처:codex-rs/core/src/unified_exec/process.rs 290–324행

창을 넘겨 산 프로세스는 먼저 입고하고, 그다음 yield를 시작해요. turn을 끊을 때 마지막 Arc가 떨어졌다고 백그라운드 프로세스를 죽이면 안 됩니다. yield가 프로세스가 이미 끝난 뒤에야 끝나면, 매니저가 거절 검사를 한 번 더 돌려요. 이때 오케스트레이터는 이미 Ok를 돌려준 뒤입니다. 이 오류는 handler로 바로 돌아가, 본문이 있는 도구 회신이 되고 process_id는 비워요.

출처:codex-rs/core/src/unified_exec/process_manager.rs 535–556행 · codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs 384–407행

막 띄움 150밀리초 안에 종료 거절을 검사, 오케스트레이터가 봄 창을 넘겨 삶 먼저 입고, 오케스트레이터는 이미 Ok 문을 지나면 두 번째 spawn 문이 닫히면 거절 본문을 모델에 넘김 회신은 여전히 Ok 두 번째 spawn 없음
수업용 구조도: 램프가 켜져야 재시도가 가능하고, 꺼지면 입고해요.
왜 오래가는가

한 번짜리 명령은 끝날 때까지 기다렸다가 판정해도 돼요. 지속 프로세스는 마감이 있어야 합니다. 마감을 빼먹으면 몇 초 뒤에 실패한 명령을 거절로 보고 다시 맨몸으로 돌려요. 부작용은 이미 디스크에 쓰였고, 두 번째는 다른 프로세스이며, 소스에는 롤백이 없습니다.

아이디어 3 · 재시도에는 문이 있고, Esc는 프로세스를 안 죽여요
어떤 문제를 푸는가

모듈 머리에 적혀 있어요. 거절된 뒤 정책대로 SandboxType::None으로 다시 시도하고, 캐시에 기대 한 번 더 묻지 않는다고요. Never, OnRequest, Guardian strict, deny-read가 있는 프로필은 “다시 묻지 않기”나 샌드박스 없는 재시도를 꺼 버립니다. 사용자가 Esc를 눌러 vim까지 데려가면, 다음 턴은 이 세션을 못 찾아요.

출처:codex-rs/core/src/unified_exec/mod.rs 7–8행

아이디어는 무엇인가

오케스트레이터는 오류를 하나만 알아봐요. SandboxErr::Denied. 알아본 뒤 문 다섯을 지납니다. unified_exec는 스스로 업그레이드하겠다고 밝혀요. NeverOnRequest는 기본으로 샌드박스 없는 재시도를 하지 않고, 원문이 있는 거절을 호출 쪽에 드러냅니다. 프로필에 deny-read가 있으면 샌드박스를 우회할 때 그 거절 읽기가 조용히 통과되므로 unsandboxed를 꺼요. Guardian의 strict auto-review는 첫 승인을 샌드박스 안 시도에만 덮습니다. 두 번째가 unsandboxed를 허용하면 None에 착지해요.

출처:codex-rs/core/src/tools/runtimes/unified_exec.rs 159–161행 · codex-rs/core/src/tools/sandboxing.rs 330–337행 · 269–278행 · codex-rs/core/src/tools/orchestrator.rs 411–415행 · 444–460행

사용자가 TUI에서 Esc를 누르면 프로토콜 입구는 Interrupt예요. 지금 작업을 중단하고, 백그라운드 terminal은 안 죽입니다. 백그라운드를 전부 죽이려면 CleanBackgroundTerminals가 따로 있어요. 먼저 입고한 뒤 yield하면, turn 토큰이 취소돼도 프로세스 Arc는 남아 있습니다. pipe 세션은 보통 키를 못 받고, \u{3}만 interrupt로 가요.

출처:codex-rs/protocol/src/protocol.rs 546–552행

램프가 켜져야 재시도가 가능하고, 꺼지면 입고해요. Esc는 생각만 멈춰요.
왜 오래가는가

사용자가 고른 격리 수준을 런타임이 몰래 바꾸면 안 돼요. 생각을 멈추는 것과 터미널을 멈추는 것은 다른 일이에요. 취소는 기다림만 멈추고, 이미 등록된 프로세스는 안 죽입니다.

가로 비교 · 지속 터미널은 어느 층에 두나

DeepSeek Harness: 터미널 도구 여섯, 거기다 jobs

DSH는 지속 PTY를 독립 도구 족으로 만들어요. 열기, 쓰기, 읽기, 시그널, 닫기, 목록. 백그라운드 전송은 ctx.jobs를 재사용하고, 수집은 job_output, 중지는 job_kill입니다. 시스템 프롬프트에 적혀 있어요. 호출을 건너뛰는 터미널 상태나 대화형 stdin이 필요할 때만 터미널을 쓰고, 한 번짜리 일은 shell이나 읽기/쓰기 도구를 우선하라고.

Codex는 열기와 쓰기를 도구 둘로 접고, 읽기는 다음 write_stdin이나 빈 poll에 합쳐요. DSH에는 샌드박스 거절 뒤 오케스트레이터가 자동으로 샌드박스 없이 재시도하는 짝이 없습니다. 실패한 뒤 누가 한 번 더 돌리느냐, 양쪽 답이 달라요.

양쪽 모두 소스 대조 완료 · 2026-08-22 · DSH · 터미널 세션

Grok: 세션을 열 때 지속 여부를 고름

Grok에는 unified_exec 모듈이 없어요. 지속성을 세션 단위 백엔드 선택으로 둡니다. 부모 세션 재사용, ACP 클라이언트 터미널, 로컬 지속, 로컬 비지속. 백엔드를 고르면 그 모양으로 끝까지 달려요.

Codex는 같은 질문을 한 번의 exec_command에 올려요. 프로세스가 yield를 넘겨 살면 process_id를 돌려줍니다. Grok은 세션 전체가 백엔드 하나를 공유하고, 자식 에이전트는 부모 백엔드를 재사용해요. 양쪽 모두 한 번짜리 bash -c가 cwd와 대화 상태를 못 지킨다는 건 인정합니다. 착지점만 달라요.

양쪽 모두 소스 대조 완료 · 2026-08-22

출처:packages/terminal/tool-terminal/src/index.ts 156–160행 · crates/codegen/xai-grok-shell/src/session/acp_session_impl/spawn.rs 2048–2070행

수업 실습
01

어느 쪽이 두 번째 spawn을 하나

같은 npm install을 먼저 UnlessTrusted로 타고, 다시 Never로 돌려요. 그다음 명령을 sleep 2로 바꿉니다. 밀어 가며 보세요. 어느 쪽이 두 번째 spawn을 하는지, 어느 회신에 아직 process_id가 있는지, 램프가 꺼진 뒤 오케스트레이터가 거절을 못 보는 이유.

Takeaway:명령 하나가 통합 입구에 들어가면 tty와 원격 환경으로 PTY, pipe, exec-server로 갈라져요. 샌드박스 거절은 150밀리초 창 안에서 알아봐야 정책대로 다시 돌릴 수 있어요. 창을 넘겨 산 것은 먼저 입고합니다. Esc는 turn만 멈춰요.