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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/latency-observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,40 @@ out, and input transport before the agent on the way in, are outside this
record. A browser-perceived figure needs a matched audio measurement in the
browser.

## Browser audio smoke evidence

The credentialed `voice` and `barge` browser-check flows also capture
`jimAudio`: received byte/packet deltas, peak decoded RMS, playback-blocked
state and a bounded result per observed audio subscription. The harness uses
LiveKit's public track-specific `getRTCStatsReport()` and an analyser on the
same native track attached as Jim. It records no device labels or audio
samples.

Sampling waits up to 120 seconds for Speaking, then samples at 100 ms
intervals for at most 10 seconds, stopping early once both counters grow and
RMS exceeds 0.001. Counter resets and receiver changes start a new baseline;
different track generations never share deltas. At most 64 subscriptions are
sampled; exceeding that limit is an unavailable observation, not a healthy
verdict.

`unavailable` means there were insufficient counter pairs or decoded samples,
no attached Jim track, no Speaking interval, cancellation, a sampling error or
a subscription limit. `transport-silent` means bytes or packets, either one,
did not grow across the observed counter pairs, and `decoded-silent` means both
grew but decoded RMS stayed at or below the floor. These are separate failures.
The capture retains failed observations, and each resubscription that keeps its
track through its sampling window must pass independently. A generation whose
track is replaced, unsubscribed or disconnected before Speaking or within its
first second of it, before both counters grew and RMS exceeded the floor, is
marked `superseded`, since losing the track cut its window short and a fresh
analyser first reads near zero; it cannot fail an otherwise healthy
replacement, and the last generation must pass. A generation that sampled
longer is judged on its evidence even if its track then ends. Unmeasured
counters and RMS are `null`, not observed zeroes. This measures transport and
decoded signal during Speaking, not output-device audibility, physical level or
per-word delivery. The RMS floor is a smoke-check threshold, not a calibrated
speech detector. Local loopback tests do not replace the isolated provider run.

## Validation

The credential-free gate covers the trace lifecycle, the waterfall and the
Expand Down
19 changes: 12 additions & 7 deletions docs/meet-tab-audio-manual-check.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,25 @@ re-vendored, or before relying on the mode for a real interview.
confirm Jim moves to it. This step is what catches a broken device list or a
preference that fails to persist, so do not skip it when Jim is already
audible.
3. In Meet, click Present now.
4. Choose "A tab".
5. Select the CodeTrial tab.
6. Enable "Share tab audio" before clicking Share.
7. Confirm Meet's microphone is still the candidate microphone, not "None" and
3. Open "Audio controls" on the stage. Jim's volume and mute set the level of
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
the tab audio Meet captures, and the volume is saved across sessions, so a
low level left from an earlier run reaches the interviewer as a quiet Jim.
Press Reset volume, and confirm the slider reads 100% and "Mute Jim" is not
pressed.
4. In Meet, click Present now.
5. Choose "A tab".
6. Select the CodeTrial tab.
7. Enable "Share tab audio" before clicking Share.
8. Confirm Meet's microphone is still the candidate microphone, not "None" and
not a virtual device.
8. Record 10 seconds in Meet while Jim is speaking and the candidate says one
9. Record 10 seconds in Meet while Jim is speaking and the candidate says one
sentence.

## Pass criteria

All five must hold:

- The recording contains Jim's voice.
- The recording contains Jim's voice at the level set in step 3.
- The recording contains the CodeTrial tab video.
- The recording contains the candidate's voice.
- Meet's selected microphone is still the candidate's physical microphone at
Expand Down
26 changes: 24 additions & 2 deletions scripts/browser-check.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
const { chromium } = require(process.env.PLAYWRIGHT_PATH);
const assert = require("node:assert/strict");
const fs = require("fs");
const { mediaProbeSource, assertJimAudio } = require("./media-probe.cjs");
const { installBargeReplay } = require("./barge-replay.cjs");
const {
FloorCapture,
Expand Down Expand Up @@ -793,6 +794,8 @@ async function isolateRustAgent(
try {
const page = await browser.newPage();
let bargeReplay = null;
let jimAudio = null;
let probingAudio = false;
if (process.env.BROWSER_CHECK_FLOW === "barge") {
await page.addInitScript(
installBargeReplay,
Expand Down Expand Up @@ -2310,6 +2313,10 @@ PatternLexicon.prototype.search = function(word) {
}

await page.goto(interviewUrl(PROBLEM_IDS.twoSum));
probingAudio = credentialed && ["voice", "barge"].includes(flow);
if (probingAudio) {
await page.evaluate(mediaProbeSource());
}
await clearMediaGate(page);
if (credentialed && !rustAgentIdentity) {
roomName = await page
Expand Down Expand Up @@ -2478,6 +2485,14 @@ PatternLexicon.prototype.search = function(word) {
);
}
}
if (probingAudio) {
jimAudio = await page.evaluate(() =>
globalThis.__finishJimAudioProbe(),
);
if (process.env.BROWSER_CHECK_CAPTURE)
writeIncompleteCapture({ jimAudio, bargeReplay });
assertJimAudio(jimAudio);
}
if (credentialed && flow === "barge") {
if (!floorLogPath)
throw new Error(
Expand Down Expand Up @@ -2543,6 +2558,7 @@ PatternLexicon.prototype.search = function(word) {
? readFloorRecords(floorLogPath)
: [],
bargeReplay,
jimAudio,
latencyLog: (agentOutput.join("") + "\n" + readServerLog())
.split("\n")
.filter((line) => line.includes("codetrial latency "))
Expand All @@ -2557,10 +2573,16 @@ PatternLexicon.prototype.search = function(word) {
})(),
agentFailure ?? never,
]).catch(async (error) => {
if (process.env.BROWSER_CHECK_CAPTURE && floorLogPath)
if (probingAudio && !jimAudio) {
jimAudio = await page
.evaluate(() => globalThis.__finishJimAudioProbe(true))
.catch(() => null);
}
if (process.env.BROWSER_CHECK_CAPTURE && (floorLogPath || probingAudio))
writeIncompleteCapture({
floorRecords: readFloorRecords(floorLogPath),
floorRecords: floorLogPath ? readFloorRecords(floorLogPath) : [],
bargeReplay,
jimAudio,
});
console.error(
redact(
Expand Down
Loading