Skip to main content

bamboo_engine/session_app/child_session/
mod.rs

1//! Child session management use cases.
2//!
3//! Provides the application-layer logic for managing child sessions within
4//! a root session. The server layer implements `ChildSessionPort` to supply
5//! the infrastructure operations (load, save, schedule, cancel).
6
7use async_trait::async_trait;
8use bamboo_domain::Session;
9
10mod actions;
11mod helpers;
12
13#[cfg(test)]
14mod tests;
15
16pub use actions::{
17    cancel_child_action, create_child_action, delete_child_action, get_child_action,
18    list_children_action, run_child_action, send_message_to_child_action, update_child_action,
19};
20pub use helpers::{
21    compute_status_guidance, format_child_assignment, map_child_entry, metadata_text,
22    normalize_non_empty_optional, normalize_required_text, replace_or_append_last_user_message,
23    resolve_system_prompt, truncate_after_index, truncate_after_last_user,
24};
25
26// ---------------------------------------------------------------------------
27// Error type
28// ---------------------------------------------------------------------------
29
30#[derive(Debug, thiserror::Error)]
31pub enum ChildSessionError {
32    #[error("session not found: {0}")]
33    NotFound(String),
34    #[error("session is not a root session: {0}")]
35    NotRootSession(String),
36    #[error("session is not a child session: {0}")]
37    NotChildSession(String),
38    #[error("child session {child_id} does not belong to parent {parent_id}")]
39    NotChildOfParent { child_id: String, parent_id: String },
40    #[error("{0}")]
41    InvalidArguments(String),
42    #[error("{0}")]
43    Execution(String),
44}
45
46// ---------------------------------------------------------------------------
47// Value types
48// ---------------------------------------------------------------------------
49
50/// Summary of a child session for listing.
51#[derive(Debug, Clone)]
52pub struct ChildSessionEntry {
53    pub child_session_id: String,
54    pub title: String,
55    pub pinned: bool,
56    pub message_count: usize,
57    pub updated_at: String,
58    pub last_run_status: Option<String>,
59    pub last_run_error: Option<String>,
60}
61
62/// Result of deleting a child session.
63#[derive(Debug, Clone)]
64pub struct DeleteChildResult {
65    pub deleted: bool,
66    pub cancelled_running_child: bool,
67}
68
69/// Diagnostic snapshot of a running child session runner.
70#[derive(Debug, Clone)]
71pub struct ChildRunnerInfo {
72    pub started_at: Option<chrono::DateTime<chrono::Utc>>,
73    pub completed_at: Option<chrono::DateTime<chrono::Utc>>,
74    pub last_tool_name: Option<String>,
75    pub last_tool_phase: Option<String>,
76    pub last_event_at: Option<chrono::DateTime<chrono::Utc>>,
77    pub round_count: u32,
78}
79
80/// Default system prompt for child sessions.
81pub const CHILD_SYSTEM_PROMPT: &str = r#"You are a **Child Session**, delegated by a parent session.
82
83Requirements:
84- Focus only on the assigned task and avoid unrelated conversation.
85- You may use tools to complete the task.
86- Do not create or trigger any additional child sessions (no recursive spawn).
87- Keep output concise: provide the conclusion first, then only necessary evidence or steps.
88"#;
89
90/// System prompt for plan-mode exploration child sessions.
91pub const PLAN_AGENT_SYSTEM_PROMPT: &str = r#"You are a **Plan Agent**, a read-only exploration specialist delegated by a parent session.
92
93Your role is EXCLUSIVELY to explore the codebase and gather information to help design an implementation plan. You MUST NOT modify anything.
94
95=== CRITICAL: READ-ONLY MODE — NO FILE MODIFICATIONS ===
96
97You are FORBIDDEN from using these tools:
98- Write — do not create new files
99- Edit — do not modify existing files
100- NotebookEdit — do not edit notebooks
101- Bash — do not execute shell commands
102- BashOutput — do not execute shell commands
103- KillShell — do not manage processes
104- SubAgent — do not spawn further child sessions
105
106You MAY use these read-only tools:
107- Read — read file contents
108- Glob — list files matching patterns
109- Grep — search code for patterns
110- GetFileInfo — get file metadata
111- WebFetch — fetch web content
112- WebSearch — search the web
113- MemoryNote — write observations to session memory
114
115Requirements:
116- Focus only on the assigned exploration task.
117- Provide clear, structured findings: what you discovered, where the relevant code is, and what it does.
118- Keep output concise but thorough — the parent session needs enough detail to design a plan.
119- If you cannot find something after reasonable searching, say so clearly.
120"#;
121
122/// Input for creating a child session.
123#[derive(Debug, Clone)]
124pub struct CreateChildInput {
125    pub parent_session: Session,
126    pub child_id: String,
127    pub title: String,
128    pub responsibility: String,
129    pub assignment_prompt: String,
130    pub subagent_type: String,
131    /// Absolute path to the working directory for the child session.
132    pub workspace: String,
133    /// Optional model override resolved from subagent_type routing.
134    /// When `None`, the child inherits the parent session's model.
135    pub model_override: Option<String>,
136    /// Optional provider+model override resolved from subagent routing.
137    /// When present, this preserves cross-provider routing for child execution.
138    pub model_ref_override: Option<bamboo_domain::ProviderModelRef>,
139    /// Runtime metadata resolved from subagent routing (e.g. external agent config).
140    pub runtime_metadata: std::collections::HashMap<String, String>,
141    /// Optional system prompt override resolved from the
142    /// `SubagentProfileRegistry`. When `None`, the child falls back to
143    /// the legacy hard-coded prompts (`PLAN_AGENT_SYSTEM_PROMPT` for
144    /// `subagent_type == "plan"`, `CHILD_SYSTEM_PROMPT` otherwise) so
145    /// that callers that have not yet been wired up keep their pre-PR-3
146    /// behaviour byte-for-byte.
147    pub system_prompt_override: Option<String>,
148    /// Whether to immediately enqueue the child for execution.
149    /// Defaults to `true`.
150    pub auto_run: bool,
151    /// Optional reasoning effort to apply to the child's own LLM calls.
152    /// `None` (the default) leaves `Session::reasoning_effort` at `None`,
153    /// so the provider falls back to its default. The child does NOT
154    /// inherit the parent's reasoning_effort — fan-out children that
155    /// only need a quick lookup should not pay for `xhigh` reasoning
156    /// just because the orchestrator is running at `xhigh`.
157    pub reasoning_effort: Option<bamboo_domain::ReasoningEffort>,
158}
159
160/// Result of creating a child session.
161#[derive(Debug, Clone)]
162pub struct CreateChildResult {
163    pub child_session_id: String,
164    pub model: String,
165}
166
167/// A queued follow-up message stored in session metadata for later injection.
168#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
169pub struct QueuedInjectedMessage {
170    pub content: String,
171    #[serde(default)]
172    pub created_at: Option<chrono::DateTime<chrono::Utc>>,
173}
174
175// ---------------------------------------------------------------------------
176// Port trait
177// ---------------------------------------------------------------------------
178
179#[async_trait]
180pub trait ChildSessionPort: Send + Sync {
181    async fn load_root_session(&self, root_id: &str) -> Result<Session, ChildSessionError>;
182    async fn load_child_for_parent(
183        &self,
184        parent_id: &str,
185        child_id: &str,
186    ) -> Result<Session, ChildSessionError>;
187    async fn save_child_session(&self, child: &mut Session) -> Result<(), ChildSessionError>;
188    async fn is_child_running(&self, child_id: &str) -> bool;
189    async fn list_children(&self, parent_id: &str) -> Vec<ChildSessionEntry>;
190    async fn enqueue_child_run(
191        &self,
192        parent: &Session,
193        child: &Session,
194    ) -> Result<(), ChildSessionError>;
195    async fn cancel_child_run_and_wait(&self, child_id: &str) -> Result<(), ChildSessionError>;
196    async fn delete_child_session(
197        &self,
198        parent_id: &str,
199        child_id: &str,
200    ) -> Result<DeleteChildResult, ChildSessionError>;
201    /// Return live diagnostic info for a running child session, if available.
202    async fn get_child_runner_info(&self, child_id: &str) -> Option<ChildRunnerInfo>;
203}