Skip to main content

acton_core/actor/managed_agent/
idle.rs

1/*
2 * Copyright (c) 2024. Govcraft
3 *
4 * Licensed under either of
5 *   * Apache License, Version 2.0 (the "License");
6 *     you may not use this file except in compliance with the License.
7 *     You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8 *   * MIT license: http://opensource.org/licenses/MIT
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the applicable License for the specific language governing permissions and
14 * limitations under that License.
15 */
16
17use std::any::TypeId;
18
19use crate::traits::ActonMessageReply;
20use std::fmt::Debug;
21use std::future::Future;
22use std::mem;
23
24use acton_ern::Ern;
25use tokio::sync::mpsc::channel;
26use tracing::*;
27
28use crate::actor::{AgentConfig, ManagedAgent, Started};
29use crate::common::{
30    AgentHandle, AgentRuntime, Envelope, FutureBox, OutboundEnvelope, ReactorItem,
31};
32use crate::message::MessageContext;
33use crate::prelude::ActonMessage;
34use crate::traits::AgentHandleInterface;
35
36/// Type-state marker for a [`ManagedAgent`] that has been configured but not yet started.
37///
38/// When a `ManagedAgent` is in the `Idle` state, it can be configured with message handlers
39/// (via [`ManagedAgent::mutate_on`]) and lifecycle hooks (e.g., [`ManagedAgent::before_start`],
40/// [`ManagedAgent::after_stop`]). Once configuration is complete, the agent can be
41/// transitioned to the [`Started`](super::started::Started) state by calling [`ManagedAgent::start`].
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] // Add common derives
43pub struct Idle;
44
45
46impl<State: Default + Send + Debug + 'static> ManagedAgent<Idle, State> {
47    /// Registers an asynchronous message handler for a specific message type `M`.
48    ///
49    /// This method is called during the agent's configuration phase (while in the `Idle` state).
50    /// It associates a specific message type `M` with a closure (`message_processor`) that
51    /// will be executed when the agent receives a message of that type after it has started.
52    ///
53    /// The framework handles the necessary type erasure and downcasting internally. The
54    /// provided `message_processor` receives the agent (in the `Started` state) and a
55    /// [`MessageContext`] containing the concrete message and metadata.
56    ///
57    /// # Type Parameters
58    ///
59    /// *   `M`: The concrete message type this handler will process. Must implement
60    ///     [`ActonMessage`], `Clone`, `Send`, `Sync`, and be `'static`.
61    ///
62    /// # Arguments
63    ///
64    /// *   `message_processor`: An asynchronous closure that takes the agent (`&mut ManagedAgent<Started, State>`)
65    ///     and the message context (`&mut MessageContext<M>`) and returns a `Future`
66    ///     (specifically, a [`FutureBox`]). This closure contains the logic for handling messages of type `M`.
67    ///
68    /// # Returns
69    ///
70    /// Returns a mutable reference to `self` to allow for method chaining during configuration.
71    #[instrument(skip(self, message_processor), level = "debug")]
72    pub fn mutate_on<M>(
73        &mut self,
74        message_processor: impl for<'a> Fn(&'a mut ManagedAgent<Started, State>, &'a mut MessageContext<M>) -> FutureBox
75            + Send
76            + Sync
77            + 'static,
78    ) -> &mut Self
79    where
80        M: ActonMessage + Clone + Send + Sync + 'static,
81    {
82        let type_id = TypeId::of::<M>();
83        trace!(type_name=std::any::type_name::<M>(),type_id=?type_id, " Adding mutable message handler");
84        let handler_box = Box::new(
85            move |actor: &mut ManagedAgent<Started, State>, envelope: &mut Envelope| -> FutureBox {
86                if let Some(concrete_msg) = downcast_message::<M>(&*envelope.message) {
87                    trace!(
88                        "Downcast successful for message type: {}",
89                        std::any::type_name::<M>()
90                    );
91                    let mut msg_context = {
92                        let origin_envelope = OutboundEnvelope::new_with_recipient(
93                            envelope.reply_to.clone(),
94                            envelope.recipient.clone(),
95                            actor.handle.cancellation_token.clone(),
96                        );
97                        let reply_envelope = OutboundEnvelope::new_with_recipient(
98                            envelope.recipient.clone(),
99                            envelope.reply_to.clone(),
100                            actor.handle.cancellation_token.clone(),
101                        );
102                        MessageContext {
103                            message: concrete_msg.clone(),
104                            timestamp: envelope.timestamp,
105                            origin_envelope,
106                            reply_envelope,
107                        }
108                    };
109                    message_processor(actor, &mut msg_context)
110                } else {
111                    error!(
112                        type_name = std::any::type_name::<M>(),
113                        "Message handler called with incompatible message type (downcast failed)"
114                    );
115                    Box::pin(async {})
116                }
117            },
118        );
119        self.message_handlers
120            .insert(type_id, ReactorItem::FutureReactor(handler_box));
121        self
122    }
123
124    /// Registers an asynchronous error handler for a specific error type `E`.
125    ///
126    /// This allows the agent to handle errors of type `E` by executing the given closure
127    /// whenever a message handler returns an error of this type.
128    ///
129    /// # Type Parameters
130    ///
131    /// * `E`: The concrete error type to handle. Must implement `std::error::Error` and be `'static`.
132    ///
133    /// # Arguments
134    /// * `error_handler`: The handler closure executed with agent, envelope, and error reference.
135    ///
136    /// # Returns
137    /// A mutable reference to `self` for chaining.
138    pub fn on_error<M, E>(
139        &mut self,
140        error_handler: impl for<'a, 'b> Fn(
141                &'a mut ManagedAgent<Started, State>,
142                &'b mut MessageContext<M>,
143                &'b E,
144            ) -> crate::common::FutureBox
145            + Send
146            + Sync
147            + 'static,
148    ) -> &mut Self
149    where
150        M: ActonMessage + Clone + Send + Sync + 'static,
151        E: std::error::Error + 'static,
152    {
153        use std::any::TypeId;
154        let message_type_id = TypeId::of::<M>();
155        let error_type_id = TypeId::of::<E>();
156
157        // Wrap handler for dynamic dispatch
158        let handler_box: Box<crate::common::ErrorHandler<State>> =
159            Box::new(move |agent, envelope, err| {
160                if let Some(concrete_msg) = downcast_message::<M>(&*envelope.message) {
161                    // Downcast the error to &E
162                    if let Some(specific_err) = err.downcast_ref::<E>() {
163                        let mut msg_context = {
164                            let origin_envelope = OutboundEnvelope::new_with_recipient(
165                                envelope.reply_to.clone(),
166                                envelope.recipient.clone(),
167                                agent.handle.cancellation_token.clone(),
168                            );
169                            let reply_envelope = OutboundEnvelope::new_with_recipient(
170                                envelope.recipient.clone(),
171                                envelope.reply_to.clone(),
172                                agent.handle.cancellation_token.clone(),
173                            );
174                            MessageContext {
175                                message: concrete_msg.clone(),
176                                timestamp: envelope.timestamp,
177                                origin_envelope,
178                                reply_envelope,
179                            }
180                        };
181                        error_handler(agent, &mut msg_context, specific_err)
182                    } else {
183                        // If type doesn't match, do nothing
184                        Box::pin(async {})
185                    }
186                } else {
187                    // If type doesn't match, do nothing
188                    Box::pin(async {})
189                }
190            });
191        self.error_handler_map
192            .insert((message_type_id, error_type_id), handler_box);
193        self
194    }
195
196    /// Registers an asynchronous read-only message handler for a specific message type `M`.
197    ///
198    /// This method is called during the agent's configuration phase (while in the `Idle` state).
199    /// It associates a specific message type `M` with a closure (`message_processor`) that
200    /// will be executed when the agent receives a message of that type after it has started.
201    ///
202    /// Unlike `mutate_on`, handlers registered with `act_on` operate on an immutable reference
203    /// to the agent (`&ManagedAgent`) and can be executed concurrently with other read-only handlers.
204    /// Message ordering is not guaranteed for read-only handlers.
205    ///
206    /// # Type Parameters
207    ///
208    /// *   `M`: The concrete message type this handler will process. Must implement
209    ///     [`ActonMessage`], `Clone`, `Send`, `Sync`, and be `'static`.
210    ///
211    /// # Arguments
212    ///
213    /// *   `message_processor`: An asynchronous closure that takes the agent (`&ManagedAgent<Started, State>`)
214    ///     and the message context (`&mut MessageContext<M>`) and returns a `Future`
215    ///     (specifically, a [`FutureBox`]). This closure contains the logic for handling messages of type `M`.
216    ///
217    /// # Returns
218    ///
219    /// Returns a mutable reference to `self` to allow for method chaining during configuration.
220    #[instrument(skip(self, message_processor), level = "debug")]
221    pub fn act_on<M>(
222        &mut self,
223        message_processor: impl for<'a> Fn(&'a ManagedAgent<Started, State>, &'a mut MessageContext<M>) -> FutureBox
224            + Send
225            + Sync
226            + 'static,
227    ) -> &mut Self
228    where
229        M: ActonMessage + Clone + Send + Sync + 'static,
230    {
231        let type_id = TypeId::of::<M>();
232        trace!(type_name=std::any::type_name::<M>(),type_id=?type_id, " Adding read-only message handler");
233        let handler_box = Box::new(
234            move |actor: &ManagedAgent<Started, State>, envelope: &mut Envelope| -> FutureBox {
235                if let Some(concrete_msg) = downcast_message::<M>(&*envelope.message) {
236                    trace!(
237                        "Downcast successful for message type: {}",
238                        std::any::type_name::<M>()
239                    );
240                    let mut msg_context = {
241                        let origin_envelope = OutboundEnvelope::new_with_recipient(
242                            envelope.reply_to.clone(),
243                            envelope.recipient.clone(),
244                            actor.handle.cancellation_token.clone(),
245                        );
246                        let reply_envelope = OutboundEnvelope::new_with_recipient(
247                            envelope.recipient.clone(),
248                            envelope.reply_to.clone(),
249                            actor.handle.cancellation_token.clone(),
250                        );
251                        MessageContext {
252                            message: concrete_msg.clone(),
253                            timestamp: envelope.timestamp,
254                            origin_envelope,
255                            reply_envelope,
256                        }
257                    };
258                    message_processor(actor, &mut msg_context)
259                } else {
260                    error!(
261                        type_name = std::any::type_name::<M>(),
262                        "Read-only message handler called with incompatible message type (downcast failed)"
263                    );
264                    Box::pin(async {})
265                }
266            },
267        );
268        self.read_only_handlers
269            .insert(type_id, ReactorItem::FutureReactorReadOnly(handler_box));
270        self
271    }
272    /// Registers an asynchronous read-only message handler for a specific message type `M` that returns a Result.
273    ///
274    /// This method is called during the agent's configuration phase (while in the `Idle` state).
275    /// It associates a specific message type `M` with a closure (`message_processor`) that
276    /// will be executed when the agent receives a message of that type after it has started.
277    ///
278    /// Unlike `mutate_on_fallible`, handlers registered with `act_on_fallible` operate on an immutable reference
279    /// to the agent (`&ManagedAgent`) and can be executed concurrently with other read-only handlers.
280    /// Message ordering is not guaranteed for read-only handlers.
281    ///
282    /// # Type Parameters
283    ///
284    /// *   `M`: The concrete message type this handler will process. Must implement
285    ///     [`ActonMessage`], `Clone`, `Send`, `Sync`, and be `'static`.
286    /// *   `T`: The success type returned by the handler. Must implement [`ActonMessageReply`] and be `'static`.
287    /// *   `E`: The error type returned by the handler. Must implement [`std::error::Error`] and be `'static`.
288    ///
289    /// # Arguments
290    ///
291    /// *   `message_processor`: An asynchronous closure that takes the agent (`&ManagedAgent<Started, State>`)
292    ///     and the message context (`&mut MessageContext<M>`) and returns a `Future`
293    ///     (specifically, a [`FutureBoxResult`]). This closure contains the logic for handling messages of type `M`.
294    ///
295    /// # Returns
296    ///
297    /// Returns a mutable reference to `self` to allow for method chaining during configuration.
298    #[instrument(skip(self, message_processor), level = "debug")]
299    pub fn act_on_fallible<M, T, E>(
300        &mut self,
301        message_processor: impl for<'a> Fn(
302                &'a ManagedAgent<Started, State>,
303                &'a mut MessageContext<M>,
304            ) -> std::pin::Pin<
305                Box<dyn std::future::Future<Output = Result<T, E>> + Send + Sync + 'static>,
306            > + Send
307            + Sync
308            + 'static,
309    ) -> &mut Self
310    where
311        M: ActonMessage + Clone + Send + Sync + 'static,
312        T: ActonMessageReply + 'static,
313        E: std::error::Error + Send + Sync + 'static,
314    {
315        let type_id = TypeId::of::<M>();
316        trace!(type_name=std::any::type_name::<M>(),type_id=?type_id, " Adding read-only Result-returning message handler");
317        let handler_box = Box::new(
318            move |actor: &ManagedAgent<Started, State>,
319                  envelope: &mut Envelope|
320                  -> crate::common::FutureBoxResult {
321                if let Some(concrete_msg) = downcast_message::<M>(&*envelope.message) {
322                    trace!(
323                        "Downcast successful for message type: {}",
324                        std::any::type_name::<M>()
325                    );
326                    let mut msg_context = {
327                        let origin_envelope = OutboundEnvelope::new_with_recipient(
328                            envelope.reply_to.clone(),
329                            envelope.recipient.clone(),
330                            actor.handle.cancellation_token.clone(),
331                        );
332                        let reply_envelope = OutboundEnvelope::new_with_recipient(
333                            envelope.recipient.clone(),
334                            envelope.reply_to.clone(),
335                            actor.handle.cancellation_token.clone(),
336                        );
337                        MessageContext {
338                            message: concrete_msg.clone(),
339                            timestamp: envelope.timestamp,
340                            origin_envelope,
341                            reply_envelope,
342                        }
343                    };
344                    let fut = message_processor(actor, &mut msg_context);
345                    Box::pin(async move {
346                        match fut.await {
347                            Ok(val) => Ok(Box::new(val) as Box<dyn ActonMessageReply + Send>),
348                            Err(e) => {
349                                let error_type_id = TypeId::of::<E>();
350                                Err((
351                                    Box::new(e) as Box<dyn std::error::Error + Send + Sync>,
352                                    error_type_id,
353                                ))
354                            }
355                        }
356                    })
357                } else {
358                    error!(
359                        type_name = std::any::type_name::<M>(),
360                        "Read-only Result handler called with incompatible message type (downcast failed)"
361                    );
362                    Box::pin(async { Ok(Box::new(()) as Box<dyn ActonMessageReply + Send>) })
363                }
364            },
365        );
366        self.read_only_handlers
367            .insert(type_id, ReactorItem::FutureReactorReadOnlyResult(handler_box));
368        self
369    }
370
371    /// Registers an asynchronous message handler for a specific message type `M` that returns a Result (new style, preferred).
372    pub fn mutate_on_fallible<M, T, E>(
373        &mut self,
374        message_processor: impl for<'a> Fn(
375                &'a mut ManagedAgent<Started, State>,
376                &'a mut MessageContext<M>,
377            ) -> std::pin::Pin<
378                Box<dyn std::future::Future<Output = Result<T, E>> + Send + Sync + 'static>,
379            > + Send
380            + Sync
381            + 'static,
382    ) -> &mut Self
383    where
384        M: ActonMessage + Clone + Send + Sync + 'static,
385        T: ActonMessageReply + 'static,
386        E: std::error::Error + Send + Sync + 'static,
387    {
388        let type_id = TypeId::of::<M>();
389        trace!(type_name=std::any::type_name::<M>(),type_id=?type_id, " Adding Result-returning message handler");
390        let handler_box = Box::new(
391            move |actor: &mut ManagedAgent<Started, State>,
392                  envelope: &mut Envelope|
393                  -> crate::common::FutureBoxResult {
394                if let Some(concrete_msg) = downcast_message::<M>(&*envelope.message) {
395                    trace!(
396                        "Downcast successful for message type: {}",
397                        std::any::type_name::<M>()
398                    );
399                    let mut msg_context = {
400                        let origin_envelope = OutboundEnvelope::new_with_recipient(
401                            envelope.reply_to.clone(),
402                            envelope.recipient.clone(),
403                            actor.handle.cancellation_token.clone(),
404                        );
405                        let reply_envelope = OutboundEnvelope::new_with_recipient(
406                            envelope.recipient.clone(),
407                            envelope.reply_to.clone(),
408                            actor.handle.cancellation_token.clone(),
409                        );
410                        MessageContext {
411                            message: concrete_msg.clone(),
412                            timestamp: envelope.timestamp,
413                            origin_envelope,
414                            reply_envelope,
415                        }
416                    };
417                    let fut = message_processor(actor, &mut msg_context);
418                    Box::pin(async move {
419                        match fut.await {
420                            Ok(val) => Ok(Box::new(val) as Box<dyn ActonMessageReply + Send>),
421                            Err(e) => {
422                                let error_type_id = TypeId::of::<E>();
423                                Err((
424                                    Box::new(e) as Box<dyn std::error::Error + Send + Sync>,
425                                    error_type_id,
426                                ))
427                            }
428                        }
429                    })
430                } else {
431                    error!(
432                        type_name = std::any::type_name::<M>(),
433                        "Result handler called with incompatible message type (downcast failed)"
434                    );
435                    Box::pin(async { Ok(Box::new(()) as Box<dyn ActonMessageReply + Send>) })
436                }
437            },
438        );
439        self.message_handlers
440            .insert(type_id, ReactorItem::FutureReactorResult(handler_box));
441        self
442    }
443
444    /// Registers an asynchronous hook to be executed *after* the agent successfully starts its message loop.
445    ///
446    /// This hook is called once, shortly after the agent transitions to the `Started` state
447    /// and its main task begins processing messages. It receives an immutable reference
448    /// to the agent in the `Started` state.
449    ///
450    /// # Arguments
451    ///
452    /// * `f`: An asynchronous closure that takes `&ManagedAgent<Started, State>` and returns a `Future`.
453    ///
454    /// # Returns
455    ///
456    /// Returns a mutable reference to `self` for chaining.
457    pub fn after_start<F, Fut>(&mut self, f: F) -> &mut Self
458    where
459        F: for<'b> Fn(&'b ManagedAgent<Started, State>) -> Fut + Send + Sync + 'static,
460        Fut: Future<Output = ()> + Send + Sync + 'static,
461    {
462        self.after_start = Box::new(move |agent| Box::pin(f(agent)) as FutureBox);
463        self
464    }
465
466    /// Registers an asynchronous hook to be executed *before* the agent starts its message loop.
467    ///
468    /// This hook is called once, just before the agent's main task (`wake`) is spawned
469    /// during the `start` process. It receives an immutable reference to the agent,
470    /// technically still in the `Started` state contextually, though the loop hasn't begun.
471    ///
472    /// # Arguments
473    ///
474    /// * `f`: An asynchronous closure that takes `&ManagedAgent<Started, State>` and returns a `Future`.
475    ///
476    /// # Returns
477    ///
478    /// Returns a mutable reference to `self` for chaining.
479    pub fn before_start<F, Fut>(&mut self, f: F) -> &mut Self
480    where
481        F: for<'b> Fn(&'b ManagedAgent<Started, State>) -> Fut + Send + Sync + 'static,
482        Fut: Future<Output = ()> + Send + Sync + 'static,
483    {
484        self.before_start = Box::new(move |agent| Box::pin(f(agent)) as FutureBox);
485        self
486    }
487
488    /// Registers an asynchronous hook to be executed *after* the agent stops processing messages.
489    ///
490    /// This hook is called once when the agent's main loop terminates gracefully (e.g., upon
491    /// receiving a `Terminate` signal or when the inbox closes). It receives an immutable
492    /// reference to the agent in the `Started` state context.
493    ///
494    /// # Arguments
495    ///
496    /// * `f`: An asynchronous closure that takes `&ManagedAgent<Started, State>` and returns a `Future`.
497    ///
498    /// # Returns
499    ///
500    /// Returns a mutable reference to `self` for chaining.
501    pub fn after_stop<F, Fut>(&mut self, f: F) -> &mut Self
502    where
503        F: for<'b> Fn(&'b ManagedAgent<Started, State>) -> Fut + Send + Sync + 'static,
504        Fut: Future<Output = ()> + Send + Sync + 'static,
505    {
506        self.after_stop = Box::new(move |agent| Box::pin(f(agent)) as FutureBox);
507        self
508    }
509
510    /// Registers an asynchronous hook to be executed *before* the agent stops processing messages.
511    ///
512    /// This hook is called once, just before the agent's main loop begins its shutdown sequence
513    /// (e.g., after receiving `Terminate` but before fully stopping). It receives an immutable
514    /// reference to the agent in the `Started` state.
515    ///
516    /// # Arguments
517    ///
518    /// * `f`: An asynchronous closure that takes `&ManagedAgent<Started, State>` and returns a `Future`.
519    ///
520    /// # Returns
521    ///
522    /// Returns a mutable reference to `self` for chaining.
523    pub fn before_stop<F, Fut>(&mut self, f: F) -> &mut Self
524    where
525        F: for<'b> Fn(&'b ManagedAgent<Started, State>) -> Fut + Send + Sync + 'static,
526        Fut: Future<Output = ()> + Send + Sync + 'static,
527    {
528        self.before_stop = Box::new(move |agent| Box::pin(f(agent)) as FutureBox);
529        self
530    }
531
532    /// Creates the configuration for a new child agent under this agent's supervision.
533    ///
534    /// This method generates a `ManagedAgent<Idle, State>` instance pre-configured
535    /// to be a child of the current agent. It automatically derives a hierarchical
536    /// [`Ern`] for the child based on the parent's ID and the provided `name`.
537    /// The child inherits the parent's broker reference.
538    ///
539    /// The returned agent is in the `Idle` state and still needs to be configured
540    /// (e.g., with `mutate_on`, lifecycle hooks) and then started using its `start` method.
541    /// The parent agent typically calls `handle.supervise(child_handle)` after the child
542    /// is started to register it formally.
543    ///
544    /// # Arguments
545    ///
546    /// * `name`: The name segment for the child agent's [`Ern`].
547    ///
548    /// # Returns
549    ///
550    /// Returns a `Result` containing a new `ManagedAgent` instance for the child
551    /// in the `Idle` state, ready for further configuration.
552    ///
553    /// # Errors
554    ///
555    /// Returns an error if creating the child's `Ern` fails or if creating the
556    /// `AgentConfig` fails (e.g., parsing the parent ID).
557    #[instrument(skip(self))]
558    pub async fn create_child(&self, name: String) -> anyhow::Result<ManagedAgent<Idle, State>> {
559        // Configure the child with parent and broker references.
560        let config = AgentConfig::new(
561            Ern::with_root(name)?,               // Child's name segment
562            Some(self.handle.clone()),           // Parent handle
563            Some(self.runtime.broker().clone()), // Inherited broker handle
564        )?;
565        // Create the Idle agent using the internal constructor.
566        Ok(ManagedAgent::new(&Some(self.runtime().clone()), Some(config)).await)
567    }
568
569    // Internal constructor - not part of public API documentation
570    #[instrument]
571    pub(crate) async fn new(runtime: &Option<AgentRuntime>, config: Option<AgentConfig>) -> Self {
572        let mut managed_actor: ManagedAgent<Idle, State> = ManagedAgent::default();
573
574        if let Some(app) = runtime {
575            managed_actor.broker = app.0.broker.clone();
576            managed_actor.handle.broker = Box::new(Some(app.0.broker.clone()));
577            managed_actor.cancellation_token = Some(app.0.cancellation_token.child_token());
578        }
579
580        if let Some(config) = &config {
581            managed_actor.handle.id = config.id();
582            managed_actor.parent = config.parent().clone();
583            managed_actor.handle.broker = Box::new(config.get_broker().clone());
584            if let Some(broker) = config.get_broker().clone() {
585                managed_actor.broker = broker;
586            }
587        }
588
589        debug_assert!(
590            !managed_actor.inbox.is_closed(),
591            "Agent mailbox is closed in new"
592        );
593
594        trace!("NEW ACTOR: {}", &managed_actor.handle.id());
595
596        // Ensure runtime always exists; creating a new one here is an error.
597        assert!(
598            runtime.is_some(),
599            "AgentRuntime must be provided to ManagedAgent::new"
600        );
601        managed_actor.runtime = runtime.clone().unwrap();
602        managed_actor
603            .runtime
604            .0
605            .roots
606            .insert(managed_actor.handle.id(), managed_actor.handle.clone());
607
608        managed_actor.id = managed_actor.handle.id();
609
610        managed_actor
611    }
612
613    /// Starts the agent's processing loop and transitions it to the `Started` state.
614    ///
615    /// This method consumes the `ManagedAgent` in the `Idle` state. It performs the following actions:
616    /// 1.  Transitions the agent's type state from `Idle` to [`Started`](super::started::Started).
617    /// 2.  Executes the registered `before_start` lifecycle hook.
618    /// 3.  Spawns the agent's main asynchronous task (`wake`) which handles message processing.
619    /// 4.  Closes the agent's `TaskTracker` to signal that the main task has been spawned.
620    /// 5.  Returns the agent's [`AgentHandle`] for external interaction.
621    ///
622    /// After this method returns, the agent is running and ready to process messages sent to its handle.
623    ///
624    /// # Returns
625    ///
626    /// An [`AgentHandle`] that can be used to interact with the now-running agent.
627    #[instrument(skip(self))]
628    pub async fn start(mut self) -> AgentHandle {
629        trace!("Starting agent: {}", self.id());
630        trace!("Model state before start: {:?}", self.model);
631
632        // Take ownership of handlers before converting state.
633        let message_handlers = mem::take(&mut self.message_handlers);
634        let read_only_handlers = mem::take(&mut self.read_only_handlers);
635        let actor_ref = self.handle.clone(); // Clone handle before consuming self.
636
637        // Convert the agent to the Started state.
638        let active_actor: ManagedAgent<Started, State> = self.into();
639        // Leak the agent into a static reference for the spawned task.
640        // The task itself is responsible for managing the agent's lifetime.
641        let actor = Box::leak(Box::new(active_actor));
642
643        trace!("Executing before_start hook for agent: {}", actor.id());
644        (actor.before_start)(actor).await; // Execute before_start hook.
645
646        trace!("Spawning main task (wake) for agent: {}", actor.id());
647        // Spawn the main message processing loop.
648        actor_ref.tracker().spawn(actor.wake(message_handlers, read_only_handlers));
649        // Close the tracker to indicate the main task is launched.
650        actor_ref.tracker().close();
651
652        trace!("Agent {} started successfully.", actor_ref.id());
653        actor_ref // Return the handle.
654    }
655}
656
657// --- Utility Function ---
658
659/// Attempts to downcast an `ActonMessage` trait object to a concrete type `T`.
660///
661/// This utility function is used internally by the message dispatch mechanism
662/// (specifically within the closure generated by `mutate_on`) to safely convert
663/// a type-erased message (`&dyn ActonMessage`) back into its original concrete type (`&T`).
664///
665/// # Type Parameters
666///
667/// * `T`: The concrete message type to attempt downcasting to. Must be `'static`
668///   and implement [`ActonMessage`].
669///
670/// # Arguments
671///
672/// * `msg`: A reference to the `ActonMessage` trait object.
673///
674/// # Returns
675///
676/// * `Some(&T)`: If the trait object `msg` actually holds a value of type `T`.
677/// * `None`: If the trait object does not hold a value of type `T`.
678pub fn downcast_message<T: ActonMessage + 'static>(msg: &dyn ActonMessage) -> Option<&T> {
679    // Use the Any trait's downcast_ref method provided via ActonMessage's supertraits.
680    msg.as_any().downcast_ref::<T>()
681}
682
683// --- Internal Implementations ---
684// (Default, From, default_handler remain internal and undocumented)
685
686impl<State: Default + Send + Debug + 'static> From<ManagedAgent<Idle, State>>
687    for ManagedAgent<Started, State>
688{
689    fn from(value: ManagedAgent<Idle, State>) -> Self {
690        // Ensure cancellation_token is always present when transitioning to Started state
691        assert!(
692            value.cancellation_token.is_some(),
693            "Cannot transition to ManagedAgent<Started, State> without a cancellation_token"
694        );
695        // Move all fields from Idle state to Started state.
696        ManagedAgent::<Started, State> {
697            handle: value.handle,
698            parent: value.parent,
699            halt_signal: value.halt_signal,
700            id: value.id,
701            runtime: value.runtime,
702            model: value.model,
703            tracker: value.tracker,
704            inbox: value.inbox,
705            before_start: value.before_start,
706            after_start: value.after_start,
707            before_stop: value.before_stop,
708            after_stop: value.after_stop,
709            broker: value.broker,
710            message_handlers: value.message_handlers,
711            read_only_handlers: value.read_only_handlers,
712            error_handler_map: value.error_handler_map, // transfer error handlers
713            cancellation_token: value.cancellation_token,
714            _actor_state: Default::default(),
715        }
716    }
717}
718
719impl<State: Default + Send + Debug + 'static> Default for ManagedAgent<Idle, State> {
720    fn default() -> Self {
721        use crate::common::config::CONFIG;
722        let capacity = CONFIG.limits.agent_inbox_capacity;
723        let (outbox, inbox) = channel(capacity);
724        let id: Ern = Default::default();
725        let mut handle: crate::common::AgentHandle = Default::default();
726        handle.id = id.clone();
727        handle.outbox = outbox.clone();
728
729        ManagedAgent::<Idle, State> {
730            handle,
731            id,
732            inbox,
733            // Initialize lifecycle hooks with default no-op handlers.
734            before_start: Box::new(|_| default_handler()),
735            after_start: Box::new(|_| default_handler()),
736            before_stop: Box::new(|_| default_handler()),
737            after_stop: Box::new(|_| default_handler()),
738            model: State::default(),
739            broker: Default::default(),
740            error_handler_map: std::collections::HashMap::new(),
741            parent: Default::default(),
742            runtime: Default::default(),
743            halt_signal: Default::default(),
744            tracker: Default::default(),
745            cancellation_token: Default::default(),
746            message_handlers: Default::default(),
747            read_only_handlers: Default::default(),
748            _actor_state: Default::default(),
749        }
750    }
751}
752
753// Default no-op async handler for lifecycle events.
754fn default_handler() -> FutureBox {
755    Box::pin(async {})
756}