Skip to content

Commit d2ebeb3

Browse files
committed
adds subagent spawn context
1 parent da42d4e commit d2ebeb3

File tree

5 files changed

+134
-12
lines changed

5 files changed

+134
-12
lines changed

crates/agent/src/agent/mod.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ use types::{
127127
AgentSnapshot,
128128
ConversationMetadata,
129129
ConversationState,
130+
EmbeddedUserMessages,
130131
};
131132
use util::path::canonicalize_path_sys;
132133
use util::providers::{
@@ -222,6 +223,7 @@ pub struct Agent {
222223
id: AgentId,
223224
agent_config: AgentConfig,
224225

226+
embedded_user_msgs: EmbeddedUserMessages,
225227
conversation_state: ConversationState,
226228
conversation_metadata: ConversationMetadata,
227229
execution_state: ExecutionState,
@@ -303,6 +305,7 @@ impl Agent {
303305
Ok(Self {
304306
id: snapshot.id,
305307
agent_config,
308+
embedded_user_msgs: Default::default(),
306309
conversation_state: snapshot.conversation_state,
307310
conversation_metadata: snapshot.conversation_metadata,
308311
execution_state: snapshot.execution_state,
@@ -323,6 +326,10 @@ impl Agent {
323326
})
324327
}
325328

329+
pub fn set_embedded_user_msg(&mut self, msg: &str) {
330+
self.embedded_user_msgs.messages.push(msg.to_string());
331+
}
332+
326333
pub fn set_sys_provider(&mut self, provider: impl SystemProvider) {
327334
self.sys_provider = Arc::new(provider);
328335
}
@@ -981,6 +988,7 @@ impl Agent {
981988
self.make_tool_spec().await,
982989
&self.agent_config,
983990
self.agent_spawn_hooks.iter().map(|(_, c)| c),
991+
&self.embedded_user_msgs,
984992
&self.sys_provider,
985993
)
986994
.await
@@ -1720,6 +1728,7 @@ async fn format_request<T, U, P>(
17201728
mut tool_spec: Vec<ToolSpec>,
17211729
agent_config: &AgentConfig,
17221730
agent_spawn_hooks: T,
1731+
embedded_user_msgs: &EmbeddedUserMessages,
17231732
provider: &P,
17241733
) -> SendRequestArgs
17251734
where
@@ -1734,10 +1743,20 @@ where
17341743
messages.push_front(msg);
17351744
}
17361745

1746+
let system_prompt = match (agent_config.system_prompt(), embedded_user_msgs.messages.is_empty()) {
1747+
(Some(system_prompt), false) => {
1748+
let embedded_user_msgs_as_str = embedded_user_msgs.to_string();
1749+
Some(format!("{embedded_user_msgs_as_str}\n{system_prompt}"))
1750+
},
1751+
(Some(system_prompt), true) => Some(system_prompt.to_string()),
1752+
(None, false) => Some(embedded_user_msgs.to_string()),
1753+
(_, _) => None,
1754+
};
1755+
17371756
SendRequestArgs::new(
17381757
messages.into(),
17391758
if tool_spec.is_empty() { None } else { Some(tool_spec) },
1740-
agent_config.system_prompt().map(String::from),
1759+
system_prompt,
17411760
)
17421761
}
17431762

crates/agent/src/agent/tools/summary.rs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::protocol::AgentEvent;
1919

2020
/// A tool for conveying information from subagent to its main agent
2121
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
22-
/// A tool for conveying information from subagent to its main agent
22+
#[serde(rename_all = "camelCase")]
2323
pub struct Summary {
2424
/// Description of the task that was assigned to the subagent
2525
pub task_description: String,
@@ -41,19 +41,41 @@ HOW TO USE:
4141
- Provide the result of the task performed
4242
"#;
4343

44+
const SUMMARY_TOOL_SCHEMA: &str = r#"
45+
{
46+
"type": "object",
47+
"properties": {
48+
"taskDescription": {
49+
"type": "string",
50+
"description": "Description of the task that was assigned to the subagent"
51+
},
52+
"contextSummary": {
53+
"type": "string",
54+
"description": "Relevant context and information gathered during task execution"
55+
},
56+
"taskResult": {
57+
"type": "string",
58+
"description": "The final result or outcome of the completed task"
59+
}
60+
},
61+
"required": [
62+
"taskDescription",
63+
"taskResult"
64+
]
65+
}
66+
"#;
67+
4468
impl BuiltInToolTrait for Summary {
4569
fn name() -> super::BuiltInToolName {
46-
BuiltInToolName::Ls
70+
BuiltInToolName::Summary
4771
}
4872

4973
fn description() -> std::borrow::Cow<'static, str> {
5074
SUMMARY_TOOL_DESCRIPTION.into()
5175
}
5276

5377
fn input_schema() -> std::borrow::Cow<'static, str> {
54-
serde_json::to_string(&Self::tool_schema())
55-
.expect("serializing schema should not fail")
56-
.into()
78+
SUMMARY_TOOL_SCHEMA.into()
5779
}
5880
}
5981

@@ -75,6 +97,12 @@ impl Summary {
7597
mod tests {
7698
use super::*;
7799

100+
#[test]
101+
fn test_summary_tool_schema() {
102+
let schema = Summary::input_schema();
103+
println!("{:#?}", schema);
104+
}
105+
78106
#[tokio::test]
79107
async fn test_summary_tool_execute() {
80108
let (tx, mut rx) = broadcast::channel(10);

crates/agent/src/agent/types.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,52 @@ impl Default for AgentSettings {
143143
}
144144
}
145145

146+
pub struct DecoratedEmbeddedUserMessagesIterator<'a> {
147+
pub inner_iter: std::slice::Iter<'a, String>,
148+
}
149+
150+
impl<'a> Iterator for DecoratedEmbeddedUserMessagesIterator<'a> {
151+
type Item = String;
152+
153+
fn next(&mut self) -> Option<Self::Item> {
154+
self.inner_iter.next().map(|message| {
155+
format!(
156+
"{}{message}\n{}",
157+
EmbeddedUserMessages::START_HEADER,
158+
EmbeddedUserMessages::END_HEADER
159+
)
160+
})
161+
}
162+
}
163+
164+
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
165+
pub struct EmbeddedUserMessages {
166+
pub messages: Vec<String>,
167+
}
168+
169+
impl EmbeddedUserMessages {
170+
const END_HEADER: &str = "--- EMBEDDED USER MESSAGE SEGMENT END ---\n\n";
171+
const START_HEADER: &str = "--- EMBEDDED USER MESSAGE SEGMENT BEGIN ---\n";
172+
173+
pub fn to_string(&self) -> String {
174+
self.into_iter().fold(String::new(), |mut acc, msg| {
175+
acc.push_str(&msg);
176+
acc
177+
})
178+
}
179+
}
180+
181+
impl<'a> IntoIterator for &'a EmbeddedUserMessages {
182+
type IntoIter = DecoratedEmbeddedUserMessagesIterator<'a>;
183+
type Item = String;
184+
185+
fn into_iter(self) -> Self::IntoIter {
186+
DecoratedEmbeddedUserMessagesIterator {
187+
inner_iter: self.messages.iter(),
188+
}
189+
}
190+
}
191+
146192
/// State associated with a history of messages.
147193
#[derive(Debug, Clone, Serialize, Deserialize)]
148194
pub struct ConversationState {

crates/chat-cli/src/agent/mod.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ use crate::os::Os;
3737

3838
mod rts;
3939

40+
// TODO: use the one supplied by science (this one has been modified for testing)
41+
const SUBAGENT_EMBEDDED_USER_MSG: &str = r#"
42+
You are a subagent executing a task delegated to you by the main agent.
43+
After what is asked of you has concluded, call the summary tool to convey your findings to the main agent.
44+
"#;
45+
4046
#[derive(Debug, Clone, Serialize, Deserialize)]
4147
struct JsonOutput {
4248
/// Whether or not the user turn completed successfully
@@ -102,9 +108,14 @@ impl<'a> SubAgent<'a> {
102108
};
103109

104110
let mcp_manager_handle = McpManager::default().spawn();
105-
let agent = agent::Agent::new(snapshot, model, mcp_manager_handle).await?.spawn();
111+
let mut agent = agent::Agent::new(snapshot, model, mcp_manager_handle).await?;
112+
if let Some(msg) = self.embedded_user_msg {
113+
agent.set_embedded_user_msg(msg);
114+
}
106115

107-
self.main_loop(agent, output).await
116+
let agent_handle = agent.spawn();
117+
118+
self.main_loop(agent_handle, output).await
108119
}
109120

110121
async fn main_loop(&self, mut agent: AgentHandle, output: &mut impl Write) -> Result<QueryResult> {
@@ -165,6 +176,9 @@ impl<'a> SubAgent<'a> {
165176
AgentEvent::Mcp(evt) => {
166177
info!(?evt, "received mcp agent event");
167178
},
179+
AgentEvent::SubagentSummary(summary) => {
180+
println!("Summary: {:#?}", summary);
181+
},
168182
_ => {},
169183
}
170184
}
@@ -203,7 +217,7 @@ async fn test_sub_agent_routine() {
203217
let sub_agent = SubAgent {
204218
query: "What notion docs do I have",
205219
agent_name: Some("test_test"),
206-
embedded_user_msg: None,
220+
embedded_user_msg: Some(SUBAGENT_EMBEDDED_USER_MSG),
207221
dangerously_trust_all_tools: true,
208222
};
209223

crates/chat-cli/src/agent/rts/mod.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ impl RtsModel {
201201
&self,
202202
mut messages: Vec<Message>,
203203
tool_specs: Option<Vec<ToolSpec>>,
204-
_system_prompt: Option<String>,
204+
system_prompt: Option<String>,
205205
) -> Result<ConversationState, String> {
206206
debug!(?messages, ?tool_specs, "creating conversation state");
207207
let tools = tool_specs.map(|v| {
@@ -235,7 +235,22 @@ impl RtsModel {
235235
None => return Err("Empty conversation".to_string()),
236236
};
237237

238-
let history = messages
238+
let mut history = Vec::<rts::ChatMessage>::new();
239+
if let Some(system_prompt) = system_prompt {
240+
history.push(rts::ChatMessage::UserInputMessage(UserInputMessage {
241+
content: system_prompt,
242+
user_input_message_context: None,
243+
user_intent: None,
244+
images: None,
245+
model_id: None,
246+
}));
247+
history.push(rts::ChatMessage::AssistantResponseMessage(rts::AssistantResponseMessage {
248+
message_id: None,
249+
content: "I will fully incorporate this information when generating my responses, and explicitly acknowledge relevant parts of the summary when answering questions.".to_string(),
250+
tool_uses: None }));
251+
}
252+
253+
history.append(&mut messages
239254
.into_iter()
240255
.map(|m| match m.role {
241256
Role::User => {
@@ -269,7 +284,7 @@ impl RtsModel {
269284
rts::ChatMessage::AssistantResponseMessage(msg)
270285
},
271286
})
272-
.collect();
287+
.collect());
273288

274289
Ok(ConversationState {
275290
conversation_id: Some(self.conversation_id.to_string()),

0 commit comments

Comments
 (0)