What counts as one string
We compare subjects byte for byte. There is no normalisation anywhere in the pipeline — we do not trim, case-fold, or canonicalise what you send.What we do with it
The isolation key is always the pair(workspace, subject) — never the subject alone. Within your workspace, the subject partitions:
Ask for a thread belonging to a different subject and you get
404, not 403. The lookup is scoped, so the thread does not exist as far as that subject is concerned.
How it reaches us
The subject arrives one of two ways, and the difference is who vouches for it.
In relay, the workspace API key authenticates you, not the subject. Anyone holding that key can assert any subject in your workspace. That is by design — your backend is the authority on who its users are — but it means the key must never reach a browser or a mobile client.
Choosing your subject
One question decides it: what is the smallest unit that must never see another unit’s data? That unit is your subject.
Get this wrong in the permissive direction and two people share a memory partition. There is no error, no warning, and no way for us to detect it — the strings matched, so as far as Patchwork is concerned they are the same subject.
Composite subjects
Most B2B products need more than a user id. Consider an app where Jane belongs to two workspaces, Acme and Globex. If her subject isuser_9f2, she has one thread history and one memory partition across both. An agent that learned “the Q3 budget is $2.4M” while she was working in Acme will recall it while she is working in Globex. That is a tenant data leak, authored in the subject.
The fix is to compose the identifier from every dimension that must stay separate:
Minting a composite subject
In direct, the composite goes in thesub claim. Build it from your own session state:
Rules for composing
required
Both halves must be ids that never change. An email, a username, or a workspace slug can be edited — and an edited subject is a new subject with an empty history.
required
: is conventional. If your ids can contain it, user:1:ws:2 is ambiguous. Use a delimiter your id format forbids, or fixed-prefix ids like usr_9f2 and ws_acme.recommended
user:workspace and workspace:user are different strings. Pick one and never vary it — a flipped order in one code path silently creates a second partition.recommended
Every component multiplies your partitions.
user:workspace:project gives a user a separate agent memory per project, which is either exactly what you want or an agent that never remembers anything.What composition costs you
Two consequences worth accepting on purpose: No continuity across the boundary. Jane gets no shared memory between Acme and Globex. For most B2B products that is the point. If you want a genuinely cross-tenant assistant, the composite is the wrong shape. Erasure fans out. Deleting Jane means erasing every subject that contains her user id — one call per workspace she belongs to. Since erasure matches the exact string, you must be able to enumerate her memberships at deletion time. Keep that mapping in your own database; we cannot reconstruct it from the strings.Beyond one subject per person
The subject is a partition key, not an identity. That makes a few patterns available that a user id would not. Share deliberately. Give every member of a team the same subject —ws_acme — and they share one thread history and one memory. A shared inbox assistant, an ops copilot, a channel bot. Everyone sees what everyone else told it, which is either the feature or a leak, depending on whether you chose it.
Subjects without people. A nightly reconciliation job is a legitimate subject. So is a device, a kiosk, or a webhook processor. Anything that should accumulate its own memory and history is a subject.
Isolate test traffic. Prefix non-production subjects — test:user_9f2 — and you can erase every test partition without touching real data, and keep evaluation traffic out of production recall.
Segment by lifetime. If a conversation should start clean each time, put something ephemeral in the subject: ticket:8821 gives one memory partition per ticket, which ends when the ticket does.
Rules that always apply
required
The value is stored verbatim, returned in thread and run responses, included in every event payload, and written to request logs. An email address in a subject is an email address in all of those places, permanently. Use your internal id.
required
If subjects come from more than one system, prefix each source.
slack:U0APP and wa:15551234567 cannot collide; a bare U0APP and a bare 15551234567 are one namespace away from doing so.required
A subject is the address of a memory partition. Changing it does not migrate anything — it abandons it.There is no rename, no merge, and no migrate. If you pick the wrong grain and need to change it later, your options are to leave the old subject in place and start a new one, or to erase the old partition and let the agent relearn. Choose the grain as carefully as you would a primary key.
Erasing a subject
Erasure runs from the dashboard today — there is no/v1 endpoint for it. Find the subject under Memory, erase it, and every subject-scoped memory keyed on that exact string is removed.
Which puts the work back on your mapping. Deletion matches the string byte for byte, so you must erase every subject you minted for that person, including each composite you built from their id. If you cannot reproduce the string, we cannot find the data.
That is the practical cost of an identifier we never resolve, and the reason to keep your subject mapping durable rather than derived. See Memory for what is stored and how long it lives.