Skip to main content

a3s_code_core/agent_api/
governance_facade.rs

1use super::*;
2
3impl AgentSession {
4    /// Install or replace a runtime budget guard. Takes effect on the
5    /// next `send` / `stream` call (the guard is consulted at agent-
6    /// loop build time, not on the live execution). Setting `None`
7    /// clears the override so `config.budget_guard` takes over again.
8    ///
9    /// This is the entry point SDKs use to wire a host-supplied guard
10    /// after the session has already been constructed — useful when
11    /// the guard's transport (e.g. a JS callable) cannot live inside
12    /// the value-typed `SessionOptions`.
13    pub fn set_budget_guard(
14        &self,
15        guard: Option<Arc<dyn crate::budget::BudgetGuard>>,
16    ) -> crate::error::Result<()> {
17        self.close_handle.mutate_immediate(|| {
18            let mut slot = self
19                .runtime_budget_guard
20                .lock()
21                .unwrap_or_else(|p| p.into_inner());
22            *slot = guard;
23            drop(slot);
24            // Delegated children own a pre-built TaskExecutor. Refresh its parent
25            // context so the next run cannot bypass a runtime-installed ledger.
26            // SkillTool likewise owns a child AgentConfig captured at registration
27            // time, so it must be refreshed from the same runtime source of truth.
28            self.refresh_task_delegation_tools();
29            self.refresh_skill_tools();
30        })
31    }
32
33    /// Return the currently-installed runtime budget guard, if any.
34    /// `None` means the loop falls back to `config.budget_guard`.
35    pub fn budget_guard(&self) -> Option<Arc<dyn crate::budget::BudgetGuard>> {
36        self.runtime_budget_guard
37            .lock()
38            .unwrap_or_else(|p| p.into_inner())
39            .clone()
40    }
41
42    /// Install or clear the host-owned live checkpoint export sink (SDK-CP1).
43    ///
44    /// Takes effect on the next `send` / `stream` that opens a checkpoint
45    /// channel. Callback-backed SDK hosts use this after session construction
46    /// because value-typed `SessionOptions` cannot carry language callables.
47    pub fn set_session_checkpoint_export_sink(
48        &self,
49        sink: Option<Arc<dyn crate::session_checkpoint::SessionCheckpointExportSink>>,
50    ) -> crate::error::Result<()> {
51        self.close_handle.mutate_immediate(|| {
52            let mut slot = self
53                .runtime_session_checkpoint_export_sink
54                .lock()
55                .unwrap_or_else(|p| p.into_inner());
56            *slot = sink;
57        })
58    }
59
60    /// Return the currently installed live checkpoint export sink, if any.
61    pub fn session_checkpoint_export_sink(
62        &self,
63    ) -> Option<Arc<dyn crate::session_checkpoint::SessionCheckpointExportSink>> {
64        self.runtime_session_checkpoint_export_sink
65            .lock()
66            .unwrap_or_else(|p| p.into_inner())
67            .clone()
68    }
69
70    /// Return pending HITL tool confirmations for this session.
71    pub async fn pending_confirmations(&self) -> Vec<PendingConfirmationInfo> {
72        HitlControl::from_session(self)
73            .pending_confirmations()
74            .await
75    }
76
77    /// Resolve a pending HITL tool confirmation.
78    ///
79    /// Returns `Ok(true)` when a pending confirmation was found and completed,
80    /// `Ok(false)` when the tool ID is not pending or HITL is not configured.
81    pub async fn confirm_tool_use(
82        &self,
83        tool_id: &str,
84        approved: bool,
85        reason: Option<String>,
86    ) -> Result<bool> {
87        HitlControl::from_session(self)
88            .confirm_tool_use(tool_id, approved, reason)
89            .await
90    }
91
92    /// Cancel all pending HITL confirmations for this session.
93    pub async fn cancel_confirmations(&self) -> usize {
94        HitlControl::from_session(self).cancel_confirmations().await
95    }
96
97    /// Return structured verification reports recorded for this session.
98    pub fn verification_reports(&self) -> Vec<crate::verification::VerificationReport> {
99        VerificationRuntime::from_session(self).reports()
100    }
101
102    /// Return a structured summary of all verification reports recorded for this session.
103    pub fn verification_summary(&self) -> crate::verification::VerificationSummary {
104        VerificationRuntime::from_session(self).summary()
105    }
106
107    /// Return a concise human-readable verification summary for this session.
108    pub fn verification_summary_text(&self) -> String {
109        VerificationRuntime::from_session(self).summary_text()
110    }
111
112    /// Add externally produced verification reports to this session's completion evidence.
113    pub fn record_verification_reports(
114        &self,
115        reports: impl IntoIterator<Item = crate::verification::VerificationReport>,
116    ) {
117        VerificationRuntime::from_session(self).record(reports);
118    }
119
120    /// Register a hook for lifecycle event interception.
121    pub fn register_hook(&self, hook: crate::hooks::Hook) -> crate::error::Result<()> {
122        self.close_handle.mutate_immediate(|| {
123            self.ensure_compatibility_name_available(
124                crate::capability::CapabilityKind::Hook,
125                &hook.id,
126            )?;
127            HookControl::from_session(self).register_hook(hook);
128            Ok(())
129        })?
130    }
131
132    /// Unregister a hook by ID.
133    pub fn unregister_hook(
134        &self,
135        hook_id: &str,
136    ) -> crate::error::Result<Option<crate::hooks::Hook>> {
137        self.close_handle.mutate_immediate(|| {
138            self.ensure_compatibility_name_available(
139                crate::capability::CapabilityKind::Hook,
140                hook_id,
141            )?;
142            Ok(HookControl::from_session(self).unregister_hook(hook_id))
143        })?
144    }
145
146    /// Register a handler for a specific hook.
147    pub fn register_hook_handler(
148        &self,
149        hook_id: &str,
150        handler: Arc<dyn crate::hooks::HookHandler>,
151    ) -> crate::error::Result<()> {
152        let mut pending_handler = Some(handler);
153        let retired = self.close_handle.mutate_immediate(|| {
154            self.ensure_compatibility_name_available(
155                crate::capability::CapabilityKind::Hook,
156                hook_id,
157            )?;
158            let handler = pending_handler.take().ok_or_else(|| {
159                crate::error::CodeError::Capability(
160                    crate::capability::CapabilityRuntimeError::RuntimeValueInvalid {
161                        kind: crate::capability::CapabilityKind::Hook,
162                        public_name: hook_id.to_owned(),
163                        message: "Hook handler mutation was already consumed".to_owned(),
164                    },
165                )
166            })?;
167            Ok::<_, crate::error::CodeError>(
168                HookControl::from_session(self).register_hook_handler(hook_id, handler),
169            )
170        })??;
171        drop(retired);
172        Ok(())
173    }
174
175    /// Unregister a hook handler by hook ID.
176    pub fn unregister_hook_handler(&self, hook_id: &str) -> crate::error::Result<()> {
177        let retired = self.close_handle.mutate_immediate(|| {
178            self.ensure_compatibility_name_available(
179                crate::capability::CapabilityKind::Hook,
180                hook_id,
181            )?;
182            Ok::<_, crate::error::CodeError>(
183                HookControl::from_session(self).unregister_hook_handler(hook_id),
184            )
185        })??;
186        drop(retired);
187        Ok(())
188    }
189
190    /// Atomically add or replace a complete Hook definition and handler pair.
191    ///
192    /// Official SDK bridges use this method so a newly admitted Run cannot
193    /// observe the definition without its matching callback. Passing `None`
194    /// intentionally registers an event-only Hook with no gating handler.
195    pub fn register_hook_registration(
196        &self,
197        hook: crate::hooks::Hook,
198        handler: Option<Arc<dyn crate::hooks::HookHandler>>,
199    ) -> crate::error::Result<()> {
200        let mut pending_handler = Some(handler);
201        let retired = self.close_handle.mutate_immediate(|| {
202            self.ensure_compatibility_name_available(
203                crate::capability::CapabilityKind::Hook,
204                &hook.id,
205            )?;
206            let handler = pending_handler.take().ok_or_else(|| {
207                crate::error::CodeError::Capability(
208                    crate::capability::CapabilityRuntimeError::RuntimeValueInvalid {
209                        kind: crate::capability::CapabilityKind::Hook,
210                        public_name: hook.id.clone(),
211                        message: "Hook registration was already consumed".to_owned(),
212                    },
213                )
214            })?;
215            Ok::<_, crate::error::CodeError>(
216                HookControl::from_session(self).register_hook_registration(hook, handler),
217            )
218        })??;
219        drop(retired);
220        Ok(())
221    }
222
223    /// Atomically remove a complete Hook definition and handler pair.
224    pub fn unregister_hook_registration(
225        &self,
226        hook_id: &str,
227    ) -> crate::error::Result<Option<crate::hooks::Hook>> {
228        let (retired_hook, retired_handler) = self.close_handle.mutate_immediate(|| {
229            self.ensure_compatibility_name_available(
230                crate::capability::CapabilityKind::Hook,
231                hook_id,
232            )?;
233            Ok::<_, crate::error::CodeError>(
234                HookControl::from_session(self).unregister_hook_registration(hook_id),
235            )
236        })??;
237        let removed = retired_hook.as_deref().cloned();
238        drop((retired_hook, retired_handler));
239        Ok(removed)
240    }
241
242    /// Get the number of registered hooks.
243    pub fn hook_count(&self) -> usize {
244        HookControl::from_session(self).hook_count()
245    }
246
247    /// Run verification commands through the session's tool execution path.
248    pub async fn verify_commands(
249        &self,
250        subject: &str,
251        commands: &[crate::verification::VerificationCommand],
252    ) -> Result<crate::verification::VerificationReport> {
253        VerificationRuntime::from_session(self)
254            .verify_commands(subject, commands)
255            .await
256    }
257
258    /// Return project-aware verification command presets for this workspace.
259    pub fn verification_presets(&self) -> Vec<crate::verification::VerificationPreset> {
260        VerificationRuntime::from_session(self).presets()
261    }
262}