NOW · Team · Production
TeamRoom
A self-hosted workspace where a team and its AI agents share the same rooms, on the desktop and the phone. Mention an agent and it does the work on its owner’s computer, with their own Claude Code or Codex.
Architecture
Mission
My team wanted what hosted agent workspaces sell: rooms where people and AI agents are both members, where you mention an agent in a thread and it goes and does the work. We did not want to pay for it in monthly credits when each agent could just as well run on its owner’s own Claude Code.
For whom: my own team first, then anyone who would rather run this on their own server. I am the author, with a crew of fewer than five. Constraints: one VPS for the whole team; the server never runs an agent or holds an agent’s model key, so every agent runs on its owner’s computer under its owner’s login (the one exception is optional cloud speech-to-text, with a key a workspace owner adds); and clean-room, because an existing hosted product was inspected with REA for its architecture and none of its code, prompts or servers are used.
Manifest
| Layer | Choice | Version | Why |
|---|---|---|---|
| app | Tauri 2 + React 19 | — | A real desktop app with native notifications and a menu bar or tray panel, and a place to bundle the gateway so a computer starts running agents in one click. |
| app | Expo + React Native | SDK 58 | The phone app for iOS and Android on the same API: rooms, approvals, to-dos and files, with push through Expo. |
| services | Fastify + WebSockets | 5 | One API for the app and the gateways; both sign in with their first WebSocket message, and live updates fan out per room. |
| data | PostgreSQL | 17 | Rooms, runs, each agent’s queue and its memory; plain SQL migrations applied when the server starts. |
| data | Any S3-compatible bucket | — | Set per workspace by its owner, credentials encrypted with AES-256-GCM, files served through signed links. |
| agents | Claude Code CLI | — | Each agent runs on its owner’s login, headless or as a background session the owner can attach to. |
| agents | Codex CLI | — | The second runtime: headless runs, and visible threads in its local app server whose approvals come back to TeamRoom. |
| agents | MCP server in the gateway | — | The workspace tools an agent gets during a run, scoped by a token that dies with the run. |
| agents | whisper.cpp + Apple speech | — | Voice and video clips transcribed on the computer that recorded them; a cloud provider is optional per workspace. |
| infra | Docker image + Caddy | — | Self-hosting with automatic HTTPS in one compose file; CI builds the image and runs the API smoke test against it. |
| infra | npm package + compiled binary | — | The gateway as one npx command on a Linux server, or inside the desktop app, kept running by launchd, systemd or a Windows login entry. |
| infra | A public S3 bucket behind nginx | — | Release files get a bucket of their own on this site’s server: installers, the Android app, the gateway and command-line binaries, and the update manifests. The landing page reads the latest versions from it. |
| infra | GitHub Actions | — | On a release tag: builds the server image and runs the API smoke test against it, builds the gateway for Linux, macOS and Windows and the desktop installers for all three, queues the Android build, and publishes the result. |
Decisions
The server never runs an agent. It turns an @mention into a run and hands it, over a WebSocket, to the gateway on the agent owner’s computer. The gateway starts Claude Code or Codex with that person’s login and streams what the agent is doing back into the thread.
Two runtimes, one contract. Each agent runs on Claude Code or Codex. Codex runs headless, or as a visible thread in its local app server that the owner can open. Both get the same workspace tools over MCP, the same sharing rules and the same approvals.
Approvals are asked in TeamRoom while the agent runs. When a running agent hits a permission prompt, Claude Code’s PermissionRequest hook or Codex’s app server sends it to the room, and the owner allows or denies it from the desktop app, the phone or the menu bar. An agent that cannot do something says what to install or which setting would allow it.
Premade agents with controls nobody can loosen. A status reporter answers colleagues about a project from folders its owner picks: read only, summaries only, every answer checked for secrets before it posts and logged for the owner. A stand-in answers for its owner while they are away, tagged as theirs, and no one else can see it, mention it or message it. The server refuses any change that would widen either one.
Agents can ask each other, in the open. An agent asks a colleague’s agent in a private thread both owners can read; the colleague’s sharing settings decide whether it waits for an approval, and the asking agent is woken in the same session when the answer lands. To-dos can depend on each other, and a change wakes the agent waiting on it.
The phone is a client, never a runner. The Expo app uses the same API as the desktop app. Agents never run on it. Push goes through Expo, message previews can be hidden from the notification, and the phone stays quiet while its owner is active at their computer.
Voice is transcribed where it is recorded. A clip is transcribed on the sender’s computer with Apple speech or whisper.cpp, and a cloud provider is optional per workspace. No agent starts on a clip without words: its own computer transcribes the clip first and saves the transcript back.
Workspace tools over MCP, scoped to one run. Each run gets an MCP server with room history, search, posting, memory and files. Its token works only while that run is running and only in its own workspace, and --strict-mcp-config keeps the owner’s other MCP servers out of reach.
Access levels are Claude Code permission modes. Read only runs in dontAsk, can-edit in acceptEdits, full access in bypassPermissions. The owner picks one per agent, and decides who else may mention it and whether their requests wait for an approval.
Headless by default, visible on request. A visible agent starts a background Claude Code session that its owner can attach to from a terminal, approve prompts in, and keep working in after the agent has answered. A folder Claude Code has not trusted yet falls back to a headless run, and the run says why.
One agent, one run at a time. Requests queue per agent in order, wait while its computer is offline, and expire after 24 hours if they never start.
Built by sub-agents, coordinated from one session. Each workstream — server, gateway, desktop app, mobile app, release pipeline, landing page — went to its own Claude Code sub-agent with the files it owned spelled out, and each proved its part end to end against throwaway servers and databases before handing it back. The ship log below is that record.
Self-hosting is one compose file. The server ships as a Docker image behind Caddy for HTTPS. The gateway is packaged for npm, for Linux machines and servers, and bundled inside the desktop app as a compiled binary that runs as a login service on macOS, Linux and Windows. The compose files are public downloads, and a self-hosting guide on the landing page walks through them.
One tag releases everything. A version tag on main builds the server image, the gateway binaries, the desktop installers and an Android build, then publishes them to a public bucket on this site’s own server. The landing page reads the latest versions and sizes from that bucket, and the desktop app checks it for updates, which are signed with the app’s own key. Every published file has a SHA-256 checksum in a public list.
Not open source, and free in the open beta. All rights are reserved and the repository is private. Anyone can download the apps and run the server image and the gateway at no cost while the beta lasts, and the terms for running TeamRoom yourself will be published with the product. The packages already on npm stay MIT; new releases carry the new licence. The builds are betas in the plain sense: the Mac app is not notarized, the Windows installer is not code-signed, and there is no iPhone app yet.
Impact
- Lines of TypeScript
- ~85k
- Unit tests
- 242
- End-to-end API suites
- 20
- Agent model keys on the server
- 0
In open beta, and free while it lasts. TeamRoom is not open source: the repository is private and all rights are reserved. You can still run all of it. The landing page at teamroom.ndelucien.com links the desktop app for macOS, Windows and Linux and an Android app, the server image is on Docker Hub, the gateway is on npm, and the setup files for self-hosting are public downloads. It is a beta in the plain sense: the Mac app is not notarized, the Windows installer is not code-signed, the phone app is an Android download, and there is no iPhone app yet.
What exists after 200 commits and release 0.4.1: a server, a desktop app for macOS, Windows and Linux, a gateway, a command-line tool and a phone app. After the first two days it was about 85k lines of TypeScript and 2k of Rust, checked by 242 unit tests, 18 Rust tests and 20 end-to-end API suites run against throwaway servers and databases. A release tag builds the server image and runs the API smoke test against it, builds the gateway for Linux, macOS and Windows, and builds the desktop installers.
What broke
Visible sessions, in three ways. claude --bg ignores --session-id, so the gateway cannot choose the id of the session it starts, and --mcp-config takes any number of values, so a prompt placed after it was read as one more config file. The prompt now goes first, and the gateway reads the session id from what the command prints.
A follow-up in the same thread resumed the earlier session as a copy, and the copy’s transcript already held the earlier answers, so an old reply could be taken for the new one. Every request now carries a unique marker and the gateway reads only what comes after it. And a session stopped from outside reports no status at all, which left its run waiting for up to two hours; a stopped session now fails the run straight away.
The first Docker Hub publish pushed to an image with no name. The image name was built from the Docker Hub username, stored as a secret, and GitHub drops any job output that contains a secret, so the publish job received an empty name and tried to push a bare latest tag. The username is now a repository variable.
The Windows login service took five commits. It would not start from a VBScript launcher, so it now starts through WMI, and the compiled gateway did not recognise itself, because its own path came back URL-encoded with %7E for ~. Later the Codex integration could not reach Codex’s control socket at all: Node opens only named pipes on Windows, so there the gateway relays through codex app-server proxy.
Codex passed the run’s TeamRoom token to every shell command its agent ran, despite the setting meant to hide it. Testing caught it; the token is now kept out of the agent’s shell, and a real run confirmed it was gone.
A status reporter said a work folder was not a git repository when it holds 25 of them. A follow-up had resumed an old session and repeated its stale answer, and its file search walked about two million files under node_modules until it timed out. Reporters now start a fresh session for every question, look inside nested repositories, and use their own file finder that skips dependencies and build output.
A voice message reached an agent with no transcript, because the composer let Send go out before the computer had finished transcribing. Send now waits for the words, a failed transcription is shown instead of dropped, and an agent’s computer transcribes any clip that arrives without words before the agent starts.
An agent quoting the agent it had just asked (“@ops says…”) set that agent off again. A line in its instructions did not stop it, so the server now ignores those mentions in the asking agent’s reply.
Ship log
- Architecture: a hosted agent workspace studied with REA, then a clean-room design for the server, the desktop app and the gateway
- Core build: Fastify and Postgres server, Tauri desktop app, and the gateway that runs Claude Code on each computer
- Visible Claude Code sessions an agent’s owner can attach to and keep working in, falling back to headless runs
- The Claude Design handoff built out: rooms, threads, direct messages, search, settings and onboarding
- Server image built and tested in CI for Docker Hub, and a self-host compose file with Caddy; first commit
- File storage on any S3-compatible bucket and SMTP, both set per workspace with encrypted credentials
- The gateway packaged for npm, run as a login service, and compiled into a binary the desktop app bundles
- Desktop: “Use this Mac” in one click, file attachments, and the storage and email settings
- Private one-on-one chats with an agent, which answers every message without an @mention
- Landing page live at teamroom.ndelucien.com: static Astro on the design system’s tokens, added behind the VPS’s shared nginx with every other site checked before and after
- Desktop installers built in CI: a dmg for Apple silicon and for Intel, deb, rpm and AppImage, and msi and setup.exe for Windows, each with the gateway inside
- Windows: the gateway, its login service and the desktop app, with the gateway’s tests run on Windows in CI
- Codex as a second runtime: headless runs and visible threads in its app server, and live approvals asked in TeamRoom for both Codex and Claude Code
- Agent capabilities: what each agent may run, and which parts of its owner’s Claude Code setup it inherits, chosen per agent
- A rich composer with formatting, emoji and custom emoji, scheduled messages and drafts
- To-dos for people and agents: assign them, hand one to an agent and approve its plan, with due dates, reminders and repeats
- Profiles and status, and a menu bar and tray panel for what needs you on macOS, Windows and Linux
- Voice and video clips, transcribed on the computer that recorded them with Apple speech or whisper.cpp, and an optional cloud provider
- Agent tools: switches and notes per tool, an agent’s own MCP servers, a workspace skills library, and asking for what it is missing
- Files: a workspace file browser and an in-app viewer under the conversations’ access rules, and re-sharing a file without uploading it again
- Status reporters: read-only agents that answer colleagues about a project from git and notes, across nested repositories, with every answer logged for the owner
- Stand-ins: a private agent that answers for its owner while they are away, tagged as theirs, with a summary when they are back
- Slash commands shared by the desktop and phone apps, workspace skills as commands, and owner-defined commands with approval gates
- Push notifications through Expo, tested against a stand-in for its service
- Agent-to-agent questions in a private thread both owners can read, and to-do dependencies that wake the agent waiting on them
- The phone app for iOS and Android with Expo, at parity with the desktop app: built and tested on the iOS simulator and an Android phone, not in the stores yet
- Design: an agent mark, four app icon designs to choose from, and softer surfaces with gold for what needs you
- The gateway’s 0.4.0 release on npm
- Release pipeline: one version tag builds the server image, the gateway, the desktop installers and an Android build, and CI publishes them to a public bucket at s3.ndelucien.com/teamroom, with signed update packages for the desktop app
- Open beta: the landing page links the desktop apps for macOS, Windows and Linux and an Android app, the server image and the gateway, with a self-hosting guide whose setup files are public downloads; free while in beta, no iPhone app yet
- Decision: TeamRoom is not open source. All rights reserved, the repository stays private, and terms for running it yourself will be published with the product; releases already on npm stay MIT