2026-07-13 08:12:02 +07:00
|
|
|
//! `AgentDefinition` -- declarative specification for instantiating a
|
2026-07-12 11:28:39 +07:00
|
|
|
//! subagent from workflow scripts or programmatic calls.
|
2026-07-17 09:03:37 +07:00
|
|
|
//!
|
|
|
|
|
//! Also provides a shared [`spawn_subagent_with_drain`] helper that
|
|
|
|
|
//! eliminates the channel-creation + drain-thread boilerplate duplicated
|
|
|
|
|
//! across `auto/mod.rs`, `review/mod.rs`, and `workflow/engine/mod.rs`.
|
|
|
|
|
|
|
|
|
|
use super::event::SubagentEvent;
|
2026-07-11 13:16:10 +07:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
2026-07-12 11:28:39 +07:00
|
|
|
/// Declarative specification for instantiating a subagent: name, role,
|
|
|
|
|
/// optional system prompt, allowed tools, step budget, and temperature.
|
2026-07-11 13:16:10 +07:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct AgentDefinition {
|
|
|
|
|
pub name: String,
|
|
|
|
|
pub role: String,
|
|
|
|
|
pub system_prompt: Option<String>,
|
|
|
|
|
pub allowed_tools: Option<Vec<String>>,
|
|
|
|
|
pub max_steps: Option<usize>,
|
|
|
|
|
pub temperature: Option<f32>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AgentDefinition {
|
2026-07-12 11:28:39 +07:00
|
|
|
/// Create an agent definition with the required name and role; all
|
|
|
|
|
/// optional fields start as `None`.
|
2026-07-11 13:16:10 +07:00
|
|
|
pub fn new(name: String, role: String) -> Self {
|
|
|
|
|
AgentDefinition {
|
|
|
|
|
name,
|
|
|
|
|
role,
|
|
|
|
|
system_prompt: None,
|
|
|
|
|
allowed_tools: None,
|
|
|
|
|
max_steps: None,
|
|
|
|
|
temperature: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-13 04:41:26 +07:00
|
|
|
/// Builder method: set the system prompt for this agent.
|
|
|
|
|
pub fn with_system_prompt(mut self, prompt: String) -> Self {
|
|
|
|
|
self.system_prompt = Some(prompt);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Builder method: set the allowed tool list for this agent.
|
|
|
|
|
pub fn with_allowed_tools(mut self, tools: Vec<String>) -> Self {
|
|
|
|
|
self.allowed_tools = Some(tools);
|
|
|
|
|
self
|
|
|
|
|
}
|
2026-07-16 12:32:17 +07:00
|
|
|
|
|
|
|
|
/// Builder method: set the maximum step count for this agent.
|
|
|
|
|
pub fn with_max_steps(mut self, steps: usize) -> Self {
|
|
|
|
|
self.max_steps = Some(steps);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-11 13:16:10 +07:00
|
|
|
}
|
2026-07-17 09:03:37 +07:00
|
|
|
|
|
|
|
|
/// Shared subagent spawning utility: creates an mpsc channel and spawns a
|
|
|
|
|
/// drain thread that forwards every [`SubagentEvent`] to `on_event`.
|
|
|
|
|
///
|
|
|
|
|
/// Returns the sender half (for passing to [`run_subagent`](super::engine::run_subagent))
|
|
|
|
|
/// and the drain thread's join handle so the caller can keep it alive for
|
|
|
|
|
/// the duration of the subagent run.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
///
|
|
|
|
|
/// ```ignore
|
|
|
|
|
/// let (tx, _drain) = spawn_subagent_with_drain(|event| {
|
|
|
|
|
/// match &event {
|
|
|
|
|
/// SubagentEvent::ToolCall { tool, .. } => tracing::debug!("tool: {tool}"),
|
|
|
|
|
/// SubagentEvent::Completed => tracing::debug!("done"),
|
|
|
|
|
/// _ => {}
|
|
|
|
|
/// }
|
|
|
|
|
/// });
|
|
|
|
|
/// let verdict = run_subagent(&ctx, &tx)?;
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// # Duplication eliminated
|
|
|
|
|
///
|
|
|
|
|
/// Previously every subagent caller inlined the same 5-line pattern:
|
|
|
|
|
///
|
|
|
|
|
/// ```ignore
|
|
|
|
|
/// let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
|
|
|
|
/// let _drain = std::thread::spawn(move || {
|
|
|
|
|
/// while let Some(event) = rx.blocking_recv() { ... }
|
|
|
|
|
/// });
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// Callers that need a larger buffer (e.g. workflow engine uses 64) should
|
|
|
|
|
/// create the channel manually instead of using this helper.
|
|
|
|
|
pub fn spawn_subagent_with_drain<F>(
|
|
|
|
|
on_event: F,
|
|
|
|
|
) -> (tokio::sync::mpsc::Sender<SubagentEvent>, std::thread::JoinHandle<()>)
|
|
|
|
|
where
|
|
|
|
|
F: Fn(SubagentEvent) + Send + 'static,
|
|
|
|
|
{
|
|
|
|
|
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
|
|
|
|
let drain = std::thread::spawn(move || {
|
|
|
|
|
while let Some(event) = rx.blocking_recv() {
|
|
|
|
|
on_event(event);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
(tx, drain)
|
|
|
|
|
}
|