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    /// Return pending HITL tool confirmations for this session.
43    pub async fn pending_confirmations(&self) -> Vec<PendingConfirmationInfo> {
44        HitlControl::from_session(self)
45            .pending_confirmations()
46            .await
47    }
48
49    /// Resolve a pending HITL tool confirmation.
50    ///
51    /// Returns `Ok(true)` when a pending confirmation was found and completed,
52    /// `Ok(false)` when the tool ID is not pending or HITL is not configured.
53    pub async fn confirm_tool_use(
54        &self,
55        tool_id: &str,
56        approved: bool,
57        reason: Option<String>,
58    ) -> Result<bool> {
59        HitlControl::from_session(self)
60            .confirm_tool_use(tool_id, approved, reason)
61            .await
62    }
63
64    /// Cancel all pending HITL confirmations for this session.
65    pub async fn cancel_confirmations(&self) -> usize {
66        HitlControl::from_session(self).cancel_confirmations().await
67    }
68
69    /// Return structured verification reports recorded for this session.
70    pub fn verification_reports(&self) -> Vec<crate::verification::VerificationReport> {
71        VerificationRuntime::from_session(self).reports()
72    }
73
74    /// Return a structured summary of all verification reports recorded for this session.
75    pub fn verification_summary(&self) -> crate::verification::VerificationSummary {
76        VerificationRuntime::from_session(self).summary()
77    }
78
79    /// Return a concise human-readable verification summary for this session.
80    pub fn verification_summary_text(&self) -> String {
81        VerificationRuntime::from_session(self).summary_text()
82    }
83
84    /// Add externally produced verification reports to this session's completion evidence.
85    pub fn record_verification_reports(
86        &self,
87        reports: impl IntoIterator<Item = crate::verification::VerificationReport>,
88    ) {
89        VerificationRuntime::from_session(self).record(reports);
90    }
91
92    /// Register a hook for lifecycle event interception.
93    pub fn register_hook(&self, hook: crate::hooks::Hook) -> crate::error::Result<()> {
94        self.close_handle.mutate_immediate(|| {
95            self.ensure_compatibility_name_available(
96                crate::capability::CapabilityKind::Hook,
97                &hook.id,
98            )?;
99            HookControl::from_session(self).register_hook(hook);
100            Ok(())
101        })?
102    }
103
104    /// Unregister a hook by ID.
105    pub fn unregister_hook(
106        &self,
107        hook_id: &str,
108    ) -> crate::error::Result<Option<crate::hooks::Hook>> {
109        self.close_handle.mutate_immediate(|| {
110            self.ensure_compatibility_name_available(
111                crate::capability::CapabilityKind::Hook,
112                hook_id,
113            )?;
114            Ok(HookControl::from_session(self).unregister_hook(hook_id))
115        })?
116    }
117
118    /// Register a handler for a specific hook.
119    pub fn register_hook_handler(
120        &self,
121        hook_id: &str,
122        handler: Arc<dyn crate::hooks::HookHandler>,
123    ) -> crate::error::Result<()> {
124        let mut pending_handler = Some(handler);
125        let retired = self.close_handle.mutate_immediate(|| {
126            self.ensure_compatibility_name_available(
127                crate::capability::CapabilityKind::Hook,
128                hook_id,
129            )?;
130            let handler = pending_handler.take().ok_or_else(|| {
131                crate::error::CodeError::Capability(
132                    crate::capability::CapabilityRuntimeError::RuntimeValueInvalid {
133                        kind: crate::capability::CapabilityKind::Hook,
134                        public_name: hook_id.to_owned(),
135                        message: "Hook handler mutation was already consumed".to_owned(),
136                    },
137                )
138            })?;
139            Ok::<_, crate::error::CodeError>(
140                HookControl::from_session(self).register_hook_handler(hook_id, handler),
141            )
142        })??;
143        drop(retired);
144        Ok(())
145    }
146
147    /// Unregister a hook handler by hook ID.
148    pub fn unregister_hook_handler(&self, hook_id: &str) -> crate::error::Result<()> {
149        let retired = self.close_handle.mutate_immediate(|| {
150            self.ensure_compatibility_name_available(
151                crate::capability::CapabilityKind::Hook,
152                hook_id,
153            )?;
154            Ok::<_, crate::error::CodeError>(
155                HookControl::from_session(self).unregister_hook_handler(hook_id),
156            )
157        })??;
158        drop(retired);
159        Ok(())
160    }
161
162    /// Atomically add or replace a complete Hook definition and handler pair.
163    ///
164    /// Official SDK bridges use this method so a newly admitted Run cannot
165    /// observe the definition without its matching callback. Passing `None`
166    /// intentionally registers an event-only Hook with no gating handler.
167    pub fn register_hook_registration(
168        &self,
169        hook: crate::hooks::Hook,
170        handler: Option<Arc<dyn crate::hooks::HookHandler>>,
171    ) -> crate::error::Result<()> {
172        let mut pending_handler = Some(handler);
173        let retired = self.close_handle.mutate_immediate(|| {
174            self.ensure_compatibility_name_available(
175                crate::capability::CapabilityKind::Hook,
176                &hook.id,
177            )?;
178            let handler = pending_handler.take().ok_or_else(|| {
179                crate::error::CodeError::Capability(
180                    crate::capability::CapabilityRuntimeError::RuntimeValueInvalid {
181                        kind: crate::capability::CapabilityKind::Hook,
182                        public_name: hook.id.clone(),
183                        message: "Hook registration was already consumed".to_owned(),
184                    },
185                )
186            })?;
187            Ok::<_, crate::error::CodeError>(
188                HookControl::from_session(self).register_hook_registration(hook, handler),
189            )
190        })??;
191        drop(retired);
192        Ok(())
193    }
194
195    /// Atomically remove a complete Hook definition and handler pair.
196    pub fn unregister_hook_registration(
197        &self,
198        hook_id: &str,
199    ) -> crate::error::Result<Option<crate::hooks::Hook>> {
200        let (retired_hook, retired_handler) = self.close_handle.mutate_immediate(|| {
201            self.ensure_compatibility_name_available(
202                crate::capability::CapabilityKind::Hook,
203                hook_id,
204            )?;
205            Ok::<_, crate::error::CodeError>(
206                HookControl::from_session(self).unregister_hook_registration(hook_id),
207            )
208        })??;
209        let removed = retired_hook.as_deref().cloned();
210        drop((retired_hook, retired_handler));
211        Ok(removed)
212    }
213
214    /// Get the number of registered hooks.
215    pub fn hook_count(&self) -> usize {
216        HookControl::from_session(self).hook_count()
217    }
218
219    /// Run verification commands through the session's tool execution path.
220    pub async fn verify_commands(
221        &self,
222        subject: &str,
223        commands: &[crate::verification::VerificationCommand],
224    ) -> Result<crate::verification::VerificationReport> {
225        VerificationRuntime::from_session(self)
226            .verify_commands(subject, commands)
227            .await
228    }
229
230    /// Return project-aware verification command presets for this workspace.
231    pub fn verification_presets(&self) -> Vec<crate::verification::VerificationPreset> {
232        VerificationRuntime::from_session(self).presets()
233    }
234}