Skip to main content

sqlite_graphrag/commands/
daemon.rs

1use crate::constants::DAEMON_IDLE_SHUTDOWN_SECS;
2use crate::errors::AppError;
3use crate::output;
4use crate::paths::AppPaths;
5
6#[derive(clap::Args)]
7#[command(after_long_help = "EXAMPLES:\n  \
8    # Start the embedding daemon in the foreground (default 600s idle timeout)\n  \
9    sqlite-graphrag daemon\n\n  \
10    # Start with a longer idle timeout for batch ingestion\n  \
11    sqlite-graphrag daemon --idle-shutdown-secs 3600\n\n  \
12    # Health-check a running daemon (exit 4 if not running)\n  \
13    sqlite-graphrag daemon --ping\n\n  \
14    # Request graceful shutdown of a running daemon\n  \
15    sqlite-graphrag daemon --stop\n\n\
16AUTO-SPAWN BEHAVIOR:\n  \
17    recall and hybrid-search spawn a daemon automatically when none is running,\n  \
18    amortising model warm-up across multiple invocations (idle timeout 600s).\n\n  \
19    Disable per-invocation:  sqlite-graphrag recall \"query\" --autostart-daemon=false\n  \
20    Disable globally:        export SQLITE_GRAPHRAG_DAEMON_DISABLE_AUTOSTART=1\n\n  \
21    The --autostart-daemon flag takes precedence over the env var.")]
22pub struct DaemonArgs {
23    /// Idle timeout in seconds before the daemon auto-shuts down to release the embedding model.
24    /// Default 600s; raise for long-running batch ingestion to avoid cold-start overhead.
25    #[arg(long, default_value_t = DAEMON_IDLE_SHUTDOWN_SECS)]
26    pub idle_shutdown_secs: u64,
27    /// Send a health-check ping to a running daemon and exit. Returns NotFound (exit 4) if no daemon.
28    #[arg(long)]
29    pub ping: bool,
30    /// Request graceful shutdown of a running daemon. Returns NotFound (exit 4) if no daemon.
31    #[arg(long)]
32    pub stop: bool,
33    #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
34    pub json: bool,
35    #[arg(long, env = "SQLITE_GRAPHRAG_DB_PATH")]
36    pub db: Option<String>,
37}
38
39pub fn run(args: DaemonArgs) -> Result<(), AppError> {
40    let _ = args.json;
41    let paths = AppPaths::resolve(args.db.as_deref())?;
42    paths.ensure_dirs()?;
43
44    if args.ping {
45        let response = crate::daemon::try_ping(&paths.models)?
46            .ok_or_else(|| AppError::NotFound("daemon not running".to_string()))?;
47        output::emit_json(&response)?;
48        return Ok(());
49    }
50
51    if args.stop {
52        let response = crate::daemon::try_shutdown(&paths.models)?
53            .ok_or_else(|| AppError::NotFound("daemon not running".to_string()))?;
54        output::emit_json(&response)?;
55        return Ok(());
56    }
57
58    crate::daemon::run(&paths.models, args.idle_shutdown_secs)
59}