admit. The voice() mixin from @telnyx/edge-runtime/voice runs STT and TTS, takes each end of turn from STT, and asks the class for one thing per utterance, a turn engine. By default that is the direct engine in managed/direct-turn.ts. The turn goes straight to the model with the conversation held in memory, and the confirmed exchange is written behind the call. With voice.harness true the harness itself is the engine. Each turn then runs the harness’s model and tools as a direct turn, not as a persistent run, at the cost of a second or more before the first word. defineManagedVoiceAgent in managed/voice.ts wraps the managed class in the mixin:
warmCall of its own.
warmCallruns before the call is answered, as The Call Webhook shows.greetingis spoken when the call is answered. The mixin’s own says nothing.turnEnginereturns the engine above. The mixin’s own returns none.speechProvidersbuilds the STT and TTS providers fromspec.voice.replywraps the mixin’s own reply inwithFillerfrommanaged/filler.ts. When the caller is confirmed done andvoice.filler.afterMspass without a word from the model, one short phrase is spoken as its own sentence, and the reply follows. The phrase is said, never written, so it reaches neither the message log nor the caller’s memory.turnEndedlogs the turn’s clock and writes the exchange to the message log after the reply has finished playing.callEndedwrites the last reply to state withsetState()and, with memory on, pushes the call to Agent Memory under its id.
reply:
super.reply(transcript, call) is the mixin’s own reply, which hands the transcript to the engine that turnEngine() returns.
transcriptis STT’s first pass at the caller’s words, andcall.confirmedresolves to the confirmed words.- While the turn is speculative, the mixin calls the engine’s
begin()with the first pass, then commits the confirmed words or discards the turn. - Otherwise it calls the engine’s
turn()with the confirmed words, and the result’stextis spoken. - A reply the caller cuts short is still a turn. The direct engine remembers the caller’s words and what was said of the reply before the cut, with the note
[cut off by the caller]after it, in its history and in the journal; once playback reports what the caller heard, the entry is corrected to that. The next turn’s context holds both, so an interrupted “Sure, what is the new…” and the caller’s “12 Main St” line up.
voice.harness true the engine is ready({ calls: true }), the recovered harness for calls, and both calls are its own. That harness leaves out every tool marked ask, since a call cannot wait for an approval; without any ask it is the same instance texts use.
begin()streams provisional text, with no writes or tools until the turn is committed.turn()runs the input to completion and records the turn in the message log.
streamText and no harness.
Over the mount, the voice class is the managed class unchanged except for webSocket(), with the same six RPC methods. Extending the Managed Agent has the hooks’ signatures.