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