Multi-Replica Deployments

QueueConfig(redis=...) sets the event stream and cancellation manager on every replica so runs can be resumed and cancelled from any of them.

from agno.agent import Agent
from agno.db.postgres import PostgresDb
from agno.models.openai import OpenAIResponses
from agno.os import AgentOS, QueueConfig

db = PostgresDb(db_url="postgresql+psycopg://ai:ai@localhost:5532/ai")

agent = Agent(
    name="Resumable Stream Agent",
    id="resumable-stream-agent",
    model=OpenAIResponses(id="gpt-5.5"),
    db=db,
)

agent_os = AgentOS(
    agents=[agent],
    db=db,
    queue=QueueConfig(
        durable=True,
        redis="redis://localhost:6379",
    ),
)
app = agent_os.get_app()
uv pip install "agno[os]" openai psycopg redis

Set redis exactly when you run more than one replica. With one replica, in-process defaults do the same job.

One setting, two managers

queue.redis sets both the event stream and the cancellation manager on every replica that constructs AgentOS(...). No other wiring is needed.

ManagerDirectionDefaultSet by queue.redis toExplicit setter
Event stream (BaseEventStream)Events out: the executing worker publishes run events; any replica can tail or replay them for /resume, streaming submissions, and continues.InMemoryEventStreamRedisEventStreamset_event_stream() or AgentOS(event_stream=...)
Cancellation manager (BaseRunCancellationManager)Cancel in: a /cancel received by any replica is recorded in Redis; the executing worker sees it at its next checkpoint.In-memoryRedisRunCancellationManagerset_cancellation_manager()

A background run started on replica A may execute on replica B while the client's next request lands on replica C. Both directions have to cross replica boundaries. Configuring only one is a common misconfiguration: cancels reach every replica while resumed streams idle on the wrong one, or the reverse. One setting covers both so that cannot happen by accident.

In v2, cross-replica cancellation meant constructing RedisRunCancellationManager yourself and calling set_cancellation_manager() in every process, and the event buffer had no shared backend at all.

# v2: cancellation only, wired by hand in every process
from agno.run.cancel import set_cancellation_manager
from agno.run.cancellation_management.redis_cancellation_manager import RedisRunCancellationManager

set_cancellation_manager(RedisRunCancellationManager(redis_client=..., async_redis_client=...))

# v3: event stream and cancellation manager, from one setting
agent_os = AgentOS(agents=[agent], db=db, queue=QueueConfig(redis="redis://localhost:6379"))

An explicit setter still wins for either manager. queue.redis only replaces defaults, never a backend you installed yourself, so a custom cancellation manager or an AgentOS(event_stream=...) override keeps working. If exactly one of the two is explicit, AgentOS logs a warning that cancellation and events may ride different Redis instances. Keys are namespaced under {key_prefix}:run:cancellation: and {key_prefix}:os:events: when key_prefix is set.

Start a streaming run against one replica, then call /resume on another. Events replay and tail from Redis regardless of which replica executes the run.

Two Redis roles

queue.redis and db=RedisDb(...) are different settings with different requirements.

SettingRolePersistenceWhat a Redis fault costs
QueueConfig(redis=...)Coordination: event streams, cancel signalsKeys carry TTLs; persisted run data remains in the session database.Live streams and cancellation delivery degrade. Events and cancellation signals are not guaranteed to be reconstructed after a fault.
QueueConfig(db=RedisDb(...))Job storage: the queue table itselfAOF with appendfsync everysec or always.Recently accepted jobs can be lost on a Redis crash under default RDB snapshotting.

A common production layout is Postgres for db (truth and jobs) and a plain Redis or Valkey for redis (coordination). Pointing both at one Redis works, as in the Redis event stream cookbook, but the persistence requirement then applies to it.

Connection options

Pass a URL for the common case, or RedisCoordination to inject clients:

from redis import Redis
from redis.asyncio import Redis as AsyncRedis

from agno.os import QueueConfig, RedisCoordination

queue = QueueConfig(
    durable=True,
    redis=RedisCoordination(
        sync_client=Redis.from_url("redis://localhost:6379"),
        async_client=AsyncRedis.from_url("redis://localhost:6379"),
        key_prefix="checkout-os",
    ),
)
FieldDescription
urlRedis URL. Clients are constructed for you.
sync_client, async_clientExisting clients. Both are required together. The cancellation manager needs both; the event stream uses the async one.
key_prefixNamespace for every coordination key. Set a per-deployment value when several AgentOS deployments share one Redis. With the default, they read each other's runs by run_id.

Valkey works anywhere Redis does. Use standalone Redis or Valkey. RedisCluster clients are rejected for both the durable queue store and the shared event stream.

Stream retention

Per-run stream keys expire 30 minutes after the last activity, refreshed while the run is live. Each stream keeps roughly the most recent 10,000 events. A client that reconnects after expiry gets the persisted events from the database through /resume with session_id, not the live tail.

Without Redis on multiple replicas

Durable execution still works: jobs live in the database and any replica can claim them. The live view does not. A streaming submission accepted on replica A whose job is claimed by replica B produces a tail that idles until the client times out, even though the run completes. AgentOS logs this at startup:

Durable queue with the in-memory event stream: streamed views of queued runs
are replica-local. ... Set queue.redis to wire a shared event stream.

Polling is unaffected, because the run row is in the database.

Next Steps

TaskGuide
Pin jobs to a subset of replicasDeployment affinity
Keep timing settings consistent across the fleetFleet-wide settings
SSE resume protocol and meta eventsBackground Execution (SDK)