Gateway State Durability

The gateway's webhook idempotency and session history are durable by default — a restart no longer re-processes webhooks or drops history

Next

The gateway’s webhook idempotency and session history are durable by default — a restart no longer re-processes webhooks or drops history.

python
from praisonaiagents import Agent
from praisonai.gateway import Gateway

agent = Agent(name="assistant", instructions="Be helpful.")
gw = Gateway(agents=[agent])
gw.register_hook({"path": "alerts", "agent": "assistant", "auth": "secret"})
gw.start()   # webhook idempotency + session history are durable by default

When a durable store cannot initialise, the gateway logs, records a redacted gateway / durability:* degraded owner, and keeps serving in-memory so inbound delivery never stops.

Diagram

Gateway State Durability

Yes

No

📥 Inbound hook

🗄️ Idempotency
SQLite

💬 Session turn

🗄️ Session store

Durable store
available?

✅ Survives restart

⚠️ In-memory + degraded owner

What’s Durable, Out of the Box

StateBackendLocation
Webhook idempotencySQLite~/.praisonai/state/hook_idempotency.sqlite
Session historyThe configured get_default_session_store()See Gateway Session Persistence

Both survive a routine gateway restart. Webhook dedup keys persist across a redelivery window, and session history is not dropped when the process cycles.

Quick Start

  1. Start the gateway — durable by default
    python
    from praisonaiagents import Agent
    from praisonai.gateway import Gateway
    
    agent = Agent(name="assistant", instructions="Be helpful.")
    gw = Gateway(agents=[agent])
    gw.register_hook({"path": "alerts", "agent": "assistant", "auth": "secret"})
    gw.start()   # webhook idempotency + session history are durable by default

    No configuration is required — the SQLite idempotency store and the configured session store are used automatically.

  2. Opt out for tests / ephemeral runs
    yaml
    hooks:
      idempotency:
        store_backend: memory   # explicit opt-out — tests / ephemeral runs only
      hooks:
        - path: alerts
          agent: assistant
          # ...

    "memory" keeps the old in-memory behaviour when you don’t want an on-disk store.

  3. See a degraded fallback

    If a durable store cannot initialise, the boundary keeps serving in-memory and records a degraded owner:

    json
    {
      "owner_kind": "gateway",
      "owner_id": "durability:idempotency",
      "state": "cold",
      "reason": "durable idempotency store unavailable (running in-memory)",
      "retry_hint": "praisonai gateway doctor --fix"
    }

    It surfaces in praisonai gateway status under Degraded: and in GET /health under degraded_owners.

  4. Recover
    bash
    praisonai gateway doctor --fix

    Run the sanctioned repair, or hot-reload a config that fixes the store path — either clears the degraded entry automatically on the next successful build.

What Happens on Init Failure

When a durable store genuinely cannot initialise, the boundary that owns it (the praisonai-bot wrapper, praisonai_bot.bots._session):

  1. Logs the failure with the raw store path (log only).
  2. Records a gateway / durability:* degraded owner with a redacted reason and the praisonai gateway doctor --fix hint.
  3. Continues serving in-memory so inbound delivery keeps working.
  4. Auto-clears the degraded owner on the next successful (re)build of the durable store.
Diagram
Operatorgateway.health()Durable storepraisonai_bot.bots._sessionOperatorgateway.health()Durable storepraisonai_bot.bots._sessionalt[build fails][build succeeds later]build durable storeerror (path in log only)record_durability_degraded(…)fall back to in-memorystatus / health()degraded_owners: [durability:idempotency]okclear_durability_degraded(…)status / health()(entry gone)

The two owners:

owner_idMeaning
durability:idempotencyDurable webhook-idempotency store unavailable; dedup does not survive restart.
durability:sessionDurable session store unavailable; session history does not survive restart.

On the healthy path — store_backend: redis with Redis reachable — there is no durability:idempotency fact: the cross-replica store gives cluster-wide exactly-once admission. The owner only appears when the store degrades, carrying one of three reasons:

ReasonMeaning
"durable idempotency store unavailable (running in-memory)"The durable SQLite store could not initialise; dedup is per-process and does not survive restart. The most severe of the three.
"redis idempotency backend not available (running per-replica)"The operator asked for cross-replica dedup via store_backend: redis, but Redis was unreachable (or the redis package is missing) at build time; the wrapper fell back to the durable SQLite store and runs per-replica. Cross-replica dedup only holds if replicas share the SQLite state file. See Gateway Cross-Replica Idempotency.
"redis idempotency backend unreachable at runtime (dedup best-effort until it recovers)"Redis was reachable at build time but dropped later. The store fails open — it admits the delivery rather than 500-ing the hook path — so cross-replica dedup is temporarily best-effort. The fact auto-clears on the next successful Redis op after recovery.

How to See It

CLI

text
$ praisonai gateway status
...
  Degraded:
    ✗ gateway:durability:idempotency — durable idempotency store unavailable (running in-memory)  fix: praisonai gateway doctor --fix

Always printed when degraded_owners is non-empty (not gated on --deep).

Health endpoint

bash
curl http://127.0.0.1:8765/health

The same fact appears under degraded_owners:

json
{
  "degraded_owners": [
    {
      "owner_kind": "gateway",
      "owner_id": "durability:idempotency",
      "state": "cold",
      "reason": "durable idempotency store unavailable (running in-memory)",
      "retry_hint": "praisonai gateway doctor --fix"
    }
  ]
}

Agent tool

python
from praisonaiagents import Agent
from praisonaiagents.tools import gateway_status

agent = Agent(
    name="assistant",
    instructions="Call gateway_status; warn if a durability owner is degraded.",
    tools=[gateway_status],
)

An agent inside the gateway can read the same degraded_owners via gateway_status.

Best Practices

Keep the default — durable — in production

Leave store_backend unset (or sqlite). Only set memory for tests or explicitly ephemeral runs, where losing dedup on restart is acceptable.

Alert on any durability:* owner

A durability:idempotency or durability:session entry in degraded_owners means state is not surviving restart. Alert on it the same way you alert on a degraded channel or provider.

Recover with the sanctioned action

praisonai gateway doctor --fix is the one command in the retry_hint. A hot-reload that repairs the store path also clears the entry automatically — no manual clear step.

Trust the redaction

The operator-facing reason never contains the raw store path — that stays in the log. Safe to render straight to a dashboard or status probe.

Related