Working notes from building Charoite, a fully local meeting assistant for macOS. Measurements, failures, and decisions that got reversed. RSS.
Posts
-
The bench measured a path the daemon never takes
Memory in Charoite is a search over the meeting graph that feeds an answer, the live context and topic expansion. I had a bench for it: a set of 37 questions with known facts, and a score of how many come back. The score was 29 of 37. It was also not what users get.
-
Sixty mutants and a list of doors
Mutation testing is only useful if its verdict is cheap enough to run before every merge and strict enough to mean something. A whole-file pass over our code is thousands of mutants and hours, so the checker judged a range of changed lines, and a big range took hours. Today’s change replaces the range with a sample.
-
The echo filter judged the wrong unit
On a call, the microphone also hears the other side coming out of the speakers. Charoite removes that echo after the meeting by comparing the microphone’s speech intervals with the call channel’s: if a microphone segment is mostly covered by call speech, it is the other person leaking in, and it is dropped. Today I put a second diarization engine behind a switch for the call channel, and the rule broke without a single test failing.
-
A share never becomes a parent
The mutation checker judged a range one mutant at a time. For modules with slow tests a range of about a hundred mutants took hours, and it was the last step before every push. Today
mutate_check.py --jobs Nlanded: it runs the plan as N parallel shares on one machine and judges them with the same merge code CI uses. Measured on the 28th: 98 mutants with--jobs 4in 5394 s (about 90 minutes) against roughly 6 hours sequentially; 101 mutants of the graph package with--jobs 2in about 12 minutes against about 25 for one process. -
Nearly perfect on synthetic audio, 0.6 on real meetings
The first run of Nemotron 3 Diarization with real weights on Apple Silicon (an M1 Max) landed today, and the docs no longer say “no numbers yet”. No behaviour changed in that PR; it is only measurements, and the measurements disagree with each other in an instructive way.
-
Phase zero, closed
The overhaul’s phase zero is merged — eleven batches in three days, each one small enough to review in a sitting: the live transcript aggregate carved into its own module, a brain client, one Claude-CLI resolver, a file-lock helper, one config loader, a single owner for every Python launch, live probes out of the deterministic suite, and a characterization net over the seams of rename → forget → repeat.
-
A crash in a background thread should fail the run
This devlog keeps returning to false green; apparently that is the theme.
-
A mechanical move, verified mechanically
Charoite is in an overhaul phase: no new features for a while, just carving the codebase into pieces that can be reasoned about. The rhythm is one or two small batches a day, each merged through the usual review circle. This week that produced a lock helper, a single config-fallback loader, a single resolver for an external CLI the pipeline shells out to — and the first real aggregate extraction.
-
A green test for an event nobody sends
Stopping a recording is not an action, it is a wait. The daemon has to flush audio, run the post-meeting pipeline and release its lock, and the app must not open a new meeting until the old process is actually gone. In Charoite that wait used to live in five scattered flags on one service object, which is how a daemon surviving
SIGKILLcould leave the app in “stopping” forever, with the Stop button doing nothing at all.
subscribe via RSS