bamboo_engine/session_app/child_session/
mod.rs1use async_trait::async_trait;
8use bamboo_domain::session::runtime_state::ChildWaitPolicy;
9use bamboo_domain::Session;
10use std::collections::HashMap;
11
12mod actions;
13mod helpers;
14
15#[cfg(test)]
16mod tests;
17
18pub use actions::{
19 cancel_child_action, create_child_action, delete_child_action, get_child_action,
20 list_children_action, run_child_action, send_message_to_child_action, update_child_action,
21};
22pub use helpers::{
23 compute_status_guidance, format_child_assignment, map_child_entry, metadata_text,
24 normalize_non_empty_optional, normalize_required_text, replace_or_append_last_user_message,
25 resolve_system_prompt, truncate_after_index, truncate_after_last_user,
26};
27
28#[derive(Debug, thiserror::Error)]
33pub enum ChildSessionError {
34 #[error("session not found: {0}")]
35 NotFound(String),
36 #[error("session is not a root session: {0}")]
37 NotRootSession(String),
38 #[error("session is not a child session: {0}")]
39 NotChildSession(String),
40 #[error("child session {child_id} does not belong to parent {parent_id}")]
41 NotChildOfParent { child_id: String, parent_id: String },
42 #[error("{0}")]
43 InvalidArguments(String),
44 #[error("{0}")]
45 Execution(String),
46}
47
48#[derive(Debug, Clone)]
54pub struct ChildSessionEntry {
55 pub child_session_id: String,
56 pub title: String,
57 pub pinned: bool,
58 pub message_count: usize,
59 pub updated_at: String,
60 pub last_run_status: Option<String>,
61 pub last_run_error: Option<String>,
62}
63
64#[derive(Debug, Clone)]
66pub struct DeleteChildResult {
67 pub deleted: bool,
68 pub cancelled_running_child: bool,
69}
70
71#[derive(Debug, Clone)]
73pub struct ChildRunnerInfo {
74 pub started_at: Option<chrono::DateTime<chrono::Utc>>,
75 pub completed_at: Option<chrono::DateTime<chrono::Utc>>,
76 pub last_tool_name: Option<String>,
77 pub last_tool_phase: Option<String>,
78 pub last_event_at: Option<chrono::DateTime<chrono::Utc>>,
79 pub round_count: u32,
80}
81
82pub const CHILD_SYSTEM_PROMPT: &str = r#"You are a **Child Session**, delegated by a parent session.
84
85Requirements:
86- Focus only on the assigned task and avoid unrelated conversation.
87- You may use tools to complete the task.
88- Do not create or trigger any additional child sessions (no recursive spawn).
89- Keep output concise: provide the conclusion first, then only necessary evidence or steps.
90"#;
91
92pub const PLAN_AGENT_SYSTEM_PROMPT: &str = r#"You are a **Plan Agent**, a read-only exploration specialist delegated by a parent session.
94
95Your role is EXCLUSIVELY to explore the codebase and gather information to help design an implementation plan. You MUST NOT modify anything.
96
97=== CRITICAL: READ-ONLY MODE — NO FILE MODIFICATIONS ===
98
99You are FORBIDDEN from using these tools:
100- Write — do not create new files
101- Edit — do not modify existing files
102- NotebookEdit — do not edit notebooks
103- Bash — do not execute shell commands
104- BashOutput — do not execute shell commands
105- KillShell — do not manage processes
106- SubAgent — do not spawn further child sessions
107
108You MAY use these read-only tools:
109- Read — read file contents
110- Glob — list files matching patterns
111- Grep — search code for patterns
112- GetFileInfo — get file metadata
113- WebFetch — fetch web content
114- WebSearch — search the web
115- MemoryNote — write observations to session memory
116
117Requirements:
118- Focus only on the assigned exploration task.
119- Provide clear, structured findings: what you discovered, where the relevant code is, and what it does.
120- Keep output concise but thorough — the parent session needs enough detail to design a plan.
121- If you cannot find something after reasonable searching, say so clearly.
122"#;
123
124#[derive(Debug, Clone)]
126pub struct CreateChildInput {
127 pub parent_session: Session,
128 pub child_id: String,
129 pub title: String,
130 pub responsibility: String,
131 pub assignment_prompt: String,
132 pub subagent_type: String,
133 pub workspace: String,
135 pub model_override: Option<String>,
138 pub model_ref_override: Option<bamboo_domain::ProviderModelRef>,
141 pub runtime_metadata: std::collections::HashMap<String, String>,
143 pub system_prompt_override: Option<String>,
150 pub auto_run: bool,
153 pub reasoning_effort: Option<bamboo_domain::ReasoningEffort>,
160 pub lifecycle: Option<String>,
164 pub resident_name: Option<String>,
166 pub resident_context: Option<String>,
169}
170
171#[derive(Debug, Clone)]
173pub struct CreateChildResult {
174 pub child_session_id: String,
175 pub model: String,
176}
177
178#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
180pub struct QueuedInjectedMessage {
181 pub content: String,
182 #[serde(default)]
183 pub created_at: Option<chrono::DateTime<chrono::Utc>>,
184}
185
186#[async_trait]
191pub trait ChildSessionPort: Send + Sync {
192 async fn load_root_session(&self, root_id: &str) -> Result<Session, ChildSessionError>;
193 async fn load_child_for_parent(
194 &self,
195 parent_id: &str,
196 child_id: &str,
197 ) -> Result<Session, ChildSessionError>;
198 async fn save_child_session(&self, child: &mut Session) -> Result<(), ChildSessionError>;
199 async fn is_child_running(&self, child_id: &str) -> bool;
200 async fn list_children(&self, parent_id: &str) -> Vec<ChildSessionEntry>;
201 async fn enqueue_child_run(
202 &self,
203 parent: &Session,
204 child: &Session,
205 ) -> Result<(), ChildSessionError>;
206 async fn cancel_child_run_and_wait(&self, child_id: &str) -> Result<(), ChildSessionError>;
207 async fn delete_child_session(
208 &self,
209 parent_id: &str,
210 child_id: &str,
211 ) -> Result<DeleteChildResult, ChildSessionError>;
212 async fn get_child_runner_info(&self, child_id: &str) -> Option<ChildRunnerInfo>;
214
215 async fn register_parent_wait_for_child(
218 &self,
219 parent_session_id: &str,
220 child_session_id: &str,
221 tool_call_id: Option<&str>,
222 ) -> Result<(), ChildSessionError>;
223
224 async fn register_parent_wait_for_children(
228 &self,
229 parent_session_id: &str,
230 child_session_ids: &[String],
231 policy: ChildWaitPolicy,
232 ) -> Result<usize, ChildSessionError>;
233
234 async fn active_child_ids(&self, parent_session_id: &str) -> Vec<String>;
236
237 async fn find_resident_child(
242 &self,
243 root_session_id: &str,
244 resident_name: &str,
245 ) -> Option<String>;
246
247 async fn ensure_child_indexed(&self, child_session_id: &str);
251}
252
253#[async_trait]
265pub trait SubagentResolutionPort: Send + Sync {
266 async fn resolve_subagent_model(
268 &self,
269 subagent_type: &str,
270 ) -> Option<bamboo_domain::ProviderModelRef>;
271
272 async fn resolve_runtime_metadata(&self, subagent_type: &str) -> HashMap<String, String>;
274
275 fn resolve_subagent_prompt(&self, subagent_type: &str) -> String;
278}
279
280#[derive(Debug, Clone, serde::Serialize)]
282pub struct ProviderModelList {
283 pub provider: String,
284 pub models: Vec<String>,
285 #[serde(skip_serializing_if = "Option::is_none")]
288 pub error: Option<String>,
289}
290
291#[async_trait]
297pub trait ModelCatalogPort: Send + Sync {
298 async fn list_models(&self) -> Vec<ProviderModelList>;
302
303 fn default_provider(&self) -> String;
306}