The idea
Every SDK method reaches Synap in one of two ways: a single HTTPS request (REST), or a long-lived gRPC stream thatinstance.listen() opens. REST is complete on its own: you can record turns, add memories and fetch context without ever opening a stream. The stream adds the other direction: Synap follows the conversation as it happens, prepares the context your agent will need next, and pushes it into the SDK before your agent asks.
For a long-running agent, the stream is the recommended default. REST fits serverless functions, short-lived workers, scripts and backfills. Both are SDK methods; you never write HTTP or gRPC code yourself.
How each path works
REST: one request per call
A REST method sends one HTTPS request and returns Synap’s answer, or raises an error. Nothing stays open between calls. Synap learns about a turn when you record it, and does nothing for your agent until the next call arrives.The gRPC stream
listen() opens one bidirectional stream and keeps it open until you call stop_listening(). Both sides write to it:
- Your process sends events: the user’s turn, your agent’s reply, its tool calls, tool results and reasoning, and the start and end of each session. The SDK opens a session on the first event for a conversation and closes it when you stop listening.
- Synap sends back: acknowledgements for the events it has taken, and context bundles it prepared for the next turn.
Which methods use which
The table shows where a request goes when one is needed. Some reads are answered from the SDK’s local caches without a request at all.
conversation.record_message is REST. It writes the same kind of turn that a streamed user_message or assistant_message writes, so pick one path per turn: a turn you sent on the stream is already stored.
How a context fetch uses the cache
The four context fetches check the anticipation cache before anything else, and the result depends on how well a prepared bundle matches the query:- A sure match is answered from the cache. No request leaves your process.
- A borderline match waits up to 250 ms for Synap: the SDK sends the request anyway, serves Synap’s answer if it arrives in time, and serves the cached match if it does not.
- No match goes to Synap as an ordinary REST request.
query_date or max_results_per_query always goes to Synap, because a prepared bundle was not built for a specific date or per-query share. Without a stream nothing fills the anticipation cache, so every fetch takes the REST path. While the stream is open, the SDK also reports each fetch on it (what was asked and whether the cache answered), which is how Synap learns which predictions were useful.
What each path gives you, and what it costs
The stream
What you get:- Context ready before the next turn. When your agent’s reply arrives as an
assistant_message, Synap predicts what the user is likely to ask next, searches memory for it, and pushes a bundle. When the next turn fetches, the answer is often already in the process. A session start does the same for the first turn of a conversation. - The whole turn, not only its edges. Tool calls, tool results and reasoning can only be sent on the stream. They let Synap see why your agent did what it did. See Traces and tool calls.
- Delivery that survives a dropped connection. While the stream reconnects, events are buffered (up to 100 events or five minutes, whichever comes first) and replayed when it returns. Events Synap had not yet acknowledged are replayed with their original ids, so Synap drops any it already holds. Both buffers are also written to a journal on disk (
~/.synap/outbox/<instance_id>.jsonlby default), so events still pending when your process dies go out the next timelisten()runs on that machine for the same instance. See Event delivery.
- A process that stays up, and owns the stream. Call
listen()at startup andstop_listening()on your shutdown path, which flushes what is buffered for up to two seconds and closes open sessions. The server closes every stream after one hour; the SDK reconnects with backoff, up to 10 attempts. After that the stream stays down and fetches keep working over REST, with no error in your agent loop, so watchinstance.is_listeningand log fromon_disconnect. - No answer per call.
send_message()returns once the event is written to the stream or buffered, not once Synap has stored it. A refusal arrives later and shows up in the SDK’s logs, not as an exception. Use REST when a caller needs to know that a specific write landed. - One stream per process. Synap limits concurrent streams to 100 per instance and 200 per account. Open one stream for the whole process and tell users apart by the
user_idandcustomer_idon each event; callinglisten()again on the same SDK raisesListeningAlreadyActiveError. - Send and fetch from the same process. Synap delivers a conversation’s bundles only to the streams that have sent events for that conversation. If one process reports the turns and another fetches, the fetching process never receives them.
- Size. Each message on the stream must fit in 4 MB, the largest message Synap accepts.
- A runtime that can hold a gRPC connection. In Python,
grpciois a required dependency. In JavaScript and TypeScript,@grpc/grpc-jsand@grpc/proto-loaderare optional dependencies that npm installs by default;npm install --omit=optionalleaves them out (along with the local embedding model, so cache matching falls back to word matching), andlisten()then throws. Edge runtimes and browsers cannot open the stream either: they do not provide the raw TCP and HTTP/2 connection gRPC needs, andlisten()throws there rather than doing nothing.
REST
What you get:- Nothing to manage. Each call stands alone, so it works in a function that runs for one request and exits.
- An answer per call. A write returns its result or raises, so you know whether it landed.
- The same memory. Turns recorded with
record_messageare stored, compacted and turned into long-term memory the same way as streamed turns.
- No prepared context. Each fetch is a request, unless the SDK already holds an identical earlier answer in its local response cache. You pay the network round trip on every new question.
- No traces. There is no REST method for tool calls, tool results or reasoning, so Synap never sees them. The SDK can still keep tool results on your machine for local history search with
archive_tool_result(); see Traces and tool calls.
Which to choose
You can mix the two in one process. A long-running agent streams its conversation turns and still calls
memories.create over REST for a document, or for a fact that must be retrievable at once.
Opening the stream
Framework integrations
Synap’s framework integrations share one rule. When your process has an open stream, they report turns, tool calls, tool results and reasoning on it. When it does not, they write each turn withrecord_message over REST, skip the traces, and still keep tool results in the local archive. They never raise inside your framework’s loop, so an integration behaves the same whether or not you call listen(); opening the stream is what turns on prepared context and traces. See Integrations.
Related
- Real-time anticipation: what Synap does with the events the stream carries.
- Event delivery: ids, acknowledgements, buffering and replay.
- Real-time anticipation in a server: one shared stream for a multi-tenant process.
- Traces and tool calls: what to send beyond the turns.
- instance.listen and conversation.record_message: the two ways to write a turn.