204 lines
10 KiB
Markdown
204 lines
10 KiB
Markdown
# Plan: meeting notes, and shipping this as an internal Mac app
|
||
|
||
Two separate pieces of work. The meeting-notes feature is buildable today. The
|
||
packaging work is mostly not a coding problem — it's a signing, permissions and
|
||
distribution problem, and one policy question that should be answered before
|
||
any of it starts.
|
||
|
||
Nothing here is implemented yet.
|
||
|
||
---
|
||
|
||
## Part 1 — Transcribing the other side of a meeting
|
||
|
||
### Is it possible?
|
||
|
||
Yes. macOS 26 has two ways to capture audio that is being *played*, neither of
|
||
which needs a virtual audio device like BlackHole or Loopback:
|
||
|
||
| Approach | API | Notes |
|
||
| --- | --- | --- |
|
||
| **Core Audio process tap** | `AudioHardwareCreateProcessTap` + `CATapDescription` | Confirmed present in the installed SDK (`AudioHardwareTapping.h`). Can tap one process — e.g. only Zoom — rather than everything. Preferred. |
|
||
| **ScreenCaptureKit** | `SCStream` with audio only | `pyobjc-framework-ScreenCaptureKit` 12.2.1 is on PyPI. Simpler bindings, but conceptually "screen recording", and captures system-wide. |
|
||
|
||
Both need a TCC grant (Screen Recording / Audio Recording), which is currently
|
||
not granted. Same one-time prompt as the other permissions.
|
||
|
||
### Design sketch
|
||
|
||
Run it as a **second, independent pipeline** in the same process, not as a
|
||
branch of the conversation pipeline. It has a different job, a different
|
||
lifetime, and must not interfere with turn taking.
|
||
|
||
```
|
||
Zoom process audio ──► tap ──► Apple STT ──► transcript writer ──► notes/2026-08-07-standup.md
|
||
your microphone ──► tap ──► Apple STT ──┘
|
||
```
|
||
|
||
Points that matter:
|
||
|
||
- **Tap the mic separately from the output.** Two streams gives you speaker
|
||
attribution for free — output is "them", mic is "you" — without any
|
||
diarization model. This is the single biggest quality win available and it's
|
||
nearly free.
|
||
- **Reuse `apple_stt.py`.** It already transcribes accurately at 0.05–0.10 s
|
||
per utterance, and needs no model download.
|
||
- **Long-form transcription needs chunking.** `SFSpeechRecognizer` is built for
|
||
utterances, not hour-long meetings; recognition tasks end on their own. The
|
||
notes pipeline will need to segment on silence and restart tasks, and that
|
||
behaviour needs to be verified against a real long meeting before trusting
|
||
it. This is the main technical unknown.
|
||
- macOS 26 also ships `SpeechAnalyzer`/`SpeechTranscriber`, which is *designed*
|
||
for long-form audio and would be a better fit — but it's a Swift-only API and
|
||
unreachable from pyobjc. See the packaging section; this is one of the
|
||
stronger arguments for a Swift shell later.
|
||
|
||
### Turning it on and off
|
||
|
||
Two triggers, and they're not equivalent:
|
||
|
||
- **Automatic on Zoom.** Detection is trivial — Zoom is running right now
|
||
(`/Applications/zoom.us.app`, pid 89302) and installs its own
|
||
`ZoomAudioDevice` Core Audio driver. But *process running* is a poor signal:
|
||
Zoom idles in the background for hours. Better to trigger on Zoom actually
|
||
holding an active audio stream, or on a calendar event from
|
||
`meta calendar` — which knows when a meeting is genuinely scheduled.
|
||
- **By voice.** Don't do this by pattern-matching transcripts. Claude is
|
||
already in the loop, so expose `start_meeting_notes` / `stop_meeting_notes`
|
||
as SDK MCP tools and let him call them. That handles "start taking notes",
|
||
"actually stop that", and "are you recording?" without any string matching,
|
||
and it's a handful of lines.
|
||
|
||
### Before building this: the consent question
|
||
|
||
This records other people. Worth settling first, because it shapes the design
|
||
and it's much cheaper to answer now:
|
||
|
||
1. **Meta already has an official AI notetaker** with transcripts and
|
||
summaries, reachable from `meta calendar`. If that covers the need, this
|
||
feature is redundant — and it's already been through review.
|
||
2. **Some jurisdictions require all-party consent** to record a conversation.
|
||
A personal tool that silently captures colleagues is a different thing from
|
||
one that announces itself.
|
||
3. **Distributing a recorder internally is a compliance matter**, not just an
|
||
engineering one. This is the single biggest risk to the "share it with the
|
||
team" goal, and it applies to the meeting-notes feature specifically — not
|
||
to the voice assistant.
|
||
|
||
Concrete suggestions if it goes ahead: default it **off**, require an explicit
|
||
per-meeting start, make it obvious when it's running, and keep transcripts
|
||
local with a retention policy. Get a read from Privacy/Legal before it goes to
|
||
anyone else's machine.
|
||
|
||
**Recommendation:** build it for your own use behind an off-by-default flag,
|
||
and treat "ship the notes feature to the team" as a separate decision gated on
|
||
that review. The voice assistant itself carries none of this baggage and can
|
||
ship first.
|
||
|
||
---
|
||
|
||
## Part 2 — Packaging as an internal Mac app
|
||
|
||
### What's actually hard
|
||
|
||
Not the code. Three things:
|
||
|
||
**1. Permissions are the whole reason to do this.** You just granted Input
|
||
Monitoring to your terminal and noted it'll take effect on restart — that
|
||
fragility *is* the argument for a real app. TCC grants attach to a code-signed
|
||
bundle identity. Today the grants belong to your terminal, so they're shared
|
||
with everything else you run there and break when the terminal changes. A
|
||
signed `.app` with a stable bundle ID asks once, keeps it, and shows up in
|
||
System Settings under its own name. This app needs four: Microphone, Speech
|
||
Recognition, Input Monitoring, and (for meeting notes) Screen/Audio Recording.
|
||
|
||
**2. Signing.** `security find-identity -p codesigning` returns **0 valid
|
||
identities** on this machine. Unsigned or ad-hoc-signed apps have their TCC
|
||
grants invalidated on every rebuild, which makes the app unusable in practice.
|
||
This needs a real signing identity from whoever owns Mac app distribution.
|
||
It also intersects with the binary-approval policy on managed Macs: a
|
||
hand-built `.app` handed to a colleague will be killed on launch unless it
|
||
comes through the sanctioned channel.
|
||
|
||
**3. Distribution.** **Managed Software Center** is installed — that's the
|
||
internal channel, and the answer to "how do I share this". It also solves
|
||
signing and approval, because packages that ship through it are already
|
||
handled. The work is conforming to whatever that team requires, not inventing
|
||
a mechanism.
|
||
|
||
### Size: fixable, and the fix is cheap
|
||
|
||
The venv is 1.7 GB, which is a non-starter for distribution. Almost all of it
|
||
is for engines that are no longer the default:
|
||
|
||
| Package | Size | Needed by |
|
||
| --- | --- | --- |
|
||
| torch | 529 MB | mlx-whisper only |
|
||
| claude_agent_sdk `_bundled` | 260 MB | its bundled CLI — **we deliberately don't use it** |
|
||
| mlx | 183 MB | mlx-whisper |
|
||
| llvmlite + numba | 156 MB | mlx-whisper |
|
||
| scipy + sympy | 171 MB | mlx-whisper (scipy also used by the resampler) |
|
||
| onnxruntime | 69 MB | Kokoro, Silero VAD |
|
||
| av | 45 MB | faster-whisper |
|
||
|
||
Switching the shipped default to **Apple STT + Apple TTS + push-to-talk** drops
|
||
Whisper, Kokoro *and* Silero. I verified this: importing only the Apple-path
|
||
modules pulls in `scipy` and nothing else heavy, whereas importing `bot.py`
|
||
pulls in all nine. A realistic app is **250–300 MB**, most of which is the
|
||
Python runtime and pyobjc.
|
||
|
||
Two concrete prerequisites:
|
||
|
||
- Make the Whisper/Kokoro imports lazy. `bot.py` imports them at module scope
|
||
today, so they'd be bundled even when unused.
|
||
- Exclude `claude_agent_sdk/_bundled` (260 MB). The app must call the installed
|
||
`claude` CLI anyway — it's the only build that can reach the gateway — so
|
||
**Claude Code at Meta becomes a documented prerequisite**, not something the
|
||
app ships.
|
||
|
||
### Shape of the app
|
||
|
||
A **menu-bar app** fits the interaction model: no window, hold a key to talk, a
|
||
menu for voice/model/notes toggles, and an indicator showing when the mic is
|
||
live and when notes are recording.
|
||
|
||
| Option | Effort | Trade-off |
|
||
| --- | --- | --- |
|
||
| **A. Python + py2app + `rumps`** | Low | Reuses everything as-is. Bundles a Python runtime; py2app + pyobjc + code signing is fiddly but well-trodden. |
|
||
| **B. Swift shell, Python core as a subprocess** | High | Native menu bar and a much cleaner signing/TCC story. Also unlocks `SpeechAnalyzer` for long-form meeting transcription. |
|
||
| **C. Stay a CLI, distribute as an internal package** | Lowest | No TCC identity of its own, so the permissions problem stays. Fine for a handful of engineers, not for a team. |
|
||
|
||
**Recommendation: A for a pilot, with B as the path if it gets real adoption.**
|
||
A gets it onto a few machines quickly and proves whether anyone wants it. B is
|
||
justified once it needs to survive OS upgrades and support non-engineers, and
|
||
its extra value is concentrated exactly where A is weakest — signing and
|
||
long-form transcription.
|
||
|
||
### Suggested sequence
|
||
|
||
1. **Decide the notes/consent question.** Blocks part 1 only; everything else
|
||
can proceed in parallel.
|
||
2. **Slim the dependencies.** Lazy imports, exclude the bundled CLI, verify the
|
||
Apple-only path runs with Whisper/Kokoro uninstalled. Do this first — it's
|
||
pure cleanup with no external dependency, and it makes every later step
|
||
smaller.
|
||
3. **Prototype the process tap** against Zoom, behind an off-by-default flag,
|
||
for your own use. Verify long-meeting chunking on a real call.
|
||
4. **Talk to whoever owns Managed Software Center** about signing and packaging
|
||
requirements before building the bundle. Their answer may dictate the app's
|
||
shape, and finding that out after building option A would be expensive.
|
||
5. **Build the menu-bar bundle** (option A), signed, and install it on your own
|
||
machine. Confirm all four TCC grants survive a rebuild and a reboot — that's
|
||
the acceptance test that matters.
|
||
6. **Pilot with two or three teammates** before any wider push.
|
||
|
||
### Open questions
|
||
|
||
- Who owns Mac app signing and Managed Software Center packaging?
|
||
- Does the official `meta calendar` notetaker already cover the meeting-notes
|
||
need well enough to drop that feature entirely?
|
||
- Should the app require Claude Code at Meta as a prerequisite (simple, 260 MB
|
||
smaller, and the only thing that authenticates) or attempt to bundle a CLI?
|
||
- Is a menu-bar app the right shape, or would a Raycast/Alfred-style overlay
|
||
suit how people actually work better?
|