---
title: Meetings (record a meeting and get notes out of it)
---

# Meetings (Meeting notes)

## What it is

Experimental, in-development feature that records a meeting **without a bot in the
call**, transcribes it, and turns the transcript into AI-enhanced notes —
all inside Omniscio. It captures your microphone **and** the meeting's
system audio straight off your own machine, so nothing joins the call and no
third-party bot ever appears. Both audio streams go to **the transcription engine
you choose**, producing one **source-tagged transcript** (each line labelled **Me**
or **Them**). **No audio file is ever saved** — the transcript is the only artifact
of the audio. If you're offline or have no key for the engine you picked, the
meeting is still saved as a **notes-only** meeting (your typed notes, no
transcript).

A **Meetings** tab in the sidebar shows three screens: a **list** of past meetings,
a **live recording** page, and a **finished note** page. Opening a meeting from the
list lands on the note page; tapping "New meeting" opens the recording page.

## Where to find it

The **Meetings** surface in the app — its list, a meeting's own page and the settings that configure it. The switch that turns the whole feature on lives in Settings, so nothing appears until you enable it.

## How it behaves

### How to turn it on

1. Settings → Lab → enable **Meeting notes** (the master toggle; hidden until you
   flip it because the feature is still in development).
2. A **Meetings** tab appears in the sidebar. Open it.
3. Transcription works **out of the box** — the default engine is the free
   **Built-in** one, which runs on your own computer and needs no key and no
   account. Nothing to set up.

### Choosing a transcription engine

Settings → Meetings → **Transcription engine**. Six engines, and the choice is
yours; every paid one bills **your own key** (there is no company-funded
transcription), saved under Settings → Voice and shared with dictation — save it
once and both use it.

| Engine | Transcript appears | Cost |
|---|---|---|
| **Built-in** (default) | when you stop | **free, no key** |
| Grok (xAI) | when you stop | your key, bills 2 streams |
| Groq Whisper | when you stop | your key, bills 2 streams |
| Deepgram Nova-3 | **live during the call** | your key, one 2-channel stream |
| Google Gemini | **live during the call** | your key, bills 2 streams |
| ElevenLabs Scribe | **live during the call** | your key, billed per character |

Two things to know when picking:

- **"Live" vs "when you stop"** — a live engine paints words into the floating
  transcript window as they're spoken. A stop-engine shows nothing during the call
  and delivers the whole transcript the moment you press Stop. Both give you the
  same finished notes.
- **"Bills 2 streams"** — a meeting has two people's audio. Deepgram can carry both
  down one connection; the others need two, so a meeting-minute costs roughly
  double. Your daily spending cap (Settings → Meetings) counts every paid engine.

Switching engines mid-meeting is safe — the running recording keeps the engine it
started with, and your choice applies to the next one.

### The three screens

### List screen

Day-grouped rows listing your recorded meetings. Tap a row to open the note page.
Tap **New meeting** to start a recording. Empty state is shown when there are no
meetings yet.

### Live recording page

- **Serif title field** — type a title before or during recording (kept when you
  stop).
- **Notes textarea** — jot rough notes while the meeting runs; these become the
  raw notes on the finished note page and steer the AI enhance step (see below).
- **Quick Note consent card** — the very first time you start a recording, a
  one-time card explains that Omniscio will transcribe and write notes for you and
  reminds you to get consent from other participants. Acknowledging it starts
  capture and is remembered permanently; it never appears again.
- **Recording pill** at the bottom — waveform icon, live elapsed timer, and a
  **Stop** button. Title and notes are persisted only when Stop is tapped (carried
  into `meetings:create`).
- **Live transcript HUD** — while recording, a small frameless, always-on-top
  capsule floats over your call. **Hover it and it grows** to the full **Me / Them**
  transcript panel, scrolling the dialogue live as each line lands. It appears when
  the capture goes live and disappears when you Stop (or the capture ends/fails).
- **Known v1 limitation**: leaving the Meetings panel mid-recording does not stop
  capture; the capture host in main keeps running until Stop is pressed.

### Finished note page

- **Serif title** + date **Pill**.
- **Enhanced / My notes toggle** (`SegmentedControl`) — switch between the
  AI-enhanced view and your own editable raw notes. Enhanced falls back to your
  raw notes if AI enhance hasn't been run yet, so the page is never blank.
- **Markdown body** — the enhanced (or raw) notes rendered with full GFM markdown.
- **Action items** — checkbox list extracted by the AI enhance step.
- **Transcript** — the saved source-tagged transcript as **Me / Them** chat
  bubbles (hidden entirely for an offline / notes-only meeting).
- **Enhance button** (top bar, sparkles icon) — runs a single Claude pass to
  produce enhanced notes, a summary, and action items. The **transcript is the
  primary source**: the enhanced notes are built from the Me/Them dialogue, and your
  typed notes still steer. Heading-steered: any markdown headings you typed in your
  rough notes (`#`, `##`, `###`, …) are preserved verbatim as sections in the
  enhanced output, filled from the transcript. A notes-only meeting (no transcript)
  is enhanced from your notes alone.
- **Use as context** button (top bar) — copies the full meeting context block
  (title + date + summary + notes + action items + transcript) to the clipboard
  with a toast, so you can paste it into a new Claude session yourself. **It never
  spawns or runs a session** — the cost-safety guarantee is absolute.
- **Linked board items** — when Mission Control is enabled, a section below the notes shows
  chips for each PM board item linked to this meeting. Clicking a chip navigates to
  Mission Control; hovering reveals an X to unlink. A link-toggle button opens an inline
  search bar (250ms debounce, filters already-linked items) to link more items.
- **⋯ menu** (More actions) — four items:
  - **Copy notes** — copies the current view's notes to clipboard.
  - **Send notes via email** — opens your default mail client with a pre-filled
    compose (subject = meeting title, body = enhanced or raw notes + action items).
    It opens a compose window only; it never auto-sends. Very long notes are
    truncated with a notice.
  - **Send to board** — shown when Mission Control is enabled AND the meeting has unchecked
    action items. Opens a modal to pick a board and group, then creates one PM item
    per unchecked action item and links each back to the meeting.
  - **Move to trash** — soft-deletes the meeting with a brief **Undo** toast.

### What you can do

- **Record (no bot)** — capture mic + system audio simultaneously. The two sit on
  separate channels (your voice = **Me**, everyone else = **Them**) so the live
  transcript can tell you apart from the other participants. Nothing joins the
  meeting and no audio file is written.
- **Heading-steered AI notes** — type markdown headings in your rough notes while
  recording; they become required sections in the enhanced output, so the AI fills
  them in from the transcript rather than choosing its own structure.
- **Edit your own notes** — the raw notes field is editable on the My notes tab and
  saved back to the meeting automatically.
- **Delete with undo** — deleting a meeting is a soft-delete with an undo toast; the
  meeting reappears if you undo.

### Auto-detect (call HUD)

A separate opt-in sub-feature that notices when you join a call and offers to take
notes, without you having to open Omniscio first.

- **What it does** — a small frameless **"Call detected — &lt;app&gt;"** toast pops up
  in the top-right of your screen, always on top of the call window. It has a
  **Take Notes** button and a **✕** to dismiss. The toast is fixed in place (only
  the live transcript HUD can be moved). It clears on its own once the call ends.
- **How a call is detected (hybrid signal)** — Omniscio fires the toast only when BOTH
  are true at once: your **microphone is live** AND Omniscio can **name the app holding
  it**. The mic alone won't trigger it (so dictation or a voice memo never pops the
  toast), and a call app that's merely open — but not holding the mic — won't either.
  The identified app is only used to label the toast.
- **Which app holds the mic** — on **macOS** a native CoreAudio probe reports the
  apps capturing input; on **Windows** the **microphone consent store** does (Windows
  records every app's mic access there, and the entry still open is the one in the
  call). Both give the accurate answer, so detection survives you alt-tabbing away
  from the call window. When an app can't be attributed this way, Omniscio falls back
  to the **foreground window** — a native call app (Zoom, Microsoft Teams, WhatsApp,
  Webex, Slack, Discord, FaceTime) or a browser, whose window title names the in-page
  call (Google Meet, Zoom Web) when readable. When several apps hold the mic at once, a
  known call app outranks an unknown one, so a dictation tool holding the mic alongside
  Zoom never out-labels the call. Omniscio's own capture and dictation are excluded.
- **Platform coverage** — the "is the mic in use" check runs on macOS, Windows and
  Linux (CoreAudio probe, Windows consent store, `pactl`), and naming the app is wired
  on **macOS and Windows**. Linux has the mic check only, so no toast fires there; every
  check is best-effort and silently reports "no call" if it can't run, so turning the
  feature on is harmless on any OS.
- **Take Notes** — focuses Omniscio's main window and opens the Meetings **recording
  page** so you can start capturing in one click. On the first use it still shows
  the Quick Note **consent** card first.
- **Cost safety (absolute)** — detecting a call and pressing Take Notes **never
  starts recording on their own and never spawns a paid Claude session**. Take Notes
  only navigates you to the consent-gated recording page; you decide whether to
  record. Detection itself costs nothing.
- **How to turn it on** — Settings → Lab → enable **Meeting notes** (the master
  toggle) AND **Meeting call auto-detection**. Both are off by default and the
  auto-detect toggle does nothing unless Meeting notes is also on (Take Notes needs
  the Meetings panel).

### Settings

A **Meetings** section appears in Omniscio's Settings (Workflow & AI group) once **Meeting
notes** is enabled. It gathers the meeting-specific preferences:

- **Transcription engine** — which service transcribes your meetings. Defaults to the
  free Built-in engine. See "Choosing a transcription engine" above.
- **Transcription language** — the language you speak in meetings, sent to the engine so
  transcription is accurate. Defaults to English.
- **Summary language** — the language your AI-enhanced notes and summary are written in
  (independent of what was spoken). Defaults to English.
- **Internal jargon** — a comma-separated list of product names, people, and codenames
  (e.g. "Moove, Callipo, Project AlphaDuck"). These are fed to both live transcription
  and the AI enhance step so names stop getting mangled. A live counter shows how many
  terms you've entered.
- **Speaker attribution** — on by default; when off, transcripts render without the
  Me/Them distinction and copied transcript text drops the speaker labels. The
  underlying diarization is always stored, so turning it back on is retroactive.
- **Live transcript indicator** — on by default; controls whether the floating live
  transcript window appears while a meeting is being transcribed.
- **Auto-delete transcripts** — a privacy dial: Off (default), 30 days, 90 days, or 1
  year. A daily background job deletes transcripts **and any recording** older than the
  chosen period, but **always keeps your notes, AI-enhanced notes, summary, and action
  items**. A purged note shows "Transcript deleted by your retention policy" where the
  transcript used to be.

### Data & danger zone

At the bottom of the section:

- **Export all meeting data** — pick a folder; Omniscio writes one markdown file per meeting
  (title, date, notes, summary, action items, transcript) plus a `meetings.json` backup,
  then opens the folder.
- **Delete all meeting data** — a confirm-gated wipe that hard-deletes every meeting,
  transcript, and recording from your computer. There is no undo.

### Share as Google Doc

The **•••** menu on a finished note has **Share as Google Doc**. It creates a Google Doc
(titled "«Meeting title» — «date»") from the meeting's notes in your own Google Drive and
opens it. The doc is created private to you; sharing it with teammates happens in Drive,
so your own Google Workspace org controls access. Requires a connected Google account and
the Google Docs export feature enabled; it never shares automatically.

### People, Companies & Trash

A bottom icon strip in the Meetings sidebar — **Recipes / People / Companies /
Trash** — sits below the folder tree and switches the panel between the chat
recipes browser and three new views built entirely off the meetings you've
already recorded.

- **Attendee capture** — when a recording starts, Omniscio best-effort matches the
  calendar event happening right now (Google Calendar only) and snapshots its
  attendees onto the meeting. It prefers an event with a video-call link and
  allows joining up to 10 minutes early ("early-join grace") so a call you
  hopped into a few minutes ahead of its start still matches. This **never
  blocks or delays recording** — a missing calendar connection, being offline,
  or no overlapping event just means no attendees are captured, silently.
  **First-write-wins**: a meeting that already has a calendar snapshot is never
  re-matched, so resuming an old note while a different meeting is live on your
  calendar can't overwrite the original attendees.
- **People** — one row per attendee email seen across every meeting (an
  aggregation, not a stored table), sorted by most recent meeting. A synthetic
  "Me" row always counts every meeting you've recorded. Personal mail domains
  (Gmail, Outlook, iCloud, Yahoo, …) are excluded from becoming Companies but
  their people still show up here.
- **Companies** — one row per work-email domain seen across attendees, with a
  people count and a last-meeting date. Purely derived from attendee data —
  there's no company table to manage or edit.
- **Person / Company detail pages** — opening a row shows every meeting that
  person or company's domain appeared in, your own notes scoped to just those
  meetings, and an Ask-anything bar whose chat context is scoped the same way
  (only that entity's meetings feed the answer).
- **Trash** — a popover off the Trash icon lists soft-deleted meetings with
  **Restore** and confirm-gated **Delete forever** (permanent — only ever
  removes a meeting already in Trash). Trashed meetings are automatically
  purged after 30 days by a background sweeper that runs shortly after startup
  and every 24 hours after that.

Like the rest of Meetings, this is Lab-gated behind **Meeting notes**
(`meetingsEnabled`) — it isn't on the public help site until the feature ships.

### Scheduled meeting digest

The **"Prep my day"** and **"Catch me up"** meeting recipes normally run only when you
click them (they seed a chat with your recent meetings and you send it). You can also have
one run **automatically on a daily or weekly timer** — the scheduled meeting digest.

- **Opt-in, OFF by default.** Nothing runs on a schedule until you turn it on, so it never
  costs anything until you choose it. It's settings-controlled (no dedicated toggle row yet):
  `meetingDigestScheduleEnabled` (off), `meetingDigestScheduleCadence` (`daily`/`weekly`),
  `meetingDigestScheduleHour` / `…Minute`, `meetingDigestScheduleDayOfWeek` (for weekly), and
  `meetingDigestScheduleRecipeId` (`prep-my-day` or `catch-me-up`).
- **When it fires** it starts the SAME recipe you'd get by clicking it — a normal Claude
  session that gathers your calendar/meeting context via its own tools — in the background,
  so it never steals your window. **Each fire is one real (paid) session**, so this is a more
  deliberate choice than the cheap daily/weekly inbox digests.
- **Fires once per period** (a per-day marker prevents a double-run), and it does **not**
  catch up a missed fire at startup (so a restart never triggers a surprise session) — the
  next scheduled day simply runs.
- Shares the same daily/weekly schedule engine as the Daily Digest and Weekly Summary
  (`isReportDue`); see the [scheduled-report-engine contract](../../.claude/memory/contracts/scheduled-report-engine-contract.md).

### Consent & cost

- **Consent**: recording a conversation can be regulated where you live. Omniscio shows
  a one-time notice (Quick Note card) before your first recording; acknowledging it
  is your responsibility, not legal advice.
- **Cost — transcription**: the default Built-in engine is **free** and costs nothing.
  Every other engine bills **your own key** — there is no company-funded
  transcription. A meeting carries two people's audio, so a paid engine costs roughly
  **2× a single-channel stream** (except ElevenLabs, billed per character, where
  splitting the audio doesn't change the total text). Paid spend is logged to
  Omniscio's spend ledger under **"Meetings"** (Stats → Spend) and counted against the
  daily cap — which covers **every** paid engine, not just the one you're using.
- **Cost — AI notes**: the enhance step is a single Claude (Haiku-class) call, also
  logged under **"Meetings"**. Browsing, editing notes, deleting, "Use as context",
  and "Send via email" cost nothing.

## For agents

### How it works (for maintainers)

- **Gate**: `src/shared/unreleased-features.ts` (id `meetings`, `settingKey`
  `meetingsEnabled`). Default hidden; flip `status` to `'shipped'` to release.
  Never read the flag raw — route through
  `isUnreleasedFeatureVisible[InRenderer]('meetings', …)`. The sidebar row is gated
  in `Dashboard.tsx`'s `visibleProjects` filter; the panel re-checks in
  `MeetingsView`.
- **Capture** (`src/main/services/meetings-capture/` + renderer host
  `src/renderer/src/meeting-capture-host.ts`): the renderer host acquires the mic
  via `getUserMedia` and system audio via a `getDisplayMedia` loopback (video
  dropped). It then records EITHER one merged **2-channel** feed (mic = channel 0 →
  **Me**, system = channel 1 → **Them**) or the two sources as **separate tagged
  streams**, in a WebM/Opus container or raw PCM16 — whichever the selected engine
  declares. **No audio file is written** — audio is transcribed and discarded (a
  batch engine buffers it in memory only). Channels `meetings-capture:*`.
- **Engine choice** (`src/shared/meetings/stt-capabilities.ts`): the one table
  declaring, per engine, how it attributes Me/Them (`multichannel` / `diarized` /
  `dual-mono`), what audio format it needs, whether it is live or batch, and how many
  streams it bills. The transcriber, the capture host's audio shape, and the Settings
  picker all read it, so the UI can never offer an engine the transcriber can't serve.
  `src/main/services/voice/stt-provider-registry.ts` builds the engine + reads its key
  and is shared with voice dictation.
- **Live transcribe** (`src/main/services/meetings-live-transcribe/`):
  `MeetingLiveTranscriber` owns ONE connection in one of the three source modes; for
  `dual-mono`, `MeetingTranscriberSet` fans a capture across a `me` + `them`
  connection sharing ONE `order` allocator (so the two sides interleave as a
  conversation, not two monologues). Keys are read in MAIN, never crossing IPC. Each
  final is routed through `source-tagging.ts`
  (`sourceForChannel` 0→'me' / 1→'them' / else 'unknown'; `labelForSource`
  Me/Them/Speaker) and accumulated into a `MeetingTranscript`
  (`src/shared/types/meeting-transcript.ts`: `TranscriptSegment { source, speaker,
text, order, … }`, `order` monotonic across both channels). `finish()` returns the
  transcript, or `null` when offline / no key → a **notes-only** meeting (never a
  hard failure). Each finalized line emits `MEETINGS_TRANSCRIPT_SEGMENT` (strict
  payload in `push-event-schemas/meetings-capture.ts`) and appends to the transcript
  HUD via the controller seam.
- **Transcript HUD** (`meetings-live-transcribe/transcript-hud-window.ts` +
  renderer `meeting-transcript-hud.tsx` / `LiveTranscriptHud.tsx`): the always-on-top
  capsule that hover-expands (`MEETINGS_TRANSCRIPT_HUD_SET_EXPANDED` in
  `meetings-transcript-hud-handlers.ts`) to the full Me/Them panel, fed by the push
  and rendered via the shared `MeetingTranscriptBubbles.tsx`; shown/hidden on the
  capture's start/finish lifecycle. Cost source `meeting-transcription` →
  **"Meetings"**.
- **Persist + enhance**: the `meetings` table (migration
  `20260618120000-add-meetings-table.ts`), queries in
  `src/main/db/queries-meetings.ts` (soft-delete + restore), and a one-shot Claude
  enhance call in `src/main/services/meetings-enhance/` — `buildEnhancePrompt(rawNotes,
transcript)` renders the source-tagged transcript as a **Me/Them dialogue**
  (`formatTranscriptDialogue`, sorted by `order`) as the **primary** material, with
  the notes + `#`-heading steering layered on; a null transcript falls back to
  notes-only (no empty transcript block). Cost source `haiku-meeting-enhance` →
  **"Meetings"**. Channels
  `meetings:list/get/create/enhance/delete/restore/update-notes`.
- **UI**: `src/renderer/src/features/meetings/` — `MeetingsView` (list landing +
  router), `MeetingRecordingPage` (recording flow + Quick Note consent gate),
  `MeetingNotePage` (note view with Enhanced/My-notes toggle, email, use-as-context),
  `meetings-store`, `meeting-context.ts` (pure context builder), `meeting-email.ts`
  (pure email builder). `MeetingDetailDrawer.tsx` was deleted as part of the
  Granola restyle. Integration manifest `src/shared/integrations/meetings.ts`,
  virtual project `__meetings__`, mounted in `ui-registry.ts`.
- **Send via email**: renderer builds `mailto:` URL via `open-mailto.ts`; main
  validates scheme is `mailto:` only via `system:open-mailto` IPC
  (`src/main/ipc/system-open-handlers.ts`). URL capped at 16 000 chars
  (`MAX_MAILTO`); body truncated client-side when over-long.

- **Attendees / People / Companies / Trash**: pure aggregation
  `src/shared/meetings/attendee-people.ts` (`aggregatePeople`/`aggregateCompanies`
  over `attendees_json`, personal-domain exclusion, per-meeting dedup); calendar
  matcher `src/main/services/meetings-calendar-match/` (`matchMeetingToCalendar` +
  `pickOverlappingEvent`, best-effort, first-write-wins via the
  `meeting.calendarEventId` guard); queries in `queries-meetings.ts`
  (`setMeetingCalendarMatch`, `listMeetingPeople`, `listMeetingCompanies`,
  `listMeetingsForEntity`, `listTrashedMeetings`, `hardDeleteMeeting`,
  `purgeExpiredTrash`); trash sweeper
  `src/main/services/meetings-trash/index.ts` (`TRASH_RETENTION_DAYS = 30`,
  startup + 24h via `createRegisteredService`); IPC channels
  `meetings:people-list/companies-list/entity-meetings/match-calendar/trash-list/delete-forever`;
  UI `MeetingsSidebarIconStrip`, `MeetingsPeopleView`, `MeetingsCompaniesView`,
  `MeetingEntityPage` (+ `MeetingPersonPage`/`MeetingCompanyPage`),
  `MeetingsTrashPopover`, entities store `meetings-entities-store.ts`.
- **Call auto-detect** (`src/main/services/meetings-call-detect/`): a 5 s
  main-process poller (`meeting-call-detector.ts`) fires on the rising edge only
  when `isMicInUse()` AND `matchCallApp(listProcessNames())` are both true, and
  clears after 2 consecutive idle reads (debounced). `mic-probe/` is the
  cross-platform mic facade (macOS native `resources/bin/mic-probe` built by
  `scripts/build-mic-probe.mjs`, Windows CapabilityAccessManager registry, Linux
  `pactl`; each false-on-error, never throws). The always-on-top toast window
  (`meeting-hud-window.ts`, role `meeting-hud`) renders `CallDetectedToast.tsx`,
  which pulls state on mount via `MEETINGS_HUD_REQUEST_STATE` so it's never blank.
  Take Notes (`meetings-hud-handlers.ts`) focuses main + emits
  `MEETINGS_OPEN_RECORDING` only — never spawns or auto-records. Gated behind the
  `meetings` unreleased feature AND the plain `meetingsAutoDetectEnabled` toggle
  (a sub-setting of Meetings in Settings → Meetings, NOT its own Lab feature).

See the contracts
`.claude/memory/contracts/meetings-live-transcription-contract.md` (live multichannel
capture, the source-tagged transcript, no audio artifact, the transcript HUD, and
enhance-from-transcript), `.claude/memory/contracts/meeting-notes-contract.md`
(recording + persist), and
`.claude/memory/contracts/meetings-call-detect-contract.md` (call auto-detect + the
call HUD) for the load-bearing invariants and the tests that lock them.

## Related

- [meeting-rooms.md](meeting-rooms.md) — the one-click launcher for the meeting links themselves.
- [google-meet.md](google-meet.md) — the Google Meet side of joining and capturing a call.
- [fathom-mcp.md](fathom-mcp.md) — an outside notetaker your meetings can be pulled from instead.

