Skip to Content
KoDevelopersDaemon퀵스타트 & 운영

퀵스타트 & 운영

이 페이지는 qwen serve를 시작하는 방법, 작동 확인 방법, 그리고 qwen serve부터 리스닝 서버까지의 내부 호출 체인에 중점을 둡니다. 아키텍처, 컴포넌트, 와이어 프로토콜 세부 정보는 다른 데몬 심층 페이지에서 다룹니다.

1. 최단 경로

qwen serve

출력:

qwen serve listening on http://127.0.0.1:4170 (mode=http-bridge, workspace=/your/cwd) qwen serve: bound to workspace "/your/cwd" qwen serve: bearer auth disabled (loopback default). Set QWEN_SERVER_TOKEN to enable.

브라우저에서 http://127.0.0.1:4170/demo를 열면 디버그 콘솔을 확인할 수 있습니다: 채팅 UI, 이벤트 스트림, 워크스페이스 검사. 기본 루프백 개발 모드에서 createServeApp()bearerAuth 이전에 packages/cli/src/serve/routes/health-demo.ts에서 /demo 라우트를 마운트하므로 토큰이 필요 없습니다.

2. 실행 레시피

# 1. 로컬 개발 기본값 (루프백, 토큰 없음) qwen serve # 2. 명시적 워크스페이스 + 임시 포트 qwen serve --workspace /path/to/repo --port 0 # 3. 강화된 루프백 개발 (루프백에서도 bearer 강제) QWEN_SERVER_TOKEN=$(openssl rand -hex 32) qwen serve --require-auth # 4. LAN에 노출 (비루프백은 토큰 필수) QWEN_SERVER_TOKEN=$(openssl rand -hex 32) \ qwen serve --hostname 0.0.0.0 --port 4170 # 5. 다중 세션 및 더 큰 리플레이 링 튜닝 qwen serve --max-sessions 0 --event-ring-size 32000 # 6. 다중 클라이언트 협업 + 엄격한 MCP 예산 QWEN_SERVER_TOKEN=secret \ qwen serve --require-auth \ --mcp-client-budget 10 \ --mcp-budget-mode enforce # 7. settings.json에 합의 정책 설정 후 시작 # settings.json: { "policy": { "permissionStrategy": "consensus", "consensusQuorum": 2 } } qwen serve # 8. 디버그 로깅 QWEN_SERVE_DEBUG=1 qwen serve # 9. F2 풀 비활성화 (세션별 MCP 클라이언트로 폴백) QWEN_SERVE_NO_MCP_POOL=1 qwen serve # 10. 브라우저 웹 UI 교차 출처 접근 허용 QWEN_SERVER_TOKEN=secret \ qwen serve --allow-origin 'http://localhost:3000' # 11. 프롬프트 기한 + SSE 유휴 타임아웃 qwen serve --prompt-deadline-ms 300000 --writer-idle-timeout-ms 600000 # 12. 마지막 세션 종료 후 ACP 자식을 웜 상태로 유지 qwen serve --channel-idle-timeout-ms 60000 # 13. HTTP 속도 제한 활성화 QWEN_SERVE_RATE_LIMIT=1 qwen serve

강화된 루프백 레시피(3)에서 /demobearerAuth 이후에 등록됨. 일반 브라우저 탐색에는 인증 헤더가 필요하므로 curl 또는 SDK 스크립트를 사용.

3. 전체 시작 플래그

CLI는 **packages/cli/src/commands/serve.ts**에 정의됨:

플래그타입기본값필수 조건효과
--port <n>number4170-TCP 포트; 0은 OS 할당 임시 포트.
--hostname <host>string127.0.0.1비루프백은 토큰 필수바인드 주소. 루프백 값: 127.0.0.1, localhost, ::1, [::1]. [::1] 대괄호는 자동 제거; host:port 입력은 --port를 사용하라는 안내와 함께 거부.
--token <s>stringenv / none비루프백 및 --require-authBearer 토큰; 한 번 trim됨. /proc/<pid>/cmdline에 나타나므로 QWEN_SERVER_TOKEN을 선호. 부트 stderr에서도 이에 대해 경고.
--max-sessions <n>number32-워크스페이스별 활성 세션 상한. 초과 시 503 반환. 0은 무제한. NaN / 음수 값은 throw.
--max-total-sessions <n>number다중 워크스페이스 시작/복원에 따라 파생-데몬 전체 활성 세션 상한. 생략 시 워크스페이스별 상한과 시작/복원 워크스페이스 수에서 유한 기본값이 한 번 파생되며, 동적 등록은 재계산하지 않음. 0은 무제한.
--memory-budget-mb <n>[1024, 1048576] 범위의 정수cgroup/호스트 메모리의 50%관찰 전용데몬 프로세스 트리의 총 메모리 예산, 해석된 사용 가능 메모리로 제한. limits.memory 아래에 보고되며 자식 크기를 결정하지 않음.
--memory-pressure-mode <mode>off | observeobserve관찰 전용두 모드 모두 runtime.memory.pressure를 보고; observedaemon_memory_pressure 이슈를 발생. 루트 프로세스만 해당.
--child-heap-mode <mode>off | observeobserve관찰 전용observe에서는 모델링된 파티션을 limits.memory.childHeap 아래에 보고; 아무것도 적용하거나 거부하지 않음. off에서는 해당 블록의 두 수치가 null.
--max-pending-prompts-per-session <n>number5-세션당 수락되었지만 대기/실행 중인 프롬프트 상한. 초과 시 503 반환. 0 / Infinity는 무제한. 음수 또는 비정수 값은 throw.
--workspace <dir>string / 반복 가능process.cwd()-시작 워크스페이스 런타임; 반복하여 추가 격리 런타임 등록. 첫 번째가 기본. 각 값은 절대 경로여야 하며, 존재해야 하며, 디렉토리여야 함. 부트는 canonicalizeWorkspace를 통해 모든 값을 표준화. POST /session에서 cwd가 불일치하면 400 workspace_mismatch 반환.
--max-connections <n>number256-리스너 수준 server.maxConnections. 0 / Infinity는 무제한. NaN / 음수 값은 fail-open 동작을 방지하기 위해 부트 실패.
--require-authbooleanfalse토큰 필수루프백 및 /health로 bearer 인증을 확장. 토큰 없으면 부트 거부.
--enable-session-shellbooleanfalse토큰 필수직접 POST /session/:id/shell 실행을 활성화. 호출자는 세션에 바인딩된 X-Qwen-Client-Id도 전송해야 함.
--event-ring-size <n>number8000-세션별 SSE 리플레이 링 깊이. 소프트 상한은 MAX_EVENT_RING_SIZE = 1_000_000; 범위 초과 값은 브리지 생성 시 throw.
--http-bridgebooleantrue-브리지 모드: 프로덕션은 하나의 기본 qwen --acp 자식을 예열하려고 하며 실패 시 첫 사용 시 재시도; 신뢰된 보조는 필요 시 하나를 시작하고, 신뢰할 수 없는 보조는 ACP를 시작할 수 없음. Stage 2 인-프로세스 모드는 아직 구현되지 않음; --no-http-bridge는 폴백하여 stderr에 출력.
--mcp-client-budget <n>numbernonemcp-budget-mode=enforce 시 필수워크스페이스 MCP 클라이언트 상한. 양의 정수여야 함.
--mcp-budget-mode <m>'enforce' | 'warn' | 'off'예산 설정 시 warn, 아니면 offenforce--mcp-client-budget 필수enforce는 거부, warn은 75%에서 경고만, off는 관찰 전용.
--allow-origin <pattern>반복 가능 stringnone-기본 Origin 거부를 대체하는 CORS 허용 목록. *는 토큰 필수.
--allow-private-auth-base-urlbooleanfalse-로컬호스트 / 프라이빗 네트워크 인증 제공자 baseUrl 설치를 허용. 신뢰된 로컬 개발에만 사용.
--prompt-deadline-ms <n>numbernone-서버 측 프롬프트 벽시계 제한(ms); 타임아웃 시 프롬프트 중단.
--writer-idle-timeout-ms <n>numbernone-SSE 연결별 유휴 타임아웃(ms).
--channel-idle-timeout-ms <n>number0-마지막 세션 종료 후 ACP 자식을 유지. 0은 즉시 회수.
--initialize-timeout-ms <n>number10000-ACP 자식 요청 타임아웃, 초기화 핸드셰이크 포함(ms).
--session-reap-interval-ms <n>number60000-세션 리퍼 스캔 간격. 0은 비활성화.
--session-idle-timeout-ms <n>number1800000-연결 끊긴 세션 유휴 타임아웃. 0은 비활성화.
--rate-limit / --no-rate-limitbooleanenv / off-티어별 HTTP 속도 제한을 활성화 또는 비활성화.
--rate-limit-prompt <n>number10--rate-limit윈도우당 프롬프트 요청 수.
--rate-limit-mutation <n>number30--rate-limit윈도우당 mutation 요청 수.
--rate-limit-read <n>number120--rate-limit윈도우당 읽기 요청 수.
--rate-limit-window-ms <n>number60000--rate-limit속도 제한 윈도우 길이; >= 1000이어야 함.

4. 환경 변수

환경 변수해당 플래그 / 효과
QWEN_SERVER_TOKEN--token과 동일; --token이 우선. 부트 시 한 번 trim되어 cat token.txt의 개행문자를 제거.
QWEN_SERVE_DEBUG1 / true / on / yes(대소문자 무관)가 상세 stderr 로그를 활성화.
QWEN_SERVE_NO_MCP_POOL1은 워크스페이스 MCP 풀을 완전히 비활성화하고 세션별 McpClientManager로 폴백. capabilities에서 mcp_workspace_pool / mcp_pool_restart 광고가 중단.
QWEN_SERVE_MCP_CLIENT_BUDGETACP 자식 내부 예산 입력. CLI가 childEnvOverrides를 통해 --mcp-client-budget에서 생성; 부모 프로세스 환경 폴백이 아님.
QWEN_SERVE_MCP_BUDGET_MODEACP 자식 내부 예산 모드. CLI가 childEnvOverrides를 통해 --mcp-budget-mode에서 생성; 부모 프로세스 환경 폴백이 아님.
QWEN_SERVE_PROMPT_DEADLINE_MS--prompt-deadline-ms의 환경 폴백.
QWEN_SERVE_WRITER_IDLE_TIMEOUT_MS--writer-idle-timeout-ms의 환경 폴백.
QWEN_SERVE_MCP_POOL_TRANSPORTSACP 자식이 읽음. 쉼표로 구분된 풀 전송 허용 목록; 기본값은 stdio,websocket.
QWEN_SERVE_MCP_POOL_DRAIN_MSACP 자식이 읽음. 풀 항목 유휴 드레인 지연; 기본값은 30000, 1000..600000 ms로 제한.
QWEN_SERVE_RATE_LIMIT1 / true가 속도 제한을 활성화; CLI 플래그가 우선.
QWEN_SERVE_RATE_LIMIT_PROMPT--rate-limit-prompt의 환경 폴백.
QWEN_SERVE_RATE_LIMIT_MUTATION--rate-limit-mutation의 환경 폴백.
QWEN_SERVE_RATE_LIMIT_READ--rate-limit-read의 환경 폴백.
QWEN_SERVE_RATE_LIMIT_WINDOW_MS--rate-limit-window-ms의 환경 폴백.

핸들별 환경 오버라이드는 의도적: 동일한 프로세스에서 실행되는 두 데몬이 process.env에서 레이스하지 않음. defaultSpawnChannelFactory는 spawn 시 환경을 스냅샷.

5. settings.json도 읽힘

부트는 loadSettings(boundWorkspace)를 한 번 호출:

타입동작
policy.permissionStrategy'first-responder' | 'designated' | 'consensus' | 'local-only'BridgeOptions.permissionPolicy를 설정. 부트는 validatePolicyConfig로 검증; 알 수 없는 값은 자동으로 폴백하지 않고 InvalidPolicyConfigError를 throw.
policy.consensusQuorum양의 정수consensus 정책의 N. 기본값은 floor(M/2)+1. 비consensus 정책 아래에서 설정되면 무시되며 부트가 stderr 경고를 출력.
context.fileNamestringgetCurrentGeminiMdFilename()을 오버라이드하고 POST /workspace/init이 쓰는 파일을 제어.
tools.disabledstring[]다음 ACP 자식 spawn에 영향을 주기 전 normalizeDisabledToolList()를 통해 정규화(trim, 빈 항목 제거, 중복 제거).
tools.approvalModestring기본 세션 승인 모드.
telemetryobjectOTel 설정: enabled, otlpEndpoint, otlpProtocol, 시그널별 엔드포인트 등. 17-configuration.md 참조.

설정 I/O 실패(잘못된 JSON 등)는 기본값으로 폴백. InvalidPolicyConfigError는 예외: 정책 설정 오류는 부트를 명시적으로 실패시킴.

6. 부트 거부 시나리오 (명시적 실패)

run-qwen-serve.ts는 다음 상황에서 폴백 대신 의도적으로 throw:

시나리오오류 접두사
토큰 없는 비루프백 바인드Refusing to bind ... without a bearer token
토큰 없는 --require-authRefusing to start with --require-auth set but no bearer token
--workspace가 존재하지 않거나, 디렉토리가 아니거나, 절대 경로가 아님Invalid --workspace ...
--workspace stat 권한 거부Invalid --workspace ...: permission denied
--mcp-client-budget이 양의 정수가 아님Must be a positive integer
예산 없는 --mcp-budget-mode=enforcerequires a positive mcpClientBudget
--hostnamelocalhost:4170 형식으로 작성됨looks like a "host:port" combination. Use --port
--hostname [::1]:8080Invalid --hostname ... brackets indi... [truncated]
--max-connectionsNaN 또는 음수Must be >= 0
--event-ring-size > 1_000_000브리지 생성 시 throw
토큰 없는 --allow-origin '*'Refusing to start with --allow-origin '*' but no bearer token configured
--prompt-deadline-ms / --writer-idle-timeout-ms가 양의 정수가 아님Must be a positive integer
--initialize-timeout-ms가 양의 정수가 아니거나 2^31-1을 초과Must be a positive integer / Exceeds maximum JS timer delay
알 수 없는 policy.permissionStrategy 또는 양수가 아닌 policy.consensusQuorumInvalidPolicyConfigError

7. Curl 검증 체크리스트

# 1. 활성 상태 curl http://127.0.0.1:4170/health # -> {"status":"ok"} # 1.1 Deep 헬스 curl -s 'http://127.0.0.1:4170/health?deep=1' | jq # 2. Capabilities curl -s http://127.0.0.1:4170/capabilities | jq # 3. Preflight 준비 상태 curl -s http://127.0.0.1:4170/workspace/preflight | jq # 4. 환경 스냅샷 (비밀은 존재 여부만 보고) curl -s http://127.0.0.1:4170/workspace/env | jq # 5. MCP 풀 / 예산 스냅샷 curl -s http://127.0.0.1:4170/workspace/mcp | jq # 6. 세션 생성 curl -s -X POST http://127.0.0.1:4170/session \ -H 'Content-Type: application/json' \ -H 'X-Qwen-Client-Id: curl-debug' \ -d '{}' | jq # 7. SSE 테일 (<sid> 교체) curl -N \ -H 'Accept: text/event-stream' \ -H 'X-Qwen-Client-Id: curl-debug' \ -H 'Last-Event-ID: 0' \ 'http://127.0.0.1:4170/session/<sid>/events' # 8. 데모 페이지 open http://127.0.0.1:4170/demo

Bearer 인증이 활성화되면 모든 요청에 -H "Authorization: Bearer $QWEN_SERVER_TOKEN"을 추가.

8. 데모 페이지를 사용할 수 있는가?

예. packages/cli/src/serve/demo.tsgetDemoHtml(port)로 구현되며, 외부 의존성 없는 자체 포함 HTML.

실행 모드/demo 등록 위치직접 브라우저 탐색
--require-auth 없는 루프백routes/health-demo.ts, createServeApp()bearerAuth 이전에 마운트토큰 없이 작동
--require-auth 있는 루프백routes/health-demo.ts, createServeApp()bearerAuth 이후에 마운트일반 브라우저에서는 사용 어려움; curl 또는 SDK 사용
비루프백 바인드routes/health-demo.ts, createServeApp()bearerAuth 이후에 마운트위와 동일

CSP는 default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'이며 X-Frame-Options: DENY도 포함. 페이지는 'self'(데몬)만 fetch할 수 있으며 외부 스크립트나 스타일을 로드할 수 없음.

9. qwen serve부터 리스닝 서버까지의 호출 체인

qwen serve | v (process) packages/cli/index.ts main() | v gemini.tsx main() - parseArguments() | v (yargs assembly) config/config.ts import { serveCommand } ... config/config.ts .command(serveCommand) config/config.ts await yargsInstance.parse() | v (handler) commands/serve.ts handler(argv) - 부트 사전 검사 commands/serve.ts const { runQwenServe } = await import('../serve/index.js') # 지연 로드 commands/serve.ts await runQwenServe({...}) | v serve/run-qwen-serve.ts runQwenServe(opts, deps) | |- 토큰 trim | |- hostname 불일치 폴백 | |- auth 사전 검사 | |- 워크스페이스 검증 + 표준화 | |- MCP 예산 검증 + childEnvOverrides | |- loadSettings + validatePolicyConfig | |- PermissionAuditRing + publisher | |- resolveBridgeFsFactory | `- createHttpAcpBridge({...}) | v serve/run-qwen-serve.ts const app = createServeApp(opts, () => actualPort, {...}) | v serve/server.ts createServeApp() - Express 앱 빌드 (**리스닝하지 않음**) | |- 미들웨어 체인 (Host 허용 목록 / CORS / bearerAuth / mutation gate / 속도 제한) | |- 라우트 마운팅 (health / demo / capabilities / workspace / session / SSE / ACP HTTP) | `- return app | v serve/run-qwen-serve.ts server = app.listen(port, hostname, cb) | |- server.maxConnections = cap | |- actualPort = server.address().port | |- "qwen serve listening on ..." 출력 | |- SIGINT / SIGTERM 등록 (onSignal) | `- resolve(handle: RunHandle) | v commands/serve.ts await blockForever() // 시그널까지 무한 차단

핵심 사실:

  • createServeApp은 빌드만 수행; 리스닝하지 않음. 미들웨어와 라우트가 마운트된 express() 인스턴스를 반환. 호출자가 app.listen()을 소유. server.test.ts는 약 25개 케이스에서 이 방식으로 팩토리를 사용하므로, 팩토리는 의도적으로 수명 주기를 소유하지 않음.
  • () => actualPort는 지연 클로저. actualPortapp.listen 콜백에서 할당됨. hostAllowlist 미들웨어는 필요 시 이를 읽으므로 임시 포트(--port 0)도 Host 헤더를 올바르게 게이트.
  • await blockForever()는 의도적. yargs.parse()가 resolve되면 CLI 최상위 레벨이 대화형 TUI 진입점(gemini.tsx)으로 전달됨. SIGINT / SIGTERM은 runQwenServeonSignal 경로를 통해 종료.

10. HTTP 라우트 파일 분할

주요 어셈블리는 server.tscreateServeApp()에서 이루어지며, 미들웨어를 연결하고 집중화된 라우트 모듈을 마운트:

라우트파일마운팅 진입점
/health, /demopackages/cli/src/serve/routes/health-demo.tshealthDemoRoutes.register()
/daemon/statuspackages/cli/src/serve/routes/daemon-status.tsregisterDaemonStatusRoutes()
/capabilities, 워크스페이스 초기화/도구/MCP 변경 라우트, ACP HTTP 브리지packages/cli/src/serve/server.tscreateServeApp() 내부에서 직접 등록
워크스페이스 상태, env, preflight, MCP/도구/제공자/skill 요약packages/cli/src/serve/routes/workspace-status.tsregisterWorkspaceStatusRoutes(), registerWorkspaceDiagnosticStatusRoutes()
워크스페이스 확장 및 확장 작업packages/cli/src/serve/routes/workspace-extensions.tsregisterWorkspaceExtensionRoutes()
/workspace/memory (GET/POST)packages/cli/src/serve/workspace-memory.tsmountWorkspaceMemoryRoutes()
모든 /workspace/agents CRUD 라우트packages/cli/src/serve/workspace-agents.tsmountWorkspaceAgentsRoutes()
GET /file, /file/bytes, /list, /glob, /statpackages/cli/src/serve/routes/workspace-file-read.tsregisterWorkspaceFileReadRoutes()
POST /file/write, /file/editpackages/cli/src/serve/routes/workspace-file-write.tsregisterWorkspaceFileWriteRoutes()
워크스페이스 설정, trust, settings, 권한, 음성 라우트packages/cli/src/serve/routes/workspace-*.tsregisterWorkspaceSetupGithubRoutes(), registerWorkspaceTrustRoutes()
워크스페이스 인증 제공자 및 device-flow 라우트packages/cli/src/serve/routes/workspace-auth.tsregisterWorkspaceAuthRoutes()
세션 수명 주기, 프롬프트, 메타데이터, 언어, shell, recap, rewind, branch, 목록 라우트packages/cli/src/serve/routes/session.tsregisterSessionRoutes()
GET /session/:id/events SSE 스트림packages/cli/src/serve/routes/sse-events.tsregisterSseEventsRoutes()
권한 응답 라우트packages/cli/src/serve/routes/permission.tsregisterPermissionRoutes()

전체 라우트 및 와이어 프로토콜 참조는 ../qwen-serve-protocol.md를 참조. 아키텍처는 01-architecture.md를 참조.

11. Graceful vs 하드 종료

  • 첫 SIGINT / SIGTERM -> runQwenServe onSignal -> 2단계 graceful shutdown:
    1. bridge.shutdown(): 각 채널에 KILL_HARD_DEADLINE_MS(10초), 이후 channel.kill().
    2. server.close(): 진행 중 요청 드레인, SHUTDOWN_FORCE_CLOSE_MS(5초) 후 closeAllConnections(), 이어 2초 추가 기한 적용.
  • 종료 중 두 번째 SIGINT / SIGTERM -> bridge.killAllSync()가 모든 ACP 자식을 동기적으로 SIGKILL하고 process.exit(1)을 호출하여 고아 프로세스를 방지.

runQwenServe가 반환하는 RunHandle.close()는 임베더와 테스트를 위한 프로그래밍적 종료.

12. 임베딩 호출 (CLI 우회)

import { runQwenServe } from '@qwen-code/qwen-code/serve'; const handle = await runQwenServe({ port: 0, // 임시 포트 hostname: '127.0.0.1', mode: 'http-bridge', maxSessions: 20, workspace: '/abs/path/to/repo', }); console.log(`Daemon at ${handle.url}`); // ... handle.bridge를 직접 호출하거나 handle.server에 접근 await handle.close(); // 프로그래밍적 종료

또는 Express 앱을 직접 가져와 직접 리스닝:

import { createServeApp } from '@qwen-code/qwen-code/serve'; const app = createServeApp( { port: 0, hostname: '127.0.0.1', mode: 'http-bridge', maxSessions: 20, }, () => 0, { /* deps: bridge, fsFactory, ... */ }, ); const server = app.listen(0, '127.0.0.1', () => { console.log('listening on', server.address()); });

참고: createServeApp을 직접 호출할 때 기본 fsFactory.trusted = false. 에이전트 측 ACP writeTextFileuntrusted_workspace로 거부되며 stderr 경고가 한 번 출력됨. 명시적 trust와 함께 deps.fsFactory를 주입하거나, deps.bridge를 주입하거나, trust-gate 기본 동작을 수용.

13. 디버깅 레시피

19-observability.md의 디버깅 섹션을 참조. 일반적인 명령어:

# 데몬이 살아 있는가? curl http://127.0.0.1:4170/health # 어떤 capabilities가 광고되는가? curl -s http://127.0.0.1:4170/capabilities | jq # 데몬-호스트 준비 상태 curl -s http://127.0.0.1:4170/workspace/preflight | jq # 라이브 SSE 테일 curl -N -H 'Accept: text/event-stream' \ -H 'Last-Event-ID: 0' \ 'http://127.0.0.1:4170/session/<sid>/events' # 상세 로그 QWEN_SERVE_DEBUG=1 qwen serve

참고 문헌

  • CLI 진입: packages/cli/src/commands/serve.ts
  • 부트스트랩: packages/cli/src/serve/run-qwen-serve.ts
  • Express 팩토리: packages/cli/src/serve/server.ts
  • 미들웨어: packages/cli/src/serve/auth.ts
  • 브리지 팩토리: packages/acp-bridge/src/bridge.ts
  • 데모 페이지 HTML: packages/cli/src/serve/demo.ts
  • 사용자 문서: ../../users/qwen-serve.md
  • 와이어 프로토콜: ../qwen-serve-protocol.md
Last updated on