Skip to main content

Agent

Struct Agent 

Source
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 Orchestration or used independently

Fields§

§id: String

Stable identifier referenced inside orchestration coordination.

§name: String

Human-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

Source

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.

Source

pub fn with_expertise(self, expertise: impl Into<String>) -> Self

Attach a brief description of the agent’s domain expertise.

Source

pub fn with_personality(self, personality: impl Into<String>) -> Self

Attach a personality descriptor used to diversify prompts.

Source

pub fn with_metadata( self, key: impl Into<String>, value: impl Into<String>, ) -> Self

Add arbitrary metadata to the agent definition.

Source

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 128k
Source

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);
Source

pub fn with_shared_tools(self, registry: Arc<RwLock<ToolRegistry>>) -> Self

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_b
Source

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.

Source

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.

Source

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()))
));
Source

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.

Source

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);
Source

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));
Source

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.

Source

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());
Source

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;
Source

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);
}
Source

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);
Source

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.

Source

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();
Source

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();
Source

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 CloneAgent 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);
Source

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).

Source

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.

Source

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.

Source

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.

Source

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):

  1. SendStarted — at entry
  2. LLMCallStarted — before each LLM call
  3. LLMCallCompleted — after each LLM call
  4. ToolCallDetected — when a tool call is parsed
  5. ToolExecutionCompleted — after tool execution
  6. ToolMaxIterationsReached — if the loop cap is hit
  7. SendCompleted — at exit
Source

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,
);
Source

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.

Source

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.

Trait Implementations§

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more