Postgres sessions
Persist session state to Postgres for failover and very-long-lived conversations.
By default, agent-webkit-server holds session state in memory. That's fine for single-host deployments. For multi-host failover or sessions that need to survive process restarts, swap in the Postgres adapter.
pip install "agent-webkit-server[fastapi,postgres]"What it does
The Postgres adapter implements the SessionStore protocol — append, load, close. The engine writes session events to Postgres in addition to the in-memory EventLog. On a fresh process, it can rehydrate a session's event history from the table.
What it does not do:
- It does not give you cross-host live fan-out. Two browsers on two different servers cannot tail the same session in real time. (See Architecture for why.)
- It does not replay an in-flight
query(). If a host dies mid-turn, that turn is lost; the session can be reopened but the user will need to send their last message again.
What it does give you:
- Failover. If host A dies, the next request for that session lands on host B and rehydrates from Postgres.
- Very long sessions. Increase
idle_timeout_saggressively without burning memory — the data is on disk.
Setup
import os
from agent_webkit_server.adapters.fastapi import create_app
from agent_webkit_server.adapters.pg_session_store import PgSessionStore
from agent_webkit_server.auth import AuthConfig
# Connect (creates a small asyncpg pool)
store = await PgSessionStore.connect(os.environ["DATABASE_URL"], min_size=1, max_size=10)
# Or, reuse an existing pool
# store = PgSessionStore.from_pool(my_existing_pool)
app = create_app(auth=AuthConfig.from_env(), session_store=store)The default sdk_factory forwards session_store to ClaudeAgentOptions, so the SDK persists session state through it automatically. If you supply your own sdk_factory, you're responsible for wiring session_store into your own ClaudeAgentOptions.
Schema
The adapter manages its own table on first connect. The schema is intentionally narrow:
- One row per appended event, keyed by
(session_id, seq). event(text),data(jsonb),created_at.
You don't query this table directly; the adapter abstracts over it. If you need to introspect, the key argument to append / load includes the session id.
Sticky routing
You still need sticky routing at your load balancer. The reason: live fan-out is in-process, so a session that's already running on host A can't suddenly serve events through host B without rehydrating. Sticky routing avoids the rehydration roundtrip on every request.
Recommended approaches:
- Sticky session cookies. Most LBs support this. Cookie value = session_id or user_id.
- Path-based routing. Hash the
session_idfrom the URL and pin to a host.
When a host dies, the LB reroutes; the new host rehydrates from Postgres on the first request for that session. The user sees a brief stall, no lost messages (except the in-flight turn if there was one).
Operations
- Pool sizing. Default
min_size=1, max_size=10. Bump for high session-creation rates. Sessions don't keep a connection idle — they only borrow when appending. - Vacuum. Old events accumulate. Add a
DELETE FROM agent_webkit_events WHERE created_at < NOW() - INTERVAL '30 days'cron job — or whatever your retention policy is. - Indexes. The adapter creates the index it needs (
(session_id, seq)). Don't add others unless you're querying the table directly. - Backups. Standard. The data is your audit log.
What to use instead
If failover isn't a hard requirement, don't bother. The default in-memory store is faster, simpler, and has zero ops cost. Switch only when:
- You need session continuity across deploys (not just restarts).
- You have a multi-host setup where failover matters more than session-pinning latency.
- You want a queryable audit log of every event the agent emitted.
For single-host with the only goal being "survive a deploy": consider just running zero-downtime deploys (rolling, blue-green) and letting in-flight sessions complete on the old host. Often that's enough.
Where to next
- Server API — PgSessionStore
- Architecture — what we didn't build
- Deployment guide — sizing, scaling, eviction.