Skip to main content

tycode_core/spawn/
mod.rs

1//! Spawn module for agent lifecycle management.
2//!
3//! Owns the agent stack (Vec<ActiveAgent>) and all lifecycle operations.
4//! Single source of truth for agent hierarchy.
5
6use std::collections::HashSet;
7use std::sync::{Arc, RwLock};
8
9use crate::agents::agent::ActiveAgent;
10use crate::agents::catalog::AgentCatalog;
11use crate::module::{ContextComponent, Module, PromptComponent};
12use crate::tools::r#trait::ToolExecutor;
13use crate::Agent;
14
15pub mod complete_task;
16pub mod spawn_agent;
17
18pub use complete_task::CompleteTask;
19pub use spawn_agent::SpawnAgent;
20
21pub struct SpawnModule {
22    catalog: Arc<AgentCatalog>,
23    agents: Arc<RwLock<Vec<ActiveAgent>>>,
24}
25
26impl SpawnModule {
27    pub fn new(catalog: Arc<AgentCatalog>, initial_agent: Arc<dyn Agent>) -> Self {
28        Self {
29            catalog,
30            agents: Arc::new(RwLock::new(vec![ActiveAgent::new(initial_agent)])),
31        }
32    }
33
34    /// Get the current agent name (top of stack)
35    pub fn current_agent_name(&self) -> Option<String> {
36        self.agents
37            .read()
38            .ok()?
39            .last()
40            .map(|a| a.agent.name().to_string())
41    }
42
43    /// Execute closure with read access to current agent
44    pub fn with_current_agent<F, R>(&self, f: F) -> Option<R>
45    where
46        F: FnOnce(&ActiveAgent) -> R,
47    {
48        let agents = self.agents.read().ok()?;
49        agents.last().map(f)
50    }
51
52    /// Execute closure with mutable access to current agent
53    pub fn with_current_agent_mut<F, R>(&self, f: F) -> Option<R>
54    where
55        F: FnOnce(&mut ActiveAgent) -> R,
56    {
57        let mut agents = self.agents.write().ok()?;
58        agents.last_mut().map(f)
59    }
60
61    /// Execute closure with read access to root agent
62    pub fn with_root_agent<F, R>(&self, f: F) -> Option<R>
63    where
64        F: FnOnce(&ActiveAgent) -> R,
65    {
66        let agents = self.agents.read().ok()?;
67        agents.first().map(f)
68    }
69
70    /// Execute closure with mutable access to root agent
71    pub fn with_root_agent_mut<F, R>(&self, f: F) -> Option<R>
72    where
73        F: FnOnce(&mut ActiveAgent) -> R,
74    {
75        let mut agents = self.agents.write().ok()?;
76        agents.first_mut().map(f)
77    }
78
79    /// Push a new agent onto the stack
80    pub fn push_agent(&self, agent: ActiveAgent) {
81        if let Ok(mut agents) = self.agents.write() {
82            agents.push(agent);
83        }
84    }
85
86    /// Pop current agent if stack has > 1 agent (preserves root)
87    pub fn pop_agent(&self) -> Option<ActiveAgent> {
88        let mut agents = self.agents.write().ok()?;
89        if agents.len() > 1 {
90            agents.pop()
91        } else {
92            None
93        }
94    }
95
96    /// Get stack depth
97    pub fn stack_depth(&self) -> usize {
98        self.agents.read().map(|a| a.len()).unwrap_or(0)
99    }
100
101    /// Clear stack and reset with new root agent
102    pub fn reset_to_agent(&self, agent: Arc<dyn Agent>) {
103        if let Ok(mut agents) = self.agents.write() {
104            agents.clear();
105            agents.push(ActiveAgent::new(agent));
106        }
107    }
108
109    /// Get reference to catalog
110    pub fn catalog(&self) -> &Arc<AgentCatalog> {
111        &self.catalog
112    }
113
114    /// Execute closure with read access to all agents
115    pub fn with_agents<F, R>(&self, f: F) -> Option<R>
116    where
117        F: FnOnce(&[ActiveAgent]) -> R,
118    {
119        let agents = self.agents.read().ok()?;
120        Some(f(&agents))
121    }
122}
123
124/// Agent hierarchy for spawn permissions.
125/// Lower level = higher privilege (can spawn more agents).
126/// Agents can only spawn agents at levels below them.
127///
128/// Hierarchy:
129///   tycode (L0) > coordinator (L1) > coder (L2) > leaves (L3)
130///   Leaves: context, debugger, planner, review
131fn agent_level(agent: &str) -> u8 {
132    match agent {
133        "tycode" => 0,
134        "coordinator" => 1,
135        "coder" => 2,
136        // Leaf agents - cannot spawn anything
137        "context" | "debugger" | "planner" | "review" => 3,
138        // Unknown agents default to leaf (most restrictive)
139        _ => 3,
140    }
141}
142
143/// Returns the set of agents that can be spawned by the given agent.
144/// Based on hierarchical chain: can only spawn agents at lower levels.
145pub fn allowed_agents_for(agent: &str) -> HashSet<String> {
146    let level = agent_level(agent);
147
148    // Collect all agents at levels below this agent's level
149    let all_agents = [
150        ("coordinator", 1),
151        ("coder", 2),
152        ("context", 3),
153        ("debugger", 3),
154        ("planner", 3),
155        ("review", 3),
156    ];
157
158    all_agents
159        .into_iter()
160        .filter(|(_, agent_level)| *agent_level > level)
161        .map(|(name, _)| name.to_string())
162        .collect()
163}
164
165impl Module for SpawnModule {
166    fn prompt_components(&self) -> Vec<Arc<dyn PromptComponent>> {
167        vec![]
168    }
169
170    fn context_components(&self) -> Vec<Arc<dyn ContextComponent>> {
171        vec![]
172    }
173
174    fn tools(&self) -> Vec<Arc<dyn ToolExecutor>> {
175        let current = self.current_agent_name().unwrap_or_default();
176        let allowed = allowed_agents_for(&current);
177
178        let mut tools: Vec<Arc<dyn ToolExecutor>> = vec![Arc::new(CompleteTask)];
179
180        if !allowed.is_empty() {
181            tools.push(Arc::new(SpawnAgent::new(
182                self.catalog.clone(),
183                allowed,
184                current,
185            )));
186        }
187
188        tools
189    }
190}