package config // VoiceConfig — the client↔core TCP surface + the stt/tts worker-module // seams. // // Enabled gates wiring; Bind is the TCP address (inside the wg tunnel in // production; "127.0.0.1:9100" for the local smoke). Lang is the default // language hint passed to both stt and tts (per-call overrides later). // // Stt and Tts are the worker-module seams. nil Stt ⇒ daemon wires the // in-process stt.Stub (the "no models on disk" floor — the loop is // exercisable end-to-end with a deterministic no-model transcriber). // non-nil Stt with Socket ⇒ daemon wires stt.Remote dialing that unix // socket (cmd/mavsttd serves the other end; production swaps in a // faster-whisper handler in cmd/mavsttd, no daemon or stt-package // change). Tts mirrors for tts.Remote + cmd/mavttsd. // // Embedder configures the router's sentence embedder. When all three // paths are set, the daemon constructs an ONNX multilingual embedder // (in-process); when nil, it falls back to the floor HashEmbedder stub // (deterministic, no model files required — good for CI and smoke). // // The daemon refuses to start if Voice.Enabled but Bind is empty — the // bind is the one operational config the surface can't default (127.0.0.1 // is too relaxed for production, a wg-tunnel address is the user's); // surfacing the gap explicitly beats an idle listener the user thinks is // wired but isn't reachable. type VoiceConfig struct { Enabled bool `json:"enabled,omitempty"` Bind string `json:"bind,omitempty"` Lang string `json:"lang,omitempty"` Stt *WorkerConfig `json:"stt,omitempty"` Tts *TtsConfig `json:"tts,omitempty"` Embedder *EmbedderConfig `json:"embedder,omitempty"` // RouterThreshold — the minimum confidence score for the intent classifier // (stage 3 gate). Below this → clarify, don't guess. 0 ⇒ // DefaultRouterThreshold (0.55), tuned for the ONNX embedder; the // HashEmbedder floor scores lexically and may need a lower value. // // There is no way to ask for "permissive, never clarify" through this // field: normaliseVoice replaces anything ≤ 0 with the default, so a // written 0 is the default and a written negative is too. RouterThreshold float64 `json:"router_threshold,omitempty"` // LLMRouter — route with the resident model instead of the embedding // classifier. On by default since Vikunja #320. // // Measured on the held-out fixture (docs/evals/2026-07-31-routing.md): 63.2% of // intents right against the classifier's 50.0%, and no route errors. It // costs about 1s per turn instead of 30ms. // // It is safe to leave on. The model can refuse — it answers "unknown" when // it cannot route, and the turn drops to the classifier and its clarify // gate. Any LLM error does the same, so a turn never breaks on the model. // Slot extraction runs on LLM decisions too, so acts get their Fn and // reminders their Time. // // Set it false to go back to the classifier, e.g. on a box with no // llama-server or when 1s a turn is too slow. // // It is a pointer so that "missing from the file" and "explicitly false" // are different things: missing means on, false means off. Read it with // UseLLMRouter(), not directly. LLMRouter *bool `json:"llm_router,omitempty"` // QueryMinScore — the note-recall confidence gate. Top cosine below this // ⇒ "I don't know" instead of a guess. Tuned for the ONNX embedder (0.55); // the HashEmbedder floor scores lexically and may never clear it. 0.55 // default if unset. QueryMinScore float64 `json:"query_min_score,omitempty"` // QueryMinMargin — the second half of the recall gate: the top hit must // beat the runner-up by more than this. The absolute score above cannot do // the job on its own, because the e5 embedder puts every cosine in one // narrow high band, so a made-up question scores as high as a real one. // The margin asks whether one note is clearly the best instead. // Negative ⇒ off. 0 ⇒ the default below. QueryMinMargin float64 `json:"query_min_margin,omitempty"` // ClarifyMaxAttempts — how many clarifying questions she may ask about one // request before she gives up and says she did not understand. Default 3. ClarifyMaxAttempts int `json:"clarify_max_attempts,omitempty"` // Persona — optional prompt prefix that tunes maven's character. Prepended // to every LLM system prompt (nudge phrasing, note queries, general // knowledge). Empty string ⇒ current hardcoded persona (feminine-gendered // Russian self-reference). Example: "Be formal and answer in English only." Persona string `json:"persona,omitempty"` // OwnerName / City — optional facts about the owner, added to the shared // context block (internal/persona). Empty is fine: the block still states // who he is grammatically (a man, addressed as "ты") and the current time. // Nothing about correct behaviour may depend on these being filled in. OwnerName string `json:"owner_name,omitempty"` City string `json:"city,omitempty"` // Weather — the weather provider config. nil ⇒ the daemon wires // the stub provider (returns ErrNotConfigured — "погода не настроена"). // Set provider to "open-meteo" to use the keyless Open-Meteo API. Weather *WeatherConfig `json:"weather,omitempty"` // Tools — the enabled act allowlist. Each is a spoken verb → argv the // executor runs (args from the utterance appended). Editing this set is the // human-only "enable" act (per spec); maven can't add to it from a request. // Empty ⇒ every act is refused (nothing enabled). Tools []ToolConfig `json:"tools,omitempty"` // ToolTimeout bounds each tool invocation. Zero ⇒ executor default (30s). ToolTimeout Duration `json:"tool_timeout,omitempty"` }