telos_agent/orchestration/subagent/
mod.rs1pub mod builtins;
4pub mod definition;
5pub mod fork;
6pub mod registry;
7mod tool;
8pub mod worktree;
9
10use std::sync::Arc;
11
12use crate::config::AgentConfig;
13use crate::error::AgentError;
14use crate::model::provider::ModelProvider;
15use crate::tools::api::ToolRegistry;
16
17pub use definition::{AgentDefinition, AgentIsolation, AgentSource};
18pub use fork::{ForkExecution, ForkLens, ForkResult, ForkShared, Synapse};
19pub use registry::SubagentRegistry;
20pub use tool::SubagentTool;
21pub use worktree::{WorktreeInfo, create_subagent_worktree};
22
23pub fn register_subagent_tool(
25 tools: &mut ToolRegistry,
26 provider: Arc<dyn ModelProvider + Send + Sync>,
27 config: &AgentConfig,
28) -> Result<(), AgentError> {
29 let mut subagents = SubagentRegistry::with_builtin_agents();
30 if let Some(plugin_registry) = &config.plugin_registry
31 && let Err(errors) = plugin_registry.apply_subagents(&mut subagents)
32 {
33 for error in errors {
34 tracing::warn!(error = %error, "failed to load plugin subagent component");
35 }
36 }
37
38 tools.register(SubagentTool::with_registry(provider, tools.clone(), config.clone(), subagents));
39 Ok(())
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45 use crate::integrations::plugin::{PluginId, PluginRegistry};
46 use crate::model::mock::MockProvider;
47 use crate::tools::api::ToolContext;
48 use serde_json::json;
49
50 #[tokio::test]
51 async fn register_subagent_tool_loads_plugin_subagents() {
52 let temp = tempfile::tempdir().unwrap();
53 let plugin_dir = temp.path().join("installed").join("agent-plugin@mkt");
54 std::fs::create_dir_all(plugin_dir.join("agents")).unwrap();
55 std::fs::write(
56 plugin_dir.join("plugin.json"),
57 serde_json::to_string_pretty(&json!({
58 "name": "agent-plugin",
59 "version": "1.0.0",
60 "agents": ["./agents/auditor.md"]
61 }))
62 .unwrap(),
63 )
64 .unwrap();
65 std::fs::write(
66 plugin_dir.join("agents").join("auditor.md"),
67 r#"---
68name: auditor
69description: Audit plugin-provided behavior.
70tools: [Read]
71---
72You audit plugin behavior.
73"#,
74 )
75 .unwrap();
76
77 let mut plugins = PluginRegistry::new(temp.path());
78 plugins.discover_installed().unwrap();
79 plugins.enable(&PluginId::parse("agent-plugin@mkt").unwrap()).unwrap();
80
81 let config =
82 AgentConfig { plugin_registry: Some(Arc::new(plugins)), ..AgentConfig::default() };
83 let mut tools = ToolRegistry::new();
84 register_subagent_tool(&mut tools, Arc::new(MockProvider::new(vec![])), &config).unwrap();
85
86 let subagent = tools.get("subagent").unwrap();
87 subagent
88 .validate(
89 &json!({
90 "prompt": "audit",
91 "subagent_type": "agent-plugin:auditor"
92 }),
93 &ToolContext::dummy(),
94 )
95 .await
96 .unwrap();
97 }
98
99 #[tokio::test]
100 async fn register_subagent_tool_keeps_valid_plugin_subagents_when_one_fails() {
101 let temp = tempfile::tempdir().unwrap();
102 let plugin_dir = temp.path().join("installed").join("agent-plugin@mkt");
103 std::fs::create_dir_all(plugin_dir.join("agents")).unwrap();
104 std::fs::write(
105 plugin_dir.join("plugin.json"),
106 serde_json::to_string_pretty(&json!({
107 "name": "agent-plugin",
108 "version": "1.0.0",
109 "agents": ["./agents/auditor.md", "./agents/bad.md"]
110 }))
111 .unwrap(),
112 )
113 .unwrap();
114 std::fs::write(
115 plugin_dir.join("agents").join("auditor.md"),
116 r#"---
117name: auditor
118description: Audit plugin-provided behavior.
119tools: [Read]
120---
121You audit plugin behavior.
122"#,
123 )
124 .unwrap();
125 std::fs::write(
126 plugin_dir.join("agents").join("bad.md"),
127 r#"---
128name: bad
129tools: [Read]
130---
131Missing required description.
132"#,
133 )
134 .unwrap();
135
136 let mut plugins = PluginRegistry::new(temp.path());
137 plugins.discover_installed().unwrap();
138 plugins.enable(&PluginId::parse("agent-plugin@mkt").unwrap()).unwrap();
139
140 let config =
141 AgentConfig { plugin_registry: Some(Arc::new(plugins)), ..AgentConfig::default() };
142 let mut tools = ToolRegistry::new();
143 register_subagent_tool(&mut tools, Arc::new(MockProvider::new(vec![])), &config).unwrap();
144
145 let subagent = tools.get("subagent").unwrap();
146 subagent
147 .validate(
148 &json!({
149 "prompt": "audit",
150 "subagent_type": "agent-plugin:auditor"
151 }),
152 &ToolContext::dummy(),
153 )
154 .await
155 .unwrap();
156 }
157}