Skip to content

PATTERN Cited by 1 source

Pluggable state backend

Definition

A factoring in which a system's state needs — key-value cache, distributed locks, thread / session subscriptions, TTL-based expiry, namespaced key prefixes — are expressed against a generic state interface, and the concrete backend (Redis, ioredis, PostgreSQL, etc.) is supplied as an adapter instance at construction time.

The developer writes:

import { Chat } from "chat";
import { createPostgresState } from "@chat-adapter/state-postgres";

const bot = new Chat({
  adapters: { slack: createSlackAdapter() },
  state: createPostgresState(),
});
and can swap createPostgresState() for createRedisState() without changing the rest of the bot's code.

(Source: sources/2026-04-21-vercel-chat-sdk-brings-agents-to-your-users.)

Why it exists

The canonical reason to abstract state in a chat-bot / agent SDK is that teams already run some store — Postgres, Redis, or something else — and forcing them to add a second dependency for bot state is a deployment tax.

Vercel's Chat SDK post makes this explicit: "PostgreSQL is now supported as a production-ready option, so teams already running Postgres can persist bot state without adding Redis to their infrastructure." The adapter is the primitive that makes this swap possible without a rewrite.

Common interface (minimum viable)

From the Chat SDK post, the state layer covers:

  1. Key-value cache with TTL for ephemeral data (rate-limit counters, in-flight conversation state).
  2. Distributed locks across multiple bot instances — needed e.g. so two replicas don't both thread.subscribe() to the same mention and post duplicate responses.
  3. Namespaced state via a "configurable key prefix" — lets multiple bots share a store without colliding.
  4. Thread subscriptions — the bot-layer abstraction over "this thread is one the agent should track".

Chat SDK's backend adapters (2026-04-21 snapshot)

Backend Package Notes
Redis @chat-adapter/state-redis Shipped from launch.
ioredis (implicit in post) Shipped from launch.
PostgreSQL @chat-adapter/state-postgres Uses pg (node-postgres), raw SQL. "Automatically creates the required tables on first connect." Distributed lock across instances. Community PR #154 by @bai.

What the Chat SDK post does NOT disclose

  • Distributed-lock implementation on Postgres. The post says the Postgres adapter "supports…distributed locking across multiple instances", but doesn't disclose whether it uses pg_advisory_lock, row-level SELECT … FOR UPDATE, or an application-level heartbeat lease. Each has different correctness properties under network partitions.
  • Consistency guarantees. Redis's default TTL-based cache has weak ordering guarantees vs Postgres's MVCC. Whether the SDK's interface papers over this — or surfaces backend-specific semantics — is unspecified.
  • Migration semantics. Swapping from Redis to Postgres mid-flight requires migrating live state; the post doesn't cover migration tooling.

The pluggable-backend pattern more broadly

This pattern appears beyond chat SDKs — wherever a framework doesn't want to prescribe infrastructure: - BullMQ / Bee-Queue (Node job queues, Redis-pinned historically; pluggable backend variants exist). - Django's cache framework (local-mem / Redis / Memcached / database-cache / file-cache as swappable backends via Python entry points). - LangChain's memory/storage abstractions.

The Chat SDK's instance is distinctive because the adapter layer is also where distributed locking and namespacing live — those are not always part of a cache abstraction. Chat SDK puts them there because chat bots in production need both.

Trade-offs

  • Lowest-common-denominator interface. The common interface can only expose operations every adapter supports. Power users may want Postgres-specific advisory-lock semantics or Redis-specific Lua scripting — they're unreachable through the shared interface.
  • Backend-dependent reliability. A correctness bug in one adapter doesn't affect the other, but it also doesn't get tested through the other — adapter parity takes active effort.
  • Operational complexity shifts, not disappears. Postgres's connection-pool tuning, Redis's eviction policies — the adapter abstracts the interface but not the operational burden.
  • No free cross-backend failover. If Redis goes down, you can't fail over to Postgres without a restart; the backend choice is effectively deploy-time.

Seen in

Last updated · 476 distilled / 1,218 read