agents-go
Deploying

Graceful drain

Draining a worker so a deploy or scale-in never drops a call.

Voice sessions can last minutes. A rolling deploy or a scale-in must never cut a live call — so before a worker terminates, you drain it: stop taking new work, let the in-flight work finish.

Draining a worker

func (w *Worker) Drain(ctx context.Context) error

Drain stops the worker accepting new jobs and waits for outstanding work to complete before returning. "Outstanding" counts both:

  • running jobs — active sessions still on a call, and
  • accepted-but-not-yet-assigned reservations — jobs the worker has taken but not started.

Counting reservations too is what closes the race where a job is accepted the instant a naive drain would have declared itself done.

Deploy / scale-in flow

Signal shutdown

On SIGTERM (or your orchestrator's pre-stop hook), call Drain instead of exiting immediately.

Let calls finish

The worker reports full, so the dispatcher places new sessions on other workers; existing sessions run to their natural end.

Exit

Drain returns once everything has finished (or your drain-deadline context fires). Then terminate the process.

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

go func() { _ = w.Run(runCtx, source) }()

<-ctx.Done()                 // shutdown signal
drainCtx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
_ = w.Drain(drainCtx)        // wait out live calls, bounded by the deadline

Give the drain a deadline (via the context) that matches your orchestrator's termination grace period, so a stuck session can't hold a deploy open forever.

Boundaries

Drain covers in-process completion. Reconnect/retry and job migration across nodes are handled by the LiveKit wire adapter and remain future work — a drained worker finishes its own calls; it doesn't hand live calls to another worker.

On this page