Skip to main content

bamboo_engine/sdk/
runner.rs

1//! Ergonomic child-session runner facade over the canonical spawn core.
2//!
3//! [`ChildRunner`] is a thin wrapper around the existing [`SpawnContext`] (it
4//! does NOT introduce a parallel `RuntimeDeps` god-struct). It builds a
5//! [`SpawnJob`] and delegates to [`crate::sdk::spawn::run_child_spawn`] — the
6//! single canonical spawn path.
7//!
8//! Sub-agents are plain full agents: there is no per-role tool trimming, so the
9//! job's `disabled_tools` is always `None` here (the unrelated global
10//! `config.disabled_tools` path is applied elsewhere, not via this runner).
11//!
12//! The assignment prompt and system prompt already live in the persisted child
13//! session (matching real spawn semantics: `ExecuteRequest.initial_message` is
14//! empty; the last user message in the child drives execution). `RunChildInput`
15//! therefore stays minimal.
16
17use bamboo_agent_core::AgentEvent;
18use tokio::sync::broadcast;
19
20use crate::runtime::execution::session_events::get_or_create_event_sender;
21use crate::runtime::execution::spawn::{SpawnContext, SpawnJob};
22
23/// Minimal input to run a child session.
24///
25/// The persisted child session already holds the system prompt + the pending user
26/// message, so only routing identifiers and the resolved model are required here.
27#[derive(Debug, Clone)]
28pub struct RunChildInput {
29    /// Child session id (already persisted with kind=child + a pending user msg).
30    pub child_session_id: String,
31    /// Parent session id whose event stream receives `SubAgent*` events.
32    pub parent_session_id: String,
33    /// Resolved model string for the child run.
34    pub model: String,
35}
36
37/// Ergonomic facade for child spawns.
38///
39/// Reuses [`SpawnContext`] (agent, tools, caches, router, completion handler) —
40/// the same dependency bundle the background scheduler uses.
41pub struct ChildRunner {
42    ctx: SpawnContext,
43}
44
45/// Construct a [`ChildRunner`] from an existing [`SpawnContext`].
46pub fn child_runner(ctx: SpawnContext) -> ChildRunner {
47    ChildRunner::new(ctx)
48}
49
50impl ChildRunner {
51    /// Create a runner over the given spawn context.
52    pub fn new(ctx: SpawnContext) -> Self {
53        Self { ctx }
54    }
55
56    /// Build a [`SpawnJob`] for the given input.
57    ///
58    /// Sub-agents are full agents with the full toolset, so `disabled_tools`
59    /// is always `None`.
60    pub(crate) fn build_job(&self, input: &RunChildInput) -> SpawnJob {
61        SpawnJob {
62            parent_session_id: input.parent_session_id.clone(),
63            child_session_id: input.child_session_id.clone(),
64            model: input.model.clone(),
65            disabled_tools: None,
66        }
67    }
68
69    /// Run a child session via the canonical spawn core.
70    ///
71    /// ANTI-FORK: constructs a [`SpawnJob`] and delegates to
72    /// [`crate::sdk::spawn::run_child_spawn`]; there is no inline execute/finalize.
73    pub async fn run_child(&self, input: RunChildInput) -> Result<(), String> {
74        let job = self.build_job(&input);
75        crate::sdk::spawn::run_child_spawn(self.ctx.clone(), job).await
76    }
77
78    /// Run a child session and return a receiver of the child's
79    /// [`AgentEvent`] stream.
80    ///
81    /// The receiver is subscribed from the existing broadcast infra
82    /// (`ctx.session_event_senders`) *before* the spawn is started, so no events
83    /// are missed. This reuses the canonical broadcast channel — it does NOT
84    /// invent a parallel `RunOutcomeStream`/`status_rx` mpsc.
85    pub async fn run_child_stream(
86        &self,
87        input: RunChildInput,
88    ) -> Result<broadcast::Receiver<AgentEvent>, String> {
89        let child_tx =
90            get_or_create_event_sender(&self.ctx.session_event_senders, &input.child_session_id)
91                .await;
92        let rx = child_tx.subscribe();
93        let job = self.build_job(&input);
94        crate::sdk::spawn::run_child_spawn(self.ctx.clone(), job).await?;
95        Ok(rx)
96    }
97}