# RAWP 1.0: Remote Agent Wire Protocol | 항목 | 값 | | ---------------------- | ------------------ | | 상태 | Stable | | 버전 | 1.0.2 | | 데이터 평면 규격 | **RAWP-DPS 1.0.1** | | 클라이언트 렌더링 규격 | **RAWP-CRS 1.0.1** | --- ## 1. 개요 (Introduction) RAWP 1.0은 중앙 제어 서버(Master Server)가 원격지의 게이트웨이(Local Client)를 안전하게 제어하기 위한 Push 기반의 분산 제어 프로토콜이다. 제어 및 상태 모니터링을 위한 Stateless HTTP 통신과, 실시간 데이터 입출력을 위한 Stateful WebSocket 통신으로 제어 평면(Control Plane)과 데이터 평면(Data Plane)을 분리한다. ### 1.1. 요구사항 표기 규약 (Requirements Notation) 본 문서의 "MUST", "MUST NOT", "REQUIRED", "SHOULD", "SHOULD NOT", "OPTIONAL"은 RFC 2119를 따른다. ### 1.2. 용어 정의 (Terminology) | 용어 | 정의 | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Master Server | 클라이언트 노드를 관리하고 제어 명령을 하달하는 중앙 서버 (Control Plane). | | Local Client | 마스터 서버의 명령을 수신하는 HTTP 엔드포인트를 소유하며, 로컬 프로세스(Agent)의 실행 및 I/O를 담당하는 에지 게이트웨이 (Execution Plane). | | Agent | 클라이언트 환경에서 실행되는 실제 단위 작업 프로세스 (예: LLM, 스크립트 등). | | Agent Session | 특정 에이전트의 실행부터 종료까지의 라이프사이클 및 I/O 스트림의 논리적 단위. `session_id`(UUID v4)로 식별한다. 상태 머신: `INIT → RUNNING → DETACHED → TERMINATED`. 하나의 Agent Session에 복수의 WSS Connection이 바인딩될 수 있다. 본 문서에서 "세션"은 별도 명시가 없는 한 Agent Session을 의미한다. | | WSS Connection | Agent Session에 바인딩된 WebSocket 전송 채널. 티켓(§5.3)을 통해 수립되며, 네트워크 단절 시 소멸하고 재연결(reattach)으로 재수립할 수 있다. 동일 Agent Session에 여러 WSS Connection이 동시에 존재할 수 있다 (예: 마스터 연결 + 로컬 UI 연결, 또는 다중 엣지 연결). | | Config Scope | 클라이언트 설정의 논리적 분류 단위. 각 스코프는 독립된 엔드포인트, 독립된 `config_version`, 독립된 갱신 주기를 갖는다. | | Session-Pinned Config | 세션 초기화 시점에 바인딩된 설정 스냅샷. 해당 세션의 전체 라이프사이클 동안 불변이다. | **Agent Session과 WSS Connection의 관계**: Agent Session은 에이전트의 논리적 생명주기이고, WSS Connection은 그 세션의 데이터를 전달하는 전송 채널이다. Agent Session이 `RUNNING` 상태에서 WSS Connection이 단절되면 Agent Session은 `DETACHED`로 전이하지만 에이전트 프로세스는 계속 실행된다. WSS Connection이 재수립(reattach)되면 Agent Session은 `RUNNING`으로 복귀한다. Agent Session이 `TERMINATED`되면 모든 WSS Connection이 종료된다. ### 1.3. 호환성 및 파싱 규약 (Forward Compatibility) - **필드 무시 원칙 (Postel's Law)**: 클라이언트와 서버의 JSON 파서는 스펙에 정의되지 않은 알 수 없는 키(Unknown Key)를 수신하더라도 파싱 에러를 발생시키지 않고 조용히 무시해야 한다 (MUST). - **API 단종 예고**: 서버는 향후 지원이 중단될 API 응답에 `Warning: 299 - "Deprecated API"` HTTP 헤더를 포함할 수 있으며, 수신자는 이를 감지 시 시스템 로그에 기록해야 한다 (MUST). --- ## 2. Discovery 및 엔드포인트 라우팅 ### 2.1. API 버전 라우팅 모든 HTTP 및 WSS URI는 반드시 버전을 명시해야 한다 (예: `/v1/`). 페어링 시점부터 적용되며, 버전이 누락되거나 불일치할 경우 수신 측은 `404 Not Found` 또는 `426 Upgrade Required`를 반환해야 한다 (MUST). ### 2.2. Discovery Endpoint (Master Server) 클라이언트가 서버의 호환 버전을 동적으로 파악하기 위한 표준 진입점이다. **Endpoint**: `GET /.well-known/rawp-configuration` **Response** (200 OK): ```json { "supported_versions": ["v1", "v1.1"], "data_plane_protocol": "rawp-dps-1.0", "endpoints": { "pair": "/v1/auth/pair", "metrics": "/v1/nodes/metrics" } } ``` --- ## 3. Phase 1: 등록, 페어링 및 키 교환 (Registration & Lifecycle) 클라이언트와 마스터 서버가 상호 통신을 위한 토큰 페어(Access/Refresh)를 교환하고 검증하는 단계다. ### 3.1. 페어링 URL 발급 (Master Server) - **형식**: `https://{master_host}/v1/auth/pair?token={pairing_token}` - **제약 조건**: `pairing_token`은 발급 후 15분 이내에 사용되지 않으면 반드시 폐기되어야 한다 (MUST). ### 3.2. 등록 주소 검증 규약 (Address Validation) 클라이언트가 제출하는 `client_address`에 대한 마스터 서버의 검증 규칙: - **Allowed Schemes**: `https://` 사용 강제 (단, 명시적인 개발 플래그 활성화 시 `http://` 허용). - **Loopback Forbidden**: `127.0.0.1`, `localhost` 등 루프백 및 링크 로컬 주소는 등록을 거부해야 한다 (MUST). - **Private Network**: `192.168.x.x` 등 사설 IP는 허용된다. ### 3.3. 등록 요청 및 상호 키 교환 (Mutual Registration) 클라이언트는 등록 시 자신이 마스터 서버를 호출할 때 쓸 토큰을 발급받음과 동시에, 마스터 서버가 자신을 호출할 때 사용할 초기 토큰 페어를 생성하여 전달해야 한다 (MUST). **Endpoint**: `POST /v1/auth/pair` (Master Server 제공) **Request** (Client → Master): ```json { "pairing_token": "String (필수, 최소 32자 이상)", "client_address": "String (필수, 향후 마스터가 호출할 URI)", "device_name": "String (필수, 최대 100자)", "client_credentials": { "access_token": "String (필수, 마스터가 클라이언트를 호출할 때 사용할 토큰)", "refresh_token": "String (필수, 클라이언트용 갱신 토큰)", "expires_in": "Number (필수, 초 단위)" } } ``` **Response** (Master → Client) (200 OK): ```json { "access_token": "String (필수, 클라이언트가 마스터를 호출할 때 사용할 Bearer 토큰)", "refresh_token": "String (필수, 마스터용 장기 갱신 토큰)", "expires_in": "Number (필수)", "server_credentials": "Object (필수, 서버 서명 및 자격 증명용 JWK 공개키)" } ``` ### 3.4. 토큰 갱신 엔드포인트 대칭 구현 (Token Refresh) 양측은 타 API 호출 시 `401 Unauthorized`를 받으면, 발급 주체의 갱신 엔드포인트를 호출하여 토큰을 갱신하고 원래 요청을 재시도해야 한다 (MUST). #### 3.4.1. 클라이언트의 마스터 토큰 갱신 (Client → Master) - **Endpoint**: `POST /v1/auth/refresh` (Master Server 제공) - **Request**: `{"refresh_token": "String (필수)"}` - **Response**: §3.3의 Master → Client 응답 포맷과 동일. #### 3.4.2. 마스터의 클라이언트 토큰 갱신 (Master → Client) - **Endpoint**: `POST /v1/auth/refresh` (Local Client 제공) - **Request**: `{"refresh_token": "String (필수)"}` - **Response**: ```json { "access_token": "String (필수)", "refresh_token": "String (필수)", "expires_in": "Number (필수)" } ``` #### 3.4.3. 토큰 갱신 상세 규칙 **Refresh Token Rotation**: 토큰 갱신 응답에는 항상 새로운 `refresh_token`이 포함되어야 한다 (MUST). 갱신에 사용된 기존 `refresh_token`은 즉시 무효화해야 한다 (MUST). 이를 통해 탈취된 토큰의 재사용을 방지한다. **Replay 감지**: 이미 사용(무효화)된 `refresh_token`으로 갱신이 시도되면, 토큰 발급 주체는 해당 클라이언트(또는 사용자)에게 발급된 모든 토큰(access + refresh)을 즉시 무효화해야 한다 (MUST). 이는 토큰 탈취 후 공격자와 정당한 사용자가 경쟁적으로 갱신하는 시나리오를 차단한다. 무효화 후 `401 Unauthorized`를 반환한다. **갱신 실패 복구**: `refresh_token` 자체가 만료되었거나 무효화되어 갱신이 불가능한 경우(`401 Unauthorized` 수신), 클라이언트는 재페어링(§3.3) 절차를 개시해야 한다 (MUST). #### 3.4.4. Edge → Master 토큰 관리 엣지 노드(§9)가 마스터 서버에 접속할 때 사용하는 사용자 토큰의 갱신 및 폐기 절차이다. **토큰 갱신 — Endpoint**: `POST /v1/edge/auth/refresh` (Master Server 제공) **Request**: `{"refresh_token": "String (필수)"}` **Response (200 OK):** ```json { "access_token": "String (필수)", "refresh_token": "String (필수, 새로 발급된 토큰)", "expires_in": "Number (필수, 초 단위)" } ``` §3.4.3의 Refresh Token Rotation 및 Replay 감지 규칙이 동일하게 적용된다 (MUST). **토큰 폐기 (로그아웃) — Endpoint**: `POST /v1/edge/auth/revoke` (Master Server 제공) **Request**: `{"refresh_token": "String (필수)"}` **Response**: `204 No Content` 마스터는 해당 `refresh_token`과 연관된 모든 `access_token`을 즉시 무효화해야 한다 (MUST). 폐기 후 해당 토큰으로의 모든 API 호출은 `401 Unauthorized`를 반환한다. ### 3.5. 등록 해제 (Unregister) 클라이언트가 자발적으로 네트워크에서 이탈할 때 잔여 리소스를 정리한다. - **Endpoint**: `DELETE /v1/nodes/self` (Master Server 제공) - **Headers**: `Authorization: Bearer {client_access_token}` - **제약 조건**: 마스터 서버는 해당 클라이언트와 연결된 모든 WSS 세션을 즉시 강제 종료하고 상태를 정리해야 한다 (MUST). 관련된 모든 토큰은 즉시 무효화된다. - **Response**: `204 No Content` --- ## 4. Phase 2: 제어 및 모니터링 인터페이스 (Master → Client HTTP) 마스터 서버가 클라이언트를 호출할 때는 반드시 발급받은 `access_token`을 사용하며, 클라이언트는 서명을 검증해야 한다 (MUST). ### 4.1. 클라이언트 설정 동기화 (Client Configuration) 클라이언트는 시스템 한계치와 지원 능력을 서버에 동기화한다. 설정은 의미적 성격에 따라 독립된 Config Scope로 분리되며, 각 스코프는 전용 엔드포인트를 통해 전체 교체(Full Replacement) 방식으로 동기화된다. 마스터 서버는 수신한 설정 값으로 요청을 스로틀링하며, 보고되지 않은 기능을 요구해서는 안 된다 (MUST). #### 4.1.1. Config Scope 분리 원칙 클라이언트 설정은 다음 두 개의 Config Scope로 분리된다: | Config Scope | 엔드포인트 | 성격 | 변경 트리거 | | ------------------- | ----------------------------------- | -------------------------------------------------- | --------------------------------------------------------- | | **Resource Limits** | `PUT /v1/nodes/config/limits` | 수량적 운영 제약 — 처리량, 전송 크기, 메모리, 정책 | 시스템 부하 변동, 메모리 압박, 운영 정책 조정 | | **Capabilities** | `PUT /v1/nodes/config/capabilities` | 질적 기능 선언 — 클라이언트가 지원하는 기능 목록 | 플러그인/도구 로드·언로드, 모델 교체, 소프트웨어 업데이트 | 각 스코프는 독립된 `config_version`을 가지며, 한 스코프의 갱신이 다른 스코프의 버전에 영향을 주지 않는다 (MUST). 클라이언트는 변경이 발생한 스코프의 엔드포인트만 호출하면 되고, 변경되지 않은 스코프를 함께 재전송할 필요가 없다. #### 4.1.2. 초기 동기화 (Initial Synchronization) 클라이언트는 페어링 완료(§3.3) 직후 그리고 재기동 직후, **양쪽 Config Scope를 모두** 동기화해야 한다 (MUST). 초기 동기화 시 `config_version`은 `1`로 시작한다. 서버는 양쪽 스코프의 초기 동기화가 완료되기 전까지 해당 클라이언트에 대한 세션 초기화(§5.1) 요청을 보류해야 한다 (MUST). 초기 동기화 미완료 상태에서 세션 초기화가 시도되면 서버는 `503 Service Unavailable`을 반환해야 한다 (MUST). 마스터 서버가 특정 스코프에 대한 설정을 보유하지 않은 상태에서 해당 클라이언트의 헬스 체크(§4.2) 또는 에이전트 탐색(§4.3)을 수행하는 것은 허용된다. 설정 동기화 보류 규칙은 세션 초기화에만 적용된다. #### 4.1.3. 런타임 설정 갱신 (Runtime Configuration Update) 클라이언트는 런타임 중 설정 변경이 발생할 때마다 해당 스코프의 엔드포인트를 호출하여 서버에 변경 사항을 동기화해야 한다 (MUST). 각 호출은 해당 스코프의 **전체 필드를 포함하는 완전한 스냅샷**이어야 하며, 부분 필드만 포함하는 것은 허용되지 않는다 (MUST NOT). 서버는 수신한 스냅샷으로 해당 스코프의 기존 상태를 전체 교체한다. #### 4.1.4. 설정 버전 관리 및 순서 보장 (Config Versioning) 모든 Config Scope 요청에는 `config_version` 필드가 포함되어야 한다 (MUST). 이 값은 각 스코프별로 독립적이며, 클라이언트가 해당 스코프의 설정을 변경할 때마다 반드시 1씩 단조 증가시켜야 한다 (MUST). **순서 보장 규칙**: 마스터 서버는 수신한 `config_version`이 현재 보유한 해당 스코프의 버전보다 큰 경우에만 설정을 갱신해야 한다 (MUST). 현재 보유 버전 이하의 값이 수신되면 네트워크 지연에 의한 역전(stale update)으로 간주하고, `409 Conflict`를 반환하여 거부해야 한다 (MUST). 이때 응답 바디에는 서버가 현재 보유 중인 `config_version`을 포함하여 클라이언트가 재동기화할 수 있도록 해야 한다 (MUST). **클라이언트 재기동 시 버전 처리**: 클라이언트가 재기동될 때 이전 `config_version`을 영속적으로 보관하지 못하는 경우를 위해, 다음의 특수 규칙을 적용한다: `config_version`이 `1`인 요청은 서버가 보유한 버전과 무관하게 항상 수락해야 한다 (MUST). 이는 재기동 후 초기 동기화를 보장하기 위한 것이며, 서버는 `config_version: 1` 수신 시 내부 버전 카운터를 `1`로 리셋해야 한다. 단, 활성 세션이 존재하는 상태에서 `config_version: 1`이 수신되면, 서버는 이를 클라이언트 재기동 시그널로 해석하여 시스템 로그에 기록해야 한다 (MUST). #### 4.1.5. Resource Limits 동기화 클라이언트의 수량적 운영 제약을 서버에 동기화한다. **Endpoint**: `PUT /v1/nodes/config/limits` (Master Server 제공) **Request** (Client → Master): ```json { "config_version": "Number (필수, 이 스코프의 단조 증가 정수, 초기값 1)", "effective_at": "String (필수, ISO 8601, 클라이언트가 이 설정을 적용한 시각)", "reason": "String (선택, 변경 사유. 예: 'memory_pressure', 'scheduled_maintenance')", "limits": { "rate_limit": "Number (필수, 초당 처리 가능한 최대 HTTP 요청 수. 양의 정수)", "max_frame_size": "Number (필수, WSS 단일 프레임 최대 바이트. 최소 1024)", "reattach_window": "Number (필수, WSS 단절 시 DETACHED 상태 유지 시간 초. 0 이상 정수. 0이면 DETACHED 미지원으로, 단절 시 즉시 TERMINATED 전이)", "max_buffer_size": "Number (필수, 최대 메모리 버퍼 바이트. 양의 정수. max_frame_size 이상이어야 함)", "buffer_overflow_policy": "String (필수, 'RING' 또는 'DROP')" } } ``` **서버 검증 규칙 (MUST)**: | 필드 | 검증 조건 | 위반 시 에러 | | ------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------- | | `config_version` | 양의 정수. 현재 보유 버전보다 큰 값이거나, 정확히 `1` (재기동) | `409 Conflict` (버전 역전) | | `effective_at` | 유효한 ISO 8601 형식. 미래 시각 불허 (서버 현재 시각 + 30초 허용 오차 초과 시 거부) | `422` `EFFECTIVE_AT_IN_FUTURE` | | `limits.rate_limit` | 양의 정수 (> 0). 소수점 불허 | `422` `INVALID_RATE_LIMIT` | | `limits.max_frame_size` | 양의 정수. 최소 `1024` | `422` `FRAME_SIZE_TOO_SMALL` | | `limits.reattach_window` | 0 이상 정수 | `422` `INVALID_REATTACH_WINDOW` | | `limits.max_buffer_size` | 양의 정수. `max_frame_size` 이상이어야 함 | `422` `BUFFER_SMALLER_THAN_FRAME` | | `limits.buffer_overflow_policy` | `"RING"` 또는 `"DROP"` 중 하나 | `422` `INVALID_OVERFLOW_POLICY` | | 모든 필드 존재 여부 | `limits` 객체 내 모든 필드가 존재해야 함 (전체 교체) | `400` `CONFIG_FIELD_MISSING` | **Response (200 OK):** ```json { "config_version": "Number (수신한 버전 에코백)", "accepted_at": "String (ISO 8601, 서버가 설정을 수락하고 저장한 시각)", "active_sessions_affected": "Number (현재 RUNNING 또는 DETACHED 상태의 세션 수. 이 세션들은 이전 설정으로 계속 동작함. §4.1.8 참조)" } ``` #### 4.1.6. Capabilities 동기화 클라이언트가 지원하는 기능 목록을 서버에 동기화한다. **Endpoint**: `PUT /v1/nodes/config/capabilities` (Master Server 제공) **Request** (Client → Master): ```json { "config_version": "Number (필수, 이 스코프의 단조 증가 정수, 초기값 1)", "effective_at": "String (필수, ISO 8601, 클라이언트가 이 설정을 적용한 시각)", "reason": "String (선택, 변경 사유. 예: 'plugin_loaded: vision_v2', 'model_replaced: gpt4o')", "capabilities": [ "String (지원 기능 식별자 배열. 예: 'vision_v1', 'tool_call_v2'. 빈 배열 허용)" ] } ``` **서버 검증 규칙 (MUST)**: | 필드 | 검증 조건 | 위반 시 에러 | | ----------------- | ------------------------------------------------------------------------------------------ | ------------------------------------- | | `config_version` | §4.1.5의 `config_version` 검증과 동일 | `409 Conflict` (버전 역전) | | `effective_at` | §4.1.5의 `effective_at` 검증과 동일 | `422` `EFFECTIVE_AT_IN_FUTURE` | | `capabilities` | JSON 배열이어야 함. 빈 배열 허용 | `422` `INVALID_CAPABILITIES_FORMAT` | | `capabilities[*]` | 각 항목은 비어 있지 않은 문자열. 최대 128자. 허용 문자: `a-z`, `A-Z`, `0-9`, `_`, `.`, `-` | `422` `INVALID_CAPABILITY_IDENTIFIER` | | 중복 검사 | 배열 내 동일한 값이 2회 이상 출현하면 거부 | `422` `DUPLICATE_CAPABILITY` | **서버의 미인식 Capability 처리**: 서버는 자신이 인식하지 못하는 capability 식별자를 수신하더라도, 형식 검증을 통과하면 거부 없이 저장해야 한다 (MUST). 이는 §1.3의 필드 무시 원칙(Postel's Law)과 일관되며, 클라이언트가 서버보다 먼저 새로운 기능을 탑재하는 시나리오를 허용하기 위함이다. **Response (200 OK):** ```json { "config_version": "Number (수신한 버전 에코백)", "accepted_at": "String (ISO 8601, 서버가 설정을 수락하고 저장한 시각)", "active_sessions_affected": "Number (현재 RUNNING 또는 DETACHED 상태의 세션 수. 이 세션들은 이전 capabilities로 계속 동작함. §4.1.8 참조)" } ``` #### 4.1.7. 설정 갱신 에러 응답 (Config Update Errors) 설정 갱신 요청이 검증에 실패한 경우, 서버는 §7.1의 에러 응답 표준 규격을 확장한 다음 포맷으로 응답해야 한다 (MUST). **검증 실패 (422 Unprocessable Entity):** ```json { "error_code": "String (에러 코드. 예: 'CONFIG_VALIDATION_FAILED')", "message": "String (휴먼 리더블 디버깅 메시지)", "field_errors": [ { "field": "String (문제가 된 필드의 JSON Path. 예: 'limits.rate_limit', 'capabilities[2]')", "constraint": "String (위반된 제약 조건의 식별자. 예: 'POSITIVE_INTEGER', 'MIN_VALUE:1024', 'ENUM:RING|DROP')", "reason": "String (휴먼 리더블 설명. 예: 'MUST be a positive integer')" } ] } ``` `field_errors` 배열에는 검증에 실패한 **모든** 필드가 포함되어야 한다 (MUST). 첫 번째 위반에서 중단하고 나머지를 생략해서는 안 된다. 이는 클라이언트가 한 번의 응답으로 모든 위반 사항을 파악하고 수정할 수 있도록 하기 위함이다. **버전 충돌 (409 Conflict):** ```json { "error_code": "CONFIG_VERSION_CONFLICT", "message": "String (휴먼 리더블 디버깅 메시지)", "current_config_version": "Number (서버가 현재 보유 중인 해당 스코프의 config_version)" } ``` `current_config_version`을 통해 클라이언트는 자신의 `config_version`을 서버 값 이상으로 재조정한 후 재시도할 수 있다. #### 4.1.8. 설정 적용 범위: Session-Pinned Config 런타임 설정 갱신은 **새로 초기화되는 세션에만 적용**되며, 이미 `RUNNING` 또는 `DETACHED` 상태인 세션에는 소급 적용되지 않는다 (MUST NOT). 이를 Session-Pinned Config라 한다. **바인딩 규칙:** - **신규 세션** (`reattach: false`): 마스터 서버가 `POST /v1/session/init` (§5.1)을 호출하는 시점에 보유한 각 스코프의 최신 설정 스냅샷을 해당 세션에 바인딩한다. 바인딩된 설정은 세션이 `TERMINATED` 상태로 전이될 때까지 불변이다 (MUST). - **세션 재연결** (`reattach: true`): 원래 세션에 바인딩된 설정을 그대로 유지한다. 재연결 시점의 최신 설정으로 교체하지 않는다 (MUST NOT). **서버 추적 의무**: 마스터 서버는 각 세션에 바인딩된 `config_version` 쌍(limits 스코프 버전 + capabilities 스코프 버전)을 세션 메타데이터로 보관해야 한다 (MUST). 이 정보는 세션의 행동을 예측하고 디버깅하는 데 필요하다. **활성 세션에 대한 서버의 행동 원칙**: 설정 갱신 후에도 활성 세션은 바인딩된 이전 설정에 따라 동작하므로, 서버는 다음을 준수해야 한다 (MUST): - 활성 세션에 대한 스로틀링은 해당 세션에 바인딩된 `rate_limit` 값을 기준으로 적용한다. 갱신된 값이 아니다. - 활성 세션에 대한 기능 요구는 해당 세션에 바인딩된 `capabilities` 목록 내에서만 허용한다. 갱신 후 추가된 capability를 기존 세션에 요구해서는 안 된다. - 갱신 후 제거된 capability가 활성 세션에 바인딩되어 있는 경우, 해당 세션은 정상적으로 계속 동작한다. 서버가 해당 세션을 강제 종료하거나 기능을 제한해서는 안 된다 (MUST NOT). #### 4.1.9. 레거시 호환: POST /v1/nodes/config (지원 종료) `POST /v1/nodes/config` 엔드포인트는 지원이 종료되었다. 모든 클라이언트는 §4.1.5 및 §4.1.6의 분리된 PUT 엔드포인트를 사용해야 한다 (MUST). ### 4.2. 헬스 체크 (Health Check) **Endpoint**: `GET /v1/health` (Local Client 제공) **Response**: ```json { "status": "String (필수, 'online', 'busy', 'error')", "uptime": "Number (필수, 클라이언트 가동 초)", "active_sessions": "Number (선택, 현재 유지 중인 세션 수)" } ``` **상태 전이 규칙**: 활성 세션이 0개면 즉시 `online`. 1개 이상 존재 시 `busy`. 세션이 0이 되면 즉시 `online`으로 복귀해야 한다 (MUST). ### 4.3. 에이전트 탐색 및 관리 (Agent Discovery & Management) 클라이언트 내 실행 가능한 에이전트 목록 조회 및 관리를 제공한다. **Endpoint**: `GET /v1/agents` (Local Client 제공) **Response**: ```json { "agents": [ { "id": "String (필수, 고유 식별자)", "name": "String (필수, 세션 식별자로 사용될 이름)", "is_active": "Boolean (필수, 에이전트 활성화 상태)", "type": "String (필수, 'builtin' | 'custom')", "adapter_type": "String (필수, 'process' | 'sdk'. RAWP-DPS 1.0.1 §17.1 참조)", "supported_dps_versions": [ "String (필수, 지원하는 DPS 프로토콜 버전. 예: 'rawp-dps-1.0')" ], "models": [ { "id": "String (필수, 모델 식별자)", "name": "String (필수, 표시명)", "is_default": "Boolean (필수)" } ], "options": [ { "name": "String (필수, 옵션 이름)", "type": "String (필수, 'string' | 'boolean' | 'number' | 'enum')", "required": "Boolean (필수)", "default": "Any (선택, 기본값)", "enum_values": ["String (선택, type이 'enum'일 때 허용 값 목록)"], "description": "String (선택, 옵션 설명)" } ], "single_turn_process": "Boolean (선택, 기본값 false. RAWP-DPS 1.0.1 §18 참조)", "description": "String (선택, 에이전트 설명)" } ] } ``` `type: "builtin"`은 클라이언트에 사전 탑재된 에이전트, `type: "custom"`은 사용자가 등록한 커스텀 에이전트를 의미한다. `models` 배열이 비어 있으면 에이전트가 모델 선택을 지원하지 않음을 의미한다. #### 4.3.1. 에이전트 상세 조회 **Endpoint**: `GET /v1/agents/{agent_id}` (Local Client 제공) **Response (200 OK)**: §4.3의 `agents` 배열 내 단일 에이전트 객체와 동일한 스키마. **에러**: 존재하지 않는 `agent_id` 시 `404 Not Found`. #### 4.3.2. 커스텀 에이전트 등록 **Endpoint**: `POST /v1/agents` (Local Client 제공) **Request**: ```json { "name": "String (필수, 최대 100자)", "adapter_type": "String (필수, 'process' | 'sdk')", "command": "String (adapter_type이 'process'일 때 필수, 실행 명령어)", "supported_dps_versions": ["String (필수)"], "models": ["Object (선택, §4.3의 모델 스키마)"], "options": ["Object (선택, §4.3의 옵션 스키마)"], "single_turn_process": "Boolean (선택)", "resume_flag": "String (선택, single_turn_process: true일 때. RAWP-DPS 1.0.1 §18.1 참조)", "description": "String (선택)" } ``` **Response (201 Created)**: 생성된 에이전트 객체 (`type: "custom"`, `is_active: true`). **제약 조건**: `name`이 기존 에이전트와 중복되면 `409 Conflict`를 반환한다 (MUST). #### 4.3.3. 커스텀 에이전트 수정 **Endpoint**: `PATCH /v1/agents/{agent_id}` (Local Client 제공) **Request**: §4.3.2의 필드 중 변경할 필드만 포함. `type: "builtin"` 에이전트는 수정할 수 없다 (MUST NOT). 시도 시 `403 Forbidden`. **Response (200 OK)**: 수정된 에이전트 객체. #### 4.3.4. 커스텀 에이전트 삭제 **Endpoint**: `DELETE /v1/agents/{agent_id}` (Local Client 제공) **제약 조건**: `type: "builtin"` 에이전트는 삭제할 수 없다 (MUST NOT). 시도 시 `403 Forbidden`. 해당 에이전트에 활성 세션이 존재하면 `409 Conflict`를 반환한다 (MUST). **Response**: `204 No Content`. #### 4.3.5. 에이전트 활성화/비활성화 **Endpoint**: `PATCH /v1/agents/{agent_id}/status` (Local Client 제공) **Request**: `{"is_active": "Boolean (필수)"}` **Response (200 OK)**: `{"id": "String", "is_active": "Boolean"}` 비활성화된 에이전트(`is_active: false`)는 `GET /v1/agents` 목록에 포함되지만, 세션 초기화(§5.1)에서 해당 `agent_name` 사용이 거부된다 (MUST). 거부 시 `400 Bad Request` (error_code: `"AGENT_INACTIVE"`). ### 4.4. 파일 탐색 (Directory Browsing) 인가된 작업 경로 내의 디렉토리를 조회한다. **Endpoint**: `POST /v1/fs/list` (Local Client 제공) **Request**: `{"target_path": "String (필수, 조회할 절대 경로)"}` --- ## 5. Phase 3: 세션 라이프사이클 및 연결 (Session & I/O) **세션 생명주기**: `INIT` → `RUNNING` → `DETACHED`(소켓 단절 시) → `TERMINATED` ### 5.1. 세션 초기화 및 재연결 (Session Initialization) 신규 에이전트 실행 또는 DETACHED 상태 프로세스에 WSS 바인딩을 위한 티켓을 발급한다. **Endpoint**: `POST /v1/session/init` (Local Client 제공) **Request**: ```json { "session_id": "String (필수, UUID v4)", "agent_name": "String (reattach: false 시 필수)", "workspace_path": "String (reattach: false 시 필수)", "ticket": "String (필수, WSS 연결 인증용 난수 티켓)", "reattach": "Boolean (선택, 기본값 false. true 시 기존 구동 중 프로세스 바인딩)", "last_sync_timestamp": "String (선택, ISO 8601)", "last_message_id": "String (선택, UUID)" } ``` **제약 조건**: `reattach: true`이나 해당 `session_id`가 존재하지 않거나 종료된 경우 `404 Not Found` 반환 (MUST). **Config 바인딩 제약 조건**: 마스터 서버는 `reattach: false`로 세션을 초기화할 때, §4.1.8에 따라 양쪽 Config Scope(limits, capabilities)의 최신 스냅샷을 해당 세션에 바인딩해야 한다 (MUST). 양쪽 스코프의 초기 동기화(§4.1.2)가 완료되지 않은 클라이언트에 대해서는 세션 초기화를 시도해서는 안 된다 (MUST NOT). ### 5.2. 세션 명시적 종료 (Session Termination) **Endpoint**: `DELETE /v1/session/{session_id}` (Local Client 제공) **제약 조건**: 클라이언트는 자식 프로세스에 `SIGTERM`을 전송하고, 5초 내 종료되지 않으면 `SIGKILL`로 강제 종료 후 메모리 버퍼 및 소켓을 파기해야 한다 (MUST). **WSS 알림 의무**: 세션 종료 처리 후 WSS 연결을 닫기 전에, 해당 세션에 바인딩된 모든 WSS Connection에 RAWP-DPS 1.0.1 §7.7의 `session.deleted` 이벤트를 발송해야 한다 (MUST). 발송 순서는 RAWP-DPS 1.0.1 §7.5.3(진행 중 항목 정리) → `session.deleted` → WSS 종료이다. ### 5.3. WebSocket 연결 수립 (WebSocket Upgrade) **Connection URI**: `wss://{client_address}/v1/ws/stream?ticket={ticket}&session_id={session_id}` **인가 절차** (MUST): 1. 클라이언트는 HTTP Upgrade 요청 수신 시 HTTP 헤더의 `Sec-WebSocket-Protocol`을 확인해야 한다. RAWP-DPS 1.0.1 적용 시 `rawp-dps-1.0`을 우선 수락하고, 미지원 시 `rawp-1.0`으로 폴백한다. 양측 모두 미지원 시 `426` 또는 `400`을 반환해야 한다. 2. Query Parameter의 `ticket`과 `session_id`를 검증하고, 만료되거나 불일치 시 `401 Unauthorized`를 반환 후 연결을 거절한다. 3. 사용된 티켓은 1회 사용 시 즉시 무효화(Burn)해야 한다. ### 5.4. 세션 이름 변경 (Session Rename) 세션에 사용자 친화적 이름을 부여하고, 변경 사항을 모든 참여자에게 전파한다. #### 5.4.1. 마스터 → 클라이언트 이름 변경 **Endpoint**: `PATCH /v1/session/{session_id}` (Local Client 제공) **Request**: ```json { "name": "String (필수, 새 세션 이름. 최대 200자)" } ``` **Response (200 OK)**: ```json { "session_id": "String (필수)", "name": "String (필수, 변경된 이름)", "updated_at": "String (필수, ISO 8601)" } ``` **제약 조건**: 존재하지 않거나 `TERMINATED` 상태인 세션에 대한 요청은 `404 Not Found`를 반환한다 (MUST). **WSS 알림 의무**: HTTP 응답 후, 클라이언트는 해당 세션에 바인딩된 모든 WSS Connection에 RAWP-DPS 1.0.1 §7.6의 `session.renamed` 이벤트를 발송해야 한다 (MUST). 이는 로컬 UI WSS(§10.6)와 마스터 WSS를 모두 포함한다. #### 5.4.2. 엣지 → 마스터 이름 변경 **Endpoint**: `PATCH /v1/edge/sessions/{session_id}` (Master Server 제공) **Request/Response**: §5.4.1과 동일한 스키마. 마스터는 이름 변경 수신 시 대상 로컬 클라이언트의 `PATCH /v1/session/{session_id}`를 호출하여 전파하고, 해당 세션에 연결된 모든 엣지 WSS에 RAWP-DPS 1.0.1 §7.6의 `session.renamed` 이벤트를 브로드캐스트해야 한다 (MUST). #### 5.4.3. 로컬 세션 이름 변경 **Endpoint**: `PATCH /local/v1/sessions/{session_id}` (Local Client 제공) **Request/Response**: §5.4.1과 동일한 스키마. HTTP 응답 후, 클라이언트는 해당 세션에 바인딩된 모든 WSS Connection에 `session.renamed` 이벤트를 발송해야 한다 (MUST). --- ## 6. 데이터 평면 스트리밍 규격 (Data Plane WSS Protocol) > **본 절은 별도 규격으로 분리되었다.** > > WSS 연결 수립(§5.3) 이후 교환되는 데이터 프레임의 Envelope 구조, 이벤트 타입 및 페이로드 정의는 **RAWP-DPS 1.0.1** 규격을 따른다. > > RAWP-DPS 1.0.1은 다음을 정의한다: > > - **Envelope 구조**: 버전 필드(`v`), 네임스페이스 기반 이벤트 타입, Turn 추적(`turn_id`), 요청-응답 상관(`parent_id`)을 포함하는 확장된 프레임 구조 (RAWP-DPS §2) > - **에이전트 출력** (`agent.*`): 텍스트 스트리밍, 사고(Thinking) 스트리밍, 상호작용 요청, 에러 보고, 파일 전송, 상태 변경 (RAWP-DPS §4) > - **도구 호출** (`tool.*`): 도구 호출 요청/결과/실시간 스트리밍, 도구 카탈로그 고지 (RAWP-DPS §5) > - **제어 명령** (`control.*`): 프롬프트 전달, 프롬프트 취소, 상호작용 응답, 컨텍스트 압축 요청, 동작 모드 전환 (RAWP-DPS §6) > - **세션 이벤트** (`session.*`): 이력 복구, 컨텍스트 압축 보고, 사용량 지표, Turn 라이프사이클, 능력 협상 (RAWP-DPS §7, §12) > - **태스크 관리** (RAWP-DPS §8), **계획 문서** (RAWP-DPS §9), **서브에이전트 위임** (RAWP-DPS §10), **구조화 출력** (RAWP-DPS §11) > - **파일 참조** (RAWP-DPS §16): 인라인 파일 참조 토큰 포맷, 실시간 퍼지 파일 검색, 토큰 이스케이프 규칙, 어댑터별 변환 규칙 > > 구현자는 RAWP-DPS 1.0.1 규격 문서를 참조하여 데이터 평면을 구현해야 한다 (MUST). --- ## 7. 보안 및 예외 처리 (Security & Errors) ### 7.1. HTTP 에러 응답 표준 규격 클라이언트나 서버가 HTTP 오류를 반환할 때, 다음 JSON 포맷을 Body에 포함해야 한다 (MUST). ```json { "error_code": "String (e.g., TOKEN_EXPIRED, SESSION_NOT_FOUND, INVALID_PATH)", "message": "String (휴먼 리더블 디버깅 메시지)" } ``` **상태 코드 매핑 규칙**: | HTTP 상태 코드 | 사유 | | --------------------------- | --------------------------------------------------------------------------------------------------------------- | | `400 Bad Request` | 필수 파라미터 누락, 잘못된 UUID 포맷. | | `401 Unauthorized` | 토큰 만료, 서명 불일치, WSS Upgrade 티켓 무효. | | `403 Forbidden` | White-list 없는 명령어, 인가되지 않은 디렉토리 접근. | | `404 Not Found` | 존재하지 않는 `session_id` 제어 시도. | | `409 Conflict` | 이미 `RUNNING` 중인 세션에 `reattach: false`로 초기화 시도. Config Scope의 `config_version` 역전 감지 (§4.1.4). | | `422 Unprocessable Entity` | 설정 갱신 시 필드값 형식, 범위, 상호 정합성 위반 (§4.1.7). | | `429 Too Many Requests` | `rate_limit` 초과. | | `500 Internal Server Error` | 내부 파일 시스템 오류, 바인딩 충돌 등. | | `503 Service Unavailable` | 초기 설정 동기화 미완료 상태에서 세션 초기화 시도 (§4.1.2). | ### 7.2. 보안 제약 및 필수 보호 장치 (MUST) - **경로 검증 (Path Normalization)**: 클라이언트는 모든 파일/디렉토리 조회 요청 시 경로 내 `../` 등을 정규화하고 인가된 `workspace_path` 범위 내인지 반드시 검증해야 한다 (Directory Traversal 방어). - **명령어 및 에이전트 보호 (Whitelisting)**: 활성화된 `agent_name`에 매핑된 명령어만 실행 가능해야 하며, 임의의 쉘 스크립트나 바이너리를 원격으로 주입받아 실행하는 시도는 차단해야 한다 (RCE 방어). - **버퍼 관리 및 OOM 방어**: WSS 단절 중 자식 프로세스를 유지할 때 발생하는 출력은 버퍼에 저장하되, `max_buffer_size`에 도달하면 반드시 사전에 합의된 `buffer_overflow_policy`(RING/DROP)에 따라 강제로 메모리를 관리하여 게이트웨이 다운을 방지해야 한다. - **도구 호출 보안**: RAWP-DPS 1.0.1 적용 시, `tool.catalog.publish`에서 고지된 도구만 `tool.invoke.request`에 사용 가능하며, 고지되지 않은 도구의 호출 요청은 클라이언트가 거부해야 한다 (MUST). 자세한 도구 보안 규칙은 RAWP-DPS 1.0.1 §15를 참조한다. ### 7.3. Rate Limiting **적용 대상**: Rate Limiting은 HTTP 엔드포인트에 대한 요청에 적용된다. WSS 프레임은 Rate Limit 대상이 아니다. WSS는 이미 세션당 단일 연결이므로 별도의 프레임 수준 제한을 두지 않는다. **`limits.rate_limit` 필드의 의미**: §4.1.5에서 클라이언트가 보고하는 `limits.rate_limit`은 해당 클라이언트가 초당 처리 가능한 최대 HTTP 요청 수를 의미한다. 마스터 서버는 이 값을 초과하는 빈도로 해당 클라이언트에 HTTP 요청을 보내서는 안 된다 (MUST NOT). **429 응답 형식**: Rate Limit을 초과한 요청에 대해 `429 Too Many Requests`를 반환할 때, 다음을 포함해야 한다 (MUST): ```json { "error_code": "RATE_LIMITED", "message": "String (휴먼 리더블 디버깅 메시지)", "retry_after": "Number (필수, 초 단위, 다음 요청까지 대기해야 할 최소 시간)" } ``` `Retry-After` HTTP 헤더에도 동일한 값을 초 단위로 포함해야 한다 (MUST). **양방향 적용**: 마스터가 클라이언트의 `rate_limit`을 초과하면 클라이언트가 429를 반환하고, 클라이언트가 마스터의 내부 Rate Limit을 초과하면 마스터가 429를 반환한다. 양측 모두 본 절의 응답 형식을 따른다 (MUST). --- ## 8. 클라이언트 UI/UX 구현 지침 (Client UI Implementer Notes) > **본 절은 별도 규격으로 분리되었다.** > > RAWP 프로토콜의 제어 평면 이벤트와 RAWP-DPS 1.0.1의 데이터 평면 이벤트를 사용자에게 시각적으로 전달하는 클라이언트 렌더링 규격은 **RAWP-CRS 1.0.1** 규격을 따른다. > > RAWP-CRS 1.0.1은 다음을 정의한다: > > - **메시지 아키텍처**: User(우측 버블), Agent(좌측 버블리스), System(중앙 정렬) 세 행위자의 시각적 정체성 및 레이아웃 규칙 (RAWP-CRS §2) > - **유저 메시지**: 채팅 버블 스타일, 슬래시 명령어 자동 완성 UI, 명령어 실행 버블 렌더링, **파일 참조 입력** (RAWP-CRS §3) > - **에이전트 메시지**: 버블리스 디자인 원칙, 마크다운 렌더링 컴포넌트 명세, 점진적 텍스트 스트리밍, 사고 과정 시각화, 도구 호출 결과, Code Diff 뷰어, 에이전트 상태 표시, 태스크 목록 (RAWP-CRS §4) > - **시스템 메시지**: 생략 금지 원칙, 드롭다운 패턴, 중지 피드백, 에러/경고 심각도별 시각 차등 (RAWP-CRS §5) > - **비스트리밍 환경 적응**: Discord, Slack 등 완성형 플랫폼의 Typing Indicator 활용, 버퍼링, 사고 과정 우회 처리 (RAWP-CRS §6) > > 구현자는 RAWP-CRS 1.0.1 규격 문서를 참조하여 클라이언트 UI를 구현해야 한다 (MUST). --- ## 9. Edge Node API (사용자 접점 통신 규약) 본 장은 사용자가 원격지에서 UI(웹 대시보드, 모바일 앱, 챗봇 게이트웨이 등)를 통해 마스터 서버(Master Server)에 접속하여 로컬 클라이언트(Local Client)를 제어하기 위한 Northbound API를 정의한다. **아키텍처 정의 (3-Tier):** ``` [Edge Node (User UI)] ↔ (Edge API) ↔ [Master Server] ↔ (RAWP/Southbound) ↔ [Local Client] ``` ### 9.1. Edge Node 인증 및 권한 (Authentication) 엣지 노드는 마스터 서버의 로컬 클라이언트 인증 방식(Pairing Token)과 분리된 **OIDC(OpenID Connect) 기반** 사용자 인증 체계를 사용한다. 세부 로그인 방법(지원 프로바이더, 허용 흐름 등)은 마스터 서버가 Discovery 엔드포인트를 통해 공개하고, 엣지 노드가 이를 조회하여 협상한다. 모든 Edge API 요청은 `Authorization: Bearer {access_token}` 헤더를 포함해야 한다 (MUST). #### 9.1.1. 인증 구성 Discovery 엣지 노드는 로그인 UI를 구성하기 전에 마스터 서버의 인증 구성을 조회해야 한다 (MUST). **Endpoint**: `GET /v1/edge/auth/configuration` (Master Server 제공, 인증 불필요) **Response (200 OK)**: ```json { "issuer": "String (필수, OIDC Issuer URL. 예: 'https://auth.example.com')", "authorization_endpoint": "String (필수, OIDC Authorization Endpoint URL)", "token_endpoint": "String (필수, OIDC Token Endpoint URL)", "userinfo_endpoint": "String (선택, OIDC UserInfo Endpoint URL)", "end_session_endpoint": "String (선택, OIDC End Session Endpoint URL)", "supported_flows": [ "String (필수, 지원하는 OIDC 인증 흐름 목록. 아래 열거 값)" ], "supported_scopes": [ "String (필수, 허용하는 OIDC Scope 목록. 최소 'openid' 포함)" ], "client_id": "String (필수, 엣지 노드가 사용할 OIDC Client ID)", "redirect_uris": ["String (필수, 허용되는 Redirect URI 패턴 목록)"] } ``` **`supported_flows` 열거 값:** | 값 | 설명 | | -------------------------------- | ------------------------------------------------------------------------- | | `authorization_code` | Authorization Code Flow (PKCE 필수). 브라우저 기반 엣지 노드의 기본 흐름. | | `authorization_code_with_device` | Device Authorization Grant (RFC 8628). 브라우저가 없는 환경(TV, CLI 등). | 마스터 서버는 최소 `authorization_code`를 지원해야 한다 (MUST). #### 9.1.2. Authorization Code Flow (PKCE) 브라우저 기반 엣지 노드의 표준 인증 흐름이다. RFC 7636(PKCE)을 필수 적용한다 (MUST). **흐름:** 1. 엣지 노드가 `code_verifier`(최소 43자 랜덤 문자열)를 생성하고, SHA-256 해시하여 `code_challenge`를 만든다. 2. 엣지 노드가 사용자를 `authorization_endpoint`로 리디렉트한다: ``` {authorization_endpoint}? response_type=code &client_id={client_id} &redirect_uri={redirect_uri} &scope=openid {추가_scope} &state={csrf_state} &code_challenge={code_challenge} &code_challenge_method=S256 ``` 3. 사용자가 인증을 완료하면, OIDC 프로바이더가 `redirect_uri`로 `code`와 `state`를 반환한다. 4. 엣지 노드가 `state`를 검증한 후, 마스터 서버의 토큰 교환 엔드포인트를 호출한다. #### 9.1.3. 토큰 교환 엣지 노드가 OIDC Authorization Code를 마스터 서버의 Access/Refresh Token으로 교환한다. 마스터 서버는 OIDC 프로바이더에 코드를 검증하고, 자체 토큰을 발급한다. **Endpoint**: `POST /v1/edge/auth/token` (Master Server 제공) **Request**: ```json { "grant_type": "String (필수, 'authorization_code')", "code": "String (필수, OIDC Authorization Code)", "redirect_uri": "String (필수, 인증 요청 시 사용한 redirect_uri와 동일해야 함)", "code_verifier": "String (필수, PKCE code_verifier)" } ``` **Response (200 OK)**: ```json { "access_token": "String (필수, 마스터 서버 발급 Bearer 토큰)", "refresh_token": "String (필수, 갱신용 토큰)", "token_type": "String (필수, 'Bearer')", "expires_in": "Number (필수, access_token 만료까지 초)", "user_id": "String (필수, 인증된 사용자 식별자)", "id_token": "String (선택, OIDC ID Token. 마스터가 전달하는 경우)" } ``` **에러 응답 (401 Unauthorized)**: ```json { "error_code": "String (필수, 'INVALID_CODE' | 'CODE_EXPIRED' | 'INVALID_REDIRECT_URI' | 'PKCE_MISMATCH')", "message": "String (필수, 휴먼 리더블 에러 메시지)" } ``` #### 9.1.4. 토큰 갱신 및 폐기 **토큰 갱신**: §3.4.4의 `POST /v1/edge/auth/refresh`를 사용한다. **토큰 폐기 (로그아웃)**: §3.4.4의 `POST /v1/edge/auth/revoke`를 사용한다. OIDC `end_session_endpoint`가 Discovery에 포함된 경우, 엣지 노드는 마스터 토큰 폐기와 함께 OIDC 세션도 종료해야 한다 (SHOULD). §3.4.3의 Refresh Token Rotation 및 Replay 감지 규칙이 Edge 인증에도 동일하게 적용된다 (MUST). ### 9.2. 로컬 머신(Node) 풀 관리 및 상태 모니터링 사용자 계정에 바인딩된 로컬 클라이언트 목록과 현재 상태를 조회한다. #### 9.2.1. 연결된 노드 목록 조회 **Endpoint:** `GET /v1/edge/nodes` **Response (200 OK):** ```json { "nodes": [ { "node_id": "String (필수, 클라이언트 식별자)", "device_name": "String (필수, 등록 시 제공된 이름)", "status": "String (필수, 'online' | 'busy' | 'offline')", "last_seen": "String (필수, ISO 8601, 마스터 서버와 마지막 통신 시각)", "capabilities": [ "String (선택, 로컬 머신이 지원하는 기능 목록. §4.1.6에서 동기화된 최신 값 반영)" ], "active_sessions_count": "Number (필수)", "config_versions": { "limits": "Number (선택, 현재 보유 중인 Resource Limits config_version)", "capabilities": "Number (선택, 현재 보유 중인 Capabilities config_version)" } } ] } ``` #### 9.2.2. 로컬 머신별 사용량 및 지표 (Metrics) 해당 노드에서 발생한 세션들의 누적/실시간 사용량을 조회한다. **Endpoint:** `GET /v1/edge/nodes/{node_id}/metrics` **Query Parameters:** `?period=today` (선택, `'today'`, `'week'`, `'month'`) **Response (200 OK):** ```json { "node_id": "String (필수)", "period": "String (필수)", "aggregated_usage": { "total_input_tokens": "Number", "total_output_tokens": "Number", "estimated_cost": "Number (USD 기준)", "total_tool_invocations": "Number" }, "health": { "uptime_seconds": "Number", "last_error": "String (선택, 최근 발생한 치명적 에러)" } } ``` #### 9.2.3. 노드 설정 조회 (Node Configuration Lookup) 엣지 노드가 특정 로컬 머신의 현재 설정 상태를 조회한다. **Endpoint:** `GET /v1/edge/nodes/{node_id}/config` **Response (200 OK):** ```json { "node_id": "String (필수)", "limits": { "config_version": "Number (필수)", "effective_at": "String (필수, ISO 8601)", "limits": { "rate_limit": "Number", "max_frame_size": "Number", "reattach_window": "Number", "max_buffer_size": "Number", "buffer_overflow_policy": "String" } }, "capabilities": { "config_version": "Number (필수)", "effective_at": "String (필수, ISO 8601)", "capabilities": ["String"] } } ``` 이 엔드포인트는 Pull 방식으로 최신 설정을 확인하기 위한 것이다. 실시간 Push 알림은 §9.4.4를 참조한다. ### 9.3. 엣지 세션(Edge Session) 라이프사이클 관리 엣지 노드는 마스터 서버를 통해 특정 로컬 머신에 새로운 세션을 생성하거나 기존 세션을 조회할 수 있다. #### 9.3.1. 원격 세션 생성 (Edge → Master) 사용자가 특정 로컬 머신을 선택하여 대화를 시작할 때 호출한다. **Endpoint:** `POST /v1/edge/nodes/{node_id}/sessions` **Request:** ```json { "agent_name": "String (필수, 실행할 에이전트)", "workspace_path": "String (선택, 작업 디렉토리)" } ``` **Response (201 Created):** ```json { "session_id": "String (필수, UUID v4)", "edge_ws_ticket": "String (필수, 엣지용 WSS 연결 티켓. 1회용)", "status": "String (필수, 'INIT' | 'RUNNING')", "pinned_config_versions": { "limits": "Number (필수, 이 세션에 바인딩된 Resource Limits config_version)", "capabilities": "Number (필수, 이 세션에 바인딩된 Capabilities config_version)" } } ``` **동작 원리:** 이 요청을 받은 마스터 서버는 즉시 대상 로컬 머신의 `POST /v1/session/init` (본 스펙 §5.1)을 호출하여 로컬 세션을 확보한 뒤 응답해야 한다. #### 9.3.2. 활성 세션 목록 조회 사용자 계정에 귀속된 모든 세션의 현재 상태를 조회한다. **Endpoint:** `GET /v1/edge/sessions` **Query Parameters:** | 파라미터 | 타입 | 필수 | 설명 | | --------- | ------ | ---- | ----------------------------------------------------------------------------------------------- | | `status` | String | 선택 | 세션 상태 필터. `'INIT'`, `'RUNNING'`, `'DETACHED'` 중 하나. 미지정 시 모든 상태의 세션을 반환. | | `node_id` | String | 선택 | 특정 노드에 귀속된 세션만 필터링. | **Response (200 OK):** ```json { "sessions": [ { "session_id": "String (필수, UUID v4)", "node_id": "String (필수, 세션이 실행 중인 로컬 클라이언트 식별자)", "agent_name": "String (필수, 실행 중인 에이전트 이름)", "status": "String (필수, 'INIT' | 'RUNNING' | 'DETACHED')", "created_at": "String (필수, ISO 8601, 세션 생성 시각)", "last_activity_at": "String (필수, ISO 8601, 마지막 이벤트 수신 시각)", "pinned_config_versions": { "limits": "Number (필수, 이 세션에 바인딩된 Resource Limits config_version)", "capabilities": "Number (필수, 이 세션에 바인딩된 Capabilities config_version)" } } ] } ``` `pinned_config_versions`를 통해 엣지 노드는 각 세션이 어떤 시점의 설정으로 동작 중인지를 목록 수준에서 확인할 수 있다. 노드의 현재 `config_versions`(§9.2.1)와 비교하면, 세션이 최신 설정인지 이전 설정인지를 즉시 판별할 수 있다. `TERMINATED` 상태의 세션은 이미 종료되었으므로 목록에 포함하지 않는다 (MUST NOT). 종료된 세션의 이력 조회가 필요한 경우 별도의 세션 이력 API를 통해 제공한다. ### 9.4. 엣지 데이터 평면 (Edge WebSocket 통신) 엣지 노드는 발급받은 티켓을 사용하여 마스터 서버와 WSS를 연결하며, 마스터 서버는 이 연결을 대상 로컬 머신과 투명하게 중계(Transparent Proxy)한다. **Connection URI:** `wss://{master_host}/v1/edge/ws/stream?ticket={edge_ws_ticket}&session_id={session_id}` #### 9.4.1. 프로토콜 투명성 (Protocol Transparency) 엣지 노드와 마스터 서버 간의 WSS 통신은 RAWP-DPS 1.0.1 규격을 완전히 동일하게 사용한다 (MUST). 엣지 노드는 본 스펙 §6의 `control.*` 이벤트(사용자 프롬프트, 인터랙션 응답 등)를 마스터 서버로 발송하며, 마스터는 이를 로컬 머신으로 포워딩한다. 엣지 노드는 로컬 머신이 발송한 `agent.*`, `tool.*`, `session.*` 이벤트를 마스터 서버를 통해 수신하여 사용자 UI에 렌더링한다. #### 9.4.2. 마스터 서버의 중계 및 개입 규칙 마스터 서버는 단순한 파이프 역할을 넘어 다음의 제어 책임을 가진다: **라우팅**: 엣지 노드의 발신 프레임을 올바른 로컬 머신의 소켓으로 전달. **사용량 집계**: 중계되는 `session.usage` 이벤트를 인터셉트하여 데이터베이스에 누적 저장 (§9.2.2 지표 제공용). **파일 검색 중계**: `control.file.search`, `control.file.search.cancel`, `session.file.candidates` 이벤트는 중계 대상에 포함된다. `session.file.candidates`는 `session.usage`와 달리 인터셉트 없이 단순 포워딩 대상이다. **보안 필터링**: 엣지 노드가 허가되지 않은 `control.prompt.request` 내장 도구(e.g., 로컬 시스템 파괴성 명령어)를 전송하려 할 경우, 마스터 서버 단에서 프레임을 드롭하고 엣지 노드에 `session.error`를 반환해야 한다 (MUST). #### 9.4.3. 다중 접점 동기화 (Multi-device Synchronization) 동일한 `session_id`에 대해 여러 엣지 노드(예: 모바일 앱과 데스크톱 브라우저 동시 접속)가 연결을 요청할 경우, 마스터 서버는 RAWP-DPS 1.0.1 §2.4의 브로드캐스트 원칙과 동일한 정책을 적용해야 한다 (MUST). - 로컬 클라이언트로부터 수신한 모든 DPS 프레임(`agent.*`, `tool.*`, `session.*`)을 해당 세션에 연결된 모든 엣지 WSS로 브로드캐스트해야 한다 (MUST). - 엣지 노드가 발송한 `control.*` 프레임(예: `control.prompt.request`)도 동일 세션에 연결된 다른 모든 엣지 WSS로 중계해야 한다 (MUST). 이를 통해 엣지 A가 전송한 프롬프트를 엣지 B도 수신하여 표시할 수 있다. - 중계 시 `message_id` 기반 멱등성 처리를 수행하여 발신 엣지에 자신의 프레임이 되돌아오는 것을 방지해야 한다 (MUST). **동시 프롬프트 충돌 처리**: 동일 세션에 대해 여러 엣지 노드가 동시에 `control.prompt.request`를 발송한 경우, 마스터 서버는 `message_id` 타임스탬프 기준 선착순(first-come)으로 하나만 수락하고, 후발 요청에 대해서는 RAWP-DPS 1.0.1 §7.4.1의 `session.error` (error_code: `"CONCURRENT_PROMPT"`, fatal: `false`)를 반환해야 한다 (MUST). #### 9.4.4. 노드 설정 변경 Push 알림 (Node Config Change Notification) 로컬 클라이언트가 §4.1.5 또는 §4.1.6을 통해 설정을 갱신하면, 마스터 서버는 해당 노드에 연결된 모든 활성 엣지 WSS 소켓에 설정 변경 알림 프레임을 전송해야 한다 (MUST). 이 알림을 통해 엣지 노드는 UI에 반영할 설정 변경(예: capabilities 변경으로 인한 기능 목록 갱신)을 실시간으로 인지할 수 있다. **알림 프레임 구조**: 이 알림은 RAWP-DPS 1.0.1의 Envelope 구조(RAWP-DPS §2)를 따르되, 세션 단위가 아닌 노드 단위의 이벤트이므로 `session_id`와 `turn_id`를 포함하지 않는다. 마스터 서버가 자체적으로 생성하여 발송하는 프레임이다. ```json { "v": "rawp-dps-1.0", "type": "node.config.changed", "message_id": "String (필수, UUID v4)", "timestamp": "String (필수, ISO 8601)", "payload": { "node_id": "String (필수, 설정이 변경된 노드의 식별자)", "changed_scope": "String (필수, 'limits' | 'capabilities')", "config_version": "Number (필수, 갱신된 스코프의 새 config_version)", "reason": "String (선택, 클라이언트가 갱신 요청 시 전달한 reason 값 전파)" } } ``` **엣지 노드의 처리 규칙**: - `node.config.changed` 수신 시, 엣지 노드는 `changed_scope`에 따라 관련된 로컬 캐시를 무효화해야 한다 (MUST). - 상세 설정 값이 필요하면 §9.2.3의 `GET /v1/edge/nodes/{node_id}/config`을 Pull 호출하여 최신 값을 획득한다. 알림 프레임 자체에는 변경된 설정의 전체 값을 포함하지 않는다. 이는 알림 프레임의 크기를 최소화하고, 엣지 노드가 필요한 시점에만 상세 데이터를 가져오도록 하기 위함이다. - `changed_scope: "capabilities"` 수신 시, 엣지 UI가 기능 목록이나 에이전트 지원 기능 표시를 동적으로 갱신하는 경우, Pull 호출 후 UI를 갱신해야 한다 (SHOULD). - 활성 세션에는 §4.1.8에 따라 이전 설정이 유지되므로, 현재 진행 중인 세션의 동작을 변경할 필요는 없다. **전송 범위**: 마스터 서버는 해당 노드(`node_id`)에 연결된 모든 엣지 WSS 소켓에 알림을 브로드캐스트한다. 특정 세션의 WSS가 아닌, 해당 노드와 관련된 모든 엣지 연결이 대상이다. 세션 WSS가 아직 연결되지 않았더라도, 해당 노드에 대해 다른 세션의 WSS가 활성 상태이면 해당 소켓을 통해 알림이 전달된다. --- ## 10. 로컬 세션 관리 (Local Session Management) 본 장은 사용자가 데스크톱 앱에서 마스터 서버 없이 직접 에이전트와 대화하기 위한 로컬 세션 프로토콜을 정의한다. 로컬 세션은 §5의 원격 세션과 동일한 세션 상태 머신(`INIT → RUNNING → DETACHED → TERMINATED`)과 RAWP-DPS 1.0.1 프로토콜을 공유하되, 인증과 보안 모델이 다르다. ### 10.1. 로컬 세션 엔드포인트 로컬 세션 전용 엔드포인트는 `/local/v1/` 접두사를 사용하여 원격(마스터 발) 엔드포인트(`/v1/`)와 네임스페이스를 분리한다. #### 10.1.1. 로컬 세션 생성 **Endpoint**: `POST /local/v1/sessions` (Local Client 제공) **Request**: ```json { "agent_name": "String (필수, 실행할 에이전트 이름. §4.3의 에이전트 목록 참조)", "workspace_path": "String (필수, 작업 디렉토리 절대 경로)", "session_name": "String (선택, 세션 표시명. 생략 시 agent_name + 생성 시각으로 자동 생성)" } ``` **Response (201 Created)**: ```json { "session_id": "String (필수, UUID v4)", "ws_ticket": "String (필수, 1회용 WSS 연결 티켓)", "status": "String (필수, 'INIT')" } ``` WSS 연결: `ws://127.0.0.1:{port}/v1/ws/stream?ticket={ws_ticket}&session_id={session_id}` #### 10.1.2. 로컬 세션 재개 **Endpoint**: `POST /local/v1/sessions/{session_id}/resume` (Local Client 제공) **Request**: ```json { "last_sync_timestamp": "String (선택, ISO 8601)", "last_message_id": "String (선택, UUID)" } ``` **Response (200 OK)**: ```json { "session_id": "String (필수)", "ws_ticket": "String (필수, 1회용 WSS 연결 티켓)", "status": "String (필수, 'RUNNING')" } ``` **제약 조건**: 존재하지 않거나 `TERMINATED` 상태인 세션에 대한 요청은 `404 Not Found`를 반환한다 (MUST). #### 10.1.3. 로컬 세션 종료 **Endpoint**: `DELETE /local/v1/sessions/{session_id}` (Local Client 제공) **제약 조건**: §5.2와 동일한 종료 절차를 따른다. `session.deleted` WSS 알림 의무(§5.2)도 동일하게 적용된다. **Response**: `204 No Content`. #### 10.1.4. 로컬 세션 목록 조회 **Endpoint**: `GET /local/v1/sessions` (Local Client 제공) **Query Parameters**: | 파라미터 | 타입 | 필수 | 설명 | | -------- | ------ | ---- | --------------------------------------------------------------------------- | | `status` | String | 선택 | `'INIT'`, `'RUNNING'`, `'DETACHED'` 중 하나. 미지정 시 모든 활성 세션 반환. | | `origin` | String | 선택 | `'local'` 또는 `'remote'`. §10.3 참조. 미지정 시 모든 origin의 세션 반환. | **Response (200 OK)**: ```json { "sessions": [ { "session_id": "String (필수)", "agent_name": "String (필수)", "workspace_path": "String (필수)", "session_name": "String (필수)", "status": "String (필수, 'INIT' | 'RUNNING' | 'DETACHED')", "origin": "String (필수, §10.3 참조)", "created_at": "String (필수, ISO 8601)", "last_activity_at": "String (필수, ISO 8601)" } ] } ``` ### 10.2. 로컬 인증 면제 - 클라이언트의 HTTP/WSS 서버가 루프백 주소(`127.0.0.1` 또는 `::1`)에 바인딩된 경우, `/local/v1/` 접두 엔드포인트에 대해 `Authorization` 헤더 검증을 면제해야 한다 (MUST). - 클라이언트가 외부 네트워크 주소에 바인딩된 경우, `/local/v1/` 엔드포인트를 노출해서는 안 된다 (MUST NOT). 이는 인증 없는 외부 접근을 차단하기 위함이다. ### 10.3. 세션 식별 (Session Origin) 모든 세션은 `origin` 속성을 가진다: | `origin` 값 | 설명 | | ---------------------- | ------------------------------------------------------------------------------- | | `"local"` | §10.1.1을 통해 로컬에서 생성된 세션 | | `"remote:{server_id}"` | §5.1을 통해 마스터 서버가 생성한 원격 세션. `server_id`는 마스터의 고유 식별자. | `origin`은 세션 생성 시 결정되며 변경할 수 없다 (MUST NOT). ### 10.4. 로컬 세션 보안 모델 - **경로 검증 면제**: 로컬 세션(`origin: "local"`)은 §7.2의 경로 검증(`workspace_path` 범위 제한)을 적용하지 않는다. 사용자가 직접 조작하는 환경이므로 파일시스템 접근 범위를 제한하지 않는다. - **도구 승인**: 로컬 세션에서의 도구 승인 요청은 RAWP-DPS 1.0.1 §4.3.1의 `agent.interaction.request`를 통해 사용자에게 직접 표시된다. 마스터를 경유하지 않는다. ### 10.5. 로컬-원격 세션 공존 - 동일 에이전트에 대해 로컬 세션과 원격 세션이 동시에 존재할 수 있다 (MUST 지원). - 각 세션은 독립된 에이전트 프로세스 또는 SDK 인스턴스를 가진다. 세션 간 에이전트 상태를 공유하지 않는다 (MUST NOT). ### 10.6. 원격 세션 로컬 감지 마스터가 `POST /v1/session/init`(§5.1)로 원격 세션을 생성하면, 클라이언트는 해당 세션을 `origin: "remote:{server_id}"`로 등록한다. 이 세션은 `GET /local/v1/sessions`(§10.1.4) 응답에 포함되어야 한다 (MUST). 로컬 UI가 원격 세션에 연결할 때는 동일 클라이언트의 로컬 WSS 엔드포인트를 사용한다: ``` ws://127.0.0.1:{port}/v1/ws/stream?ticket={local_ticket}&session_id={session_id} ``` 이때 `local_ticket`은 `/local/v1/sessions/{session_id}/resume`(§10.1.2)을 통해 발급받는다. 로컬 UI WSS 연결과 마스터 WSS 연결은 독립적이며, 에이전트의 DPS 이벤트는 양쪽 소켓 모두에 전달되어야 한다 (MUST). --- ## 11. 게이트웨이 생명주기 (Gateway Lifecycle) 본 장은 로컬 클라이언트의 HTTP/WSS 서버(게이트웨이)의 시작, 중지, 재시작에 대한 프로토콜 계약을 정의한다. ### 11.1. 게이트웨이 상태 머신 ``` STOPPED → STARTING → RUNNING → STOPPING → STOPPED ↑ RESTARTING ─┘ ``` | 상태 | 설명 | | ------------ | ----------------------------------------------------- | | `STOPPED` | HTTP/WSS 서버 미기동. 모든 포트 미바인딩. | | `STARTING` | 서버 초기화 중. 포트 바인딩 시도. | | `RUNNING` | 서버 가동 중. 요청 수신 가능. | | `STOPPING` | 서버 종료 중. 신규 연결 거부, 기존 세션 정리. | | `RESTARTING` | `STOPPING` → `STARTING` 연쇄 전이. 설정 변경 적용 등. | ### 11.2. 상태별 동작 규칙 **STARTING**: 신규 세션 초기화(§5.1) 및 로컬 세션 생성(§10.1.1) 요청을 수락하지 않는다 (MUST NOT). 헬스체크(§4.2)에는 응답할 수 있다 (MAY). **STOPPING**: 신규 연결을 거부하고, 모든 활성 세션을 정리한다. 활성 세션에 대해 RAWP-DPS 1.0.1 §7.5.3의 종료 절차를 수행한 후, WSS 연결을 종료해야 한다 (MUST). **RESTARTING**: `STOPPING` 완료 후 `STARTING`으로 전이한다. 재시작 전후로 세션은 보존되지 않는다. ### 11.3. 헬스체크 확장 §4.2의 `GET /v1/health` 응답에 게이트웨이 상태 필드를 추가한다: ```json { "status": "String (필수, 'online', 'busy', 'error')", "uptime": "Number (필수, 클라이언트 가동 초)", "active_sessions": "Number (선택, 현재 유지 중인 세션 수)", "gateway_state": "String (필수, 'STOPPED' | 'STARTING' | 'RUNNING' | 'STOPPING' | 'RESTARTING')" } ``` `gateway_state`가 `RUNNING`이 아닌 상태에서 세션 초기화(§5.1)가 시도되면 `503 Service Unavailable`을 반환해야 한다 (MUST). --- ## 부록: 관련 규격 문서 | 문서 | 설명 | | ----------------------- | ---------------------------------------------------- | | **RAWP-DPS 1.0.1** | 데이터 평면 스트리밍 규격 (현행). 본 문서 §6이 참조. | | **RAWP-CRS 1.0.1** | 클라이언트 렌더링 규격 (현행). 본 문서 §8이 참조. | | **RAWP-DPS-0.1-Legacy** | 데이터 평면 스트리밍 규격 (지원 종료). |