LODY/정리

AI 네이티브 엔지니어링 / OpenAI Codex는 어떻게 동작할까

OpenAI Codex는 어떻게 동작할까

정리: ByteByteGo 톤의 구성을 참고했고, 사실 관계는 OpenAI 엔지니어링 블로그·공식 발표 등 공개 자료에 맞춥니다. 각주는 해당 문단 바로 아래에 둡니다.

OpenAI가 클라우드 기반 코딩 agent인 Codex를 내놓았을 때, 가장 어려웠던 문제는 의외로 AI 모델 자체가 아니었다.

물론 모델은 중요했다. codex-1은 OpenAI의 o3를 소프트웨어 엔지니어링에 맞게 fine-tuning한 모델로, Codex의 핵심 추론 엔진 역할을 한다. 하지만 실제 제품을 만드는 과정에서 더 까다로웠던 것은 모델 바깥의 문제들이었다. Codex가 제대로 동작하려면 사용자의 요청만 보는 것으로는 부족했다. 현재 작업 환경, 프로젝트별 규칙, 이전 대화 내용, 도구 실행 결과처럼 여기저기 흩어져 있는 맥락을 함께 모아, 모델이 바로 이해할 수 있는 하나의 prompt로 정리해야 했다. 결국 진짜 과제는 모델 하나를 잘 만드는 일이 아니라, 모델이 제대로 일할 수 있는 시스템 전체를 설계하는 일이었다.

주 1. Codex는 코딩 작업을 수행하는 AI agent 제품이고, codex-1은 그 안에서 추론을 담당하는 모델이다. fine-tuning은 특정 목적에 맞게 모델을 추가 학습시키는 과정이다. prompt는 모델에 전달되는 전체 입력으로, 사용자 질문뿐 아니라 각종 지시와 환경 정보까지 포함한다.

Codex 팀은 이 agent를 VS Code 같은 개발 환경에서도 자연스럽게 동작하게 만들고 싶었다. 처음에는 MCP를 사용하는 방식을 시도했다. 하지만 곧 한계가 드러났다. 실제 agent는 단순히 도구를 한 번 호출하고 결과를 받는 수준으로 끝나지 않는다. 작업 중간 상태를 streaming으로 보여줘야 하고, 어떤 명령은 실행 전에 사용자 승인을 받아야 하며, 코드 변경 사항은 diff 형태로 구조적으로 전달할 수 있어야 한다. 이런 상호작용은 당시 MCP가 제공하던 방식만으로는 충분히 담아내기 어려웠다. 결국 OpenAI는 Codex를 위해 별도의 protocol을 직접 만들었다.

주 2. MCP는 Model Context Protocol이다. AI와 외부 도구를 연결하기 위한 표준 인터페이스 계열로 이해할 수 있다. streaming은 결과를 한 번에 주는 대신 점진적으로 흘려보내는 방식이고, diff는 변경 전후 코드의 차이를 보여주는 형식이다. protocol은 서로 다른 시스템이 약속된 방식으로 통신하기 위한 규칙이다.

이 글에서 진짜로 주목할 부분도 바로 여기다. Codex를 가능하게 만든 것은 단지 좋은 모델이 아니라, 그 모델 주위를 감싸는 orchestration layer였다. 즉, 모델이 어떤 순서로 생각하고 도구를 쓰는지, 긴 대화를 어떻게 관리하는지, 그리고 같은 agent를 terminal·웹·IDE 같은 여러 환경에서 어떻게 재사용할지를 함께 풀어낸 구조가 Codex의 핵심이었다.

주 3. orchestration layer는 모델, 도구 실행, 상태 관리, 사용자 승인, 환경 정보를 묶어 하나의 agent처럼 동작하게 만드는 제어 계층이다.

참고: 이 글은 OpenAI Engineering Team이 공개적으로 공유한 내용을 바탕으로 정리한 것이다.

주 4. 여기서 말하는 공개 정보는 주로 OpenAI 엔지니어링 블로그와 공식 발표 자료를 뜻한다.


Codex란 무엇인가

Codex는 단순한 코드 자동완성 도구가 아니다. 기능 구현을 맡길 수도 있고, 버그를 수정하게 할 수도 있으며, 코드베이스에 대해 질문을 던지거나 pull request 초안을 제안하게 할 수도 있다. 다시 말해 Codex는 “코드를 조금 도와주는 모델”이 아니라, 실제 개발 작업을 일정 범위 안에서 대신 수행하는 coding agent에 가깝다.

주 5. codebase는 하나의 프로젝트를 이루는 전체 코드와 그 구조를 뜻한다. pull request는 변경한 코드를 검토·병합하기 위해 제안하는 단위다. coding agent는 코딩 관련 작업을 스스로 여러 단계에 걸쳐 수행하는 시스템을 말한다.

각 작업은 독립된 클라우드 sandbox 안에서 돌아간다. 이 sandbox에는 사용자의 repository가 미리 로드되어 있고, 서로 다른 작업을 병렬로 여러 개 실행할 수도 있다. 사용자는 각 작업이 어디까지 진행됐는지 실시간으로 확인할 수 있다. 이 구조 덕분에 Codex는 단일 응답을 생성하는 챗봇이라기보다, 격리된 실행 환경 안에서 실제 작업을 처리하는 원격 협업자처럼 동작한다.

주 6. sandbox는 외부 환경과 분리된 실행 공간이다. repository는 Git 저장소를 뜻한다. 병렬 실행은 여러 task를 동시에 처리하는 방식을 말한다.

Codex의 내부를 이해하려면 크게 세 가지 층위를 보면 된다. 첫째는 실제 작업을 반복적으로 수행하는 agent loop, 둘째는 점점 커지는 입력을 다루는 prompt 및 context 관리, 셋째는 하나의 agent를 다양한 제품 표면에서 재사용할 수 있게 만든 multi-surface architecture다.

주 7. context 관리는 모델이 현재까지의 작업 상태를 이해하도록 필요한 정보를 유지·압축·전달하는 문제다. multi-surface architecture는 CLI, 웹, IDE처럼 여러 사용자 접점을 하나의 공통 코어 위에 올리는 설계를 뜻한다.


Codex의 중심, Agent Loop

Codex의 핵심에는 agent loop가 있다. 기본 흐름만 놓고 보면 단순하다. 사용자가 요청을 내면 agent가 prompt를 구성하고, 모델이 그 prompt를 바탕으로 다음 행동을 결정한다. 그리고 그 결과를 다시 받아 다음 단계를 이어간다.

주 8. agent loop는 “추론 → 행동 → 결과 반영 → 다시 추론”의 반복 구조다.

하지만 중요한 점은, 모델의 응답이 항상 곧바로 사용자에게 보여줄 최종 답변은 아니라는 것이다. 오히려 많은 경우 모델은 먼저 어떤 행동이 필요하다고 판단한다. 예를 들어 특정 파일을 읽거나, shell command를 실행하거나, 테스트를 돌리거나, 파일을 수정하라고 지시할 수 있다. 이때 모델은 직접 그런 작업을 수행하는 대신 tool call을 반환하고, agent는 그 요청을 실제로 실행한 뒤 결과를 다시 prompt에 붙인다. 그런 다음 모델은 새로 얻은 정보를 바탕으로 다시 다음 행동을 정한다. 이 과정은 한 번으로 끝나지 않고 여러 차례 반복될 수 있다.

주 9. tool call은 모델이 외부 실행 환경에 특정 작업을 요청하는 구조화된 명령이다. shell command는 터미널에서 실행하는 명령어다.

예를 들어 “auth module의 버그를 고쳐줘”라는 요청 하나를 생각해보자. Codex는 먼저 관련 파일을 읽고, 현재 어떤 테스트가 실패하는지 확인한 다음, 코드를 수정하고, 다시 테스트를 돌리고, 필요하면 lint 오류나 타입 오류를 고친 뒤, 마지막으로 한 번 더 검증을 거쳐 결과를 정리할 수 있다. 즉, 사람 개발자가 하듯이 “상황 파악 → 수정 → 검증 → 보완”이라는 흐름을 여러 번 반복하는 셈이다.

주 10. lint는 코드 스타일이나 잠재적 오류를 검사하는 과정이고, 타입 오류는 type checker가 잡아내는 타입 불일치 문제를 뜻한다.

여기서 모델과 시스템의 역할은 분명히 나뉜다. 무엇을 해야 할지를 판단하는 것은 모델이다. 하지만 실제 명령 실행, 결과 수집, 권한 확인, 종료 시점 판단 같은 일은 harness가 맡는다. 그래서 Codex의 성격은 “모델이 전부 알아서 한다”와는 다르다. 실제로는 모델이 사고를 담당하고, 주변 시스템이 실행과 통제를 맡는 구조에 가깝다.

주 11. harness는 모델이 안전하고 일관되게 도구를 사용할 수 있도록 감싸는 실행 프레임워크다.

이 구분은 단순한 구현 세부사항이 아니라, Codex가 어떤 종류의 일에 잘 맞는지를 설명해준다. OpenAI 내부에서도 Codex는 반복적이지만 범위가 비교적 분명한 작업에 잘 맞는다고 한다. 예를 들어 refactoring, 이름 변경, 테스트 작성, on-call 이슈 분류 같은 작업이 여기에 해당한다.

주 12. refactoring은 동작은 유지하면서 코드 구조를 개선하는 작업이다. on-call 이슈 분류는 장애 대응 중 들어오는 문제를 확인하고 우선순위를 정리하는 일을 말한다.

그리고 agent loop에는 바깥쪽 층이 하나 더 있다. 한 번의 추론과 도구 사용 흐름을 OpenAI는 하나의 turn으로 본다. 하지만 실제 대화는 turn 하나로 끝나지 않는다. 사용자가 후속 메시지를 내면, 이전까지의 turn 전체가 다음 prompt에 다시 들어간다. 여기에는 단순한 대화만이 아니라, tool call과 그 결과까지 포함된다. 바로 이 지점부터 비용과 복잡성이 빠르게 커지기 시작한다.

주 13. turn은 사용자 요청 하나와 그에 따른 agent의 연쇄 작업을 묶은 단위다.


Prompt를 쌓고, Memory를 관리하는 방법

사용자가 Codex에 요청을 입력하면, 그 한 줄짜리 메시지만 모델로 들어가는 것이 아니다. 실제 prompt의 맨 아래에는 사용자의 요청이 놓이지만, 그 위로는 현재 작업 디렉터리와 shell 같은 환경 정보, repository 안의 AGENTS.md 파일 내용, sandbox 권한 규칙, 설정 파일에 담긴 developer instruction, 모델별 instruction, tool definition, 그리고 system message 같은 정보가 층층이 쌓인다.

주 14. AGENTS.md는 프로젝트별 agent 사용 지침을 담는 파일이다. 예를 들어 코딩 규칙, 테스트 실행 방법, 프로젝트 구조 설명 등이 들어갈 수 있다. tool definition은 어떤 도구를 어떤 형식으로 호출할 수 있는지 설명하는 정보다.

이처럼 prompt는 단순한 질문이 아니라, 여러 층의 문맥이 결합된 입력이다. 각 층에는 system, developer, user 같은 role이 붙는데, 이 role은 어떤 지시가 더 우선하는지 모델에게 알려주는 신호 역할을 한다. 상단 레이어의 순서는 서버가 통제하고, 나머지 일부는 클라이언트가 관리한다. 덕분에 모델은 자신이 어떤 환경에서, 어떤 규칙 아래 일하고 있는지를 충분히 이해할 수 있다. 그러나 동시에 prompt는 사용자가 한마디 입력하기도 전에 이미 꽤 커져 있는 상태가 된다.

주 15. 일반적으로 system 메시지는 가장 높은 우선순위를 갖고, 그다음이 developer, 마지막이 user다. 이는 상충하는 지시가 들어왔을 때 어떤 지시를 더 따를지 판단하는 기준이 된다.

문제는 여기서 끝나지 않는다. 모델이 수행한 모든 tool call의 결과는 다시 prompt 뒤에 계속 붙는다. 그리고 다음 turn이 시작되면, 이전 turn들 전체가 다시 포함된다. 즉, 매번 새 정보만 보내는 것이 아니라, 기존 기록까지 함께 다시 보내는 구조다.

주 16. 이런 방식은 재현성과 일관성에는 유리하지만, 입력 크기를 빠르게 키운다.

그래서 대화 전체를 기준으로 보면, API에 전송되는 JSON의 총량은 사실상 제곱에 가깝게 증가한다. 첫 번째 turn에서 보낸 내용이 두 번째에도 다시 포함되고, 두 번째까지의 내용이 세 번째에도 또 포함되기 때문이다. 대화가 길어질수록 누적 전송량은 점점 더 빠르게 커질 수밖에 없다.

주 17. 글에서 말하는 “quadratic”은 누적 전송량 관점의 설명이다. 매 turn마다 이전 문맥 전체를 재전송하기 때문에 총량이 빠르게 증가한다.

그렇다면 서버가 대화 상태를 기억하게 해두고, 매번 전체를 다시 보내지 않으면 되지 않을까? 기술적으로는 가능하다. 하지만 OpenAI는 의도적으로 그렇게 하지 않았다. 그렇게 하면 각 요청이 stateless하지 않게 되고, Zero Data Retention이 필요한 고객을 지원하기 어려워지기 때문이다. 따라서 Codex의 각 요청은 가능한 한 self-contained하게 유지되며, 필요한 문맥을 요청 자체가 모두 들고 다니는 방향을 택했다.

주 18. stateless는 각 요청이 독립적이라는 뜻이다. Zero Data Retention은 서비스 제공자가 사용자 데이터를 장기 저장하지 않도록 요구하는 보안·운영 조건이다. self-contained는 필요한 정보를 요청 자체가 모두 포함한다는 의미다.

이 구조에서 비용을 줄이는 핵심 장치는 prompt caching이다. Codex는 보통 기존 prompt 뒤에 새 내용을 덧붙이는 방식으로 대화를 이어간다. 그러면 이전 prompt는 다음 prompt의 정확한 prefix가 된다. 이 성질을 이용하면 이전 inference에서 이미 계산한 앞부분을 재사용할 수 있다. 다시 말해, 전송량은 계속 커지더라도 실제 모델 계산량은 어느 정도 억제할 수 있다.

주 19. prompt caching은 이전 요청과 동일한 앞부분에 대한 계산을 재사용하는 최적화다. prefix는 입력의 앞부분이 완전히 동일한 상태를 뜻한다.

다만 이 prefix 구조는 쉽게 깨질 수 있다. prompt의 앞이나 중간이 조금만 달라져도 캐시가 무너질 수 있기 때문이다. 예를 들어 모델이 바뀌거나, 도구 목록 순서가 바뀌거나, sandbox 설정이 달라지면 이전과 동일한 prefix가 아니게 된다. 실제로 OpenAI는 MCP tool을 추가하는 과정에서 tool 목록이 요청마다 일정한 순서로 정렬되지 않는 버그를 겪었고, 그 사소한 차이만으로도 cache hit이 크게 줄었다고 한다.

주 20. cache hit은 이전 계산 결과를 재사용할 수 있는 경우를 말한다. 입력이 거의 같아 보여도 토큰 순서가 달라지면 캐시가 깨질 수 있다.

그리고 대화가 충분히 길어지면, caching이 있더라도 결국 context window의 한계에 도달한다. 즉, 모델이 한 번에 읽고 처리할 수 있는 최대 토큰 수를 넘게 되는 것이다. 이 시점이 되면 Codex는 대화를 compact한다. 전체 이력을 그대로 들고 가는 대신, 지금까지 무슨 일이 있었는지에 대한 핵심 이해를 더 작은 형태로 보존하는 방식으로 바꾼다. 글에서는 이것을 모델의 latent state를 담은 encrypted payload를 이용하는 방식으로 설명한다. 구현은 훨씬 더 복잡하겠지만, 핵심은 분명하다. 긴 대화의 문맥을 어떻게 줄이고도 의미를 보존할 것인가는 Codex 같은 agent에서 중심적인 시스템 문제라는 점이다.

주 21. context window는 모델이 한 번의 inference에서 처리할 수 있는 최대 문맥 길이다. compact는 긴 기록을 더 작은 표현으로 압축하는 과정이고, latent state는 모델 내부에 형성된 의미 상태를 가리킨다. encrypted payload는 그 상태를 안전하게 담아 전달하기 위한 형태다.

이 맥락에서 AGENTS.md는 꽤 중요한 설계 포인트다. OpenAI는 프로젝트 특화 지식을 시스템 내부에 하드코딩하는 대신, 개발자가 repository 안에 AGENTS.md를 두고 프로젝트의 규칙을 직접 설명하도록 했다. Codex는 이 파일을 읽어 어떤 명령으로 테스트를 실행해야 하는지, 어떤 코딩 관례를 따라야 하는지, 코드베이스를 어떻게 탐색해야 하는지 파악한다. 즉, 일반적인 agent 코어는 유지하되, 프로젝트별 차이는 저장소 안의 문서로 주입하는 방식이다.

주 22. 이 구조는 agent 자체를 범용적으로 유지하면서, 프로젝트 특수성은 저장소 내부 문서로 공급하는 방식이라고 볼 수 있다.


여러 환경에서 같은 Agent를 동작시키기

Codex는 처음에는 CLI 도구로 시작했다. 사용자는 terminal에서 Codex를 실행했고, agent는 로컬 코드베이스를 기준으로 동작했다.

주 23. CLI는 Command Line Interface의 약자로, 명령줄 기반 인터페이스를 뜻한다.

하지만 곧 요구사항이 늘어났다. VS Code 안에서도 돌아가야 했고, 웹 앱에서도 제공되어야 했으며, macOS 데스크톱 앱에도 들어가야 했다. 더 나아가 JetBrains나 Xcode 같은 서드파티 IDE도 Codex와 연동하길 원했다. 문제는 각 환경마다 agent 로직을 새로 구현할 수는 없다는 점이었다. 이렇게 되면 기능을 하나 수정할 때마다 모든 플랫폼을 따로 손봐야 하기 때문이다.

주 24. 여기서 말하는 surface는 사용자가 agent를 접하는 실행 환경 또는 인터페이스를 뜻한다.

앞서 말했듯, 처음 시도는 MCP 기반 접근이었다. 그러나 실제 agent 경험은 단순한 요청-응답 모델보다 훨씬 복잡했다. agent는 작업 중간 진행 상황을 흘려보내야 하고, 특정 command 실행 전에는 사용자의 승인을 받아야 하며, 때로는 구조화된 diff까지 전달해야 했다. 이러한 상호작용은 “도구를 하나 호출하고 결과를 받는다” 수준의 인터페이스만으로는 충분히 표현하기 어려웠다.

주 25. 여기서 중요한 것은 단발성 호출이 아니라, 상태를 가진 긴 상호작용 세션을 다룬다는 점이다.

그래서 OpenAI는 App Server라는 구조를 만들었다. agent loop, thread 관리, tool 실행, 설정, 인증 등 핵심 로직은 모두 하나의 코드베이스에 모았고, 이것을 OpenAI는 Codex core라고 불렀다. 그리고 App Server는 이 core를 외부 클라이언트가 사용할 수 있도록 JSON-RPC 기반의 protocol로 감싼다. 통신 채널은 표준 입력과 출력, 즉 stdio를 사용한다.

주 26. App Server는 공통 agent 코어를 여러 클라이언트에서 사용할 수 있도록 감싸는 실행 서버다. JSON-RPC는 JSON 형식으로 원격 호출을 주고받는 경량 프로토콜이고, stdio는 standard input/output를 뜻한다.

이 protocol의 중요한 특징은 완전한 양방향성이다. 클라이언트는 서버에 thread를 시작하거나 task를 제출하는 요청을 보낼 수 있다. 동시에 서버도 클라이언트에 요청을 보낼 수 있다. 예를 들어 위험한 shell command를 실행하기 전에 사용자에게 “허용할지 말지”를 묻는 방식이다. 사용자가 응답할 때까지 agent의 turn은 잠시 멈춰 있고, 승인 여부가 결정되면 다시 이어서 진행된다. 이 구조 덕분에 승인 정책을 agent 내부 로직에 하드코딩하지 않고도, 자율성과 통제를 함께 유지할 수 있다.

주 27. thread는 하나의 작업 대화 단위다. 이러한 승인 흐름은 human-in-the-loop 설계의 대표적인 예다.

각 surface는 이 구조를 조금씩 다르게 사용한다. VS Code extension과 데스크톱 앱은 App Server 바이너리를 번들에 포함해 배포하고, 이를 child process로 실행한 뒤 양방향 stdio 채널을 유지한다. 반면 웹 앱은 App Server를 클라우드 컨테이너 안에서 실행한다. 워커가 repository를 checkout하고, 바이너리를 띄운 뒤, 발생하는 이벤트를 HTTP로 브라우저에 스트리밍한다. 이 경우 상태는 서버 쪽에 남기 때문에 사용자가 브라우저 탭을 닫더라도 작업 자체는 계속 진행될 수 있다.

주 28. child process는 다른 프로그램이 실행한 하위 프로세스다. checkout은 저장소 내용을 작업 환경으로 가져오는 과정을 뜻한다.

또한 Xcode 같은 파트너 환경에서는 이 구조가 배포 관점에서도 장점이 있다. 파트너는 클라이언트를 자주 바꾸지 않고도, 더 새로운 App Server 바이너리와 연결하는 식으로 기능을 업데이트할 수 있다. protocol이 backward compatible하게 설계되어 있다면, 비교적 오래된 클라이언트도 새로운 서버와 계속 통신할 수 있기 때문이다.

주 29. backward compatible은 이전 버전과의 호환성을 유지한다는 뜻이다.

흥미로운 점은, 이 구조가 처음부터 완성된 설계로 등장한 것이 아니라는 점이다. Codex는 CLI로 시작했고, MCP라는 한 차례의 우회 경로를 거쳤으며, 그 경험을 통해 결국 지금의 App Server 구조에 도달했다. 이 흐름은 시스템 설계에서 자주 보이는 패턴을 잘 보여준다. 처음부터 정답 같은 추상화가 보이는 경우는 드물고, 실제로 맞지 않는 구조를 한 번 겪어본 뒤에야 더 적절한 추상화가 선명해지는 경우가 많다.

주 30. abstraction은 복잡한 내부를 감추고 일관된 인터페이스를 제공하는 설계 단위를 말한다.


결론

OpenAI의 사례가 보여주는 것은 꽤 분명하다. 모델은 중요한 부품이지만, agent 자체는 모델 하나로 이루어지지 않는다. 실제 제품의 핵심은 모델을 둘러싼 시스템, 즉 도구 실행, 권한 관리, 문맥 유지, 캐싱, 압축, 그리고 여러 실행 환경을 잇는 공통 구조에 있다. 다시 말해 “좋은 모델”만으로는 충분하지 않고, “좋은 시스템”이 함께 있어야 비로소 usable한 agent가 된다.

주 31. 여기서 usable하다는 것은 단순히 똑똑하다는 뜻이 아니라, 실제 개발 워크플로 안에서 안정적으로 쓸 수 있다는 의미에 가깝다.

이런 동작 방식을 이해하면 Codex를 더 잘 활용하는 데도 도움이 된다. 예를 들어 AGENTS.md를 잘 작성하면 agent가 프로젝트 문맥을 더 정확히 파악할 수 있다. 작업을 작고 명확하게 나누면 agent loop가 다음 행동을 결정하기 쉬워진다. 또 긴 대화가 context window 한계와 compaction 때문에 점점 비효율적이 될 수 있다는 점을 알면, 완전히 다른 작업은 새 thread에서 시작하는 편이 더 낫다는 것도 이해할 수 있다.

주 32. 실무적으로는 “작업 범위를 좁게 잡기”, “프로젝트 규칙을 문서화하기”, “새 과업은 새 thread로 분리하기”가 중요한 사용 전략이 된다.

물론 Codex에도 아직 한계는 있다. 프런트엔드 작업에서 image input을 직접 받아 처리하지 못하고, 작업 중간에 agent를 세밀하게 다시 조정하는 것도 쉽지 않다. 원격으로 agent에게 일을 맡기는 방식은 직접 편집하는 것보다 시간이 더 걸릴 수 있고, 이런 협업 방식 자체에도 적응이 필요하다. OpenAI는 Codex가 앞으로 비동기적으로 함께 일하는 동료 같은 경험에 가까워지기를 기대하고 있지만, 현재 제품은 아직 그 비전으로 가는 과정에 있는 상태라고 볼 수 있다.

주 33. image input은 스크린샷이나 디자인 시안 같은 시각 정보를 직접 받아 처리하는 기능이다. 비동기 협업은 실시간으로 계속 붙어서 수정하는 방식보다는, 일을 맡기고 나중에 결과를 확인하는 방식에 가깝다.


참고 자료