evo-dispatch 워크그룹 푸시 채널 규격 (초안 r1)
원천 · framework/evomind/web/workflow-project-evo-dispatch기준 · 릴리스 v0.1.0커밋 · 734af815파일 · docs/protocol/push.md기준선 · 2026-11-12
- 기반 규격:
client-protocol.mdv1,workgroup.md. 이 문서는 그 위의 선택 확장이며schema_version은"1"그대로다. - 상태: 초안 r1 — (규격), (서버 구현). 클라이언트는 아직 없다.
- 근거: evo-cli의 dispatch-dev 요청(
35b4ebc2, 2026-11-03)과 evo-dispatch 사용자 확인(푸시는 워크그룹 메시지에만 쓰고 나머지는 지금 방식을 그대로 둔다).
1. 목표와 한계
섹션 제목: “1. 목표와 한계”워크그룹(workgroup.md) 안 자동 처리가 활성 신호(20초)의 unread 확인에 기대면 요청 하나마다 최대 20초 늦는다. 세션마다 서버 → 클라이언트 푸시 채널을 열어, 워크그룹 주소로 온 메시지가 저장되는 순간 받는 세션에 알린다.
- 푸시는 워크그룹 메시지(
to.kind가workgroup·workgroup_member)에만 한다. 그 밖의 메시지(user·agent·workspace·session)는 지금처럼 활성 신호의unread로 알아챈다. 이 규격은 그쪽을 바꾸지 않는다. - 푸시는 가속 수단일 뿐이다. 채널이 없거나 끊겨도 활성 신호의
unread와dispatch_inbox로 같은 메시지를 받는다. 읽음 표시·상태·E2E는 그대로다. - 알림에는 내용이 없다(제목·본문·
enc없음). 클라이언트는 알림의id로dispatch_read를 부른다. - 서버가 한 대(프로세스 하나)일 때를 기준으로 한다. 서버를 여러 대로 늘리면 채널 등록을 공유하는 방법이 따로 필요하다(§7 미정).
2. 연결
섹션 제목: “2. 연결”| 항목 | 값 |
|---|---|
| 경로 | GET /v1/push |
| 응답 | 200, Content-Type: text/event-stream (Server-Sent Events) |
| 인증 | 다른 요청과 같다: Authorization: Bearer, X-Dispatch-Client-Kind, X-Dispatch-Session-Id(규격 §9). 활성 세션일 때만 열린다 |
| 오류 | 형식·세션 오류는 invalid_request·unauthorized·forbidden·not_found 등 규격 §9 코드. 연결 수 한도를 넘으면 rate_limited(429, Retry-After) |
- 서버는 호출 세션이 활성(규격 §4)일 때만 채널을 연다. 활성이 아니면
not_found(404)처럼 다른 요청과 같게 답한다. - 연결이 열리면 서버는 먼저
ready이벤트를 보낸다(§3). 이어서 그 세션이 아직 읽지 않은 워크그룹 메시지를message.created로 하나씩 다시 보낸다(재연결로 놓친 것을 채운다). - 같은 세션이 채널을 또 열면 이전 채널을 닫고 새 채널로 바꾼다(
session.replaced를 보내고 닫는다).
3. 이벤트
섹션 제목: “3. 이벤트”SSE 형식(event:, data: 한 줄 JSON, 빈 줄)을 따른다. id:는 쓰지 않는다(§4의 재연결 규칙은 Last-Event-ID에 기대지 않는다).
| 이벤트 | data |
뜻 |
|---|---|---|
ready |
{"schema_version":"1","ping_seconds":15,"expires_at":"<UTC 시각>"} |
채널이 열렸다. expires_at은 이 채널을 서버가 닫을 시각(토큰 만료와 같다, §5) |
message.created |
아래 | 워크그룹 메시지가 저장됐다 |
session.ended |
{"reason":"rotated"|"ended"|"expired"} |
이 세션이 끝났다(§5). 서버가 채널을 닫는다 |
session.replaced |
{} |
같은 세션이 채널을 또 열어 이 채널을 닫는다 |
message.created의 data:
{ "schema_version": "1", "id": "<메시지 id>", "kind": "request", "to": { "kind": "workgroup", "workgroup_id": "<id>" }, "from": { "client_kind": "evo-cli", "session_id": "<id>" }, "created_at": "<UTC 시각>"}to는workgroup_member이면alias도 담는다. 메시지의extensions.execution과enc, 제목·본문은 싣지 않는다.- 같은 메시지가 재연결 재전송으로 두 번 올 수 있다. 클라이언트는
id로 중복을 거른다. : ping주석 줄을ping_seconds(15초)마다 보낸다. 클라이언트는 45초 동안 아무것도(주석 포함) 받지 못하면 연결이 죽은 것으로 보고 닫고 다시 연다.
4. 재연결과 대체 경로
섹션 제목: “4. 재연결과 대체 경로”- 끊기면 클라이언트가 다시 연다. 서버는 연결할 때마다 읽지 않은 워크그룹 메시지를 다시 보내므로
Last-Event-ID나 마지막 시각을 보내지 않는다. - 다시 열 때는 지수 백오프(1초에서 시작해 최대 30초)로 기다린다.
rate_limited(429)는Retry-After를 따른다. - 채널이 닫혀 있는 동안에도 활성 신호의
unread와dispatch_inbox는 그대로 쓰인다. 클라이언트는unread가 늘면 지금처럼dispatch_inbox를 부른다. 푸시 지원 클라이언트도 활성 신호를 같은 주기로 계속 보낸다(활성 판정이 거기에 걸려 있다). - 서버가 푸시를 지원하지 않으면(
GET /v1/push가404·405) 클라이언트는 푸시를 포기하고 지금 방식만 쓴다.
5. 세션 수명과 토큰
섹션 제목: “5. 세션 수명과 토큰”- 토큰 만료: 채널은 열 때 쓴 access token의 만료 시각(
exp)에 서버가 닫는다. 클라이언트는 새 토큰으로 다시 연다. 만료 전에 미리 새 채널을 열면 이전 채널이session.replaced로 닫힌다. - 세션 교체(
/clear·/compact, 규격 §5): 서버가 이전 세션의 채널에session.ended(rotated)를 보내고 닫는다. 클라이언트는 새session_id로 다시 연다. 워크그룹 멤버 자격은 새 세션으로 이어진다(workgroup.md§2). end·TTL 만료: 서버가session.ended(ended·expired)를 보내고 닫는다.- 푸시 채널 연결 자체는 활성 신호가 아니다. 채널이 열려 있어도 활성 신호가 90초 동안 없으면 세션은 비활성이다.
6. 한도
섹션 제목: “6. 한도”- 세션마다 채널 1개(§2).
- 사용자마다 동시 채널 16개(워크그룹 최대 인원과 같다). 넘으면 새 연결을
rate_limited(429)로 거절한다. - 서버 전체 동시 채널은 512개까지다(설정
DISPATCH_PUSH_MAX_CHANNELS). 넘으면 새 연결을rate_limited(429)로 거절한다. 사용자 한도와 별개로 서버를 지키기 위한 상한이며, 이미 열린 같은 사용자·세션 채널을 다시 쓰는 경우에는 세지 않는다. 클라이언트는 429를 받으면Retry-After만큼 기다렸다 다시 연다(§4). - 요청 수 한도(규격 §9, 분당 300)는
GET /v1/push연결 시도에도 센다. 열려 있는 동안의 이벤트는 세지 않는다. - 서버는 연결 수를 메모리 안에서만 센다(재시작하면 채널이 모두 끊기고 클라이언트가 다시 연다).
7. 보안과 미정
섹션 제목: “7. 보안과 미정”- 알림에는 메시지 내용이 없고, 받는 세션 자신에게만 간다(다른 세션·다른 사용자의 알림은 받을 수 없다).
- 채널은 일반 요청과 같은 인증과 사용자 격리를 쓴다. 푸시를 받았다고 해서 자동 처리가 허용되는 것은 아니다. 자동 처리 조건은
workgroup.md§4 그대로다. - 미정: 서버를 여러 대로 늘릴 때의 채널 등록 공유, 워크그룹 밖 메시지로 푸시를 넓히는 일, 프록시가 오래 열린 연결을 끊는 설정(개발 서버 앞단 프록시의 읽기 시간 제한은 15초 주기 ping보다 길어야 한다).
Evo에게 묻기문서 근거 안에서만 답합니다
답변은 문서를 근거로 만들지만 틀릴 수 있습니다. 출처 문서를 함께 확인하세요.