AgentSession:コーディング Agent のオーケストレーション層
AgentSession は pi-coding-agent の中で一番重い層だ——直接 LLM を走らせるわけでもなく、直接 UI を描くわけでもなく、その中間に挟まる。上に対してはユーザー入力と UI イベントを受け取り、下に対しては pi-agent-core の Agent を駆動してループを走らせる。モデル登録、ツール定義、システムプロンプト構築、コンテキスト圧縮、失敗リトライ、メッセージキューイングは全部ここが管轄する。「汎用 Agent を状態付き・復帰可能・スキルとツール付きのコーディングアシスタントに包む」組み立て工場と捉えればいい。
責務
AgentSession は 4 つをやる:
- 組み立て:
AgentSessionConfigにAgentインスタンス、モデルレジストリ (registry)、ツール定義、拡張ランタイム、システムプロンプトビルダーを詰め、構築時にbeforeToolCall/afterToolCallフックとイベント購読を取り付ける。packages/coding-agent/src/core/agent-session.ts:313-330参照。 - 入力受付:
prompt(text)が主入り口。/スラッシュコマンド、ファイル型プロンプトテンプレートの展開、テキストからAgentMessage[]への変換を処理し、下層のAgentに渡す。packages/coding-agent/src/core/agent-session.ts:967-1000参照。 - イベント転送:
Agentのイベントストリームを購読し、AgentSessionEventに包み直して上層 UI に送る。packages/coding-agent/src/core/agent-session.ts:330-335参照。 - キューイングとリトライ:ストリーミング出力中にユーザーが打ち続けた時、
streamingBehaviorに基づいてsteer(差し込み) かfollowUp(キューイング) に振り分ける。単に捨てたりブロックしたりはしない。packages/coding-agent/src/core/agent-session.ts:1181-1296参照。
設計動機
なぜ UI に直接 Agent.prompt を呼ばせないのか? コーディングアシスタントは「汎用 Agent が管轄しない」横断ロジックを山ほど必要とするからだ:スラッシュコマンド (/model、/settings)、プロンプトテンプレート展開、モデル/API key 検証、コンテキスト超過時の自動圧縮、ツール呼び出し前後の認可とテレメトリ。これを Agent に押し込むと汎用ループが重くなるし、UI 層に置くと print モード、rpc モード、拡張で重複実装する羽目になる。AgentSession を唯一の入り口として切り出し、3 つの実行モードが同じオーケストレーションを共用し、UI はイベントをレンダリングするだけにする。
主要ファイル
packages/coding-agent/src/core/agent-session.ts:121-149—AgentSessionEventとAgentSessionConfig型。イベントの形と組み立てインターフェースを定義。packages/coding-agent/src/core/agent-session.ts:244-253—class AgentSession宣言と内部状態フィールド。packages/coding-agent/src/core/agent-session.ts:313-335— コンストラクタ:beforeToolCall/afterToolCallフックを取り付け、Agentイベントを購読。packages/coding-agent/src/core/agent-session.ts:379-435— ツール呼び出し前後のフック実装。認可とイベント再送を行う。packages/coding-agent/src/core/agent-session.ts:967-1109—prompt主フロー:コマンドディスパッチ、テンプレート展開、this.agent.prompt(messages)。packages/coding-agent/src/core/agent-session.ts:1181-1296—steer/followUpのキューイング意味。packages/coding-agent/src/core/agent-session.ts:1317-1389—sendUserMessage(プログラム入り口) とabort。packages/coding-agent/src/core/agent-session.ts:713-743—subscribe。UI はこれでイベントを受け取る。
コンストラクタでフックと購読を取り付けるのが、全体のオーケストレーションの起点になる:
// packages/coding-agent/src/core/agent-session.ts:313-335
constructor(config: AgentSessionConfig) {
// ... config を格納、状態初期化 ...
this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
// ... 略 ...
}prompt はユーザーのテキストを AgentMessage[] にしてから下層の Agent に渡す:
// packages/coding-agent/src/core/agent-session.ts:1107-1112
try {
await this.agent.prompt(messages);
} catch (error) {
// ... 失敗処理 ...
}データフロー
ユーザーが TUI エディタで Enter を押す → InteractiveMode.defaultEditor.onSubmit → session.prompt(text):
ストリーミング中にユーザーが打ち続ける時、prompt は無理に押し込まず振り分ける:
境界と失敗
- スラッシュコマンド衝突:拡張が登録したコマンドはストリーミング中でも即時実行できる (自身で LLM インタラクションを管轄) 。キューには積まれない。コマンドがキュー不可の場合は直接 throw する。
packages/coding-agent/src/core/agent-session.ts:1255-1259参照。 - モデル/key 欠落:非ストリームパスはモデルと API key を検証し、欠落時は空リクエストを送らず throw する。
- 圧縮の中止:
abortは主ループ、圧縮コントローラ、分岐要約コントローラを同時にキャンセルする。packages/coding-agent/src/core/agent-session.ts:1744-1752参照。 - リトライ:
Agent.emit()は同期的に_handleAgentEventを呼ぶ一方、prompt()はwaitForRetry()する。失敗後はリトライロジックが次ターンを再送するか決める。
小ねた
AgentSession はコーディングアシスタントの「組み立て工場 + イベントバス」だ:Agent を組み立て、イベントを転送し、差し込みをキューイングし、ツールフックを管轄する。上を見れば InteractiveMode/runPrintMode/runRpcMode の 3 つの UI モード、下を見れば pi-agent-core の汎用ループ。組み立ての詳細は createAgentSession 装配、下層のループは 二重 while ループ 参照。