pub struct Agent {
pub id: String,
pub name: String,
pub expertise: Option<String>,
pub personality: Option<String>,
pub metadata: HashMap<String, String>,
/* private fields */
}Expand description
Represents an agent with identity, expertise, optional tool access, and event observability.
Agents are LLM-powered entities that can:
- Generate responses based on system prompts and user messages
- Access tools through a
ToolRegistry(single or multi-protocol) - Maintain per-agent conversation memory via
LLMSession - Persist findings and decisions via
MentisDb - Handle context window exhaustion via pluggable
ContextStrategy - Emit
AgentEvents for real-time observability of LLM calls and tool usage - Be orchestrated by
Orchestrationor used independently
Fields§
§id: StringStable identifier referenced inside orchestration coordination.
name: StringHuman-readable display name for logging and UI surfaces.
expertise: Option<String>Free-form description of the agent’s strengths that will be embedded into prompts.
personality: Option<String>Persona hints that help diversify the tone of generated responses.
metadata: HashMap<String, String>Arbitrary metadata associated with the agent (e.g. department, region).
Implementations§
Source§impl Agent
impl Agent
Sourcepub fn new(
id: impl Into<String>,
name: impl Into<String>,
client: Arc<dyn ClientWrapper>,
) -> Self
pub fn new( id: impl Into<String>, name: impl Into<String>, client: Arc<dyn ClientWrapper>, ) -> Self
Create a new agent with the mandatory identity information.
Internally creates an LLMSession with the provided client, an empty
system prompt, and a 128k token budget. Tools default to an empty
ToolRegistry and the context strategy defaults to TrimStrategy.
Sourcepub fn with_expertise(self, expertise: impl Into<String>) -> Self
pub fn with_expertise(self, expertise: impl Into<String>) -> Self
Attach a brief description of the agent’s domain expertise.
Sourcepub fn with_personality(self, personality: impl Into<String>) -> Self
pub fn with_personality(self, personality: impl Into<String>) -> Self
Attach a personality descriptor used to diversify prompts.
Sourcepub fn with_metadata(
self,
key: impl Into<String>,
value: impl Into<String>,
) -> Self
pub fn with_metadata( self, key: impl Into<String>, value: impl Into<String>, ) -> Self
Add arbitrary metadata to the agent definition.
Sourcepub fn with_max_tokens(self, max_tokens: usize) -> Self
pub fn with_max_tokens(self, max_tokens: usize) -> Self
Override the default token budget (builder pattern).
Recreates the internal LLMSession with the new budget while keeping
the same client. History is reset (the session starts empty).
§Example
use cloudllm::Agent;
use cloudllm::clients::openai::OpenAIClient;
use std::sync::Arc;
let agent = Agent::new(
"a1", "Agent",
Arc::new(OpenAIClient::new_with_model_string("key", "gpt-4o")),
)
.with_max_tokens(32_000); // 32k instead of the default 128kSourcepub fn with_tools(self, registry: ToolRegistry) -> Self
pub fn with_tools(self, registry: ToolRegistry) -> Self
Grant the agent access to a registry of tools.
Takes ownership of the registry and wraps it in Arc<RwLock<_>>.
§Example: Single Protocol
let registry = ToolRegistry::new(
Arc::new(CustomToolProtocol::new())
);
agent.with_tools(registry);§Example: Multiple Protocols
let mut registry = ToolRegistry::empty();
registry.add_protocol("local", Arc::new(local_protocol)).await?;
registry.add_protocol("youtube", Arc::new(youtube_mcp)).await?;
agent.with_tools(registry);Share a mutable tool registry across multiple agents.
This allows runtime mutations (add/remove protocols) to be visible to all agents sharing the same registry. Use this when agents in an orchestration need to see the same tool set and react to hot-swaps.
§Example
use cloudllm::Agent;
use cloudllm::tool_protocol::ToolRegistry;
use cloudllm::clients::openai::OpenAIClient;
use std::sync::Arc;
use tokio::sync::RwLock;
let shared = Arc::new(RwLock::new(ToolRegistry::empty()));
let client = Arc::new(OpenAIClient::new_with_model_string("key", "gpt-4o"));
let agent_a = Agent::new("a", "Agent A", client.clone())
.with_shared_tools(shared.clone());
let agent_b = Agent::new("b", "Agent B", client)
.with_shared_tools(shared.clone());
// Adding a protocol via agent_a is visible to agent_bSourcepub fn with_grok_tools(self, grok_tools: Vec<GrokTool>) -> Self
pub fn with_grok_tools(self, grok_tools: Vec<GrokTool>) -> Self
Forward xAI server-side tools (web_search, x_search, etc.) to the underlying client. Only supported by Grok clients.
Sourcepub fn with_openai_tools(self, openai_tools: Vec<OpenAITool>) -> Self
pub fn with_openai_tools(self, openai_tools: Vec<OpenAITool>) -> Self
Forward OpenAI server-side tools (web_search, file_search, code_interpreter) to the underlying client. Only supported by OpenAI clients.
Sourcepub fn context_collapse_strategy(
self,
strategy: Box<dyn ContextStrategy>,
) -> Self
pub fn context_collapse_strategy( self, strategy: Box<dyn ContextStrategy>, ) -> Self
Set the context collapse strategy (builder pattern).
The strategy determines when and how the agent compacts its conversation
history. See the context_strategy module
for available implementations.
§Example
use cloudllm::Agent;
use cloudllm::context_strategy::{NoveltyAwareStrategy, SelfCompressionStrategy};
use cloudllm::clients::openai::OpenAIClient;
use std::sync::Arc;
let agent = Agent::new(
"a1", "Agent",
Arc::new(OpenAIClient::new_with_model_string("key", "gpt-4o")),
)
.context_collapse_strategy(Box::new(
NoveltyAwareStrategy::new(Box::new(SelfCompressionStrategy::default()))
));Sourcepub fn set_context_collapse_strategy(
&mut self,
strategy: Box<dyn ContextStrategy>,
)
pub fn set_context_collapse_strategy( &mut self, strategy: Box<dyn ContextStrategy>, )
Replace the context collapse strategy at runtime.
Unlike context_collapse_strategy
(which consumes self), this takes &mut self so the strategy can be
swapped on a live agent.
Sourcepub fn with_mentisdb(self, chain: Arc<RwLock<MentisDb>>) -> Self
pub fn with_mentisdb(self, chain: Arc<RwLock<MentisDb>>) -> Self
Attach a MentisDb for persistent memory (builder pattern).
Once attached, the agent can record findings and decisions via
commit, and context strategies like
SelfCompressionStrategy
will persist compression summaries to the chain automatically.
The chain is wrapped in Arc<RwLock<_>> so it can be shared across
forked agents or accessed concurrently.
§Example
use cloudllm::Agent;
use mentisdb::MentisDb;
use cloudllm::clients::openai::OpenAIClient;
use std::sync::Arc;
use std::path::PathBuf;
use tokio::sync::RwLock;
let chain = Arc::new(RwLock::new(
MentisDb::open(
&PathBuf::from("chains"), "a1", "Agent", Some("ML"), None,
).unwrap()
));
let agent = Agent::new(
"a1", "Agent",
Arc::new(OpenAIClient::new_with_model_string("key", "gpt-4o")),
)
.with_mentisdb(chain);Sourcepub fn with_event_handler(self, handler: Arc<dyn EventHandler>) -> Self
pub fn with_event_handler(self, handler: Arc<dyn EventHandler>) -> Self
Attach an EventHandler that will receive lifecycle events (builder pattern).
The handler receives AgentEvents for LLM calls, tool usage, thought
commits, protocol mutations, fork operations, and session changes.
When this agent is added to an Orchestration
via add_agent(), the orchestration’s handler (if any) will override this one.
§Example
use cloudllm::Agent;
use cloudllm::event::{AgentEvent, EventHandler};
use cloudllm::clients::openai::OpenAIClient;
use async_trait::async_trait;
use std::sync::Arc;
struct MyHandler;
#[async_trait]
impl EventHandler for MyHandler {
async fn on_agent_event(&self, event: &AgentEvent) {
println!("{:?}", event);
}
}
let agent = Agent::new("a1", "Agent", Arc::new(
OpenAIClient::new_with_model_string("key", "gpt-4o"),
))
.with_event_handler(Arc::new(MyHandler));Sourcepub fn set_event_handler(&mut self, handler: Arc<dyn EventHandler>)
pub fn set_event_handler(&mut self, handler: Arc<dyn EventHandler>)
Set or replace the event handler at runtime.
Unlike with_event_handler (which consumes self
in the builder chain), this takes &mut self so the handler can be attached
to a live agent. Used internally by Orchestration::add_agent
to propagate the orchestration’s handler to each agent.
Sourcepub async fn add_protocol(
&self,
name: &str,
protocol: Arc<dyn ToolProtocol>,
) -> Result<(), Box<dyn Error + Send + Sync>>
pub async fn add_protocol( &self, name: &str, protocol: Arc<dyn ToolProtocol>, ) -> Result<(), Box<dyn Error + Send + Sync>>
Add a new tool protocol at runtime.
The protocol is discovered (its tools are listed) and then registered
under name. If the agent’s tool registry is shared via
with_shared_tools, the new protocol is
immediately visible to all agents sharing the same registry.
§Example
use cloudllm::Agent;
use cloudllm::tool_protocols::CustomToolProtocol;
use cloudllm::clients::openai::OpenAIClient;
use std::sync::Arc;
let agent = Agent::new(
"a1", "Agent",
Arc::new(OpenAIClient::new_with_model_string("key", "gpt-4o")),
);
agent.add_protocol("custom", Arc::new(CustomToolProtocol::new())).await.unwrap();
assert!(!agent.list_tools().await.is_empty());Sourcepub async fn remove_protocol(&self, name: &str)
pub async fn remove_protocol(&self, name: &str)
Remove a tool protocol at runtime.
All tools registered under name are removed. If the protocol name
does not exist, this is a no-op.
§Example
agent.remove_protocol("custom").await;Sourcepub async fn list_tools(&self) -> Vec<String>
pub async fn list_tools(&self) -> Vec<String>
List all tool names currently available to this agent.
Returns the name of every tool across all registered protocols.
§Example
let tools = agent.list_tools().await;
for name in &tools {
println!("Available: {}", name);
}Sourcepub async fn commit(
&self,
entry_type: ThoughtType,
content: impl Into<String>,
) -> Result<()>
pub async fn commit( &self, entry_type: ThoughtType, content: impl Into<String>, ) -> Result<()>
Append a thought to this agent’s MentisDb.
This is a convenience wrapper that acquires a write lock on the chain
and calls MentisDb::append. If no chain is attached, the call
is a silent no-op.
§Example
use cloudllm::Agent;
use mentisdb::{MentisDb, ThoughtType};
use cloudllm::clients::openai::OpenAIClient;
use std::sync::Arc;
use std::path::PathBuf;
use tokio::sync::RwLock;
let chain = Arc::new(RwLock::new(
MentisDb::open(&PathBuf::from("/tmp/ch"), "a1", "Agent", None, None).unwrap()
));
let agent = Agent::new(
"a1", "Agent",
Arc::new(OpenAIClient::new_with_model_string("key", "gpt-4o")),
).with_mentisdb(chain);
agent.commit(ThoughtType::Finding, "Latency increased 3x").await.unwrap();
agent.commit(ThoughtType::Decision, "Enable caching").await.unwrap();
let entries = agent.thought_entries().await.unwrap();
assert_eq!(entries.len(), 2);Sourcepub async fn thought_entries(&self) -> Option<Vec<Thought>>
pub async fn thought_entries(&self) -> Option<Vec<Thought>>
Return a snapshot of all thoughts in this agent’s chain.
Returns None if no MentisDb is attached, or Some(vec) with
cloned thoughts otherwise.
Sourcepub fn resume_from_chain(
id: impl Into<String>,
name: impl Into<String>,
client: Arc<dyn ClientWrapper>,
max_tokens: usize,
chain: Arc<RwLock<MentisDb>>,
thought_index: u64,
) -> Result<Self, Box<dyn Error + Send + Sync>>
pub fn resume_from_chain( id: impl Into<String>, name: impl Into<String>, client: Arc<dyn ClientWrapper>, max_tokens: usize, chain: Arc<RwLock<MentisDb>>, thought_index: u64, ) -> Result<Self, Box<dyn Error + Send + Sync>>
Resume an agent from a specific thought in an existing chain.
Resolves the context graph at thought_index via
MentisDb::resolve_context and injects the resulting bootstrap
prompt into a fresh LLMSession. The agent starts with the
critical reasoning context already in its history, ready to continue
where it left off.
§Example
use cloudllm::Agent;
use mentisdb::{MentisDb, ThoughtType};
use cloudllm::clients::openai::OpenAIClient;
use std::sync::Arc;
use std::path::PathBuf;
use tokio::sync::RwLock;
// Assume a chain was previously populated
let chain = Arc::new(RwLock::new(
MentisDb::open(
&PathBuf::from("chains"), "a1", "Agent", Some("ML"), None,
).unwrap()
));
let agent = Agent::resume_from_chain(
"a1", "Agent",
Arc::new(OpenAIClient::new_with_model_string("key", "gpt-4o")),
128_000,
chain,
5, // resume from thought #5
).unwrap();Sourcepub fn resume_from_latest(
id: impl Into<String>,
name: impl Into<String>,
client: Arc<dyn ClientWrapper>,
max_tokens: usize,
chain: Arc<RwLock<MentisDb>>,
) -> Result<Self, Box<dyn Error + Send + Sync>>
pub fn resume_from_latest( id: impl Into<String>, name: impl Into<String>, client: Arc<dyn ClientWrapper>, max_tokens: usize, chain: Arc<RwLock<MentisDb>>, ) -> Result<Self, Box<dyn Error + Send + Sync>>
Resume an agent from the latest thought in an existing chain.
Convenience wrapper around resume_from_chain
that automatically targets the last thought in the chain.
§Example
use cloudllm::Agent;
use mentisdb::MentisDb;
use cloudllm::clients::openai::OpenAIClient;
use std::sync::Arc;
use std::path::PathBuf;
use tokio::sync::RwLock;
let chain = Arc::new(RwLock::new(
MentisDb::open(
&PathBuf::from("chains"), "a1", "Agent", None, None,
).unwrap()
));
let agent = Agent::resume_from_latest(
"a1", "Agent",
Arc::new(OpenAIClient::new_with_model_string("key", "gpt-4o")),
128_000,
chain,
).unwrap();Sourcepub fn fork(&self) -> Self
pub fn fork(&self) -> Self
Create a lightweight copy for parallel execution.
The fork shares the same tool registry and thought chain (via Arc)
but has a fresh, empty LLMSession backed by the same client.
Identity fields (id, name, expertise, personality, metadata)
are cloned. The context strategy is reset to TrimStrategy since
forked agents are typically short-lived.
This replaces Clone — Agent is intentionally not Clone because
cloning a full LLMSession (with its bumpalo arena) would be expensive
and semantically misleading.
§Example
use cloudllm::Agent;
use cloudllm::clients::openai::OpenAIClient;
use std::sync::Arc;
let agent = Agent::new(
"analyst", "Analyst",
Arc::new(OpenAIClient::new_with_model_string("key", "gpt-4o")),
).with_expertise("Cloud Architecture");
// Fork for parallel execution — identity is preserved
let forked = agent.fork();
assert_eq!(forked.id, agent.id);
assert_eq!(forked.expertise, agent.expertise);Sourcepub fn fork_with_context(&self) -> Self
pub fn fork_with_context(&self) -> Self
Create a lightweight copy that also carries forward session context.
Like fork, the clone shares tool registry and thought
chain via Arc, but additionally copies the current system prompt and
conversation history into the new session. Use this when a parallel
task needs the accumulated context (e.g., later rounds of an
orchestration).
Sourcepub fn set_system_prompt(&mut self, base_prompt: &str)
pub fn set_system_prompt(&mut self, base_prompt: &str)
Set the agent’s LLMSession system prompt, augmented with expertise and personality.
Called by orchestration modes during setup so each agent has its system prompt configured once before generation begins.
Sourcepub fn receive_message(&mut self, role: Role, content: String)
pub fn receive_message(&mut self, role: Role, content: String)
Inject a message into this agent’s session history without sending to the LLM.
Used by orchestration hub-routing to feed specific messages (e.g., other
agents’ responses) into this agent’s context before calling send.
Sourcepub fn session_history_len(&self) -> usize
pub fn session_history_len(&self) -> usize
Return the number of messages in this agent’s session history.
Useful for orchestration to check whether the agent has been initialized.
Sourcepub async fn send(
&mut self,
user_message: &str,
) -> Result<AgentResponse, Box<dyn Error + Send + Sync>>
pub async fn send( &mut self, user_message: &str, ) -> Result<AgentResponse, Box<dyn Error + Send + Sync>>
Send a message using the agent’s own session history.
This is the primary method used by orchestration modes. Unlike
generate_with_tokens which takes an
external conversation history, this method relies on the session’s
accumulated messages (populated via [receive_message] and prior
send calls). The session handles system prompt, history, and
auto-trimming automatically.
§Tool Loop
After the initial LLM call, the method checks whether the response
contains a tool call ({"tool_call": {"name": "...", "parameters": {...}}}).
If so, the tool is executed via the ToolRegistry, the result is
fed back into the session as a follow-up message, and the LLM is
called again. This loop runs for up to 5 iterations.
§Events Emitted
The following AgentEvents are emitted during send() (in order):
SendStarted— at entryLLMCallStarted— before each LLM callLLMCallCompleted— after each LLM callToolCallDetected— when a tool call is parsedToolExecutionCompleted— after tool executionToolMaxIterationsReached— if the loop cap is hitSendCompleted— at exit
Sourcepub fn client(&self) -> &Arc<dyn ClientWrapper>
pub fn client(&self) -> &Arc<dyn ClientWrapper>
Borrow the underlying ClientWrapper from the session.
Useful for creating new sessions or agents that share the same LLM provider connection.
§Example
use cloudllm::Agent;
use cloudllm::LLMSession;
use cloudllm::clients::openai::OpenAIClient;
use std::sync::Arc;
let agent = Agent::new(
"a1", "Agent",
Arc::new(OpenAIClient::new_with_model_string("key", "gpt-4o")),
);
// Create a standalone session sharing the same provider
let session = LLMSession::new(
agent.client().clone(),
"system prompt".into(),
8_192,
);Sourcepub async fn generate_with_tokens(
&self,
system_prompt: &str,
user_message: &str,
conversation_history: &[OrchestrationMessage],
) -> Result<AgentResponse, Box<dyn Error + Send + Sync>>
pub async fn generate_with_tokens( &self, system_prompt: &str, user_message: &str, conversation_history: &[OrchestrationMessage], ) -> Result<AgentResponse, Box<dyn Error + Send + Sync>>
Send a message to the backing model and capture the response plus token usage.
Unlike send which uses the agent’s own session, this
method takes an explicit system prompt and conversation history. Each
call builds a fresh message array — there is no session state carried
between calls. This makes it suitable for one-shot interactions or
when you manage conversation history externally.
The tool loop (up to 5 iterations) works identically to send() and
emits the same AgentEvents.
§Parameters
system_prompt— Base system prompt (augmented with the agent’s expertise and personality automatically).user_message— The user’s question or instruction.conversation_history— Prior messages to include as context.
§Returns
An AgentResponse containing the final text and cumulative token usage.
Sourcepub async fn generate(
&self,
system_prompt: &str,
user_message: &str,
conversation_history: &[OrchestrationMessage],
) -> Result<String, Box<dyn Error + Send + Sync>>
pub async fn generate( &self, system_prompt: &str, user_message: &str, conversation_history: &[OrchestrationMessage], ) -> Result<String, Box<dyn Error + Send + Sync>>
Convenience wrapper around generate_with_tokens
that discards token-usage data and returns only the response text.
Useful for quick one-shot interactions where you don’t need to track token consumption. All events are still emitted normally.