OpenAI GPT-Live Full-Duplex Voice System Deep Dive: Speech and Reasoning Layer Decoupling
1. Introduction: The Cambrian Explosion of Voice AI
On July 8, 2026, OpenAI officially launched GPT-Live, its third-generation voice interaction system. On August 3, the OpenAI engineering team published a detailed technical blog titled “How we built a realtime system for responsive voice AI in six months,” revealing the underlying architecture of GPT-Live. More technical details were disseminated through media channels on August 4.
The core breakthrough of this system is twofold: abandoning the turn-based architecture that has dominated voice AI for years in favor of a native full-duplex architecture, and completely decoupling the speech interaction layer from the deep reasoning layer at the system level.
This article provides a deep-dive engineering analysis of the GPT-Live architecture, covering full-duplex audio pipelines, speech-reasoning decoupling, concurrent state management, protocol optimization, and complete code implementations.
Sources: This article’s technical details are primarily based on OpenAI’s official blog “How we built a realtime system for responsive voice AI in six months” (https://openai.com/index/continuous-voice-interaction-with-gpt-live/) and “Introducing GPT‑Live” (https://openai.com/index/introducing-gpt-live/).
2. Architecture Evolution: From Cascaded to Full-Duplex
2.1 First Generation: Cascaded System
OpenAI’s original voice system (ChatGPT Voice, 2023) used a classic three-stage cascaded architecture:
User Speech → [STT (Whisper)] → Text → [LLM (GPT-4)] → Text → [TTS] → Speech Response
Problems:
- Each stage runs serially, accumulating latency (typical end-to-end latency > 2 seconds)
- Tone, rhythm, and emotional cues are lost in STT transcription
- Cannot handle interruptions, filler words, or other natural conversational behaviors
2.2 Second Generation: Speech-to-Speech + Turn Detection
The Advanced Voice Mode released in 2024 merged STT and TTS into a single model, processing audio directly:
User Speech → [Voice Model (Multimodal)] → Speech Response
↑
[Turn Detector] ← Silence Detection
Improvements: Reduced latency, preserved paralinguistic information (tone, pace).
Remaining issues:
- Still relied on a turn detector to determine conversational boundaries
- The turn detector faced a dilemma: guess too soon and interrupt the user, guess too late and feel sluggish
- Fundamentally still turn-based, unable to achieve true parallel conversation
2.3 Third Generation: GPT-Live Full-Duplex Architecture
GPT-Live completely re-engineered the foundational logic of voice interaction:
┌─────────────────┐
User Audio ──────────►│ GPT-Live-1 │◄──────── User Audio
│ (Full-Duplex │────────► User Audio
User Audio ──────────►│ Voice Model) │
└────────┬────────┘
│ Async RPC
▼
┌─────────────────┐
│ GPT-5.5 │
│ (Frontier │
│ Reasoning │
│ Model) │
└─────────────────┘
Key Innovations:
- Full-duplex audio: The model processes input and output audio streams simultaneously
- Turn detector removed: The model itself controls conversational rhythm
- Asynchronous delegation: The voice model can delegate complex tasks to GPT-5.5
- Dedicated media path: Audio flows on a separate fast path, isolated from business logic
According to OpenAI’s engineering team, the architectural decision involved migrating the system from Python asyncio to Go over six months, rewriting model inference, context management, and media transport layers (Source: OpenAI Engineering Blog).
3. Full-Duplex Audio Architecture Deep Dive
3.1 Core Concept: Full-Duplex vs Half-Duplex
| Feature | Half-Duplex (Legacy) | Full-Duplex (GPT-Live) |
|---|---|---|
| Simultaneous TX/RX | No | Yes |
| Interruption support | Must wait for turn end | Anytime |
| Filler words | Difficult (needs separate handling) | Native support |
| Conversation rhythm | Turn detector | Model itself |
| Latency profile | Bursty (process one turn, then respond) | Continuous streaming |
3.2 Audio Pipeline Design
GPT-Live’s audio pipeline consists of these key components:
[Client Microphone]
│
▼
[WebRTC Audio Track] ───► [Jitter Buffer]
│ │
│ ▼
│ [Audio Preprocessing]
│ ├── Acoustic Echo Cancellation (AEC)
│ ├── Noise Suppression (NS)
│ ├── Automatic Gain Control (AGC)
│ └── Voice Activity Detection (VAD)
│ │
│ ▼
│ [Audio Encoder] ◄── Opus Codec
│ │
│ ▼
│ [Go Media Frontend] ────► [Stateful Inference Engine]
│ │
│ ▼
│ [GPT-Live-1 Full-Duplex Model]
│ │
│ ┌──────────┴──────────┐
│ ▼ ▼
│ [Audio Generator] [Async Delegator]
│ │ │
│ ▼ ▼
│ [Opus Decoder] [GPT-5.5 Inference]
│ │
│ ▼
└───────────────────────────────── [WebRTC Audio Output]
Key Design Principle: Media flow is completely separated from application logic. Audio moves between the client and the voice model on a dedicated fast path, while tool use and other application work happen behind an asynchronous RPC boundary (Source: OpenAI Engineering Blog).
3.3 Code Implementation: Python Full-Duplex Audio Pipeline
The following is a simplified full-duplex audio pipeline implementation demonstrating the core logic of WebRTC stream processing, audio buffer management, and VAD detection:
"""
Full-Duplex Audio Pipeline Implementation
Simulating the core audio stream processing logic of GPT-Live
"""
import asyncio
import collections
import struct
import time
import wave
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional, Callable, Awaitable
class AudioState(Enum):
"""Audio stream states"""
SILENCE = "silence"
LISTENING = "listening"
SPEAKING = "speaking"
BARGE_IN = "barge_in"
@dataclass
class AudioFrame:
"""Audio frame data structure"""
timestamp: float
data: bytes
sample_rate: int = 16000
channels: int = 1
sample_width: int = 2 # 16-bit PCM
@property
def duration_seconds(self) -> float:
return len(self.data) / (self.sample_rate * self.channels * self.sample_width)
@property
def rms(self) -> float:
"""Calculate RMS energy of the audio frame"""
if not self.data:
return 0.0
samples = struct.unpack_from(f"<{len(self.data)//2}h", self.data)
sum_squares = sum(s * s for s in samples)
return (sum_squares / len(samples)) ** 0.5
class AudioRingBuffer:
"""
Audio ring buffer
Manages continuously incoming audio data with sliding window reads
"""
def __init__(self, max_duration_seconds: float = 30.0, sample_rate: int = 16000):
self.sample_rate = sample_rate
self.max_samples = int(max_duration_seconds * sample_rate)
self.buffer = collections.deque(maxlen=self.max_samples)
self._lock = asyncio.Lock()
async def write(self, samples: list[int]) -> None:
"""Write audio samples"""
async with self._lock:
self.buffer.extend(samples)
async def read(self, num_samples: int) -> list[int]:
"""Read the most recent N samples"""
async with self._lock:
if len(self.buffer) < num_samples:
return list(self.buffer)
return list(self.buffer)[-num_samples:]
async def clear(self) -> None:
async with self._lock:
self.buffer.clear()
@property
async def duration_seconds(self) -> float:
async with self._lock:
return len(self.buffer) / self.sample_rate
class VoiceActivityDetector:
"""
Voice Activity Detector (VAD)
Based on energy levels and adaptive thresholds
Simulates GPT-Live's built-in VAD logic (no external turn detector)
"""
def __init__(
self,
frame_duration_ms: int = 30,
silence_ratio_threshold: float = 0.3,
min_speech_frames: int = 3,
min_silence_frames: int = 15, # ~450ms silence considered a pause
energy_threshold: float = 100.0,
):
self.frame_duration_ms = frame_duration_ms
self.silence_ratio_threshold = silence_ratio_threshold
self.min_speech_frames = min_speech_frames
self.min_silence_frames = min_silence_frames
self.energy_threshold = energy_threshold
self._speech_frames = 0
self._silence_frames = 0
self._is_speech = False
self._recent_frames: list[bool] = []
def is_speech_frame(self, frame: AudioFrame) -> bool:
"""Determine if a frame contains speech"""
return frame.rms > self.energy_threshold
def process_frame(self, frame: AudioFrame) -> AudioState:
"""
Process an audio frame and return the current state
GPT-Live's core innovation: the model makes interaction decisions
at every frame, rather than relying on a binary turn detector
"""
is_speech = self.is_speech_frame(frame)
self._recent_frames.append(is_speech)
# Smoothing window
window_size = 10
if len(self._recent_frames) > window_size:
self._recent_frames.pop(0)
speech_ratio = sum(self._recent_frames) / len(self._recent_frames)
if speech_ratio > self.silence_ratio_threshold:
self._speech_frames += 1
self._silence_frames = 0
else:
self._silence_frames += 1
self._speech_frames = 0
# State transitions
if not self._is_speech and self._speech_frames >= self.min_speech_frames:
self._is_speech = True
return AudioState.LISTENING
if self._is_speech and self._silence_frames >= self.min_silence_frames:
self._is_speech = False
return AudioState.SILENCE
return AudioState.LISTENING if self._is_speech else AudioState.SILENCE
def reset(self) -> None:
self._speech_frames = 0
self._silence_frames = 0
self._is_speech = False
self._recent_frames.clear()
class EchoCanceller:
"""
Simplified echo canceller
Conceptual implementation using adaptive NLMS filter
"""
def __init__(self, filter_length: int = 512, mu: float = 0.01):
self.filter_length = filter_length
self.mu = mu
self.weights = [0.0] * filter_length
self.reference_buffer: list[float] = [0.0] * filter_length
def process(self, mic_signal: list[float], ref_signal: list[float]) -> list[float]:
"""
Process microphone signal, subtract echo component from reference signal
mic_signal: microphone input
ref_signal: speaker output reference
"""
output = []
for n in range(len(mic_signal)):
# Update reference buffer
self.reference_buffer.pop(0)
self.reference_buffer.append(ref_signal[n] if n < len(ref_signal) else 0.0)
# Estimate echo
echo_estimate = sum(
self.weights[i] * self.reference_buffer[self.filter_length - 1 - i]
for i in range(self.filter_length)
)
# Error signal (echo-cancelled signal)
error = mic_signal[n] - echo_estimate
# Update filter weights (NLMS adaptation)
norm = sum(x * x for x in self.reference_buffer)
if norm > 1e-10:
for i in range(self.filter_length):
self.weights[i] += (
self.mu * error * self.reference_buffer[self.filter_length - 1 - i] / norm
)
output.append(error)
return output
class FullDuplexAudioPipeline:
"""
Full-duplex audio pipeline (complete implementation)
Simulating GPT-Live's core audio stream processing
"""
def __init__(
self,
sample_rate: int = 16000,
frame_duration_ms: int = 20, # 20ms per frame, matching Opus
vad: Optional[VoiceActivityDetector] = None,
echo_canceller: Optional[EchoCanceller] = None,
):
self.sample_rate = sample_rate
self.frame_duration_ms = frame_duration_ms
self.frame_size = int(sample_rate * frame_duration_ms / 1000)
self.vad = vad or VoiceActivityDetector()
self.echo_canceller = echo_canceller or EchoCanceller()
self.input_buffer = AudioRingBuffer(max_duration_seconds=60.0)
self.output_buffer = AudioRingBuffer(max_duration_seconds=10.0)
self.state = AudioState.SILENCE
self._running = False
self._on_audio_frame: Optional[Callable[[AudioFrame], Awaitable[None]]] = None
self._on_state_change: Optional[Callable[[AudioState], Awaitable[None]]] = None
async def push_input_frame(self, frame: AudioFrame) -> None:
"""
Process incoming audio frame (from microphone)
Core of full-duplex: input processing does not block output
"""
# 1. Echo cancellation
ref_samples = await self.output_buffer.read(self.frame_size)
if ref_samples:
mic_samples = struct.unpack_from(f"<{len(frame.data)//2}h", frame.data)
cleaned = self.echo_canceller.process(
list(mic_samples), ref_samples
)
frame.data = struct.pack(f"<{len(cleaned)}h", *[int(s) for s in cleaned])
# 2. Write to input buffer
samples = struct.unpack_from(f"<{len(frame.data)//2}h", frame.data)
await self.input_buffer.write(list(samples))
# 3. VAD detection (simulated - in GPT-Live this is built into the model)
new_state = self.vad.process_frame(frame)
if new_state != self.state:
self.state = new_state
if self._on_state_change:
await self._on_state_change(new_state)
# 4. Callback notification
if self._on_audio_frame:
await self._on_audio_frame(frame)
async def push_output_frame(self, frame: AudioFrame) -> None:
"""
Process output audio frame (from model-generated speech)
Also written to output buffer for echo cancellation
"""
samples = struct.unpack_from(f"<{len(frame.data)//2}h", frame.data)
await self.output_buffer.write(list(samples))
async def start(self) -> None:
self._running = True
self.state = AudioState.LISTENING
async def stop(self) -> None:
self._running = False
await self.input_buffer.clear()
await self.output_buffer.clear()
def on_audio_frame(self, callback: Callable[[AudioFrame], Awaitable[None]]) -> None:
self._on_audio_frame = callback
def on_state_change(self, callback: Callable[[AudioState], Awaitable[None]]) -> None:
self._on_state_change = callback
3.4 WebRTC Transport Layer Optimization
The OpenAI team made significant optimizations at the transport layer. The key decision was rewriting the media frontend and inference logic in Go, replacing the previous Python asyncio implementation. The new system’s p95 latency matched the previous system’s p50 (Source: OpenAI Engineering Blog).
WebRTC’s native advantages:
- Packet loss recovery (via FEC and RTX)
- Clock drift compensation (NTP time synchronization)
- Adaptive network response (via congestion control algorithms)
- Audio stretching/compression (handles packet loss and jitter)
Here’s the core Go implementation of a WebRTC audio track:
// Full-duplex WebRTC audio session management
// Simulating GPT-Live's Go-based media frontend
package media
import (
"context"
"encoding/binary"
"io"
"log"
"sync"
"time"
"github.com/pion/webrtc/v4"
"github.com/pion/rtp"
)
// AudioCodec defines audio codec parameters
type AudioCodec struct {
SampleRate int
Channels int
FrameSize int // Samples per frame
Bitrate int
}
// DefaultOpusCodec Opus default configuration (codec used by GPT-Live)
var DefaultOpusCodec = AudioCodec{
SampleRate: 48000,
Channels: 1,
FrameSize: 960, // 20ms @ 48kHz
Bitrate: 32000,
}
// AudioFrame represents an audio frame
type AudioFrame struct {
Timestamp time.Time
Sequence uint16
Data []byte
Duration time.Duration
}
// AudioTrack wraps a WebRTC audio track
type AudioTrack struct {
track *webrtc.TrackLocalStaticSample
codec AudioCodec
seqNum uint16
mu sync.Mutex
onFrame func(AudioFrame)
writeCh chan AudioFrame
ctx context.Context
cancel context.CancelFunc
}
// NewAudioTrack creates a new audio track
func NewAudioTrack(codec AudioCodec) (*AudioTrack, error) {
opusCodec := webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeOpus,
ClockRate: uint32(codec.SampleRate),
Channels: uint16(codec.Channels),
}
track, err := webrtc.NewTrackLocalStaticSample(
opusCodec, "audio", "voice-stream",
)
if err != nil {
return nil, err
}
ctx, cancel := context.WithCancel(context.Background())
return &AudioTrack{
track: track,
codec: codec,
seqNum: 0,
writeCh: make(chan AudioFrame, 256), // Buffer 256 frames
ctx: ctx,
cancel: cancel,
}, nil
}
// WriteFrame writes an audio frame (non-blocking, sent by background goroutine)
func (at *AudioTrack) WriteFrame(frame AudioFrame) error {
select {
case at.writeCh <- frame:
return nil
default:
// Buffer full, drop oldest frame (maintain real-time)
select {
case <-at.writeCh:
// Drop one frame
default:
}
at.writeCh <- frame
return nil
}
}
// Start begins the audio transmission loop
func (at *AudioTrack) Start() {
go func() {
ticker := time.NewTicker(20 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-at.ctx.Done():
return
case frame := <-at.writeCh:
at.mu.Lock()
at.seqNum++
seq := at.seqNum
at.mu.Unlock()
// Build RTP packet
rtpPacket := &rtp.Packet{
Header: rtp.Header{
Version: 2,
PayloadType: 111,
SequenceNumber: seq,
Timestamp: uint32(frame.Timestamp.UnixMicro() * at.codec.SampleRate / 1_000_000),
SSRC: 0xABCDEF01,
Marker: false,
},
Payload: frame.Data,
}
raw, err := rtpPacket.Marshal()
if err != nil {
log.Printf("failed to marshal RTP packet: %v", err)
continue
}
if _, err := at.track.Write(raw); err != nil {
log.Printf("failed to write to track: %v", err)
}
case <-ticker.C:
// Heartbeat: ensure connection stays active
}
}
}()
}
// Stop stops the audio track
func (at *AudioTrack) Stop() {
at.cancel()
}
// MediaSession represents a full-duplex media session
type MediaSession struct {
pc *webrtc.PeerConnection
inputTrack *webrtc.TrackRemote
outputTrack *AudioTrack
clientID string
onFrame func(AudioFrame)
mu sync.RWMutex
}
// NewMediaSession creates a new full-duplex media session
func NewMediaSession(clientID string, api *webrtc.API) (*MediaSession, error) {
config := webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
}
pc, err := api.NewPeerConnection(config)
if err != nil {
return nil, err
}
session := &MediaSession{
pc: pc,
clientID: clientID,
}
// Set up incoming track handler
pc.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
session.mu.Lock()
session.inputTrack = track
session.mu.Unlock()
go session.handleIncomingTrack(track)
})
// Create output track
outputTrack, err := NewAudioTrack(DefaultOpusCodec)
if err != nil {
pc.Close()
return nil, err
}
if _, err := pc.AddTrack(outputTrack.track); err != nil {
pc.Close()
return nil, err
}
session.outputTrack = outputTrack
return session, nil
}
// handleIncomingTrack processes incoming audio stream
func (ms *MediaSession) handleIncomingTrack(track *webrtc.TrackRemote) {
// Jitter buffer: 200ms jitter tolerance
jitterBuffer := NewJitterBuffer(200 * time.Millisecond)
for {
rtpPacket, _, err := track.ReadRTP()
if err != nil {
if err == io.EOF {
return
}
log.Printf("read RTP error: %v", err)
continue
}
frame := AudioFrame{
Timestamp: time.Now(),
Sequence: rtpPacket.Header.SequenceNumber,
Data: rtpPacket.Payload,
Duration: 20 * time.Millisecond,
}
// Process through jitter buffer
jitterBuffer.Push(frame)
}
}
// Start starts the media session
func (ms *MediaSession) Start() {
ms.outputTrack.Start()
}
// Stop stops the media session
func (ms *MediaSession) Stop() {
ms.outputTrack.Stop()
ms.pc.Close()
}
// JitterBuffer handles network delay variation
// GPT-Live uses the same mechanism
type JitterBuffer struct {
buffer []AudioFrame
maxSize int
cond *sync.Cond
mu sync.Mutex
}
func NewJitterBuffer(maxDuration time.Duration) *JitterBuffer {
maxSize := int(maxDuration / (20 * time.Millisecond))
return &JitterBuffer{
buffer: make([]AudioFrame, 0, maxSize),
maxSize: maxSize,
cond: sync.NewCond(&sync.Mutex{}),
}
}
func (jb *JitterBuffer) Push(frame AudioFrame) {
jb.mu.Lock()
defer jb.mu.Unlock()
if len(jb.buffer) >= jb.maxSize {
jb.buffer = jb.buffer[1:]
}
jb.buffer = append(jb.buffer, frame)
jb.cond.Signal()
}
func (jb *JitterBuffer) Pop() (AudioFrame, bool) {
jb.mu.Lock()
defer jb.mu.Unlock()
for len(jb.buffer) == 0 {
jb.cond.Wait()
}
frame := jb.buffer[0]
jb.buffer = jb.buffer[1:]
return frame, true
}
4. Speech Layer and Reasoning Layer Decoupling Architecture
4.1 Dual-Model Architecture Design
GPT-Live’s most critical architectural innovation is the complete separation of “talking” (conversational interaction) from “thinking” (deep reasoning) at the system level. This is achieved through two independent models:
- GPT-Live-1 (Frontend): Full-duplex voice model responsible for low-latency, natural conversational interaction
- GPT-5.5 (Backend): Frontier reasoning model responsible for search, complex reasoning, and tool use
The interaction between these two layers is handled through an asynchronous RPC boundary. The core media path is never blocked by backend reasoning.
4.2 Architecture Diagram
┌─────────────────────────────────────────────────────────────────┐
│ GPT-Live System Architecture │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Live Media Path │ │
│ │ │ │
│ │ ┌────────┐ ┌──────────┐ ┌────────────────┐ │ │
│ │ │Client │◄──►│ Go Media │◄──►│ GPT-Live-1 │ │ │
│ │ │WebRTC │ │ Frontend │ │ Full-Duplex │ │ │
│ │ └────────┘ └──────────┘ │ Voice Model │ │ │
│ │ └───────┬────────┘ │ │
│ │ │ │ │
│ └────────────────────────────────────────┼────────────┘ │
│ │ │
│ Async RPC Boundary │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Delegation Path (Async) │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Inference Scheduler (Go) │ │ │
│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │
│ │ │ │ Session │ │ Context │ │ Result │ │ │ │
│ │ │ │ Manager │ │ Cache │ │ Merger │ │ │ │
│ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │
│ │ └──────────────────────┬───────────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────────────────▼───────────────────────┐ │ │
│ │ │ GPT-5.5 Inference Instance │ │ │
│ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │
│ │ │ │ Search │ │ Reasoning│ │ Tool Use │ │ │ │
│ │ │ │ (Web) │ │ (Chain) │ │ (Func) │ │ │ │
│ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
4.3 Async Delegation Scheduler Implementation (Go)
Below is the core implementation of GPT-Live’s asynchronous inference scheduler:
// Async Inference Scheduler
// Manages concurrent task scheduling between the frontend voice model
// and the backend reasoning model
package scheduler
import (
"context"
"fmt"
"log"
"sync"
"time"
)
// DelegationRequest represents a delegation request
type DelegationRequest struct {
ID string
SessionID string
Query string
Context *ConversationContext
Tools []ToolDefinition
Effort ReasoningEffort
CreatedAt time.Time
ResultCh chan *DelegationResult
}
// DelegationResult represents a delegation result
type DelegationResult struct {
RequestID string
Content string
ToolResults []ToolResult
TokenUsage TokenUsage
Latency time.Duration
Error error
}
// ReasoningEffort defines reasoning intensity
type ReasoningEffort int
const (
EffortInstant ReasoningEffort = iota
EffortMedium
EffortHigh
)
// TokenUsage tracks token consumption
type TokenUsage struct {
PromptTokens int
CompletionTokens int
TotalTokens int
}
// ToolDefinition defines a tool
type ToolDefinition struct {
Name string
Description string
Parameters map[string]interface{}
}
// ToolResult represents a tool call result
type ToolResult struct {
ToolName string
Content string
Success bool
}
// ConversationContext holds the conversation state
type ConversationContext struct {
Messages []Message
TokenCount int
LastActivity time.Time
}
// Message represents a conversation message
type Message struct {
Role string
Content string
AudioID string
Time time.Time
}
// SessionState tracks a session's state
type SessionState struct {
ID string
VoiceModelID string
FrontierModelID string
Context *ConversationContext
PendingRequests map[string]*DelegationRequest
CreatedAt time.Time
mu sync.RWMutex
}
// InferenceScheduler manages async task delegation
// Core responsibility: scheduling async tasks between voice model
// and frontier reasoning model
type InferenceScheduler struct {
mu sync.RWMutex
sessions map[string]*SessionState
frontierModel FrontierModelClient
promptCache *PromptCache
workerPool *WorkerPool
maxRetries int
}
// FrontierModelClient interface for frontier model inference
type FrontierModelClient interface {
Infer(ctx context.Context, req *DelegationRequest) (*DelegationResult, error)
PrefillContext(ctx context.Context, sessionID string, context *ConversationContext) error
HealthCheck(ctx context.Context) bool
}
// NewInferenceScheduler creates a new inference scheduler
func NewInferenceScheduler(
frontier FrontierModelClient,
maxWorkers int,
cacheSize int,
) *InferenceScheduler {
return &InferenceScheduler{
sessions: make(map[string]*SessionState),
frontierModel: frontier,
promptCache: NewPromptCache(cacheSize),
workerPool: NewWorkerPool(maxWorkers),
maxRetries: 3,
}
}
// RegisterSession registers a new session
// Pre-creates a frontier inference session and pre-fills context
// when the voice session starts
func (s *InferenceScheduler) RegisterSession(
sessionID string,
initialContext *ConversationContext,
) error {
s.mu.Lock()
defer s.mu.Unlock()
state := &SessionState{
ID: sessionID,
Context: initialContext,
PendingRequests: make(map[string]*DelegationRequest),
CreatedAt: time.Now(),
}
// Pre-fill frontier model context (key GPT-Live optimization)
// Ensures prompt is processed before first delegation request
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := s.frontierModel.PrefillContext(ctx, sessionID, initialContext); err != nil {
log.Printf("failed to prefill context for session %s: %v", sessionID, err)
// Non-fatal; continue registration
}
s.sessions[sessionID] = state
log.Printf("registered session %s with prefilled frontier context", sessionID)
return nil
}
// DelegateTask asynchronously delegates a task
// Core GPT-Live operation: voice model delegates complex tasks
// to the backend reasoning model
// Returns a channel allowing the voice model to continue talking while waiting
func (s *InferenceScheduler) DelegateTask(
sessionID string,
query string,
tools []ToolDefinition,
effort ReasoningEffort,
) (<-chan *DelegationResult, error) {
s.mu.RLock()
session, exists := s.sessions[sessionID]
s.mu.RUnlock()
if !exists {
return nil, fmt.Errorf("session %s not found", sessionID)
}
req := &DelegationRequest{
ID: generateRequestID(),
SessionID: sessionID,
Query: query,
Context: session.Context,
Tools: tools,
Effort: effort,
CreatedAt: time.Now(),
ResultCh: make(chan *DelegationResult, 1),
}
session.mu.Lock()
session.PendingRequests[req.ID] = req
session.mu.Unlock()
// Submit to worker pool asynchronously
s.workerPool.Submit(func() {
result := s.executeDelegationWithRetry(req)
session.mu.Lock()
delete(session.PendingRequests, req.ID)
session.mu.Unlock()
req.ResultCh <- result
close(req.ResultCh)
})
return req.ResultCh, nil
}
// executeDelegationWithRetry executes delegation with retry logic
func (s *InferenceScheduler) executeDelegationWithRetry(req *DelegationRequest) *DelegationResult {
var lastErr error
for attempt := 0; attempt <= s.maxRetries; attempt++ {
if attempt > 0 {
backoff := time.Duration(100*attempt) * time.Millisecond
time.Sleep(backoff)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
start := time.Now()
result, err := s.frontierModel.Infer(ctx, req)
cancel()
if err == nil {
result.Latency = time.Since(start)
return result
}
lastErr = err
log.Printf("delegation attempt %d failed for request %s: %v",
attempt+1, req.ID, err)
}
return &DelegationResult{
RequestID: req.ID,
Error: fmt.Errorf("all retries failed: %v", lastErr),
}
}
// GetSessionContext retrieves session context
func (s *InferenceScheduler) GetSessionContext(sessionID string) *ConversationContext {
s.mu.RLock()
defer s.mu.RUnlock()
if session, exists := s.sessions[sessionID]; exists {
return session.Context
}
return nil
}
// UpdateSessionContext updates session context
// Handles synchronization between background task results
// and real-time user corrections
func (s *InferenceScheduler) UpdateSessionContext(
sessionID string,
messages []Message,
) error {
s.mu.Lock()
defer s.mu.Unlock()
session, exists := s.sessions[sessionID]
if !exists {
return fmt.Errorf("session %s not found", sessionID)
}
session.mu.Lock()
defer session.mu.Unlock()
session.Context.Messages = append(session.Context.Messages, messages...)
session.Context.TokenCount = calculateTokenCount(session.Context.Messages)
session.Context.LastActivity = time.Now()
return nil
}
// UnregisterSession removes a session
func (s *InferenceScheduler) UnregisterSession(sessionID string) {
s.mu.Lock()
defer s.mu.Unlock()
if session, exists := s.sessions[sessionID]; exists {
session.mu.Lock()
for _, req := range session.PendingRequests {
close(req.ResultCh)
}
session.mu.Unlock()
delete(s.sessions, sessionID)
log.Printf("unregistered session %s", sessionID)
}
}
// WorkerPool goroutine worker pool
type WorkerPool struct {
workers chan struct{}
wg sync.WaitGroup
}
func NewWorkerPool(maxWorkers int) *WorkerPool {
return &WorkerPool{
workers: make(chan struct{}, maxWorkers),
}
}
func (wp *WorkerPool) Submit(task func()) {
wp.workers <- struct{}{}
wp.wg.Add(1)
go func() {
defer func() {
<-wp.workers
wp.wg.Done()
}()
task()
}()
}
func (wp *WorkerPool) Wait() {
wp.wg.Wait()
}
// PromptCache caches prompt prefixes with KV state
type PromptCache struct {
mu sync.RWMutex
items map[string]*CachedPrompt
size int
}
type CachedPrompt struct {
Prefix string
KVState []byte // Cached KV cache state
CreatedAt time.Time
HitCount int
}
func NewPromptCache(size int) *PromptCache {
return &PromptCache{
items: make(map[string]*CachedPrompt),
size: size,
}
}
func (pc *PromptCache) Get(key string) (*CachedPrompt, bool) {
pc.mu.RLock()
defer pc.mu.RUnlock()
item, ok := pc.items[key]
if ok {
item.HitCount++
}
return item, ok
}
func (pc *PromptCache) Set(key string, item *CachedPrompt) {
pc.mu.Lock()
defer pc.mu.Unlock()
if len(pc.items) >= pc.size {
// LRU eviction
var minKey string
minHits := int(^uint(0) >> 1)
for k, v := range pc.items {
if v.HitCount < minHits {
minHits = v.HitCount
minKey = k
}
}
delete(pc.items, minKey)
}
pc.items[key] = item
}
// Helper functions
func generateRequestID() string {
return fmt.Sprintf("req_%d_%d", time.Now().UnixNano(), randInt(1000, 9999))
}
func randInt(min, max int) int {
return min + int(time.Now().UnixNano()%int64(max-min+1))
}
func calculateTokenCount(messages []Message) int {
total := 0
for _, msg := range messages {
total += len(msg.Content) / 4 // Rough estimate
}
return total
}
4.4 Latency Optimization Strategies
GPT-Live employs multiple optimization layers on the delegation path to ensure backend reasoning results integrate quickly into the conversation:
1. Pre-filled Inference Sessions
When a voice session starts, the application server simultaneously creates an inference session for the frontier model, pre-filling it with initial context. This ensures that by the time the first delegation request is made, the prompt has already been fully processed. The inference session remains available for the entire voice call (Source: OpenAI Engineering Blog).
2. Stable Session Affinity
Consecutive requests are bound to the same inference instance, leveraging prompt caching to significantly reduce latency for subsequent requests.
3. Tiered Reasoning Effort
Users can choose between Instant, Medium, and High reasoning effort levels, corresponding to GPT-5.5 Instant and GPT-5.5 Thinking model configurations.
// Reasoning effort configuration
func (s *InferenceScheduler) getEffortConfig(effort ReasoningEffort) EffortConfig {
switch effort {
case EffortInstant:
return EffortConfig{
ModelName: "gpt-5.5-instant",
MaxTokens: 512,
ReasoningEffort: 0,
Temperature: 0.7,
Timeout: 10 * time.Second,
}
case EffortMedium:
return EffortConfig{
ModelName: "gpt-5.5-thinking",
MaxTokens: 2048,
ReasoningEffort: 1,
Temperature: 0.5,
Timeout: 30 * time.Second,
}
case EffortHigh:
return EffortConfig{
ModelName: "gpt-5.5-thinking",
MaxTokens: 4096,
ReasoningEffort: 2,
Temperature: 0.3,
Timeout: 60 * time.Second,
}
default:
return EffortConfig{}
}
}
type EffortConfig struct {
ModelName string
MaxTokens int
ReasoningEffort int
Temperature float64
Timeout time.Duration
}
5. State Management and Context Compaction
5.1 Challenges of Stateful Inference
Voice sessions can last for extended periods (tens of minutes or more), with context continuously growing and model instances spinning up and down based on demand. GPT-Live needs to solve three core problems:
- Context overflow: Accumulated context exceeds the model’s window
- KV cache invalidation: After context compaction, attention key-value caches need rebuilding
- Seamless handoff: Model instance switching must not interrupt the audio stream
5.2 Seamless Handoff Mechanism
The OpenAI team built a seamless handoff mechanism across model instances:
Timeline:
┌──────────────────────────────────────────────────────────────┐
│ Model Instance A (running) │
│ ├── Continuously processing audio stream │
│ ├── Context growing │
│ └── Context compaction triggered │
│ │
│ ┌─ Background Operation ───────────────────────────┐ │
│ │ ① Create compacted context from current context │ │
│ │ ② Warm up Model Instance B, prefill compacted │ │
│ │ context │ │
│ │ ③ Instance A and B run inference in parallel │ │
│ │ ④ When B is ready, audio stream switches to B │ │
│ │ seamlessly │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ Model Instance B (takes over) │
│ ├── Continues audio processing (no interruption) │
│ └── Context already compacted, can continue growing │
└──────────────────────────────────────────────────────────────┘
5.3 Context Manager Implementation
// Context Manager
// Handles session context compaction, handoff, and state synchronization
package context
import (
"context"
"log"
"sync"
"time"
)
// ContextState represents context processing state
type ContextState int
const (
StateActive ContextState = iota
StateCompacting
StateReady
StateSwitching
)
// SessionContext holds session context data
type SessionContext struct {
ID string
Messages []Message
TokenCount int
KVState []byte
LastActivity time.Time
State ContextState
StateChangedAt time.Time
mu sync.RWMutex
}
// Message represents a conversation message
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
Time time.Time `json:"time"`
}
// ContextManager manages session contexts
type ContextManager struct {
mu sync.RWMutex
sessions map[string]*SessionContext
maxContextTokens int
compactionRatio float64
compactionFunc CompactionFunc
}
// CompactionFunc defines the context compaction function signature
type CompactionFunc func(messages []Message, maxTokens int) ([]Message, int)
// NewContextManager creates a new context manager
func NewContextManager(
maxContextTokens int,
compactionRatio float64,
compactionFunc CompactionFunc,
) *ContextManager {
if compactionFunc == nil {
compactionFunc = defaultCompactionFunc
}
return &ContextManager{
sessions: make(map[string]*SessionContext),
maxContextTokens: maxContextTokens,
compactionRatio: compactionRatio,
compactionFunc: compactionFunc,
}
}
// defaultCompactionFunc implements the default compaction strategy
// Strategy: keep system prompt and recent messages, summarize middle history
func defaultCompactionFunc(messages []Message, maxTokens int) ([]Message, int) {
if len(messages) == 0 {
return messages, 0
}
// Keep system prompt (first message)
systemMsg := messages[0]
// Keep recent N messages
keepRecent := 10
if keepRecent >= len(messages) {
keepRecent = len(messages) - 1
}
compacted := make([]Message, 0, keepRecent+2)
compacted = append(compacted, systemMsg)
// Summarize middle messages
midMessages := messages[1 : len(messages)-keepRecent]
if len(midMessages) > 0 {
summary := summarizeMessages(midMessages)
compacted = append(compacted, Message{
Role: "system",
Content: summary,
Time: time.Now(),
})
}
// Append recent messages
compacted = append(compacted, messages[len(messages)-keepRecent:]...)
totalTokens := 0
for _, msg := range compacted {
totalTokens += len(msg.Content) / 4
}
return compacted, totalTokens
}
// summarizeMessages creates a summary of messages (simplified)
func summarizeMessages(messages []Message) string {
if len(messages) == 0 {
return ""
}
summary := "[Compressed conversation summary: "
summary += "The conversation covers "
if len(messages) <= 5 {
summary += "a brief exchange"
} else {
summary += "an extended discussion"
}
summary += " with " + messages[0].Role + " starting the conversation"
summary += " ending with " + messages[len(messages)-1].Role + "'s last message at "
summary += messages[len(messages)-1].Time.Format("15:04:05")
summary += "]"
return summary
}
// RegisterSession registers a new session
func (cm *ContextManager) RegisterSession(id string, initialMessages []Message) {
cm.mu.Lock()
defer cm.mu.Unlock()
tokenCount := 0
for _, msg := range initialMessages {
tokenCount += len(msg.Content) / 4
}
cm.sessions[id] = &SessionContext{
ID: id,
Messages: initialMessages,
TokenCount: tokenCount,
LastActivity: time.Now(),
State: StateActive,
StateChangedAt: time.Now(),
}
log.Printf("registered session context: %s (%d tokens)", id, tokenCount)
}
// AppendMessage appends a message to the session
func (cm *ContextManager) AppendMessage(sessionID string, msg Message) error {
cm.mu.Lock()
session, exists := cm.sessions[sessionID]
cm.mu.Unlock()
if !exists {
return nil
}
session.mu.Lock()
defer session.mu.Unlock()
session.Messages = append(session.Messages, msg)
session.TokenCount += len(msg.Content) / 4
session.LastActivity = time.Now()
// Check if compaction is needed
if session.TokenCount > cm.maxContextTokens {
go cm.CompactContext(sessionID)
}
return nil
}
// CompactContext asynchronously compacts context
// Core logic: keep original instance running while preparing new instance
func (cm *ContextManager) CompactContext(sessionID string) {
cm.mu.RLock()
session, exists := cm.sessions[sessionID]
cm.mu.RUnlock()
if !exists {
return
}
session.mu.Lock()
if session.State == StateCompacting || session.State == StateSwitching {
session.mu.Unlock()
return
}
session.State = StateCompacting
session.StateChangedAt = time.Now()
currentMessages := make([]Message, len(session.Messages))
copy(currentMessages, session.Messages)
session.mu.Unlock()
// Execute compaction in background (does not block media path)
targetTokens := int(float64(cm.maxContextTokens) * cm.compactionRatio)
compactedMessages, newTokenCount := cm.compactionFunc(currentMessages, targetTokens)
log.Printf("compacted session %s: %d -> %d tokens",
sessionID, session.TokenCount, newTokenCount)
// Update context
session.mu.Lock()
session.Messages = compactedMessages
session.TokenCount = newTokenCount
session.KVState = nil // KV cache invalidated, needs rebuild
session.State = StateReady
session.StateChangedAt = time.Now()
session.mu.Unlock()
// Notify scheduler to prepare new instance
log.Printf("context compaction complete for session %s, ready for handoff", sessionID)
}
// GetSessionContext retrieves a snapshot of the session context
func (cm *ContextManager) GetSessionContext(sessionID string) *SessionContext {
cm.mu.RLock()
session, exists := cm.sessions[sessionID]
cm.mu.RUnlock()
if !exists {
return nil
}
session.mu.RLock()
defer session.mu.RUnlock()
ctxCopy := &SessionContext{
ID: session.ID,
Messages: make([]Message, len(session.Messages)),
TokenCount: session.TokenCount,
LastActivity: session.LastActivity,
State: session.State,
}
copy(ctxCopy.Messages, session.Messages)
return ctxCopy
}
// UnregisterSession removes a session
func (cm *ContextManager) UnregisterSession(sessionID string) {
cm.mu.Lock()
defer cm.mu.Unlock()
delete(cm.sessions, sessionID)
log.Printf("unregistered session context: %s", sessionID)
}
6. Deriving Discrete Turns from Continuous Speech
6.1 The Core Problem
Although GPT-Live’s voice model operates on continuous speech streams, peripheral systems like ChatGPT’s UI, safety analysis, and logging still require discrete user and assistant messages. This creates a fundamental tension: how to extract discrete conversational turns from a continuous input stream?
6.2 Speculative Message Queue
OpenAI’s solution maintains a speculative message queue:
Timeline ────────────────────────────────────────────────────────────►
User Speech: "well...I was wondering...umm...GPT-5.5 performance..."
↑ ↑ ↑ ↑
│ │ │ │
Msg State: [Provisional] [Provisional] [Provisional] [Finalized]
Speaker:User Speaker:User Speaker:User Speaker:User
Text:mutable Text:mutable Text:mutable Text:locked
Core logic:
- The newest message is always provisional
- As more speech arrives, its text, timing, and speaker assignment can all change
- Only when a speaker has sustained the floor long enough for reliable attribution does the server finalize the message
- The system maintains two views: a speculative view (for real-time UI updates) and an authoritative view (for analytics logging)
6.3 Message Extractor Implementation
"""
Extracting discrete messages from continuous speech streams
Simulating GPT-Live's speculative message queue design
"""
import asyncio
import time
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
class Speaker(Enum):
USER = "user"
ASSISTANT = "assistant"
UNKNOWN = "unknown"
class MessageStatus(Enum):
PROVISIONAL = "provisional"
FINALIZED = "finalized"
@dataclass
class TranscriptSegment:
text: str
speaker: Speaker
start_time: float
end_time: float
confidence: float
is_acknowledgment: bool = False
@dataclass
class DiscreteMessage:
id: str
speaker: Speaker
text: str
start_time: float
end_time: float
status: MessageStatus
segments: list[TranscriptSegment] = field(default_factory=list)
is_acknowledgment: bool = False
class SpeculativeMessageQueue:
"""
Speculative message queue
Extracts discrete messages from continuous speech streams
Maintains two views: speculative (UI) and authoritative (logging)
"""
def __init__(
self,
finalize_after_silence_ms: float = 800.0,
min_utterance_ms: float = 300.0,
acknowledgment_threshold_ms: float = 500.0,
):
self.finalize_after_silence_ms = finalize_after_silence_ms
self.min_utterance_ms = min_utterance_ms
self.acknowledgment_threshold_ms = acknowledgment_threshold_ms
self._queue: list[DiscreteMessage] = []
self._current_message: Optional[DiscreteMessage] = None
self._last_segment_time: float = 0.0
self._message_counter = 0
self._speculative_view: list[DiscreteMessage] = []
self._authoritative_view: list[DiscreteMessage] = []
def process_segment(self, segment: TranscriptSegment) -> list[DiscreteMessage]:
now = segment.start_time
changes = []
time_since_last = (now - self._last_segment_time) * 1000
if time_since_last > self.finalize_after_silence_ms:
if self._current_message and self._current_message.status == MessageStatus.PROVISIONAL:
self._finalize_current_message()
changes.append(self._current_message)
self._start_new_message(segment)
changes.append(self._current_message)
elif self._current_message is None:
self._start_new_message(segment)
changes.append(self._current_message)
elif segment.speaker != self._current_message.speaker:
if self._current_message.status == MessageStatus.PROVISIONAL:
if self._is_acknowledgment(segment):
segment.is_acknowledgment = True
self._current_message.segments.append(segment)
self._current_message.is_acknowledgment = True
else:
self._finalize_current_message()
changes.append(self._current_message)
self._start_new_message(segment)
changes.append(self._current_message)
else:
self._current_message.segments.append(segment)
self._current_message.text = self._build_text(self._current_message.segments)
self._current_message.end_time = segment.end_time
self._update_speculative_view()
self._last_segment_time = segment.end_time
return changes
def _start_new_message(self, segment: TranscriptSegment) -> None:
self._message_counter += 1
self._current_message = DiscreteMessage(
id=f"msg_{self._message_counter}",
speaker=segment.speaker,
text=segment.text,
start_time=segment.start_time,
end_time=segment.end_time,
status=MessageStatus.PROVISIONAL,
segments=[segment],
is_acknowledgment=segment.is_acknowledgment,
)
self._queue.append(self._current_message)
self._update_speculative_view()
def _finalize_current_message(self) -> None:
if self._current_message is None:
return
duration = (self._current_message.end_time - self._current_message.start_time) * 1000
if duration < self.min_utterance_ms and not self._current_message.is_acknowledgment:
self._queue.remove(self._current_message)
self._update_speculative_view()
self._current_message = None
return
self._current_message.status = MessageStatus.FINALIZED
self._authoritative_view.append(self._current_message)
self._current_message = None
def _is_acknowledgment(self, segment: TranscriptSegment) -> bool:
acknowledgment_words = {
"mhmm", "uh-huh", "yeah", "okay", "got it", "right",
"i see", "aha", "mm", "hmm", "嗯", "嗯哼", "好的",
}
return segment.text.strip().lower() in acknowledgment_words
def _build_text(self, segments: list[TranscriptSegment]) -> str:
return " ".join(seg.text for seg in segments)
def _update_speculative_view(self) -> None:
self._speculative_view = [
msg for msg in self._queue
if msg.status == MessageStatus.PROVISIONAL
]
def get_speculative_view(self) -> list[DiscreteMessage]:
return list(self._speculative_view)
def get_authoritative_view(self) -> list[DiscreteMessage]:
return list(self._authoritative_view)
def get_all_messages(self) -> list[DiscreteMessage]:
return list(self._queue)
# Usage demonstration
async def demo_speculative_queue():
queue = SpeculativeMessageQueue()
segments = [
TranscriptSegment("well", Speaker.USER, 0.0, 0.3, 0.95),
TranscriptSegment("I was wondering", Speaker.USER, 0.3, 0.8, 0.92),
TranscriptSegment("how GPT-5.5 performs", Speaker.USER, 0.8, 1.8, 0.88),
# 800ms silence → message finalized
# Assistant starts answering
TranscriptSegment("sure", Speaker.ASSISTANT, 2.8, 3.0, 0.97),
TranscriptSegment("GPT-5.5 compared to previous", Speaker.ASSISTANT, 3.0, 3.8, 0.95),
# User interrupts
TranscriptSegment("specific numbers", Speaker.USER, 3.8, 4.2, 0.90),
]
for seg in segments:
changes = queue.process_segment(seg)
if changes:
for msg in changes:
print(f"[{msg.status.value}] {msg.speaker.value}: {msg.text}")
print(f"\nSpeculative view: {len(queue.get_speculative_view())} messages")
print(f"Authoritative view: {len(queue.get_authoritative_view())} messages")
for msg in queue.get_authoritative_view():
print(f" [{msg.speaker.value}] {msg.text}")
asyncio.run(demo_speculative_queue())
7. Session Startup Optimization: WARP and Instant Connect
7.1 Standard WebRTC Handshake Overhead
A standard WebRTC connection establishment requires 6 network round trips:
Client Server
│ │
│── ICE Binding Request ──────────────────►│ RTT 1
│◄── ICE Binding Response ─────────────────│
│ │
│── DTLS ClientHello ─────────────────────►│ RTT 2
│◄── DTLS ServerHello ─────────────────────│
│◄── DTLS Certificate ─────────────────────│ RTT 3
│── DTLS Finished ────────────────────────►│
│ │
│── SCTP INIT ────────────────────────────►│ RTT 4
│◄── SCTP INIT_ACK ────────────────────────│
│── SCTP COOKIE_ECHO ─────────────────────►│ RTT 5
│◄── SCTP COOKIE_ACK ──────────────────────│
│ │
│── DCEP ---- data channel ───────────────►│ RTT 6
│ │
6 RTTs = 300-600ms latency
7.2 WARP Protocol Optimization
OpenAI collaborated with the WebRTC community to design WARP (WebRTC Abridged Roundtrip Protocol), reducing 6 network round trips to just 1 (Source: OpenAI Engineering Blog).
WARP’s core optimizations:
- SPED: Piggyback DTLS handshake over ICE
- DTLS 1.3: Use faster DTLS 1.3 handshake (1-RTT vs 2-RTT)
- SNAP: Pre-negotiate SCTP handshake
- Pre-negotiated data channels: Bypass DCEP
WARP has been submitted as an IETF draft (https://datatracker.ietf.org/doc/draft-uberti-tsvwg-warp/) and has been integrated into libwebrtc and Pion.
7.3 Instant Connect
Beyond WARP, OpenAI developed Instant Connect technology, which pre-negotiates SDP parameters before connection:
// Instant Connect Implementation
// Pre-negotiates SDP parameters for single-UDP-packet session startup
package transport
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"log"
"net"
"time"
)
// PreNegotiatedSession represents a pre-negotiated session
type PreNegotiatedSession struct {
SessionID string
Ufrag string
Pwd string
Fingerprint string
AudioCodec string
CreatedAt time.Time
ExpiresAt time.Time
IsUsed bool
}
// InstantConnectManager manages instant connections
type InstantConnectManager struct {
preSessions map[string]*PreNegotiatedSession
sessionTTL time.Duration
audioCodec string
serverUfrag string
serverPwd string
serverFingerprint string
}
// NewInstantConnectManager creates a new instant connect manager
func NewInstantConnectManager(
sessionTTL time.Duration,
audioCodec string,
) *InstantConnectManager {
return &InstantConnectManager{
preSessions: make(map[string]*PreNegotiatedSession),
sessionTTL: sessionTTL,
audioCodec: audioCodec,
serverUfrag: generateICEUfrag(),
serverPwd: generateICEPwd(),
serverFingerprint: generateFingerprint(),
}
}
// GeneratePreSession generates a pre-negotiated session
// Completed before the user even clicks the button
func (m *InstantConnectManager) GeneratePreSession() *PreNegotiatedSession {
sessionID := generateSessionID()
session := &PreNegotiatedSession{
SessionID: sessionID,
Ufrag: generateICEUfrag(),
Pwd: generateICEPwd(),
Fingerprint: m.serverFingerprint,
AudioCodec: m.audioCodec,
CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(m.sessionTTL),
IsUsed: false,
}
m.preSessions[sessionID] = session
log.Printf("generated pre-negotiated session: %s (expires %v)",
sessionID, session.ExpiresAt)
return session
}
// HandleFirstPacket handles the first UDP packet
// Server responds immediately when the client sends the first media packet
func (m *InstantConnectManager) HandleFirstPacket(
conn *net.UDPConn,
addr *net.UDPAddr,
packet []byte,
) (*PreNegotiatedSession, error) {
sessionID := extractSessionID(packet)
session, exists := m.preSessions[sessionID]
if !exists {
return nil, nil // Fall back to standard signaling
}
if time.Now().After(session.ExpiresAt) {
delete(m.preSessions, sessionID)
return nil, nil
}
if session.IsUsed {
return nil, nil
}
session.IsUsed = true
// Respond immediately
response := m.buildImmediateResponse(session)
conn.WriteTo(response, addr)
log.Printf("instant connect for session %s from %s", sessionID, addr)
return session, nil
}
// buildImmediateResponse builds an immediate response
func (m *InstantConnectManager) buildImmediateResponse(
session *PreNegotiatedSession,
) []byte {
response := map[string]interface{}{
"type": "instant_connect",
"session_id": session.SessionID,
"ufrag": m.serverUfrag,
"pwd": m.serverPwd,
"fingerprint": m.serverFingerprint,
"codec": session.AudioCodec,
"timestamp": time.Now().UnixMilli(),
}
data, _ := json.Marshal(response)
return data
}
// Helper functions
func generateSessionID() string {
b := make([]byte, 16)
rand.Read(b)
return hex.EncodeToString(b)
}
func generateICEUfrag() string {
b := make([]byte, 8)
rand.Read(b)
return hex.EncodeToString(b)
}
func generateICEPwd() string {
b := make([]byte, 22)
rand.Read(b)
return hex.EncodeToString(b)
}
func generateFingerprint() string {
return "sha-256 " + generateSessionID()
}
func extractSessionID(packet []byte) string {
if len(packet) < 4 {
return ""
}
return string(packet[4:])
}
The combination of WARP and Instant Connect allows the client to start a session with a single UDP packet, with the server responding immediately. Compared to the traditional 6-round-trip handshake, startup latency is reduced by approximately 80-90% (Source: OpenAI Engineering Blog).
8. Production Testing and Deployment
8.1 Shadow Testing
Before deploying to production, OpenAI conducted months of shadow testing — routing production traffic to both the legacy Advanced Voice Mode and the new GPT-Live system simultaneously, with the new system running in read-only mode without affecting user experience.
Key lessons from shadow testing:
Capacity ≠ GPU throughput: Voice sessions stay open and send frames continuously, so CPU-side stream handlers, queues, and network paths must scale alongside inference. Under real load, supporting components saturated earlier than load test estimates predicted.
Geography is a first-order concern: Routing a session to distant capacity adds delay at several points during startup and streaming.
Long-running sessions expose deep issues: Extended sessions revealed memory and persistence pressure, compaction and state restoration during reconnects, and race conditions during client disconnection.
8.2 Observability
The OpenAI team added more granular telemetry, validation against known-good configurations, staged deployment ramps, and the ability to isolate or disable individual paths quickly.
9. Conclusion and Outlook
GPT-Live’s full-duplex architecture represents a fundamental shift in voice AI from “turn-based conversation” to “continuous streaming conversation.” Its core breakthroughs can be summarized at three levels:
- Architecture: Full-duplex audio + media/logic separation + async delegation, achieving complete decoupling of the speech layer from the reasoning layer
- Engineering: Go-rewritten media frontend (p95 = old p50), WARP protocol (6 RTT → 1 RTT), Instant Connect (single UDP packet startup)
- Product: Removal of the turn detector, speculative message queue, seamless context compaction
OpenAI states that this architecture is already becoming a broader platform for realtime interaction, set to power more devices, apps, and modalities without sacrificing the immediacy that makes voice conversation feel truly live (Source: OpenAI Engineering Blog).
References
- OpenAI, “Introducing GPT‑Live”, July 8, 2026. https://openai.com/index/introducing-gpt-live/
- Justin Uberti and Zahan Malkani, “How we built a realtime system for responsive voice AI in six months”, August 3, 2026. https://openai.com/index/continuous-voice-interaction-with-gpt-live/
- OpenAI, “Delivering low-latency voice AI at scale”, 2026. https://openai.com/index/delivering-low-latency-voice-ai-at-scale/
- IETF, “WARP: WebRTC Abridged Roundtrip Protocol”, https://datatracker.ietf.org/doc/draft-uberti-tsvwg-warp/
- IETF, “SPED: Speedy DTLS Encapsulation”, https://datatracker.ietf.org/doc/draft-hancke-webrtc-sped/
- IETF, “SNAP: SCTP Negotiation Acceleration Protocol”, https://datatracker.ietf.org/doc/draft-hancke-tsvwg-snap/
- RFC 9147, “The Datagram Transport Layer Security (DTLS) Protocol Version 1.3”, https://www.rfc-editor.org/rfc/rfc9147.html
- RFC 8832, “WebRTC Data Channel Establishment Protocol (DCEP)”, https://www.rfc-editor.org/rfc/rfc8832.html