bamboo-engine 2026.10.10

Execution engine and orchestration for the Bamboo agent framework
Documentation
use tokio::sync::mpsc;

use crate::runtime::config::AgentLoopConfig;
use crate::runtime::task_context::TaskLoopContext;
use bamboo_agent_core::tools::{ToolCall, ToolResult};
use bamboo_agent_core::{AgentEvent, Session};

mod progress;
mod taskwrite;

pub(super) async fn maybe_apply_ticket_task(
    tool_call: &ToolCall,
    result: &ToolResult,
    session: &mut Session,
    config: &AgentLoopConfig,
) -> Option<ToolResult> {
    if tool_call.function.name != "Task" || !result.success {
        return None;
    }
    if !session
        .metadata
        .contains_key(crate::ticket_worker_plan::TICKET_LOCAL_PLAN_KEY)
        && config.ticket_worker_plan.is_none()
    {
        return None;
    }
    let outcome = async {
        let plan = config.ticket_worker_plan.as_ref().ok_or_else(|| {
            bamboo_tickets::Error::ScopeDenied(
                "Ticket Worker requires a trusted LocalPlan permit".into(),
            )
        })?;
        let args = serde_json::from_str(&tool_call.function.arguments)?;
        plan.apply_task(session, &tool_call.id, &args).await
    }
    .await;
    let mut resolved = result.clone();
    if let Err(error) = outcome {
        resolved.success = false;
        resolved.result = error.to_string();
    } else if let Some(request) = session
        .metadata
        .get(crate::ticket_worker_plan::TICKET_QUESTION_YIELD_KEY)
    {
        resolved.result = serde_json::json!({"status":"waiting_for_answer","request":serde_json::from_str::<serde_json::Value>(request).unwrap_or_default()}).to_string();
    }
    Some(resolved)
}

pub(super) async fn track_task_progress(
    task_context: &mut Option<TaskLoopContext>,
    event_tx: &mpsc::Sender<AgentEvent>,
    session_id: &str,
    tool_call: &ToolCall,
    result: &ToolResult,
    round: usize,
) {
    progress::track_task_progress(task_context, event_tx, session_id, tool_call, result, round)
        .await;
}

pub(super) async fn maybe_handle_taskwrite(
    tool_call: &ToolCall,
    result: &ToolResult,
    session: &mut Session,
    session_id: &str,
    event_tx: &mpsc::Sender<AgentEvent>,
    config: &AgentLoopConfig,
    task_context: &mut Option<TaskLoopContext>,
) {
    taskwrite::maybe_handle_taskwrite(
        tool_call,
        result,
        session,
        session_id,
        event_tx,
        config,
        task_context,
    )
    .await;
}

#[cfg(test)]
mod tests;