Rooms
A room is a memory space shared across Mnemoverse accounts. Instead of every agent keeping its own private pool, members of a room write to and read from one shared pool — using the same /memory/write and /memory/read calls you already make, with xroom:<room_id> in the existing domain field. There is no new endpoint and no new request field to learn.
Agent A (vendor 1, key mk_live_A...) ──┐
├── domain: "xroom:room_01J8ZQ5XK7E9GVA2M4N6P8R0TC" ──► one shared memory
Agent B (vendor 2, key mk_live_B...) ──┘A room is its own memory bucket, separate from every member's personal memory. An atom written to a room lives in the room — not in the writer's account. Nothing you keep in your own memory is shared by joining a room; only what you explicitly write with the room's domain goes into it. Reading a room searches the room's atoms, not your own.
Rooms implements the semantics of the memcommons v0.1 specification (experimental) — a vendor-neutral specification for shared agent-memory spaces whose invariants were derived from the running behavior documented on this page: membership checked on every request, the two-scope invite lifecycle, forward-only revocation, and provenance-on-read. (Rooms serves the xroom: address token, which predates the spec; the spec's neutral scheme is mspace:.)
Beta — the data path is live; create / invite / join are self-service
Works today with any mk_live_ API key: writing, reading, batch-writing, feedback, and consolidation against a room you are a member of — on POST /memory/write, /memory/write_batch, /memory/read, /memory/feedback, and /memory/consolidate.
Self-service now: creating a room, minting an invite code, joining with one, and re-finding your rooms — as four Beta tools (memory_create_room, memory_invite_to_room, memory_join_room, memory_list_rooms) in the local MCP server and the remote connector. The local tool mints a single-use code; the remote tool and REST API default to one redemption but can request a higher max_uses. The console separately supports room and member management, email invitations, and browser redemption of an invite link; it does not mint share codes. See Join a shared room for the full owner-and-joiner flow.
Still via the console or on request: changing a member's scope, revoking access, archiving a room, and permanently deleting a room's data. A fuller console UI is planned.
Three Jobs a Room Does
1. Shared Memory for a Dev Team
Your team runs coding agents — each engineer with their own API key, so each agent's memory is private by default. What one agent learned debugging staging on Tuesday, another agent re-derives from scratch on Thursday.
With a room, every member writes findings to the same address:
curl -X POST https://core.mnemoverse.com/api/v1/memory/write \
-H "X-Api-Key: mk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Staging deploys fail when REDIS_URL is unset — the worker falls back to localhost and times out. Fix: set REDIS_URL in the staging env group.",
"concepts": ["staging", "redis", "deploy"],
"domain": "xroom:room_01J8ZQ5XK7E9GVA2M4N6P8R0TC"
}'Response:
{
"stored": true,
"atom_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"importance": 0.82,
"reason": "novel insight — high knowledge delta"
}The body is otherwise the normal write schema — content (required, 1–10,000 characters) plus optional concepts (up to 256 items), metadata (serialized UTF-8 JSON up to 65,536 bytes), fingerprint (max 512 characters), and external_ref (an author object is also accepted, but it is honored only for service integrations and ignored on API-key calls); unknown fields and values above these stability caps are rejected with 400 (VALIDATION_ERROR).
Two days later a teammate's agent — different API key, different account — queries the same room:
curl -X POST https://core.mnemoverse.com/api/v1/memory/read \
-H "X-Api-Key: mk_live_TEAMMATE_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "why do staging deploys time out?",
"domain": "xroom:room_01J8ZQ5XK7E9GVA2M4N6P8R0TC",
"top_k": 5
}'Response:
{
"items": [
{
"atom_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"content": "Staging deploys fail when REDIS_URL is unset — the worker falls back to localhost and times out. Fix: set REDIS_URL in the staging env group.",
"relevance": 0.91,
"similarity": 0.86,
"valence": 0.0,
"importance": 0.82,
"source": "semantic",
"concepts": ["staging", "redis", "deploy"],
"metadata": {}
}
],
"episodic_hit": false,
"query_concepts": ["staging", "deploy", "timeout"],
"expanded_concepts": ["staging", "deploy", "timeout", "redis"],
"search_time_ms": 14.2
}The atom one member wrote comes back on another member's key. Retrieval works exactly as it does in your own account — relevance ranking, Hebbian concept expansion (expanded_concepts pulled in redis from learned associations) — just over the shared pool. Each returned item also carries a domain field (and provenance when the atom is authored), and /memory/read accepts authorship filters (author_principal, author_agent, author_client_env, author_is_external) — useful for telling apart who wrote what in a shared space. See the API Reference for the full schemas.
2. One Person, Two Assistants: Claude and ChatGPT Sharing Context
You use Claude for some work and ChatGPT for other work, and each one only knows what you told it. If both connect to Mnemoverse through the MCP server or the remote connector, a room gives them one shared address for context that should follow you across assistants.
Both data-path tools in the MCP server — memory_write and memory_read — take a domain parameter and forward it to the API unchanged. So the integration is nothing more than passing the room address as the domain. When Claude saves something:
{
"content": "Flight to Lisbon booked for Sept 12, returning Sept 19. Hotel not booked yet.",
"domain": "xroom:room_01J8ZQ5XK7E9GVA2M4N6P8R0TC"
}And when ChatGPT later calls memory_read:
{
"query": "when is the Lisbon trip?",
"domain": "xroom:room_01J8ZQ5XK7E9GVA2M4N6P8R0TC"
}The domain string flows through the MCP server untouched; membership is checked server-side on every request. In practice you put the room address in each assistant's project instructions or system prompt ("store and look up shared context in domain xroom:room_...") — the assistants will generally pass it on their memory calls, but prompt-following is best-effort, so spot-check early sessions to confirm the domain is actually being used.
One honest caveat: if both assistants authenticate with the same API key, they already share memory — a plain domain is enough. A room earns its keep when the two connections authenticate as different accounts (separate keys, a teammate's setup) and you still want one shared pool.
3. Agent Handoff
A research agent digs into a problem overnight; a coding agent picks up the findings in the morning. Different agents, different keys — the room is the handoff surface.
The research agent drops its findings in one batch. POST /memory/write_batch resolves the domain per item, so a single batch can mix room writes with private ones:
curl -X POST https://core.mnemoverse.com/api/v1/memory/write_batch \
-H "X-Api-Key: mk_live_RESEARCH_AGENT_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"content": "The N+1 queries come from the serializer re-fetching org settings per row. Batch-load via select_related fixes it.",
"concepts": ["n-plus-one", "serializer", "performance"],
"domain": "xroom:room_01J8ZQ5XK7E9GVA2M4N6P8R0TC"
},
{
"content": "Benchmark: endpoint P95 drops from 1.8s to 240ms with the batch-load patch on the staging dataset.",
"concepts": ["benchmark", "latency", "performance"],
"domain": "xroom:room_01J8ZQ5XK7E9GVA2M4N6P8R0TC"
},
{
"content": "Note to self: profiling harness lives in scripts/profile_api.py.",
"domain": "project:perf-sprint"
}
]
}'Response:
{
"total_count": 3,
"stored_count": 3,
"results": [
{ "index": 0, "stored": true, "atom_id": "1f0e3dad-9908-4a55-8d6f-1c1b2f0a9e11", "importance": 0.79, "reason": "", "error": null },
{ "index": 1, "stored": true, "atom_id": "2a8b6c1e-4d3f-4b7a-9e2c-5f6a7b8c9d0e", "importance": 0.74, "reason": "", "error": null },
{ "index": 2, "stored": true, "atom_id": "3c9d7e2f-5a4b-4c8d-af3e-6a7b8c9d0e1f", "importance": 0.61, "reason": "", "error": null }
]
}Each item carries a reason string from the write gate. Stored items currently use an empty string; a refused item carries the gate explanation. The empty default also keeps older cached responses on the hyphenated endpoint readable.
If an item addresses a room the caller cannot use, the request fails with that item's real status — a 403 or 404, not a generic 500.
The coding agent reads the room (same POST /memory/read call as in job 1, its own key), applies the fix, and closes the loop with feedback — telling the memory which findings actually helped:
curl -X POST https://core.mnemoverse.com/api/v1/memory/feedback \
-H "X-Api-Key: mk_live_CODING_AGENT_KEY" \
-H "Content-Type: application/json" \
-d '{
"atom_ids": ["1f0e3dad-9908-4a55-8d6f-1c1b2f0a9e11", "2a8b6c1e-4d3f-4b7a-9e2c-5f6a7b8c9d0e"],
"outcome": 1.0,
"query_concepts": ["n-plus-one", "performance"],
"domain": "xroom:room_01J8ZQ5XK7E9GVA2M4N6P8R0TC"
}'Response:
{
"updated_count": 2,
"avg_valence": 0.5,
"coactivation_edges": 4,
"feedback_time_ms": 8.3
}Feedback mutates ranking weights, so it requires read_write membership. That is also how you build a one-directional handoff: give a consumer agent read scope and it can retrieve everything but change nothing. A write attempt on a read-scope key:
curl -X POST https://core.mnemoverse.com/api/v1/memory/write \
-H "X-Api-Key: mk_live_READONLY_CONSUMER_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Consumers should not be able to add this.",
"domain": "xroom:room_01J8ZQ5XK7E9GVA2M4N6P8R0TC"
}'returns 403 in the standard error envelope:
{
"code": "FORBIDDEN",
"message": "Read-only membership cannot write to this room",
"requestId": "...",
"retryable": false,
"details": null
}The room stays a one-way publication channel: the writing side curates what consumers see, and the reading side cannot pollute it.
The Address: xroom:<room_id>
- A room address is the literal prefix
xroom:followed by the room id. Room ids have the shaperoom_<ulid>— e.g.room_01J8ZQ5XK7E9GVA2M4N6P8R0TC— and you receive yours when the room is set up. - The prefix must be lowercase
xroom:with no leading whitespace. A near-miss of the canonical form — uppercaseXROOM:, a stray leading space — is rejected with 400 rather than silently treated as a private domain. Anything that doesn't look likexroom:at all (say, a mistypedxrom:room_...) is treated as an ordinary private domain in your own account — so double-check the address you paste. - The room id may only contain letters, digits, underscores, and hyphens.
- The address is not a secret capability. Knowing a room id grants no access to its contents: access is enforced by the API's membership check on every request, and non-members get 403. The one thing a bare id does reveal is existence — since 404 means "no such room" and 403 means "exists, but you are not a member" (see Errors), any authenticated caller can confirm whether a room exists.
room: is not xroom:
The legacy room:Y namespace label (no x) is an ordinary domain string inside your own account — nothing written there is shared with anyone. Only the xroom: prefix addresses a shared room.
Which Endpoints Accept Room Domains
| Endpoint | Rooms | Membership scope required |
|---|---|---|
POST /memory/write | Yes | read_write |
POST /memory/write_batch | Yes — resolved per item; a batch may mix room and private domains | read_write |
POST /memory/read | Yes | read or read_write |
POST /memory/feedback | Yes | read_write |
POST /memory/consolidate | Yes | read_write |
GET /memory/stats | No — takes no domain; always reports your own account | — |
POST /memory/query, POST /memory/read-batch, POST /memory/write-batch | No — xroom: domains return 400 | — |
DELETE /memory/domain/{domain} | No — xroom: domains return 400 | — |
Mind the underscore: the room-capable batch write is /memory/write_batch. The hyphenated /memory/write-batch is a different endpoint and rejects room domains.
Membership, Scopes, and Lifecycle
There are exactly two membership scopes:
| Scope | Can do | Cannot do |
|---|---|---|
read | POST /memory/read against the room | Any write-path call (write, write_batch, feedback, consolidate) — 403 |
read_write (invite default) | Everything in the table above | — |
How membership behaves over its lifetime:
- Grants can expire. A membership may carry an expiry time; without one, it is open-ended but revocable. When a grant expires, requests simply start returning 403
Not an active member of this room. - Revocation is immediate and forward-only. A revoked member loses access from that moment on. There is no retroactive un-sharing: whatever the member already read, it keeps, and atoms it wrote remain in the room. Re-inviting a revoked member restores access.
- Rooms can be archived. Archiving is a soft closure: members' data-path calls return 403
Room is archived(a non-member still gets the membership 403 — see Errors). Permanent deletion of a room's data is handled on request in beta — there is no user-facing wipe. - Enforcement is per-request. Membership and scope are checked by the API's membership check on every call — an expired or revoked grant stops working on the very next request.
In beta, inviting is self-service — mint a code with memory_invite_to_room, or invite a member by email from the console. The local MCP tool mints a single-use code; the remote MCP tool and REST API default to one redemption and accept a higher max_uses. Changing an existing grant's scope or expiry, revoking, and archiving are done from the console or on request (contact us).
Joining in the browser
An invite's join link points at https://console.mnemoverse.com/join/<code>. A human recipient:
- Opens the link and signs in to their Mnemoverse account — Google, GitHub, or email. Google or GitHub sign-in may provision an account. An email user without an account must sign up, then reopen the original invite link before accepting it.
- Sees a Beta join confirmation, the invite code, and an explicit Accept invitation button — nothing is joined until they click it, and the room name is not shown yet.
- Clicks Accept invitation, joining as whichever account they signed in with, at the scope (
readorread_write) the owner set when minting the code.
After acceptance, the page shows the room name. From there their assistant accesses the room exactly like an agent that joined via memory_join_room — by passing domain: "xroom:<room_id>" to the memory tools (MCP Server). A read member can use memory_read only; memory_write, memory_feedback, and consolidation require read_write. The browser flow handles sign-in, explicit acceptance, and creation of the membership; the assistant's memory data path is unchanged.
Billing
Usage is always billed to the authenticated caller — the account whose API key makes the request — never to the room owner. A member's write into a shared room consumes the member's quota; so does a member's read. Two consequences worth knowing:
- Your daily quota is enforced against your own account, whichever rooms you touch (Pricing).
- A room owner cannot be cost-attacked through invitees' traffic: inviting someone gives them access, not your bill.
Only successful requests (HTTP status below 400) count toward usage.
Errors
Room errors use the standard error body: {code, message, requestId, retryable, details}.
| HTTP | code | When | message |
|---|---|---|---|
| 400 | VALIDATION_ERROR | Domain looks like a room address but isn't canonical (uppercase prefix, leading space) | Non-canonical room address; use 'xroom:<room_id>' (no leading space, lowercase prefix) |
| 400 | VALIDATION_ERROR | Room id contains characters outside letters, digits, _, - | Malformed room id: ... |
| 403 | FORBIDDEN | Room id is empty or reserved | Invalid room address |
| 401 | UNAUTHORIZED | Request to a room address is not authenticated | Caller org not identified |
| 404 | NOT_FOUND | Room does not exist | Room not found |
| 403 | FORBIDDEN | You are not an active member — never invited, grant expired, or revoked | Not an active member of this room |
| 403 | FORBIDDEN | Room is archived (members only; see note below) | Room is archived |
| 403 | FORBIDDEN | read-scope member calls write, feedback, or consolidate | Read-only membership cannot write to this room |
| 400 | VALIDATION_ERROR | Room domain sent to POST /memory/query, POST /memory/read-batch, or POST /memory/write-batch | xroom: rooms are only supported on the v2 memory API (/memory/write, /memory/read). |
| 400 | VALIDATION_ERROR | Room domain sent to DELETE /memory/domain/{domain} | Room domains (xroom:) are not supported on this delete endpoint. |
| 503 | INTERNAL (retryable) | Rooms storage is temporarily unavailable — retry | Service unavailable: rooms require the DB backend |
Two deliberate behaviors to be aware of when handling these:
- 404 vs 403 is meaningful: 404 means the room does not exist; 403 means it exists but you are not an active member.
- Membership is checked before archive state, so a non-member of an archived room still gets the membership 403 — outsiders cannot probe whether a room has been archived.
Beta Limitations
Explicitly, what rooms do not do in beta:
- Partial self-service room management. Creating a room, minting an invite code, joining with one, and listing your rooms are self-service through four Beta MCP tools (
memory_create_room/memory_invite_to_room/memory_join_room/memory_list_rooms). The console supports room and member management, email invitations, and browser redemption, but does not mint share codes. Changing existing grants, revoking members, and archiving are done from the console or on request via contact; a fuller console UI is planned. - No room listing or room stats via API key.
GET /memory/statsreports your own account only, and there is no API-key way to enumerate the rooms you belong to. Keep your room addresses in your own configuration. - No user-facing room deletion.
DELETE /memory/domain/{domain}rejects room domains, andDELETE /memory/atoms/{atom_id}only reaches atoms in your own account. The MCPmemory_delete_domaintool hits the same guard, so it cannot wipe a room either. Permanent deletion of room data is handled on request. - Rooms only work on the endpoints listed above.
POST /memory/query,POST /memory/read-batch, and the hyphenatedPOST /memory/write-batchrejectxroom:domains with 400. - No retroactive un-sharing. Revoking a member stops future access immediately, but does not claw back what they already read — and atoms they wrote stay in the room. Treat a room's contents as shared with everyone who has ever been an active member.
Related
- Getting Started — get a free API key and make your first write/read in two minutes.
- API Reference — full request and response schemas for the memory endpoints.
- MCP Server — the
@mnemoverse/mcp-memory-serverpackage whosememory_write/memory_readtools carry thedomainparameter. - Agent Setup — per-client walkthroughs for connecting Claude, ChatGPT, and editors.
- Multi-Tenant Platform — how accounts, domains, and isolation work underneath rooms.
- Security — how keys, tokens, and your data are handled.
- memcommons v0.1 specification — the vendor-neutral shared-memory-spaces specification (experimental) whose semantics Rooms implements.