agent: lumotia-rebrand — fix Codex cross-model review blockers
Some checks failed
check / cargo check (macos-latest) (push) Has been cancelled
check / cargo check (ubuntu-22.04) (push) Has been cancelled
check / cargo check (windows-latest) (push) Has been cancelled
check / svelte build + lint (push) Has been cancelled

Codex independent review found 11 blockers post-cascade. All addressed.

CRITICAL (data-loss / crash):

10. crates/core/src/paths.rs — migrate_legacy_data_dir_inner used
    fs::rename which fails with EXDEV when source + target are on
    different filesystems (encrypted-home, bind mounts, separate
    $XDG_DATA_HOME partition). Combined with the Phase 5 QC fix that
    made migration errors fatal, this would crash on first launch
    for any user whose data dir spans filesystems. Added
    rename_or_copy_tree() that falls back to copy_dir_recursive +
    remove_dir_all on CrossesDevices / errno 18 (EXDEV). Symlinks
    preserved verbatim. Same fallback applied to magnotia.db ->
    lumotia.db inside the dir.

11. Added 4 unit tests: copy_dir_recursive preserves nested
    structure, rename_or_copy_tree same-filesystem happy path,
    is_cross_device classifies CrossesDevices kind + raw errno 18.

Doc residuals (blockers 1-9):

1. crates/cloud-providers/Cargo.toml — "Wyrdnote pending rebrand"
   description.
2. crates/cloud-providers/src/provider.rs — module docs + test
   fixture Wyrdnote refs.
3. crates/transcription/src/orchestrator.rs — module docs + test
   fixture Wyrdnote refs.
4. docs/roadmap/2026-05-10-pkm-phase-tooling-shortlist.md — phase
   name + outputs/wyrdnote path refs.
5. docs/architecture-map/04-llm-formatting-mcp/llm-tests.md —
   MAGNOTIA_LLM_TEST_MODEL env var.
6. .../cloud-providers-stubs.md — MAGNOTIA_API_KEY_*.
7. docs/architecture-map/03-audio-transcription/tests-and-fixtures.md
   — MAGNOTIA_WHISPER_*.
8. docs/gpu-tuning/plan.md — MAGNOTIA_BENCH_RUN.
9. docs/superpowers/specs/2026-05-09-battery-gpu-aware-thread-tuning-
   design.md + corresponding plan — MAGNOTIA_POWER_STATE_OVERRIDE,
   MAGNOTIA_INFERENCE_THREADS.

cargo test --workspace: 343 pass / 0 fail (up from 339; +4 EXDEV
fallback tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 13:39:21 +01:00
parent f093d18a5e
commit 2491c7a7dd
11 changed files with 235 additions and 70 deletions

View File

@@ -48,17 +48,17 @@ All three skip silently if the env vars are unset, so `cargo test` on a fresh ma
### `whisper_rs_smoke.rs`
- Env vars: `MAGNOTIA_WHISPER_TEST_MODEL` (path to a ggml/gguf Whisper model).
- Env vars: `LUMOTIA_WHISPER_TEST_MODEL` (path to a ggml/gguf Whisper model).
- Loads the context, builds a state, calls `set_initial_prompt("Wren, CORBEL, ADHD")` (proves the API still exists), runs inference on 1 s of silence, exercises `full_n_segments` and `get_segment(i).to_str()`. Smoke test only; assertion is "did this run without panicking".
### `jfk_bench.rs`
- Env vars: `MAGNOTIA_WHISPER_TEST_MODEL`, `MAGNOTIA_WHISPER_TEST_AUDIO` (path to a 16 kHz mono 16-bit PCM WAV — assertions in the test enforce this format on the fixture, not the runtime).
- Env vars: `LUMOTIA_WHISPER_TEST_MODEL`, `LUMOTIA_WHISPER_TEST_AUDIO` (path to a 16 kHz mono 16-bit PCM WAV — assertions in the test enforce this format on the fixture, not the runtime).
- Reports cold-load time, cold-transcribe RTF, warm-transcribe RTF, peak RSS read from `/proc/{pid}/status`. Hardcodes `set_n_threads(6)` for both runs (so a sweep across thread counts is a separate test, not this one).
### `thread_sweep.rs`
- Same env vars as `jfk_bench.rs`. Adds `MAGNOTIA_POWER_STATE_OVERRIDE` (set internally per panel to `ac` or `battery`) so `inference_thread_count` returns its predicted pick for each combination of (power state × GPU offload) and the empirical RTF table can be compared against the helper's choice.
- Same env vars as `jfk_bench.rs`. Adds `LUMOTIA_POWER_STATE_OVERRIDE` (set internally per panel to `ac` or `battery`) so `inference_thread_count` returns its predicted pick for each combination of (power state × GPU offload) and the empirical RTF table can be compared against the helper's choice.
- Runs the JFK clip at `n_threads = 1, 2, 4, physical, logical` (plus `8` if logical ≥ 8), takes the min of two runs per setting, prints a four-panel table:
1. AC, CPU.
2. AC, GPU (Vulkan).
@@ -69,9 +69,9 @@ All three skip silently if the env vars are unset, so `cargo test` on a fresh ma
## Watch-outs
- **All three integration tests are quiet on missing env vars.** A green `cargo test` does NOT mean the Whisper path works; it means "either the path works or you didn't set the env vars". CI configurations must explicitly set `MAGNOTIA_WHISPER_TEST_MODEL` to exercise these.
- **All three integration tests are quiet on missing env vars.** A green `cargo test` does NOT mean the Whisper path works; it means "either the path works or you didn't set the env vars". CI configurations must explicitly set `LUMOTIA_WHISPER_TEST_MODEL` to exercise these.
- **`jfk_bench.rs` hardcodes `n_threads = 6`.** Production code uses `inference_thread_count(Workload::Whisper, gpu_offloaded)` which is power-aware. Bench numbers are not directly comparable to runtime numbers without rerunning with the helper-picked thread count.
- **`thread_sweep.rs` mutates `MAGNOTIA_POWER_STATE_OVERRIDE` via `env::set_var`.** Globally process-wide. Concurrent tests in the same `cargo test` invocation that read `MAGNOTIA_POWER_STATE_OVERRIDE` will race. The test removes the var at the end (`thread_sweep.rs:94`).
- **`thread_sweep.rs` mutates `LUMOTIA_POWER_STATE_OVERRIDE` via `env::set_var`.** Globally process-wide. Concurrent tests in the same `cargo test` invocation that read `LUMOTIA_POWER_STATE_OVERRIDE` will race. The test removes the var at the end (`thread_sweep.rs:94`).
- **`tempfile 3` is a dev-dep, not a runtime dep.** Production code never creates temp files.
- **`num_cpus = "1"` (the version, not "one CPU").** Test-only labelling; production uses the slice 5 helper.
- **In-tree HTTP fixtures use ephemeral ports (`bind 127.0.0.1:0`) and run on a tokio task.** No port-collision risk, but a paranoid sandbox that blocks raw TCP loopback would break these tests.

View File

@@ -45,7 +45,7 @@ That is the entire public surface. Anything else is an implementation detail of
`store_api_key(provider, key)` (`crates/cloud-providers/src/keystore.rs:15`):
- Acquires the global `Mutex<HashMap<String, String>>` (`api_key_store` static, `:37`).
- Inserts under the key `provider_env_key(provider)` which formats as `MAGNOTIA_API_KEY_{PROVIDER_UPPERCASED}`.
- Inserts under the key `provider_env_key(provider)` which formats as `LUMOTIA_API_KEY_{PROVIDER_UPPERCASED}`.
- Returns nothing — last-write-wins.
`retrieve_api_key(provider)` (`crates/cloud-providers/src/keystore.rs:27`):
@@ -54,7 +54,7 @@ That is the entire public surface. Anything else is an implementation detail of
- Falls back to `std::env::var(env_key)` if not present in memory.
- Returns `Option<String>`.
The fallback is the why behind the `MAGNOTIA_API_KEY_<PROVIDER>` naming convention: an operator can inject a key via the environment without going through the in-memory store, which is useful for headless / CI runs.
The fallback is the why behind the `LUMOTIA_API_KEY_<PROVIDER>` naming convention: an operator can inject a key via the environment without going through the in-memory store, which is useful for headless / CI runs.
### Documented TODO
@@ -90,7 +90,7 @@ Today:
```
caller (currently no in-tree caller)
→ store_api_key(provider, key)
→ api_key_store().lock().insert("MAGNOTIA_API_KEY_{PROVIDER}", key)
→ api_key_store().lock().insert("LUMOTIA_API_KEY_{PROVIDER}", key)
→ retrieve_api_key(provider)
→ check in-memory map
→ fall back to std::env::var
@@ -113,7 +113,7 @@ The formatting pipeline does not need to change to consume cloud-transcribed seg
- **Empty crate is intentional.** Removing it would be premature; the workspace shape and the BYOK plan are both implied by its presence. But anyone doing slice analysis ("what does this crate do?") needs to know it does almost nothing today.
- **API keys vanish on restart.** The TODO is explicit. Until `keyring` integration lands, every cloud-provider feature using these helpers will need to re-prompt the user on every startup or break for headless deployments. Acceptable for a stub; not acceptable for a shipped feature.
- **Env-var fallback is `MAGNOTIA_API_KEY_<PROVIDER>`.** Provider names are uppercased in the env-key construction. A provider name with hyphens or underscores will produce a slightly weird-looking env var; not broken, but worth knowing. `provider = "open-ai"` becomes `MAGNOTIA_API_KEY_OPEN-AI`.
- **Env-var fallback is `LUMOTIA_API_KEY_<PROVIDER>`.** Provider names are uppercased in the env-key construction. A provider name with hyphens or underscores will produce a slightly weird-looking env var; not broken, but worth knowing. `provider = "open-ai"` becomes `LUMOTIA_API_KEY_OPEN-AI`.
- **No threading concerns beyond the mutex.** `Mutex<HashMap>` is fine here because keys are written rarely and read on the path of a network call that dwarfs any contention. The previous note in the doc-comment about "undefined behaviour of mutating process environment variables from arbitrary threads" refers to a discarded design that used `std::env::set_var` — that approach is unsound under the modern Rust memory model. The current in-memory map is the safe replacement.
- **`lumotia-core` dependency is declared but not currently used in this file.** It is reserved for when the providers themselves arrive (they will likely consume `Segment` and tuning helpers).
- **Security of the in-memory map is process-lifetime only.** A core dump or a memory-inspection attack reveals the keys. The `keyring`-backed replacement will inherit OS-level protections; until then the threat model is "user trusts their own machine".

View File

@@ -9,7 +9,7 @@ last_verified: 2026/05/09
> **Where you are:** [Architecture map](../README.md) → [LLM, Formatting, MCP](README.md) → Tests
**Plain English summary.** Two integration smoke tests live under `crates/llm/tests/`. Both gate on the `MAGNOTIA_LLM_TEST_MODEL` environment variable and skip silently when it is not set. They exist to verify that real model loading and inference works end-to-end, but never run in default `cargo test` runs because model load is heavy.
**Plain English summary.** Two integration smoke tests live under `crates/llm/tests/`. Both gate on the `LUMOTIA_LLM_TEST_MODEL` environment variable and skip silently when it is not set. They exist to verify that real model loading and inference works end-to-end, but never run in default `cargo test` runs because model load is heavy.
## At a glance
@@ -44,7 +44,7 @@ Verifies the Phase 9 `extract_content_tags` surface against a real loaded model.
Path-and-line summary:
- Env-var gate at `crates/llm/tests/content_tags_smoke.rs:17-23`. Same `MAGNOTIA_LLM_TEST_MODEL` as `smoke.rs`.
- Env-var gate at `crates/llm/tests/content_tags_smoke.rs:17-23`. Same `LUMOTIA_LLM_TEST_MODEL` as `smoke.rs`.
- Loads the 2B tier (the smoke tests deliberately use the smallest tier so they run quickly even on modest hardware).
- Realistic transcript: a multi-sentence dictation about a grant application and a meeting.
- Calls `engine.extract_content_tags(transcript)` and asserts:
@@ -57,7 +57,7 @@ The character-class assertion mirrors `CONTENT_TAGS_GRAMMAR`'s `topic-char ::= [
### Run command (from both files' header comments)
```bash
MAGNOTIA_LLM_TEST_MODEL=/path/to/model.gguf cargo test -p lumotia-llm \
LUMOTIA_LLM_TEST_MODEL=/path/to/model.gguf cargo test -p lumotia-llm \
--test content_tags_smoke -- --nocapture
```
@@ -84,7 +84,7 @@ Default `cargo test -p lumotia-llm` runs the unit tests (no model load) plus ski
## Data flow (for the smoke tests)
```
env MAGNOTIA_LLM_TEST_MODEL
env LUMOTIA_LLM_TEST_MODEL
→ if unset: print message, return (no failure)
→ else: PathBuf
→ LlmEngine::new()
@@ -99,7 +99,7 @@ env MAGNOTIA_LLM_TEST_MODEL
- **Smoke tests need a 2B model file.** The 2B Q4_K_M GGUF is ~1.28 GB; downloading it once is a manual prerequisite. CI is not currently configured to fetch it.
- **Smoke tests load the 2B tier specifically.** The smoke test was written against the smallest tier so it runs in a few seconds on a modest machine. Running on the 27B tier through this path would take minutes per test and would saturate VRAM.
- **No content_tags smoke for the empty-transcript error path.** The test only exercises the happy path. The error path is covered by `extract_content_tags`'s logic and would only surface here if a regression broke the typed deserialise.
- **`MAGNOTIA_LLM_TEST_MODEL` is the only env-gate.** No second gate for "I have a Vulkan-capable GPU available". `use_gpu: true` is hard-coded; on a CPU-only machine the test will still pass but run slower and produce a llama.cpp warning.
- **`LUMOTIA_LLM_TEST_MODEL` is the only env-gate.** No second gate for "I have a Vulkan-capable GPU available". `use_gpu: true` is hard-coded; on a CPU-only machine the test will still pass but run slower and produce a llama.cpp warning.
## See also

View File

@@ -137,7 +137,7 @@ crates/bench/
│ └── librivox-sample.txt
└── src/
├── main.rs # CLI + parent process
├── runner.rs # subprocess harness (child entry gate: MAGNOTIA_BENCH_RUN=1)
├── runner.rs # subprocess harness (child entry gate: LUMOTIA_BENCH_RUN=1)
├── matrix.rs # grid-search + top-k logic
├── metrics.rs # RTF + WER + optional VRAM sampling
└── profile.rs # TOML serialise

View File

@@ -1,43 +1,43 @@
---
name: Wyrdnote PKM phase — embeddings, reranking, extraction tooling shortlist
description: Bookmarked candidates to evaluate when the Wyrdnote PKM phase lands. Embedding model server + reranker + entity extractor for semantic search across notes. Not load-bearing for the dictation app; consumed by the PKM workbench layer that scales the product post-public-beta.
name: Lumotia PKM phase — embeddings, reranking, extraction tooling shortlist
description: Bookmarked candidates to evaluate when the Lumotia PKM phase lands. Embedding model server + reranker + entity extractor for semantic search across notes. Not load-bearing for the dictation app; consumed by the PKM workbench layer that scales the product post-public-beta.
type: roadmap
tags: [wyrdnote, pkm, embeddings, reranker, tooling, shortlist, deferred]
tags: [lumotia, pkm, embeddings, reranker, tooling, shortlist, deferred]
created: 2026/05/10
status: deferred
author: Wren on behalf of Jake Sames
related:
- outputs/wyrdnote/2026-05-10-engine-architecture-spec.md (CORBEL-Main)
- outputs/lumotia/2026-05-10-engine-architecture-spec.md (CORBEL-Main)
---
# Wyrdnote PKM phase: tooling shortlist
# Lumotia PKM phase: tooling shortlist
The Wyrdnote engine architecture spec frames the product as a voice-first dictation app today, scaling to a local-first semantic PKM workbench tomorrow. The PKM scaling needs a serving stack for embeddings, reranking, and entity extraction. This note bookmarks the candidates worth evaluating when that phase lands.
The Lumotia engine architecture spec frames the product as a voice-first dictation app today, scaling to a local-first semantic PKM workbench tomorrow. The PKM scaling needs a serving stack for embeddings, reranking, and entity extraction. This note bookmarks the candidates worth evaluating when that phase lands.
Not in scope for the current roadmap (v0.1 dictation completeness, then the engine-architecture phases A through G in `outputs/wyrdnote/2026-05-10-engine-architecture-spec.md`). Pull this note off the shelf when PKM work begins.
Not in scope for the current roadmap (v0.1 dictation completeness, then the engine-architecture phases A through G in `outputs/lumotia/2026-05-10-engine-architecture-spec.md`). Pull this note off the shelf when PKM work begins.
## Why deferred
- PKM phase sits post-public-beta; pulling tooling decisions forward burns evaluation time on infrastructure that earns its keep months later.
- The candidates below evolve fast; a 2026/05/10 bake-off would not generalise to a 2026/Q4 decision.
- Wyrdnote's local-first stance means the chosen stack must run on a workstation with a single consumer GPU. Cloud-default tools are out, but BYO-cloud (user supplies endpoint) is acceptable as a configurable backend.
- Lumotia's local-first stance means the chosen stack must run on a workstation with a single consumer GPU. Cloud-default tools are out, but BYO-cloud (user supplies endpoint) is acceptable as a configurable backend.
## Primary candidate (the trigger for this note)
**SIE — Superlinked Inference Engine.** `https://github.com/superlinked/sie`. Apache-2.0. Open-source inference server bundling embeddings, reranking, and entity extraction behind three API functions (`encode`, `score`, `extract`). 85+ models hot-swappable, MTEB-verified in CI. OpenAI-compatible `/v1/embeddings` endpoint. Auto-detects CUDA / Apple Silicon / CPU. Ships a production stack (KEDA autoscaling, Grafana, Terraform for GKE/EKS, Helm chart) but the single-binary path is what matters for Wyrdnote.
**SIE — Superlinked Inference Engine.** `https://github.com/superlinked/sie`. Apache-2.0. Open-source inference server bundling embeddings, reranking, and entity extraction behind three API functions (`encode`, `score`, `extract`). 85+ models hot-swappable, MTEB-verified in CI. OpenAI-compatible `/v1/embeddings` endpoint. Auto-detects CUDA / Apple Silicon / CPU. Ships a production stack (KEDA autoscaling, Grafana, Terraform for GKE/EKS, Helm chart) but the single-binary path is what matters for Lumotia.
Status as of bookmark: 25 commits, 1.7k stars, COMPATIBILITY.md acknowledging known rough edges. Worth re-checking maturity before selection.
Telemetry on by default; disable with `SIE_TELEMETRY_DISABLED=1` or `DO_NOT_TRACK=1`. Wyrdnote's stewardship posture means this would be off in any embedded deployment.
Telemetry on by default; disable with `SIE_TELEMETRY_DISABLED=1` or `DO_NOT_TRACK=1`. Lumotia's stewardship posture means this would be off in any embedded deployment.
## Adjacent candidates to evaluate alongside SIE
When the PKM phase begins, run a head-to-head on a Wyrdnote-shaped corpus (real user note-lengths, mixed dictation + manual edits, British English vocabulary). Candidates worth pitting against SIE:
When the PKM phase begins, run a head-to-head on a Lumotia-shaped corpus (real user note-lengths, mixed dictation + manual edits, British English vocabulary). Candidates worth pitting against SIE:
- **Ollama + a chosen embedding model** (e.g. `mxbai-embed-large`, `nomic-embed-text-v2`, `bge-m3`). Already in Jake's wider plan for Smart Connections backend. Same single-binary character; no rerank or extract bundled.
- **text-embeddings-inference** (HuggingFace, official). Apache-2.0. Embeddings + rerank only, no extract. Production-mature.
- **Infinity** (`michaelfeil/infinity`). MIT. Embeddings + rerank + classification. Active community, strong perf benchmarks.
- **llama.cpp embeddings mode**. The serving model is rougher but the inference engine is what Wyrdnote already uses for transcript cleanup; consolidating onto one inference runtime has real merit.
- **llama.cpp embeddings mode**. The serving model is rougher but the inference engine is what Lumotia already uses for transcript cleanup; consolidating onto one inference runtime has real merit.
- **Voyage AI / Cohere embed + rerank** as cloud BYO-API-key fallback for users who do not want to run a local model. Commercial; user-supplied key only.
- **Sentence-transformers** as the lowest-rung baseline for the bake-off. Pure Python, well understood, runs anywhere. Sets the floor.
@@ -47,8 +47,8 @@ When this note is pulled off the shelf, score each candidate on:
1. **Self-host single-binary footprint.** Disk + RAM + VRAM at idle with the chosen embedding model loaded.
2. **Cold-start latency.** First-encode-after-launch on the user's hardware. Pairs with the warmup coordinator from Phase D of the engine spec.
3. **Throughput on Wyrdnote-shape corpus.** Tokens / second, batch-size 32, 512-token sequences.
4. **Quality on a Wyrdnote eval set.** Build a small (~200 query) eval set from real notes; measure NDCG@10 and MRR.
3. **Throughput on Lumotia-shape corpus.** Tokens / second, batch-size 32, 512-token sequences.
4. **Quality on a Lumotia eval set.** Build a small (~200 query) eval set from real notes; measure NDCG@10 and MRR.
5. **Reranker availability.** SIE bundles it; Ollama does not. The reranker matters more than the embedding model past a certain corpus size.
6. **Extract / NER capability.** Useful for auto-tagging notes, surfacing "people mentioned this week" views.
7. **OEM-licensability.** AGPL-3.0 + dual-licence OEM exception (≥£2k/yr) means we cannot wrap a GPL-only inference server. Apache-2.0 and MIT are clean. SIE is Apache-2.0; check the others.
@@ -58,7 +58,7 @@ When this note is pulled off the shelf, score each candidate on:
When ANY of:
- Wyrdnote engine architecture spec Phases A through G are merged AND public beta has shipped.
- Lumotia engine architecture spec Phases A through G are merged AND public beta has shipped.
- A user-facing PKM feature lands on the roadmap (semantic search, "related notes" pane, auto-tag, knowledge-graph view).
- The Ollama + Smart Connections route in Jake's wider stack hits a quality ceiling that SIE-class tooling could lift.

View File

@@ -263,7 +263,7 @@ git commit -m "feat(core): parse_power_state_from_dir reads sysfs power_supply A
This task adds the public probe entry point. Two override paths:
1. `with_override(state, |closure|)` — in-process, serialised via a `TEST_LOCK` mutex held for the body. Used by unit tests because cargo's parallel runner makes env-var-based tests flaky.
2. `MAGNOTIA_POWER_STATE_OVERRIDE` env var — used by `thread_sweep.rs` integration test (set externally, not from inside `cargo test`'s parallel block).
2. `LUMOTIA_POWER_STATE_OVERRIDE` env var — used by `thread_sweep.rs` integration test (set externally, not from inside `cargo test`'s parallel block).
- [ ] **Step 1: Write failing unit tests using `with_override`**
@@ -296,21 +296,21 @@ Append inside `mod tests` in `crates/core/src/power.rs`:
// env-var path tested in isolation under TEST_LOCK so it
// doesn't race with the in-process override tests.
with_override(None, || {
std::env::set_var("MAGNOTIA_POWER_STATE_OVERRIDE", "battery");
std::env::set_var("LUMOTIA_POWER_STATE_OVERRIDE", "battery");
assert_eq!(probe_power_state(), PowerState::OnBattery);
std::env::remove_var("MAGNOTIA_POWER_STATE_OVERRIDE");
std::env::remove_var("LUMOTIA_POWER_STATE_OVERRIDE");
});
}
#[test]
fn env_var_override_garbage_falls_through() {
with_override(None, || {
std::env::set_var("MAGNOTIA_POWER_STATE_OVERRIDE", "nonsense");
std::env::set_var("LUMOTIA_POWER_STATE_OVERRIDE", "nonsense");
// Garbage value falls through to the platform probe.
// We can't assert the platform result so just assert it
// doesn't panic.
let _ = probe_power_state();
std::env::remove_var("MAGNOTIA_POWER_STATE_OVERRIDE");
std::env::remove_var("LUMOTIA_POWER_STATE_OVERRIDE");
});
}
```
@@ -331,7 +331,7 @@ use std::sync::Mutex;
///
/// Resolution order (highest to lowest priority):
/// 1. In-process test override (set via `with_override` from unit tests).
/// 2. `MAGNOTIA_POWER_STATE_OVERRIDE` env var (`ac` | `battery` | `unknown`,
/// 2. `LUMOTIA_POWER_STATE_OVERRIDE` env var (`ac` | `battery` | `unknown`,
/// case-insensitive). Used by `thread_sweep.rs` integration tests.
/// 3. Linux: `parse_power_state_from_dir("/sys/class/power_supply")`.
/// 4. macOS / Windows / other: `Unknown`.
@@ -351,7 +351,7 @@ pub fn probe_power_state() -> PowerState {
}
fn env_override() -> Option<PowerState> {
let raw = std::env::var("MAGNOTIA_POWER_STATE_OVERRIDE").ok()?;
let raw = std::env::var("LUMOTIA_POWER_STATE_OVERRIDE").ok()?;
match raw.trim().to_ascii_lowercase().as_str() {
"ac" => Some(PowerState::OnAc),
"battery" => Some(PowerState::OnBattery),
@@ -562,7 +562,7 @@ pub const MIN_INFERENCE_THREADS: usize = 2;
/// 8 is a conservative ceiling that leaves <5% on the table for
/// big-iron desktops while keeping consumer 6c/12t laptops out of
/// contention territory. Users can override at runtime via
/// MAGNOTIA_INFERENCE_THREADS.
/// LUMOTIA_INFERENCE_THREADS.
pub const MAX_INFERENCE_THREADS: usize = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -583,13 +583,13 @@ const GPU_FLOOR_WHISPER: usize = 4;
/// the battery and GPU-offload heuristics.
///
/// Resolution order:
/// 1. `MAGNOTIA_INFERENCE_THREADS=N` — absolute bypass, returns N.
/// 1. `LUMOTIA_INFERENCE_THREADS=N` — absolute bypass, returns N.
/// 2. base = num_cpus::get_physical() (fallback: available_parallelism).
/// 3. on battery → base /= 2.
/// 4. gpu_offloaded → base = min(base, gpu_floor(workload)).
/// 5. clamp to [MIN_INFERENCE_THREADS, MAX_INFERENCE_THREADS].
pub fn inference_thread_count(_workload: Workload, _gpu_offloaded: bool) -> usize {
if let Ok(s) = std::env::var("MAGNOTIA_INFERENCE_THREADS") {
if let Ok(s) = std::env::var("LUMOTIA_INFERENCE_THREADS") {
if let Ok(n) = s.parse::<usize>() {
if n > 0 {
return n;
@@ -617,7 +617,7 @@ mod tests {
// should return physical-core count clamped to [2, 8].
// We can't pin physical exactly without mocking num_cpus; just
// assert the result is in range.
std::env::remove_var("MAGNOTIA_INFERENCE_THREADS");
std::env::remove_var("LUMOTIA_INFERENCE_THREADS");
let n = inference_thread_count(Workload::Llm, false);
assert!(n >= MIN_INFERENCE_THREADS && n <= MAX_INFERENCE_THREADS,
"got {n}, expected within [{MIN_INFERENCE_THREADS}, {MAX_INFERENCE_THREADS}]");
@@ -625,10 +625,10 @@ mod tests {
#[test]
fn env_var_bypasses_clamps() {
std::env::set_var("MAGNOTIA_INFERENCE_THREADS", "10");
std::env::set_var("LUMOTIA_INFERENCE_THREADS", "10");
let n = inference_thread_count(Workload::Llm, true);
assert_eq!(n, 10);
std::env::remove_var("MAGNOTIA_INFERENCE_THREADS");
std::env::remove_var("LUMOTIA_INFERENCE_THREADS");
}
#[test]
@@ -672,7 +672,7 @@ Add to `mod tests` in `crates/core/src/tuning.rs`:
```rust
#[test]
fn battery_halves_thread_count() {
std::env::remove_var("MAGNOTIA_INFERENCE_THREADS");
std::env::remove_var("LUMOTIA_INFERENCE_THREADS");
// Lock both the test override mutex and assert.
crate::power::with_override(Some(PowerState::OnBattery), || {
let on_battery = inference_thread_count(Workload::Llm, false);
@@ -709,7 +709,7 @@ Modify `inference_thread_count` in `crates/core/src/tuning.rs`. Replace the body
```rust
pub fn inference_thread_count(workload: Workload, gpu_offloaded: bool) -> usize {
if let Ok(s) = std::env::var("MAGNOTIA_INFERENCE_THREADS") {
if let Ok(s) = std::env::var("LUMOTIA_INFERENCE_THREADS") {
if let Ok(n) = s.parse::<usize>() {
if n > 0 {
return n;
@@ -760,7 +760,7 @@ Add to `mod tests` in `crates/core/src/tuning.rs`:
```rust
#[test]
fn gpu_offload_clamps_llm_to_floor() {
std::env::remove_var("MAGNOTIA_INFERENCE_THREADS");
std::env::remove_var("LUMOTIA_INFERENCE_THREADS");
crate::power::with_override(Some(PowerState::OnAc), || {
let n = inference_thread_count(Workload::Llm, true);
assert!(n <= GPU_FLOOR_LLM.max(MIN_INFERENCE_THREADS),
@@ -771,7 +771,7 @@ Add to `mod tests` in `crates/core/src/tuning.rs`:
#[test]
fn gpu_offload_clamps_whisper_to_floor() {
std::env::remove_var("MAGNOTIA_INFERENCE_THREADS");
std::env::remove_var("LUMOTIA_INFERENCE_THREADS");
crate::power::with_override(Some(PowerState::OnAc), || {
let n = inference_thread_count(Workload::Whisper, true);
// Whisper floor is 4 on machines with >=4 physical cores;
@@ -790,7 +790,7 @@ Add to `mod tests` in `crates/core/src/tuning.rs`:
#[test]
fn gpu_offload_off_does_not_clamp_below_battery_calc() {
std::env::remove_var("MAGNOTIA_INFERENCE_THREADS");
std::env::remove_var("LUMOTIA_INFERENCE_THREADS");
crate::power::with_override(Some(PowerState::OnAc), || {
let no_gpu = inference_thread_count(Workload::Llm, false);
let with_gpu = inference_thread_count(Workload::Llm, true);
@@ -811,7 +811,7 @@ Modify `inference_thread_count` in `crates/core/src/tuning.rs`:
```rust
pub fn inference_thread_count(workload: Workload, gpu_offloaded: bool) -> usize {
if let Ok(s) = std::env::var("MAGNOTIA_INFERENCE_THREADS") {
if let Ok(s) = std::env::var("LUMOTIA_INFERENCE_THREADS") {
if let Ok(n) = s.parse::<usize>() {
if n > 0 {
return n;
@@ -869,7 +869,7 @@ Add to `mod tests` in `crates/core/src/tuning.rs`:
// Smoke: helper should run without panicking when logging is
// wired. This is covered by the other tests too, but kept
// explicitly to document the behaviour.
std::env::remove_var("MAGNOTIA_INFERENCE_THREADS");
std::env::remove_var("LUMOTIA_INFERENCE_THREADS");
crate::power::with_override(Some(PowerState::OnBattery), || {
let _ = inference_thread_count(Workload::Llm, true);
let _ = inference_thread_count(Workload::Whisper, false);
@@ -903,7 +903,7 @@ Update `inference_thread_count` to log once per tuple:
```rust
pub fn inference_thread_count(workload: Workload, gpu_offloaded: bool) -> usize {
if let Ok(s) = std::env::var("MAGNOTIA_INFERENCE_THREADS") {
if let Ok(s) = std::env::var("LUMOTIA_INFERENCE_THREADS") {
if let Ok(n) = s.parse::<usize>() {
if n > 0 {
return n;
@@ -1313,10 +1313,10 @@ fn thread_count_scaling() -> std::io::Result<()> {
("AC, GPU (Vulkan if available)", "ac"),
("battery, GPU (Vulkan if available)", "battery"),
] {
std::env::set_var("MAGNOTIA_POWER_STATE_OVERRIDE", power_var);
std::env::set_var("LUMOTIA_POWER_STATE_OVERRIDE", power_var);
run_sweep_panel(label, &model_path, &audio, n_threads_values)?;
}
std::env::remove_var("MAGNOTIA_POWER_STATE_OVERRIDE");
std::env::remove_var("LUMOTIA_POWER_STATE_OVERRIDE");
Ok(())
}
```

View File

@@ -61,7 +61,7 @@ pub fn probe_power_state() -> PowerState;
**TTL cache**: `OnceLock<Mutex<(PowerState, Instant)>>` with 10s TTL. Re-probe on expiry. Battery state changes slowly; 10s is far below human-perceptible latency for thread-count adjustment.
**Test override**: `MAGNOTIA_POWER_STATE_OVERRIDE=ac|battery|unknown` short-circuits the probe. Always-on (not just `cfg(test)`) so `thread_sweep.rs` can drive both states without unplugging hardware. Documented as test-only; users overriding it in production is benign.
**Test override**: `LUMOTIA_POWER_STATE_OVERRIDE=ac|battery|unknown` short-circuits the probe. Always-on (not just `cfg(test)`) so `thread_sweep.rs` can drive both states without unplugging hardware. Documented as test-only; users overriding it in production is benign.
### `tuning.rs`
@@ -81,8 +81,8 @@ const GPU_FLOOR_LLM: usize = 2;
const GPU_FLOOR_WHISPER: usize = 4;
pub fn inference_thread_count(workload: Workload, gpu_offloaded: bool) -> usize {
// 1. MAGNOTIA_INFERENCE_THREADS env override → absolute bypass
if let Ok(s) = std::env::var("MAGNOTIA_INFERENCE_THREADS") {
// 1. LUMOTIA_INFERENCE_THREADS env override → absolute bypass
if let Ok(s) = std::env::var("LUMOTIA_INFERENCE_THREADS") {
if let Ok(n) = s.parse::<usize>() {
if n > 0 { return n; }
}
@@ -156,15 +156,15 @@ Compile-time feature check (`whisper-vulkan` is in default features) plus runtim
### Override behaviour
- `MAGNOTIA_INFERENCE_THREADS=N` (existing, unchanged) — absolute bypass. Returns N regardless of power/GPU state. For benchmarking, big-iron desktops, and users who want to opt out.
- `MAGNOTIA_POWER_STATE_OVERRIDE` — test only.
- `LUMOTIA_INFERENCE_THREADS=N` (existing, unchanged) — absolute bypass. Returns N regardless of power/GPU state. For benchmarking, big-iron desktops, and users who want to opt out.
- `LUMOTIA_POWER_STATE_OVERRIDE` — test only.
- No fine-grained `MAGNOTIA_BATTERY_CLAMP=off` or similar in v1. Add only if real users hit a case where the existing all-or-nothing override is insufficient.
## Tests
### Existing test extended
`crates/transcription/tests/thread_sweep.rs` already runs the JFK clip across n_threads values and prints an RTF table. Extend to four blocks driven by `MAGNOTIA_POWER_STATE_OVERRIDE`:
`crates/transcription/tests/thread_sweep.rs` already runs the JFK clip across n_threads values and prints an RTF table. Extend to four blocks driven by `LUMOTIA_POWER_STATE_OVERRIDE`:
```
=== n_threads scaling: AC, CPU ===
@@ -181,7 +181,7 @@ Each block uses the same `n_threads` sweep `[1, 2, 4, 6, 8, 12]` and reports xc_
- `battery_clamp_halves_on_battery` — set override to battery, assert returned value is roughly half of AC.
- `gpu_floor_lower_for_llm_than_whisper` — both with `gpu_offloaded=true`, assert Llm ≤ Whisper.
- `env_var_bypasses_all_clamps` — set `MAGNOTIA_INFERENCE_THREADS=10`, set battery, set gpu_offloaded; assert returns 10.
- `env_var_bypasses_all_clamps` — set `LUMOTIA_INFERENCE_THREADS=10`, set battery, set gpu_offloaded; assert returns 10.
- `clamp_to_min_when_low_core_count` — fall through to min on 1-2 logical cores via available_parallelism (use a mock or just verify clamp inside the function).
- `clamp_to_max_for_huge_machines` — assert MAX_INFERENCE_THREADS=8 still binds.