Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions LESSONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ the row is `open`, so several start past the 14-day limit and need a decision.
| L-011 | 2026-10-16 | The first in-room plan listed `pyannote-rs` as a fallback engine. The M0 probe found it does not compile against Kōrero's `ort` (29 errors), ignores the model's speaker-change output and drifts about 60 ms per 10 s. | Compile a dependency and read its source before naming it in a plan, even as a fallback. | advice-only | `app-dev-pm` Phase 1.5 already requires probing claims; this was a planning slip, caught by M0 before any app code. |
| L-012 | 2026-10-16 | M0's first scores were worse than calling everyone one speaker. The cause was the CAM++ embedding model packaged with `pyannote-rs`: it ran without error and gave stable output, but told AMI speakers apart no better than chance, even on headset audio. The harness was checked first (reference timing, feature scale, window type), then models were compared on clean labelled turns; WeSpeaker ResNet34-LM separated them 82–100% of the time. | A model that runs is not a model that works: score it on labelled data, and check the harness before trusting a bad number. | check-added | `diar_ami_der` in `src-tauri/src/diarise.rs` scores the shipped models on four AMI far-field meetings and fails above a 25% mean error (manual: 177 MB of audio, not in CI). Its own scorer agrees with pyannote.metrics to within 0.2 points. |
| L-013 | 2026-10-16 | Editing `src/bindings.ts` with a script that normalised line endings turned a 31-line change into a 2,003-line diff: the file is stored in git with mixed CRLF and LF lines, and the rewrite made them uniform. The same session then wrote garbled macrons into a decision row, because Windows PowerShell 5.1 read a script saved without a byte-order mark as ANSI. Both were caught before any push. | Before committing a scripted edit, compare `git diff --numstat` with and without `--ignore-cr-at-eol`, and search the added lines for mojibake (Ã, Å); write text with macrons from Python or a UTF-8-with-BOM script. | open | A `.gitattributes` rule for `src/bindings.ts` (or regenerating it in CI) and a mojibake check on added lines would make these checks. |
| L-014 | 2026-10-23 | Dictation clean-up failed whenever the local model was not already loaded and took over 30 s to load (twice on 23 Oct; one cold load measured 108 s with 3 GB of RAM free). The 30 s limit covered loading as well as answering, and giving up cancelled the request, which made Ollama abort the half-finished load, so the next try started from nothing. The first look at the evidence also went wrong: Windows showed `korero.log` as 0 bytes (a stale directory size for a file open for append), and the log rotation (keep one, 500 KB) had already deleted the record of the earlier failure. | A time limit on a request must not also cover a cost that varies tenfold with the machine's state (here, loading a model); separate the wait from the work, and never cancel work whose result is still wanted. Read a log by opening it, not by its listed size. Keep more than one log file. | check-added | D-010 (1.45.1): `cleanup_wait_tests` (giving up at the cursor does not cancel the clean-up; only Ollama may finish late; the pre-warm leaves room for a dictation), `late_clean_up_fills_only_an_empty_entry`, `dictation_and_warm_outlive_a_slow_cold_load`, and the live `live_cold_dictation_survives_the_cursor_wait`; log rotation keeps four earlier files. |
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Two things do reach the network, and it is worth naming them rather than roundin
speech models are downloaded on first use, and the app asks GitHub once at startup whether a newer
release exists. Neither carries audio, transcripts, or anything about you. No telemetry, ever.

Current version: **v1.45.0** ([release notes](https://github.com/nkeating-cmd/korero/releases/tag/v1.45.0)).
Current version: **v1.45.1** ([release notes](https://github.com/nkeating-cmd/korero/releases/tag/v1.45.1)).

---

Expand Down Expand Up @@ -51,6 +51,7 @@ Current version: **v1.45.0** ([release notes](https://github.com/nkeating-cmd/ko
- **Latch / hands-free mode** — double-tap a shortcut to lock recording on for long dictation; tap once to stop.

### Reliability
- **Clean-up survives a slow local model** (v1.45.1, Ollama). When you start a clean-up dictation and your Ollama model isn't loaded, Kōrero starts loading it straight away, so the load happens while you speak. If the clean-up still isn't ready 30 seconds after you stop, your words go in as you said them, the pill says *Pasted as spoken · clean-up continues*, and the clean-up keeps going: if it succeeds, the cleaned version appears on that dictation in History (it never replaces a correction you made in the meantime). Before, a model that took longer than 30 seconds to load lost the clean-up entirely, and giving up made Ollama throw away the half-loaded model. Cloud and custom providers keep their 30-second limit. Kōrero also keeps its last five log files instead of one.
- **Nothing lost, nothing hidden** (v1.42). If a paste fails, the text goes to the clipboard and the dictation pill says so. No microphone shows on the pill with a Fix button. A note's dictation always lands in the note it started in, even if you change page, and a failed clean-up is labelled instead of passing as done.
- **Meetings keep working when you leave the tab** (v1.41). Stop, re-transcribe, import, recover, notes and refine all run in the background, and the spinner and progress are still there when you come back. One queue now handles every read and write of the meetings store, so a page you return to can no longer save its older copy over a finished result.
- **Capture warnings while there's still time to fix them** (v1.41). If the microphone hears nothing for two minutes, or almost nothing comes through from the computer's audio output after 90 seconds, Kōrero warns you mid-meeting with a toast and a taskbar flash. Stop explains why a side came back empty.
Expand Down
1 change: 1 addition & 0 deletions docs/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ in the Kōrero claude.ai project), where some carried older labels (shown in bra
| D-007 | 2026-10-16 | Accepted | **In-room speaker separation runs in Kōrero's own code on its existing ONNX runtime** (`ort 2.0.0-rc.12`), after Stop, on the CPU, never live: pyannote segmentation-3.0 (MIT) read for speaker changes, not only speech; WeSpeaker ResNet34-LM speaker embeddings (CC BY 4.0, attribution in the third-party notices); clustering of the whole meeting on segments of 1.5 s or more, clusters under 20 s of speech folded into the nearest one, short fragments attached by voice. Plan: `KORERO_1.45_IN-ROOM-SPEAKERS-PLAN_2026-10-16.md` (v2). (Approved by Nic 16 Oct 2026: "Execute D007".) | M0 probe, 16 Oct, on four AMI test meetings recorded by one far-field room microphone (92 minutes): mean error 23.1% with a 0.25 s collar, 28.1% strict, against 22.4% strict for pyannote's own pipeline on all 16 test meetings; right number of people in 2 of 4 meetings (one too many in one, two too few in the other). About 80 s of CPU time per hour of audio on 4 threads. It builds in the same binary as `transcribe-rs` with one `ort`. | `pyannote-rs` 0.3.4: does not compile against `ort` rc.12 (29 errors); reads the model only as a speech detector; its clock drifts 60.6 ms per 10 s; its packaged CAM++ embedding model told AMI speakers apart no better than chance, even on headset audio (57.5% error with the right model, 80.5% as published). `sherpa-onnx`: a second ONNX runtime beside `ort`. Telling the clusterer the number of people: worse (31.5%) than finding it. The notes model guessing speakers: invents attributions. | Results on Nic's own room recordings differ from AMI; `knf-rs` (C++ build, `ndarray` 0.16) is replaced by Rust feature code. |
| D-008 | 2026-10-16 | Accepted | **The speaker-separation models are downloaded on first use, not bundled.** "Separate speakers" fetches pyannote segmentation-3.0 (5,983,836 B) and WeSpeaker ResNet34-LM (26,530,550 B) into the models folder (`diarise/`), writes each as `.partial`, and keeps it only if its size and sha256 match the values pinned in `diarise.rs`; a mismatch is deleted and never loaded. Sources for now: the `pyannote-rs` v0.1.0 and sherpa-onnx speaker-recognition-models GitHub releases. Filterbank features come from `knf-rs` 0.3.2 (MIT; a C++ build through CMake and bindgen, and a second `ndarray`, 0.16, internal to it). | Bundling would roughly triple the installer (14 MB to about 46 MB) for every user, including those who never record in a room. Download-on-first-use matches how Kōrero already fetches speech models and stays offline afterwards. Pinning size and hash means a changed or substituted file at either URL fails closed. | Bundle in the installer: simplest offline story, but the size cost falls on everyone. Mirror to Kōrero's own GitHub release now: more control, but publishing a release is Nic's call. Rust filterbanks instead of `knf-rs`: removes the C++ build, but needs a parity check first (plan M2 step c). | Either upstream URL changes or disappears (the pinned hash makes that a clean failure, not a silent one): mirror both files to a Kōrero release. The Rust filterbank parity check passes (drop `knf-rs`). |
| D-009 | 2026-10-17 | Accepted | **Meetings are chosen as "On a call" or "In the room" before Record** (remembered on the computer). In the room: the microphone only is recorded (no system capture, so no call-audio warnings); the live transcript is a preview with a lower energy floor (0.003, Silero still deciding what is speech); and after Stop the saved transcript is rebuilt from the speaker model's turns, one line per turn with its speaker and true start time (`diarise::meeting_room_transcript`). If that fails or finds nothing, the live lines are kept. (Nic, 17 Oct: "there should be the ability to select if it is a in-person meeting up front, so it knows there is nothing on the call".) | On four AMI room-microphone meetings, the fixed 0.010 floor passed 2-9% of labelled speech in three (median speech frame RMS 0.0024-0.0029; 57% in the fourth, a louder room), so live capture and the energy-based rebuild at Stop both lose most far-field speech. The segmentation model misses only 7-15% there (M0). Transcribing per turn also removes the estimated line ends and the mid-sentence speaker changes of D-007's labelling. | A lower fixed floor everywhere: rooms vary five-fold in level, and calls would pass more noise. An adaptive noise-floor gate: more moving parts and still energy-based; worth it later for calls. Asking the user for the number of people: made separation worse in M0. | Results on Nic's own rooms; overlapping speech (transcribed once, for whoever was already speaking); Hybrid meetings (B8). |
| D-010 | 2026-10-23 | Accepted | **A local Ollama dictation clean-up is never cancelled for running late.** The person at the cursor waits at most 30 s (`actions::CURSOR_WAIT`, as before). If the clean-up is not back by then, the words are pasted as spoken and the pill says "Pasted as spoken · clean-up continues". The clean-up carries on as its own task (up to `ollama_chat::DICTATION_LIMIT`, 300 s) and is filed on that dictation's History entry only while the entry still holds the same words and has no clean-up. Other providers (cloud, `custom`) keep their own 30 s client limit and are waited for as before, because a late result could never arrive (`can_finish_late`). History retry and Notes wait for the answer. When a clean-up dictation starts and the Ollama model is **not loaded at all**, Kōrero loads it (`prewarm_cleanup_model` → `warm_if_cold`; no transcript text sent) at a context with room for about 4,000 characters of dictation (`PREWARM_TRANSCRIPT_CHARS`, 6,144 tokens with the default prompt), so the request reuses that load. A pre-warm may run 300 s (was 120 s). Bug fix; Nic, 23 Oct: "make sure that what you fix is the best fix that's as persistent as possible". | Two clean-ups on 23 Oct on the main laptop failed at 30.1 s with the model still loading (Ollama log 13:47 and 14:17; Kōrero log: `loaded=None`, "didn't finish its answer within 30 s"). Cold loads of gemma4:12b took 14-19 s on 19-20 Oct and 108 s on 23 Oct with about 3 GB of RAM free, so no fixed wait at the cursor covers every load. Probes on 23 Oct: when the request waiting on a load disconnects, Ollama aborts the load even if another request is waiting (B), so each give-up threw the half-loaded model away; a request sent while a pre-warm is loading at the same context joins that load (C: one load, answered 17.6 s after sending). Live test, cold model: answered 35.0 s after sending, one load, no abort. Costs accepted, measured or bounded: loading the model while recording slowed transcribing a 30 s clip by 0.2-0.3 s (2.55-2.86 s alone, 2.77-3.17 s with a cold load running; two runs each, 3 GB RAM free); a cancelled recording leaves the model loaded for its 30 min keep-alive; late clean-ups are not capped and each can hold an Ollama request for up to 300 s; a dictation over about 4,000 characters still reloads the model at a bigger context. An independent review found three Major issues, fixed before merge: the late path and its pill text applied to providers that can never finish late; a pre-warm sized for the prompt alone made long dictations load the model twice; the overlap with transcription was unmeasured. | Raise the 30 s limit to cover the slowest load: a paste arriving two minutes after Stop lands in whatever window has focus by then, and 108 s may not be the worst. Keep the model loaded all day (`keep_alive` -1): holds about 7.5 GB of graphics memory whether or not anyone dictates, against D-004. Warn only: tells the person, but the clean-up is still lost and the load still thrown away. Skip the pre-warm when memory is low: the measured cost was small, and the low-memory case is where the pre-warm saves most. | Ollama stops aborting a shared load when one waiter leaves; another local runtime is added (`custom` has neither the late path nor a pre-warm); late clean-ups pile up in practice; people find the late History update confusing. |
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "korero"
version = "1.45.0"
version = "1.45.1"
description = "Kōrero — speech-to-text, NZ English"
authors = ["Kyt Services Ltd <nic@kyt.nz>", "cjpais (upstream)"]
edition = "2021"
Expand Down
Loading
Loading