콘텐츠로 이동

evo-dispatch 워크그룹 푸시 채널 규격 (초안 r1)

원천 · framework/evomind/web/workflow-project-evo-dispatch기준 · 릴리스 v0.1.0커밋 · 734af815파일 · docs/protocol/push.md기준선 · 2026-11-12

  • 기반 규격: client-protocol.md v1, workgroup.md. 이 문서는 그 위의 선택 확장이며 schema_version은 "1" 그대로다.
  • 상태: 초안 r1 — (규격), (서버 구현). 클라이언트는 아직 없다.
  • 근거: evo-cli의 dispatch-dev 요청(35b4ebc2, 2026-11-03)과 evo-dispatch 사용자 확인(푸시는 워크그룹 메시지에만 쓰고 나머지는 지금 방식을 그대로 둔다).

워크그룹(workgroup.md) 안 자동 처리가 활성 신호(20초)의 unread 확인에 기대면 요청 하나마다 최대 20초 늦는다. 세션마다 서버 → 클라이언트 푸시 채널을 열어, 워크그룹 주소로 온 메시지가 저장되는 순간 받는 세션에 알린다.

  • 푸시는 워크그룹 메시지(to.kind가 workgroup·workgroup_member)에만 한다. 그 밖의 메시지(user·agent·workspace·session)는 지금처럼 활성 신호의 unread로 알아챈다. 이 규격은 그쪽을 바꾸지 않는다.
  • 푸시는 가속 수단일 뿐이다. 채널이 없거나 끊겨도 활성 신호의 unread와 dispatch_inbox로 같은 메시지를 받는다. 읽음 표시·상태·E2E는 그대로다.
  • 알림에는 내용이 없다(제목·본문·enc 없음). 클라이언트는 알림의 id로 dispatch_read를 부른다.
  • 서버가 한 대(프로세스 하나)일 때를 기준으로 한다. 서버를 여러 대로 늘리면 채널 등록을 공유하는 방법이 따로 필요하다(§7 미정).
항목 값
경로 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를 보내고 닫는다).

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초 동안 아무것도(주석 포함) 받지 못하면 연결이 죽은 것으로 보고 닫고 다시 연다.
  • 끊기면 클라이언트가 다시 연다. 서버는 연결할 때마다 읽지 않은 워크그룹 메시지를 다시 보내므로 Last-Event-ID나 마지막 시각을 보내지 않는다.
  • 다시 열 때는 지수 백오프(1초에서 시작해 최대 30초)로 기다린다. rate_limited(429)는 Retry-After를 따른다.
  • 채널이 닫혀 있는 동안에도 활성 신호의 unread와 dispatch_inbox는 그대로 쓰인다. 클라이언트는 unread가 늘면 지금처럼 dispatch_inbox를 부른다. 푸시 지원 클라이언트도 활성 신호를 같은 주기로 계속 보낸다(활성 판정이 거기에 걸려 있다).
  • 서버가 푸시를 지원하지 않으면(GET /v1/push가 404·405) 클라이언트는 푸시를 포기하고 지금 방식만 쓴다.
  • 토큰 만료: 채널은 열 때 쓴 access token의 만료 시각(exp)에 서버가 닫는다. 클라이언트는 새 토큰으로 다시 연다. 만료 전에 미리 새 채널을 열면 이전 채널이 session.replaced로 닫힌다.
  • 세션 교체(/clear·/compact, 규격 §5): 서버가 이전 세션의 채널에 session.ended(rotated)를 보내고 닫는다. 클라이언트는 새 session_id로 다시 연다. 워크그룹 멤버 자격은 새 세션으로 이어진다(workgroup.md §2).
  • end·TTL 만료: 서버가 session.ended(ended·expired)를 보내고 닫는다.
  • 푸시 채널 연결 자체는 활성 신호가 아니다. 채널이 열려 있어도 활성 신호가 90초 동안 없으면 세션은 비활성이다.
  • 세션마다 채널 1개(§2).
  • 사용자마다 동시 채널 16개(워크그룹 최대 인원과 같다). 넘으면 새 연결을 rate_limited(429)로 거절한다.
  • 서버 전체 동시 채널은 512개까지다(설정 DISPATCH_PUSH_MAX_CHANNELS). 넘으면 새 연결을 rate_limited(429)로 거절한다. 사용자 한도와 별개로 서버를 지키기 위한 상한이며, 이미 열린 같은 사용자·세션 채널을 다시 쓰는 경우에는 세지 않는다. 클라이언트는 429를 받으면 Retry-After만큼 기다렸다 다시 연다(§4).
  • 요청 수 한도(규격 §9, 분당 300)는 GET /v1/push 연결 시도에도 센다. 열려 있는 동안의 이벤트는 세지 않는다.
  • 서버는 연결 수를 메모리 안에서만 센다(재시작하면 채널이 모두 끊기고 클라이언트가 다시 연다).
  • 알림에는 메시지 내용이 없고, 받는 세션 자신에게만 간다(다른 세션·다른 사용자의 알림은 받을 수 없다).
  • 채널은 일반 요청과 같은 인증과 사용자 격리를 쓴다. 푸시를 받았다고 해서 자동 처리가 허용되는 것은 아니다. 자동 처리 조건은 workgroup.md §4 그대로다.
  • 미정: 서버를 여러 대로 늘릴 때의 채널 등록 공유, 워크그룹 밖 메시지로 푸시를 넓히는 일, 프록시가 오래 열린 연결을 끊는 설정(개발 서버 앞단 프록시의 읽기 시간 제한은 15초 주기 ping보다 길어야 한다).