One stream for the process
The instinct is to open a stream per user session. Don’t. Synap enforces per-instance and per-client stream quotas — currently 100 concurrent streams per instance and 200 per client. A stream-per-session design saturates that quota at real concurrency and surplus connections enter aRESOURCE_EXHAUSTED reconnect loop, which degrades the sessions that did connect.
Instead, open one stream at process startup and distinguish users with the user_id / customer_id you already pass on every call.
Scope lives on the request, not the stream. One process-wide stream serves every tenant because
send_message() and fetch() each carry their own user_id / customer_id.Scope every call, per request
With a shared stream, correctness depends entirely on passing the right identifiers on each call. Nothing about the stream itself is tenant-specific.Expect reconnects
Streams have a maximum lifetime of one hour, after which the server closes them. This is normal operation, not an error: the SDK reconnects with exponential backoff (10 attempts, and the counter resets on every successful connect), andon_reconnect fires.
A server that runs for a week will reconnect well over a hundred times. Log reconnects at INFO, not WARNING, or you will train yourself to ignore them.
The failure that stays silent
This is the one worth alerting on. If the stream dies permanently — quota exhaustion, a network partition longer than the backoff window, revoked credentials — your application keeps working.fetch() falls through to REST and returns correct results. Latency rises, and nothing else changes. No exception reaches your handler.
That is the intended design, and it is why streaming is safe to adopt. It also means you will not notice unless you look:
is_listening going false and staying false. Do not alert on individual disconnects — those are routine.
The JavaScript/TypeScript SDK never raises from
listen() at all: it logs a console warning and falls back to HTTP. There, checking synap.isListening is the only way to know the stream is up.Checklist
- One stream per process, opened at startup, closed at shutdown
listen()failure logged, never fatal to bootuser_idandcustomer_idon everysend_messageandfetchassistant_messageemitted after the reply, so it warms the next turn- One owner for durable memory: either ingest explicitly, or rely on compaction promotion — not both on the same text
is_listeningexported to your health endpoint and alerted on
How anticipation works
The two-channel model, event types, and what the platform does with each one.