SDK changes — what moved between versions
Every change that needs your attention, newest first. Which version to load: Versions.
Part of the SDK · every call: the reference.
Changes that need your attention
Production apps import a fixed version (/sdk/<version>/): a released version never changes, so an upgrade happens only
when you change the number in the address. A page that imports /sdk/latest/ or the live build (/witbitz-sdk.js)
gets each change on its next load — use those for development and demos only (Versions). Every
release, and every change that can break existing code, is listed here, newest first.
2026-10-27
- SDK 3.10.0 — Safe media. Nothing breaks.
createSpace({ safeMedia: true }),configure({ safeMedia })andspace.safeMedia.set(on): others' photos and PDFs are shown only from copies an attested enclave made safe, as in the Spaces app (Safe media).space.safeMedia.copy(message, { at, i })returns a copy only once it verifies on the device;.isOriginal(copy, b64)checks the original before you open it;.make(message)makes a copy for everyone.send()makes the copies of what it sends while the setting is on (a signed-in session).- Events
'safeMedia'and'safeCopy'. A copy is never a message: before 3.10.0 a Safe media room's copies were listed inmessagesas entries withkind: 'safe'— filter them out if you stay on an older version.
2026-10-26
- SDK 3.9.0 — temporary rooms, page tools on a plain room, a site's visitor limit; and the sync sweep's fixes. Nothing breaks.
createSpace({ temporary: true | { days } }): a room the deployment wipes when idle —{ days }(1–7) keeps it that many days after its last question;space.temporary. Refused rather than faked:temporary_unavailable,invalid_temporary,vault_unsupported(Temporary rooms).space.send(text, { tools }): your page's own functions for one question on a plain room, in a lane's action shape (Page tools).admin.billing.visitorLimit({ perNetwork, total } | null): a cap on questions from people who are not signed in; a visitor over it gets{ ok: false, status: 429, error: 'visitor_limit', scope }fromsend()(Visitor limits).- A room named after this member opened it now gets its name (the name doc is read again on the settings' cadence).
- Outside agents: a resumed lane's waiting drafts reach your
'a2a'listener even when it is attached afteropenLaneresolves (they wait for one); an empty assistant line is never taken for a draft. - In a vault:
wb.vault.infofollows the vault's sign-in and account (it was the welcome, frozen); "only who manages it changes" is decided with hidden members counted (it said "anyone else can no longer read" for a role change); a lane of an attested Space is refused withattested, as outside a vault.
2026-10-26
- SDK 3.8.1 — the vault kit's headers add
Cross-Origin-Opener-Policy: same-origin. ⚑ If you host a vault, add it to every response of your vault's origin: a page that opens your vault in a window then gets no handle on it (the protection Witbitz's own vault has). Re-run the kit (vault-kit.mjs, 3.8.1) andHEADERS.mdlists it for nginx, Caddy and Apache; your screens andvault.jsare kept. Nothing else changes for your app.
2026-10-25
- SDK 3.8.0 — outside agents' waiting drafts survive a reload, and the sync sweep's fixes; one change in a vault.
lane.snapshot()carries the outside-agent tasks still waiting for a draft or for the member (a2a); resume the lane with it and each waiting draft comes back as an'a2a'event (Outside agents). Before, a reload lost them, and the agent's task stayed "working".- Outside agents: a job agent's report (
job: true) is never drafted as a reply; a shared-room line mirrored into the lane is never taken for its assistant's draft; the agent's name is no longer kept as the member's name. - ⚑ In a vault,
wb.backup.remove(room)returns a promise (it resolved nothing you could see; a refusal was swallowed) —awaitit.wb.vault.openRoom(room)refuses a room the vault does not hold (not_in_vault), andJSON.stringify(wb)shows the vault's sign-in. - In a vault:
changeMembers/rotateKeywake a sleeping confidential Space first (it failed withconfig_unavailable); webhook and job connections keepwebhook/job; a role-only change is said as one and emits norekeyed;invite.enroll()resolves'vault';startMcp's sign-in comes back to your page's/mcp-callback. - Cloud computers: a Deny with a
messagefalls back to the plain refusal on a connector from before messages (given thesession); the published p2y image is accepted.
2026-10-24
- SDK 3.7.0 — the person's cloud computers; nothing breaks.
wb.computers:list,create,open,removeandusage, and an openComputerwith its sessions, approvals, questions, files and live events — the Witbitz app's Code section, from your page (Cloud computers). Kept sealed in your app's account; opening checks the published Witbitz image and follows a new one (images); its hours are charged to the person's AWS key — your app's own key under their account, with its own monthly cap. Not in vault mode yet (vault_mode). - SDK 3.6.1 — a confidential lane of a standard Space sees its Space; nothing breaks.
privateLane({ tier: 'enclave' })on a standard Space made a lane whose assistant never saw the room: each turn named the standard room as its bridge, which the confidential tier cannot read, and nothing copied the room in. It now works as a lane of the other tier opened withopenLane: your device mirrors the Space into it (withoutmirror: true), and its turns carry no bridge and not the Space's key (Across tiers). A lane on its Space's own tier is unchanged. - SDK 3.6.0 — mark an agent that takes jobs; nothing breaks.
lane.a2a.addPeer(name, { job: true })(and.peersshowsjob: true): that agent's messages are reports — its run has ended and it reads no reply — so the lane's assistant tells the member what came back and, if more is needed, proposes a new job withconnection_callinstead of answering the agent. Add your Claude Code routine's peer this way (Outside agents). Also: when a confidential Space's enclave cannot be reached for a moment (it is being replaced or relaunched),changeMembersandrotateKeywake it and read again instead of failing with "only an email-gated Space"; a read that still fails isconfig_unavailable(retry). - SDK 3.5.0 — outside agents over MCP, and agents that take jobs; nothing breaks.
lane.a2a.mcpUrl(also returned byenableandaddPeer): the lane's door for agents that take a custom MCP server — Claude Code, Grok, Cursor — with the same per-peer token as A2A.wb.connections.attach({ webhook: true, job }): an agent started by a web address (a Claude Code routine's trigger) as a connection; a Space granted it tells the agent, in every job, to report back withmessage_member. See Outside agents.
2026-10-23
- SDK 3.4.0 — MCP servers as connections; nothing breaks.
wb.connections.startMcp,attachMcp,finishMcpandmcpTools, andspace.connections.grant(conn, { tools, auto })for an MCP connection (MCP servers): the server's sign-in runs inside the Sentinel, and the grant brings its recommended tools (reads on their own, changes ask) or the ones you choose. In a vault they run through it. - SDK 3.3.0 — your tenant's origins, signed; nothing breaks if you do not.
- With a signing key set,
admin.origins.declare([...])also files the same list, signed, with the Sentinel;admin.signing.publishOrigins()files it directly andadmin.signing.status()shows the list held (Signing your tenant's requests). From then on only your own pages can name your tenant there — a connection through your OAuth app, a user's erase list, an MCP or GitHub App sign-in. Until you file a list, nothing changes. - SDK 3.2.0 — your tenant can sign its own requests; nothing breaks if you do not.
- Tenant signing keys (Signing your tenant's requests):
new WitbitzAdmin({ …, signingKey }),WitbitzAdmin.generateSigningKey(),admin.signing.register()andadmin.signing.status(). Once you register a key, the Sentinel refuses any request for your tenant that your server did not sign — Witbitz's own servers included. Until you register one, your requests are taken on your tenant key as before. - Your users' sign-in is checked inside the Sentinel, and each of your users' connections, Siri inbox and agent lanes now runs in its own process there, with its own key. No API change.
- SDK 3.1.0 — nothing to change in your code; move from 3.0.0 when you can.
- What the account used:
wb.account.usage({ month })returns the month's totals by payer and day, and every SDK turn now carries its receipt address (What the account used). In a vault, the vault holds the key. - Link Spaces change in place too: an open Space's members change without a new room, and each member you add gets
a link of their own. A page whose link no longer opens the Space meets
pendingwithreason: 'link_changed', notremoved. - Member changes: removing someone also removes their @username entries; one person added by email and by @username counts once; a role-only change keeps the key; a rotation skips removed members and only owners and admins may rotate; a third member makes a direct chat a group.
- In a vault: a page cannot change a Space's owners or admins without the person (
changeMembersasks them); the vault checks a member change is allowed before it asks;wb.invitesays the vault keeps the key; errors namewb.vault.signIn;wb.signOut()refuses withvault_signs_in;connections.detach(id, { spaces })works; a call that holds a screen for the person waits 15 minutes; an open lane of a verified Space is refused (lane_downgrade). - Outside agents: an answer to an outside agent is the lane's own reply, never sent to the shared Space.
2026-10-22
- SDK 3.0.0 — members change in place, and only in place; pin 3.0.0 if your users are in verified rooms. A verified
room's members (and its key) change without a new room (Changing members in place):
space.changeMembers(),space.rotateKey(),wb.invite, and therekeyed/removed/pendingevents. ⚑ Breaking:space.fork()is retired — it throwsuse_change_membersand makes nothing;plan()refuses ahistorywith the same code and answersin_placefor a new member list;capabilities().featuresno longer listsfork. A move an older client made is still followed (A Space no longer moves). ⚑ A page on 2.2.x cannot follow a re-keyed room: once a room is re-keyed (by your app on 3.0.0, or by a member in the Spaces app), every call a 2.2.x page makes with the old key is refused withstale_key. Move to/sdk/3.0.0/. Also: - A new Witbitz Sentinel build is followed everywhere.
wb.a2a,wb.erasureand the admin client (WitbitzAdmin,witbitz-admin.mjs) attested with the release's pins alone, so 2.2.4's outside agents, erase list and provider calls failed once the Sentinel moved to a build after its pins. They follow the published claim now, as the rest of the SDK does (sentinel: { follow: 'off' }or your ownpins: never). - No private lane for an attested Space — a Space whose owner policy requires an attested app, not a confidential
Space:
privateLane()throwsattested(no app signs a lane its owner policy yet), as the Spaces app waits. - Lanes linked across tiers:
lane.link(otherLane)takes a lane of either tier, as in the Spaces app. The rooms a lane reads (setLinks) stay on its own tier. - A Space made in a vault names its creator its owner, as outside a vault.
2026-10-18
- SDK 2.2.4 — the admin client as one file for your server.
witbitz-admin.mjsin each release from 2.2.4: download it and import it from your own code (Builder calls). The import our docs showed before —from 'https://witbitz-spaces.pages.dev/witbitz-admin.js'— does not load in Node. Also:admin.origins.declareanswersunproven, the origins whose host does not yet say they are yours (prove an origin); and this release knows the current Sentinel build (st20). - SDK 2.2.3 — move to it before the Sentinel your release pins is retired. A new standing-Sentinel build is now
followed (
sentinel.follow, default'published'— see Sign in); no earlier release can follow one, so their connections, Siri and outside agents stop when the build they pin is retired. Also (from the daily review): a private lane of a verified Space is gated to its member (it was an open room) —sign_in_requiredwith nobody signed in,lane_downgradefor anadmissionthat would open it; the signed-in creator on a verified Space's list is its owner, as in the app;fork()of a room with no owners but a creator key refusesnot_creator(its members follow only a move the creator signed);siri.collect()no longer acks a capture the Space did not take;connections.detach(id, { spaces })also drops it from those Spaces;approve()/deny()treat a 200 that decided nothing as a refusal; a linked lane's ask waits until the other lane's whole history is read; the room's name is re-asked every 20 of the room's entries (not this handle's sends);witbitz-admin.jserrors carry acode; a backup's email step passes the server's reason as the code (update_required). In a vault: a reclaimed worker is restarted (calls waiting on it failworker_restarted), per-user billing (useUserToken) reaches the vault's turns,lane.link()is recorded on the account, a lane's Siri drops go into the lane, a confidential move is refused before the question withvault_unsupportedonmoveRefused, errors namewb.vault.signIn,account.lastErroris anError, speech-key limits say how long to wait, andopenLanerefuses a tier other than the lane's. Rebuild your vault with the 2.2.3 kit to get the vault's half. The examples on this page now load 2.2.3.
2026-10-17
- SDK 2.2.2 — update if your page restores backups with
wb.backup. The email step of a protected restore now sends the key the server needs to check the address it actually mails (gk), and the server REQUIRES it: from today,wb.backupin SDK 2.2.1 or earlier fails at the email step (2.2.1 saysemail_refused; the server's reason isupdate_required). Load 2.2.2 (orlatest). In a vault, rebuild it with the 2.2.2 kit too: a vault built with an earlier kit keeps the old backup code, whatever SDK the page loads. It also carries the fixes made in the app since 2.2.1.
2026-10-15
- SDK 2.2.1 (fixes from the daily review; nothing to change in your code): in a vault, a private lane's
connectionsandsirigo through the vault as a Space's do;space.plan()judges a change as the vault's signed-in member;turn.abort()stops the turn on the server; a member's vault follows a move of a split-key Space, and both vaults keep it split; a vault whosescreens.jscame from 2.1.x shows the kit's own recovery-code, restore and Siri-key screens (startVaulttakes abrandfor them). Everywhere: a stored answer's late stream frames are no longer replayed as apartial. Types:VaultRoom.lane/tier,Connection.callKeyoptional (none in a vault),rotateKey()may resolvenull,wb.vault.on('account'). Rebuild a vault with the 2.2.1 kit to get its fixes. The examples on this page now load 2.2.1. - SDK 2.2.0 (vault mode): everything the SDK offers now works in a vault — live streams; connections, usernames,
the Siri inbox and outside agents through the vault's sign-in; private and linked lanes,
sealedProof();fork()and following a move; confidential, split-key and direct-chat Spaces; the backup, the account index and the erase list; dictation and reading aloud. What changes for a page already in a vault: those calls now work instead of refusingvault_unsupported;wb.siri.shortcut.keyisnull(wb.vault.showSiriKey()shows it in the vault);wb.backup.enable()resolves{ enabled: true }andrestore()takes the code in the vault's screen; a Space's settings read carries no connection key;openLanetakes the lane's room id. Rebuild your vault with the 2.2.0 kit (it replacescore/only). Ascreens.jskept from 2.1.x has no recovery-code, restore or Siri-key screen: the vault shows the kit's own for those (from 2.2.1; in 2.2.0 those calls refusedno_screen) until you add yours. Outside a vault: a confidentialfork()no longer fails when the enclave already holds the new Space's settings, and a Space with no admins follows only a move its creator key signed (the app's rule). The examples on this page now load 2.2.0. - SDK 2.1.2 (vault mode only): seventeen more members that half-worked in a vault now refuse by name — the
backup's and the account index's getters and methods,
space.connections/space.siri, andwb.dictation()/wb.speak()/wb.speechwithout a user token. A test now walks every public SDK member in a vault, so a new one cannot ship undecided. The examples on this page now load 2.1.2. Outside a vault nothing changed. - SDK 2.1.1 (vault mode only): in a vault, what is not available yet now refuses by name (
vault_unsupported,vault_signs_in) instead of half-working — 2.1.0's backup handed out a recovery code that recorded nothing, its connections / usernames / Siri said "sign in first" while signed in, andsealedProof()threw a TypeError. The examples on this page now load 2.1.1. Outside a vault nothing changed. - SDK 2.1.0 (Versions): the examples on this page now load
/sdk/2.1.0/witbitz-sdk.js. It carries the 2026-10-13 changes below (error codes on every error,plan()answering a refused gate instead of throwing). It adds vault mode —Witbitz.inVault({ vault }),wb.vault, and the kit tool (/sdk/2.1.0/vault-kit.mjs): your app on one origin, the room keys in a vault you host on another — plus what the 2.0.0 entry below calls "in the next release": direct chats andwb.speak().
2026-10-13
- SDK 2.0.0 (Versions). The examples on this page now load
/sdk/2.0.0/witbitz-sdk.js; 1.1.0 and 1.0.0 are unchanged. It is a new major version because it carries the breaking changes of 2026-10-08 to 2026-10-11 below — before you move from 1.1.0, check those entries marked Changed and Removed (the ones most likely to touch your code:configure()refusing fields it used to drop,admission.approve: 'admins'andadmission.removeUsersrefused,admission()withoutusernames/approve, norevokedreason,rename()/approve()/deny()rejecting when there was no answer, actions signed by the member's name instead of"You"). New over 1.1.0:space.run(), builder mode,space.title/rename(),react/replyTo,usageon a turn,isLaneCopy, signed webhooks inconnections.attach, registered MCP apps (admin.providers.attachMcp), and the confidential-room key healing. - New: direct chats —
createSpace({ direct: email })andspace.directPeer(), the Spaces app's one-to-one chat (live build; in the next release). - New:
wb.speak(text)— text read aloud with the Spaces app's Play aloud player (live build; in the next release). - Changed: every error the SDK throws now carries a
code(Errors); an argument a call cannot use isinvalid_argument, a link that is not a Space linkinvalid_link, and a poll error with no code of its ownunavailable. Messages are unchanged. - Changed:
plan()returns{ how: 'refused', code: 'invalid_admission' }(orremoved) for a gatefork()would refuse, as documented; it used to throw.fork()'s own error for such a gate now carriescode: 'invalid_admission'.
2026-10-11
- Changed:
configure()refuses what it cannot change in place, before sending anything —create_time(tools,immediateTools,tier,key,agent.tools),use_fork(admission;laneon a lane),unknown_field(anything else). It used to drop them without a word. - Changed:
rename()'s message is the app's (renameRefusal.js), and a gated room's refusal carriese.reason. - Changed:
connections.revokeand the Siri record throw the server's code (orfailed/refused), notno_answer, when the server answered. - Changed:
tell_lanerefuses while the asking lane's history is still loading (the hop it continues is not known yet). - Changed:
approve()/deny()reject when the decision got NO answer (status 0 — the network, "Hide my IP"); they used to resolve as decided. Such a decision may still have reached the room:pending()says whether the card is still waiting. - Changed: an @username is only a redirect. Inviting one stores a hash of that account's email, never the username;
admission.removeUsersis refused (code: 'removed') andadmission()no longer returnsusernames(a legacy room's @username entries count inhidden). - Changed:
lane.link(other)links lanes of different tiers (it threwtier_mismatch). - Removed: a room's
revokedlist. Removing someone is a move to a room that does not list them (space.fork()); an existing room's list is ignored, andnot_authorizedno longer carriesreason: 'revoked'. - Changed: a confidential room no longer carries an acceptance block. Which enclave build is accepted is each person's own choice in the Spaces app (kept on their account); the SDK seals a room key to any build Witbitz publishes.
- Removed:
admission.approve: 'admins'(every card admin-only). Creating or moving a room with it is refused (code: 'removed'), and an existing room's setting is ignored: any member decides an ordinary card. Admins still decide the cards that write an admin-only collection or call a connection.admission()no longer returnsapprove. - Changed:
rename()throwsunavailablewhen the room did not answer or failed (status 0, 5xx); it used to write the shared doc and resolveok: false, as if an older server had taken the name.
2026-10-10
- Changed (TypeScript):
Message.spkis typed as the public key it always was ({ kty, crv, x, y }), notstring. Comparex/y; see Messages for "is this mine". - New: in a confidential room, a key the enclave no longer holds (
bad_mkseal, after the enclave was replaced) or none at all (the room was opened while its enclave slept) is healed when the enclave answers: the SDK attests it and sends the call once more — a send, a reaction, a row, an approval, a rename, a run. If the enclave cannot be reached or verified, the call fails as before:bad_mkseal, orcode: 'enclave_unavailable'(try again shortly). - Changed: after a refusal no retry cures (
not_on_list,revoked,gate_unreadable) the poll and the proposals check read once a minute instead of nonstop; a failed proposals read keeps the cards you have (it used to empty them). - New:
isLaneCopy(m)— the app's own test for a lane's copy of its shared room's line; filter on it, notsource. - Changed: a collection declared
write: 'admins'in a room with no admins or owners is refused at creation (admins_required) instead of accepted and never enforced. - Changed: who an action is signed by.
approve,deny,react,rename,configure, row writes and connection grants used to default to"You"— which other members then read ("You renamed…"). With nofrom/by, they now use the name this member last sent or acted under, else"A member". Passfromto choose.send()still defaults to"You". A lane's crossing is signed the same way. - New: every turn (and
run) carries the room's model pick from the Spaces app's model chip, and a handle re-reads the room's reply mode and model when they are 20 s old;space.reactandsend(…, { replyTo }); a room used only through the SDK is named after its first exchange;sealedProof()works in a verified room (read with the key). - New:
space.run()— the assistant as a function: one answer, optionally structured by aschema, and nothing written to the room. Standard rooms only for now; paid by your user's or your app's key, 4 at once and 60 a minute per room. - New: builder mode —
framing: 'builder'(your instructions and tools only, no Spaces framing),dataWrites: false(data_querywithout the write tools) andsend(…, { showPrompt: true })(the exact prompt a turn sent). Nothing changes for a room that sets none of them. - New: your app's name in the sign-in mail — "Your Keepwarden code", from "Keepwarden via Witbitz" — for a
sign-in on one of your declared origins;
admin.app.rename(name)sets it. - New:
usageonsend()'s answer and onspace.turn()'sfinished— tokens in, out and cached, and the provider's cost (What a turn used). - A refused builder call (
wb.users.erase,wb.origins…) now carries the server's reason:e.codeis the server's error code ande.bodyits answer. A bare 503 from the gateway ise.code === 'gateway_timeout'— the call ran past 30 s and may have finished; it is safe to call again.users.erasenow stops inside that limit and answersincomplete: truewhen there is more to do.
2026-10-08
- New:
space.title,space.rename()and thetitleevent (The room's name). In the next release;/sdk/1.1.0/is unchanged. A room's name entries never appear inmessages.
2026-10-07
- SDK 1.1.0 (Versions). The examples on this page now load
/sdk/1.1.0/witbitz-sdk.js; 1.0.0 is unchanged. Over 1.0.0, 1.1.0 carries everything below and under 2026-10-05 that 1.0.0 did not: row schemas, sign-ins scoped to your app, two devices syncing an account at once,wb.protect, and types that match what the code returns. send()andLane.send()results always carry a booleanok. It isfalseon a refusal such asuser_token_required, which used to come back as{ ok: true, error }, andtrueon a reply, a silent turn, or a turn still running. Code that checkserrorkeeps working. Row writes answer{ ok: true, id, ops }.- New:
wb.erasure— the rooms the SDK creates can be deleted when you erase a user (Erasing a user who has left). - New:
platformToolsoncreateSpace,privateLaneandconfigure(Choosing the platform tools). - New: restricted keys,
admin.keys(Restricted keys);admin.rooms.list()andadmin.rooms.idleDeletion()(Your rooms, and the idle rule);wb.protect(Protect). - A moved room keeps its verified marks: rows carry
_carried, messagescarriedandcarriedBy(fork). - Email-gated rooms stream live like open rooms (Live updates). No code change.
2026-10-05
- Sign-ins made on your website are scoped to your app (Sign in). Connections, Siri, A2A and dictation
made from your page now live under your app's own account for that user — connections or Siri set up before this
change under the plain email are not visible to new sign-ins on your page.
wb.usernames.claim()/.release()from your page answerfirst_party_required(the @username is the user's global name;.mine()and.resolve()still work). Room admission is unchanged. - New: row schemas — a collection's
schemais enforced on every write, yours and the assistant's (invalid_row); a schema with a rule we cannot check is refused at declaration (invalid_schema). - New:
space.plan(change)— what a change requires (in place, a move, or a new room) and whether the history comes along, without making it. New:wb.capabilities()and theSDK_VERSIONexport. - This page is reorganised as a learning path: quickstart → build an app → the AI's authority → people → personal AI → production → the security model → builder operations; the full call table is the surface, near the end. Every section and link kept its anchor.
- SDK 1.0.0, the first versioned release (Versions). The examples on this page now load
/sdk/1.0.0/witbitz-sdk.js./witbitz-sdk.jsstill serves the live build, as before. - New:
space.turn(text)— a handle on one turn's answer (A turn you can hold). - New:
space.on('message', cb, { replay: false })hears only what arrives after the room's history was read. The default (history replays into the callback) is unchanged. on()now returns a function that removes the listener. It still chains (space.on(…).on(…)works as before), but it is no longer theSpaceitself: code that kepton()'s return value as the space should keep the space instead.- New:
WitbitzAdmininwitbitz-admin.js, the server-side client for builder calls (Builder calls).wb.usersisadmin.customersthere. Nothing is removed: the page client'swb.origins,wb.users,wb.billing,wb.rooms,wb.hosts,wb.providersandwb.verifyTenantKey()delegate to it, and a page that passestenantKeytonew Witbitznow logs a console warning. - A
Space,Laneand theWitbitzclient no longer show their key, link or tokens when logged or serialised:console.log(space),JSON.stringify(space)and error reporters print{ room, shareLink: '[REDACTED]', … }. The properties that hold them are no longer enumerable, soObject.keys(space)and{ ...space }no longer list_mk,shareLink,_clientor_wb. Read them by name as before:space.key,space.shareLink,wb.session. lane.a2a.addPeerrefuses a 33rd agent on one lane (too_many), as the Spaces app always has.- A private lane created without
agent.instructionsnow uses the Spaces app's lane instructions (a fuller version of the old default: it also tells the assistant never to paste a message meant for the room as a normal reply). - New:
space.on('update', ({ id, message, fields }) => …)— a message you already have changed in place (a reaction, an image caption, a reply's cards), andspace.messagesholds the new version where the old one stood. Before, these were never seen. A mirrored copy of a message (sh:<room>:<id>) is now the same message as its original. - Docs corrected (no code change):
wb.openSpace()never read the page's URL on its own, whatever this page said — passlocation.href(Invite links).space.deny(id, { from })takesfrom, likeapprove.self: truemarks the agent's output, never your own message (Messages). Creating rooms needs a tenant id and a declared origin (Do I need a tenant?). New: the proposal round trip, error codes and types. wb.backup.enable()now writes the account format the Spaces app uses. A backup you already have keeps its format (wb.backup.format === 'legacy') through every restore and save;await wb.backup.upgrade()moves it over (same code, same rooms — a client on an older SDK can then no longer restore it). Each app still has its own vault: a restore never opens a Witbitz account's backup, and the Spaces app never imports yours.- New:
wb.refreshSession()renews a signed-in session without a new code (Sign in). signInerrors now also carrycode,reasonand a readablehint; their messages are unchanged.- A device action (
mode: 'await') that throws now answers the turn with"The tool failed on this device: <message>"instead of the bare message. partial: a status with no words yet ({ text: '', step }— "Deciding whether to reply…", "Working on it…") now ends with{ text: null }when the turn ends without a reply. Before, it could stay on screen. Handletext: nullas "nothing in progress" (you already do if you clear the bubble after a reply).partial: a status that could not be opened is ignored; before, it could arrive as an unreadablestep.- New, nothing to change:
space.pending()and theproposalsevent (everything waiting, including proposals decided on another device),deny(id, { from }).
2026-10-02 (second release)
data.put/batchrefuse a row with anidfield or a field starting with_(reserved_field). Pass the row's key as{ id }. Rows are read without such fields.space.connections.grantrefuses a Space that isn't email-gated (gate_required).- A lane's
mirror: trueno longer copies the shared room's row writes; passmirror: { data: true }to keep them. - In a gated room,
admission(),admits()andisAdmin()throwsign_in_requiredbefore the member signs in. - Fixed:
adminsandapprove: 'admins'now reach the room; before this release they were dropped at creation.
2026-10-02
approve()/deny()throw when the room refuses (not_admin,not_authorized, …) and keep the card; before, the card was dropped andresolvedfired.
Machine-readable source: sdk-changes.md · Generated 2026-10-27T07:05:42Z · build f301388c · every doc in one fetch: llms-full.txt (HTML)