- ホーム
- Documentation
- セッション
- `/handoff` 生成パイプライン
`/handoff` 生成パイプライン
本ドキュメントでは、コーディングエージェントが現時点で /handoff を実装する方法について説明します。トリガーパス、生成プロンプト、補完キャプチャ、セッション切り替え、コンテキスト再注入を対象とします。
対象:
- インタラクティブな
/handoffコマンドディスパッチ AgentSession.handoff()のライフサイクルと状態遷移- ハンドオフ出力がアシスタント出力からキャプチャされる方法
- 旧セッションと新セッションがハンドオフデータを永続化する際の違い
- 成功・キャンセル・失敗時のUI動作
対象外:
- 汎用ツリーナビゲーション/ブランチ内部
- ハンドオフ以外のセッションコマンド(
/new、/fork、/resume)
実装ファイル
Section titled “実装ファイル”../src/modes/controllers/input-controller.ts../src/modes/controllers/command-controller.ts../src/session/agent-session.ts../src/session/session-manager.ts../src/extensibility/slash-commands.ts
トリガーパス
Section titled “トリガーパス”/handoffはビルトインのスラッシュコマンドメタデータ(slash-commands.ts)にオプションのインラインヒント[focus instructions]付きで宣言されます。- インタラクティブな入力処理(
InputController)において、/handoffまたは/handoff ...に一致する送信テキストが通常のプロンプト送信前にインターセプトされます。 - エディターがクリアされ、
handleHandoffCommand(customInstructions?)が呼び出されます。 CommandController.handleHandoffCommandは現在のエントリーを使用してプリフライトガードを実行します:type === "message"のエントリー数をカウントします。< 2の場合、Nothing to hand off (no messages yet)と警告して返ります。
同じ最小コンテンツガードが AgentSession.handoff() 内にも存在し、違反時はスローされます。これによりUIとセッション両レイヤーで安全性が二重化されます。
エンドツーエンドのライフサイクル
Section titled “エンドツーエンドのライフサイクル”1) ハンドオフ生成の開始
Section titled “1) ハンドオフ生成の開始”AgentSession.handoff(customInstructions?):
- 現在のブランチエントリーを読み込む(
sessionManager.getBranch()) - 最小メッセージ数を検証する(
>= 2) #handoffAbortControllerを作成する- 構造化されたハンドオフドキュメント(
Goal、Constraints & Preferences、Progress、Key Decisions、Critical Context、Next Steps)を要求する固定のインラインプロンプトを構築する - カスタム指示が指定されている場合は
Additional focus: ...を追加する
プロンプトは以下の方法で送信されます:
await this.prompt(handoffPrompt, { expandPromptTemplates: false });expandPromptTemplates: false により、この内部指示ペイロードに対するスラッシュ/プロンプトテンプレート展開が防止されます。
2) 補完のキャプチャ
Section titled “2) 補完のキャプチャ”プロンプト送信前に、handoff() がセッションイベントをサブスクライブして agent_end を待機します。
agent_end 時に、エージェントの状態から最新の assistant メッセージを逆順でスキャンし、type === "text" のすべての content ブロックを \n で連結することでハンドオフテキストを抽出します。
抽出に関する重要な前提条件:
- テキストブロックのみが使用され、非テキストコンテンツは無視されます。
- 最新のアシスタントメッセージがハンドオフ生成に対応していることを前提としています。
- マークダウンセクションのパースやフォーマット適合性の検証は行いません。
- アシスタント出力にテキストブロックがない場合、ハンドオフは欠損として扱われます。
3) キャンセルチェック
Section titled “3) キャンセルチェック”以下のいずれかの条件が成立した場合、handoff() は undefined を返します:
- キャプチャされたハンドオフテキストがない、または
#handoffAbortController.signal.abortedが true である
finally 内で常に #handoffAbortController がクリアされます。
4) 新セッションの作成
Section titled “4) 新セッションの作成”テキストがキャプチャされ、かつ中断されていない場合:
- 現在のセッションライターをフラッシュする(
sessionManager.flush()) - 新しいセッションを開始する(
sessionManager.newSession()) - インメモリのエージェント状態をリセットする(
agent.reset()) agent.sessionIdを新しいセッションIDに再バインドする- キューに入ったコンテキスト配列をクリアする(
#steeringMessages、#followUpMessages、#pendingNextTurnMessages) - Todoリマインダーカウンターをリセットする
newSession() は新しいヘッダーと空のエントリーリスト(リーフを null にリセット)を作成します。ハンドオフパスでは parentSession は渡されません。
5) ハンドオフコンテキストの注入
Section titled “5) ハンドオフコンテキストの注入”生成されたハンドオフドキュメントはラップされ、新しいセッションに custom_message エントリーとして追加されます:
<handoff-context>...handoff text...</handoff-context>
The above is a handoff document from a previous session. Use this context to continue the work seamlessly.挿入呼び出し:
this.sessionManager.appendCustomMessageEntry("handoff", handoffContent, true);セマンティクス:
customType:"handoff"display:true(TUIリビルドで表示される)- エントリータイプ:
custom_message(LLMコンテキストに参加する)
6) アクティブなエージェントコンテキストの再構築
Section titled “6) アクティブなエージェントコンテキストの再構築”注入後:
sessionManager.buildSessionContext()が現在のリーフのメッセージリストを解決するagent.replaceMessages(sessionContext.messages)により注入されたハンドオフメッセージがアクティブコンテキストになる- メソッドが
{ document: handoffText }を返す
この時点で、新しいセッションのアクティブなLLMコンテキストには、旧セッションのトランスクリプトではなく、注入されたハンドオフメッセージが含まれます。
永続化モデル: 旧セッションと新セッション
Section titled “永続化モデル: 旧セッションと新セッション”旧セッション
Section titled “旧セッション”生成中は通常のメッセージ永続化が有効なままです。アシスタントのハンドオフレスポンスは message_end 時に通常の message エントリーとして永続化されます。
結果: 元のセッションには、履歴トランスクリプトの一部として生成されたハンドオフが表示されます。
新セッション
Section titled “新セッション”セッションリセット後、ハンドオフは customType: "handoff" の custom_message として永続化されます。
buildSessionContext() はこのエントリーを createCustomMessage(...) 経由でランタイムのカスタム/ユーザーコンテキストメッセージに変換するため、新セッションの以降のプロンプトに含まれます。
コントローラー/UI動作
Section titled “コントローラー/UI動作”CommandController.handleHandoffCommand の動作:
await session.handoff(customInstructions)を呼び出す- 結果が
undefinedの場合:showError("Handoff cancelled") - 成功時:
rebuildChatFromMessages()(注入されたハンドオフを含む新しいセッションコンテキストを読み込む)- ステータスラインとエディタートップボーダーを無効化する
- Todoをリロードする
- 成功チャットラインを追加する:
New session started with handoff context
- 例外発生時:
- メッセージが
"Handoff cancelled"またはエラー名がAbortErrorの場合:showError("Handoff cancelled") - それ以外:
showError("Handoff failed: <message>")
- メッセージが
- 最後にレンダリングを要求する
キャンセルセマンティクス(現在の動作)
Section titled “キャンセルセマンティクス(現在の動作)”セッションレベルのキャンセルプリミティブ
Section titled “セッションレベルのキャンセルプリミティブ”AgentSession が公開するもの:
abortHandoff()→#handoffAbortControllerを中断するisGeneratingHandoff→ コントローラーが存在する間は true
この中断パスが使用された場合、ハンドオフサブスクライバーは Error("Handoff cancelled") で拒否され、コマンドコントローラーがキャンセルUIにマッピングします。
インタラクティブな /handoff パスの制限
Section titled “インタラクティブな /handoff パスの制限”現在のインタラクティブコントローラーの配線では、/handoff は abortHandoff() を呼び出す専用のEscapeハンドラーをインストールしません(コンパクション/ブランチサマリーパスが一時的に editor.onEscape をオーバーライドするのとは異なります)。
実際の影響:
- セッションレベルのキャンセルサポートは存在しますが、
/handoffコマンドパスにはハンドオフ専用のキーバインドフックがありません。 - ユーザーの中断は広範なエージェント中断パスを通じて発生する可能性がありますが、それは
abortHandoff()が使用する明示的なキャンセルチャンネルとは異なります。
中断と失敗の違い
Section titled “中断と失敗の違い”現在のUI分類:
-
中断/キャンセル
abortHandoff()パスが"Handoff cancelled"をトリガーする、またはAbortErrorがスローされる- UIには
Handoff cancelledと表示される
-
失敗
handoff()/プロンプトパイプライン(モデル/API検証エラー、ランタイム例外など)からスローされるその他のエラー- UIには
Handoff failed: ...と表示される
追加の注意点: 生成が完了してもテキストが抽出されなかった場合、handoff() は undefined を返し、コントローラーは現在失敗ではなくキャンセルとして報告します。
短セッションおよび最小コンテンツのガードレール
Section titled “短セッションおよび最小コンテンツのガードレール”低シグナルなハンドオフを防ぐための2つのガード:
- UIレイヤー(
handleHandoffCommand):< 2のメッセージエントリーに対して警告し早期リターンする - セッションレイヤー(
handoff()): 同じ条件をエラーとしてスローする
これにより、空または空に近いハンドオフコンテキストで新しいセッションが作成されることを防ぎます。
状態遷移サマリー
Section titled “状態遷移サマリー”高レベルの状態フロー:
- インタラクティブなスラッシュコマンドがインターセプトされる
- プリフライトのメッセージ数ガード
#handoffAbortControllerが作成される(isGeneratingHandoff = true)- 内部ハンドオフプロンプトが送信される(通常のアシスタント生成としてチャットに表示される)
agent_end時に最新のアシスタントテキストが抽出される- 欠損または中断の場合 →
undefinedを返すかキャンセルエラーパスへ - 存在する場合:
- 旧セッションをフラッシュする
- 新しい空のセッションを作成する
- ランタイムキュー/カウンターをリセットする
custom_message(handoff)を追加する- アクティブなエージェントメッセージを再構築して置き換える
- コントローラーがチャットUIを再構築して成功を通知する
#handoffAbortControllerがクリアされる(isGeneratingHandoff = false)
既知の前提条件と制限事項
Section titled “既知の前提条件と制限事項”- ハンドオフ抽出はヒューリスティック(「最後のアシスタントテキストブロック」)であり、構造的な検証は行われません。
- 生成されたマークダウンが要求されたセクションフォーマットに従っているかどうかのハードチェックはありません。
- 抽出されたテキストが欠損している場合、コントローラーのUXではキャンセルとして報告されます。
/handoffのインタラクティブフローには現在、専用のEscape→abortHandoff()バインディングがありません。- このパスでは新セッションの系譜メタデータ(
parentSession)は設定されません。