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}