Create a Space
A Space is created by the client. The server stores a public record, but the secret that opens the Space is minted and distributed by the browser.
This page describes the production shape used by the Spaces app.
The create-time contract
Creating a Space registers:
| Field | Visibility | Purpose |
|---|---|---|
room |
public | The stable Space id, usually sp-.... |
commit |
public | A commitment to the room key mk; proves future requests carry the right key. |
configSealed |
ciphertext | Agent persona, tools, admission policy, allow-list, model config, owner policy. |
gated |
public marker | Present only when the room is read-gated, so keyless reads can fail closed. |
gk |
public | Optional room invite public key for per-member signed attribution. |
invite |
ciphertext or pad | Used by multi-share invite flows; solo rooms carry a random pad to avoid leaking key model. |
The server does not receive mk on create.
Key models
The current client supports:
| Model | Meaning |
|---|---|
1-of-1 |
One link carries the full room key in the fragment. |
2-of-2 |
Two shares are required; used for paired/couple-style rooms. |
{ k, n } |
Shamir threshold sharing for teams. |
The link shape keeps the room id visible and the key material in the fragment:
https://your-app.example/space?room=sp-example#mk=...
https://your-app.example/space?room=sp-example#s=...
Fragments are not sent to the server by the browser. The app reads them locally and reconstructs mk.
Basic create flow
The reference client wraps the details:
const client = makeSpaceClient({ endpoint: '/space' })
const { room, mk, commit, links } = await client.create({
keyModel: '1-of-1',
agentName: 'Guide',
persona: 'Help the group make decisions clearly.',
tools: ['show_chart', 'write_pdf']
})
Under the hood, this calls:
{
"op": "create",
"room": "sp-example",
"commit": "<commit(mk)>",
"configSealed": "<sealed config envelope>",
"invite": "<pad or sealed invite>"
}
Gated Spaces
For an email-gated Space, the admission rule and allow-list live inside the sealed config. The public record also gets
a gated:true marker. That marker leaks only that the room is gated, not who is allowed.
await client.create({
admission: 'email',
allow: ['ada@example.com', 'grace@example.com'],
requireChallenge: true
})
Reads and writes are then refused unless the member presents an allow-listed identity. Holding the link and mk is
not enough.
Owner-governed Spaces
The owner rule adds a signed App Policy at create time. This lets an app owner mandate admission tiers for every room created under that app. It is built, opt-in, and inert unless configured.
Read: Owner-governed rooms
What can go wrong
- A duplicate
roomreturns409 exists. - Missing
commitreturns400 commit_required. - A governed room with an invalid owner policy is refused.
- A private/gated room without the right identity may create successfully but later refuse reads or writes.
Code map
| Concern | File |
|---|---|
| Client create wrapper | spaces/public/spaceClient.js |
| Key creation and link parsing | spaces/public/spaceConfig.js |
| Create handler | agent/spaceHandler.mjs |
| Sealed config | spaces/public/e2ee.js, agent/envelope.mjs |
Machine-readable source: create-space.md · every doc in one fetch: llms-full.txt (HTML) · ← quickstart · agent-turns →