Prerequisites
Before you begin, make sure you have:- Python 3.11+ installed on your machine (this tutorial is Python; see the note above for the Node equivalent)
- A Synap account with access to the Dashboard
- An instance created in the Dashboard (see Quickstart if you haven’t done this yet). For best results, upload a Use-Case Markdown file when creating your instance; see Use-Case Markdown for the template and authoring guide.
- An OpenAI API key (or any LLM provider; we use OpenAI in this tutorial for simplicity). No paid key yet? Use Google Gemini’s free tier; see the Gemini snippet in Setup → Integration.
Set Up Your Project
Configure Your Environment
.env file with your credentials. You will need two values:- SYNAP_API_KEY: The API key generated for your instance (format:
synap_<random>). Generate one from the Dashboard: open your instance, click Generate API Key, and copy the key; it is shown only once. - SYNAP_INSTANCE_ID: The instance id shown alongside the key in the Dashboard (format:
inst_plus 16 hex characters). Optional, sinceinitialize()resolves it from the API key, but the Dashboard gives you both together so you may as well paste both. - OPENAI_API_KEY: Your OpenAI API key
Initialize the SDK
sdk.instance.listen()opens one long-lived stream for the whole process. Your agent reports each turn on it, and Synap pushes anticipated context back so retrieval resolves locally. This is the Agent Integration.cache_backend="sqlite"enables local caching for faster repeated retrievals.log_level="INFO"is appropriate for development. Switch to"WARNING"or"ERROR"in production.- The
sdkobject is a module-level singleton. Import it from any module and it will reference the same initialized instance.
on_reconnect receives the attempt count, on_disconnect the reason.Create the FastAPI Application with Lifespan
main.py file. Start with the application lifespan manager, which ensures the SDK initializes on startup and shuts down cleanly when the server stops.lifespan context manager is the recommended way to manage startup/shutdown in modern FastAPI applications (v0.95+). It replaces the older @app.on_event("startup") and @app.on_event("shutdown") hooks.Build the Chat Endpoint
main.py. This endpoint performs five operations in sequence:- Report the incoming user message on the stream
- Retrieve relevant memories from Synap
- Build a system prompt enriched with memory context
- Call the LLM with the enriched prompt
- Report the assistant’s reply on the stream
send_message is what makes the conversation
retrievable: it registers the conversation and appends the turn to its
history, exactly as record_message does over REST. Context fetched by
conversation_id only returns turns that were reported, so an
unregistered conversation returns empty results by design.Step 1: Report the User Message
Step 1: Report the User Message
sdk.instance.send_message() publishes the turn on the open stream. Synap
appends it to the conversation’s rolling history and registers the
conversation under this conversation_id, the same effect
conversation.record_message() has over REST. That registration is what
later lets conversation.context.fetch(conversation_id=...) resolve scope
and return the conversation’s turns. Skip it and the first fetch for a
brand-new conversation_id comes back empty, by design.Reporting it also tells Synap what the agent is doing, so it can anticipate
what context to push next.Step 2: Memory Retrieval
Step 2: Memory Retrieval
sdk.conversation.context.fetch() call searches Synap’s memory stores for memories relevant to the user’s message. Key parameters:search_query: A list of strings used for semantic search. Passing the user’s message ensures we find contextually relevant memories.max_results=5: Limits context to the top 5 most relevant memories, keeping the prompt concise.types=["facts", "preferences"]: Retrieves only facts and preferences. Other types includeepisodes,emotions, andtemporal. Useallto retrieve every type.mode="fast": Uses thefastretrieval path (lower latency). Useaccuratewhen precision matters more; accurate adds graph traversal, LLM subquery decomposition and reranking on top of the same vector search.
Step 3: Prompt Construction
Step 3: Prompt Construction
92%) is included to help the LLM weigh how certain each piece of information is. You can omit confidence scores if you prefer a cleaner prompt.Step 4: LLM Call
Step 4: LLM Call
Step 5: Report the Reply, and why there is no ingestion call
Step 5: Report the Reply, and why there is no ingestion call
send_message(role="assistant", ...) completes the turn
in the conversation’s rolling history, so the next turn’s context.fetch
sees the full exchange, and it is the event that pre-warms anticipation for
the next turn, which is why it belongs after the LLM call, not before.Notice what is not here: memories.create(). Both reported turns become
long-term memory on their own, when this conversation compacts: at 3,000
tokens, 10 messages, or 5 minutes of inactivity. Synap promotes the raw turns
into the same ingestion pipeline memories.create() would have used.Add explicit ingestion only for content that is not a conversation turn
(documents, tickets, backfills) or that must be retrievable sooner than
compaction. Never for text you already reported; that extracts it twice.
See Agent Integration.Add a Health Check Endpoint
Run and Test
send_message: the exchange is already in the conversation’s history, so context.fetch returns it and the assistant naturally references the Japan trip. Had the turn never been reported, that fetch would come back empty by design.memories_used climb are created when the conversation compacts, which for a quiet conversation means about five minutes.To see it without waiting, force compaction once you have sent a few turns:memories_used will now be non-zero. In production you never call this; the thresholds and the idle timer handle it.Verify in the Dashboard
- API call counts reflecting your test requests
- Memory counts showing extracted facts, preferences, and entities
- Ingestion history with the conversation turns you sent

The instance detail page shows API activity and memory counts after your test requests.
Complete Code
Here is the final version of each file for reference. The Python tabs are FastAPI; the JavaScript tabs are Express.Agent integration
If you are wiring Synap into a live agent (a chat assistant, a copilot, a voice bot, anything with a conversation loop), this is the integration to build. Your agent reports what happened; Synap handles retrieval and memory formation.
memories.create() in it.
Why there is no ingestion call
Conversation turns you report withsend_message() are persisted to conversation history. When the conversation compacts, Synap promotes those raw turns into the long-term ingestion pipeline: the same extraction stages memories.create() runs, producing memories of the same quality.
Compaction happens on any of three triggers:
send_message() is sufficient on its own.Open one stream for the process
listen() is gRPC, so it needs Node plus
@grpc/grpc-js and @grpc/proto-loader. Importing the SDK in an Edge
route stays safe, only the stream is unavailable there.Report the whole turn, not just the two ends
Most integrations report the user’s message and the assistant’s reply and stop there. That works, and it leaves most of the value on the table. Synap then sees the question and the answer and nothing about the work in between, which is where the next question is usually decided. Five things happen in a turn. Report all five.Where each call goes
end_session is only for a conversation
that ends while the process lives on: a call hangs up, a chat window closes.
Check your own work
An integration is finished when all of these are true. If you are an agent wiring this up, check them before you report back.-
listen()is called once at startup, andstop_listening()on shutdown. - Every turn reports a
user_messageand anassistant_message. - Tool calls and results are reported, and share a
tool_call_id. - Reasoning is reported where the framework exposes it.
- Every event carries
conversation_idanduser_id(pluscustomer_idon B2B). - No
send_messagecall setsroleby hand for a tool or reasoning event. Use the typed methods; the role and the event type have to agree, and getting that wrong fails silently. -
end_sessionis called when a conversation ends and the process keeps running.
Requirements
send_message() does not validate it, but fetch() and every other call do.What still uses REST
The stream is not the entire transport, and it is not meant to be. Two things go over HTTP:initialize(): resolves your client and instance from the API keyfetch()on an anticipation miss: pushed bundles cover what Synap predicted; anything it did not predict is fetched normally
fetch() is one call whether it resolves from the local anticipation cache in about a millisecond or falls through to the network.
When to still call memories.create()
Promotion is automatic but not instant. A turn becomes retrievable when its conversation compacts, which for a quiet conversation means about five minutes. Reach for explicit ingestion in three cases:
document_type, mode, custom metadata, or to write at customer or client scope, ingest explicitly.Framework integrations
Five packages drive the stream for you, from the framework’s own hooks. With any other framework, or none, use the loop above directly: it is plain SDK calls and composes with anything.listen() is running, so adding one changes
nothing until you open a stream.
How anticipation works
Running it in a server
Integration reference
Framework Integration
- FastAPI
- Flask
- Express (Node)
- Next.js (Vercel AI SDK)
- Django
lifespan event for initialization and shutdown, and access the SDK instance from your route handlers.Async/Sync Bridging
asyncio.run(): Simplest approach
asyncio.run(): Simplest approach
asyncio.run() creates a new event loop each time. This is fine for low-frequency calls but adds overhead for high-throughput applications.Dedicated event loop: Best for web frameworks
Dedicated event loop: Best for web frameworks
asgiref.sync_to_async: Django-specific
asgiref.sync_to_async: Django-specific
asgiref provides utilities for bridging:LLM Provider Integration
- OpenAI
- Anthropic
- Google Gemini (free)
- Vercel AI SDK
gpt-4o:Error Handling and Graceful Degradation
Synap should enhance your application, not be a single point of failure. Design your integration to degrade gracefully when Synap is unavailable.error.code, not instanceof. With a dual
ESM/CJS dependency graph a consumer can hold two copies of the same error
class, and instanceof then fails against the copy it was not built from.
isSynapError(e) narrows to Synap’s own taxonomy, so a bug in your code
rethrows instead of being swallowed as “Synap was down”. See
How it differs from the Python SDK.