agent_engine/tools/subagent/
steer.rs1use super::super::{Tool, ToolContext};
11use crate::runtime::subagent::SubagentStatus;
12use crate::{Result, RuntimeError};
13use serde_json::{json, Value};
14
15pub struct SubagentSteerTool;
16
17#[async_trait::async_trait]
18impl Tool for SubagentSteerTool {
19 fn origin(&self) -> crate::tools::ToolOrigin {
20 crate::tools::ToolOrigin::Builtin
21 }
22
23 fn name(&self) -> &str {
24 "subagent_steer"
25 }
26
27 fn description(&self) -> &str {
28 "Inject a guidance message into a running reactive subagent. Use this to \
29 correct course, provide new context, or impose constraints mid-run without \
30 stopping the subagent. Returns {\"acknowledged\": true} on success or an \
31 error payload if the subagent is no longer running."
32 }
33
34 fn parameters(&self) -> Value {
35 json!({
36 "type": "object",
37 "properties": {
38 "handle_id": {
39 "type": "string",
40 "description": "Handle ID returned by subagent_start (e.g. \"sa_3\")."
41 },
42 "message": {
43 "type": "string",
44 "description": "Guidance message to inject into the subagent's context. \
45 Keep it concise — the subagent sees this as a mid-run \
46 user message."
47 }
48 },
49 "required": ["handle_id", "message"]
50 })
51 }
52
53 async fn execute(&self, params: Value, ctx: ToolContext) -> Result<String> {
54 let handle_id = params["handle_id"]
55 .as_str()
56 .ok_or_else(|| RuntimeError::Tool("Missing 'handle_id' parameter".to_string()))?
57 .to_string();
58
59 let message = params["message"]
60 .as_str()
61 .ok_or_else(|| RuntimeError::Tool("Missing 'message' parameter".to_string()))?
62 .to_string();
63
64 let registry = ctx.capabilities.subagent_registry.as_ref().ok_or_else(|| {
67 RuntimeError::Tool("SubagentRegistry not available on this ToolContext".to_string())
68 })?;
69
70 let reg = registry.lock().unwrap();
71
72 let handle = reg.get(&handle_id).ok_or_else(|| {
73 RuntimeError::Tool(format!("No subagent found with handle_id '{}'", handle_id))
74 })?;
75
76 if handle.status() != SubagentStatus::Running {
79 return Ok(json!({
80 "acknowledged": false,
81 "error": format!(
82 "Subagent '{}' is '{}' — steering is only possible while running.",
83 handle_id,
84 handle.status().as_str()
85 )
86 })
87 .to_string());
88 }
89
90 if let Some(orchestration) = &ctx.capabilities.orchestration {
97 orchestration
98 .steer(&handle_id)
99 .map_err(RuntimeError::Tool)?;
100 }
101 match handle.steer(&message) {
102 Ok(()) => Ok(json!({ "acknowledged": true }).to_string()),
103 Err(e) => {
104 tracing::debug!(
105 "subagent_steer: steer failed for handle '{}': {}",
106 handle_id,
107 e
108 );
109 Ok(json!({
110 "acknowledged": false,
111 "error": e
112 })
113 .to_string())
114 }
115 }
116 }
117}