Skip to main content
A subject is a string you choose. Patchwork stores it, partitions on it, and returns it. We never parse it, validate its shape, look it up, or resolve it to a person. That is the whole contract, and everything below follows from it. A subject can name a user, a team, a device, a tenant, a support ticket, or a nightly job. We have no opinion, because we have no way to form one.
All five are equally valid. The only string we reject is an empty one.

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.
The third is the one that bites: a trailing space from a template or a CSV column is invisible in a log and produces a partition you will never look at again. Normalise on your side, because we will not do it for you. There is no length limit, but in relay the value travels in an HTTP header — keep it short enough that proxies do not object. You never create a subject. It exists the moment you first use it, and a request naming a subject with no history simply starts one.

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.
A subject-scoped endpoint requires a subject. Omitting Patchwork-Subject in relay is a 400 subject_required — it is not a wider mode, and there is no way to ask for every subject at once.Earlier versions answered a subject-less relay read with the whole workspace. Forgetting one header was then enough to hand one of your customers another customer’s conversations, so it is now refused.

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 is user_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:
Same human. Two subjects. Two thread lists, two memory partitions, no crossover — because to Patchwork these are simply two different strings.

Minting a composite subject

In direct, the composite goes in the sub claim. Build it from your own session state:
In relay, your backend composes the same string and sends it as a header:
Derive every component server-side. If the workspace id comes from a request parameter, a user can mint a subject for a workspace they do not belong to, and read that partition’s threads and memory.Patchwork cannot catch this. We do not know that your string has two parts, that the second part is a workspace, or that Jane is not a member of it. Authorising the pair is your job, and it must happen before you build the string.

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.