Coreframe Labs

Automated performance audit

Performance audits for LiveKit & WebRTC

A conversational voice pipeline has roughly 200ms of end-to-end budget before it stops feeling like a conversation. We have shipped one that holds under 200ms; the difference between that and a second-and-a-half of lag is almost never the model — it is how the stages are wired together.

Check a file now — free

Paste one file. The free tier runs a single security pass and returns up to three findings, each with the line of code that evidences it. Nothing is stored, nothing is used for training, and detected secrets are redacted before analysis.

Paste one file0 / 400 lines

Not stored. Not trained on. Secrets redacted before analysis.

What we find most often in LiveKit & WebRTC

Each of these is a real pattern, with the code that causes it and the change that fixes it.

1. Waiting for a complete transcript before starting the LLM

Symptom
Every response feels a beat late, no matter how fast the model is.

The pattern

const transcript = await stt.transcribe(audioBuffer);   // waits for silence
const reply = await llm.complete(transcript);          // waits for full reply
const speech = await tts.synthesize(reply);            // waits for full audio
await publish(speech);

Why it bites
Each stage waits for the one before it to finish completely, so the user hears nothing until the sum of all three elapses. The pipeline is capable of overlapping almost all of that work — this shape simply forbids it.

The fix

// Overlap the stages: stream partials forward as they arrive.
stt.on("interim", (text) => llm.prime(text));

stt.on("final", async (text) => {
  const stream = await llm.stream(text);
  for await (const chunk of stream) {
    // Synthesize and publish sentence-by-sentence — first audio in ~300ms
    // rather than after the full completion.
    tts.push(chunk);
  }
});

2. Waiting for the whole TTS clip before publishing

Symptom
Time-to-first-audio scales with how long the answer is.

The pattern

const audio = await tts.synthesize(fullReply);   // 2s of speech = 2s wait
await room.localParticipant.publishTrack(audio);

Why it bites
Synthesizing to a complete buffer means the user waits for the last word to be generated before hearing the first. A long answer is punished twice — once in generation, once in playback.

The fix

// Publish the track first, then write frames as they synthesize.
const source = new AudioSource(24_000, 1);
await room.localParticipant.publishTrack(
  LocalAudioTrack.createAudioTrack("agent", source),
);
for await (const frame of tts.stream(fullReply)) {
  await source.captureFrame(frame);
}

3. Silent fallback to TURN relay

Symptom
Latency is fine for most users and terrible for a specific subset, usually on corporate networks.

The pattern

const config = { iceServers: [{ urls: "turn:turn.example.com", username, credential }] };
// no STUN entry — every peer relays

Why it bites
Without a STUN server, peers never discover a direct path and all media routes through TURN. That adds a full round trip through your relay and its bandwidth cost, and because it still works, nobody notices until someone measures it.

The fix

const config = {
  iceServers: [
    { urls: "stun:stun.l.google.com:19302" },        // try direct first
    { urls: "turn:turn.example.com", username, credential },  // fall back
  ],
  iceTransportPolicy: "all",
};
// Monitor: getStats() candidate-pair type should be "srflx"/"host", not "relay".

An automated pass reads one file. An audit reads the system.

The tool above is deliberately narrow — one file, one dimension, one pass. The issues that actually take systems down live between files: in the trust boundary nobody documented, the query pattern that only emerges at scale, the cache that was correct until the second tenant arrived. That is what the Coreframe Technical Audit is for — five business days, a written report, a live findings call, and a remediation proposal you can execute with or without us.

£1,500–£2,500 solo · £3,000–£5,000 funded startup · £3,000–£6,000 SME

See what the full audit covers →

Related audits

All stack audits →