1use super::*;
2
3type OpsLifecyclePersistenceReceiver = crate::tokio::sync::mpsc::UnboundedReceiver<
4 crate::ops_lifecycle::OpsLifecyclePersistenceRequest,
5>;
6
7#[derive(Debug, Clone)]
8struct RuntimeOpsLifecycleDurabilityAuthority {
9 action: crate::meerkat_machine::dsl::RuntimeOpsLifecycleDurabilityAction,
10}
11
12#[derive(Debug, Clone)]
13struct RuntimeLifecycleRecoveryObservation {
14 runtime_state: RuntimeState,
15 agent_runtime_id: Option<LogicalRuntimeId>,
16 fence_token: Option<u64>,
17 runtime_generation: Option<crate::meerkat_machine::dsl::Generation>,
18 runtime_epoch_id: Option<crate::meerkat_machine::dsl::RuntimeEpochId>,
19 recovered_from_snapshot: bool,
20}
21
22impl RuntimeLifecycleRecoveryObservation {
23 fn from_snapshot(snapshot: Option<crate::store::MachineLifecycleSnapshot>) -> Self {
24 let Some(snapshot) = snapshot else {
25 return Self {
26 runtime_state: RuntimeState::Idle,
27 agent_runtime_id: None,
28 fence_token: None,
29 runtime_generation: None,
30 runtime_epoch_id: None,
31 recovered_from_snapshot: false,
32 };
33 };
34 let binding = snapshot.binding();
35 Self {
36 runtime_state: snapshot.runtime_state(),
37 agent_runtime_id: binding
38 .agent_runtime_id()
39 .map(|value| LogicalRuntimeId::new(value.to_owned())),
40 fence_token: binding.fence_token(),
41 runtime_generation: binding
42 .runtime_generation()
43 .map(crate::meerkat_machine::dsl::Generation::from),
44 runtime_epoch_id: binding
45 .runtime_epoch_id()
46 .map(crate::meerkat_machine::dsl::RuntimeEpochId::from),
47 recovered_from_snapshot: true,
48 }
49 }
50
51 fn requires_observed_recovery(&self) -> bool {
52 self.recovered_from_snapshot
53 && (self.runtime_state != RuntimeState::Idle
54 || self.agent_runtime_id.is_some()
55 || self.fence_token.is_some()
56 || self.runtime_generation.is_some()
57 || self.runtime_epoch_id.is_some())
58 }
59}
60
61fn fresh_registered_runtime_authority(
62 session_id: &SessionId,
63 context: &'static str,
64) -> Result<crate::meerkat_machine::dsl::MeerkatMachineAuthority, RuntimeDriverError> {
65 let mut authority = super::dsl_authority::new_initialized_authority(context);
66 crate::meerkat_machine::dsl::MeerkatMachineMutator::apply(
67 &mut authority,
68 crate::meerkat_machine::dsl::MeerkatMachineInput::RegisterSession {
69 session_id: crate::meerkat_machine::dsl::SessionId::from_domain(session_id),
70 },
71 )
72 .map_err(|err| {
73 RuntimeDriverError::Internal(super::dsl_authority::map_error(
74 err,
75 "fresh session registration",
76 ))
77 })?;
78 Ok(authority)
79}
80
81fn runtime_ops_lifecycle_durability_authority_from_effects(
82 session_id: &SessionId,
83 effects: &[crate::meerkat_machine::dsl::MeerkatMachineEffect],
84) -> Result<RuntimeOpsLifecycleDurabilityAuthority, RuntimeDriverError> {
85 let expected_session_id = crate::meerkat_machine::dsl::SessionId::from_domain(session_id);
86 effects
87 .iter()
88 .find_map(|effect| match effect {
89 crate::meerkat_machine::dsl::MeerkatMachineEffect::RuntimeOpsLifecycleDurabilityResolved {
90 session_id,
91 action,
92 ..
93 } if session_id == &expected_session_id => {
94 Some(RuntimeOpsLifecycleDurabilityAuthority { action: *action })
95 }
96 _ => None,
97 })
98 .ok_or_else(|| {
99 RuntimeDriverError::Internal(format!(
100 "UnregisterSession for session '{session_id}' emitted no RuntimeOpsLifecycleDurabilityResolved effect"
101 ))
102 })
103}
104
105async fn persist_ops_lifecycle_request(
106 store: &Arc<dyn RuntimeStore>,
107 runtime_id: &LogicalRuntimeId,
108 request: crate::ops_lifecycle::OpsLifecyclePersistenceRequest,
109) {
110 let result = store
111 .persist_ops_lifecycle(runtime_id, request.snapshot())
112 .await
113 .map_err(|error| {
114 meerkat_core::ops_lifecycle::OpsLifecycleError::Internal(format!(
115 "failed to persist ops lifecycle snapshot: {error}"
116 ))
117 });
118 if let Err(error) = &result {
119 tracing::warn!(
120 %runtime_id,
121 error = %error,
122 "failed to persist ops lifecycle snapshot"
123 );
124 }
125 request.complete(result);
126}
127
128#[cfg(not(target_arch = "wasm32"))]
129fn spawn_ops_lifecycle_persistence_worker(
130 store: Arc<dyn RuntimeStore>,
131 runtime_id: LogicalRuntimeId,
132 mut persist_rx: OpsLifecyclePersistenceReceiver,
133) {
134 let thread_name = format!("ops-lifecycle-persist-{runtime_id}");
135 let worker_runtime_id = runtime_id.clone();
136 let spawn_result = std::thread::Builder::new()
137 .name(thread_name)
138 .spawn(move || {
139 let runtime = match crate::tokio::runtime::Builder::new_current_thread()
140 .enable_all()
141 .build()
142 {
143 Ok(runtime) => runtime,
144 Err(error) => {
145 tracing::error!(
146 %worker_runtime_id,
147 error = %error,
148 "failed to start ops lifecycle persistence worker runtime"
149 );
150 return;
151 }
152 };
153 runtime.block_on(async move {
154 while let Some(request) = persist_rx.recv().await {
155 persist_ops_lifecycle_request(&store, &worker_runtime_id, request).await;
156 }
157 });
158 });
159 if let Err(error) = spawn_result {
160 tracing::error!(
161 %runtime_id,
162 error = %error,
163 "failed to spawn ops lifecycle persistence worker"
164 );
165 }
166}
167
168#[cfg(target_arch = "wasm32")]
169fn spawn_ops_lifecycle_persistence_worker(
170 store: Arc<dyn RuntimeStore>,
171 runtime_id: LogicalRuntimeId,
172 mut persist_rx: OpsLifecyclePersistenceReceiver,
173) {
174 crate::tokio::spawn(async move {
175 while let Some(request) = persist_rx.recv().await {
176 persist_ops_lifecycle_request(&store, &runtime_id, request).await;
177 }
178 });
179}
180
181impl MeerkatMachine {
182 async fn durable_lifecycle_for_registration(
183 &self,
184 runtime_id: &LogicalRuntimeId,
185 ) -> Result<Option<crate::store::MachineLifecycleSnapshot>, RuntimeDriverError> {
186 let Some(store) = self.store.as_ref() else {
187 return Ok(None);
188 };
189 crate::store::load_machine_lifecycle(store.as_ref(), runtime_id)
190 .await
191 .map_err(|err| RuntimeDriverError::Internal(err.to_string()))
192 }
193
194 pub(super) async fn register_session_inner(
195 &self,
196 session_id: SessionId,
197 ) -> Result<bool, RuntimeDriverError> {
198 let storeless = self.store.is_none();
199 tracing::debug!(%session_id, storeless, "MeerkatMachine::register_session_inner start");
200 #[cfg(target_arch = "wasm32")]
201 if storeless {
202 {
203 tracing::debug!(%session_id, "MeerkatMachine::register_session_inner attempting storeless existing check lock");
204 let mut sessions = self.sessions.try_write().map_err(|_| {
205 tracing::warn!(
206 %session_id,
207 "storeless session map busy while checking existing registration"
208 );
209 RuntimeDriverError::Internal(format!(
210 "storeless session map busy while registering {session_id}"
211 ))
212 })?;
213 tracing::debug!(%session_id, "MeerkatMachine::register_session_inner locked storeless existing check");
214 if let Some(existing) = sessions.get_mut(&session_id) {
215 tracing::debug!(
216 %session_id,
217 "MeerkatMachine::register_session_inner found existing session"
218 );
219 if existing.clear_dead_attachment() {
220 existing.stage_generated_executor_exit_observation().map_err(|reason| {
221 RuntimeDriverError::Internal(format!(
222 "generated MeerkatMachine rejected executor-exit observation: {reason}"
223 ))
224 })?;
225 }
226 return Ok(false);
227 }
228 }
229 return self.register_storeless_session_inner_sync_build_step(session_id);
230 }
231 #[cfg(not(target_arch = "wasm32"))]
232 if storeless {
233 return Box::pin(self.register_storeless_session_inner(session_id)).await;
234 }
235 Box::pin(self.register_session_inner_impl(session_id)).await
236 }
237
238 #[cfg(target_arch = "wasm32")]
239 #[inline(never)]
240 #[allow(dead_code)]
241 fn register_storeless_session_inner_sync(
242 &self,
243 session_id: SessionId,
244 ) -> Result<bool, RuntimeDriverError> {
245 tracing::debug!(%session_id, "MeerkatMachine::register_storeless_session_inner_sync start");
246 {
247 tracing::debug!(%session_id, "MeerkatMachine::register_storeless_session_inner_sync attempting existing check lock");
248 let mut sessions = self.sessions.try_write().map_err(|_| {
249 tracing::warn!(
250 %session_id,
251 "storeless session map busy while checking existing registration"
252 );
253 RuntimeDriverError::Internal(format!(
254 "storeless session map busy while registering {session_id}"
255 ))
256 })?;
257 tracing::debug!(%session_id, "MeerkatMachine::register_storeless_session_inner_sync locked existing check");
258 if let Some(existing) = sessions.get_mut(&session_id) {
259 tracing::debug!(
260 %session_id,
261 "MeerkatMachine::register_session_inner found existing session"
262 );
263 if existing.clear_dead_attachment() {
264 existing.stage_generated_executor_exit_observation().map_err(|reason| {
265 RuntimeDriverError::Internal(format!(
266 "generated MeerkatMachine rejected executor-exit observation: {reason}"
267 ))
268 })?;
269 }
270 return Ok(false);
271 }
272 }
273 self.register_storeless_session_inner_sync_build_step(session_id)
274 }
275
276 #[cfg(target_arch = "wasm32")]
277 #[inline(never)]
278 pub(super) fn register_storeless_session_inner_sync_build_step(
279 &self,
280 session_id: SessionId,
281 ) -> Result<bool, RuntimeDriverError> {
282 let (runtime_id, session_entry) = self.make_storeless_session_entry_sync(&session_id)?;
283 self.insert_storeless_session_sync(session_id, runtime_id, session_entry)
284 }
285
286 #[cfg(target_arch = "wasm32")]
287 #[inline(never)]
288 fn make_storeless_session_entry_sync(
289 &self,
290 session_id: &SessionId,
291 ) -> Result<(LogicalRuntimeId, RuntimeSessionEntry), RuntimeDriverError> {
292 let runtime_id = Self::logical_runtime_id(session_id);
293 let recovered_authority =
294 fresh_registered_runtime_authority(session_id, "fresh storeless session registration")?;
295 let initial_runtime_state =
296 super::dsl_authority::runtime_phase_from_authority(&recovered_authority);
297 let dsl_authority = Arc::new(std::sync::Mutex::new(recovered_authority));
298 let entry = self.make_driver(
299 runtime_id.clone(),
300 Arc::clone(&dsl_authority),
301 initial_runtime_state,
302 );
303 let control_projection = entry.control_projection_handle();
304 let (ops_lifecycle, epoch_id, cursor_state) = Self::fresh_ops_state();
305 let handle_teardown_gate = crate::handles::HandleTeardownGate::open();
306 let tool_visibility_owner = Arc::new(MachineToolVisibilityOwner::new());
307 tool_visibility_owner.bind_dsl_authority(Arc::clone(&dsl_authority));
308 let session_entry = RuntimeSessionEntry {
309 runtime_id: runtime_id.clone(),
310 mutation_gate: Arc::new(Mutex::new(())),
311 control_projection,
312 driver: Arc::new(Mutex::new(entry)),
313 ops_lifecycle,
314 epoch_id,
315 handle_teardown_gate,
316 cursor_state,
317 completions: Arc::new(Mutex::new(crate::completion::CompletionRegistry::new())),
318 tool_visibility_owner,
319 attachment_slot: RuntimeLoopAttachmentSlot::Empty,
320 provisional_interrupt_handle: None,
321 dsl_authority,
322 drain_slot: CommsDrainSlot::new(),
323 };
324 Ok((runtime_id, session_entry))
325 }
326
327 #[cfg(target_arch = "wasm32")]
328 #[inline(never)]
329 fn insert_storeless_session_sync(
330 &self,
331 session_id: SessionId,
332 runtime_id: LogicalRuntimeId,
333 session_entry: RuntimeSessionEntry,
334 ) -> Result<bool, RuntimeDriverError> {
335 let mut sessions = self.sessions.try_write().map_err(|_| {
336 tracing::warn!(
337 %session_id,
338 "storeless session map busy while inserting registration"
339 );
340 RuntimeDriverError::Internal(format!(
341 "storeless session map busy while inserting {session_id}"
342 ))
343 })?;
344 tracing::debug!(%session_id, "MeerkatMachine::register_storeless_session_inner_sync locked insert");
345 if let Some(existing) = sessions.get_mut(&session_id) {
346 if existing.clear_dead_attachment() {
347 existing
348 .stage_generated_executor_exit_observation()
349 .map_err(|reason| {
350 RuntimeDriverError::Internal(format!(
351 "generated MeerkatMachine rejected executor-exit observation: {reason}"
352 ))
353 })?;
354 }
355 Ok(false)
356 } else {
357 sessions.insert(session_id, session_entry);
358 tracing::debug!(
359 %runtime_id,
360 "MeerkatMachine::register_session_inner inserted storeless session"
361 );
362 Ok(true)
363 }
364 }
365
366 #[cfg(not(target_arch = "wasm32"))]
367 async fn register_storeless_session_inner(
368 &self,
369 session_id: SessionId,
370 ) -> Result<bool, RuntimeDriverError> {
371 #[cfg(target_arch = "wasm32")]
372 {
373 let mut sessions = self.sessions.try_write().map_err(|_| {
374 RuntimeDriverError::Internal(format!(
375 "storeless session map busy while registering {session_id}"
376 ))
377 })?;
378 if let Some(existing) = sessions.get_mut(&session_id) {
379 tracing::debug!(
380 %session_id,
381 "MeerkatMachine::register_session_inner found existing session"
382 );
383 if existing.clear_dead_attachment() {
384 existing.stage_generated_executor_exit_observation().map_err(|reason| {
385 RuntimeDriverError::Internal(format!(
386 "generated MeerkatMachine rejected executor-exit observation: {reason}"
387 ))
388 })?;
389 }
390 return Ok(false);
391 }
392 }
393 #[cfg(not(target_arch = "wasm32"))]
394 {
395 let mut sessions = self.sessions.write().await;
396 if let Some(existing) = sessions.get_mut(&session_id) {
397 tracing::debug!(
398 %session_id,
399 "MeerkatMachine::register_session_inner found existing session"
400 );
401 if existing.clear_dead_attachment() {
402 existing.stage_generated_executor_exit_observation().map_err(|reason| {
403 RuntimeDriverError::Internal(format!(
404 "generated MeerkatMachine rejected executor-exit observation: {reason}"
405 ))
406 })?;
407 }
408 return Ok(false);
409 }
410 }
411
412 let runtime_id = Self::logical_runtime_id(&session_id);
413 let recovered_authority = fresh_registered_runtime_authority(
414 &session_id,
415 "fresh storeless session registration",
416 )?;
417 let initial_runtime_state =
418 super::dsl_authority::runtime_phase_from_authority(&recovered_authority);
419 let dsl_authority = Arc::new(std::sync::Mutex::new(recovered_authority));
420 let mut entry = self.make_driver(
421 runtime_id.clone(),
422 Arc::clone(&dsl_authority),
423 initial_runtime_state,
424 );
425 tracing::debug!(
426 %session_id,
427 %runtime_id,
428 "MeerkatMachine::register_session_inner recovering storeless driver"
429 );
430 if let Err(err) = entry.as_driver_mut().recover().await {
431 tracing::error!(%session_id, error = %err, "failed to recover runtime driver during registration");
432 return Err(err);
433 }
434 let control_projection = entry.control_projection_handle();
435
436 let (ops_lifecycle, epoch_id, cursor_state) = Self::fresh_ops_state();
437 let handle_teardown_gate = crate::handles::HandleTeardownGate::open();
438 let tool_visibility_owner = Arc::new(MachineToolVisibilityOwner::new());
439 tool_visibility_owner.bind_dsl_authority(Arc::clone(&dsl_authority));
440 let session_entry = RuntimeSessionEntry {
441 runtime_id: runtime_id.clone(),
442 mutation_gate: Arc::new(Mutex::new(())),
443 control_projection,
444 driver: Arc::new(Mutex::new(entry)),
445 ops_lifecycle,
446 epoch_id,
447 handle_teardown_gate,
448 cursor_state,
449 completions: Arc::new(Mutex::new(crate::completion::CompletionRegistry::new())),
450 tool_visibility_owner,
451 attachment_slot: RuntimeLoopAttachmentSlot::Empty,
452 provisional_interrupt_handle: None,
453 dsl_authority,
454 drain_slot: CommsDrainSlot::new(),
455 };
456 #[cfg(target_arch = "wasm32")]
457 {
458 let mut sessions = self.sessions.try_write().map_err(|_| {
459 RuntimeDriverError::Internal(format!(
460 "storeless session map busy while inserting {session_id}"
461 ))
462 })?;
463 if let Some(existing) = sessions.get_mut(&session_id) {
464 if existing.clear_dead_attachment() {
465 existing
466 .stage_generated_executor_exit_observation()
467 .map_err(|reason| {
468 RuntimeDriverError::Internal(format!(
469 "generated MeerkatMachine rejected executor-exit observation: {reason}"
470 ))
471 })?;
472 }
473 Ok(false)
474 } else {
475 sessions.insert(session_id, session_entry);
476 tracing::debug!(
477 %runtime_id,
478 "MeerkatMachine::register_session_inner inserted storeless session"
479 );
480 Ok(true)
481 }
482 }
483 #[cfg(not(target_arch = "wasm32"))]
484 {
485 let mut sessions = self.sessions.write().await;
486 if let Some(existing) = sessions.get_mut(&session_id) {
487 if existing.clear_dead_attachment() {
488 existing
489 .stage_generated_executor_exit_observation()
490 .map_err(|reason| {
491 RuntimeDriverError::Internal(format!(
492 "generated MeerkatMachine rejected executor-exit observation: {reason}"
493 ))
494 })?;
495 }
496 Ok(false)
497 } else {
498 sessions.insert(session_id, session_entry);
499 tracing::debug!(
500 %runtime_id,
501 "MeerkatMachine::register_session_inner inserted storeless session"
502 );
503 Ok(true)
504 }
505 }
506 }
507
508 async fn register_session_inner_impl(
509 &self,
510 session_id: SessionId,
511 ) -> Result<bool, RuntimeDriverError> {
512 {
513 let mut sessions = self.sessions.write().await;
514 if let Some(existing) = sessions.get_mut(&session_id) {
515 tracing::debug!(
516 %session_id,
517 "MeerkatMachine::register_session_inner found existing session"
518 );
519 if existing.clear_dead_attachment() {
520 existing.stage_generated_executor_exit_observation().map_err(|reason| {
521 RuntimeDriverError::Internal(format!(
522 "generated MeerkatMachine rejected executor-exit observation: {reason}"
523 ))
524 })?;
525 }
526 return Ok(false);
527 }
528 }
529
530 let runtime_id = Self::logical_runtime_id(&session_id);
531 tracing::debug!(
532 %session_id,
533 %runtime_id,
534 "MeerkatMachine::register_session_inner loading durable lifecycle"
535 );
536 let recovery_observation = RuntimeLifecycleRecoveryObservation::from_snapshot(
537 self.durable_lifecycle_for_registration(&runtime_id).await?,
538 );
539 tracing::debug!(
540 %session_id,
541 %runtime_id,
542 "MeerkatMachine::register_session_inner loaded durable lifecycle"
543 );
544 let observed_runtime_state = recovery_observation.runtime_state;
545 let requires_observed_recovery = recovery_observation.requires_observed_recovery();
546 let recovered_authority = if requires_observed_recovery {
547 super::dsl_authority::recover_authority_from_runtime_observation(
548 &session_id,
549 observed_runtime_state,
550 recovery_observation.agent_runtime_id.as_ref(),
551 None,
552 None,
553 std::collections::BTreeSet::new(),
554 recovery_observation.fence_token,
555 recovery_observation.runtime_generation,
556 recovery_observation.runtime_epoch_id,
557 )
558 .map_err(|err| {
559 RuntimeDriverError::Internal(super::dsl_authority::map_error(
560 err,
561 "session registration DSL recovery",
562 ))
563 })?
564 } else {
565 fresh_registered_runtime_authority(&session_id, "fresh session registration")?
566 };
567 let initial_runtime_state =
571 super::dsl_authority::runtime_phase_from_authority(&recovered_authority);
572 let dsl_authority = Arc::new(std::sync::Mutex::new(recovered_authority));
573 tracing::debug!(
574 %session_id,
575 %runtime_id,
576 ?initial_runtime_state,
577 "MeerkatMachine::register_session_inner recovered authority"
578 );
579 let mut entry = self.make_driver(
580 runtime_id.clone(),
581 Arc::clone(&dsl_authority),
582 initial_runtime_state,
583 );
584 tracing::debug!(
585 %session_id,
586 %runtime_id,
587 "MeerkatMachine::register_session_inner recovering driver"
588 );
589 if let Err(err) = entry.as_driver_mut().recover().await {
590 tracing::error!(%session_id, error = %err, "failed to recover runtime driver during registration");
591 return Err(err);
592 }
593 tracing::debug!(
594 %session_id,
595 %runtime_id,
596 "MeerkatMachine::register_session_inner recovered driver"
597 );
598 let control_projection = entry.control_projection_handle();
599
600 tracing::debug!(
601 %session_id,
602 %runtime_id,
603 "MeerkatMachine::register_session_inner recovering ops state"
604 );
605 let (ops_lifecycle, epoch_id, cursor_state) = if self.store.is_some()
606 || (requires_observed_recovery && initial_runtime_state != RuntimeState::Idle)
607 {
608 self.recover_or_create_ops_state(&session_id, &runtime_id)
609 .await?
610 } else {
611 Self::fresh_ops_state()
612 };
613 tracing::debug!(
614 %session_id,
615 %runtime_id,
616 %epoch_id,
617 "MeerkatMachine::register_session_inner recovered ops state"
618 );
619
620 let tool_visibility_owner = Arc::new(MachineToolVisibilityOwner::new());
621 tool_visibility_owner.bind_dsl_authority(Arc::clone(&dsl_authority));
625 let handle_teardown_gate = crate::handles::HandleTeardownGate::open();
626 let session_entry = RuntimeSessionEntry {
627 runtime_id: runtime_id.clone(),
628 mutation_gate: Arc::new(Mutex::new(())),
629 control_projection,
630 driver: Arc::new(Mutex::new(entry)),
631 ops_lifecycle,
632 epoch_id,
633 handle_teardown_gate,
634 cursor_state,
635 completions: Arc::new(Mutex::new(crate::completion::CompletionRegistry::new())),
636 tool_visibility_owner,
637 attachment_slot: RuntimeLoopAttachmentSlot::Empty,
638 provisional_interrupt_handle: None,
639 dsl_authority,
640 drain_slot: CommsDrainSlot::new(),
641 };
642 tracing::debug!(
643 %session_id,
644 %runtime_id,
645 "MeerkatMachine::register_session_inner inserting session"
646 );
647 let mut sessions = self.sessions.write().await;
648 if let Some(existing) = sessions.get_mut(&session_id) {
649 tracing::debug!(
650 %session_id,
651 %runtime_id,
652 "MeerkatMachine::register_session_inner found existing session before insert"
653 );
654 if existing.clear_dead_attachment() {
655 existing
656 .stage_generated_executor_exit_observation()
657 .map_err(|reason| {
658 RuntimeDriverError::Internal(format!(
659 "generated MeerkatMachine rejected executor-exit observation: {reason}"
660 ))
661 })?;
662 }
663 Ok(false)
664 } else {
665 sessions.insert(session_id, session_entry);
666 tracing::debug!(
667 %runtime_id,
668 "MeerkatMachine::register_session_inner inserted session"
669 );
670 Ok(true)
671 }
672 }
673
674 pub(super) async fn unregister_session_inner_if_epoch(
675 &self,
676 session_id: &SessionId,
677 epoch_id: &meerkat_core::RuntimeEpochId,
678 ) {
679 let Some(gate_guard) = self.lock_current_session_mutation_gate(session_id).await else {
680 return;
681 };
682 {
683 let sessions = self.sessions.read().await;
684 let Some(entry) = sessions.get(session_id) else {
685 return;
686 };
687 if &entry.epoch_id != epoch_id {
688 return;
689 }
690 }
691 if let Err(err) = self
692 .unregister_session_inner_locked_authorized(session_id, gate_guard)
693 .await
694 {
695 tracing::warn!(
696 %session_id,
697 error = %err,
698 "generated MeerkatMachine rejected epoch-scoped session unregister"
699 );
700 }
701 }
702
703 pub async fn set_session_silent_intents(
708 &self,
709 session_id: &SessionId,
710 intents: Vec<String>,
711 ) -> Result<(), RuntimeDriverError> {
712 match self
713 .execute_meerkat_machine_command(
714 None,
715 MeerkatMachineCommand::SetSilentIntents {
716 session_id: session_id.clone(),
717 intents,
718 },
719 )
720 .await
721 .map_err(MeerkatMachine::driver_error_from_command_error)?
722 {
723 MeerkatMachineCommandResult::Unit => Ok(()),
724 other => Err(RuntimeDriverError::Internal(format!(
725 "set_session_silent_intents: unexpected command result variant: {other:?}"
726 ))),
727 }
728 }
729
730 pub async fn commit_service_turn_terminal_receipt(
731 &self,
732 session_id: &SessionId,
733 ) -> Result<(), RuntimeDriverError> {
734 match self
735 .execute_meerkat_machine_command(
736 None,
737 MeerkatMachineCommand::CommitServiceTurnTerminalReceipt {
738 session_id: session_id.clone(),
739 },
740 )
741 .await
742 .map_err(|err| match err {
743 MeerkatMachineCommandError::Driver(err) => err,
744 MeerkatMachineCommandError::Control(err) => {
745 RuntimeDriverError::Internal(err.to_string())
746 }
747 })? {
748 MeerkatMachineCommandResult::Unit => Ok(()),
749 _ => Err(RuntimeDriverError::Internal(
750 "commit_service_turn_terminal_receipt: unexpected command result variant".into(),
751 )),
752 }
753 }
754
755 pub async fn register_session_with_executor(
759 self: &Arc<Self>,
760 session_id: SessionId,
761 executor: Box<dyn meerkat_core::lifecycle::CoreExecutor>,
762 ) -> Result<(), RuntimeDriverError> {
763 match self
764 .execute_meerkat_machine_command(
765 Some(Arc::clone(self)),
766 MeerkatMachineCommand::EnsureSessionWithExecutor {
767 session_id,
768 executor,
769 },
770 )
771 .await
772 .map_err(MeerkatMachine::driver_error_from_command_error)?
773 {
774 MeerkatMachineCommandResult::Unit => Ok(()),
775 other => Err(RuntimeDriverError::Internal(format!(
776 "register_session_with_executor: unexpected command result variant: {other:?}"
777 ))),
778 }
779 }
780
781 pub async fn ensure_session_with_executor(
788 self: &Arc<Self>,
789 session_id: SessionId,
790 executor: Box<dyn meerkat_core::lifecycle::CoreExecutor>,
791 ) -> Result<(), RuntimeDriverError> {
792 match self
793 .execute_meerkat_machine_command(
794 Some(Arc::clone(self)),
795 MeerkatMachineCommand::EnsureSessionWithExecutor {
796 session_id,
797 executor,
798 },
799 )
800 .await
801 .map_err(MeerkatMachine::driver_error_from_command_error)?
802 {
803 MeerkatMachineCommandResult::Unit => Ok(()),
804 other => Err(RuntimeDriverError::Internal(format!(
805 "ensure_session_with_executor: unexpected command result variant: {other:?}"
806 ))),
807 }
808 }
809
810 pub async fn install_prepared_session_interrupt_handle(
818 &self,
819 session_id: &SessionId,
820 handle: Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>,
821 ) -> Result<(), RuntimeDriverError> {
822 let mut sessions = self.sessions.write().await;
823 let entry = sessions
824 .get_mut(session_id)
825 .ok_or(RuntimeDriverError::NotReady {
826 state: RuntimeState::Destroyed,
827 })?;
828 if entry.clear_dead_attachment() {
829 entry
830 .stage_generated_executor_exit_observation()
831 .map_err(|reason| {
832 RuntimeDriverError::Internal(format!(
833 "generated MeerkatMachine rejected executor-exit observation: {reason}"
834 ))
835 })?;
836 }
837 entry.install_provisional_interrupt_handle(handle);
838 Ok(())
839 }
840
841 pub(super) async fn ensure_session_with_executor_inner(
842 self: &Arc<Self>,
843 session_id: SessionId,
844 executor: Box<dyn meerkat_core::lifecycle::CoreExecutor>,
845 ) -> Result<(), RuntimeDriverError> {
846 enum ExistingExecutorClaim {
847 AlreadyClaimed,
848 Rejected(String),
849 Claimed {
850 gate: Arc<Mutex<()>>,
851 driver: SharedDriver,
852 completions: SharedCompletionRegistry,
853 ops_lifecycle: Arc<crate::ops_lifecycle::RuntimeOpsLifecycleRegistry>,
854 dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
855 staged: Box<StagedSessionDslInput>,
856 repaired_dead_attachment: bool,
857 _gate_guard: crate::tokio::sync::OwnedMutexGuard<()>,
858 },
859 }
860
861 let existing = loop {
862 if let Some(gate) = self.session_mutation_gate(&session_id).await {
863 let gate_guard = Arc::clone(&gate).lock_owned().await;
864 let mut sessions = self.sessions.write().await;
865 let Some(entry) = sessions.get_mut(&session_id) else {
866 continue;
867 };
868 if !Arc::ptr_eq(&entry.mutation_gate, &gate) {
869 continue;
870 }
871 let repaired_dead_attachment = entry.clear_dead_attachment();
872 let repaired_deferred_stop =
873 repaired_dead_attachment && entry.generated_stop_deferred();
874 if repaired_dead_attachment
875 && !repaired_deferred_stop
876 && let Err(reason) = entry.stage_generated_executor_exit_observation()
877 {
878 break ExistingExecutorClaim::Rejected(reason);
879 }
880 if entry.generated_executor_registration_active() && !repaired_deferred_stop {
881 break ExistingExecutorClaim::AlreadyClaimed;
882 }
883 if entry.has_live_attachment() {
884 match entry.stage_generated_executor_registration_claim(&session_id) {
885 Ok(_) => break ExistingExecutorClaim::AlreadyClaimed,
886 Err(reason) => break ExistingExecutorClaim::Rejected(reason),
887 }
888 }
889 match entry.stage_generated_executor_registration_claim(&session_id) {
890 Ok(staged) => {
891 break ExistingExecutorClaim::Claimed {
892 gate,
893 driver: entry.driver.clone(),
894 completions: entry.completions.clone(),
895 ops_lifecycle: entry.ops_lifecycle.clone(),
896 dsl_authority: Arc::clone(&entry.dsl_authority),
897 staged: Box::new(staged),
898 repaired_dead_attachment,
899 _gate_guard: gate_guard,
900 };
901 }
902 Err(reason) => break ExistingExecutorClaim::Rejected(reason),
903 }
904 }
905
906 let runtime_id = Self::logical_runtime_id(&session_id);
907 let recovery_observation =
908 match self.durable_lifecycle_for_registration(&runtime_id).await {
909 Ok(snapshot) => RuntimeLifecycleRecoveryObservation::from_snapshot(snapshot),
910 Err(err) => {
911 tracing::error!(
912 %session_id,
913 error = %err,
914 "failed to load durable runtime state during executor registration"
915 );
916 return Err(err);
917 }
918 };
919 let observed_runtime_state = recovery_observation.runtime_state;
920 let requires_observed_recovery = recovery_observation.requires_observed_recovery();
921 let recovered_authority = if requires_observed_recovery {
922 match super::dsl_authority::recover_authority_from_runtime_observation(
923 &session_id,
924 observed_runtime_state,
925 recovery_observation.agent_runtime_id.as_ref(),
926 None,
927 None,
928 std::collections::BTreeSet::new(),
929 recovery_observation.fence_token,
930 recovery_observation.runtime_generation,
931 recovery_observation.runtime_epoch_id,
932 ) {
933 Ok(authority) => authority,
934 Err(err) => {
935 let mapped =
936 super::dsl_authority::map_error(err, "session recovery DSL recovery");
937 tracing::error!(
938 %session_id,
939 error = %mapped,
940 "failed to recover generated runtime authority during executor registration"
941 );
942 return Err(RuntimeDriverError::Internal(mapped));
943 }
944 }
945 } else {
946 fresh_registered_runtime_authority(&session_id, "fresh executor registration")?
947 };
948 let initial_runtime_state =
952 super::dsl_authority::runtime_phase_from_authority(&recovered_authority);
953 let dsl_authority = Arc::new(std::sync::Mutex::new(recovered_authority));
954 let mut recovered_entry = self.make_driver(
955 runtime_id.clone(),
956 Arc::clone(&dsl_authority),
957 initial_runtime_state,
958 );
959 if let Err(err) = recovered_entry.as_driver_mut().recover().await {
960 tracing::error!(
961 %session_id,
962 error = %err,
963 "failed to recover runtime driver during registration"
964 );
965 return Err(err);
966 }
967 let (recovered_ops, recovered_epoch, recovered_cursors) = if self.store.is_some()
970 || (requires_observed_recovery && initial_runtime_state != RuntimeState::Idle)
971 {
972 match self
973 .recover_or_create_ops_state(&session_id, &runtime_id)
974 .await
975 {
976 Ok(recovered) => recovered,
977 Err(err) => {
978 tracing::error!(
979 %session_id,
980 error = %err,
981 "failed to recover ops lifecycle during executor registration"
982 );
983 return Err(err);
984 }
985 }
986 } else {
987 Self::fresh_ops_state()
988 };
989
990 let mutation_gate = Arc::new(Mutex::new(()));
991 let gate_guard = Arc::clone(&mutation_gate).lock_owned().await;
992 let mut sessions = self.sessions.write().await;
993 if sessions.contains_key(&session_id) {
994 continue;
995 }
996
997 let control_projection = recovered_entry.control_projection_handle();
998 let driver = Arc::new(Mutex::new(recovered_entry));
999 let completions = Arc::new(Mutex::new(crate::completion::CompletionRegistry::new()));
1000 let tool_visibility_owner = Arc::new(MachineToolVisibilityOwner::new());
1001 tool_visibility_owner.bind_dsl_authority(Arc::clone(&dsl_authority));
1004 sessions.insert(
1005 session_id.clone(),
1006 RuntimeSessionEntry {
1007 runtime_id,
1008 mutation_gate: Arc::clone(&mutation_gate),
1009 control_projection,
1010 driver: driver.clone(),
1011 ops_lifecycle: recovered_ops.clone(),
1012 epoch_id: recovered_epoch,
1013 handle_teardown_gate: crate::handles::HandleTeardownGate::open(),
1014 cursor_state: recovered_cursors,
1015 completions: completions.clone(),
1016 tool_visibility_owner,
1017 attachment_slot: RuntimeLoopAttachmentSlot::Empty,
1018 provisional_interrupt_handle: None,
1019 dsl_authority: Arc::clone(&dsl_authority),
1020 drain_slot: CommsDrainSlot::new(),
1021 },
1022 );
1023 let Some(entry) = sessions.get_mut(&session_id) else {
1024 return Err(RuntimeDriverError::Internal(format!(
1025 "session {session_id} missing after executor recovery insert"
1026 )));
1027 };
1028 match entry.stage_generated_executor_registration_claim(&session_id) {
1029 Ok(staged) => {
1030 break ExistingExecutorClaim::Claimed {
1031 gate: mutation_gate,
1032 driver,
1033 completions,
1034 ops_lifecycle: recovered_ops,
1035 dsl_authority,
1036 staged: Box::new(staged),
1037 repaired_dead_attachment: false,
1038 _gate_guard: gate_guard,
1039 };
1040 }
1041 Err(reason) => {
1042 sessions.remove(&session_id);
1043 break ExistingExecutorClaim::Rejected(reason);
1044 }
1045 }
1046 };
1047
1048 let (
1049 driver,
1050 completions,
1051 ops_lifecycle,
1052 dsl_authority,
1053 staged_registration,
1054 repaired_dead_attachment,
1055 registration_gate,
1056 _gate_guard,
1057 ) = match existing {
1058 ExistingExecutorClaim::AlreadyClaimed => {
1059 return Ok(());
1060 }
1061 ExistingExecutorClaim::Rejected(reason) => {
1062 tracing::warn!(
1063 %session_id,
1064 error = %reason,
1065 "generated MeerkatMachine rejected executor registration"
1066 );
1067 return Err(self
1070 .classify_session_dsl_rejection(&session_id, reason)
1071 .await);
1072 }
1073 ExistingExecutorClaim::Claimed {
1074 gate,
1075 driver,
1076 completions,
1077 ops_lifecycle,
1078 dsl_authority,
1079 staged,
1080 repaired_dead_attachment,
1081 _gate_guard,
1082 } => (
1083 driver,
1084 completions,
1085 ops_lifecycle,
1086 dsl_authority,
1087 staged,
1088 repaired_dead_attachment,
1089 gate,
1090 _gate_guard,
1091 ),
1092 };
1093
1094 let should_wake = {
1095 let mut driver_guard = driver.lock().await;
1096 driver_guard.sync_control_projection_from_dsl_authority();
1097 if repaired_dead_attachment {
1098 tracing::warn!(
1099 %session_id,
1100 "runtime driver registration was repaired by generated executor authority; publishing attachment"
1101 );
1102 }
1103 !driver_guard.as_driver().active_input_ids().is_empty()
1104 };
1105
1106 if let Some(ref store) = self.store {
1108 let (persist_tx, persist_rx) = crate::tokio::sync::mpsc::unbounded_channel::<
1109 crate::ops_lifecycle::OpsLifecyclePersistenceRequest,
1110 >();
1111 let (entry_epoch_id, entry_cursor, runtime_id) = {
1112 let sessions = self.sessions.read().await;
1113 sessions.get(&session_id).map_or_else(
1114 || {
1115 (
1116 meerkat_core::RuntimeEpochId::new(),
1117 Arc::new(meerkat_core::EpochCursorState::new()),
1118 Self::logical_runtime_id(&session_id),
1119 )
1120 },
1121 |entry| {
1122 (
1123 entry.epoch_id.clone(),
1124 Arc::clone(&entry.cursor_state),
1125 entry.runtime_id.clone(),
1126 )
1127 },
1128 )
1129 };
1130 spawn_ops_lifecycle_persistence_worker(Arc::clone(store), runtime_id, persist_rx);
1131 ops_lifecycle.set_persistence_channel(persist_tx, entry_epoch_id, entry_cursor);
1132 }
1133
1134 let completion_feed = ops_lifecycle.completion_feed_handle();
1136
1137 let boundary_handle = executor.boundary_handle();
1138 let interrupt_handle = executor.interrupt_handle();
1139 let (wake_tx, wake_rx) = mpsc::channel(16);
1140 let (effect_tx, effect_rx) = mpsc::channel(16);
1141 let entry_cursor_state = {
1142 let sessions = self.sessions.read().await;
1143 sessions
1144 .get(&session_id)
1145 .map(|e| Arc::clone(&e.cursor_state))
1146 };
1147 let mut pending_loop_handle =
1148 Some(crate::runtime_loop::spawn_runtime_loop_with_completions(
1149 driver.clone(),
1150 executor,
1151 wake_rx,
1152 effect_rx,
1153 Some(completions.clone()),
1154 Some(completion_feed),
1155 Some(Arc::clone(&ops_lifecycle) as Arc<dyn meerkat_core::OpsLifecycleRegistry>),
1156 entry_cursor_state,
1157 Arc::downgrade(self),
1158 session_id.clone(),
1159 ));
1160
1161 let (published, detach_after_abort) = {
1162 let mut sessions = self.sessions.write().await;
1163 match sessions.get_mut(&session_id) {
1164 None => (false, true),
1165 Some(entry) => {
1166 entry.clear_dead_attachment();
1167 if entry.has_live_attachment() {
1168 (false, false)
1169 } else if !Arc::ptr_eq(&entry.mutation_gate, ®istration_gate)
1170 || !Arc::ptr_eq(&entry.dsl_authority, &dsl_authority)
1171 || !Arc::ptr_eq(&entry.driver, &driver)
1172 || !Arc::ptr_eq(&entry.completions, &completions)
1173 {
1174 tracing::warn!(
1175 %session_id,
1176 "runtime session entry changed while wiring executor; aborting stale loop attachment"
1177 );
1178 (false, true)
1179 } else {
1180 match pending_loop_handle.take() {
1181 Some(loop_handle) => {
1182 entry.attach_runtime_loop(
1183 wake_tx.clone(),
1184 effect_tx,
1185 boundary_handle,
1186 interrupt_handle,
1187 loop_handle,
1188 );
1189 (true, false)
1190 }
1191 None => {
1192 tracing::error!(
1193 %session_id,
1194 "runtime loop handle missing during attachment publish"
1195 );
1196 (false, true)
1197 }
1198 }
1199 }
1200 }
1201 }
1202 };
1203
1204 if !published {
1205 if let Some(loop_handle) = pending_loop_handle.take() {
1206 loop_handle.abort();
1207 }
1208 if detach_after_abort {
1209 Self::restore_dsl_authority_snapshot(
1210 &dsl_authority,
1211 staged_registration.previous_snapshot,
1212 );
1213 let mut driver_guard = driver.lock().await;
1214 driver_guard.sync_control_projection_from_dsl_authority();
1215 return Err(RuntimeDriverError::Internal(
1216 "runtime session entry changed while wiring executor".into(),
1217 ));
1218 }
1219 return Ok(());
1220 }
1221
1222 if should_wake {
1223 let _ = wake_tx.try_send(());
1224 }
1225 Ok(())
1226 }
1227
1228 pub async fn unregister_session(&self, session_id: &SessionId) {
1233 self.unregister_session_inner(session_id).await;
1234 }
1235
1236 async fn stage_begin_unregister_session_authority(
1240 &self,
1241 session_id: &SessionId,
1242 ) -> Result<StagedSessionDslInput, String> {
1243 let begin_input = {
1244 let authority = self.session_dsl_authority(session_id).await?;
1245 let authority = authority
1246 .lock()
1247 .unwrap_or_else(std::sync::PoisonError::into_inner);
1248 let state = authority.state();
1249 crate::meerkat_machine::dsl::MeerkatMachineInput::BeginUnregisterSession {
1250 session_id: crate::meerkat_machine::dsl::SessionId::from_domain(session_id),
1251 agent_runtime_id: state.active_runtime_id.clone(),
1252 fence_token: state.active_fence_token,
1253 generation: state.active_runtime_generation,
1254 runtime_epoch_id: state.active_runtime_epoch_id.clone(),
1255 }
1256 };
1257 self.stage_session_dsl_transition(session_id, begin_input, "BeginUnregisterSession")
1258 .await
1259 }
1260
1261 async fn stage_unregister_session_authority(
1262 &self,
1263 session_id: &SessionId,
1264 ) -> Result<
1265 (
1266 StagedSessionDslInput,
1267 RuntimeOpsLifecycleDurabilityAuthority,
1268 ),
1269 RuntimeDriverError,
1270 > {
1271 let (durability_input, unregister_input) = {
1272 let authority = self.session_dsl_authority(session_id).await.map_err(|reason| {
1273 RuntimeDriverError::ValidationFailed {
1274 reason: format!(
1275 "generated unregister authority unavailable for session {session_id}: {reason}"
1276 ),
1277 }
1278 })?;
1279 let authority = authority
1280 .lock()
1281 .unwrap_or_else(std::sync::PoisonError::into_inner);
1282 let state = authority.state();
1283 let dsl_session_id = crate::meerkat_machine::dsl::SessionId::from_domain(session_id);
1284 let agent_runtime_id = state.active_runtime_id.clone();
1285 let fence_token = state.active_fence_token;
1286 let generation = state.active_runtime_generation;
1287 let runtime_epoch_id = state.active_runtime_epoch_id.clone();
1288 (
1289 crate::meerkat_machine::dsl::MeerkatMachineInput::ResolveRuntimeOpsLifecycleDurability {
1290 session_id: dsl_session_id.clone(),
1291 agent_runtime_id: agent_runtime_id.clone(),
1292 fence_token,
1293 generation,
1294 runtime_epoch_id: runtime_epoch_id.clone(),
1295 },
1296 crate::meerkat_machine::dsl::MeerkatMachineInput::UnregisterSession {
1297 session_id: dsl_session_id,
1298 agent_runtime_id,
1299 fence_token,
1300 generation,
1301 runtime_epoch_id,
1302 },
1303 )
1304 };
1305 let authority = if self.store.is_some() {
1306 let durability_effects = self
1307 .preview_session_dsl_input(
1308 session_id,
1309 durability_input,
1310 "ResolveRuntimeOpsLifecycleDurability",
1311 )
1312 .await
1313 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
1314 runtime_ops_lifecycle_durability_authority_from_effects(
1315 session_id,
1316 &durability_effects,
1317 )?
1318 } else {
1319 RuntimeOpsLifecycleDurabilityAuthority {
1320 action:
1321 crate::meerkat_machine::dsl::RuntimeOpsLifecycleDurabilityAction::RetainSnapshot,
1322 }
1323 };
1324 let staged = self
1325 .stage_session_dsl_transition(session_id, unregister_input, "UnregisterSession")
1326 .await
1327 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
1328 Ok((staged, authority))
1329 }
1330
1331 async fn finalize_unregistered_session(
1332 &self,
1333 runtime_id: LogicalRuntimeId,
1334 driver: SharedDriver,
1335 durability_authority: RuntimeOpsLifecycleDurabilityAuthority,
1336 ) -> Result<(), RuntimeDriverError> {
1337 let mut driver = driver.lock().await;
1338 driver.sync_control_projection_from_dsl_authority();
1339 drop(driver);
1340
1341 if durability_authority.action
1342 != crate::meerkat_machine::dsl::RuntimeOpsLifecycleDurabilityAction::DeleteSnapshot
1343 {
1344 return Ok(());
1345 }
1346 let Some(store) = self.store.as_ref() else {
1347 return Ok(());
1348 };
1349 store.delete_ops_lifecycle(&runtime_id).await.map_err(|err| {
1350 RuntimeDriverError::Internal(format!(
1351 "failed to delete ops lifecycle snapshot for unregistered runtime {runtime_id}: {err}"
1352 ))
1353 })?;
1354 Ok(())
1355 }
1356
1357 pub(super) async fn unregister_session_inner(&self, session_id: &SessionId) {
1358 tracing::info!(%session_id, "MeerkatMachine::unregister_session_inner start");
1359 let Some(gate_guard) = self.lock_current_session_mutation_gate(session_id).await else {
1360 tracing::info!(%session_id, "MeerkatMachine::unregister_session_inner no mutation gate");
1361 return;
1362 };
1363 tracing::info!(%session_id, "MeerkatMachine::unregister_session_inner locked mutation gate");
1364 if let Err(err) =
1365 Box::pin(self.unregister_session_inner_locked_authorized(session_id, gate_guard)).await
1366 {
1367 tracing::warn!(
1368 %session_id,
1369 error = %err,
1370 "generated MeerkatMachine rejected session unregister"
1371 );
1372 }
1373 tracing::info!(%session_id, "MeerkatMachine::unregister_session_inner complete");
1374 }
1375
1376 pub(super) async fn unregister_session_inner_locked_authorized(
1407 &self,
1408 session_id: &SessionId,
1409 gate_guard: crate::tokio::sync::OwnedMutexGuard<()>,
1410 ) -> Result<(), RuntimeDriverError> {
1411 tracing::info!(%session_id, "MeerkatMachine::unregister_session_inner_locked_authorized start");
1412 let driver_handle = {
1413 let sessions = self.sessions.read().await;
1414 sessions
1415 .get(session_id)
1416 .map(|entry| Arc::clone(&entry.driver))
1417 .ok_or(RuntimeDriverError::NotReady {
1418 state: RuntimeState::Destroyed,
1419 })?
1420 };
1421
1422 tracing::info!(%session_id, "MeerkatMachine::unregister_session_inner_locked_authorized beginning drain window");
1428 let retry_final_unregister_only = match self
1429 .stage_begin_unregister_session_authority(session_id)
1430 .await
1431 {
1432 Ok(staged) => {
1433 self.commit_session_dsl_transition(session_id, staged, "BeginUnregisterSession")
1434 .await
1435 .map_err(RuntimeDriverError::Internal)?;
1436 false
1437 }
1438 Err(reason) => {
1439 let already_draining =
1440 self.session_dsl_state(session_id).await.is_ok_and(|state| {
1441 state.registration_phase
1442 == crate::meerkat_machine::dsl::RegistrationPhase::Draining
1443 });
1444 if already_draining {
1445 tracing::debug!(
1446 %session_id,
1447 "BeginUnregisterSession rejected: drain already in progress; attempting final unregister retry only"
1448 );
1449 true
1450 } else {
1451 return Err(self
1452 .classify_session_dsl_rejection(session_id, reason)
1453 .await);
1454 }
1455 }
1456 };
1457
1458 if retry_final_unregister_only {
1459 return self
1460 .retry_final_unregister_after_completed_drain(
1461 session_id,
1462 Arc::clone(&driver_handle),
1463 )
1464 .await;
1465 }
1466
1467 let (loop_handle, loop_interrupt_handle, drain_handle) = {
1473 let mut sessions = self.sessions.write().await;
1474 match sessions.get_mut(session_id) {
1475 Some(entry) => {
1476 let interrupt_handle = entry.interrupt_handle();
1477 (
1478 entry.take_loop_join_handle(),
1479 interrupt_handle,
1480 entry.drain_slot.abort_keeping_handle(),
1481 )
1482 }
1483 None => (None, None, None),
1484 }
1485 };
1486
1487 drop(gate_guard);
1490 tracing::info!(%session_id, "MeerkatMachine::unregister_session_inner_locked_authorized awaiting runtime-loop and comms-drain quiescence");
1491 let mut runtime_loop_forced_abort = false;
1496 let mut comms_drain_forced_abort = false;
1497 if let Some(loop_handle) = loop_handle {
1498 if let Some(interrupt_handle) = loop_interrupt_handle
1506 && let Err(error) = interrupt_handle
1507 .hard_cancel_current_run("runtime session unregistered".to_string())
1508 .await
1509 {
1510 tracing::debug!(
1511 %session_id,
1512 %error,
1513 "in-flight run hard-cancel during unregister drain returned an error (benign if no run was active)"
1514 );
1515 }
1516
1517 const RUNTIME_LOOP_DRAIN_GRACE: std::time::Duration = std::time::Duration::from_secs(2);
1526 let abort_handle = loop_handle.abort_handle();
1527 match crate::tokio::time::timeout(RUNTIME_LOOP_DRAIN_GRACE, loop_handle).await {
1528 Ok(Ok(())) => {}
1529 Ok(Err(join_error)) => {
1530 tracing::warn!(
1531 %session_id,
1532 error = %join_error,
1533 "runtime loop task ended abnormally during unregister drain"
1534 );
1535 }
1536 Err(_elapsed) => {
1537 abort_handle.abort();
1538 runtime_loop_forced_abort = true;
1539 tracing::warn!(
1540 %session_id,
1541 "runtime loop did not quiesce within the unregister drain grace window after hard-cancel; aborting the stuck loop task"
1542 );
1543 }
1544 }
1545 }
1546 if let Some(drain_handle) = drain_handle {
1547 const COMMS_DRAIN_GRACE: std::time::Duration = std::time::Duration::from_secs(2);
1558 let drain_abort = drain_handle.abort_handle();
1559 match crate::tokio::time::timeout(COMMS_DRAIN_GRACE, drain_handle).await {
1560 Ok(Ok(())) => {}
1561 Ok(Err(join_error)) if join_error.is_cancelled() => {}
1562 Ok(Err(join_error)) => {
1563 tracing::warn!(
1564 %session_id,
1565 error = %join_error,
1566 "comms drain task ended abnormally during unregister drain"
1567 );
1568 }
1569 Err(_elapsed) => {
1570 drain_abort.abort();
1571 comms_drain_forced_abort = true;
1572 tracing::warn!(
1573 %session_id,
1574 "comms drain task did not quiesce within the unregister drain grace window; abandoning the already-aborted drain task so teardown cannot stall"
1575 );
1576 }
1577 }
1578 }
1579
1580 let Some(_gate_guard) = self.lock_current_session_mutation_gate(session_id).await else {
1584 tracing::debug!(
1585 %session_id,
1586 "session removed by a concurrent teardown during unregister drain (benign)"
1587 );
1588 return Ok(());
1589 };
1590 tracing::info!(%session_id, "MeerkatMachine::unregister_session_inner_locked_authorized re-acquired mutation gate after drain");
1591
1592 let runtime_terminated_completion_authority =
1597 crate::meerkat_machine::driver::machine_resolve_runtime_terminated_completion_result(
1598 &driver_handle,
1599 )
1600 .await?;
1601 {
1602 let completions = {
1603 let sessions = self.sessions.read().await;
1604 sessions
1605 .get(session_id)
1606 .map(|entry| Arc::clone(&entry.completions))
1607 };
1608 if let Some(completions) = completions {
1609 completions.lock().await.resolve_all_runtime_terminated(
1613 "runtime session unregistered",
1614 runtime_terminated_completion_authority,
1615 );
1616 }
1617 }
1618
1619 for (input, context) in [
1624 (
1625 crate::meerkat_machine::dsl::MeerkatMachineInput::RuntimeLoopStoppedForUnregister {
1626 session_id: crate::meerkat_machine::dsl::SessionId::from_domain(session_id),
1627 forced_abort: runtime_loop_forced_abort,
1628 },
1629 "RuntimeLoopStoppedForUnregister",
1630 ),
1631 (
1632 crate::meerkat_machine::dsl::MeerkatMachineInput::CommsDrainExitedForUnregister {
1633 session_id: crate::meerkat_machine::dsl::SessionId::from_domain(session_id),
1634 forced_abort: comms_drain_forced_abort,
1635 },
1636 "CommsDrainExitedForUnregister",
1637 ),
1638 (
1639 crate::meerkat_machine::dsl::MeerkatMachineInput::CompletionWaitersResolvedForUnregister {
1640 session_id: crate::meerkat_machine::dsl::SessionId::from_domain(session_id),
1641 },
1642 "CompletionWaitersResolvedForUnregister",
1643 ),
1644 ] {
1645 let staged = match self
1646 .stage_session_dsl_transition(session_id, input, context)
1647 .await
1648 {
1649 Ok(staged) => staged,
1650 Err(reason) => return Err(RuntimeDriverError::ValidationFailed { reason }),
1651 };
1652 self.commit_session_dsl_transition(session_id, staged, context)
1653 .await
1654 .map_err(RuntimeDriverError::Internal)?;
1655 }
1656
1657 tracing::info!(%session_id, "MeerkatMachine::unregister_session_inner_locked_authorized staging unregister");
1659 let (staged, durability_authority) =
1660 self.stage_unregister_session_authority(session_id).await?;
1661 let unregister_rollback_snapshot = staged.previous_snapshot.clone();
1662 tracing::info!(%session_id, "MeerkatMachine::unregister_session_inner_locked_authorized committing unregister");
1663 self.commit_session_dsl_transition(session_id, staged, "UnregisterSession")
1664 .await
1665 .map_err(RuntimeDriverError::Internal)?;
1666 tracing::info!(%session_id, "MeerkatMachine::unregister_session_inner_locked_authorized committed unregister");
1667 if durability_authority.action
1668 == crate::meerkat_machine::dsl::RuntimeOpsLifecycleDurabilityAction::DeleteSnapshot
1669 {
1670 driver_handle
1671 .lock()
1672 .await
1673 .persist_current_machine_lifecycle("unregister")
1674 .await?;
1675 }
1676 let finalize_target = {
1677 let sessions = self.sessions.read().await;
1678 sessions
1679 .get(session_id)
1680 .map(|entry| (entry.runtime_id.clone(), Arc::clone(&entry.driver)))
1681 };
1682 if let Some((runtime_id, driver)) = finalize_target {
1683 tracing::info!(%session_id, "MeerkatMachine::unregister_session_inner_locked_authorized finalizing durable unregister");
1684 if let Err(err) = self
1685 .finalize_unregistered_session(
1686 runtime_id,
1687 Arc::clone(&driver),
1688 durability_authority,
1689 )
1690 .await
1691 {
1692 self.restore_session_dsl_state(session_id, unregister_rollback_snapshot)
1693 .await;
1694 driver
1695 .lock()
1696 .await
1697 .sync_control_projection_from_dsl_authority();
1698 if let Err(rollback_error) = driver
1699 .lock()
1700 .await
1701 .persist_current_machine_lifecycle("unregister rollback")
1702 .await
1703 {
1704 return Err(RuntimeDriverError::Internal(format!(
1705 "{err}; additionally failed to persist unregister rollback: {rollback_error}"
1706 )));
1707 }
1708 return Err(err);
1709 }
1710 }
1711 tracing::info!(%session_id, "MeerkatMachine::unregister_session_inner_locked_authorized removing entry");
1712 let entry = {
1713 let mut sessions = self.sessions.write().await;
1714 if let Some(entry) = sessions.get_mut(session_id) {
1719 entry.close_handle_teardown_gate();
1720 abort_slot(&mut entry.drain_slot);
1721 }
1722 sessions.remove(session_id)
1723 };
1724 drop(entry);
1725 tracing::info!(%session_id, "MeerkatMachine::unregister_session_inner_locked_authorized complete");
1726 Ok(())
1727 }
1728
1729 async fn retry_final_unregister_after_completed_drain(
1730 &self,
1731 session_id: &SessionId,
1732 driver_handle: SharedDriver,
1733 ) -> Result<(), RuntimeDriverError> {
1734 let (staged, durability_authority) =
1735 match self.stage_unregister_session_authority(session_id).await {
1736 Ok(pair) => pair,
1737 Err(err @ RuntimeDriverError::ValidationFailed { .. }) => {
1738 tracing::debug!(
1739 %session_id,
1740 error = %err,
1741 "final unregister retry is not ready; original drain remains in progress"
1742 );
1743 return Ok(());
1744 }
1745 Err(err) => return Err(err),
1746 };
1747 let unregister_rollback_snapshot = staged.previous_snapshot.clone();
1748 self.commit_session_dsl_transition(session_id, staged, "UnregisterSession")
1749 .await
1750 .map_err(RuntimeDriverError::Internal)?;
1751 if durability_authority.action
1752 == crate::meerkat_machine::dsl::RuntimeOpsLifecycleDurabilityAction::DeleteSnapshot
1753 {
1754 driver_handle
1755 .lock()
1756 .await
1757 .persist_current_machine_lifecycle("unregister")
1758 .await?;
1759 }
1760 let finalize_target = {
1761 let sessions = self.sessions.read().await;
1762 sessions
1763 .get(session_id)
1764 .map(|entry| (entry.runtime_id.clone(), Arc::clone(&entry.driver)))
1765 };
1766 if let Some((runtime_id, driver)) = finalize_target
1767 && let Err(err) = self
1768 .finalize_unregistered_session(
1769 runtime_id,
1770 Arc::clone(&driver),
1771 durability_authority,
1772 )
1773 .await
1774 {
1775 self.restore_session_dsl_state(session_id, unregister_rollback_snapshot)
1776 .await;
1777 driver
1778 .lock()
1779 .await
1780 .sync_control_projection_from_dsl_authority();
1781 if let Err(rollback_error) = driver
1782 .lock()
1783 .await
1784 .persist_current_machine_lifecycle("unregister rollback")
1785 .await
1786 {
1787 return Err(RuntimeDriverError::Internal(format!(
1788 "{err}; additionally failed to persist unregister rollback: {rollback_error}"
1789 )));
1790 }
1791 return Err(err);
1792 }
1793 let entry = {
1794 let mut sessions = self.sessions.write().await;
1795 if let Some(entry) = sessions.get_mut(session_id) {
1796 entry.close_handle_teardown_gate();
1797 abort_slot(&mut entry.drain_slot);
1798 }
1799 sessions.remove(session_id)
1800 };
1801 drop(entry);
1802 Ok(())
1803 }
1804
1805 pub async fn contains_session(&self, session_id: &SessionId) -> bool {
1807 self.sessions.read().await.contains_key(session_id)
1808 }
1809
1810 #[cfg(target_arch = "wasm32")]
1813 pub async fn discard_terminal_storeless_session(&self, session_id: &SessionId) -> bool {
1814 if self.store.is_some() {
1815 return false;
1816 }
1817 let Some(snapshot) = self.meerkat_machine_archive_snapshot(session_id).await else {
1818 return false;
1819 };
1820 if !matches!(
1821 snapshot.control.phase,
1822 RuntimeState::Retired | RuntimeState::Stopped
1823 ) || !snapshot.queue.is_empty()
1824 || !snapshot.steer_queue.is_empty()
1825 {
1826 return false;
1827 }
1828 let Some(_gate_guard) = self.lock_current_session_mutation_gate(session_id).await else {
1829 return false;
1830 };
1831 let (driver_handle, completions) = {
1832 let sessions = self.sessions.read().await;
1833 let Some(entry) = sessions.get(session_id) else {
1834 return false;
1835 };
1836 (Arc::clone(&entry.driver), Arc::clone(&entry.completions))
1837 };
1838 let runtime_terminated_completion_authority =
1839 match crate::meerkat_machine::driver::machine_resolve_runtime_terminated_completion_result(
1840 &driver_handle,
1841 )
1842 .await
1843 {
1844 Ok(authority) => authority,
1845 Err(err) => {
1846 tracing::warn!(
1847 %session_id,
1848 error = %err,
1849 "failed to resolve terminal completion authority for storeless WASM session discard"
1850 );
1851 return false;
1852 }
1853 };
1854 completions.lock().await.resolve_all_runtime_terminated(
1855 "storeless WASM session discarded",
1856 runtime_terminated_completion_authority,
1857 );
1858
1859 match self
1866 .stage_begin_unregister_session_authority(session_id)
1867 .await
1868 {
1869 Ok(staged) => {
1870 if let Err(err) = self
1871 .commit_session_dsl_transition(session_id, staged, "BeginUnregisterSession")
1872 .await
1873 {
1874 tracing::warn!(
1875 %session_id,
1876 error = %err,
1877 "failed to open drain window for storeless WASM session discard"
1878 );
1879 return false;
1880 }
1881 }
1882 Err(reason) => {
1883 tracing::warn!(
1884 %session_id,
1885 error = %reason,
1886 "generated MeerkatMachine rejected drain-window open for storeless WASM session discard"
1887 );
1888 return false;
1889 }
1890 }
1891 for (input, context) in [
1895 (
1896 crate::meerkat_machine::dsl::MeerkatMachineInput::RuntimeLoopStoppedForUnregister {
1897 session_id: crate::meerkat_machine::dsl::SessionId::from_domain(session_id),
1898 forced_abort: false,
1899 },
1900 "RuntimeLoopStoppedForUnregister",
1901 ),
1902 (
1903 crate::meerkat_machine::dsl::MeerkatMachineInput::CommsDrainExitedForUnregister {
1904 session_id: crate::meerkat_machine::dsl::SessionId::from_domain(session_id),
1905 forced_abort: false,
1906 },
1907 "CommsDrainExitedForUnregister",
1908 ),
1909 (
1910 crate::meerkat_machine::dsl::MeerkatMachineInput::CompletionWaitersResolvedForUnregister {
1911 session_id: crate::meerkat_machine::dsl::SessionId::from_domain(session_id),
1912 },
1913 "CompletionWaitersResolvedForUnregister",
1914 ),
1915 ] {
1916 match self
1917 .stage_session_dsl_transition(session_id, input, context)
1918 .await
1919 {
1920 Ok(staged) => {
1921 if let Err(err) = self
1922 .commit_session_dsl_transition(session_id, staged, context)
1923 .await
1924 {
1925 tracing::warn!(
1926 %session_id,
1927 error = %err,
1928 "failed to close drain obligation for storeless WASM session discard"
1929 );
1930 return false;
1931 }
1932 }
1933 Err(reason) => {
1934 tracing::warn!(
1935 %session_id,
1936 error = %reason,
1937 "generated MeerkatMachine rejected drain feedback for storeless WASM session discard"
1938 );
1939 return false;
1940 }
1941 }
1942 }
1943 let (staged, _durability) = match self.stage_unregister_session_authority(session_id).await
1944 {
1945 Ok(pair) => pair,
1946 Err(err) => {
1947 tracing::warn!(
1948 %session_id,
1949 error = %err,
1950 "failed to stage final unregister for storeless WASM session discard"
1951 );
1952 return false;
1953 }
1954 };
1955 if let Err(err) = self
1956 .commit_session_dsl_transition(session_id, staged, "UnregisterSession")
1957 .await
1958 {
1959 tracing::warn!(
1960 %session_id,
1961 error = %err,
1962 "failed to commit final unregister for storeless WASM session discard"
1963 );
1964 return false;
1965 }
1966
1967 let entry = {
1968 let mut sessions = self.sessions.write().await;
1969 if let Some(entry) = sessions.get_mut(session_id) {
1970 abort_slot(&mut entry.drain_slot);
1971 }
1972 sessions.remove(session_id)
1973 };
1974 let Some(entry) = entry else {
1975 return false;
1976 };
1977 let runtime_id = entry.runtime_id.clone();
1978 let driver = Arc::clone(&entry.driver);
1979 if let Err(err) = self
1980 .finalize_unregistered_session(
1981 runtime_id,
1982 driver,
1983 RuntimeOpsLifecycleDurabilityAuthority {
1984 action:
1985 crate::meerkat_machine::dsl::RuntimeOpsLifecycleDurabilityAction::RetainSnapshot,
1986 },
1987 )
1988 .await
1989 {
1990 tracing::warn!(
1991 %session_id,
1992 error = %err,
1993 "failed to finalize storeless WASM session discard"
1994 );
1995 return false;
1996 }
1997 true
1998 }
1999
2000 pub async fn session_has_executor(
2007 &self,
2008 session_id: &SessionId,
2009 ) -> Result<bool, RuntimeDriverError> {
2010 match self
2011 .execute_meerkat_machine_command(
2012 None,
2013 MeerkatMachineCommand::SessionHasExecutor {
2014 session_id: session_id.clone(),
2015 },
2016 )
2017 .await
2018 {
2019 Ok(MeerkatMachineCommandResult::Bool(present)) => Ok(present),
2020 Ok(other) => Err(RuntimeDriverError::Internal(format!(
2021 "session_has_executor: unexpected command result variant: {other:?}"
2022 ))),
2023 Err(error) => Err(MeerkatMachine::driver_error_from_command_error(error)),
2024 }
2025 }
2026
2027 pub async fn wake_runtime_if_active_inputs(
2033 &self,
2034 session_id: &SessionId,
2035 ) -> Result<bool, RuntimeDriverError> {
2036 let (driver, wake_tx) = {
2037 let sessions = self.sessions.read().await;
2038 let entry = sessions
2039 .get(session_id)
2040 .ok_or(RuntimeDriverError::NotReady {
2041 state: RuntimeState::Destroyed,
2042 })?;
2043 (entry.driver.clone(), entry.wake_sender())
2044 };
2045
2046 let has_active_inputs = {
2047 let driver = driver.lock().await;
2048 !driver.as_driver().active_input_ids().is_empty()
2049 };
2050 if !has_active_inputs {
2051 return Ok(false);
2052 }
2053
2054 let Some(wake_tx) = wake_tx else {
2055 return Err(RuntimeDriverError::NotReady {
2056 state: RuntimeState::Idle,
2057 });
2058 };
2059
2060 match wake_tx.try_send(()) {
2061 Ok(()) | Err(mpsc::error::TrySendError::Full(())) => Ok(true),
2062 Err(mpsc::error::TrySendError::Closed(())) => Err(RuntimeDriverError::NotReady {
2063 state: RuntimeState::Idle,
2064 }),
2065 }
2066 }
2067
2068 pub async fn session_has_comms(
2074 &self,
2075 session_id: &SessionId,
2076 ) -> Result<bool, RuntimeDriverError> {
2077 match self
2078 .execute_meerkat_machine_command(
2079 None,
2080 MeerkatMachineCommand::SessionHasComms {
2081 session_id: session_id.clone(),
2082 },
2083 )
2084 .await
2085 {
2086 Ok(MeerkatMachineCommandResult::Bool(present)) => Ok(present),
2087 Ok(other) => Err(RuntimeDriverError::Internal(format!(
2088 "session_has_comms: unexpected command result variant: {other:?}"
2089 ))),
2090 Err(error) => Err(MeerkatMachine::driver_error_from_command_error(error)),
2091 }
2092 }
2093
2094 pub async fn resolve_transcript_edit_admission(
2105 &self,
2106 session_id: &SessionId,
2107 runtime_running: bool,
2108 has_active_inputs: bool,
2109 ) -> Result<crate::meerkat_machine::dsl::TranscriptEditAdmissionKind, RuntimeDriverError> {
2110 let (_, effects) = self
2111 .apply_session_dsl_input(
2112 session_id,
2113 crate::meerkat_machine::dsl::MeerkatMachineInput::ResolveTranscriptEditAdmission {
2114 runtime_running,
2115 has_active_inputs,
2116 },
2117 "ResolveTranscriptEditAdmission",
2118 )
2119 .await
2120 .map_err(RuntimeDriverError::Internal)?;
2121 effects
2122 .as_slice()
2123 .iter()
2124 .find_map(|effect| {
2125 match effect {
2126 crate::meerkat_machine::dsl::MeerkatMachineEffect::TranscriptEditAdmissionResolved {
2127 verdict,
2128 } => Some(*verdict),
2129 _ => None,
2130 }
2131 })
2132 .ok_or_else(|| {
2133 RuntimeDriverError::Internal(
2134 "transcript-edit admission emitted no authority verdict".to_string(),
2135 )
2136 })
2137 }
2138
2139 pub async fn cancel_after_boundary(
2141 &self,
2142 session_id: &SessionId,
2143 ) -> Result<(), RuntimeDriverError> {
2144 self.execute_meerkat_machine_command(
2145 None,
2146 MeerkatMachineCommand::CancelAfterBoundary {
2147 session_id: session_id.clone(),
2148 },
2149 )
2150 .await
2151 .map_err(MeerkatMachine::driver_error_from_command_error)
2152 .map(|_| ())
2153 }
2154
2155 pub async fn abandon_retired_pending_inputs(
2158 &self,
2159 session_id: &SessionId,
2160 reason: impl Into<String>,
2161 ) -> Result<usize, RuntimeDriverError> {
2162 let reason = reason.into();
2163 let state = self
2164 .existing_session_runtime_state(session_id)
2165 .await
2166 .unwrap_or(RuntimeState::Destroyed);
2167 if state != RuntimeState::Retired {
2168 return Err(RuntimeDriverError::NotReady { state });
2169 }
2170
2171 let gate = self.session_mutation_gate(session_id).await;
2172 let _gate_guard = match gate {
2173 Some(ref g) => Some(g.lock().await),
2174 None => None,
2175 };
2176
2177 let (driver, completions) = {
2178 let sessions = self.sessions.read().await;
2179 let entry = sessions
2180 .get(session_id)
2181 .ok_or(RuntimeDriverError::NotReady {
2182 state: RuntimeState::Destroyed,
2183 })?;
2184 (entry.driver.clone(), entry.completions.clone())
2185 };
2186
2187 let abandoned = {
2188 let mut driver = driver.lock().await;
2189 driver
2190 .abandon_pending_inputs(crate::input_state::InputAbandonReason::Retired)
2191 .await?
2192 };
2193 let result_class =
2194 crate::meerkat_machine::driver::machine_resolve_runtime_terminated_completion_result(
2195 &driver,
2196 )
2197 .await?;
2198 completions
2199 .lock()
2200 .await
2201 .resolve_all_runtime_terminated(&reason, result_class);
2202 Ok(abandoned)
2203 }
2204
2205 pub async fn stage_persistent_filter(
2207 &self,
2208 session_id: &SessionId,
2209 filter: meerkat_core::ToolFilter,
2210 witnesses: std::collections::BTreeMap<
2211 meerkat_core::ToolName,
2212 meerkat_core::ToolVisibilityWitness,
2213 >,
2214 ) -> Result<meerkat_core::ToolScopeRevision, RuntimeDriverError> {
2215 match self
2216 .execute_meerkat_machine_command(
2217 None,
2218 MeerkatMachineCommand::StagePersistentFilter {
2219 session_id: session_id.clone(),
2220 filter,
2221 witnesses,
2222 },
2223 )
2224 .await
2225 .map_err(MeerkatMachine::driver_error_from_command_error)?
2226 {
2227 MeerkatMachineCommandResult::VisibilityRevision(revision) => Ok(revision),
2228 other => Err(RuntimeDriverError::Internal(format!(
2229 "unexpected MeerkatMachineCommandResult for stage_persistent_filter: {other:?}"
2230 ))),
2231 }
2232 }
2233
2234 pub async fn request_deferred_tools(
2236 &self,
2237 session_id: &SessionId,
2238 authorities: Vec<meerkat_core::DeferredToolLoadAuthority>,
2239 ) -> Result<meerkat_core::ToolScopeRevision, RuntimeDriverError> {
2240 match self
2241 .execute_meerkat_machine_command(
2242 None,
2243 MeerkatMachineCommand::RequestDeferredTools {
2244 session_id: session_id.clone(),
2245 authorities,
2246 },
2247 )
2248 .await
2249 .map_err(MeerkatMachine::driver_error_from_command_error)?
2250 {
2251 MeerkatMachineCommandResult::VisibilityRevision(revision) => Ok(revision),
2252 other => Err(RuntimeDriverError::Internal(format!(
2253 "unexpected MeerkatMachineCommandResult for request_deferred_tools: {other:?}"
2254 ))),
2255 }
2256 }
2257
2258 pub async fn publish_committed_visible_set(
2266 &self,
2267 session_id: &SessionId,
2268 visibility_state: meerkat_core::SessionToolVisibilityState,
2269 ) -> Result<meerkat_core::SessionToolVisibilityState, RuntimeDriverError> {
2270 match self
2271 .execute_meerkat_machine_command(
2272 None,
2273 MeerkatMachineCommand::PublishCommittedVisibleSet {
2274 session_id: session_id.clone(),
2275 visibility_state: Box::new(visibility_state),
2276 },
2277 )
2278 .await
2279 .map_err(MeerkatMachine::driver_error_from_command_error)?
2280 {
2281 MeerkatMachineCommandResult::VisibilityPublished(state) => Ok(state),
2282 other => Err(RuntimeDriverError::Internal(format!(
2283 "unexpected MeerkatMachineCommandResult for publish_committed_visible_set: {other:?}"
2284 ))),
2285 }
2286 }
2287
2288 pub fn set_session_llm_reconfigure_host(&self, host: Arc<dyn SessionLlmReconfigureHost>) {
2290 *self
2291 .llm_reconfigure_host
2292 .write()
2293 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(host);
2294 }
2295
2296 }