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}