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):
{
"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):
{
"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):
{
"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):
{
"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):
{
"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):
{
"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:
{
"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:
{
"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:
{
"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 (필수, 조회할 절대 경로)"}