에이전트 런타임이 무거워졌다: Codex App Server를 통한 샌드박스(Sandbox)의 재고
요약
AI 에이전트가 복잡한 작업을 수행함에 따라 런타임 환경의 중요성이 커지고 있습니다. 클라우드 샌드박스 대신 데스크톱 앱을 활용하여 사용자의 로컬 환경을 런타임으로 사용하는 방식의 이점과 Codex App Server 활용 방안을 제안합니다.
핵심 포인트
- 에이전트에게는 파일 시스템, 셸, 런타임 등 작업 공간(Sandbox)이 필수적임
- 단일 사용자 에이전트의 경우 클라우드보다 데스크톱 앱이 더 효율적인 런타임이 될 수 있음
- 로컬 실행은 인프라 비용 절감뿐 아니라 지연 시간 및 권한 설계 방식에 변화를 줌
- Codex App Server를 통해 기존의 강력한 에이전트 루프를 재사용 가능
AI 에이전트가 단순한 채팅창을 넘어 — 파일을 읽고 쓰고, 의존성(dependencies)을 설치하고, 테스트를 실행하고, 실패를 관찰하며 재시도하는 단계로 넘어가면 — 모델 단독으로는 더 이상 제품이 되지 못합니다. 에이전트에게는 작업할 공간:
- 파일 시스템 (filesystem)
- 셸(shell) 및 프로세스 실행 (process execution)
- 언어 런타임 (language runtimes) 및 라이브러리
- Git과 같은 개발 도구 (dev tools)
- 장기 실행 프로세스 (long-running processes)
- 실행 로그 및 차이점 (execution logs and diffs)
- 권한 및 승인 UI (permissions and an approval UI)
- 세션 간 유지되는 상태 (state that survives between sessions)
웹에서는 이를 제공하는 일반적인 방법으로 사용자당 격리된 컨테이너(container)나 마이크로VM(microVM)을 생성하는 것입니다. AWS, Cloudflare, Vercel 모두 정확히 이 용도로 훌륭한 서비스를 제공합니다. 하지만 모든 에이전트 제품이 클라우드 샌드박스를 소유해야 할까요?
저는 이전에 The Wrapper Got Heavy에서, 우리가 얇은 "래퍼(wrapper)"라고 치부했던 것이 샌드박스에 에이전트 루프(agent loop), 그리고 상태 중력(state gravity)이 더해진 무거운 런타임(runtime)이 되었다고 쓴 적이 있습니다. 이 포스트는 그 후속편과 같습니다. 런타임이 무거워졌다면, 다음 질문은 그것을 어디에서 실행할 것인가 — 클라우드인가, 아니면 사용자의 개인 기기인가 하는 점입니다.
최근 저는 다른 형태를 더 자주 권장하고 있습니다. 단일 사용자 에이전트의 경우, 저는 먼저 Codex나 Claude Code를 데스크톱 앱 내부에서 실행하는 것을 고려합니다. 이것은 단순히 "로컬이 더 저렴하다"는 차원의 문제가 아닙니다. 실행을 사용자의 PC로 옮기는 것은 인프라 이상의 것을 변화시킵니다 — 파일 접근, 상태, 지연 시간(latency), 그리고 권한 UI를 설계하는 방식을 변화시킵니다. 이어지는 내용은 최종 결론이 아닌, 하나의 작업 가설(working thesis)로 간주해 주시기 바랍니다.
TL;DR
- 단일 사용자 AI 에이전트의 경우, 데스크톱 앱이 클라우드 샌드박스 (Sandbox)보다 더 자연스러운 런타임 (Runtime)이 될 수 있습니다.
- 클라우드에 두 번째 환경을 프로비저닝 (Provisioning) 하는 대신 사용자의 기존 환경과 컴퓨팅 자원을 사용할 수 있으며, Codex App Server를 사용하면 에이전트 루프 (Agent loop)를 새로 구축하는 대신 기존의 강력한 루프를 재사용할 수 있습니다.
- 하지만 Codex는 SDK가 아닙니다. 버전 고정 (Version pinning) 및 호환성 관리가 필요한 _임베디드 런타임 (Embedded runtime)_으로 취급하십시오.
클라우드 샌드박스 (Cloud sandboxes)는 이미 많은 발전을 이루었습니다
미리 말씀드리자면, 클라우드 샌드박스가 나쁘다는 뜻은 아닙니다. 신뢰할 수 없는 수많은 사용자의 코드를 안전하게 실행해야 하는 퍼블릭 서비스의 경우, 클라우드 내의 강력한 격리 경계 (Isolation boundary)는 필수적입니다.
**AWS AgentCore Runtime**은 세션당 전용 마이크로VM (microVM)을 할당합니다. 각 세션은 고유한 커널 (Kernel), 메모리, 파일 시스템을 가지며, 완료 시 마이크로VM은 파괴되고 메모리는 초기화 (Sanitized) 됩니다. 기반이 되는 Firecracker는 VM당 5 MiB 미만의 오버헤드를 가진 KVM 기반의 경량 마이크로VM입니다 (AWS의 도구 설명서).
**Cloudflare Sandbox SDK**는 명령 실행, 파일 작업, 백그라운드 프로세스 및 포트 포워딩 (Port forwarding)을 API로 노출합니다. 이 서비스의 라이프사이클 (Lifecycle)을 이해할 필요가 있습니다. 기본적으로 10분 동안 활동이 없으면 중지되고 다음번에 새로 시작되므로, 일반적인 로컬 디스크 상태는 손실됩니다. 따라서 지속 가능한 상태 (Durable state)는 R2와 같은 곳으로 내보내야 합니다.
**Vercel Sandbox**는 스트리밍, 파일 관리, 네트워크 정책 및 스냅샷 (Snapshots) 기능이 포함된 격리된 Linux 마이크로VM (Firecracker)을 SDK를 통해 제공합니다. 지속 가능한 샌드박스 (Persistent sandboxes)가 기본값입니다: 중지 시 파일 시스템이 스냅샷으로 저장되며, 재개 시 복구됩니다.
어려운 부분은 "VM을 시작하는 것"이 아닙니다
클라우드에서 샌드박스(Sandbox)를 구축할 때, 어려운 점은 부팅 API가 아닙니다. 실제 제품을 만들 때는 다음과 같은 문제들도 해결해야 합니다:
| 영역 | 설계해야 하는 사항 |
|---|---|
| 라이프사이클 (Lifecycle) | 시작(start), 절전(sleep), 재개(resume), 파괴(destroy), 타임아웃(timeout) |
| ... |
예를 들어, Cloudflare Containers는 중지된 상태에서 시작하는 데 시간이 걸릴 수 있으며, 후속 요청을 올바른 인스턴스로 라우팅(routing)하고, 해당 인스턴스가 다른 위치에서 재시작될 가능성까지 고려해야 합니다. 심지어 AgentCore 세션조차도 요청이 동일한 마이크로VM(microVM)에 도달할 수 있도록 세션 ID(session ID)를 유지해야 합니다. 이를 놓치면 콜드 스타트(cold start)가 발생합니다 (마이크로VM 스티키니스 (microVM stickiness)).
이 중 어느 것도 클라우드의 결함은 아닙니다. 이는 수많은 신뢰할 수 없는 사용자들이 하나의 서버 플릿(fleet)을 공유할 수 있도록 하기 위해 요구되는 복잡성입니다. 문제는 단일 사용자 데스크톱 제품에 실제로 그러한 복잡성이 필요한가 하는 점입니다.
데스크톱에서는 사용자의 PC가 이미 완성된 런타임(runtime)입니다
개발자의 머신에는 이미 작업 중인 저장소(repo), Git 히스토리 및 자격 증명(credentials), 언어 런타임(language runtimes) 및 의존성 캐시(dependency caches), Docker 및 로컬 데이터베이스, SSH 설정, IDE, 그리고 충분한 저장 공간, 메모리, 때로는 GPU까지 갖춰져 있습니다.
클라우드 방식은 이 모든 것을 별도의 환경에서 재구축하고, 동기화 상태를 유지하며, 필요에 따라 지속(persist)하거나 복구(restore)해야 합니다 (복제(clone), 의존성 설치, 비밀 정보(secrets) 주입, 결과 반환 등). 물론 지속성이 기본값인 Vercel Sandbox나 Cloudflare의 R2 기반 백업/복구와 같은 옵션들이 있기는 하지만, "별도의 환경을 구축하고 이를 동기화한다"는 형태는 여전히 유지됩니다. 데스크톱에서는 단순히 해당 디렉토리에서 에이전트를 실행하기만 하면 됩니다. 네트워크 파일 동기화도, 원격 저장소로의 업로드도, 이미지 빌드도 필요하지 않습니다. CPU, 메모리, 저장 공간은 클라우드 제공업체가 앱 벤더에게 측정하여 청구하는 비용이 아니라, 사용자가 이미 소유하고 있는 자원입니다.
한 가지 중요한 주의 사항이 있습니다. Codex나 Claude Code 프로세스가 로컬(local)에서 실행되더라도, LLM 추론 (inference)은 로컬 GPU에서 실행되지 않습니다. 파일 및 명령 작업은 로컬에서 이루어지지만, 모델과의 대화는 네트워크를 통해 전달됩니다. 로컬 GPU는 에이전트가 시작하는 워크로드(빌드, ML, 이미지 처리 등)에 도움을 줄 수 있지만, 단일 사용자 에이전트에게 그것이 주요 매력인 경우는 드뭅니다. 더 크고 일상적인 이점은 로컬 SSD 상의 대규모 로컬 파일에 대한 빠른 작업(파싱 (parsing), 인덱싱 (indexing), 검색 (searching), 임베딩 (embedding))과 사용자의 기존 툴체인 (toolchain)이 그대로 작동한다는 점입니다.
따라서 데스크톱 설정이 제공업체로 하여금 피하거나 크게 줄일 수 있는 것은 샌드박스(sandbox)의 CPU/메모리/디스크, 영구 저장소 (durable storage), 데이터 송신 (egress), 오케스트레이션 (orchestration), 그리고 모니터링 인프라 (monitoring infra)에 대한 '지속적인' 비용입니다. 그 대가로 사용자는 배포, 업데이트, OS별 지원, 그리고 로컬 실행의 보안 설계를 떠맡게 됩니다. 따라서 이것은 "로컬이 항상 더 저렴하다"는 뜻이 아니라, 컴퓨팅 비용과 복잡성의 부담이 제공업체에서 사용자의 기기로 이동한다는 의미입니다. 그럼에도 불구하고, 사용자당 실행 플랫폼을 운영할 필요가 없다는 것은 매우 큰 이점이라고 생각합니다.
PTY는 앱에 대화형 로컬 실행을 가져옵니다
Claude Code나 Codex CLI와 같은 도구를 앱에 내장할 때, child_process.exec()를 호출하는 것만으로는 항상 충분하지 않습니다. 이러한 CLI들은 진행 상황 스트리밍 (streaming progress), 입력 대기, 터미널 너비에 맞춘 적응, ANSI 제어 시퀀스 (ANSI control sequences), Ctrl+C와 같은 시그널 (signals), 그리고 장기 실행 프로세스 유지와 같은 대화형 작업을 수행합니다.
이때 필요한 것이 바로 PTY (pseudo-terminal, 가상 터미널)입니다. Microsoft의 node-pty는 macOS/Linux에서는 forkpty를 사용하고 Windows에서는 ConPTY를 사용하여 자식 프로세스가 실제 터미널에 연결되어 있다고 믿게 만듭니다. 디스플레이를 위해서는 xterm.js (VS Code의 통합 터미널이 사용하는 것과 동일한 것)를 사용할 수 있습니다. 전체적인 구조는 대략 다음과 같습니다:
┌────────────────────────────────────────────┐
│ 데스크톱 UI (채팅 / Diff / 승인 / 터미널) │
└──────────────────┬─────────────────────────┘
...
Codex App Server 자체와 통신하는 데는 PTY (Pseudo-Terminal)가 필요하지 않다는 점에 유의하세요. App Server는 일반적인 stdio (JSONL)를 통해 연결됩니다. PTY는 사용자용 터미널 및 대화형 자식 프로세스 (셸, 개발 서버, 진행 상황을 그리는 CLI 등)를 위한 것입니다. App Server는 자체적인 PTY 기반 명령 실행 API를 가지고 있지만, 이는 연결 전송 방식과는 별개입니다.
Codex는 데스크톱 앱에 임베딩하기에 매우 적합합니다
제가 Codex를 선택하는 구체적인 이유는 Codex App Server 때문입니다. 이는 단순히 프롬프트를 보내는 API가 아니라, 커스텀 클라이언트에서 Codex의 에이전트 메커니즘을 구동하기 위한 로컬 서버 프로세스입니다. 이 서버는 스레드 생성/재개/포크/지속성(persistence), 턴 시작/중단/턴 중간 입력, 명령 및 파일 변경에 대한 승인 요청, 실행 이벤트 및 Diff의 스트리밍, 설정, ChatGPT 로그인, 그리고 MCP 및 Skills와의 통합을 처리합니다.
App Server는 스레드를 장기 실행되는 자식 프로세스로 유지하며 양방향 JSON-RPC를 통해 연결됩니다. 단일 입력이 많은 이벤트를 생성하며, 승인이 필요한 경우 서버는 클라이언트에 요청을 보내고 답변이 올 때까지 해당 턴을 일시 중지합니다. 통합 패턴은 깔끔합니다. 각 OS별 Codex 바이너리를 묶고, 검증된 버전을 고정(pin)한 뒤, 이를 자식 프로세스로 실행하고 stdin/stdout을 통해 JSONL을 교환하는 방식입니다. Codex의 자체 데스크톱 앱과 VS Code 확장 프로그램도 동일한 구조를 사용합니다.
연결 자체는 가볍습니다. app-server 하위 명령어로 번들링된(버전 고정) Codex 바이너리를 자식 프로세스로 실행하고 표준 I/O를 통해 연결합니다. stdio가 기본 전송 방식이므로 네트워크 포트를 열 필요가 없습니다. 그런 다음 자식 프로세스의 stdout을 JSONL 형식으로 한 줄씩 읽습니다. 연결당 한 번 initialize 요청을 보내고, 응답을 받은 후에는 initialized 알림(클라이언트가 보내는 것이며 서버에서 반환되는 것이 아님)을 보냅니다. 그런 다음 작업 디렉터리(cwd), 샌드박스 모드(예: workspace-write), 승인 정책(예: on-request)과 함께 thread/start를 호출합니다. 이렇게 하면 하나의 스레드가 올라오고, 그곳에서 이벤트—모델 출력, diff, 승인 요청—가 stdout을 통해 다시 흐릅니다.
실제로는 요청 ID와 Promise 매핑, 충돌 처리 및 재시작, 이벤트 순서 지정, 백프레셔(backpressure), 그리고 승인을 위해 일시 중지되는 턴 처리를 추가해야 합니다. 마지막 세 가지 항목에서 '가볍다'는 첫인상이 깨집니다—진행 중인 턴과 경쟁하는 재시작이거나 초기화 흐름 자체가 실제 작업(그리고 아래에서 제가 발견한 놀라운 점)이 있는 곳입니다. codex app-server generate-ts를 사용하여 번들링된 바이너리와 일치하는 타입을 생성할 수 있어, 바이너리/클라이언트 조합을 고정하기가 쉽습니다. WebSocket 전송 방식도 있지만, 이는 공식적으로 실험적/미지원입니다. 앱 내부에서는 포트를 닫고 stdio를 사용하는 것이 더 간단하고 안전합니다.
Codex 자체 로컬 샌드박스를 얻게 됩니다
에이전트를 로컬에서 실행하는 것은 모델에게 사용자의 권한으로 원하는 모든 것을 하도록 내버려 두는 것과는 다릅니다. 로컬에서도 Codex는 OS가 강제하는 샌드박스 및 승인 정책을 결합합니다:
- macOS: Seatbelt
- Linux / WSL2: bubblewrap (bwrap) + seccomp (bwrap가 없는 경우 사용자 네임스페이스 (user namespaces)에 의존하는 번들된 헬퍼로 폴백 (falling back))
- Windows: PowerShell에서 실행될 때는 전용 저권한 사용자 (low-privilege user), 파일 시스템 경계 (filesystem boundaries), 방화벽을 사용하는 네이티브 샌드박스 (native sandbox)를 사용하며; WSL2에서 실행될 때는 Linux 샌드박스를 사용합니다.
표준 workspace-write + on-request 설정에서, 작업 디렉토리 외부로 나가거나 인터넷에 접속하려면 승인이 필요합니다. 샌드박스 및 승인 (Sandbox and approvals)은 두 개의 별개 다이얼과 같습니다. 전자는 기술적으로 무엇이 가능한지를 제어하고, 후자는 언제 멈춰서 물어볼지를 제어합니다. OS별 샌드박스를 처음부터 직접 구축할 필요는 없습니다. 실행 측면은 기존의 경계 (boundaries)를 활용합니다. 또한 App Server는 승인 이벤트 (approval events)를 구조화된 데이터로 반환하므로, 사용자가 가공되지 않은 로그에서 y를 입력하게 만드는 대신 제품 네이티브 승인 대화 상자 (product-native approval dialog)를 구축할 수 있습니다.
그럼에도 불구하고, Codex를 임베딩하는 것은 간단하지 않습니다
장점들을 기술했지만, 이를 임베딩하는 것은 유지 관리 측면에서 실제적인 비용이 따른다는 점을 솔직하게 말씀드려야겠습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기