Skip to content

Gateway ships an unsafe default reliability posture — no admission ceiling and restarts cut in-flight turns unless the operator opts in #3438

Description

@MervinPraison

Summary

The gateway/bot runtime already ships strong reliability building blocks — a durable inbound journal, a durable outbound outbox, a gateway-wide admission gate, and graceful-shutdown draining — but the two strongest lifecycle protections (the graceful-drain window and inbound admission/backpressure) are individually opt-in. An operator who runs the gateway the obvious way (BotOS(agent=...), praisonai gateway start, or a minimal gateway.yaml) silently gets a deployment with no admission ceiling and only a 5-second drain, so a burst can fan out unboundedly and a rolling restart can cut in-flight agent turns. This contradicts the SDK's safe-by-default / hard-to-misuse / production-ready pillars: the safe posture exists, but it must be discovered and switched on.

Current behaviour

resolve_reliability() maps the default (reliability=None / "default") to a posture with no admission ceiling, and the module docstring states the problem outright:

# src/praisonai-bot/praisonai_bot/bots/_reliability.py (module docstring)
# ... the two strongest lifecycle knobs (graceful drain and inbound admission)
# are individually opt-in. An operator running the gateway the "obvious" way
# therefore silently gets a no-backpressure deployment that cuts in-flight
# turns on restart.
#
# "default" / None
#     A sane, small graceful-drain window (5s) so a restart doesn't cut
#     in-flight turns, but no admission ceiling (unbounded, legacy dispatch).

BotOS.__init__ defaults the posture to None, and resolve_reliability/normalize_reliability treat None/"none" as "no preset" — returning early with no admission knobs:

# src/praisonai-bot/praisonai_bot/bots/botos.py
reliability: Optional[str] = None,
...
_resolved = resolve_reliability(reliability, ...)

# src/praisonai-bot/praisonai_bot/bots/_reliability.py
if reliability is None:
    return None
...
if name in ("", "none"):
    return None

The production profile (15-second drain, CPU-scaled admission ceiling, bounded fair wait queue, strict outbound ordering) is fully implemented — it is simply not the default. The three-way surface is already wired (BotOS(reliability=...), --reliability, gateway.reliability in YAML — see src/praisonai-bot/praisonai_bot/cli/features/gateway.py), so only the default value is unsafe.

Desired behaviour

Running the gateway with no explicit reliability setting should be production-safe by default: a graceful-drain window that does not cut mid-turn replies on restart, plus a bounded admission ceiling and fair wait queue, with an explicit reliability="off" / --reliability off to opt back into today's immediate-teardown behaviour. Bind posture should inform the default — a gateway bound to a non-loopback interface (an actual deployment) should never come up without backpressure.

Layer placement

  • Primary layer: wrapper (praisonai, via the praisonai-bot gateway runtime it aliases; BotOS is explicitly a wrapper concern)
  • Why not core: core owns only the policy protocols (GatewayConcurrencyPolicyProtocol, DrainTimeoutPolicy); the chosen default posture and the heavy BotOS runtime live in the wrapper/bot layer.
  • Why not wrapper: it is wrapper — this is the primary.
  • Why not tools: nothing here is agent-callable.
  • Why not plugins: this is the framework's own default operating posture, not a lifecycle hook a plugin wraps.
  • Secondary touch (optional): core — surface the safe posture as a documented default on the reliability protocol / GatewayConfig so every runtime resolves it identically.
  • 3-way surface (CLI + YAML + Python): yes — the switch already exists on all three; this changes the default each resolves to.

Proposed approach

  • Extension point: config default + preset resolver (resolve_reliability).
  • Make the resolver's default posture apply the production-style admission ceiling + drain instead of None, keyed off bind posture, keeping explicit fields and reliability="off" as overrides.

Resolution sketch

# Before (today) — no reliability arg => no admission ceiling, 5s drain
botos = BotOS(agent=my_agent, platforms=["telegram", "discord"])
# unbounded dispatch; a rolling restart can cut in-flight turns

# After (proposed) — safe by default
botos = BotOS(agent=my_agent, platforms=["telegram", "discord"])
# graceful drain + bounded admission ceiling + fair wait queue applied automatically

# opt out explicitly
botos = BotOS(agent=my_agent, platforms=[...], reliability="off")
# resolve_reliability: default posture becomes production-safe (illustrative)
def resolve_reliability(reliability, *, bind_host="127.0.0.1", ...):
    profile = normalize_reliability(reliability)
    if profile is None:  # no explicit preset selected
        profile = "production" if _is_externally_bound(bind_host) else "default+admission"
    ...

Severity

High — the safe posture already exists but is off by default, so real deployments silently run without backpressure and lose in-flight turns on restart, contradicting the SDK's safe-by-default / production-ready contract. The fix is low-risk because the production machinery is already implemented and selectable.

Validation

Confirmed by reading src/praisonai-bot/praisonai_bot/bots/_reliability.py (module docstring; normalize_reliability/resolve_reliability; _DEFAULT_DRAIN_SECONDS = 5.0; _KNOWN_PROFILES = ("production", "default", "off"); the production branch that builds the CPU-scaled ceiling + bounded queue) and src/praisonai-bot/praisonai_bot/bots/botos.py (reliability: Optional[str] = None feeding resolve_reliability). The three-way surface was traced through src/praisonai-bot/praisonai_bot/cli/features/gateway.py (--reliability, gateway.reliability). The production posture is implemented and selectable today; only the default is unsafe.

Metadata

Metadata

Assignees

No one assigned

    Labels

    claudeAuto-trigger Claude analysisdocumentationImprovements or additions to documentation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions