oficina / meet Public
Calls
Calls is video calling for one team. A member makes a meeting, now or for later, and sends one link; anyone who follows it sets up their camera and microphone on a holding screen, waits to be let in if they are not on the team, and talks in a room of up to four, with chat beside the call and a screen to share. An agent can also create, list, relink and end meetings over an API. Every screen works with JavaScript disabled except browser media participation — the camera preview and join button on the holding screen, and the call itself in the room — and, once a call exists, the room's two other script-only parts: the chat panel and the live admission queue with its Admit controls. Both pages still render their title, start time, standing, floor notice, the paragraph that says what needs script, and a member's navigation with scripts off (spec §9).
ICE servers come from the CARLOS platform relay, delivered as CARLOS_RELAY and minted fresh for each call at GET /m/{sqid}/ice; Calls names no STUN or TURN server of its own (suite ruling 6). With no relay configured, calls are direct only: two browsers connect when ICE can find a working path straight between them, which the holding screen states plainly above the join button: "This team's Calls has no relay yet. Calls usually connect only between people on the same network." Enabling or changing the relay restarts the instance. When the relay is in the path it forwards DTLS-SRTP packets encrypted between the browsers, so it carries bytes it cannot read; Calls' own process never receives a media packet at all. With the relay on, a call that still fails says "could not connect, even through the relay" and offers retry; it does not diagnose the network. One known cause is a network that allows only TCP 443, because the relay's TLS listener is deferred by the platform — a possibility, not a verdict (spec §0.1, §5.2, §5.5, §5.6, §7.3, §15.13).
Sign-in
Sign-in is a magic link to an address on the team's allowlist (CALLS_ALLOWED_ADDRESSES). Following a meeting link gives an outsider a guest grant at sign-in — never a way into a room without a name — and the allowlist otherwise gates sign-in exactly as it does for a member (spec §3, §7.1). An operator can also enable Home sign-in with all four of CALLS_HOME_ISSUER, CALLS_HOME_CONSUMER_ID, CALLS_HOME_KEYS_BASE64 and CALLS_HOME_DEK_BASE64 — never a partial set — which links an existing member after a fresh, independent local sign-in and explicit confirmation; it never creates a person or grants membership on its own (spec §7.1.1). Because a member's address can be rate-limited on purpose and gets a different answer from an unknown one, whether a given address is on the team is not a secret the sign-in form can keep; that residue is stated rather than claimed away (spec §3.2).
The room
The room is a peer-to-peer mesh of up to four admitted participants, negotiated with the W3C perfect-negotiation pattern over an SSE stream and a POST signal per peer. A guest waits in a queue until an admitted member lets them in; removing a participant and ending a meeting each go through their own confirm page — there is no in-room control that skips one (spec §3.5). A member can retrieve the current invitation again from the holding screen or the room with Share (POST /m/{sqid}/share). On a meeting whose only link predates the 2026-09-12 sharing update, the first Share mints a new recoverable invitation alongside the old, still-valid one rather than opening it; every Share after that retrieves the same recoverable one. Replacing a link revokes every live one. Chat is fanned out live to admitted peers and never stored (spec §6). Screen share adds a second tile per sharer (spec §5.4). The room lives in one process's memory, so every deploy drops every live call (spec §15.11).
Agents
A member mints an agent's bearer token from Settings; it is shown once and the row after it keeps only its hash. The token works on /api/v1 only — never a cookie there, and never a bearer on an HTML route — for five operations: list meetings, create one, replace its link, end it, and read one meeting's detail. It cannot join a call (spec §8). An agent's reach is re-derived from its operator's current standing on every request, so an operator who leaves the team takes their agent's reach with them at once, with no separate revocation step. Requests are limited per agent to one a second sustained, with a burst of 60 (spec §8, §15.17).
Native apps
The SwiftUI Mac and iPhone apps use this same server and a shared Go core (callscore/, protocol/); there are no embedded webviews. Build the Mac app with make native-mac. See native/README.md and native/ios/README.md for setup, native sign-in and the native gates.
Building and gates
CGO_ENABLED=0 make ci (vet, gofmt, tests, migration check) is the one gate a runner and a push share; ci itself does not set the variable, so the runner and every push must. Three things ship or run under CGO_ENABLED=1, each isolated from the server: make race builds a server test binary, never a release, for any change to internal/calls/hub or the signalling stream; make native-test's own callscore race sub-step does the same for the shared Go core; and the native Mac and iPhone/iPad bindings under native/ bridge Go to Objective-C with real cgo, isolated in native/go.mod and built with make native-mac. make browser drives a real call across two real Chromium contexts, 31 scenarios in about nine minutes; it needs the Claude Code bash sandbox off, because the sandbox blocks UDP and every call then fails with frames stuck at 0. make release VERSION=<sha> stamps that version into /api/version, so a deploy can be checked against what it is meant to be running.
Deploying
Calls deploys as a CARLOS unit route. Build the artifact first — make release VERSION=<sha> produces releases/calls-linux-arm64, stamped with the version /api/version will report (strings -a releases/calls-linux-arm64 | grep -cx <sha> confirms the stamp) — then deploy it: carlos deploy --app calls --account oficina --channel edge --version <sha> --target linux-arm64 releases/calls-linux-arm64, which waits until /api/version reports it — but only once a build probe is configured for the app: carlos apps build-probe --app calls --account oficina /api/version, set on the flagship app since 2026-09-09. A new app or account needs that command run once before the wait means anything; without it carlos deploy watches X-Carlos-Version instead, which can report the new version adopted before the running process has actually changed. --account is a global flag; carlos ship uploads only, while carlos deploy is ship, promote and wait (its own help text). Building for your own architecture and shipping that uploads fine and fails to exec on the instance.
These are the settings a deploy needs (config.go, standing.go). Five refuse startup when empty outside -env development: CALLS_ORIGIN (the external origin, scheme included); CALLS_INSTANCE_KEY; CALLS_DEK (base64 of exactly 32 decoded bytes, not 32 base64 characters); CALLS_ALLOWED_ADDRESSES (comma-separated, non-empty — this is the team); and mail, CALLS_SMTP_* or the platform's CARLOS_SMTP_*. Two are optional, each with its own default rather than a refusal: CALLS_ZONE (an IANA zone, defaulting to UTC when unset) and CARLOS_RELAY (empty means no relay, §5.5 — only a malformed value refuses startup), delivered by carlos relay enable --app calls rather than set by hand. Secrets — CALLS_INSTANCE_KEY, CALLS_DEK, CALLS_SMTP_PASS and CALLS_APNS_KEY — go through carlos secrets; the rest go through carlos env.
Apple push is a sixth group, and all of it or none of it: CALLS_APNS_KEY (the .p8 as Apple issues it, PEM and multi-line), CALLS_APNS_KEY_ID, CALLS_APNS_TEAM_ID, CALLS_APNS_TOPIC_IOS and CALLS_APNS_TOPIC_MACOS, with CALLS_APNS_SANDBOX for a development build's tokens. Unset, the server sends no push and says so nowhere — a knock still reaches every member in the room over their own stream, which is where this app was before push existed. Partly set, startup refuses: half a push key is a mistake to catch at boot rather than in a goroutine on the first knock. Mail reads CALLS_SMTP_* with CARLOS_SMTP_* as a per-variable fallback; the platform supplies a sender too (CARLOS_SMTP_FROM), which CALLS_SMTP_FROM can optionally override, so carlos email enable alone is enough to send mail. carlos relay enable --app calls turns on the platform relay. Every deploy, and every relay change, restarts the instance and drops every live call — no drain exists, so deploy outside meeting hours. Migrations apply once each at boot (msg=migrate in the log) and are never re-run; a database that already applied a migration file under an earlier name fails at boot rather than silently diverging. The deploy wait can time out before the hand-over while the instance then serves the new version a minute later — check /api/version before retrying.
Canary a build on its own host: carlos canary --app calls --name slice2 composed slice2.calls.bds.oncarlos.com, and the console refused it ("invalid host, an instance host is one DNS label followed by .bds.oncarlos.com") — as recorded on 2026-09-10 in platform discussion #124, open as of that record. The recipe actually used: carlos instances create --app calls --account oficina --host slice2-calls.bds.oncarlos.com --channel canary/slice2 --environment slice2. The slice2 environment bundle must already exist, created separately, carrying CALLS_ORIGIN for the canary host; without it sign-in refuses on the canary while GETs look healthy. Then carlos deploy --account oficina --app calls --channel canary/slice2 --host slice2-calls.bds.oncarlos.com --version <sha> releases/calls-linux-arm64.
Where things are written down
The design lives in docs/superpowers/specs/2026-09-07-meet-design.md, the slice plans in docs/superpowers/plans/, and each slice's review in docs/superpowers/reviews/. PRODUCT.md covers what Calls is for and what it deliberately leaves out; AGENTS.md covers the conventions the code is held to. Spec §15.18 is the disposition of record for what slice 6 delivered, deferred (the acknowledgements page) and declined (the Estilo theme pin, the production promotion).
Meeting access settings
Team members can set Meeting defaults in /settings and change either option for an individual meeting from its Settings link. New meetings permit guests and have no waiting room unless the team changes those defaults. Defaults apply to every creation path, including agents. Existing meetings keep their original sign-in and waiting-room requirements during migration.
When sign-in is off, an invitation holder can enter a display name to join as a guest. Guest sessions cannot create meetings, change settings, or admit other guests. With the waiting room on, a team member admits guests; with it off, guests enter directly, subject to the same call capacity limit. Changes apply at entry; admitted participants keep their participation. Turning the waiting room off admits queued guests up to the call capacity.
Invitation and browser holding pages offer Join in the app, using the oficina-calls://join?url=… URL scheme registered by the Mac and iOS bundles. On the invitation page (nobody signed in yet) the link carries the invitation, and the app runs its own browser sign-in for it, guests included. On the holding page (signed in, as a member or a guest) it goes through /m/{sqid}/app first, which makes a five-minute, one-use offer bound to this browser session and opens the app with it (&offer=…); the app's ordinary native sign-in request names the offer, and the page the person is on turns into the confirmation for it, with the app's code — so the app gets this person's own standing in this meeting without a second sign-in, in whatever browser they happen to be using. An app already signed in as someone who can join the meeting uses its own session and needs no browser. Browser joining remains available when the app is not installed.