@gotcos/glasses-server 6.91.1 → 6.93.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +64 -2
- package/CHANGELOG.md +154 -29
- package/README.md +90 -1
- package/docs/active-query-jobs.md +1 -1
- package/docs/test-plan-6.71.7.md +1 -1
- package/managed-runtime-contract.json +10 -1
- package/package.json +2 -1
- package/server/lib/ask-session-id.ts +2 -1
- package/server/lib/claude-circuit.ts +8 -4
- package/server/lib/claude-text-runner.ts +113 -0
- package/server/lib/dictation-fillers-fixture-cases.ts +214 -0
- package/server/lib/dictation-fillers.ts +30 -0
- package/server/lib/dictation-meaning-guard.ts +47 -0
- package/server/lib/dictation-polish-pipeline.ts +90 -12
- package/server/lib/embedding-eviction.ts +60 -2
- package/server/lib/fuzzy-correct.ts +1 -1
- package/server/lib/hallucination-filter.ts +1 -1
- package/server/lib/imported-library-rows.ts +4 -2
- package/server/lib/live-cos-ask.ts +3 -2
- package/server/lib/mac-live-voices.ts +57 -0
- package/server/lib/mac-recording-asr.ts +16 -8
- package/server/lib/mac-speaker-projection.ts +211 -0
- package/server/lib/mac-stream-diarizer.ts +185 -0
- package/server/lib/mac-voice-assignment.ts +361 -0
- package/server/lib/mac-voice-labels.ts +70 -8
- package/server/lib/mac-window-replay.ts +271 -0
- package/server/lib/mac-window-voices.ts +390 -0
- package/server/lib/meeting-actions.ts +1 -1
- package/server/lib/meeting-ask-owner.ts +4 -4
- package/server/lib/meeting-ask-transcript-sources.ts +41 -3
- package/server/lib/meeting-audio-archive.ts +4 -0
- package/server/lib/meeting-batch-transcribe.ts +7 -1
- package/server/lib/meeting-context-keys.ts +6 -3
- package/server/lib/meeting-corrections.ts +4 -4
- package/server/lib/meeting-fact-check.ts +3 -3
- package/server/lib/meeting-parse.ts +1 -1
- package/server/lib/meeting-relabel-enrolment.ts +8 -8
- package/server/lib/meeting-relabel.ts +3 -3
- package/server/lib/meeting-scribe-content.ts +1 -1
- package/server/lib/meeting-speaker-review.ts +8 -8
- package/server/lib/meeting-store.ts +19 -9
- package/server/lib/nemotron-final.ts +114 -9
- package/server/lib/nemotron-live.ts +58 -6
- package/server/lib/phone-recording.ts +114 -0
- package/server/lib/prompt-edit.ts +84 -3
- package/server/lib/prompt-tail-guard.ts +1 -1
- package/server/lib/speaker-embeddings.ts +154 -28
- package/server/lib/speaker-name.ts +1 -1
- package/server/lib/stranded-session-actions.ts +7 -2
- package/server/lib/token-audit.ts +6 -1
- package/server/lib/training-audio-provenance.ts +3 -2
- package/server/lib/transcript-cleanup-context.ts +3 -3
- package/server/lib/transcript-cleanup-model.ts +1 -1
- package/server/lib/transcript-cleanup-spans.ts +10 -10
- package/server/lib/transcript-cleanup-text.ts +4 -4
- package/server/lib/transcript-cleanup.ts +1 -1
- package/server/lib/unsaved-audio-quarantine.ts +10 -1
- package/server/lib/voice-directory.ts +1 -1
- package/server/lib/voice-evidence-guard.ts +2 -1
- package/server/lib/voice-profile-store.ts +11 -5
- package/server/lib/whisper-local.ts +1 -1
- package/server/routes/dictation-polish-settings.ts +8 -1
- package/server/routes/health.ts +8 -0
- package/server/routes/meeting.ts +189 -19
- package/server/routes/meetings.ts +9 -5
- package/server/routes/prompt-drafts.ts +3 -2
- package/server/routes/prompt-edit.ts +47 -5
- package/server/routes/transcribe-stream.ts +140 -15
- package/server/routes/voice.ts +12 -4
- package/server/scripts/dictation-parity-6920-mutation-gate.mjs +229 -0
- package/server/scripts/generate-dictation-fillers-fixture.ts +18 -0
- package/server/scripts/mac-voice-assignment-6930-mutation-gate.mjs +225 -0
- package/server/scripts/mac-window-voices-6930-mutation-gate.mjs +193 -0
package/.env.example
CHANGED
|
@@ -64,9 +64,38 @@ BIND_HOST=0.0.0.0
|
|
|
64
64
|
# pass over the whole meeting labels its voices Voice 1, Voice 2, ... (unnamed,
|
|
65
65
|
# stable for the meeting). No voice profile is read or written, and renaming a
|
|
66
66
|
# Voice N is text only. Crowded or unclear rooms stay Ext. On by default; 0 turns
|
|
67
|
-
# it off and Mac recordings stay all Ext.
|
|
67
|
+
# it off (and both live modes below) and Mac recordings stay all Ext.
|
|
68
68
|
# COS_MAC_VOICE_LABELS=0
|
|
69
69
|
|
|
70
|
+
# 6.92.1 / 6.93.0 live Mac voices, ON by default as the window replay. COS_MAC_LIVE_VOICES=off
|
|
71
|
+
# (also 0, false, no, none, disable, disabled, never) makes every live Mac chunk read Ext, exactly
|
|
72
|
+
# as 6.92.0 (the after-save pass above still numbers the voices). Any other value that is not
|
|
73
|
+
# window or anchors still means window and logs mac_live_voices_unrecognised once. One switch, three values:
|
|
74
|
+
# off the opt-out: 6.92.0.
|
|
75
|
+
# anchors opt-in, the 6.92.1 per-chunk path: a chunk reads Voice N after two clean, agreeing
|
|
76
|
+
# observations. On natural audio it rarely fires.
|
|
77
|
+
# window the default (6.93.0): each live chunk is diarized inside the last five durable
|
|
78
|
+
# chunks (30 s), so a voice keeps its number across 6 s boundaries. A carried
|
|
79
|
+
# voice is used only when the chunk diarized alone agrees (one extra short run).
|
|
80
|
+
# That catches a merge of two voices speaking in the same chunk; it does not yet
|
|
81
|
+
# catch a clean hand-over to a new voice at a chunk boundary: that speaker can
|
|
82
|
+
# read the previous number. The saved meeting keeps live numbers, so it keeps that
|
|
83
|
+
# number too; assigning such a Voice N to a profile is refused (409) unless forced.
|
|
84
|
+
# Window runs get a timeout sized from their length and fall back to the
|
|
85
|
+
# per-chunk run on a timeout; 2 of the last 3 window timeouts stop the window for that capture. Unlabeled speech stays Speaker.
|
|
86
|
+
# No names, no enrollment, no training on their own (only an explicit assignment in COS Control
|
|
87
|
+
# adds samples, and it is gated). COS_MAC_VOICE_LABELS=0 turns both off. Restart the server
|
|
88
|
+
# after changing it; GET /api/health shows capabilities.macLiveVoices.mode.
|
|
89
|
+
# COS_MAC_LIVE_VOICES=off
|
|
90
|
+
|
|
91
|
+
# 6.93.0 iPhone microphone meetings, OFF by default. 1 lets the server accept a meeting
|
|
92
|
+
# recorded from the iPhone microphone (COS Glasses 6.10.655 and COS Control 0.5.351 or
|
|
93
|
+
# newer). It also needs the HQ engine set to Parakeet Ultra with Parakeet installed;
|
|
94
|
+
# GET /api/health then reports capabilities.phoneMicrophoneMeetings.enabled true.
|
|
95
|
+
# Phone meetings are anonymous (every line is Ext) and never train a voice profile.
|
|
96
|
+
# Not iOS system audio: it cannot record Zoom, YouTube or calls playing on the phone.
|
|
97
|
+
# COS_IPHONE_MICROPHONE_MEETINGS=1
|
|
98
|
+
|
|
70
99
|
# 6.91.0 dictation polish. Prompt v4 is the default on Sonnet (Haiku and Luna keep
|
|
71
100
|
# v3): self-corrections first ("two, actually three" keeps three), restarts, grammar
|
|
72
101
|
# in your own words, no added em dashes. 3 (or COS_DICTATION_POLISH_PROMPT=v3) puts
|
|
@@ -82,11 +111,44 @@ BIND_HOST=0.0.0.0
|
|
|
82
111
|
# COS_DICTATION_REGISTERS=1 makes it literal). On by default; 0 stops it applying
|
|
83
112
|
# everywhere (the capability then says enabled false; the rules are kept).
|
|
84
113
|
# COS_DICTATION_LEARNED_STYLE=0
|
|
85
|
-
# The meaning guard runs
|
|
114
|
+
# The meaning guard runs only on Sonnet with v4 (never on Haiku, Luna, other models,
|
|
86
115
|
# prompt v3 or meeting lines): it refuses a polish that lost a negation, number,
|
|
87
116
|
# weekday or month, or name you said (or added a negation or number), and pastes the
|
|
88
117
|
# unpolished text. 0 turns it off.
|
|
89
118
|
# COS_DICTATION_MEANING_GUARD=0
|
|
119
|
+
# Daily polish budgets, one per caller (local day): Mac dictation and G2 messages
|
|
120
|
+
# (default 300; the older shared variable is its fallback), G2 meeting lines (200),
|
|
121
|
+
# prompt drafts (200).
|
|
122
|
+
# COS_DICTATION_AUTOCLEAN_MAX_PER_DAY=300
|
|
123
|
+
# COS_DICTATION_AUTOCLEAN_MAX_PER_DAY_MESSAGE=300
|
|
124
|
+
# COS_DICTATION_AUTOCLEAN_MAX_PER_DAY_MEETING=200
|
|
125
|
+
# COS_DICTATION_AUTOCLEAN_MAX_PER_DAY_PROMPT_DRAFT=200
|
|
126
|
+
# The polish prompt for every model (v1 to v4); the alias above takes 3 or v3.
|
|
127
|
+
# COS_DICTATION_POLISH_PROMPT=v3
|
|
128
|
+
# The deterministic um/uh pass before the model (English text only since 6.92.0;
|
|
129
|
+
# never meeting lines). On by default; 0 sends the text to the model as heard.
|
|
130
|
+
# COS_DICTATION_FILLER_PASS=0
|
|
131
|
+
# "forward slash pricing" written "/pricing" before the model. On by default.
|
|
132
|
+
# COS_DICTATION_SPOKEN_PATHS=0
|
|
133
|
+
# Start the polish process at key press (Claude models). On by default.
|
|
134
|
+
# COS_DICTATION_PREWARM=0
|
|
135
|
+
# Skip the model when the text has nothing for it to fix. Off by default.
|
|
136
|
+
# COS_DICTATION_POLISH_SKIP_CLEAN=1
|
|
137
|
+
# 6.92.0: the polish breakers are keyed by model and caller group (Mac dictation, G2
|
|
138
|
+
# messages and prompt drafts; meeting lines on their own). A client that stops waiting
|
|
139
|
+
# never counts as a failure; this many such aborts in a row, each after 4 s or more of
|
|
140
|
+
# model time, pause that model's polish for _MS (default 4 and 300000, 5 minutes).
|
|
141
|
+
# COS_DICTATION_SLOW_BREAKER_ABORTS=4
|
|
142
|
+
# COS_DICTATION_SLOW_BREAKER_MS=300000
|
|
143
|
+
# 6.92.0: G2's prompt edit. On by default; 0 answers 503 "Edit is off". At most
|
|
144
|
+
# _DAILY_CAP edits a local day (default 200, its own counter), then 429 "Edit limit
|
|
145
|
+
# reached"; 3 failures in a row pause it 10 minutes (503 "Edit paused"). The model,
|
|
146
|
+
# effort and timeout default to sonnet, low and 45000 ms.
|
|
147
|
+
# COS_PROMPT_EDIT=0
|
|
148
|
+
# COS_PROMPT_EDIT_DAILY_CAP=200
|
|
149
|
+
# COS_PROMPT_EDIT_MODEL=sonnet
|
|
150
|
+
# COS_PROMPT_EDIT_EFFORT=low
|
|
151
|
+
# COS_PROMPT_EDIT_TIMEOUT_MS=45000
|
|
90
152
|
|
|
91
153
|
# Build 204+ uses server-owned durable query jobs by default. The server fsyncs an
|
|
92
154
|
# accepted prompt before returning 202, then keeps provider work running if the
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,128 @@
|
|
|
1
|
+
## 6.93.0 - Live Mac speaker numbering on by default, assign a Mac voice to an existing voice profile, and iPhone microphone meetings (opt-in), plus the unpublished 6.92.1
|
|
2
|
+
|
|
3
|
+
2026-10-26. Pairs with COS Control 0.5.351. Includes everything in 6.92.1 (never published on its own): the per-chunk anonymous anchors (`COS_MAC_LIVE_VOICES=anchors`), final-pass crosswalk of live numbers, the body-parser floor, and the isolated continuous-speaker protocol modules (still unwired).
|
|
4
|
+
|
|
5
|
+
- **Switches in this release:** `COS_MAC_LIVE_VOICES` is **on by default** (Miles 2026-10-26): unset runs the window replay below. `COS_MAC_LIVE_VOICES=off` (also `0`, `false`, `no`, `none`, `disable`, `disabled`, `never`) is the opt-out and gives exactly 6.92.0 for a Mac recording: every live chunk is `Ext` and the after-save pass numbers every confident voice. `COS_MAC_LIVE_VOICES=anchors` keeps the 6.92.1 per-chunk anchors. Any other value (including an empty one) is the default, so a typo never silently turns it off; an unrecognised non-empty value logs `mac_live_voices_unrecognised` once so it is not silent either. The environment is read when the server starts, so a change needs a restart. `COS_MAC_VOICE_LABELS` keeps its 6.90.0 meaning (the after-save pass, on by default; `0` turns off everything `Voice N`, live and saved). `COS_IPHONE_MICROPHONE_MEETINGS` stays off by default.
|
|
6
|
+
- **Publish order:** ship COS Control 0.5.351 on the appcast before (or together with) this server on npm `latest`. With the live window on by default, a Mac recording now sends `Voice N` speaker labels to every Control. 0.5.349 and 0.5.350 predate the anonymous Voice chip: their live meeting strip shows each `Voice N` as a named participant with the initial V. 0.5.351 draws it as an unnamed speaker numbered N. The server cannot tell which Control is asking (Mac chunk uploads carry no client version), so it does not gate on it; an installed 0.5.349 or 0.5.350 can set `COS_MAC_LIVE_VOICES=off` until it updates. The assignment bullet below also needs 0.5.351: older Controls never send `assignVoiceProfile`, so nothing changes for them.
|
|
7
|
+
- **Known limit, now reaching everyone (R1):** a clean hand-over between two people exactly at a chunk boundary, where a new voice speaks alone in the next chunk, can give the new speaker the previous speaker's number until the overlap ages out. The chunk alone shows one clean voice, so the agreement check cannot see it. The save-time pass keeps every live number (it does not renumber the meeting), so the merge stays in the saved meeting. A `Voice N` is never a person and never trains a profile on its own. It can become a wrong name only through an explicit assignment, which the next bullet gates: a group under the 0.55 floor, a mixed group, or too little audio is refused unless the person forces it. The only way to avoid the wrong number itself is `COS_MAC_LIVE_VOICES=off`.
|
|
8
|
+
- **Assign a Mac voice to an existing voice profile (new):** `POST /api/meeting/:sessionId/relabel` accepts `assignVoiceProfile: true` for a Mac recording. `from` must be an unnamed `Voice N` and `to` must be an already enrolled profile name (exact match; a placeholder or unknown name is `404 unknown_profile`, and this path never creates a profile). **The wearer's own profile is never a target: `409 owner_profile`, not overridable.** A Mac recording banks no per-chunk embeddings, so the server extracts them now from the chunk WAVs in the 7-day meeting-audio archive: whole chunks where this voice is the only one, or this voice's turns of at least 3 s (trimmed 0.15 s at each end) where another voice also speaks, using the saved pass's turns when present. At most 40 audio units are embedded, spread across the meeting, then the dominant mutually coherent group is kept (floor 0.55) and at most 10 diverse samples are chosen. A dry run (`dryRun: true`, or no `confirm`) writes nothing and returns an `enrolment` block: the person, the most samples that would be added, `attempted`, `clusterSkipped` (units thrown out as a different voice), `groupSize` and `groupSec`, the cosine of this voice's group against the person's stored centroid with `belowFloor`, and `gates`. **Three gates, each `409` on a confirm unless the request carries `force: true`:** `below_floor` (the group is under 0.55 of the stored profile, the same floor `merge-profiles` refuses at), `mixed_voice` (units were thrown out and the kept group is under two thirds of what was embedded, so the label holds more than one voice), `too_little_audio` (one coherent unit under 6 s). COS Control offers `force` only as a separate "Add anyway" after showing the counts. A confirm that would add nothing (`audio_not_retained`, `no_usable_audio`, `profile_full`, no speaker model) is `422 nothing_to_enrol` and changes nothing, rename included. Samples are tagged `mac-assign:<sessionId>`, not `correction:`: they rank as assisted, are not protected, never vouch for identity in held-voice suggestions, are capped at 10 per profile and never above the profile's other samples (a profile with 2 other samples holds at most 2), and may only ever replace another Mac-assigned sample (a full profile with none to replace refuses them), so they cannot evict a G2 correction, a `g2-training` or a `fireflies` sample. They are also the weakest evidence a profile holds: when any other kind of sample needs room they go first, before an `auto:`, unknown or `fireflies` one. `merge-profiles` holds the merged profile to the same cap and drops surplus Mac-assigned samples oldest first. `GET /api/voice/profiles` lists them as one `mac-assign` bucket, not one key per meeting. "Not in this meeting" still retracts them by session. **It does not stall the server:** the plan yields to the event loop between units, stops at a 12 s deadline (`COS_MAC_ASSIGN_DEADLINE_MS`, 1 to 25 s; the vectors already made are kept, so asking again continues), reuses the preview's vectors on the apply, and writes the profile store once instead of once per sample. It is refused with `409 capture_active` while any meeting capture (glasses, Mac or iPhone) is live, `409 assign_in_progress` for a second request on the same meeting, and `409 meeting_changed` if the meeting's bytes or `expectedRevision` moved while the audio was read. Without the flag a Mac rename is still text only. `server/scripts/mac-voice-assignment-6930-mutation-gate.mjs` is the mutation gate for this path. Limit: the rename is durable before the samples are written, and the Voice N is gone after it, so a store that cannot be written reports `enrolled: 0` and `skipped: store_unavailable` and the rename stays; to retry, rename that person back to `Voice N` and assign again (the store's 0.95 near-duplicate gate skips what already landed).
|
|
9
|
+
- **Final polish (speaker profiles):** `POST /api/voice/merge-profiles` now refuses, with code `merge_would_empty_profile` (409, nothing changed, same in a preview), a merge that would leave the profile with no samples, for example two profiles that hold only Mac-assigned samples. The relabel preview no longer names an API field when assigning is not possible: it says the speaker can still be renamed in the transcript without adding voice samples. A correction arriving at its quota still replaces the oldest correction before any Mac-assigned sample is touched (now pinned). Test fixtures use fictional names only.
|
|
10
|
+
- **iPhone microphone meetings (new, off by default):** with `COS_IPHONE_MICROPHONE_MEETINGS=1`, the HQ engine on Parakeet Ultra and Parakeet installed, `GET /api/health` and `/api/models` report `capabilities.phoneMicrophoneMeetings` (`enabled`, `protocolVersion` 1, `sourceType` `iphone_microphone`, `speakerPolicy` `anonymous`, `asrModel` `parakeet-ultra`, `dictionary`, `reason`). COS Glasses 6.10.655 then records from the iPhone microphone through the same indexed `POST /api/transcribe-stream` upload as a glasses meeting, tagged `sourceType=iphone_microphone&speakerPolicy=anonymous`. IDs are `phone_<epoch ms>_<uuid>`. Admission writes a durable registry before audio or ASR; every accepted index gets a hash receipt, an exact retry is idempotent and conflicting bytes are 409. Live and saved transcription are strict Parakeet Ultra with the existing profile dictionary corrections: no Whisper, cloud or preview fallback, and a decode failure is a retryable `503 phone_parakeet_<reason>` that keeps the audio. Every line is `Ext`: no wearer label, no voice matching, no embeddings, no enrollment, no training. Phone meetings skip the glasses merge engine and the operations hand-off, key Ask and attached files on `phone:<captureId>`, and export as `iPhone Microphone`. Remote Stop from Control is refused with `phone_capture_stop_on_iphone`; the phone stops itself. Repeated or non-canonical query fields (`sessionId`, `chunkIndex`, `elapsed`, `startTime`) are refused with 400 before admission. Microphone only: it does not record iOS system audio, Zoom, YouTube or calls. Switch: `COS_IPHONE_MICROPHONE_MEETINGS`, default off, forwarded by the managed runtime contract. Switch it on only after COS Control 0.5.351 is the installed Control, because 0.5.350 does not understand phone captures. See `docs/iphone-microphone-protocol.md`.
|
|
11
|
+
- **Why:** live Mac chunks were diarized one 6 s file at a time, so the model forgot every voice at each boundary. On a natural 36 s capture the 6.92.1 anchors produced no live labels at all (6 of 6 chunks `Ext`).
|
|
12
|
+
- **What:** by default (`COS_MAC_LIVE_VOICES` unset or `window`, and `COS_MAC_VOICE_LABELS` not off), each Mac chunk is diarized inside a window of the durable chunks before it (at most 5 chunks, 30 s), with the same pinned CLI. Window slots are carried to capture tracks only through frames this capture already attributed: one to one, at least 1 s of exclusive overlap, at least 80% agreement, **and only if the chunk diarized alone agrees** (it places at least half of the slot's exclusive frames, at least 90% of those on one channel; one extra short run, only when a slot is carried). This catches a window model that merged two voices which BOTH speak in the current chunk: that slot is not carried and its frames are stored untracked, so the error does not feed the next window. It does **not** catch a clean hand-over at a chunk boundary (voice A through chunk k-1, a new voice C alone in chunk k, the window model putting C on A's old slot): the chunk alone shows one clean voice and agrees, so C can read A's number until the overlap ages out. That case is not yet caught; it needs evidence from more than one chunk (an embedding against the track's proof audio). This is now the default (see the known limit above). A track reads `Voice N` after clean proof in two distinct chunks with distinct audio (2 s alone, purity 0.9). Numbers only grow. A track no remembered frame refers to is evicted and its id reused, so a long meeting cannot spend the 12 ids. Corrupt state, or Voice labels with no window state, withhold labels instead of restarting at Voice 1, and say so once in the log.
|
|
13
|
+
- **Timeouts:** the window run's timeout is sized from its length (1.5 s per 6 s of audio: 7.5 s for 30 s), not the per-chunk 1.5 s. Two of the last three window timeouts stop the window for the rest of that capture (per-chunk runs only, logged once, counted in `capabilities.macLiveVoices.window.disabledCaptures`), so a slow Mac pays the long wait twice, not on every chunk (a rolling count, so alternating timeouts also trip it). A window timeout never counts toward the breaker shared with G2 and every other capture; it, and a refused window geometry (reported as its own reason, not `bad_output`), fall back to the plain per-chunk run for that chunk.
|
|
14
|
+
- **Limit:** a voice silent for longer than the window cannot be carried. It reads `Ext` live, or a new number after its own two proofs; the carry rules do not hand it another voice's number (but see the clean hand-over limit above). Settled chunks are not relabeled live. The save-time pass keeps every live number and, for a confident final channel the live path never proved and no live turn contradicts, now numbers it above the live numbers (it used to leave it `Ext`, which could leave a saved meeting with fewer numbered voices than 6.92.0). Words that already carry a live number are never changed by it. With no live `Voice N` at all (`COS_MAC_LIVE_VOICES=off`) the pass is exactly the 6.92.0 pass.
|
|
15
|
+
- **Unchanged:** strict named identity (0.88 floor), G2 and phone paths. A Mac rename is text only unless the request carries `assignVoiceProfile: true` (above); nothing else enrolls or trains from a Mac recording. No audio, embedding or probability matrix is stored: only per-frame track ids for the last 4 chunks in the private active-session file.
|
|
16
|
+
- **Cost (measured on the pinned CLI, warm, before the agreement run was added):** about 0.26 s wall (max 0.30 s) and 0.1 s CPU per 6 s chunk for a 30 s window, about 46 MB peak RSS for the CLI process. The agreement run is one ordinary per-chunk run, sequential, only when a slot is carried: measured on the same fixtures, median 0.15 s and max 0.16 s wall over 19 runs (the window runs: median 0.26 s, max 0.29 s over 22), so about 0.4 s of CLI time per chunk with a carried voice, and it holds a second live-lane slot while it runs. If that run is refused as busy, carried voices are withheld for that chunk (they show Speaker).
|
|
17
|
+
- **Switch:** `COS_MAC_LIVE_VOICES`, default `window`; `off` is the opt-out, `anchors` is the 6.92.1 per-chunk path (this path replaces the anchors). Forwarded by the managed runtime contract. `GET /api/health` reports `capabilities.macLiveVoices.mode` and, for `window`, counts only. One counts-only log line per window run.
|
|
18
|
+
- **Qualification (private fixtures, counts only):** natural 6 chunk capture: 2 voices, 75% of speech labeled live, 100% agreement with one continuous pass on labeled frames. Synthetic A B A B (known voices): 0% wrong-voice frames, 2 of 2 returns kept, 2 of 3 true speaker switches detected live, 24.9% of speech unlabeled live (first chunk of each voice). Synthetic return after 31.5 s of silence: 0% wrong-voice, the returning voice stays unlabeled (48.2% of that fixture's speech unlabeled live). These were measured before the agreement run was added; the synthetic voices are far apart (cross-voice cosine 0.10 to 0.17), so they are a floor. No independently annotated natural meeting exists yet, so natural accuracy is not established. `server/scripts/mac-window-voices-6930-mutation-gate.mjs` kills every listed mutant from a green baseline.
|
|
19
|
+
|
|
20
|
+
## 6.92.1 - Conservative anonymous voice continuity for live Mac meetings
|
|
21
|
+
|
|
22
|
+
- Successful live Nemotron tracks can carry capture-local Voice N labels after two independent clean audio observations agree. (In 6.93.0 these are `COS_MAC_LIVE_VOICES=anchors`; the default is the window replay; 6.92.1 was never published.) Uncertain, overlapping or short evidence remains Ext. The existing named-identity threshold, ASR choices and feature settings are unchanged.
|
|
23
|
+
- Private continuity anchors survive capture recovery, reject duplicate audio as independent support, and never enter the global profile store or public DTOs.
|
|
24
|
+
- Finalization preserves established live voice numbers and withholds channel mappings that contradict mixed live turns. Mac meeting-only renames still do not train profiles.
|
|
25
|
+
- Synthetic real-model qualification distinguishes two known voices at unchanged thresholds; six short natural archived chunks remained unidentified. This is conservative continuity, not a general speaker-accuracy guarantee.
|
|
26
|
+
- The published runtime requires body-parser 2.4.0 or newer, including fresh npm consumer installs, to fix compressed-request trailing-byte denial of service (GHSA-27r3-jxmh-p828). Express remains on its compatible 5.x API.
|
|
27
|
+
- Companion and old Control DTOs remain compatible. Control 0.5.350 displays distinct anonymous participant badges. The iPhone microphone capability (6.93.0) is advertised only with `COS_IPHONE_MICROPHONE_MEETINGS=1`.
|
|
28
|
+
|
|
29
|
+
## 6.92.0 - Dictation parity, Wave A (server): breakers that tell an abort from a failure, bounded prompt edit, a filler pass that leaves other languages and paths alone
|
|
30
|
+
|
|
31
|
+
2026-10-25, plan r3 ("dictation parity", Wave A; pairs with COS Control 0.5.345). Zero `claude -p` calls: every
|
|
32
|
+
change is proven by execution tests, a fake CLI and mutation gates.
|
|
33
|
+
|
|
34
|
+
**What changes by default:**
|
|
35
|
+
- **Polish breakers** (`server/lib/dictation-polish-pipeline.ts`) are keyed by (model, bucket group): `dictation`
|
|
36
|
+
(Mac dictation, G2 messages, prompt drafts) and `meeting`. A G2 meeting that fails no longer turns off message polish.
|
|
37
|
+
- A client abort (Control stopped waiting, the request closed) is **neutral** to the 2-failure breaker: it neither
|
|
38
|
+
counts nor resets. Before, two slow Sonnet polishes past Control's wait could turn Sonnet polish off for 30 minutes.
|
|
39
|
+
- A new **slow breaker** per key: `COS_DICTATION_SLOW_BREAKER_ABORTS` (default 4) aborts in a row, each after at least
|
|
40
|
+
4 s of model time (stamped when the abort happens, not after the provider process is killed), with no success between, skip polish as `breaker_open` for
|
|
41
|
+
`COS_DICTATION_SLOW_BREAKER_MS` (default 300000, 5 minutes). An abort sooner than 4 s (a disconnect right after a
|
|
42
|
+
sidecar restart) does not count. Any success closes both breakers.
|
|
43
|
+
- Each abort writes a token-audit row with `polish_outcome: "aborted"`, codes only. `aborted` is audit-only: a
|
|
44
|
+
finalize answer's `polishOutcome` keeps its three values.
|
|
45
|
+
- `POST /api/dictation/polish/prewarm` asks the same keyed breaker as the finalize for its surface.
|
|
46
|
+
- **The filler pass** (`server/lib/dictation-fillers.ts`, on since 6.79.1) had removed German and Portuguese words
|
|
47
|
+
("um drei Uhr" became "drei Uhr", "Er kommt morgen." became "Kommt morgen.") and damaged paths ("src/um.txt" became
|
|
48
|
+
"src/.txt"). Now:
|
|
49
|
+
- **Token exemptions:** a filler is kept when the character right before it is one of `/ . @ _ : # $`, or the
|
|
50
|
+
character right after it is one of `/ @ _ # $`, or a `.` or `:` followed by a letter or digit. A sentence-final
|
|
51
|
+
"um." or "um:" still goes.
|
|
52
|
+
- **Language gate** (`stripFillersIfEnglish`, in the caller; `stripDisfluencies` stays the pure token rules) uses its
|
|
53
|
+
own function, `fillerTextLooksEnglish`. The meaning guard keeps 6.91.1's `looksEnglish` byte for byte, so "merci
|
|
54
|
+
Dana" and "gracias Marco" polished without the name are still refused. The filler gate: not English when
|
|
55
|
+
`looksEnglish` says so; under 12 words, English words (the function words, plus a short-reply list: yes, no, yeah,
|
|
56
|
+
yep, ok, okay, sure, thanks, thank, please, sorry, hi, hey, hello, good, great, fine, cool, done, right, maybe,
|
|
57
|
+
sounds, call, send, tell, ask, am, pm, weekdays, months but "may") must outnumber any foreign function word
|
|
58
|
+
(German, Portuguese, Spanish, French, Dutch; never an all-caps acronym), and an "um" or "er" said inline needs at
|
|
59
|
+
least one English word beside it. So "um yes", "er no", "um thanks Dana", "um Tuesday" and "um 5 pm works" lose
|
|
60
|
+
their filler as in 6.91.1, while "Comprei um carro", "Er kommt morgen." and "Wir treffen uns um 5 Uhr" keep it.
|
|
61
|
+
Known cost: an inline "um" beside only a name ("um Dana") is kept.
|
|
62
|
+
- **Canonical fixture** `server/lib/__fixtures__/dictation-fillers.json`, generated by the server's own functions
|
|
63
|
+
(`server/scripts/generate-dictation-fillers-fixture.ts`) from hand-written expectations: 140 rows, 108 scope `both`
|
|
64
|
+
(the token rules, which COS Control's port must match byte for byte; its 5 short-reply rows hold through both
|
|
65
|
+
repos' language gates too) and 32 scope `server` (the server's language gate). Its sha256,
|
|
66
|
+
`a531d0a99b4661dedcde5d81dbaa304eae73f916eb30e2efd7589e1e0a71de55`, is pinned in
|
|
67
|
+
`dictation-fillers-fixture.test.ts` and in Control's copy.
|
|
68
|
+
- **G2 prompt edit** (`POST /api/prompt-edit`) now answers how it stops. Its limits and settings are unchanged (24,000
|
|
69
|
+
character drafts, 2,000 character instructions, `COS_PROMPT_EDIT_MODEL`, `_EFFORT`, `_TIMEOUT_MS`: sonnet, low,
|
|
70
|
+
45000), and so is its environment, so a Claude CLI signed in with an API key keeps working.
|
|
71
|
+
- Runs through a new isolated runner, `server/lib/claude-text-runner.ts`: `--tools ''`, no MCP, hooks off in exactly
|
|
72
|
+
one `--settings`, `--permission-mode dontAsk`, no session file, a scratch working directory, a verified absolute
|
|
73
|
+
binary. `spawnClaudeText` stays for meeting summaries (prompt-edit.ts stays TOOL_CAPABLE in the key-lanes census;
|
|
74
|
+
the runner is TOOL_LESS).
|
|
75
|
+
- Switch `COS_PROMPT_EDIT` (read with `readSwitch`, on by default): off answers `503 Edit is off`.
|
|
76
|
+
- Cap `COS_PROMPT_EDIT_DAILY_CAP` (default 200 a local day) in its own counter file (`.prompt_edit_count.json`),
|
|
77
|
+
never the dictation polish counter: `429 Edit limit reached`.
|
|
78
|
+
- Breaker: 3 CLI failures in a row open it for 10 minutes (`503 Edit paused`); a client abort is neutral.
|
|
79
|
+
- A failure answers `500 Edit failed`; the CLI's stdout and stderr tails are gone from the answer and the log.
|
|
80
|
+
- Every refusal is `text/plain` and 24 characters or fewer, so G2 6.10.654 shows `Edit failed.` then, for example,
|
|
81
|
+
`API 429: Edit limit reached` (40 characters at most). A contract test runs 654's own `ApiHTTPError` and
|
|
82
|
+
`applyVoicePromptEdit`, copied verbatim into `server/lib/__fixtures__/g2-6.10.654-prompt-edit.json`, against the
|
|
83
|
+
real refusals: the footer fits and no undo entry is pushed.
|
|
84
|
+
- One audit row per edit that ran (`caller: prompt_edit`, `prompt_edit_outcome` ok, failed or aborted; sizes only).
|
|
85
|
+
- **`GET` and `PUT /api/dictation/polish/options`** add `meaningGuard` (false only with `COS_DICTATION_MEANING_GUARD=0`)
|
|
86
|
+
and `stylePairs` (the model and prompt pairs the guard, Your style and the register run on, as `/rules` computes
|
|
87
|
+
them), so Control 0.5.345's picker line tells the truth. Older servers omit both.
|
|
88
|
+
|
|
89
|
+
**Corrections and housekeeping:**
|
|
90
|
+
- The meaning guard runs **only on Sonnet with v4** (a Haiku forced to v4 gets none). The 6.91.0 wording "where v4
|
|
91
|
+
runs" is corrected in `.env.example` and the README.
|
|
92
|
+
- Eval harness limits: the custom-polish default `--max-calls` is 205 (its default run is 201 calls; it was 200, so a
|
|
93
|
+
default run refused itself) and the format eval's is 50 (it was 40, under the canary's 45). Case counts are pinned
|
|
94
|
+
in `server/scripts/dictation-eval-harness.test.ts`. Nothing was run.
|
|
95
|
+
- Managed runtime: `COS_DICTATION_SLOW_BREAKER_ABORTS`, `_MS`, `COS_PROMPT_EDIT`, `_DAILY_CAP`, `_MODEL`, `_EFFORT` and
|
|
96
|
+
`_TIMEOUT_MS` join `managed-runtime-contract.json` `optionalEnvironment`. `dictation-polish-budget.test.ts` now
|
|
97
|
+
checks all 20 dictation and prompt-edit keys there and in `.env.example`, including the five it missed
|
|
98
|
+
(`SPOKEN_PATHS`, `PROMPT_VERSION`, `LEARNED_STYLE`, `MEANING_GUARD`, `REGISTERS`).
|
|
99
|
+
- Test isolation: the polish goldens (`dictation-polish-prompt-v4.test.ts`), `prompt-drafts`, `dictation-polish-settings`,
|
|
100
|
+
the prompt-edit tests, `dictation-polish-breakers` and `transcript-cleanup` each get a private `COS_PROFILE_PATH` as
|
|
101
|
+
well as a private `COS_DATA_DIR`, restored after the file. In a 4-worker run `transcript-cleanup.test.ts` wrote the
|
|
102
|
+
vocabulary "Jewel360" into the run's shared profile while the goldens read it.
|
|
103
|
+
- The key-lanes off-identity recordings (`server/lib/__fixtures__/key-lanes/off-*.json`, recorded by hand, not run by
|
|
104
|
+
the suite) still show prompt edit's 6.91.1 argv; a re-recording will show the runner's.
|
|
105
|
+
|
|
106
|
+
**Gates:** mutation gates, each from a green baseline:
|
|
107
|
+
- new `server/scripts/dictation-parity-6920-mutation-gate.mjs`: **64 of 64**, each killed by its named test (baseline
|
|
108
|
+
360 tests in 13 files): abort-neutral, abort then error counts one, the slow threshold, open time and env read, the
|
|
109
|
+
4 s model-time rule and its boundary, the slow breaker consulted and reset, the aborted audit row, the bucket key
|
|
110
|
+
(meeting apart, prompt drafts with messages), prewarm keyed, prompt edit's switch and default, cap, cap boundary and
|
|
111
|
+
default, call counted, own counter, breaker, threshold, failure and success, abort neutral, text/plain refusal, no
|
|
112
|
+
CLI tails, the 24-character refusal, the runner (no tools, scratch directory, environment kept), the language gate
|
|
113
|
+
(wired in the filler step and the pipeline, its own function with the long-text ratio and the short-reply words,
|
|
114
|
+
foreign words, all-caps, inline um/er, a pause makes a hesitation, and the meaning guard keeping 6.91.1's check), each
|
|
115
|
+
token exemption character before and after, the dot and colon needing a letter or digit, both exemption checks
|
|
116
|
+
wired, the options `meaningGuard` and `stylePairs`, and both harness defaults;
|
|
117
|
+
- dictation polish 92 of 92 (`prewarm-breaker` retargeted to the keyed breaker), format 63 of 63, custom polish 46 of
|
|
118
|
+
46, voice style 183 of 183 (its anchors unchanged from 6.91.1);
|
|
119
|
+
- dictation STT 21 of 22: its two anchors in `stt-settings.ts` already matched twice at 6.91.1, so the gate stopped
|
|
120
|
+
before its first mutant; a leading newline now pins each. The survivor, `hq-parakeet-warms`
|
|
121
|
+
(`server/lib/stt-runtime.ts`), is in code this release does not touch and was hidden by that stop;
|
|
122
|
+
- full suite, 4 workers, scratch HOME, COS_DATA_DIR and COS_PROFILE_PATH: 9,180 passed, 14 skipped, 0 failed;
|
|
123
|
+
- `check-gate-anchors.mjs --base 96be0b8`: every anchor in a changed file matches once (the one pre-existing
|
|
124
|
+
`manifest-lock-version-drift` miss is unchanged); `tsc --noEmit` clean.
|
|
125
|
+
|
|
1
126
|
## 6.91.1 - The meaning guard reads clock times, so a time format you asked for is not refused
|
|
2
127
|
|
|
3
128
|
2026-10-25: canary C5 on the pair that ships (Sonnet, v4, instructions and the learned style) found the 6.91.0 guard
|
|
@@ -653,7 +778,7 @@ dictation is honored by the AI polish, and a mapping like that works from then o
|
|
|
653
778
|
never a line break or a spaced dash,
|
|
654
779
|
case-insensitive, with punctuation around it ("pricing,"). A key goes ahead of any earlier key whose words it
|
|
655
780
|
contains, so "forward slash build and price" is not cut short by "forward slash build"; chains a profile relies on
|
|
656
|
-
("Myles", then "Miles
|
|
781
|
+
("Myles", then "Miles Ukmya") keep their order. A written form is inserted literally ("$&" is not a pattern).
|
|
657
782
|
Same pass on every path that already ran it: dictation finalize, prompt drafts, G2 live lines, meeting batch
|
|
658
783
|
transcripts, previews. It runs before the AI polish.
|
|
659
784
|
- **Spoken paths, deterministic.** For messages and prompt drafts (never meeting lines), "forward slash" plus one word
|
|
@@ -1056,10 +1181,10 @@ Pairs with COS Glasses companion 6.10.645. Includes 6.77.0. No COS Control chang
|
|
|
1056
1181
|
|
|
1057
1182
|
Includes 6.76.2. Off until Miles turns it on. No COS Control or companion change is required; Control can add the toggle, the screen feed and the Dictionary suggestions later against the contracts below.
|
|
1058
1183
|
|
|
1059
|
-
- Miles, 2026-10-17: after a transcription (Parakeet Ultra or Whisper), run Haiku or Sonnet over the passages where the engine was unsure, with screen and textual awareness, so word hallucinations and misheard names ("
|
|
1184
|
+
- Miles, 2026-10-17: after a transcription (Parakeet Ultra or Whisper), run Haiku or Sonnet over the passages where the engine was unsure, with screen and textual awareness, so word hallucinations and misheard names ("Faith Ukmya", "Yukmya", "eucmya", "U cmya" for Faith Ukamya) are resolved.
|
|
1060
1185
|
- Where it runs. Once per saved meeting, in the durable finalization job after the HQ batch text and the Nemotron relabel are written, and before the meeting is handed to COS operations or summarised, so both read the cleaned text. Orphan recovery runs it too. Never during live transcription: it is a separate module no transcription file imports (pinned by a test), and a meeting whose session is still live is skipped. It never fails a save.
|
|
1061
|
-
- Selection, no model (`transcript-cleanup-spans.ts`): low confidence (Parakeet below 0.50, Whisper below 0.40, from the batch words in the sidecar, aligned to the saved transcript); a word near a known name when the engine was unsure of it (below 0.9: one edit past fuzzy-correct's limit for a capitalised word, or the same consonant skeleton and onset, so
|
|
1062
|
-
- Deterministic fixes first, free (fuzzy-correct's own rule, exported as `fuzzyReplacementFor`): only capitalised words that are not already a known spelling; this meeting's own screen and file terms as a temporary per-meeting vocabulary at any confidence; the curated dictionary (vocabulary and corrections) and the owner only where the engine was unsure; never an inflection ("Setting" is never a "Settings" button). Measured read-only on 68 real meetings (38.5 hours): rewriting lowercase words, or using every enrolled name as a target, swapped everyday words and real names for other names (giant to grant, Kelsey to Kelley, Slack to Black), so neither is done; with these rules the same meetings got 27 fixes, all real (
|
|
1186
|
+
- Selection, no model (`transcript-cleanup-spans.ts`): low confidence (Parakeet below 0.50, Whisper below 0.40, from the batch words in the sidecar, aligned to the saved transcript); a word near a known name when the engine was unsure of it (below 0.9: one edit past fuzzy-correct's limit for a capitalised word, or the same consonant skeleton and onset, so eucmya reads like Ukamya); two or three short pieces that join into a name ("U cmya"), never function words; the leading-y rule at any confidence ("Yukmya"); an out-of-vocabulary capital mid-sentence beside a known name; a phrase repeated three times or more. Spans stay inside one speaker turn and one clause, hold at most 6 words (12 for a repeat), carry 12 words of context each side, and are cut to 40 per meeting with names first.
|
|
1187
|
+
- Deterministic fixes first, free (fuzzy-correct's own rule, exported as `fuzzyReplacementFor`): only capitalised words that are not already a known spelling; this meeting's own screen and file terms as a temporary per-meeting vocabulary at any confidence; the curated dictionary (vocabulary and corrections) and the owner only where the engine was unsure; never an inflection ("Setting" is never a "Settings" button). Measured read-only on 68 real meetings (38.5 hours): rewriting lowercase words, or using every enrolled name as a target, swapped everyday words and real names for other names (giant to grant, Kelsey to Kelley, Slack to Black), so neither is done; with these rules the same meetings got 27 fixes, all real (Mohun to Mohan, MarketPOS to MarktPOS).
|
|
1063
1188
|
- Context pack: meeting title, date and attendees; COS people (enrolled speaker names); the owner card (6.76.0); the profile vocabulary and heard-to-written corrections (the COS dictionary dictation uses); text of files dropped on the meeting (the Meeting Ask file list); up to 6 local notes hits for the names the spans are near (memories and earlier meetings by keyword, and the memory bridge listing when memory search finds nothing, as the Live COS Ask does; never this meeting; nothing leaves the Mac; 3 s budget); and up to 3 screen lines per span, nearest in time (45 s) or naming the span's name. Every text is bounded and passed through the secret redactor.
|
|
1064
1189
|
- The call: one per meeting, batched, through the shared Claude CLI runner (`--tools ""`, no MCP servers, no hooks, empty scratch workspace), pinned `claude-haiku-5-5` by default or `claude-sonnet-5-5`, low effort, 1,500 output tokens, 75 s timeout, 24,000-character prompt, token audit source `transcript-cleanup`. Never an SDK. The reply is strict JSON by span id (`keep` or `replace`); a fenced reply or one followed by prose still parses.
|
|
1065
1190
|
- Guards on every edit (`transcript-cleanup-model.ts`): unknown or duplicate span id, bad action, no-op, a bracket or line break (a forged speaker label), more than one word over the span or twice its length, a neighbouring context word (text outside the span), a changed number or unit, an added or dropped negation, and any word that is neither a misheard span word (close spelling or same sound) nor a term this meeting's context knows. A repeat may only shrink to a run of its own words. Edits are applied by the span's offsets, never by the model's text.
|
|
@@ -1075,7 +1200,7 @@ Includes 6.76.2. Off until Miles turns it on. No COS Control or companion change
|
|
|
1075
1200
|
- Capability: `/api/transcribe/capabilities` adds `transcriptCleanup: {protocol: 1, suggestions: 1, screenContext: 1}`; `/api/health` adds `capabilities.transcriptCleanup: {protocol: 1, enabled}` (codes only, it is public).
|
|
1076
1201
|
- Older path, for the record: the app's former server had a whole-transcript `claude -p sonnet` rewrite (`correctTranscript`, 40 KB cap, breaker after 2 failures for 30 minutes, `correction_skipped`), deleted with that fork on 2026-08-11. This server had no transcript LLM pass until now, and the COS repo's `sync_meetings.py` gates only title, summary and action extraction (`COS_MEETING_LLM`); it never rewrites transcript text. This pass is span-level, guarded and undoable instead.
|
|
1077
1202
|
- Evidence and limits: the 2026-10-13 silver eval of Haiku on doubtful Parakeet spans (no screen, owner or file context) was a WER tie with Parakeet alone. Real-model quality with this context is unmeasured. Read-only selection on 68 real meetings (38.5 h, 379,273 words, 100% aligned, no model call): 149 doubtful spans an hour found (low confidence 93%), 48.9 an hour sent, a median prompt of about 1,840 input tokens, 33 meetings at the 40-span cap, at most 333 ms to select.
|
|
1078
|
-
- Tests: `transcript-cleanup.test.ts` (33: end to end with a fake Claude provider, including the
|
|
1203
|
+
- Tests: `transcript-cleanup.test.ts` (33: end to end with a fake Claude provider, including the Ukmya, Yukmya, eucmya and U cmya case with the owner card and a screen line, every stop, undo, the notes lookup, the per-meeting vocabulary and suggestions), `transcript-cleanup-spans.test.ts` (26: selection, fixes, edit guards, prompt), `transcript-cleanup-provider.test.ts` (1: the real runner wiring), `routes/transcript-cleanup.test.ts` (13: auth on every route, screen bounds, settings, suggestions through the real glossary writer, undo, the sweep), `meeting.test.ts` (2 new: runs once after the relabel and before the job completes; a throwing cleanup never fails the save, the default writes nothing), `health.test.ts` (1 new) and `transcribe.test.ts` (capability). `server/scripts/transcript-cleanup-mutation-gate.mjs` (`--check` validates every target): 107 mutants over the switch, model pin, every stop, selection, the fixes, every edit guard, the ledger and undo, the screen store and route, the notes lookup, suggestions and the save wiring, each killed by a named test against a green baseline (115 tests in 7 files). Full suite in isolated TMPDIR, HOME and data dir with COS_SCRIPTS_DIR unset: 7316 passed, 13 skipped, 0 failed; typecheck clean.
|
|
1079
1204
|
|
|
1080
1205
|
## 6.76.2 - Live cues v2: a switch COS Control can flip, the owner's own notes as memory, and SubQ or Jev for the gate
|
|
1081
1206
|
|
|
@@ -1090,7 +1215,7 @@ Pairs with COS Glasses companion 6.10.644 and COS Control 0.5.322. Includes 6.76
|
|
|
1090
1215
|
- When SubQ serves cues, the live transcript window (up to 2,200 characters per call) and the candidate notes go to SubQ, which retains data by default.
|
|
1091
1216
|
- Capability additions (optional fields; `shared/live-cues-v2.ts` changed additively, new sha256 174728e7...): `memory` (`cue_memory`, `notes` or `none`) and `decisionProvider` (`jev` or `subq`). New reasons: `decision_not_configured` (no key for the chosen provider; `jev_not_configured` is kept for Jev chosen while only a SubQ key exists), `decision_cap_reached`, `decision_breaker_open` (SubQ) and `decision_invalid_provider`. The same decision codes stop a running meeting's cues quietly with that reason; other SubQ errors count toward the meeting breaker as Jev errors do.
|
|
1092
1217
|
- Live COS Ask finds the owner's notes too. When `searchMemories` does not answer (it fails, its meaning arm fails as on the public runtime, or it finds nothing), the Ask's COS search also reads the memory bridge listing and matches it with the same keyword scorer, the same 0.35 floor and the same confirmed-domain filter as other COS hits, fused by rank with any memory hits (at most 3 memory sources). Listed notes are cited as COS memory sources (H ids, `memoryId`), counted in the `cos_search` phase time and result count, and the notices add "COS also searched the notes in its memory list by keyword." whenever one is cited. The listing is bounded at 3 s and the Ask's own cancel; a failed listing changes nothing. A memory search that answers is used exactly as before, the same sources in the same order and key order, and the listing is never read. With nothing found in either, the notices are the existing empty wording, word for word. On the public runtime this lets the 6.76.0 person lookup ("Who is X?") answer from the owner's note. One helper serves both cues and the Ask: `server/lib/cos-bridge-notes.ts` (bridge listing, keyword scoring, local dates, rank fusion).
|
|
1093
|
-
- Tests: `live-cues-v2.test.ts` (52, 13 new), `live-cues-v2-notes.test.ts` (11), `live-cues-v2-notes-bridge.test.ts` (2: the real searchers over a scratch bridge, a markdown note and a saved meeting), `live-cues-v2-settings.test.ts` (3), `live-cues-v2-settings-route.test.ts` (10: behind the real token gate, a module reload as the restart, and the real deps with faked network sending the gate to SubQ then Jev), `health.test.ts` (1 new: `/api/health` and `/api/models` flip with the setting), `live-cos-ask-memory-fallback.test.ts` (8), `live-cos-ask-
|
|
1218
|
+
- Tests: `live-cues-v2.test.ts` (52, 13 new), `live-cues-v2-notes.test.ts` (11), `live-cues-v2-notes-bridge.test.ts` (2: the real searchers over a scratch bridge, a markdown note and a saved meeting), `live-cues-v2-settings.test.ts` (3), `live-cues-v2-settings-route.test.ts` (10: behind the real token gate, a module reload as the restart, and the real deps with faked network sending the gate to SubQ then Jev), `health.test.ts` (1 new: `/api/health` and `/api/models` flip with the setting), `live-cos-ask-memory-fallback.test.ts` (8), `live-cos-ask-owner-runtime.test.ts` (3: the shipped public memory runtime 0.1.0 installed in a scratch dir with one accepted note; `searchMemories` alone finds nothing, the Ask answers "Who is Delmar?" from the note). `server/scripts/live-cues-v2-notes-mutation-gate.mjs`: 85 mutants over the cue switch, the cue notes fallback, the shared helper, the decision provider selection (global setting ignored, only-key fallback, explicit override both ways) and the Ask fallback (13 Ask guards), each killed by a named test against a green baseline (160 tests in 12 files). Full suite 7240 passed, 13 skipped, 0 failed; typecheck clean.
|
|
1094
1219
|
|
|
1095
1220
|
## 6.76.1 - Stop a glasses meeting from COS Control
|
|
1096
1221
|
|
|
@@ -1104,10 +1229,10 @@ Pairs with COS Glasses companion 6.10.644 and COS Control 0.5.321. Includes 6.76
|
|
|
1104
1229
|
- Tests: `server/routes/meeting-stop-request.test.ts` (9) over a real router; seven mutants (window ignored, window off by one, a refused request claimable, a not-live session asked, a Mac capture asked, no display event, a request id from another session) each killed.
|
|
1105
1230
|
## 6.76.0 - Live COS Ask knows who owns this COS, looks in COS for person questions, and times every phase
|
|
1106
1231
|
|
|
1107
|
-
- Field report (
|
|
1232
|
+
- Field report (Faith, 2026-10-17): on her own COS, "Who is Faith Ukamya?" (heard as "Faith Ukmya") was answered "These sources say nothing about who Faith Ukamya is" from one transcript passage.
|
|
1108
1233
|
- Owner card. A typed question gets a `profile` source built from the profile the server already keeps: `owner_name`, `owner_speaker_label`, `home_city`, and the owner's own `system_prompt_context` notes with any sentence holding a street address, zip, phone number or email removed. It is cited with an H id like other COS history, returned in `sources` when cited, and named "COS profile" on the lens From line. No profile name: no card, and the prompt is byte-for-byte as before. Presets never get it.
|
|
1109
|
-
- ASR spellings of the owner's name read as the owner: one edit for names under seven letters (two from seven), one edit after a recognizer-added leading y (
|
|
1110
|
-
- Person and self-history questions always look in COS first. "Who is X", "tell me about X" and the owner's own history ("what was going on with me when I was pregnant with
|
|
1234
|
+
- ASR spellings of the owner's name read as the owner: one edit for names under seven letters (two from seven), one edit after a recognizer-added leading y (Ukmya and Yukmya both read as Ukamya). Full name, a surname after a lead-in word ("who is Ukmya"), or an exact first name as the whole subject. A different first name with the same surname ("Felix Ukamya") is not the owner. The answer's notices say how the question was read.
|
|
1235
|
+
- Person and self-history questions always look in COS first. "Who is X", "tell me about X" and the owner's own history ("what was going on with me when I was pregnant with Felix?") run one COS lookup (memories and earlier meetings, every domain, not only the confirmed meeting domain) before the first model call, whatever the planner would choose. It takes one of the two lookups and saves the planner hop. It never goes to the web: web search stays gated exactly as before, so a public "who is" question still reaches the gated web through the planner after an empty COS lookup.
|
|
1111
1236
|
- An empty COS lookup is never proof of absence. The prompt lists each COS lookup with its result count, the notices say what was searched, and an answer that reads as "nothing about X" after an empty lookup gets one sentence saying what COS searched and that this does not mean COS has nothing on it.
|
|
1112
1237
|
- Latency, measured on Miles's Mac before changing anything: "through 210666 ms" and "288457 ms" in the report are `throughMs`, the meeting position when the question was asked (3:30 and 4:48 into the recording), not how long the answer took. The 98 completed Asks in the journal took 3.2 s to 24.3 s (median 6.7 s), and the durable runtime stops any Ask at 30 s. Per model call (the live server's token audit since 2026-10-10): Sonnet median 4.6 s, max 9.4 s (n=121); Opus median 6.1 s, max 12.3 s (n=11); Haiku median 3.8 s, max 7.0 s (n=10); web search median 9.1 s, max 10.5 s (n=10). No search timing existed before this release.
|
|
1113
1238
|
- Per-phase timing. Each exchange carries `timings` (`totalMs` and ordered `phases`: plan, answer, review, cos_search, web_search, public_topic, fact_check, with `forced`, `failed` and result counts), kept on failed and timed-out Asks too, and each Ask logs one line `[meeting-ask] timing <requestId> <action> <outcome> total=...ms <phase>=<ms>[/<results>] ...` with no question, answer or query text.
|
|
@@ -1643,13 +1768,13 @@ Local hardening candidate; stable promotion remains gated on native UI and G2 ac
|
|
|
1643
1768
|
|
|
1644
1769
|
### Fixes 6.61.0 labelling other people as the wearer: a split track is named only by a confident voiceprint, never by the glasses' default label
|
|
1645
1770
|
|
|
1646
|
-
- Miles, 2026-10-02 10:19, in a live meeting on 6.61.0: "MU is being assigned to loudest speaker vs my actual voice signature ... I saw
|
|
1771
|
+
- Miles, 2026-10-02 10:19, in a live meeting on 6.61.0: "MU is being assigned to loudest speaker vs my actual voice signature ... I saw Jasper Quillen voice showing up as MU." In that session (`meeting_1790953219564_yhwyxs`, read from a copy), 40 turns read MU. 25 were genuine (voiceprint at 0.65 or more, Even Hub `self`). 11 had similarity 0 in two-track chunks where Even Hub heard mostly someone else ("Okay.", "I can send a message. Yeah.", "to have as we play into calendar."): a short second track took the client's label, and the client's label is the glasses' default wearer label (`MU`), not evidence of who spoke. 2 more ("Of course. All right." at 0.612, and 0.625) were a track's own voiceprint check returning the wearer from the full search, which accepts any profile from 0.55, below the 0.65 at which the voiceprint verifies the wearer.
|
|
1647
1772
|
- **The client's label never names a track.** In a split chunk, a short track that is not the dominant one is `Ext`. The client's label is still what 6.60.0 used it for: a whole chunk no voiceprint could run on (under 2 s, or no voiceprint model). A one-track chunk is labelled exactly as 6.60.0 labelled the chunk.
|
|
1648
1773
|
- **The wearer needs owner-verify confidence in a split chunk.** A track named the wearer (`owner_speaker_label`) by its own voiceprint check, or a short dominant track that would take the whole chunk's voiceprint, keeps the wearer's name only at `OWNER_VERIFY_SIMILARITY` (0.65, now one constant shared with the voiceprint's owner verify); between 0.55 and 0.65 it is `Ext`. Any other name keeps the search threshold, as before. The two 0.665 and 0.716 turns where Even Hub heard mostly someone else are above the floor and stay, as reviewed.
|
|
1649
1774
|
- **The final pass applies the same rule.** Its channel map was already built only from chunks at 0.65 or more. Its per-chunk fallback (the live turn a word sat in, else the chunk's label) now treats a wearer name in a split chunk below 0.65 as `Ext`, so a 6.61.0 turn like these is never carried into a final transcript.
|
|
1650
1775
|
- **Logged and counted.** Every chunk where a would-be wearer name was refused logs one line, `[diarizer] owner-guard chunk #N refused=K client_label=X weak_owner=Y`; the chunk's `diarizer.ownerGuard` lists the reasons, and `readiness.diarizer.ownerGuard` counts them since start.
|
|
1651
1776
|
- **Replayed on the field session (read-only copies of its 195 Nemotron chunks and their audio, the real CLI and the real voiceprint):** all 28 genuine wearer turns (0.65 or more, Even Hub `self`) stay MU; 0 of the 11 similarity-0 MU turns and 0 of the 2 weak ones (0.612, 0.625) are MU any more; the 2 borderline turns stay MU; the guard refused 13 client labels and 2 weak wearer matches; no chunk fell back.
|
|
1652
|
-
- Tests: the field chunks rebuilt from their records ("Okay." with Even Hub self 19/121, "I can send a message. Yeah." at self 3/125 with
|
|
1777
|
+
- Tests: the field chunks rebuilt from their records ("Okay." with Even Hub self 19/121, "I can send a message. Yeah." at self 3/125 with Corin keeping his own track, "Of course. All right." at 0.612), the wearer staying the wearer in a one-track chunk (0.703) and a split one (0.716), a weak wearer match refused on both paths (the whole-chunk voiceprint a short dominant turn takes, and a track's own check), the client label never naming a track, the owner-guard log line and count, the route passing `owner_speaker_label`, and the final pass refusing a 6.61.0 similarity-0 MU turn while keeping a confident one. The mutation gate adds a case for each guard and retargets the three 6.61.0 cases the guard replaced: baseline 169 tests in 11 files, 104 of 104 killed. Full suite 5,723 passed, 2 skipped.
|
|
1653
1778
|
- No Install hooks and no COS Control change.
|
|
1654
1779
|
|
|
1655
1780
|
## 6.61.0
|
|
@@ -1669,7 +1794,7 @@ Local hardening candidate; stable promotion remains gated on native UI and G2 ac
|
|
|
1669
1794
|
- **The final transcript.** When a meeting is saved, the finalization job runs one Nemotron pass over the whole meeting after the HQ batch is persisted, while the raw chunk audio still exists, and before the job reaches the ops handoff. One run over the timeline keeps the channels consistent for the whole call. A channel takes a name only from chunks the voiceprint was sure of (similarity at least 0.65, the review panel's own floor, a real name, one-sided by Even Hub, or by Nemotron itself when a chunk has no Even Hub histogram), with at least 3 such chunks and 80% of them agreeing; several channels may map to one person, and a channel two people share stays unmapped. Following the canary's retro plan, a room with more than four identities or all eight channels in use (a channel counts with half a second of speech, not a blip) gets no meeting-wide map. Every word then takes its channel's name, or keeps its per-chunk name (the live Nemotron turn it sat in, else its existing label). The timeline is built asynchronously in steps, so a long meeting does not freeze live uploads; a timeline far longer than the audio it holds (more than 1.5 times, plus 10 minutes) is a bad clock and the pass is skipped as `too_long` before anything is allocated.
|
|
1670
1795
|
- **Only labels change.** The saved transcript is relabelled in place, word by word, against the relabelled speaker words (a 12-word window, then a three-word anchor past anything cleaning removed). A line splits where its speaker changes and is never merged with its neighbour, so a pass that changes no label reproduces the transcript byte for byte, writes nothing and moves no revision (`no_change`). Nothing is written unless the result has exactly the same words in the same order. Each changed batch word keeps `originalSpeaker`, a changed streaming chunk keeps `originalSpeaker` and gains `finalSegments`, and the sidecar keeps the whole pre-relabel transcript under `diarization.originalTranscript` with the channel map and its evidence. If the markdown write fails the sidecar is put back byte for byte; if even that fails the pass reports `sidecar_restore_failed` and says so in the log.
|
|
1671
1796
|
- **A person's edit always wins.** A meeting with a human correction (`correctionRevision` above 0) is never relabelled. The pass re-reads both files just before writing; if a relabel, a deattribution or a summary landed during its CLI run, it stands down (`changed_during_pass`) and writes nothing. Relabel and deattribute answer 409 `meeting_finalizing` (retryable) while a finalization job exists for the meeting, as held naming already did. A second pass over an applied meeting is a no-op. If the pass fails in any way the meeting stays exactly as saved, and the job goes on.
|
|
1672
|
-
- **Voice profiles stay safe.** Since a chunk's label can now be its dominant turn's name, naming a voice no longer enrols a chunk whose own whole-chunk voiceprint named a third person, or a chunk Nemotron split into more than one turn; the response says how many it refused (`otherVoiceSkipped`, present only when it happened). A wrong existing label is still how a new person first appears (the
|
|
1797
|
+
- **Voice profiles stay safe.** Since a chunk's label can now be its dominant turn's name, naming a voice no longer enrols a chunk whose own whole-chunk voiceprint named a third person, or a chunk Nemotron split into more than one turn; the response says how many it refused (`otherVoiceSkipped`, present only when it happened). A wrong existing label is still how a new person first appears (the Leo case).
|
|
1673
1798
|
- **When COS sync sees it.** The relabel completes before the meeting is visible as final: the job is still in `batch_pending` when it runs, and `sync_meetings.py --g2-only --g2-import-file` runs from the ops handoff only after it returns. Sync reads speakers from the markdown transcript and, for a batch meeting, from `batchSegments[].speakerWords`, both already relabelled; `batchTranscript` is kept equal to the canonical text. No sync change is needed. With early sync on, the claim made at save is marked HQ pending and the final import replaces its transcript, as it already did for HQ.
|
|
1674
1799
|
- **Offline by construction.** The server always passes `--models <dir>`; that path in FluidAudio has no network call, while the CLI run without it downloads from Hugging Face and deletes a cache whose weights marker it does not recognise. The directory is `COS_NEMOTRON_MODELS`, else `~/.cos-glasses/models/nemotron-3-diarization`, which the warm-up links to FluidAudio's own cache (`~/Library/Application Support/FluidAudio/Models/nemotron-3-diarization`, bundle `monolithic/v2`). An incomplete explicit directory is never replaced by another copy. With no complete local copy the CLI is never run. Probed 2026-10-02 under `sandbox-exec` with all network denied: the CLI ran from the linked directory and from the cache. The CLI has no version command, so the warm-up logs its sha256 once and whether it is the verified v0.17.4 build (`readiness.diarizer.cliKnown`).
|
|
1675
1800
|
- **The CLI gets nothing it does not need.** It runs with PATH, HOME, TMPDIR, USER, LOGNAME and the locale only: no API token, provider key or OpenAI key reaches it (verified warm with only those on 2026-10-02).
|
|
@@ -1679,11 +1804,11 @@ Local hardening candidate; stable promotion remains gated on native UI and G2 ac
|
|
|
1679
1804
|
- **After updating.** No Install hooks and no COS Control change. The first start compiles the model for the Neural Engine in the background (about 30 s on the M3 Ultra); chunks use the voiceprint until `readiness.diarizer.active` reads `nemotron`. No LLM is called anywhere in transcription.
|
|
1680
1805
|
- **Real-data replay (read-only, 2026-10-02).** Clones of three meetings from the canary list, `COS_DATA_DIR` pointed at a scratch copy so not even the calibration log touched the store, the live path chunk by chunk exactly as the route runs it (Nemotron started first, the whole-chunk voiceprint, Whisper large-v3 through a private whisper-server with the live server's arguments, then the track names), then the final pass on a sidecar carrying those live turns. The live replay ran on `dacd8be`-era code, with the mutation gate alongside part of it, so the times are a busy-Mac upper bound.
|
|
1681
1806
|
- **Disclosure: what the agreement numbers measure.** They measure how rarely Nemotron disagrees with a label the voiceprint was already sure of, not whether a label is right. The live path gives a one-track chunk the stored voiceprint label by construction, so its agreement is high by design on those chunks; the final pass maps channels from the very chunks it is then scored on. Nobody listened to the changed turns. The independent check available here is the last number in each final line: final word labels against the per-track voiceprint names of the live turns.
|
|
1682
|
-
-
|
|
1807
|
+
- Elliot and Miles, Thu 10:01 (505 chunks with text): Nemotron labelled 502 (3 `no_speech`); 114 chunks (22.7%) gained a second speaker, 1.31 segments per chunk; on 270 high-confidence chunks the dominant name matched the stored label 270 times. Final: channels {0: Elliot, 1: MU}, 2,201 of 8,484 words relabelled (988 Ext to Elliot, 455 Ext to MU, 609 turn splits); majority agreement 1.000 on 132 one-sided and 241 high-confidence chunks; final words against the voiceprint-named live tracks 0.983 (4,589 words).
|
|
1683
1808
|
- Ryan and Miles, Thu 16:03 (853): 852 labelled (1 `no_speech`); 289 (33.9%) gained a second speaker, 1.49 segments per chunk; dominant agreement 0.995 on 659. Final: {0: Ryan, 1: MU, 2: Ryan}, 4,432 of 19,140 words relabelled; majority agreement 0.993 one-sided (274), 0.974 high-confidence (650); against live tracks 0.974 (12,969 words).
|
|
1684
1809
|
- Five people, Wed 07:31 (753): 747 labelled (6 `no_speech`); 175 (23.4%) gained a second speaker, 1.32 per chunk; dominant agreement 0.995 on 365. Final: crowded (5 identities), so no meeting-wide map; the live turns relabelled 2,977 of 12,226 words; majority agreement 0.995 one-sided (183), 0.980 high-confidence (346); against live tracks 0.999.
|
|
1685
1810
|
- Latency with Whisper load, all 2,111 chunks: Nemotron's own run p50 140 to 141 ms, p95 149 to 157 ms, max 196, 302 and 680 ms; what the chunk waited beyond Whisper (the end of Nemotron plus naming the tracks) p50 0 ms, p95 89 to 114 ms, max 129, 142 and 213 ms; no chunk timed out or found the lane busy. The final pass took 19 s, 30 s and 28 s for 52, 86 and 80 minutes of audio.
|
|
1686
|
-
- The final pass re-run on the QA-fixed code, on the stored sidecars without live turns: 10:01 applied ({0:
|
|
1811
|
+
- The final pass re-run on the QA-fixed code, on the stored sidecars without live turns: 10:01 applied ({0: Elliot, 1: MU}, 1,935 of 8,484 words, one-sided 1.000, high-confidence 0.996); 16:03 applied ({0: Ryan, 1: MU, 2: Ryan}, 4,234 of 19,140, 0.993 and 0.974); 07:31 crowded with no live turns to apply, so `no_change`: nothing written.
|
|
1687
1812
|
- The rule that the chunk `speaker` is the voice carrying the words came after the replay; it changes none of the 2,101 replayed chunks' names (every replayed `speaker` carried words).
|
|
1688
1813
|
- The replay found a real bug before release: the first final pass regenerated the transcript from the relabelled words and cleaned it again, which changed which URL-only lines cleaning dropped, so the word check refused every batch meeting. Fixed by relabelling in place.
|
|
1689
1814
|
- **QA round 1** (the Skeptic and the Ghost Hunter, on `dacd8be`). Fixed: the public health leak (blocker); the final pass overwriting a correction or summary made during its run; rename enrolment able to take a chunk the voiceprint gave to someone else; the CLI inheriting the server's tokens; health reading `nemotron` while every chunk fell back; no breaker on repeated budget kills; `no_change` that almost never fired because lines were merged; no cap on a timeline stretched by a bad `elapsed`; temp audio and CLI runs left behind by an exit; a warm-up that could stick on `warming`; recovery leaving turns that no longer joined to the text; untestable guards (a seam for the writer and the relabeller now makes the word check reachable); missing telemetry; constants defined twice. 28 of the new regression tests fail on `dacd8be` and pass here.
|
|
@@ -2225,7 +2350,7 @@ The phone can list the skills this Mac will actually run.
|
|
|
2225
2350
|
|
|
2226
2351
|
- `GET /api/skills` returns the slash catalog from the selected workspace (`.agents/skills`, then `.claude/skills`) plus `~/.claude/skills`. Nested agent folders flatten with `:`. Generated `source-command-*` duplicates stay out. Paths never leave the box.
|
|
2227
2352
|
- COS Glasses 6.9.468 paints that list on Views instead of a hardcoded cheat sheet.
|
|
2228
|
-
- A held voice can be heard before it is named. `GET /api/voice/ext-audio` lists `chunkIndices` per held session and `GET /api/voice/ext-audio/:sessionId/sample?chunk=<index>` serves that one chunk (no `chunk` still serves the newest). COS Control's Add-a-voice panel uses it to play portions of a session before naming it (
|
|
2353
|
+
- A held voice can be heard before it is named. `GET /api/voice/ext-audio` lists `chunkIndices` per held session and `GET /api/voice/ext-audio/:sessionId/sample?chunk=<index>` serves that one chunk (no `chunk` still serves the newest). COS Control's Add-a-voice panel uses it to play portions of a session before naming it (Faith, 2026-09-12: "no way to listen to the voices that are here").
|
|
2229
2354
|
- Recent is a rolling window. `GET /api/sessions/today/all-messages` keeps its path for COS Control and the phone but now answers with the newest 30 messages (`?limit=` up to 100) across every live session and as many archived days as it takes, deduplicated against their archive mirrors. Messages leave Recent only by ageing out of the window; nothing is hidden by the calendar. Control's Recent view and the phone's history recovery read this without change.
|
|
2230
2355
|
- Yesterday's turns no longer vanish for a day. The archive mirror ran every 24 h from boot (21:25 on the current uptime), so finished sessions from the previous local day were neither "today" for `GET /api/sessions/today/all-messages` nor in any day archive until the evening; on 2026-09-12 07:00 Control showed no turns while ten from the 11th sat live in `sessions.json`. The mirror now runs at boot, at the next local midnight, and lazily from the today and archive-listing routes, once per local day. `checkYesterdayArchive` keys on the local day like everything else.
|
|
2231
2356
|
|
|
@@ -2294,7 +2419,7 @@ Voice profiles named after a spoken sentence are repaired at load.
|
|
|
2294
2419
|
`Unnamed voice N`, keeps every embedding and its provenance, records
|
|
2295
2420
|
`renamedFrom` (the first 60 characters) and `needsName: true`, and counts it
|
|
2296
2421
|
in the load repairs (`profilesRenamed`). Legacy short labels ("MU",
|
|
2297
|
-
"Speaker 2", "
|
|
2422
|
+
"Speaker 2", "Theo M.") are never touched. `GET /api/voice/profiles` carries
|
|
2298
2423
|
`needsName` and `renamedFrom` so COS Control's speaker review can offer the
|
|
2299
2424
|
rename; `POST /api/voice/merge-profiles` folds the placeholder into the
|
|
2300
2425
|
right person as before.
|
|
@@ -2699,7 +2824,7 @@ The brief is waiting before you ask for it.
|
|
|
2699
2824
|
|
|
2700
2825
|
A start-of-day brief now runs on a schedule inside the server and lands in
|
|
2701
2826
|
the inbox as an ordinary numbered reply — no prompt, no "run the good-morning
|
|
2702
|
-
skill", no phone awake at 07:00.
|
|
2827
|
+
skill", no phone awake at 07:00. Lin Keats Lee's first-week note said it
|
|
2703
2828
|
plainly: he deployed COS to his G2 expecting a push at the start of the day and
|
|
2704
2829
|
nothing came, because until now the only thing that could start a brief was a
|
|
2705
2830
|
person typing. The server is the one process awake at that hour, so it owns
|
|
@@ -3752,16 +3877,16 @@ bring the leftover cards back.
|
|
|
3752
3877
|
Enrolment after `POST /relabel` only fired when `from` was a placeholder (`Ext`,
|
|
3753
3878
|
`Unknown`, `Unidentified N`). The live path in Speakers review is the other one:
|
|
3754
3879
|
the identifier weakly matches someone already enrolled, and the reviewer says
|
|
3755
|
-
**This is someone else → Use "
|
|
3756
|
-
`meeting_1787234635703_t4iz74`:
|
|
3757
|
-
`applied`, 78 profiles, no
|
|
3880
|
+
**This is someone else → Use "Leo Ashby"**. Measured 2026-08-20 on
|
|
3881
|
+
`meeting_1787234635703_t4iz74`: Cole Garner → Leo Ashby, 19 chunks, ledger
|
|
3882
|
+
`applied`, 78 profiles, no Leo. Backfill used the same guard, so the meeting
|
|
3758
3883
|
could not be enrolled after the fact either (`eligible: 0`, `skippedNamedSource: 1`).
|
|
3759
3884
|
- **Enrol by target, not by source.** `enrolNamedVoice` still skips a placeholder
|
|
3760
3885
|
`to` and an empty `changed` list. A real `to` enrols those chunks — creates the
|
|
3761
3886
|
profile when it does not exist, appends when it does — through the same raw-index
|
|
3762
3887
|
map, coherence gate, 20-sample cap, and `correction:<sessionId>` tag. Global fold
|
|
3763
3888
|
of two identities remains `merge-profiles`. Per-meeting chunk assignment is not
|
|
3764
|
-
that. Mutating the old `from`-placeholder guard back in fails the new
|
|
3889
|
+
that. Mutating the old `from`-placeholder guard back in fails the new Cole → Leo
|
|
3765
3890
|
test.
|
|
3766
3891
|
|
|
3767
3892
|
## 6.36.16
|
|
@@ -4142,7 +4267,7 @@ bring the leftover cards back.
|
|
|
4142
4267
|
- **`POST /api/meeting/:sessionId/backfill-enrolment`** — train a profile from a voice
|
|
4143
4268
|
that was named BEFORE enrolment shipped. Those meetings have a correct transcript and
|
|
4144
4269
|
no profile, and re-running the rename cannot help: the voice is already a real name
|
|
4145
|
-
there, so the placeholder guard correctly declines. The live case is
|
|
4270
|
+
there, so the placeholder guard correctly declines. The live case is Wren Hale —
|
|
4146
4271
|
60 and 109 chunks across two meetings, 182 sidecar mentions, absent from a 77-profile
|
|
4147
4272
|
store.
|
|
4148
4273
|
- The correction LEDGER already holds what enrolment needs: the original `from`, the
|
|
@@ -4151,8 +4276,8 @@ bring the leftover cards back.
|
|
|
4151
4276
|
coherence, the diversity cap and the `correction:<sessionId>` tag all apply
|
|
4152
4277
|
identically. No second implementation to drift.
|
|
4153
4278
|
- **Named-source corrections are excluded, by the existing rule rather than a new one.**
|
|
4154
|
-
`Ext ->
|
|
4155
|
-
fix, and training on it would put
|
|
4279
|
+
`Ext -> Wren` is training data; `Avery Winters -> Wren` is a mis-attribution
|
|
4280
|
+
fix, and training on it would put Avery's voice into Wren's profile. Both rows
|
|
4156
4281
|
are reported; only the placeholder one attempts anything. Mutation-verified: dropping
|
|
4157
4282
|
the placeholder guard fails that test.
|
|
4158
4283
|
- **Runs in-process, which is the point.** The voice store is owned by the running
|
|
@@ -4235,11 +4360,11 @@ bring the leftover cards back.
|
|
|
4235
4360
|
## 6.27.10
|
|
4236
4361
|
- **Naming an unidentified voice now creates a real profile.** `POST /api/meeting/:id/relabel`
|
|
4237
4362
|
was TEXT ONLY: it rewrote `speaker` strings in the sidecar and never touched the
|
|
4238
|
-
voice store. Saving "
|
|
4363
|
+
voice store. Saving "Wren Hale" over 109 segments labelled that one meeting and
|
|
4239
4364
|
taught the system nothing — she never appeared in `/api/voice/profiles`, the review
|
|
4240
4365
|
panel still offered her as `new name` inside the SAME meeting, no later meeting
|
|
4241
4366
|
could match her, and there was no profile to add further chunks against.
|
|
4242
|
-
Verified on the live store: 77 profiles, no
|
|
4367
|
+
Verified on the live store: 77 profiles, no Wren Hale, while both sidecars
|
|
4243
4368
|
carried her name.
|
|
4244
4369
|
- The server already had `/api/voice/enroll-ext` for exactly this and the naming flow
|
|
4245
4370
|
never called it. Relabel now enrols the embeddings of the chunks it actually
|
|
@@ -5134,7 +5259,7 @@ nothing she could name would work.
|
|
|
5134
5259
|
|
|
5135
5260
|
Worth recording for the next person who tests this: only the WORD-TIMING path
|
|
5136
5261
|
can overflow. Measured, the chunk-estimate path credits a contested second to
|
|
5137
|
-
one speaker (two speakers at identical timestamps gave MU 273s and
|
|
5262
|
+
one speaker (two speakers at identical timestamps gave MU 273s and Mira 0s), so
|
|
5138
5263
|
a chunk-sourced meeting can never trip the note.
|
|
5139
5264
|
|
|
5140
5265
|
## 6.21.29
|
|
@@ -5160,7 +5285,7 @@ without the floor.
|
|
|
5160
5285
|
cosine floor, a human typing the name, and the wearer exemption (identity comes
|
|
5161
5286
|
from holding the device, not from a score). The route carried `isOwner` and
|
|
5162
5287
|
`confirmedByHuman` and discarded both. The output now names the warrant per
|
|
5163
|
-
person: `"MU" by wearing the device; "
|
|
5288
|
+
person: `"MU" by wearing the device; "Mira Orlen" by voice match; "Theo Marsh"
|
|
5164
5289
|
because a human named that voice`. Labels are quoted, so a typed "Smith, John"
|
|
5165
5290
|
cannot read as two people, and the caveat now covers different SPELLINGS of a
|
|
5166
5291
|
confirmed name (labels are `MU` while the prose says "the user").
|
|
@@ -5420,7 +5545,7 @@ Ditto meeting and found eleven attributed voices, most of them wrong.
|
|
|
5420
5545
|
|
|
5421
5546
|
- A NAME MUST BE EARNED. `identifySpeaker` accepts a match at 0.55, so one
|
|
5422
5547
|
segment could arrive in the review panel wearing somebody's full name — Richard
|
|
5423
|
-
|
|
5548
|
+
Jennings on 1 segment at 0.60, Theo Marsh on 1 at 0.55. Voices now carry
|
|
5424
5549
|
`nameAsserted` plus `assertionBlockers`; below the floor a client must render
|
|
5425
5550
|
"unidentified" and offer the label only as a scored candidate. Floors:
|
|
5426
5551
|
similarity >= 0.65 AND >= 3 segments AND not thrashing. The owner gets no
|