1717
1818use serde:: { Deserialize , Serialize } ;
1919
20+ use crate :: orchestration:: { IterationTimings , RoutingMode } ;
2021use crate :: {
2122 AgentContext , ApprovalCompleted , ApprovalPending , ApprovalRequested , McpServerStatus ,
2223 ProgressToken , WorkerPhase ,
@@ -41,15 +42,11 @@ impl AgentEvent {
4142
4243#[ derive( Clone , Debug , Serialize , Deserialize ) ]
4344#[ serde( tag = "outcome" , rename_all = "snake_case" ) ]
44- pub enum ToolOutcome {
45+ pub enum Outcome {
4546 Success { result : String } ,
4647 Failure { error : String } ,
4748}
4849
49- /// What an agent has to say about its own execution.
50- ///
51- /// `#[non_exhaustive]` because the vocabulary grows as producers move onto this
52- /// schema — orchestration events in particular are not yet modelled here.
5350#[ derive( Clone , Debug , Serialize , Deserialize ) ]
5451#[ serde( tag = "type" , rename_all = "snake_case" ) ]
5552#[ non_exhaustive]
@@ -70,6 +67,8 @@ pub enum AgentEventPayload {
7067
7168 Reasoning {
7269 content : String ,
70+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
71+ task_id : Option < usize > ,
7372 } ,
7473
7574 /// The model's decision to call a tool, ahead of any execution.
@@ -84,14 +83,22 @@ pub enum AgentEventPayload {
8483 tool_name : String ,
8584 #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
8685 progress_token : Option < ProgressToken > ,
86+ /// Present when the producer knows them at start; a run that announces
87+ /// the call separately carries them on [`Self::ToolRequested`].
88+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
89+ arguments : Option < serde_json:: Value > ,
90+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
91+ task_id : Option < usize > ,
8792 } ,
8893
8994 ToolComplete {
9095 tool_id : String ,
9196 tool_name : String ,
9297 duration_ms : u64 ,
9398 #[ serde( flatten) ]
94- outcome : ToolOutcome ,
99+ outcome : Outcome ,
100+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
101+ task_id : Option < usize > ,
95102 } ,
96103
97104 ToolProgress {
@@ -106,7 +113,7 @@ pub enum AgentEventPayload {
106113 WorkerPhase {
107114 phase : WorkerPhase ,
108115 #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
109- task_id : Option < String > ,
116+ task_id : Option < usize > ,
110117 } ,
111118
112119 /// Provider-billed tokens for one turn.
@@ -142,6 +149,76 @@ pub enum AgentEventPayload {
142149 ApprovalPending ( ApprovalPending ) ,
143150
144151 ApprovalCompleted ( ApprovalCompleted ) ,
152+
153+ PlanCreated {
154+ goal : String ,
155+ tasks : Vec < String > ,
156+ routing_mode : RoutingMode ,
157+ routing_rationale : String ,
158+ planning_response : String ,
159+ } ,
160+
161+ DirectAnswer {
162+ response : String ,
163+ routing_rationale : String ,
164+ } ,
165+
166+ ClarificationNeeded {
167+ question : String ,
168+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
169+ options : Option < Vec < String > > ,
170+ routing_rationale : String ,
171+ } ,
172+
173+ TaskStarted {
174+ task_id : usize ,
175+ description : String ,
176+ orchestrator_id : String ,
177+ } ,
178+
179+ TaskCompleted {
180+ task_id : usize ,
181+ duration_ms : u64 ,
182+ orchestrator_id : String ,
183+ #[ serde( flatten) ]
184+ outcome : Outcome ,
185+ } ,
186+
187+ /// A worker's gated call is waiting on an approval the run will not block
188+ /// for; one per parked call.
189+ TaskBlocked {
190+ task_id : usize ,
191+ tool_call_id : String ,
192+ decision_id : String ,
193+ tool_name : String ,
194+ orchestrator_id : String ,
195+ } ,
196+
197+ /// The run stopped to await its parked approvals. Terminal.
198+ RunParked {
199+ run_id : String ,
200+ decision_ids : Vec < String > ,
201+ expires_at : String ,
202+ iteration : usize ,
203+ } ,
204+
205+ IterationComplete {
206+ iteration : usize ,
207+ will_replan : bool ,
208+ reasoning : String ,
209+ gaps : Vec < String > ,
210+ timings : IterationTimings ,
211+ } ,
212+
213+ ReplanStarted {
214+ iteration : usize ,
215+ /// `"coordinator"` or `"failure"`.
216+ trigger : String ,
217+ } ,
218+
219+ Synthesizing {
220+ iteration : usize ,
221+ } ,
145222}
146223
147224#[ cfg( test) ]
@@ -174,6 +251,8 @@ mod tests {
174251 tool_id : "call_1" . to_string ( ) ,
175252 tool_name : "list_files" . to_string ( ) ,
176253 progress_token : None ,
254+ arguments : None ,
255+ task_id : None ,
177256 } ) ;
178257 assert ! ( matches!( start, AgentEventPayload :: ToolStart { .. } ) ) ;
179258
@@ -188,10 +267,11 @@ mod tests {
188267 #[ test]
189268 fn tool_outcome_flattens_onto_tool_complete ( ) {
190269 let json = serde_json:: to_value ( AgentEventPayload :: ToolComplete {
270+ task_id : None ,
191271 tool_id : "call_1" . to_string ( ) ,
192272 tool_name : "list_files" . to_string ( ) ,
193273 duration_ms : 12 ,
194- outcome : ToolOutcome :: Failure {
274+ outcome : Outcome :: Failure {
195275 error : "boom" . to_string ( ) ,
196276 } ,
197277 } )
@@ -202,6 +282,29 @@ mod tests {
202282 assert_eq ! ( json[ "error" ] , "boom" ) ;
203283 }
204284
285+ /// `ToolStart` is emitted by a lone agent and by an orchestration worker,
286+ /// so nothing about the payload says which frames it becomes — only the
287+ /// envelope does.
288+ #[ test]
289+ fn a_shared_variant_is_told_apart_by_its_agent_not_its_payload ( ) {
290+ let payload = || AgentEventPayload :: ToolStart {
291+ tool_id : "call_1" . to_string ( ) ,
292+ tool_name : "list_files" . to_string ( ) ,
293+ progress_token : None ,
294+ arguments : None ,
295+ task_id : None ,
296+ } ;
297+
298+ let alone = AgentEvent :: single_agent ( payload ( ) ) ;
299+ let worker = AgentEvent :: new (
300+ AgentContext :: worker ( "log_worker" , None , "coordinator" ) ,
301+ payload ( ) ,
302+ ) ;
303+
304+ assert ! ( alone. agent. is_single_agent( ) ) ;
305+ assert ! ( !worker. agent. is_single_agent( ) ) ;
306+ }
307+
205308 #[ test]
206309 fn an_event_carries_its_emitting_agent ( ) {
207310 let event = AgentEvent :: new (
0 commit comments