degraded_owners list with an exact next action.
Before this registry the gateway recorded degradation in several disconnected places but surfaced only degraded channels through health(). Provider credential failures, unresolved SecretRef capabilities, and route-level failures were classified but never exposed — a partial degraded state where some failures stayed invisible until a message silently went nowhere. A single process-local registry now collects every degraded owner at the boundary that owns it, so health() lists them all with a consistent, redacted shape and an actionable next step. The registry now covers both sides of the contract — the write side (record degradation where it happens) and the read side (guard a dispatch path before it targets a degraded owner).
This is a purely additive surface — there is no
gateway.yaml knob and no CLI flag. The channel-only channels map in health() is unchanged, so existing monitors keep working. The registry, its dataclass, its protocol, and the closed vocabularies are all public exports from praisonaiagents.gateway.DegradedCapabilityProtocol (write contract: mark/clear/list_degraded) is unchanged. Registries that also implement the point read find(...) satisfy the extended DegradedCapabilityLookupProtocol. The module-level assert_owner_available() guard degrades gracefully on legacy registries by scanning list_degraded() — so upgrading the SDK never breaks an external registry written against the original contract.Quick Start
1
See it in health()
The 90% path — read the surface, no code to write. Any degraded owner (channel, provider, capability, route, or gateway) appears in one place.Example output when a Slack token is unavailable at boot and an MCP capability failed to resolve a secret:
2
Record your own degraded owner
Advanced — for integrations building on the gateway. Mark degradation at the boundary that owns it, then clear on recovery.
3
Guard a dispatch path (fail-closed)
Call the guard before an agent targets an owner so a recorded degradation short-circuits into a typed, redacted outcome instead of a silent failure.
How It Works
Each owner writes into the registry at its own boundary;health() reads a sorted snapshot on demand.
The registry is process-local and thread-safe, keyed on (owner_kind, owner_id) so a repeated failure for the same owner updates the existing entry instead of stacking duplicates.
Fields Reference
DegradedOwner is a frozen dataclass with five fields — retry_hint is the only one with a default.
Closed Vocabularies
Two tuples define the only valid values — import them instead of hard-coding string literals.
Both are exported from
praisonaiagents.gateway, so callers can validate against them before constructing a DegradedOwner.
route:redis-pubsub is the built-in owner recorded by the gateway HA fan-out adapter (RedisPubSubAdapter) whenever the Redis pub/sub connection is dropped — the first shipped route owner and a good exemplar for the route owner_kind. See Real-Time Push Notifications → HA & cross-instance delivery.Concrete owners shipped with the gateway
Registry Methods
DegradedCapabilityRegistry is the default in-process implementation of the DegradedCapabilityProtocol.
cold vs stale
Pick the state by whether a last-known-good value exists.
Use cold when the owner never resolved a working value (a token that was never set). Use stale when it is still serving from a last-known-good value while degraded.
The health() Surface
degraded_owners sits alongside the existing channels map and lists every degraded owner in one redacted shape.
- The channel-only
channelsmap is unchanged — this is a purely additive surface, safe for existing monitors. degraded_ownersis present only when the list is non-empty.- The aggregation is defensive —
health()never raises even if a registry read fails.
The gateway / fleet owner
When the fleet-level crash-loop breaker trips, the health monitor records exactly one gateway owner — owner_id = "fleet" — so a fleet-wide reconnect storm shows up as a single, actionable fact instead of one entry per channel.
It auto-clears on the next monitor sweep once the storm subsides — no external
health() / gateway status poll is needed. Wait one breaker_cooldown_s after fixing the systemic cause and the entry disappears.
Fail-Closed Guard
Before dispatching work for an owner, call the guard so a recorded degradation short-circuits into a typed outcome instead of a silent failure. Pick the right call for how you hold the registry. Three call shapes cover every dispatch path.OwnerUnavailable carries only redacted, operator-safe fields — never token or secret material.
The message reads
{owner_kind} {owner_id!r} unavailable ({state}): {reason} — {retry_hint}, and to_dict() returns the same redacted shape as DegradedOwner:
Common Patterns
Read the surface once and act on it — for alerting or for CI. Poll for degradation from an ops script — alert on any channel (matches existing on-call playbooks) plus any provider (new).OwnerUnavailable into a chat reply — catch the guard’s exception and hand the operator the exact next action instead of a hang or a 500.
Best Practices
Use the closed vocabularies
Use the closed vocabularies
Import
OWNER_KINDS and DEGRADED_STATES from praisonaiagents.gateway and validate against them before constructing a DegradedOwner. The frozen dataclass raises ValueError on an unknown owner_kind or state, so checking first turns a runtime crash into a clean guard.Redact secrets in reason
Redact secrets in reason
The registry is operator-facing — a token or secret in
reason surfaces on every dashboard, log line, and status probe. Say what failed, not what the value was: "auth rejected (401)", not the key itself.Set an actionable retry_hint
Set an actionable retry_hint
praisonai gateway doctor --fix is the sanctioned default — the command exists and actually repairs a weak/missing gateway auth token, then re-validates (see Gateway CLI › Auto-repair). Every retry_hint in this registry MUST name a command that exists. Prefer a specific one that names the exact env var, secret file, or rotation command — e.g. re-set OPENAI_API_KEY — so the operator’s next step is unambiguous.Clear on recovery
Clear on recovery
mark() alone leaves the entry until the process restarts. Pair every mark() at the failure point with a clear() on the success path so health() reflects reality instead of a stale failure.Guard before you dispatch, not after
Guard before you dispatch, not after
Call
assert_owner_available() immediately before the work targeting that owner. A guard after the fact converts a silent failure into a loud one but wastes the round-trip; a guard before it keeps the failure typed and free.Related
Degraded Channel Isolation
The channel-specific boot-time isolation contract this composes over.
Gateway Channel Supervision
The runtime supervisor that produces the
stale channel entries.Gateway CLI
gateway doctor / gateway status — the operator surfaces that render degraded_owners.Gateway Secret References
The source of
credential unavailable reasons.Weak / Placeholder Secret Guard
The most common producer of the
secret unresolved records this guard then fails-closed against.
