Multiplayer by Default
Section titled “Multiplayer by Default”Collaboration on computers has mostly meant taking turns: screen sharing where one person drives and everyone else watches, pair programming where two people share one keyboard, code review where changes move through a queue. Google Docs showed that real-time collaboration does not require turn-taking: multiple cursors, simultaneous edits, every change visible to everyone as it happens. Hoody applies the same model to an entire computer.
Share a URL and the other person is in: the same terminal, the same file system, the same database, the same browser, the same desktop. Everyone works in the same environment at the same time rather than watching one another work. One person can sit in a browser tab while another connects over ssh hoody.com from a plane, and both see the same container, the same terminals, and the same state in real time.
None of this is a separate collaboration feature; it follows from making everything HTTP. When every service is a URL, a second participant is just another connection. And because the Hoody Agent itself runs in a container, sharing your whole working view means sharing one more URL.
How sharing works
Section titled “How sharing works”Every Hoody service has a URL, and sending that URL to someone is the entire sharing mechanism:
Your terminal: https://PROJECT-CONTAINER-terminal-1.SERVER.containers.hoody.comYour display: https://PROJECT-CONTAINER-display-1.SERVER.containers.hoody.comYour VS Code: https://PROJECT-CONTAINER-code-1.SERVER.containers.hoody.comYour database: https://PROJECT-CONTAINER-sqlite-1.SERVER.containers.hoody.comYour agent: https://PROJECT-CONTAINER-agent-1.SERVER.containers.hoody.comSend any of those URLs to someone and they open it in their browser; they are now in your environment. Joining involves no installation, no invitation system, no account creation at your end, no VPN, and no SSH key exchange.
Multiplayer terminals
Section titled “Multiplayer terminals”A terminal URL accepts multiple people at once. Everyone sees the identical live screen, anyone can type, and every keystroke appears for everyone in real time.
A shared session
Section titled “A shared session”┌─────────────────────────────────────────────────┐│ root@container:~# ││ ││ $ npm test ← Alice ││ Running 47 tests... ││ ✓ All tests passed ││ ││ $ git status ← Bob ││ On branch feature/auth ││ modified: src/auth.ts ││ ││ $ cat src/auth.ts ← the agent ││ // AI reviewing the file... ││ │└─────────────────────────────────────────────────┘Three participants, two humans and an AI agent, are attached to the same live shell. It is one terminal, with one screen, one cursor, and one stream of output, identical for everyone. Each participant sees output the moment it happens; there is no screen share to wait for.
There is nothing to set up. Open the terminal URL from multiple browsers:
# Read the container details; nothing else is requiredhoody containers get $CONTAINER_ID
# Output includes id, project_id, name, status, server_name, etc.# Construct and share the terminal URL:# https://PROJECT-CONTAINER-terminal-1.SERVER.containers.hoody.comimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
// Get the terminal URLconst container = await client.api.containers.get(CONTAINER_ID);
const terminalUrl = `https://${container.data.project_id}-${container.data.id}-terminal-1.${container.data.server_name}.containers.hoody.com`;
// Share this URL; anyone who opens it joins the terminal sessionconsole.log('Share this URL:', terminalUrl);# Get container details to construct the terminal URLcurl "https://api.hoody.com/api/v1/containers/$CONTAINER_ID" \ -H "Authorization: Bearer $HOODY_TOKEN"
# The terminal URL is:# https://{project_id}-{container_id}-terminal-1.{server}.containers.hoody.com# Share it; anyone who opens it joins the session.One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Fetches the container’s details so you can assemble the terminal URL from project_id, id, and server_name in the response.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/CONTAINER_ID&method=GET&bearer_token=HOODY_TOKEN&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
Multiple terminal instances
Section titled “Multiple terminal instances”Instance numbers give you separate terminal sessions for different tasks:
terminal-1 → Alice and Bob debug the backend togetherterminal-2 → Carol and the AI agent work on the frontendterminal-3 → Dave monitors logs independentlyterminal-4 → Shared team standup terminal for commandsEach instance is a separate URL that can be shared on its own. Every instance supports multiple simultaneous users.
Shared displays
Section titled “Shared displays”Displays extend the same model to graphical applications. Multiple people see the same desktop, the same browser, the same GUI application, and all of them can interact with it simultaneously.
Display URL: https://PROJECT-CONTAINER-display-1.SERVER.containers.hoody.com
Alice sees: The React app running in the container's ChromeBob sees: The exact same view, in real timeCarol clicks: A button in the app; Alice and Bob see the result instantlyThis differs from screen sharing in that no one rebroadcasts a monitor. Every participant connects to the same live display session, with full input, straight from the source, and the experience is identical for each of them.
Typical uses for shared displays
Section titled “Typical uses for shared displays”Live debugging: Everyone sees the same browser, so one person triggers the bug while the rest watch the network tab, the console, and the DOM at the same time.
Design review: A designer opens Figma in the container’s browser and the team reviews together, pointing at elements and making live changes.
AI observation: The agent runs browser automation in display-1 while the team watches it navigate, click, and fill forms, and verifies the behavior is correct.
Collaborative file editing
Section titled “Collaborative file editing”hoody-code gives everyone VS Code in a browser, and multiple people can edit the same codebase simultaneously:
https://PROJECT-CONTAINER-code-1.SERVER.containers.hoody.comOpen this URL from different browsers. Each person gets a full VS Code instance connected to the same filesystem. File changes propagate instantly.
Workspace sharing
Section titled “Workspace sharing”The Hoody Agent puts your whole working context in one multi-pane view: AI chat beside live terminals, files, and a display preview. Because the Agent is streamed as a shared session, its URL is multiplayer like everything else; everyone who opens it sees the same panes, live.
https://PROJECT-CONTAINER-agent-1.SERVER.containers.hoody.comYou can also compose your own view. Every service is an iframe-able URL, so you can put terminal-1, display-1, code-1, and sqlite-1 side by side in a plain HTML page and send the link. Each pane is the real, live service, and each is independently multiplayer.
┌──────────────────────────────────────────────────────────────┐│ SHARED WORKSPACE: "Team Dashboard" ││ ││ ┌─────────────────────┐ ┌──────────────────────────────┐ ││ │ Terminal-1 │ │ Display-1 │ ││ │ (Backend logs) │ │ (App preview) │ ││ │ │ │ │ ││ │ Alice & Bob here │ │ Everyone sees this │ ││ └─────────────────────┘ └──────────────────────────────┘ ││ ││ ┌─────────────────────┐ ┌──────────────────────────────┐ ││ │ Code-1 │ │ SQLite-1 │ ││ │ (VS Code) │ │ (Database browser) │ ││ │ │ │ │ ││ │ Carol editing │ │ Dave checking data │ ││ └─────────────────────┘ └──────────────────────────────┘ ││ │└──────────────────────────────────────────────────────────────┘One URL carries four services and four participants, everything in sync. Nothing here is a static screenshot; every pane is the live service itself, open to everyone at once.
Permission layers
Section titled “Permission layers”Open by default does not mean open forever. When you are ready to control access, Hoody provides granular proxy permissions:
# Build the permissions document one field at a time with the granular commands.# Writes are optimistic-concurrency guarded, and every successful write bumps# file_version, so read a fresh ETag before each mutation (a stale --if-match# is rejected with 412).v() { hoody containers proxy permissions get -c $CONTAINER_ID -o json | jq -r '.file_version'; }
# Set a password on the containerhoody containers proxy groups password set -c $CONTAINER_ID \ --group-name team \ --auth-username team --auth-password 'team-access-2026' \ --salt team-salt-2026 --algorithm sha256 \ --if-match "file:v$(v)"
# Grant the services the group may reach, one program per callfor program in terminal files display; do hoody containers proxy groups permissions set -c $CONTAINER_ID \ --group-name team --program $program --access true --if-match "file:v$(v)"done
hoody containers proxy groups permissions set -c $CONTAINER_ID \ --group-name team --program http --access '[8080]' --if-match "file:v$(v)"
# Everything not granted above is deniedhoody containers proxy default --default deny -c $CONTAINER_ID --if-match "file:v$(v)"
# Alternative: reset this container's permissions document, then restrict by IP.# Each IP group takes one CIDR; add multiple groups for multiple ranges.hoody containers proxy permissions delete -c $CONTAINER_ID --if-match "file:v$(v)" -yhoody containers proxy default --default deny -c $CONTAINER_ID --if-match "file:v$(v)"
hoody containers proxy groups ip set -c $CONTAINER_ID \ --group-name office-vpn --range 203.0.113.50/32 --if-match "file:v$(v)"hoody containers proxy groups ip set -c $CONTAINER_ID \ --group-name office-lan --range 198.51.100.0/24 --if-match "file:v$(v)"
for group in office-vpn office-lan; do for program in terminal files display; do hoody containers proxy groups permissions set -c $CONTAINER_ID \ --group-name $group --program $program --access true --if-match "file:v$(v)" done hoody containers proxy groups permissions set -c $CONTAINER_ID \ --group-name $group --program http --access '[8080]' --if-match "file:v$(v)"done
# Alternative: reset this container's permissions document, then use a shared-secret token carried in a header.hoody containers proxy permissions delete -c $CONTAINER_ID --if-match "file:v$(v)" -yhoody containers proxy default --default deny -c $CONTAINER_ID --if-match "file:v$(v)"
hoody containers proxy groups token set -c $CONTAINER_ID \ --group-name realm --if-match "file:v$(v)" \ --body '{"header":"Authorization","value":"'$REALM_TOKEN'"}'
for program in terminal files display; do hoody containers proxy groups permissions set -c $CONTAINER_ID \ --group-name realm --program $program --access true --if-match "file:v$(v)"done
hoody containers proxy groups permissions set -c $CONTAINER_ID \ --group-name realm --program http --access '[8080]' --if-match "file:v$(v)"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
// Writes need an If-Match precondition (the current file_version is returned// as a header by GET). Pass it via the `ifMatch` option, e.g. { ifMatch: 'file:v1' }.
// Password protectionawait client.api.proxyPermissionsContainer.replace(CONTAINER_ID, { project: PROJECT_ID, container: CONTAINER_ID, groups: { team: { type: 'password', username: 'team', password: 'team-access-2026', salt: 'team-salt-2026' } }, permissions: { team: { terminal: true, files: true, display: true, http: [8080] } }, default: 'deny'}, { ifMatch: 'file:v1' });
// Or IP restrictionawait client.api.proxyPermissionsContainer.replace(CONTAINER_ID, { project: PROJECT_ID, container: CONTAINER_ID, groups: { office_primary: { type: 'ip', range: '203.0.113.50/32' }, office_subnet: { type: 'ip', range: '198.51.100.0/24' } }, permissions: { office_primary: { terminal: true, files: true, display: true, http: [8080] }, office_subnet: { terminal: true, files: true, display: true, http: [8080] } }, default: 'deny'}, { ifMatch: 'file:v1' });# Password protection# Writes require an If-Match precondition (read the current file_version via GET first)curl -X PUT "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/proxy/permissions" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "If-Match: file:v1" \ -d '{"project":"'$PROJECT_ID'","container":"'$CONTAINER_ID'","groups":{"team":{"type":"password","username":"team","password":"team-access-2026","salt":"team-salt-2026"}},"permissions":{"team":{"terminal":true,"files":true,"display":true,"http":[8080]}},"default":"deny"}'
# Or IP restrictioncurl -X PUT "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/proxy/permissions" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -H "If-Match: file:v1" \ -d '{"project":"'$PROJECT_ID'","container":"'$CONTAINER_ID'","groups":{"office_primary":{"type":"ip","range":"203.0.113.50/32"},"office_subnet":{"type":"ip","range":"198.51.100.0/24"}},"permissions":{"office_primary":{"terminal":true,"files":true,"display":true,"http":[8080]},"office_subnet":{"terminal":true,"files":true,"display":true,"http":[8080]}},"default":"deny"}'One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Replaces the container’s whole permissions document: the first link password-protects it for the team, the second instead restricts it to two office IP ranges. Route the links through a different running container’s curl-1 (OTHER_CONTAINER_ID above) — no group here is granted curl, so once this document is in place, CONTAINER_ID’s own curl-1 would deny the request that applied it. Fetch the current document first and put its file_version in place of file:v1, or the call is rejected.
# Password protection
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/CONTAINER_ID/proxy/permissions&method=PUT&bearer_token=HOODY_TOKEN&header=If-Match:%20file:v1&json={"project":"PROJECT_ID","container":"CONTAINER_ID","groups":{"team":{"type":"password","username":"team","password":"team-access-2026","salt":"team-salt-2026"}},"permissions":{"team":{"terminal":true,"files":true,"display":true,"http":[8080]}},"default":"deny"}&response=transparent
# IP restriction
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/CONTAINER_ID/proxy/permissions&method=PUT&bearer_token=HOODY_TOKEN&header=If-Match:%20file:v1&json={"project":"PROJECT_ID","container":"CONTAINER_ID","groups":{"office_primary":{"type":"ip","range":"203.0.113.50/32"},"office_subnet":{"type":"ip","range":"198.51.100.0/24"}},"permissions":{"office_primary":{"terminal":true,"files":true,"display":true,"http":[8080]},"office_subnet":{"terminal":true,"files":true,"display":true,"http":[8080]}},"default":"deny"}&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
A typical progression:
- Development: wide open. Share the URL and anyone can join.
- Staging: password protected. Team members know the password; external parties have to ask.
- Production: IP restricted or token gated. Only authorized traffic reaches the services.
Permissions apply at the proxy level, so one document protects every service in the container. You set the rules once and they are enforced for everything the container exposes.
Collaboration patterns
Section titled “Collaboration patterns”Pair programming
Section titled “Pair programming”Two developers share one container from two browser tabs:
Developer A: Opens code-1 URL → Edits src/components/Header.tsxDeveloper B: Opens code-1 URL → Edits src/components/Footer.tsxBoth: Open terminal-1 URL → See each other's commandsBoth: Open display-1 URL → See the app update liveThere is no screen-sharing latency and no “let me take control.” Both developers have full access to everything, and the filesystem is the single source of truth.
Client demos
Section titled “Client demos”Show a client the work in progress, live and interactive:
You: Open display-1 URL → Present the running applicationClient: Opens the same URL → Clicks around, tests features, asks questionsYou: Open terminal-1 → Make live changes in response to feedbackClient: Sees changes immediately in display-1The client does not install anything and does not need an account. Opening a URL in their browser is the entire onboarding process.
Team debugging
Section titled “Team debugging”During a production issue, everyone needs to see the same thing at the same time:
Lead: Opens terminal-1 → Tails the error logsBackend: Opens terminal-2 → Queries the database for corrupted recordsFrontend: Opens display-1 → Reproduces the bug in the browserDevOps: Opens terminal-3 → Checks network configurationEveryone: Opens each other's terminal URLs for every perspectiveFour people bring four perspectives to one container and coordinate in real time, without a Zoom call.
AI and human collaboration
Section titled “AI and human collaboration”Humans and AI agents can work together in the same container:
# Open an agent session, then dispatch a blocking turn into itSESSION_ID=$(hoody agent sessions create --realm global --container "$CONTAINER_ID" -o json | jq -r '.id')
hoody agent sessions prompt-sync \ --id "$SESSION_ID" \ --text "Implement the user profile page based on the design in /docs/profile-mockup.png" \ --policy auto_approve \ --realm global
# While the agent works, you and your team observe in real time:# terminal-1 URL → Watch the agent execute commands# code-1 URL → Watch the agent write code# display-1 URL → Watch the app update# agent-1 URL → Send prompts to steer the agent (HTTP API / hoody CLI)import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
// First open a session, then dispatch a blocking turn into it.const session = await client.agent.sessions.createSession({ realm: 'global', container: CONTAINER_ID });
const result = await client.agent.sessions.promptSync( session.id, { text: 'Implement the user profile page based on the design in /docs/profile-mockup.png' }, { policy: 'auto_approve' },);
// Or call the HTTP surface directly with raw fetch:// const agentBase = `https://${PROJECT_ID}-${CONTAINER_ID}-agent-1.${SERVER}.containers.hoody.com`;// await fetch(`${agentBase}/api/v1/agent/sessions/${session.id}/prompt:sync?policy=auto_approve`, {// method: 'POST',// headers: { 'Content-Type': 'application/json' },// body: JSON.stringify({ text: 'Implement the user profile page...' }),// });
// Team observes via URLs:// terminal-1 → Agent commands in real time// code-1 → Code changes as they happen// display-1 → App preview updates live// agent-1 → Send prompts to steer the agent (HTTP API / hoody CLI)# 1. Create a sessionAGENT_BASE="https://$PROJECT_ID-$CONTAINER_ID-agent-1.$SERVER.containers.hoody.com"SESSION_ID=$(curl -sX POST "$AGENT_BASE/api/v1/agent/sessions?realm=global" \ -H "Content-Type: application/json" -d '{}' | jq -r '.id')
# 2. Dispatch a blocking turn (auto-approve via the ?policy query param,# or pass -H "X-Hoody-Gate-Policy: auto_approve" instead)curl -X POST "$AGENT_BASE/api/v1/agent/sessions/$SESSION_ID/prompt:sync?policy=auto_approve" \ -H "Content-Type: application/json" \ -d '{ "text": "Implement the user profile page based on the design in /docs/profile-mockup.png" }'
# Team watches via service URLs:# https://$PROJECT_ID-$CONTAINER_ID-terminal-1.$SERVER.containers.hoody.com → Agent's commands# https://$PROJECT_ID-$CONTAINER_ID-code-1.$SERVER.containers.hoody.com → Code changes# https://$PROJECT_ID-$CONTAINER_ID-display-1.$SERVER.containers.hoody.com → Live preview# https://$PROJECT_ID-$CONTAINER_ID-agent-1.$SERVER.containers.hoody.com → Send prompts to steer the agentOne request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Run the first link, take the id from its response, and put it in SESSION_ID in the second. The second dispatches the prompt and waits for it to finish; policy=auto_approve lets the agent proceed through confirm gates unattended.
# Create the session
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-agent-1.SERVER.containers.hoody.com/api/v1/agent/sessions?realm=global&method=POST&json={}&response=transparent
# Dispatch a blocking turn
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-agent-1.SERVER.containers.hoody.com/api/v1/agent/sessions/SESSION_ID/prompt:sync?policy=auto_approve&method=POST&json={"text":"Implement%20the%20user%20profile%20page%20based%20on%20the%20design%20in%20/docs/profile-mockup.png"}&response=transparent The team watches the AI work. Someone notices a mistake and corrects the agent with a follow-up prompt; someone else opens the terminal and fixes a configuration issue the agent missed. The agent continues building, now on the right track, with human judgment and AI execution running at the same time.
Instance numbers
Section titled “Instance numbers”When multiple people and agents share a container, use instance numbers to avoid stepping on each other:
terminal-1 → Team lead (oversight, commands)terminal-2 → AI agent (automated execution)terminal-3 → Backend developer (database queries)terminal-4 → Frontend developer (build tools)
display-1 → App preview (shared)display-2 → AI agent's browser automation (shared observation)
agent-1 → Primary AI agent (feature work)agent-2 → Secondary AI agent (testing)
code-1 → Developer A's VS Codecode-2 → Developer B's VS CodeAll instances share the same container filesystem and network. Each one is a separate access point that can be shared independently.
Comparison with traditional tools
Section titled “Comparison with traditional tools”Traditional collaboration tools add a sharing layer on top of single-user systems. In Hoody, multiplayer is a property of the architecture: every service URL accepts multiple connections.
| Traditional | Hoody |
|---|---|
| Install screen sharing software | Share the URL |
| One person drives at a time | Everyone has full control |
| Video encoding introduces lag | Direct HTTP connection, no encoding |
| Requires same time zone for effectiveness | Asynchronous access to the same URL |
| Setup per collaboration session | None; URLs are permanent |
| Cannot share with AI agents | Agents use the same URLs as humans |
The URL is the collaboration mechanism. There is nothing to configure or install; anyone with the URL opens it and joins.
What’s Next
Section titled “What’s Next”- Building a Full-Stack Application: build something together
- The Vibe Coding Revolution: AI and human collaborative development
- Deploying Autonomous AI Agents: multi-agent orchestration patterns
- Proxy Permissions: fine-grained access control