Meetings and transcription
Control Center can record a call and hand you back a clean writeup — a summary, action items and decisions — with speech recognition and speaker separation running on your own hardware. Think Granola, but self-hosted and wired into the same workspace your agents live in.
What a meeting is
Section titled “What a meeting is”A meeting is a recorded, transcribed session. It is not the same thing as a calendar event: an event is a scheduled commitment synced from Google, while a meeting is something you actually recorded. The two can be linked, since you can start a recording from an event, but they stay distinct records.
Every meeting is workspace-scoped, like everything else in Control Center. Meetings recorded in one workspace never surface in another.
Where the work happens
Section titled “Where the work happens”“On-device” is the right instinct but the wrong noun. The client captures audio
and streams 16 kHz PCM16 frames to the machine running cc_server. That
host does everything else: it writes the per-channel WAVs under
<dataDir>/meetings/<meetingId>/, transcribes in a worker isolate, filters
echo and diarizes after the fact. On a standalone desktop that host is the same
machine. Pointed at a remote server, it is not — so “nothing leaves my laptop”
is only true when the server is your laptop.
There is one further exception and it is the important one.
Everything before the summary — capture, transcription, echo cancellation, diarization, voiceprint matching — runs on the server with no network call.
Which models and which are optional
Section titled “Which models and which are optional”The embedding and diarization models are force-installed on first boot, so speaker separation works out of the box. The speech (ASR) model is opt-in: it is selectable and several hundred megabytes, so you choose one.
Four ship in the picker, differing in size, language coverage and accuracy:
- Parakeet TDT 0.6B v3 — multilingual, the recommended default (~600 MB)
- Parakeet TDT 0.6B v2 — English (~480 MB)
- Whisper large-v3-turbo — multilingual (~626 MB)
- Whisper base.en — English-only, the small historical default (~198 MB)
Models are the only artifacts the server fetches at runtime; every native library ships in the bundle.
How capture works
Section titled “How capture works”A meeting records two audio channels at once:
- You (“me”): your microphone.
- Them (“them”): the meeting’s audio output, captured by a driver-free loopback — Core Audio process/device taps on macOS, WASAPI loopback on Windows, a PipeWire / PulseAudio monitor on Linux.
Because the two channels are captured separately, the transcript is
speaker-attributed from the start: your words are tagged me, everyone on the
call is tagged them.
Your own voice bleeds from the system output back into the mic and would otherwise be transcribed twice. Two filters stop that. Signal-level AEC3 runs on every remote-mode recording and is required — if the native AEC library cannot load, the recording refuses to start rather than silently streaming an echo-polluted mic. (In-person mode has no far-end reference to cancel against, so AEC is an identity passthrough there.) On top of it, a host-side text-level echo filter runs on every platform.
All OS-level microphone processing is deliberately disabled. Enabling the platform’s own echo cancellation or auto-gain on macOS switches the mic to Voice Processing I/O, which in this build killed both the mic and the system-audio tap.
Transcription and diarization
Section titled “Transcription and diarization”While you record, audio is decoded in rolling windows — cut on a short trailing silence, or at a maximum window length — off the UI thread. Each window becomes a speaker-tagged transcript segment with millisecond offsets.
Windows that never rose above an RMS threshold are skipped without being decoded at all, because a speech model renders silence as hallucinated non-speech tokens and the decode is wasted CPU. The practical consequence: a consistently very quiet speaker produces no transcript. If someone comes back empty, check their input level before blaming the model.
After you stop, diarization runs offline over the recording and splits the
remote channel into individual speakers (Person 1, Person 2, …) that you can
rename. The number of speakers is inferred from a clustering threshold, not from
a count you set — so it is a threshold artifact and a quiet or overlapping
speaker can be merged into a neighbour. The transcript is rendered as
[mm:ss] SPEAKER: text lines.
Voice profiles
Section titled “Voice profiles”Renaming a speaker does more than label one transcript. Control Center can save the speaker’s voiceprint as a voice profile, so it recognizes them automatically in future meetings. When you name a diarized speaker, you’re asked whether to save the voiceprint; if you do, it’s blended into a running centroid for that name and matched against new speakers by cosine similarity, so the same person shows up with their name next time without you relabeling them.
Voice profiles are workspace-scoped and never cross the boundary. Renaming a speaker who was previously saved un-enrolls the old name and offers to enroll the new one; deleting a profile removes the stored voiceprint but leaves any names already applied to past meetings intact.
The summary pipeline
Section titled “The summary pipeline”When a recording stops, the server publishes a MeetingRecordingStopped
domain event. The built-in
meeting_summary pipeline template is triggered by that event. The recorder
doesn’t wait on it; the meeting simply transitions through its status lifecycle:
recording → processing → doneThe summary agent receives the title, your rough live notes and the transcript, and returns structured JSON. The pipeline’s persist steps then write that JSON to discrete rows:
enhancedNotesandsummary→ the meeting’s notes- each action item → a
MeetingActionItemrow (content, owner, optional ticket link) - each decision → a
MeetingDecisionrow
Action items and decisions are never parsed out of free-form markdown, only
from the agent’s structured arrays. If a run produces no structured output, the
persist steps are skipped and the raw transcript is kept as a fallback, so you
never lose the record. Nothing in the pipeline marks the meeting done — a
single reconciler does that from the run’s terminal event, so a sibling step
failing cannot strand a half-written meeting that already looks finished.
Where meetings show up
Section titled “Where meetings show up”| Surface | What it shows |
|---|---|
/meetings |
The list of meetings, with action-item and decision counts |
/meetings/record |
The live recording HUD: your notes on one side, the streaming transcript on the other |
/meetings/:meetingId |
A meeting’s detail: Notes, Transcript, Action items and Decisions tabs |
Platform support
Section titled “Platform support”Capture is driver-free on all three desktop platforms (Core Audio taps on macOS, WASAPI on Windows, PipeWire on Linux).
The web client records too, which the rest of this page’s desktop framing
can hide: the browser takes your mic through getUserMedia and the meeting
audio through getDisplayMedia (screenshare), downsamples to 16 kHz PCM16 and
streams to the same host. Three caveats apply there:
- The shared surface must carry an audio track. Safari, Firefox and macOS full-screen sharing yield none and the recording refuses to start.
- There is no signal-level AEC and no input level meter; only the host-side text echo filter applies.
- “Re-run summary” and “cancel processing” are inert on web.
The calendar’s Start recording & link action is desktop-only and reports that plainly on web, even though the meetings screen’s own record flow works there.
See also
Section titled “See also”- Calendar and scheduling: record a meeting straight from a calendar event
- Pipelines and automation: the engine behind
meeting_summary - Tickets and delegation: link a meeting’s action items to tickets
- Domain events:
MeetingRecordingStoppedandMeetingStartingSoon - Record and summarize a meeting