Skip to main content
A voice agent is a normal cruq agent that runs over a phone call instead of a chat box. Someone dials in, or the agent dials out, and the two of you talk. The first use case we built is lead qualification in Hinglish: the agent greets the caller, asks the qualifying questions, and records a structured result. The agent on the call is the same governed cruq agent you build everywhere else. It owns the prompt, tools, knowledge, tracing, and metering. Voice is a channel on top, not a separate product.

The loop

A voice turn runs a streaming pipeline:
The caller’s speech is transcribed as they talk, the agent’s reply streams into speech, and the audio plays back down the phone line. Voice activity detection handles turn-taking and barge-in, so the caller can talk over the agent and it stops and listens. The number that matters is perceived latency: the gap from the caller finishing their sentence to the first audio of the reply. Below about 800ms a call feels like a conversation. Above it, people start talking over the pause.

The stack

The Phase 0 spike measured every stage on real calls from India and picked the vendors from the numbers:
  • Orchestration: Pipecat. Transport-swappable, good observability.
  • Telephony: Exotel. India-native and DLT-ready. Handles inbound and outbound on the same handler; the call’s start event tags it inbound or outbound and carries the caller number.
  • STT: Sarvam. Accurate Hindi and Hinglish transcription, and it finalizes before the end of the turn, so it adds no perceived latency.
  • TTS: Smallest Lightning. 5 to 140ms to first byte.
  • LLM: governed by cruq. The pipeline drives a real cruq agent through agent-service rather than calling a provider directly, so the call inherits the agent’s prompt, tools, KB, tracing, and metering.

Latency, honestly

The loop, telephony, STT, TTS, and Hinglish quality all validated end to end on real calls. One thing sits above target: With gemini-2.5-flash-lite, perceived p50 landed around 1130ms, with the LLM’s first token being almost all of it. Everything else is already within budget. The next lever is a low-time-to-first-token model (a Groq or Cerebras served Llama), which should cut first token to a few hundred milliseconds and bring perceived latency toward 550 to 650ms, at a possible cost to Hinglish quality worth A/B testing.

Language

Forcing Hinglish in the system prompt produces a natural romanized Hinglish greeting and qualification, with no drift back to English. Sarvam transcribes the caller’s Hindi and Hinglish accurately in both directions.

Every call is recorded

A call persists three things, all tenant-scoped:
  • A recording (WAV, caller and agent mixed) in object storage.
  • A transcript (the conversation, timestamped) as JSON.
  • A voice_calls row in Postgres with the vendors used, turn count, duration, and perceived-latency p50, under row-level security like every other tenant table.
You review them in the app’s Voice Calls page: a list of calls, and a detail view with the transcript and recording playback.

Pausing on a call

The Ask a Human tool works over voice. When the agent hits something it should not decide alone, it speaks the question, parks the run as paused, and resumes on the caller’s next turn using the same thread. The call does not drop while it waits.

Status

Phase 0 (the throwaway spike, apps/voice-spike) validated the loop, vendors, and Hinglish quality on real phone calls. Phase 1 shipped the governed cruq-agent backend, call recording and transcript persistence to object storage and Postgres, the Voice Calls page, and the Kubernetes deploy scaffolding. Bulk or commercial calling in India still requires DLT registration before it goes wide.
backchannel (in the sibling hf-voice repo) is a separate, open experiment in a full-duplex voice framework with first-class barge-in and turn detection. It is not part of the product runtime described here.