SNA

기능 × 런타임 매트릭스

SNA 기능이 Claude Code, Codex, OpenCode, Grok Build, Cursor에서 어떻게 동작하는지 비교합니다.

모든 SNA 기능은 같은 AgentProvider 인터페이스를 거치지만, 각 런타임의 네이티브 표면은 다릅니다. 그래서 5개 런타임의 능력이 완전히 일치하지는 않습니다. 이 페이지는 어떤 기능이 어디서 실제로 동작하는지와 행별 주의점을 함께 정리합니다.

행이 인 경우, SNA API 레벨에서는 해당 필드를 받을 수 있지만 런타임이 무시하거나, respawn으로 폴백하거나, 대응되는 네이티브 표면이 없다는 뜻입니다.

코드 감사 기준: 2026-05-21, packages/core/src/core/providers/*, core/providers/acp/base.ts, runtime adapter 테스트 스위트 기준. 런타임 메모는 Claude Code 1.x, Codex 0.x (app-server), OpenCode 0.x (serve), Grok Build CLI 0.1.x, Cursor CLI 2026.05를 기준으로 둡니다.

런타임 특성

능력Claude CodeCodexOpenCodeGrok BuildCursor
와이어 프로토콜claude -p stream-json / --resume JSONLcodex app-server JSON-RPC stdioopencode serve HTTP + SDKgrok agent stdio ACP JSON-RPC stdiocursor-agent acp ACP JSON-RPC stdio
supportsRuntimePooling
supportsCwdPerThread해당 없음해당 없음해당 없음
공개 표준 프로토콜✗ 벤더✗ 벤더✗ 벤더ACPACP
Cold-start 비용호출당 약 1초 spawn데몬 약 2초 spawn, 재사용데몬 약 3초 spawn, 재사용세션당 약 2초 spawn세션당 약 2초 spawn

Claude Code, Grok Build, Cursor는 호출 또는 세션 단위로 무상태입니다. spawn마다 새 자식 프로세스가 뜹니다. Codex와 OpenCode는 RuntimePool을 통해 데몬을 공유합니다. Codex 데몬만 cwd에 종속되지 않으므로, 단일 풀 데몬 위에서 서로 다른 작업 디렉토리의 세션을 호스팅할 수 있는 유일한 런타임입니다. ACP를 사용하는 두 런타임(Grok Build, Cursor)은 core/providers/acp/base.ts를 공유합니다.

세션 라이프사이클

능력Claude CodeCodexOpenCodeGrok BuildCursor
spawn(opts) → AgentProcess
세션 중 AgentEvent 스트리밍
같은 세션 이어가기✓ 같은 stream-json 프로세스✓ 같은 Codex thread✓ 같은 OpenCode session✓ 같은 ACP session✓ 같은 ACP session
turn 중간 interrupt()✓ (ACP session/cancel)✓ (ACP session/cancel)
kill() / closeThread()✓ (풀 refcount 인식)✓ (풀 refcount 인식)
complete(opts) one-shot✓ (grok -p)✓ (cursor-agent -p)
complete() onDelta 스트리밍✓ stream-jsonitem/agentMessage/deltaprompt_async SSE 텍스트 델타✓ streaming-json text 이벤트✓ stream-json assistant 청크 (model_call_id 없음)
listModels()✓ static catalogcodex debug models 프로브 + static fallbackopencode CLI 프로브✓ static (grok-build 단일 항목)cursor-agent models 텍스트 파싱 + static fallback

"같은 세션 이어가기"는 기본 채팅 경로입니다. 같은 SNA 세션에 다시 agent.send를 호출하면 됩니다. resumeSessionId와 정규화 히스토리 재주입은 복구나 재시작을 위한 경로이지, 클라이언트가 매 턴 실행할 작업이 아닙니다.

OpenCode의 complete()는 두 경로를 씁니다. 콜백이 없으면 낮은 오버헤드의 동기 SDK 호출을 유지하고, onDelta가 있으면 prompt_async와 이벤트 스트림으로 전환합니다. 스트리밍 경로도 최종 텍스트 파트와 assistant 메시지 메타데이터에서 resolve하므로, 호출자는 실시간 텍스트 청크와 최종 usage를 모두 받을 수 있습니다.

이벤트 풍부도

모든 런타임은 같은 AgentEvent union으로 정규화되지만, 네이티브 스트림이 노출하는 세밀함은 다릅니다. 실제 UI 품질에는 이 차이가 큽니다. 툴 호출 카드, 스트리밍 JSON 인자, 명령 출력 패널, 토큰/비용 표시, 새로고침 후 대화 복원 품질이 여기서 갈립니다.

이벤트 항목Claude CodeCodexOpenCodeGrok BuildCursor
Assistant text delta✓ stream-json의 assistant_deltaitem/agentMessage/deltamessage.part.delta✓ ACP agent_message_chunk✓ ACP agent_message_chunk
최종 assistant 이벤트assistant / resultitem/completed agent message✓ finalized text part✓ ACP chunk를 complete 직전에 합성✓ ACP chunk를 complete 직전에 합성
Thinking deltathinking_deltaitem/reasoning/summaryTextDelta✓ reasoning message.part.delta✓ ACP agent_thought_chunk✓ ACP agent_thought_chunk
최종 thinking 이벤트✓ signed thinking block✓ completed reasoning item✓ finalized reasoning part✓ ACP thought chunk에서 합성✓ ACP thought chunk에서 합성
콘텐츠 시작 lifecycle✓ block이 tool일 때만 이벤트로 노출agentMessage / reasoning start는 무시✓ text/reasoning part start는 추적만 하고 tool로 내보내지 않음✓ message/thought chunk는 content이며 tool이 아님✓ message/thought chunk는 content이며 tool이 아님
Tool call 시작streaming: true 포함 tool_use✓ typed item start (shell, file_change, MCP, hosted tools)✓ running/pending tool part✓ ACP tool_call✓ ACP tool_call
Tool input delta✓ Anthropic input_json_deltatool_use_delta✗ 네이티브 input delta 없음✗ 네이티브 input delta 없음⚠ ACP tool_call_update로 input 갱신은 가능하지만 byte/token 단위 delta는 아님⚠ ACP tool_call_update로 input 갱신은 가능하지만 byte/token 단위 delta는 아님
Tool output streaming✗ 최종 tool_result만 있음✓ shell outputDeltatool_use update로 전달. file patch delta는 버림✗ 최종 part status만 있음✗ 최종/update payload만 전달✗ 최종/update payload만 전달
Tool result payloadtoolUseId, isError, text/JSON content✓ item별 상세 payload: exitCode, status, durationMs, MCP error, web_search, image_generation.savedPath, revisedPrompt, unknown-tool rawtitle, isError, output/error✓ ACP rawOutput, locations, status, kind, Grok tool-name unwrap✓ ACP rawOutput, locations, status, kind, Cursor tool-kind unwrap
세션 완료 메타데이터✓ duration, API duration, cost, token/cache usage, model, context window✓ duration과 token/cache usage. cost는 보고하지 않음⚠ session stream은 provider 필드만 표시. one-shot complete()는 OpenCode가 주는 usage/cost 반환✓ ACP stopReason만 있음✓ ACP stopReason만 있음
알 수 없는 새 tool 노출✓ native tool name은 그대로 노출✓ generic tool_use / tool_result + raw payload로 fail-open✓ tool처럼 보이는 미지의 SSE event는 tool_use, tool_use_delta, tool_result와 raw properties로 fail-open✓ tool처럼 보이는 미지의 ACP update는 rawUpdate와 함께 fail-open✓ tool처럼 보이는 미지의 ACP update는 rawUpdate와 함께 fail-open

가장 큰 비대칭은 tool_use_delta입니다. 현재 tool input의 partial JSON을 스트리밍하는 런타임은 Claude Code뿐입니다. Codex는 item lifecycle metadata와 live shell output은 더 풍부하지만, partial tool argument는 없습니다. ACP 런타임은 tool_call_update와 최종 raw output은 주지만, SNA에서는 세밀한 delta가 아니라 이벤트 단위 update로 다룹니다.

Assistant text와 reasoning의 lifecycle start는 tool_use로 노출하지 않습니다. 런타임이 content block 시작을 알려주더라도 실제 tool 호출만 tool_use / tool_result 계약에 들어갑니다.

인플레이스 변경 (applyPatch)

각 런타임은 어떤 SessionPatch 필드를 respawn 없이 인플레이스로 적용할 수 있는지 선언합니다. "leftover"로 반환된 필드는 세션 매니저가 kill 후 history replay와 함께 respawn해야 합니다.

필드Claude CodeCodexOpenCodeGrok BuildCursor
model✓ control_request set_model✓ next-turn override✓ next-prompt override✗ leftover (respawn)✗ leftover (respawn)
permissionMode✓ control_request✓ next-turn sandbox 정책✓ next-prompt agent override✓ SNA permission gate 갱신✓ SNA permission gate 갱신
cwd✗ leftover✓ next-turn override (유일하게 가능한 런타임)✗ leftover✗ leftover✗ leftover

ACP를 사용하는 런타임은 여전히 modelcwd를 leftover로 반환합니다. Grok Build / Cursor가 사용하는 ACP 버전에는 해당 세션 안에서 이 둘을 바꾸는 mutator가 없습니다. permissionMode는 다릅니다. ACP 권한 게이트는 SNA 어댑터가 소유하므로, bypassPermissions로 전환하거나 되돌리는 작업은 네이티브 프로세스를 재시작하지 않고 인플레이스로 적용할 수 있습니다.

권한 흐름

능력Claude CodeCodexOpenCodeGrok BuildCursor
permission_needed AgentEvent emit✓ hook 브리지 경유✓ JSON-RPC server-request 경유✓ SSE permission.updated 경유✓ ACP session/request_permission 경유✓ ACP session/request_permission 경유
respondToPermission(id, approved)✗ 외부 hook 스크립트가 승인 처리✓ JSON-RPC response✓ POST /permissions/:id✓ ACP {outcome:{outcome:"selected",optionId}}✓ ACP {outcome:{outcome:"selected",optionId}}
양방향 (런타임이 우리를 기다림)✗ hook 스크립트가 결정
bypassPermissions 자동 승인✓ hook 스크립트가 항상 허용✓ server-request에 자동 응답✓ HTTP permission API로 자동 응답✓ ACP base가 자동으로 allow_* 응답✓ ACP base가 자동으로 allow_* 응답

Claude Code의 권한 모델은 out-of-band입니다. SNA가 hook 스크립트를 써두면 CLI가 그것을 호출하고, 스크립트가 permissionMode와 allow/deny 룰을 기준으로 판단합니다. CLI는 SNA 프로세스를 기다리지 않습니다. 나머지 네 런타임은 양방향입니다.

설정 노브 (SpawnOptions)

필드Claude CodeCodexOpenCodeGrok BuildCursor
systemPrompt--system-promptthread/startbaseInstructionsappendSystemPrompt와 결합✓ ACP base 첫-turn resource block (헤드리스 complete() 경로는 --system-prompt-override도 지원)✓ ACP base 첫-turn resource block (CLI flag 자체가 없음. fold가 공유 base를 타고 들어감)
appendSystemPrompt--append-system-promptthread/startdeveloperInstructionssystemPrompt와 결합✓ 같은 첫-turn block에 systemPrompt와 결합 (complete 경로는 --rules도 지원)✓ 같은 첫-turn block에 systemPrompt와 결합
permissionMode: "plan"✓ 네이티브 --permission-mode plan✗ plan 표면 없음; read-only sandbox로 매핑✓ OpenCode agent plan으로 매핑✗ 현재 adapter 매핑 없음✗ 현재 adapter 매핑 없음
allowedTools✓ 네이티브 --allowedTools✓ PreToolUse hook (나머지 거부)⚠ 엄격한 allowlist가 아님. 나열된 도구는 켜지만 나머지는 OpenCode 기본값 유지✓ ACP base가 session/request_permission 경계에서 미허용 도구 자동 deny✓ ACP base가 session/request_permission 경계에서 미허용 도구 자동 deny
disallowedTools✓ 네이티브 --disallowedTools✓ PreToolUse hook✓ 나열된 도구 비활성✓ ACP base가 permission 경계에서 나열된 도구 자동 deny✓ ACP base가 permission 경계에서 나열된 도구 자동 deny
mcpServers--mcp-config JSONconfig.toml [mcp_servers.*] 기록✓ SDK를 통해 daemon config에 주입✓ stdio→HTTP bridge + <cwd>/.grok/config.toml✓ stdio→HTTP bridge + <cwd>/.cursor/mcp.json (기존 user entries와 머지, exit에 원복)
reasoningLevel (0..5)toClaudeEffort--efforttoCodexEffortmodel_reasoning_effort✗ 무시toGrokEffort--effortapplyCursorReasoning → 모델 ID 접미사 (gpt-5.3-codexgpt-5.3-codex-high)
configDirCLAUDE_CONFIG_DIRCODEX_HOME✗ 대응 없음✗ 대응 없음 (Grok Build은 ~/.grok 고정)✗ 대응 없음 (Cursor는 ~/.cursor 고정; CURSOR_CONFIG_DIRcli-config.json만 redirect, hook은 안 옮김)
resumeSessionId✓ 네이티브 --resume <sessionId>thread/resume API✓ 기존 OpenCode session id✗ 어댑터에 연결되지 않음✗ 어댑터에 연결되지 않음

providerOptions (런타임별 escape hatch)

SNA는 모든 런타임이 같은 의미로 지원할 수 있는 값만 런타임 중립 필드로 올립니다. API endpoint override는 그 기준을 충족하지 않습니다. Codex는 네이티브 -c model_providers.* 설정으로 OpenAI-compatible gateway를 가리킬 수 있고, Claude Code는 Anthropic 전용 환경 변수를 사용하며, OpenCode는 자체 설정 파일을 사용하고, Cursor는 현재 SNA가 지원하는 base URL override가 없습니다. 가짜 공통 apiBaseUrl을 만들지 말고 각 런타임의 네이티브 escape hatch를 사용하십시오.

Claude CodeCodexOpenCodeGrok BuildCursor
settings--settings JSON에 병합hooks.PreToolUse만 반영
settingSources--setting-sources
strictMcpConfig--strict-mcp-config
maxTurns--max-turns
disableSlashCommands--disable-slash-commands
serviceTier✗ 잘못 과금됨 (/fast는 모델, tier가 아님)priority / flex / batch
profilecodex app-servercodex exec--profile로 전달. runtime pool도 분리
config (-c key=value)codex app-servercodex exec에 전달. OpenRouter나 로컬 모델 서버 같은 OpenAI-compatible gateway는 이 경로를 사용
hooksHash / configHash✓ 생성된 hooks/config용 수동 runtime pool 분리 key
serverUrl✓ spawn 생략, 기존 데몬에 attach
modelProviderId{providerID, modelID}의 provider 절반
agent✓ build / plan / etc.
opencodeSessionId✓ 새 세션 생성 전 기존 세션 resume
logLevel✓ SDK daemon config의 logLevel
opencodeConfigHash✓ runtime pool 분리 key

Cursor는 현재 SNA-specific providerOptions 키가 없습니다. CLI가 받는 모든 노브(model, reasoning, mode)는 타입화된 SpawnOptions 필드에 매핑됩니다. 미래에 Cursor가 새 플래그를 추가하면 여기에 등장합니다.

런타임 간 history replay

모든 런타임은 새 세션에 이전 대화를 넣는 자체 메커니즘을 갖고 있습니다. SNA의 정규화 CanonicalBlock[] history는 어댑터 계층에서 네이티브 형태로 번역됩니다.

런타임메커니즘어댑터
Claude CodeAnthropic 포맷 JSONL 작성 → --resume <filepath>로 경로 전달history/claude-code.ts
Codexthread/resume(history=ResponseItems) JSON-RPChistory/codex.ts
OpenCode첫 prompt에 history prelude TextPartInput과 embed용 file part를 prependhistory/opencode.ts
Grok Build / Cursorsession/prompt에서 transcript를 단일 ACP resource content block으로 직렬화core/providers/acp/base.ts 공유 (serializeHistoryForAcp + buildHistoryPromptBlock)

ACP 경로는 네이티브 resume 경로와 의도적으로 다릅니다. probing 결과 ACP session/load는 UI 이벤트만 replay하고 모델 컨텍스트는 복원하지 않았습니다. 하위 세션 저장소는 벤더 내부 포맷(Grok은 xAI 내부, Cursor는 ~/.cursor/chats/ 아래 SQLite)이며 session load 시 재생성됩니다. 그래서 SNA는 저장 계층을 건드리지 않습니다. 대신 첫 turn에 prior transcript를 인라인으로 embed하고, 그 컨텍스트는 같은 세션의 후속 turn에서도 유지되므로 재주입은 필요 없습니다.

알려진 빈틈과 검증 한계

  • Plan mode는 런타임별로 다릅니다: Claude Code는 네이티브 plan mode를 지원하고, OpenCode는 plan agent로 매핑합니다. Codex는 plan을 기본 read-only sandbox처럼 처리하며, Grok Build / Cursor 어댑터는 아직 plan-mode flag를 연결하지 않습니다. ACP 런타임은 SNA 쪽 권한 게이트 동작은 인플레이스로 바꿀 수 있지만, 이것이 네이티브 plan mode와 같지는 않습니다.
  • ACP 런타임의 allowedTools / disallowedTools glob/패턴 매칭: 현재 auto-deny intercept는 도구 이름을 정확 문자열 동등성으로만 매치합니다. 실제 워크로드에서 필요해지면 glob 패턴(예: "shell.*")을 추가하는 것이 자연스럽습니다.
  • 이미지 입력 최소 크기: Grok Build는 8×8 미만, 총 512px 미만 이미지 거부(정사각형은 23×23 이상 필요); Cursor는 16×16부터 허용. 둘 다 서버 측에서 검증하므로 SNA는 image block을 그대로 전달합니다. 썸네일 같은 작은 이미지를 다루는 앱은 Grok 호환을 위해 32×32 이상으로 upscale해야 합니다.

목차