Skip to main content

SubAgentTool

Struct SubAgentTool 

Source
pub struct SubAgentTool { /* private fields */ }
Expand description

A sub-agent as a tool: wrap another reasoning loop as a Tool; once registered into the main loop’s ToolRegistry, the model can delegate on demand during conversation.

Two forms, chosen by conversation need:

  • from_agent: persistent — holds the sub-agent instance and continues the same conversation across calls (context accumulates), suited to repeatedly consulting a “resident expert”;
  • from_factory: dynamic — each call creates a transient sub-agent through the factory, discarded when done, suited to one-shot delegation; the factory sees this call’s arguments and can freely assemble the sub-agent (type / tool subset / system prompt).

The sub-agent only needs to implement the Agent trait and be Send. On call, the model’s arguments are passed to the sub-agent as their JSON text form, and the sub-agent’s answer text is returned as the tool result to the main loop. Calls to the same tool instance are serialized (internal lock); different tool instances run in parallel.

Cancellation semantics: a sub-agent run receives no cancellation signal (tool calls have no cancellation parameter); when the main loop is cancelled, unfinished sub-agent calls abort as their future is dropped (non-cooperative), and already-recorded messages are kept.

§Examples

Any type implementing the Agent trait can be a sub-agent — this example uses a minimal application-written type (independent of the built-in loops):

use molo::agent::{Agent, AgentError, SubAgentTool};
use molo::tool::{SharedState, Tool, ToolContext};
use molo::{Message, RunContext, RunMetadata, RunOutput, RunRequest, RunSummary};
use serde_json::json;

/// Demo sub-agent: echoes the input back verbatim.
struct Echo;
#[molo::async_trait]
impl Agent for Echo {
    async fn run_request_with_context(
        &mut self,
        request: RunRequest,
        context: RunContext,
    ) -> Result<RunOutput, AgentError> {
        let answer = format!("echo: {}", input_text(request));
        Ok(run_output(context.run_id, answer))
    }
}

fn input_text(request: RunRequest) -> String {
    request.input.as_text().unwrap_or("").to_string()
}

fn run_output(run_id: String, answer: String) -> RunOutput {
    RunOutput {
        run_id,
        answer: answer.clone(),
        summary: RunSummary::default(),
        final_message: Message::assistant(answer),
        artifacts: Vec::new(),
        metadata: RunMetadata::new(),
    }
}

let tool = SubAgentTool::from_agent(
    "echo",
    "Hand the content to the echo sub-agent",
    json!({ "type": "object", "properties": { "message": { "type": "string" } } }),
    Box::new(Echo),
);

let run = RunContext::new("sub-agent-doc");
let state = SharedState::default();
let result = tool
    .call(
        json!({ "message": "Hello" }),
        ToolContext::new(&run, &state, "call-sub-agent", "echo"),
    )
    .await?;
assert_eq!(result, "echo: {\"message\":\"Hello\"}");

Implementations§

Source§

impl SubAgentTool

Source

pub fn from_agent( name: impl Into<String>, description: impl Into<String>, parameters: Value, agent: Box<dyn Agent + Send>, ) -> Self

Persistent form: holds the sub-agent instance and continues the same conversation across calls.

§Parameters
  • name / description: the name and purpose exposed to the model (the model’s basis for choosing the tool);
  • parameters: the JSON Schema for the sub-task input (prefer generating it with schemars::schema_for! from a serde struct);
  • agent: the sub-agent instance (implements the Agent trait and is Send).
Source

pub fn from_factory( name: impl Into<String>, description: impl Into<String>, parameters: Value, factory: impl Fn(Value) -> Result<Box<dyn Agent + Send>, ToolError> + Send + Sync + 'static, ) -> Self

Dynamic form: each call creates a transient sub-agent through the factory, discarded when done.

The factory receives this call’s arguments (JSON) and can assemble the sub-agent accordingly — choosing the type, trimming the tool subset, or writing a system prompt; failing to parse the arguments returns ToolError::InvalidArguments.

§Parameters
  • name / description: the name and purpose exposed to the model (the model’s basis for choosing the tool);
  • parameters: the JSON Schema for the sub-task input (prefer generating it with schemars::schema_for! from a serde struct);
  • factory: the factory run on each call, returning the new sub-agent.
Source

pub fn from_react( name: impl Into<String>, description: impl Into<String>, provider: impl Provider + Clone + 'static, tools: ToolRegistry, parameters: Value, ) -> Self

Convenience form: each call builds a standard ReAct sub-agent from the system_prompt / task fields in the model’s arguments — the model (main agent) defines the sub-agent’s system prompt and task at call time, no hand-written factory needed.

The parameters schema should include two fields (both degrade gracefully when missing): system_prompt (the sub-agent’s system prompt, empty when missing) and task (the sub-agent’s task, empty input when missing). What’s passed to the sub-agent as input is the text of the task field (not the whole argument JSON).

§Parameters
  • name / description: the name and purpose exposed to the model (the model’s basis for choosing the tool);
  • provider: the sub-agent’s provider (must be Clone — every call builds an independent loop); sharing one provider instance between the main and sub agents is fine;
  • tools: the sub-agent’s tool set;
  • parameters: the JSON Schema for the sub-task input.
§Examples
use molo::agent::SubAgentTool;
use molo::provider::{FakeProvider, FakeReply};
use molo::RunContext;
use molo::tool::{SharedState, Tool, ToolContext, ToolRegistry};
use serde_json::json;

let fake = FakeProvider::new([FakeReply::Text("sub answer".into())]);
let tool = SubAgentTool::from_react(
    "delegate",
    "Delegate a sub-task, defining the sub-agent's system prompt and task",
    fake,
    ToolRegistry::new(),
    json!({ "type": "object", "properties": {
        "system_prompt": { "type": "string" },
        "task": { "type": "string" },
    } }),
);

// The model defines the system prompt and task → a new sub-agent is
// created and run
let run = RunContext::new("sub-agent-doc");
let state = SharedState::default();
let result = tool
    .call(
        json!({ "system_prompt": "You are a reviewer", "task": "Review this code" }),
        ToolContext::new(&run, &state, "call-sub-agent", "delegate"),
    )
    .await?;
assert_eq!(result, "sub answer");

Trait Implementations§

Source§

impl Debug for SubAgentTool

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Tool for SubAgentTool

Source§

fn schema(&self) -> ToolSchema

Model perspective: this tool’s definition.
Source§

fn call<'life0, 'life1, 'async_trait>( &'life0 self, arguments: Value, _context: ToolContext<'life1>, ) -> Pin<Box<dyn Future<Output = Result<ToolResult, ToolError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Execution perspective: run this tool. Read more

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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

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: Sized + 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: Sized + 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> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

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

Source§

type Error = !

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

fn try_from(value: U) -> Result<T, !>

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