# RAWP-DPS 1.0: Data Plane Streaming Specification **Remote Agent Wire Protocol — Data Plane Streaming** | 항목 | 값 | | ---- | ---------------------- | | 상태 | Draft | | 버전 | 1.0.1 | | 날짜 | 2026-03-16 | | 호환 | RAWP 1.0.2 (제어 평면) | --- ## 1. 개요 (Introduction) RAWP-DPS 1.0은 RAWP 1.0의 데이터 평면(§6)을 대체하는 독립 스트리밍 규격이다. RAWP-DPS 1.0은 다음 목표를 달성하기 위해 설계되었다: 1. **완전성(Completeness)**: Claude Code를 포함한 에이전트 코딩 도구의 모든 출력 형태를 단일 프레임 규격으로 포맷화한다. 2. **범용성(Generality)**: 특정 에이전트에 종속되지 않으며, 임의의 에이전트 도구 체계를 수용할 수 있는 확장 가능한 타입 시스템을 제공한다. 3. **호환성(Compatibility)**: 알 수 없는 타입의 정방향 호환성(Forward Compatibility)을 구조적으로 보장한다. ### 1.1. 요구사항 표기 규약 본 문서의 "MUST", "MUST NOT", "REQUIRED", "SHOULD", "SHOULD NOT", "OPTIONAL"은 RFC 2119를 따른다. ### 1.2. RAWP 1.0과의 관계 RAWP-DPS 1.0은 RAWP 1.0의 제어 평면(§1–§5, §7–§8)을 변경하지 않는다. 세션 초기화(§5.1), WebSocket 수립(§5.3), 보안 규격(§7)은 그대로 유지되며, 본 규격은 WSS 연결 수립 **이후** 교환되는 데이터 프레임의 포맷만을 재정의한다. ### 1.3. RAWP-1.0-Legacy (지원 종료) RAWP-1.0-Legacy는 지원이 종료되었다. 모든 구현은 본 규격(`rawp-dps-1.0`)만을 사용해야 한다 (MUST). ### 1.4. 용어 정의 본 문서에서 추가로 사용하는 용어: | 용어 | 정의 | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Agent Session | RAWP 1.0.2 §1.2에서 정의. 에이전트의 논리적 생명주기 단위. 본 규격의 모든 Frame은 `session_id` 필드를 통해 특정 Agent Session에 귀속된다. 본 문서에서 "세션"은 별도 명시가 없는 한 Agent Session을 의미한다. | | WSS Connection | RAWP 1.0.2 §1.2에서 정의. Agent Session에 바인딩된 WebSocket 전송 채널. 본 규격의 Frame은 WSS Connection을 통해 송수신된다. 하나의 Agent Session에 복수의 WSS Connection이 동시에 존재할 수 있다. | | Frame | WSS Connection을 통해 송수신되는 하나의 JSON 메시지 단위 | | Content Block | 단일 Frame 내에서 독립적 의미를 가지는 출력 조각 | | Turn | 에이전트의 단일 추론-행동 사이클 (프롬프트 수신 → 도구 사용 → 응답 완료). 하나의 Agent Session 내에서 순차적으로 발생한다. | | Tool Invocation | 에이전트가 외부 도구를 호출하는 행위와 그 결과의 쌍 | | Namespace | 이벤트 타입의 계층적 분류 체계 (`agent.`, `tool.`, `session.` 등) | --- ## 2. Envelope 구조 (Frame Envelope) ### 2.1. 공통 Envelope 송수신되는 모든 WSS 프레임은 다음 JSON 구조를 준수해야 한다 (MUST). ```json { "v": 1, "type": "String (필수, 네임스페이스 기반 이벤트 타입)", "message_id": "String (필수, UUID v4)", "timestamp": "String (필수, ISO 8601, 밀리초 정밀도 권장)", "session_id": "String (필수, 소속 세션 식별자)", "turn_id": "String (선택, 현재 Turn 식별자, UUID v4)", "parent_id": "String (선택, 이 메시지가 응답하는 대상 message_id)", "metadata": {}, "payload": {} } ``` ### 필드 상세 | 필드 | 필수 | 설명 | | ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `v` | MUST | Envelope 버전. 본 규격에서는 항상 정수 `1`. 수신자는 자신이 지원하지 않는 `v` 값을 수신하면 해당 프레임을 무시하고 `session.error`로 응답해야 한다 (MUST). | | `type` | MUST | 네임스페이스 기반 이벤트 타입. §3에서 정의. | | `message_id` | MUST | 프레임 고유 식별자. 중복 수신 시 멱등성(Idempotency) 판단 기준. | | `timestamp` | MUST | 발신 시각. ISO 8601. 밀리초 정밀도 권장 (SHOULD). | | `session_id` | MUST | Agent Session 식별자 (UUID v4). RAWP 1.0.2 §5.1에서 발급. WSS Connection이 아닌 Agent Session을 식별한다. | | `turn_id` | SHOULD | 현재 에이전트 Turn의 식별자. 동일 Turn 내 모든 프레임은 같은 `turn_id`를 공유. | | `parent_id` | OPTIONAL | 요청-응답 상관 관계. `agent.prompt.request`에 대한 `agent.prompt.delta`는 해당 요청의 `message_id`를 `parent_id`로 참조. | | `metadata` | OPTIONAL | 확장 및 디버깅용 자유 형식 객체. 빈 객체 `{}` 허용. | | `payload` | MUST | 이벤트 타입별 페이로드. §3에서 각 타입별로 정의. | ### 2.2. 파싱 규약 - **Postel's Law**: 수신자는 `payload` 내 알 수 없는 키를 무시해야 한다 (MUST). - **Unknown Type**: 수신자는 자신이 처리할 수 없는 `type`을 수신하면 해당 프레임을 무시하고, 로그에 기록해야 한다 (MUST). 에러를 발생시키거나 연결을 종료해서는 안 된다 (MUST NOT). - **Envelope 버전**: `v` 필드가 지원 범위를 벗어나면 프레임을 무시하고 `session.error`를 발송해야 한다 (MUST). ### 2.3. 프레임 순서 보장 WSS는 메시지 순서를 보장하므로, 동일 연결 내 프레임은 발신 순서대로 수신됨을 전제한다. 단, 재연결(reattach) 후 `session.history` 프레임을 통해 과거 이력이 재전송될 때는 `timestamp`와 `message_id` 기준으로 순서를 재구성해야 한다 (MUST). ### 2.4. 다중 마스터 환경 (Multi-Master Fan-out) 클라이언트가 복수의 마스터 서버에 동시 등록된 환경(RAWP 1.0.2 §3 참조)에서의 DPS 프레임 분배 규칙을 정의한다. **브로드캐스트 원칙**: 클라이언트는 하나의 Agent Session에서 발생하는 모든 DPS 프레임을 해당 세션에 연결된 **모든** WSS Connection으로 브로드캐스트해야 한다 (MUST). 여기에는 세션을 생성한 마스터의 WSS뿐 아니라, 다른 마스터가 동일 세션에 연결한 WSS도 포함된다. - 에이전트 출력 프레임(`agent.*`, `tool.*`, `session.*`)은 해당 세션에 바인딩된 모든 WSS Connection에 전송해야 한다 (MUST). - 마스터가 발송한 `control.*` 프레임(예: `control.prompt.request`)도 해당 세션에 바인딩된 다른 모든 WSS Connection에 중계해야 한다 (MUST). 이를 통해 마스터 A가 전송한 프롬프트를 마스터 B도 수신하여 사용자에게 표시할 수 있다. - 로컬 UI WSS Connection(RAWP 1.0.2 §10.6)도 브로드캐스트 대상에 포함된다 (MUST). - 로컬 세션(RAWP 1.0.2 §10, `origin: "local"`)의 DPS 프레임은 마스터에 전송하지 않는다 (MUST NOT). 로컬 UI WSS에만 전달한다. - `control.*` 프레임의 중계 시 `message_id` 기반 멱등성 처리를 수행하여 발신자에게 자신의 프레임이 되돌아오는 것을 방지해야 한다 (MUST). 즉, 프레임을 발송한 WSS Connection에는 해당 프레임을 재전송하지 않는다. --- ## 3. 이벤트 타입 시스템 (Event Type System) ### 3.1. 네임스페이스 구조 이벤트 타입은 점(`.`)으로 구분된 계층 구조를 따른다: ``` {namespace}.{category}.{action} ``` 본 규격은 다음 최상위 네임스페이스를 정의한다: | 네임스페이스 | 방향 | 설명 | | ------------ | --------------- | ----------------------------------- | | `agent` | Client → Master | 에이전트의 출력, 상태, 요청 | | `control` | Master → Client | 사용자 입력, 제어 명령 | | `tool` | Client → Master | 도구 호출 및 결과 | | `session` | 양방향 | 세션 수준 이벤트 (이력, 압축, 에러) | #### 3.1.1. 확장 네임스페이스 본 규격에 정의되지 않은 커스텀 이벤트는 `x-{vendor}` 접두사를 사용해야 한다 (MUST). ``` x-mycompany.custom.event_name ``` 수신자는 `x-` 접두 네임스페이스를 알 수 없는 타입으로 취급하여 무시할 수 있다. ### 3.2. 레거시 타입 매핑 (지원 종료) RAWP-1.0-Legacy 이벤트 타입과의 매핑은 지원이 종료되었다. 본 규격의 이벤트 타입만을 사용해야 한다 (MUST). --- ## 4. 에이전트 출력 이벤트 (`agent.*`) ### 4.1. 텍스트 스트리밍 에이전트의 자연어 응답을 실시간으로 전달한다. #### 4.1.1. `agent.text.delta` (Client → Master) ```json { "type": "agent.text.delta", "payload": { "content_index": "Number (필수, 0-based, 현재 Content Block 순번)", "text": "String (필수, 텍스트 조각)" } } ``` #### 4.1.2. `agent.text.done` (Client → Master) 텍스트 스트리밍이 완료되었음을 알린다. 하나의 Content Block이 종료될 때마다 발송한다. ```json { "type": "agent.text.done", "payload": { "content_index": "Number (필수)", "full_text": "String (선택, 전체 텍스트. 수신 측 검증용)" } } ``` ### 4.2. 추론/사고 스트리밍 (Thinking) 에이전트의 내부 추론 과정을 전달한다. Extended Thinking, Chain-of-Thought 등에 사용. #### 4.2.1. `agent.thinking.delta` (Client → Master) ```json { "type": "agent.thinking.delta", "payload": { "content_index": "Number (필수)", "text": "String (필수, 사고 조각)", "redacted": "Boolean (선택, 기본값 false. 보안상 수정된 내용 여부)" } } ``` #### 4.2.2. `agent.thinking.done` (Client → Master) ```json { "type": "agent.thinking.done", "payload": { "content_index": "Number (필수)" } } ``` ### 4.3. 상호작용 요청 (Interaction Request) 에이전트가 사용자의 승인 또는 선택을 요구할 때 발송한다. #### 4.3.1. `agent.interaction.request` (Client → Master) ```json { "type": "agent.interaction.request", "payload": { "interaction_id": "String (필수, UUID v4)", "interaction_type": "String (필수, 아래 열거 값)", "question_text": "String (필수, 사용자에게 표시할 질문)", "timeout_sec": "Number (필수, 응답 대기 한계 시간)", "options": [ { "value": "String (필수, 시스템 전달용 식별자)", "label": "String (필수, 사용자 노출용 텍스트)", "description": "String (선택, 부가 설명)", "risk_level": "String (선택, 'safe' | 'moderate' | 'dangerous')" } ], "context": { "tool_name": "String (선택, 승인이 필요한 도구명)", "tool_input": "Object (선택, 도구 입력 요약)", "affected_paths": ["String (선택, 영향받는 파일 경로 목록)"] }, "default_value": "String (선택, 타임아웃 시 자동 선택될 값)" } } ``` **`interaction_type` 열거 값:** | 값 | 설명 | | -------------- | -------------------------------------------------- | | `YN` | 예/아니오 이진 선택 | | `SELECT` | 다중 옵션 중 단일 선택 | | `MULTI_SELECT` | 다중 옵션 중 복수 선택 | | `TEXT_INPUT` | 사용자 자유 텍스트 입력 | | `PERMISSION` | 도구 실행 권한 승인 (Claude Code의 도구 승인 흐름) | ### 4.4. 에러 보고 #### 4.4.1. `agent.error` (Client → Master) ```json { "type": "agent.error", "payload": { "error_code": "String (필수)", "message": "String (필수, 휴먼 리더블 에러)", "severity": "String (필수, 'fatal' | 'error' | 'warning')", "recoverable": "Boolean (필수, 에이전트가 자체 복구 가능한지 여부)", "details": { "module": "String (선택)", "trace": "String (선택, 스택 트레이스)", "related_tool": "String (선택, 에러 유발 도구명)", "related_invocation_id": "String (선택, 에러 유발 도구 호출 ID)" } } } ``` `severity`가 `fatal`이면 클라이언트는 프로세스를 정리해야 한다 (MUST). `warning`이면 세션을 유지한다. ### 4.5. 파일 전송 #### 4.5.1. `agent.file.transfer` (양방향) 바이너리는 WSS를 태우지 않고 HTTP 스토리지 URL로 교환한다. ```json { "type": "agent.file.transfer", "payload": { "transfer_id": "String (필수, UUID v4)", "direction": "String (필수, 'upload' | 'download')", "file_url": "String (필수, 스토리지 URI)", "file_name": "String (필수)", "mime_type": "String (필수, IANA MIME 타입)", "size_bytes": "Number (필수)", "checksum": "String (선택, SHA-256 해시)", "context": { "workspace_relative_path": "String (선택, 워크스페이스 기준 상대 경로)", "related_tool_invocation_id": "String (선택)" } } } ``` ### 4.6. 에이전트 상태 변경 #### 4.6.1. `agent.state.changed` (Client → Master) 에이전트의 내부 상태 전이를 마스터에 통보한다. ```json { "type": "agent.state.changed", "payload": { "previous_state": "String (필수)", "current_state": "String (필수)", "reason": "String (선택, 전이 사유)" } } ``` **예약된 상태 값**: `idle`, `thinking`, `tool_calling`, `awaiting_input`, `error` 확장 상태는 `x-{vendor}.{state_name}` 형식을 따른다. --- ## 5. 도구 호출 이벤트 (`tool.*`) 에이전트가 도구(Tool)를 호출하고 그 결과를 보고하는 이벤트 군이다. Claude Code의 Bash, Read, Write, Edit, MultiEdit, Glob, Grep, LS, WebFetch, WebSearch, NotebookRead, NotebookEdit, TodoRead, TodoWrite, Task(Subagent) 등 **모든** 도구 호출을 통일된 구조로 표현한다. ### 5.1. 설계 원칙 도구 호출 이벤트는 **도구 비종속(Tool-Agnostic)** 설계를 따른다. 즉, 프로토콜은 도구의 이름, 입력 스키마, 출력 형태를 사전에 정의하지 않으며, 임의의 도구를 동일한 Envelope으로 수용한다. 도구별 의미론은 `tool_name`과 `input`/`output` 필드의 내용으로 결정된다. ### 5.2. 도구 호출 요청 #### 5.2.1. `tool.invoke.request` (Client → Master) 에이전트가 도구를 호출하려 함을 마스터에 통보한다. 승인이 필요한 도구의 경우 `agent.interaction.request`가 선행한다. ```json { "type": "tool.invoke.request", "payload": { "invocation_id": "String (필수, UUID v4, 이 호출의 고유 식별자)", "tool_name": "String (필수, 도구명. 예: 'Bash', 'Read', 'Edit', 'TodoWrite')", "tool_version": "String (선택, 도구 버전)", "input": "Object (필수, 도구별 입력 파라미터)", "parallel_group_id": "String (선택, 병렬 호출 그룹 ID. 동시에 발송되는 도구 호출을 그룹화)" } } ``` ### 5.3. 도구 실행 결과 #### 5.3.1. `tool.invoke.result` (Client → Master) 도구 실행이 완료된 후 결과를 보고한다. ```json { "type": "tool.invoke.result", "payload": { "invocation_id": "String (필수, 대응하는 tool.invoke.request의 invocation_id)", "tool_name": "String (필수)", "status": "String (필수, 'success' | 'error' | 'timeout' | 'cancelled')", "output": "Any (필수, 도구별 출력. 구조는 도구 의존적)", "output_type": "String (필수, 출력 해석 힌트. 아래 열거 값)", "duration_ms": "Number (선택, 실행 소요 시간 밀리초)", "truncated": "Boolean (선택, 출력이 잘렸는지 여부)" } } ``` **`output_type` 열거 값:** | 값 | 설명 | 대표 도구 | | -------------- | --------------------------------------------- | ------------------------- | | `text` | 평문 텍스트 | Bash stdout, Grep 결과 | | `structured` | JSON 구조체 | TodoRead, Agent Discovery | | `file_content` | 파일 본문 (라인 번호 포함 가능) | Read, NotebookRead | | `file_list` | 파일 경로 목록 | Glob, LS | | `diff` | 변경사항 차분 (unified diff 또는 구조화 diff) | Edit, MultiEdit 결과 | | `web_content` | 웹 페이지/검색 결과 | WebFetch, WebSearch | | `empty` | 출력 없음 (부수효과만 발생) | Write, NotebookEdit | | `binary_ref` | 바이너리 참조 (agent.file.transfer 연계) | 이미지 생성 등 | | `agent_result` | 서브에이전트 실행 결과 | Task(Subagent) | #### 5.3.2. `tool.invoke.stream` (Client → Master) 장시간 실행 도구의 중간 출력을 스트리밍한다 (예: Bash 명령의 실시간 stdout). ```json { "type": "tool.invoke.stream", "payload": { "invocation_id": "String (필수)", "tool_name": "String (필수)", "stream_type": "String (필수, 'stdout' | 'stderr' | 'progress')", "chunk": "String (필수, 출력 조각)", "sequence": "Number (필수, 0-based 순번, 순서 재구성용)" } } ``` #### 5.3.3. `output_type` 결정 규칙 `tool.invoke.result`의 `output_type` 값은 다음 우선순위에 따라 결정해야 한다 (MUST): 1. **도구 카탈로그 사전 선언**: `tool.catalog.publish`(§5.4.1)에서 해당 도구의 `output_type`이 사전 선언된 경우, 해당 값을 사용한다. 이것이 최우선이다. 2. **도구 카테고리 기반 기본값**: 사전 선언이 없으면, `tool.catalog.publish`의 `category` 필드에 따른 기본값을 적용한다: | `category` | 도구 동작 | 기본 `output_type` | | ----------------- | --------------------------------------- | ---------------------------------- | | `filesystem` | 파일 쓰기/수정 (Write, Edit, MultiEdit) | `diff` (변경 발생 시) 또는 `empty` | | `filesystem` | 파일 읽기 (Read, NotebookRead) | `file_content` | | `filesystem` | 파일 탐색 (Glob, LS) | `file_list` | | `shell` | 셸 명령 실행 (Bash) | `text` | | `search` | 검색 (Grep, WebSearch) | `text` 또는 `file_list` | | `web` | 웹 콘텐츠 접근 (WebFetch) | `web_content` | | `task_management` | 태스크 관리 (TodoRead, TodoWrite) | `structured` | | `agent` | 서브에이전트 위임 (Task) | `agent_result` | 3. **폴백**: 위 규칙으로 결정할 수 없으면 `text`로 설정한다 (MUST). `output_type`은 수신 측(마스터, 엣지 노드)이 결과를 적절한 뷰어로 렌더링하기 위한 힌트이다. 각 `output_type`의 렌더링 규칙은 RAWP-CRS 1.0.1 §4.5를 참조한다. ### 5.4. 도구 사용 가능 목록 고지 (Tool Catalog) #### 5.4.1. `tool.catalog.publish` (Client → Master) 에이전트가 현재 세션에서 사용 가능한 도구 목록을 마스터에 고지한다. 세션 시작 직후 또는 도구 구성 변경 시 발송한다. ```json { "type": "tool.catalog.publish", "payload": { "tools": [ { "name": "String (필수, 도구명)", "version": "String (선택)", "description": "String (필수, 도구 설명)", "input_schema": "Object (선택, JSON Schema)", "output_type": "String (선택, 기본 output_type)", "requires_approval": "Boolean (선택, 실행 전 승인 필요 여부)", "category": "String (선택, 도구 분류. 아래 열거 값)", "status": "String (선택, 기본값 'available'. 아래 열거 값)", "status_description": "String (선택, 현재 상태에 대한 사유 설명. 없으면 UI에 상태 사유를 표시하지 않는다)" } ], "diagnostics": [ { "severity": "String (필수, 'info' | 'warning' | 'error' | 'fatal')", "tool_name": "String (선택, 특정 도구에 관한 진단이면 해당 도구명)", "code": "String (필수, 진단 코드. 예: 'TOOL_UNAVAILABLE', 'VERSION_MISMATCH', 'DEPENDENCY_MISSING')", "message": "String (필수, 휴먼 리더블 진단 메시지)" } ] } } ``` **`category` 열거 값:** | 값 | 설명 | 예시 | | ----------------- | ----------------- | -------------------------------------- | | `filesystem` | 파일 시스템 조작 | Read, Write, Edit, MultiEdit, Glob, LS | | `shell` | 셸 명령 실행 | Bash | | `search` | 검색 | Grep, WebSearch | | `web` | 웹 콘텐츠 접근 | WebFetch | | `notebook` | 노트북 조작 | NotebookRead, NotebookEdit | | `task_management` | 태스크/TODO 관리 | TodoRead, TodoWrite | | `agent` | 서브에이전트 위임 | Task | | `planning` | 계획 모드 | EnterPlanMode, ExitPlanMode | | `mcp` | MCP 서버 도구 | 동적 MCP 도구 | | `custom` | 사용자 정의 도구 | 커스텀 도구 | **`status` 열거 값:** | 값 | 설명 | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `available` | 도구가 정상 동작 가능. 기본값. `status` 필드가 생략되면 `available`로 간주한다. | | `unavailable` | 도구를 사용할 수 없음. 의존성 누락, 권한 부족, 실행 파일 미설치 등. 마스터는 이 도구에 대한 `tool.invoke.request` 발송을 차단해야 한다 (MUST). | | `degraded` | 도구가 동작하지만 일부 기능이 제한됨. 사용은 허용되나 사용자에게 제한 사항을 고지해야 한다 (SHOULD). | `status`가 `unavailable` 또는 `degraded`인 도구는 `status_description`에 사유를 명시하는 것을 권장한다 (SHOULD). 예: `"실행 파일을 찾을 수 없음"`, `"API 키 미설정"`. `status_description`이 없으면 UI에 상태 사유를 표시하지 않는다. 도구 구성이 변경되어 `status`가 전이되면(예: 의존성 설치로 `unavailable` → `available`) 클라이언트는 `tool.catalog.publish`를 재발송해야 한다 (MUST). **`diagnostics` 필드 규칙:** - `diagnostics` 배열은 선택적이다 (OPTIONAL). 진단 사항이 없으면 생략하거나 빈 배열을 전송할 수 있다. - `severity: "fatal"` 진단이 포함된 도구는 마스터가 해당 도구의 사용을 차단해야 한다 (MUST). 해당 도구에 대한 `tool.invoke.request`가 수신되면 `session.error`를 반환한다. - `severity: "error"` 진단이 포함된 도구는 마스터가 사용자에게 경고를 표시해야 한다 (SHOULD). 도구 사용 자체는 허용된다. - `severity: "warning"` 또는 `"info"` 진단은 로그 기록 또는 UI 표시용이며, 도구 사용에 제한을 두지 않는다. - `tool_name`이 생략된 진단은 도구 카탈로그 전체에 대한 진단이다 (예: 에이전트 초기화 경고). --- ## 6. 제어 이벤트 (`control.*`) 마스터 서버가 클라이언트(에이전트)로 전달하는 제어 명령이다. ### 6.1. 프롬프트/입력 전달 #### 6.1.1. `control.prompt.request` (Master → Client) ```json { "type": "control.prompt.request", "payload": { "prompt_text": "String (필수, 사용자 입력. 파일 참조 토큰 포함 가능, §16.2 참조)", "input_type": "String (선택, 기본값 'text'. 아래 열거 값)", "context_files": [ { "file_url": "String (필수, 참조 파일 URL)", "file_name": "String (필수)", "mime_type": "String (선택)", "intent": "String (선택, 'reference' | 'edit_target' | 'context')" } ], "file_references": [ { "ref_id": "String (필수, prompt_text 내 토큰의 ref_id와 일치)", "path": "String (필수, workspace 기준 상대 경로)", "name": "String (필수, 파일명)", "extension": "String (선택, 확장자)", "mime_type": "String (선택, IANA MIME 타입)", "size_bytes": "Number (선택)", "workspace_relative_path": "String (필수, 워크스페이스 기준 상대 경로)" } ], "config_overrides": { "output_format": "String (선택, 'text' | 'json' | 'stream-json')", "json_schema": "Object (선택, 구조화 출력 스키마)", "max_turns": "Number (선택, 최대 Turn 수 제한)", "allowed_tools": ["String (선택, 허용 도구 목록)"], "denied_tools": ["String (선택, 금지 도구 목록)"] } } } ``` **`input_type` 열거 값:** | 값 | 설명 | | --------------- | ------------------------------------- | | `text` | 일반 텍스트 프롬프트 | | `slash_command` | 슬래시 명령 (예: `/compact`, `/cost`) | | `continuation` | 이전 Turn에 이어지는 후속 입력 | | `piped_stdin` | 파이프된 표준 입력 (headless 모드) | #### 6.1.2. `control.prompt.cancel` (Master → Client) 진행 중인 에이전트 Turn을 취소한다. 수신 시 클라이언트는 현재 실행 중인 도구를 가능한 한 빨리 중단하고, `agent.text.done`(is_cancelled: true)을 발송해야 한다 (MUST). ```json { "type": "control.prompt.cancel", "payload": { "target_turn_id": "String (필수, 취소 대상 turn_id)", "reason": "String (선택, 'user_cancelled' | 'timeout' | 'quota_exceeded')" } } ``` ### 6.2. 상호작용 응답 #### 6.2.1. `control.interaction.response` (Master → Client) ```json { "type": "control.interaction.response", "payload": { "interaction_id": "String (필수, 대응하는 agent.interaction.request의 interaction_id)", "selected_value": "String | [String] (필수, 단일 선택 또는 복수 선택 값)", "text_input": "String (선택, TEXT_INPUT 타입의 사용자 자유 입력)" } } ``` #### 6.2.2. `control.interaction.timeout` (Master → Client) ```json { "type": "control.interaction.timeout", "payload": { "interaction_id": "String (필수)" } } ``` ### 6.3. 세션 제어 명령 #### 6.3.1. `control.session.compact` (Master → Client) 마스터가 컨텍스트 압축을 요청한다 (사용자의 `/compact` 명령 등). ```json { "type": "control.session.compact", "payload": { "instruction": "String (선택, 압축 시 보존할 내용 지시)", "target_token_ratio": "Number (선택, 0.0–1.0, 목표 압축 비율)" } } ``` **자동 압축 트리거 조건:** 에이전트는 `session.capabilities`에서 `context_compaction: true`를 고지한 경우에 한하여 자동 압축을 수행할 수 있다 (MAY). 자동 압축의 트리거 조건은 다음을 따른다: - `context_window.utilization` ≥ 0.80이면 압축을 고려해야 한다 (SHOULD). - `context_window.utilization` ≥ 0.95이면 압축을 수행해야 한다 (MUST). - 자동 압축 시 `session.compacted`(§7.2.1)의 `trigger` 필드는 `"auto"`로 설정한다. - 마스터의 `control.session.compact` 없이 에이전트가 자체적으로 수행한다. **`target_token_ratio` 필드 의미:** - `target_token_ratio`는 압축 후 목표 컨텍스트 윈도우 사용률을 의미한다. 값 범위: `0.0`(최대 압축) ~ `1.0`(압축하지 않음). - 예: `target_token_ratio: 0.5`이면 컨텍스트 윈도우의 50% 이하로 압축을 목표로 한다. - 이 값은 best-effort이다. 에이전트가 정확한 비율 달성을 보장하지 않는다. - 필드가 생략되면 에이전트의 기본 압축 전략을 따른다. **압축 미지원 시 동작:** `session.capabilities`에서 `context_compaction: false`이거나 미고지인 클라이언트가 `control.session.compact`를 수신하면, `session.error` (error_code: `"COMPACTION_NOT_SUPPORTED"`, fatal: `false`)를 반환해야 한다 (MUST). #### 6.3.2. `control.mode.switch` (Master → Client) 에이전트 동작 모드를 전환한다. ```json { "type": "control.mode.switch", "payload": { "mode": "String (필수, 아래 열거 값)", "config": "Object (선택, 모드별 추가 설정)" } } ``` **`mode` 열거 값:** | 값 | 설명 | | --------- | -------------------------------------------- | | `default` | 기본 실행 모드 (읽기/쓰기/실행 모두 가능) | | `plan` | 계획 모드 (읽기/검색만 허용, 쓰기/실행 차단) | | `review` | 코드 리뷰 모드 | **모드별 도구 사용 제한:** | 모드 | 허용 도구 카테고리 | 금지 도구 카테고리 | | --------- | -------------------------------------------------------------- | --------------------------------------------------- | | `default` | 모든 카테고리 | 없음 | | `plan` | `search`, `filesystem`(읽기 전용: Read, Glob, LS, Grep), `web` | `filesystem`(쓰기: Write, Edit, MultiEdit), `shell` | | `review` | `search`, `filesystem`(읽기 전용), `web` | `filesystem`(쓰기), `shell` | 금지된 카테고리의 도구가 에이전트에 의해 호출 시도되면, 클라이언트는 해당 `tool.invoke.request`를 차단하고 `tool.invoke.result` (status: `"error"`, output: `"Tool not allowed in current mode"`)를 에이전트에 반환해야 한다 (MUST). **모드 전환 규칙:** - Turn 진행 중(`session.turn.start` 수신 후 `session.turn.end` 수신 전)에는 모드를 전환할 수 없다 (MUST NOT). 진행 중에 `control.mode.switch`를 수신하면 `session.error` (error_code: `"MODE_SWITCH_DURING_TURN"`, fatal: `false`)를 반환해야 한다 (MUST). - 모드 전환 성공 시 `agent.state.changed` (reason: `"mode_switch"`)를 발송해야 한다 (MUST). - 전환 후 첫 Turn의 `session.turn.start`에 변경된 `mode` 필드를 포함해야 한다 (MUST). **미지원 시 동작:** `session.capabilities`에서 `plan_mode: false`이거나 미고지인 클라이언트가 `control.mode.switch`를 수신하면, `session.error` (error_code: `"MODE_NOT_SUPPORTED"`, fatal: `false`)를 반환해야 한다 (MUST). 마스터는 `plan_mode` 미지원 클라이언트에 `control.mode.switch`를 발송해서는 안 된다 (MUST NOT). --- ## 7. 세션 이벤트 (`session.*`) ### 7.1. 이력 복구 #### 7.1.1. `session.history` (Client → Master) 재연결(reattach) 성공 직후 선제적으로 발송한다. ```json { "type": "session.history", "payload": { "frames": [ "Object[] (필수, 단절 동안 버퍼에 쌓인 과거 프레임의 배열. 각 요소는 본 규격의 Envelope 전체)" ], "buffer_status": { "policy_applied": "String (필수, 'RING' | 'DROP' | 'NONE')", "truncated": "Boolean (필수, 데이터 유실 발생 여부)", "lost_frame_count": "Number (선택, 유실된 프레임 수 추정치)", "buffer_high_watermark": "Number (선택, 버퍼 최대 사용량 바이트)" }, "last_sync_timestamp": "String (필수, ISO 8601, 마지막 동기화 시각)", "last_message_id": "String (필수, 마지막 동기화 message_id)" } } ``` `buffer_status.truncated`가 `true`이면 마스터는 데이터 불완전성을 사용자에게 고지해야 한다 (MUST). ### 7.2. 컨텍스트 압축 보고 #### 7.2.1. `session.compacted` (Client → Master) 에이전트가 자동 또는 수동 컨텍스트 압축을 수행한 후 결과를 보고한다. ```json { "type": "session.compacted", "payload": { "summary": "String (필수, 압축 요약 텍스트)", "previous_token_count": "Number (필수, 압축 전 토큰 수)", "current_token_count": "Number (필수, 압축 후 토큰 수)", "preserved_elements": { "files_modified": ["String (선택, 수정된 파일 경로)"], "decisions_made": ["String (선택, 주요 결정 사항)"], "errors_encountered": ["String (선택, 미해결 에러)"], "active_todos": ["Object (선택, 활성 TODO 항목)"] }, "trigger": "String (필수, 'auto' | 'manual' | 'requested')" } } ``` ### 7.3. 사용량 지표 #### 7.3.1. `session.usage` (양방향, 주로 Client → Master) 텍스트 생성 완료 시점(`agent.text.done`) 또는 Turn 종료 시 반드시 동반 발송해야 한다 (MUST). ```json { "type": "session.usage", "payload": { "turn_id": "String (선택, 사용량이 소속된 Turn)", "token_usage": { "input_tokens": "Number (필수)", "output_tokens": "Number (필수)", "cache_read_tokens": "Number (선택, 캐시 히트 토큰)", "cache_write_tokens": "Number (선택, 캐시 기록 토큰)", "thinking_tokens": "Number (선택, 사고 토큰)" }, "cost_usage": { "limit": "Number (필수, -1은 무제한)", "used": "Number (필수)", "unit": "String (필수, 'USD' 등)" }, "message_usage": { "limit": "Number (필수)", "used": "Number (필수)", "unit": "String (필수, 'COUNT')" }, "context_window": { "capacity": "Number (선택, 최대 토큰 윈도우)", "used": "Number (선택, 현재 사용 중 토큰)", "utilization": "Number (선택, 0.0–1.0, 사용률)" }, "time_to_reset": "String (필수, ISO 8601)" } } ``` ### 7.4. 세션 에러 #### 7.4.1. `session.error` (양방향) 세션 수준의 프로토콜 에러를 보고한다. 에이전트 수준 에러(`agent.error`)와 구분된다. ```json { "type": "session.error", "payload": { "error_code": "String (필수)", "message": "String (필수)", "fatal": "Boolean (필수, true 시 세션 종료 절차 개시)" } } ``` ### 7.5. Turn 라이프사이클 #### 7.5.1. `session.turn.start` (Client → Master) 에이전트가 새 Turn을 시작함을 알린다. ```json { "type": "session.turn.start", "payload": { "turn_id": "String (필수, UUID v4)", "turn_index": "Number (필수, 0-based 순번)", "mode": "String (선택, 현재 동작 모드)" } } ``` #### 7.5.2. `session.turn.end` (Client → Master) Turn이 완료되었음을 알린다. 이 이벤트 직후 `session.usage`가 동반되어야 한다 (MUST). ```json { "type": "session.turn.end", "payload": { "turn_id": "String (필수)", "stop_reason": "String (필수, 아래 열거 값)", "tool_invocation_count": "Number (선택, 이 Turn에서의 도구 호출 횟수)" } } ``` **`stop_reason` 열거 값:** | 값 | 설명 | | ---------------- | ------------------------------- | | `end_turn` | 정상 완료 | | `max_tokens` | 최대 토큰 도달 | | `cancelled` | 사용자/시스템 취소 | | `error` | 에러로 인한 중단 | | `tool_use` | 도구 사용 후 Turn 전환 (멀티턴) | | `awaiting_input` | 사용자 입력 대기 | #### 7.5.3. 세션 종료 시 진행 중 항목 처리 세션이 종료(RAWP 1.0.2 §5.2 `DELETE /v1/session/{session_id}` 또는 `session.error` (fatal: `true`))될 때, 진행 중인 Turn이 존재하면 다음 순서로 정리 프레임을 발송해야 한다 (MUST): 1. 모든 미완료 도구 호출에 대해 `tool.invoke.result` (status: `"cancelled"`)를 발송한다. 2. `session.turn.end` (stop_reason: `"error"`)를 발송한다. 3. WSS 연결을 종료한다. 위 프레임들은 WSS 연결 종료 전에 모두 발송 완료되어야 한다 (MUST). 진행 중인 Turn이 없으면 즉시 WSS를 종료한다. ### 7.6. 세션 이름 변경 #### 7.6.1. `session.renamed` (양방향) ```json { "type": "session.renamed", "payload": { "session_id": "String (필수, 대상 세션 식별자)", "name": "String (필수, 변경된 세션 이름)", "previous_name": "String (선택, 변경 전 세션 이름)", "renamed_by": "String (필수, 'edge' | 'client' | 'master')" } } ``` `renamed_by`는 이름 변경을 요청한 주체를 식별한다. 마스터는 이 이벤트를 해당 세션에 연결된 모든 엣지 WSS로 브로드캐스트해야 한다 (MUST). ### 7.7. 세션 삭제 #### 7.7.1. `session.deleted` (양방향) 세션이 삭제(RAWP 1.0.2 §5.2 또는 §10.1.3)되었음을 통보한다. 마스터 또는 로컬 UI가 연결된 모든 참여자에게 세션 종료를 알리는 데 사용된다. ```json { "type": "session.deleted", "payload": { "session_id": "String (필수, 삭제된 세션 식별자)", "reason": "String (필수, 'user_request' | 'master_request' | 'timeout' | 'error')", "deleted_by": "String (필수, 'edge' | 'client' | 'master' | 'system')" } } ``` **`reason` 열거 값:** | 값 | 설명 | | ---------------- | ------------------------------------------------------ | | `user_request` | 사용자가 UI에서 명시적으로 세션 삭제 | | `master_request` | 마스터 서버가 `DELETE /v1/session/{session_id}`로 종료 | | `timeout` | `reattach_window` 초과로 DETACHED 세션이 만료 | | `error` | 복구 불가능한 에러로 세션 강제 종료 | 이 이벤트는 §7.5.3의 종료 정리 프레임 이후, WSS 연결 종료 직전에 발송해야 한다 (MUST). 마스터는 이 이벤트를 해당 세션에 연결된 모든 엣지 WSS로 브로드캐스트해야 한다 (MUST). ### 7.8. WSS 연결 단절 복구 (Disconnect Recovery) WSS Connection이 비정상적으로 종료(네트워크 단절, 서버 셧다운 등)된 후 클라이언트 또는 엣지 노드의 미완료 메시지 정리(Finalization) 및 재연결 시 복구(Unfinalization) 절차를 정의한다. #### 7.8.1. Finalization (미완료 메시지 정리) WSS Connection의 `close` 또는 `error` 이벤트를 수신하면, 수신 측은 해당 세션의 미완료 상태를 다음 규칙에 따라 즉시 정리해야 한다 (MUST): | 미완료 상태 | Finalization 동작 | | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 스트리밍 중인 텍스트 블록 (`agent.text.delta` 수신 후 `agent.text.done` 미수신) | 스트리밍을 종료하고, 수신된 텍스트까지를 확정한다. | | 스트리밍 중인 사고 블록 (`agent.thinking.delta` 수신 후 `agent.thinking.done` 미수신) | 사고 스트리밍을 종료한다. | | 미완료 도구 호출 (`tool.invoke.request` 수신 후 `tool.invoke.result` 미수신) | 로컬에서 `tool.invoke.result` (status: `"cancelled"`, output: `"Connection lost"`)를 합성하여 쌍을 완성한다. 합성된 결과에는 `synthetic: true` 플래그를 포함해야 한다 (MUST). | | 미완료 Turn (`session.turn.start` 수신 후 `session.turn.end` 미수신) | Turn을 종료 처리한다. stop_reason은 `"disconnected"`로 간주한다. | | 대기 중인 상호작용 (`agent.interaction.request` 수신 후 `control.interaction.response` 미발송) | 상호작용 요청을 폐기하여 UI 블로킹을 해제한다. | | 에이전트 상태 (`thinking`, `tool_calling` 등 비유휴 상태) | `idle` 상태로 전환하여 입력 가능 상태를 복원한다. | **합성 블록의 식별**: 로컬에서 합성한 모든 블록(cancelled tool-result, 종료된 Turn 등)은 `synthetic: true` 플래그를 Envelope의 `metadata` 객체에 포함해야 한다 (MUST). 이 플래그는 §7.8.2의 Unfinalization에서 합성 블록을 정확히 식별하고 제거하기 위해 필요하다. ```json { "metadata": { "synthetic": true } } ``` #### 7.8.2. Finalization 트리거 시점 다음 이벤트가 발생하면 Finalization을 수행해야 한다 (MUST): - **WSS close/error 이벤트 수신**: 네트워크 단절, 서버 셧다운, 타임아웃 등으로 WSS Connection이 비정상 종료된 경우. - **앱 재시작 후 영속 상태 복원**: 앱이 재시작되어 이전 세션의 영속 상태를 복원할 때, 해당 세션의 WSS Connection이 이미 존재하지 않는 경우. - **세션 명시적 종료**: §7.5.3 및 §7.7의 절차가 우선 적용된다. 서버 측에서 정리 프레임이 정상 발송된 경우 Finalization은 불필요하다. 정리 프레임 없이 연결이 끊어진 경우에만 Finalization을 수행한다. #### 7.8.3. Unfinalization (재연결 복구) `reattach_window`(RAWP 1.0.2 §4.1.5) 내에 WSS 재연결(reattach)이 성공하고 `session.history`(§7.1.1)를 수신한 경우, 이전에 수행한 Finalization을 되돌려야 한다 (MUST): 1. `metadata.synthetic: true`가 포함된 로컬 합성 블록(cancelled tool-result 등)을 식별하여 제거한다. 2. `session.history`의 `frames` 배열에 포함된 프레임을 순서대로 재생하여 실제 서버 측 상태로 복원한다. 3. `session.history` 재생 완료 후, 정상 메시지 수신을 재개한다. **Unfinalization 불가 판정**: 다음의 경우 Unfinalization을 수행하지 않고 Finalization 상태를 유지한다: - `session.history`의 `buffer_status.truncated`가 `true`인 경우. 데이터 유실이 발생했으므로 완전한 복구가 불가능하다. - `session.deleted` 이벤트가 `session.history`의 `frames`에 포함된 경우. 세션이 이미 종료되었으므로 복구 대상이 아니다. --- ## 8. 태스크 관리 규격 (Task Management) Claude Code의 TodoRead/TodoWrite에 대응하는 태스크 관리 이벤트를 정의한다. §5의 도구 호출(`tool.invoke.*`)로도 표현 가능하나, 태스크 관리는 UI 렌더링과 밀접하므로 전용 페이로드 구조를 정의한다. ### 8.1. 태스크 객체 스키마 ```json { "id": "String (필수, 고유 식별자)", "content": "String (필수, 태스크 설명)", "status": "String (필수, 'pending' | 'in_progress' | 'completed' | 'cancelled')", "priority": "String (선택, 'critical' | 'high' | 'medium' | 'low')", "created_at": "String (선택, ISO 8601)", "updated_at": "String (선택, ISO 8601)", "parent_id": "String (선택, 부모 태스크 ID, 계층 구조 지원)", "metadata": "Object (선택, 추가 정보)" } ``` ### 8.2. 도구 결과 내 태스크 전달 태스크 관리 도구(`TodoRead`, `TodoWrite` 또는 동등한 도구)의 결과는 `tool.invoke.result`에서 `output_type: "structured"`와 함께 다음 `output` 구조를 사용한다: **TodoRead 결과:** ```json { "output_type": "structured", "output": { "todos": ["Task Object (§8.1의 스키마를 따르는 배열)"] } } ``` **TodoWrite 결과:** ```json { "output_type": "structured", "output": { "success": "Boolean (필수)", "count": "Number (필수, 반영된 태스크 수)", "todos": ["Task Object (선택, 갱신 후 전체 목록)"] } } ``` --- ## 9. 계획 문서 규격 (Planning Document) 에이전트가 계획 모드에서 생성하는 계획 문서의 포맷을 정의한다. ### 9.1. 계획 문서 전달 계획 문서는 `agent.text.delta`/`agent.text.done`의 일반 텍스트 스트리밍으로 전달하되, `metadata` 필드에 계획 문서 마커를 포함한다. ```json { "type": "agent.text.delta", "metadata": { "content_role": "plan", "plan_id": "String (선택, UUID v4)" }, "payload": { "content_index": 0, "text": "## Implementation Plan\n\n1. First, analyze the existing auth module..." } } ``` ### 9.2. 계획 확정/거부 마스터는 계획에 대한 사용자 응답을 `control.interaction.response`로 전달한다. 에이전트는 계획 발송 후 `agent.interaction.request`(interaction_type: `YN`)로 계획 승인을 요청해야 한다 (SHOULD). --- ## 10. 서브에이전트 위임 규격 (Subagent Delegation) 에이전트가 하위 에이전트(서브에이전트)를 생성하여 작업을 위임하는 구조를 정의한다. 서브에이전트는 부모 에이전트와 동일한 Agent Session 내에서 실행되며, 서브에이전트 내부에서 발생하는 모든 DPS 프레임은 부모와 동일한 `session_id`를 공유한다. ### 10.1. 서브에이전트 호출 `tool.invoke.request`에서 서브에이전트를 생성하는 도구를 호출한다. `payload`에 `subagent` 필드가 존재하면 이 도구 호출이 서브에이전트를 생성함을 의미한다. ```json { "type": "tool.invoke.request", "payload": { "invocation_id": "String (필수, UUID v4. 이 값이 서브에이전트의 parent_invocation_id가 된다)", "tool_name": "String (필수, 예: 'Task', 'Agent')", "input": { "description": "String (필수, 서브에이전트 임무 설명)", "prompt": "String (필수, 서브에이전트에게 전달할 프롬프트)", "allowed_tools": ["String (선택, 서브에이전트 허용 도구 목록)"], "model": "String (선택, 사용할 모델)" }, "subagent": { "agent_type": "String (선택, 서브에이전트 유형. 예: 'Explore', 'Plan', 'general-purpose')", "description": "String (선택, 서브에이전트 임무 요약)" } } } ``` `subagent` 필드가 없으면 일반 도구 호출이다. ### 10.2. 서브에이전트 컨텍스트 (`metadata.subagent_context`) 서브에이전트 내부에서 발생하는 모든 DPS 프레임은 `metadata.subagent_context` 필드를 포함해야 한다 (MUST). 이를 통해 수신 측은 각 메시지의 출처 에이전트를 식별할 수 있다. ```json { "metadata": { "subagent_context": { "parent_invocation_id": "String (필수, 이 서브에이전트를 호출한 tool.invoke.request의 invocation_id)", "agent_type": "String (선택, 서브에이전트 유형)", "depth": "Number (필수, 중첩 깊이. 0 = 메인 에이전트, 1 = 서브에이전트, 2 = 서브의 서브에이전트)" } } } ``` **규칙:** - `subagent_context`가 없거나 `null`인 프레임은 메인 에이전트(`depth: 0`)의 메시지로 취급해야 한다 (MUST). - 서브에이전트 내부의 `agent.*`, `tool.*`, `session.*` 모든 프레임에 포함해야 한다 (MUST). - `depth`는 발신 측(클라이언트)이 서브에이전트 생성 시점에 이미 알고 있는 값이다. 수신 측이 전체 프레임 히스토리 없이 단일 프레임만으로 중첩 깊이를 결정할 수 있도록 명시적으로 포함한다. - 중첩 서브에이전트의 경우 `parent_invocation_id`는 직계 부모의 `invocation_id`만 가리킨다. 전체 위임 체인이 필요한 경우 `parent_invocation_id`를 재귀 추적하여 복원한다. ### 10.3. 서브에이전트 생명주기 이벤트 #### 10.3.1. `agent.subagent.started` (Client → Master) 서브에이전트가 시작되었음을 통보한다. `tool.invoke.request`(`subagent` 포함) 발송 직후에 발송해야 한다 (MUST). ```json { "type": "agent.subagent.started", "payload": { "parent_invocation_id": "String (필수, 대응하는 tool.invoke.request의 invocation_id)", "agent_type": "String (선택, 서브에이전트 유형)", "description": "String (선택, 서브에이전트 임무 설명)", "depth": "Number (필수, 중첩 깊이. 1 = 서브에이전트, 2 = 서브의 서브에이전트)" } } ``` #### 10.3.2. `agent.subagent.ended` (Client → Master) 서브에이전트가 종료되었음을 통보한다. `tool.invoke.result`(output_type: `agent_result`) 발송 직전에 발송해야 한다 (MUST). ```json { "type": "agent.subagent.ended", "payload": { "parent_invocation_id": "String (필수)", "status": "String (필수, 'completed' | 'failed' | 'cancelled')", "duration_ms": "Number (선택, 서브에이전트 실행 시간 밀리초)" } } ``` **이벤트 순서:** ``` tool.invoke.request (subagent 포함) → agent.subagent.started → 서브에이전트 내부 메시지들 (subagent_context 포함) → agent.subagent.ended → tool.invoke.result (output_type: "agent_result") ``` ### 10.4. 서브에이전트 출력 서브에이전트의 중간 출력은 `tool.invoke.stream`으로 중계되거나, `subagent_context`가 포함된 개별 DPS 프레임(`agent.text.delta`, `tool.invoke.request` 등)으로 마스터에 직접 노출될 수 있다. 완료 시 `tool.invoke.result`(output_type: `agent_result`)로 최종 결과가 보고된다. ### 10.5. 사용량 보고 규칙 서브에이전트 내에서 발생하는 `session.usage`는 `subagent_context`를 포함하여 독립적으로 보고해야 한다 (MUST). 서브에이전트의 사용량을 메인 에이전트의 누적 사용량에 합산할지 여부는 마스터 또는 엣지 노드가 결정한다. 본 규격은 합산 정책을 규정하지 않는다. ### 10.6. 능력 협상 `session.capabilities`(§12.2.1)의 `features` 객체에 서브에이전트 관련 능력을 고지한다: | 필드 | 설명 | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `subagent` | 서브에이전트 도구 호출 지원 여부 (기존) | | `subagent_context` | `metadata.subagent_context` 필드 제공 여부. `true`이면 서브에이전트 메시지에 컨텍스트를 포함한다. `false`이거나 미고지이면 모든 메시지를 플랫하게 처리한다. | `subagent_context: false`인 클라이언트의 메시지에 대해 마스터 및 CRS는 그룹핑을 시도해서는 안 된다 (MUST NOT). ### 10.7. 서브에이전트 미지원 폴백 서브에이전트를 지원하지 않는 에이전트(프로세스 기반 어댑터 등)의 경우: - `subagent_context`를 생략하거나 `null`로 설정해야 한다 (MUST). - 모든 메시지는 메인 에이전트의 메시지로 취급된다. - `output_type: "agent_result"`인 `tool.invoke.result`는 `subagent_context` 없이도 서브에이전트 결과임을 암시하지만, 내부 메시지 그룹핑은 불가능하다. --- ## 11. 구조화 출력 규격 (Structured Output) 에이전트가 JSON Schema에 부합하는 구조화 응답을 반환할 때의 포맷을 정의한다. ### 11.1. 구조화 출력 완료 `session.turn.end` 이벤트의 `payload`에 구조화 출력을 포함한다: ```json { "type": "session.turn.end", "payload": { "turn_id": "...", "stop_reason": "end_turn", "structured_output": { "schema_id": "String (선택, 요청 시 제공된 스키마 식별자)", "data": "Object (필수, 스키마에 부합하는 JSON 데이터)", "validation_status": "String (필수, 'valid' | 'partial' | 'failed')" } } } ``` --- ## 12. 명령어 및 능력 고지 (Command & Capability Negotiation) ### 12.1. 명령어 목록 조회 에이전트가 지원하는 슬래시 명령어 목록을 마스터에 고지한다. #### 12.1.1. `agent.commands.publish` (Client → Master) ```json { "type": "agent.commands.publish", "payload": { "commands": [ { "name": "String (필수, 슬래시 명령명. 예: 'compact', 'cost', 'clear')", "description": "String (필수, 명령 설명)", "parameters": [ { "name": "String (필수)", "type": "String (필수, 'string' | 'number' | 'boolean')", "required": "Boolean (필수)", "description": "String (선택)" } ], "category": "String (선택, 'session' | 'display' | 'config' | 'navigation')" } ] } } ``` ### 12.2. 능력 협상 세션 시작 직후, 양측은 `session.capabilities` 이벤트를 교환하여 지원하는 기능 목록을 협상한다. #### 12.2.1. `session.capabilities` (양방향) ```json { "type": "session.capabilities", "payload": { "protocol_version": "String (필수, 'rawp-dps-1.0')", "supported_namespaces": ["String (필수, 지원하는 네임스페이스 목록)"], "features": { "thinking_stream": "Boolean (선택, 사고 스트리밍 지원)", "tool_streaming": "Boolean (선택, 도구 출력 스트리밍 지원)", "structured_output": "Boolean (선택, 구조화 출력 지원)", "subagent": "Boolean (선택, 서브에이전트 지원)", "context_compaction": "Boolean (선택, 컨텍스트 압축 지원)", "plan_mode": "Boolean (선택, 계획 모드 지원)", "file_reference": "Boolean (선택, 파일 참조 토큰 해석 지원 여부)", "file_search_fuzzy": "Boolean (선택, 퍼지 파일 검색 지원 여부)" }, "max_frame_size": "Number (선택, 최대 단일 프레임 바이트)", "extensions": "Object (선택, 벤더 확장 기능)" } } ``` 수신 측은 상대방이 고지하지 않은 `features`의 이벤트를 발송해서는 안 된다 (MUST NOT). 예를 들어 마스터가 `thinking_stream: false`이면, 클라이언트는 `agent.thinking.delta`를 발송하지 않아야 한다. **파일 참조 관련 능력 기반 필터링 규칙 (MUST)**: - 마스터는 클라이언트가 `file_search_fuzzy: true`를 고지하지 않은 경우, `control.file.search`를 발송해서는 안 된다 (MUST NOT). - `file_reference: false`이거나 미고지인 클라이언트에게 `file_references`가 포함된 `control.prompt.request`가 전달되는 경우, 클라이언트는 §16.2.2의 파싱 순서에 따라 토큰을 추출한 뒤 `display_path`를 리터럴 텍스트로 치환하여 에이전트에 전달한다 (MUST). --- ## 13. 프레임 시퀀스 예시 ### 13.1. 초기 세션 수립 흐름 WSS 연결이 최초 수립된 직후, 양측은 능력 협상을 수행하고 클라이언트는 도구 및 명령어 목록을 고지해야 한다 (MUST). 이 초기화 시퀀스가 완료되어야 마스터는 첫 프롬프트를 전송할 수 있다. ``` [WSS 연결 수립 - RAWP 1.0 §5.3 절차] Client → Master: session.capabilities (클라이언트 능력 고지) Master → Client: session.capabilities (마스터 능력 고지) Client → Master: tool.catalog.publish (사용 가능 도구 목록 고지) Client → Master: agent.commands.publish (지원 슬래시 명령어 목록 고지) Client → Master: agent.state.changed (idle 상태 진입 통보) [초기화 완료 — 마스터는 이 시점부터 control.prompt.request 발송 가능] ``` **순서 규약**: 1. `session.capabilities` 교환은 다른 모든 이벤트에 선행해야 한다 (MUST). 능력 협상이 완료되어야 양측이 어떤 이벤트 타입을 사용할 수 있는지 결정되기 때문이다. 2. `tool.catalog.publish`와 `agent.commands.publish`는 `session.capabilities` 이후, 첫 `control.prompt.request` 이전에 발송되어야 한다 (MUST). 3. `agent.state.changed`(current_state: `idle`)는 클라이언트가 프롬프트 수신 준비가 완료되었음을 알리는 신호이다 (SHOULD). 4. 마스터는 `agent.state.changed`(current_state: `idle`) 수신 전에 `control.prompt.request`를 발송해서는 안 된다 (SHOULD NOT). 단, 클라이언트가 `agent.state.changed` 이벤트를 지원하지 않는 경우(`session.capabilities`에서 미고지), 마스터는 `agent.commands.publish` 수신 완료를 초기화 완료 신호로 간주할 수 있다 (MAY). ### 13.2. 기본 프롬프트-응답 흐름 ``` Master → Client: control.prompt.request (사용자 입력) Client → Master: session.turn.start (Turn 시작) Client → Master: agent.thinking.delta (사고 스트림, 선택적) Client → Master: agent.thinking.done Client → Master: agent.text.delta (응답 스트림) Client → Master: agent.text.delta Client → Master: agent.text.done (텍스트 완료) Client → Master: session.turn.end (Turn 종료) Client → Master: session.usage (사용량 보고) ``` ### 13.3. 도구 사용 흐름 (승인 포함) ``` Master → Client: control.prompt.request (사용자: "이 파일을 수정해줘") Client → Master: session.turn.start Client → Master: agent.thinking.delta (사고: 어떻게 수정할지 결정) Client → Master: agent.thinking.done Client → Master: tool.invoke.request (Edit 도구 호출 요청) Client → Master: agent.interaction.request (실행 권한 승인 요청) Master → Client: control.interaction.response (사용자: 승인) Client → Master: tool.invoke.result (Edit 결과: diff) Client → Master: agent.text.delta (결과 설명) Client → Master: agent.text.done Client → Master: session.turn.end Client → Master: session.usage ``` ### 13.4. 병렬 도구 호출 흐름 ``` Client → Master: tool.invoke.request (Bash: git status, parallel_group_id: "pg1") Client → Master: tool.invoke.request (Bash: git diff, parallel_group_id: "pg1") Client → Master: tool.invoke.result (git status 결과) Client → Master: tool.invoke.result (git diff 결과) ``` ### 13.5. 서브에이전트 위임 흐름 ``` Client → Master: tool.invoke.request (Task: 코드 검색 위임) Client → Master: tool.invoke.stream (서브에이전트 중간 출력) Client → Master: tool.invoke.stream Client → Master: tool.invoke.result (서브에이전트 최종 결과, output_type: agent_result) ``` ### 13.6. 컨텍스트 압축 흐름 ``` Master → Client: control.session.compact (사용자: /compact 명령) Client → Master: session.compacted (압축 완료 보고) Client → Master: session.usage (압축 후 사용량) ``` ### 13.7. 재연결(Reattach) 후 이력 복구 흐름 ``` [WSS 연결 수립 - RAWP 1.0 §5.3 절차] Client → Master: session.capabilities (능력 협상) Master → Client: session.capabilities Client → Master: session.history (버퍼 이력 전송) Client → Master: tool.catalog.publish (도구 목록 재고지) Client → Master: agent.commands.publish (명령어 목록 재고지) [정상 통신 재개] ``` ### 13.8. 파일 참조 검색 흐름 ``` [사용자가 입력창에 "@src" 입력 — Edge Node에서 디바운싱 후 Master로 전달] Master → Client: control.file.search (query: "src", query_id: "q-001") Client → Master: session.file.candidates (query_id: "q-001", 후보 파일 목록) [사용자가 추가 타이핑 "@src/auth"] Master → Client: control.file.search (query: "src/auth", query_id: "q-002") Client → Master: session.file.candidates (query_id: "q-002", 갱신된 후보 목록) [사용자가 Enter로 "src/auth.ts" 선택 후 메시지 전송] Master → Client: control.prompt.request (prompt_text: "분석해줘 <@file:ref_0|src/auth.ts>", file_references: [{ref_id:"ref_0", path:"src/auth.ts", ...}]) ``` --- ## 14. 하위 호환성 (Backward Compatibility) ### 14.1. RAWP-1.0-Legacy (지원 종료) RAWP-1.0-Legacy와의 하위 호환성 지원은 종료되었다. 모든 구현은 본 규격(`rawp-dps-1.0`)만을 사용해야 한다 (MUST). ### 14.2. 정방향 호환성 규칙 1. **Unknown Type 무시**: 수신자는 알 수 없는 `type`을 가진 프레임을 무시해야 한다 (MUST). 에러를 발생시키거나 연결을 종료해서는 안 된다 (MUST NOT). 2. **Unknown Field 무시**: `payload` 내 알 수 없는 키는 무시해야 한다 (MUST). 3. **Capabilities 기반 필터링**: `session.capabilities`에서 고지하지 않은 기능의 이벤트는 발송하지 않아야 한다 (MUST NOT). 4. **버전 필드**: Envelope의 `v` 필드로 구조적 비호환을 감지한다. `v` 값이 지원 범위 밖이면 프레임을 무시하고 `session.error`로 응답한다. 5. **확장 네임스페이스**: `x-{vendor}.*` 접두 이벤트는 자유롭게 추가 가능하며, 수신자는 이를 무시할 수 있다. --- ## 15. 보안 고려사항 본 규격은 RAWP 1.0 §7의 보안 제약을 계승하며, 추가로 다음을 정의한다: 1. **도구 호출 화이트리스트**: `tool.catalog.publish`에서 고지된 도구만 `tool.invoke.request`에 사용 가능하다. 고지되지 않은 `tool_name`의 호출 요청은 클라이언트가 거부해야 한다 (MUST). 2. **서브에이전트 격리**: 서브에이전트의 `allowed_tools`는 부모 에이전트의 도구 범위를 초과할 수 없다 (MUST NOT). 3. **사고 스트림 검열**: `agent.thinking.delta`의 `redacted` 플래그가 `true`인 프레임의 `text`는 부분 또는 전체 마스킹된 내용이며, 마스터는 이를 사용자에게 원문으로 노출해서는 안 된다 (MUST NOT). 4. **구조화 출력 검증**: `structured_output.validation_status`가 `failed`이면 마스터는 해당 데이터를 하위 시스템에 전달하기 전 별도 검증을 수행해야 한다 (MUST). --- ## 16. 파일 참조 규격 (File Reference) 사용자가 프롬프트 텍스트 내에서 로컬 파일을 인라인으로 참조하기 위한 실시간 퍼지 검색, 인라인 토큰 포맷, 이스케이프 규칙, 어댑터별 변환 규칙을 정의한다. ### 16.1. 파일 검색 이벤트 #### 16.1.1. `control.file.search` (Master → Client) 마스터가 클라이언트에 파일 퍼지 검색을 요청한다. 사용자가 `@` 이후 문자를 입력할 때마다 디바운싱을 거쳐 발송된다. ```json { "type": "control.file.search", "payload": { "query_id": "String (필수, UUID v4, 이 검색 요청의 고유 식별자)", "query": "String (필수, 퍼지 검색 문자열. 빈 문자열이면 최근 수정 파일 목록 반환)", "scope": "String (선택, 'workspace' | 'project' | 'global', 기본값 'workspace')", "max_results": "Number (선택, 기본값 20, 최대 50)", "filters": { "extensions": ["String (선택, 확장자 필터. 예: ['.ts', '.tsx'])"], "exclude_patterns": [ "String (선택, 제외 glob 패턴. 예: ['node_modules/**', '.git/**'])" ] } } } ``` `query_id`는 사용자가 빠르게 타이핑할 때 여러 검색 요청이 동시에 진행되는 상황에서, 늦게 도착한 이전 쿼리의 결과가 최신 결과를 덮어쓰는 문제를 방지하기 위해 필수이다. #### 16.1.2. `session.file.candidates` (Client → Master) ```json { "type": "session.file.candidates", "parent_id": "String (필수, 대응하는 control.file.search의 message_id)", "payload": { "query_id": "String (필수, 대응하는 query_id 에코백)", "candidates": [ { "path": "String (필수, workspace_path 기준 상대 경로)", "name": "String (필수, 파일명)", "directory": "String (필수, 소속 디렉토리 경로)", "extension": "String (선택, 확장자. 예: '.ts')", "mime_type": "String (선택, IANA MIME 타입)", "size_bytes": "Number (선택)", "modified_at": "String (선택, ISO 8601, 최종 수정 시각)", "match_score": "Number (선택, 0.0–1.0, 퍼지 매칭 점수)", "match_ranges": [ { "start": "Number (필수, 매칭된 문자 시작 인덱스)", "end": "Number (필수, 매칭된 문자 종료 인덱스)" } ] } ], "total_count": "Number (선택, 필터 적용 후 전체 후보 수. max_results로 잘린 경우 참고용)", "truncated": "Boolean (필수, max_results 초과로 잘렸는지 여부)" } } ``` `match_ranges`는 CRS에서 퍼지 매칭된 문자를 하이라이팅하기 위해 포함된다. 예: 쿼리 `"aut"`, 결과 `"auth.ts"` → `[{start:0, end:3}]`으로 `aut` 부분을 강조. #### 16.1.3. `control.file.search.cancel` (Master → Client) 진행 중인 검색을 취소한다. 사용자가 `@` 모드에서 Esc를 누르거나 `@`를 지웠을 때 발송한다. ```json { "type": "control.file.search.cancel", "payload": { "query_id": "String (필수, 취소 대상 query_id)" } } ``` ### 16.2. 파일 참조 인라인 토큰 포맷 (File Reference Token) `control.prompt.request`의 `prompt_text` 내에서 파일 참조를 표현하는 인라인 토큰 포맷을 정의한다. #### 16.2.1. 토큰 형식 ``` <@file:ref_id|display_path> ``` 예시: `"이 파일의 버그를 찾아줘 <@file:ref_0|src/auth.ts>"` **구성요소:** | 부분 | 설명 | | -------------- | ------------------------------------------------ | | `<@file:` | 파일 참조 토큰 시작 마커 | | `ref_id` | `file_references` 배열 내 항목과 매핑되는 식별자 | | `\|` | 구분자 | | `display_path` | 사용자에게 표시된 경로 (팝업에서 선택한 표시명) | | `>` | 토큰 종료 마커 | **생성 경로의 단일성**: 파일 참조 토큰은 **오직 UI의 파일 선택 팝업(CRS §3.4)을 통해서만** 생성될 수 있다 (MUST). 사용자가 키보드로 직접 입력한 텍스트에서는 토큰이 생성되어서는 안 된다 (MUST NOT). 이를 보장하기 위한 이스케이프 규칙은 §16.2.2에서 정의한다. **파싱 정규식**: 토큰은 이스케이프되지 않은 패턴만 매칭하는 다음 정규식으로 식별한다 (MUST): ``` (?]+)> ``` **매핑 실패 처리**: 토큰의 `ref_id`가 `file_references` 배열에 매핑되지 않으면, 수신 측은 해당 토큰을 리터럴 텍스트로 처리해야 한다 (MUST). 토큰을 무시하거나 삭제해서는 안 된다 (MUST NOT). #### 16.2.2. 토큰 이스케이프 규칙 (Token Escaping) 사용자가 직접 `<@file:...>` 패턴을 타이핑하여 토큰을 위조하는 것을 방지하고, 일반 텍스트에서 토큰 패턴과 우연히 일치하는 문자열이 파일 참조로 오파싱되는 것을 방지하기 위한 이스케이프 규칙이다. **이스케이프 테이블**: | 리터럴 입력 | 이스케이프 결과 | 근거 | | ----------- | --------------- | -------------------------------------------------------------------------------------------------------------------- | | `\` | `\\` | 백슬래시 리터럴 보존 | | `<@` | `\<@` | 모든 토큰 시작 패턴 보호. 현재의 `<@file:` 뿐 아니라, 향후 확장 토큰(`<@user:`, `<@symbol:` 등)과의 충돌도 예방한다. | **적용 주체 및 시점**: - **이스케이프(인코딩)**: 엣지 노드(또는 마스터 서버)가 `control.prompt.request`의 `prompt_text`를 구성할 때, 사용자가 직접 입력한 텍스트 구간에 대해 이스케이프를 적용한다 (MUST). UI 파일 선택 팝업을 통해 삽입된 토큰 구간은 이스케이프 대상에서 제외한다 (MUST). - **언이스케이프(디코딩)**: 수신 측(로컬 클라이언트)이 `prompt_text`를 파싱할 때, 아래의 파싱 순서에 따라 토큰 추출 후 나머지 텍스트에서 이스케이프 시퀀스를 해제한다 (MUST). **파싱 순서 (MUST)**: 1. 이스케이프되지 않은 토큰 패턴 매칭: 정규식 `(?]+)>`으로 유효한 토큰을 추출한다. 2. 매칭된 토큰을 `file_references` 배열과 대조하여 유효성을 검증한다. 3. 토큰이 아닌 나머지 텍스트 구간에서 이스케이프 시퀀스를 해제한다: `\\` → `\`, `\<@` → `<@` 순서로 치환한다. **구현 예시**: ``` # 사용자 입력 (입력창에서의 원문) "<@file:injection>은 위조이고, 이건 진짜야 {토큰: src/auth.ts}" # 전송 시 prompt_text (이스케이프 적용) "\<@file:injection>은 위조이고, 이건 진짜야 <@file:ref_0|src/auth.ts>" # 수신 측 파싱 결과 - 토큰: ref_0 → src/auth.ts (유효한 파일 참조) - 텍스트: "<@file:injection>은 위조이고, 이건 진짜야" (리터럴) ``` ### 16.3. 어댑터별 변환 규칙 (Adapter Conversion) 로컬 클라이언트는 에이전트의 요구사항에 따라 인라인 토큰을 변환해야 한다 (MUST). 변환 전략은 에이전트별로 다르며, 클라이언트 구현에 위임한다. 본 규격은 대표적 변환 패턴을 다음과 같이 예시한다: | 어댑터 | 변환 예시 | 설명 | | ----------------- | ------------------------------------------------------------------------------- | ----------------------------------------- | | Claude Code | 토큰을 `prompt_text`에서 제거하고, `context_files` 또는 도구 호출로 분리 | 에이전트가 파일 내용을 자체 로드하는 경우 | | 범용 LLM | `<@file:ref_0\|src/auth.ts>` → `[File: src/auth.ts]` 또는 파일 내용 인라인 삽입 | 에이전트가 파일 참조 개념이 없는 경우 | | 스크립트 에이전트 | 토큰에서 경로를 추출하여 프로세스 인자로 전달 | CLI 기반 에이전트 | **변환 불가 시 동작**: 클라이언트가 특정 토큰을 해석할 수 없는 경우, `display_path`를 리터럴 텍스트로 치환하여 에이전트에 전달해야 한다 (MUST). 토큰을 무시하거나 삭제해서는 안 된다 (MUST NOT). ### 16.4. `control.prompt.request` 확장 §6.1.1의 `control.prompt.request`에 `file_references` 필드가 추가되었다. 상세 스키마는 §6.1.1을 참조한다. **`file_references`와 기존 `context_files`의 관계**: `context_files`는 프롬프트와 함께 첨부되는 참조 파일이고, `file_references`는 프롬프트 텍스트 내에서 인라인으로 언급된 파일의 메타데이터이다. 양자는 독립적이며, 동일 파일이 양쪽에 모두 존재할 수 있다. ### 16.5. 능력 협상 (Capability Negotiation) §12.2.1의 `session.capabilities`에 파일 참조 관련 능력 필드가 추가되었다. 상세 스키마와 필터링 규칙은 §12.2.1을 참조한다. --- ## 17. 에이전트 어댑터 프로토콜 (Agent Adapter Protocol) 에이전트의 실행 방식에 따라 DPS 이벤트의 생성 및 전달 방식이 달라진다. 본 절은 어댑터 유형별로 DPS 프레임과의 계약을 정의한다. 어댑터의 내부 동작 방식은 규정하지 않으며, DPS 프레임 수준의 프로토콜 계약만을 정의한다. ### 17.1. 어댑터 유형 분류 에이전트 메타데이터(RAWP 1.0.2 §4.3)의 `adapter_type` 필드로 어댑터 유형을 구분한다: | 유형 | `adapter_type` | 설명 | | ------------- | -------------- | ----------------------------------------------------------------------- | | 프로세스 기반 | `process` | 별도 자식 프로세스(child process)로 에이전트를 실행. stdio를 통해 통신. | | SDK 기반 | `sdk` | 클라이언트와 동일 프로세스 내에서 SDK API를 통해 에이전트를 실행. | ### 17.2. 프로세스 어댑터 프로토콜 계약 #### 17.2.1. 환경 변수 프로세스 기반 에이전트 생성 시 다음 환경 변수를 주입해야 한다 (MUST): | 환경 변수 | 값 | 설명 | | --------------------- | ----------------------- | ----------------------------------------- | | `RAWP_SESSION_ID` | 세션 식별자 (UUID v4) | 에이전트가 자신의 세션을 식별하기 위한 값 | | `RAWP_WORKSPACE_PATH` | 작업 디렉토리 절대 경로 | 에이전트의 파일 시스템 작업 기준 경로 | | `RAWP_DPS_VERSION` | `rawp-dps-1.0` | 사용 중인 DPS 프로토콜 버전 | #### 17.2.2. Exit Code → DPS 이벤트 매핑 에이전트 프로세스 종료 시 exit code에 따라 다음 DPS 이벤트를 발송해야 한다 (MUST): | Exit Code | 해석 | DPS 이벤트 | | --------- | -------------------- | -------------------------------------------------------------------------------------------------------------- | | `0` | 정상 종료 | `session.turn.end` (stop_reason: `"end_turn"`) | | `1`–`127` | 에러 종료 | `agent.error` (severity: `"fatal"`) → `session.turn.end` (stop_reason: `"error"`) | | `128+N` | 시그널 N에 의한 종료 | `agent.error` (severity: `"fatal"`, error_code: `"SIGNAL_EXIT"`) → `session.turn.end` (stop_reason: `"error"`) | **예외**: `single_turn_process: true`(§18) 어댑터의 exit code 0은 Turn 완료이며 세션 종료가 아니다. §18.2 참조. #### 17.2.3. 종료 시그널 순서 RAWP 1.0.2 §5.2의 세션 명시적 종료 시, 프로세스 기반 에이전트에 대한 종료 시그널 순서: 1. `SIGTERM` 전송. 2. 유예 시간(5초) 내 프로세스가 종료되지 않으면 `SIGKILL` 전송. 3. 프로세스 종료 확인 후 WSS 정리 프레임 발송(§7.5.3). ### 17.3. SDK 어댑터 프로토콜 계약 #### 17.3.1. 도구 승인 콜백 브릿지 SDK가 도구 실행 전 사용자 승인을 요구하는 경우, 다음의 DPS 이벤트 시퀀스를 따른다: ``` SDK 콜백 발생 → Client: agent.interaction.request (interaction_type: "PERMISSION") 발송 → Master: control.interaction.response 또는 control.interaction.timeout 발송 → Client: SDK 콜백에 승인/거부 결과 전달 ``` - `control.interaction.timeout` 수신 시 SDK 콜백에 거부를 전달해야 한다 (MUST). - `agent.interaction.request`의 `context.tool_name`에 승인 대상 도구명을 포함해야 한다 (MUST). #### 17.3.2. 동시 프롬프트 방지 하나의 세션에서 동시에 진행할 수 있는 에이전트 쿼리(프롬프트 처리)는 최대 1개이다 (MUST). 에이전트가 현재 Turn을 처리 중인 상태(`session.turn.start` 발송 후 `session.turn.end` 발송 전)에서 새 `control.prompt.request`를 수신하면, 클라이언트는 `session.error` (error_code: `"PROMPT_IN_PROGRESS"`, fatal: `false`)를 반환하고 해당 프롬프트를 거부해야 한다 (MUST). ### 17.4. 공통 규칙 - `session.capabilities` 교환 시 클라이언트는 현재 세션의 에이전트 어댑터 유형을 마스터에 보고해야 한다 (SHOULD). `features` 객체에 `adapter_type` 필드(`"process"` 또는 `"sdk"`)를 포함한다. - `context_compaction` 미지원 어댑터가 `control.session.compact`를 수신하면 §6.3.1의 미지원 규칙을 따른다. - `plan_mode` 미지원 어댑터가 `control.mode.switch`를 수신하면 §6.3.2의 미지원 규칙을 따른다. --- ## 18. 단일 턴 프로세스 재생성 (Single-Turn Process Respawn) 일부 프로세스 기반 에이전트는 하나의 프롬프트를 처리한 후 프로세스가 종료된다. 다음 프롬프트 시 이전 세션 컨텍스트를 유지하면서 새 프로세스를 생성하는 "재생성(respawn)" 패턴을 정의한다. ### 18.1. 어댑터 플래그 에이전트 메타데이터(RAWP 1.0.2 §4.3)에 다음 필드를 포함한다: | 필드 | 타입 | 설명 | | --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------- | | `single_turn_process` | Boolean | `true`이면 프로세스가 Turn 완료 후 exit code 0으로 종료되는 것이 정상 동작이다. | | `resume_flag` | String | 재생성 시 이전 세션 컨텍스트를 전달하기 위한 CLI 플래그 템플릿. `{session_id}` 플레이스홀더를 실제 값으로 치환한다. | ### 18.2. Exit Code별 동작 `single_turn_process: true`인 에이전트의 exit code 해석: | Exit Code | 동작 | 세션 상태 | | --------- | -------------------------------------------- | ------------------------------------------ | | `0` | 정상 Turn 완료. 프로세스 종료는 예상된 동작. | `idle` (다음 프롬프트 대기, 프로세스 없음) | | `1`–`127` | 에러 종료. §17.2.2와 동일. | `TERMINATED` | | `128+N` | 시그널 종료. §17.2.2와 동일. | `TERMINATED` | exit code 0에서의 핵심 차이: 일반 프로세스 어댑터(§17.2.2)에서는 exit code 0이 세션 종료를 의미하지만, `single_turn_process: true`에서는 Turn 완료만을 의미하며 세션은 유지된다. ### 18.3. 세션 ID 전달 재생성 시 이전 세션 컨텍스트를 전달하기 위해 `resume_flag` 템플릿을 사용한다: - 에이전트 프로세스가 첫 Turn 실행 중 내부 세션 ID를 보고하면(예: `session.capabilities`의 `extensions.agent_session_id`), 클라이언트는 이를 보존해야 한다 (MUST). - 재생성 시 보존된 세션 ID를 `resume_flag` 템플릿의 `{session_id}` 플레이스홀더에 치환하여 프로세스 인자에 포함한다. - 예: `resume_flag: "--resume {session_id}"` → 실제 실행: `agent-cli --resume abc-123-def` ### 18.4. 상태 전이 ``` [프롬프트 수신] → RUNNING (프로세스 생성, 에이전트 실행) → Turn 완료 (exit 0) → idle (프로세스 종료, 다음 프롬프트 대기) → [다음 프롬프트 수신] → RUNNING (프로세스 재생성, resume_flag 포함) → ... ``` - `idle` 상태에서 프로세스는 존재하지 않는다. 이것이 일반 프로세스 어댑터와의 핵심 차이다. - `idle` 상태에서 `DELETE /v1/session/{session_id}`(RAWP 1.0.2 §5.2)를 수신하면 종료할 프로세스가 없으므로 즉시 `TERMINATED`로 전이한다. - `RUNNING` 상태에서의 세션 종료는 §17.2.3의 종료 시그널 순서를 따른다. --- ## 부록 A. Claude Code 출력 유형 전수 조사 및 매핑 본 규격 설계의 근거가 된 Claude Code의 전체 출력 유형과 RAWP-DPS 1.0 이벤트 매핑 표: ### A.1. 핵심 도구 출력 | Claude Code 도구 | 입력 요약 | 출력 형태 | RAWP-DPS 매핑 | output_type | | ---------------- | --------------------------------------- | -------------------------- | ------------------------------------------------------------------- | ----------------------- | | **Bash** | `command`, `description` | stdout/stderr 텍스트 | `tool.invoke.request` → `tool.invoke.stream` → `tool.invoke.result` | `text` | | **Read** | `file_path`, `offset`, `limit` | 파일 내용 (라인 번호 포함) | `tool.invoke.result` | `file_content` | | **Write** | `file_path`, `content` | 성공/실패 | `tool.invoke.result` | `empty` | | **Edit** | `file_path`, `old_string`, `new_string` | 변경된 코드 + 컨텍스트 | `tool.invoke.result` | `diff` | | **MultiEdit** | `file_path`, `edits[]` | 복수 변경사항 | `tool.invoke.result` | `diff` | | **Glob** | `pattern`, `path` | 매칭 파일 경로 목록 | `tool.invoke.result` | `file_list` | | **Grep** | `pattern`, `path`, `include` | 매칭 라인/파일 목록 | `tool.invoke.result` | `text` 또는 `file_list` | | **LS** | `path` | 디렉토리 내용 | `tool.invoke.result` | `file_list` | ### A.2. 확장 도구 출력 | Claude Code 도구 | RAWP-DPS 매핑 | output_type | | ------------------- | ------------------------------------------------------------------- | -------------------- | | **WebFetch** | `tool.invoke.result` | `web_content` | | **WebSearch** | `tool.invoke.result` | `web_content` | | **NotebookRead** | `tool.invoke.result` | `file_content` | | **NotebookEdit** | `tool.invoke.result` | `empty` 또는 `diff` | | **TodoRead** | `tool.invoke.result` | `structured` (§8.2) | | **TodoWrite** | `tool.invoke.result` | `structured` (§8.2) | | **Task (Subagent)** | `tool.invoke.request` → `tool.invoke.stream` → `tool.invoke.result` | `agent_result` (§10) | ### A.3. 비도구 출력 | Claude Code 출력 | RAWP-DPS 매핑 | | -------------------- | ------------------------------------------------------ | | 텍스트 응답 스트리밍 | `agent.text.delta` → `agent.text.done` | | Extended Thinking | `agent.thinking.delta` → `agent.thinking.done` | | 도구 실행 권한 요청 | `agent.interaction.request` (type: `PERMISSION`) | | 컨텍스트 자동 압축 | `session.compacted` (trigger: `auto`) | | `/compact` 수동 압축 | `control.session.compact` → `session.compacted` | | 토큰/비용 보고 | `session.usage` | | Plan 모드 진입/이탈 | `control.mode.switch` (mode: `plan` / `default`) | | 계획 문서 출력 | `agent.text.delta` (metadata.content_role: `plan`) | | 슬래시 명령 입력 | `control.prompt.request` (input_type: `slash_command`) | | 에러 보고 | `agent.error` | | 세션 종료 | `session.turn.end` + RAWP 1.0 §5.2 | ### A.4. RAWP-1.0-Legacy (지원 종료) RAWP-1.0-Legacy와의 비교 항목은 지원 종료로 더 이상 유지하지 않는다. --- ## 부록 B. 이벤트 타입 전체 목록 (Quick Reference) | 이벤트 타입 | 방향 | §참조 | | ------------------------------ | ------ | ------ | | `agent.text.delta` | C→M | 4.1.1 | | `agent.text.done` | C→M | 4.1.2 | | `agent.thinking.delta` | C→M | 4.2.1 | | `agent.thinking.done` | C→M | 4.2.2 | | `agent.interaction.request` | C→M | 4.3.1 | | `agent.error` | C→M | 4.4.1 | | `agent.file.transfer` | 양방향 | 4.5.1 | | `agent.state.changed` | C→M | 4.6.1 | | `agent.commands.publish` | C→M | 12.1.1 | | `agent.subagent.started` | C→M | 10.3.1 | | `agent.subagent.ended` | C→M | 10.3.2 | | `tool.invoke.request` | C→M | 5.2.1 | | `tool.invoke.result` | C→M | 5.3.1 | | `tool.invoke.stream` | C→M | 5.3.2 | | `tool.catalog.publish` | C→M | 5.4.1 | | `control.prompt.request` | M→C | 6.1.1 | | `control.prompt.cancel` | M→C | 6.1.2 | | `control.interaction.response` | M→C | 6.2.1 | | `control.interaction.timeout` | M→C | 6.2.2 | | `control.session.compact` | M→C | 6.3.1 | | `control.mode.switch` | M→C | 6.3.2 | | `control.file.search` | M→C | 16.1.1 | | `control.file.search.cancel` | M→C | 16.1.3 | | `session.history` | C→M | 7.1.1 | | `session.compacted` | C→M | 7.2.1 | | `session.usage` | 양방향 | 7.3.1 | | `session.error` | 양방향 | 7.4.1 | | `session.turn.start` | C→M | 7.5.1 | | `session.turn.end` | C→M | 7.5.2 | | `session.capabilities` | 양방향 | 12.2.1 | | `session.file.candidates` | C→M | 16.1.2 | | `session.renamed` | 양방향 | 7.6.1 | | `session.deleted` | 양방향 | 7.7.1 |