ceylon_core/
agent.rs

1use crate::error::Result;
2use crate::message::Message;
3use crate::request_queue::RequestQueue;
4use async_trait::async_trait;
5use std::sync::Arc;
6
7/// The core trait that defines an autonomous agent.
8///
9/// Agents are the primary unit of computation in Ceylon. Each agent:
10/// - Has a unique name for identification and message routing
11/// - Receives lifecycle callbacks (`on_start`, `on_stop`)
12/// - Processes messages via `on_message` or `on_generic_message`
13/// - Optionally exposes tools via `tool_invoker`
14///
15/// # Implementing an Agent
16///
17/// ```rust,no_run
18/// use ceylon_core::{Agent, AgentContext, Message};
19/// use ceylon_core::error::Result;
20/// use async_trait::async_trait;
21///
22/// struct CounterAgent {
23///     count: u32,
24/// }
25///
26/// #[async_trait]
27/// impl Agent for CounterAgent {
28///     fn name(&self) -> String {
29///         "counter".to_string()
30///     }
31///
32///     async fn on_start(&mut self, _ctx: &mut AgentContext) -> Result<()> {
33///         println!("Counter starting with count: {}", self.count);
34///         Ok(())
35///     }
36///
37///     async fn on_message(&mut self, msg: Message, ctx: &mut AgentContext) -> Result<()> {
38///         self.count += 1;
39///         if let Some(id) = msg.correlation_id() {
40///             ctx.report_result(&id, format!("Count: {}", self.count));
41///         }
42///         Ok(())
43///     }
44/// }
45/// ```
46#[async_trait]
47pub trait Agent: Send + Sync {
48    /// Returns the unique name of this agent.
49    ///
50    /// This name is used for message routing and identification within a mesh.
51    fn name(&self) -> String;
52
53    /// Called when the agent starts.
54    ///
55    /// Use this for initialization logic. The mesh is running but other agents
56    /// may not have started yet.
57    async fn on_start(&mut self, _ctx: &mut AgentContext) -> Result<()> {
58        Ok(())
59    }
60
61    /// Called when a binary message is received.
62    ///
63    /// # Arguments
64    /// * `msg` - The received message with binary payload
65    /// * `ctx` - The agent context for mesh interaction
66    async fn on_message(&mut self, _msg: Message, _ctx: &mut AgentContext) -> Result<()> {
67        Ok(())
68    }
69
70    /// Handle a generic string message and return a response.
71    ///
72    /// Default implementation echoes the content back.
73    async fn on_generic_message(
74        &mut self,
75        msg: crate::message::GenericMessage,
76        _ctx: &mut AgentContext,
77    ) -> Result<crate::message::GenericResponse> {
78        Ok(crate::message::GenericResponse::new(msg.content))
79    }
80
81    /// Called when the agent is stopping.
82    ///
83    /// Use this for cleanup logic.
84    async fn on_stop(&mut self, _ctx: &mut AgentContext) -> Result<()> {
85        Ok(())
86    }
87
88    /// Get the tool invoker for this agent (if it has actions).
89    ///
90    /// Return `Some` if this agent exposes tools that can be called by LLMs.
91    fn tool_invoker(&self) -> Option<&crate::action::ToolInvoker> {
92        None
93    }
94
95    /// Get mutable tool invoker for dynamic tool registration.
96    fn tool_invoker_mut(&mut self) -> Option<&mut crate::action::ToolInvoker> {
97        None
98    }
99}
100
101/// Runtime context provided to agents during lifecycle and message handling.
102///
103/// The context gives agents access to mesh information and utilities
104/// for interacting with the request/response system.
105///
106/// # Example
107///
108/// ```rust,no_run
109/// # use ceylon_core::{Agent, AgentContext, Message};
110/// # use ceylon_core::error::Result;
111/// # use async_trait::async_trait;
112/// # struct MyAgent;
113/// # #[async_trait] impl Agent for MyAgent {
114/// #     fn name(&self) -> String { "my".into() }
115/// async fn on_message(&mut self, msg: Message, ctx: &mut AgentContext) -> Result<()> {
116///     println!("Running in mesh: {}", ctx.mesh_name);
117///     if let Some(id) = msg.correlation_id() {
118///         ctx.report_result(&id, "Done!".to_string());
119///     }
120///     Ok(())
121/// }
122/// # }
123/// ```
124pub struct AgentContext {
125    /// The name of the mesh this agent is running in.
126    pub mesh_name: String,
127    request_queue: Option<Arc<RequestQueue>>,
128}
129
130impl AgentContext {
131    /// Create a new agent context.
132    ///
133    /// # Arguments
134    /// * `mesh_name` - Name of the mesh
135    /// * `request_queue` - Optional request queue for request/response pattern
136    pub fn new(mesh_name: String, request_queue: Option<Arc<RequestQueue>>) -> Self {
137        Self {
138            mesh_name,
139            request_queue,
140        }
141    }
142
143    /// Report a result for a pending request.
144    ///
145    /// Call this to send a response back to the requester when handling
146    /// a message with a correlation ID.
147    pub fn report_result(&self, request_id: &str, response: String) {
148        if let Some(queue) = &self.request_queue {
149            queue.complete(request_id, response);
150        }
151    }
152
153    /// Check if this context has a request queue available.
154    pub fn has_request_queue(&self) -> bool {
155        self.request_queue.is_some()
156    }
157}