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, 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            ),
393            payload,
394        )
395    }
396
397    // Helper function to create a SCOPE_CREATED event
398    fn scope_created_event(scope_id: ScopeId) -> Event<serde_json::Value> {
399        create_event(
400            EventKind::SCOPE_CREATED,
401            json!({
402                "scope_id": scope_id,
403            }),
404        )
405    }
406
407    // Helper function to create a SCOPE_CLOSED event (success)
408    fn scope_closed_event(scope_id: ScopeId) -> Event<serde_json::Value> {
409        create_event(
410            EventKind::SCOPE_CLOSED,
411            json!({
412                "scope_id": scope_id,
413            }),
414        )
415    }
416
417    // Helper function to create a SCOPE_CLOSED event with error
418    fn scope_closed_error_event(scope_id: ScopeId, error: &str) -> Event<serde_json::Value> {
419        create_event(
420            EventKind::SCOPE_CLOSED,
421            json!({
422                "scope_id": scope_id,
423                "error": error,
424            }),
425        )
426    }
427
428    // Helper function to create a DELEGATION_CREATED event.
429    // Payload mirrors the real relay output: "to_agent_id" + "agent_id" + "delegation_id".
430    fn delegation_created_event(
431        to: AgentId,
432        delegation_id: DelegationId,
433    ) -> Event<serde_json::Value> {
434        create_event(
435            EventKind::DELEGATION_CREATED,
436            json!({
437                "to_agent_id": to,
438                "agent_id": to,
439                "delegation_id": delegation_id,
440            }),
441        )
442    }
443
444    // Helper function to create a DELEGATION_COMPLETED event
445    fn delegation_completed_event() -> Event<serde_json::Value> {
446        create_event(EventKind::DELEGATION_COMPLETED, json!({}))
447    }
448
449    // Helper function to create a DELEGATION_FAILED event
450    fn delegation_failed_event() -> Event<serde_json::Value> {
451        create_event(EventKind::DELEGATION_FAILED, json!({}))
452    }
453
454    // Helper function to create a DELEGATION_ACCEPTED event
455    fn delegation_accepted_event() -> Event<serde_json::Value> {
456        create_event(EventKind::DELEGATION_ACCEPTED, json!({}))
457    }
458
459    // Helper function to create a DELEGATION_STARTED event
460    fn delegation_started_event() -> Event<serde_json::Value> {
461        create_event(EventKind::DELEGATION_STARTED, json!({}))
462    }
463
464    // Helper function to create a DELEGATION_REJECTED event
465    fn delegation_rejected_event() -> Event<serde_json::Value> {
466        create_event(EventKind::DELEGATION_REJECTED, json!({}))
467    }
468
469    // ========================================================================
470    // Tests for AgentState::from_events()
471    // ========================================================================
472
473    #[test]
474    fn test_from_events_empty_returns_idle() {
475        let events = vec![];
476        let state = AgentState::from_events(&events);
477        assert_eq!(state, AgentState::Idle);
478    }
479
480    #[test]
481    fn test_from_events_scope_created_returns_gathering() {
482        let scope_id = ScopeId::now_v7();
483        let events = vec![scope_created_event(scope_id)];
484
485        let state = AgentState::from_events(&events);
486        assert_eq!(state, AgentState::Gathering { scope_id });
487    }
488
489    #[test]
490    fn test_from_events_scope_closed_returns_complete() {
491        let scope_id = ScopeId::now_v7();
492        let events = vec![scope_created_event(scope_id), scope_closed_event(scope_id)];
493
494        let state = AgentState::from_events(&events);
495        assert_eq!(state, AgentState::Complete { scope_id });
496    }
497
498    #[test]
499    fn test_from_events_scope_closed_with_error_returns_failed() {
500        let scope_id = ScopeId::now_v7();
501        let error_msg = "Test error occurred";
502        let events = vec![
503            scope_created_event(scope_id),
504            scope_closed_error_event(scope_id, error_msg),
505        ];
506
507        let state = AgentState::from_events(&events);
508        assert_eq!(
509            state,
510            AgentState::Failed {
511                scope_id,
512                error: error_msg.to_string()
513            }
514        );
515    }
516
517    #[test]
518    fn test_from_events_delegation_created_returns_delegating() {
519        let to = AgentId::now_v7();
520        let delegation_id = DelegationId::now_v7();
521        let events = vec![delegation_created_event(to, delegation_id)];
522
523        let state = AgentState::from_events(&events);
524        assert_eq!(state, AgentState::Delegating { to, delegation_id });
525    }
526
527    #[test]
528    fn test_from_events_delegation_completed_returns_idle() {
529        let to = AgentId::now_v7();
530        let delegation_id = DelegationId::now_v7();
531        let events = vec![
532            delegation_created_event(to, delegation_id),
533            delegation_completed_event(),
534        ];
535
536        let state = AgentState::from_events(&events);
537        assert_eq!(state, AgentState::Idle);
538    }
539
540    #[test]
541    fn test_from_events_delegation_failed_returns_idle() {
542        let to = AgentId::now_v7();
543        let delegation_id = DelegationId::now_v7();
544        let events = vec![
545            delegation_created_event(to, delegation_id),
546            delegation_failed_event(),
547        ];
548
549        let state = AgentState::from_events(&events);
550        assert_eq!(state, AgentState::Idle);
551    }
552
553    #[test]
554    fn test_from_events_delegation_accepted_stays_delegating() {
555        let to = AgentId::now_v7();
556        let delegation_id = DelegationId::now_v7();
557        let events = vec![
558            delegation_created_event(to, delegation_id),
559            delegation_accepted_event(),
560        ];
561
562        let state = AgentState::from_events(&events);
563        assert_eq!(state, AgentState::Delegating { to, delegation_id });
564    }
565
566    #[test]
567    fn test_from_events_delegation_started_stays_delegating() {
568        let to = AgentId::now_v7();
569        let delegation_id = DelegationId::now_v7();
570        let events = vec![
571            delegation_created_event(to, delegation_id),
572            delegation_accepted_event(),
573            delegation_started_event(),
574        ];
575
576        let state = AgentState::from_events(&events);
577        assert_eq!(state, AgentState::Delegating { to, delegation_id });
578    }
579
580    #[test]
581    fn test_from_events_delegation_rejected_returns_idle() {
582        let to = AgentId::now_v7();
583        let delegation_id = DelegationId::now_v7();
584        let events = vec![
585            delegation_created_event(to, delegation_id),
586            delegation_rejected_event(),
587        ];
588
589        let state = AgentState::from_events(&events);
590        assert_eq!(state, AgentState::Idle);
591    }
592
593    #[test]
594    fn test_from_events_full_delegation_lifecycle() {
595        let to = AgentId::now_v7();
596        let delegation_id = DelegationId::now_v7();
597        let events = vec![
598            delegation_created_event(to, delegation_id),
599            delegation_accepted_event(),
600            delegation_started_event(),
601            delegation_completed_event(),
602        ];
603
604        let state = AgentState::from_events(&events);
605        assert_eq!(state, AgentState::Idle);
606    }
607
608    #[test]
609    fn test_from_events_multiple_scopes_uses_latest() {
610        let scope_id_1 = ScopeId::now_v7();
611        let scope_id_2 = ScopeId::now_v7();
612        let events = vec![
613            scope_created_event(scope_id_1),
614            scope_closed_event(scope_id_1),
615            scope_created_event(scope_id_2),
616        ];
617
618        let state = AgentState::from_events(&events);
619        assert_eq!(
620            state,
621            AgentState::Gathering {
622                scope_id: scope_id_2
623            }
624        );
625    }
626
627    #[test]
628    fn test_from_events_scope_then_delegation() {
629        let scope_id = ScopeId::now_v7();
630        let to = AgentId::now_v7();
631        let delegation_id = DelegationId::now_v7();
632
633        let events = vec![
634            scope_created_event(scope_id),
635            scope_closed_event(scope_id),
636            delegation_created_event(to, delegation_id),
637        ];
638
639        let state = AgentState::from_events(&events);
640        assert_eq!(state, AgentState::Delegating { to, delegation_id });
641    }
642
643    #[test]
644    fn test_from_events_delegation_then_scope() {
645        let to = AgentId::now_v7();
646        let delegation_id = DelegationId::now_v7();
647        let scope_id = ScopeId::now_v7();
648
649        let events = vec![
650            delegation_created_event(to, delegation_id),
651            delegation_completed_event(),
652            scope_created_event(scope_id),
653        ];
654
655        let state = AgentState::from_events(&events);
656        assert_eq!(state, AgentState::Gathering { scope_id });
657    }
658
659    // Helper function to create an AGENT_STATUS_CHANGED event
660    fn agent_status_changed_event(
661        status: &str,
662        payload_extra: serde_json::Value,
663    ) -> Event<serde_json::Value> {
664        let mut payload = payload_extra;
665        payload
666            .as_object_mut()
667            .unwrap()
668            .insert("status".to_string(), json!(status));
669        create_event(EventKind::AGENT_STATUS_CHANGED, payload)
670    }
671
672    #[test]
673    fn test_from_events_agent_status_changed_to_idle() {
674        let scope_id = ScopeId::now_v7();
675        let events = vec![
676            scope_created_event(scope_id),
677            agent_status_changed_event("idle", json!({})),
678        ];
679        let state = AgentState::from_events(&events);
680        assert_eq!(state, AgentState::Idle);
681    }
682
683    #[test]
684    fn test_from_events_agent_status_changed_to_failed() {
685        let scope_id = ScopeId::now_v7();
686        let events = vec![
687            scope_created_event(scope_id),
688            agent_status_changed_event("failed", json!({ "scope_id": scope_id, "error": "crash" })),
689        ];
690        let state = AgentState::from_events(&events);
691        assert_eq!(
692            state,
693            AgentState::Failed {
694                scope_id,
695                error: "crash".to_string()
696            }
697        );
698    }
699
700    #[test]
701    fn test_from_events_agent_status_changed_unknown_ignored() {
702        let scope_id = ScopeId::now_v7();
703        let events = vec![
704            scope_created_event(scope_id),
705            agent_status_changed_event("unknown_status", json!({})),
706        ];
707        // Unknown status should be ignored, state stays as Gathering
708        let state = AgentState::from_events(&events);
709        assert_eq!(state, AgentState::Gathering { scope_id });
710    }
711
712    #[test]
713    fn test_from_events_ignores_unknown_event_kinds() {
714        let scope_id = ScopeId::now_v7();
715        let events = vec![
716            create_event(EventKind::SYSTEM_HEARTBEAT, json!({})),
717            scope_created_event(scope_id),
718            create_event(EventKind::MESSAGE_SENT, json!({})),
719        ];
720
721        let state = AgentState::from_events(&events);
722        assert_eq!(state, AgentState::Gathering { scope_id });
723    }
724
725    #[test]
726    fn test_from_events_complex_sequence() {
727        let scope_id_1 = ScopeId::now_v7();
728        let scope_id_2 = ScopeId::now_v7();
729        let to = AgentId::now_v7();
730        let delegation_id = DelegationId::now_v7();
731
732        let events = vec![
733            scope_created_event(scope_id_1),
734            scope_closed_event(scope_id_1),
735            delegation_created_event(to, delegation_id),
736            delegation_completed_event(),
737            scope_created_event(scope_id_2),
738            scope_closed_error_event(scope_id_2, "Something went wrong"),
739        ];
740
741        let state = AgentState::from_events(&events);
742        assert_eq!(
743            state,
744            AgentState::Failed {
745                scope_id: scope_id_2,
746                error: "Something went wrong".to_string()
747            }
748        );
749    }
750
751    // ========================================================================
752    // Tests for AgentState::can_transition_to()
753    // ========================================================================
754
755    #[test]
756    fn test_idle_can_transition_to_gathering() {
757        let from = AgentState::Idle;
758        let to = AgentState::Gathering {
759            scope_id: ScopeId::now_v7(),
760        };
761        assert!(from.can_transition_to(&to));
762    }
763
764    #[test]
765    fn test_idle_can_transition_to_delegating() {
766        let from = AgentState::Idle;
767        let to = AgentState::Delegating {
768            to: AgentId::now_v7(),
769            delegation_id: DelegationId::now_v7(),
770        };
771        assert!(from.can_transition_to(&to));
772    }
773
774    #[test]
775    fn test_idle_cannot_transition_to_planning() {
776        let from = AgentState::Idle;
777        let to = AgentState::Planning {
778            scope_id: ScopeId::now_v7(),
779        };
780        assert!(!from.can_transition_to(&to));
781    }
782
783    #[test]
784    fn test_idle_cannot_transition_to_executing() {
785        let from = AgentState::Idle;
786        let to = AgentState::Executing {
787            scope_id: ScopeId::now_v7(),
788            tool_call_id: "test_call".to_string(),
789        };
790        assert!(!from.can_transition_to(&to));
791    }
792
793    #[test]
794    fn test_gathering_can_transition_to_planning() {
795        let scope_id = ScopeId::now_v7();
796        let from = AgentState::Gathering { scope_id };
797        let to = AgentState::Planning { scope_id };
798        assert!(from.can_transition_to(&to));
799    }
800
801    #[test]
802    fn test_gathering_can_transition_to_failed() {
803        let scope_id = ScopeId::now_v7();
804        let from = AgentState::Gathering { scope_id };
805        let to = AgentState::Failed {
806            scope_id,
807            error: "Error".to_string(),
808        };
809        assert!(from.can_transition_to(&to));
810    }
811
812    #[test]
813    fn test_gathering_cannot_transition_to_executing() {
814        let scope_id = ScopeId::now_v7();
815        let from = AgentState::Gathering { scope_id };
816        let to = AgentState::Executing {
817            scope_id,
818            tool_call_id: "test_call".to_string(),
819        };
820        assert!(!from.can_transition_to(&to));
821    }
822
823    #[test]
824    fn test_planning_can_transition_to_executing() {
825        let scope_id = ScopeId::now_v7();
826        let from = AgentState::Planning { scope_id };
827        let to = AgentState::Executing {
828            scope_id,
829            tool_call_id: "test_call".to_string(),
830        };
831        assert!(from.can_transition_to(&to));
832    }
833
834    #[test]
835    fn test_planning_can_transition_to_failed() {
836        let scope_id = ScopeId::now_v7();
837        let from = AgentState::Planning { scope_id };
838        let to = AgentState::Failed {
839            scope_id,
840            error: "Error".to_string(),
841        };
842        assert!(from.can_transition_to(&to));
843    }
844
845    #[test]
846    fn test_planning_cannot_transition_to_gathering() {
847        let scope_id = ScopeId::now_v7();
848        let from = AgentState::Planning { scope_id };
849        let to = AgentState::Gathering { scope_id };
850        assert!(!from.can_transition_to(&to));
851    }
852
853    #[test]
854    fn test_executing_can_transition_to_waiting() {
855        let scope_id = ScopeId::now_v7();
856        let from = AgentState::Executing {
857            scope_id,
858            tool_call_id: "test_call".to_string(),
859        };
860        let to = AgentState::Waiting {
861            for_event_type: "USER_INPUT".to_string(),
862        };
863        assert!(from.can_transition_to(&to));
864    }
865
866    #[test]
867    fn test_executing_can_transition_to_complete() {
868        let scope_id = ScopeId::now_v7();
869        let from = AgentState::Executing {
870            scope_id,
871            tool_call_id: "test_call".to_string(),
872        };
873        let to = AgentState::Complete { scope_id };
874        assert!(from.can_transition_to(&to));
875    }
876
877    #[test]
878    fn test_executing_can_transition_to_failed() {
879        let scope_id = ScopeId::now_v7();
880        let from = AgentState::Executing {
881            scope_id,
882            tool_call_id: "test_call".to_string(),
883        };
884        let to = AgentState::Failed {
885            scope_id,
886            error: "Error".to_string(),
887        };
888        assert!(from.can_transition_to(&to));
889    }
890
891    #[test]
892    fn test_executing_cannot_transition_to_planning() {
893        let scope_id = ScopeId::now_v7();
894        let from = AgentState::Executing {
895            scope_id,
896            tool_call_id: "test_call".to_string(),
897        };
898        let to = AgentState::Planning { scope_id };
899        assert!(!from.can_transition_to(&to));
900    }
901
902    #[test]
903    fn test_waiting_can_transition_to_executing() {
904        let scope_id = ScopeId::now_v7();
905        let from = AgentState::Waiting {
906            for_event_type: "USER_INPUT".to_string(),
907        };
908        let to = AgentState::Executing {
909            scope_id,
910            tool_call_id: "test_call".to_string(),
911        };
912        assert!(from.can_transition_to(&to));
913    }
914
915    #[test]
916    fn test_waiting_can_transition_to_failed() {
917        let scope_id = ScopeId::now_v7();
918        let from = AgentState::Waiting {
919            for_event_type: "USER_INPUT".to_string(),
920        };
921        let to = AgentState::Failed {
922            scope_id,
923            error: "Error".to_string(),
924        };
925        assert!(from.can_transition_to(&to));
926    }
927
928    #[test]
929    fn test_waiting_cannot_transition_to_complete() {
930        let scope_id = ScopeId::now_v7();
931        let from = AgentState::Waiting {
932            for_event_type: "USER_INPUT".to_string(),
933        };
934        let to = AgentState::Complete { scope_id };
935        assert!(!from.can_transition_to(&to));
936    }
937
938    #[test]
939    fn test_delegating_can_transition_to_idle() {
940        let from = AgentState::Delegating {
941            to: AgentId::now_v7(),
942            delegation_id: DelegationId::now_v7(),
943        };
944        let to = AgentState::Idle;
945        assert!(from.can_transition_to(&to));
946    }
947
948    #[test]
949    fn test_delegating_can_transition_to_failed() {
950        let scope_id = ScopeId::now_v7();
951        let from = AgentState::Delegating {
952            to: AgentId::now_v7(),
953            delegation_id: DelegationId::now_v7(),
954        };
955        let to = AgentState::Failed {
956            scope_id,
957            error: "Error".to_string(),
958        };
959        assert!(from.can_transition_to(&to));
960    }
961
962    #[test]
963    fn test_delegating_cannot_transition_to_gathering() {
964        let scope_id = ScopeId::now_v7();
965        let from = AgentState::Delegating {
966            to: AgentId::now_v7(),
967            delegation_id: DelegationId::now_v7(),
968        };
969        let to = AgentState::Gathering { scope_id };
970        assert!(!from.can_transition_to(&to));
971    }
972
973    #[test]
974    fn test_complete_can_transition_to_idle() {
975        let from = AgentState::Complete {
976            scope_id: ScopeId::now_v7(),
977        };
978        let to = AgentState::Idle;
979        assert!(from.can_transition_to(&to));
980    }
981
982    #[test]
983    fn test_complete_cannot_transition_to_gathering() {
984        let scope_id = ScopeId::now_v7();
985        let from = AgentState::Complete { scope_id };
986        let to = AgentState::Gathering { scope_id };
987        assert!(!from.can_transition_to(&to));
988    }
989
990    #[test]
991    fn test_failed_can_transition_to_idle() {
992        let from = AgentState::Failed {
993            scope_id: ScopeId::now_v7(),
994            error: "Error".to_string(),
995        };
996        let to = AgentState::Idle;
997        assert!(from.can_transition_to(&to));
998    }
999
1000    #[test]
1001    fn test_failed_cannot_transition_to_gathering() {
1002        let scope_id = ScopeId::now_v7();
1003        let from = AgentState::Failed {
1004            scope_id,
1005            error: "Error".to_string(),
1006        };
1007        let to = AgentState::Gathering { scope_id };
1008        assert!(!from.can_transition_to(&to));
1009    }
1010
1011    #[test]
1012    fn test_invalid_self_transition_idle_to_idle() {
1013        let from = AgentState::Idle;
1014        let to = AgentState::Idle;
1015        assert!(!from.can_transition_to(&to));
1016    }
1017
1018    #[test]
1019    fn test_invalid_self_transition_gathering_to_gathering() {
1020        let scope_id = ScopeId::now_v7();
1021        let from = AgentState::Gathering { scope_id };
1022        let to = AgentState::Gathering { scope_id };
1023        assert!(!from.can_transition_to(&to));
1024    }
1025
1026    // ========================================================================
1027    // Tests for AgentState::valid_transitions()
1028    // ========================================================================
1029
1030    #[test]
1031    fn test_valid_transitions_from_idle() {
1032        let state = AgentState::Idle;
1033        let transitions = state.valid_transitions();
1034        assert_eq!(transitions.len(), 2);
1035        assert!(transitions
1036            .iter()
1037            .any(|s| matches!(s, AgentState::Gathering { .. })));
1038        assert!(transitions
1039            .iter()
1040            .any(|s| matches!(s, AgentState::Delegating { .. })));
1041    }
1042
1043    #[test]
1044    fn test_valid_transitions_from_gathering() {
1045        let scope_id = ScopeId::now_v7();
1046        let state = AgentState::Gathering { scope_id };
1047        let transitions = state.valid_transitions();
1048        assert_eq!(transitions.len(), 2);
1049        assert!(transitions
1050            .iter()
1051            .any(|s| matches!(s, AgentState::Planning { .. })));
1052        assert!(transitions
1053            .iter()
1054            .any(|s| matches!(s, AgentState::Failed { .. })));
1055    }
1056
1057    #[test]
1058    fn test_valid_transitions_from_planning() {
1059        let scope_id = ScopeId::now_v7();
1060        let state = AgentState::Planning { scope_id };
1061        let transitions = state.valid_transitions();
1062        assert_eq!(transitions.len(), 2);
1063        assert!(transitions
1064            .iter()
1065            .any(|s| matches!(s, AgentState::Executing { .. })));
1066        assert!(transitions
1067            .iter()
1068            .any(|s| matches!(s, AgentState::Failed { .. })));
1069    }
1070
1071    #[test]
1072    fn test_valid_transitions_from_executing() {
1073        let scope_id = ScopeId::now_v7();
1074        let state = AgentState::Executing {
1075            scope_id,
1076            tool_call_id: "test_call".to_string(),
1077        };
1078        let transitions = state.valid_transitions();
1079        assert_eq!(transitions.len(), 3);
1080        assert!(transitions
1081            .iter()
1082            .any(|s| matches!(s, AgentState::Waiting { .. })));
1083        assert!(transitions
1084            .iter()
1085            .any(|s| matches!(s, AgentState::Complete { .. })));
1086        assert!(transitions
1087            .iter()
1088            .any(|s| matches!(s, AgentState::Failed { .. })));
1089    }
1090
1091    #[test]
1092    fn test_valid_transitions_from_waiting() {
1093        let state = AgentState::Waiting {
1094            for_event_type: "USER_INPUT".to_string(),
1095        };
1096        let transitions = state.valid_transitions();
1097        assert_eq!(transitions.len(), 2);
1098        assert!(transitions
1099            .iter()
1100            .any(|s| matches!(s, AgentState::Executing { .. })));
1101        assert!(transitions
1102            .iter()
1103            .any(|s| matches!(s, AgentState::Failed { .. })));
1104    }
1105
1106    #[test]
1107    fn test_valid_transitions_from_delegating() {
1108        let state = AgentState::Delegating {
1109            to: AgentId::now_v7(),
1110            delegation_id: DelegationId::now_v7(),
1111        };
1112        let transitions = state.valid_transitions();
1113        assert_eq!(transitions.len(), 2);
1114        assert!(transitions.iter().any(|s| matches!(s, AgentState::Idle)));
1115        assert!(transitions
1116            .iter()
1117            .any(|s| matches!(s, AgentState::Failed { .. })));
1118    }
1119
1120    #[test]
1121    fn test_valid_transitions_from_complete() {
1122        let state = AgentState::Complete {
1123            scope_id: ScopeId::now_v7(),
1124        };
1125        let transitions = state.valid_transitions();
1126        assert_eq!(transitions.len(), 1);
1127        assert!(transitions.iter().any(|s| matches!(s, AgentState::Idle)));
1128    }
1129
1130    #[test]
1131    fn test_valid_transitions_from_failed() {
1132        let state = AgentState::Failed {
1133            scope_id: ScopeId::now_v7(),
1134            error: "Error".to_string(),
1135        };
1136        let transitions = state.valid_transitions();
1137        assert_eq!(transitions.len(), 1);
1138        assert!(transitions.iter().any(|s| matches!(s, AgentState::Idle)));
1139    }
1140}