Persistence (Redis or Postgres)

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-password

Postgres:

# .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=10

Or 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=require

Keeping 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

VariableDefault when unsetWhat it sets
LEAFMESH_AGENCY_NAMEnoneThe agency's name in the store; LeafMesh.from_store() starts this agency
LEAFMESH_STOREredisredis or postgres
Redis
REDIS_URLnoneThe 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_HOSTlocalhostServer host
REDIS_PORT6379Server port
REDIS_PASSWORDnoneServer password
REDIS_DB0Database number
REDIS_DEFAULT_TTL3600 (1 hour)Lifetime of general data (yields, mesh communications), seconds
REDIS_SESSION_TTL604800 (7 days)Lifetime of sessions and conversation history, seconds
REDIS_SSLfalseSet true to require TLS
REDIS_SSL_CERT_REQSrequiredCertificate check: required, optional or none
REDIS_SSL_CA_CERTSnonePath to the CA bundle
REDIS_SSL_CERTFILE, REDIS_SSL_KEYFILEnoneClient certificate and key (mutual TLS)
REDIS_SSL_CHECK_HOSTNAMEtrueCheck the server hostname against its certificate
REDIS_CLUSTER_NODESnoneComma-separated host:port list. Setting it turns on Redis Cluster mode.
Postgres
DATABASE_URLnoneThe 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_HOSTlocalhostServer host
POSTGRES_PORT5432Server port
POSTGRES_DBleafmeshDatabase name
POSTGRES_USER, POSTGRES_PASSWORDnoneCredentials
POSTGRES_SSLMODEunset (the driver's own default)Postgres SSL mode
POSTGRES_DEFAULT_TTL3600 (1 hour)Lifetime of general data, seconds
POSTGRES_SESSION_TTL604800 (7 days)Lifetime of sessions, seconds
POSTGRES_POOL_MIN1Smallest connection pool size
POSTGRES_POOL_MAX10Largest 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

StateExamples
Sessionsconversation history, session metadata, TTLs
Agent stateyields, intermediate results, agent registry
Mesh communicationinter-agent calls, event streams, consumer groups
Human-in-the-looppending/resolved requests, operator replies
Follow-upsdurable 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.

Last updated: 11/10/2026