Skip to content

Carry the engine's speech evidence to where it can be read, and put the leak's guard where its comment already was - #364

Open
lilseyi wants to merge 2 commits into
mainfrom
claude/multi-agent-orchestration-6cwo3c-segment-evidence
Open

Carry the engine's speech evidence to where it can be read, and put the leak's guard where its comment already was#364
lilseyi wants to merge 2 commits into
mainfrom
claude/multi-agent-orchestration-6cwo3c-segment-evidence

Conversation

@lilseyi

@lilseyi lilseyi commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Three defects were reported from hardware last night. One is real and is fixed here. The other two are the same fact: the build they were measured on predates the fixes for them. That is established from the workflow run rather than argued.

1 — The evidence that decides the invented-speech fix now reaches the client

The silence refusal works and does not finish the job: 166 words / 90s became 29 words / 45s, the meeting was still filed, and hasNothingCaptured is still unreachable. What decides the next move is whether the survivors sit just past the thresholds (move the threshold — cheap) or far from them (the engine decoded silence confidently — no threshold reaches it). Nobody could answer it, because no_speech_prob, avg_logprob and duration_after_vad were read inside the transcription Worker, used for control flow, and dropped.

First finding, established before anything was built: the provider does return them. The alternative — that the fields are absent — would have made plumbing them a waste, so it was checked two ways:

  • The deployed model's published output schema carries segments[].{avg_logprob, no_speech_prob, …} and transcription_info.{duration, duration_after_vad}. It carries no confidence, which is why the distinct set of confidence values across every segment in the bucket is [null] and always will be on this model.
  • They demonstrably arrive, which is stronger than a schema: the VAD rule cannot fire on this deployment at all (vad_filter defaults to false), so the measured threefold reduction was produced entirely by the rule that requires both per-segment fields to be present. A rule that cannot fire without two fields, firing repeatedly on real audio, is those two fields arriving.

Where it now lives: a per-chunk summary, on the wire, to the recorder that posted the audio. The Worker answers with evidence beside refused; the gateway carries it as speechEvidence exactly the way it already carries refusedSegments — rebuilt key by key, judging nothing; the desktop writes one structured line per answered chunk, meeting_speech_evidence chunk=… kept=… refused=… kept_no_speech_max=… ….

Against the constraints, each one load-bearing:

  • Readable without gateway log access — it is the desktop's own log, in the process the person diagnosing already has. The Worker's log is in an account they do not have, which is precisely what blocked the diagnosis.
  • No numbers in front of a person — nothing renders it and nothing writes it into a note. Not on the segment: a segment is rendered at a person and written into their bucket. The sentence a person needs already exists in words (CAPTURE_NOTICES.silent).
  • No transcript text anywhere new — every value is a number or null; the only string is the client's own chunkId. Both the gateway and the desktop rebuild the object from keys they declare, so a field the far end grows does not ride along (checked with a planted transcript key at both hops).
  • Honest about omissionnull is never filled in with 0 on any hop, and prints as absent. 0 is a legal no_speech_prob; substituting it would publish a confident measurement of silence that nobody made.

Nothing branches on any of it. The Convex path is deliberately not plumbed — nothing on the phone can read a log line, so it would land in Convex's logs, the same inaccessible place one provider along, and a field nobody reads rots. Argued in docs/decisions/meetings.md.

2 and 3 — The duplicate deliveries and the parked rows are one build

The installed build is workflow run 34181715875: head_sha fd0081e (#351), started 02:55:14Z. dropMisaddressed and the controller's detach both landed in 7bc99ca (#353), merged 03:00:52Z — five minutes and thirty-eight seconds after that build started. Neither fix was in the binary. The dispatch message named #353 because it was sent after the merge was requested and before it landed.

Both reports are correct observations of the old code, reproduced and then shown absent:

  • On fd0081e, three meetings driven through the real MeetingsController deliver meeting N's segments N times — the ratio-equals-index shape reported from hardware — and leave meeting 1's record holding all three meetings' words.
  • On main, the same run delivers each segment exactly once and leaves the recorder's subscription count back at zero between meetings.

What clears the eight parked rows: any build cut from 7bc99ca or later. They are dropped once, on the first read of the queue, with meeting_segments_misaddressed_dropped rows=<n> logged. Nothing is lost — every one of those rows also went out under the meeting that produced it and was acked there.

The recorder-to-bridge attachment is correct, and was. What was missing is the check. Its own attach() docblock states the hazard in the exact words the report used — "a recorder is created per configuration and stop() must genuinely detach, or a second meeting is fed by two subscriptions and every segment is emitted twice" — and the only test standing on it ran one meeting, the single length at which a per-meeting leak is invisible. A comment describing the failure it sits above, with no test at that length, is how one shape gets reported three times. So the guard is now where the comment is:

  • three meetings in one run, each segment delivered exactly once;
  • the recorder's subscription count asserted back at its baseline after every stop() — the stricter half, because a fix that merely halved a leak passes a delivery count and fails a baseline;
  • the whole subscription set released, not the segment half;
  • the overlapping-start() race, which is the only path where attach()'s opening detach?.() is reachable at all — deleting that line failed nothing in the app's whole suite until this test.

Every other consumer of the same channel, checked: capture/level.ts (onLevel) attaches in its own effect and detaches in that effect's cleanup; ThisMachineCard holds connection.onChange and onPendingApproval the same way; the shell side emits once per segment from one call site and the preload's subscribe removes the same listener reference it added. The controller-to-recorder layer is #353's, with three-meeting coverage already. No other leaked subscription was found.

Three instances of one shape — controller-to-recorder (#353), the phone's equivalent (#358), and this comment's hazard — suggest a structural answer: one attach/detach helper owning a subscription set, or a check that every subscription taken in this subsystem is released. That is larger than this pull request, so it is proposed in the decision doc rather than built.

Sabotage

Every count is failing tests/checks, run whole-suite and reverted.

infra/transcribe-worker

edit FAIL
evidence dropped from the answer entirely 9
a null reading filled in with 0 6
the kept and refused populations swapped 3
statedNoSpeech counted over kept segments rather than all of them 2
duration_after_vad reported only when the rule would act on it 1

apps/mcp

edit FAIL
drop speechEvidence from the answer 3
forward raw.evidence instead of rebuilding it 1
default an absent reading to 0 rather than null 1

apps/mobile (capture/desktop.ts, whole mobile suite)

edit FAIL
stop() does not detach, so one subscription leaks per meeting 4
attach() does not detach the previous subscriptions first 1
only the segment subscription is released, not the capture-state one 4

apps/desktop (main/transcribe.ts, recorded in the test docblock): speechEvidence dropped 2; body.speechEvidence forwarded whole 1; an unreadable reading defaulted to 0 1.

Suites

pnpm install, then turbo run typecheck (12/12) and turbo run test --force (12/12), rebased onto e47a770:

  • infra/transcribe-worker 125 (was 119) — vitest
  • apps/mcp ALL PASS
  • apps/desktop ALL PASS
  • apps/mobile 4101 passed, 1 todo, 220 suites
  • packages/meetings ALL PASS
  • apps/convex pass

No pre-existing failures on origin/main; the mobile editor test that flakes under parallel load did not flake in any run here.

Compatibility, both directions

Additive on every hop. An old Worker gives the gateway no evidencespeechEvidence: null; an old gateway gives the desktop no field → null; an old desktop ignores the new key. Each is checked, and none of them is a row of zeros.

What still needs a Mac

  • Reading the numbers. The plumbing lands here; the answer to threshold or signal comes from running a signed build carrying it and reading the meeting_speech_evidence lines from a quiet-room recording. That build must be cut from this merge or later — cut it after merging, not from main as of last night.
  • Clearing the eight parked rows — install any build from 7bc99ca or later and check the log line.
  • The launch smoke — it cannot run in this container (Electron refuses to run as root without --no-sandbox); the desktop bundle builds and the per-PR gate runs it.

🤖 Generated with Claude Code

https://claude.ai/code/session_011Ht7GUWMd9t4xDNED9NDEz


Generated by Claude Code

The silence refusal cut invented words from 1.84/s to 0.64/s on the owner's Mac
and did not stop them, and nobody could tell whether the threshold was wrong or
the signal was: `no_speech_prob`, `avg_logprob` and `duration_after_vad` were
read inside the transcription Worker, used for control flow, and dropped. The
segment that reaches the bucket carries one number, `confidence`, whose distinct
set of values across every segment ever produced is `[null]` — because the
deployed model does not emit that field and this repository refuses to invent
one.

Established first, since "the provider omits them" would have been the finding:
it does not. The published output schema of the deployed model carries both
per-segment fields and both `transcription_info` durations, and they
demonstrably arrive — the VAD rule cannot fire on this deployment at all
(`vad_filter` defaults to false), so the measured reduction was produced
entirely by the rule that needs `no_speech_prob` and `avg_logprob` together.

So the Worker now answers with a per-chunk `evidence` summary beside `refused`:
counts of what the engine stated, the extremes over the kept and the refused
populations on each axis, and the two durations as stated. The gateway carries
it as `speechEvidence` the way it already carries `refusedSegments` — rebuilt
key by key, judging nothing — and the desktop writes one structured line per
answered chunk, in the process the person diagnosing a recording already has.

Not on the segment, because a segment is rendered at a person and written into
their note; not in the Worker's log, because reaching that needs an account the
person diagnosing does not have, which is exactly what blocked the diagnosis.
`null` is never filled in with `0` on any hop and prints as `absent`: "it did
not say" and "it measured silence" must stay different answers. Nothing branches
on any of it, nothing renders it, and every value is a number — there is no
field here that can hold a word anybody said.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Ht7GUWMd9t4xDNED9NDEz
Two reports against the signed build — the eight parked outbox rows did not
clear, and every segment reached the page once per meeting in the run — are one
fact. That build is workflow run 34181715875, `head_sha` `fd0081e`, started
02:55:14Z; `dropMisaddressed` and the controller's detach both landed in
`7bc99ca` (#353), merged 03:00:52Z. Neither fix was in the binary. Reproduced
against `fd0081e` — three meetings deliver meeting N's segments N times and each
record ends holding every later meeting's words — and absent on `main`, where
the same run delivers each once and leaves no subscription behind. Any build cut
from `7bc99ca` or later drops the parked rows on the first read of the queue and
logs `meeting_segments_misaddressed_dropped rows=<n>`; nothing is lost, because
every one of those rows also went out under its own meeting and was acked.

The recorder-to-bridge attachment named in the third report is correct and was.
What was missing is the check: its own docblock states the hazard — "a recorder
is created per configuration and `stop()` must genuinely detach, or a second
meeting is fed by two subscriptions" — and the only test on it ran one meeting,
the single length at which a per-meeting leak is invisible. So the guard now
sits where the comment does: three meetings in one run with each segment
delivered exactly once, the recorder's subscription count asserted back at its
baseline after every stop (a fix that halved a leak passes the first and fails
the second), the whole subscription set released rather than the segment half,
and the overlapping-start race — the one path where `attach()`'s opening
`detach?.()` is reachable, and deleting that line failed nothing in the app's
whole suite until now.

Sabotage, whole mobile suite, reverted: `stop()` not detaching 4; `attach()` not
detaching first 1; only the segment subscription released 4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Ht7GUWMd9t4xDNED9NDEz
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants