Skip to main content

telos_agent/orchestration/subagent/
mod.rs

1//! Subagent module — in-process nested agents and the Fork concurrent-execution engine.
2
3pub 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
23/// Register the in-process subagent tool with built-in and plugin-provided agent definitions.
24pub 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}