| name | codex-meeting-recorder |
| description | Record and live-transcribe a meeting or live conversation from a Codex thread on macOS, saving streaming transcript text to the workspace with OpenAI Realtime transcription, then summarize notes in-thread. Use when the user asks to start, stop, capture, transcribe, ask questions about, or summarize a meeting recording from the current thread. |
Codex Meeting Recorder
Record and live-transcribe a meeting from the current workspace. The default v2 path streams macOS system audio and microphone audio to OpenAI Realtime transcription, writes transcript text as it arrives, shows a minimal live preview, and leaves the transcript files in the workspace for Codex to answer questions during or after the meeting.
Requirements
- macOS 15 or newer.
- Xcode command line tools with Swift.
OPENAI_API_KEY available when live transcription starts.
websocket-client available to the realtime worker. If missing, the controller can use uv run --with websocket-client automatically when uv is installed.
- Python OpenAI SDK available only when using the legacy finished-file transcriber. If missing, the controller can use
uv run --with openai automatically when uv is installed.
- Browser plugin available for opening the localhost preview in Codex's in-app browser.
- First recording may prompt for Screen Recording and Microphone permissions. If macOS grants permission but capture fails, ask the user to restart Codex/Terminal and retry.
- Start commands should be run outside Codex's command sandbox when available, because the live status page binds a localhost port and macOS permissions may need direct process access.
Output Layout
Use a workspace-local folder:
recordings/
YYYY-MM-DD-HHMMSS/
recording.mp4
live_transcript.txt
transcript_formatting_prompt.md
formatted_transcript.md
metadata.json
recorder.log
audio-health.log
audio-capture.log
status-server.log
In v2, live_transcript.txt is the canonical live text stream. The preview reads this file and renders only the transcript text plus a blinking cursor while transcription is active. Codex should also use this file as the source of truth when answering questions during the meeting. Live text is source-labeled as [Microphone] or [System] because the realtime worker runs separate transcription sessions for microphone and system audio. The preview page renders these plain-text tags as small icon badges, but the transcript file stays plain text for search, audit, and downstream formatting.
When the meeting stops, the controller writes transcript_formatting_prompt.md and a pending formatted_transcript.md placeholder. Treat formatted_transcript.md as a post-meeting artifact, not a live source. Launch a Codex subagent with the formatting prompt so it can replace the placeholder with a readable final Markdown transcript while the main agent prepares meeting notes.
The formatted transcript should include a meeting title, a short description, participants and mentioned people, source and quality notes, a readable transcript, and assumptions/corrections. The subagent should use **You:** for [Microphone] and **Others:** for [System], combine chunks into logical sentences and paragraphs, fix likely spellings from transcript and workspace context, and document meaningful inferences or corrections. Source labels are deterministic audio-source labels only; do not present them as true speaker diarization.
Attendee inference is a known weak spot. Unless the user or calendar metadata identifies the participants, do not assume every named person was present. People may mention absent colleagues, future invitees, clients, partners, or decision-makers. In the final artifact, separate confirmed participants from possible participants and people merely mentioned, and include a note that names inferred from the transcript may not reflect actual attendance.
The legacy recording.mp4 and scripts/transcription.py path remains available for finished-file transcription, but the default start command uses realtime transcription.
While transcription is active, the controller starts a localhost preview page and stores its URL in recordings/.current-recording.json.
By default, startup runs a short source-specific audio health check before declaring the recorder live. The check probes enabled microphone and system audio separately, records bytes captured, sample count, RMS, peak, thresholds, and warnings in metadata.json, and writes helper diagnostics to audio-health.log. If a source appears silent, report the warning to the user before the meeting continues. Use --strict-audio-health-check when startup should fail on a silent enabled source, or --no-audio-health-check only when debugging the health check itself. The live realtime gate defaults are intentionally sensitive (--silence-threshold 8 --peak-threshold 80) so quiet but valid microphone input is not dropped after passing the health check.
Source diarization is deterministic for the first layer: microphone audio and system audio are emitted from one ScreenCaptureKit stream as source-tagged PCM chunks, then routed to separate realtime transcription sessions. The labels survive in live_transcript.txt, formatted_transcript.md, optional transcript_events.jsonl, and metadata.json. Do not treat [System] as true speaker diarization; individual remote speaker labels still require a later post-meeting diarization pass. The default overlap policy is suppress-mic, meaning microphone chunks are withheld while system audio is active to reduce speaker bleed in the microphone stream. Use --source-overlap-policy keep when preserving interruptions matters more than suppressing duplicate system audio.
The default meeting-mode realtime settings favor transcript quality over instant captions: --delay medium, --commit-interval 6.0, and --audio-chunk-ms 200. Use --delay minimal or shorter commit intervals only for live-caption behavior where latency matters more than transcript quality.
For debugging only, start --save-events writes transcript_events.jsonl, and start --save-raw-audio writes input_audio.pcm. Do not enable raw audio for ordinary meetings because PCM grows quickly.
Commands
Set the skill path if needed:
export CODEX_MEETING_RECORDER_SKILL="/path/to/codex-meeting-recorder"
Start live transcription:
python3 "$CODEX_MEETING_RECORDER_SKILL/scripts/recorderctl.py" start --workspace .
After start, immediately open the returned status_url in Codex's in-app browser/preview, then share the URL with the user. Do not use open <status_url>, open -a, or any other macOS/default-browser command for the preview. Use the Browser plugin workflow in references/open_preview_in_codex_browser.md, explicitly set browser visibility to true, and navigate the preview pane to the returned URL. The preview is intentionally minimal: a white page with transcribed text and a subtle blinking cursor while transcription is live.
Stop live transcription:
python3 "$CODEX_MEETING_RECORDER_SKILL/scripts/recorderctl.py" stop --workspace .
During a meeting, answer questions by reading the current transcript file from the active state:
python3 "$CODEX_MEETING_RECORDER_SKILL/scripts/recorderctl.py" status --workspace .
Then read or search the transcript_file path from the returned JSON. Prefer the file over the preview server as the source of truth.
Optional realtime settings:
python3 "$CODEX_MEETING_RECORDER_SKILL/scripts/recorderctl.py" start --workspace . --delay minimal
python3 "$CODEX_MEETING_RECORDER_SKILL/scripts/recorderctl.py" start --workspace . --no-system-audio
python3 "$CODEX_MEETING_RECORDER_SKILL/scripts/recorderctl.py" start --workspace . --save-events
python3 "$CODEX_MEETING_RECORDER_SKILL/scripts/recorderctl.py" start --workspace . --strict-audio-health-check
python3 "$CODEX_MEETING_RECORDER_SKILL/scripts/recorderctl.py" start --workspace . --no-audio-health-check
python3 "$CODEX_MEETING_RECORDER_SKILL/scripts/recorderctl.py" start --workspace . --delay high --commit-interval 8.0
python3 "$CODEX_MEETING_RECORDER_SKILL/scripts/recorderctl.py" start --workspace . --backend local-nemotron
local-nemotron is a placeholder adapter boundary for future local streaming ASR and intentionally exits until that backend is implemented.
Legacy stop-and-transcribe for an MP4 recording:
python3 "$CODEX_MEETING_RECORDER_SKILL/scripts/recorderctl.py" stop --workspace . --transcribe
The --transcribe path invokes this skill's bundled transcription helper:
"$CODEX_MEETING_RECORDER_SKILL/scripts/transcription.py"
If the caller's Python environment is missing the OpenAI SDK, the controller may fall back to uv run --with openai.
Check status:
python3 "$CODEX_MEETING_RECORDER_SKILL/scripts/recorderctl.py" status --workspace .
Serve only the live status page, usually for debugging:
python3 "$CODEX_MEETING_RECORDER_SKILL/scripts/recorderctl.py" serve-status --workspace . --port 47832
Transcribe the latest stopped recording:
python3 "$CODEX_MEETING_RECORDER_SKILL/scripts/recorderctl.py" transcribe --workspace .
Prepare a formatting prompt for an existing realtime recording:
python3 "$CODEX_MEETING_RECORDER_SKILL/scripts/recorderctl.py" prepare-formatting recordings/YYYY-MM-DD-HHMMSS
Workflow
- Start live transcription when the user asks.
- Immediately open the returned
status_url in Codex's in-app browser/preview so the live transcript is visible without the user doing anything. Follow references/open_preview_in_codex_browser.md; never use shell open for this URL. For the in-app browser, explicitly set the browser visibility capability to true before navigation.
- Return the
status_url so the user can also open it manually if needed.
- Leave the background realtime worker running until the user asks to stop.
- If the user asks questions during the meeting, load or search the active
transcript_file; do not build a separate preview API client.
- Stop the worker with
recorderctl.py stop --workspace ..
- Launch a subagent to create the final formatted transcript:
- Give it
transcript_formatting_prompt_file from the stop/status JSON.
- Tell it to replace
formatted_transcript_file.
- Keep
live_transcript.txt, metadata.json, and other raw artifacts unchanged for audit.
- Read the subagent-produced
formatted_transcript_file when available, otherwise transcript_file, and provide concise meeting notes in the thread.
Notes Style
Summarize with:
- Decisions
- Action items with owners when stated
- Open questions
- Key discussion points
Keep notes grounded in the transcript. If speaker labels are unavailable, do not invent them.
Current Limitations
- v2 writes live transcript files, not a combined MP4, on the default realtime path. Separate system/microphone tracks are a future improvement.
- v2 uses
gpt-realtime-whisper through scripts/realtime_transcription.py, with source-tagged audio routed to separate realtime sessions for microphone and system audio. The backend boundary is intentionally small so a local streaming ASR backend, such as Nemotron via NeMo/Riva, can be added later.
- ScreenCaptureKit permission prompts are controlled by macOS.
- The realtime worker uses
caffeinate while capture is active because ScreenCaptureKit can fail to enumerate displays when the display is asleep.
- A source-specific startup health check warns when microphone or system audio appears silent before the realtime worker starts.
- A small local silence gate avoids sending empty audio commits during quiet moments.
- Raw Realtime events and raw PCM audio are debug-only opt-ins, not normal meeting outputs.
- The Swift helper still supports the legacy MP4 recording mode, but the realtime path uses
stream-pcm-json to emit source-tagged 24 kHz mono PCM for the Realtime API.