Documentation
Spaces, and letting somebody else read one
A Space is three things at once: a rule about what gets filed there, an audience it is read by, and a retention policy. It is not a folder, and almost everything surprising about Spaces follows from that. Two of the three are built; the filing rule is a design that is not connected to anything yet, and this page says so where it comes up.
A memory is not in a Space
Filing writes a row in a link table — space_memberships, one per memory-and-Space pair, carrying how it got there, how sure that was, and the reason in words. The column has three values — explicit, inferred, rule — and today every row is explicit, because nothing files anything automatically. See below. Three consequences, and all three are the point:
One memory sits in several Spaces
A decision about the ledger can be filed in Work, in Acme and in Architecture. It is still one memory. Copying it per Space would make three that drift apart the first time any of them is corrected.
A correction reaches all of them
Because there is one row to correct. Supersede it once and every Space it was filed into shows the new version and keeps the old one readable.
Removing it from a Space does not remove it from you
The membership is ended, not deleted, so a re-add is visibly a re-add. The memory itself is untouched. Deleting a whole Space is the one place you have to say which you meant —
memories: keepordelete, with no default, because guessing loses somebody’s material either way. Even then a memory is only destroyed if this was the last Space holding it.
Universal is the floor, not a Space
no membership row
space_memberships
Every memory on your account is in Universal by definition, so there is deliberately no row saying so — it would be one row per memory carrying no information. Asking “what does Universal hold” is asking what your account holds. Asking for Universal plus two Spaces is that same question, unioned with the membership rows for those two.
This is why a Space cannot lose you anything. The Space is a view; the memory lives underneath it.
Where something gets filed when you did not say
Read top to bottom; the first one that has an answer wins.
A caller that names Spaces spaceIds on remember, on an upload, on a connection, or on a conversation. An explicit choice is never second-guessed. | every surface |
A folder that was set up pm setup writes a Space id into .persistmemory.json beside your code, and every command in that folder uses it. --space overrides one command; PERSISTMEMORY_SPACE overrides a shell. | CLI |
A Space an assistant was told to work in use_space fixes where captures are filed AND where questions are answered from, until it is changed. current_space reports which — so a narrow answer is not read as a complete one. | MCP |
The conversation it came from A conversation carries its own space_ids, and extraction inherits them. Turns appended to a conversation filed in Acme become memories filed in Acme. | chat, Telegram, Meet |
The account default When nothing above said anything. One policy in one file, so the CLI and a chat cannot disagree about where an unattributed note landed. | fallback |
The audience rule
A Space has an audience — how wide its own readership is. A memory has a scope, carried from the source it came from and never widened. Filing compares the two, and refuses when the Space is wider than the memory. A Space no wider than the memory adds no readers, so it is allowed; a Space wider than it would add some, so it is not.
Said plainly: organising your notes cannot quietly become a disclosure. You can put a public fact into your most private Space. You cannot put a private one into a Space other people read.
| scope | who that means |
|---|---|
| private | One person. What a provider did not label also lands here — silence is read as the most restrictive answer, not the most convenient one. |
| shared | The people a source named. A DM, a file shared with three colleagues. |
| space | A Space's own readership. Defined in the ladder and not yet produced by anything: no connector labels material this way today, so you will not see it on a memory. |
| workspace | An organisation. Drive files marked organisation-wide arrive at this width. |
| universal | Public. A published page, a post anyone can read. |
Narrowest at the top. A memory drawn from several sources keeps the narrowest of them, because something derived from a private source is as protected as that source.
409 with the sentence “That memory is more private than <Space>, so filing it there would show it to people it was not meant for. Nothing was changed.” The whole request is refused rather than partly applied — filing four of five and reporting success would leave you believing all five landed.A Space you make by hand has no declared audience, and an undeclared audience is read as private — so nothing is refused until a Space is genuinely read by more people than its owner.
Sharing a Space
A Space can be given to another person to read. The record is a grant — space_collaborators — and it is the first thing in this system that lets one person see another person’s memories, so what it does not do matters as much as what it does.
It grants sight, never ownership
A collaborator reads the owner’s memories through the Space. Nothing is copied into their account. That single decision is what makes revocation one update rather than a cascade nobody can reason about: end the grant and the memories are gone from their next query, with no orphaned copy and no citation pointing at something they can no longer open.
Read only, and no roles
Every grant means exactly one thing: this person may read this Space. There is no writer and no admin, because nothing would honour one. Every write still matches on the owner’s id, so a shared Space never hands out edit rights on somebody else’s memories.
Being invited grants nothing
Access begins when the recipient accepts, and retrieval checks for the acceptance rather than the invitation. The case this was built for is matching meeting attendees by address — and an attendee list is chosen by whoever booked the meeting. If an invitation granted access, a stranger could put their own material inside your assistant by scheduling a call with you.
Their contradictions stay theirs
Conflicts are scoped to one person’s record and settled by supersession. Because nothing is copied, a memory you can see through a shared Space never enters your conflict set — it cannot supersede something you believe. Your assistant can still cite it: “the meeting note says X, though you recorded Y”, which is the honest answer anyway.
Retention
A Space may carry retentionDays: an age after which a memory filed here is worth reviewing, whatever its freshness. It exists because freshness decays from the last time something was mentioned, so a claim repeated every week stays maximally fresh however much the world has moved on.
Doing it
Over HTTP
GET /api/v1/spaces your Spaces, paged
POST /api/v1/spaces { name, description?, kind?, parentId?, retentionDays? }
GET /api/v1/spaces/default the account fallback
PATCH /api/v1/spaces/default set or clear it
POST /api/v1/spaces/merge { sourceIds[2..20], name } — sources are left alone
GET /api/v1/spaces/{id}
PATCH /api/v1/spaces/{id} { name?, description?, retentionDays?, archived? }
DELETE /api/v1/spaces/{id} { memories: "keep" | "delete" } — required
GET /api/v1/spaces/{id}/memories what is filed here
POST /api/v1/spaces/{id}/memories { memoryIds } — refused if it would widen the audience
DELETE /api/v1/spaces/{id}/memories { memoryIds } — unfiles, never deletesMembership is a sub-resource with its own POST and DELETE rather than a field on the Space, so adding one memory does not mean reading every id and sending them all back — which is how two clients silently lose each other’s changes.
From a terminal
pm spaces list
pm spaces create "Acme"
pm spaces delete "Acme" --memories keep # or --memories delete
pm spaces merge "Work" "Personal" --name "All"
pm setup --space "Acme" # this folder, from now on
pm search "why postgres" --space Acme,Work # one command, by name or idFrom an assistant
list_spaces turns a name into an id, use_space fixes the one this connection works in, and current_space says which. The choice steers filing and retrieval together — a default that filed one way and searched another would put material somewhere the next question could not see. See the MCP server.
From a chat
/spaces lists them and /space <name> sends everything from that chat to one, until /space none.
A Space is not a permission boundary between you and us, and it is not encryption. It decides which memories answer which question, who else can read them, and how long they stay interesting. Everything in every Space belongs to one account until a grant says otherwise.