> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/nemo/labs-voice-agent/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/nemo/labs-voice-agent/_mcp/server.

# Audio Capture and Logging

NeMo Labs Voice Agent writes two independent outputs. Per-session **audio and transcript capture** is off by
default. The pipeline also emits a **rotating text log** at runtime. This page explains how to enable audio
capture and interpret both outputs.

## Enable Audio Capture

Audio capture is controlled by two keys in the `transport:` block of the top-level server config
(`examples/generic_voice_agent/server/server_configs/default.yaml`, and the same block in
`default_nvidia.yaml`):

```yaml
transport:
  audio_out_10ms_chunks: 8
  record_audio_data: true          # default: false
  audio_log_dir: "./audio_logs"    # default: "./audio_logs"
```

| Key | Default | Effect |
| --- | --- | --- |
| `transport.record_audio_data` | `false` | Enables capture. When false, no capture code runs. |
| `transport.audio_log_dir` | `"./audio_logs"` | Base directory, resolved relative to the server's working directory. |

`build_audio_logger` in `nemo_voice_agent/pipecat/services/nemo/builders.py` reads those two keys.
When `record_audio_data` is false it returns `None`. Every downstream service—speech-to-text (STT),
text-to-speech (TTS), turn-taking, and diarization—is then constructed with `audio_logger=None`, so capture
adds no runtime work.

The session ID is generated at **server start**, not per client connection:
`session_YYYYMMDD_HHMMSS`. Because the server keeps one pipeline alive across reconnects, as described in
[Server Config](/nemo/labs-voice-agent/build-voice-agents/configure/server-config), a client that disconnects and reconnects keeps appending to the
same session directory.

`audio_logs/`, `audio_logs_user/`, and `audio_logs_agent/` are gitignored.

## What Lands on Disk

Each capture session produces per-speaker audio and metadata plus a whole-session stereo mixdown.

```text
audio_logs/
└── session_20260806_141200/
    ├── user/
    │   ├── 00001_141207.wav
    │   ├── 00001_141207.json
    │   └── ...
    ├── agent/
    │   ├── 00001_141203.wav
    │   ├── 00001_141203.json
    │   └── ...
    ├── session_metadata.json
    └── conversation_stereo.wav
```

| Artifact | Contents |
| --- | --- |
| `user/NNNNN_HHMMSS.wav` | One user turn, mono 16-bit at 16 kHz, sliced out of the continuous input buffer by the turn's start/end times. |
| `user/NNNNN_HHMMSS.json` | Metadata and the final transcription for that turn. |
| `agent/NNNNN_HHMMSS.wav` | One synthesized TTS segment (mono 16-bit) at the TTS service's output sample rate. A single agent turn is usually several segments. |
| `agent/NNNNN_HHMMSS.json` | Metadata and the text that was synthesized. |
| `session_metadata.json` | Rolling index of every entry; rewritten after each save and again at finalize. |
| `conversation_stereo.wav` | Whole-session mixdown, left channel = agent, right channel = user, 16 kHz. Written only at finalize. |

The `NNNNN` prefix is a per-speaker counter (the user and agent counters are separate), and `HHMMSS` is
wall-clock time at save. User turns get a 0.8-second pre-roll prepended, clamped so a turn never
overlaps the previous entry's end time.

### Metadata Fields

Both sides share `base_name`, `counter`, `turn_index`, `speaker`, `timestamp` (ISO 8601),
`start_time` / `end_time` (float seconds from the first audio frame of the session),
`audio_file`, `sample_rate`, `num_channels`, and `audio_duration_sec`. Beyond that:

| Field | Side | Meaning |
| --- | --- | --- |
| `transcription` | user | Final automatic speech recognition (ASR) text for the turn. |
| `is_backchannel` | user | True when turn-taking classified the utterance as a backchannel, so it did not interrupt the bot. Refer to [Turn Taking](/nemo/labs-voice-agent/about/core-concepts/speech-pipeline/turn-taking-backchannels). |
| `num_audio_chunks`, `num_transcription_chunks` | user | How many streaming chunks were merged into this turn. |
| `model`, `backend` | user | ASR model name and backend that produced the transcription. |
| `text` | agent | The text handed to TTS for this segment. |
| `model` | agent | TTS model name. |
| `cutoff_time` | agent | `null` when the segment played to completion; a float (seconds from session start) when the user interrupted. Interruption stamps the same value on **every** segment of the current turn and zeroes the agent channel of the stereo mix after that point. |

`session_metadata.json` holds `session_id`, `start_time`, `last_updated`, a flat `user_entries`
list, and an `agent_entries` list where each element is itself the list of segments for one agent
turn. `finalize_session` adds `end_time`, `total_user_entries`, `total_agent_segments`, and
`total_agent_turns`.

## How Capture Is Wired

`AudioLogger` (`nemo_voice_agent/pipecat/services/nemo/audio_logger.py`) is a plain object passed
into the services by the builders. Each service pushes data into it at the right moment.

| Source | What It Contributes |
| --- | --- |
| STT service | Stamps the session's first-audio timestamp and appends **every** input chunk to the continuous user buffer without voice activity detection (VAD) gating. It also stages the turn's audio and transcription. The user channel therefore includes silence. |
| Turn-taking service | Marks backchannel turns, records when the bot begins speaking, sets `cutoff_time` on interruption, and advances the turn index for user turns. |
| TTS service | Calls `log_agent_audio` one time per synthesized segment and advances the turn index for agent turns. |
| `RTVIAudioLoggerObserver` | A pipeline observer that flushes the staged user turn to disk when a `TranscriptionFrame` is pushed. It is added to the task's observer list unconditionally and no-ops when the logger is `None`. |
| `run_bot_websocket_server` | Calls `finalize_session` on client disconnect, on session timeout, and on pipeline shutdown — this is what writes `conversation_stereo.wav`. |

Two limitations worth knowing:

- `build_audio_logger` passes only the directory, session ID, and enabled flag. `AudioLogger`'s
  other constructor arguments (user sample rate, pre-roll seconds, rounding precision) are **not**
  exposed through YAML, so their defaults always apply. Change them by constructing `AudioLogger`
  yourself. Refer to [Builders](/nemo/labs-voice-agent/build-voice-agents/extend/pipelines/the-builder-api).
- The `AudioLogger` docstring records a known issue with `conversation_stereo.wav`: the two channels
  need roughly a -0.8 s offset applied to sound in sync. The per-turn WAV files are unaffected.

Quick check after a session:

```bash
ls audio_logs/session_*/
python -m json.tool audio_logs/session_*/session_metadata.json | head -40
```

## Server Log File

Logging is configured by `setup_logging` in `nemo_voice_agent/utils/misc.py`, which installs a
colorized stderr sink plus a file sink with `rotation="1 day"` at `DEBUG` level. Each day, loguru
rolls the active file aside into a timestamped sibling, so when debugging a failure check the
**newest** `bot_server.*.log` and not only `bot_server.log`.

`setup_rotating_log` wraps that with a rename-or-delete step for a pre-existing file: it either
removes the old log or renames it to `bot_server.<YYYYmmdd_HHMMSS>.log` before installing the sinks.

| Key | Default | Read by |
| --- | --- | --- |
| `server.log_file` | `"bot_server.log"` | `evaluation/bot_server.py` |
| `server.log_level` | `"DEBUG"` | `evaluation/bot_server.py` |
| `server.create_new_log` | `false` | `evaluation/bot_server.py` (roll the existing log aside at startup) |
| `server.overwrite_existing_log` | `false` | `evaluation/bot_server.py` (delete instead of rename) |

**Gotcha:** the example server (`examples/generic_voice_agent/server/server.py`) calls
`setup_logging()` with no arguments, so it always writes `bot_server.log` at `DEBUG` regardless of
the `server.log_file` / `server.log_level` values in `default.yaml`. Those keys take effect for the
evaluation bot servers, which resolve them through `resolve_log_file_path` and `setup_rotating_log`.
The call is repeated after service construction because model libraries reconfigure loguru during
import.

## Evaluation Runs

The evaluation role configs (`evaluation/server_configs/agent.yaml` and `user.yaml`) also ship with
`record_audio_data: false`, and give each role its own `audio_log_dir` (`./audio_logs_agent`,
`./audio_logs_user`) so the two bots do not collide. Independently of this, the bridge writes
`conversation_log.wav` (stereo), `conversation_log.txt`, and `conversation_log.seglst.json` into each
scenario's output directory. For output details, refer to
[Evaluation Results](/nemo/labs-voice-agent/evaluate-voice-agents/run-evaluations/reading-results).

## Related Pages

Use these pages to continue configuring or inspecting the runtime:

- [Server Config](/nemo/labs-voice-agent/build-voice-agents/configure/server-config) — the rest of the top-level YAML.
- [Configuration](/nemo/labs-voice-agent/build-voice-agents/configure/overview) — how `default.yaml` and the model sub-configs merge.
- [Config Schema](/nemo/labs-voice-agent/reference/runtime/server-config-schema) — full key reference.
- [Troubleshooting](/nemo/labs-voice-agent/troubleshooting/troubleshooting) — reading the logs when a session misbehaves.