세션 생명주기 및 Identity
개요
데몬 세션은 하나의 ACP sessionId에 고정된 하나의 논리적 대화입니다. 브리지는 세션마다 03-acp-bridge.md에 설명된 SessionEntry를 유지하며, ACP 자식 연결과 HTTP 측 관리를 결합합니다: 프롬프트 FIFO, 모델 변경 FIFO, 이벤트 버스, 대기 중인 권한, 연결된 클라이언트, 하트비트, 복원 상태, 터미널 프레임 톰스톤.
데몬 클라이언트는 X-Qwen-Client-Id로 식별됩니다. 이는 HTTP 호출자가 요청에 붙이는 불투명하고 데몬이 검증하는 문자열입니다. 브리지는 어떤 클라이언트가 어떤 세션에 연결되어 있는지 추적하며, originator client id를 사용해 designated 권한 정책, 감사 추적, 이벤트 귀인을 처리합니다.
이 문서는 모든 세션 생명주기 전환(create / attach / load / resume / close / die / evict)과 데몬이 노출하는 모든 identity 서피스를 설명합니다.
책임
- 세션 생성, 연결, 복원, 정리.
X-Qwen-Client-Id검증 및 잘못된 id 거부.- 세션당 다중 연결 클라이언트 추적(
clientIds: Map<string, count>,attachCount). - 송신 이벤트에
originatorClientId기록. - 대시보드에서 어떤 클라이언트가 여전히 연결되어 있는지 확인할 수 있도록 하트비트 실행.
- 운영자가
PATCH /session/:id/metadata로 설정하는 세션 메타데이터(displayName) 제공. - 터미널 프레임 송신 처리(
session_died,session_closed,client_evicted,stream_error).
아키텍처
| 관심사 | 소스 | 비고 |
|---|---|---|
SessionEntry | packages/acp-bridge/src/bridge.ts | 세션별 구조체. 전체 필드 목록은 03-acp-bridge.md 참조. |
BridgeSession (공개) | packages/acp-bridge/src/bridgeTypes.ts | { sessionId, workspaceCwd, attached, clientId?, createdAt? } — HTTP 핸들러에 반환. |
BridgeSessionState | packages/acp-bridge/src/bridgeTypes.ts | LoadSessionResponse | ResumeSessionResponse — 엔트리에 restoreState로 캐시. |
DaemonSession (SDK) | packages/sdk-typescript/src/daemon/types.ts | { sessionId, workspaceCwd, attached, clientId?, createdAt? }. |
| Client-id 검증 | packages/acp-bridge/src/bridge.ts (spawnOrAttach 근처) | 패턴 [A-Za-z0-9._:-]{1,128}. 잘못된 형식이면 InvalidClientIdError. |
| 세션 연결 끊김 정리 | packages/cli/src/serve/server.ts | attachCount + spawnOwnerWantedKill로 spawn 소유자 연결 끊김 추적. |
상태 머신
Attach vs spawn
sessionScope: 'single'(기본값)에서 브리지의 defaultEntry는 모든 연결 클라이언트가 공유합니다. defaultEntry가 이미 존재하는 상태에서 POST /session이 도착하면 새 ACP 자식을 생성하지 않고 attached: true를 반환합니다. 브리지는 attachCount를 동기적으로 증가시키고 호출자의 X-Qwen-Client-Id를 clientIds에 등록합니다.
sessionScope: 'thread'에서는 각 스레드가 고유한 세션을 생성할 수 있습니다. 호출자는 여전히 maxSessions을 준수해야 합니다.
Identity
X-Qwen-Client-Id는 선택 사항이지만 강력히 권장됩니다. 데몬이 호출자를 대신해 생성하지 않습니다. 클라이언트가 직접 선택하여 요청 간에 재사용하면, 데몬이 투표 귀인, 이벤트 감사, 재연결 감지를 수행할 수 있습니다.
각 독립 컨트롤러는 고유하고 안정적인 ID를 사용해야 합니다. Web Shell은 호환성을 위해 역사적 webui_ 접두사를 유지합니다. 호스트와 임베디드 Web Shell은 의도적으로 하나의 논리적 컨트롤러로 동작할 때만 ID를 공유해야 합니다. 일단 공유되면 데몬 로그는 어느 쪽이 요청을 발생시켰는지 구별할 수 없습니다.
검증 규칙:
- 문자 집합:
[A-Za-z0-9._:-]. - 길이: 1–128.
- 범위를 벗어나면:
InvalidClientIdError(400).
데몬은 다음 조건을 모두 만족할 때 송신 SSE 이벤트에 originatorClientId를 기록합니다:
- 이벤트를 촉발한 요청에
X-Qwen-Client-Id가 포함되어 있고, AND - 해당 id가 세션의
clientIds집합에 현재 등록되어 있으며, AND - 세션에
activePromptOriginatorClientId가 설정되어 있음(인라인sessionUpdate및permission_request는 활성 프롬프트의 originator를 상속).
X-Qwen-Client-Id가 없는 익명 호출자는 first-responder 정책에서 정상 작동합니다. designated는 permission_forbidden{ reason: 'designated_mismatch' }로 투표를 거부합니다. consensus는 투표자가 발행 시점의 votersAtIssue 스냅샷에 없으므로 동일한 forbidden 사유로 거부합니다. local-only만이 익명 루프백 투표자를 허용하는 유일한 정책입니다.
워크플로
생성 또는 연결
로드 / 재개
POST /session/:id/load — 영속화된 세션을 복원하고 현재 경계 replay 스냅샷 창을 반환합니다(session/load 알림 또는 응답 모드 리플레이가 응답 반환 전에 시드됨).
POST /session/:id/resume — 리플레이 없이 복원(connection.unstable_resumeSession, 안정 session_resume 데몬 capability로 노출. unstable_session_resume는 더 이상 사용되지 않는 별칭으로 유지).
공통:
- 채널의 세션별
pendingRestoreIds집합을 사용해 동시 복원 호출을 병합(RestoreInProgressError). - 엔트리에
restoreState를 캐시하여 늦게 연결하는 클라이언트도 원래 복원자와 동일한 페이로드를 받도록 함.
영속화된 Part 4A worktree 세션의 경우, 복원은 이 생명주기의 무결성 검증 기반 확장입니다. 사이드카는 요청된 워크스페이스 루트를 명시적으로 식별하며, 체크아웃은 해당 .qwen/worktrees/ 디렉토리 아래에 정규적으로 포함되어야 하고, 그 마커는 정확히 복원된 세션 ID를 포함하는 단일 링크 일반 파일이어야 합니다. 데몬은 이 검사들을 통과한 후에만 유휴 상태의 복원된 자식을 재배치합니다. 활성 자식은 보고된 cwd가 이미 worktree와 동일할 때만 허용되며, 보고된 cwd가 없거나 다른 곳에 있는 활성 자식은 프롬프트 아래에서 재배치되는 대신 실패를 닫습니다. 단, 복원 프롬프트를 지연할 수 없는 콜드 복원은 예외입니다(suppressWorktreeContextRestore가 꺼져 있어 브리지가 질문을 주차하지 않고 다시 전송한 경우). 이 형태는 pre-4B 결과를 유지하여 worktreeState 없이 정규 worktree 메타데이터를 반환하며, 재배치되지 않고 세션이 생존합니다. 재배치 및 허용된 응답은 worktreeState: "persisted-v1"과 함께 정규 worktree 메타데이터를 반환합니다. supersededBy를 가진 사이드카는 절대 복원되지 않습니다. 라우트는 대체 세션 id와 함께 409 worktree_session_superseded를 반환합니다. 이 분류는 마커 읽기 전에 해당 링크만으로 결정되므로, 호출자는 해당 id의 로드가 성공한 후에만 리디렉션하고 bookkeeping을 복구합니다. pre-commit 중단된 전송은 마커 소유자가 아닌 대체자를 지명하며, 자체적으로 복원될 수 없고 재시도된 리셋에 의해 정리됩니다. supersedes 링크가 이전 사이드카와 일치하지만 마커가 이동하지 않았거나 존재하지 않는 복원된 대체자는 409 worktree_reset_interrupted를 반환하며, 복구는 초과된 세션에 대해 리셋을 재시도하는 것입니다. 일치하는 링크 쌍 없이 마커가 누락된 경우 409 worktree_marker_missing를 반환하며, 복구는 복원을 재시도하는 것이 아니라 작업을 리셋하는 것입니다. 어떤 복원 경로도 마커를 재생성하지 않기 때문입니다. 중단 분류가 먼저 확인됩니다. 잘못된 Part 4A 상태는 기존 연결을 분리하거나 requireZeroAttaches로 콜드 복원을 종료합니다. 누락된 사이드카 역시 증명을 제공하지 않습니다. 유효한 복원 소스가 Channel 소유인 경우, 라우트는 Part 4A 또는 분류 불가능한 사이드카 상태에 대한 ACP 에이전트의 best-effort 정리를 억제하여 검증 실패 시 불확실한 체크아웃 증거를 보존합니다. 영속화된 소스 메타데이터가 우선하며, 없을 때 로드/재개 요청이 유효한 소스를 제공합니다. workspaceCwd 없는 구조적으로 유효한 레거시 사이드카는 기존 best-effort 에이전트 복원을 유지합니다: 요청된 워크스페이스 루트 또는 Git 저장소 최상위 레벨 중 하나를 식별해야 하며, 마커 증명 없이 포함 검사를 받고, 에이전트에 의해 정리될 수 있으며, worktreeState 없이 worktree를 반환할 수 있습니다. 명시적 레거시 호환성 경우를 제외하고, 유효한 복원 소스가 Channel 소유가 아닌 세션만 라우트 검증 전에 기존 best-effort 정리를 유지합니다.
Worktree 소유권 이전(POST /session/:id/worktree-reset, session_worktree_reset_v1로 광고)은 Channel 작업 리셋을 위해 이 생명주기를 확장합니다: 데몬은 루트 워크스페이스에 새로운 스레드 범위 대체자를 생성하고, 검증된 체크아웃으로 재배치하며, 사이드카 쌍을 연결합니다(먼저 이전 세션에 supersededBy, 그 다음 대체자에 supersedes). 체크아웃별 라우트 락과 프롬프트 허용을 포함하여 체크아웃에서 작업을 시작하거나 세션 cwd를 이동하는 7개의 다른 작성자(rewind, cwd 변경, branch, fork, shell, goal control, workflow-task 작업)를 펜싱하는 허용 배리어 하에서 마커를 대체자로 전환합니다. 이 경우 release 및 stop 경로는 설계상 펜싱되지 않습니다. 그런 다음 초과된 세션의 클라이언트 등록과 인메모리 worktree 연관을 차단합니다. 차단 결과는 초과된 세션이 실제로 사라졌는지 보고합니다: 자식이 아직 백그라운드 작업을 보유하고 있는 생존자는 배리어를 무장 상태로 유지하고, 로그에 기록되며, 성공 응답으로 덮어쓰는 대신 호출자에게 supersededSessionLive: true로 보고됩니다. 전송 중 초과된 세션에서 허용된 펜싱 작성자는 409 worktree_reset_active로 거부됩니다. 재시도가 어떤 크래시 창을 되돌리고 어떤 것이 운영자 복구를 위해 실패를 닫는지를 포함한 전체 실패 분류는 qwen-serve-protocol.md의 라우트와 함께 문서화되어 있습니다.
하트비트
POST /session/:id/heartbeat는 clientId와 무관하게 sessionLastSeenAt를 갱신합니다. 요청에 등록된 X-Qwen-Client-Id가 포함되어 있으면 clientLastSeenAt.set(clientId, Date.now())도 갱신됩니다. 클라이언트별 축출은 v1에서 구현되지 않았습니다. 폐지 정책은 F-series Wave 5에서 계획 중입니다. 현재 하트비트는 대시보드와 PR 24의 예정된 폐지 정책을 위한 관측 가능성을 제공합니다.
메타데이터
PATCH /session/:id/metadata는 {displayName?}을 받습니다. 검증:
- 최대 길이:
MAX_DISPLAY_NAME_LENGTH = 256. - 제어 문자 포함 불가(
hasControlCharacter가 코드 포인트 ≤ 0x1f 또는 == 0x7f를 거부). - 위반 시
InvalidSessionMetadataError(400).
성공 시 모든 구독자에게 session_metadata_updated를 전송합니다.
종료
| 터미널 프레임 | 트리거 |
|---|---|
session_closed | DELETE /session/:id(client_close) 또는 프로그래밍 방식 종료. |
session_died | channel.exited가 어떤 이유로든 발생(크래시, 자식 kill). OS 종료 경로를 사용한 경우 exitCode? + signalCode? 포함. |
client_evicted | EventBus의 구독자별 큐 오버플로(10-event-bus.md 참조). 세션 수준 종료가 아니라 해당 구독자만 종료. |
stream_error | SubscriberLimitExceededError 또는 기타 라우트 수준 스트림 실패. |
대기 중인 권한은 모든 종료 경로에서 mediator.forgetSession(sessionId)를 통해 {kind:'cancelled', reason:'session_closed'}로 해결됩니다.
연결 끊김 정리 가드
spawn 소유 클라이언트의 HTTP 응답을 작성할 수 없을 때(TCP 핸드셰이크 중 리셋), 라우트는 killSession({ requireZeroAttaches: true })을 호출합니다. 다른 클라이언트가 이미 연결되어 있으면(attachCount > 0) 가드가 단락되고 세션은 유지됩니다. spawnOwnerWantedKill = true로 의도를 기록하여, 나중에 detachClient()가 attachCount를 0으로 만들면 지연된 정리가 완료됩니다. 이 가드가 없으면 빠르게 연결이 끊기는 spawn 소유자가 정상 세션을 매번 종료해 버립니다.
상태 및 생명주기
생명주기에 중요한 SessionEntry 필드:
| 필드 | 타입 | 의미 |
|---|---|---|
clientIds | Map<string, number> | 등록된 클라이언트 id → 등록 참조 카운트. |
attachCount | number | spawnOrAttach가 이 엔트리에 대해 attached: true를 반환한 횟수. |
activePromptOriginatorClientId | string? | 현재 실행 중인 프롬프트의 originator. |
restoreState | BridgeSessionState? | 캐시된 로드/재개 응답. 늦게 연결하는 클라이언트도 일관된 페이로드를 확인. |
spawnOwnerWantedKill | boolean | 지연 정리 톰스톤(위의 연결 끊김 정리 참조). |
sessionLastSeenAt | number? | 모든 클라이언트의 가장 최근 하트비트(epoch ms). |
clientLastSeenAt | Map<string, number> | 클라이언트별 하트비트. |
pendingPermissionIds | Set<string> | 현재 대기 중인 ACP requestId. 취소/종료 시 cancelled로 해결하는 데 사용. |
의존성
- ACP 레이어:
connection.newSession,connection.unstable_resumeSession,connection.loadSession. 03-acp-bridge.md— 브리지 아키텍처 전체.04-permission-mediation.md— originator + identity가 정책 결정에 미치는 영향.10-event-bus.md— 터미널 프레임 전달.
추가 세션 엔드포인트
다음 엔드포인트는 기본 생명주기 서피스를 확장합니다:
논블로킹 프롬프트(non_blocking_prompt capability 태그)
POST /session/:id/prompt는 이제 프롬프트가 완료될 때까지 블로킹하는 대신 HTTP 202와 { promptId, lastEventId }를 반환합니다. 실제 결과는 SSE에서 turn_complete / turn_error로 전달되며, promptId 필드가 해당 이벤트를 202 응답과 연결합니다. DaemonSessionClient.prompt()은 활성 이벤트 구독이 있을 때 자동으로 논블로킹 경로를 사용하고 SSE 스트림에서 결과를 투명하게 매칭합니다.
세션 요약(session_recap capability 태그)
POST /session/:id/recap은 빠른 모델에게 “어디까지 했는지” 한 줄 요약을 요청합니다. { sessionId, recap: string | null }을 반환하며, null은 히스토리가 너무 짧거나 모델이 일시적으로 실패했음을 의미합니다. 이 엔드포인트는 best-effort입니다.
세션 사이드 질문(session_btw capability 태그)
POST /session/:id/btw는 메인 대화 흐름을 방해하지 않고 세션 컨텍스트에 대해 일회성 질문을 합니다. 캐시 경로에서 runForkedAgent를 사용하여 단일 턴, 도구 없는 LLM 호출을 수행하고 { sessionId, answer: string | null }을 반환합니다. 구현은 BTW_MAX_INPUT_LENGTH, 크로스 세션 유출 가드, 타임아웃 처리를 적용합니다.
셸 명령어 실행
POST /session/:id/shell은 LLM을 거치지 않고 데몬 호스트에서 직접 셸 명령어를 실행합니다. 세션 SSE 버스에서 user_shell_command / user_shell_result 이벤트로 출력을 스트리밍하고, 명령어와 결과를 LLM 대화 히스토리에 주입합니다. 응답은 { exitCode, output, aborted }입니다. 활성 보조 워크스페이스 세션의 경우, 단일 REST 라우트가 세션 소유자를 확인하고 해당 런타임의 브리지에서 실행하므로 명령어는 소유 워크스페이스 cwd에서 시작됩니다. 이 라우트는 경로 샌드박스를 제공하지 않습니다. 워크스페이스 한정 ACP 클라이언트는 소유 워크스페이스 연결에서 _qwen/session/shell을 계속 사용할 수 있습니다.
세션 되감기
GET /session/:id/rewind/snapshots 및 POST /session/:id/rewind는 소유 활성 워크스페이스 런타임을 확인합니다. 영속화된 세션은 되감기 전에 로드하거나 재개해야 합니다. 되감기는 대화 히스토리를 자르고 edit 및 write_file로 추적되는 파일을 선택적으로 복원합니다. 셸 명령어, Git, 스크립트, 수동 변경은 되돌리지 않습니다. 파일 복원은 best-effort이므로, 응답이 대화 히스토리가 이미 이동한 후 rewound: false 및 filesFailed[]를 보고할 수 있습니다. SDK 되감기 호출은 클라이언트가 ACP 전송을 사용하는 경우에도 항상 owner-aware REST를 사용합니다. 변이는 엄격한 REST 인증을 유지해야 하기 때문입니다.
세션 분리
POST /session/:id/detach는 attachCount를 감소시켜 클라이언트를 세션에서 명시적으로 분리합니다. 이것만으로 세션이 종료되지는 않습니다. 다른 연결이나 구독자가 남아있지 않으면 세션이 정리됩니다. 이 엔드포인트는 204를 반환합니다.
배치 세션 삭제
POST /sessions/delete는 { sessionIds: string[] }(최대 100개 id)를 받아 브리지 세션을 종료하고 활성 또는 아카이브된 트랜스크립트 파일을 삭제합니다. 동일한 id에 대해 활성 및 아카이브 JSONL 파일이 모두 존재하면 하드 삭제가 둘 다 제거하므로 운영자가 충돌을 정리할 수 있습니다. 활성 및 아카이브 worktree 사이드카를 정리하지만, 파일 히스토리 스냅샷, 서브에이전트 트랜스크립트, 런타임 사이드카는 그대로 둡니다. 복원력을 위해 Promise.allSettled를 사용하며 { removed, notFound, errors }를 반환합니다.
세션 아카이브
POST /sessions/archive는 비활성 세션 JSONL 파일을 chats/에서 chats/archive/로 이동합니다. 대상 세션이 활성 상태이면 데몬은 먼저 세션별 아카이브 게이트에 진입하고 ACP 자식이 ChatRecordingService를 플러시하도록 엄격 종료를 수행합니다. 종료나 플러시에 실패하면 아카이브는 JSONL을 제자리에 둡니다.
POST /sessions/unarchive는 아카이브된 JSONL 파일을 chats/로 다시 이동합니다. 이것은 저장소 상태 전환일 뿐이므로 클라이언트는 이후에 session/load 또는 session/resume을 호출해야 합니다. 아카이브된 세션은 로드/재개 시 409 session_archived를 반환하며, 아카이브 전환과 경합하는 변이는 409 session_archiving을 반환합니다.
비어 있거나 손상되었거나 고아 상태인 일반 트랜스크립트 파일도 대화로 로드할 수 없는 경우에도 이러한 생명주기 작업의 대상이 될 수 있습니다. 소유권 안전 검사는 의도적으로 실패를 닫고 운영자의 개입을 요구할 수 있습니다. 작성자가 인증된 핸드오프 증명을 봉인한 후에 변경된 파일은 운영자가 봉인된 잠금과 변경된 바이트를 해결할 때까지 SessionTranscriptChangedError로 실패합니다. 경계 있는 소유권 읽기 창을 초과하는 JSON 형태의 첫 물리적 레코드는 레코드가 복구되거나 축소될 때까지 SessionTranscriptIdentityUnavailableError로 실패합니다. 비객체 접두사를 가진 과도하게 큰 손상 레코드는 대상 자격을 유지합니다. 파싱 가능한 복구 레코드에는 문자열 sessionId와 cwd 소유권 필드가 포함되어야 하며, 혼합 로컬/외부 아카이브 상태도 실패를 닫습니다. session_storage_conflict_repair가 광고될 때 아카이브와 언아카이브는 resolveConflicts: true를 받습니다. 아카이브는 아카이브된 사본을 유지하고 언아카이브는 활성 사본을 유지합니다. 이 옵션이 없으면 활성/아카이브 충돌은 두 영속 사본 중 어느 것도 이동, 제거, 덮어쓰기하지 않으며 배치 errors 배열에서 반환됩니다. 아카이브는 여전히 충돌을 분류하기 전에 활성 세션을 엄격히 종료하며, 이로 인해 대기 중인 레코드가 활성 트랜스크립트로 플러시될 수 있습니다. 워크스페이스 한정 생명주기 라우트는 이제 이전의 HTTP 409 session_conflict 응답 대신 HTTP 200 배치 엔벨로프를 사용합니다.
컨텍스트 사용량(session_context_usage capability 태그)
GET /session/:id/context-usage는 구조화된 컨텍스트 창 사용량을 반환합니다. ?detail=true는 도구, 메모리, skill별로 그룹화된 세분화된 사용량을 포함합니다.
세션 통계(session_stats capability 태그)
GET /session/:id/stats는 사용 통계를 반환합니다: 모델 지표(입력/출력 토큰, 캐시 읽기/쓰기, 총 비용), 도구별 호출 카운트 및 레이턴시, 파일 편집 카운트, 활성 세션의 skill별 호출 카운트. skills 블록은 이 세션의 skill 본문 로드 및 skill 슬래시 명령어만 반영하며, 크로스 세션 활동 집계가 아닙니다.
세션 작업(session_tasks capability 태그)
GET /session/:id/tasks는 에이전트 작업, 셸 작업, 모니터 작업 및 이들의 생명주기 상태에 대한 배경 작업 스냅샷을 반환합니다. 다른 서브에이전트가 생성한 에이전트 엔트리는 선택적 계보 필드(parentAgentId, parentName, depth)를 가지므로 클라이언트가 중첩된 서브에이전트를 트리로 렌더링할 수 있습니다. 페이로드 예시는 qwen-serve-protocol.md를 참조하세요.
session_monitor_tool_correlation capability는 모니터 엔트리가 toolUseId를 보장하여 클라이언트가 트랜스크립트 도구 호출을 해당 작업 세부 정보와 연결할 수 있게 합니다.
세션 LSP 상태(session_lsp capability 태그)
GET /session/:id/lsp는 데몬 클라이언트를 위한 정리된 세션별 LSP 상태를 반환합니다: 활성화 여부, 집계 서버 카운트, 사용 불가/초기화 상태, 서버별 name, status, languages, transport, command, error. 비활성화 또는 사용 불가 LSP는 전송 오류가 아닌 HTTP 200 상태 데이터로 표현됩니다.
압축 리플레이
POST /session/:id/load는 이제 BridgeRestoredSession을 반환하며, 여기에는 compactedReplay?: BridgeEvent[], liveJournal?: BridgeEvent[], lastEventId?: number가 포함될 수 있습니다. 이 필드들은 활성 세션의 데몬의 경계 내 메모리 리플레이 창이며 전체 트랜스크립트 API가 아닙니다. 기본 창 상선은 활성 세션당 4 MiB(--compacted-replay-max-bytes)이며, 부팅 시 잘못된 상선을 거부합니다. 하드 상한은 256 MiB입니다. compactedReplay는 TurnBoundaryCompactionEngine에서 생성됩니다: 턴 경계에서 연속 텍스트/사고 블록을 접고, 도구 호출 시퀀스를 최종 상태로 축소하며, 임시 신호를 버리고, O(tokens) 로그 대신 O(turns) 리플레이 로그를 생성합니다(일반적으로 25-30배 축소). 해당 바이트 창에서 이전 리플레이 엔트리가 삭제되면 compactedReplay[0]는 합성 id가 없는 history_truncated 마커이며 {reason: 'replay_window_exceeded', truncatedEvents, retainedEvents, maxBytes, truncatedTurns?, fullTranscriptAvailable: boolean}를 포함합니다. fullTranscriptAvailable은 capability 플래그입니다: true는 클라이언트가 GET /session/:id/transcript로 전체 영속 트랜스크립트를 페이지할 수 있음을 의미하고, false는 경계 내 리플레이만 사용 가능함을 의미합니다. 클라이언트는 이를 상태로 렌더링하고 보유된 리플레이를 정상적으로 적용해야 합니다. 재동기화 루프를 촉발해서는 안 됩니다.
ACP 자식 예열
bridge.preheat()는 명시적 임베더에게 여전히 사용 가능하지만, qwen serve도 호환성을 위해 시작 후 신뢰된 기본 자식을 예열하려고 시도합니다. 실패한 예열은 치명적이지 않으며 다음 런타임 명령이나 세션에서 재시도합니다. 신뢰된 보조 자식은 첫 사용 시 시작됩니다. 워크가 활성 상태인 동안 Workspace Runtime이 자식을 소유합니다. 모든 세션 및 관리 리스가 소진된 후, 생략되거나 0인 channelIdleTimeoutMs는 자식을 즉시 정리합니다. 일반 예열 자체는 첫 사용을 위해 보존되며 해당 정리기를 작동시키지 않습니다. 양수로 설정된 지연 또는 활성 킵얼라이브는 더 긴 남은 창 동안 자식을 재사용 가능하게 유지합니다. 공개 Workspace Runtime ensure 명령은 갱신 가능한 10분 워크스페이스 리스를 추가합니다. 각 성공적인 호출은 해당 창을 재설정하며, 채널이 이미 활성 상태인 경우에도 마찬가지입니다.
설정
BridgeOptions.maxSessions(기본값 32) — 상한.BridgeOptions.sessionScope(기본값'single'; 선택적'thread').BridgeOptions.initializeTimeoutMs(기본값 10s) — ACP 자식 시작 기한(Channel 팩토리 +initialize핸드셰이크) 및 기본 요청 타임아웃.BridgeOptions.sessionRestoreTimeoutMs(기본값 60s) — ACPloadSession/unstable_resumeSession기한. 기본값 60s이며, 명시적으로 설정된 initialize 타임아웃이 이를 올릴 수 있지만 낮출 수는 없습니다.BridgeOptions.channelIdleTimeoutMs(설정되지 않거나0이면 런타임 작업 소진 후 정리. 단, 일반 예열은 첫 사용을 위해 보존됨. 양수 값 또는 활성 킵얼라이브는 정리를 지연시키며, 더 긴 지연이 우선됨).- Capability 태그:
session_create,session_id_override,session_scope_override,session_load,session_resume,unstable_session_resume(더 이상 사용되지 않는 별칭),session_list,session_info,session_close,session_metadata,session_set_model,client_identity,client_heartbeat,session_recap,session_generation,session_btw,session_context_usage,session_tasks,session_monitor_tool_correlation,session_stats,session_lsp,session_resources,session_status,non_blocking_prompt.
무상태 생성(session_generation capability 태그)
POST /session/:id/generate는 { "prompt": string }을 받아 started, 선택적 thinking, delta, done, 또는 error 이벤트와 함께 요청 범위 SSE 스트림을 반환합니다. 이 요청은 대화 히스토리를 읽지 않고, 턴을 기록하지 않으며, 도구를 노출하지 않습니다. ACP 자식은 사용 가능한 경우 유효한 설정된 빠른 모델을 사용하고, 그렇지 않으면 세션의 메인 모델을 사용합니다.
주의사항 및 알려진 제한
connection.unstable_resumeSession은 ACP 레이어에서 여전히 불안정할 수 있지만, 데몬은 커밋된 v1 라우트 계약을session_resume로 광고합니다.unstable_session_resume는 더 이상 사용되지 않는 호환성 별칭으로만 유지됩니다.- v1에는 클라이언트별 축출이 없습니다. 세션별 및 구독자별 종료만 존재합니다. 폐지 정책은 F-series Wave 5 / PR 24입니다.
client_evicted는 세션별이 아닌 구독자별입니다. SSE 구독자가 축출된 클라이언트는 재연결할 수 있습니다.- 익명 클라이언트(
X-Qwen-Client-Id없음)는designated또는consensus정책에서 투표할 수 없습니다.
참고 자료
packages/acp-bridge/src/bridge.ts(SessionEntry 정의)packages/acp-bridge/src/bridgeTypes.ts(HttpAcpBridge,BridgeSession,BridgeSessionState)packages/sdk-typescript/src/daemon/types.ts(DaemonSession)packages/sdk-typescript/src/daemon/DaemonSessionClient.ts- Wire 참고:
../qwen-serve-protocol.md(라우트 카탈로그).