Skip to main content

bamboo_engine/external_agents/local_task/
local_skill_binding.rs

1//! App-facing binding for an already owned same-process ordinary Child.
2use super::*;
3use crate::runtime::config::SkillExecutionHost;
4
5/// Execution proof supplied only by the actual local runner after admission.
6/// It is neither serialized nor constructible from an input/Session snapshot.
7pub struct LocalChildSkillAuthority {
8    store: Arc<bamboo_storage::SessionStoreV2>,
9    owner: ChildActorRuntimeWrite,
10    cancel: CancellationToken,
11    reader_callable: AtomicBool,
12}
13impl LocalChildSkillAuthority {
14    pub(super) fn bind(
15        store: Arc<bamboo_storage::SessionStoreV2>,
16        execution: &Execution,
17    ) -> Arc<Self> {
18        Arc::new(Self {
19            store,
20            owner: execution.owner.clone(),
21            cancel: execution.cancel.clone(),
22            reader_callable: AtomicBool::new(false),
23        })
24    }
25    // Set only by the real runner after its final positive/disabled/read-only
26    // tool boundary is installed. The app cannot promote this observation.
27    pub(super) fn finalize_tools(&self, tools: &dyn ToolExecutor) {
28        self.reader_callable
29            .store(tools.owns_exact_tool("skills_read"), Ordering::Release);
30    }
31    pub fn reader_callable(&self) -> bool {
32        self.reader_callable.load(Ordering::Acquire)
33    }
34    pub fn session_id(&self) -> &str {
35        &self.owner.fence.actor_id
36    }
37    pub fn cancel_token(&self) -> &CancellationToken {
38        &self.cancel
39    }
40    pub fn check_session(&self, session: &Session) -> Result<(), AgentError> {
41        if self.cancel.is_cancelled() {
42            return Err(AgentError::Cancelled);
43        }
44        if session.kind != SessionKind::Child
45            || !session.authority_identity.is_ordinary()
46            || session.id != self.owner.fence.actor_id
47            || session.created_at != self.owner.created_at
48            || session.root_session_id != self.owner.root_session_id
49            || session.parent_session_id.as_deref() != Some(self.owner.parent_session_id.as_str())
50            || session.parent_created_at != Some(self.owner.parent_created_at)
51            || session.spawn_depth != self.owner.spawn_depth
52            || session.project_id_meta() != self.owner.project_id
53            || skill_host::strict_child(session)?
54        {
55            return Err(failure("ordinary local Child Skills identity changed"));
56        }
57        Ok(())
58    }
59    /// Fresh canonical scope plus the same live Actor fence/ancestor check used
60    /// by local tools. This guarded check publishes no event or checkpoint.
61    pub async fn current_session(&self) -> Result<Session, AgentError> {
62        let session = self
63            .store
64            .load_session(self.session_id())
65            .await
66            .map_err(failure)?
67            .ok_or_else(|| failure("ordinary local Child disappeared"))?;
68        self.check_session(&session)?;
69        self.store
70            .publish_child_actor_runtime_event(&self.owner, Box::new(|check| check()))
71            .await
72            .map_err(failure)?;
73        self.check_session(&session)?;
74        Ok(session)
75    }
76}
77
78/// The app supplies its existing catalog/read services; Engine retains the
79/// real admission, writer, task lifetime and outer local permission ceiling.
80pub struct LocalChildSkillBinding {
81    pub host: Arc<dyn SkillExecutionHost>,
82    pub tools: Arc<dyn ToolExecutor>,
83}
84#[async_trait]
85pub trait LocalChildSkillFactory: Send + Sync {
86    async fn bind(
87        &self,
88        authority: Arc<LocalChildSkillAuthority>,
89        session: &Session,
90        tools: Arc<dyn ToolExecutor>,
91    ) -> Result<LocalChildSkillBinding, AgentError>;
92}