evo-dispatch 설계 흐름도
원천 · framework/evomind/web/workflow-project-evo-dispatch기준 · 릴리스 v0.1.0커밋 · 734af815파일 · docs/protocol/design-flow.md기준선 · 2026-11-12
- 기준 규격:
client-protocol.mdv1 (r4, 2026-10-18 승인) - 이 문서는 규격 내용을 그림으로 요약한다. 규격과 다르면
client-protocol.md가 우선한다.
핵심 원칙:
- 여러 사용자가 각자 여러 세션을 가진다. 요청은 내 세션 사이에서만 오간다.
- 활성 세션끼리만 요청을 보내고 받는다.
- dispatch는 전달만 한다. 받은 쪽의 처리 방식에는 관여하지 않는다.
- GitLab·git을 전제하지 않는다.
- 쓰고 남은 데이터는 지운다.
1. 전체 구성
섹션 제목: “1. 전체 구성”모델은 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 -- "로그인" --> KC2. 클라이언트 안에서 일어나는 일 (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초 뒤 종료 판정" .-> SRV3. 요청 흐름
섹션 제목: “3. 요청 흐름”받을 활성 세션이 없으면 보내기가 거부된다. 받는 세션 목록은 보내는 순간 고정된다.
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) end4. 요청 상태
섹션 제목: “4. 요청 상태”stateDiagram-v2 direction LR [*] --> unread: 보내기 (받는 세션 1개 이상) unread --> read: 받는 세션이 읽음 표시 unread --> cancelled: 보낸 쪽 취소 unread --> expired: 받는 세션이 모두 종료 read --> [*]: 7일 뒤 삭제 cancelled --> [*]: 7일 뒤 삭제 expired --> [*]: 7일 뒤 삭제금지된 전이:
- 읽음 → 안 읽음
- 읽음 → 취소
- 취소·만료에서 다른 상태로
- 받는 세션이 아닌 세션의 읽음 표시
5. 세션 교체와 종료
섹션 제목: “5. 세션 교체와 종료”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"]6. 데이터 저장과 정리
섹션 제목: “6. 데이터 저장과 정리”활성 신호는 세션당 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"]7. 진행 단계
섹션 제목: “7. 진행 단계”flowchart LR S1["1단계 · 중계<br/>활성 세션끼리 보내기·받기"] --> S2["2단계 · 승인 후 실행<br/>(미정)"] --> S3["3단계 · 원격 실행<br/>(미정)"]1단계는 구현·배포됐다. 2·3단계용 필드는 extensions.execution 자리만 예약했다. 꺼진 세션을 대신 실행하는 일은 상주 프로그램을 전제했으나 은 2026-10-26 취소했고(#6), 워크플로우 그룹 실행을 만들 때 새 Issue로 다시 검토한다.
답변은 문서를 근거로 만들지만 틀릴 수 있습니다. 출처 문서를 함께 확인하세요.