通常のリクエスト処理では、入力は実行開始前に決まります。Agentの実行は複数のModel RequestやTool Callにまたがるため、その間にもユーザー、プラグイン、スケジュラー、他のAgentから新しい情報が届き続ける可能性があります。
そのため、Agent Runtimeにおける入力操作を「もう1件メッセージを送る」と表すだけでは不十分です。
Runtimeは少なくとも、次の4点を決める必要があります。
答えが異なれば、挙動もまったく変わります。
すべての新規メッセージが即座に新しいモデル実行チェーンを作ると、同じSessionが複数のLoopから同時に変更されます。すべてのメッセージが現在のタスクの完全な終了を待つと、ユーザーは進行中の道筋を適宜調整できません。Contextの変化ごとにAgentを自動的に起動すると、必要のないModel Callが大量に発生しうります。
そのため、Input DeliveryはAgent Runtimeの独立した設計課題です。
PiのSteering / Follow-up QueueとDeepSeek HarnessのInboxは、抽象化の異なる2つの段階を示しています。
なぜ prompt() ですべての入力を扱えないのか
まず、最も直接的なAPIを見てみましょう。
Agentがidleなら、意味は明確です。Agent自身が主導する新しい処理を開始します。
問題は、Agentがすでにrunningの場合に発生します。
現在のチェーンが次のだと仮定します。
Model Request
↓
Tool Call
↓
Tool Execution
↓
Tool Result
↓
Next Model Request
この状態でprompt(newMessage)をもう一度呼び出すと、Runtimeには何をするか決める必要があります。
A. 创建第二条并行 Loop
B. 取消当前 Loop,再启动新 Loop
C. 把消息加入当前 Loop 的下一次模型请求
D. 等当前 Loop 停止后再处理
この4つの挙動はいずれも妥当ですが、同じ暗黙の意味として扱うことはできません。
PiはactiveRunがある状態でprompt()を再度呼び出すことを直接禁止し、呼び出し側がsteer()、followUp()、abort()のいずれかを明示的に選ぶことを求めています。[1]
この制限により、同時入力の問題は「Runtimeが推測する」から「呼び出し側が意図を明示する」に変わりました。
入力制御はまず実行境界に依存する
前の2つの記事では、重要な境界が2つ定義されています。
Step Boundary
一次模型决策及其 Tool 工作结束
Turn Boundary
当前连续工作到达停止点
Input Deliveryのセマンティクスは,实际上この2つの境界の上に成り立っています。
RuntimeがStepとTurnを安定して定義していなければ、次のような意味を正確に区別できません。
“尽快生效”到底是现在,还是下次模型调用前
“当前工作结束以后”到底在哪个时刻
そのため、SteerとFollow-upは異なる実行時点に対応しており、その分類はStepとTurnの境界に由来します。
Piは2つのQueueで2つの実行時点を直接表す
PiのAgentは現在、次の2つのQueueを保持しています。
どちらのQueueもone-at-a-timeとallの2つのdrain modeをサポートし、デフォルトでは一度に1件だけ消費します。[1]
公開APIは次の通りです。
2つのAPIの最も重要な違いは、入力が消費されるタイミングです。
Steering:直近の次のモデル判断に影響する
Pi RPCドキュメントは、Steeringの配信タイミングをかなり具体的に定義しています。Agentが作業中の場合、Steering MessageはまずQueueに入り、現在のAssistant Turnが生成したTool Callsの実行が完了した後、次のLLM Callの前に配信されます。[2]
実行順序は次のようになります。
ここには重要な点が2つあります。
第一に、Steeringは、すでにProviderに送信済みのModel Requestを変更しません。モデルが生成を開始すれば、そのRequestで使うContextは確定しています。
第二に、Steeringは現在のToolをデフォルトで中断することもありません。Tool Callは、先行するモデル判断から生じたEffectです。PiではTool Callを完了させ、その後で次のモデル判断に新しい入力を入れます。
したがって、Steeringの意味は、次のようにより正確に表現できます。
現在の進行中の作業を壊すことなく、新しい入力をできるだけ早く後続のモデル判断に反映させる。
ここがStep Boundaryの価値です。
Steeringを暗黙にCancelと同じ扱いにするべきではない理由
ユーザーが「この方向では続けない」と入力した場合、プロダクト側はToolを直ちに止めることを求めるかもしれません。一方、別の入力は条件を補うだけで、現在の操作をキャンセルする必要はありません。
たとえば:
“接下来不要修改测试文件”
前者なら、現在のToolの完了を待ったうえで、次のステップへ反映できます。
一方:
“停止执行”
こちらは通常、現在のアクティブな処理を直接キャンセルする必要があります。
すべてのSteeringが暗黙にCancelを起こすようにすると、追加情報だけでも現在のToolの実行が途中で壊れてしまいます。すべてのSteeringからCancelを一切許可しなければ、緊急停止を表現できません。
そこでPiは2つの制御を分けています。
steer
改变后续决策
abort
终止当前活动
メッセージ本文からユーザーの意図を推測するより、この分離のほうが安定しています。
Follow-up:現在の連続作業が停止点に達するのを待つ
Follow-upの作用時点は、Steeringより後になります。
Piは、現在のTool CallsとSteering Messagesがともに残っておらず、Agentの作業を終わろうとしているときにだけ、Follow-up Queueを消費します。[2]
Current Work
├── Model
├── Tool
├── Model
└── Final
↓
Stop Boundary
↓
Follow-up Queue
↓
Next Work
したがってFollow-upは、次のような操作に向いています。
当前工作保持完整
完成以后继续处理新的要求
PiのrunLoop()にある2層のループは、このセマンティクスをそのまま符号化しています。[3]
Inner Loop
Tool + Steering
Outer Loop
Follow-up
「すべてのメッセージを1つのFIFO Queueに入れる」方式より、一段だけ明確な実行関係が加わります。
2つのQueueだけで、簡単なDelivery Modelは構成できる
データ構造だけを見ると、Steering QueueとFollow-up Queueは、いずれも未処理メッセージのリストです。
重要なのは、それぞれが異なる消費地点に結び付いていることです。
steeringQueue
→ next decision boundary
followUpQueue
→ stop boundary
したがって、Piはすでに2つのDelivery Targetを表現しています。ターゲットは、異なるQueueとAPIに直接埋め込まれているだけです。
この設計の利点は、その単純さです。
呼び出し側は次のどちらかを選ぶだけです。
Runtime内部で別のフィールドを解釈する必要もなく、Queueそのものが意味を表します。
一方で、配信ポリシーが増えるにつれて、APIとQueueの数も増える可能性があります。
たとえば、システムが次も表現しようとしている場合です。
加入下一次 Context,但不要主动启动 Agent
只在某个 Tool 完成后可见
高优先级插队
只对某个子 Agent 生效
2つの固定Queueでは、制約が次第に厳しくなります。
DeepSeek Harnessは、この層の意味をさらにパラメータ化しています。
DeepSeek HarnessはInboxで入力の入口を統一する
DeepSeek Harnessが現在公開しているAgent APIには、次のものが含まれます。
ここで:
followup()、steer()、inject()は、異なるパラメータの組み合わせを簡潔に表すラッパーとして考えられます。[4]
すると、入力は次のように分解できます。
Payload
+
Target
+
Wakeup
↓
Inbox
这一步はPiの2つのQueueよりも1段抽象化のレベルが高いです。
PiではAPI名そのものが配信方法を表します。DSHでは、配信方法がデータに明示的に埋め込まれます。
targetは入力がどの実行境界に属するかを表す
target = next-stepは、入力が直近のStep Boundaryで後続のモデル判断に入ることを表します。
target = next-turnは、現在のTurnの終了を待ったのち、後続の処理として消費されることを表します。
したがってPiの2つの概念は、次のように対応します。
steer
≈ next-step
follow-up
≈ next-turn
ここでは「ほぼ等しい」とするほうが正確です。フレームワークによって、idle状態、wakeup、lifecycle hookの具体的なセマンティクスはまだ異なるためです。
共通している考え方は、入力がどの実行時点に属するかを明示する必要がある、ということです。
同じ入力でも、idleとrunningでは結果が異なる
Delivery Policyは、Agentの現在の状態も踏まえて解釈する必要があります。
まず、簡略化したマトリクスを示します。
最も重要なのは、targetが作用時点を表すのに対し、wakeupが起動権限を表すという点です。両者を組み合わせることで、Runtimeは現在の状態における実際の挙動を決められます。
APIがsend(message)しか提供していなければ、この違いを実装内部に隠すしかありません。idleなら自動起動、runningなら自動キューイングという挙動は便利に見えますが、呼び出し側からは、メッセージが現在の処理に入るのか、後続の処理を作るのかが分かりません。
明示的なPolicyはプロトコルを少し複雑にします。しかし、状態に関する暗黙の挙動を避けられるため、SDK、RPC、Web UI、プラグインが同じRuntimeを利用する場面では、通常は安定性のほうが重要です。
wakeupを独立した軸に해야 하는理由
targetだけでは不十分です。
Runtimeは、次のContextに影響する情報を受け取る chouientoday Spiridon。与此同时 may not want to immediately issue Model Request. その情報 때문에直ちにModel Requestを発行したくない場合もあります。
たとえば:
文件索引更新
环境变量变化
插件补充新的上下文
异步任务返回一段参考信息
これらの情報は次のモデル計算に反映できますが、Agentがidleなら、直ちに起動させる必要まであるとは限りません。
すべてのInbox Messageが自動的に実行を開始すると:
background state changed
↓
message appended
↓
agent wakes
↓
model request
「状態更新」と「処理の作成」が結びついてしまいます。
DeepSeek Harnessはwakeupを独立させることで、次のように表現できます。
next-step + wakeup=false
只影响未来 Context
next-turn + wakeup=true
形成需要执行的新工作
これにより、「モデルには何を見せるべきか」と「Agentは今すぐ走らせるべきか」を、別々の制御軸として扱えるようになります。
inject()の意味はここにある
inject(message)は、Agentを起動せずに、後続のContextへ情報を追加できるようにします。[4]
Agentがrunningなら、この入力は適切なStep Boundaryでclaimされます。AgentがidleならInboxに残り、実際に処理を開始できる入力が将来届くまで待ちます。
したがって、3種類の操作は次のように理解できます。
具体的な実装上の細部は各フレームワークの現行定義に従いますが、設計上は、DSHが入力を「チャットメッセージ」からRuntime Mailbox内の作業シグナルへと拡張しています。
Input、Wakeup、Cancelは別の3つの軸にする
PiとDSHを並べて見ると、より一般的な制御モデルが得られます。
外部入力には、少なくとも互いに独立した3つの問いがあります。
Content
模型或 Runtime 要接收什么信息
Delivery
这条信息在什么边界生效
Activation
这条信息是否触发 Agent 从 idle 进入 active
Cancelは4つ目の軸になります。
Interruption
是否终止当前已有工作
次のように表現できます。
実際に2つのインターフェースとして実装する必要はありません。重要なのは、sendMessage()というアクションにすべてのセマンティクスを埋め込まないことです。
Runtimeに自然言語からDeliveryを推測させない理由
製品側は、UIで通常のEnter、Steer、Follow-up、Stopをそれぞれ別のボタンにできますし、上位Agentに戦略を自動選択させることもできます。
ただし、下層のRuntimeが受け取るべきなのは、すでに確定した配信セマンティクスです。
Runtimeがメッセージ本文から推測するなら:
“顺便补充一下……”
→ follow-up ?
“先别这样做……”
→ steer ? abort ?
挙動は言語理解モデルに依存し、呼び出し側に安定した契約を提供するのが難しくなります。
より可靠な層分けは次の通りです。
UI / Orchestrator
决定用户意图和 Delivery Policy
↓
Agent Runtime
严格执行 Delivery Policy
これにより、Runtimeは決定的な挙動を保てます。
Queueのdrain modeも配信の一部
PiのPendingMessageQueueは、one-at-a-timeとallの2つのdrain modeをサポートしています。[1]
この詳細が示しているのは、「next-step」と決めても、バッチ処理の方針が残るということです。
Stepの実行中に3つのSteeringが連続して届いた場合を見てみましょう。
S1
S2
S3
allモードなら、次のModel Requestの前に、すべての入力を一度に追加できます。
Tool Result
+ S1
+ S2
+ S3
→ Model Request
one-at-a-timeなら1件だけ消費し、後続の入力は後の判断境界で引き続き投入されます。
どちらの方式でも、モデルが情報を取り込むリズムも、ユーザーが「連続して要件を追加する」と感じた体験も変わります。
そのため、Delivery Policyをさらに拡張できます。
これらのフィールドが必要かどうかは、製品の複雑さによります。中心にある抽象化は、配信タイミングの明示化です。
Inboxがメモリ上のQueueから観測可能な状態へ発展した理由
DeepSeek Harnessは現在、InboxをAgent-owned durable projectionとして説明しています。next-turnとnext-stepごとに順序付けられたpending-message listを保持し、append、replace、remove、clear、splice、claimなどの操作によって正規化されたmutation eventを形成します。[4]
これは、Inboxが実行時のスケジューリングだけを担当するわけではないことを意味します。
外部システムは次を観察できます。
当前有哪些输入正在等待
每条输入属于 next-step 还是 next-turn
哪些输入已经被 claim
哪些输入被删除或替换
UIは待機中の指示を表示でき、復旧ロジックも待機状態を再構築できます。
通常のメモリ上Queueの責務は、通常は次の2つだけです。
enqueue
→ dequeue
一方、DSHのInboxは、Agent-owned Mailbox Stateのほうにかなり近いです。
また、DSHのEvent / Projectionという考え方を、実行制御の層へ拡張したものでもあります。
次の単元でSession Event Logを扱う際にも、このdurable projectionをさらに掘り下げます。
入力ソースが増えると、Mailbox抽象はChat Queueより安定する
初期のAgentプロダクトの入力は、通常User Messageだけでした。Harnessの機能が増えるにつれ、入力ソースも広がります。
User
Plugin
Scheduler
SubAgent
External Event
Approval System
これらの入力をすべて、可視のチャットメッセージとして表示する必要はありません。
たとえば、スケジュラーはバックグラウンド処理を起動するためにAgentを起こすだけでよく、プラグインは環境情報を次のContextへ注入するだけでよく、SubAgentが返した結果は親Agentの現在のStepに入る必要があるかもしれません。
Runtimeの主要インターフェースが「Chat Message」だけなら、上層はさまざまな制御情報をUser Messageに偽装することが多くなり、会話履歴が制御プロトコルとUI表示を同時に担うことになります。
Mailbox / AgentInputモデルなら、Payloadをさらに一般化できます。
Delivery Policyは、それぞれがいつ消費されるかを担当します。
これにより入力経路は「チャットキュー」からRuntimeの統一された外部イベント入口へと進化します。それでも、どの入力が最終的にモデルから見えるMessageへ変換されるかは、Context Builderが決めます。
長時間ToolはSteering境界の代償を示す
Steeringは現在のToolの完了を待ってから作用するため、意味は明快ですが、明白な代償もあります。
あるToolの実行時間が長ければ:
ユーザーは、Steeringが十分にタイムリーではないと感じます。
ここでは、Steeringのセマンティクスを単純に変えて、実行中のToolへ強制的に割り込ませるべきではありません。より妥当な方法は、通常は次のとおりです。
Tool 支持 AbortSignal
+
产品允许用户显式 Stop
+
必要时 Stop 后再提交新的 Steer / Prompt
または、対話可能なToolに対して、より細かな内部制御プロトコルを定義します。
つまりStep Boundaryによる安定性にもコストがあります。境界が粗いほど、新入力への応答が遅れやすくなります。
Agent Runtimeの制御設計では、「状態の一貫性」と「操作の即応性」の間で、適切な粒度を選ぶ必要があります。
複数Toolを並行実行する場合はStepの完了条件を先に定義する
もう1つの境界問題は、1つのAssistant Messageが複数のTool Callを同時に生成する場合です。
Assistant
├── Tool A
├── Tool B
└── Tool C
A、B、Cを並行実行するなら、SteeringはAの完了直後に投入すべきか、それともTool Batch全体の完了を待つか。
これは、RuntimeがStepをどう定義するかによります。
Pi RPCドキュメントでは、現在のAssistant TurnのTool Callsの実行完了後にSteeringを配信すると説明されています。[2]
したがって、次のように理解できます。
Model Decision
↓
Tool Batch
↓ all relevant tool calls complete
Step Boundary
↓
Steering
この選択なら、Steeringが、1回のモデル判断から生成されたTool Effectの一部だけに影響する事態を避けられます。
より細かな制御を必要とするRuntimeでは、Tool Batchをさらに明示的なサブ段階へ分割しなければなりません。そうしないと、挙動が一致しなくなります。
自分でRuntimeを設計するときのDelivery Policyの書き方
PiとDSHの実装からさらに抽象化すると、Delivery Policyを次のように定義できます。
最初からすべてのフィールドを実装する必要はありません。
最小版で必要なのは、targetとwakeupだけです。
これだけで、3種類の一般的なセマンティクスをカバーできます。
next-step + wakeup=true
尽快改变后续决策
next-turn + wakeup=true
当前工作完成后继续
next-step + wakeup=false
只注入后续 Context
Cancelは独立した制御APIのままにします。
その後、複数ソース、優先度、タイムアウト、永続化の必要が生じたら、Delivery Policyを拡張すればよいのです。
UIは意図、Runtimeは実行セマンティクスを担う
Input Deliveryは、最終的に製品上の操作に反映されます。
成熟したAgent UIでは、次のボタンを同時に提供かもしれません。
Send
正常发起工作
Steer
调整当前工作后续方向
Queue / Follow-up
排在当前工作之后
Stop
立即取消当前执行
これらのボタンは単なるUI上の違いではなく、それぞれ異なるRuntime APIに対応しています。
そのため、フロントエンドが次のものだけを持つべきではありません。
より安定したプロトコルでは、明示的なアクションやDelivery Policyを一緒に渡します。
バックエンドは規約どおりInboxへ登録し、正しい境界でclaimするだけで構いません。
これにより、UI、Transport、Runtimeが同じ挙動について一貫したセマンティクスを共有できます。
Delivery Policyは観測可能であるべき
入力がQueueまたはInboxに入ると、外部システムは通常、その現在の状態を知る必要があります。そうでなければ、UIには「送信済み」としか表示できず、メッセージがすでにモデルへ入ったのか、まだ待機中なのか、キャンセルによって破棄されたのかを区別できません。
観測可能な入力ライフサイクルは、小さく保てます。
PENDING
↓ claim
CONSUMED
PENDING
↓ remove / cancel
DISCARDED
Event Logを使うシステムなら、input/queued、input/claimed、input/removedなどの事実を記録できます。
この種の状態はモデル推論自体には直接価値がありませんが、UI、復旧、デバッグには重要です。Input Deliveryは実行層と製品層の双方をつなぐものだからです。実行層はいつclaimするかを気にし、製品層は、入力がまだ有効かどうか关心します。
Input DeliveryとAgent Loopの接続位置
この単元全体のモデルをまとめると、次のようになります。
2種類の制御方向があります。
Input Delivery
决定新的信息何时进入
Execution Control
决定已有工作是否继续
この2つを分けたことで、Steer、Follow-up、Inject、Abortの境界はより明確で安定します。
単元1で形成された実行モデル
4本の記事は、ここまでで次のような完全なRuntime関係にまとめられます。
1本目ではRuntimeがなぜ現れるのかを説明し、2本目では多層的生命循環を確立し、3本目ではPiのソースコードを使ってLoopとStateがどう進むかを検証し、4本目では実行中の新しい入力を明確なDelivery Modelに組み込みました。
締めくくって残す設計判断は、次のとおりです。
Agentの入力は、内容と配信セマンティクスの両方を記述するべきです。Delivery Target、Wakeup、Cancellationは異なる制御軸に属します。これらを明示すれば、Runtimeが暗黙の状態から、メッセージがどのように作用するかを推測することを避けられます。
次の単元からは永続化扱います。実行モデルが確定した後は、どの状態を長期的な事実として保存し、どの状態を復旧時に再計算できるのかをさらに検討する必要があります。PiがSessionをツリーとして表現する理由と、DeepSeek HarnessがSessionの事実源としてEvent Logを選んだ理由も扱います。
参考資料
[1] Pi Agentソース:https://github.com/badlogic/pi-mono/blob/main/packages/agent/src/agent.ts
[2] Pi RPCドキュメント:https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/rpc.md
[3] Pi agent-loop.ts:https://github.com/badlogic/pi-mono/blob/main/packages/agent/src/agent-loop.ts
[4] DeepSeek Harness Core / Agent Inbox:https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/subsystems/core.md
[5] DeepSeek Harness Agent Lifecycle:https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/agent-lifecycle.md