Skip to content

SMOODEV-1892: Redis + NATS Backplane backends — cross-pod scale-out#9

Merged
brentrager merged 1 commit into
mainfrom
SMOODEV-1892-backplane-impls
Jun 14, 2026
Merged

SMOODEV-1892: Redis + NATS Backplane backends — cross-pod scale-out#9
brentrager merged 1 commit into
mainfrom
SMOODEV-1892-backplane-impls

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

What

Two distributed Backplane implementations behind the trait landed in #7, as sibling adapter crates (mirroring adapters/postgres):

  • adapters/backplane-redisRedisBackplane over Redis/Valkey pub/sub
  • adapters/backplane-natsNatsBackplane over NATS subjects

Each wraps a per-pod InMemoryBackplane (local registry + delivery) and adds a bus for cross-pod fan-out: publish(Target, event) delivers to local sockets immediately (the returned count), then broadcasts a BackplaneEnvelope; every other pod's subscriber re-resolves the Target against its own registry and delivers to its sockets. The origin pod skips its own echo. This is the classic pub/sub fan-out shape — no shared connection registry, each pod authoritative for its own sockets.

Why

The default InMemoryBackplane only reaches sockets on the current process. This makes the same publish() call reach a socket on any replica — required to run the WS service with >1 pod (horizontal scale-out) and the cross-pod path for non-AI publishers (job status, ingestion progress, notifications). Foundation: ADR-027 / epic SMOODEV-1887.

Surface

  • lib: Target is now Serialize/Deserialize; new shared BackplaneEnvelope wire type so a host's own transport adapter can speak the same format. No runtime dep added to the lib.
  • server: SMOOTH_AGENT_BACKPLANE (memory | redis/valkey | nats) + SMOOTH_AGENT_BACKPLANE_URL selection in build_state_from_env_async. Default stays single-process in-memory — no behavior change for existing deployments.

Tests

Cross-pod fan-out proven end-to-end over real Redis and real NATS via testcontainers (two backplane instances = two pods; publish on pod A reaches a socket on pod B; origin pod doesn't double-deliver). Skips cleanly when Docker is unavailable, like the Postgres adapter. fmt + clippy clean (the 2 lib clippy warnings are pre-existing on main).

🤖 Generated with Claude Code

Two distributed Backplane impls behind the existing trait, as sibling adapter
crates (mirroring adapters/postgres): RedisBackplane (Redis/Valkey pub/sub) and
NatsBackplane (NATS subjects). Each wraps a per-pod InMemoryBackplane for local
registry + delivery and adds a bus for cross-pod fan-out — publish() delivers to
local sockets, then broadcasts a BackplaneEnvelope so every other pod re-resolves
the Target against its own registry and delivers to its sockets (origin skips its
echo). Same publish() now reaches a socket on any replica → horizontal scale-out
and the cross-pod path for non-AI publishers.

- lib: Target is Serialize/Deserialize; new shared BackplaneEnvelope wire type.
- server: SMOOTH_AGENT_BACKPLANE (memory|redis|valkey|nats) + _URL selection in
  build_state_from_env_async; default stays single-process in-memory.
- tests: cross-pod fan-out proven end-to-end over real Redis + NATS via
  testcontainers (skip without Docker, like the PG adapter).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jun 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 084e167

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@smooai/smooth-operator Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@brentrager brentrager merged commit e9fa854 into main Jun 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant