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

10 KiB
Raw Blame History

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.

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?