콘텐츠로 이동

evo-dispatch 설계 흐름도

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

  • 기준 규격: client-protocol.md v1 (r4, 2026-10-18 승인)
  • 이 문서는 규격 내용을 그림으로 요약한다. 규격과 다르면 client-protocol.md가 우선한다.

핵심 원칙:

  • 여러 사용자가 각자 여러 세션을 가진다. 요청은 내 세션 사이에서만 오간다.
  • 활성 세션끼리만 요청을 보내고 받는다.
  • dispatch는 전달만 한다. 받은 쪽의 처리 방식에는 관여하지 않는다.
  • GitLab·git을 전제하지 않는다.
  • 쓰고 남은 데이터는 지운다.

모델은 MCP 도구로 요청을 보내고 확인한다. 활성 신호는 클라이언트 프로그램이 HTTP로 직접 보낸다. 서버는 모든 요청에서 Keycloak 토큰을 검증하고, 토큰의 사용자 안에서만 세션과 요청을 찾는다. 그림은 사용자 한 명 기준이며, 다른 사용자도 같은 구성으로 서버 하나를 함께 쓴다.

flowchart LR
subgraph PC["사용자 PC"]
CLI["evo CLI"]
MAC["evo Mac 앱<br/>(evo sidecar)"]
WIN["evo Windows 앱<br/>(직접 구현)"]
end
WEB["evo-service 웹<br/>(구현)"]
KC[("Keycloak<br/>OIDC")]
subgraph SRV["evo-dispatch 서버 (GH-5, Java 25 · Spring Boot 4.1.2)"]
MCP["MCP 도구<br/>send · inbox · read · sessions<br/>workgroup 3종(확장)"]
PRS["활성 신호 HTTP<br/>heartbeat · rotate · end"]
CLN["정리 작업<br/>1분마다, 삭제만"]
DB[("Postgres<br/>세션 · 요청 · 기기")]
end
CLI -- "MCP + Bearer 토큰" --> MCP
CLI -- "20초마다" --> PRS
MAC --> MCP
MAC --> PRS
WIN --> MCP
WIN --> PRS
WEB -- "MCP + Bearer 토큰" --> MCP
MCP --> DB
PRS --> DB
CLN --> DB
SRV -- "JWKS로 토큰 검증" --> KC
CLI -- "로그인" --> KC

2. 클라이언트 안에서 일어나는 일 (Evo CLI 기준)

섹션 제목: “2. 클라이언트 안에서 일어나는 일 (Evo CLI 기준)”

Mac 앱은 evo 엔진을 sidecar로 내장해 같은 동작을 이어받는다. Windows 앱은 evo 엔진을 쓰지 않고 직접 구현하고(규격 §8), 웹은 앱이 아니라 evo-service 서버가 대화 단위로 같은 일을 한다. 앱 화면 알림은 후속이다.

flowchart TD
subgraph EVO["evo CLI 프로세스"]
START["세션 시작<br/>(새로 시작 · --resume · --fork)"]
LOOP["활성 신호 루프<br/>20초마다 heartbeat"]
ROT["/clear · /compact<br/>→ rotate 신호"]
QUIT["quit · 터미널 닫기"]
UI["화면 표시<br/>안 읽은 요청 N건"]
HOOK["UserPromptSubmit hook (선택)<br/>받은 요청을 대화에 주입"]
MODEL["모델<br/>MCP 도구 호출"]
end
SRV["evo-dispatch 서버"]
START --> LOOP
LOOP -- "heartbeat" --> SRV
SRV -- "응답: 안 읽은 요청 수" --> LOOP
LOOP --> UI
UI --> HOOK
HOOK --> MODEL
MODEL -- "dispatch_inbox · dispatch_read · dispatch_send" --> SRV
ROT -- "previous_session_id 포함" --> SRV
QUIT -- "end (보낼 수 있을 때만)" --> SRV
QUIT -. "신호가 끊기면 90초 뒤 종료 판정" .-> SRV

받을 활성 세션이 없으면 보내기가 거부된다. 받는 세션 목록은 보내는 순간 고정된다.

sequenceDiagram
autonumber
participant A as 세션 A (보내는 쪽)
participant D as evo-dispatch
participant B as 세션 B (agent CD01)
B->>D: heartbeat (agent=CD01)
A->>D: dispatch_sessions
D-->>A: 내 활성 세션 목록
A->>D: dispatch_send (to: agent CD01)
alt CD01 활성 세션 있음
D-->>D: 저장 (unread, recipients = [세션 B])
D-->>A: 성공 + 받는 세션 목록
else 활성 세션 없음
D-->>A: 거부 (no_active_recipient), 저장 안 함
end
B->>D: heartbeat
D-->>B: 안 읽은 요청 1건
B->>D: dispatch_inbox → dispatch_read
D-->>D: read, read_by = 세션 B, closed_at 기록
Note over B: 받은 쪽이 자기 방식대로 처리<br/>(dispatch는 관여하지 않음)
opt 결과를 알리고 싶을 때 (A가 아직 활성이면)
B->>D: dispatch_send (kind: reply, to: 세션 A)
end
stateDiagram-v2
direction LR
[*] --> unread: 보내기 (받는 세션 1개 이상)
unread --> read: 받는 세션이 읽음 표시
unread --> cancelled: 보낸 쪽 취소
unread --> expired: 받는 세션이 모두 종료
read --> [*]: 7일 뒤 삭제
cancelled --> [*]: 7일 뒤 삭제
expired --> [*]: 7일 뒤 삭제

금지된 전이:

  • 읽음 → 안 읽음
  • 읽음 → 취소
  • 취소·만료에서 다른 상태로
  • 받는 세션이 아닌 세션의 읽음 표시
flowchart TD
E["세션에 일어난 일"] --> K{"종류"}
K -- "/clear · /compact" --> R["rotate 신호<br/>(같은 기기만)"]
R --> R2["안 읽은 요청의 받는 세션<br/>이전 ID → 새 ID"]
R2 --> R3["이전 세션 종료(교체)"]
K -- "--fork" --> F["새 세션 등록<br/>원본 요청은 옮기지 않음"]
K -- "--resume · --continue" --> RS{"90초 안에 돌아왔나?"}
RS -- 예 --> RS1["같은 세션 계속 활성"]
RS -- 아니요 --> RS2["새 활성 기간으로 등록<br/>종료 전 요청은 되살리지 않음"]
K -- "quit · 터미널 닫기 · 비정상 종료" --> Q["end 신호 또는 90초 신호 끊김"]
Q --> Q2["세션 종료"]
Q2 --> Q3{"이 세션이 받는 안 읽은 요청에<br/>다른 활성 받는 세션이 있나?"}
Q3 -- 있음 --> Q4["그대로 둠"]
Q3 -- 없음 --> Q5["expired"]

활성 신호는 세션당 1행만 덮어쓴다. 정리 작업은 서버 안에서 1분마다 돌며 삭제만 맡는다. 서버가 여러 대면 Postgres advisory lock으로 한 대만 실행한다. 활성·만료 판정은 조회 시점에 계산하므로, 정리 작업이 늦어도 동작은 틀리지 않는다.

flowchart LR
HB["heartbeat (20초)"] --> UP["세션 행의<br/>마지막 신호 시각만 갱신<br/>(이력 없음)"]
subgraph JOB["정리 작업 (1분마다)"]
J1["종료 후 24시간 지난 세션 삭제"]
J2["closed_at 후 7일 지난 요청 삭제"]
J3["마지막 세션 종료 후 30일 지난 기기 삭제"]
end
Q["조회 시점 판정"] --> Q1["활성 = 마지막 신호 + 90초 > 지금"]
Q --> Q2["안 읽은 요청의 받는 세션이 모두 비활성 → expired"]
flowchart LR
S1["1단계 · 중계<br/>활성 세션끼리 보내기·받기"] --> S2["2단계 · 승인 후 실행<br/>(미정)"] --> S3["3단계 · 원격 실행<br/>(미정)"]

1단계는 구현·배포됐다. 2·3단계용 필드는 extensions.execution 자리만 예약했다. 꺼진 세션을 대신 실행하는 일은 상주 프로그램을 전제했으나 은 2026-10-26 취소했고(#6), 워크플로우 그룹 실행을 만들 때 새 Issue로 다시 검토한다.