Skip to main content

superfluid_daemon/
serve.rs

1//! `superfluid serve`: the server, as a library function so that another
2//! program can run it.
3
4use crate as superfluid_daemon;
5
6use std::os::unix::net::UnixListener;
7use std::path::{Path, PathBuf};
8use std::sync::Arc;
9
10use superfluid_daemon::runtime_pick::{RuntimeId, RuntimePick};
11use superfluid_daemon::{api, Daemon, EngineHost, SessionStore};
12
13fn retired(flag: &str, why: &str) {
14    eprintln!("superfluid: {flag} is retired — {why}");
15}
16
17fn sampling_f32(flag: &str, raw: &str, lo: f32, hi: f32) -> f32 {
18    let v: f32 = raw.parse().unwrap_or_else(|_| {
19        eprintln!("superfluid: {flag} expects a number, got {raw:?}");
20        std::process::exit(2);
21    });
22    if !v.is_finite() || v < lo || v > hi {
23        eprintln!("superfluid: {flag} must be a finite number in [{lo}, {hi}], got {v}");
24        std::process::exit(2);
25    }
26    v
27}
28
29fn usage_error(msg: &str, command: &str) -> ! {
30    eprintln!("superfluid: {msg}");
31    eprintln!("Run 'superfluid {command} --help' for its options.");
32    std::process::exit(2);
33}
34
35fn needs_value(flag: &str) -> ! {
36    usage_error(&format!("{flag} needs a value"), "serve")
37}
38
39fn num<T: std::str::FromStr>(flag: &str, raw: String) -> T {
40    raw.parse().unwrap_or_else(|_| usage_error(&format!("{flag} expects a number, got {raw:?}"), "serve"))
41}
42
43fn unknown_serve_flag(flag: &str) -> ! {
44    let hint = superfluid_daemon::cli::suggest(flag, superfluid_daemon::cli::serve_flag_names())
45        .map(|s| format!(" (did you mean {s}?)"))
46        .unwrap_or_default();
47    usage_error(&format!("serve has no option {flag}{hint}"), "serve")
48}
49
50fn human_bytes(n: u64) -> String {
51    const GIB: f64 = (1u64 << 30) as f64;
52    const MIB: f64 = (1u64 << 20) as f64;
53    if n as f64 >= GIB {
54        format!("{:.1} GiB", n as f64 / GIB)
55    } else {
56        format!("{:.0} MiB", n as f64 / MIB)
57    }
58}
59
60pub(crate) fn settle_park_dir(dir: &std::path::Path, parking: bool, budget_gb: u64) {
61    if parking {
62        let budget = budget_gb.saturating_mul(1 << 30);
63        if budget == 0 {
64            return;
65        }
66        let freed = superfluid_daemon::park::sweep(dir, budget);
67        if freed > 0 {
68            eprintln!(
69                "superfluid: park retention sweep freed {} in {} at startup (budget {budget_gb} GiB)",
70                human_bytes(freed),
71                dir.display()
72            );
73        }
74        return;
75    }
76    let stale: u64 = std::fs::read_dir(dir)
77        .map(|rd| {
78            rd.filter_map(|e| e.ok())
79                .filter(|e| {
80                    let n = e.file_name();
81                    let n = n.to_string_lossy();
82                    n.ends_with(".park") || n.ends_with(".park.tmp")
83                })
84                .filter_map(|e| e.metadata().ok().map(|m| m.len()))
85                .sum()
86        })
87        .unwrap_or(0);
88    if stale > 0 {
89        eprintln!(
90            "superfluid: {} of park artifacts in {} are from an earlier run with --park; \
91             nothing reads or reclaims them while parking is off. Delete them, or pass \
92             --park to put them back under --park-budget-gb.",
93            human_bytes(stale),
94            dir.display()
95        );
96    }
97}
98
99pub const SUN_PATH_LEN: usize = if cfg!(target_os = "linux") { 108 } else { 104 };
100
101fn listen_or_exit(flag: &str, addr: &str) -> std::net::TcpListener {
102    std::net::TcpListener::bind(addr).unwrap_or_else(|e| {
103        let hint = match e.kind() {
104            std::io::ErrorKind::AddrInUse => " (another process listens there: stop it or pick another port, e.g. --port 8454)",
105            std::io::ErrorKind::AddrNotAvailable => " (no interface on this machine has that address)",
106            std::io::ErrorKind::PermissionDenied => " (a port under 1024 needs privileges)",
107            _ => "",
108        };
109        eprintln!("superfluid: cannot listen on {flag} {addr}: {e}{hint}");
110        std::process::exit(1);
111    })
112}
113
114/// What a program running [`run`] in place of `superfluid serve` changes.
115/// [`Embedding::default`] is `superfluid serve` itself.
116#[derive(Debug, Clone)]
117pub struct Embedding {
118    /// The APIs served beside the OpenAI-compatible routes.
119    pub http: superfluid_daemon::openai::HttpSurface,
120    /// How a request's `model` picks the model that serves it.
121    pub model_naming: superfluid_daemon::openai::ModelNaming,
122    /// Names registered artifacts (initial, additional and model-dir models).
123    /// Receives already registered names so an embedding can disambiguate
124    /// variants. Explicit HTTP model IDs are not rewritten.
125    pub model_name: fn(&Path, &std::collections::HashSet<String>) -> String,
126    /// Sampling policy for omitted parameters; None is superfluid's default.
127    pub sampling_fallback: Option<superfluid_daemon::openai::SamplingFallback>,
128    /// The name and version the HTTP API reports for the server.
129    pub identity: superfluid_daemon::openai::ServerIdentity,
130    /// Whether the native session API listens on a unix socket (`--socket`).
131    /// Without it the HTTP API is all there is, and `--no-http` is refused.
132    pub session_socket: bool,
133    /// The program that runs a linked runtime's worker, in place of the `superfluid-workerd`
134    /// beside this executable; it passes [`crate::workerd::main`]'s arguments to it.
135    pub workerd: Option<PathBuf>,
136}
137
138impl Default for Embedding {
139    fn default() -> Embedding {
140        Embedding {
141            http: superfluid_daemon::openai::HttpSurface::default(),
142            model_naming: superfluid_daemon::openai::ModelNaming::default(),
143            model_name: superfluid_daemon::registry::distinct_name,
144            sampling_fallback: None,
145            identity: superfluid_daemon::openai::ServerIdentity::default(),
146            session_socket: true,
147            workerd: None,
148        }
149    }
150}
151
152/// Runs `superfluid serve`; `args` is its command line after `serve`.
153pub fn run(args: &[String], embedding: &Embedding) {
154    let mut models: Vec<PathBuf> = Vec::new();
155    let mut pull_flags = superfluid_daemon::pulls::PullFlags::default();
156    let mut runtime_pick = RuntimePick::default();
157    let mut api_key: Option<String> = None;
158    let mut rate_limit: u32 = 0;
159    let mut host: Option<String> = None;
160    let mut port: Option<u16> = None;
161    let mut files_max_bytes: Option<u64> = None;
162    let mut files_expiry: Option<u64> = None;
163    let mut files_sweep: u64 = 300;
164    let mut drain_timeout: u64 = 60;
165    let mut nonstream_keepalive: u64 = 0;
166    let mut default_max_tokens: Option<u32> = None;
167    let mut log_file: Option<PathBuf> = None;
168    let mut model_dir: Option<PathBuf> = None;
169    let mut tool_envelope: Option<superfluid_daemon::codec::ToolEnvelope> = None;
170    let mut idle_timeout: u64 = 0;
171    let mut sessions: Option<PathBuf> = None;
172    let mut socket: Option<PathBuf> = None;
173    let mut http: Option<String> = None;
174    let mut no_http = false;
175    let mut max_context: Option<i32> = None;
176    let mut max_context_auto_flag = false;
177    let mut max_batch: u32 = 8;
178    let mut op_temperature: Option<f32> = None;
179    let mut op_top_p: Option<f32> = None;
180    let mut op_top_k: Option<u32> = None;
181    let mut op_min_p: Option<f32> = None;
182    let mut op_repeat_penalty: Option<f32> = None;
183    let mut op_spec_max_temperature: Option<f32> = None;
184    let mut park = false;
185    let mut sessions_cache = false;
186    let mut park_lossy = false;
187    let mut park_budget_gb: u64 = 20;
188    let mut tool_lease_ms: u64 = 0;
189    let mut prefix_blob_budget_mb: u64 = 0;
190    let mut kv_bits_arg: Option<i32> = None;
191    let mut worker_process = true;
192    let mut os_pressure = true;
193    let mut dialect = String::from("auto");
194    let mut pressure_high: u8 = 0;
195    let mut pressure_low: u8 = 0;
196    let mut pin_budget_pct: u8 = superfluid_daemon::DaemonOptions::default().pin_budget_pct;
197    let mut tick_decode_budget: u32 = 0;
198    let mut tick_target_ms: u32 = 0;
199    let mut prefill_budget: u32 = superfluid_daemon::DaemonOptions::default().prefill_budget;
200    let mut starvation_ticks: u32 = 8;
201    let mut class_lanes_spec: Option<String> = None;
202    let mut http_default_qos = superfluid_daemon::openai::RequestQos::AGENT;
203    let mut http_qos_header = true;
204    let mut http_allow_batch_invariant = false;
205    let mut key_policy_path: Option<PathBuf> = None;
206    let mut qos_flags: Vec<&'static str> = Vec::new();
207    let mut log_dir: Option<PathBuf> = None;
208    let mut log_filter = String::from("info");
209    let mut speculate: Option<String> = None;
210    let mut speculation = superfluid_daemon::SpeculationOptions::default();
211    let mut spec_bitexact: Option<bool> = None;
212    let mut completion_deadline_ms: u64 = 0;
213    let mut completion_bucket = superfluid_daemon::completion_bucket::BucketConfig::default();
214    let mut fim_model: Option<PathBuf> = None;
215    let mut fim_max_context: Option<i32> = None;
216    let mut fim_max_batch: u32 = 2;
217    let mut web: Option<String> = None;
218    let mut web_origins: Vec<String> = Vec::new();
219    let mut otlp_metrics: Option<String> = None;
220    let mut otlp_interval_ms: u64 = 15_000;
221    let mut otlp_endpoint: Option<String> = None;
222    let mut otlp_cfg = superfluid_daemon::otlp::OtlpConfig::default();
223    let mut tui_flag = false;
224    let mut no_tui = false;
225    let mut fleet: Option<String> = None;
226    let mut fleet_auth: Option<PathBuf> = None;
227    let mut fleet_listen: Option<String> = None;
228    let mut fleet_policy = String::from("load-aware");
229    let mut fleet_pool_high: u32 = superfluid_daemon::fleet_manager::DEFAULT_POOL_HIGH_PCT;
230    let mut fleet_conns: usize = superfluid_daemon::fleet_manager::DEFAULT_CONNS_PER_NODE;
231    // A runtime's option, kept as given and resolved once the catalog says which runtimes are here.
232    let mut option_candidates: Vec<(String, Option<String>)> = Vec::new();
233    let args = superfluid_daemon::cli::split_equals(args, superfluid_daemon::cli::serve_takes_value);
234    let mut it = args.iter();
235    while let Some(a) = it.next() {
236        let mut val = || it.next().cloned().unwrap_or_else(|| needs_value(a));
237        // SERVE-FLAGS-BEGIN: every flag `serve` reads is listed in superfluid_daemon::cli (a test checks).
238        match a.as_str() {
239            "--model" => models.push(PathBuf::from(val())),
240            "--offline" => pull_flags.offline = true,
241            flag if flag.starts_with("--pull-") => {
242                let spec = &flag["--pull-".len()..];
243                let (name, value) = match spec.split_once('=') {
244                    Some((n, v)) => (n.to_string(), Some(v.to_string())),
245                    None => (spec.to_string(), it.clone().next().filter(|v| !v.starts_with("--")).cloned()),
246                };
247                if value.is_some() && !spec.contains('=') {
248                    it.next();
249                }
250                if name.is_empty() {
251                    unknown_serve_flag(a);
252                }
253                pull_flags.options.push((name, value));
254            }
255            "--runtime" => {
256                let v = val();
257                if let Err(e) = runtime_pick.add(&v) {
258                    eprintln!("superfluid: --runtime: {e}");
259                    std::process::exit(2);
260                }
261            }
262            "--api-key" => api_key = Some(val()),
263            "--rate-limit" => rate_limit = num(a, val()),
264            "--files-max-bytes" => {
265                files_max_bytes = Some(num(a, val()))
266            }
267            "--files-expiry" => files_expiry = Some(num(a, val())),
268            "--files-sweep" => files_sweep = num(a, val()),
269            "--drain-timeout" => drain_timeout = num(a, val()),
270            "--nonstream-keepalive" => {
271                nonstream_keepalive = num(a, val())
272            }
273            "--max-tokens" => {
274                default_max_tokens = Some(num(a, val()))
275            }
276            "--log-file" => log_file = Some(PathBuf::from(val())),
277            "--model-dir" => model_dir = Some(PathBuf::from(val())),
278            "--tool-call-parser" => {
279                let v = val();
280                if v != "auto" {
281                    tool_envelope = Some(
282                        superfluid_daemon::codec::ToolEnvelope::from_name(&v).unwrap_or_else(|| {
283                            eprintln!(
284                                "superfluid: unknown --tool-call-parser '{v}' (have: auto, {})",
285                                superfluid_daemon::codec::ToolEnvelope::names().join(", ")
286                            );
287                            std::process::exit(2);
288                        }),
289                    );
290                }
291            }
292            "--idle-timeout" => idle_timeout = num(a, val()),
293            "--host" => host = Some(val()),
294            "--port" => port = Some(num(a, val())),
295            "--max-batch-size" => max_batch = num(a, val()),
296            "--request-timeout" => {
297                completion_deadline_ms = num(a, val())
298            }
299            "--verbose" | "-v" => log_filter = "debug".into(),
300            "--help" | "-h" => {
301                print!("{}", superfluid_daemon::cli::serve_help());
302                return;
303            }
304            "--sessions" => sessions = Some(PathBuf::from(val())),
305            "--socket" => socket = Some(PathBuf::from(val())),
306            "--http" => http = Some(val()),
307            "--no-http" => no_http = true,
308            "--max-context" => {
309                let v = val();
310                max_context_auto_flag = v == "auto";
311                max_context = if v == "auto" { None } else { Some(num(a, v)) };
312            }
313            "--max-batch" => max_batch = num(a, val()),
314            "--temperature" => op_temperature = Some(sampling_f32("--temperature", &val(), 0.0, 2.0)),
315            "--top-p" => op_top_p = Some(sampling_f32("--top-p", &val(), 0.0, 1.0)),
316            "--top-k" => op_top_k = Some(num(a, val())),
317            "--min-p" => op_min_p = Some(sampling_f32("--min-p", &val(), 0.0, 1.0)),
318            "--repeat-penalty" => {
319                op_repeat_penalty = Some(sampling_f32("--repeat-penalty", &val(), 0.0, 2.0))
320            }
321            "--park" => park = true,
322            "--no-park" => park = false,
323            "--sessions-cache" => sessions_cache = true,
324            "--park-lossy" => {
325                park = true;
326                park_lossy = true;
327            }
328            "--park-budget-gb" => park_budget_gb = num(a, val()),
329            "--tool-lease-ms" => tool_lease_ms = num(a, val()),
330            "--prefix-blob-budget-mb" => {
331                prefix_blob_budget_mb = num(a, val())
332            }
333            "--kv-bits" => kv_bits_arg = Some(num(a, val())),
334            "--dialect" => dialect = val(),
335            "--worker-process" => worker_process = true,
336            "--no-worker-process" => worker_process = false,
337            "--os-pressure" => os_pressure = true,
338            "--no-os-pressure" => os_pressure = false,
339            "--pressure-high" => pressure_high = num(a, val()),
340            "--pressure-low" => pressure_low = num(a, val()),
341            "--pin-budget-pct" => pin_budget_pct = num(a, val()),
342            "--log-dir" => log_dir = Some(PathBuf::from(val())),
343            "--basert-lib" => std::env::set_var("BASERT_LIB", val()),
344            "--speculate" => {
345                let v = val();
346                speculate = if v == "off" { None } else { Some(v) };
347            }
348            "--spec-draft-tokens" => {
349                let n: u32 = num(a, val());
350                if !(1..=15).contains(&n) {
351                    eprintln!("superfluid: --spec-draft-tokens must be in 1..=15");
352                    std::process::exit(2);
353                }
354                speculation.max_draft = Some(n);
355            }
356            "--spec-adaptive" => speculation.adaptive = Some(true),
357            "--no-spec-adaptive" => speculation.adaptive = Some(false),
358            "--spec-min-yield" => {
359                let v: f64 = num(a, val());
360                if !v.is_finite() || v < 0.0 {
361                    eprintln!("superfluid: --spec-min-yield must be finite and non-negative");
362                    std::process::exit(2);
363                }
364                speculation.min_yield = Some(v);
365            }
366            "--spec-yield-rounds" => {
367                let n: u32 = num(a, val());
368                if n == 0 {
369                    eprintln!("superfluid: --spec-yield-rounds must be at least 1");
370                    std::process::exit(2);
371                }
372                speculation.yield_rounds = Some(n);
373            }
374            "--spec-throughput-gate" => speculation.throughput_gate = Some(true),
375            "--no-spec-throughput-gate" => speculation.throughput_gate = Some(false),
376            "--spec-gate-probe-tokens" => {
377                speculation.gate_probe_tokens = Some(num(a, val()))
378            }
379            "--spec-max-temperature" => {
380                op_spec_max_temperature = Some(sampling_f32("--spec-max-temperature", &val(), 0.0, 2.0))
381            }
382            "--spec-min-speedup" => {
383                let v: f64 = num(a, val());
384                if !v.is_finite() || v < 1.0 {
385                    eprintln!("superfluid: --spec-min-speedup must be finite and at least 1.0");
386                    std::process::exit(2);
387                }
388                speculation.min_speedup = Some(v);
389            }
390            "--spec-gate-reprobe" => {
391                speculation.gate_reprobe = Some(num(a, val()))
392            }
393            "--spec-gate-reprobe-max" => {
394                speculation.gate_reprobe_max = Some(num(a, val()))
395            }
396            "--dspark-confidence" => {
397                let v: f32 = num(a, val());
398                if !v.is_finite() || v <= 0.0 {
399                    eprintln!("superfluid: --dspark-confidence must be finite and positive");
400                    std::process::exit(2);
401                }
402                speculation.dspark_confidence = Some(v);
403            }
404            "--spec-bitexact" => spec_bitexact = Some(true),
405            "--no-spec-bitexact" => spec_bitexact = Some(false),
406            "--log-filter" => log_filter = val(),
407            "--tick-decode-budget" => {
408                tick_decode_budget = num(a, val())
409            }
410            "--tick-target-ms" => tick_target_ms = num(a, val()),
411            "--prefill-budget" => prefill_budget = num(a, val()),
412            "--starvation-ticks" => starvation_ticks = num(a, val()),
413            "--class-lanes" => {
414                class_lanes_spec = Some(val());
415                qos_flags.push("--class-lanes");
416            }
417            "--http-default-qos" => {
418                qos_flags.push("--http-default-qos");
419                let v = val();
420                http_default_qos.class = superfluid_daemon::qos::parse(&v).unwrap_or_else(|| {
421                    eprintln!(
422                        "superfluid: --http-default-qos expects one of {}, got {v:?}",
423                        superfluid_daemon::qos::NAMES.join(", ")
424                    );
425                    std::process::exit(2);
426                });
427            }
428            "--no-http-qos-header" => {
429                http_qos_header = false;
430                qos_flags.push("--no-http-qos-header");
431            }
432            "--http-allow-batch-invariant" => {
433                http_allow_batch_invariant = true;
434                qos_flags.push("--http-allow-batch-invariant");
435            }
436            "--key-policy" => key_policy_path = Some(PathBuf::from(val())),
437            "--completion-deadline-ms" => {
438                completion_deadline_ms = num(a, val())
439            }
440            "--completion-rate" => {
441                completion_bucket.rate_per_sec = num(a, val())
442            }
443            "--completion-burst" => {
444                completion_bucket.burst = num(a, val())
445            }
446            "--fim-model" => fim_model = Some(PathBuf::from(val())),
447            "--fim-max-context" => {
448                fim_max_context = Some(num(a, val()))
449            }
450            "--fim-max-batch" => fim_max_batch = num(a, val()),
451            "--web" => web = Some(val()),
452            "--web-origin" => web_origins.push(val()),
453            "--otlp-metrics" => otlp_metrics = Some(val()),
454            "--tui" => tui_flag = true,
455            "--no-tui" => no_tui = true,
456            "--otlp-interval-ms" => otlp_interval_ms = num(a, val()),
457            "--otlp-endpoint" => otlp_endpoint = Some(val()),
458            "--otlp-header" => {
459                let kv = val();
460                let (k, v) = kv.split_once('=').unwrap_or_else(|| usage_error(&format!("--otlp-header expects k=v, got {kv:?}"), "serve"));
461                otlp_cfg.headers.push((k.trim().to_string(), v.trim().to_string()));
462            }
463            "--otlp-service-name" => otlp_cfg.service_name = val(),
464            "--otlp-filter" => otlp_cfg.filter = val(),
465            "--otlp-queue" => otlp_cfg.queue = num(a, val()),
466            "--otlp-batch-ms" => {
467                let ms = num(a, val());
468                otlp_cfg.interval = std::time::Duration::from_millis(ms);
469            }
470            "--otlp-timeout-ms" => {
471                let ms = num(a, val());
472                otlp_cfg.timeout = std::time::Duration::from_millis(ms);
473            }
474            "--fleet" => fleet = Some(val()),
475            "--fleet-auth" => fleet_auth = Some(PathBuf::from(val())),
476            "--fleet-listen" => fleet_listen = Some(val()),
477            "--fleet-policy" => fleet_policy = val(),
478            "--fleet-pool-high" => fleet_pool_high = num(a, val()),
479            "--fleet-conns-per-node" => {
480                fleet_conns = num(a, val())
481            }
482            "--paged-kv" => retired(a, "paged KV is always on"),
483            "--prefix-cache" => retired(a, "prefix reuse is always on"),
484            "--continuous-batching" => {
485                if let Some(n) = it.clone().next().and_then(|v| v.parse::<u32>().ok()) {
486                    let _ = it.next();
487                    max_batch = n;
488                    retired(a, "always on; its width became --max-batch");
489                } else {
490                    retired(a, "continuous batching is always on");
491                }
492            }
493            "--prefix-cache-file" | "--prefix-cache-save-interval" => {
494                let _ = val();
495                retired(
496                    a,
497                    "superseded by the WAL session log (tokens, always durable) and the \
498                     in-memory prefix cache; KV on disk is --park, which is OFF by default \
499                     and applies to sessions resumed by id through the session API",
500                );
501            }
502            "--files-dir" => {
503                let _ = val();
504                retired(a, "the file store lives under --sessions");
505            }
506            "--media-dir" => {
507                let _ = val();
508                retired(
509                    a,
510                    "superfluid accepts only data: image URLs and never reads a local path, so there is nothing to allow-list",
511                );
512            }
513            "--prefill-chunk" => {
514                let _ = val();
515                retired(a, "an engine-level knob; a runtime takes its own, --mlx-prefill-chunk or --llamacpp-ubatch-size");
516            }
517            "--metallib" | "--gpu-wait-timeout-ms" | "--decode-replay" => {
518                let _ = val();
519                retired(a, "an engine-level knob superfluid does not expose");
520            }
521            "--paged-weights" | "--no-paged-weights-retry" | "--no-baked-decode" => {
522                retired(a, "an engine-level knob superfluid does not expose")
523            }
524            other if !other.starts_with('-') => models.push(PathBuf::from(other)),
525            _ => {
526                let (flag, given) = match a.split_once('=') {
527                    Some((f, v)) => (f, Some(v.to_string())),
528                    None => (a.as_str(), None),
529                };
530                if !superfluid_daemon::runtime_options::looks_like(flag) {
531                    unknown_serve_flag(a);
532                }
533                let value = match given {
534                    Some(v) => Some(v),
535                    None => match it.clone().next() {
536                        Some(v) if !v.starts_with("--") => {
537                            it.next();
538                            Some(v.clone())
539                        }
540                        _ => None,
541                    },
542                };
543                option_candidates.push((flag.to_string(), value));
544            }
545        }
546        // SERVE-FLAGS-END
547    }
548    if models.is_empty() {
549        usage_error(
550            "serve needs a model: superfluid serve <path|org/model[:tag]>, e.g. \
551             superfluid serve unsloth/Qwen3.8-27B-GGUF:UD-Q4_K_M",
552            "serve",
553        );
554    }
555    superfluid_daemon::speculate_resolve::allow_catalog_fetch(!pull_flags.offline);
556    let catalog = Arc::new(match &embedding.workerd {
557        Some(workerd) => superfluid_daemon::runtimes::Catalog::with_workerd(workerd.clone()),
558        None => superfluid_daemon::runtimes::Catalog::from_env(),
559    });
560    let runtime_options: Vec<superfluid_daemon::runtime_options::Given> = {
561        let ids = catalog.ids();
562        option_candidates
563            .iter()
564            .map(|(flag, value)| match superfluid_daemon::runtime_options::resolve(flag, value.as_deref().unwrap_or_default(), &ids) {
565                None => unknown_serve_flag(flag),
566                Some(_) if value.is_none() => needs_value(flag),
567                Some(given) => given,
568            })
569            .collect()
570    };
571    superfluid_daemon::runtime_options::refuse_repeats(&runtime_options).unwrap_or_else(|e| usage_error(&e, "serve"));
572    // Set before the runtime is required, so an option that makes it usable here counts for the first probe.
573    superfluid_daemon::runtime_options::set(superfluid_daemon::runtime_options::given_pairs(&runtime_options));
574    // The llama.cpp tokenizer this process loads finds its library the way the worker does.
575    superfluid_daemon::runtime_options::export_to_daemon();
576    let sessions = Some(sessions.unwrap_or_else(|| superfluid_daemon::cli::default_sessions(catalog.home())));
577    let socket = if embedding.session_socket {
578        Some(socket.unwrap_or_else(|| superfluid_daemon::cli::default_socket(catalog.home(), SUN_PATH_LEN)))
579    } else {
580        if socket.is_some() {
581            usage_error("--socket: this server has no session socket", "serve");
582        }
583        if no_http {
584            usage_error("--no-http leaves nothing to serve: this server has no session socket", "serve");
585        }
586        None
587    };
588    if no_http && (http.is_some() || host.is_some() || port.is_some()) {
589        usage_error("--no-http contradicts --http/--host/--port", "serve");
590    }
591    let mut pulled_names: std::collections::HashMap<PathBuf, String> = std::collections::HashMap::new();
592    for m in models.iter_mut() {
593        let token = m.to_string_lossy().into_owned();
594        if !superfluid_daemon::pulls::looks_like_id(&token) {
595            continue;
596        }
597        let runtime = match runtime_pick.explicit(m) {
598            Some(name) => catalog.named(name).ok().or_else(|| RuntimeId::parse(name)).unwrap_or_else(|| {
599                eprintln!("superfluid: --runtime '{name}' is not a runtime id");
600                std::process::exit(2);
601            }),
602            None => RuntimeId::new(superfluid_daemon::cli::runtime_for_id(&token)),
603        };
604        // The runtime first, so one missing exits as it would for a model given by path (4).
605        if let Err(e) = superfluid_daemon::pulls::ensure_installed(&catalog, runtime, pull_flags.offline) {
606            refuse(e.exit_code(), e.to_string());
607        }
608        let path = superfluid_daemon::pulls::pull(&catalog, runtime, &token, &pull_flags).unwrap_or_else(|e| {
609            eprintln!("superfluid: {e}");
610            std::process::exit(1);
611        });
612        if let Err(e) = runtime_pick.add(&format!("{}={}", path.display(), runtime.name())) {
613            eprintln!("superfluid: {e}");
614            std::process::exit(2);
615        }
616        pulled_names.insert(path.clone(), superfluid_daemon::pulls::served_name(&token));
617        *m = path;
618    }
619    if !pull_flags.options.is_empty() && pulled_names.is_empty() {
620        eprintln!("superfluid: --pull-* options apply to models given by id; no --model here is one");
621        std::process::exit(2);
622    }
623    let (Some(model), Some(sessions)) = (models.first().cloned(), sessions) else {
624        unreachable!("a model and sessions are both set above");
625    };
626    if http.is_none() && !no_http {
627        http = Some(format!(
628            "{}:{}",
629            host.unwrap_or_else(|| "127.0.0.1".into()),
630            port.unwrap_or(8453)
631        ));
632    }
633    if let Some(socket) = socket.as_ref().filter(|s| s.as_os_str().len() >= SUN_PATH_LEN) {
634        eprintln!(
635            "superfluid: --socket {} is {} bytes; a unix socket path must be under {SUN_PATH_LEN} bytes here \
636             (pick a shorter one)",
637            socket.display(),
638            socket.as_os_str().len()
639        );
640        std::process::exit(2);
641    }
642    for (flag, addr) in [("--http", &http), ("--web", &web)] {
643        if let Some(addr) = addr {
644            drop(listen_or_exit(flag, addr));
645        }
646    }
647    let picked = runtime_pick.explicit(&model).is_some();
648    fn refuse(code: i32, e: String) -> ! {
649        eprintln!("superfluid: {e}");
650        std::process::exit(code)
651    }
652    let runtime = match runtime_pick.resolve(&model, &catalog) {
653        Err(e) => refuse(2, if picked { e } else { not_there(&model).unwrap_or(e) }),
654        Ok(rt) => match catalog.require(rt) {
655            Ok(rt) => rt,
656            Err(_) => {
657                // The runtime answers without its options and refuses one of them: say which. Asked once, since
658                // asking starts a worker.
659                if let Some(why) = superfluid_daemon::runtime_options::blamed(&catalog, rt, &runtime_options) {
660                    refuse(2, why);
661                }
662                if let Some(why) = not_there(&model) {
663                    refuse(2, why);
664                }
665                // Not here, or cannot run here: exit 4, so a host can tell it from a flag it got wrong;
666                // an install tried and failed is 1, a failure along the way (`pulls::Unready`).
667                superfluid_daemon::pulls::ensure_installed(&catalog, rt, pull_flags.offline)
668                    .unwrap_or_else(|e| refuse(e.exit_code(), e.to_string()))
669            }
670        },
671    };
672    if let Err(e) = refuse_statically(&runtime, park_lossy, kv_bits_arg, speculate.as_deref()) {
673        refuse(2, e);
674    }
675    // Each runtime's options are checked against what its worker declares before any model loads.
676    superfluid_daemon::runtime_options::settle(&runtime_options, &runtime, &catalog).unwrap_or_else(|e| refuse(2, e));
677    eprintln!("superfluid: runtime {} serves {} ({})", runtime.id.name(), model.display(), describe_worker(&runtime));
678    if let Some(on) = spec_bitexact {
679        std::env::set_var("BASERT_SPEC_BITEXACT", if on { "1" } else { "0" });
680    }
681    let (speculate_primary, speculate_fallbacks): (Option<String>, Vec<String>) = match speculation_for(
682        speculate.as_deref(),
683        &runtime.static_capabilities(),
684        &model,
685        runtime.id.name(),
686    )
687    .map(|d| superfluid_daemon::speculate_resolve::resolve_all(d, &model))
688    {
689        None => (None, Vec::new()),
690        Some(Ok(mut ranked)) => {
691            if let Some(r) = ranked.first() {
692                eprintln!("superfluid: speculation: {r}");
693            } else {
694                let dirs = superfluid_daemon::speculate_resolve::auto_search_dirs(&model);
695                let dirs: Vec<_> = dirs.iter().map(|(d, _)| d.display().to_string()).collect();
696                eprintln!(
697                    "superfluid: speculation: auto found no head in {} and no fitting drafter in {} — decoding plainly",
698                    model.display(),
699                    dirs.join(", ")
700                );
701            }
702            let primary = (!ranked.is_empty()).then(|| ranked.remove(0));
703            (primary, ranked)
704        }
705        Some(Err(e)) => {
706            eprintln!("superfluid: {e}");
707            std::process::exit(2);
708        }
709    };
710    if op_spec_max_temperature.is_some() && speculate.is_none() {
711        eprintln!("superfluid: --spec-max-temperature ignored: no --speculate strategy is configured");
712    }
713    if tui_flag && !cfg!(feature = "tui") {
714        eprintln!(
715            "superfluid: --tui: this build carries no TUI (build superfluid-daemon with the `tui` feature, \
716             which `basert` includes)"
717        );
718        std::process::exit(2);
719    }
720    let class_lanes = match class_lanes_spec.as_deref() {
721        None => [0; 4],
722        Some(spec) => superfluid_daemon::qos::parse_class_lanes(spec, max_batch as usize).unwrap_or_else(|e| {
723            eprintln!("superfluid: --class-lanes: {e}");
724            std::process::exit(2);
725        }),
726    };
727    let key_policy = key_policy_path.as_deref().map(|p| {
728        let table = superfluid_daemon::keypolicy::KeyTable::load(p, api_key.as_deref())
729            .unwrap_or_else(|e| {
730                eprintln!("superfluid: --key-policy {e}");
731                std::process::exit(2);
732            });
733        eprintln!("superfluid: key policy: {} key(s) from {}", table.len(), p.display());
734        Arc::new(table)
735    });
736    if let Err(e) = completion_bucket.validate() {
737        eprintln!("superfluid: --completion-rate/--completion-burst: {e}");
738        std::process::exit(2);
739    }
740    if fim_max_batch == 0 {
741        eprintln!("superfluid: --fim-max-batch must be at least 1");
742        std::process::exit(2);
743    }
744    if sessions_cache && park {
745        eprintln!(
746            "superfluid: --sessions-cache discards the session log at each start, which \
747             --park{} needs to resume parked sessions. Use one or the other.",
748            if park_lossy { "-lossy" } else { "" }
749        );
750        std::process::exit(2);
751    }
752    if models.len() > 1 && !worker_process {
753        eprintln!("superfluid: {} models — enabling --worker-process (each needs its own address space)", models.len());
754        worker_process = true;
755    }
756    if fim_model.is_some() && !worker_process {
757        eprintln!("superfluid: --fim-model needs its own worker — enabling --worker-process");
758        worker_process = true;
759    }
760    if model_dir.is_some() && !worker_process {
761        eprintln!("superfluid: --model-dir needs a per-model worker — enabling --worker-process");
762        worker_process = true;
763    }
764    if let Some(path) = &log_file {
765        use std::os::fd::AsRawFd;
766        match std::fs::OpenOptions::new().create(true).append(true).open(path) {
767            Ok(f) => {
768                // SAFETY: `f` is an open, valid fd for the duration of the
769                // call; dup2 onto STDERR_FILENO is the documented way to
770                // redirect a stream. The original stderr is closed by dup2.
771                unsafe {
772                    libc::dup2(f.as_raw_fd(), libc::STDERR_FILENO);
773                }
774                drop(f);
775            }
776            Err(e) => {
777                eprintln!("superfluid: could not open --log-file {}: {e}", path.display());
778                std::process::exit(1);
779            }
780        }
781    }
782    if api_key.is_none() && key_policy.is_none() {
783        let loopback = |addr: &str| {
784            addr.starts_with("localhost:") || addr.parse::<std::net::SocketAddr>().is_ok_and(|a| a.ip().is_loopback())
785        };
786        if http.as_deref().is_none_or(loopback) && web.as_deref().is_none_or(loopback) {
787            eprintln!("superfluid: no --api-key: the HTTP API is open to every process on this machine");
788        } else {
789            eprintln!(
790                "superfluid: WARNING --api-key not set. Authentication is OFF — anyone \
791                 who can reach this port can use the API. Bind loopback or set --api-key."
792            );
793        }
794    }
795    let fleet_mode = fleet.is_some() || fleet_listen.is_some();
796    if park && !fleet_mode {
797        let budget = if park_budget_gb == 0 {
798            "NO budget: --park-budget-gb 0 disables the retention sweep, so the \
799             directory grows without limit"
800                .to_string()
801        } else {
802            format!("budget {park_budget_gb} GiB, oldest evicted first")
803        };
804        eprintln!(
805            "superfluid: WARNING --park is on. Every finishing DURABLE session copies its \
806             whole KV off the GPU on the tick thread before it retires — seconds and \
807             gigabytes at agent context, and every other lane waits — then keeps an \
808             artifact under <sessions>/park ({budget}). A /v1/* request mints a fresh \
809             session and can never resume it, so its TERMINAL park is skipped; one \
810             PREEMPTED under load still parks, because it is requeued under the same id \
811             and adopts that artifact. This pays off only for sessions you resume by id \
812             through the session API."
813        );
814    }
815
816    let model_name = pulled_names.get(&model).cloned().unwrap_or_else(|| (embedding.model_name)(&model, &Default::default()));
817    if prefix_blob_budget_mb > 0 {
818        std::env::set_var("BASERT_PREFIX_BLOB_BUDGET_MB", prefix_blob_budget_mb.to_string());
819    }
820    if std::env::var_os("BASERT_YARN").is_some() {
821        eprintln!(
822            "superfluid: WARNING: BASERT_YARN is set but has no effect on this path — the rope \
823             kernels disable YaRN under paged/batched dispatch, which serving always uses. \
824             Long requests run UNSCALED past the trained window."
825        );
826    }
827
828    let tui_on =
829        cfg!(feature = "tui") && !no_tui && (tui_flag || std::io::IsTerminal::is_terminal(&std::io::stdout()));
830    let tui_ring = if tui_on {
831        Some(superfluid_daemon::telemetry::new_log_ring())
832    } else {
833        None
834    };
835    #[cfg(feature = "tui")]
836    let stderr_capture = tui_ring.as_ref().and_then(|ring| {
837        match superfluid_daemon::tui::capture_stderr(ring.clone()) {
838            Ok(guard) => Some(guard),
839            Err(e) => {
840                eprintln!("superfluid: stderr capture unavailable, engine output may overdraw the TUI: {e}");
841                None
842            }
843        }
844    });
845    #[cfg(feature = "tui")]
846    let worker_stderr = stderr_capture.as_ref().map(|c| c.worker_stderr());
847    #[cfg(not(feature = "tui"))]
848    let worker_stderr: Option<std::sync::Arc<std::os::fd::OwnedFd>> = None;
849    let log_dir_eff = log_dir.or(if tui_on { Some(sessions.join("logs")) } else { None });
850    if let Some(url) = &otlp_metrics {
851        if let Err(e) = superfluid_daemon::otlp::validate_http_url(url) {
852            eprintln!("superfluid: --otlp-metrics: {e}");
853            std::process::exit(2);
854        }
855        if otlp_interval_ms == 0 {
856            eprintln!("superfluid: --otlp-interval-ms must be > 0");
857            std::process::exit(2);
858        }
859    }
860    if fleet_mode && otlp_endpoint.is_some() {
861        eprintln!(
862            "superfluid: --otlp-endpoint is not supported in fleet-head mode (the fleet head \
863             has no scheduler to trace); set it on each node's superfluid instead"
864        );
865        std::process::exit(2);
866    }
867    let otlp = otlp_endpoint.map(|endpoint| {
868        let mut cfg = otlp_cfg;
869        cfg.endpoint = endpoint;
870        if let Err(e) = cfg.validate() {
871            eprintln!("superfluid: --otlp-endpoint: {e}");
872            std::process::exit(2);
873        }
874        eprintln!(
875            "superfluid: pushing operational traces to {} ({}an EGRESS: ids, lengths, timings and \
876             codes only, never session content; spans: {})",
877            cfg.traces_url(),
878            if cfg.is_loopback() { "" } else { "NON-LOOPBACK, " },
879            cfg.filter
880        );
881        cfg
882    });
883    use superfluid_daemon::telemetry::{LogConfig, LogSink};
884    let sink = match log_dir_eff {
885        Some(dir) => LogSink::File(LogConfig {
886            dir,
887            filter: log_filter,
888            ..Default::default()
889        }),
890        None => LogSink::Stderr(log_filter),
891    };
892    if let Err(e) = superfluid_daemon::telemetry::init_sinks(sink, tui_ring.clone(), otlp) {
893        eprintln!("superfluid: telemetry: {e}");
894    }
895    std::fs::create_dir_all(&sessions).expect("create sessions dir");
896    let _sessions_lock = if !fleet_mode { Some(lock_sessions_dir(&sessions)) } else { None };
897    if sessions_cache && !fleet_mode {
898        rotate_session_wal(&sessions);
899        if let Ok(rd) = std::fs::read_dir(sessions.join("models")) {
900            for dir in rd.filter_map(|e| e.ok()).map(|e| e.path()).filter(|d| d.is_dir()) {
901                rotate_session_wal(&dir);
902            }
903        }
904    }
905    if !fleet_mode {
906        settle_park_dir(&sessions.join("park"), park, park_budget_gb);
907        if let Ok(rd) = std::fs::read_dir(sessions.join("models")) {
908            let mut dirs: Vec<std::path::PathBuf> = rd
909                .filter_map(|e| e.ok())
910                .map(|e| e.path().join("park"))
911                .filter(|d| d.is_dir())
912                .collect();
913            dirs.sort();
914            for dir in dirs {
915                settle_park_dir(&dir, park, park_budget_gb);
916            }
917        }
918    }
919
920    if let Some(b) = kv_bits_arg {
921        if ![0, 4, 8, 16, 84].contains(&b) {
922            eprintln!("--kv-bits {b}: expected 0 (auto), 4 (q4_0), 8 (q8_0), 16 (f16) or 84 (K q8_0 / V q4_0)");
923            std::process::exit(2);
924        }
925    }
926    let kv_bits = match (park_lossy, kv_bits_arg) {
927        (true, None) | (true, Some(16)) => 16,
928        (true, Some(other)) => {
929            eprintln!("--park-lossy requires F16 KV pools; --kv-bits {other} conflicts");
930            std::process::exit(2);
931        }
932        (false, v) => v.unwrap_or(0),
933    };
934
935    let dir_default = max_context.is_none() && !max_context_auto_flag && model_dir.is_some();
936    // A fleet head holds no model; its window is the nodes', learned when it reaches them.
937    let max_context_auto = max_context.is_none() && !dir_default && !fleet_mode;
938    let sizing_lanes = if fim_model.is_some() { max_batch.max(fim_max_batch) } else { max_batch };
939    // (the window each lane's share of the device holds, the model's
940    // trained window)
941    let mut worker_sized: Option<(u64, u64)> = None;
942    let unsized_why: Option<(String, String)> = if max_context_auto {
943        let sizing = runtime.id.base();
944        let elsewhere = models.iter().chain(fim_model.iter()).find_map(|m| {
945            let on = if fim_model.as_ref() == Some(m) { catalog.reader_of(m) } else { runtime_pick.resolve(m, &catalog) };
946            on.ok().filter(|id| id.base() != sizing).map(|id| (m, id))
947        });
948        match elsewhere {
949            Some((m, id)) => Some((
950                format!(
951                    "--max-context auto sizes the window for the {} runtime, and {} is served by {}",
952                    sizing.name(),
953                    m.display(),
954                    id.base().name()
955                ),
956                format!(
957                    "not sized for this device: the {} runtime sizes it, and {} is served by {}",
958                    sizing.name(),
959                    m.display(),
960                    id.base().name()
961                ),
962            )),
963            None if !superfluid_daemon::linked::sizes_context(sizing) => {
964                let set: Vec<&PathBuf> = models.iter().chain(fim_model.iter()).collect();
965                let sized = set
966                    .iter()
967                    .map(|m| superfluid_daemon::runtimes::sizing_of(&runtime, m))
968                    .collect::<Result<Vec<_>, String>>()
969                    .and_then(|each| {
970                        let trained = each.iter().map(|s| s.trained_context).min().unwrap_or(0);
971                        superfluid_adapter_kit::sizing::window(&each, sizing_lanes).map(|n| (n, trained)).ok_or_else(|| {
972                            format!("the {} runtime reported no device budget to size a window against", sizing.name())
973                        })
974                    });
975                match sized {
976                    Ok(n) => {
977                        worker_sized = Some(n);
978                        None
979                    }
980                    Err(why) => Some((format!("--max-context auto: {why}"), why)),
981                }
982            }
983            None => None,
984        }
985    } else {
986        None
987    };
988    let max_context_auto = max_context_auto && unsized_why.is_none();
989    let ctx = if let Some(n) = max_context {
990        n
991    } else if fleet_mode {
992        8192
993    } else if dir_default {
994        eprintln!(
995            "superfluid: context window 8192 tokens (--model-dir loads models on demand at this window, so it is not \
996             sized to fill the device with the startup model; --max-context N|auto overrides)"
997        );
998        8192
999    } else if let Some((asked, kept)) = unsized_why {
1000        if max_context_auto_flag {
1001            eprintln!("superfluid: {asked}: pass --max-context <tokens>");
1002            std::process::exit(2);
1003        }
1004        eprintln!("superfluid: context window 8192 tokens ({kept}; --max-context N pins it)");
1005        8192
1006    } else if let Some((per_lane, trained)) = worker_sized {
1007        // One conversation may grow past its share of the pool, up to the trained window.
1008        let whole = per_lane.saturating_mul(u64::from(sizing_lanes.max(1)));
1009        let n = i32::try_from(whole.min(trained.max(per_lane))).unwrap_or(i32::MAX);
1010        eprintln!(
1011            "superfluid: context window {n} tokens (auto: sized for this device, {per_lane} for each of {sizing_lanes} lanes{}{}, \
1012             one conversation up to the trained {trained}; --max-context N pins it)",
1013            if models.len() > 1 { format!(", {} models", models.len()) } else { String::new() },
1014            if fim_model.is_some() { ", FIM satellite included" } else { "" },
1015        );
1016        n
1017    } else {
1018        use superfluid_daemon::speculate_resolve::{resolve_all, sizing_path, SpecLoad};
1019        let ranked: Vec<Vec<String>> = models
1020            .iter()
1021            .map(|m| {
1022                if *m == model {
1023                    speculate_primary.iter().chain(speculate_fallbacks.iter()).cloned().collect()
1024                } else {
1025                    speculate.as_deref().and_then(|d| resolve_all(d, m).ok()).unwrap_or_default()
1026                }
1027            })
1028            .collect();
1029        let sizing_models: Vec<PathBuf> = models.iter().cloned().chain(fim_model.iter().cloned()).collect();
1030        let lanes = if fim_model.is_some() { max_batch.max(fim_max_batch) } else { max_batch } as i32;
1031        let speculator_entry = |i: usize, r: &str| -> Option<(PathBuf, bool, Option<usize>)> {
1032            let (path, load) = sizing_path(r, &models[i])?;
1033            Some((path, load == SpecLoad::EmbeddedHead, (load == SpecLoad::Sidecar).then_some(i)))
1034        };
1035        let speculators_for = |choice: &[Option<&String>]| -> Vec<(PathBuf, bool, Option<usize>)> {
1036            (0..models.len()).filter_map(|i| speculator_entry(i, choice[i].or(ranked[i].first())?)).collect()
1037        };
1038        let sizing = runtime.id.base();
1039        let size = |speculators: &[(PathBuf, bool, Option<usize>)]| {
1040            superfluid_daemon::linked::suggest_max_context(sizing, &sizing_models, speculators, lanes, kv_bits).unwrap_or_else(|e| {
1041                eprintln!("superfluid: {e}");
1042                std::process::exit(2);
1043            })
1044        };
1045        let first_picks: Vec<Option<&String>> = vec![None; models.len()];
1046        let n_spec = speculators_for(&first_picks).len();
1047        const MAX_COMBINATIONS: usize = 16;
1048        let counts: Vec<usize> = ranked.iter().map(|c| c.len().max(1)).collect();
1049        let few_enough = counts
1050            .iter()
1051            .try_fold(1usize, |p, &c| p.checked_mul(c).filter(|p| *p <= MAX_COMBINATIONS))
1052            .is_some();
1053        let (sized, note) = if few_enough {
1054            let mut best: Option<(i32, Vec<Option<&String>>)> = None;
1055            let mut idx = vec![0usize; models.len()];
1056            loop {
1057                let choice: Vec<Option<&String>> =
1058                    idx.iter().enumerate().map(|(i, &k)| (k > 0).then(|| &ranked[i][k])).collect();
1059                if let Some(n) = size(&speculators_for(&choice)) {
1060                    if best.as_ref().is_none_or(|(b, _)| n < *b) {
1061                        best = Some((n, choice));
1062                    }
1063                }
1064                let mut i = 0;
1065                while i < idx.len() {
1066                    idx[i] += 1;
1067                    if idx[i] < counts[i] {
1068                        break;
1069                    }
1070                    idx[i] = 0;
1071                    i += 1;
1072                }
1073                if i == idx.len() {
1074                    break;
1075                }
1076            }
1077            let fallbacks: Vec<&str> =
1078                best.iter().flat_map(|(_, c)| c.iter().flatten().map(|s| s.as_str())).collect();
1079            let note = if fallbacks.is_empty() {
1080                String::new()
1081            } else {
1082                format!(", sized for the fallback {}", fallbacks.join(" + "))
1083            };
1084            (best.map(|(n, _)| n), note)
1085        } else {
1086            let all: Vec<(PathBuf, bool, Option<usize>)> = (0..models.len())
1087                .flat_map(|i| ranked[i].iter().filter_map(move |r| speculator_entry(i, r)))
1088                .collect();
1089            let note = format!(", every one of {} drafter candidates charged at once", all.len());
1090            (size(&all), note)
1091        };
1092        match sized {
1093            Some(n) => {
1094                eprintln!(
1095                    "superfluid: context window {n} tokens (auto: sized for this device, {lanes} lanes{}{}{}; --max-context N pins it)",
1096                    if n_spec == 0 { String::new() } else { format!(", {n_spec} speculator(s)") },
1097                    if fim_model.is_some() { ", FIM satellite included" } else { "" },
1098                    note
1099                );
1100                n
1101            }
1102            None => {
1103                eprintln!(
1104                    "superfluid: context window 8192 tokens (auto could not size this device/model set; --max-context N pins it)"
1105                );
1106                8192
1107            }
1108        }
1109    }
1110    .max(512);
1111    if let Some(n) = max_context {
1112        eprintln!("superfluid: context window {ctx} tokens (--max-context {n})");
1113    }
1114    // A worker process starts loading now, while the tokenizer and chat
1115    // template are built: the two do not depend on each other, and the load
1116    // is most of startup.
1117    let spawns_worker = !runtime.in_process_capable() || (worker_process && runtime.worker.bin.is_file());
1118    let early_host = (!fleet_mode && spawns_worker).then(|| {
1119        let spec = superfluid_daemon::WorkerSpec {
1120            bin: runtime.worker.bin.clone(),
1121            args: runtime.serve_args(&model, ctx, max_batch, kv_bits),
1122            stderr: worker_stderr.clone(),
1123        };
1124        std::thread::spawn(move || EngineHost::spawn_process(spec))
1125    });
1126    let tokenizer = open_tokenizer(&runtime, &model).unwrap_or_else(|e| {
1127        eprintln!("superfluid: {e}");
1128        std::process::exit(2);
1129    });
1130    let codec: Box<dyn superfluid_daemon::TextCodec + Send + Sync> = match codec_for_dialect(&dialect, tokenizer) {
1131        Ok((c, kind)) => {
1132            if dialect == "auto" {
1133                match kind {
1134                    "template" => eprintln!("superfluid: serving via the model's own chat template"),
1135                    "raw" => eprintln!(
1136                        "superfluid: model has no chat template; serving raw (chat routes will refuse)"
1137                    ),
1138                    _ => {}
1139                }
1140            }
1141            c
1142        }
1143        Err(e) => {
1144            eprintln!("superfluid: {e}");
1145            std::process::exit(2);
1146        }
1147    };
1148
1149    if fleet_mode {
1150        let fleet_addrs = fleet.clone().unwrap_or_default();
1151        use superfluid_daemon::fleet_manager::{FleetManager, NodeSpec, PlacementPolicy};
1152        let auth: Vec<u8> = match &fleet_auth {
1153            Some(pth) => std::fs::read(pth)
1154                .map(|t| t.trim_ascii().to_vec())
1155                .unwrap_or_else(|e| {
1156                    eprintln!("superfluid: cannot read --fleet-auth {}: {e}", pth.display());
1157                    std::process::exit(2);
1158                }),
1159            None => superfluid_daemon::fleet_token::read(catalog.home()).unwrap_or_default(),
1160        };
1161        if auth.is_empty() {
1162            eprintln!("superfluid: no fleet token (`superfluid fleet token` makes one): the links to the nodes are not encrypted, and only nodes on this machine may join");
1163        }
1164        let specs: Vec<NodeSpec> = fleet_addrs
1165            .split(',')
1166            .map(str::trim)
1167            .filter(|a| !a.is_empty())
1168            .map(|a| NodeSpec { addr: a.to_string(), auth: auth.clone() })
1169            .collect();
1170        if specs.is_empty() && fleet_listen.is_none() {
1171            eprintln!("superfluid: --fleet needs at least one host:port, or --fleet-listen for nodes that join");
1172            std::process::exit(2);
1173        }
1174        let policy = match fleet_policy.as_str() {
1175            "load-aware" => PlacementPolicy::LoadAware,
1176            "least-loaded" => PlacementPolicy::LeastLoaded,
1177            "round-robin" => PlacementPolicy::RoundRobin,
1178            other => {
1179                eprintln!(
1180                    "superfluid: unknown --fleet-policy '{other}' \
1181                     (load-aware|least-loaded|round-robin)"
1182                );
1183                std::process::exit(2);
1184            }
1185        };
1186        let Some(addr) = http else {
1187            eprintln!("superfluid: --fleet requires --http <addr> (the fleet OpenAI endpoint)");
1188            std::process::exit(2);
1189        };
1190        let lease = superfluid_proto::linkf::Lease { duration_ms: 60_000, renew_by_ms: 40_000 };
1191        let n_nodes = specs.len();
1192        let manager = Arc::new(FleetManager::with_options(
1193            specs,
1194            policy,
1195            lease,
1196            fleet_pool_high,
1197            fleet_conns,
1198        ));
1199        let mut reached = 0;
1200        for node in manager.probe() {
1201            match node {
1202                Ok(n) => {
1203                    reached += 1;
1204                    eprintln!(
1205                        "superfluid: fleet node '{}' at {}: {}, context window {} tokens, {} lanes",
1206                        n.identity,
1207                        n.addr,
1208                        n.models.join(", "),
1209                        n.max_context_tokens,
1210                        n.max_lanes
1211                    );
1212                    if !n.models.contains(&model_name) {
1213                        eprintln!(
1214                            "superfluid: fleet node '{}' serves {}, not {model_name}, so nothing is placed \
1215                             there; start it with --model-name {model_name}",
1216                            n.identity,
1217                            n.models.join(", ")
1218                        );
1219                    }
1220                }
1221                Err(e) => eprintln!("superfluid: {e}; retrying it in the background"),
1222            }
1223        }
1224        if max_context.is_none() {
1225            match manager.advertised_context(&model_name) {
1226                Some(n) => eprintln!("superfluid: context window {n} tokens (the largest node's)"),
1227                None => eprintln!(
1228                    "superfluid: context window unknown until a node serving {model_name} answers"
1229                ),
1230            }
1231        }
1232        manager.spawn_keepalive();
1233        let _announced = fleet_listen.as_deref().map(|listen| {
1234            let listener = std::net::TcpListener::bind(listen).unwrap_or_else(|e| {
1235                eprintln!("superfluid: --fleet-listen {listen}: {e}");
1236                std::process::exit(1);
1237            });
1238            let port = listener.local_addr().map(|a| a.port()).unwrap_or_default();
1239            eprintln!("superfluid: fleet head takes joining nodes on {listen}");
1240            let (m, a, name) = (Arc::clone(&manager), auth.clone(), model_name.clone());
1241            std::thread::spawn(move || superfluid_daemon::fleet_join::accept_nodes(listener, m, a, name));
1242            match superfluid_daemon::fleet_mdns::advertise(port, &model_name) {
1243                Ok(d) => Some(d),
1244                Err(e) => {
1245                    eprintln!("superfluid: not announced on the local network ({e}); give nodes this head's address");
1246                    None
1247                }
1248            }
1249        });
1250        if !qos_flags.is_empty() {
1251            eprintln!(
1252                "superfluid: {} not supported in fleet-head mode (the fleet head has no \
1253                 local scheduler and Link F carries no QoS class); set them on each \
1254                 node's own `superfluid serve` instead",
1255                qos_flags.join(", ")
1256            );
1257            std::process::exit(2);
1258        }
1259        if op_spec_max_temperature.is_some() {
1260            eprintln!(
1261                "superfluid: --spec-max-temperature is not supported in fleet-head mode; \
1262                 set it on each node's own `superfluid serve` instead"
1263            );
1264            std::process::exit(2);
1265        }
1266        if fim_model.is_some() {
1267            eprintln!(
1268                "superfluid: --fim-model is not supported in fleet-head mode (the head hosts no \
1269                 engine); set it on a node's own `superfluid serve` instead"
1270            );
1271            std::process::exit(2);
1272        }
1273        if op_repeat_penalty.is_some() {
1274            eprintln!(
1275                "superfluid: --repeat-penalty is not supported in fleet-head mode \
1276                 (the fleet protocol carries no per-request penalty); \
1277                 set it on each node's own `superfluid serve` instead"
1278            );
1279            std::process::exit(2);
1280        }
1281        let http_listener = listen_or_exit("--http", &addr);
1282        eprintln!(
1283            "superfluid: fleet-head openai api on http://{addr} over {n_nodes} node(s), {reached} reachable"
1284        );
1285        if key_policy.is_some() {
1286            eprintln!(
1287                "superfluid: --key-policy at the fleet head: keys, rate and concurrency limits \
1288                 apply; class and max_class do not (the head has no local scheduler)"
1289            );
1290        }
1291        let codec_arc: Arc<dyn superfluid_daemon::TextCodec + Send + Sync> = Arc::from(codec);
1292        superfluid_daemon::fleet_openai::serve_blocking(
1293            http_listener,
1294            manager,
1295            codec_arc,
1296            "raw".to_string(),
1297            model_name.clone(),
1298            max_context.map_or(0, |n| n.max(512) as u32),
1299            superfluid_daemon::openai::ServeConfig {
1300                api_key: api_key.clone(),
1301                rate_limit_per_minute: rate_limit,
1302                keys: key_policy.clone(),
1303                sampling: superfluid_daemon::openai::SamplingOverrides {
1304                    temperature: op_temperature,
1305                    top_p: op_top_p,
1306                    top_k: op_top_k,
1307                    min_p: op_min_p,
1308                    repeat_penalty: op_repeat_penalty,
1309                    spec_max_temperature: None,
1310                    fallback: embedding.sampling_fallback,
1311                },
1312                ..Default::default()
1313            },
1314        )
1315        .expect("fleet serve");
1316        return;
1317    }
1318
1319    let wal_path = sessions.join("wal.log");
1320    let store = match SessionStore::open(&wal_path) {
1321        Ok(store) => store,
1322        Err(e @ (superfluid_daemon::DaemonError::WalCorrupt | superfluid_daemon::DaemonError::Codec(_))) => {
1323            eprintln!(
1324                "superfluid: the session log {} is damaged ({e}). A record with more of the log \
1325                 after it fails its checksum, or a whole record does not decode, so this was not a \
1326                 crash mid-write (a torn last record is dropped on its own); this server won't guess \
1327                 which history to keep. Move the file aside to start fresh, or run with \
1328                 --sessions-cache if sessions don't need to survive a restart.",
1329                wal_path.display()
1330            );
1331            std::process::exit(1);
1332        }
1333        Err(e) => {
1334            eprintln!("superfluid: could not open the session log {}: {e}", wal_path.display());
1335            std::process::exit(1);
1336        }
1337    };
1338
1339    let workerd_bin = catalog.workerd().to_path_buf();
1340    let worker_process = if !runtime.in_process_capable() {
1341        if !worker_process {
1342            eprintln!(
1343                "superfluid: --no-worker-process: the {} runtime runs in its own worker ({})",
1344                runtime.id.name(),
1345                runtime.worker.bin.display()
1346            );
1347        }
1348        true
1349    } else if worker_process && !runtime.worker.bin.is_file() {
1350        if fim_model.is_some() {
1351            eprintln!(
1352                "superfluid: --fim-model needs superfluid-workerd (the satellite runs in its own \
1353                 worker), not found at {}",
1354                workerd_bin.display()
1355            );
1356            std::process::exit(1);
1357        }
1358        if models.len() > 1 {
1359            eprintln!(
1360                "superfluid: {} models need superfluid-workerd (one address space each), \
1361                 not found at {}",
1362                models.len(),
1363                workerd_bin.display()
1364            );
1365            std::process::exit(1);
1366        }
1367        eprintln!(
1368            "superfluid: superfluid-workerd not found at {}; running the engine in-process \
1369             (install the bundled workerd or pass --no-worker-process to silence)",
1370            workerd_bin.display()
1371        );
1372        false
1373    } else {
1374        worker_process
1375    };
1376    let model_display = model.display().to_string();
1377    let model_source = model.to_string_lossy().into_owned();
1378    let host = if worker_process {
1379        match early_host {
1380            Some(started) => started.join().expect("worker start thread"),
1381            None => EngineHost::spawn_process(superfluid_daemon::WorkerSpec {
1382                bin: runtime.worker.bin.clone(),
1383                args: runtime.serve_args(&model, ctx, max_batch, kv_bits),
1384                stderr: worker_stderr.clone(),
1385            }),
1386        }
1387        .unwrap_or_else(|e| {
1388            #[cfg(feature = "tui")]
1389            if let Some(c) = stderr_capture.as_ref() {
1390                c.drain(std::time::Duration::from_secs(2));
1391            }
1392            eprintln!(
1393                "superfluid: could not start the engine worker for '{model_display}': {e} \
1394                 (the worker's stderr above carries the engine's reason)"
1395            );
1396            std::process::exit(1);
1397        })
1398    } else {
1399        superfluid_daemon::linked::spawn(runtime.id, model, ctx, max_batch, kv_bits)
1400            .unwrap_or_else(|| Err(format!("this build does not link the {} runtime", runtime.id)))
1401            .unwrap_or_else(|e| {
1402                eprintln!("superfluid: could not load '{model_display}': {e}");
1403                std::process::exit(1);
1404            })
1405    };
1406
1407    let caps = host.capabilities().read().expect("capability record").clone();
1408    if let Err(e) = caps.check_startup_flags(park_lossy, kv_bits_arg, speculate.as_deref()) {
1409        eprintln!("superfluid: {e}");
1410        std::process::exit(2);
1411    }
1412    if prefix_blob_budget_mb > 0 {
1413        if let superfluid_daemon::capabilities::Cap::No(why) = caps.get("ops", "space_promote") {
1414            eprintln!("superfluid: --prefix-blob-budget-mb has no effect on runtime {}: {why}", runtime.id.name());
1415        }
1416    }
1417
1418    let listener = socket.as_ref().map(|socket| {
1419        let _ = std::fs::remove_file(socket);
1420        let listener = UnixListener::bind(socket).unwrap_or_else(|e| {
1421            eprintln!("superfluid: cannot listen on --socket {}: {e}", socket.display());
1422            std::process::exit(1);
1423        });
1424        eprintln!("superfluid: serving on {}", socket.display());
1425        listener
1426    });
1427    let park_dir = park.then(|| sessions.join("park"));
1428
1429    let codec: Box<dyn superfluid_daemon::TextCodec + Send + Sync> = match tool_envelope {
1430        Some(env) => {
1431            if let Some(why) = superfluid_daemon::codec::tool_envelope_override_conflict(&*codec, env) {
1432                eprintln!("superfluid: {why}");
1433                std::process::exit(2);
1434            }
1435            eprintln!("superfluid: tool-call envelope overridden to '{}'", env.name());
1436            Box::new(superfluid_daemon::codec::WithToolEnvelope::new(codec, env))
1437        }
1438        None => codec,
1439    };
1440    let daemon = Arc::new(
1441        Daemon::with_options(
1442            store,
1443            host,
1444            codec,
1445            superfluid_daemon::DaemonOptions {
1446                max_lanes: max_batch as usize,
1447                park_dir,
1448                park_lossy,
1449                park_budget_bytes: park_budget_gb.saturating_mul(1 << 30),
1450                tool_lease_ms,
1451                tick_decode_budget,
1452                tick_target_ms,
1453                runtime_id: Some(runtime.id.name()),
1454                prefill_budget,
1455                agent_starvation_ticks: starvation_ticks,
1456                class_lanes,
1457                pressure_high_pct: if pressure_high > 0 { pressure_high } else { 85 },
1458                pressure_low_pct: if pressure_low > 0 { pressure_low } else { 70 },
1459                pin_budget_pct,
1460                pressure_source: if os_pressure {
1461                    superfluid_daemon::pressure::PressureConfig::Os
1462                } else {
1463                    superfluid_daemon::pressure::PressureConfig::None
1464                },
1465                speculate: speculate_primary.clone(),
1466                speculate_auto: speculate.as_deref().map(str::trim) == Some("auto"),
1467                speculate_fallbacks: speculate_fallbacks.clone(),
1468                speculation: speculation.clone(),
1469                media_dir: Some(sessions.join("media")),
1470                files_max_bytes,
1471                files_expiry_secs: files_expiry,
1472                completion_deadline_ms,
1473                trace_scope: superfluid_daemon::otlp::TraceScope::model(&model_name),
1474                completion_bucket,
1475            },
1476        )
1477        .unwrap_or_else(|e| {
1478            #[cfg(feature = "tui")]
1479            if let Some(c) = stderr_capture.as_ref() {
1480                c.drain(std::time::Duration::from_secs(2));
1481            }
1482            eprintln!("superfluid: cannot serve '{model_display}': {e}");
1483            std::process::exit(2);
1484        }),
1485    );
1486    {
1487        let bits = daemon.sched_stats().kv_bits.load(std::sync::atomic::Ordering::Relaxed);
1488        match superfluid_daemon::scheduler::kv_bits_label(bits) {
1489            Some(label) if kv_bits != 0 && bits != kv_bits as u64 => {
1490                eprintln!("superfluid: kv cache {label} (--kv-bits {kv_bits} overridden by the engine, see above)")
1491            }
1492            Some(label) => eprintln!("superfluid: kv cache {label}"),
1493            None => {}
1494        }
1495    }
1496    let fim_satellite: Option<(String, Arc<Daemon>)> = fim_model.as_ref().map(|path| {
1497        let runtime = catalog.reader_of(path).and_then(|id| catalog.require(id)).unwrap_or_else(|e| {
1498            eprintln!("superfluid: --fim-model {}: {e}", path.display());
1499            std::process::exit(2);
1500        });
1501        let mut fim_ctx = fim_max_context.unwrap_or(ctx.min(8192)).max(512);
1502        if max_context_auto && fim_ctx > ctx {
1503            eprintln!(
1504                "superfluid: --fim-max-context {fim_ctx} is above the window sized for this device; \
1505                 the FIM satellite runs at {ctx}"
1506            );
1507            fim_ctx = ctx;
1508        }
1509        let sat = build_fim_satellite(&FimSatelliteCfg {
1510            model: path.clone(),
1511            runtime,
1512            sessions: sessions.clone(),
1513            ctx: fim_ctx,
1514            max_batch: fim_max_batch,
1515            kv_bits,
1516            pressure_high,
1517            pressure_low,
1518            os_pressure,
1519            completion_deadline_ms,
1520            worker_stderr: worker_stderr.clone(),
1521        })
1522        .unwrap_or_else(|e| {
1523            #[cfg(feature = "tui")]
1524            if let Some(c) = stderr_capture.as_ref() {
1525                c.drain(std::time::Duration::from_secs(2));
1526            }
1527            eprintln!("superfluid: could not start the --fim-model satellite '{}': {e}", path.display());
1528            std::process::exit(1);
1529        });
1530        let name = superfluid_daemon::registry::model_id_for(path);
1531        eprintln!("superfluid: FIM completions served by satellite '{name}'");
1532        (name, sat)
1533    });
1534    if let Some((name, sat)) = &fim_satellite {
1535        if let Err(e) = daemon.attach_fim_satellite(name.clone(), Arc::clone(sat)) {
1536            eprintln!(
1537                "superfluid: --fim-model {}: {e} (its tokenizer has no atomic <|fim_prefix|>/<|fim_suffix|>/<|fim_middle|> tokens)",
1538                fim_model.as_ref().map(|p| p.display().to_string()).unwrap_or_default()
1539            );
1540            std::process::exit(2);
1541        }
1542    }
1543    let state_names: StateNames = Arc::default();
1544    let registry = if worker_process {
1545        let cfg = ModelBuildCfg {
1546            model_name: embedding.model_name,
1547            base_sessions: sessions.clone(),
1548            state_names: Arc::clone(&state_names),
1549            catalog: Arc::clone(&catalog),
1550            pick: runtime_pick.clone(),
1551            dialect: dialect.clone(),
1552            ctx,
1553            max_batch,
1554            kv_bits,
1555            park,
1556            park_lossy,
1557            park_budget_gb,
1558            tool_lease_ms,
1559            tick_decode_budget,
1560            tick_target_ms,
1561            prefill_budget,
1562            starvation_ticks,
1563            class_lanes,
1564            os_pressure,
1565            pressure_high_pct: if pressure_high > 0 { pressure_high } else { 85 },
1566            pressure_low_pct: if pressure_low > 0 { pressure_low } else { 70 },
1567            pin_budget_pct,
1568            speculate: speculate.clone(),
1569            speculation: speculation.clone(),
1570            completion_deadline_ms,
1571            completion_bucket,
1572            fim_satellite: fim_satellite.clone(),
1573            worker_stderr: worker_stderr.clone(),
1574        };
1575        let reload_cfg = cfg.clone();
1576        let loader: superfluid_daemon::registry::ModelLoader =
1577            Box::new(move |id: &str, runtime| build_loaded_daemon(&cfg, std::path::Path::new(id), runtime));
1578        let registry = superfluid_daemon::registry::ModelRegistry::with_initial(model_name.clone(), Arc::clone(&daemon), loader);
1579        registry.remember_source_on(&model_name, &model_source, Some(runtime.id));
1580        let names = Arc::clone(&catalog);
1581        registry.set_runtime_names(Box::new(move |name: &str| {
1582            names.named(name).map_err(|why| format!("{why} ({})", names.here_described()))
1583        }));
1584        registry.set_reloader(Box::new(move |id: &str, runtime, retired: &Daemon| {
1585            reload_daemon(&reload_cfg, std::path::Path::new(id), runtime, retired)
1586        }));
1587        Arc::new(registry)
1588    } else {
1589        Arc::new(superfluid_daemon::registry::ModelRegistry::single(
1590            model_name.clone(),
1591            Arc::clone(&daemon),
1592        ))
1593    };
1594    {
1595        let (registry, name) = (Arc::downgrade(&registry), model_name.clone());
1596        daemon.on_retired(Box::new(move || {
1597            if let Some(registry) = registry.upgrade() {
1598                let _ = registry.resolve(Some(&name));
1599            }
1600        }));
1601    }
1602    let same = |p: &std::path::Path| p.canonicalize().unwrap_or_else(|_| p.to_path_buf());
1603    let mut taken: std::collections::HashSet<String> = std::collections::HashSet::from([model_name.clone()]);
1604    let mut given: std::collections::HashSet<PathBuf> = models.first().map(|m| same(m)).into_iter().collect();
1605    for extra in models.iter().skip(1) {
1606        if !given.insert(same(extra)) {
1607            eprintln!("superfluid: model '{}' already loaded, skipping", (embedding.model_name)(extra, &Default::default()));
1608            continue;
1609        }
1610        let name = match pulled_names.get(extra) {
1611            Some(name) => name.clone(),
1612            None => {
1613                let name = (embedding.model_name)(extra, &taken);
1614                // The worker names the model again with nothing taken, so a
1615                // name the collision chose is recorded for it to find.
1616                if name != (embedding.model_name)(extra, &Default::default()) {
1617                    eprintln!("superfluid: {} is registered as '{name}' (another model took its stem)", extra.display());
1618                    state_names.lock().expect("state names").insert(extra.clone(), name.clone());
1619                }
1620                name
1621            }
1622        };
1623        taken.insert(name.clone());
1624        match registry.load_as(&name, &extra.to_string_lossy()) {
1625            Ok(true) => eprintln!("superfluid: loaded model '{name}'"),
1626            Ok(false) => eprintln!("superfluid: model '{name}' already loaded, skipping"),
1627            Err(e) => {
1628                #[cfg(feature = "tui")]
1629                if let Some(c) = stderr_capture.as_ref() {
1630                    c.drain(std::time::Duration::from_secs(2));
1631                }
1632                eprintln!("superfluid: could not load '{}': {e}", extra.display());
1633                std::process::exit(1);
1634            }
1635        }
1636    }
1637    if let Some(dir) = &model_dir {
1638        let mut n = 0;
1639        let here = catalog.survey();
1640        match artifacts_under(dir, &here, MODEL_DIR_DEPTH) {
1641            Ok(paths) => {
1642                for path in paths {
1643                    let refused = runtime_pick
1644                        .resolve(&path, &catalog)
1645                        .and_then(|rt| catalog.require(rt))
1646                        .and_then(|rt| refuse_statically(&rt, park_lossy, kv_bits_arg, speculate.as_deref()));
1647                    if let Err(e) = refused {
1648                        eprintln!("superfluid: --model-dir: skipping {}: {e}", path.display());
1649                        continue;
1650                    }
1651                    if !given.insert(same(&path)) {
1652                        continue;
1653                    }
1654                    let name = (embedding.model_name)(&path, &taken);
1655                    if registry.is_loaded(&name) {
1656                        taken.insert(name);
1657                        continue;
1658                    }
1659                    if name != (embedding.model_name)(&path, &Default::default()) {
1660                        eprintln!(
1661                            "superfluid: --model-dir: {} is registered as '{name}' (another artifact took its stem)",
1662                            path.display()
1663                        );
1664                        state_names.lock().expect("state names").insert(path.clone(), name.clone());
1665                    }
1666                    registry.register_known(&name, &path);
1667                    taken.insert(name);
1668                    n += 1;
1669                }
1670            }
1671            Err(e) => {
1672                eprintln!("superfluid: --model-dir {}: {e}", dir.display());
1673                std::process::exit(1);
1674            }
1675        }
1676        eprintln!("superfluid: {n} model(s) known from {} (loaded on first use)", dir.display());
1677    }
1678    if idle_timeout > 0 {
1679        registry.track_idle();
1680        let reg = Arc::clone(&registry);
1681        let max_idle = std::time::Duration::from_secs(idle_timeout);
1682        let tick = std::time::Duration::from_secs((idle_timeout / 4).max(1));
1683        std::thread::spawn(move || loop {
1684            std::thread::sleep(tick);
1685            for name in reg.sweep_idle(max_idle) {
1686                eprintln!("superfluid: unloaded idle model '{name}'");
1687            }
1688        });
1689    }
1690    if files_expiry.is_some() && files_sweep > 0 {
1691        let d = Arc::clone(&daemon);
1692        std::thread::spawn(move || loop {
1693            std::thread::sleep(std::time::Duration::from_secs(files_sweep));
1694            let n = d.files().sweep();
1695            if n > 0 {
1696                eprintln!("superfluid: files sweep removed {n} expired file(s)");
1697            }
1698        });
1699    }
1700    let mut http_thread = None;
1701    if let Some(addr) = http {
1702        let http_listener = listen_or_exit("--http", &addr);
1703        eprintln!("superfluid: openai api on http://{addr}{}", embedding.http.also_served());
1704        superfluid_daemon::selfupdate::check_in_background(catalog.home().to_path_buf(), pull_flags.offline);
1705        if nonstream_keepalive > 0 {
1706            eprintln!(
1707                "superfluid: non-streaming keep-alive on: 200 at admission, then a space every \
1708                 {nonstream_keepalive}s until the JSON body (x-superfluid-keepalive: whitespace)"
1709            );
1710        }
1711        let reg = Arc::clone(&registry);
1712        let serve_cfg = superfluid_daemon::openai::ServeConfig {
1713            api_key: api_key.clone(),
1714            rate_limit_per_minute: rate_limit,
1715            drain_timeout_secs: drain_timeout,
1716            default_max_tokens,
1717            max_context: ctx as u32,
1718            sampling: superfluid_daemon::openai::SamplingOverrides {
1719                temperature: op_temperature,
1720                top_p: op_top_p,
1721                top_k: op_top_k,
1722                min_p: op_min_p,
1723                repeat_penalty: op_repeat_penalty,
1724                spec_max_temperature: op_spec_max_temperature,
1725                fallback: embedding.sampling_fallback,
1726            },
1727            qos: superfluid_daemon::openai::HttpQosPolicy {
1728                default: http_default_qos,
1729                honor_headers: http_qos_header,
1730                allow_batch_invariant: http_allow_batch_invariant,
1731            },
1732            keys: key_policy.clone(),
1733            nonstream_keepalive_secs: nonstream_keepalive,
1734            surface: embedding.http,
1735            model_naming: embedding.model_naming,
1736            identity: embedding.identity.clone(),
1737        };
1738        http_thread = Some(std::thread::spawn(move || {
1739            superfluid_daemon::openai::serve_blocking_config(http_listener, reg, serve_cfg)
1740        }));
1741    }
1742    if let Some(addr) = web {
1743        if !addr.starts_with("127.0.0.1:") && !addr.starts_with("[::1]:") && !addr.starts_with("localhost:") {
1744            eprintln!("superfluid: --web must bind loopback (127.0.0.1/::1)");
1745            std::process::exit(2);
1746        }
1747        let token_path = sessions.join("web-token");
1748        let token = superfluid_daemon::apiweb::mint_token(&token_path).expect("mint web token");
1749        let web_listener = listen_or_exit("--web", &addr);
1750        eprintln!("superfluid: api-web on http://{addr} (token: {})", token_path.display());
1751        let d = Arc::clone(&daemon);
1752        std::thread::spawn(move || {
1753            let _ = superfluid_daemon::apiweb::serve_blocking(web_listener, d, token, web_origins);
1754        });
1755    }
1756    let _otlp_pusher = otlp_metrics.map(|url| {
1757        eprintln!(
1758            "superfluid: pushing metrics to {url}/v1/metrics every {otlp_interval_ms}ms (an EGRESS)"
1759        );
1760        superfluid_daemon::export::MetricsPusher::start(
1761            url,
1762            std::time::Duration::from_millis(otlp_interval_ms),
1763            daemon.sched_stats(),
1764        )
1765    });
1766    #[cfg(feature = "tui")]
1767    if let Some(ring) = tui_ring {
1768        if let Some(listener) = listener {
1769            let serve_daemon = Arc::clone(&daemon);
1770            std::thread::spawn(move || {
1771                let _ = api::serve(listener, serve_daemon);
1772            });
1773        }
1774        let node = superfluid_daemon::tui::NodeInfo {
1775            identity: hostname(),
1776            model: model_name.clone(),
1777            engine: if worker_process { "native (worker)".into() } else { "native".into() },
1778            max_lanes: max_batch,
1779        };
1780        superfluid_daemon::tui::run(daemon, ring, node, stderr_capture.as_ref()).expect("tui");
1781        drop(stderr_capture);
1782        return;
1783    }
1784    let Some(listener) = listener else {
1785        // No session socket: the HTTP API is the server, and the process
1786        // lasts as long as it does.
1787        match http_thread.map(std::thread::JoinHandle::join) {
1788            Some(Ok(Ok(()))) => return,
1789            Some(Ok(Err(e))) => eprintln!("superfluid: the HTTP API stopped: {e}"),
1790            Some(Err(_)) => eprintln!("superfluid: the HTTP API stopped: its thread panicked"),
1791            None => unreachable!("without a session socket --no-http is refused"),
1792        }
1793        std::process::exit(1);
1794    };
1795    api::serve(listener, daemon).expect("serve");
1796}
1797
1798#[cfg(feature = "tui")]
1799fn hostname() -> String {
1800    let mut buf = [0u8; 256];
1801    // SAFETY: `buf` is a valid 256-byte writable buffer and `buf.len()` is its
1802    // exact capacity; gethostname writes at most that many bytes and NUL-
1803    // terminates within it. We only read `buf` after checking rc == 0.
1804    let rc = unsafe { libc::gethostname(buf.as_mut_ptr().cast::<libc::c_char>(), buf.len()) };
1805    if rc == 0 {
1806        let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
1807        String::from_utf8_lossy(&buf[..end]).into_owned()
1808    } else {
1809        "local".into()
1810    }
1811}
1812
1813type StateNames = Arc<std::sync::Mutex<std::collections::HashMap<PathBuf, String>>>;
1814
1815#[derive(Clone)]
1816struct ModelBuildCfg {
1817    model_name: fn(&Path, &std::collections::HashSet<String>) -> String,
1818    base_sessions: std::path::PathBuf,
1819    state_names: StateNames,
1820    catalog: Arc<superfluid_daemon::runtimes::Catalog>,
1821    pick: RuntimePick,
1822    dialect: String,
1823    ctx: i32,
1824    max_batch: u32,
1825    kv_bits: i32,
1826    park: bool,
1827    park_lossy: bool,
1828    park_budget_gb: u64,
1829    tool_lease_ms: u64,
1830    tick_decode_budget: u32,
1831    tick_target_ms: u32,
1832    prefill_budget: u32,
1833    starvation_ticks: u32,
1834    class_lanes: [usize; 4],
1835    os_pressure: bool,
1836    pressure_high_pct: u8,
1837    pressure_low_pct: u8,
1838    pin_budget_pct: u8,
1839    speculate: Option<String>,
1840    speculation: superfluid_daemon::SpeculationOptions,
1841    completion_deadline_ms: u64,
1842    completion_bucket: superfluid_daemon::completion_bucket::BucketConfig,
1843    fim_satellite: Option<(String, Arc<Daemon>)>,
1844    worker_stderr: Option<std::sync::Arc<std::os::fd::OwnedFd>>,
1845}
1846
1847struct FimSatelliteCfg {
1848    model: PathBuf,
1849    runtime: superfluid_daemon::runtimes::Runtime,
1850    sessions: PathBuf,
1851    ctx: i32,
1852    max_batch: u32,
1853    kv_bits: i32,
1854    pressure_high: u8,
1855    pressure_low: u8,
1856    os_pressure: bool,
1857    completion_deadline_ms: u64,
1858    worker_stderr: Option<std::sync::Arc<std::os::fd::OwnedFd>>,
1859}
1860
1861fn build_fim_satellite(cfg: &FimSatelliteCfg) -> Result<Arc<Daemon>, superfluid_daemon::DaemonError> {
1862    use superfluid_daemon::DaemonError;
1863    let tok = open_tokenizer(&cfg.runtime, &cfg.model)
1864        .map_err(|e| DaemonError::Generation(format!("satellite tokenizer load failed: {e}")))?;
1865    let codec = superfluid_daemon::BundleCodec::from_tokenizer(tok);
1866    let host = EngineHost::spawn_process(superfluid_daemon::WorkerSpec {
1867        bin: cfg.runtime.worker.bin.clone(),
1868        args: cfg.runtime.serve_args(&cfg.model, cfg.ctx, cfg.max_batch, cfg.kv_bits),
1869        stderr: cfg.worker_stderr.clone(),
1870    })?;
1871    let daemon = Daemon::with_options(
1872        SessionStore::ephemeral(),
1873        host,
1874        Box::new(codec),
1875        superfluid_daemon::DaemonOptions {
1876            max_lanes: cfg.max_batch as usize,
1877            park_dir: None,
1878            pressure_high_pct: if cfg.pressure_high > 0 { cfg.pressure_high } else { 85 },
1879            pressure_low_pct: if cfg.pressure_low > 0 { cfg.pressure_low } else { 70 },
1880            pressure_source: if cfg.os_pressure {
1881                superfluid_daemon::pressure::PressureConfig::Os
1882            } else {
1883                superfluid_daemon::pressure::PressureConfig::None
1884            },
1885            media_dir: Some(cfg.sessions.join("fim").join("media")),
1886            completion_deadline_ms: cfg.completion_deadline_ms,
1887            trace_scope: superfluid_daemon::otlp::TraceScope::model(
1888                &superfluid_daemon::registry::model_id_for(&cfg.model),
1889            ),
1890            ..Default::default()
1891        },
1892    )?;
1893    Ok(Arc::new(daemon))
1894}
1895
1896fn build_codec_for(
1897    dialect: &str,
1898    runtime: &superfluid_daemon::runtimes::Runtime,
1899    model: &std::path::Path,
1900) -> Result<Box<dyn superfluid_daemon::TextCodec + Send + Sync>, superfluid_daemon::DaemonError> {
1901    use superfluid_daemon::DaemonError;
1902    let tok = open_tokenizer(runtime, model).map_err(DaemonError::Generation)?;
1903    let (codec, _) = codec_for_dialect(dialect, tok).map_err(DaemonError::Generation)?;
1904    Ok(codec)
1905}
1906
1907fn refuse_statically(
1908    runtime: &superfluid_daemon::runtimes::Runtime,
1909    park_lossy: bool,
1910    kv_bits: Option<i32>,
1911    speculate: Option<&str>,
1912) -> Result<(), String> {
1913    runtime.static_capabilities().check_startup_flags(park_lossy, kv_bits, speculate)
1914}
1915
1916fn describe_worker(runtime: &superfluid_daemon::runtimes::Runtime) -> String {
1917    let version = runtime.version.as_deref().map(|v| format!("{v}, ")).unwrap_or_default();
1918    format!("{version}{}: {}", runtime.worker.source.name(), runtime.worker.bin.display())
1919}
1920
1921fn open_tokenizer(
1922    runtime: &superfluid_daemon::runtimes::Runtime,
1923    model: &std::path::Path,
1924) -> Result<Arc<dyn superfluid_engine::Tokenizer>, String> {
1925    use superfluid_engine::artifact::TokenizerSource;
1926    if let Some(why) = not_there(model) {
1927        return Err(why);
1928    }
1929    if let Some(lib) = &runtime.tokenizer {
1930        let tok = superfluid_daemon::dylib_tokenizer::DylibTokenizer::open(lib, model)?;
1931        return Ok(Arc::new(tok));
1932    }
1933    if runtime.info.tokenizer == Some(TokenizerSource::HuggingFace) {
1934        let tok = superfluid_tokenizer_hf::HfTokenizer::load(model)
1935            .map_err(|e| format!("tokenizer load failed for {}: {e}", model.display()))?;
1936        return Ok(Arc::new(tok));
1937    }
1938    superfluid_daemon::linked::tokenizer(runtime.id, model).unwrap_or_else(|| {
1939        Err(format!(
1940            "no tokenizer for {} here: the {} runtime ships none beside its worker, and this superfluid does not link it",
1941            model.display(),
1942            runtime.id
1943        ))
1944    })
1945}
1946
1947/// How deep `--model-dir` looks: a hub cache nests a model three levels
1948/// down (`<org>/<repo>/<variant>/model.base`).
1949const MODEL_DIR_DEPTH: usize = 8;
1950
1951/// The artifacts under `dir` that a runtime here reads, in name order. An
1952/// entry that one reads is taken whole (an MLX model is a directory); a
1953/// directory that none reads is looked into.
1954fn artifacts_under(
1955    dir: &std::path::Path,
1956    here: &[superfluid_daemon::runtimes::Found],
1957    depth: usize,
1958) -> std::io::Result<Vec<PathBuf>> {
1959    let mut paths: Vec<PathBuf> = std::fs::read_dir(dir)?.flatten().map(|e| e.path()).collect();
1960    paths.sort();
1961    let mut found = Vec::new();
1962    for path in paths {
1963        if !superfluid_daemon::runtimes::readers_in(here, &path).is_empty() {
1964            found.push(path);
1965        } else if depth > 0 && path.is_dir() {
1966            found.extend(artifacts_under(&path, here, depth - 1).unwrap_or_default());
1967        }
1968    }
1969    Ok(found)
1970}
1971
1972fn not_there(model: &std::path::Path) -> Option<String> {
1973    (!model.exists()).then(|| format!("{}: no such file or directory", model.display()))
1974}
1975
1976fn codec_for_dialect(
1977    dialect: &str,
1978    tok: Arc<dyn superfluid_engine::Tokenizer>,
1979) -> Result<(Box<dyn superfluid_daemon::TextCodec + Send + Sync>, &'static str), String> {
1980    Ok(match dialect {
1981        "chatml" => (
1982            Box::new(
1983                superfluid_daemon::codec::chatml_codec_from_tokenizer(tok)
1984                    .map_err(|e| format!("--dialect chatml refused: {e}"))?,
1985            ),
1986            "chatml",
1987        ),
1988        "atem" => (
1989            Box::new(
1990                superfluid_daemon::atem::AtemCodec::from_tokenizer(tok)
1991                    .map_err(|_| "--dialect atem refused: the tokenizer lacks the ATEM markers".to_string())?,
1992            ),
1993            "atem",
1994        ),
1995        "raw" => (Box::new(superfluid_daemon::BundleCodec::from_tokenizer(tok)), "raw"),
1996        "template" => (
1997            Box::new(
1998                superfluid_daemon::template_codec::TemplateCodec::from_tokenizer(tok)
1999                    .map_err(|_| "--dialect template refused: the model carries no usable chat template".to_string())?,
2000            ),
2001            "template",
2002        ),
2003        "auto" => superfluid_daemon::codec::auto_codec_from_tokenizer(tok),
2004        other => return Err(format!("unknown dialect '{other}' (chatml|atem|raw|template|auto)")),
2005    })
2006}
2007
2008fn speculation_for<'a>(
2009    directive: Option<&'a str>,
2010    caps: &superfluid_daemon::capabilities::Capabilities,
2011    model: &std::path::Path,
2012    runtime: &str,
2013) -> Option<&'a str> {
2014    let d = directive?;
2015    if d.trim() != "auto" {
2016        return Some(d);
2017    }
2018    match caps.declines_auto_speculation() {
2019        Some(why) => {
2020            eprintln!("superfluid: speculation: auto: {} on the {runtime} runtime verifies no drafts ({why}) — decoding plainly", model.display());
2021            None
2022        }
2023        // The other runtimes draft from the prompt alone: there is no
2024        // drafter artifact for `auto` to look for.
2025        None if runtime != "basert" => {
2026            eprintln!("superfluid: speculation: auto: prompt-lookup on the {runtime} runtime");
2027            Some("prompt-lookup")
2028        }
2029        None => Some(d),
2030    }
2031}
2032
2033fn unsupported_runtime(param: Option<&'static str>) -> impl Fn(String) -> superfluid_daemon::DaemonError {
2034    move |m: String| superfluid_daemon::DaemonError::Unsupported(superfluid_daemon::Refusal::new("unsupported_runtime", param, m))
2035}
2036
2037fn runtime_for(
2038    cfg: &ModelBuildCfg,
2039    model_path: &std::path::Path,
2040    requested: Option<RuntimeId>,
2041) -> Result<superfluid_daemon::runtimes::Runtime, superfluid_daemon::DaemonError> {
2042    let runtime = cfg
2043        .pick
2044        .resolve_with(model_path, requested, &cfg.catalog)
2045        .and_then(|rt| cfg.catalog.require(rt))
2046        .map_err(unsupported_runtime(Some(if requested.is_some() { "runtime" } else { "model" })))?;
2047    refuse_statically(&runtime, cfg.park_lossy, Some(cfg.kv_bits), cfg.speculate.as_deref()).map_err(unsupported_runtime(None))?;
2048    Ok(runtime)
2049}
2050
2051fn engine_on(
2052    cfg: &ModelBuildCfg,
2053    runtime: &superfluid_daemon::runtimes::Runtime,
2054    model_path: &std::path::Path,
2055) -> Result<(Box<dyn superfluid_daemon::TextCodec + Send + Sync>, EngineHost), superfluid_daemon::DaemonError> {
2056    use superfluid_daemon::DaemonError;
2057    let codec = build_codec_for(&cfg.dialect, runtime, model_path)?;
2058    let host = EngineHost::spawn_process(superfluid_daemon::WorkerSpec {
2059        bin: runtime.worker.bin.clone(),
2060        args: runtime.serve_args(model_path, cfg.ctx.max(512), cfg.max_batch, cfg.kv_bits),
2061        stderr: cfg.worker_stderr.clone(),
2062    })
2063    .map_err(|_| DaemonError::Generation("engine worker spawn failed".to_string()))?;
2064    host.capabilities()
2065        .read()
2066        .expect("capability record")
2067        .check_startup_flags(cfg.park_lossy, Some(cfg.kv_bits), cfg.speculate.as_deref())
2068        .map_err(unsupported_runtime(None))?;
2069    Ok((codec, host))
2070}
2071
2072fn reload_daemon(
2073    cfg: &ModelBuildCfg,
2074    model_path: &std::path::Path,
2075    requested: Option<RuntimeId>,
2076    retired: &Daemon,
2077) -> Result<(), superfluid_daemon::DaemonError> {
2078    retired.reload_with(|| {
2079        let runtime = runtime_for(cfg, model_path, requested)?;
2080        let (codec, host) = engine_on(cfg, &runtime, model_path)?;
2081        Ok((host, codec))
2082    })
2083}
2084
2085fn build_loaded_daemon(
2086    cfg: &ModelBuildCfg,
2087    model_path: &std::path::Path,
2088    requested: Option<RuntimeId>,
2089) -> Result<Arc<Daemon>, superfluid_daemon::DaemonError> {
2090    use superfluid_daemon::DaemonError;
2091    let id: String = cfg
2092        .state_names
2093        .lock()
2094        .expect("state names")
2095        .get(model_path)
2096        .cloned()
2097        .unwrap_or_else(|| (cfg.model_name)(model_path, &Default::default()));
2098    let mdir = superfluid_daemon::registry::state_dir(&cfg.base_sessions.join("models"), &id)?;
2099    let runtime = runtime_for(cfg, model_path, requested)?;
2100    settle_park_dir(&mdir.join("park"), cfg.park, cfg.park_budget_gb);
2101    let store = SessionStore::open(&mdir.join("wal.log"))?;
2102    let (codec, host) = engine_on(cfg, &runtime, model_path)?;
2103    let park_dir = cfg.park.then(|| mdir.join("park"));
2104    let caps = host.capabilities().read().expect("capability record").clone();
2105    let mut speculate_ranked = match speculation_for(cfg.speculate.as_deref(), &caps, model_path, runtime.id.name()) {
2106        Some(d) => superfluid_daemon::speculate_resolve::resolve_all(d, model_path).map_err(DaemonError::Generation)?,
2107        None => Vec::new(),
2108    };
2109    let speculate_first = (!speculate_ranked.is_empty()).then(|| speculate_ranked.remove(0));
2110    let daemon = Daemon::with_options(
2111        store,
2112        host,
2113        codec,
2114        superfluid_daemon::DaemonOptions {
2115            max_lanes: cfg.max_batch as usize,
2116            park_dir,
2117            park_lossy: cfg.park_lossy,
2118            park_budget_bytes: cfg.park_budget_gb.saturating_mul(1 << 30),
2119            tool_lease_ms: cfg.tool_lease_ms,
2120            tick_decode_budget: cfg.tick_decode_budget,
2121            tick_target_ms: cfg.tick_target_ms,
2122            prefill_budget: cfg.prefill_budget,
2123            agent_starvation_ticks: cfg.starvation_ticks,
2124            class_lanes: cfg.class_lanes,
2125            pressure_high_pct: cfg.pressure_high_pct,
2126            pressure_low_pct: cfg.pressure_low_pct,
2127            pin_budget_pct: cfg.pin_budget_pct,
2128            pressure_source: if cfg.os_pressure {
2129                superfluid_daemon::pressure::PressureConfig::Os
2130            } else {
2131                superfluid_daemon::pressure::PressureConfig::None
2132            },
2133            speculate: speculate_first,
2134            speculate_auto: cfg.speculate.as_deref().map(str::trim) == Some("auto"),
2135            speculate_fallbacks: speculate_ranked,
2136            speculation: cfg.speculation.clone(),
2137            media_dir: Some(mdir.join("media")),
2138            completion_deadline_ms: cfg.completion_deadline_ms,
2139            trace_scope: superfluid_daemon::otlp::TraceScope::model(&id),
2140            completion_bucket: cfg.completion_bucket,
2141            runtime_id: Some(runtime.id.name()),
2142            ..Default::default()
2143        },
2144    )?;
2145    if let Some((name, sat)) = &cfg.fim_satellite {
2146        daemon.attach_fim_satellite(name.clone(), Arc::clone(sat))?;
2147    }
2148    Ok(Arc::new(daemon))
2149}
2150
2151fn lock_sessions_dir(sessions: &std::path::Path) -> std::fs::File {
2152    use std::os::fd::AsRawFd;
2153    let path = sessions.join("serve.lock");
2154    let file = match std::fs::OpenOptions::new().read(true).write(true).create(true).truncate(false).open(&path) {
2155        Ok(file) => file,
2156        Err(e) => {
2157            eprintln!("superfluid: could not open {}: {e}", path.display());
2158            std::process::exit(1);
2159        }
2160    };
2161    loop {
2162        // SAFETY: `file` is an open descriptor for the duration of the call.
2163        let rc = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) };
2164        if rc == 0 {
2165            return file;
2166        }
2167        let err = std::io::Error::last_os_error();
2168        match err.kind() {
2169            std::io::ErrorKind::Interrupted => continue,
2170            std::io::ErrorKind::WouldBlock => {
2171                eprintln!(
2172                    "superfluid: another server is already using the sessions directory {}. \
2173                     Stop it, or give this one its own --sessions directory.",
2174                    sessions.display()
2175                );
2176                std::process::exit(1);
2177            }
2178            _ => {
2179                eprintln!("superfluid: could not lock {}: {err}", path.display());
2180                std::process::exit(1);
2181            }
2182        }
2183    }
2184}
2185
2186fn rotate_session_wal(dir: &std::path::Path) {
2187    use superfluid_daemon::store_id::store_id_path;
2188    let wal = dir.join("wal.log");
2189    let prev = dir.join("wal.log.prev");
2190    let keep = std::fs::metadata(&wal).is_ok_and(|m| m.len() > 0);
2191    let aside = |from: &std::path::Path, to: Option<&std::path::Path>| -> bool {
2192        if !from.exists() {
2193            return false;
2194        }
2195        if to.is_some_and(|to| std::fs::rename(from, to).is_ok()) {
2196            return true;
2197        }
2198        if let Err(e) = std::fs::remove_file(from) {
2199            eprintln!(
2200                "superfluid: --sessions-cache could not set aside or remove {} ({e}); \
2201                 remove it, or run without --sessions-cache.",
2202                from.display()
2203            );
2204            std::process::exit(1);
2205        }
2206        false
2207    };
2208    if keep {
2209        let _ = std::fs::remove_file(store_id_path(&prev));
2210    }
2211    if keep && aside(&wal, Some(&prev)) {
2212        aside(&store_id_path(&wal), Some(&store_id_path(&prev)));
2213    } else {
2214        aside(&wal, None);
2215        aside(&store_id_path(&wal), None);
2216    }
2217}
2218#[cfg(test)]
2219mod tests {
2220    use super::*;
2221    use superfluid_daemon::runtimes::{Found, Info};
2222    use superfluid_engine::artifact::Format;
2223
2224    fn reads(id: &'static str, format: Format) -> Found {
2225        let info = Info { aliases: Vec::new(), formats: vec![format], tokenizer: None, named: None };
2226        Found { id: RuntimeId::new(id), worker: None, info: Some(info), usable: Err("not started".into()) }
2227    }
2228
2229    #[test]
2230    fn model_dir_looks_into_a_hub_cache_and_takes_a_directory_model_whole() {
2231        let root = std::env::temp_dir().join(format!("superfluid-model-dir-{}", std::process::id()));
2232        let _ = std::fs::remove_dir_all(&root);
2233        let file = |rel: &str| {
2234            let p = root.join(rel);
2235            std::fs::create_dir_all(p.parent().unwrap()).unwrap();
2236            std::fs::write(&p, b"BASE....").unwrap();
2237            p
2238        };
2239        let flat = file("Flat-Q4.base");
2240        let q4 = file("org/Model/default-q4/model.base");
2241        file("org/Model/default-q4/hub.json");
2242        let q8 = file("org/Model/default-q8/model.base");
2243        file("notes.txt");
2244        file("mlx-model/config.json");
2245        file("mlx-model/weights.safetensors");
2246        file("mlx-model/stray.base");
2247        let here = [
2248            reads("basert", Format::file("base", "a .base bundle", &["base"], None)),
2249            reads("mlx", Format::directory("mlx", "an MLX model", &["config.json"], &["safetensors"])),
2250        ];
2251        let found = artifacts_under(&root, &here, MODEL_DIR_DEPTH).unwrap();
2252        assert_eq!(found, vec![flat, root.join("mlx-model"), q4, q8]);
2253        assert!(artifacts_under(&root, &here, 0).unwrap().len() == 2, "depth 0 reads only the top level");
2254    }
2255}