외부 프로토콜은 투영입니다
IDE가 보는 것은 Thread / Turn / Item이지 커널 EventMsg가 아니에요. turn/start는 먼저 응답을 돌려주고 그다음 이벤트 스트림을 밉니다. 승인은 역방향 요청이라, 회신이 없으면 이 turn이 멈춰요.
turn/start 회신은 요청이 받아들여졌다는 뜻일 뿐이고, 진짜 시작은 turn/started를 봐요. Python SDK와 TypeScript SDK는 같은 프로토콜 면을 걷지 않습니다.
turn/start의 params에 들어가요. 엔터면 재생입니다.
turn/start 응답은 바로 돌아오고, 요청이 받아들여졌다는 뜻일 뿐이에요. 진짜 돌기 시작은 뒤의 turn/started 알림을 기다려요.편집기 플러그인을 쓰고 있어요. 디버거에는 이미 커널이 던지는 이벤트가 보입니다. turn_started, exec_command_begin, 필드는 snake_case예요. 첫 패킷이 오면 맞지가 않습니다. 메서드는 turn/started이고 가운데가 슬래시예요. 필드는 threadId, startedAt입니다.
명령이 시작할 때 기다리던 exec_command_begin은 안 오고, item/started가 오며 안에 type: “commandExecution” item이 들어 있어요. 승인은 더 이상해요. 서버가 거꾸로 request를 보내고, response를 줘야 이 turn이 멈추지 않습니다.
편집기가 81개 EventMsg로 switch를 쓰면, 내부 이벤트를 하나 더할 때마다 클라이언트 업그레이드예요. deprecated 별칭도 저장소 안 호환 문제에서 외부 계약이 됩니다.
디스패치 apply_bespoke_event_handling이 커널 Event 하나를 먹고, 규칙 넷으로 외부 메시지를 접어요.
EventMsg의 snake_case type이 turn/started, item/agentMessage/delta 같은 자원 경로가 되고, 필드는 camelCase로 바뀌어요.
delta와 도구 수명이 ThreadItem에 접힌 뒤 item/started나 item/completed에 들어가요. IDE는 item의 type으로 카드를 그립니다.
ExecCommandBegin, ViewImageToolCall, match 끝의 와일드 팔은 라이브 알림이 없어요. 옛 이벤트는 여전히 rollout으로 퍼집니다.
ItemStarted(DynamicToolCall) 하나는 알림도 보내고 item/tool/call ServerRequest도 보낸 뒤, 클라이언트가 실행하기를 기다려요.
출처:codex-rs/app-server/src/bespoke_event_handling.rs 159–188행;codex-rs/app-server/src/bespoke_event_handling.rs 880–918행;codex-rs/app-server/src/bespoke_event_handling.rs 996–1036행
item_event_to_server_notification은 일대일, 상태 없는 투영만 덮어요. 이름은 정문처럼 보이지만, 호출부가 조수인 줄 압니다. ExecCommandBegin은 조수에서는 item/started가 될 수 있어도, 디스패치에서는 deprecated 빈 팔로 들어가요. 라이브 명령 카드는 뒤의 ItemStarted에서 옵니다. 디스패치를 믿으세요.
출처:codex-rs/app-server-protocol/src/protocol/event_mapping.rs 25–37행;codex-rs/app-server-protocol/src/protocol/item_builders.rs 1–11행
커널은 일어난 일로 이름을 짓고, 바깥은 사용자가 본 것으로 이름을 지어요. 안에서는 deprecated 이벤트를 rollout에 계속 보낼 수 있고, 디스패치는 주석 한 줄로 버리면 됩니다. 언어를 바꿔 다시 써도 이 표는 남아요. 왼쪽은 내부 type, 오른쪽은 남기기·이름 바꾸기·버리기·가르기.
모르는 줄은 실패해야 해요. 빈 기본값은 와일드 팔과 같아서, 새 이벤트는 컴파일을 통과하고 IDE stdout에는 아무것도 없습니다.
출처:codex-rs/app-server/src/bespoke_event_handling.rs 1238–1245행
동료가 turn/start 응답을 이미 시작된 turn으로 읽어요. 응답은 바로 오고, 안은 items가 빈 turn입니다. 모델은 아직 입을 열지 않았어요. 진짜 시작은 뒤의 turn/started 알림입니다.
출처:codex-rs/app-server/README.md 81–81행
승인을 평범한 notification으로 만들면 클라이언트가 무시할 수 있어요. turn은 기다리며 멈추고, 시간 초과나 중단까지 갑니다.
선에서 풀리는 객체는 넷뿐입니다. id 있는 request, id 없는 notification, 성공 response, 오류 response. JSON-RPC처럼 보이지만 구조체에 jsonrpc 필드가 없어요. 상수 JSONRPC_VERSION은 있고, 라이브 키는 없습니다.
출처:codex-rs/app-server-protocol/src/rpc.rs 1–11행;codex-rs/app-server-protocol/src/rpc.rs 34–72행
외부 메시지는 네 벌이고, 대칭이 아니에요.
1. ClientRequest:클라이언트가 묻고 회신을 기다려요. initialize, turn/start가 안정 면의 등뼈입니다.
2. ServerNotification:서버가 밀고 회신을 기다리지 않아요. turn/started, item/started가 여기 있습니다.
3. ServerRequest:서버가 사람에게 물어요. 첫 안정 메서드는 item/commandExecution/requestApproval입니다.
4. ClientNotification:펼치면 Initialized뿐입니다.
출처:codex-rs/app-server-protocol/src/protocol/common.rs 1663–1670행;codex-rs/app-server-protocol/src/protocol/common.rs 1954–1956행
요청은 영수증이 필요하고, 알림은 방송이며, 역방향 요청은 사람을 고리에 끌어들입니다. 셋을 하나로 섞으면 편집기는 돌기만 기다리거나 승인 버튼을 빠뜨려요. id가 맞으면 과부하 때 request 실패를 호출 쪽에 돌려, 승인이 매달리지 않게 할 수 있습니다.
실험 메서드에는 메서드 급 표시가 57개예요. 둘째 포트에 기대면 안정 고객과 모험 고객이 두 곳에 붙습니다. TUI가 같은 프로세스라고 EventMsg를 받기 시작하면, 현장 알림과 원격 IDE가 각자 item을 씁니다.
실험 면은 initialize 때 불리언 experimentalApi 하나이고, 기본은 false예요. 다시 initialize하면 Already initialized를 받습니다. 스위치를 안 켜고 server/diagnostics를 치면 오류 -32600, 문장은 고정된 server/diagnostics requires experimentalApi capability예요. Python SDK는 이 기본을 True로 바꿔, 공식 스크립트는 이미 실험 계약 위에 서 있습니다.
출처:codex-rs/app-server/src/message_processor.rs 891–895행;sdk/python/src/openai_codex/client.py 209–209행
TUI는 core에 바로 붙지 않아요. 내장은 운반체만 바꿉니다. socket과 stdio가 메모리 채널이 되고, MessageProcessor는 남아요. 요청은 여전히 ClientRequest이고, 응답은 같은 envelope을 걷습니다. 프로세스 안은 transport-local이지 protocol-free가 아니에요.
출처:codex-rs/app-server/src/in_process.rs 1–24행
TypeScript SDK는 이 길을 안 가요. 맞추는 것은 exec --experimental-json이고, 이벤트 type은 점, 필드는 snake_case, 전체 열거는 변형 8개뿐입니다. initialize도 승인 request도 없어요. 차이는 프로토콜 면이지 언어가 아닙니다.
출처:sdk/typescript/src/exec.ts 89–90행;codex-rs/exec/src/exec_events.rs 8–37행
원격과 로컬의 차이는 네트워크에 떨어져야지, 의미에 떨어지면 안 돼요. 실험 면은 capability를 써서, 문서에 “실험”이라고 한 줄 쓰는 것보다 단단합니다. 불리언 하나가 안정 면과 실험 면을 가르고, schema는 두 벌을 내며, 기본 벌에는 실험 필드가 없어요.
DeepSeek Harness: 커널 타입이 곧 프로토콜 타입
DSH 입구 다섯이 같은 플러그인 나무를 씁니다. headless 입구 설정은 스스로를 composition base로 써, 플러그인을 맞추고 이벤트 타입을 따로 만들지 않아요. 프로세스를 넘을 때 Typert가 TypeScript 타입 그래프에서 stub을 만들고, @Remote('create')가 돌려주는 것은 identity이지 다른 표시 모델이 아닙니다.
이벤트 필드 하나를 바꾸면 얼굴 다섯이 같이 움직여요. 이득은 Python이 thread/started를 보고 TypeScript가 thread.started를 보는 갈라짐이 없다는 것입니다. Codex는 반대로, 안은 deprecated로 두고 rollout에 계속 퍼뜨리며, 외부 계약은 투영 층에서 얼려요. 투영을 빠뜨리면 고객도 못 보고, 기능만 사라집니다.
Claude Code: 입구 표시, 둘째 프로토콜 면은 없어요
복원 소스에서 찾을 수 있는 것은 입구 판단이에요. CLAUDE_CODE_ENTRYPOINT === 'claude-vscode'이면 claude-vscode를 반환합니다. 맞서는 외부 IDE 프로토콜 crate는 없어요. 확장은 MCP와 프로세스 입구로 끼어 들고, 제3자 IDE에는 schema가 있는 양방향 RPC가 없습니다.
Codex는 투영 층 유지 비용을 내고, VS Code 확장과 Python SDK, 로컬 TUI가 같은 v2를 쓰게 해요.
소스 대조 완료 · 2026-08-22 · restored-src/src/main.tsx 823–823행회신이 왔어요. 이제 돌아야 할까요
turn/start 응답은 이미 왔고, items는 비어 있어요. 편집기는 지금 돌아야 할까요, 아니면 turn/started를 기다려야 할까요? 커널이 EventMsg 변형을 하나 더했는데 투영이 못 따라가면, stdout에는 무엇이 보이나요?
한 걸음 더: 같은 turn에서 모델이 질문이 필요한 명령을 돌리려 해요. Python 클라이언트는 팝업을 띄우고 회신할 수 있는데, TypeScript의 Thread.run()은 왜 못 하나요?