Skip to main content

clawft_kernel/
talk_loop_service.rs

1//! `SystemService` impl + the native async run loop for the
2//! [`TalkModeLoop`](super::TalkModeLoop). Split from `talk_loop.rs` to stay
3//! under the 500-line ceiling (CLAUDE.md). A child module can reach the parent's
4//! private fields, so the impl still touches `self.tick` directly.
5
6use std::sync::Arc;
7
8use async_trait::async_trait;
9
10use super::TalkModeLoop;
11use crate::health::HealthStatus;
12use crate::service::{ServiceType, SystemService};
13
14#[async_trait]
15impl SystemService for TalkModeLoop {
16    fn name(&self) -> &str {
17        "voice.talk_mode"
18    }
19
20    fn service_type(&self) -> ServiceType {
21        ServiceType::Core
22    }
23
24    async fn start(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
25        self.tick.set_running(true);
26        Ok(())
27    }
28
29    async fn stop(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
30        self.tick.set_running(false);
31        Ok(())
32    }
33
34    async fn health_check(&self) -> HealthStatus {
35        if self.tick.is_running() {
36            HealthStatus::Healthy
37        } else {
38            HealthStatus::Degraded("talk-mode tick not running".into())
39        }
40    }
41}
42
43/// Drive the Talk-Mode loop on the ADR-047 self-calibrating cadence until
44/// `cancel` fires or the tick is stopped. Modeled on
45/// [`run_democritus_loop`](crate::cognitive_tick::run_democritus_loop) but for
46/// turn-taking. Spawned by the daemon (Phase 6). Native only (tokio/tokio-util).
47#[cfg(feature = "native")]
48pub async fn run_talk_loop(loop_: Arc<TalkModeLoop>, cancel: tokio_util::sync::CancellationToken) {
49    loop_.tick.set_running(true);
50    tracing::info!("Talk-Mode loop started");
51    loop {
52        let interval_ms = loop_.tick.current_interval_ms();
53        if interval_ms == 0 {
54            tracing::warn!("Talk-Mode loop: tick interval is 0, exiting");
55            break;
56        }
57        tokio::select! {
58            _ = cancel.cancelled() => break,
59            _ = tokio::time::sleep(tokio::time::Duration::from_millis(interval_ms as u64)) => {}
60        }
61        if !loop_.tick.is_running() {
62            break;
63        }
64        loop_.tick();
65    }
66    loop_.tick.set_running(false);
67    tracing::info!("Talk-Mode loop exited");
68}