> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Push Delivery Store

> Durable, at-least-once push delivery that survives gateway restarts

Push delivery persists pending, un-acknowledged events to disk so a gateway restart never silently drops a message.

> [!NOTE]
>
> This page covers durable at-least-once delivery. For **whether** and **what** to notify — per-recipient categories, quiet hours, and content redaction — see [Notification Policy](https://praison.ai/docs/features/gateway-notification-policy.md), the layer above delivery.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Push Delivery"
        Agent[🤖 Agent] --> Store[💾 SQLite Store]
        Store --> Client[📱 Client]
        Client -->|ACK| Store
        Store -.->|Restart replay| Client
    end

    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef store fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef client fill:#10B981,stroke:#7C90A0,color:#fff

    class Agent agent
    class Store store
    class Client client
```

## Quick Start

**1. Default (durable, zero config)**

Enable push and delivery is durable out of the box — no flags, no external services.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonaiagents.gateway import GatewayConfig, PushConfig

agent = Agent(name="Notifier", instructions="Push updates to subscribed clients")

gateway = GatewayConfig(
    push=PushConfig(enabled=True)   # durable-by-default SQLite backend
)
```

Pending events are written to `~/.praisonai/state/push_delivery.sqlite` and replayed on restart.

**2. Explicit ephemeral (opt-out for tests)**

Switch to `memory` when durability is unwanted, such as throwaway test runs.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.gateway import GatewayConfig, PushConfig, DeliveryConfig

gateway = GatewayConfig(
    push=PushConfig(
        enabled=True,
        delivery=DeliveryConfig(store_backend="memory"),
    )
)
```

**3. Redis for horizontal scaling**

Point delivery at Redis to fan out across multiple gateway processes.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.gateway import GatewayConfig, PushConfig, DeliveryConfig, RedisConfig

gateway = GatewayConfig(
    push=PushConfig(
        enabled=True,
        redis=RedisConfig(url="redis://localhost:6379/0"),
        delivery=DeliveryConfig(store_backend="redis"),
    )
)
```

The same settings load from `gateway.yaml`:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
gateway:
  push:
    enabled: true
    delivery:
      store_backend: sqlite   # default; also: redis | memory
      ack_timeout: 30
      max_retries: 3
      message_ttl: 86400
```

***

## How It Works

The store persists each pending event, replays it after a restart, and evicts the row once the client acknowledges.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Agent
    participant Store as SqlitePushStore
    participant Client

    Agent->>Store: persist(event, client_id)
    Store->>Client: deliver(event)
    Note over Client: gateway restarts
    Store->>Store: reload _pending_acks from disk
    Client->>Store: reconnect
    Store->>Client: replay pending events
    Client-->>Store: ACK
    Store->>Store: evict acked row
```

| Stage | What happens |
| - | - |
| Persist | Event and its recipient `client_id` are written to the SQLite WAL journal |
| Deliver | The gateway sends the event and waits for an ACK |
| Restart replay | Pending rows are reloaded and scheduled for immediate redelivery |
| Evict | The row is deleted on ACK or once older than `message_ttl` |

***

## Choosing a Backend

Pick the backend that matches your durability and scaling needs.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Start{Need durability?}
    Start -->|No, tests only| Memory[memory]
    Start -->|Yes| Scale{Multi-process fan-out?}
    Scale -->|No| SQLite[sqlite ✓ default]
    Scale -->|Yes| Redis[redis]

    classDef choice fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef pick fill:#10B981,stroke:#7C90A0,color:#fff
    classDef alt fill:#F59E0B,stroke:#7C90A0,color:#fff

    class Start,Scale choice
    class SQLite pick
    class Memory,Redis alt
```

***

## Configuration Options

`DeliveryConfig` controls the at-least-once guarantee.

| Option | Type | Default | Description |
| - | - | - | - |
| `enabled` | `bool` | `True` | Toggle delivery guarantees |
| `ack_timeout` | `int` | `30` | Seconds to wait for ACK before retrying |
| `max_retries` | `int` | `3` | Maximum retry attempts |
| `retry_backoff` | `float` | `2.0` | Exponential backoff multiplier |
| `message_ttl` | `int` | `86400` | Retention seconds for un-acked messages |
| `store_backend` | `str` | `"sqlite"` | `"sqlite"` \| `"redis"` \| `"memory"` |

An invalid `store_backend` raises `ValueError` at construction time — the closed set is validated in `__post_init__`.

### Where State Lives

| Backend | Location | Survives restart? | Multi-process? |
| - | - | - | - |
| `sqlite` (default) | `~/.praisonai/state/push_delivery.sqlite` (WAL) | ✅ | ❌ single process |
| `redis` | Redis (uses `RedisConfig`) | ✅ | ✅ |
| `memory` | RAM | ❌ | ❌ |

***

## Common Patterns

Three setups cover most deployments.

> [tab] Survives a redeploy
>
> Keep the default backend so a mid-notification restart replays on reconnect.
>
> ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> from praisonaiagents.gateway import GatewayConfig, PushConfig
>
> gateway = GatewayConfig(push=PushConfig(enabled=True))
> ```

> [tab] Horizontal scale-out
>
> Switch to `redis` when running more than one gateway replica behind a load balancer.
>
> ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> from praisonaiagents.gateway import GatewayConfig, PushConfig, DeliveryConfig, RedisConfig
>
> gateway = GatewayConfig(
>     push=PushConfig(
>         enabled=True,
>         redis=RedisConfig(url="redis://localhost:6379/0"),
>         delivery=DeliveryConfig(store_backend="redis"),
>     )
> )
> ```

> [tab] Throwaway CI
>
> Use `memory` so no SQLite artefacts leak into the CI workspace.
>
> ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> from praisonaiagents.gateway import GatewayConfig, PushConfig, DeliveryConfig
>
> gateway = GatewayConfig(
>     push=PushConfig(
>         enabled=True,
>         delivery=DeliveryConfig(store_backend="memory"),
>     )
> )
> ```

***

## Best Practices

> [accordion] Keep the default
>
> SQLite is durable, zero-dependency, and appropriate for the vast majority of deployments.

> [accordion] Choose redis only for multi-process gateways
>
> For a single gateway, SQLite is faster and simpler. Reach for `redis` when fanning out across replicas.

> [accordion] Use memory only for tests
>
> Any restart loses in-flight events silently, so reserve `memory` for throwaway runs.

> [accordion] Back up the state directory
>
> Include `~/.praisonai/state/` in your agent-state backups. The file is small — rows evict on ACK and TTL — but it holds the durability guarantee.

***

## Related

> [card] [Gateway Overview](https://praison.ai/docs/features/gateway-overview.md)
>
> Configure and run the gateway that hosts push delivery.

> [card] [Durable Delivery](https://praison.ai/docs/features/durable-delivery.md)
>
> Distinguish outbound platform delivery from gateway push delivery.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.