Skip to Content
デベロッパーガイドDaemonTyped Daemon Event Schema v1

Typed Daemon Event Schema v1

概要

GET /session/:id/events でデーモンから出力されるすべての SSE フレームは、{ id, v, type, data, originatorClientId?, _meta? } の形状を持ちます。v: 1 は現在の EVENT_SCHEMA_VERSION です。typepackages/sdk-typescript/src/daemon/events.ts にあるクローズドでバージョン固定された DAEMON_KNOWN_EVENT_TYPE_VALUES セットから取得されます。現在のセットには 47 種類の既知のイベントタイプがあります。エンベロープの _meta フィールドは、packages/cli/src/serve/routes/sse-events.tsformatSseFrame() によって SSE 書き込み境界でスタンプされます。Envelope-level metadata を参照してください。

SDK は asKnownDaemonEvent(evt) を公開しています。これは既知のイベントタイプに対しては判別共用体である KnownDaemonEvent を、その他のタイプに対しては undefined を返します。したがって、SDK を利用する側は、新しいデーモンがイベントタイプを追加した際に、SDK を同時にアップグレードすることなく前方互換性を処理できます。セッションリデューサーはこれらを unrecognizedKnownEventCount として記録します。

ワイヤーフォーマットは ../qwen-serve-protocol.md にあります。このページは各イベントのペイロード契約です。

責務

  • イベント語彙(DAEMON_KNOWN_EVENT_TYPE_VALUES)の単一の信頼できる情報源(Single Source of Truth)を提供します。
  • 各イベントタイプに対する型付きエンベロープ(DaemonEventEnvelope<TType, TData>)を提供します。
  • イベントストリームを SDK のビュー状態に射影する純粋なリデューサー(reduceDaemonSessionEvent, reduceDaemonAuthEvent)を提供します。
  • 情報提供シグナルとして typed_event_schema ケイパビリティタグをブロードキャストします。タグが存在しない場合でも、asKnownDaemonEventunknown にフォールバックします。

Event vocabulary (47 known types)

ドメイン別にグループ化されています。

Core session

TypeDirectionTriggerKey payload fields
session_updateS->C任意の ACP sessionUpdate 通知: エージェントテキスト、思考、ツール呼び出し、またはプランsessionUpdate: string, content?: ...(不透明な ACP 形状)
session_metadata_updatedS->CPATCH /session/:id/metadatasessionId, displayName?
session_diedS->C 終端channel.exitedsessionId, reason, exitCode? | null, signalCode? | null
session_closedS->C 終端DELETE /session/:id またはプログラムによるクローズsessionId, reason: 'client_close' | string, closedBy?
session_snapshotS->C 合成SSE アタッチ / リプレイ後のスナップショットフレームsessionId, currentModelId: string | null, currentApprovalMode: string | null

Subscriber-level synthetic frames

TypeTriggerNotes
client_evictedサブスクライバーごとの EventBus キューのオーバーフロー。id なしreason: 'queue_overflow' | 'queue_bytes_overflow' | string, droppedAfter?: number, queueSize?: number, maxQueued?: number, queuedBytes?: number, maxQueuedBytes?: number, eventBytes?: number; 現在のサブスクライバーに対してのみ終端となり、セッション自体は存続します。
slow_client_warningライブフレームのバックログまたはライブシリアライズバイトのバックログが 75% 以上。強制プッシュされ、id を持ちませんqueueSize, maxQueued, lastEventId, queuedBytes?, maxQueuedBytes?, threshold?: 'frames' | 'bytes' | 'frames_and_bytes'; フレームとバイトの両方の測定値が 37.5% 未満に下がった後に再設定されます。
stream_errorSubscriberLimitExceededError または別のルートストリームエラーerror: string; サブスクリプションに対して終端となります。
state_resync_requiredsubscribe({lastEventId}) がデーモンリングに [lastEventId+1, earliestInRing-1] が保持されなくなったこと、またはクライアントカーソルが前のバスエポックのものであることを検出します。残りのリプレイフレームのに強制プッシュされ、id を持ちませんreason: 'ring_evicted' | 'epoch_reset' | string, lastDeliveredId: number, earliestAvailableId: number。これは終端ではなくリカバリーシグナルです。SSE ストリームはオープンされたままとなり、リプレイとライブフレームが継続します。SDK リデューサーは awaitingResync = true を設定し、呼び出し元が loadSession でリセットするまでデルタをスキップします。
replay_completeLast-Event-ID リプレイループが終了した後に発行される ID なしセンチネル。クリーンなリプレイとリングエビクトのパスの両方に対して、data.replayedCount === 0 の場合でも発行されます。id なしreplayedCount: number; コンシューマーがタイムアウトなしでキャッチアップ UI を確実に削除できるようにします。

Permissions (F3 + base)

TypeDirectionTriggerKey payload fields
permission_requestS->Cエージェントが requestPermission を呼び出すrequestId, sessionId, toolCall, options[]; エンベロープはプロンプトのオリジネーターからの originatorClientId をスタンプします。
permission_resolvedS->Cメディエーターが決定したrequestId, outcome(ACP PermissionOutcome
permission_already_resolvedS->Cリクエストがすでに決定された後に投票が到着したrequestId, sessionId, outcome
permission_partial_voteS->Cconsensus ポリシーが最終的ではない投票を記録するrequestId, sessionId, votesReceived, votesNeeded (>= 1), quorum, optionTallies: Record<string, number>, originatorClientId?
permission_forbiddenS->Cポリシーが投票を拒否するrequestId, sessionId, clientId?, reason: 'designated_mismatch' | 'remote_not_allowed', originatorClientId?; 匿名の投票者は clientId を省略します。

Models

TypeDirectionPayload
model_switchedS->CsessionId, modelId
model_switch_failedS->CsessionId, requestedModelId, error: string

MCP guardrails (PR 14b + F2)

TypeDirectionPayload
mcp_budget_warningS->CliveCount, reservedCount, budget, thresholdRatio: 0.75, mode: 'warn' | 'enforce', scope?: 'workspace' | 'session'
mcp_child_refused_batchS->CrefusedServers: [{ name, transport, reason: 'budget_exhausted' }], budget, liveCount, reservedCount, mode: 'enforce', scope?: 'workspace' | 'session'
mcp_server_restartedS->CF2 マルチエントリープール再起動用の serverName, durationMs, entryIndex?
mcp_server_restart_refusedS->CserverName, reason: 'budget_would_exceed' | 'in_flight' | 'disabled' | 'restart_failed', entryIndex?, details?。4番目の値である restart_failed は、プールモードのマルチエントリー再起動における根本的なハード障害を伝えます。MCP_RESTART_REFUSED_REASONS は未知の理由を拒否します。古い SDK リデューサーは、parseDaemonEventundefined を返すため、追加された新しい理由の値をサイレントに破棄します。新しい理由を、それを知る SDK とともにリリースしてください。

ミューテーション制御 (Wave 4 PR 16+17)

TypeDirectionPayload
memory_changedS->Cファイルメモリ: scope: 'workspace' | 'global', filePath, mode, bytesWritten; 管理メモリ: scope: 'managed', source, taskId, touchedScopes
agent_changedS->Cchange: 'created' | 'updated' | 'deleted', name, level: 'project' | 'user'
approval_mode_changedS->CsessionId, previous, next, persisted: boolean
tool_toggledS->CtoolName, enabled; 次回の ACP 子プロセスの生成に影響し、すでに実行中のセッションは変更しません。
settings_changedS->Cワークスペース設定の書き込みが完了しました。ペイロードはオープンです。コンシューマーは read-after-write でリフレッシュする必要があります。
settings_reloadedS->Cデーモンのワークスペースサービスが設定を再読み込みしました。ペイロードはオープンです。
trust_change_requestedS->CworkspaceCwd, desiredState: 'trusted' | 'untrusted', reason?
workspace_initializedS->Cpath, action: 'created' | 'overwrote' | 'noop', originatorClientId?
github_setup_completedS->CreleaseTag, readmeUrl, secretsUrl?, workflows: [{path, status, sizeBytes?, error?}], gitignore: {path, status, added?, error?}

memory_changed はセッションレスの管理メモリタスクもカバーします。これらのペイロードでは、scope"managed"source"workspace_memory_remember""workspace_memory_forget"、または "workspace_memory_dream" のいずれか、taskId はキューイングされたタスク ID、touchedScopes は変更された管理メモリのスコープ("user""project")の一覧です。remember/forget/dream タスクが管理メモリを変更せずに完了した場合、イベントは発行されません。

認証デバイスフロー (PR 21)

これらのイベントはセッション単位ではなくワークスペース単位でキー付けされます。セッションリデューサーはこれらを no-op として扱い、reduceDaemonAuthEvent がこれらをワークスペースレベルのステートに投影します。

TypeDirectionPayload
auth_device_flow_startedS->CdeviceFlowId, providerId, expiresAt
auth_device_flow_throttledS->CdeviceFlowId, intervalMs
auth_device_flow_authorizedS->CdeviceFlowId, providerId, expiresAt?, accountAlias?
auth_device_flow_failedS->CdeviceFlowId, errorKind, hint?
auth_device_flow_cancelledS->CdeviceFlowId

MCP ランタイムミューテーション

TypeDirectionTriggerKey payload fields
mcp_server_addedS->CPOST /workspace/mcp/servers 経由でランタイムにサーバーが追加されたname, transport, replaced, shadowedSettings, toolCount, originatorClientId
mcp_server_removedS->Cランタイムでサーバーが削除されたname, wasShadowingSettings, originatorClientId

拡張機能のライフサイクル

TypeDirectionTriggerKey payload fields
extensions_changedS->Cバックグラウンドでの拡張機能のインストール/リフレッシュ作業の完了、またはステータスの変更refreshed, failed, status?: 'installed' | 'enabled' | 'disabled' | 'updated' | 'uninstalled' | 'failed', source?, name?, version?, error?

ターン中のメッセージ挿入

TypeDirectionTriggerKey payload fields
mid_turn_message_injectedS->CWeb シェルまたはリモートクライアントが POST /session/:id/inject 経由で実行中のターンにメッセージを挿入しましたsessionId, messages: string[], originatorClientId?; コンシューマーは重複排除を行う前に、originatorClientId を自身の ID と比較しなければなりません

ターンのライフサイクル / アシスタントプッシュ

TypeDirectionTriggerKey payload fields
prompt_cancelledS->C明示的な cancelSession ルートまたは発信元の SSE 切断によってプロンプトがキャンセルされましたエンベロープには、キャンセルを実行したクライアントの originatorClientId がスタンプされます。これは「キャンセルが要求された」ことを意味し、「キャンセルが確定した」わけではありません。ピアサブスクライバーはプロンプトが終了したことを認識します。
turn_completeS->Cターンが正常に完了しましたsessionId, stopReason, promptId?promptId は非ブロッキングプロンプトレスポンス(202)にリンクします。SDK はこれを通じて SSE イベントを発信元のプロンプトにマッチングします。
turn_errorS->Cターンが失敗しましたsessionId, message, code?, promptId?; 上記と同じ promptId の相関メカニズム。
session_rewoundS->CPOST /session/:id/rewind が成功しましたsessionId, promptId, targetTurnIndex, filesChanged[], filesFailed[], originatorClientId?
session_branchedS->CPOST /session/:id/branch によって既存のセッションからブランチが作成されましたsourceSessionId, newSessionId, displayName, originatorClientId?
followup_suggestionS->CACP 子が end_turn 後にゴーストテキストのフォローアップ提案を生成し、セッションごとの SSE 経由で転送されましたsessionId, suggestion, promptId; ワイヤー上では getFilterReason()===null である提案のみが伝送されます。クライアントはこれらを入力プレースホルダーのゴーストテキストとしてレンダリングし、次の sendPrompt で無効化します。
user_shell_commandS->Cユーザーが POST /session/:id/shell 経由でシェルコマンドを開始しました。同じセッション内の他のサブスクライバーにファンアウトされますsessionId, command, shellId, originatorClientId?。型付きの DaemonXxxData インターフェースはまだ存在しないため、asKnownDaemonEventundefined を返し、UI ノーマライザーはそれをアドホックに解析します。
user_shell_resultS->C上記のシェルコマンドの結果sessionId, shellId, exitCode, output, aborteduser_shell_command と同じく、アドホックな解析に関する注意事項が適用されます。

アーキテクチャ

ConcernSourceNotes
EVENT_SCHEMA_VERSION = 1packages/acp-bridge/src/eventBus.ts全フレームで送信されます。
DAEMON_KNOWN_EVENT_TYPE_VALUESpackages/sdk-typescript/src/daemon/events.ts47 種類のタイプを持つ閉じたリスト。
DaemonEventEnvelope<TType, TData>events.ts汎用エンベロープ。
DaemonKnownEventTypeevents.tstypeof DAEMON_KNOWN_EVENT_TYPE_VALUES[number]
Per-event payload typesevents.tsほとんどのイベントタイプには DaemonXxxData インターフェースがあります。user_shell_* は現在、UI ノーマライザーによってアドホックに解析されます。
asKnownDaemonEvent(evt)events.tsKnownDaemonEvent | undefined を返します。
reduceDaemonSessionEvent(state, evt)events.tsDaemonSessionViewState に投影します。
reduceDaemonAuthEvent(state, evt)events.tsDaemonAuthState に投影します。
isWorkspaceScopedBudgetEvent(evt)events.tsF2 scope: 'workspace' を検出します。

DaemonSessionViewState

reduceDaemonSessionEvent はこのビューステートを埋めます。CLI TUI アダプター、DaemonChannelBridge、および VS Code IDE がこれを消費します。主要なフィールド:

  • alive: boolean - ターミナルフレーム(session_diedsession_closedclient_evictedstream_error)の後に false になります。
  • currentModelId?: string - model_switched から。
  • displayName?: string - session_metadata_updated から。
  • pendingPermissions: Record<string, DaemonPermissionRequestData> - requestId をキーとするオープンなリクエスト。permission_resolved / permission_already_resolved によってクリアされます。
  • lastSessionUpdate?: DaemonSessionUpdateData - 最新の session_update
  • lastModelSwitchFailure?: DaemonModelSwitchFailedData - model_switch_failed から。
  • terminalEvent? - 生のターミナルイベント。
  • streamError?: DaemonStreamErrorData - 最新の stream_error ペイロード。
  • unrecognizedKnownEventCount, lastUnrecognizedKnownEvent? - イベントは asKnownDaemonEvent によって認識されましたが、リデューサーにはまだ専用のステートがありません。
  • droppedPermissionRequestCount, lastDroppedPermissionRequestId? - 不正な形式のパーミッションリクエストが pending マップに入れませんでした。
  • unmatchedPermissionResolutionCount, lastUnmatchedPermissionResolutionId? - パーミッションの解決に対応する pending リクエストがありませんでした。
  • slowClientWarningCount, lastSlowClientWarning? - slow_client_warning から。
  • mcpBudgetWarningCount, lastMcpBudgetWarning? - mcp_budget_warning から。
  • mcpChildRefusedBatchCount, lastMcpChildRefusedBatch? - mcp_child_refused_batch から。
  • lastWorkspaceMutation?, lastWorkspaceMutationType? - memory_changed / agent_changed から。
  • approvalMode?, approvalModeChangedCount, lastApprovalModeChange? - approval_mode_changed から。
  • toolToggleCount, lastToolToggle? - tool_toggled から。
  • workspaceInitCount, lastWorkspaceInit? - workspace_initialized から。
  • mcpRestartCount, lastMcpRestart? - mcp_server_restarted から。
  • mcpRestartRefusedCount, lastMcpRestartRefused? - mcp_server_restart_refused から。
  • settings_changed / settings_reloaded - asKnownDaemonEvent によって認識されます。セッションリデューサーは専用のビューステートフィールドを保持せず、UI は通常これらをリフレッシュシグナルとして扱います。
  • permissionVoteProgress: Record<string, DaemonPermissionPartialVoteData> - コンセンサス投票の進捗。
  • forbiddenVotes: DaemonPermissionForbiddenData[], forbiddenVoteCount - ポリシーによって拒否された投票レコード。最大 32 件まで。
  • awaitingResync: boolean - state_resync_required によってセットされます。コンシューマーがビューステートをリセットするとクリアされます。
  • resyncRequiredCount, lastResyncRequired? - 再同期のオブザーバビリティ。
  • lastFollowupSuggestion?: DaemonFollowupSuggestionData - デーモンによってプッシュされた最新のフォローアップ提案。
  • lastTurnComplete?: DaemonTurnCompleteData - 最新の正常に完了したターン。
  • lastTurnError?: DaemonTurnErrorData - 最新のターンエラー。
  • rewindCount, lastRewind?, lastBranch? - 最新の rewind / branch イベント。

DaemonAuthState

providerId ごとに 1 つのエントリを持ち、auth_device_flow_* によって駆動されます。各フローは { deviceFlowId, status, providerId, expiresAt?, lastThrottleIntervalMs?, lastError? } を公開します。

Flow

Producer side

Consumer side (SDK)

エンベロープレベルのメタデータ

各イベントの data ペイロードに加えて、デーモンは 2 つのエンベロープレベルのフィールドを付与します。

_meta.serverTimestamp - デーモンクロック

packages/acp-bridge/src/eventBus.ts 内の EventBus.publish() は、イベントがバスに入る際に _meta.serverTimestamp を付与します。BridgeEvent 型は _meta?: Record<string, unknown> を含むため、内部のデーモンコンシューマーはバスで公開されるすべてのイベントで _meta参照できますpackages/cli/src/serve/routes/sse-events.ts 内の formatSseFrame() は、EventBus.publish をバイパスする合成フレーム(例: stream_error)に対してのみフォールバックタイムスタンプを提供します。

{ "id": 47, "v": 1, "type": "session_update", "data": { ... }, "_meta": { "serverTimestamp": 1716287345123 } }

このマージは、入力イベントから既存の _meta キーを保持します ({...input._meta, serverTimestamp: Date.now()})。プロデューサーは 追加のエンベロープレベルの _meta キーを付与できます。EventBus.publish は タイムスタンプで上書きするのではなく、それらをマージします。

重要な理由: 相対時間を表示したり、トランスクリプトブロックをソートしたりするマルチクライアント UI では、各ブラウザ/タブ/スマートフォンのローカルクロックではなく、サーバー時間を使用する必要があります。サーバーでのタイムスタンプ付与により、クライアント間での順序が一貫して保たれます。

SDK からのアクセス: event._meta?.serverTimestamp を優先して使用してください。互換性パスでは event.serverTimestampevent.data._meta.serverTimestamp も参照される場合があります。ACP ペイロードの data._meta とデーモンエンベロープの _meta を混同しないでください。

originatorClientId

登録済みの X-Qwen-Client-Id を含むリクエストによってトリガーされたイベントは、このフィールドを付与する場合があります。08-session-lifecycle.md を参照してください。

ツール呼び出しの _meta (provenance / serverId)

これはエンベロープの _meta とは別物です。ACP の session/update ペイロードは event.data._meta に独自の _meta を持つことができます。ToolCallEmitter (packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.ts) は、emitStartemitResult、および emitError で 2 つのフィールドを付与します。

フィールドタイプ解決ルール
provenance'builtin' | 'mcp' | 'subagent'ToolCallEmitter.resolveToolProvenance: subagentMeta があれば subagent が優先されます。ツール名が mcp__<server>__<tool> に一致する場合は mcp に、それ以外は builtin にマッピングされます。
serverIdprovenance === 'mcp' の場合のみ stringmcp__<serverId>__<tool> からヒューリスティックに抽出されます。

既存の _meta.toolName 表示名は保持されます。UI はこれらのフィールドを使用して、ツール名を再パースすることなく、builtin / MCP サーバー / subagent のバッジをレンダリングします。

SDK リデューサーの動作

packages/sdk-typescript/src/daemon/events.ts 内の reduceDaemonSessionEvent(state, evt) は、ストリームを DaemonSessionViewState に射影します。resync 関連のフィールドは次のとおりです。

  • awaitingResync: boolean - state_resync_required によって設定されます。呼び出し元は、通常 POST /session/:id/load がビュー状態をリセットした後にこれをクリアします。
  • resyncRequiredCount: number - 観測性カウンター。
  • lastResyncRequired?: DaemonStateResyncRequiredData - 最新のペイロード。

awaitingResync = true の間、リデューサーはデルタの適用をスキップし、閉じたセットである RESYNC_PASSTHROUGH_TYPES のみ許可します。

パススルータイプresync 中にも適用される理由
state_resync_requiredまれな 2 回目の resync で lastResyncRequired / resyncRequiredCount を更新する必要があります。
session_died終端ストリームシグナルは resync 中も可視化される必要があります。
session_closed上記と同じ。
client_evicted上記と同じ。
stream_error上記と同じ。
session_snapshotフル状態の権威あるフレーム。resync 中に適用しても安全です。

lastEventId は resync 中も advanceLastEventId(base) を通じて単調に増加し続けます。呼び出し元がリセットして awaitingResync をクリアした後、後続のデルタは正しいカーソルに整列します。

reduceDaemonAuthEvent は、概念的に device-flow イベントを {deviceFlowId, status, providerId, expiresAt?, lastThrottleIntervalMs?, lastError?} という形状のワークスペースレベルの認証状態エントリに射影します。 コード上、リデューサーは DaemonDeviceFlowReducerStatestatuserrorKindhintintervalMslastSeenEventIdauthorizedExpiresAt、および accountAlias を保存します。 デーモンイベントのペイロード自体は、上記のイベントごとの形状のままです。

状態と前方互換性

  • DAEMON_KNOWN_EVENT_TYPE_VALUES に追加することで、既知のイベントタイプを追加します。古い SDK はフォールバックパスを通じて認識されないイベントタイプに対して undefined を返し、unrecognizedKnownEventCount をインクリメントします。新しい SDK は識別共用体(discriminated union)に依存します。
  • ペイロードはオープン({ [key: string]: unknown })であるため、既存のペイロードにオプションフィールドを追加しても安全です。
  • 既存のペイロードの形状を変更することは破壊的変更となるため、EVENT_SCHEMA_VERSION を上げる必要があり、さらに caps.features.typed_event_schema_v2 などの互換性のあるケーパビリティタグを公開する必要があります。
  • id はセッションごとに単調増加します。サブスクライバーレベルの合成フレーム(client_evictedslow_client_warningstream_errorstate_resync_requiredreplay_completesession_snapshot)には意図的に id が含まれていないため、他のサブスクライバーはギャップを目にしません。
  • originatorClientIddata ではなくエンベロープに存在します。F3 partial-vote / forbidden ペイロードは、mergeOriginator を通じてこれも data にマージするため、ビュー状態のコンシューマーはエンベロープを保持する必要がありません。

依存関係

設定

  • 常に公開されるもの: typed_event_schemamcp_guardrail_events、および permission_mediation(サポートされているポリシーモード付き)。
  • スキーマ自体を直接制御する環境変数やフラグはありません。QWEN_SERVE_NO_MCP_POOL=1 は、MCP イベントの scope'workspace' から不在(absent)または 'session' に変更します。

注意事項と既知の制限

  • 6 つの合成フレームタイプには意図的に id がありません。SDK コードはすべてのイベントに id があることを前提としてはいけません。
  • permission_partial_voteconsensus 下でのみ出現します。permission_forbiddendesignatedconsensus、および local-only 下に出現しますが、first-responder 下には出現しません。
  • mcp_child_refused_batchmode: 'enforce' でのみ出現します。warn モードでは決して拒否しません。
  • auth_device_flow_* イベントはセッションキーではありません。DaemonSessionClient を介して消費する場合は、セッションリデューサーではなく reduceDaemonAuthEvent を使用してください。

参考文献

  • packages/sdk-typescript/src/daemon/events.ts
  • packages/acp-bridge/src/eventBus.ts (EVENT_SCHEMA_VERSION)
  • packages/cli/src/serve/capabilities.ts (typed_event_schemamcp_guardrail_eventspermission_mediation)
  • Wire 参照: ../qwen-serve-protocol.md
Last updated on