The interviewer asks about something you genuinely did — three years ago, on a project whose details have gone soft. You know you solved it. You cannot produce the specifics in the four seconds before the pause turns awkward.
So I designed a second screen for that moment — and have just started building the MVP.
interviewer asks → "How did you handle cluster backups?" ▸ etcd snapshots to object storage, nightly ▸ restore rehearsed quarterly — ~8 min end to end ▸ first rehearsal caught uploads silently failing The idea: it listens to the interviewer through system audio, searches a folder of notes I wrote about my own projects, and puts three or four short reminders on a second screen the moment a question ends.
Not an answer to read out — reminders. I see my own work and tell the story myself, out loud. A teleprompter that only knows my biography, and only ever speaks in fragments.
This is an MVP I have only just started. The pipeline is written and each piece is verified on its own, but it has never run end to end against a live conversation. What follows is the design and the reasoning — not a finished product.
The 800 ms rule
A hint that arrives after I have started answering is worse than no hint at all: it pulls my eyes off the camera to read something I no longer need.
So the target is under 800 ms from the end of the question to the first bullet. Every other choice is downstream of that number.
system audio (the interviewer) → audio/capture.py PipeWire sink monitor → PCM, 16 kHz mono, 100 ms chunks → stt/deepgram.py Nova-3 streaming; speech_final → trigger → rag/search.py FTS5 + sqlite-vec, weighted RRF, ~20 ms, no network → llm/groq.py Llama-3.3-70b, bullets streamed line by line → Teleprompter.tsx dark panel, latency badge per answer | Stage | Budgeted |
|---|---|
| PCM chunk from PipeWire | ~20 ms |
| Deepgram streaming STT | 150–200 ms |
| Hybrid retrieval | ~20 ms |
| Groq generation to first bullet | 250–400 ms |
| Render | ~30 ms |
| Total to first bullet | 470–670 ms |
Two consequences worth naming. Stream at every hop — each stage emits as soon as it has something usable, and stream_bullets() buffers until a newline, because half a bullet on screen is worse than none when it is about to be read aloud. Trigger on speech_final, not a timer — Deepgram's endpointing decides when the interviewer stopped; acting on interim text fires three or four times per question, each on a fragment of a sentence.
Three things measurement killed
Every rejected option was rejected on a number, from hardware I already had.
A local LLM. The version I wanted — nothing leaves the laptop, no keys, no bill. Gemma 3 1B on this machine: 12.7 seconds to the first token, 15–19 seconds per reply. Two orders of magnitude outside the budget; no quantisation closes a gap that size.
Local speech-to-text (faster-whisper). Whisper is batch-oriented — the clock only starts when the question ends. Measured here, medium runs at roughly 2× realtime: an eight-second question costs sixteen seconds. small is faster and visibly worse on technical vocabulary, which is exactly the vocabulary that matters when the question is about etcd.
Qdrant, or any vector DB in a container. The corpus is a handful of markdown files. Retrieval against one SQLite file with sqlite-vec and FTS5 is ~20 ms — and a container is one more process that has to already be running five minutes before an interview.
That last one is the rule the whole app runs on: it serves one user on one machine, and anything that would make it scale is something that can fail to start at the worst possible moment. One FastAPI process, one websocket, one browser tab. A session is a websocket connection.
The claim I had to retract across the whole repository
I wrote — in the README, in a source comment, in the dev script, in the tracker card — that without headphones the copilot would hear me instead of the interviewer, because my voice would leak into the capture through the speakers.
That is wrong. It was never true.
A microphone is a source. What the app records is the monitor of the output sink — only what the system plays. The two never meet unless something deliberately routes one into the other (module-loopback, pw-loopback, mic self-monitoring). None of those is a default.
The part that stings: the disproof was already in my own verification run. I had pointed the capture probe at a live microphone in a room that was not silent and watched the level hold at exactly 0.000 for four seconds, moving only when I played audio. I wrote that number down, then wrote the opposite in the docs — because I was carrying an intuition over from a different project, where the leak genuinely happens in the other direction.
The lesson about audio on Linux: direction is everything, and it is counter-intuitive enough that you verify it per project instead of reasoning from memory. A capture level that stays at 0.000 while audio plays is pointed at the wrong node — and that failure is invisible from the UI.
A spoken question is not a search query
The retrieval layer came from my second-brain repo, where it had worked for months. It broke immediately here.
FTS5 receives the question as query syntax, and a real question is not a keyword list:
"So, how did you protect cluster state?"
BM25 scored how, did and you alongside the words carrying the meaning. Conversational filler outvoted cluster state, and the top hit came back as a Terraform note instead of the Kubernetes one. For a search box that is an edge case; for a live interview it is the normal input mode.
So stopwords are dropped before tokens reach FTS5 — with one exception list, because nothing short that a DevOps answer might hinge on can be filtered: CI, CD, VM, DB, S3, k8s all survive. A stopword list built for prose will cheerfully eat the most important token in the sentence.
What crosses the network
| Data | Goes to | Stored |
|---|---|---|
| Audio of the system output (the interviewer) | Deepgram | no |
| Transcribed question text | Groq | no |
| Retrieved fragments of my notes | Groq | no |
| My microphone | nowhere — never captured | no |
| Generated bullets | stay in the browser tab | no |
Nothing is written to disk during a session: no transcript, no history, no record of what was asked. And nothing speaks on my behalf — there is no voice and no avatar, because the candidate answering is the entire point.
Two honest caveats. The interviewer did not consent to being transcribed, and whether that is lawful depends on where both parties are; the fair framing is closer to "I am taking notes with software" than to "I am recording this call", but it is not obviously neither. The knowledge base goes to Groq with every question — that is what retrieval is — so it holds the shape of the work ("3-node cluster, etcd snapshots nightly, restore ~8 minutes") and never hostnames, credentials or NDA client names.
What is verified, and what is not
| Audio capture | ✅ verified — 39 chunks in 4 s (expected ~40), level moved only on playback |
| Retrieval | ✅ verified — paraphrased, cross-language and exact-tool queries land correctly |
| Panel | ✅ verified — three states, console clean |
| Full pipeline end to end | ⚠️ code complete, never run live — no API keys in it yet |
| Knowledge base | ❌ one example file, with invented numbers |
Which means the 800 ms budget is calculated, not measured. The latency numbers on the panel come from a scripted session that speaks the real protocol so the frontend can be built without credentials. Until it has run against live Deepgram and Groq, those are projections — calling them results would be a lie with a number attached.
I will come back with measured numbers once the keys are in and it has survived a real conversation.
About the author
I'm Alex — a DevOps engineer. AlterEgo DevOps is my own project: a copilot that reads my own notes back to me while I answer. I write about the engineering decisions behind what I build — including the ones measurement talked me out of.
- 🐙 Project on GitHub: AlterEgo-DevOps
- 💼 LinkedIn: alex-d
- 🐦 X: @mainoceanm
- 📣 Telegram — DevOps notes: join the channel
- ✉️ mainoceanm@gmail.com
Which interview question do you always wish you had answered better? Tell me in the comments.
