Skip to main content

bamboo_subagent/
launcher.rs

1//! `WorkerLauncher`: the seam between "spawn a local subprocess" and "connect to
2//! a remote worker" (see `docs/remote-actor-plan.md` §3.1).
3//!
4//! Phase 0 ships only [`LocalSubprocessLauncher`], a zero-behavior-change wrapper
5//! over [`crate::fleet::spawn_worker`]. Remote launchers (connect to a resident
6//! `wss://` worker, or schedule one via a control plane) plug in later behind the
7//! same trait, so the rest of the fleet/runner code never branches on *where* a
8//! worker runs — only on this abstraction.
9
10use std::path::PathBuf;
11use std::time::Duration;
12
13use async_trait::async_trait;
14
15use crate::fleet::{spawn_worker, SpawnedChild};
16use crate::provision::ProvisionSpec;
17use crate::transport::TransportResult;
18
19/// Brings up (or connects to) one actor worker for a [`ProvisionSpec`] and waits
20/// up to `wait` for it to become reachable (self-registered in discovery).
21///
22/// Abstracts *how* a worker comes to exist so callers stay placement-agnostic.
23#[async_trait]
24pub trait WorkerLauncher: Send + Sync {
25    async fn launch(&self, spec: &ProvisionSpec, wait: Duration) -> TransportResult<SpawnedChild>;
26}
27
28/// The current, default behavior: spawn the worker binary as a local OS
29/// subprocess and provision it over stdin. A direct, zero-behavior-change
30/// wrapper over [`spawn_worker`].
31pub struct LocalSubprocessLauncher {
32    pub worker_bin: PathBuf,
33    pub worker_args: Vec<String>,
34}
35
36impl LocalSubprocessLauncher {
37    pub fn new(worker_bin: impl Into<PathBuf>, worker_args: Vec<String>) -> Self {
38        Self {
39            worker_bin: worker_bin.into(),
40            worker_args,
41        }
42    }
43}
44
45#[async_trait]
46impl WorkerLauncher for LocalSubprocessLauncher {
47    async fn launch(&self, spec: &ProvisionSpec, wait: Duration) -> TransportResult<SpawnedChild> {
48        spawn_worker(&self.worker_bin, &self.worker_args, spec, wait).await
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    /// The launcher must be object-safe so it can be stored as
57    /// `Arc<dyn WorkerLauncher>` (how the fleet will hold the chosen placement).
58    #[test]
59    fn local_subprocess_launcher_is_a_trait_object() {
60        let launcher = LocalSubprocessLauncher::new("/bin/true", vec!["subagent-worker".into()]);
61        let _dyn: &dyn WorkerLauncher = &launcher;
62        assert_eq!(launcher.worker_bin, PathBuf::from("/bin/true"));
63        assert_eq!(launcher.worker_args, vec!["subagent-worker".to_string()]);
64    }
65}