guard() (control)
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() is control only. It 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
the framework-neutral core (routing ContextVars and shared spend / limit state).
Passive vs active
This is the core distinction. An observer can capture cost and latency but can never reroute, throttle, or block a call, so control has to live in a wrapper built at provider-construction time.attach() | guard() | |
|---|---|---|
| Role | observe (passive) | control (active) |
| Effect on the call | none; measures only | reroutes, throttles, or blocks |
| Metrics | the single meter | none (attach meters) |
| Where it sits | bound to the session / task | wraps one provider |
| Opt-in? | yes, per session | yes, per provider |
attach() on every session for cost and latency. Reach for guard() on the
specific providers where you want fallback or limits.
Signature
- fallback: on a primary-provider error, each fallback runs in order until
one succeeds.
attach()stampsfallback_from=<primary>andstatus="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"). When the bucket is empty the guarded call raisesRateLimitExceeded. - budget: a spend cap parsed from the DSL (
"$5.00/day","$100/month"). The guard reads the window’s accumulated spend from the core and raisesBudgetExceededErrorwhen it is at or over the cap. Budget enforcement depends on the cost dataattach()writes, which closes the measure-then-enforce loop.
LiveKit
guard() returns a subclass of the LiveKit base (livekit.agents.{llm,stt,tts})
so the result slots into an AgentSession unchanged:
Pipecat
The API is identical. Pass a native Pipecat service;guard() returns a wrapped
service you place in the pipeline where the original went:
Pipecat fallback scope
Fallback on the Pipecat path switches providers before the first output frame. If the primary service fails to produce its first frame,guard() tries the next
provider. Once the primary has started streaming output, there is no mid-stream
recovery: a failure partway through a response is surfaced, not silently
patched over by swapping to a fallback. Size a Pipecat fallback for
“primary is down / rejecting” rather than “primary died halfway through a token
stream.”
What guard() does not do
- It does not manage provider keys or config. You pass native providers you have already configured; this fits bring-your-own-keys.
- It does not meter. Cost and latency come from
attach(). - It does not abstract across frameworks.
guard()wraps native providers of one framework at a time; 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.
- Migration guide: moving fallback and limits off
the deprecated factories onto
guard().