Witbitz docs HomeTrustAll docs

Rooms and messages — what a Space holds

A Space is a room: its members, their messages and the assistant's, a name, and the files people share. This page is how your page makes or opens a room, reads what is in it and writes to it.

Part of the SDK · every call: the reference.


Create and open a room

javascript
const space = await wb.createSpace({ agent: { name: 'Helper', instructions: 'You help a small group plan together.' } })
const same = await wb.openSpace(link)
await space.ready
const saved = space.snapshot()
space.close()

Temporary rooms

javascript
const desk = await wb.createSpace({ agent, tier: 'enclave', temporary: { days: 7 } })   // kept 7 days after its last question
const quick = await wb.createSpace({ agent, temporary: true })                          // wiped about 30 minutes after it goes quiet

Messages: who sent what

space.on('message', cb) hears every entry in the room — your messages, other members', the assistant's replies, store writes and system lines — and space.messages is the list so far. On a room you open, the history is replayed to cb first; pass { replay: false } to hear only what arrives after it. Every on() returns a function that removes the listener.

A message is the room's entry as stored, decrypted on the device, with from taken from name when it has none. Its fields depend on its kind (Message types them all):

kind What it is Notable fields
'text' A member's message, an assistant's reply, a private lane's message into the room, or a system line from, text; replies: self, sources?, usage?, cert? (confidential rooms); from a lane: via: 'bridge', draft.by; system lines: sys
'media' A member's message with images or files the bytes: space.attachment(att)
'data' A write to the store (also fires 'data') ops; self when the assistant wrote it
'mcp' A call from your app to the assistant over the embed bridge mcpName, mcpArgs
'fork' A move an older client made (also fires 'moved') handled for you

Any message may also carry verified, user and spk (the sender passed the room's gate), replyTo, md, and voice (it was dictated). One copied in by an older client's move carries carried — its marks from the room it was written in — and carriedBy.

self: true means the room's assistant produced it. The server sets it on the assistant's replies and data writes and on nothing else; it never means "I sent this".

javascript
space.on('message', (m) => {
  if (m.sys) return renderSystemLine(m)                    // a settings change, a connection call
  if (m.self && m.kind === 'text') return renderReply(m)   // the assistant
  render(m, { mine: m.from === MY_NAME })                  // MY_NAME: the `from` you send with
})

In a gated room, from is only a name the sender typed. What the server verified is m.user, the sender's @username when they have one, and m.spk, the signing key of the device that sent it — each device has its own. So "is this mine" is m.user against your own @username, or, for a member with none, m.spk against this device's key, which reads your other devices' messages as someone else's. The Spaces app checks both:

javascript
const me = await wb.usernames.mine().catch(() => null)   // throws when signed out
const isMine = (m) => !!(me && me.username && m.user === me.username) ||
  !!(m.spk && session.sign && m.spk.x === session.sign.pub.x && m.spk.y === session.sign.pub.y)

Send

javascript
const r = await space.send('Plan us a weekend in Kyoto', { from: 'Dana' })
if (!r.ok) showError(r.error)

Replies and reactions

javascript
await space.send('Saturday works for me', { replyTo: m })
await space.react(m, '👍')
await space.react(m, '👍', { off: true })
space.on('update', ({ message, fields }) => rerender(message))

A reply is shown with the message it answers quoted beside it, as in the Spaces app; it carries replyTo: { key, name?, excerpt? }. A reaction is written on the message itself — no new entry, no assistant turn — and reaches every device as an 'update', the event for any message changed in place (a reaction, an image's caption, a reply's cards). A reacted message carries reactions, emoji → the names that reacted; in a verified room reactKeys maps each name to its key.

Attachments

The images and files on a message are stored apart from it, sealed under the room key. await space.attachment(att) fetches one — message.images[i] or message.files[i] — and opens it on the device: { b64, mime, name? }.

Safe media

In a room with Safe media on, a photo or a PDF someone else posted is never opened on a member's device. What is shown is a safe copy: JPEG images made once, when the file is posted, by an attested Witbitz enclave (the tools enclave). It opens the file in a one-shot process with no network and no keys, and keeps nothing. A PDF's copy is its first page. The copy is stored beside its original, and each device checks the enclave's hardware attestation over the copy's exact bytes before it draws anything. A copy that does not check out is never shown. The same setting is Safe media in the Spaces app (⋯ → This Space).

javascript
const space = await wb.createSpace({ agent: { name: 'Desk' }, safeMedia: true })   // or, later:
await space.safeMedia.set(true)                    // = space.configure({ safeMedia: true }); a line says who turned it on

space.on('message', async (m) => {
  if (!m.images?.length || m.self) return
  const copy = await space.safeMedia.copy(m)       // { ok, versions: [{ w, h, b64 }] }, or { ok: false, why }
  if (copy.ok) draw(copy.versions.at(-1).b64)      // the largest version, image/jpeg
  else showCard(m)                                 // no verified copy (yet): show a card with the name instead, never the original
})
space.on('safeCopy', ({ for: f }) => redraw(f.id)) // a copy arrived after its message

What the enclave sees: the file, sealed by the posting device to the enclave's attested key. The Space server carries it but cannot read it. The enclave answers with the copies and a signed statement that names the original's hash and each copy's hash; the hardware attests that statement. In a vault (vault mode), the setting and copy() work, make() is refused (vault_unsupported), and send() makes no copies.

The room's name

space.title is the room's name, the same on every member's device and in the Spaces app; null until it has one. space.rename(name) changes it: every member sees the new name and is notified as for a message, and the Spaces app shows "Alon renamed this Space to “Kyoto”".

javascript
space.on('title', ({ title, manual, by }) => { document.title = title })
await space.rename('Kyoto in September', { by: 'Alon' })   // → { ok: true, title: 'Kyoto in September' }

While a room holds one person and the assistant, its name follows the conversation: one is made after the first exchange and revised as the subject changes, with no line and no notification. Once anyone renames the room, or a second person joins, no name is made again.

Live updates

In a browser, each open room holds one socket on the Witbitz relay (wss://code-relay.witbitz.chat), and what happens in the room arrives down it at once: the assistant's reply grows on partial as it is written, and a new message is fetched the moment it lands. It works in every room — open, gated and confidential.

Machine-readable source: sdk-rooms.md · Generated 2026-10-27T07:05:42Z · build f301388c · every doc in one fetch: llms-full.txt (HTML)