cellstate_core/
agent_state.rs

1//! Agent state derived from event history (NOT stored in DB)
2//!
3//! Re-export path: cellstate_core::agent_state::*
4
5use crate::identity::EntityIdType;
6use crate::{AgentId, BeliefId, DelegationId, Effect, GoalId, PlanId, ScopeId};
7use serde::{Deserialize, Serialize};
8
9/// Agent state derived from event history (NOT stored in DB)
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum AgentState {
13    Idle,
14    Gathering {
15        scope_id: ScopeId,
16    },
17    Planning {
18        scope_id: ScopeId,
19    },
20    Executing {
21        scope_id: ScopeId,
22        tool_call_id: String,
23    },
24    Waiting {
25        for_event_type: String,
26    },
27    Delegating {
28        to: AgentId,
29        delegation_id: DelegationId,
30    },
31    Complete {
32        scope_id: ScopeId,
33    },
34    Failed {
35        scope_id: ScopeId,
36        error: String,
37    },
38}
39
40impl AgentState {
41    /// Derive agent state from event history by scanning in chronological order.
42    /// Handles SCOPE_CREATED/SCOPE_CLOSED, DELEGATION_*, and AGENT_STATUS_CHANGED.
43    ///
44    /// AGENT_STATUS_CHANGED events carry a `"status"` field in their payload.
45    /// Recognised values: `"executing"`, `"waiting"`, `"failed"`, `"idle"`.
46    /// This prevents silent state divergence when an agent crashes mid-execution
47    /// and an explicit status change event is emitted.
48    pub fn from_events(events: &[crate::Event<serde_json::Value>]) -> Self {
49        use crate::EventKind;
50
51        let mut current_state = AgentState::Idle;
52
53        // Scan events in chronological order to build up state
54        for event in events.iter() {
55            match event.header.event_kind {
56                EventKind::SCOPE_CREATED => {
57                    if let Some(scope_id) = extract_scope_id(&event.payload) {
58                        current_state = AgentState::Gathering { scope_id };
59                    }
60                }
61                EventKind::SCOPE_CLOSED => {
62                    if let Some(scope_id) = extract_scope_id(&event.payload) {
63                        // Check if there's an error in the payload
64                        if let Some(error) = extract_error(&event.payload) {
65                            current_state = AgentState::Failed { scope_id, error };
66                        } else {
67                            current_state = AgentState::Complete { scope_id };
68                        }
69                    }
70                }
71                EventKind::DELEGATION_CREATED => {
72                    if let (Some(to), Some(delegation_id)) = (
73                        extract_agent_id(&event.payload),
74                        extract_delegation_id(&event.payload),
75                    ) {
76                        current_state = AgentState::Delegating { to, delegation_id };
77                    }
78                }
79                // Intermediate delegation events — the delegation is still in
80                // progress, so the agent stays in Delegating state.
81                EventKind::DELEGATION_ACCEPTED | EventKind::DELEGATION_STARTED => {
82                    // No state change: agent remains Delegating while the
83                    // delegatee is actively working.
84                }
85                // Terminal delegation events — delegation is finished.
86                EventKind::DELEGATION_COMPLETED
87                | EventKind::DELEGATION_FAILED
88                | EventKind::DELEGATION_REJECTED => {
89                    current_state = AgentState::Idle;
90                }
91                // Explicit agent status changes (e.g. crash recovery, admin override).
92                EventKind::AGENT_STATUS_CHANGED => {
93                    if let Some(new_state) = extract_agent_state(&event.payload) {
94                        current_state = new_state;
95                    }
96                }
97                _ => {
98                    // Ignore other event types
99                }
100            }
101        }
102
103        current_state
104    }
105
106    /// Check if a transition from current state to target state is valid.
107    /// Valid transitions per the state machine:
108    /// - Idle -> Gathering, Delegating
109    /// - Gathering -> Planning, Failed
110    /// - Planning -> Executing, Failed
111    /// - Executing -> Waiting, Complete, Failed
112    /// - Waiting -> Executing, Failed
113    /// - Delegating -> Idle, Failed
114    /// - Complete -> Idle
115    /// - Failed -> Idle
116    pub fn can_transition_to(&self, target: &AgentState) -> bool {
117        use AgentState::*;
118
119        match (self, target) {
120            // Idle transitions
121            (Idle, Gathering { .. }) => true,
122            (Idle, Delegating { .. }) => true,
123
124            // Gathering transitions
125            (Gathering { .. }, Planning { .. }) => true,
126            (Gathering { .. }, Failed { .. }) => true,
127
128            // Planning transitions
129            (Planning { .. }, Executing { .. }) => true,
130            (Planning { .. }, Failed { .. }) => true,
131
132            // Executing transitions
133            (Executing { .. }, Waiting { .. }) => true,
134            (Executing { .. }, Complete { .. }) => true,
135            (Executing { .. }, Failed { .. }) => true,
136
137            // Waiting transitions
138            (Waiting { .. }, Executing { .. }) => true,
139            (Waiting { .. }, Failed { .. }) => true,
140
141            // Delegating transitions
142            (Delegating { .. }, Idle) => true,
143            (Delegating { .. }, Failed { .. }) => true,
144
145            // Complete transitions
146            (Complete { .. }, Idle) => true,
147
148            // Failed transitions
149            (Failed { .. }, Idle) => true,
150
151            // No other transitions are valid
152            _ => false,
153        }
154    }
155
156    /// Return a vector of states reachable from the current state.
157    /// Uses placeholder data where necessary (scope_id preserved when available).
158    pub fn valid_transitions(&self) -> Vec<AgentState> {
159        use AgentState::*;
160
161        match self {
162            Idle => vec![
163                // Gathering and Delegating require IDs - use nil IDs as placeholders
164                Gathering {
165                    scope_id: ScopeId::nil(),
166                },
167                Delegating {
168                    to: AgentId::nil(),
169                    delegation_id: DelegationId::nil(),
170                },
171            ],
172            Gathering { scope_id } => vec![
173                Planning {
174                    scope_id: *scope_id,
175                },
176                Failed {
177                    scope_id: *scope_id,
178                    error: String::new(),
179                },
180            ],
181            Planning { scope_id } => vec![
182                Executing {
183                    scope_id: *scope_id,
184                    tool_call_id: String::new(),
185                },
186                Failed {
187                    scope_id: *scope_id,
188                    error: String::new(),
189                },
190            ],
191            Executing { scope_id, .. } => vec![
192                Waiting {
193                    for_event_type: String::new(),
194                },
195                Complete {
196                    scope_id: *scope_id,
197                },
198                Failed {
199                    scope_id: *scope_id,
200                    error: String::new(),
201                },
202            ],
203            Waiting { .. } => vec![
204                // Executing requires scope_id which we don't have in Waiting - use nil ID
205                Executing {
206                    scope_id: ScopeId::nil(),
207                    tool_call_id: String::new(),
208                },
209                Failed {
210                    scope_id: ScopeId::nil(),
211                    error: String::new(),
212                },
213            ],
214            Delegating { .. } => vec![
215                Idle,
216                Failed {
217                    scope_id: ScopeId::nil(),
218                    error: String::new(),
219                },
220            ],
221            Complete { .. } => vec![Idle],
222            Failed { .. } => vec![Idle],
223        }
224    }
225}
226
227// Helper functions to extract data from event payloads
228
229fn extract_scope_id(payload: &serde_json::Value) -> Option<ScopeId> {
230    payload
231        .get("scope_id")
232        .and_then(|v| serde_json::from_value(v.clone()).ok())
233}
234
235fn extract_agent_id(payload: &serde_json::Value) -> Option<AgentId> {
236    // Try fields in order of specificity: relay delegation payloads use
237    // "to_agent_id" / "delegatee_id", generic events use "agent_id",
238    // and test helpers may use "to".
239    payload
240        .get("to_agent_id")
241        .or_else(|| payload.get("delegatee_id"))
242        .or_else(|| payload.get("agent_id"))
243        .or_else(|| payload.get("to"))
244        .and_then(|v| serde_json::from_value(v.clone()).ok())
245}
246
247fn extract_delegation_id(payload: &serde_json::Value) -> Option<DelegationId> {
248    payload
249        .get("delegation_id")
250        .and_then(|v| serde_json::from_value(v.clone()).ok())
251}
252
253fn extract_error(payload: &serde_json::Value) -> Option<String> {
254    payload
255        .get("error")
256        .and_then(|v| v.as_str())
257        .map(|s| s.to_string())
258}
259
260/// Extract agent state from AGENT_STATUS_CHANGED payload.
261///
262/// Expected payload shape: `{ "status": "idle"|"executing"|"waiting"|"failed", ... }`
263fn extract_agent_state(payload: &serde_json::Value) -> Option<AgentState> {
264    let status = payload.get("status").and_then(|v| v.as_str())?;
265    match status {
266        "idle" => Some(AgentState::Idle),
267        "executing" => {
268            let scope_id = extract_scope_id(payload).unwrap_or_else(ScopeId::nil);
269            let tool_call_id = payload
270                .get("tool_call_id")
271                .and_then(|v| v.as_str())
272                .unwrap_or("")
273                .to_string();
274            Some(AgentState::Executing {
275                scope_id,
276                tool_call_id,
277            })
278        }
279        "waiting" => {
280            let for_event_type = payload
281                .get("for_event_type")
282                .and_then(|v| v.as_str())
283                .unwrap_or("")
284                .to_string();
285            Some(AgentState::Waiting { for_event_type })
286        }
287        "failed" => {
288            let scope_id = extract_scope_id(payload).unwrap_or_else(ScopeId::nil);
289            let error = extract_error(payload).unwrap_or_default();
290            Some(AgentState::Failed { scope_id, error })
291        }
292        "gathering" => {
293            let scope_id = extract_scope_id(payload).unwrap_or_else(ScopeId::nil);
294            Some(AgentState::Gathering { scope_id })
295        }
296        "planning" => {
297            let scope_id = extract_scope_id(payload).unwrap_or_else(ScopeId::nil);
298            Some(AgentState::Planning { scope_id })
299        }
300        _ => None, // Unknown status string — ignore gracefully
301    }
302}
303
304/// Lifecycle hooks for agent state transitions and BDI events.
305///
306/// All methods have default no-op implementations so consumers only
307/// override the hooks they care about. Return `Effect::Err(...)` to
308/// signal that the hook vetoed the transition (caller decides policy).
309pub trait AgentLifecycle: Send + Sync {
310    // ── Checkpoint / resume (Trigger.dev pattern) ──────────────────
311
312    /// Called when the agent enters a wait state (checkpoint).
313    fn on_wait(&self, _state: &AgentState) -> Effect<()> {
314        Effect::Ok(())
315    }
316
317    /// Called when the agent resumes from a wait state.
318    fn on_resume(&self, _state: &AgentState) -> Effect<()> {
319        Effect::Ok(())
320    }
321
322    // ── Goal lifecycle ─────────────────────────────────────────────
323
324    /// A new goal was created for this agent.
325    fn on_goal_created(&self, _goal_id: GoalId, _description: &str) -> Effect<()> {
326        Effect::Ok(())
327    }
328
329    /// A goal transitioned to Active.
330    fn on_goal_activated(&self, _goal_id: GoalId) -> Effect<()> {
331        Effect::Ok(())
332    }
333
334    /// A goal was achieved (all criteria satisfied).
335    fn on_goal_achieved(&self, _goal_id: GoalId) -> Effect<()> {
336        Effect::Ok(())
337    }
338
339    /// A goal failed (deadline, max retries, or explicit failure).
340    fn on_goal_failed(&self, _goal_id: GoalId, _reason: &str) -> Effect<()> {
341        Effect::Ok(())
342    }
343
344    // ── Plan lifecycle ─────────────────────────────────────────────
345
346    /// A new plan was created for a goal.
347    fn on_plan_created(&self, _plan_id: PlanId, _goal_id: GoalId) -> Effect<()> {
348        Effect::Ok(())
349    }
350
351    /// A plan completed (all steps done).
352    fn on_plan_completed(&self, _plan_id: PlanId) -> Effect<()> {
353        Effect::Ok(())
354    }
355
356    // ── Belief lifecycle ───────────────────────────────────────────
357
358    /// A belief was created or its confidence was updated.
359    fn on_belief_updated(&self, _belief_id: BeliefId, _confidence: f32) -> Effect<()> {
360        Effect::Ok(())
361    }
362
363    /// A belief was superseded by a newer belief.
364    fn on_belief_superseded(&self, _old: BeliefId, _new: BeliefId) -> Effect<()> {
365        Effect::Ok(())
366    }
367
368    // ── Drift lifecycle ────────────────────────────────────────────
369
370    /// Drift was detected between this agent and another.
371    fn on_drift_detected(&self, _other_agent: AgentId, _composite_score: f64) -> Effect<()> {
372        Effect::Ok(())
373    }
374}
375
376#[cfg(test)]
377mod tests {
378    use super::*;
379    use crate::{DagPosition, Event, EventFlags, EventHeader, EventId, EventKind};
380    use serde_json::json;
381
382    // Helper function to create a test event with a given kind and payload
383    fn create_event(event_kind: EventKind, payload: serde_json::Value) -> Event<serde_json::Value> {
384        Event::new(
385            EventHeader::new(
386                EventId::now_v7(),
387                EventId::now_v7(),
388                chrono::Utc::now().timestamp_micros(),
389                DagPosition::root(),
390                0,
391                event_kind,
392                EventFlags::empty(),
393                None,
394            ),
395            payload,
396        )
397    }
398
399    // Helper function to create a SCOPE_CREATED event
400    fn scope_created_event(scope_id: ScopeId) -> Event<serde_json::Value> {
401        create_event(
402            EventKind::SCOPE_CREATED,
403            json!({
404                "scope_id": scope_id,
405            }),
406        )
407    }
408
409    // Helper function to create a SCOPE_CLOSED event (success)
410    fn scope_closed_event(scope_id: ScopeId) -> Event<serde_json::Value> {
411        create_event(
412            EventKind::SCOPE_CLOSED,
413            json!({
414                "scope_id": scope_id,
415            }),
416        )
417    }
418
419    // Helper function to create a SCOPE_CLOSED event with error
420    fn scope_closed_error_event(scope_id: ScopeId, error: &str) -> Event<serde_json::Value> {
421        create_event(
422            EventKind::SCOPE_CLOSED,
423            json!({
424                "scope_id": scope_id,
425                "error": error,
426            }),
427        )
428    }
429
430    // Helper function to create a DELEGATION_CREATED event.
431    // Payload mirrors the real relay output: "to_agent_id" + "agent_id" + "delegation_id".
432    fn delegation_created_event(
433        to: AgentId,
434        delegation_id: DelegationId,
435    ) -> Event<serde_json::Value> {
436        create_event(
437            EventKind::DELEGATION_CREATED,
438            json!({
439                "to_agent_id": to,
440                "agent_id": to,
441                "delegation_id": delegation_id,
442            }),
443        )
444    }
445
446    // Helper function to create a DELEGATION_COMPLETED event
447    fn delegation_completed_event() -> Event<serde_json::Value> {
448        create_event(EventKind::DELEGATION_COMPLETED, json!({}))
449    }
450
451    // Helper function to create a DELEGATION_FAILED event
452    fn delegation_failed_event() -> Event<serde_json::Value> {
453        create_event(EventKind::DELEGATION_FAILED, json!({}))
454    }
455
456    // Helper function to create a DELEGATION_ACCEPTED event
457    fn delegation_accepted_event() -> Event<serde_json::Value> {
458        create_event(EventKind::DELEGATION_ACCEPTED, json!({}))
459    }
460
461    // Helper function to create a DELEGATION_STARTED event
462    fn delegation_started_event() -> Event<serde_json::Value> {
463        create_event(EventKind::DELEGATION_STARTED, json!({}))
464    }
465
466    // Helper function to create a DELEGATION_REJECTED event
467    fn delegation_rejected_event() -> Event<serde_json::Value> {
468        create_event(EventKind::DELEGATION_REJECTED, json!({}))
469    }
470
471    // ========================================================================
472    // Tests for AgentState::from_events()
473    // ========================================================================
474
475    #[test]
476    fn test_from_events_empty_returns_idle() {
477        let events = vec![];
478        let state = AgentState::from_events(&events);
479        assert_eq!(state, AgentState::Idle);
480    }
481
482    #[test]
483    fn test_from_events_scope_created_returns_gathering() {
484        let scope_id = ScopeId::now_v7();
485        let events = vec![scope_created_event(scope_id)];
486
487        let state = AgentState::from_events(&events);
488        assert_eq!(state, AgentState::Gathering { scope_id });
489    }
490
491    #[test]
492    fn test_from_events_scope_closed_returns_complete() {
493        let scope_id = ScopeId::now_v7();
494        let events = vec![scope_created_event(scope_id), scope_closed_event(scope_id)];
495
496        let state = AgentState::from_events(&events);
497        assert_eq!(state, AgentState::Complete { scope_id });
498    }
499
500    #[test]
501    fn test_from_events_scope_closed_with_error_returns_failed() {
502        let scope_id = ScopeId::now_v7();
503        let error_msg = "Test error occurred";
504        let events = vec![
505            scope_created_event(scope_id),
506            scope_closed_error_event(scope_id, error_msg),
507        ];
508
509        let state = AgentState::from_events(&events);
510        assert_eq!(
511            state,
512            AgentState::Failed {
513                scope_id,
514                error: error_msg.to_string()
515            }
516        );
517    }
518
519    #[test]
520    fn test_from_events_delegation_created_returns_delegating() {
521        let to = AgentId::now_v7();
522        let delegation_id = DelegationId::now_v7();
523        let events = vec![delegation_created_event(to, delegation_id)];
524
525        let state = AgentState::from_events(&events);
526        assert_eq!(state, AgentState::Delegating { to, delegation_id });
527    }
528
529    #[test]
530    fn test_from_events_delegation_completed_returns_idle() {
531        let to = AgentId::now_v7();
532        let delegation_id = DelegationId::now_v7();
533        let events = vec![
534            delegation_created_event(to, delegation_id),
535            delegation_completed_event(),
536        ];
537
538        let state = AgentState::from_events(&events);
539        assert_eq!(state, AgentState::Idle);
540    }
541
542    #[test]
543    fn test_from_events_delegation_failed_returns_idle() {
544        let to = AgentId::now_v7();
545        let delegation_id = DelegationId::now_v7();
546        let events = vec![
547            delegation_created_event(to, delegation_id),
548            delegation_failed_event(),
549        ];
550
551        let state = AgentState::from_events(&events);
552        assert_eq!(state, AgentState::Idle);
553    }
554
555    #[test]
556    fn test_from_events_delegation_accepted_stays_delegating() {
557        let to = AgentId::now_v7();
558        let delegation_id = DelegationId::now_v7();
559        let events = vec![
560            delegation_created_event(to, delegation_id),
561            delegation_accepted_event(),
562        ];
563
564        let state = AgentState::from_events(&events);
565        assert_eq!(state, AgentState::Delegating { to, delegation_id });
566    }
567
568    #[test]
569    fn test_from_events_delegation_started_stays_delegating() {
570        let to = AgentId::now_v7();
571        let delegation_id = DelegationId::now_v7();
572        let events = vec![
573            delegation_created_event(to, delegation_id),
574            delegation_accepted_event(),
575            delegation_started_event(),
576        ];
577
578        let state = AgentState::from_events(&events);
579        assert_eq!(state, AgentState::Delegating { to, delegation_id });
580    }
581
582    #[test]
583    fn test_from_events_delegation_rejected_returns_idle() {
584        let to = AgentId::now_v7();
585        let delegation_id = DelegationId::now_v7();
586        let events = vec![
587            delegation_created_event(to, delegation_id),
588            delegation_rejected_event(),
589        ];
590
591        let state = AgentState::from_events(&events);
592        assert_eq!(state, AgentState::Idle);
593    }
594
595    #[test]
596    fn test_from_events_full_delegation_lifecycle() {
597        let to = AgentId::now_v7();
598        let delegation_id = DelegationId::now_v7();
599        let events = vec![
600            delegation_created_event(to, delegation_id),
601            delegation_accepted_event(),
602            delegation_started_event(),
603            delegation_completed_event(),
604        ];
605
606        let state = AgentState::from_events(&events);
607        assert_eq!(state, AgentState::Idle);
608    }
609
610    #[test]
611    fn test_from_events_multiple_scopes_uses_latest() {
612        let scope_id_1 = ScopeId::now_v7();
613        let scope_id_2 = ScopeId::now_v7();
614        let events = vec![
615            scope_created_event(scope_id_1),
616            scope_closed_event(scope_id_1),
617            scope_created_event(scope_id_2),
618        ];
619
620        let state = AgentState::from_events(&events);
621        assert_eq!(
622            state,
623            AgentState::Gathering {
624                scope_id: scope_id_2
625            }
626        );
627    }
628
629    #[test]
630    fn test_from_events_scope_then_delegation() {
631        let scope_id = ScopeId::now_v7();
632        let to = AgentId::now_v7();
633        let delegation_id = DelegationId::now_v7();
634
635        let events = vec![
636            scope_created_event(scope_id),
637            scope_closed_event(scope_id),
638            delegation_created_event(to, delegation_id),
639        ];
640
641        let state = AgentState::from_events(&events);
642        assert_eq!(state, AgentState::Delegating { to, delegation_id });
643    }
644
645    #[test]
646    fn test_from_events_delegation_then_scope() {
647        let to = AgentId::now_v7();
648        let delegation_id = DelegationId::now_v7();
649        let scope_id = ScopeId::now_v7();
650
651        let events = vec![
652            delegation_created_event(to, delegation_id),
653            delegation_completed_event(),
654            scope_created_event(scope_id),
655        ];
656
657        let state = AgentState::from_events(&events);
658        assert_eq!(state, AgentState::Gathering { scope_id });
659    }
660
661    // Helper function to create an AGENT_STATUS_CHANGED event
662    fn agent_status_changed_event(
663        status: &str,
664        payload_extra: serde_json::Value,
665    ) -> Event<serde_json::Value> {
666        let mut payload = payload_extra;
667        payload
668            .as_object_mut()
669            .unwrap()
670            .insert("status".to_string(), json!(status));
671        create_event(EventKind::AGENT_STATUS_CHANGED, payload)
672    }
673
674    #[test]
675    fn test_from_events_agent_status_changed_to_idle() {
676        let scope_id = ScopeId::now_v7();
677        let events = vec![
678            scope_created_event(scope_id),
679            agent_status_changed_event("idle", json!({})),
680        ];
681        let state = AgentState::from_events(&events);
682        assert_eq!(state, AgentState::Idle);
683    }
684
685    #[test]
686    fn test_from_events_agent_status_changed_to_failed() {
687        let scope_id = ScopeId::now_v7();
688        let events = vec![
689            scope_created_event(scope_id),
690            agent_status_changed_event("failed", json!({ "scope_id": scope_id, "error": "crash" })),
691        ];
692        let state = AgentState::from_events(&events);
693        assert_eq!(
694            state,
695            AgentState::Failed {
696                scope_id,
697                error: "crash".to_string()
698            }
699        );
700    }
701
702    #[test]
703    fn test_from_events_agent_status_changed_unknown_ignored() {
704        let scope_id = ScopeId::now_v7();
705        let events = vec![
706            scope_created_event(scope_id),
707            agent_status_changed_event("unknown_status", json!({})),
708        ];
709        // Unknown status should be ignored, state stays as Gathering
710        let state = AgentState::from_events(&events);
711        assert_eq!(state, AgentState::Gathering { scope_id });
712    }
713
714    #[test]
715    fn test_from_events_ignores_unknown_event_kinds() {
716        let scope_id = ScopeId::now_v7();
717        let events = vec![
718            create_event(EventKind::SYSTEM_HEARTBEAT, json!({})),
719            scope_created_event(scope_id),
720            create_event(EventKind::MESSAGE_SENT, json!({})),
721        ];
722
723        let state = AgentState::from_events(&events);
724        assert_eq!(state, AgentState::Gathering { scope_id });
725    }
726
727    #[test]
728    fn test_from_events_complex_sequence() {
729        let scope_id_1 = ScopeId::now_v7();
730        let scope_id_2 = ScopeId::now_v7();
731        let to = AgentId::now_v7();
732        let delegation_id = DelegationId::now_v7();
733
734        let events = vec![
735            scope_created_event(scope_id_1),
736            scope_closed_event(scope_id_1),
737            delegation_created_event(to, delegation_id),
738            delegation_completed_event(),
739            scope_created_event(scope_id_2),
740            scope_closed_error_event(scope_id_2, "Something went wrong"),
741        ];
742
743        let state = AgentState::from_events(&events);
744        assert_eq!(
745            state,
746            AgentState::Failed {
747                scope_id: scope_id_2,
748                error: "Something went wrong".to_string()
749            }
750        );
751    }
752
753    // ========================================================================
754    // Tests for AgentState::can_transition_to()
755    // ========================================================================
756
757    #[test]
758    fn test_idle_can_transition_to_gathering() {
759        let from = AgentState::Idle;
760        let to = AgentState::Gathering {
761            scope_id: ScopeId::now_v7(),
762        };
763        assert!(from.can_transition_to(&to));
764    }
765
766    #[test]
767    fn test_idle_can_transition_to_delegating() {
768        let from = AgentState::Idle;
769        let to = AgentState::Delegating {
770            to: AgentId::now_v7(),
771            delegation_id: DelegationId::now_v7(),
772        };
773        assert!(from.can_transition_to(&to));
774    }
775
776    #[test]
777    fn test_idle_cannot_transition_to_planning() {
778        let from = AgentState::Idle;
779        let to = AgentState::Planning {
780            scope_id: ScopeId::now_v7(),
781        };
782        assert!(!from.can_transition_to(&to));
783    }
784
785    #[test]
786    fn test_idle_cannot_transition_to_executing() {
787        let from = AgentState::Idle;
788        let to = AgentState::Executing {
789            scope_id: ScopeId::now_v7(),
790            tool_call_id: "test_call".to_string(),
791        };
792        assert!(!from.can_transition_to(&to));
793    }
794
795    #[test]
796    fn test_gathering_can_transition_to_planning() {
797        let scope_id = ScopeId::now_v7();
798        let from = AgentState::Gathering { scope_id };
799        let to = AgentState::Planning { scope_id };
800        assert!(from.can_transition_to(&to));
801    }
802
803    #[test]
804    fn test_gathering_can_transition_to_failed() {
805        let scope_id = ScopeId::now_v7();
806        let from = AgentState::Gathering { scope_id };
807        let to = AgentState::Failed {
808            scope_id,
809            error: "Error".to_string(),
810        };
811        assert!(from.can_transition_to(&to));
812    }
813
814    #[test]
815    fn test_gathering_cannot_transition_to_executing() {
816        let scope_id = ScopeId::now_v7();
817        let from = AgentState::Gathering { scope_id };
818        let to = AgentState::Executing {
819            scope_id,
820            tool_call_id: "test_call".to_string(),
821        };
822        assert!(!from.can_transition_to(&to));
823    }
824
825    #[test]
826    fn test_planning_can_transition_to_executing() {
827        let scope_id = ScopeId::now_v7();
828        let from = AgentState::Planning { scope_id };
829        let to = AgentState::Executing {
830            scope_id,
831            tool_call_id: "test_call".to_string(),
832        };
833        assert!(from.can_transition_to(&to));
834    }
835
836    #[test]
837    fn test_planning_can_transition_to_failed() {
838        let scope_id = ScopeId::now_v7();
839        let from = AgentState::Planning { scope_id };
840        let to = AgentState::Failed {
841            scope_id,
842            error: "Error".to_string(),
843        };
844        assert!(from.can_transition_to(&to));
845    }
846
847    #[test]
848    fn test_planning_cannot_transition_to_gathering() {
849        let scope_id = ScopeId::now_v7();
850        let from = AgentState::Planning { scope_id };
851        let to = AgentState::Gathering { scope_id };
852        assert!(!from.can_transition_to(&to));
853    }
854
855    #[test]
856    fn test_executing_can_transition_to_waiting() {
857        let scope_id = ScopeId::now_v7();
858        let from = AgentState::Executing {
859            scope_id,
860            tool_call_id: "test_call".to_string(),
861        };
862        let to = AgentState::Waiting {
863            for_event_type: "USER_INPUT".to_string(),
864        };
865        assert!(from.can_transition_to(&to));
866    }
867
868    #[test]
869    fn test_executing_can_transition_to_complete() {
870        let scope_id = ScopeId::now_v7();
871        let from = AgentState::Executing {
872            scope_id,
873            tool_call_id: "test_call".to_string(),
874        };
875        let to = AgentState::Complete { scope_id };
876        assert!(from.can_transition_to(&to));
877    }
878
879    #[test]
880    fn test_executing_can_transition_to_failed() {
881        let scope_id = ScopeId::now_v7();
882        let from = AgentState::Executing {
883            scope_id,
884            tool_call_id: "test_call".to_string(),
885        };
886        let to = AgentState::Failed {
887            scope_id,
888            error: "Error".to_string(),
889        };
890        assert!(from.can_transition_to(&to));
891    }
892
893    #[test]
894    fn test_executing_cannot_transition_to_planning() {
895        let scope_id = ScopeId::now_v7();
896        let from = AgentState::Executing {
897            scope_id,
898            tool_call_id: "test_call".to_string(),
899        };
900        let to = AgentState::Planning { scope_id };
901        assert!(!from.can_transition_to(&to));
902    }
903
904    #[test]
905    fn test_waiting_can_transition_to_executing() {
906        let scope_id = ScopeId::now_v7();
907        let from = AgentState::Waiting {
908            for_event_type: "USER_INPUT".to_string(),
909        };
910        let to = AgentState::Executing {
911            scope_id,
912            tool_call_id: "test_call".to_string(),
913        };
914        assert!(from.can_transition_to(&to));
915    }
916
917    #[test]
918    fn test_waiting_can_transition_to_failed() {
919        let scope_id = ScopeId::now_v7();
920        let from = AgentState::Waiting {
921            for_event_type: "USER_INPUT".to_string(),
922        };
923        let to = AgentState::Failed {
924            scope_id,
925            error: "Error".to_string(),
926        };
927        assert!(from.can_transition_to(&to));
928    }
929
930    #[test]
931    fn test_waiting_cannot_transition_to_complete() {
932        let scope_id = ScopeId::now_v7();
933        let from = AgentState::Waiting {
934            for_event_type: "USER_INPUT".to_string(),
935        };
936        let to = AgentState::Complete { scope_id };
937        assert!(!from.can_transition_to(&to));
938    }
939
940    #[test]
941    fn test_delegating_can_transition_to_idle() {
942        let from = AgentState::Delegating {
943            to: AgentId::now_v7(),
944            delegation_id: DelegationId::now_v7(),
945        };
946        let to = AgentState::Idle;
947        assert!(from.can_transition_to(&to));
948    }
949
950    #[test]
951    fn test_delegating_can_transition_to_failed() {
952        let scope_id = ScopeId::now_v7();
953        let from = AgentState::Delegating {
954            to: AgentId::now_v7(),
955            delegation_id: DelegationId::now_v7(),
956        };
957        let to = AgentState::Failed {
958            scope_id,
959            error: "Error".to_string(),
960        };
961        assert!(from.can_transition_to(&to));
962    }
963
964    #[test]
965    fn test_delegating_cannot_transition_to_gathering() {
966        let scope_id = ScopeId::now_v7();
967        let from = AgentState::Delegating {
968            to: AgentId::now_v7(),
969            delegation_id: DelegationId::now_v7(),
970        };
971        let to = AgentState::Gathering { scope_id };
972        assert!(!from.can_transition_to(&to));
973    }
974
975    #[test]
976    fn test_complete_can_transition_to_idle() {
977        let from = AgentState::Complete {
978            scope_id: ScopeId::now_v7(),
979        };
980        let to = AgentState::Idle;
981        assert!(from.can_transition_to(&to));
982    }
983
984    #[test]
985    fn test_complete_cannot_transition_to_gathering() {
986        let scope_id = ScopeId::now_v7();
987        let from = AgentState::Complete { scope_id };
988        let to = AgentState::Gathering { scope_id };
989        assert!(!from.can_transition_to(&to));
990    }
991
992    #[test]
993    fn test_failed_can_transition_to_idle() {
994        let from = AgentState::Failed {
995            scope_id: ScopeId::now_v7(),
996            error: "Error".to_string(),
997        };
998        let to = AgentState::Idle;
999        assert!(from.can_transition_to(&to));
1000    }
1001
1002    #[test]
1003    fn test_failed_cannot_transition_to_gathering() {
1004        let scope_id = ScopeId::now_v7();
1005        let from = AgentState::Failed {
1006            scope_id,
1007            error: "Error".to_string(),
1008        };
1009        let to = AgentState::Gathering { scope_id };
1010        assert!(!from.can_transition_to(&to));
1011    }
1012
1013    #[test]
1014    fn test_invalid_self_transition_idle_to_idle() {
1015        let from = AgentState::Idle;
1016        let to = AgentState::Idle;
1017        assert!(!from.can_transition_to(&to));
1018    }
1019
1020    #[test]
1021    fn test_invalid_self_transition_gathering_to_gathering() {
1022        let scope_id = ScopeId::now_v7();
1023        let from = AgentState::Gathering { scope_id };
1024        let to = AgentState::Gathering { scope_id };
1025        assert!(!from.can_transition_to(&to));
1026    }
1027
1028    // ========================================================================
1029    // Tests for AgentState::valid_transitions()
1030    // ========================================================================
1031
1032    #[test]
1033    fn test_valid_transitions_from_idle() {
1034        let state = AgentState::Idle;
1035        let transitions = state.valid_transitions();
1036        assert_eq!(transitions.len(), 2);
1037        assert!(transitions
1038            .iter()
1039            .any(|s| matches!(s, AgentState::Gathering { .. })));
1040        assert!(transitions
1041            .iter()
1042            .any(|s| matches!(s, AgentState::Delegating { .. })));
1043    }
1044
1045    #[test]
1046    fn test_valid_transitions_from_gathering() {
1047        let scope_id = ScopeId::now_v7();
1048        let state = AgentState::Gathering { scope_id };
1049        let transitions = state.valid_transitions();
1050        assert_eq!(transitions.len(), 2);
1051        assert!(transitions
1052            .iter()
1053            .any(|s| matches!(s, AgentState::Planning { .. })));
1054        assert!(transitions
1055            .iter()
1056            .any(|s| matches!(s, AgentState::Failed { .. })));
1057    }
1058
1059    #[test]
1060    fn test_valid_transitions_from_planning() {
1061        let scope_id = ScopeId::now_v7();
1062        let state = AgentState::Planning { scope_id };
1063        let transitions = state.valid_transitions();
1064        assert_eq!(transitions.len(), 2);
1065        assert!(transitions
1066            .iter()
1067            .any(|s| matches!(s, AgentState::Executing { .. })));
1068        assert!(transitions
1069            .iter()
1070            .any(|s| matches!(s, AgentState::Failed { .. })));
1071    }
1072
1073    #[test]
1074    fn test_valid_transitions_from_executing() {
1075        let scope_id = ScopeId::now_v7();
1076        let state = AgentState::Executing {
1077            scope_id,
1078            tool_call_id: "test_call".to_string(),
1079        };
1080        let transitions = state.valid_transitions();
1081        assert_eq!(transitions.len(), 3);
1082        assert!(transitions
1083            .iter()
1084            .any(|s| matches!(s, AgentState::Waiting { .. })));
1085        assert!(transitions
1086            .iter()
1087            .any(|s| matches!(s, AgentState::Complete { .. })));
1088        assert!(transitions
1089            .iter()
1090            .any(|s| matches!(s, AgentState::Failed { .. })));
1091    }
1092
1093    #[test]
1094    fn test_valid_transitions_from_waiting() {
1095        let state = AgentState::Waiting {
1096            for_event_type: "USER_INPUT".to_string(),
1097        };
1098        let transitions = state.valid_transitions();
1099        assert_eq!(transitions.len(), 2);
1100        assert!(transitions
1101            .iter()
1102            .any(|s| matches!(s, AgentState::Executing { .. })));
1103        assert!(transitions
1104            .iter()
1105            .any(|s| matches!(s, AgentState::Failed { .. })));
1106    }
1107
1108    #[test]
1109    fn test_valid_transitions_from_delegating() {
1110        let state = AgentState::Delegating {
1111            to: AgentId::now_v7(),
1112            delegation_id: DelegationId::now_v7(),
1113        };
1114        let transitions = state.valid_transitions();
1115        assert_eq!(transitions.len(), 2);
1116        assert!(transitions.iter().any(|s| matches!(s, AgentState::Idle)));
1117        assert!(transitions
1118            .iter()
1119            .any(|s| matches!(s, AgentState::Failed { .. })));
1120    }
1121
1122    #[test]
1123    fn test_valid_transitions_from_complete() {
1124        let state = AgentState::Complete {
1125            scope_id: ScopeId::now_v7(),
1126        };
1127        let transitions = state.valid_transitions();
1128        assert_eq!(transitions.len(), 1);
1129        assert!(transitions.iter().any(|s| matches!(s, AgentState::Idle)));
1130    }
1131
1132    #[test]
1133    fn test_valid_transitions_from_failed() {
1134        let state = AgentState::Failed {
1135            scope_id: ScopeId::now_v7(),
1136            error: "Error".to_string(),
1137        };
1138        let transitions = state.valid_transitions();
1139        assert_eq!(transitions.len(), 1);
1140        assert!(transitions.iter().any(|s| matches!(s, AgentState::Idle)));
1141    }
1142}