Skip to content

Add warm transcribe recorder daemon#565

Open
basnijholt wants to merge 5 commits into
mainfrom
codex/transcribe-recorder-daemon
Open

Add warm transcribe recorder daemon#565
basnijholt wants to merge 5 commits into
mainfrom
codex/transcribe-recorder-daemon

Conversation

@basnijholt
Copy link
Copy Markdown
Owner

@basnijholt basnijholt commented May 29, 2026

Summary

  • Add agent-cli daemon transcribe-recorder for a warm recorder daemon controlled over a Unix socket.
  • Keep the input stream open with pre-roll buffering so hotkeys can start capturing without Python/audio cold-start latency.
  • Add JSON control commands for serve, start, stop, toggle, status, and reload, plus focused tests for buffer/control behavior.
  • Return the same machine-readable not-running JSON when Unix-socket clients fail because the daemon is missing, the socket path is invalid, or Unix sockets are unsupported on the platform.

Test Plan

  • Rebased on current origin/main.
  • uv run ruff check agent_cli/daemon/transcribe_recorder.py agent_cli/daemon/cli.py tests/agents/test_transcribe_recorder.py tests/test_cli.py
  • uv run --all-extras pytest -q (1317 passed, 4 skipped locally)
  • uv run pre-commit run --files agent_cli/daemon/transcribe_recorder.py tests/agents/test_transcribe_recorder.py
  • Live daemon smoke: serve on a temp Unix socket with --extra audio, then status, idle stop, idle reload, start, duplicate start, recording status, and recording reload rejection through the CLI JSON client.
  • Direct Unix-socket smoke: sent JSON requests with a raw AF_UNIX socket and verified status and unknown-action responses.
  • End-to-end active recording smoke: sent raw-socket start, recorded briefly, sent raw-socket stop, and received status: transcribed with transcript fields.
  • Real microphone recording: recorded ~10 seconds through the warm daemon, stop returned status: transcribed with a non-empty transcript, and wrote a 982 KB WAV file.
  • Shutdown smoke: SIGTERM cleanup removed temp sockets; post-shutdown status --json returned the not-running error with exit code 1.
  • GitHub Actions passed across macOS, Ubuntu, and Windows for Python 3.11/3.13.

@basnijholt basnijholt force-pushed the codex/transcribe-recorder-daemon branch 3 times, most recently from 782321f to 4b5356c Compare June 4, 2026 21:58
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Jun 4, 2026

Greptile Summary

Adds agent-cli daemon transcribe-recorder, a warm Unix-socket recorder daemon that keeps the audio input stream open continuously and buffers a configurable pre-roll window so hotkey-triggered recording starts without cold-start latency. JSON control commands (serve, start, stop, toggle, status, reload) are handled over a local Unix socket with bounded client-side timeouts and consistent machine-readable error payloads.

  • transcribe_recorder.py (new, 593 lines): WarmAudioBuffer (thread-safe pre-roll deque + recording list), WarmRecorder (owns the open audio stream and reader thread), TranscribeDaemon (async socket command dispatcher calling into the existing transcribe_agent), _serve / _request helpers, and Typer sub-commands for all six actions.
  • daemon/cli.py: Registers the new transcribe-recorder sub-typer on the existing daemon app — a two-line change.
  • Tests: test_transcribe_recorder.py covers buffer semantics, JSON error paths, timeout handling, WAV cleanup on write failure, and the Windows/no-Unix-sockets guard.

Confidence Score: 5/5

Safe to merge; the new daemon code is well-structured with appropriate platform guards, error handling, and test coverage.

The core recording and socket-dispatch logic is correct, platform portability is handled, and all previously reported issues have been addressed. The two remaining observations — an unbounded recording buffer if stop is never called, and the absence of a server-side readline deadline — are hardening concerns rather than defects in the current happy path.

agent_cli/daemon/transcribe_recorder.py — specifically the WarmAudioBuffer recording list and the handle_client coroutine.

Important Files Changed

Filename Overview
agent_cli/daemon/transcribe_recorder.py New warm recorder daemon: thread-safe pre-roll buffer, Unix socket command server, and async client helpers — well-structured with good platform guards; the recording buffer has no upper-size cap and the server-side readline lacks a timeout.
agent_cli/daemon/cli.py Adds the transcribe-recorder sub-typer to the daemon app; change is minimal and correct.
tests/agents/test_transcribe_recorder.py Focused unit and integration tests covering buffer pre-roll semantics, duplicate-start/empty-stop, JSON error paths, timeout handling, WAV temp-file cleanup on failure, and serve-side Unix socket unsupported guard.
tests/test_cli.py Adds a smoke test confirming the transcribe-recorder sub-command is registered and shows expected sub-commands in its help output.
docs/commands/daemon.md Documentation updated to describe the new transcribe-recorder sub-commands (serve, start, stop, toggle, status, reload) with usage examples.

Reviews (4): Last reviewed commit: "Fix Windows CI timeouts" | Re-trigger Greptile

Comment thread agent_cli/daemon/transcribe_recorder.py
Comment thread agent_cli/daemon/transcribe_recorder.py Outdated
Comment thread agent_cli/daemon/transcribe_recorder.py Outdated
@basnijholt basnijholt force-pushed the codex/transcribe-recorder-daemon branch from 4b5356c to 3e699f9 Compare June 8, 2026 04:55
Comment on lines +457 to +460
def _print_response(response: dict[str, Any], *, json_output: bool) -> None:
if json_output:
print(json.dumps(response))
return
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 --json always exits 0, even when the daemon reports an error

_print_response prints the JSON response and returns unconditionally when json_output=True, so daemon-side errors (e.g. "Cannot reload while recording", "No audio captured") produce exit code 0. The --json flag is specifically designed for scripting, so a caller running stop --json; echo $? will see 0 even after a failed transcription. Transport-level errors (connection refused, timeout) do exit 1 — this inconsistency means callers have to parse ok for some failures but can rely on exit code for others, defeating the purpose of a uniform machine-readable interface.

Suggested change
def _print_response(response: dict[str, Any], *, json_output: bool) -> None:
if json_output:
print(json.dumps(response))
return
def _print_response(response: dict[str, Any], *, json_output: bool) -> None:
if json_output:
print(json.dumps(response))
if not response.get("ok"):
raise typer.Exit(1)
return

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant