End users & tenancy
Each of your end users has a memory of their own, named by one header and kept in a database of its own.
An end user is one person your app serves. twin memory keeps each end user's memory apart from every other
end user's. You name the end user on every request with the X-Twin-Account header.
The header names the end user
Every /v1/memory/* request carries X-Twin-Account. Use the id your app already has for the person, such as
user_42.
curl https://api.twinmemory.dev/v1/memory/search \
-H "Authorization: Bearer $TWIN_MEMORY_API_KEY" \
-H "X-Twin-Account: user_42" \
-H "Content-Type: application/json" \
-d '{ "query": "What does Alice eat?" }'The end user is never a field in a request body. Bodies are what a model fills in when it calls memory as a tool. A model that could write an account name into a body could name somebody else. A header is set by your code, not by the model.
A request without the header is refused with 400. There is no default end user.
How an account is formed
Your key decides the organisation and the project. The header decides the end user. Together they form the account a request reaches:
<org>.<project>.<X-Twin-Account>For example, a key for organisation o7k2m9qx4ab and project production, sent with X-Twin-Account: user_42,
reaches o7k2m9qx4ab.production.user_42. No header value can reach another organisation or another project,
because every account a key reaches begins with that key's organisation and project.
You never send or see the full account id. Error messages name the end user as you sent it.
Isolation
- One database per end user. Each account is its own SQLite file. One end user's writes, reads and background work never touch another's file.
- Every row is keyed by the account too. Inside the file, the account is the first column of every key, a second guard should a statement ever reach the wrong file.
- Requests for one end user run one at a time, in order. Requests for different end users run side by side. See Limits.
Two end users holding the same text is expected. If two of your users are in one group chat, add the chat's messages to each of them. Each copy is that end user's to search and to erase.
The id rule
An end-user id may use letters, digits, ., _ and -, and must begin with a letter or a digit.
The full account id, <org>.<project>.<user>, may be at most 64 characters. So the longest end-user id you can
send is:
64 - length(org) - length(project) - 2| Part | Length |
|---|---|
| Organisation id | always 11 characters, such as o7k2m9qx4ab |
| Project slug | 1 to 16 characters |
| End-user id | what is left: at least 35 characters |
An end-user id of 35 characters or fewer always fits. With the project production (10 characters), you have 41. If your ids are longer, such as email addresses or long UUIDs with prefixes, map them to a shorter stable
id, for example a hash, and keep the mapping on your side.
A header that does not fit the rule is refused with 400, and the message gives your project's limit:
{ "detail": "X-Twin-Account is not a usable end user id: 'a.very.long.id…' (letters, digits, '.', '_' and '-', starting with a letter or digit, at most 41 characters)" }The first write creates the end user
You do not create end users ahead of time. The first add for a new header value creates its
memory.
Reading an end user nothing was written for answers 404:
{ "detail": "no memory for end user 'bob' yet" }Treat this as empty memory, not as a failure. A new user of your app has no memory until your backend adds something for them.
Deleting a whole end user
forget erases named items, sessions or threads. To erase everything an end user has, delete
the end user from the console's End users page. That deletes the end user's database file and anything
still waiting to land for them.
Related
- Authentication & keys: read keys, read and write keys, and projects.
- Multi-tenant SaaS: mapping your users and workspaces to end users.
- Erasure & receipts: what a forget removes.