Skip to main content
Read Identifiers & Scopes first. This page assumes you know what customer_id and user_id do.
If your account uses the default Client → Customer → User chain, nothing here changes how you integrate. This page matters when that shape does not describe your business.

What a level is

A scope is a position on a ladder. Your account is the top rung, and each rung below it narrows who a memory belongs to. The default ladder has three:
Every memory is filed at exactly one rung, and it carries that rung plus every rung above it. That stored list is what a read filters on.

One ladder per account

You have one ladder, and every instance you run shares it. There is no per-instance ladder. This follows from what an id means. A customer id identifies the same customer across your whole account, and a user id the same person, whichever instance sent the request. Two instances writing about customer acme are writing about one customer. If they disagreed about what the levels are, the same records would carry different level names depending on which instance wrote them, and a read from one would not find what the other stored. Instances keep their own memory settings. The switch that turns nested scoping on is also per instance, so you can enable it for one instance and watch it before enabling the rest. The shape is shared; the moment each instance starts using it is not.

If your account is B2C

Some accounts are set up so that a customer and a person are the same thing: one consumer, using your product for themselves, belonging to no organisation. Synap calls this user_context_isolation: equals_customer, and your integration sends a user_id and no customer_id. On those accounts the customer rung and the user rung are one node. Not two levels that happen to have one member each. The same node, reached by either name:
That has three consequences worth knowing before you change your ladder. A rung between customer and user has nowhere to sit. It would be a container between one person and the same person. Synap will accept the shape, and a request naming it will resolve, but it is almost never what anyone means. A rung ABOVE the customer is the shape that works. Something people belong to, rather than something inside a person:
That groups consumers by something real, and does not fight the collapse. Your integration must keep sending only user_id. Sending a customer_id to a B2C account is refused with customer_id_not_accepted_on_b2c, and a scope path does not change that. If you send a path, name the customer rung with the same value as the user rung.
A deeper ladder means your writes must send a path. With a rung between the customer and the user, user_id alone can no longer say which one a memory belongs to, and Synap refuses the write rather than guessing. Upgrade to Python SDK 0.4.8 or JS SDK 0.4.7 and send the full path before adding such a rung, not after. One gap to know about first: only memories.create takes a path today. Transcript ingest does not, so if you push transcripts, talk to us before you add a rung between your customer and your user.

Where your ladder comes from

You do not start from a blank ladder, and you do not start from ours either. When you onboard, you give us a use-case document: what your agent does, who it talks to, and what it should remember. That document already answers the scoping question, usually in your own words. A fintech client wrote “Client: the company operating the platform. Customer: an Indian retail consumer.” A clinical client wrote “memory is about the patient, not the clinician using the application,” which is a different shape entirely and says so plainly. So we read it and propose a ladder from it: how many rungs, what each one is called, and one sentence per rung explaining what it holds. The proposal quotes the line in your document that each rung came from, so you can see the reasoning rather than take it on trust.
The proposal is a draft. It is not applied, and nothing about how your memories are stored or read changes until you accept it. See Ratifying it.
If you did not upload a use-case document, or it does not say anything about who your users are, we propose the default three rungs and tell you that is what happened. We would rather show you a plain default and say so than invent a structure from a document that does not support one.

What we will and will not infer

We name rungs from what you wrote. We do not invent privacy boundaries from prose that does not ask for one. A rung decides who can read whose memories. Adding one that you did not ask for would be a privacy decision made by inference, so a rung we are not confident about is raised as a question in the proposal rather than added to it.

Ratifying it

A proposed ladder does nothing. It is a draft until a person accepts it, and until then your account keeps using the default chain. Accepting records who accepted and when. That record matters because a ladder is close to permanent: a rung can be renamed at any time and can never be deleted, only retired, because memories already resolve through it. Before you accept, you can:
  • rename any rung, which is free and changes nothing about who reads what
  • remove a proposed rung you do not want
  • add one we did not propose
After you accept, renaming stays free, adding a rung is a migration we show you the size of first, and removing is no longer possible.
An unratified ladder is never used at runtime. If you never accept the proposal, your account keeps the default Client → Customer → User chain indefinitely and nothing breaks.

Which direction a read travels

This is the rule worth reading twice, because it is easy to hold backwards. A request sees its own rung and every rung above it. It never sees a rung below, and never a neighbour. So on the default ladder: A memory filed at the user level is not visible to a request that identifies only the customer. One person’s private context does not surface for their colleague, and it does not surface for a customer-wide question.
A memory’s stored chain is the path down to it. That is the list of rungs the memory can be read from, not a list of places the memory is visible. A fact filed at the user rung carries client, customer, user, and it is still readable only by that user.

Nothing sits below the person

The rung that identifies a person is always the last one. You cannot add a rung under it, and Synap refuses a ladder that tries. Two reasons, and both are hard to undo later. A rung under a person multiplies the work of consolidating memories against the busiest part of your tree, and it makes deletion ambiguous: erasing a person would first have to decide what happens to everything filed underneath them. The account rung at the top is fixed in the same way. It is your account itself, so nothing can go above it. Every rung you add goes somewhere in between.

Ranking: closer material wins

Filtering decides what a request is allowed to read. Ranking decides what it sees first. Among the memories a request may read, the ones filed closer to the person asking rank above the ones filed further up. A person’s own preference outranks a fact that is true of their whole organization, when both match the question equally well. This is a ranking adjustment only: it never makes a memory readable that was not, and it never hides one that was.

A level’s name can change, its key cannot

Every level has two parts.
  • The label is what people read: “Customer”, “Practice”, “Region”. Change it whenever you like. Nothing stored depends on it.
  • The key is what stored memories resolve through: customer, user. It never changes, because every memory already filed under it would stop resolving.
For the same reason a level can never be deleted, only deprecated. A deprecated level stops being offered for new structure, and everything already filed under it stays readable.

Sending a scope path

Requests normally identify a scope with customer_id and user_id, and Synap maps those onto the default three rungs. That is what almost every integration does, and it needs no change. Why two ids only ever reach three rungs. They are matched by ROLE, not by position: customer_id goes to whichever rung holds tenant_of_record, and user_id to whichever holds identity. There are exactly three reserved roles, so two ids can address exactly three rungs. A rung you added yourself holds no reserved role, so there is no field for it and no way to guess one. That is the whole difference: On the default three rungs the two are equivalent, and a path is simply the longer way to say the same thing. A path starts to matter only when your ladder has a rung the ids cannot name. To address a level beyond those three, pass a scope path from the SDK, naming every rung by its key. On a read it is the scope_path argument:
On a write it is the scope argument on memories.create:
Two rules apply, and both fail loudly rather than guessing:
  • Every rung must be named. A path that skips a level is refused, and the error names the level you left out. Synap never infers a missing id, because a wrong guess at or above the organization level would cross a tenant boundary.
  • The feature must be enabled for your instance. If it is not, sending a scope map is an error rather than being ignored. Ignoring it would widen the request to the whole organization when you asked to narrow it to one team.
Check your SDK version before you rely on this. Reads take a path from the Python SDK at 0.4.6 or later, and from the JS SDK at 0.4.4 or later. Writes take one on memories.create from Python 0.4.8 and JS 0.4.7. On anything older, the scope argument on a fetch names which method you called (user, customer, client), not a path, so an older SDK reaches the default three rungs and nothing beyond them. Upgrade with pip install -U maximem-synap or npm install @maximem/synap-js-sdk@latest. gRPC cannot carry a path at all.
Transcript ingest does not take a scope path in any released SDK yet. If your ladder has a rung between your customer and your user and you push transcripts, talk to us before you add it.

What you can reach today

Being precise about this matters more than making the feature sound finished. The honest summary: Synap’s scoping is correct and safe on the shape accounts already use. Driving a different shape needs an SDK new enough to send a scope path, and on writes that means memories.create. Which of these you can reach is decided by what your own integration sends, and that is something you can change. You do not have to go and find out what it sends: the Scope Ladder page in your dashboard lists the SDK versions we have seen in your recent requests, tells you the oldest one, and says so plainly when it has not seen any requests from you at all.

Adding a level

Check what your integration can send before you add one. A level outside the default three is only reachable by a call that sends a scope path. If your calls come from a Python SDK older than 0.4.8, or a JS SDK older than 0.4.7, your writes cannot send one, so adding a level does not give you a level: writes aimed at it fail with CannotPlaceWrite. Upgrade first, with pip install -U maximem-synap or npm install @maximem/synap-js-sdk@latest.
This is a fact about your integration, not a limit on your account, and it goes away when you upgrade. The Scope Ladder page in your dashboard checks it for you: it names the SDK versions in your recent traffic, and it will not let you add a level the oldest of them cannot address. If it has not seen any requests from you, or your requests do not report a version, it says that rather than guessing. A level goes between two existing ones. It cannot go above your account rung or below the rung that identifies a person, so on the default ladder that means above Customer or above User. Either way it is a migration rather than a setting: every node beneath the insertion point is re-parented under a new node at the new level, in one transaction, and Synap creates one placeholder parent per existing group so nothing is left without one. Those placeholders are yours to rename. Reads are unaffected while this happens. Memories above the insertion point are never touched, and no memory changes the group it belongs to.
One thing lags. Existing memories do not immediately carry the new level as one of their ancestors, so a query asking for everything beneath the new level is incomplete until Synap finishes stamping them. Ordinary retrieval is not affected. Ask us to run the backfill after you add a level.