WMOS

가이드

라우터 사용하기

좌석 운영 가이드입니다. 작업을 보내고, 받고, 결과를 돌려주고, 복구하는 방법을 다룹니다. 여기 없는 것은 어댑터의 영역입니다.

두 계층, 하나의 규칙

라우터는 불투명한 봉투를 전달합니다. 본문을 해석하지 않고, 권한을 판단하지 않고, 변경을 승인하지 않습니다. 그 일은 시스템별 어댑터가 합니다.

규칙의 결과는 분명합니다. 업무 로직을 바꿔도 라우터를 바꿀 필요가 없고, 메시지 본문으로 권한을 넓힐 수 없습니다. 어댑터에 더 많은 접근이 필요하면 메시지가 아니라 grant를 바꿉니다.

첫 메시지 전에

네 가지가 필요합니다. 좌석 ID, 그 좌석의 자격증명, 파일럿에서 받은 라우터 주소, 그리고 대상 좌석에 닿을 수 있는 grant입니다.

  • 자격증명은 발급 시 한 번만 표시됩니다. 런타임 비밀 저장소에 두고 저장소에는 넣지 않습니다
  • 인증은 Bearer <credential>이며 URL의 :caller가 인증 신원과 일치해야 합니다
  • SSE 스트림은 좌석당 하나입니다. 정적 좌석은 재접속 시 이전 스트림을 대체하고, 동적 프로필은 두 번째 소비자를 409로 거부합니다
  • 좌석 간 도달은 to 필드만이 아니라 저장된 grant가 결정합니다

작업 보내기

작업은 /agents/:caller/a2a로 POST합니다. 최소 본문은 메시지 키, 대상 목록, 텍스트입니다. 아래 예시는 합성이며 운영 라우터로 보내면 안 됩니다.

POST /agents/alpha/a2a
Authorization: Bearer <credential>
Content-Type: application/json

{
  "kind": "job",
  "context_key": "msg_0123456789abcdef0123456789abcdef",
  "correlation_id": "corr_0123456789abcdef0123456789abcdef",
  "routing": { "to": ["beta"] },
  "body": { "text": "수신 여부를 답신해 주세요." }
}

성공은 작업 완료가 아니라 접수·큐 등록입니다. 응답은 ok, session_id, correlation_id, jobs[]를 돌려주고 각 job은 job_id, agent_id, kind, delivered를 가집니다. delivered는 라우팅 사실이지 모델 결과가 아닙니다.

대상이 여러 개면 독립 child job이 생기고 각각 결과와 egress를 가집니다. 라우터는 결과를 종합하지 않습니다.

작업 받기

좌석은 GET /agents/:id/events를 text/event-stream으로 소비합니다. 각 이벤트는 event, 영속 id(event_seq), JSON data를 가집니다. 재접속은 Last-Event-ID로 합니다. heartbeat 주석은 job이 아닙니다.

  • job — 검증·중복 확인 후 시스템에 실행하고 해당 job의 결과를 제출
  • listen — cc 참조입니다. 결과나 답신을 만들지 않습니다
  • egress — 원발신자에게 묶인 payload입니다. 처리하고 중복 방어를 저장한 뒤 ACK

결과 반환

결과가 증거입니다. 결과 없는 job은 끝난 것이 아니고, 실제 시스템 출력 없는 success는 다음 시스템이 발견할 거짓입니다.

/agents/:id/result에 job_id와 result를 POST합니다. status는 success, error, blocked이고 a2a_status는 resolved, out, continue, blocked입니다. activities는 선택 관측 자료이며 비어 있어도 됩니다.

{
  "job_id": "job-example",
  "result": {
    "status": "success",
    "a2a_status": "resolved",
    "response_text": "수신 확인했습니다.",
    "error": null,
    "activities": []
  }
}

완료·실패·승인 대기를 구분하세요. 타임아웃이나 검증되지 않은 모델 답변을 success로 바꾸지 마세요.

Egress와 원대화

egress 이벤트는 원발신자에게 묶인 결과를 운반합니다. 본문에서 목적지를 추측하지 말고 저장된 binding을 확인하고, 중복 방어를 적용한 뒤 egress_id로 /agents/:id/egress/ack를 호출합니다.

결과 전달과 ACK는 별개의 증거입니다. 결과를 새 canonical 답신으로 보내는 프로필은 원요청 검증도 함께 해야 합니다.

네 개의 식별자, 섞지 마세요

수명이 다른 값입니다. 하나를 다른 것으로 대체하는 것이 가장 흔한 통합 버그입니다.

  • context_key — 중복 제거에 쓰는 메시지 식별자
  • correlation_id — 결과가 보존해야 하는 요청 체인
  • session_id — 생략 시 라우터가 발급하는 대화 식별자
  • 호스트 스레드·대화 ID — 런타임의 속성이지 라우터의 것이 아님

실제로 일어나는 실패

아래는 사고가 아니라 정상 운영의 일부로 다뤄야 합니다.

  • 401/403 — 자격증명 누락·교체, 또는 :caller와 신원 불일치
  • 409 — 다른 소비자가 이미 이 좌석의 동적 스트림을 점유
  • 429/5xx — 백오프 후 재시도. POST 성공은 큐 등록만 증명
  • 중복 전달 — 도착 순서가 아니라 job_id로 중복 제거
  • 스트림 끊김 — Last-Event-ID로 재접속하고 부작용을 재실행하지 않음
  • 작업 중 타임아웃 — error 또는 blocked 제출. 출력 없는 success 금지

운영 체크리스트

파일럿 오픈 전에 계정 페이지에서 자격증명을 교체하고, /health와 /ready를 확인하고, 좌석당 소비자를 하나로 유지하고, 로그에 무엇을 남길지 정합니다.

고객 텍스트와 자격증명을 티켓에 붙여넣지 마세요. 라우터는 라우팅 메타데이터를 저장하고, payload와 보존은 어댑터가 소유합니다.