Persistence Backend (Redis or Postgres)
LeafMesh keeps its business-logic state — sessions, agent state, yields, mesh communication, human-in-the-loop requests, and scheduled follow-ups — in a persistence backend. You choose Redis or Postgres; the same code runs on either, so the choice is a deployment decision, not a code change.
Choosing a backend
From 2.4.482 the backend is set only in .env, never in the YAML. config.yaml
no longer reads a persistence: or top-level redis: block; if an older file still
has one, LeafMesh logs a warning at boot ("persistence block is no longer read") and
ignores it. Set only what you need; everything you leave out keeps its default.
Redis (the default):
# .env
LEAFMESH_AGENCY_NAME=my-agency
LEAFMESH_STORE=redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=your-passwordPostgres:
# .env
LEAFMESH_AGENCY_NAME=my-agency
LEAFMESH_STORE=postgres
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=leafmesh
POSTGRES_USER=leafmesh
POSTGRES_PASSWORD=your-password
POSTGRES_POOL_MIN=1
POSTGRES_POOL_MAX=10Or name the whole server in one URL. The single names (REDIS_HOST,
POSTGRES_DB and so on) fill in anything the URL leaves out:
# .env
# rediss:// instead of redis:// turns TLS on
REDIS_URL=redis://user:pw@host:6379/4
# or, for Postgres
DATABASE_URL=postgresql://user:pw@host:5432/db?sslmode=requireKeeping the address and password in .env means they never land in the agency's
YAML, which is stored and edited in Studio. Studio shows whether the store is
reachable under Infrastructure → Datastores, along with its latency, the
server, the settings in force and which .env names set them; it never shows
the address or the password.
All store settings
| Variable | Default when unset | What it sets |
|---|---|---|
LEAFMESH_AGENCY_NAME | none | The agency's name in the store; LeafMesh.from_store() starts this agency |
LEAFMESH_STORE | redis | redis or postgres |
| Redis | ||
REDIS_URL | none | The whole server in one URL, e.g. redis://user:pw@host:6379/4. rediss:// turns TLS on. The single names below fill in anything it leaves out. |
REDIS_HOST | localhost | Server host |
REDIS_PORT | 6379 | Server port |
REDIS_PASSWORD | none | Server password |
REDIS_DB | 0 | Database number |
REDIS_DEFAULT_TTL | 3600 (1 hour) | Lifetime of general data (yields, mesh communications), seconds |
REDIS_SESSION_TTL | 604800 (7 days) | Lifetime of sessions and conversation history, seconds |
REDIS_SSL | false | Set true to require TLS |
REDIS_SSL_CERT_REQS | required | Certificate check: required, optional or none |
REDIS_SSL_CA_CERTS | none | Path to the CA bundle |
REDIS_SSL_CERTFILE, REDIS_SSL_KEYFILE | none | Client certificate and key (mutual TLS) |
REDIS_SSL_CHECK_HOSTNAME | true | Check the server hostname against its certificate |
REDIS_CLUSTER_NODES | none | Comma-separated host:port list. Setting it turns on Redis Cluster mode. |
| Postgres | ||
DATABASE_URL | none | The whole server in one URL, e.g. postgresql://user:pw@host:5432/db?sslmode=require. The single names below fill in anything it leaves out. |
POSTGRES_HOST | localhost | Server host |
POSTGRES_PORT | 5432 | Server port |
POSTGRES_DB | leafmesh | Database name |
POSTGRES_USER, POSTGRES_PASSWORD | none | Credentials |
POSTGRES_SSLMODE | unset (the driver's own default) | Postgres SSL mode |
POSTGRES_DEFAULT_TTL | 3600 (1 hour) | Lifetime of general data, seconds |
POSTGRES_SESSION_TTL | 604800 (7 days) | Lifetime of sessions, seconds |
POSTGRES_POOL_MIN | 1 | Smallest connection pool size |
POSTGRES_POOL_MAX | 10 | Largest connection pool size |
See Redis Integration for TLS and cluster examples.
The Postgres backend works behind transaction-pooling proxies (PgBouncer, Supabase, RDS Proxy) without extra configuration.
Both backends provide the same guarantees the mesh relies on: durable state across restarts, atomic updates, ordered reads, and event delivery to exactly one worker.
On delivery semantics, precisely: an event is delivered to one worker — never fanned out to all of them — and is retried if that worker dies before acknowledging it. That is at-least-once, not exactly-once. In practice this only shows up in handlers that are not naturally idempotent, such as incrementing a counter; anything that writes a keyed record is unaffected. Design handlers to tolerate a repeat and the distinction never costs you anything.
When to pick which:
- Redis — lowest-latency default; ideal when you already run Redis or want the simplest setup.
- Postgres — when you standardize on a relational store, need SQL-level auditability, or already operate managed Postgres.
What lives in the backend
| State | Examples |
|---|---|
| Sessions | conversation history, session metadata, TTLs |
| Agent state | yields, intermediate results, agent registry |
| Mesh communication | inter-agent calls, event streams, consumer groups |
| Human-in-the-loop | pending/resolved requests, operator replies |
| Follow-ups | durable scheduled-continuation records |
Observability data (traces, metrics) is not stored here — it flows through a separate telemetry path and never mixes with business-logic state.
High availability
Both backends support LeafMesh's high-availability mode: event delivery is exactly-once across workers via durable consumer groups, singleton background tasks (schedulers, the manager) are elected by a lease so only one worker runs them, and in-flight work is reclaimed by a sibling if a worker dies. See Scaling for running multiple workers.
Multi-tenancy
When you pass a tenant identifier (see Multi-Tenancy), state is fully isolated per tenant on both backends, so one tenant's sessions, memory, and events are isolated from another's.