Capabilities & Protocol Versioning
Overview
GET /capabilities is the daemon preflight endpoint. Every SDK client should read it before calling any other route so it can learn which protocol version the daemon speaks, which feature tags are enabled, and which workspace runtimes the daemon accepts. The contract:
- There is one protocol version:
v1.SERVE_PROTOCOL_VERSION = 'v1'andSUPPORTED_SERVE_PROTOCOL_VERSIONS = ['v1']. v1 is additive internally; breaking frame-shape changes are reserved for v2. - Each tag has a
sinceversion. Future v2 daemons can advertise both v1 and v2 tags. - Some tags are conditional. Tags listed in
CONDITIONAL_SERVE_FEATURESare advertised only when the corresponding deployment toggle is enabled. Tag presence means the behavior exists. - Capability tag = behavior contract. Adding new behavior under an existing tag can silently break clients that preflighted the old tag. New behavior needs a new tag.
The complete registry lives in packages/cli/src/serve/capabilities.ts.
Responsibilities
- Declare every feature the daemon may advertise.
- Filter advertised features by protocol version and deployment toggles.
- Expose
getRegisteredServeFeatures()(all keys, unfiltered),getAdvertisedServeFeatures(version, toggles)(filtered), andgetServeProtocolVersions()(envelope{ current, supported }). - Preserve the invariant “tag present means behavior present”.
server.test.tsincludes a test that every conditional tag advertises when its toggle is on; adding a conditional tag without a predicate fails that test.
Architecture
Capability envelope
/capabilities returns:
{
v: 1, // CAPABILITIES_SCHEMA_VERSION
mode: 'http-bridge',
features: ServeFeature[],
workspaceCwd: string,
workspaces?: Array<{ id: string, cwd: string, primary: boolean, trusted: boolean }>,
protocol?: { current: 'v1', supported: ['v1'] },
policy?: { permission: PermissionPolicy },
}workspaceCwd is the canonical primary workspace path (see 02-serve-runtime.md). Current daemons use workspaces[] as the registered runtime catalog; multi_workspace_sessions indicates that more than one runtime is active. policy.permission is the active mediator policy.
ServeCapabilityDescriptor
interface ServeCapabilityDescriptor {
since: ServeProtocolVersion; // current = 'v1'
modes?: readonly string[]; // lists operation modes when a feature has modes
}Four v1 tags use modes:
mcp_guardrails: { since: 'v1', modes: ['warn', 'enforce'] }- clients should preflight'enforce'before relying on refusal behavior.permission_mediation: { since: 'v1', modes: ['first-responder', 'designated', 'consensus', 'local-only'] }- this is the build-time supported set; the active policy is inpolicy.permission.workspace_voice_transcription: { since: 'v1', modes: ['batch'] }- the transcription path the daemon offers.voice_transcribe: { since: 'v1', modes: ['streaming', 'batch'] }- the two transcription paths available on the/voice/streamWebSocket.
Conditional tags
export const CONDITIONAL_SERVE_FEATURES: ReadonlyMap<
ServeFeature,
(toggles: AdvertiseFeatureToggles) => boolean
> = new Map([
['require_auth', (t) => t.requireAuth === true],
['mcp_workspace_pool', (t) => t.mcpPoolActive === true],
['mcp_pool_restart', (t) => t.mcpPoolActive === true],
['allow_origin', (t) => t.allowOriginActive === true],
[
'prompt_absolute_deadline',
(t) => typeof t.promptDeadlineMs === 'number' && t.promptDeadlineMs > 0,
],
[
'writer_idle_timeout',
(t) =>
typeof t.writerIdleTimeoutMs === 'number' && t.writerIdleTimeoutMs > 0,
],
['workspace_settings', (t) => t.persistSettingAvailable === true],
['user_language_sync', (t) => t.persistSettingAvailable === true],
['workspace_voice', (t) => t.persistSettingAvailable === true],
[
'workspace_voice_transcription',
(t) => t.voiceTranscriptionAvailable === true,
],
['session_shell_command', (t) => t.sessionShellCommandEnabled === true],
[
'multi_workspace_session_rewind',
(t) => t.multiWorkspaceSessionsEnabled === true,
],
[
'multi_workspace_session_shell',
(t) =>
t.multiWorkspaceSessionsEnabled === true &&
t.sessionShellCommandEnabled === true,
],
['rate_limit', (t) => t.rateLimit === true],
['workspace_reload', (t) => t.reloadAvailable === true],
['voice_transcribe', (t) => t.voiceWsAvailable !== false],
]);The Map stores membership and predicate together. Adding a new conditional tag requires two coordinated changes:
- Register the tag and its
sinceversion inSERVE_CAPABILITY_REGISTRY. - Add its predicate to
CONDITIONAL_SERVE_FEATURES.
Baseline tags are not present in the Map and are advertised unconditionally. This is intentionally represented by absence rather than by a separate Set.
v1 tags grouped by domain
Foundation: health, daemon_status, capabilities.
Sessions: session_create, session_id_override, session_scope_override, session_load, session_resume, unstable_session_resume, session_list, session_info, session_prompt, session_mid_turn_message_mutation, session_cancel, session_events, session_set_model, session_close, session_metadata, session_archive, session_storage_conflict_repair, session_export, session_transcript, session_context, session_context_usage, session_supported_commands, session_tasks, session_monitor_tool_correlation, session_stats, session_lsp, session_resources, session_status, session_approval_mode_control, session_recap, session_btw, session_shell_command (conditional), session_language, user_language_sync (conditional), session_rewind, session_hooks, session_branch.
Streaming: slow_client_warning, typed_event_schema.
Identity and heartbeat: client_identity, client_heartbeat.
Permissions: session_permission_vote, permission_vote, permission_mediation (modes: ['first-responder', 'designated', 'consensus', 'local-only']).
Workspace read-only snapshots: workspace_mcp, workspace_skills, workspace_providers, workspace_acp_status, workspace_env, workspace_preflight, workspace_hooks, workspace_extensions.
Extension management: extension_management_v2 adds the global /extensions/* catalog/mutation/operation contract and the workspace activation projection. It is separate from the published workspace_extensions compatibility surface and from workspace_qualified_rest_core.
Local Extension installation: extension_local_path_install allows an absolute path on the daemon host in the existing source field of both Extension install routes. It is separate from extension_management_v2 because the primary-workspace compatibility route also supports it, and clients must not send local paths to older daemons.
V2 Extension batch activation: extension_batch_activation_v2 adds queued global default-activation and selected-workspace override batches to extension_management_v2. Clients must pre-flight it independently because older V2 daemons expose only singular activation routes.
Explicit Extension activation refresh: extension_activation_explicit_refresh means singular and batch activation operations finish at the durable policy commit without directly refreshing active sessions. Clients that need immediate application should wait for activation success and submit the workspace runtime-refresh operation for each workspace whose sessions must apply the change immediately; a global default batch changes the default activation each workspace inherits unless that workspace holds an exact override for the name (or matches a legacy path rule), and it has no single refresh that covers every runtime, so workspaces the caller does not refresh converge only on the next 30-second generation-reconciler pass. Daemons without this tag already include refresh in the activation operation, so callers must not submit a compatibility refresh there.
Workspace-qualified session reads: workspace_persisted_transcript, workspace_session_export, workspace_archived_session_export, workspace_session_live_state. The active and archived export tags are independent from each other and from session_export and workspace_qualified_rest_core, so clients must pre-flight the exact storage state they intend to export. Persisted transcript paging permits an untrusted secondary under its bounded read policy; both full export paths remain trusted-only. workspace_session_live_state is likewise independent from workspace_qualified_rest_core and is trusted-only: it serves the selected runtime’s memory-only live-session snapshot and catalog version and does not extend the untrusted-secondary persisted read policy to live bridge state.
Workspace mutation (Wave 4+): workspace_memory, workspace_agents, workspace_agent_generate, workspace_acp_preheat, workspace_tool_toggle, workspace_skill_settings_toggle, workspace_skill_settings_batch_toggle, workspace_settings (conditional), workspace_permissions, workspace_init, workspace_github_setup, workspace_trust, workspace_mcp_restart, workspace_mcp_manage, workspace_file_read, workspace_file_bytes, workspace_file_read_cursor, workspace_file_write, workspace_file_upload, workspace_reload (conditional). The two Skill settings tags replace the retired catalog-validated workspace_skill_toggle and workspace_skill_batch_toggle tags.
MCP guardrails: mcp_guardrails (modes: ['warn', 'enforce']), mcp_guardrail_events, mcp_server_runtime_mutation, mcp_workspace_pool (conditional), mcp_pool_restart (conditional).
Prompt control: prompt_absolute_deadline (conditional), writer_idle_timeout (conditional), non_blocking_prompt.
Auth: auth_provider_install, auth_device_flow, require_auth (conditional), allow_origin (conditional).
Voice: workspace_voice (conditional), workspace_voice_transcription (conditional, modes: ['batch']), voice_transcribe (conditional, modes: ['streaming', 'batch']).
Rate limiting: rate_limit (conditional).
Multi-workspace session routing: multi_workspace_sessions (conditional),
multi_workspace_session_rewind (conditional), and
multi_workspace_session_shell (conditional). A client may use rewind for
a primary session with session_rewind; a secondary live session additionally
requires multi_workspace_session_rewind. Shell uses the equivalent
session_shell_command plus multi_workspace_session_shell pairing for a
secondary session. ACP-native clients continue to use the _qwen.methods
returned by initialize; no ACP rewind vendor method is advertised.
Bold tags have modes or are conditional.
Flow
Daemon side: assemble envelope
Client side: feature preflight
State and lifecycle
CAPABILITIES_SCHEMA_VERSIONis the wire envelope shape version, currently1. Bump it only for an envelope break.SERVE_PROTOCOL_VERSION = 'v1'is the protocol-feature version. Adding features inside v1 is additive; old clients do not see new behavior unless they preflight the new tag. Corrected behavior may replace a capability inside v1: the replacement tag supersedes the old tag, the old tag stops being advertised, and clients must preflight the replacement. Removing a feature without a replacement is a v2 break.EVENT_SCHEMA_VERSION = 1is the SSE framevfield (see09-event-schema.md). It is an independent version axis; bumping event schema does not imply bumping protocol version, and vice versa.session_resumeis the stable daemon capability forPOST /session/:id/resume.unstable_session_resumeremains advertised as a deprecated alias because the underlying ACP method is still namedconnection.unstable_resumeSession; new clients should feature-detectsession_resume.
Dependencies
- Read by
packages/cli/src/serve/server.tswhen building/capabilitiesresponses. - Toggle input comes from
runQwenServe/createServeApp, including authentication, MCP, origin, prompt, settings, shell, rate-limit, reload, and live workspace-runtime-count state. - The active
permissionpolicy in the envelope comes fromBridgeOptions.permissionPolicy, which itself readssettings.jsonpolicy.permissionStrategy.
Configuration
| Source | Knob | Effect on capabilities |
|---|---|---|
| CLI flag | --require-auth | Advertises require_auth. |
| Env | QWEN_SERVE_NO_MCP_POOL=1 | Stops advertising mcp_workspace_pool and mcp_pool_restart; MCP events no longer stamp scope: 'workspace'. |
| CLI flag | --mcp-client-budget=N, --mcp-budget-mode={off,warn,enforce} | Does not change the tag set (mcp_guardrails is always advertised), but changes per-server reservation and refusal behavior. |
| CLI flag / env | --rate-limit / QWEN_SERVE_RATE_LIMIT=1 | Advertises rate_limit. |
| Embedded option | persistSettingAvailable | Advertises workspace_settings, user_language_sync, and workspace_voice. |
| Embedded option | voiceTranscriptionAvailable | Advertises workspace_voice_transcription. |
| CLI flag / embedded option | --enable-session-shell / sessionShellCommandEnabled | Advertises session_shell_command. |
| Runtime state | More than one registered workspace runtime | Advertises multi_workspace_sessions and multi_workspace_session_rewind; also advertises multi_workspace_session_shell when session shell is effectively enabled. |
| Embedded option | reloadAvailable | Advertises workspace_reload. |
| Embedded option | voiceWsAvailable | Advertises voice_transcribe. |
settings.json | policy.permissionStrategy | Sets envelope policy.permission. |
Caveats and known limits
--require-authhides preflight. With--require-auth, all routes, including/capabilities, require bearer auth. An unauthenticated client cannot preflightcaps.features.require_auth; the 401 response body is the discovery surface. Therequire_authtag is an authenticated confirmation for hardened-deployment audit UIs.- Tag presence means behavior exists. If a future contributor adds behavior under an existing tag without bumping
since, clients that preflighted the old tag can silently receive new behavior. The convention is: new behavior gets a new tag. unstable_*tags can change shape between versions without a protocol bump. Pin an SDK version when depending on them.- The route catalog lives in
../qwen-serve-protocol.md; this page intentionally does not duplicate it.
References
packages/cli/src/serve/capabilities.tspackages/cli/src/serve/types.ts(ServeOptions,CapabilitiesEnvelope)packages/cli/src/serve/server.ts(envelope assembly)packages/acp-bridge/src/eventBus.ts(EVENT_SCHEMA_VERSION)- Wire reference:
../qwen-serve-protocol.md - Auth and deployment guardrails:
12-auth-security.md