pai_engine/
main.rs

1use anyhow::Result;
2use clap::{ArgAction, Parser};
3use pai_core::adapters::HardcodedFlowRunner;
4use pai_core::domain::{EventBus, SessionManager};
5use pai_core::ports::{InferenceError, InferencePort};
6use std::path::PathBuf;
7use std::sync::Arc;
8use tracing::{debug, error, info};
9use tracing_subscriber::FmtSubscriber;
10
11/// Command line arguments for the paiOS engine.
12#[derive(Parser, Debug)]
13#[command(author, version, about, long_about = None)]
14struct Args {
15    /// Path to the configuration file
16    #[arg(short, long)]
17    config: Option<PathBuf>,
18
19    /// Increase verbosity (-v, -vv, -vvv)
20    #[arg(short, long, action = ArgAction::Count)]
21    verbose: u8,
22}
23
24#[tokio::main]
25async fn main() -> Result<()> {
26    // 1. Parse command line arguments
27    let args = Args::parse();
28
29    // 2. Initialize logging (tracing) based on verbosity
30    let log_level = match args.verbose {
31        0 => tracing::Level::INFO,
32        1 => tracing::Level::DEBUG,
33        _ => tracing::Level::TRACE,
34    };
35
36    let subscriber = FmtSubscriber::builder().with_max_level(log_level).finish();
37
38    tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
39
40    // 3. Bootstrap engine — core session orchestration (stub inference until real adapters wire in)
41    #[derive(Debug)]
42    struct StubInference;
43
44    impl InferencePort for StubInference {
45        fn complete(&self, prompt: &str) -> Result<String, InferenceError> {
46            Ok(format!("[stub] {prompt}"))
47        }
48    }
49
50    let (event_bus, event_rx) = EventBus::channel(64);
51    let flow_runner = Arc::new(HardcodedFlowRunner::new(
52        Arc::new(StubInference),
53        event_bus.clone(),
54    ));
55    let session = SessionManager::new(flow_runner, event_bus);
56
57    // Keep the sole consumer alive so the mpsc channel stays open; drain events so publishes never
58    // fail with Closed/Full during normal operation.
59    tokio::spawn(async move {
60        let mut event_rx = event_rx;
61        while let Some(ev) = event_rx.recv().await {
62            debug!(target: "pai_engine::event_bus", ?ev, "domain event");
63        }
64    });
65    info!(
66        "Booting paiOS engine workspace (session state: {:?})...",
67        session.state_machine().state()
68    );
69
70    if let Some(path) = args.config {
71        info!("Using configuration from: {}", path.display());
72    } else {
73        info!("No configuration file provided, using defaults.");
74    }
75
76    // TODO: In future steps, construct adapters and wire domain crates via `core`.
77
78    // 4. Wait for shutdown signal to keep the process alive
79    if let Err(e) = tokio::signal::ctrl_c().await {
80        error!("Failed to listen for shutdown signal: {e}");
81    }
82
83    info!("Shutdown signal received. Exiting paiOS engine.");
84
85    Ok(())
86}