enki_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 Enki. 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 enki_core::{Agent, AgentContext, Message};
19/// use enki_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 enki_core::{Agent, AgentContext, Message};
110/// # use enki_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/// ```
124use serde::{Deserialize, Serialize};
125
126/// Events specific to agent execution flow
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub enum AgentEvent {
129    MessageSent {
130        from: String,
131        to: String,
132        content: String,
133    },
134    ToolCalled {
135        agent: String,
136        tool: String,
137        input: String,
138    },
139    ToolResult {
140        agent: String,
141        tool: String,
142        result: String,
143    },
144    // Generic event for extensibility
145    Custom {
146        kind: String,
147        data: String,
148    },
149}
150
151pub struct AgentContext {
152    /// The name of the mesh this agent is running in.
153    pub mesh_name: String,
154    request_queue: Option<Arc<RequestQueue>>,
155    listeners: Vec<Arc<dyn Fn(&AgentEvent) + Send + Sync>>,
156}
157
158impl AgentContext {
159    /// Create a new agent context.
160    ///
161    /// # Arguments
162    /// * `mesh_name` - Name of the mesh
163    /// * `request_queue` - Optional request queue for request/response pattern
164    pub fn new(mesh_name: String, request_queue: Option<Arc<RequestQueue>>) -> Self {
165        Self {
166            mesh_name,
167            request_queue,
168            listeners: Vec::new(),
169        }
170    }
171
172    /// Report a result for a pending request.
173    ///
174    /// Call this to send a response back to the requester when handling
175    /// a message with a correlation ID.
176    pub fn report_result(&self, request_id: &str, response: String) {
177        if let Some(queue) = &self.request_queue {
178            queue.complete(request_id, response);
179        }
180    }
181
182    /// Check if this context has a request queue available.
183    pub fn has_request_queue(&self) -> bool {
184        self.request_queue.is_some()
185    }
186
187    /// Register an event listener.
188    pub fn listen(&mut self, callback: impl Fn(&AgentEvent) + Send + Sync + 'static) {
189        self.listeners.push(Arc::new(callback));
190    }
191
192    /// Emit an event to all listeners.
193    pub fn emit(&self, event: AgentEvent) {
194        for listener in &self.listeners {
195            listener(&event);
196        }
197    }
198}