OUTPUT.md 4.7 KB

Output & Data

Transcripts reach you three ways: human-readable stdout, machine-readable JSONL, and optional WAV clips.

Each conversation writes to its own folder under session_dir (default transcripts/), named <YYYY-MM-DD-HH-MM-SS> after its first transcription. The transcript log, saved WAVs, the optional brief, and TUI screenshots all live inside it. A new folder starts once session_gap_min minutes pass without speech, or on a fresh process start.

Stdout (human-readable):

[14:23:45] (es) Hola, cómo estás?  [rms=0.041 end_of_turn]
[14:23:52] (en) What time is it?  [rms=0.038 end_of_turn]
[14:24:10] (es) Gracias por tu ayuda.  [rms=0.052 pause]

JSONL (one line per utterance; only written when jsonl = true, at <session_dir>/<id>/transcripts.jsonl):

{"ts": "2025-09-20T14:23:45.123456", "session_id": "2025-09-20-14-23-45", "lang": "es", "lang_prob": 0.95, "duration_s": 2.5, "rms": 0.04123, "end_reason": "end_of_turn", "text": "Hola, cómo estás?", "transcribe_s": 0.42}
{"ts": "2025-09-20T14:23:52.654321", "session_id": "2025-09-20-14-23-45", "lang": "en", "lang_prob": 0.98, "duration_s": 1.8, "rms": 0.03811, "end_reason": "pause", "text": "What time is it?", "transcribe_s": 0.31}

session_id names the conversation folder (<session_dir>/<session_id>/) so records can be correlated back to their transcript log and audio. Records from one conversation all carry the same value; a new folder gets a new one.

duration_s is the length of the utterance audio; transcribe_s is the wall-clock time the worker spent transcribing it (inference latency), rounded to milliseconds. Their ratio is the real-time factor. The field is omitted if the timing was not measured (e.g. emit() called directly without transcribe_s).

end_reason is one of end_of_turn (full silence_ms elapsed), pause (chunked at chunk_pause_ms because it passed soft_max_s), hard_cap (max_utterance_s reached), or flush (shutdown mid-utterance).

Speaker fields — every JSONL record carries five additive, back-compatible speaker fields (always present; null when unknown):

{"ts": "2026-09-24T10:01:03.123456", "lang": "en", "lang_prob": 0.98, "duration_s": 3.2, "rms": 0.031, "end_reason": "pause", "text": "turn on the lights", "seg_id": 142, "speaker_id": "spk_0001", "speaker_status": "matched", "speaker": "Efren", "speaker_score": 0.71}
  • seg_id — stable correlation key for the segment (int, null when unknown).
  • speaker_id — stable cluster key for cross-reference (e.g. "spk_0001", null when unknown).
  • speaker_status — one of:
    • matched — best score >= T_id (threshold_id).
    • uncertain — T_new <= score < T_id (threshold_new <= score < threshold_id).
    • unknown — below T_new and no profile minted this utterance.
    • none — no embedding computed (model off, audio too short, or error).
  • speaker — display label: matched+named -> the name ("Efren"), matched+unnamed -> "spk_0001", uncertain -> "<top1>?" (e.g. "spk_0001?"), unknown/none -> "?".
  • speaker_score — the winning cosine similarity, rounded to 4 dp (null when unknown).

Note: "?" above is the stored JSONL speaker value. The assistant resolves the same turn to the literal "Unknown" before relaying it to viewers, so the TUI header shows Unknown (bold red) where the JSONL record holds "?".

Existing tooling that ignores unknown JSON keys keeps working unchanged.

Audio files — when save_audio = true, each transcribed utterance is also written as a 16 kHz mono WAV under <session_dir>/<id>/audio/, and the record gains an "audio" field with its path:

{..., "end_reason": "pause", "text": "Hola", "audio": "transcripts/2025-09-20-14-23-45/audio/142345-123-pause.wav"}

Saving is best-effort: if a write fails (e.g. disk full), the transcript is still emitted and a warning is logged.

⚠️ Disk space — saved audio is never rotated or cleaned up automatically. At 16 kHz mono 16-bit it costs 32 KB/s, i.e. ~1.9 MB/min, ~115 MB/hour, ~2.8 GB/day of continuous speech. Silence is not written, so real usage is lower — but an always-on daemon in a talkative room still accumulates quickly. Point session_dir at a volume with headroom and prune it yourself (e.g. a cron job removing files older than N days), and leave save_audio = false unless you actually need the clips for auditing.

Triage I/O dump (assistant-side) — with [triage].dump_io = true the assistant writes one self-contained JSON per triage under troubleshoot/triage/YYYY-MM-DD/HHMMSS-mmm.json (the Laya request/response plus the LLM request/response). It is separate from the daemon transcript output above, off by default, and best-effort. See TRIAGE.md.