Files
VoiceAgent/PLAN.md
T
2026-08-07 18:15:36 -04:00

204 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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?