- Major versions introduce breaking changes to the API or SDK interfaces
- Minor versions add new features in a backward-compatible manner
- Patch versions contain backward-compatible bug fixes
Compatibility Matrix
Use this table to pick compatible versions when pinning dependencies.Core SDK
JavaScript / TypeScript SDK
Integration packages
Pin numbers above reflect the minimum tested. Newer minor/patch versions of the framework will typically work, but if a framework ships a breaking change, the integration package will pin around it explicitly. Check the integration package’s own changelog for the exact pin if you suspect a compatibility issue.
Cloud API
The Cloud API is versioned at the URL prefix (/v1/...). The current SDK targets v1 exclusively. v2 is not on the near-term roadmap.
JavaScript / TypeScript SDK
Releases of@maximem/synap-js-sdk. Versioned independently of the Python SDK.
js v0.4.6, 2026-08-27
Fixed
- The options added in 0.4.5 accept
undefinedexplicitly.logger,sdk_st_authoritativeandst_verbatim_overlaywere declared as plain optional, so underexactOptionalPropertyTypesyou could not pass a value that might be absent, which is the normal shape when configuration comes from the environment. 0.4.4 had already done this forapiKey,clientId,instanceIdandbaseUrl; these now match.
js v0.4.5, 2026-08-27
Closes the surface the JavaScript SDK was missing against Python. Each item below was previously documented as “Python only”, which was the wrong place to fix a missing feature.Added
sdk_st_authoritative, and theSYNAP_SDK_ST_AUTHORITATIVEenvironment variable. With it on,conversation.context.get_context_for_prompt()andget_compacted()render from the SDK’s own short-term store when the conversation is warm, skipping the cloud round trip. That is a metered call saved as well as a round trip, and JavaScript had no way to ask for it. Off by default. The three prompt styles (structured,narrative,bullet_points) produce byte-identical output to the Python formatter, so an application that switches languages does not silently change what its model sees.st_verbatim_overlayas a client option. The behaviour existed but could only be reached throughSYNAP_ST_VERBATIM_OVERLAY. The environment variable still wins over the option, in both directions, matching Python.logger, on the constructor and onconfigure(). Every diagnostic the SDK emits goes through it; the default remainsconsole.warn. Python routes these through the stdlibloggingmodule, which is why it takeslog_levelandlogger; there is no global logger in JavaScript, so this takes the sink directly.log_levelremains accepted and ignored.record_thinking({ step_index, thought_type }). Both are folded into the event’s metadata map under those exact keys, which is how Python transmits them: the proto has no dedicated fields. A caller-supplied metadata entry of the same name is not overwritten.TimeoutConfig.streamIdle, defaulting to 60 seconds as in Python.api_base_urlas an alias forbaseUrl, so one configuration object works against either SDK unchanged.
js v0.4.4, 2026-08-27
Fixed
retryPolicy: nulldisables retries, as the documentation always said it did. It restored the default three-attempt policy instead, and the constructor rejectednulloutright, so there was no way to turn retries off from JavaScript at all. Python’sSDKConfig(retry_policy=None)leavesmax_attemptsat 1 and this now matches. If you passnulltoconfigure()expecting the defaults back, pass the policy explicitly instead.- Insufficient-credit errors carry the top-up links again. The 402 handler read a
required_creditskey the server never sends (it sendsminimum_required_credits), sorequiredCreditswas alwaysnullagainst a real server, andrecovery_urlandredeem_urlwere dropped entirely.InsufficientCreditsErrornow exposesrequiredCredits,recoveryUrlandredeemUrl. instance.send_messagereports tool calls.tool_nameandtool_argswere missing from the options, though the proto carries both fields and the Python SDK sends them, so a tool call could not be reported from JavaScript.tool_argsis JSON-encoded intotool_args_json, matching Python.credits.get_ledgeraccepts its filters.entry_type,from_timeandto_timewere absent, and the defaultlimitwas 50 against Python’s 100, so the same call returned different pages in the two SDKs.context_mode: "conversation-summary"is reachable. The mode was documented and typed but its parameters were never forwarded, so the call fell through to a normal in-conversation fetch.include_profileandlast_n_conversationsare forwarded, and an unknown mode now raises instead of being ignored.- Context fetches apply the B2C check. Retrieval skipped the scope validation the write paths perform, so a
customer_idsent to a B2C instance reached the server and came back as an opaque HTTP 400.
Changed
- Response types replace
Record<string, unknown>.credits.get_balance,get_ledger,estimateandredeem, plusconversation.ingest_transcriptandmemories.batch_create, returned bare JSON, so every field read wasunknownin TypeScript. They now return types mirroring the Python models:CreditBalance,CreditLedgerPage,CreditEstimate,RedeemResult,TranscriptIngestResult,BatchCreateResult. CreateMemoryResultrequiresingestion_id,document_id,statusandqueued_at, matching Python’sCreateMemoryResponse. All four were optional, which made the commonest two-line pattern in the documentation,create()thenwait_for_completion(result.ingestion_id), fail to compile understrict.apiKey,clientId,instanceIdandbaseUrlacceptundefinedexplicitly, sonew SynapClient({ apiKey: process.env.SYNAP_API_KEY })compiles underexactOptionalPropertyTypes. Undefined already meant “read the environment”; the type now says so.
Added
COMPACTION_LEVELSand theCompactionLeveltype, mirroring Python’s enum.compaction_levelwas an untypedstring, so a typo reached the server rather than the compiler.
js v0.4.3, 2026-08-26
Fixed
- B2C instances take
user_idalone.memories.create,memories.create_from_file,conversation.record_messageandrecord_messages_batchall required acustomer_id, so on a B2C instance, where the server rejects one, the only correct call could not be expressed: two of them threwcustomer_id is requiredbefore a request was ever made. A batch validates every message before sending any, so one bad message cannot half-apply a batch. Where the server is too old to report its isolation mode, nothing is enforced, so a new SDK against an old server refuses nothing.
js v0.4.2, 2026-08-25
Fixed
SYNAP_BASE_URLis honoured again. 0.4.0 and 0.4.1 ignored it and used the Synap Cloud default, so a deployment configured through the environment sent its requests to Synap Cloud instead. Nothing failed, which is what made it hard to spot. An explicitbaseUrlpassed to the constructor still wins over the environment.SYNAP_GRPC_TLSis accepted as well asSYNAP_GRPC_USE_TLS. A plaintext deployment configured with the former was attempting TLS and could not connect.
js v0.4.1, 2026-08-25
Fixed
- Anticipated bundles are now read on the fetch path. The stream stored them and no fetch consulted the store, so every retrieval was a cloud call and the stream had no effect on anything. Scope widening follows the same rules as the Python SDK: a customer-scope lookup widens to client-shared bundles but never to user-scoped ones.
- One client per identity, per process. Each
new SynapClient()built its own anticipation cache, so an application constructing a client per request never saw a warm one. Clients for the same identity now share state, and the instance id resolved duringinitialize()becomes an additional identity so constructing by id later finds the same client. - A turn is visible to the next fetch. Messages recorded through
conversation.record_messageor the stream are held locally and merged into the following conversation-scope fetch, so a fast follow-up sees the turn that preceded it rather than waiting for the server to compact.SYNAP_ST_VERBATIM_OVERLAY=0disables it. - Periodic user-summary injection. A cached user summary is folded into the response every fifth turn of a conversation, matching the Python SDK. Requires a
user_idon the fetch; without one the summary cannot be scoped and is skipped. context_usedandcontext_assembledare emitted. These drive per-prefetch scoring and the Requests-page audit. Both ride the anticipation stream and are skipped when it is not open.
js v0.4.0, 2026-08-24
Changed
- The SDK is now a native TypeScript client. Earlier versions were a Node.js wrapper that spawned the Python SDK as a subprocess, which meant Python 3.11+ on every host, no support for Edge runtimes or Workers, and one call at a time through a single pipe. The SDK now talks to the API directly.
- Node.js 20+ is required.
- The runtime setup step is gone.
npx synap-js-sdk setupno longer does anything and can be removed from install and CI scripts. It still exits0, so leaving it in place does not break a build.~/.synap-js-sdkis orphaned and safe to delete. - The package ships both ES modules and CommonJS.
require()andimportboth work, each with its own type definitions. listen()is opt-in. Earlier versions opened an anticipation stream automatically during initialization. It now starts only when you callinstance.listen(), and needs@grpc/grpc-jsand@grpc/proto-loaderinstalled.- The local cache no longer persists across restarts. It was a SQLite file on disk; it is now in memory, so that a native module does not compromise serverless portability.
Removed
setupPythonRuntime()andsetupTypeScriptExtension(). Neither has anything to do.createClient(options), which was an alias fornew SynapClient(options).resolveInstanceId().initialize()resolves the instance id from the API key; read it fromclient.instance_id.createSynapError(). Construct the error classes directly, or branch onerror.code.- Deleting memories by user id. It relied on a list held in the bridge process, so it silently deleted nothing in serverless while reporting success. Pass an explicit
memory_id. - The five
SYNAP_PYTHON_*andSYNAP_PY_SDK_*environment variables, along withSYNAP_JS_SDK_HOME.
Upgrading from 0.3.x? No client methods were removed, and
require() still works, so most projects change nothing beyond the install and dropping the setup step.Python SDK
v0.4.3, 2026-08-05
Fixed
- The local cache is now scoped to the instance, not just the account. It lived at
~/.synap/{client_id}/and addressed its files by scope and entity id alone.client_ididentifies the Synap account, which can own several instances, and those are separate memory stores sharing no data (so two instances under one account wrote to the sameusers/{user_id}.db, and where the samecustomer_idoruser_idappeared under both, one instance could be served the other’s cached context. The path is now~/.synap/{client_id}/{instance_id}/and the instance is part of the cache key. Server-side scoping was never affected) this was local disk only, and it needed two instances of one account live in the same process, which only became a supported shape in 0.4.1.
Your cache starts cold after this upgrade and refills on demand; entries under the old path are not migrated. That is deliberate: a pre-0.4.3 directory may hold entries from more than one instance, and there is no way to tell which belongs to which, so moving them would carry the contamination forward. Nothing is deleted; you can remove the old
~/.synap/{client_id}/*.db, customers/ and users/ entries once you have upgraded. No server-side data is involved.v0.4.2, 2026-08-05
Fixed
- One Synap instance no longer ends up with two live SDKs. An SDK built from an API key alone is identified by that key, because its
instance_iddoes not exist yet; it is resolved from the key duringinitialize(). The resolved id was never recorded, so a laterMaximemSynapSDK(instance_id="inst_…")for that same instance missed the lookup and built a second SDK: two anticipation caches, two short-term stores, twoListenstreams and a lower local cache hit rate for one instance.initialize()now records the resolved id as an additional identity for the SDK. Constructing by API key keeps working exactly as before, and aninstance_idalready held by another live SDK is never taken from it. - Concurrent first constructions no longer diverge. Several threads racing to build the first SDK for one identity could all miss the registry and the last one to finish would win, leaving every other caller holding an SDK the registry did not know about, with its own connections and caches. Construction now claims its slot atomically; the callers that lose the race receive the winner. This was reachable on a normal cold start in a multi-threaded server.
shutdown()releases every identity the SDK holds, so nothing hands out an SDK whose transports are already closed.- Test modules are no longer published inside the package.
maximem_synap-0.4.1shipped sixtest_*.pymodules intosite-packages, one importing an internal server module that does not exist outside our monorepo, which could fail any test run that collected them. The 0.4.2 wheel and sdist contain none. maximem-synap-nemo-agent-toolkit: SDK teardown never ran. Both entry points probed for aclose()method the SDK does not have (it isshutdown()), so the guard silently did nothing and every workflow leaked its HTTP transport, gRPC stream and telemetry collector.
No API was added, removed or renamed, and there is no data migration. Callers passing an explicit
instance_id see byte-identical behaviour.v0.4.1, 2026-08-04
Fixed
- Cross-tenant credential and state sharing when one process used more than one API key. The SDK keeps one instance per Synap instance, keyed on
instance_id, butinstance_idis optional and empty at construction time, because it is resolved from the API key later, duringinitialize(). EveryMaximemSynapSDK(api_key=...)therefore landed in the same registry slot, and the second one adopted the first’s entire state, credentials included. In a process serving two tenants, tenant B’s SDK authenticated as tenant A: B’s reads returned A’s memory and B’s writes were committed into A’s instance. It was silent (no exception, no warning, no log line) because the server correctly authenticated the key it received; it simply received the wrong one. The registry now derives its key from the credential when noinstance_idis given (stored as a truncated SHA-256 digest, never in plaintext). shutdown()left a stale registry entry. Registration used the constructor’sinstance_idand unregistration usedself.instance_id, whichinitialize()may have replaced in between. The entry survived teardown, so the registry kept handing out an SDK whose transports, stream and cache were already closed.- A
_force_newSDK’sshutdown()evicted the real singleton. Unregistration had no ownership check, so a throwaway SDK (a test fixture, an adapter, the JS bridge) removed whatever sat on its key, taking the application’s live SDK out of the registry mid-conversation.
Changed
- The registry now derives its key from the API key, so each distinct key gets its own SDK, including two keys issued against the same instance, which each authenticate as themselves. Only an explicitly passed
instance_idstill collapses several keys onto one SDK, unchanged from before. If you rotate a key inside a long-running process and construct byinstance_id, callawait sdk.shutdown()before reconstructing, or restart the worker. See Rotating a key in a long-running process.
v0.4.0, 2026-07-17
Added
conversation.ingest_transcript(...): one-shot async push of a full conversation transcript (string or typedTranscriptTurnlist) plus optional client analysis JSON and metadata. Returns immediately with aTranscriptIngestResponse(ingestion_id,status,summary_status, …); poll withmemories.status()/wait_for_completion(). Idempotent on(conversation_id, transcript): an identical re-push returnsstatus="duplicate"with the originalingestion_id; a different transcript under the sameconversation_idraisesTranscriptConflictError. Unlikerecord_message,conversation_idis an arbitrary client string (no UUID validation); the server coerces it and echoes the original asexternal_conversation_id.- Conversation-summary fetch:
fetch(...),user.context.fetch(...)and the unifiedsdk.fetch(...)gaincontext_mode("in-conversation"default /"conversation-summary"),include_profile(defaultTrue) andlast_n_conversations(default1, range 0–20). In summary mode the response carries a callerprofileand previous-conversation summaries instead of item lists: the call-start read for async integrations. In the unifiedsdk.fetch, these three params are forwarded only to the user-scope sub-fetch. user.get_profile(user_id, customer_id=None): convenience getter returning a typedUserProfileModel(client-defined critical attributes + free-text overview). RaisesContextNotFoundError(404) when no profile exists.- New typed models:
TranscriptTurn,TranscriptIngestResponse,UserProfileModel,ProfileAttributeModel,ConversationSummaryModel, all with a.rawescape hatch and unknown-field tolerance.ContextResponseandUnifiedContextResponsegain optionalprofile/conversationsfields, andUnifiedContextResponse.format_for_prompt()renders## Caller Profileand## Previous Conversationssections when present (byte-identical output when absent). - New exceptions:
ConflictErrorandTranscriptConflictError(both permanent).InsufficientCreditsErroris now exported at the top level.
Changed
- HTTP 409 and 422 mapping: the transport now maps 422 →
InvalidInputErrorand 409 →ConflictError(discriminated on a{"detail": {"code": "transcript_conflict"}}body toTranscriptConflictError). Both are permanent and never retried. Previously both status codes fell through to a retryable transient error. conversation.compact()409 semantics (behavioral): a “Compaction already in progress” 409 now raises a permanentConflictErrorimmediately instead of being retried as a transient error and eventually surfacing as one. Intentional: a 409 here means another compaction already holds the lock, and retrying cannot change that. CatchConflictError(or its baseSynapPermanentError) where you previously caught the transient/retry-exhausted error.
v0.2.0, 2026-07-06
Added
precision_levelfetch parameter: All context fetch calls now accept an optionalprecision_levelparameter ("high"or"medium"). With"high"(the default), behavior is unchanged: results go through an additional relevance-refinement pass before being returned."medium"skips the refinement pass for faster responses; recall isn’t impacted (the same candidate memories are searched), but outputs are less precisely filtered.
v0.1.2, 2025-01-15
Added
- Entity Resolution integration: The ingestion pipeline now resolves entities across conversations. Mentions of the same entity (e.g., “John”, “John Smith”, “my manager”) are linked to a canonical entity record.
- Review queue for entity resolution: Ambiguous entity matches are routed to a review queue for human verification via the Dashboard.
- Auto-registration of unresolved entities: New entities that do not match any existing record are automatically registered at the CUSTOMER scope for future lookups.
- Semantic entity matching: Entity resolution uses semantic similarity matching, catching variations that exact string matching would miss.
Changed
- Entity resolution is now integrated before persistence in the ingestion flow.
- Entity resolution runs as a graceful degradation feature: if unavailable, the pipeline continues without resolution and logs a warning.
Fixed
- Fixed non-deterministic test behavior caused by Python set iteration order in pipeline stage tests.
v0.1.1, 2025-01-10
Added
- Memory Architecture Configuration (MACA) system: Full configuration lifecycle with init, update, review, apply, and rollback operations.
- Admin API Groups A-D: Client lifecycle (10 endpoints), instance lifecycle (12 endpoints), config management (10 endpoints), and setup/onboarding (6 endpoints).
- Dashboard API routes: Configuration detail and history endpoints for the web UI.
- Configuration persistence and workflow improvements: Added storage and workflow support for configuration metadata, approvals, and history.
- Configuration validation improvements: Stronger schema and business-rule validation for submitted configuration files.
- Setup and architecture management improvements: Better onboarding and dashboard-facing configuration management flows.
Changed
- Admin API response shapes were standardized for consistency across dashboard routes.
- Configuration version numbers are parsed from semantic version strings (“1.0.0” becomes version
1).
Fixed
- Resolved circular import issues in
admin_api/__init__.pyby using lazy imports (inside methods) for cross-manager dependencies.
v0.1.0, 2025-01-05
Added
- Initial release of the Synap SDK and API.
- Memory ingestion pipeline: Four-stage async pipeline (extraction, categorization, entity resolution, storage) with
fastandlong-rangeprocessing modes. - Context retrieval:
POST /v1/context/fetchwith vector search, graph traversal, and re-ranking. Supportsfastandaccurateretrieval modes. - Context compaction:
POST /v1/context/compactwithadaptive,aggressive,balanced, andconservativestrategies. - Instance management: Full CRUD for instances via the Dashboard API, including API key generation and revocation.
- API key authentication:
synap_<random>keys issued from the Dashboard, used asAuthorization: Bearerfor all SDK communication (REST and gRPC). - Cloud auth layer: Production-ready authentication and authorization foundation for SDK and dashboard operations.
- Memory Architecture Configurators: Initial configuration system for storage, ingestion, and retrieval controls.
- Scope system: Four-level scope chain (USER > CUSTOMER > CLIENT > WORLD) with proper isolation and inheritance.
- Webhook system: Five event types (
conversation.started,conversation.ended,context.retrieved,config.applied,compaction.completed) with HMAC-SHA256 signature verification. - Analytics: Usage metrics, latency percentiles, and token tracking with minute/hour/day rollup buckets.
- Python SDK: Fully async SDK with typed exceptions and automatic retries.
- PostgreSQL backend: Persistent storage for clients, instances, credentials, memories, and analytics data.
- Production key management integration: Cloud-integrated key management support.
Security
- API keys are hashed (SHA-256) at rest and shown to the user only once at generation time.
- Revoking an API key takes effect immediately.
- AuthContext is immutable once created, preventing tampering.
Versions prior to 1.0.0 may include breaking changes in minor version increments as the API stabilizes. We recommend pinning to a specific version in production and testing upgrades in staging first.