세션 및 대화 상태
게이트웨이, 채널, 크론 작업 전반에 걸쳐 세션이 범위 지정되고, 영속화되고, 직렬화되고, 공유되는 방식을 설명합니다.
세션은 단일 대화 스레드로, 순서가 있는 메시지 목록과 소량의 상태 정보(대화 상대, 현재 처리 중인 턴 여부, 마지막 활동 시각)로 구성됩니다. Revka는 세션을 유지함으로써 에이전트가 재시작 후 컨텍스트를 복원하고, 대시보드에서 이전 대화를 목록으로 보거나 다시 열 수 있으며, 예약된 작업이 사용자가 이미 읽고 있는 스레드에 결과를 추가할 수 있도록 합니다.
이 페이지에서는 세션의 범위 지정 및 이름 규칙, 영속화 방식, 동시 턴의 직렬화 처리, 그리고 세션 작업에 사용하는 REST 엔드포인트·툴·설정 키를 설명합니다. 게이트웨이 위에 채팅 UI를 구축하는 경우 실시간: WebSocket, SSE & Live Canvas와 크론, 세션 & 첨부파일 API도 참고하세요.
세션의 출처
섹션 제목: “세션의 출처”모든 대화 채널에서 세션이 생성되지만, 세션은 두 개의 별도 저장소에 보관됩니다.
- 게이트웨이(대시보드) 세션 —
/ws/chatWebSocket에 의해 생성됩니다. 대시보드에 표시되는 채팅이 이에 해당합니다.gw_접두사가 붙은 키로 저장되며,[gateway] session_persistence가 활성화된 경우 SQLite에 영속화됩니다. - 채널 세션 — Telegram, Discord, Slack, Matrix 등의 메시징 채널에서 메시지가 수신될 때 생성됩니다. 키에는 채널 이름이 접두사로 붙습니다(예:
discord_…,telegram_…).[channels] session_persistence가 활성화된 경우{workspace}/sessions/경로에 영속화됩니다.
GET /api/sessions 엔드포인트는 두 종류의 세션을 모두 반환합니다. 게이트웨이 세션은 항상 포함되며, 채널 영속화가 활성화된 경우 채널 세션도 포함됩니다.
session_id 형식
섹션 제목: “session_id 형식”세션은 세션 ID로 식별되지만, 영속화 레이어는 채널 접두사가 추가된 세션 키로 저장합니다. 원시 저장소를 읽거나 에이전트 간 툴을 사용할 때 이 차이를 이해하는 것이 중요합니다.
| 채널 | 전달하는 세션 ID | 저장 키 | 비고 |
|---|---|---|---|
| 게이트웨이 / 대시보드 | UUID(자동 생성) 또는 operator-main 같은 지정 이름 | gw_<id> | REST API는 gw_ 접두사를 숨기므로 경로에 ID만 전달합니다. |
| 채널 (스레드 없음) | 발신자에서 파생 | <channel>_<sender> | 예: telegram_alice |
| 채널 (스레드 있음) | 스레드 + 발신자에서 파생 | <channel>_<thread>_<sender> | Slack/Discord 스레드는 스레드 ID를 포함합니다. |
핵심 사항:
/ws/chat에서session_id를 생략하면 게이트웨이가 새 UUID를 발급하고, 기존 ID(또는operator-main같은 고정 이름)를 전달하면 세션을 이어서 진행합니다.- 범용
/webhook엔드포인트는 선택적X-Session-Id헤더를 통해 실행 범위를 특정 세션으로 한정합니다. - 에이전트 간 세션 툴(아래 참조)은
session_id로 전체 세션 키를 받으며, 채널 세션의 경우 해당 툴에서 사용하는 규칙은channel__identifier형식입니다(예:telegram__alice).
세션 영속화 설정
섹션 제목: “세션 영속화 설정”영속화는 두 저장소에 대해 독립적으로 제어됩니다. 기본값은 모두 활성화입니다.
게이트웨이 세션 — [gateway]
섹션 제목: “게이트웨이 세션 — [gateway]”[gateway]# Persist gateway WebSocket chat sessions to SQLite. Default: true.session_persistence = true
# Auto-archive stale gateway sessions older than N hours. 0 = disabled. Default: 0.session_ttl_hours = 0| 키 | 타입 | 기본값 | 설명 |
|---|---|---|---|
session_persistence | bool | true | false로 설정하면 게이트웨이는 세션 백엔드를 유지하지 않습니다. 세션 목록/메시지/상태 엔드포인트는 session_persistence: false와 함께 빈 결과를 반환하며, 채팅은 재시작 후 유지되지 않습니다. |
session_ttl_hours | u32 | 0 | 0보다 크면, 데몬 시작 시 이 시간보다 오래된 세션이 아카이브됩니다. 0은 TTL 정리를 비활성화합니다. |
채널 세션 — [channels]
섹션 제목: “채널 세션 — [channels]”[channels]# Persist channel conversation history so sessions survive daemon restarts.# Files are stored in {workspace}/sessions/. Default: true.session_persistence = true
# Backend: "sqlite" (default) or "jsonl" (legacy). SQLite adds FTS5 search,# metadata tracking, and TTL cleanup.session_backend = "sqlite"
# Auto-archive stale channel sessions older than this many hours. 0 disables. Default: 0.session_ttl_hours = 0| 키 | 타입 | 기본값 | 설명 |
|---|---|---|---|
session_persistence | bool | true | 재시작 후에도 채널 대화 기록을 유지합니다. |
session_backend | string | "sqlite" | "sqlite"(FTS5 검색, 메타데이터, TTL 지원) 또는 "jsonl"(레거시 플랫 파일). |
session_ttl_hours | u32 | 0 | N시간보다 오래된 채널 세션을 자동 아카이브합니다. 0은 비활성화합니다. |
게이트웨이 세션 영속화는 대시보드 Config 페이지에서 전환할 수 있습니다. 크론, 비용 & 설정 페이지를 참고하세요.
채팅 세션 — 목록 조회, 메시지, 상태, 삭제, 이름 변경
섹션 제목: “채팅 세션 — 목록 조회, 메시지, 상태, 삭제, 이름 변경”모든 세션 엔드포인트는 페어링 Bearer 토큰을 필요로 합니다. 토큰 발급 방법은 페어링 & 인증을 참고하세요.
Authorization: Bearer <token>세션 목록 조회
섹션 제목: “세션 목록 조회”GET /api/sessions최근 활동 순으로 정렬된 모든 세션(게이트웨이 + 채널)과 아카이브된 세션 ID 목록을 반환합니다.
{ "sessions": [ { "id": "8d43b6ef-0f18-4c3f-b04c-3a03f79e2c72", "channel": "dashboard", "started_at": "2026-06-18T09:00:00+00:00", "last_activity": "2026-06-18T09:14:22+00:00", "status": "active", "message_count": 12, "name": "Release planning" }, { "id": "telegram_alice", "channel": "telegram", "started_at": "2026-06-17T20:10:00+00:00", "last_activity": "2026-06-17T20:31:05+00:00", "status": "idle", "message_count": 4 } ], "archived_session_ids": ["3f0c…"]}status 필드는 대략적인 최근성 플래그입니다. 마지막 활동이 5분 이내이면 active, 그 외에는 idle입니다.
실행 중인 세션 목록 조회
섹션 제목: “실행 중인 세션 목록 조회”GET /api/sessions/running현재 턴이 처리 중인(running) 세션만 반환합니다. 각 항목에는 session_id, created_at, last_activity, message_count가 포함됩니다.
트랜스크립트 불러오기
섹션 제목: “트랜스크립트 불러오기”GET /api/sessions/{id}/messages{ "session_id": "8d43b6ef-…", "messages": [ { "role": "user", "content": "Plan the next release" }, { "role": "assistant", "content": "Here is a draft plan…" } ], "session_persistence": true}세션 상태 조회
섹션 제목: “세션 상태 조회”GET /api/sessions/{id}/state{ "session_id": "8d43b6ef-…", "state": "running", "turn_id": "f1c2…", "turn_started_at": "2026-06-18T09:14:00+00:00"}state는 idle, running, error 중 하나입니다. turn_id와 turn_started_at은 턴이 활성 상태일 때만 나타납니다. 세션이 없으면 404를 반환합니다.
세션 이름 변경
섹션 제목: “세션 이름 변경”PUT /api/sessions/{id}Content-Type: application/json
{ "name": "Release planning" }이름은 비어 있으면 안 됩니다. 세션이 이미 존재해야 하며, 없으면 404를 반환합니다. 응답: {"session_id": "…", "name": "Release planning"}.
세션 삭제(아카이브)
섹션 제목: “세션 삭제(아카이브)”DELETE /api/sessions/{id}삭제 시 세션을 완전히 지우지 않고 아카이브(소프트 삭제)합니다. 기록은 복구 가능한 상태로 남으며, ID는 목록 응답의 archived_session_ids에 나타납니다.
{ "deleted": true, "archived": true, "archived_at": "2026-06-18T10:00:00+00:00", "session_id": "8d43b6ef-…" }첨부파일 업로드
섹션 제목: “첨부파일 업로드”파일은 특정 세션에 첨부되며, 이후 WebSocket 메시지에서 ID로 참조됩니다.
POST /api/sessions/{session_id}/attachmentsContent-Type: multipart/form-data응답에는 file_id가 포함되며, 다음 /ws/chat 메시지의 attachments 배열에 이를 포함하면 됩니다. 이미지는 비전 모델용 [IMAGE:…] 마커로, 그 외 파일은 [DOCUMENT:…]로 변환됩니다. 파일당 최대 25 MiB입니다. 전체 첨부파일 흐름은 크론, 세션 & 첨부파일 API를 참고하세요.
세션별 액터 큐
섹션 제목: “세션별 액터 큐”하나의 세션에서 두 개의 턴이 동시에 실행되면 SQLite 트랜스크립트 쓰기가 손상되고 상태 전이가 뒤섞일 수 있습니다. 게이트웨이는 세션별 액터 큐로 이를 방지합니다. 각 세션은 최대 하나의 처리 중인 턴만 허용하며, 추가 요청은 FIFO 순서로 대기합니다.
/ws/chat에서의 동작:
- 세션당 하나의 턴. 처리 중인 세션에 두 번째 메시지가 도착하면 현재 턴이 완료될 때까지 대기합니다.
- 대기열 제한. 대기열 깊이 한도에 도달하면 새 요청은
SESSION_BUSYWebSocket 오류 코드와 함께 거부됩니다. 오류 메시지는Session <id> queue full (<N> pending requests)입니다. - 락 타임아웃. 락 타임아웃보다 오래 대기한 요청은
Previous message is still being processed — please wait, or retry once it completes (timeout: <N>s)오류와 함께 실패합니다. 이 메시지에는 세션 ID가 의도적으로 포함되지 않습니다. - 유휴 제거. 유휴 TTL을 초과한 세션 슬롯은 메모리에서 제거됩니다(영속화된 트랜스크립트는 영향을 받지 않습니다).
락 타임아웃의 기본값은 300초이며, 환경 변수로 설정합니다.
| 환경 변수 | 기본값 | 설명 |
|---|---|---|
REVKA_GATEWAY_SESSION_LOCK_TIMEOUT_SECS | 300 | 큐에 대기 중인 턴이 세션 락을 획득하지 못할 경우 타임아웃까지 대기하는 시간(초). 유효하지 않거나 0인 값은 경고를 기록하고 기본값으로 폴백합니다. |
에이전트 간 세션 툴
섹션 제목: “에이전트 간 세션 툴”세 가지 에이전트 툴을 통해 한 에이전트가 다른 대화의 세션을 읽고 쓸 수 있습니다. 이는 에이전트 간 통신의 기반이 됩니다. 이 툴들은 전체 세션 키를 사용하여 세션 백엔드에 직접 접근합니다.
sessions_list
섹션 제목: “sessions_list”채널, 메시지 수, 마지막 활동 시각과 함께 활성 세션 목록을 반환합니다.
limit(integer, 선택, 기본값50) — 반환할 최대 세션 수.
각 줄에 하나의 세션을 나타내는 텍스트 형식으로 반환합니다: - <key>: channel=<channel>, messages=<N>, last_activity=<ts>.
sessions_history
섹션 제목: “sessions_history”특정 세션의 최근 메시지 기록을 읽습니다.
session_id(string, 필수) — 세션 키, 예:telegram__user123.limit(integer, 선택, 기본값20) — 반환할 최근 메시지 수.
보안 정책에서 Read 툴 권한이 필요합니다. 비어 있거나 알 수 없는 세션은 오류 대신 “No messages found” 안내를 반환합니다.
sessions_send
섹션 제목: “sessions_send”대상 세션의 기록에 user 메시지를 추가하여 대상 에이전트가 다음 턴에 메시지를 받을 수 있도록 합니다.
session_id(string, 필수) — 대상 세션 키.message(string, 필수) — 전송할 내용. 비어 있으면 안 됩니다.
Act 툴 권한이 필요합니다. Read와 Act 권한 게이팅의 동작 방식은 툴 개요와 보안 모델을 참고하세요.
크론 출력을 세션으로 라우팅
섹션 제목: “크론 출력을 세션으로 라우팅”에이전트 크론 작업은 session_target으로 실행할 세션을 지정합니다.
| 값 | 동작 |
|---|---|
isolated (기본값) | 각 실행은 빈 컨텍스트에서 새로 시작합니다. 대부분의 자동화 작업에 권장됩니다. |
main | 작업이 사용자의 기본 인터랙티브 채팅과 동일한 세션에서 실행되어 최근 대화 컨텍스트를 참조할 수 있습니다. |
{ "schedule": { "kind": "cron", "expr": "0 9 * * 1-5", "tz": "America/New_York" }, "job_type": "agent", "prompt": "Summarize overnight alerts", "session_target": "isolated", "delivery": { "mode": "announce", "channel": "discord", "to": "1234567890" }}session_target은 에이전트 작업을 생성하는 모든 곳에서 사용할 수 있습니다. cron_add 툴, [[cron.jobs]] 설정 블록, POST /api/cron 본문 모두에서 지원됩니다.
전체 스케줄링 기능은 revka cron과 에이전트 작업 & 전달 아래의 크론 가이드를 참고하세요.
턴의 상태 흐름
섹션 제목: “턴의 상태 흐름”-
클라이언트가 기존
session_id로/ws/chat에 연결하거나, 생략하여 새 UUID를 발급받습니다. 게이트웨이는resumed와message_count를 포함한session_start를 전송합니다. -
클라이언트가
message를 전송합니다. 액터 큐가 세션 락을 획득하고 세션 상태가turn_id와 함께running으로 전환됩니다. -
에이전트는
chunk프레임,tool_call/tool_result이벤트, 마지막으로done프레임을 스트리밍합니다. 상태는idle로 돌아옵니다. -
락을 획득하지 못하면 클라이언트는
SESSION_BUSY오류를 받습니다. 턴 중간에 실패가 발생하면 상태는error가 됩니다. -
영속화가 활성화된 경우, 트랜스크립트가 SQLite에 기록되어 재시작 후
GET /api/sessions/{id}/messages로 조회할 수 있습니다.