Skip to main content

bamboo_domain/session/
terminal_runtime.rs

1//! Host run identity and terminal read-model projection. These values are not
2//! execution authority: Storage validates the current incarnation/activation.
3
4use super::Session;
5use crate::{AgentRuntimeState, AgentStatusState};
6
7const HOST_RUN_ID: &str = "runtime.host_run_id";
8
9impl Session {
10    pub fn host_run_id(&self) -> Option<&str> {
11        self.metadata.get(HOST_RUN_ID).map(String::as_str)
12    }
13
14    pub fn set_host_run_id(&mut self, run_id: &str) {
15        self.metadata.insert(HOST_RUN_ID.into(), run_id.into());
16        self.metadata.insert(
17            "runtime.host_launch_generation".into(),
18            self.child_launch_generation().to_string(),
19        );
20        // Materialize the new Host runtime before its completion source can
21        // bind the pipeline identity. Keep durable waits and accounting.
22        if self.agent_runtime_state.is_none() {
23            self.agent_runtime_state = self
24                .metadata
25                .get("agent.runtime.state")
26                .and_then(|raw| serde_json::from_str(raw).ok());
27        }
28        if !self.has_runtime_wait() {
29            let state = self
30                .agent_runtime_state
31                .get_or_insert_with(|| AgentRuntimeState::new(&self.id));
32            state.status = AgentStatusState::Initializing;
33            if let Ok(raw) = serde_json::to_string(state) {
34                self.metadata.insert("agent.runtime.state".into(), raw);
35            }
36        }
37    }
38
39    pub fn host_run_matches_launch(&self) -> bool {
40        match self.metadata.get("runtime.host_launch_generation") {
41            Some(value) => value.parse::<u64>().ok() == Some(self.child_launch_generation()),
42            None => self.host_run_id().is_none(),
43        }
44    }
45
46    /// A completed activation may have handed execution to a durable wait.
47    /// Neither a successful Worker return nor an absent runner clears it.
48    pub fn has_runtime_wait(&self) -> bool {
49        let legacy = self
50            .metadata
51            .get("agent.runtime.state")
52            .and_then(|raw| serde_json::from_str::<AgentRuntimeState>(raw).ok());
53        self.pending_question.is_some()
54            || self
55                .metadata
56                .get("runtime.suspend_reason")
57                .is_some_and(|s| !s.trim().is_empty())
58            || self
59                .agent_runtime_state
60                .as_ref()
61                .or(legacy.as_ref())
62                .is_some_and(|state| {
63                    state.status == AgentStatusState::Suspended
64                        || state.suspension.is_some()
65                        || state.waiting_for_children.is_some()
66                        || state.waiting_for_bash.is_some()
67                        || state.plan_mode.as_ref().is_some_and(|plan| {
68                            plan.status == crate::PlanModeStatus::AwaitingApproval
69                        })
70                })
71    }
72
73    /// Project an already trusted final outcome, preserving round/accounting
74    /// data and the legacy mirror. Never infer terminal truth from typed state.
75    pub fn synchronize_terminal_runtime_status(&mut self) {
76        if self.has_runtime_wait() {
77            return;
78        }
79        let status = match self.last_run_status().as_deref() {
80            Some("completed" | "skipped") => AgentStatusState::Completed,
81            Some("cancelled") => AgentStatusState::Cancelled,
82            Some("error" | "failed" | "timeout") => AgentStatusState::Failed,
83            _ => return,
84        };
85        if self.agent_runtime_state.is_none() {
86            self.agent_runtime_state = self
87                .metadata
88                .get("agent.runtime.state")
89                .and_then(|raw| serde_json::from_str::<AgentRuntimeState>(raw).ok());
90        }
91        // Legacy sources can bind the absence of a runtime identity. Adding
92        // one after sealing would invalidate their exact completion proof.
93        if self.agent_runtime_state.is_none()
94            && self
95                .metadata
96                .contains_key("runtime.child_completion_source_v1")
97        {
98            return;
99        }
100        let state = self
101            .agent_runtime_state
102            .get_or_insert_with(|| AgentRuntimeState::new(&self.id));
103        state.status = status;
104        state.tools.active_tool_calls = 0;
105        if let Ok(raw) = serde_json::to_string(state) {
106            self.metadata.insert("agent.runtime.state".into(), raw);
107        }
108    }
109}