twin memory

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.

claude.ai and ChatGPT connections

An assistant connected by signing in sends no header. Its end user is chosen on the connect card when the person signs in, their own by default, and the signed token carries it. A header sent with that token is ignored. See claude.ai and ChatGPT.

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
PartLength
Organisation idalways 11 characters, such as o7k2m9qx4ab
Project slug1 to 16 characters
End-user idwhat 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.

On this page