# RAWP 1.0: Remote Agent Wire Protocol | 항목 | 값 | | ---------------------- | ---------------- | | 상태 | Stable | | 버전 | 1.0 | | 데이터 평면 규격 | **RAWP-DPS 1.0** | | 클라이언트 렌더링 규격 | **RAWP-CRS 1.0** | --- ## 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, 스크립트 등). | | Session | 특정 에이전트의 실행부터 종료까지의 라이프사이클 및 I/O 스트림의 논리적 단위. | | Config Scope | 클라이언트 설정의 논리적 분류 단위. 각 스코프는 독립된 엔드포인트, 독립된 `config_version`, 독립된 갱신 주기를 갖는다. | | Session-Pinned Config | 세션 초기화 시점에 바인딩된 설정 스냅샷. 해당 세션의 전체 라이프사이클 동안 불변이다. | ### 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" } } ``` > **변경 사항**: RAWP-DPS 1.0 적용 시 `data_plane_protocol` 필드가 추가되었다. 이 필드는 서버가 지원하는 데이터 평면 규격을 명시하며, 클라이언트는 이를 확인하여 호환되는 데이터 평면 규격을 선택해야 한다 (MUST). 필드가 없으면 RAWP-DPS-0.1-Legacy로 간주한다. --- ## 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.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 (Deprecated) > **본 엔드포인트는 단종 예고(Deprecated) 상태이다.** 새로운 클라이언트 구현은 §4.1.5 및 §4.1.6의 분리된 PUT 엔드포인트를 사용해야 한다 (MUST). **Endpoint**: `POST /v1/nodes/config` (Master Server 제공) 기존 클라이언트와의 하위 호환을 위해 서버는 이 엔드포인트를 계속 수락할 수 있다 (MAY). 수신된 요청은 서버 내부에서 limits와 capabilities 스코프로 분리 저장한다. 이 경우 `config_version`은 양쪽 스코프 모두 `1`로 초기화한다. 서버는 이 엔드포인트로의 응답에 반드시 `Warning: 299 - "Deprecated: Use PUT /v1/nodes/config/limits and PUT /v1/nodes/config/capabilities"` HTTP 헤더를 포함해야 한다 (MUST). **Request** (Client → Master): ```json { "rate_limit": "Number (필수, 초당 처리 가능한 최대 HTTP 요청 수)", "max_frame_size": "Number (필수, WSS 단일 프레임 최대 바이트)", "reattach_window": "Number (필수, WSS 단절 시 대기 시간 초)", "max_buffer_size": "Number (필수, 최대 메모리 버퍼 바이트)", "buffer_overflow_policy": "String (필수, 'RING' 또는 'DROP')", "capabilities": ["String (선택, e.g., 'vision_v1', 'tool_call_v2')"] } ``` ### 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) 클라이언트 내 실행 가능한 에이전트 목록을 조회한다. **Endpoint**: `GET /v1/agents` (Local Client 제공) **Response**: ```json { "agents": [ { "id": "String (고유 식별자)", "name": "String (세션 식별자로 사용될 이름)", "is_active": "Boolean (에이전트 활성화 상태)" } ] } ``` ### 4.4. 파일 탐색 (Directory Browsing) 인가된 작업 경로 내의 디렉토리를 조회한다. **Endpoint**: `POST /v1/fs/list` (Local Client 제공) **Request**: `{"target_path": "String (필수, 조회할 절대 경로)"}` > **변경 사항**: 실시간 퍼지 파일 검색은 RAWP-DPS 1.0 §16의 WSS 이벤트(`control.file.search` / `session.file.candidates`)를 통해 제공된다. 본 엔드포인트는 디렉토리 단위 일괄 조회용으로 유지한다. --- ## 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). ### 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 적용 시 `rawp-dps-1.0`을 우선 수락하고, 미지원 시 `rawp-1.0`으로 폴백한다. 양측 모두 미지원 시 `426` 또는 `400`을 반환해야 한다. 2. Query Parameter의 `ticket`과 `session_id`를 검증하고, 만료되거나 불일치 시 `401 Unauthorized`를 반환 후 연결을 거절한다. 3. 사용된 티켓은 1회 사용 시 즉시 무효화(Burn)해야 한다. > **변경 사항**: RAWP-DPS 1.0 적용에 따라 `Sec-WebSocket-Protocol` 협상 절차가 확장되었다. `rawp-dps-1.0`이 우선이며, 레거시 호환을 위해 `rawp-1.0`도 수락 가능하다. 자세한 프로토콜 협상 규칙은 RAWP-DPS 1.0 §1.3을 참조한다. --- ## 6. 데이터 평면 스트리밍 규격 (Data Plane WSS Protocol) > **본 절은 별도 규격으로 분리되었다.** > > WSS 연결 수립(§5.3) 이후 교환되는 데이터 프레임의 Envelope 구조, 이벤트 타입 및 페이로드 정의는 **RAWP-DPS 1.0** 규격을 따른다. > > RAWP-DPS 1.0은 다음을 정의한다: > > - **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 규격 문서를 참조하여 데이터 평면을 구현해야 한다 (MUST). > > **레거시 호환**: RAWP-DPS-0.1-Legacy만 지원하는 피어와의 통신이 필요한 경우, RAWP-DPS 1.0 §14의 하위 호환성 가이드라인 및 브릿지 변환 규칙을 따른다. --- ## 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 적용 시, `tool.catalog.publish`에서 고지된 도구만 `tool.invoke.request`에 사용 가능하며, 고지되지 않은 도구의 호출 요청은 클라이언트가 거부해야 한다 (MUST). 자세한 도구 보안 규칙은 RAWP-DPS 1.0 §15를 참조한다. --- ## 8. 클라이언트 UI/UX 구현 지침 (Client UI Implementer Notes) > **본 절은 별도 규격으로 분리되었다.** > > RAWP 프로토콜의 제어 평면 이벤트와 RAWP-DPS 1.0의 데이터 평면 이벤트를 사용자에게 시각적으로 전달하는 클라이언트 렌더링 규격은 **RAWP-CRS 1.0** 규격을 따른다. > > RAWP-CRS 1.0은 다음을 정의한다: > > - **메시지 아키텍처**: 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 규격 문서를 참조하여 클라이언트 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 기반 JWT)를 사용해야 한다. 모든 Edge API 요청은 `Authorization: Bearer {user_access_token}` 헤더를 포함해야 한다 (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)" } } ] } ``` > **변경 사항**: `capabilities` 필드는 §4.1.6을 통해 동기화된 최신 값을 반영한다. `config_versions` 객체가 추가되어 각 Config Scope의 현재 버전을 엣지 노드에서 조회할 수 있다. ### 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)" } } ``` > **변경 사항**: `pinned_config_versions` 객체가 추가되어, 이 세션에 바인딩된 설정 버전을 엣지 노드가 확인할 수 있다. §4.1.8의 Session-Pinned Config 참조. **동작 원리:** 이 요청을 받은 마스터 서버는 즉시 대상 로컬 머신의 `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 규격을 완전히 동일하게 사용한다 (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 프레임을 모든 연결된 엣지 소켓으로 브로드캐스트(Fan-out)하여 UI 상태를 동기화해야 한다 (SHOULD). 단, `control.*` 이벤트 발송 시 동시성 충돌을 막기 위해 `message_id` 기반 멱등성 처리를 수행해야 한다. ### 9.4.4. 노드 설정 변경 Push 알림 (Node Config Change Notification) 로컬 클라이언트가 §4.1.5 또는 §4.1.6을 통해 설정을 갱신하면, 마스터 서버는 해당 노드에 연결된 모든 활성 엣지 WSS 소켓에 설정 변경 알림 프레임을 전송해야 한다 (MUST). 이 알림을 통해 엣지 노드는 UI에 반영할 설정 변경(예: capabilities 변경으로 인한 기능 목록 갱신)을 실시간으로 인지할 수 있다. **알림 프레임 구조**: 이 알림은 RAWP-DPS 1.0의 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가 활성 상태이면 해당 소켓을 통해 알림이 전달된다. --- ## 부록: 관련 규격 문서 | 문서 | 설명 | | ----------------------- | ----------------------------------------------------- | | **RAWP-DPS 1.0** | 데이터 평면 스트리밍 규격 (현행). 본 문서 §6이 참조. | | **RAWP-CRS 1.0** | 클라이언트 렌더링 규격 (현행). 본 문서 §8이 참조. | | **RAWP-DPS-0.1-Legacy** | 데이터 평면 스트리밍 규격 (단종 예고). 레거시 호환용. |