arbiter_engine/agent.rs
1//! The agent module contains the core agent abstraction for the Arbiter Engine.
2
3use std::{fmt::Debug, sync::Arc};
4
5use arbiter_core::middleware::ArbiterMiddleware;
6use serde::{de::DeserializeOwned, Serialize};
7
8use super::*;
9use crate::{
10 machine::{Behavior, Engine, StateMachine},
11 messager::Messager,
12};
13
14/// An agent is an entity capable of processing events and producing actions.
15/// These are the core actors in simulations or in onchain systems.
16/// Agents can be connected of other agents either as a dependent, or a
17/// dependency.
18///
19/// # How it works
20/// When the [`World`] that owns the [`Agent`] is ran, it has each [`Agent`] run
21/// each of its [`Behavior`]s `startup()` methods. The [`Behavior`]s themselves
22/// will return a stream of events that then let the [`Behavior`] move into the
23/// `State::Processing` stage.
24#[derive(Debug)]
25pub struct Agent {
26 /// Identifier for this agent.
27 /// Used for routing messages.
28 pub id: String,
29
30 /// The messager the agent uses to send and receive messages from other
31 /// agents.
32 pub messager: Messager,
33
34 /// The client the agent uses to interact with the blockchain.
35 pub client: Arc<ArbiterMiddleware>,
36
37 /// The engines/behaviors that the agent uses to sync, startup, and process
38 /// events.
39 pub(crate) behavior_engines: Vec<Box<dyn StateMachine>>,
40}
41
42impl Agent {
43 /// Creates a new [`AgentBuilder`] instance with a specified identifier.
44 ///
45 /// This method initializes an [`AgentBuilder`] with the provided `id` and
46 /// sets the `behavior_engines` field to `None`. The returned
47 /// [`AgentBuilder`] can be further configured using its methods before
48 /// finalizing the creation of an [`Agent`].
49 ///
50 /// # Arguments
51 ///
52 /// * `id` - A string slice that holds the identifier for the agent being
53 /// built.
54 ///
55 /// # Returns
56 ///
57 /// Returns an [`AgentBuilder`] instance that can be used to configure and
58 /// build an [`Agent`].
59 pub fn builder(id: &str) -> AgentBuilder {
60 AgentBuilder {
61 id: id.to_owned(),
62 behavior_engines: None,
63 }
64 }
65}
66
67/// [`AgentBuilder`] represents the intermediate state of agent creation before
68/// it is converted into a full on [`Agent`]
69pub struct AgentBuilder {
70 /// Identifier for this agent.
71 /// Used for routing messages.
72 pub id: String,
73 /// The engines/behaviors that the agent uses to sync, startup, and process
74 /// events.
75 behavior_engines: Option<Vec<Box<dyn StateMachine>>>,
76}
77
78impl AgentBuilder {
79 /// Appends a behavior onto an [`AgentBuilder`]. Behaviors are initialized
80 /// when the agent builder is added to the [`crate::world::World`]
81 pub fn with_behavior<E: DeserializeOwned + Serialize + Send + Sync + Debug + 'static>(
82 mut self,
83 behavior: impl Behavior<E> + 'static,
84 ) -> Self {
85 let engine = Engine::new(behavior);
86 if let Some(engines) = &mut self.behavior_engines {
87 engines.push(Box::new(engine));
88 } else {
89 self.behavior_engines = Some(vec![Box::new(engine)]);
90 };
91 self
92 }
93
94 /// Adds a state machine engine to the agent builder.
95 ///
96 /// This method allows for the addition of a custom state machine engine to
97 /// the agent's behavior engines. If the agent builder already has some
98 /// engines, the new engine is appended to the list. If no engines are
99 /// present, a new list is created with the provided engine as its first
100 /// element.
101 ///
102 /// # Parameters
103 ///
104 /// - `engine`: The state machine engine to be added to the agent builder.
105 /// This engine must
106 /// implement the `StateMachine` trait and is expected to be provided as a
107 /// boxed trait object to allow for dynamic dispatch.
108 ///
109 /// # Returns
110 ///
111 /// Returns the `AgentBuilder` instance to allow for method chaining.
112 pub(crate) fn with_engine(mut self, engine: Box<dyn StateMachine>) -> Self {
113 if let Some(engines) = &mut self.behavior_engines {
114 engines.push(engine);
115 } else {
116 self.behavior_engines = Some(vec![engine]);
117 };
118 self
119 }
120
121 /// Constructs and returns a new [`Agent`] instance using the provided
122 /// `client` and `messager`.
123 ///
124 /// This method finalizes the building process of an [`Agent`] by taking
125 /// ownership of the builder, and attempting to construct an `Agent`
126 /// with the accumulated configurations and the provided `client` and
127 /// `messager`. The `client` is an [`Arc<RevmMiddleware>`] that represents
128 /// the connection to the blockchain or environment, and `messager` is a
129 /// communication layer for the agent.
130 ///
131 /// # Parameters
132 ///
133 /// - `client`: A shared [`Arc<RevmMiddleware>`] instance that provides the
134 /// agent with access to the blockchain or environment.
135 /// - `messager`: A [`Messager`] instance for the agent to communicate with
136 /// other agents or systems.
137 ///
138 /// # Returns
139 ///
140 /// Returns a `Result` that, on success, contains the newly created
141 /// [`Agent`] instance. On failure, it returns an
142 /// [`AgentBuildError::MissingBehaviorEngines`] error indicating that the
143 /// agent was attempted to be built without any behavior engines
144 /// configured.
145 ///
146 /// # Examples
147 ///
148 /// ```ignore
149 /// let agent_builder = AgentBuilder::new("agent_id");
150 /// let client = Arc::new(RevmMiddleware::new(...));
151 /// let messager = Messager::new(...);
152 /// let agent = agent_builder.build(client, messager).expect("Failed to build agent");
153 /// ```
154 pub fn build(
155 self,
156 client: Arc<ArbiterMiddleware>,
157 messager: Messager,
158 ) -> Result<Agent, ArbiterEngineError> {
159 match self.behavior_engines {
160 Some(engines) => Ok(Agent {
161 id: self.id,
162 messager,
163 client,
164 behavior_engines: engines,
165 }),
166 None => Err(ArbiterEngineError::AgentBuildError(
167 "Missing behavior engines".to_owned(),
168 )),
169 }
170 }
171}