1use crate::{AgentLoop, Outcome};
12use harness_core::{Guide, HarnessError, Model, Sensor, SubagentStatus, Task, Tool, World};
13use std::sync::Arc;
14
15pub struct SubagentSpec {
17 pub name: String,
18 pub task: Task,
19 pub tools: Vec<Arc<dyn Tool>>,
20 pub guides: Vec<Arc<dyn Guide>>,
21 pub sensors: Vec<Arc<dyn Sensor>>,
22 pub max_iters: u32,
23}
24
25impl SubagentSpec {
26 pub fn new(name: impl Into<String>, task: Task) -> Self {
27 Self {
28 name: name.into(),
29 task,
30 tools: Vec::new(),
31 guides: Vec::new(),
32 sensors: Vec::new(),
33 max_iters: 12,
34 }
35 }
36
37 pub fn with_tool(mut self, t: Arc<dyn Tool>) -> Self {
38 self.tools.push(t);
39 self
40 }
41
42 pub fn with_guide(mut self, g: Arc<dyn Guide>) -> Self {
43 self.guides.push(g);
44 self
45 }
46
47 pub fn with_sensor(mut self, s: Arc<dyn Sensor>) -> Self {
48 self.sensors.push(s);
49 self
50 }
51
52 pub fn with_max_iters(mut self, n: u32) -> Self {
53 self.max_iters = n;
54 self
55 }
56}
57
58#[derive(Debug, Clone)]
60pub struct SubagentReport {
61 pub name: String,
62 pub status: SubagentStatus,
63 pub text: Option<String>,
64 pub iters: u32,
65 pub usage: harness_core::Usage,
69}
70
71pub struct Subagent<M: Model> {
73 pub spec: SubagentSpec,
74 pub loop_: AgentLoop<M>,
75}
76
77impl<M: Model> Subagent<M> {
78 pub fn new(model: M, spec: SubagentSpec) -> Self {
79 let mut loop_ = AgentLoop::new(model);
80 for t in &spec.tools {
81 loop_ = loop_.with_tool(t.clone());
82 }
83 for g in &spec.guides {
84 loop_ = loop_.with_guide(g.clone());
85 }
86 for s in &spec.sensors {
87 loop_ = loop_.with_sensor(s.clone());
88 }
89 Self { spec, loop_ }
90 }
91
92 pub async fn run(self, world: &mut World) -> Result<SubagentReport, HarnessError> {
93 let name = self.spec.name.clone();
94 let max = self.spec.max_iters;
95 let task = self.spec.task.clone();
96 let outcome = self.loop_.run_with_max_iters(task, world, max).await?;
97 let report = match outcome {
98 Outcome::Done {
99 text, iters, usage, ..
100 } => SubagentReport {
101 name,
102 status: SubagentStatus::Done,
103 text,
104 iters,
105 usage,
106 },
107 Outcome::BudgetExhausted {
108 iters,
109 last_text,
110 usage,
111 ..
112 } => SubagentReport {
113 name,
114 status: SubagentStatus::Blocked,
115 text: last_text,
116 iters,
117 usage,
118 },
119 };
120 tracing::info!(
121 subagent = %report.name,
122 status = ?report.status,
123 iters = report.iters,
124 "subagent completed"
125 );
126 Ok(report)
127 }
128}