bamboo_engine/runtime/
agent.rs1use std::sync::Arc;
7
8use bamboo_agent_core::Session;
9
10use crate::runtime::{AgentRuntime, AgentRuntimeBuilder, ExecuteRequest};
11use bamboo_domain::RuntimeSessionPersistence;
12
13#[derive(Clone)]
25pub struct Agent {
26 runtime: Arc<AgentRuntime>,
27}
28
29pub struct DirectExecutionLease {
35 target_session_id: String,
36 router: Option<Arc<crate::session_activation::SessionActivationRouter>>,
37 registration: Option<crate::session_activation::SessionRunRegistration>,
38}
39
40impl Agent {
41 pub fn from_runtime(runtime: Arc<AgentRuntime>) -> Self {
43 Agent { runtime }
44 }
45
46 pub fn builder() -> AgentBuilder {
48 AgentBuilder::new()
49 }
50
51 pub async fn execute(
53 &self,
54 session: &mut Session,
55 req: ExecuteRequest,
56 ) -> crate::runtime::runner::Result<()> {
57 self.runtime.execute(session, req).await
58 }
59
60 pub async fn execute_direct(
71 &self,
72 session: &mut Session,
73 req: ExecuteRequest,
74 ) -> crate::runtime::runner::Result<()> {
75 let lease = self.begin_direct_execution(&session.id).await?;
76 self.execute_direct_registered(session, req, lease).await
77 }
78
79 pub async fn begin_direct_execution(
82 &self,
83 target_session_id: &str,
84 ) -> crate::runtime::runner::Result<DirectExecutionLease> {
85 let Some(router) = self.activation_router().cloned() else {
86 return Ok(DirectExecutionLease {
87 target_session_id: target_session_id.to_string(),
88 router: None,
89 registration: None,
90 });
91 };
92 let run_id = format!("sdk-direct-{}", uuid::Uuid::new_v4());
93 let registration = router
94 .register_run(target_session_id, &run_id)
95 .await
96 .map_err(|error| bamboo_agent_core::AgentError::LLM(error.to_string()))?;
97 Ok(DirectExecutionLease {
98 target_session_id: target_session_id.to_string(),
99 router: Some(router),
100 registration: Some(registration),
101 })
102 }
103
104 pub async fn execute_direct_registered(
107 &self,
108 session: &mut Session,
109 req: ExecuteRequest,
110 mut lease: DirectExecutionLease,
111 ) -> crate::runtime::runner::Result<()> {
112 if lease.target_session_id != session.id {
113 return Err(bamboo_agent_core::AgentError::LLM(format!(
114 "direct execution lease target {} does not match session {}",
115 lease.target_session_id, session.id
116 )));
117 }
118 let Some(router) = lease.router.take() else {
119 return self.execute(session, req).await;
120 };
121 let mut registration = lease.registration.take().ok_or_else(|| {
122 bamboo_agent_core::AgentError::LLM(
123 "direct execution lease is missing its router registration".to_string(),
124 )
125 })?;
126 let result = self.execute(session, req).await;
127
128 let executed_admitted_generation = session
131 .session_inbox_admission()
132 .map_or(0, |state| state.last_admitted_sequence);
133 registration.begin_finalization().await;
134
135 let legacy_migration = crate::runtime::runner::state_bridge::migrate_legacy_pending_only(
136 session,
137 Some(self.storage()),
138 Some(self.persistence()),
139 self.session_inbox(),
140 )
141 .await;
142 if let Some(generation) = legacy_migration.highest_generation {
143 session.session_inbox_admission_mut().observe(generation);
144 }
145 let pending_generation = session
146 .session_inbox_admission()
147 .and_then(|state| state.pending_activation_generation());
148 if let Some(generation) = pending_generation {
149 let activation_ready = if let Some(inbox) = self.session_inbox() {
150 match inbox
151 .mark_activation_eligible(
152 &session.id,
153 generation,
154 bamboo_domain::SessionActivationPolicy::InterruptSpecificWait,
155 )
156 .await
157 {
158 Ok(()) => true,
159 Err(error) => {
160 tracing::error!(
161 session_id = %session.id,
162 %error,
163 "failed to persist direct SDK SessionInbox activation watermark"
164 );
165 false
166 }
167 }
168 } else {
169 false
170 };
171 if activation_ready {
172 if let Err(error) = bamboo_domain::SessionActivationPort::request_activation(
173 router.as_ref(),
174 &session.id,
175 generation,
176 )
177 .await
178 {
179 tracing::error!(
180 session_id = %session.id,
181 %error,
182 "failed to hand direct SDK SessionInbox generation to activation router"
183 );
184 }
185 }
186 }
187
188 if let Err(error) = self.persistence().checkpoint_runtime_session(session).await {
191 tracing::warn!(
192 session_id = %session.id,
193 %error,
194 "failed to checkpoint direct SDK terminal SessionInbox state"
195 );
196 }
197 if let Err(error) = registration.finish(executed_admitted_generation).await {
198 tracing::error!(
199 session_id = %session.id,
200 %error,
201 "direct SDK SessionInbox finalization failed"
202 );
203 }
204
205 result
206 }
207
208 pub fn storage(&self) -> &Arc<dyn bamboo_agent_core::storage::Storage> {
210 &self.runtime.storage
211 }
212
213 pub fn persistence(&self) -> &Arc<dyn RuntimeSessionPersistence> {
215 &self.runtime.persistence
216 }
217
218 pub fn session_inbox(&self) -> Option<&Arc<dyn bamboo_domain::SessionInboxPort>> {
219 self.runtime.session_inbox.as_ref()
220 }
221
222 pub async fn admit_session_inbox_at_safe_boundary(
227 &self,
228 session: &mut bamboo_agent_core::Session,
229 ) -> usize {
230 crate::runtime::runner::state_bridge::refresh_turn_boundary_with_inbox(
231 session,
232 Some(self.storage()),
233 Some(self.persistence()),
234 self.session_inbox(),
235 )
236 .await
237 .merged
238 }
239
240 pub fn activation_router(
241 &self,
242 ) -> Option<&Arc<crate::session_activation::SessionActivationRouter>> {
243 self.runtime.activation_router.as_ref()
244 }
245
246 pub fn session_messenger(&self) -> Option<&Arc<crate::SessionMessenger>> {
247 self.runtime.session_messenger.as_ref()
248 }
249
250 pub fn default_tools(&self) -> &Arc<dyn bamboo_agent_core::tools::ToolExecutor> {
260 &self.runtime.default_tools
261 }
262}
263
264pub struct AgentBuilder {
272 inner: AgentRuntimeBuilder,
273}
274
275impl AgentBuilder {
276 pub fn new() -> Self {
277 Self {
278 inner: AgentRuntimeBuilder::new(),
279 }
280 }
281
282 pub fn storage(mut self, v: Arc<dyn bamboo_agent_core::storage::Storage>) -> Self {
283 self.inner = self.inner.storage(v);
284 self
285 }
286
287 pub fn persistence(mut self, v: Arc<dyn RuntimeSessionPersistence>) -> Self {
288 self.inner = self.inner.persistence(v);
289 self
290 }
291
292 pub fn session_inbox(mut self, v: Arc<dyn bamboo_domain::SessionInboxPort>) -> Self {
293 self.inner = self.inner.session_inbox(v);
294 self
295 }
296
297 pub fn activation_router(
298 mut self,
299 v: Arc<crate::session_activation::SessionActivationRouter>,
300 ) -> Self {
301 self.inner = self.inner.activation_router(v);
302 self
303 }
304
305 pub fn session_messenger(mut self, v: Arc<crate::SessionMessenger>) -> Self {
306 self.inner = self.inner.session_messenger(v);
307 self
308 }
309
310 pub fn attachment_reader(
311 mut self,
312 v: Arc<dyn bamboo_agent_core::storage::AttachmentReader>,
313 ) -> Self {
314 self.inner = self.inner.attachment_reader(v);
315 self
316 }
317
318 pub fn skill_manager(mut self, v: Arc<bamboo_skills::SkillManager>) -> Self {
319 self.inner = self.inner.skill_manager(v);
320 self
321 }
322
323 pub fn project_context_resolver(
324 mut self,
325 v: Arc<crate::project_context::ProjectContextResolver>,
326 ) -> Self {
327 self.inner = self.inner.project_context_resolver(v);
328 self
329 }
330
331 pub fn metrics_collector(mut self, v: bamboo_metrics::MetricsCollector) -> Self {
332 self.inner = self.inner.metrics_collector(v);
333 self
334 }
335
336 pub fn config(mut self, v: Arc<tokio::sync::RwLock<bamboo_llm::Config>>) -> Self {
337 self.inner = self.inner.config(v);
338 self
339 }
340
341 pub fn provider(mut self, v: Arc<dyn bamboo_llm::LLMProvider>) -> Self {
342 self.inner = self.inner.provider(v);
343 self
344 }
345
346 pub fn default_tools(mut self, v: Arc<dyn bamboo_agent_core::tools::ToolExecutor>) -> Self {
347 self.inner = self.inner.default_tools(v);
348 self
349 }
350
351 pub fn hook_runner(mut self, v: Arc<crate::runtime::HookRunner>) -> Self {
353 self.inner = self.inner.hook_runner(v);
354 self
355 }
356
357 pub fn build(self) -> Result<Agent, &'static str> {
358 let runtime = self.inner.build()?;
359 Ok(Agent {
360 runtime: Arc::new(runtime),
361 })
362 }
363}
364
365impl Default for AgentBuilder {
366 fn default() -> Self {
367 Self::new()
368 }
369}