# RAWP-DPS 1.0: Data Plane Streaming Specification **Remote Agent Wire Protocol — Data Plane Streaming** | 항목 | 값 | | ---- | ---------------------------------- | | 상태 | Draft | | 버전 | 1.0 | | 날짜 | 2026-03-08 | | 호환 | RAWP 1.0 (제어 평면) | | 대체 | RAWP 1.0 §6 (이하 RAWP-1.0-Legacy) | --- ## 1. 개요 (Introduction) RAWP-DPS 1.0은 RAWP 1.0의 데이터 평면(§6)을 대체하는 독립 스트리밍 규격이다. 기존 RAWP 1.0 §6(이하 **RAWP-1.0-Legacy**)은 단순 텍스트 스트림, 승인 상호작용, 사용량 지표, 에러 보고, 파일 전송만을 정의하였으며, 에이전트 코딩 도구(Claude Code 등)가 생성하는 도구 호출 결과, 계획 문서, 태스크 관리, 컨텍스트 압축, 서브에이전트 위임 등 다양한 출력 형태를 포괄하지 못한다. RAWP-DPS 1.0은 다음 목표를 달성하기 위해 설계되었다: 1. **완전성(Completeness)**: Claude Code를 포함한 에이전트 코딩 도구의 모든 출력 형태를 단일 프레임 규격으로 포맷화한다. 2. **범용성(Generality)**: 특정 에이전트에 종속되지 않으며, 임의의 에이전트 도구 체계를 수용할 수 있는 확장 가능한 타입 시스템을 제공한다. 3. **호환성(Compatibility)**: 알 수 없는 타입의 정방향 호환성(Forward Compatibility)과, RAWP-1.0-Legacy 대비 하위 호환성(Backward 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 호환 모드 세션 초기화(§5.1) 시 `Sec-WebSocket-Protocol` 헤더로 프로토콜 버전을 협상한다: - `rawp-dps-1.0`: 본 규격 적용 - `rawp-1.0`: RAWP-1.0-Legacy 적용 (기존 구현 호환) 서버와 클라이언트 모두 `rawp-dps-1.0`을 우선 제안해야 하며 (SHOULD), 상대가 미지원 시 `rawp-1.0`으로 폴백해야 한다 (MUST). ### 1.4. 용어 정의 본 문서에서 추가로 사용하는 용어: | 용어 | 정의 | | --------------- | ------------------------------------------------------------------------ | | Frame | WSS를 통해 송수신되는 하나의 JSON 메시지 단위 | | Content Block | 단일 Frame 내에서 독립적 의미를 가지는 출력 조각 | | Turn | 에이전트의 단일 추론-행동 사이클 (프롬프트 수신 → 도구 사용 → 응답 완료) | | 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 | RAWP 1.0 §5.1에서 발급된 세션 식별자. | | `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). --- ## 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의 이벤트 타입과 본 규격 타입 간 대응 관계: | RAWP-1.0-Legacy | RAWP-DPS 1.0 | 비고 | | ---------------------------------------------- | -------------------------------------- | ------------------- | | `agent_session_history` | `session.history` | | | `agent_stdin_write` / `agent_prompt_req` | `control.prompt.request` | | | `agent_text_stream` / `agent_prompt_res` | `agent.text.delta` + `agent.text.done` | 분리 | | `agent_approval_req` / `agent_interaction_ask` | `agent.interaction.request` | | | `agent_approval_res` / `agent_interaction_ans` | `control.interaction.response` | | | `agent_interaction_timeout` | `control.interaction.timeout` | | | `agent_usage_metrics` | `session.usage` | | | `agent_error` | `agent.error` | | | `agent_file_transfer` | `agent.file.transfer` | | | (해당 없음) | `control.file.search` | 신규, Legacy 미지원 | | (해당 없음) | `session.file.candidates` | 신규, Legacy 미지원 | | (해당 없음) | `control.file.search.cancel` | 신규, Legacy 미지원 | --- ## 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) 에이전트가 사용자의 승인 또는 선택을 요구할 때 발송한다. RAWP-1.0-Legacy의 `agent_approval_req`/`agent_interaction_ask`를 통합 확장한다. ### 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` (양방향) RAWP-1.0-Legacy와 동일하게, 바이너리는 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.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 (선택, 도구 분류. 아래 열거 값)" } ] } } ``` **`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` | 사용자 정의 도구 | 커스텀 도구 | --- ## 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 (선택, 금지 도구 목록)"] } } } ``` > **변경 사항**: `file_references` 필드가 추가되었다. 이 필드는 `prompt_text` 내에 삽입된 인라인 파일 참조 토큰(§16.2)의 메타데이터를 제공한다. `file_references`와 기존 `context_files`의 관계: `context_files`는 프롬프트와 함께 첨부되는 참조 파일이고, `file_references`는 프롬프트 텍스트 내에서 인라인으로 언급된 파일의 메타데이터이다. 양자는 독립적이며, 동일 파일이 양쪽에 모두 존재할 수 있다. **`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, 목표 압축 비율)" } } ``` ### 6.3.2. `control.mode.switch` (Master → Client) 에이전트 동작 모드를 전환한다. ```json { "type": "control.mode.switch", "payload": { "mode": "String (필수, 아래 열거 값)", "config": "Object (선택, 모드별 추가 설정)" } } ``` **`mode` 열거 값:** | 값 | 설명 | | --------- | -------------------------------------------- | | `default` | 기본 실행 모드 (읽기/쓰기/실행 모두 가능) | | `plan` | 계획 모드 (읽기/검색만 허용, 쓰기/실행 차단) | | `review` | 코드 리뷰 모드 | --- ## 7. 세션 이벤트 (`session.*`) ### 7.1. 이력 복구 ### 7.1.1. `session.history` (Client → Master) 재연결(reattach) 성공 직후 선제적으로 발송한다. RAWP-1.0-Legacy의 `agent_session_history`를 대체한다. ```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). RAWP-1.0-Legacy의 `agent_usage_metrics`를 대체한다. ```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` | 사용자 입력 대기 | --- ## 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) 에이전트가 하위 에이전트(Task/Agent)를 생성하여 작업을 위임하는 구조를 정의한다. ### 10.1. 서브에이전트 시작 `tool.invoke.request`에서 `tool_name: "Task"` 또는 `tool_name: "Agent"`를 사용하며, `input`에 서브에이전트 설정을 포함한다: ```json { "type": "tool.invoke.request", "payload": { "invocation_id": "subagent-uuid-here", "tool_name": "Task", "input": { "description": "String (필수, 서브에이전트 임무 설명)", "prompt": "String (필수, 서브에이전트에게 전달할 프롬프트)", "allowed_tools": ["String (선택, 서브에이전트 허용 도구 목록)"], "model": "String (선택, 사용할 모델)" } } } ``` ### 10.2. 서브에이전트 출력 서브에이전트의 모든 출력은 부모 에이전트의 세션 내에서 `tool.invoke.stream`으로 중계되거나, 완료 시 `tool.invoke.result`(output_type: `agent_result`)로 최종 결과가 보고된다. 서브에이전트의 중간 이벤트를 마스터에 직접 노출하려면, 각 프레임의 `metadata`에 위임 체인을 기록한다: ```json { "metadata": { "delegation_chain": [ { "agent_id": "String (부모 에이전트 식별자)", "invocation_id": "String (위임 호출 ID)" } ] } } ``` --- ## 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`에 `file_reference`와 `file_search_fuzzy`가 추가되었다 (§16.5 참조). 수신 측은 상대방이 고지하지 않은 `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. 하위 호환성 가이드라인 ### 14.1. RAWP-1.0-Legacy 브릿지 RAWP-1.0-Legacy만 지원하는 클라이언트/서버와 통신할 때, 중간 프록시(Gateway)가 타입을 변환할 수 있다. 변환 규칙: | RAWP-DPS 1.0 수신 | RAWP-1.0-Legacy 변환 | 비고 | | --------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------ | | `agent.text.delta` + `agent.text.done` | `agent_text_stream` (is_final 플래그로 통합) | `agent.text.done` 수신 시 `is_final: true` | | `tool.invoke.request` + `tool.invoke.result` | 변환 불가, 텍스트로 직렬화 | Legacy는 도구 이벤트 미지원 | | `agent.thinking.*` | 무시(drop) | Legacy는 사고 스트림 미지원 | | `session.compacted` | 무시(drop) | Legacy는 압축 보고 미지원 | | `session.usage` | `agent_usage_metrics` | 필드 매핑 | | `session.history` | `agent_session_history` | `frames` → `history_content`로 직렬화 | | `control.file.search` | 변환 불가, 무시(drop) | Legacy는 파일 검색 미지원 | | `session.file.candidates` | 변환 불가, 무시(drop) | Legacy는 파일 검색 미지원 | | `control.file.search.cancel` | 변환 불가, 무시(drop) | Legacy는 파일 검색 미지원 | | `file_references` in `control.prompt.request` | 토큰을 `display_path` 리터럴로 치환, §16.2.2 이스케이프 해제 적용 | 텍스트 폴백 | ### 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을 참조한다. --- ## 부록 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에서 지원하지 못했으나 본 규격에서 해결된 항목: | 항목 | RAWP-1.0-Legacy | RAWP-DPS 1.0 | | ------------------------- | --------------- | -------------------------------------------------------- | | 도구 호출/결과 프레이밍 | ❌ 미지원 | ✅ `tool.invoke.*` | | 계획 문서 전달 | ❌ 미지원 | ✅ `metadata.content_role: plan` | | TODO/태스크 관리 | ❌ 미지원 | ✅ §8 + `tool.invoke.result` | | 컨텍스트 압축 보고 | ❌ 미지원 | ✅ `session.compacted` | | 명령어 목록 조회 | ❌ 미지원 | ✅ `agent.commands.publish` | | 사고 스트리밍 | ❌ 미지원 | ✅ `agent.thinking.*` | | 서브에이전트 위임 | ❌ 미지원 | ✅ §10 | | 구조화 출력 | ❌ 미지원 | ✅ §11 | | 도구 출력 실시간 스트리밍 | ❌ 미지원 | ✅ `tool.invoke.stream` | | 능력 협상 | ❌ 미지원 | ✅ `session.capabilities` | | 병렬 도구 호출 그룹화 | ❌ 미지원 | ✅ `parallel_group_id` | | Turn 라이프사이클 | ❌ 미지원 | ✅ `session.turn.*` | | 프롬프트 취소 | ❌ 미지원 | ✅ `control.prompt.cancel` | | 동작 모드 전환 | ❌ 미지원 | ✅ `control.mode.switch` | | 파일 참조 검색 | ❌ 미지원 | ✅ §16 `control.file.search` / `session.file.candidates` | --- ## 부록 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 | | `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 |