Witbitz docs HomeTrustAll docs

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

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