Agora

Multi-replica streaming (Redis)

Swap the in-process token sink for the Redis transport so any pod can serve any run's SSE stream — the same byte-for-byte envelope, fanned across replicas over Redis pub/sub plus a replayable list.

The default token sink buffers a run's tokens in process memory — great for a single instance, but it means a run started on one pod can only be re-attached on that same pod. The Redis transport removes that constraint: any replica can serve any run's SSE stream, with the SSE envelope kept byte-identical.

Select it

config/agent.ts
import { defineConfig, tokenSinks } from '@adonis-agora/agent'

export default defineConfig({
  model: () => aiSdkModel(openai('gpt-4o-mini')),
  sink: tokenSinks.redis({ connection: 'main' }),
})

tokenSinks.redis (aliased streamTransports.redis) fans a run's tokens across replicas over Redis pub/sub plus a replayable list, so a late or cross-instance subscriber replays the buffered chunks first and then follows live. Requires @adonisjs/redis installed and configured (config/redis.ts).

The default stays single-replica

Omit sink entirely to keep the in-process sink (tokenSinks.memory() selects it explicitly). Nothing about the SSE contract, the routes, or the client changes when you switch — only where the buffer lives. The @adonisjs/redis peer is imported only inside the redis thunk, so it stays fully optional.

Configuration

FieldDefaultMeaning
connectiondefault connection@adonisjs/redis connection name.
keyPrefixagent:streamKey/channel namespace.
ttlSeconds3600Sliding TTL on a run's replay keys, refreshed on every write. Set 0 to keep them until you clean up yourself.
clientBring-your-own RedisStreamClient adapter (over ioredis, node-redis, …). When set, @adonisjs/redis is not imported.

ttlSeconds is the knob that keeps Redis from filling up: the framework never calls the sink's close(), so a run's replay buffer has no natural end of life. One hour is long enough that a client re-attaching after a browser restart still replays the whole run, and short enough that yesterday's transcripts aren't sitting in memory. Raise it if you re-attach on a longer horizon; set 0 only if something else prunes the keys.

Passing a client lets you drive a non-Adonis Redis driver or a test double directly — the factory uses it verbatim. Under the hood the adapter uses a dedicated duplicated connection per subscribe (a subscribed Redis connection can't run other commands).

Pair with the durable runner

The Redis sink solves stream portability across replicas; the durable runner solves run portability and crash-survival (a suspended HITL approval resumes on any pod). Together they give you a horizontally-scaled, crash-proof agent: sink: tokenSinks.redis(...) + durable: true.

For the underlying class, RedisTokenStreamSink (and the RedisStreamClient interface) are exported from the package root.

On this page