Skip to main content
guard() is VoiceGateway’s control seam. It wraps a native provider (a LiveKit plugin or a Pipecat service) and returns a drop-in replacement of the same type that adds three controls: fallback, rate limiting, and spend caps. guard() writes no metrics; attach() is the sole meter, so guard(provider) plus attach(session) never double-counts. The two seams never call each other: they coordinate only through ContextVars and shared spend/limit state in the framework-neutral core. Use attach() on every session; reach for guard() on the specific providers where you want fallback or limits.

Signature

Raises ImportError when the provider’s framework extra is not installed, and ValueError when rate_limit / budget can’t be parsed or the provider’s framework isn’t recognized (see Frameworks and extras).
  • fallback: on a primary-provider error, each fallback runs in order until one succeeds. attach() stamps fallback_from=<primary> and status="fallback" on the row for the provider that actually produced the result. If every provider fails, the last error is re-raised.
  • rate_limit: a token bucket parsed from the DSL ("60/min", "5/s"). An empty bucket raises RateLimitExceeded.
  • budget: a spend cap parsed from the DSL ("$5.00/day", "$100/month"). guard reads the window’s accumulated spend from the core and raises BudgetExceededError when it is at or over the cap. This closes the measure-then-enforce loop: enforcement reads the cost data attach() already wrote.

Fallback only covers pre-output failures

A real constraint on both frameworks, for any streaming modality (LLM, TTS): guard opens the primary provider and starts consuming its stream. An error before the first chunk moves to the next fallback; once the primary has yielded a chunk, the request is committed to it and a later failure surfaces rather than getting silently patched over. STT is a single call-and-response on both frameworks, so it’s naturally all-or-nothing. Size a fallback chain for “primary is down” rather than “primary died mid-stream.”

Wiring

guard() returns a subclass of the LiveKit base (livekit.agents.{llm,stt,tts}) so the result slots into an AgentSession unchanged:
Guard any combination of modalities independently: only wrap the providers where you actually need fallback or limits.

What guard() does not do

It does not manage provider keys (you pass already-configured, bring-your- own-key providers), does not meter (cost and latency come from attach()), and does not abstract across frameworks: a LiveKit guard takes LiveKit fallbacks, a Pipecat guard takes Pipecat fallbacks.

See also

  • attach(): the passive meter guard composes with.
  • Frameworks and extras: the framework-neutral core and the errors you get for a missing extra or an unrecognized provider.